harshinde's picture
Update app.py
d32138a verified
import gradio as gr
import numpy as np
import laspy
import trimesh
import tempfile
import os
import warnings
# Standard imports
import asyncio
def load_laz(file_path):
"""Load LAZ/LAS file and return all data."""
las = laspy.read(file_path)
# Get XYZ - center around origin for visualization
points = np.vstack([las.x, las.y, las.z]).T
centroid = points.mean(axis=0)
points = points - centroid
# Get RGB colors (convert 16-bit to 8-bit)
rgb = None
if hasattr(las, 'red') and len(las.red) > 0:
rgb = np.vstack([las.red, las.green, las.blue]).T
rgb = (rgb / 256).astype(np.uint8) # 16-bit to 8-bit
# Get PredInstance if available
pred_instance = None
if 'PredInstance' in las.point_format.extra_dimension_names:
pred_instance = np.array(las['PredInstance'])
# Get intensity
intensity = None
if hasattr(las, 'intensity'):
intensity = np.array(las.intensity)
return {
'points': points,
'rgb': rgb,
'pred_instance': pred_instance,
'intensity': intensity,
'total_count': len(points)
}
def get_instance_colors(pred_instance):
"""Generate distinct colors for each instance."""
# Distinct colors for instances
instance_colors = np.array([
[128, 128, 128], # -1: Gray (unclassified)
[255, 0, 0], # 0: Red
[0, 255, 0], # 1: Green
[0, 0, 255], # 2: Blue
[255, 255, 0], # 3: Yellow
[255, 0, 255], # 4: Magenta
[0, 255, 255], # 5: Cyan
[255, 128, 0], # 6: Orange
[128, 0, 255], # 7: Purple
[0, 255, 128], # 8: Spring Green
[255, 128, 128], # 9: Light Red
[128, 255, 128], # 10: Light Green
[128, 128, 255], # 11: Light Blue
[255, 255, 128], # 12: Light Yellow
[255, 128, 255], # 13: Pink
[128, 255, 255], # 14: Light Cyan
], dtype=np.uint8)
# Map instance IDs to colors (-1 maps to index 0)
colors = np.zeros((len(pred_instance), 3), dtype=np.uint8)
for i, inst in enumerate(pred_instance):
idx = int(inst) + 1 # -1 -> 0, 0 -> 1, etc.
idx = max(0, min(idx, len(instance_colors) - 1))
colors[i] = instance_colors[idx]
return colors
def get_elevation_colors(points, colormap_name):
"""Apply elevation-based colormap."""
import matplotlib.pyplot as plt
z = points[:, 2]
z_norm = (z - z.min()) / (z.max() - z.min() + 1e-8)
cmap = plt.get_cmap(colormap_name)
colors = (cmap(z_norm)[:, :3] * 255).astype(np.uint8)
return colors
def get_intensity_colors(intensity):
"""Convert intensity to grayscale colors."""
i_norm = (intensity - intensity.min()) / (intensity.max() - intensity.min() + 1e-8)
gray = (i_norm * 255).astype(np.uint8)
return np.stack([gray, gray, gray], axis=1)
def load_example_file(example_name):
"""Load example file and return the file path."""
import os
base_dir = os.path.dirname(__file__)
example_path = os.path.join(base_dir, example_name)
if os.path.exists(example_path):
return example_path
return None
def visualize(file, color_mode, colormap, max_points):
"""Main visualization function."""
if file is None:
return None, "⚠️ Please upload a LAZ/LAS file"
try:
# Load data
data = load_laz(file)
points = data['points']
total = data['total_count']
# Subsample if needed
if len(points) > max_points:
indices = np.random.choice(len(points), int(max_points), replace=False)
points = points[indices]
rgb = data['rgb'][indices] if data['rgb'] is not None else None
pred_instance = data['pred_instance'][indices] if data['pred_instance'] is not None else None
intensity = data['intensity'][indices] if data['intensity'] is not None else None
else:
indices = None
rgb = data['rgb']
pred_instance = data['pred_instance']
intensity = data['intensity']
# Get colors based on mode
if color_mode == "RGB (Original)":
if rgb is not None:
colors = rgb
else:
colors = get_elevation_colors(points, colormap)
elif color_mode == "Instance Segmentation":
if pred_instance is not None:
colors = get_instance_colors(pred_instance)
else:
return None, "❌ This file does not have PredInstance data"
elif color_mode == "Elevation":
colors = get_elevation_colors(points, colormap)
elif color_mode == "Intensity":
if intensity is not None:
colors = get_intensity_colors(intensity)
else:
colors = get_elevation_colors(points, colormap)
else:
colors = get_elevation_colors(points, colormap)
# Add alpha channel
alpha = np.full((len(colors), 1), 255, dtype=np.uint8)
colors_rgba = np.hstack([colors, alpha])
# Create point cloud and export
cloud = trimesh.PointCloud(points, colors=colors_rgba)
tmp = tempfile.NamedTemporaryFile(suffix='.glb', delete=False)
tmp.close()
cloud.export(tmp.name, file_type='glb')
# Stats
instance_info = ""
if pred_instance is not None and color_mode == "Instance Segmentation":
unique, counts = np.unique(pred_instance, return_counts=True)
instance_info = "\n\n**Instance Breakdown:**\n"
for u, c in sorted(zip(unique, counts), key=lambda x: -x[1])[:10]:
instance_info += f"- Instance {int(u)}: {c:,} pts\n"
stats = f"""
### πŸ“Š Point Cloud Statistics
| Property | Value |
|----------|-------|
| Total Points | {total:,} |
| Displayed | {len(points):,} |
| X Range | {points[:,0].min():.2f} to {points[:,0].max():.2f} |
| Y Range | {points[:,1].min():.2f} to {points[:,1].max():.2f} |
| Z Range | {points[:,2].min():.2f} to {points[:,2].max():.2f} |
| Color Mode | {color_mode} |
| Has RGB | {'βœ…' if data['rgb'] is not None else '❌'} |
| Has Segmentation | {'βœ…' if data['pred_instance'] is not None else '❌'} |
{instance_info}
"""
return tmp.name, stats
except Exception as e:
import traceback
return None, f"❌ Error: {str(e)}\n```\n{traceback.format_exc()}\n```"
# UI
with gr.Blocks(title="GeoSpatial-LiDAR-3D Point Cloud Visualizer") as demo:
gr.Markdown("# 🌍 GeoSpatial-LiDAR-3D Point Cloud Visualizer")
gr.Markdown("Upload LAZ/LAS files with support for RGB colors and instance segmentation")
with gr.Row():
with gr.Column(scale=1):
file_input = gr.File(
label="πŸ“‚ Upload LAS/LAZ File",
file_types=[".las", ".laz"]
)
gr.Markdown("### πŸ“‹ Or use an example:")
example_dropdown = gr.Dropdown(
choices=["tree_raw.laz", "tree_segmentation.laz"],
label="πŸ“‚ Examples",
value="tree_raw.laz"
)
btn_load_example = gr.Button("πŸ“‚ Load Example", size="sm")
color_mode = gr.Radio(
choices=["RGB (Original)", "Instance Segmentation", "Elevation", "Intensity"],
value="RGB (Original)",
label="🎨 Color Mode"
)
colormap = gr.Dropdown(
choices=["viridis", "terrain", "plasma", "inferno", "Spectral", "coolwarm"],
value="viridis",
label="Elevation Colormap (for Elevation mode)",
visible=True
)
max_points = gr.Slider(
50000, 2000000,
value=500000,
step=50000,
label="πŸ“Š Max Points to Display"
)
btn = gr.Button("πŸš€ Visualize", variant="primary", size="lg")
gr.Markdown("""
---
### 🎨 Color Modes
- **RGB**: Original colors from file
- **Instance Segmentation**: Color by PredInstance (if available)
- **Elevation**: Color by height (Z)
- **Intensity**: Grayscale by intensity
""")
with gr.Column(scale=2):
model_output = gr.Model3D(
label="3D Visualization",
height=550,
clear_color=(0.1, 0.1, 0.15, 1.0)
)
stats_output = gr.Markdown("*Upload a file and click Visualize*")
btn.click(
visualize,
inputs=[file_input, color_mode, colormap, max_points],
outputs=[model_output, stats_output]
)
# Example file handlers
def load_and_visualize_example(example_name):
import os
if example_name is None:
return None, "⚠️ Please select an example file"
base_dir = os.path.dirname(os.path.abspath(__file__))
example_path = os.path.join(base_dir, "example", example_name)
if os.path.exists(example_path):
return visualize(example_path, "RGB (Original)", "viridis", 500000)
return None, f"❌ Example file '{example_name}' not found at {example_path}"
btn_load_example.click(
load_and_visualize_example,
inputs=[example_dropdown],
outputs=[model_output, stats_output]
)
if __name__ == "__main__":
# Launch with MCP server support for integration with other tools
# To enable MCP server, set mcp_server=True or use GRADIO_MCP_SERVER env var
demo.launch(ssr_mode=False, mcp_server=False)