OttoYu commited on
Commit
08db879
·
verified ·
1 Parent(s): bd0e3da

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +63 -38
app.py CHANGED
@@ -1,23 +1,23 @@
1
  import laspy
2
  import numpy as np
3
  import cv2
4
- from math import floor, pi
5
  from multiprocessing import Process, shared_memory, cpu_count
6
  import gradio as gr
7
- import time
8
 
9
- # ==== Constants ====
10
- TILE_DIM = 512
11
  PRECISION = 16
12
  FACTOR = 2 ** PRECISION
13
- NUM_WORKERS = cpu_count()
14
  SHARED_MEMORY_NAME = 'shared_canvas'
15
 
16
- # ==== Parameters ====
17
- IMAGE_HEIGHT = 4096 * 2
18
  POINT_SIZE = 3
19
  SCALE_COLORS = False
20
  FULL_RES = False
 
 
21
 
22
  def create_shared_memory_array(data, name, dtype):
23
  shm = shared_memory.SharedMemory(create=True, size=data.nbytes, name=name)
@@ -25,85 +25,110 @@ def create_shared_memory_array(data, name, dtype):
25
  shared_array[:] = data
26
  return shm
27
 
 
28
  def release_shared_memory(name):
29
  shm = shared_memory.SharedMemory(name=name)
30
  shm.close()
31
  shm.unlink()
32
 
 
33
  def draw_points(start, length, canvas_shape, theta, phi, radius, color):
34
  shm = shared_memory.SharedMemory(name=SHARED_MEMORY_NAME)
35
  canvas = np.ndarray(canvas_shape, dtype=np.uint16, buffer=shm.buf)
36
 
37
  for i in range(start, start + length):
38
- cv2.circle(
39
- canvas,
40
- (theta[i], phi[i]),
41
- radius[i],
42
- color=(int(color[i][0]), int(color[i][1]), int(color[i][2])),
43
- thickness=cv2.FILLED,
44
- shift=PRECISION
45
- )
 
 
46
 
47
  def project_point_cloud(input_file):
48
  try:
49
  point_cloud = laspy.read(input_file.name)
50
- except FileNotFoundError:
51
- return "Input file not found."
52
 
53
  height, width = IMAGE_HEIGHT, IMAGE_HEIGHT * 2
54
  output_shape = (height, width, 3)
 
55
  total_points = point_cloud.header.point_count
 
 
 
56
 
57
- background = np.zeros(output_shape, dtype=np.uint16)
 
 
 
58
 
59
- xyz = np.array([point_cloud.x, point_cloud.y, point_cloud.z])
60
- distances = np.linalg.norm(xyz, axis=0)
61
- distances_norm = (distances - distances.min()) / (distances.max() - distances.min())
62
 
63
- theta = ((width * FACTOR) -
64
- (((np.arctan2(point_cloud.y, point_cloud.x) + pi) / (2 * pi)) * width * FACTOR)).astype(np.uint64)
65
- phi = ((np.arccos(point_cloud.z / distances) / pi) * height * FACTOR).astype(np.uint64)
66
- radii = (FACTOR * POINT_SIZE * (1 - distances_norm) + 1).astype(np.uint64)
67
 
 
 
 
 
 
 
 
 
68
  colors = np.stack([point_cloud.blue, point_cloud.green, point_cloud.red], axis=-1).astype(np.uint16)
69
  if SCALE_COLORS:
70
  colors *= 256
 
71
 
72
- shm_canvas = create_shared_memory_array(background, SHARED_MEMORY_NAME, np.uint16)
 
73
 
 
74
  processes = []
75
- num_threads = min(NUM_WORKERS, 4)
76
- for i in range(num_threads):
77
- start = floor(i * total_points / num_threads)
78
- end = floor((i + 1) * total_points / num_threads)
79
- p = Process(target=draw_points, args=(start, end - start, output_shape, theta, phi, radii, colors))
 
80
  p.start()
81
  processes.append(p)
82
 
83
  for p in processes:
84
  p.join()
85
 
86
- final_image = np.ndarray(output_shape, dtype=np.uint16, buffer=shm_canvas.buf)
 
87
  if not FULL_RES:
88
  final_image = (final_image / 256).astype(np.uint8)
89
 
90
  output_file = "output_image.jpg"
91
  cv2.imwrite(output_file, final_image)
92
-
93
  release_shared_memory(SHARED_MEMORY_NAME)
 
 
94
  return output_file
95
 
96
- # Gradio interface
 
97
  def main(input_file):
98
  return project_point_cloud(input_file)
99
 
 
100
  iface = gr.Interface(
101
  fn=main,
102
  inputs=gr.File(label="Upload LAS File"),
103
- outputs=gr.Image(type="filepath", label="Output Image"),
104
- title="Point Cloud Projection",
105
- description="Upload a LAS file to project the point cloud into an image."
106
  )
107
 
108
  if __name__ == "__main__":
109
- iface.launch()
 
1
  import laspy
2
  import numpy as np
3
  import cv2
4
+ from math import pi
5
  from multiprocessing import Process, shared_memory, cpu_count
6
  import gradio as gr
 
7
 
8
+ # === Constants ===
 
9
  PRECISION = 16
10
  FACTOR = 2 ** PRECISION
11
+ NUM_WORKERS = min(cpu_count(), 4)
12
  SHARED_MEMORY_NAME = 'shared_canvas'
