dfrokido commited on
Commit
5f76a01
·
verified ·
1 Parent(s): eddf0e9

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +43 -26
app.py CHANGED
@@ -30,32 +30,37 @@ class PersistentCortex:
30
 
31
  def evolve_from_image(self, image: Image.Image, steps=800):
32
  img_array = np.array(image.resize((256, 256))) / 255.0
33
- target = torch.tensor(img_array, dtype=torch.float32).permute(2, 0, 1) # (3, 256, 256)
34
 
35
- # Create sampling grid: scatter points across [-1,1] x [-1,1]
36
- proj = torch.rand(self.num, 2) * 2 - 1 # (N, 2) uniform in view
37
 
38
  for _ in range(steps):
 
39
  grid = proj.unsqueeze(0).unsqueeze(0) # (1, 1, N, 2)
40
 
41
  sampled = torch.nn.functional.grid_sample(
42
- target.unsqueeze(0),
43
  grid,
44
  mode='bilinear',
45
  padding_mode='border',
46
  align_corners=True
47
- ).squeeze(0).squeeze(0).t() # (N, 3)
 
 
 
48
 
49
  brightness = sampled.mean(dim=1)
50
- attraction_strength = (brightness - brightness.mean()) * 0.02
51
- proj += attraction_strength.unsqueeze(1) * proj.normalized()
52
 
53
- # Color adaptation
54
- avg_color = target.mean(dim=[1,2])
55
  self.colors = torch.lerp(self.colors, avg_color.repeat(self.num, 1), 0.02)
56
 
57
- # Map projected points back to 3D sphere surface (simple radial projection)
58
- self.positions[:, :2] = proj * (self.positions[:, :2].norm(dim=1).unsqueeze(1) + 0.1)
 
59
 
60
  return self
61
 
@@ -67,6 +72,7 @@ class PersistentCortex:
67
  text_emb = model.encode_text(text_tokens).float()
68
  text_emb = text_emb / text_emb.norm(dim=-1, keepdim=True)
69
 
 
70
  proj = torch.nn.Linear(512, 3, bias=False)
71
  torch.nn.init.normal_(proj.weight, std=0.2)
72
  color_shift = proj(text_emb)
@@ -75,16 +81,20 @@ class PersistentCortex:
75
  return self
76
 
77
  def export_ply(self, path="output.ply"):
78
- zeros = np.zeros((self.num, 3), dtype=np.float32)
79
- log_scales = np.log(self.scales.cpu().numpy())
 
 
 
 
80
 
81
  vertex_data = np.core.records.fromarrays([
82
- self.positions.cpu().numpy(),
83
- zeros,
84
- self.colors.cpu().numpy(),
85
- self.opacities.cpu().numpy()[:, np.newaxis],
86
- log_scales,
87
- self.rotations.cpu().numpy()
88
  ], names='x,y,z,nx,ny,nz,f_dc_0,f_dc_1,f_dc_2,opacity,scale_0,scale_1,scale_2,rot_0,rot_1,rot_2,rot_3')
89
 
90
  el = plyfile.PlyElement.describe(vertex_data, 'vertex')
@@ -93,7 +103,7 @@ class PersistentCortex:
93
 
94
  def process(image: Image.Image, prompt: str = ""):
95
  if image is None:
96
- raise gr.Error("Please upload an image.")
97
 
98
  cortex = PersistentCortex(num_gaussians=8000)
99
  cortex.evolve_from_image(image, steps=800)
@@ -121,6 +131,9 @@ def process(image: Image.Image, prompt: str = ""):
121
  controls.autoRotate = false;
122
  controls.enableDamping = true;
123
  controls.dampingFactor = 0.05;
 
 
 
124
 
125
  await SPLAT.Loader.LoadAsync("/files/output.ply", scene);
126
 
@@ -133,7 +146,7 @@ def process(image: Image.Image, prompt: str = ""):
133
  </script>
