Harsh commited on
Commit
a557eb6
Β·
1 Parent(s): 5b877fe

Add GeoSpatial-LiDAR-3D Point Cloud Visualizer

Browse files
Files changed (6) hide show
  1. .gitattributes +1 -0
  2. README.md +57 -14
  3. app.py +267 -0
  4. example/tree_raw.laz +3 -0
  5. example/tree_segmentation.laz +3 -0
  6. requirements.txt +16 -0
.gitattributes CHANGED
@@ -33,3 +33,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ *.laz filter=lfs diff=lfs merge=lfs -text
README.md CHANGED
@@ -1,14 +1,57 @@
1
- ---
2
- title: GeoSpatial LiDAR 3D Point Cloud Visualizer
3
- emoji: 🐨
4
- colorFrom: pink
5
- colorTo: blue
6
- sdk: gradio
7
- sdk_version: 6.2.0
8
- app_file: app.py
9
- pinned: false
10
- license: apache-2.0
11
- short_description: Application for visualizing LiDAR point cloud data in 3D.
12
- ---
13
-
14
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # GeoSpatial-LiDAR-3D Point Cloud Visualizer
2
+
3
+ A web application for visualizing and analyzing LiDAR point cloud data in 3D.
4
+
5
+ ![Python](https://img.shields.io/badge/Python-3.9+-blue)
6
+ ![Gradio](https://img.shields.io/badge/Gradio-4.44.1+-green)
7
+ ![License](https://img.shields.io/badge/License-MIT-yellow)
8
+
9
+ ## Features
10
+
11
+ - **LAS/LAZ Format Support**: Load and visualize LiDAR point cloud files
12
+ - **Multiple Color Modes**: RGB original colors, elevation-based colormaps, intensity visualization, and instance segmentation
13
+ - **Elevation Colormaps**: Choose from viridis, terrain, plasma, inferno, Spectral, and coolwarm
14
+ - **Instance Segmentation**: Visualize segmentation data with distinct colors for each instance
15
+ - **Interactive 3D Viewer**: Rotate, zoom, and pan through your point cloud data
16
+ - **Smart Subsampling**: Handle large datasets by limiting display points with customizable limits
17
+ - **Detailed Statistics**: View point counts, coordinate ranges, RGB presence, and segmentation information
18
+ - **Example Files**: Explore sample data with pre-loaded example point clouds
19
+ - **Public Sharing**: Generate and share live links to your visualizations
20
+
21
+ ## Supported Formats
22
+
23
+ | Format | Extension | Description |
24
+ |--------|-----------|-------------|
25
+ | LAS | `.las` | Standard uncompressed LiDAR format |
26
+ | LAZ | `.laz` | Compressed LiDAR format (recommended for large files) |
27
+
28
+ ## Color Modes
29
+
30
+ - **RGB (Original)**: Display the original RGB colors from the point cloud file. Falls back to elevation colors if RGB data is unavailable
31
+ - **Instance Segmentation**: Color points based on their instance ID (PredInstance field). Assigns distinct colors to different instances
32
+ - **Elevation**: Color points by their height (Z coordinate) using your selected colormap
33
+ - **Intensity**: Display intensity values as grayscale, showing the reflectance properties of points
34
+
35
+ ## Available Colormaps
36
+
37
+ The following colormaps are available for elevation and fallback visualization:
38
+
39
+ - **viridis**: Perceptually uniform colors that work well for colorblind viewers
40
+ - **terrain**: Classic elevation visualization with natural colors
41
+ - **plasma**: Warm colors from purple to yellow
42
+ - **inferno**: Dark to bright colors from black to yellow
43
+ - **Spectral**: Full rainbow spectrum for maximum contrast
44
+ - **coolwarm**: Cool to warm colors for intuitive interpretation
45
+
46
+ ## Visualization Options
47
+
48
+ - **Max Points to Display**: Adjust the number of points displayed (50,000 to 2,000,000). Larger files are automatically subsampled for performance
49
+ - **Example Files**: Load pre-packaged sample files to quickly explore the visualizer's capabilities
50
+
51
+ ## Contributing
52
+
53
+ We welcome contributions to improve this project. You can help by:
54
+ - Adding support for additional file formats
55
+ - Improving visualization performance
56
+ - Enhancing the user interface
57
+ - Adding more colormaps or visualization modes
app.py ADDED
@@ -0,0 +1,267 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import numpy as np
3
+ import laspy
4
+ import trimesh
5
+ import tempfile
6
+ import os
7
+
8
+ def load_laz(file_path):
9
+ """Load LAZ/LAS file and return all data."""
10
+ las = laspy.read(file_path)
11
+
12
+ # Get XYZ - center around origin for visualization
13
+ points = np.vstack([las.x, las.y, las.z]).T
14
+ centroid = points.mean(axis=0)
15
+ points = points - centroid
16
+
17
+ # Get RGB colors (convert 16-bit to 8-bit)
18
+ rgb = None
19
+ if hasattr(las, 'red') and len(las.red) > 0:
20
+ rgb = np.vstack([las.red, las.green, las.blue]).T
21
+ rgb = (rgb / 256).astype(np.uint8) # 16-bit to 8-bit
22
+
23
+ # Get PredInstance if available
24
+ pred_instance = None
25
+ if 'PredInstance' in las.point_format.extra_dimension_names:
26
+ pred_instance = np.array(las['PredInstance'])
27
+
28
+ # Get intensity
29
+ intensity = None
30
+ if hasattr(las, 'intensity'):
31
+ intensity = np.array(las.intensity)
32
+
33
+ return {
34
+ 'points': points,
35
+ 'rgb': rgb,
36
+ 'pred_instance': pred_instance,
37
+ 'intensity': intensity,
38
+ 'total_count': len(points)
39
+ }
40
+
41
+ def get_instance_colors(pred_instance):
42
+ """Generate distinct colors for each instance."""
43
+ # Distinct colors for instances
44
+ instance_colors = np.array([
45
+ [128, 128, 128], # -1: Gray (unclassified)
46
+ [255, 0, 0], # 0: Red
47
+ [0, 255, 0], # 1: Green
48
+ [0, 0, 255], # 2: Blue
49
+ [255, 255, 0], # 3: Yellow
50
+ [255, 0, 255], # 4: Magenta
51
+ [0, 255, 255], # 5: Cyan
52
+ [255, 128, 0], # 6: Orange
53
+ [128, 0, 255], # 7: Purple
54
+ [0, 255, 128], # 8: Spring Green
55
+ [255, 128, 128], # 9: Light Red
56
+ [128, 255, 128], # 10: Light Green
57
+ [128, 128, 255], # 11: Light Blue
58
+ [255, 255, 128], # 12: Light Yellow
59
+ [255, 128, 255], # 13: Pink
60
+ [128, 255, 255], # 14: Light Cyan
61
+ ], dtype=np.uint8)
62
+
63
+ # Map instance IDs to colors (-1 maps to index 0)
64
+ colors = np.zeros((len(pred_instance), 3), dtype=np.uint8)
65
+ for i, inst in enumerate(pred_instance):
66
+ idx = int(inst) + 1 # -1 -> 0, 0 -> 1, etc.
67
+ idx = max(0, min(idx, len(instance_colors) - 1))
68
+ colors[i] = instance_colors[idx]
69
+
70
+ return colors
71
+
72
+ def get_elevation_colors(points, colormap_name):
73
+ """Apply elevation-based colormap."""
74
+ import matplotlib.pyplot as plt
75
+
76
+ z = points[:, 2]
77
+ z_norm = (z - z.min()) / (z.max() - z.min() + 1e-8)
78
+
79
+ cmap = plt.get_cmap(colormap_name)
80
+ colors = (cmap(z_norm)[:, :3] * 255).astype(np.uint8)
81
+
82
+ return colors
83
+
84
+ def get_intensity_colors(intensity):
85
+ """Convert intensity to grayscale colors."""
86
+ i_norm = (intensity - intensity.min()) / (intensity.max() - intensity.min() + 1e-8)
87
+ gray = (i_norm * 255).astype(np.uint8)
88
+ return np.stack([gray, gray, gray], axis=1)
89
+
90
+ def load_example_file(example_name):
91
+ """Load example file and return the file path."""
92
+ import os
93
+ base_dir = os.path.dirname(__file__)
94
+ example_path = os.path.join(base_dir, example_name)
95
+ if os.path.exists(example_path):
96
+ return example_path
97
+ return None
98
+
99
+ def visualize(file, color_mode, colormap, max_points):
100
+ """Main visualization function."""
101
+ if file is None:
102
+ return None, "⚠️ Please upload a LAZ/LAS file"
103
+
104
+ try:
105
+ # Load data
106
+ data = load_laz(file)
107
+ points = data['points']
108
+ total = data['total_count']
109
+
110
+ # Subsample if needed
111
+ if len(points) > max_points:
112
+ indices = np.random.choice(len(points), int(max_points), replace=False)
113
+ points = points[indices]
114
+ rgb = data['rgb'][indices] if data['rgb'] is not None else None
115
+ pred_instance = data['pred_instance'][indices] if data['pred_instance'] is not None else None
116
+ intensity = data['intensity'][indices] if data['intensity'] is not None else None
117
+ else:
118
+ indices = None
119
+ rgb = data['rgb']
120
+ pred_instance = data['pred_instance']
121
+ intensity = data['intensity']
122
+
123
+ # Get colors based on mode
124
+ if color_mode == "RGB (Original)":
125
+ if rgb is not None:
126
+ colors = rgb
127
+ else:
128
+ colors = get_elevation_colors(points, colormap)
129
+ elif color_mode == "Instance Segmentation":
130
+ if pred_instance is not None:
131
+ colors = get_instance_colors(pred_instance)
132
+ else:
133
+ return None, "❌ This file does not have PredInstance data"
134
+ elif color_mode == "Elevation":
135
+ colors = get_elevation_colors(points, colormap)
136
+ elif color_mode == "Intensity":
137
+ if intensity is not None:
138
+ colors = get_intensity_colors(intensity)
139
+ else:
140
+ colors = get_elevation_colors(points, colormap)
141
+ else:
142
+ colors = get_elevation_colors(points, colormap)
143
+
144
+ # Add alpha channel
145
+ alpha = np.full((len(colors), 1), 255, dtype=np.uint8)
146
+ colors_rgba = np.hstack([colors, alpha])
147
+
148
+ # Create point cloud and export
149
+ cloud = trimesh.PointCloud(points, colors=colors_rgba)
150
+
151
+ tmp = tempfile.NamedTemporaryFile(suffix='.glb', delete=False)
152
+ tmp.close()
153
+ cloud.export(tmp.name, file_type='glb')
154
+
155
+ # Stats
156
+ instance_info = ""
157
+ if pred_instance is not None and color_mode == "Instance Segmentation":
158
+ unique, counts = np.unique(pred_instance, return_counts=True)
159
+ instance_info = "\n\n**Instance Breakdown:**\n"
160
+ for u, c in sorted(zip(unique, counts), key=lambda x: -x[1])[:10]:
161
+ instance_info += f"- Instance {int(u)}: {c:,} pts\n"
162
+
163
+ stats = f"""
164
+ ### πŸ“Š Point Cloud Statistics
165
+ | Property | Value |
166
+ |----------|-------|
167
+ | Total Points | {total:,} |
168
+ | Displayed | {len(points):,} |
169
+ | X Range | {points[:,0].min():.2f} to {points[:,0].max():.2f} |
170
+ | Y Range | {points[:,1].min():.2f} to {points[:,1].max():.2f} |
171
+ | Z Range | {points[:,2].min():.2f} to {points[:,2].max():.2f} |
172
+ | Color Mode | {color_mode} |
173
+ | Has RGB | {'βœ…' if data['rgb'] is not None else '❌'} |
174
+ | Has Segmentation | {'βœ…' if data['pred_instance'] is not None else '❌'} |
175
+ {instance_info}
176
+ """
177
+
178
+ return tmp.name, stats
179
+
180
+ except Exception as e:
181
+ import traceback
182
+ return None, f"❌ Error: {str(e)}\n```\n{traceback.format_exc()}\n```"
183
+
184
+ # UI
185
+ with gr.Blocks(title="GeoSpatial-LiDAR-3D Point Cloud Visualizer") as demo:
186
+ gr.Markdown("# 🌍 GeoSpatial-LiDAR-3D Point Cloud Visualizer")
187
+ gr.Markdown("Upload LAZ/LAS files with support for RGB colors and instance segmentation")
188
+
189
+ with gr.Row():
190
+ with gr.Column(scale=1):
191
+ file_input = gr.File(
192
+ label="πŸ“‚ Upload LAS/LAZ File",
193
+ file_types=[".las", ".laz"]
194
+ )
195
+
196
+ gr.Markdown("### πŸ“‹ Or use an example:")
197
+ example_dropdown = gr.Dropdown(
198
+ choices=["tree_raw.laz", "tree_segmentation.laz"],
199
+ label="πŸ“‚ Examples",
200
+ value="tree_raw.laz"
201
+ )
202
+ btn_load_example = gr.Button("πŸ“‚ Load Example", size="sm")
203
+
204
+ color_mode = gr.Radio(
205
+ choices=["RGB (Original)", "Instance Segmentation", "Elevation", "Intensity"],
206
+ value="RGB (Original)",
207
+ label="🎨 Color Mode"
208
+ )
209
+
210
+ colormap = gr.Dropdown(
211
+ choices=["viridis", "terrain", "plasma", "inferno", "Spectral", "coolwarm"],
212
+ value="viridis",
213
+ label="Elevation Colormap (for Elevation mode)",
214
+ visible=True
215
+ )
216
+
217
+ max_points = gr.Slider(
218
+ 50000, 2000000,
219
+ value=500000,
220
+ step=50000,
221
+ label="πŸ“Š Max Points to Display"
222
+ )
223
+
224
+ btn = gr.Button("πŸš€ Visualize", variant="primary", size="lg")
225
+
226
+ gr.Markdown("""
227
+ ---
228
+ ### 🎨 Color Modes
229
+ - **RGB**: Original colors from file
230
+ - **Instance Segmentation**: Color by PredInstance (if available)
231
+ - **Elevation**: Color by height (Z)
232
+ - **Intensity**: Grayscale by intensity
233
+ """)
234
+
235
+ with gr.Column(scale=2):
236
+ model_output = gr.Model3D(
237
+ label="3D Visualization",
238
+ height=550,
239
+ clear_color=(0.1, 0.1, 0.15, 1.0)
240
+ )
241
+ stats_output = gr.Markdown("*Upload a file and click Visualize*")
242
+
243
+ btn.click(
244
+ visualize,
245
+ inputs=[file_input, color_mode, colormap, max_points],
246
+ outputs=[model_output, stats_output]
247
+ )
248
+
249
+ # Example file handlers
250
+ def load_and_visualize_example(example_name):
251
+ import os
252
+ if example_name is None:
253
+ return None, "⚠️ Please select an example file"
254
+ base_dir = os.path.dirname(os.path.abspath(__file__))
255
+ example_path = os.path.join(base_dir, "example", example_name)
256
+ if os.path.exists(example_path):
257
+ return visualize(example_path, "RGB (Original)", "viridis", 500000)
258
+ return None, f"❌ Example file '{example_name}' not found at {example_path}"
259
+
260
+ btn_load_example.click(
261
+ load_and_visualize_example,
262
+ inputs=[example_dropdown],
263
+ outputs=[model_output, stats_output]
264
+ )
265
+
266
+ if __name__ == "__main__":
267
+ demo.launch(share=True)
example/tree_raw.laz ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:3daccec3c49b71fe6894b8cea2f506753da1fdacf4e5ffe73753cf20d0827204
3
+ size 47375201
example/tree_segmentation.laz ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:abc3327349323353809cccdebccb5954c2013bd58a46f86706cef1d436e42657
3
+ size 54249090
requirements.txt ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Core
2
+ gradio>=4.44.1
3
+ numpy>=1.24.0
4
+
5
+ # LiDAR Processing
6
+ laspy[laszip]>=2.5.0
7
+ open3d>=0.17.0
8
+ trimesh>=4.0.0
9
+
10
+ # Visualization
11
+ matplotlib>=3.7.0
12
+ Pillow>=10.0.0
13
+
14
+ # Optional - for advanced processing
15
+ # pyvista>=0.43.0
16
+ # scipy>=1.11.0