# Three-dimensional reconstruction and point-cloud understanding ## Boundary Classification: **B - ecosystem combination**. `geoai-py 0.42.0` has no installed API for SfM/MVS reconstruction or point-cloud classification. This Demo uses pycolmap/COLMAP for CPU sparse SfM and OpenMVS for CPU dense MVS, meshing and texturing from a coherent photo sequence, plus Open3D for existing point-cloud geometry. `geoai.masks_to_vector` turns an elevated-surface label raster into the GeoJSON footprint output. Open3D, Rasterio, GeoPandas, Laspy, and pycolmap supply the remaining work. This is not a built-in `geoai-py` 3D reconstruction function. ## Runnable Demo Input: one existing `PLY`, `PCD`, `XYZ`, `LAS`, or `LAZ` point cloud. The first geometry branch consumes a reconstruction output and creates inspectable raster and vector outputs. The photo branch accepts a coherent JPG/JPEG sequence and creates a CPU sparse SfM point cloud. A user-supplied survey, reconstruction export, or image sequence is copied unchanged into a raw run directory before a console run is processed. ```powershell $py = .\.venvs\05-3d-pointcloud\Scripts\python.exe & $py .\capabilities\05-3d-pointcloud\run_pointcloud_understanding.py ` --input .\shared\data\raw\05-3d-pointcloud\validation-20260821\normal_site.ply ` --output .\shared\outputs\05-3d-pointcloud\validation-normal-20260821 ``` Photo sparse reconstruction: ```powershell $py = .\.venvs\05-3d-pointcloud\Scripts\python.exe & $py .\capabilities\05-3d-pointcloud\run_photo_reconstruction.py ` --input .\shared\data\raw\05-3d-pointcloud\h30t-sideview-20260820-v2 ` --output .\shared\outputs\05-3d-pointcloud\real-h30t-sideview-20260821-v2 ``` CPU dense MVS after a verified sparse model (the original source images remain unchanged; the output directory must be new): ```powershell $py = .\.venvs\05-3d-pointcloud\Scripts\python.exe & $py .\capabilities\05-3d-pointcloud\run_cpu_dense_reconstruction.py ` --input .\shared\data\raw\05-3d-pointcloud\h30t-sideview-20260820-v2 ` --sparse-model .\shared\outputs\05-3d-pointcloud\real-h30t-sideview-20260821-v2\sparse_model\0 ` --output .\shared\outputs\05-3d-pointcloud\cpu-mvs-full- ` --openmvs-bin .\shared\tools\openmvs-2.4.0\vc17\x64\Release ` --threads 12 --max-resolution 2400 --dense-resolution-level 0 ` --dense-number-views 8 --dense-number-views-fuse 2 --target-faces 800000 ``` WebODM/ODX-compatible RTK input preparation (metadata only; it neither starts WebODM nor reconstructs a model): ```powershell $py = .\.venvs\05-3d-pointcloud\Scripts\python.exe & $py .\capabilities\05-3d-pointcloud\prepare_odx_reconstruction_inputs.py ` --input .\shared\data\raw\05-3d-pointcloud\h30t-sideview-20260820-v2 ` --output .\shared\outputs\05-3d-pointcloud\odx-input-h30t-sideview- ` --webodm-reference .\reference_project\WebODM-master ``` The generated package contains `geo.txt` in ODX's `EPSG:4326` format, a camera-position GeoJSON, a source-hash manifest, recommended ODX parameters, and an explicit compatibility report. It reproduces the referenced ODX DJI conversion: `FlightYawDegree`, `90 + GimbalPitchDegree`, and `GimbalRollDegree`, with the reported RTK deviations doubled as ODX does. Leave ODX's global `--gps-accuracy` unset for this package because it would overwrite the per-photo horizontal/vertical deviations in `geo.txt`. The CPU pipeline performs voxel downsampling, RANSAC dominant-ground fitting, height-above-ground labelling, alpha-shape mesh reconstruction, rasterization, and GeoAI footprint vectorization. The output directory must be new or empty. Use `--ground-up-axis auto` only when the source-local Z axis is not vertical; it rotates a processing copy from the dominant RANSAC plane and records that rotation. The raw point cloud is never changed. ## Input and output contract Existing-point-cloud input yields `*.classified.ply`, an alpha-shape `*.reconstruction.ply` when the geometry permits it, DSM and height GeoTIFFs, an elevated-surface GeoTIFF, a PNG raster preview, elevated-footprint GeoJSON, summary CSV, and fixed `run_metadata.json`. Photo input first yields a COLMAP sparse model, coloured `sparse_points.ply`, a principal-axis PNG preview, `camera_poses_local.csv`, an EXIF GPS input manifest, and fixed `run_metadata.json`. The verified CPU dense branch additionally yields `dense.ply`, a triangle `mesh.ply`, OpenMVS texture atlas PNGs, an original `textured.glb`, a self-contained `textured_preview_filtered.glb` for reliable texture inspection, and `geometry_preview.glb` for complete-surface inspection. The reliable-texture preview flips atlas pixels vertically to match browser sampling while preserving OpenMVS UV coordinates, then removes any triangle whose four interior samples reach black atlas background or OpenMVS's orange `RGB(255,127,39)` no-coverage colour. `textured_preview_filter_report.json` records retained and rejected faces. `geometry_preview.glb` keeps every mesh face with a neutral material: it does not invent colour or texture and separates geometric coverage from texture coverage. It records the registered images, sparse/dense point counts, mesh counts, source hashes and CPU parameters. When an OpenMVS dense PLY contains fused RGB, `point_color_preview.glb` also assigns each complete-mesh vertex the RGB of its nearest fused point. This keeps all mesh faces and uses observed point colours, but it is a nearest-point colour projection rather than a per-face photo texture. Default local-coordinate thresholds: voxel size `0.20`, RANSAC plane distance `0.15`, elevated height `0.75`. Review them against the source units and density. ## Coordinates and limitations PLY/PCD/XYZ input normally has no declared CRS. Its output is explicitly `local_point_cloud_coordinates`; GeoTIFF/GeoJSON coordinates are not geographic coordinates, metres, or survey-grade locations. LAS/LAZ source CRS is not promoted in this first Demo, so it is also treated as local. The generated `validation-*` PLYs are known-geometry test fixtures, not field data or an accuracy dataset. `normal_site.ply` checks a dense flat site; `difficult_sparse_site.ply` checks sparse tilted terrain and isolated high noise. The difficult case is expected to show false elevated cells around noise: it validates handling and output, not semantic accuracy. Photo SfM uses `SIMPLE_RADIAL` camera parameters. Position priors can preserve the RTK-scale camera trajectory, but neither camera calibration nor exterior orientation is survey-grade. pycolmap's dense PatchMatch requires CUDA and remains unavailable, but the separate OpenMVS 2.4.0 Windows CPU binary has been verified for dense depth fusion, meshing and texturing. This does not create a DSM, orthomosaic, semantic class, GIS coordinate or survey-grade measurement claim. Real DJI H30T validation: 18 side-view photos from `2026-08-20` completed CPU sparse SfM in 140.832 seconds. All 18 photos registered into one camera group; the result has 11,975 coloured sparse points and 1.1992 px mean reprojection error. A six-photo representative CPU MVS check completed depth fusion, meshing and texturing in 112 seconds (232,060 dense points, 199,970 faces). The full 18-photo CPU MVS run then completed in 461 seconds: 681,788 dense points and a 399,932-face textured mesh. It uses a 1,600 px dense-image cap and 12 CPU threads, so it is a verified CPU baseline rather than a maximum-quality GPU run. A two-photo low-baseline check failed cleanly, documenting the need for sufficient baseline. Reliable-texture reprocessing of the full 18-photo output reuses the completed dense point cloud, disables mesh hole closing/smoothing, requests no texture outlier removal, and filters the browser preview as above. The new raw mesh has 346,878 faces; 128,772 faces (37.12%) pass the four-sample atlas check. The remaining faces are deliberately absent from the preview because the photos do not provide consistent texture there. This avoids orange/black placeholders, but does not create a watertight model or improve the original coverage. High-resolution CPU verification of the same 18 photos completed in 2,341.305 seconds at 2,400 x 1,774 depth maps, level 0, eight neighbouring views, and a two-view fusion threshold. It produced 3,379,097 dense points and a 799,632-face mesh, but only 293,229 faces (36.67%) passed the reliable-texture check. The mesh itself has one 799,646-face main connected component, so the dominant visible holes in reliable-texture display are coverage rejections, not deletion of the geometric surface. The console exposes both modes; this does not prove that the complete geometry is accurate in vegetation or occluded areas. The same high-resolution result also has a 799,632-face point-colour preview; it is useful for inspecting coloured complete geometry, but must not be treated as an image-texture accuracy claim. Metric diagnostic for the RTK/fixed-focal high-resolution result rejects it for height or measurement use. The read-only `diagnose_photo_pose_alignment.py` script compares source DJI RTK/LRF XMP with a sparse model and optional OpenMVS `dense.ply`. For all 18 LRF target points, the latest dense cloud has a 13.3814 m median nearest-point distance, 11.9794 m median horizontal error, and -4.8131 m median signed vertical error. The RTK camera trajectory itself is materially better (0.5978 m median fit error), so the dense failure is not a viewer/texture issue. A fixed-pose sparse trial using RTK camera centres plus LRF target directions kept camera centres at 0 m error but reached 12.0193 px mean reprojection error, compared with 1.2105 px for the RTK-constrained SfM baseline. It therefore rejects the uncalibrated assumptions that the GNSS receiver is the optical centre and the LRF target is the RGB principal point; it must not be used as an MVS input. ## ODX-aligned CPU result The original result above remains a preserved baseline. A new local sparse model uses an ODX-style configuration: `OPENCV` intrinsics (the local analogue of ODX brown calibration), RTK/GPS spatial matching with four neighbours, 2,000 px feature extraction, 18,000 features, and a 0.05 m position-prior loss scale. Both a normal six-photo set and a difficult six-photo set registered 6/6 images, with 0.7581 px and 0.7596 px mean reprojection error respectively. The complete 18-photo model registered 18/18 images with 23,084 sparse points and 0.8410 px mean reprojection error. Its CPU OpenMVS run at 2,400 px, eight views and two-view fusion completed in 2,390.086 seconds. It has 3,756,551 dense points and a 799,645-face mesh. The reliable-texture browser GLB retains 293,614 faces (36.72%) after rejecting black/orange atlas placeholders; the point-colour and neutral complete-geometry GLBs both retain all 799,645 faces. It is available in the case library as `ODX 对齐 CPU 高分重建(H30T 18 图)`. The read-only DJI RTK/LRF diagnostic reports a 0.4038 m median nearest dense point distance for all 18 targets, with 0.3073 m horizontal and 0.0709 m signed vertical median error. This is a material improvement over the preserved old baseline, but the LRF observations are used here as a diagnostic rather than independent surveyed check points. The result is suitable for visual comparison with WebODM and continuing reconstruction evaluation; it is not a survey-grade DSM, orthomosaic, GIS-coordinate, semantic-class, or engineering measurement claim. ## WebODM/ODX reference and acceptance gate The local `reference_project/WebODM-master` was inspected to understand the comparison output. WebODM is the task/UI service; the current reference's default processing node is `webodm/nodeodx` (NodeODX), which runs the actual OpenSfM/OpenDroneMap pipeline. It accepts `geo.txt`, GCPs, and a verified `cameras.json`; it can also lock verified camera parameters during bundle adjustment. These are substantive inputs, not display settings that can be copied into the current COLMAP/OpenMVS code. The new `prepare_odx_reconstruction_inputs.py` produces a checked input package from DJI metadata. For the supplied H30T sequence, the verified package at `shared/outputs/05-3d-pointcloud/odx-input-h30t-sideview-20260822-v2/` contains 18 camera records. Its median per-photo ODX horizontal/vertical observations are 0.00472 m and 0.01162 m respectively. It does not create a guessed `cameras.json`, and it excludes LRF target observations from GCP input. This workstation has neither Docker nor NodeODX/NodeODM available, so no claim is made that WebODM or ODX was executed locally. Before sending this photo set to an ODX worker or cloud service, provide one of: a camera-specific H30T RGB calibration plus antenna/camera lever-arm and boresight; or at least three surveyed stable GCPs, each marked in two or more images, with separate check points. Accept a resulting model only after it reports independent checkpoint residuals and they meet the intended measurement tolerance. Further accuracy improvement requires camera-specific H30T calibration and antenna-to-camera lever-arm/boresight data, or at least three stable surveyed 3D control points marked in two or more images with separate check points. A uniform height translation is not a valid replacement for those inputs. The sparse PLY contains only `element vertex 11975`; it has no faces or normals. A viewer that triangulates it as a surface creates false sheets or spikes. The console renders sparse data strictly as coloured `THREE.Points`; dense MVS cases offer reliable-texture and complete-geometry modes with orbit, zoom, reset and optional wireframe. A point-colour mode can additionally project fused point RGB onto every mesh vertex. Complete geometry uses a neutral material and retains all mesh faces, while reliable texture retains only faces with image evidence. The original mesh and atlas files remain downloadable for review. The CPU mesh still has holes or potentially incorrect surfaces in occluded, vegetated, low-texture or reflective regions and cannot be assumed equivalent to a higher-resolution GPU/cloud output without visual review. The separate real input `shared/data/raw/05-3d-pointcloud/pcl-table-scene-20260821/table_scene_lms400.pcd` is the PCL tutorial's `table_scene_lms400` scan, downloaded unchanged from the PCL data repository (SHA-256 `E285D415641E0D9DE695B611DB874CC8FE995E8089B77A50D6056D24D8CBCC58`). Its 460,400 source points validate reading a real PCD and the `--ground-up-axis auto` processing-copy branch. It is a tabletop scan, not an aerial reconstruction or semantic truth set. Ground/elevated is a geometric split, not building, tree, equipment, or hazard recognition. Slopes, water, vertical dominant surfaces, dense vegetation, or a weak/no ground return can invalidate the RANSAC assumption. The mesh is an inspectable alpha-shape approximation and is not guaranteed watertight or metric-accurate. Obtain registered source data and manually reviewed truth before operational or commercial use. ## Semantic point-cloud classification baseline The existing-point-cloud route now writes a separate CPU semantic-classification baseline. It uses RGB plus locally estimated ground/height, density, vertical span, and horizontal line continuity. It is still **B** work: `geoai-py` is used only for the existing elevated-surface raster-to-vector branch, not for a native point-cloud classifier. Classes are `ground`, `vegetation`, `building_structure`, `power_line`, `pole_tower`, and `other_unknown`. The last three names describe review outputs: the first version is deliberately a transparent rule baseline rather than a trained semantic model. It exports a colour-coded preview PLY (capped at 400,000 points for the browser), semantic LAS class codes, class GeoTIFF/PNG, candidate GeoJSON, class-count CSV, and `run_metadata.json`. Real LAS check: the unchanged user input `baseData/las/part_01.las` contains 10,473,127 points, RGB and return attributes, but every source LAS `classification` value is `0`; it is not semantic truth. Run `validation-las-part01-semantic-20260822-v11-original-rgb` used 0.20 local-unit voxel sampling and local-neighbourhood shape features (horizontal linearity for conductors and vertical linearity for pole/tower members). It completed in 73.586 seconds CPU with 1,370,984 output points: 356,170 ground, 714,122 vegetation, 1,031 structure, 3,183 pole/tower candidates, 8,162 power-line candidates, and 288,316 unknown. The preview retains every line/pole candidate before sampling the other classes, because uniform preview sampling hid sparse conductors. Candidate precision and recall remain unvalidated. Do not use these candidates as asset inventory, clearance, or inspection conclusions until a user reviews the browser preview and supplies labelled ground truth. The generated H30T dense cloud was also checked as `validation-h30t-dense-semantic-20260822-v2`: 3,756,551 input points became 1,205,115 sampled points in 38.673 CPU seconds. Its dominant plane was too steep to be accepted as ground, so the output uses only the local low-point fallback and records `ground_plane_available: false`. It produced 181,436 ground, 663,962 vegetation, 988 structure, 641 pole/tower candidates, zero power-line candidates and 358,088 unknown. This proves the technical route accepts the reconstructed PLY; it does not validate class accuracy for this side-view model. ## Console The independent local experiment console exposes `GET/POST /api/3d-pointcloud/runs`. It accepts at most two PLY/PCD/XYZ/LAS/LAZ files for new geometry runs, generates a server-side run ID, preserves raw source bytes and SHA-256, and stores raw, processed, and output artifacts separately. Its case library also discovers sparse photo-SfM and dense CPU-MVS result directories. Sparse results display an interactive Three.js point viewer, registration statistics, poses, input manifest and downloads. Dense results display reliable texture, point-colour and complete-geometry modes, plus dense PLY, mesh PLY and texture downloads. The point-colour mode projects fused RGB point values to the complete mesh; the complete geometry mode retains all mesh faces with a neutral material. Neither fabricates a photo texture. The sparse viewer supports orbit, zoom, point-size adjustment and reset, and never triangulates PLY vertices. It has no connection to a drone product, product account, product API, or product database. The console keeps incompatible inputs in two workflow tabs. `照片三维重建` accepts 3-30 JPG/JPEG files from one coherent flight/camera sequence through bounded binary uploads, preserves source bytes and SHA-256 values in a new run directory, and starts a background CPU sparse-SfM then OpenMVS dense-MVS job. The page polls `queued`, `sparse_sfm`, `dense_mvs`, `complete`, or `failed` and automatically adds a completed result to the case library. Enable `GPS/RTK 先验` only when all input photos have valid position metadata; it selects the verified spatial matching configuration. Disable it for ordinary cameras, which use exhaustive matching. `既有点云理解` remains the PLY/PCD/XYZ/LAS/LAZ-to- raster/vector route. Creating a new run or discovering its result does not rebuild the console; `npm run build` is only needed after changing Vue/TypeScript frontend source. The console separates this capability into four workspaces: photo 3D reconstruction; point-cloud geometry processing; point-cloud semantic classification; and annotation, training, and model application. Photo cases are isolated from point-cloud cases. Geometry and semantic workspaces each have their own filtered case library; a source run can appear in both only when it has both output families. The model workspace contains the linked annotation, training, and portable-model workflow without placing it below every semantic result. ```powershell py -3.12 .\scripts\serve_workbench_console.py --port 6188 ``` ## Licenses - `geoai-py`: MIT. This does not grant rights for data or other dependencies. - Open3D: MIT; check release and bundled dependency notices before redistribution. - Laspy: BSD-3-Clause; Rasterio, GeoPandas, and Shapely retain their own licenses. - No model weights or third-party point-cloud dataset are bundled. Record the licence of each survey/reconstruction source before commercial use. - The PCL tutorial scan remains subject to the upstream PCL data repository's licensing and attribution terms; it is validation input only and is not cleared here for commercial redistribution. - WebODM is AGPL-3.0. NodeODX/ODX, OpenSfM/OpenDroneMap, their images, and any cloud processing service require separate license and data-handling review. ## Human annotation and supervised CPU training The workbench now provides a separate human annotation and supervised training area. A semantic run writes a 400,000-point `*.semantic-annotation-source.ply` that preserves observed RGB and coordinates. Rule-coloured previews are never used as model inputs or ground truth. Brush and rectangle selections are saved as separate revisions under `shared/outputs/05-3d-pointcloud/annotations//`. The annotation viewer renders observed RGB through an sRGB-correct, anti-aliased circular point sprite. It separates navigation from annotation: browse mode uses left-drag rotation, while brush/rectangle mode reserves left-drag for labels and retains right-drag rotation, middle-drag panning and wheel zoom. Middle-button auto-scroll is suppressed within the viewer. The rotate-centre tool sets the camera target to a clicked visible point without creating a label. The model-orientation control independently rotates the displayed model about its centre on local X/Y/Z axes from 0 through 360 degrees. It is a viewer-only transform: source coordinates, semantic outputs and saved point indices remain unchanged, while point picking and rectangle selection use the transformed view. A saved revision is never edited in place. If it contains an error, the console can remove that selected revision after confirmation; it cannot delete source LAS/PLY data, semantic results, or training outputs. Each revision records source run/checksum, point indices, class codes and time. `train_pointcloud_semantic_model.py` trains a compact PointNet-style shared MLP from user-confirmed labels only. It runs locally with `--device cpu` and later uses the same code/data with `--device cuda`. Training requires two or more classes and at least 500 confirmed points per class, partitions XY blocks into train/validation/test, and writes `model.pt`, `metrics.json`, a predicted PLY, per-class precision/recall/F1 and a confusion matrix. Metrics apply only to the labelled source blocks and are not field-wide accuracy claims. CPU validation `validation-las-part01-semantic-20260822-v11-original-rgb` processed unchanged `baseData/las/part_01.las` in 73.586 seconds and created its 400,000-point RGB annotation source. Its 3,183 pole/tower and 8,162 power-line results remain rule candidates until users supply labels and supervised evaluation is completed. ## Applying a trained model The supervised model can be applied to a new coloured PLY, PCD, LAS, or LAZ point cloud without retraining. This is still B work: PyTorch, Open3D, NumPy, and Laspy perform inference; `geoai-py` is not a built-in point-cloud semantic classifier. The input must contain readable observed RGB values. XYZ-only inputs fail rather than inventing colour features. Coordinates remain unchanged. XYZ features are normalised using the new input's own centre and extent, matching the feature definition used during training without forcing the old scene coordinates onto a new site. ```powershell .\.venvs\05-3d-pointcloud\Scripts\python.exe ` .\capabilities\05-3d-pointcloud\apply_pointcloud_semantic_model.py ` --model .\shared\outputs\05-3d-pointcloud\training-runs\\model.pt ` --input .\path\to\new-rgb-cloud.las ` --output .\shared\outputs\05-3d-pointcloud\model-inference-runs\ ` --device cpu ``` Each new output directory contains `predicted-semantic-preview.ply` (a class-aware deterministic browser preview capped at 400,000 points), `predicted-semantic-classified.las` (LAS classification codes), `class-counts.csv`, `prediction-summary.json`, and `run_metadata.json` with model/input SHA-256 values, versions, normalisation, class counts, runtime, and limitations. No vector asset layer is fabricated from semantic predictions. Verified CPU check on 2026-08-24: model `semantic-model-20260824-023343-571983` applied to the 400,000-point RGB annotation source in 1.608 seconds through the CLI. The local-console upload and background-job route was also verified, including PLY and classified-LAS artifact download. This verifies the workflow, not field accuracy. Its labelled spatial test F1 is vegetation 0.979, power line 0.815, and pole/tower 0.348; the latter has substantial false-positive risk and all output remains review candidates.