134
  """
135
 
136
- status = f"Persistent 3D representation evolved from image"
137
  if prompt:
138
  status += f" and conditioned on prompt: '{prompt}'"
139
  status += "."
@@ -145,14 +158,18 @@ with gr.Blocks(title="Persistent 3D Cortex Demo") as demo:
145
  gr.Markdown("Upload an image and optionally add a text prompt to generate an evolving 3D Gaussian splat representation.")
146
 
147
  with gr.Row():
148
- img_input = gr.Image(type="pil", label="Input Image (required)")
149
- prompt_input = gr.Textbox(label="Prompt (e.g., 'glowing blue crystal')", placeholder="Optional descriptive text")
150
 
151
- generate_btn = gr.Button("Generate Persistent 3D", variant="primary")
152
 
153
- viewer_output = gr.HTML()
154
  status_output = gr.Textbox(label="Status")
155
 
156
- generate_btn.click(process, inputs=[img_input, prompt_input], outputs=[viewer_output, status_output])
 
 
 
 
157
 
158
  demo.launch()
 
30
 
31
  def evolve_from_image(self, image: Image.Image, steps=800):
32
  img_array = np.array(image.resize((256, 256))) / 255.0
33
+ target = torch.tensor(img_array, dtype=torch.float32).permute(2, 0, 1).unsqueeze(0) # (1, 3, 256, 256)
34
 
35
+ # Random projection points in [-1,1] for sampling
36
+ proj = torch.rand(self.num, 2) * 2 - 1 # (N, 2)
37
 
38
  for _ in range(steps):
39
+ # Normalize grid to [-1,1]
40
  grid = proj.unsqueeze(0).unsqueeze(0) # (1, 1, N, 2)
41
 
42
  sampled = torch.nn.functional.grid_sample(
43
+ target,
44
  grid,
45
  mode='bilinear',
46
  padding_mode='border',
47
  align_corners=True
48
+ ).squeeze(1).squeeze(0) # Squeeze H (1) and batch if needed, to (3, N)
49
+
50
+ # Transpose to (N, 3)
51
+ sampled = sampled.t()
52
 
53
  brightness = sampled.mean(dim=1)
54
+ attraction = (brightness - brightness.mean()) * 0.015
55
+ proj += attraction.unsqueeze(1) * (proj / (proj.norm(dim=1, keepdim=True) + 1e-6))
56
 
57
+ # Color lerp to image average
58
+ avg_color = target.mean(dim=[2,3]).squeeze()
59
  self.colors = torch.lerp(self.colors, avg_color.repeat(self.num, 1), 0.02)
60
 
61
+ # Apply evolved projection to positions (scale to sphere radius)
62
+ radii = self.positions.norm(dim=1, keepdim=True)
63
+ self.positions[:, :2] = proj * radii
64
 
65
  return self
66
 
 
72
  text_emb = model.encode_text(text_tokens).float()
73
  text_emb = text_emb / text_emb.norm(dim=-1, keepdim=True)
74
 
75
+ # Projection to color shift
76
  proj = torch.nn.Linear(512, 3, bias=False)
77
  torch.nn.init.normal_(proj.weight, std=0.2)
78
  color_shift = proj(text_emb)
 
81
  return self
82
 
83
  def export_ply(self, path="output.ply"):
84
+ pos = self.positions.cpu().numpy()
85
+ col = self.colors.cpu().numpy()
86
+ opa = self.opacities.cpu().numpy()
87
+ sca = np.log(self.scales.cpu().numpy())
88
+ rot = self.rotations.cpu().numpy()
89
+ nor = np.zeros_like(pos) # Normals placeholder
90
 
91
  vertex_data = np.core.records.fromarrays([
92
+ pos[:,0], pos[:,1], pos[:,2],
93
+ nor[:,0], nor[:,1], nor[:,2],
94
+ col[:,0], col[:,1], col[:,2],
95
+ opa,
96
+ sca[:,0], sca[:,1], sca[:,2],
97
+ rot[:,0], rot[:,1], rot[:,2], rot[:,3]
98
  ], names='x,y,z,nx,ny,nz,f_dc_0,f_dc_1,f_dc_2,opacity,scale_0,scale_1,scale_2,rot_0,rot_1,rot_2,rot_3')
99
 
100
  el = plyfile.PlyElement.describe(vertex_data, 'vertex')
 
103
 
104
  def process(image: Image.Image, prompt: str = ""):
105
  if image is None:
106
+ raise gr.Error("Please upload an image to proceed.")
107
 
108
  cortex = PersistentCortex(num_gaussians=8000)
109
  cortex.evolve_from_image(image, steps=800)
 
131
  controls.autoRotate = false;
132
  controls.enableDamping = true;
133
  controls.dampingFactor = 0.05;
134
+ controls.rotateSpeed = 1.0;
135
+ controls.zoomSpeed = 1.2;
136
+ controls.panSpeed = 0.8;
137
 
138
  await SPLAT.Loader.LoadAsync("/files/output.ply", scene);
139
 
 
146
  </script>
147
  """
148
 
149
+ status = "Persistent 3D representation evolved from image"
150
  if prompt:
151
  status += f" and conditioned on prompt: '{prompt}'"
152
  status += "."
 
158
  gr.Markdown("Upload an image and optionally add a text prompt to generate an evolving 3D Gaussian splat representation.")
159
 
160
  with gr.Row():
161
+ img_input = gr.Image(type="pil", label="Input Image")
162
+ prompt_input = gr.Textbox(label="Prompt (e.g., 'shiny red apple', 'futuristic city')", placeholder="Optional text prompt")
163
 
164
+ generate_btn = gr.Button("Generate & Evolve 3D", variant="primary")
165
 
166
+ viewer_output = gr.HTML(label="Interactive 3D Viewer")
167
  status_output = gr.Textbox(label="Status")
168
 
169
+ generate_btn.click(
170
+ fn=process,
171
+ inputs=[img_input, prompt_input],
172
+ outputs=[viewer_output, status_output]
173
+ )
174
 
175
  demo.launch()