Tremick commited on
Commit
7012f96
·
1 Parent(s): 275bc8b

Perf: binary PLY, downsample input, subsample 200k points max

Browse files
Files changed (1) hide show
  1. app.py +47 -26
app.py CHANGED
@@ -47,12 +47,17 @@ def _run_depth_inference(image: Image.Image):
47
  # =====================================================================
48
  @spaces.GPU(duration=120)
49
  def create_3d_point_cloud(input_pil_image):
50
- """Processes the image, calculates 3D vertices, and writes a .ply file."""
51
  if input_pil_image is None:
52
  return None
53
 
54
- # Step A: Depth inference
 
55
  image = input_pil_image.convert("RGB")
 
 
 
 
56
  post_processed = _run_depth_inference(image)
57
  depth_matrix = (
58
  post_processed[0]["predicted_depth"]
@@ -61,11 +66,12 @@ def create_3d_point_cloud(input_pil_image):
61
 
62
  # Step B: Pinhole camera projection (u, v, depth) -> (X, Y, Z)
63
  height, width = depth_matrix.shape
64
- color_np = np.array(image) / 255.0
65
 
66
- focal_length = max(width, height)
67
  cx, cy = width / 2.0, height / 2.0
68
- u, v = np.meshgrid(np.arange(width), np.arange(height))
 
69
 
70
  z = depth_matrix.flatten()
71
  x = ((u.flatten() - cx) * z) / focal_length
@@ -74,31 +80,46 @@ def create_3d_point_cloud(input_pil_image):
74
  # Keep objects between 10 cm and 30 m; discard sky / infinite background
75
  valid_mask = (z > 0.1) & (z < 30.0)
76
  x, y, z = x[valid_mask], y[valid_mask], z[valid_mask]
 
77
 
78
- rgb = color_np.reshape(-1, 3)[valid_mask]
79
- rgb_ints = (rgb * 255).astype(np.uint8)
 
 
 
 
80
 
81
- # Step C: Write ASCII PLY file
82
- ply_filename = "output_scene.ply"
83
  num_points = len(z)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
84
 
85
- with open(ply_filename, "w") as f:
86
- f.write("ply\n")
87
- f.write("format ascii 1.0\n")
88
- f.write(f"element vertex {num_points}\n")
89
- f.write("property float x\n")
90
- f.write("property float y\n")
91
- f.write("property float z\n")
92
- f.write("property uchar red\n")
93
- f.write("property uchar green\n")
94
- f.write("property uchar blue\n")
95
- f.write("end_header\n")
96
- for i in range(num_points):
97
- # Invert Y axis so the model loads right-side up
98
- f.write(
99
- f"{x[i]:.4f} {-y[i]:.4f} {z[i]:.4f} "
100
- f"{rgb_ints[i][0]} {rgb_ints[i][1]} {rgb_ints[i][2]}\n"
101
- )
102
 
103
  return ply_filename
104
 
 
47
  # =====================================================================
48
  @spaces.GPU(duration=120)
49
  def create_3d_point_cloud(input_pil_image):
50
+ """Processes the image, calculates 3D vertices, and writes a binary .ply file."""
51
  if input_pil_image is None:
52
  return None
53
 
54
+ # Step A: Downscale large images before inference.
55
+ # DepthPro quality is stable at 512px; this dramatically cuts runtime.
56
  image = input_pil_image.convert("RGB")
57
+ MAX_DIM = 512
58
+ if max(image.width, image.height) > MAX_DIM:
59
+ image.thumbnail((MAX_DIM, MAX_DIM), Image.LANCZOS)
60
+
61
  post_processed = _run_depth_inference(image)
62
  depth_matrix = (
63
  post_processed[0]["predicted_depth"]
 
66
 
67
  # Step B: Pinhole camera projection (u, v, depth) -> (X, Y, Z)
68
  height, width = depth_matrix.shape
69
+ color_np = np.array(image, dtype=np.float32) / 255.0
70
 
71
+ focal_length = float(max(width, height))
72
  cx, cy = width / 2.0, height / 2.0
73
+ u, v = np.meshgrid(np.arange(width, dtype=np.float32),
74
+ np.arange(height, dtype=np.float32))
75
 
76
  z = depth_matrix.flatten()
77
  x = ((u.flatten() - cx) * z) / focal_length
 
80
  # Keep objects between 10 cm and 30 m; discard sky / infinite background
81
  valid_mask = (z > 0.1) & (z < 30.0)
82
  x, y, z = x[valid_mask], y[valid_mask], z[valid_mask]
83
+ rgb_ints = (color_np.reshape(-1, 3)[valid_mask] * 255).astype(np.uint8)
84
 
85
+ # Step C: Subsample to keep the browser renderer fast (<= 200k points)
86
+ MAX_POINTS = 200_000
87
+ if len(z) > MAX_POINTS:
88
+ idx = np.random.choice(len(z), MAX_POINTS, replace=False)
89
+ x, y, z = x[idx], y[idx], z[idx]
90
+ rgb_ints = rgb_ints[idx]
91
 
92
+ # Step D: Write BINARY PLY via numpy (100x faster than an ASCII for-loop)
 
93
  num_points = len(z)
94
+ vertex_dtype = np.dtype([
95
+ ('x', '<f4'), ('y', '<f4'), ('z', '<f4'),
96
+ ('red', 'u1'), ('green', 'u1'), ('blue', 'u1'),
97
+ ])
98
+ vertices = np.empty(num_points, dtype=vertex_dtype)
99
+ vertices['x'] = x.astype(np.float32)
100
+ vertices['y'] = (-y).astype(np.float32) # invert Y so it loads right-side up
101
+ vertices['z'] = z.astype(np.float32)
102
+ vertices['red'] = rgb_ints[:, 0]
103
+ vertices['green'] = rgb_ints[:, 1]
104
+ vertices['blue'] = rgb_ints[:, 2]
105
+
106
+ ply_header = (
107
+ "ply\n"
108
+ "format binary_little_endian 1.0\n"
109
+ f"element vertex {num_points}\n"
110
+ "property float x\n"
111
+ "property float y\n"
112
+ "property float z\n"
113
+ "property uchar red\n"
114
+ "property uchar green\n"
115
+ "property uchar blue\n"
116
+ "end_header\n"
117
+ )
118
 
119
+ ply_filename = "/tmp/output_scene.ply"
120
+ with open(ply_filename, "wb") as f:
121
+ f.write(ply_header.encode("ascii"))
122
+ f.write(vertices.tobytes())
 
 
 
 
 
 
 
 
 
 
 
 
 
123
 
124
  return ply_filename
125