13
 
14
+ # === Parameters ===
15
+ IMAGE_HEIGHT = 4096 * 2 # Default image height
16
  POINT_SIZE = 3
17
  SCALE_COLORS = False
18
  FULL_RES = False
19
+ MAX_RADIUS = 30
20
+
21
 
22
  def create_shared_memory_array(data, name, dtype):
23
  shm = shared_memory.SharedMemory(create=True, size=data.nbytes, name=name)
 
25
  shared_array[:] = data
26
  return shm
27
 
28
+
29
  def release_shared_memory(name):
30
  shm = shared_memory.SharedMemory(name=name)
31
  shm.close()
32
  shm.unlink()
33
 
34
+
35
  def draw_points(start, length, canvas_shape, theta, phi, radius, color):
36
  shm = shared_memory.SharedMemory(name=SHARED_MEMORY_NAME)
37
  canvas = np.ndarray(canvas_shape, dtype=np.uint16, buffer=shm.buf)
38
 
39
  for i in range(start, start + length):
40
+ if 0 <= theta[i] < canvas_shape[1] * FACTOR and 0 <= phi[i] < canvas_shape[0] * FACTOR:
41
+ cv2.circle(
42
+ canvas,
43
+ (int(theta[i]), int(phi[i])),
44
+ int(radius[i]),
45
+ color=(int(color[i][0]), int(color[i][1]), int(color[i][2])),
46
+ thickness=cv2.FILLED,
47
+ shift=PRECISION
48
+ )
49
+
50
 
51
  def project_point_cloud(input_file):
52
  try:
53
  point_cloud = laspy.read(input_file.name)
54
+ except Exception as e:
55
+ return f"Failed to read LAS file: {e}"
56
 
57
  height, width = IMAGE_HEIGHT, IMAGE_HEIGHT * 2
58
  output_shape = (height, width, 3)
59
+
60
  total_points = point_cloud.header.point_count
61
+ print(f"[INFO] Loaded {total_points} points.")
62
+
63
+ canvas = np.zeros(output_shape, dtype=np.uint16)
64
 
65
+ # === Coordinate setup ===
66
+ xyz = np.vstack((point_cloud.x, point_cloud.y, point_cloud.z)).T
67
+ distances = np.linalg.norm(xyz, axis=1)
68
+ distances[distances == 0] = 1e-6 # Avoid divide by zero
69
 
70
+ # === Normalize distances for radius scaling ===
71
+ dist_norm = (distances - distances.min()) / (distances.max() - distances.min())
 
72
 
73
+ # === Compute angles ===
74
+ theta = ((np.arctan2(point_cloud.y, point_cloud.x) + pi) / (2 * pi)) * width * FACTOR
75
+ theta = np.mod(theta, width * FACTOR).astype(np.uint64)
 
76
 
77
+ z_norm = np.clip(point_cloud.z / distances, -1.0, 1.0)
78
+ phi = ((np.arccos(z_norm) / pi) * height * FACTOR).astype(np.uint64)
79
+
80
+ # === Radius based on depth (closer → larger radius) ===
81
+ radii = (FACTOR * POINT_SIZE * (1 - dist_norm) + 1).astype(np.uint64)
82
+ radii = np.clip(radii, 1, MAX_RADIUS)
83
+
84
+ # === Colors ===
85
  colors = np.stack([point_cloud.blue, point_cloud.green, point_cloud.red], axis=-1).astype(np.uint16)
86
  if SCALE_COLORS:
87
  colors *= 256
88
+ colors = np.clip(colors, 0, 65535)
89
 
90
+ # === Shared Memory Canvas ===
91
+ shm = create_shared_memory_array(canvas, SHARED_MEMORY_NAME, np.uint16)
92
 
93
+ # === Start workers ===
94
  processes = []
95
+ for i in range(NUM_WORKERS):
96
+ start = int(i * total_points / NUM_WORKERS)
97
+ end = int((i + 1) * total_points / NUM_WORKERS)
98
+ p = Process(target=draw_points, args=(
99
+ start, end - start, output_shape, theta, phi, radii, colors
100
+ ))
101
  p.start()
102
  processes.append(p)
103
 
104
  for p in processes:
105
  p.join()
106
 
107
+ # === Finalize output ===
108
+ final_image = np.ndarray(output_shape, dtype=np.uint16, buffer=shm.buf)
109
  if not FULL_RES:
110
  final_image = (final_image / 256).astype(np.uint8)
111
 
112
  output_file = "output_image.jpg"
113
  cv2.imwrite(output_file, final_image)
 
114
  release_shared_memory(SHARED_MEMORY_NAME)
115
+ print(f"[INFO] Saved output to {output_file}")
116
+
117
  return output_file
118
 
119
+
120
+ # === Gradio Interface ===
121
  def main(input_file):
122
  return project_point_cloud(input_file)
123
 
124
+
125
  iface = gr.Interface(
126
  fn=main,
127
  inputs=gr.File(label="Upload LAS File"),
128
+ outputs=gr.Image(type="filepath", label="Projected Image"),
129
+ title="Equirectangular Point Cloud Projection",
130
+ description="Upload a LAS point cloud file and project it into a 360° equirectangular image."
131
  )
132
 
133
  if __name__ == "__main__":
134
+ iface.launch()