Spaces:
Running on Zero
Running on Zero
Download pointcloud.py from CronosSa/Solaria1.0: direct link, hf CLI and curl.
- Browser
- Download file 6.01 kB
-
https://huggingface.co/spaces/CronosSa/Solaria1.0/resolve/main/pointcloud.py
- Command line
-
hf download hf://spaces/CronosSa/Solaria1.0/pointcloud.py
-
curl -L -o pointcloud.py https://huggingface.co/spaces/CronosSa/Solaria1.0/resolve/main/pointcloud.py
6.01 kB
| import numpy as np | |
| from PIL import Image | |
| def depth_to_pointcloud( | |
| depth, | |
| image=None, | |
| mask=None, | |
| stride=4 | |
| ): | |
| """ | |
| Converte mapa de profundidade em nuvem de pontos 3D. | |
| """ | |
| # Depth precisa ser uma matriz 2D | |
| depth = np.asarray( | |
| depth, | |
| dtype=np.float32 | |
| ) | |
| depth = np.squeeze(depth) | |
| if depth.ndim != 2: | |
| raise ValueError( | |
| f"Formato de depth inesperado: {depth.shape}" | |
| ) | |
| height, width = depth.shape | |
| # Grid de pixels | |
| y, x = np.mgrid[ | |
| 0:height:stride, | |
| 0:width:stride | |
| ] | |
| z = depth[ | |
| ::stride, | |
| ::stride | |
| ] | |
| # Pontos válidos | |
| valid = np.isfinite(z) | |
| # ========================================== | |
| # MÁSCARA DO OBJETO | |
| # ========================================== | |
| if mask is not None: | |
| if isinstance(mask, Image.Image): | |
| mask_image = mask | |
| else: | |
| mask_array = np.asarray(mask) | |
| # Se vier RGB/RGBA, converte corretamente | |
| if mask_array.ndim == 3: | |
| mask_array = mask_array[:, :, 0] | |
| mask_image = Image.fromarray( | |
| mask_array.astype(np.uint8) | |
| ) | |
| # Sempre transforma em escala de cinza | |
| mask_image = mask_image.convert("L") | |
| # Mesmo tamanho da profundidade | |
| mask_image = mask_image.resize( | |
| (width, height), | |
| Image.Resampling.LANCZOS | |
| ) | |
| mask_array = np.asarray( | |
| mask_image | |
| ) | |
| print( | |
| "DEBUG máscara:", | |
| mask_array.shape, | |
| flush=True | |
| ) | |
| object_mask = ( | |
| mask_array[ | |
| ::stride, | |
| ::stride | |
| ] > 128 | |
| ) | |
| print( | |
| "DEBUG object_mask:", | |
| object_mask.shape, | |
| flush=True | |
| ) | |
| print( | |
| "DEBUG valid:", | |
| valid.shape, | |
| flush=True | |
| ) | |
| valid = ( | |
| valid | |
| & object_mask | |
| ) | |
| # ========================================== | |
| # VERIFICA PONTOS | |
| # ========================================== | |
| if not np.any(valid): | |
| raise ValueError( | |
| "Nenhum ponto 3D válido foi encontrado. " | |
| "A máscara pode ter removido todo o objeto." | |
| ) | |
| # ========================================== | |
| # FILTRA PONTOS | |
| # ========================================== | |
| x = x[valid].astype( | |
| np.float32 | |
| ) | |
| y = y[valid].astype( | |
| np.float32 | |
| ) | |
| z = z[valid].astype( | |
| np.float32 | |
| ) | |
| # ========================================== | |
| # NORMALIZA PROFUNDIDADE | |
| # ========================================== | |
| z_min = np.min(z) | |
| z_max = np.max(z) | |
| if z_max - z_min > 1e-8: | |
| z = ( | |
| z - z_min | |
| ) / ( | |
| z_max - z_min | |
| ) | |
| else: | |
| z[:] = 0.5 | |
| # ========================================== | |
| # CENTRALIZA X/Y | |
| # ========================================== | |
| x = ( | |
| x - width / 2 | |
| ) / width | |
| y = -( | |
| y - height / 2 | |
| ) / height | |
| # ========================================== | |
| # ESCALA Z | |
| # ========================================== | |
| z = z * 2.0 | |
| # ========================================== | |
| # XYZ | |
| # ========================================== | |
| points = np.stack( | |
| [ | |
| x, | |
| y, | |
| z | |
| ], | |
| axis=1 | |
| ) | |
| # ========================================== | |
| # CORES | |
| # ========================================== | |
| colors = None | |
| if image is not None: | |
| image = image.convert("RGB") | |
| image = image.resize( | |
| (width, height) | |
| ) | |
| image_array = np.asarray( | |
| image | |
| ) | |
| sampled_colors = ( | |
| image_array[ | |
| ::stride, | |
| ::stride | |
| ] | |
| ) | |
| colors = sampled_colors[ | |
| valid | |
| ] | |
| return points, colors | |
| def save_pointcloud_ply( | |
| filename, | |
| points, | |
| colors=None | |
| ): | |
| """ | |
| Salva a nuvem de pontos no formato PLY. | |
| """ | |
| points = np.asarray( | |
| points, | |
| dtype=np.float32 | |
| ) | |
| if ( | |
| points.ndim != 2 | |
| or points.shape[1] != 3 | |
| ): | |
| raise ValueError( | |
| "points precisa ter formato (N, 3)." | |
| ) | |
| if colors is not None: | |
| colors = np.asarray(colors) | |
| if len(colors) != len(points): | |
| raise ValueError( | |
| "A quantidade de cores precisa " | |
| "ser igual à quantidade de pontos." | |
| ) | |
| with open( | |
| filename, | |
| "w", | |
| encoding="utf-8" | |
| ) as file: | |
| file.write("ply\n") | |
| file.write("format ascii 1.0\n") | |
| file.write( | |
| f"element vertex {len(points)}\n" | |
| ) | |
| file.write("property float x\n") | |
| file.write("property float y\n") | |
| file.write("property float z\n") | |
| if colors is not None: | |
| file.write( | |
| "property uchar red\n" | |
| ) | |
| file.write( | |
| "property uchar green\n" | |
| ) | |
| file.write( | |
| "property uchar blue\n" | |
| ) | |
| file.write("end_header\n") | |
| if colors is not None: | |
| for point, color in zip( | |
| points, | |
| colors | |
| ): | |
| x, y, z = point | |
| r, g, b = color | |
| file.write( | |
| f"{x:.6f} " | |
| f"{y:.6f} " | |
| f"{z:.6f} " | |
| f"{int(r)} " | |
| f"{int(g)} " | |
| f"{int(b)}\n" | |
| ) | |
| else: | |
| for point in points: | |
| x, y, z = point | |
| file.write( | |
| f"{x:.6f} " | |
| f"{y:.6f} " | |
| f"{z:.6f}\n" | |
| ) |