shuishen
10 hours ago fbb068ec702338d609c1ca6eddbdb9f182d8f211
capabilities/05-3d-pointcloud/README.md
@@ -1,6 +1,421 @@
# 3D and Point Cloud
# Three-dimensional reconstruction and point-cloud understanding
- 输入:重叠照片、LAS/LAZ/PLY 点云或现有三维成果。
- 输出:点云、网格、DSM、分类或构件结果。
- 首个 Demo:先分析已有 ODM/点云成果,不在本机从零训练重型模型。
## 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-<new-run-id> `
  --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-<new-run-id> `
  --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/<annotation-id>/`.
 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-id>\model.pt `
  --input .\path\to\new-rgb-cloud.las `
  --output .\shared\outputs\05-3d-pointcloud\model-inference-runs\<new-run-id> `
  --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.