Python Reference¶
Auto-generated from docstrings in backend-api/main.py via
mkdocstrings.
Note
Only public, documented functions appear below. To extend coverage,
add Google-style docstrings to handlers and helper functions in
backend-api/main.py.
ProspectFitRequest
¶
Bases: BaseModel
Request model for PROSPECT-D spectral fitting
PointSource
¶
Bases: BaseModel
Tell a downstream endpoint to read points from a live cloud SESSION
(in-RAM, source of truth) or — only as a fallback — a file on disk, instead
of an inline points array.
Octree-backed clouds keep no positions in the renderer (the geometry lives
only in the on-disk Potree octree, streamed to the GPU), so skeleton /
triangulate / c2m / icp / export resolve their points here. When session_id
is set the in-RAM session array is the point of truth (deletions honored, no
file re-read); source_path is then provenance only and may be empty (e.g. a
synthetic-scan session that never had a source file). When there is no session,
source_path is read from disk.
Resolved by _read_points_from_source (defined later, alongside the other
point-cloud loaders it reuses).
TriangulationGrid
¶
Bases: BaseModel
A voxel grid to PIN a (ball-pivot / Open3D) triangulation to, so the mesh
can later be re-used as the external triangulation for the leaf-area (LAD)
inversion. Same shape as HeliosGrid (defined later in the file), declared
separately so TriangulationRequest doesn't forward-reference it.
When set on a TriangulationRequest the backend (a) crops points to the grid's world AABB before meshing — so only points inside the box are triangulated — and (b) bins each output triangle's centroid into the grid, returning a per-triangle cell id. The renderer needs both: the crop confines the mesh to the box, and the cell ids let the LAD reuse path drop any triangle whose centroid still falls outside the grid (belt-and-suspenders for "only in-grid triangles feed the inversion").
TriangulationRequest
¶
Bases: BaseModel
Request model for point cloud triangulation
TriangulationResponse
¶
Bases: BaseModel
Response model for triangulation results
CrownFitRequest
¶
Bases: BaseModel
Fit a geometric shape to a tree crown and compute per-crown metrics.
Points come from a cloud session (source.session_id) so the per-point
classification labels (tree_instance / wood_class / ground_class) can be read
back from sess.extras — the same authoritative store the segmentation tools
write. A file/inline source is accepted for a single unlabelled tree, but
then no labels are available (whole cloud = one tree, all points = crown).
GroundSegmentationRequest
¶
Bases: BaseModel
Request model for ground/non-ground segmentation via CSF.
Provide either inline points (flat clouds) or a source descriptor
(octree-backed clouds — the backend re-reads the file). Unlike skeleton /
triangulate, segmentation must NOT downsample: the returned labels align
1:1 with the resolved point order so the renderer can attach them as a
per-point scalar. Callers leave source.max_points as None.
GroundSegmentationResponse
¶
Bases: BaseModel
Per-point ground/plant labels aligned to the resolved point order.
DemRequest
¶
Bases: BaseModel
Generate a DEM from a flat cloud's inline points or a source descriptor.
Ground-aware: pass ground_labels (1=ground, 2=plant, aligned 1:1 with the
resolved points) to grid only ground points; else CSF is run when
auto_segment_ground is set, else all points are used (lowest-return surface).
WoodSegmentationRequest
¶
Bases: BaseModel
Per-point wood/leaf segmentation from XYZ geometry.
Provide inline points (flat clouds) or a source descriptor (octree-backed
clouds — the backend re-reads the file at full resolution). Like ground
segmentation the result must NOT be downsampled, so labels align 1:1 with
the resolved point order. The tuning fields map onto segment_wood():
wood_bias is the wood-vs-leaf sensitivity (lower → more wood), the k_*
fields set the neighbourhood-scale search, reg_iters the smoothing
strength, and voxel_size (>0) enables downsample-classify-propagate for
very large clouds.
WoodSegmentationResponse
¶
Bases: BaseModel
Per-point wood/leaf labels aligned to the resolved point order.
TreeSegmentationRequest
¶
Bases: BaseModel
Request model for individual-tree segmentation via TreeIso.
Like ground segmentation, provide inline points or a source descriptor;
the result must NOT be downsampled so per-point labels align 1:1. Optional
seed_points ([[x,y,z], ...]) anchor trees for human-in-the-loop seeding —
each seed yields exactly one tree id. The remaining fields are TreeIso
parameters (defaults match Xi & Hopkinson 2022).
TreeSegmentationResponse
¶
Bases: BaseModel
Per-point tree instance ids aligned to the resolved point order.
PoseSample
¶
Bases: BaseModel
One 6-DOF platform pose sample: time + position + Hamilton (body->world) quaternion (qx, qy, qz, qw, scalar last).
FrameMeta
¶
Bases: BaseModel
Documented frame/CRS so a timestamp join is never silently wrong. up_axis
is the world up convention ('z' for Phytograph); body_convention/time_ref
are recorded for downstream interoperability (FLU/FRD, GPS-week vs relative).
PoseStream
¶
Bases: BaseModel
A dense timestamped 6-DOF platform trajectory plus its calibration — the canonical moving-platform representation (see backend-api/trajectory.py).
Quaternions are Hamilton body->world (qx,qy,qz,qw). lever_arm (body-frame
scanner optical center, meters) and boresight_rpy (sensor misalignment
roll/pitch/yaw, radians) calibrate the platform->scanner transform; the per-beam
emission origin is pos(t) + R(quat(t))·lever_arm. The backend resolver joins
this to each return's timestamp (SLERP attitude, linear position).
HeliosScanEntry
¶
Bases: BaseModel
A single scan with point data (or file path) and scanner position.
Provide either file_path (preferred for large scans) or points.
When file_path is given, the backend reads the file directly via pyhelios,
avoiding large JSON transfers.
HeliosGrid
¶
Bases: BaseModel
An explicit triangulation grid, derived from a voxel box in the UI.
Helios's XML loader requires a
HeliosTriangulationRequest
¶
Bases: BaseModel
Request model for Helios triangulation
HeliosFilterEstimate
¶
Bases: BaseModel
Auto-estimated triangulation filter, derived from the candidate edge-length distribution (Otsu separability) + a merged-multi-scan-cloud guard.
HeliosTriangulationResponse
¶
Bases: BaseModel
Response model for Helios triangulation
SpacingCheckResponse
¶
Bases: BaseModel
Verdict from /api/triangulate/check-spacing — an opt-in cross-check of the auto-estimated Lmax against the actual point spacing.
The auto-Lmax estimator (Otsu over candidate triangle-edge lengths) silently
fails on sparse shells: a thin layer of surface points generates mostly
bridge triangles spanning the cell interior, so the candidate-edge
distribution looks bimodal (eta/sep_ratio read "Medium"+) even though its
lower mode is still bridges, not surface. The chosen Lmax then far exceeds the
true point spacing and the reconstructed normals — hence G(theta) — are
garbage. The candidate edges can't self-diagnose this; an INDEPENDENT measure
of the surface scale can. We compute the median nearest-neighbor spacing of
the points strictly inside the grid cell(s) and compare it to lmax. This is
O(N log N) (a KD-tree build + query) and can take tens of seconds on a
tens-of-millions-of-points cloud, which is why it's a user-triggered button
rather than part of the triangulation, and why it's only offered when the
Otsu indicators aren't both High.
DemRaster
¶
Bases: BaseModel
A regular axis-aligned DEM elevation raster, as produced by /api/dem.
Used by terrain-following LAD to sample a ground height under each voxel
column. grid_z is the elevation per cell, row-major with row 0 = min y
(grid_z[j*nx + i]), matching the renderer's MeshEntry.demGrid. Void cells
are NaN. origin is the lower-left corner [minx, miny]; cell is the cell
size (m). Coordinates are in the SAME frame as the scan points / grid (the
renderer round-trips world_shift so the raster aligns with the cloud).
GThetaValueSpec
¶
Bases: BaseModel
How to obtain a single G(theta) value for a cell (or a whole z-level).
GThetaOverrideSpec
¶
Bases: BaseModel
Direct G(theta) override. spatial chooses constant-vs-vertical-profile; the
value spec(s) choose how each G(theta) is obtained (constant / de Wit / Beta).
- spatial="constant":
specis required and applies to every voxel (one scalar). - spatial="profile":
profileis required, one GThetaValueSpec per z-level, length must equal grid.nz (the method is the same across levels; the per-level parameters vary with height). z-level 0 is the lowest band.
LADComputeRequest
¶
Bases: BaseModel
Request model for leaf area density computation.
Reuses HeliosScanEntry for scans (each carrying its scanner origin, angular geometry, and return_type). The grid is REQUIRED (its nx/ny/nz are the LAD voxel divisions) — there is no meaningful "auto single-cell" LAD.
LADCell
¶
Bases: BaseModel
A single voxel result.
LADComputeResponse
¶
Bases: BaseModel
Response model for leaf area density computation.
SnapGridRequest
¶
Bases: BaseModel
Compute per-column ground offsets for a 'snap to ground' grid displacement.
The grid is sampled against the DEM exactly as terrain-following LAD does (one source of truth: _sample_dem_columns). The renderer applies the returned offsets to displace the grid in the viewport, then sends the SAME offsets back on the grid for the LAD inversion — so what is seen is what is inverted.
TrajectoryParseRequest
¶
Bases: BaseModel
Parse a binary trajectory file into the canonical PoseStream wire shape.
path is a server-readable file path (the renderer sends the picked path, same
as the cloud-import endpoints). format is auto-detected from the extension when
omitted. smrmsg_path optionally points at the SBET accuracy companion for a QC
warning. target_poses caps the decimated pose count.
ScanExportGrid
¶
Bases: BaseModel
A voxel-box grid to write as a Helios
ScanExportEntry
¶
Bases: BaseModel
One scan to export. Point source is one of session_id / points / file_path (resolved in that precedence, mirroring the LAD path). Scanner geometry is written into the XML; translation is applied to the points on export.
ScanExportRequest
¶
Bases: BaseModel
Export one or more scans to a Helios XML + per-scan ASCII bundle.
LidarScanMaterial
¶
Bases: BaseModel
A textured material group on a scan mesh.
texture_data is a base64-encoded image (PNG/JPG). When it carries an
alpha channel, Helios uses that channel as a transparency mask during ray
tracing — leaf textures are leaf-shaped cutouts on a transparent
background, so rays only register hits where the leaf is opaque instead of
on the full rectangular quad. triangle_indices are ordinals into the
mesh's triangles array that use this material.
LidarScanMesh
¶
Bases: BaseModel
A single mesh to load into the scannable scene (world-space coordinates).
RisleyPrismSpec
¶
Bases: BaseModel
One rotating wedge prism of a Livox-style Risley-prism deflector.
Sent in datasheet units — wedge angle in DEGREES, rotor rate in Hz — and converted to the radians / rad-per-second that pyhelios' RisleyPrism expects at the addScanRisley call site below (mirroring the deg->rad conversion the static/moving scan branches do for the angular sweep).
LidarScanScanner
¶
Bases: BaseModel
A single scanner position + acquisition geometry (mirrors ScanParameters).
LidarScanRequest
¶
Bases: BaseModel
Request model for a synthetic LiDAR scan.
LidarScanResult
¶
Bases: BaseModel
Per-scanner scan result.
LidarScanResponse
¶
Bases: BaseModel
Response model for synthetic LiDAR scan results — one entry per scanner.
SkeletonRequest
¶
Bases: BaseModel
Request model for tree skeleton extraction using BFS graph-based algorithm
SkeletonBlock
¶
Bases: BaseModel
Information about a skeleton block (cluster of points at same BFS level)
SkeletonEdge
¶
Bases: BaseModel
Edge connecting two skeleton nodes
SkeletonResponse
¶
Bases: BaseModel
Response model for BFS-based skeleton extraction
PlantGenerationRequest
¶
Bases: BaseModel
Request for generating a plant model
PlantCanopyRequest
¶
Bases: BaseModel
Request for generating a canopy of regularly spaced plants
PlantStreamRequest
¶
Bases: BaseModel
Request for streaming plant/canopy generation with progress (SSE).
mode selects single-plant vs. canopy; the relevant subset of fields is
used for each. Single plants additionally create a retained session so the
age slider keeps working after generation.
PlantMaterial
¶
Bases: BaseModel
Material definition for plant rendering
PlantMaterialGroup
¶
Bases: BaseModel
Group of triangles sharing the same material
PlantGenerationResponse
¶
Bases: BaseModel
Response containing generated plant mesh data and Helios XML structure
PlantSession
dataclass
¶
Holds an active pyhelios session for incremental plant growth
PlantSessionCreateRequest
¶
Bases: BaseModel
Request to create a new plant session
PlantSessionCreateResponse
¶
Bases: BaseModel
Response after creating a plant session
PlantSessionAdvanceRequest
¶
Bases: BaseModel
Request to advance time on a plant session
PlantSessionAdvanceResponse
¶
Bases: BaseModel
Response after advancing plant time, includes updated geometry
PlantSessionStatusResponse
¶
Bases: BaseModel
Status of a plant session
QSMBuildRequest
¶
Bases: BaseModel
Request for a full QSM build. Points come inline (points) or from a
file/octree cloud (source), mirroring /api/skeleton/extract.
For an AGGREGATE build (several pre-registered multi-view scans of ONE
tree fused into a single QSM), pass sources: each is read and the points
concatenated in world space (each source's own translation applied). This
is the only way to fuse octree-backed clouds, whose display positions are
empty client-side. sources takes precedence over points/source.
QSMCylinder
¶
Bases: BaseModel
One fitted cylinder of the woody structure.
QSMShoot
¶
Bases: BaseModel
A continuous botanical axis (a maximal chain of continuation cylinders).
QSMPhyllotaxisRequest
¶
Bases: BaseModel
Round-tripped QSM topology for phyllotaxis auto-detection. Only the cylinders + shoots are needed (radii/metrics are irrelevant here).
QSMLeavesRequest
¶
Bases: BaseModel
Add leaves to an existing QSM (cylinders + shoots round-tripped from the renderer). Exactly one texture source is used, in precedence order obj_path > texture_path > builtin_texture_name.
QSMLeavesResponse
¶
Bases: BaseModel
Textured leaf mesh. Field names mirror PlantGenerationResponse so the frontend's plantResponseToMeshData() consumes it unchanged.
QSMGrid
¶
Bases: BaseModel
The voxel grid the triangulation was built in (full extents + subdivisions).
QSMTriangulationInput
¶
Bases: BaseModel
A leaf-on Helios triangulation overlapping the QSM, from the renderer.
QSMCellTarget
¶
Bases: BaseModel
A precomputed per-cell leaf-angle target (escape hatch / test injection).
QSMAdjustLeafAnglesRequest
¶
Bases: QSMLeavesRequest
Re-place the QSM's leaves (same Phase-1 params) then adjust their angles to
a measured per-cell distribution. Exactly one of triangulation / cell_targets
must be present (with grid when using cell_targets).
PlantMorphParseRequest
¶
Bases: BaseModel
Request to parse plant XML into editable parameters
PlantMorphParseResponse
¶
Bases: BaseModel
Parsed plant structure for the morph UI
PlantMorphRequest
¶
Bases: BaseModel
Request to morph/regrow a plant from modified XML
PlantMorphResponse
¶
Bases: BaseModel
Response from POST morph
MeshImportRequest
¶
Bases: BaseModel
Request to import a textured mesh from a file on disk.
MeshImportResponse
¶
Bases: BaseModel
Imported mesh geometry + textures (mirrors PlantGenerationResponse).
PointCloudExportRequest
¶
Bases: BaseModel
Request for exporting a point cloud.
Flat clouds send inline points (+ optional colors) and format in
{"las","laz"}. Octree-backed clouds send source instead — and may request
any of {"las","laz","xyz","txt","csv","ply"} since the renderer has no
positions to format text from. The backend reads the source file, applies
pending translation, and returns base64-encoded output in all cases.
PointCloudExportResponse
¶
Bases: BaseModel
Response containing the exported file data
PointCloudImportResponse
¶
Bases: BaseModel
Response containing imported point cloud data
ColumnPlanEntry
¶
Bases: BaseModel
One column's import mapping, produced by the import wizard.
role is a Helios-style token (x/y/z/r255/g255/b255/r/g/b/intensity/
reflectance/skip) or the literal 'extra' for a carried scalar field. For an
'extra' column, slug/label give the on-disk LAS extra-dim name and the
picker label (rename), and categorical marks it for discrete colouring in
the renderer. index is the 0-based source column position.
ColumnPlan
¶
Bases: BaseModel
Explicit column layout for an XYZ-family file, from the import wizard.
When attached to an import/convert request it fully overrides header/format
auto-detection. rgb_is_255 records whether the r/g/b columns are 0-255
integers (True) or already 0-1 floats (False) so the LAS writer scales them
correctly. Applies only to ASCII formats; PLY/PCD/LAS define their own
layout and ignore it.
ImportPointCloudByPathRequest
¶
Bases: BaseModel
Path-based point-cloud import.
ascii_format is a Helios column_plan, when present, fully overrides
both — it's the explicit layout chosen in the import wizard.
PointCloudPreviewRequest
¶
Bases: BaseModel
Inspect a point-cloud file cheaply for the import wizard.
Reads only the header + first max_rows data rows (ASCII) or the header +
a few points (LAS/PLY/PCD) — never materialises the whole file. The optional
ascii_format hint biases role detection the same way the import path does.
PreviewColumn
¶
Bases: BaseModel
One source column as the wizard should present it.
detected_role is the auto-detected Helios role (or 'extra'/'skip');
suggested_slug/suggested_label mirror what import would name a carried
scalar (so the wizard's defaults match the eventual on-disk attribute).
type_hint is a sniffed value shape (integer/float/categorical/empty) used
to pre-tick the categorical checkbox. remappable is True only for ASCII
formats — PLY/PCD/LAS define their own layout, so roles can't be reassigned.
ScanCancelled
¶
Bases: Exception
Raised inside a streaming worker when its run has been cancelled.
ClientDisconnected
¶
Bases: Exception
Raised by _run_killable when the HTTP client goes away (panel closed,
Cancel button, fetch AbortController timeout) before the worker subprocess
finishes — after the worker has been SIGKILLed.
CropOctreeRegion
¶
Bases: BaseModel
Box, polygon, or sphere-union spatial region for the cloud-session edit endpoints (delete_region / filter / split / extract). See _canonical_region for validation rules — the handlers delegate to that helper.
ScalarFilter
¶
Bases: BaseModel
Keep only points whose imported scalar attribute slug matches.
Two modes
- Continuous (default): keep points in the inclusive range [min, max].
- Categorical: when
valuesis set, keep points whose value rounds to one of the listed class ids (an OR within the field —min/maxignored). Used by the filter UI's class-checkbox path for integer-valued labels likeground_class/tree_instance, where a value such as2means a discrete class, not a position on a continuum, and a multi-select keep need not be contiguous.
slug is the on-disk extra-dimension name (matches a key in the octree's
attributeRanges / the extra_dims slugs produced by _xyz_column_plan).
CloudSession
dataclass
¶
An imported point cloud held in RAM as the COMPLETE source of truth.
The full attribute set — positions, colours, intensity, and every scalar extra-dimension — lives in these arrays. The source FILE is read exactly once (at create); after that every operation (delete/crop/erase, filter, ground/tree segment, bake, downstream compute) reads or mutates these arrays and never touches the file again. The Potree octree is a derived cache rebuilt from the arrays on bake.
LasReadResult
dataclass
¶
Everything _read_las_into_arrays materialises from a normalised LAS.
A dataclass (not a tuple) because the set has grown past readable positional unpacking and carries several Optionals with subtle precision contracts. positions (N,3) float64 — full-resolution coordinates. colors (N,3) uint16 | None — kept in LAS scale for byte round-trip. intensity (N,) uint16 | None. extras {slug: (N,) float32} — scalar extra-dim columns. extra_dims_meta ordered [{slug, label}] for the octree sidecar. timestamps (N,) float64 | None — gps_time, kept OUT of float32 extras (the LAD trajectory-join key; see CloudSession.timestamps). gps_time_encoding 'adjusted_standard' | 'gps_week' | None. beam_origins (N,3) float64 | None — per-pulse emission points read from ExtraBytes (ox/oy/oz aliases); when present LAD uses them directly and skips the trajectory join.
CloudSessionCreateRequest
¶
Bases: BaseModel
Create a mutable cloud session from a source file and build its first
(derived) octree. column_plan is the import wizard's explicit layout and
is honored ONCE here, then carried for the life of the session so edits
never re-auto-detect columns (the import-wizard option-loss fix).
DeleteRegionRequest
¶
Bases: BaseModel
Mark points inside region as deleted on a cloud session. Instant: sets
the in-RAM mask; does NOT rebuild the octree. The renderer mirrors the
deletion on the GPU via its clip-volume stack, so the viewport updates
immediately.
BackfillMissesRequest
¶
Bases: BaseModel
Explicitly recover a session's sky/miss points and persist them (see POST .../backfill-misses).
Mirrors the LAD-relevant subset of HeliosScanEntry so the backfill cloud is
built on the exact same array + addScan path as /api/lad/compute. origin
is the scanner position (per-beam directions are reconstructed from it); the
optional angular raster (n_theta/n_phi/theta_/phi_) sets the scan grid the
gapfiller reconstructs misses over, falling back to a count-based estimate
when omitted. trajectory marks a moving-platform scan (per-pulse origins
joined by timestamp), which forces the timestamp gapfill path.
ResetCloudEditsRequest
¶
Bases: BaseModel
Undo. edit_count = how many committed deletes to KEEP; the mask is
restored to that snapshot in the history and later ones are discarded.
Omit to clear all deletions (edit_count = 0).
SessionSplitRequest
¶
Bases: BaseModel
Split a session into the points a filter KEEPS (stay on this session) and the points it EXCLUDES (a NEW leftover session). Operates entirely on the in-RAM arrays — no source file read. Same predicate shape as the filter.
SessionExtractRequest
¶
Bases: BaseModel
Extract the points a spatial+scalar filter SELECTS into a NEW child session, leaving the parent UNCHANGED. Operates on the in-RAM arrays — no source file read. Used by 'split into clouds' workflows that keep the classified parent and spin off per-class child clouds.
SessionExtractByColumnRequest
¶
Bases: BaseModel
Split a session into ONE child session per distinct integer value of a
categorical column (e.g. tree_instance → one cloud per tree), leaving the
parent UNCHANGED. Operates on the in-RAM arrays — no source file read. This
is the batch form of extract: instead of the renderer looping N HTTP calls
(one full octree build each, serial), the server slices all subsets under a
single lock and builds their octrees CONCURRENTLY.
SessionMergeRequest
¶
Bases: BaseModel
Concatenate the SURVIVING points of two or more sessions into one NEW session (stitch). Operates entirely on the in-RAM arrays — no source file read. The inputs are left untouched (the renderer removes them from the scene, carrying each session id for deferred-free so undo can restore them).
SessionGroundSegmentRequest
¶
Bases: BaseModel
Run CSF ground segmentation on the session's in-RAM points and append a
ground_class scalar column (1=ground, 2=plant). No source file read.
SessionDemRequest
¶
Bases: BaseModel
Generate a DEM from a session's in-RAM survivors. Ground-aware: a prior
ground_class column restricts gridding to ground points; else CSF is run
when auto_segment_ground is set, else all points are used. Optionally write
a height_above_ground (CHM) scalar back onto the cloud and rebuild.
SessionWoodSegmentRequest
¶
Bases: WoodSegmentationRequest
Run wood/leaf segmentation on the session's in-RAM points and append a
wood_class column (1=wood, 2=leaf). Inherits the segment_wood tuning
fields; points/source are ignored (the session's arrays are the source
of truth).
SessionTreeSegmentRequest
¶
Bases: TreeSegmentationRequest
Run TreeIso on the session's in-RAM points and append a tree_instance
column. Inherits the TreeIso tuning fields; points/source are ignored.
SessionTransformRequest
¶
Bases: BaseModel
Apply a rigid 4x4 transform (rotation + translation) to a session's
geometry in place, then rebuild the octree. Used by cloud-to-cloud ICP to
MOVE an octree-backed source cloud onto the target: ICP computes the matrix
on world-frame points and this bakes it into the session so the streamed
octree follows. matrix is 16 floats, ROW-MAJOR world-frame (the same
layout ICPRegistrationResponse.transformation_matrix returns).
SessionFilterRequest
¶
Bases: BaseModel
Apply a spatial + scalar filter to the session by DELETING the points the
filter excludes (sets the deleted mask). Operates entirely on the in-RAM
arrays — no source file read. region keeps points inside it (invert to
flip); scalar_filters keep points whose attribute is in range/class. A
point survives iff it passes the region AND every scalar filter.
C2MDistanceRequest
¶
Bases: BaseModel
Request for computing cloud-to-mesh distance statistics.
C2MDistanceResponse
¶
Bases: BaseModel
Response with cloud-to-mesh distance statistics.
ICPRegistrationRequest
¶
Bases: BaseModel
Request for ICP registration to align mesh to point cloud.
ICPRegistrationResponse
¶
Bases: BaseModel
Response with ICP registration transformation.
CloudToCloudICPRequest
¶
Bases: BaseModel
Request for ICP registration to align one point cloud to another.
MeshToMeshICPRequest
¶
Bases: BaseModel
Request for ICP registration to align one mesh to another.
unicode_to_ascii(s)
¶
Convert unicode subscripts to ASCII numbers for phytorch compatibility.
health_check()
¶
Health check endpoint for backend status
get_version()
¶
Version endpoint for Tauri app to check backend compatibility
device_info()
¶
Report whether synthetic-scan ray tracing runs on GPU or CPU.
The packaged Windows/Linux builds always compile the CUDA ray-tracing path (the release CI fails the build otherwise), and cudart is linked statically so a GPU build still runs on a machine with no driver — Helios's cudaGetDeviceCount() returns 0 and it falls back to CPU/OpenMP. macOS builds are always CPU-only (no CUDA on Apple hardware). So the effective path is decided entirely by a runtime probe for a usable NVIDIA GPU (pyhelios.runtime.get_gpu_runtime_info, primarily via nvidia-smi): GPU when one is present on a non-macOS build, CPU otherwise.
get_model(category, model_type)
¶
Get the appropriate phytorch model based on category and type
fit_model(file=File(...), model_category=Form(...), model_type=Form(...), method=Form('auto'), max_iterations=Form(1000))
async
¶
Fit a phytorch model to uploaded data.
list_models()
¶
List available models and their required data fields
sanitize_equation(equation)
¶
Clean up equation for Python evaluation
create_model_function(equation, param_names, input_symbols, constants, symbol_to_name)
¶
Create a callable function from an equation string
fit_custom_model(request)
async
¶
Fit a custom or built-in model to data.
get_model_builtin(model_id)
¶
Get a built-in phytorch model instance
convert_symbol_to_latex(symbol)
¶
Convert a symbol name to LaTeX, handling Greek letters and subscripts.
python_to_latex(equation, output_symbol='y', equation_type='explicit')
¶
Convert a Python/numpy equation string to LaTeX using pytexit. Preserves the original term order.
convert_to_latex(request)
async
¶
Convert a Python equation to LaTeX format.
convert_to_latex_get(equation, output_symbol='y', equation_type='explicit')
async
¶
Convert a Python equation to LaTeX format (GET version for easy testing).
export_fit_results(request)
async
¶
Export fit results to an Excel file with Parameters, Data, and Metadata sheets.
build_prosail_forward()
¶
Build the PROSPECT-D forward model using prosail library
resample_spectrum(meas_wl, meas_R, model_wl)
¶
Resample measured spectrum to model wavelengths
fit_prospect_d(wavelengths, reflectance, vis_weight=6.0, loss='soft_l1', f_scale=0.08, n_starts=6, stage1=True, do_calib=True, green_center=550.0, rededge_center=705.0, window_sigma=20.0, feature_boost=2.0, vis_min=380.0, vis_max=750.0, violet_center=410.0, violet_boost=2.0, rng_seed=42)
¶
Fit PROSPECT-D model to spectral reflectance data.
Returns dict with
- success: bool
- parameters: dict of fitted parameter values
- rmse: float
- r_squared: float
- fitted_spectrum: list of fitted reflectance values
- wavelengths: list of wavelengths for fitted spectrum
fit_prospect_model(request)
async
¶
Fit PROSPECT-D radiative transfer model to spectral reflectance data.
Input
- wavelengths: array of wavelength values (nm)
- reflectance: array of reflectance values (0-1 range)
- Various fitting options (vis_weight, n_starts, etc.)
Output
- Fitted biophysical parameters (N, Cab, Car, Cbrown, Cw, Cm, Ant)
- Optional calibration parameters (a_scale, b_offset)
- Fitted spectrum and fit statistics
triangulate_point_cloud(request, http_request)
async
¶
Triangulate a point cloud (Open3D). Returns a PHB1 binary frame.
fit_crown_endpoint(request, http_request)
async
¶
Fit crown shapes + metrics. Streams progress then a JSON tail (one entry per fitted crown), cancelable via the run-id token.
segment_ground_points(request, http_request)
async
¶
Classify a point cloud into ground (1) and plant (2) points using the
Cloth Simulation Filter. Returns per-point labels aligned to input order;
persisting the result onto an octree-backed cloud is done by
/api/cloud/session/{session_id}/segment_ground.
The CSF compute runs in a KILLABLE subprocess (see _run_killable) so the
panel's Cancel button can SIGKILL it mid-run; on client disconnect a cancelled
response is returned at once.
generate_dem(request, http_request)
async
¶
Generate a DEM from a flat cloud (inline points / source). Returns a PHB1 binary frame (heightmap mesh + regular grid).
export_dem_raster(request)
async
¶
Write a DEM grid to ESRI ASCII (.asc) or GeoTIFF (.tif); returns base64.
segment_wood_points(request, http_request)
async
¶
Classify a point cloud into wood (1) and leaf (2) points from geometry.
Returns per-point labels aligned to input order; persisting the result onto
an octree-backed cloud is done by
/api/cloud/session/{session_id}/segment_wood.
segment_trees(points, params=None, seeds=None)
¶
Assign each point a tree id (0 = unassigned, 1..N) via TreeIso.
With seeds, every TreeIso segment is reassigned to the id of its nearest
seed (each seed -> exactly one tree); otherwise TreeIso's own 1..N labels
are returned, aligned 1:1 to the input order.
segment_trees_points(request, http_request)
async
¶
Segment a multi-tree cloud into per-point tree instance ids via TreeIso.
Mirrors /api/segment/ground: inline points or a source descriptor in,
per-point integer labels out (0 = unassigned, 1..N = trees), full resolution
so labels align 1:1. Persisting onto an octree-backed cloud is done by
/api/cloud/session/{session_id}/segment_trees.
The TreeIso pipeline is CPU-bound and runs for tens of seconds on a large
tile, so it executes in a KILLABLE subprocess (_run_killable): other
/api/* requests stay responsive, and if the client disconnects (panel
closed, Cancel, fetch timeout) the worker is SIGKILLed and this returns at
once instead of holding the request open.
Labels-only by design: this interactive path returns predicted instance ids
and nothing else. If the source carries ground-truth fields (e.g. a
benchmark PLY's instance/semantic), they are NOT echoed back here — only
/apply carries source scalars through into the octree, and the eval
harness (scripts/eval_tree_segmentation.py) reads GT straight from the
file. There is no GT consumer on this endpoint.
helios_triangulate(request, http_request)
async
¶
Triangulate point cloud data using PyHelios spherical Delaunay triangulation.
Returns a PHB1 binary frame (see _bin_frame_bytes) so multi-million-triangle meshes transfer compactly and parse as zero-copy typed arrays. Keepalive chunks during the (long) computation keep WebKit's stall timeout at bay.
triangulate_check_spacing(request)
async
¶
Cross-check the auto-estimated Lmax against the actual in-grid point spacing.
An OPT-IN diagnostic (the renderer offers it as a button when the Otsu indicators aren't both High). Potentially expensive — a KD-tree over up to tens of millions of points — so it streams keepalive whitespace to survive WebKit's ~60s stall timeout, then yields the JSON verdict. Reuses HeliosTriangulationRequest so the renderer sends the exact scans + grid + lmax it triangulated with.
lad_compute(http_request)
async
¶
Compute per-voxel leaf area density via PyHelios.
Accepts either a JSON LADComputeRequest body (the default / fresh-triangulation path) or — when reusing a previously-run Helios triangulation — a PHB1 binary frame carrying the request fields in its header plus the mesh as raw buffers (see _decode_lad_request_frame). The binary path lets a 1M+ triangle mesh ride back to the backend compactly so it can be injected via setExternalTriangulation instead of being re-triangulated from scratch.
Either way the response streams PHP1 progress markers (see _bin_frame_streaming_response) ahead of the JSON result so the renderer shows a real per-stage progress bar and the keepalive survives WebKit's ~60s stall timeout. The renderer drains the markers and parses the JSON tail.
lad_snap_grid(request)
async
¶
Sample a DEM under each voxel column so the grid can be displaced to follow the ground. Returns the authoritative per-column offsets the UI both renders and feeds to the LAD inversion (HeliosGrid.column_offsets).
trajectory_parse(request)
async
¶
Parse a binary trajectory (currently SBET .sbet/.out) into the canonical PoseStream wire dict the renderer's poseStreamFromWire consumes. Text trajectories (.csv/.txt/.tsv/.traj) are parsed client-side and never hit this endpoint.
Binary parsing belongs server-side: it needs pyproj (Python-only) for the geographic->UTM projection, and the renderer only does IPC text reads.
scan_export_xml(request)
async
¶
Export scans to a Helios XML + per-scan ASCII bundle (base64 files).
lidar_scan(request, http_request)
async
¶
Ray-traced synthetic LiDAR scan. Returns a PHB1 binary frame (points + scalars per scanner can be millions of values).
cancel_run(run_id)
async
¶
Cancel an in-flight streaming op (synthetic scan / triangulation / LAD).
The streaming endpoints emit their run_id as the first PHP1 marker; the renderer POSTs it here to stop the work and free the C++/numpy memory without waiting on the (possibly huge) computation to finish. Idempotent: an unknown or already-finished run_id returns found=False rather than an error.
segment_ground(points, cloth_resolution=0.05, rigidness=3, class_threshold=0.02, iterations=500, slope_smooth=False, time_step=0.65, auto_class_threshold=False, meta=None)
¶
Classify each point as ground (1) or plant (2) via the Cloth Simulation Filter (Zhang et al. 2016).
CSF drapes an inverted cloth over the point cloud and labels points the cloth settles onto as ground. Defaults here are tuned for close-range plant scans on roughly-flat ground (cm-scale cloth resolution, high rigidness), NOT the airborne-LiDAR defaults the upstream docs assume.
Returns an int array of length len(points), aligned to input order, with values GROUND_CLASS_GROUND / GROUND_CLASS_PLANT.
With auto_class_threshold, class_threshold is ignored and derived from
the settled cloth instead (see _estimate_class_threshold). This costs no
extra simulation: CSF's class_threshold only feeds its final
point-to-cloth comparison, never the cloth physics — verified by running the
same cloud at 0.5 and 1.0 and getting bit-identical cloth_nodes.txt. So the
cloth is settled once, the threshold is read off it, and the points are
classified against the same cloth CSF would have used.
Pass a dict as meta to receive the threshold actually applied plus the
estimator's diagnostics.
Raises ImportError if the CSF extension is unavailable — the caller turns
that into a clean error response rather than a 500.
segment_wood(points, k_min=10, k_max=100, k_step=10, wood_bias=0.6, reg_k=20, reg_iters=3, min_speckle=0, branch_grow_sph=0.02, voxel_size=0.0, max_points=None, reflectance=None, reflectance_weight_max=0.4, method='geometric', backbone_support=0.0, warnings=None)
¶
Classify each point as wood (1) or leaf (2) from geometry (+ optional
reflectance assist), via one of two methods.
method="geometric" (the original) is purely point-wise. method="connectivity"
additionally roots a geodesic skeleton at the trunk base and recovers the woody
backbone — the set of points on continuous paths back to the base — which the
point-wise method can't see, so it recovers thin branches/twigs and prunes
geometrically-compact-but-disconnected leaf clumps. It REQUIRES ground removal
(the geodesic roots at the lowest points); if the base looks like residual
ground it appends a warning to warnings (a caller-supplied list) but proceeds,
and it falls back to geometry when the skeleton degenerates. See
_segment_wood_connectivity. backbone_support (0 = auto) tunes how much
subtree support a node needs to count as backbone (higher → only major scaffold).
Pipeline (classical / non-ML, runs on CPU in seconds-to-minutes):
1. Per-point local-PCA features at an eigen-entropy-optimal scale
(verticality, sphericity — Demantke 2011 / Weinmann 2015).
2. A wood saliency score = verticality + (1 − sphericity): wood is
vertical (trunk/branches) and locally COMPACT (low sphericity), while
foliage scatters the neighbourhood in 3D (high sphericity) and hangs at
varied non-vertical angles. OPTIONAL reflectance assist: when a
per-point reflectance scalar is supplied (and reflectance_weight_max
> 0), a 1-D GMM on the reflectance contributes a wood-probability term
to this score, weighted by how separable wood/leaf are in it — so it
helps high-contrast species (oak/beech: wood reads higher reflectance)
and is inert (weight ≈ 0) on low-contrast ones (almond/redbud) or when
reflectance is None. See _wood_blend_reflectance.
A 2-component 1-D Gaussian Mixture splits
the score; the higher-mean component is wood and wood_bias is its
posterior threshold (0.5 = argmax; higher → stricter / less wood; lower
→ more wood).
3. Branch recovery (branch_grow_sph > 0): the verticality-weighted seed
is high-precision but misses HORIZONTAL scaffold branches (low
verticality → low score) even though they're woody (compact/low
sphericity). Region-grow the wood label from the seed into CONNECTED
low-sphericity points (sphericity < branch_grow_sph) to recover them
(_wood_grow_branches). This is what makes thick crown branches read
as wood rather than leaf.
4. Optional speckle pruning (min_speckle > 1): flip tiny isolated wood
components back to leaf.
5. LeWoS-style graph regularisation (iterated k-NN majority vote).
Linearity is deliberately NOT used: branches are linear (cylinders) and so are needles / narrow leaves, so linearity cannot separate the two. This was validated across two benchmark families — real TLS trees (Weiser et al. heiDATA: oak/beech/maple/pine/spruce, where neighbourhood sphericity carries the signal, mean OA ≈ 0.85) and synthetic almond scans (narrow flat leaves, where verticality carries it, mean OA ≈ 0.80). The additive blend handles both; the one weak case is densely-scattered-leaf forms (e.g. the synthetic central-leader archetype) where only linearity/planarity separate — a trade accepted because production input is real TLS.
Very large clouds are handled automatically: above max_points
(default _WOOD_SEGMENT_MAX_POINTS ≈ 1.5M, env-overridable) the geometry
step runs on a voxel-downsampled subset and labels propagate back to full
resolution by nearest neighbour — the per-point k-NN feature extraction is
O(N·k_max) in memory and a multi-million-point cloud at full res can OOM the
machine. Set voxel_size > 0 to choose the downsample resolution explicitly
instead. Either way the returned labels are full-length.
Returns an int32 array length len(points), aligned to input order, with values WOOD_CLASS_WOOD / WOOD_CLASS_LEAF.
remove_statistical_outliers(points, nb_neighbors=20, std_ratio=2.0)
¶
Remove statistical outliers using k-nearest neighbors. Points with mean distance > std_ratio * global std are removed.
build_neighbor_graph(points, search_radius, max_neighbors=20)
¶
Build an undirected graph connecting neighboring points using a KD-tree.
Each point is linked to its up-to-max_neighbors nearest neighbours that
lie within search_radius. Implemented as a single batched k-NN query
(cKDTree, parallel workers) plus a radius mask — NOT a per-point
query_ball_point Python loop, which on a multi-million-point TLS cloud
materialises a Python list per point and runs for minutes. The batched
query mirrors the wood-segmentation neighbour pass (_wood_local_pca_features).
Because cKDTree returns neighbours sorted by increasing distance, taking the
first max_neighbors after dropping self and applying the radius mask is
exactly the "keep the closest within radius" semantics of the old code — a
behaviour-preserving rewrite, not a quality change.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
points
|
ndarray
|
Nx3 array of point coordinates |
required |
search_radius
|
float
|
Maximum distance to consider points as neighbors |
required |
max_neighbors
|
int
|
Maximum number of neighbors per point |
20
|
Returns:
| Type | Description |
|---|---|
dict
|
dict with 'neighbors' (per-point int32 ndarray of neighbour indices, so |
dict
|
|
select_root_set(points, threshold=0.02)
¶
Select root points near the lowest point in z-direction.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
points
|
ndarray
|
Nx3 array of point coordinates |
required |
threshold
|
float
|
Height threshold τ (meters) above lowest point |
0.02
|
Returns:
| Type | Description |
|---|---|
list
|
List of indices of root points |
bfs_label_points(neighbors, root_indices, n_points)
¶
Label all points with their BFS distance from root set.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
neighbors
|
list
|
List of neighbor indices for each point |
required |
root_indices
|
list
|
Indices of root points (labeled as 1) |
required |
n_points
|
int
|
Total number of points |
required |
Returns:
| Type | Description |
|---|---|
ndarray
|
Array of labels (distance from root, -1 for unreachable points) |
quantize_labels(labels, num_levels=60, use_nonlinear=True)
¶
Quantize BFS labels into discrete intervals.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
labels
|
ndarray
|
Raw BFS labels |
required |
num_levels
|
int
|
Number of quantization levels (Q-S in paper) |
60
|
use_nonlinear
|
bool
|
If True, use sqrt scaling for better branch detail |
True
|
Returns:
| Type | Description |
|---|---|
ndarray
|
Quantized labels (0 to num_levels) |
cluster_blocks(quantized_labels, neighbors)
¶
Cluster connected points with the same quantized label into blocks. Uses DFS to find connected components within each quantization level.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
quantized_labels
|
ndarray
|
Quantized BFS labels |
required |
neighbors
|
list
|
List of neighbor indices for each point |
required |
Returns:
| Type | Description |
|---|---|
tuple
|
(block_assignments, block_info) where: |
tuple
|
|
tuple
|
|
find_block_connectivity(block_assignments, block_info, neighbors)
¶
Find which blocks are connected based on edge connectivity in the graph.
Returns:
| Type | Description |
|---|---|
dict
|
Dict mapping block_id -> set of connected block_ids |
filter_blocks(block_info, block_assignments, connectivity, threshold_filter=30, use_proportion_filter=True, proportion_threshold=0.1)
¶
Filter out small blocks (noise, leaves, small twigs).
Filters: 1. Threshold filter: Remove blocks with fewer than threshold_filter points 2. Proportion filter: Remove blocks whose size is too small relative to parent
Returns:
| Type | Description |
|---|---|
tuple
|
(filtered_block_info, filtered_assignments, filtered_connectivity) |
compute_skeleton_nodes(points, block_info)
¶
Compute skeleton nodes as centroids of each block.
Returns:
| Type | Description |
|---|---|
dict
|
Dict mapping block_id -> centroid [x, y, z] |
build_skeleton_tree(block_info, connectivity, skeleton_nodes)
¶
Build skeleton edges based on block connectivity. Uses a tree structure rooted at the lowest-level block.
Returns:
| Type | Description |
|---|---|
tuple
|
(edges, edge_lengths) where edges are pairs of block IDs |
laplace_smooth_skeleton(skeleton_nodes, edges, iterations=2)
¶
Smooth skeleton using Laplace smoothing. Each non-bifurcation node is moved to average of itself and neighbors.
NEW_A = (A + B + C) / 3 where B is parent, C is child
count_branch_points(edges)
¶
Count nodes with more than 2 connections (branch points).
calculate_skeleton_length_from_edges(skeleton_nodes, edges)
¶
Calculate total skeleton length by summing all edge lengths.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
skeleton_nodes
|
dict
|
dict mapping block_id to [x, y, z] coordinates |
required |
edges
|
list
|
list of (parent_id, child_id) tuples |
required |
Returns:
| Type | Description |
|---|---|
float
|
Total length of all edges |
calculate_branch_orders(skeleton_nodes, edges, block_info)
¶
Calculate Strahler branch order for each node in the skeleton.
Branch order (Strahler number) classification: - Order 1: terminal branches (tips/leaves) - When two branches of same order n meet, parent gets order n+1 - When branches of different orders meet, parent gets the higher order
Uses iterative post-order traversal to avoid Python recursion limits.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
skeleton_nodes
|
dict
|
dict mapping block_id to [x, y, z] coordinates |
required |
edges
|
list
|
list of [parent_id, child_id] lists |
required |
block_info
|
list
|
list of block dictionaries with 'id' and 'level' keys |
required |
Returns:
| Type | Description |
|---|---|
dict
|
dict mapping block_id to branch order (int) |
order_skeleton_points(skeleton_nodes, edges, block_info)
¶
Order skeleton points from root to tips using BFS from lowest-level node. Returns list of [x, y, z] coordinates in order.
order_skeleton_points_with_mapping(skeleton_nodes, edges, block_info)
¶
Convert skeleton nodes dict to ordered list with index mapping. Returns tuple of (list of [x, y, z] coordinates, dict mapping block_id to array index).
Important: We must include ALL nodes and create a complete mapping so that edge indices can be correctly converted to array positions.
fit_circle_ransac(points_2d, n_iterations=100, threshold_ratio=0.02, min_inliers_ratio=0.5)
¶
Robust circle fitting using RANSAC.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
points_2d
|
ndarray
|
Nx2 array of 2D points |
required |
n_iterations
|
int
|
Number of RANSAC iterations |
100
|
threshold_ratio
|
float
|
Inlier threshold as fraction of estimated radius |
0.02
|
min_inliers_ratio
|
float
|
Minimum fraction of points that must be inliers |
0.5
|
Returns:
| Type | Description |
|---|---|
dict
|
dict with center, radius, inliers, rmse, confidence |
fit_circle_through_3_points(points)
¶
Fit a circle through exactly 3 points. Returns (center, radius) or (None, None) if collinear.
fit_circle_least_squares(points_2d, center_init=None, radius_init=None)
¶
Fit circle using least squares (for refinement).
adaptive_slice_extraction(points, principal_direction, num_slices=None, slice_thickness=None, min_points_per_slice=10, use_local_pca=True, fit_circles=True, use_ransac=True, ransac_iterations=100, ransac_threshold=0.02)
¶
Extract skeleton using adaptive slicing along principal direction. Uses local PCA to orient each slice perpendicular to local stem direction.
Returns:
| Name | Type | Description |
|---|---|---|
slices |
tuple
|
List of slice information dicts |
slice_thickness |
tuple
|
The thickness used |
remove_outlier_skeleton_points(slices, threshold_factor=2.5)
¶
Remove skeleton points that deviate significantly from the local trajectory. Uses distance from a smoothed trajectory as criterion.
smooth_skeleton_robust(skeleton_points, smoothing_factor=0.0)
¶
Smooth skeleton using spline interpolation with curvature constraints.
smooth_diameters(diameters, window_size=5)
¶
Smooth diameter profile using median filter. Returns a list of floats with NO None values (interpolates missing values). Returns None if all diameters are None.
calculate_skeleton_length(skeleton_points)
¶
Calculate total length of skeleton by summing segment lengths.
compute_skeleton(points, params)
¶
BFS graph-based tree skeleton extraction (Li et al. 2017).
Pure compute: takes the already-read (and max-points-guarded) points plus a
plain dict of the SkeletonRequest tuning fields, and returns a plain dict of
the SkeletonResponse fields (so it round-trips through JSON unchanged). The
/api/skeleton/extract endpoint and the killable subprocess worker
(seg_worker.py) both call this — the worker because it runs in its own
process so Cancel can SIGKILL it. Heavy/hang-prone (outlier removal, KD-tree,
BFS, clustering) and CPU-bound; the early-exit guards return
{"success": False, ...} dicts rather than raising.
Algorithm stages: 1. Pre-processing: Statistical outlier removal 2. Build KD-tree neighbor graph 3. Select root points near base (lowest z-coordinate) 4. BFS label all points with distance from root 5. Nonlinear quantization using sqrt scaling 6. Cluster connected points with same quantized label into blocks 7. Filter blocks (threshold filter + proportion filter) 8. Compute skeleton nodes from block centroids 9. Build skeleton edges based on block connectivity 10. Apply Laplace smoothing
extract_stem_skeleton(request, http_request)
async
¶
Extract a tree skeleton via the BFS graph algorithm (Li et al. 2017).
Reads (and downsamples) the points, applies the max-points guard, then runs
the heavy compute_skeleton pipeline in a KILLABLE subprocess so the panel's
Cancel button can SIGKILL it mid-run (see _run_killable). On client
disconnect (Cancel / fetch timeout) the worker is killed and a cancelled
response is returned at once.
build_qsm(request)
async
¶
Build a true QSM, streaming per-stage progress as PHP1 markers ahead of the JSON result (mirrors triangulation / backfill). The renderer's fetchJsonWithProgress drains the markers and parses the trailing JSON, which is the same QSMBuildResponse shape _do_qsm_build returns.
detect_qsm_phyllotaxis(request)
async
¶
Auto-detect the phyllotactic angle from the QSM's branching geometry.
Branches follow the phyllotaxis of leaves (modulo unbroken buds), so the azimuths of child shoots around each parent reveal the angle. Returns a canonical angle + pattern + leaves-per-node + a confidence; used to pre-fill the Add Leaves modal.
add_qsm_leaves(request)
async
¶
Place leaves on the terminal shoots of a QSM and return a textured mesh.
get_qsm_leaf_textures()
async
¶
List the curated built-in leaf textures available for QSM leaf placement.
adjust_qsm_leaf_angles(request)
async
¶
Adjust a QSM's leaf angles to match a measured per-cell distribution.
get_available_plant_models()
async
¶
Get list of available plant models from pyhelios library
create_plant_session(request)
async
¶
Create a new plant session for incremental growth simulation. The session keeps the pyhelios context alive for efficient time stepping.
advance_plant_session(session_id, request)
async
¶
Advance time for a plant session and return updated geometry.
get_plant_session_status(session_id)
async
¶
Get the current status of a plant session.
delete_plant_session(session_id)
async
¶
Delete a plant session and free resources.
list_plant_sessions()
async
¶
List all active plant sessions.
parse_plant_morph_parameters(request)
async
¶
Parse a plant structure XML string into editable parameters.
morph_plant(request)
async
¶
Rebuild a plant from modified structure XML.
generate_plant_model(request)
async
¶
Generate a procedural plant model using pyhelios PlantArchitecture.
Uses direct primitive extraction from pyhelios Context API to get valid geometry. Textured organs carry Helios's own per-vertex UV coordinates (V-flipped for three.js), so leaf textures sample the correct cell of the leaf atlas.
generate_plant_canopy(request)
async
¶
Generate a canopy of regularly spaced plants using pyhelios PlantArchitecture.
Builds a grid of count_x x count_y plants of the same species, spaced by
spacing_x / spacing_y meters and centered on the canopy center. All plants
are added to a single context and returned as one merged mesh, matching the
single-plant /api/plant/generate response shape so the renderer is identical.
generate_plant_stream(request, http_request)
async
¶
Generate a single plant or a canopy with Server-Sent Events progress.
Emits
event: run_id data: {"run_id": "..."} (first, so the client can cancel)
event: progress data: {"progress": 0.0-1.0, "message": "..."}
event: result data:
Progress maps the C++ growth phase to 0–0.6 (via setProgressCallback), geometry extraction to 0.6–0.95, and JSON serialization to the final 1.0. Single-plant builds create a retained session (echoed as session_id) so the age slider keeps working; canopies are stateless.
Cancellation: the run_id ride the first event; a client POSTing /api/cancel/{run_id} (or disconnecting) flips a shared ctypes flag that the C++ canopy/advanceTime loops poll, so a long build stops between plants/timesteps and its Context/PlantArchitecture are torn down promptly.
import_textured_mesh(request)
async
¶
Parse an OBJ (+ MTL + texture images) from disk into textured geometry.
export_point_cloud_las(request)
async
¶
Export a point cloud.
Flat clouds export LAS/LAZ via laspy. Octree-backed clouds send a source
descriptor and may export any of LAS/LAZ/XYZ/TXT/CSV/PLY/OBJ — the backend
reads the source file (the renderer has no positions to format). Returns
base64-encoded file data that can be downloaded on the frontend.
import_point_cloud_las(file=File(...))
async
¶
Import a LAS or LAZ file (multipart upload) and stream back a packed binary
(PHX1) point-cloud response — the SAME format as import_by_path, decoded
straight into Float32Arrays by the renderer.
This is the no-disk-path fallback (a File blob with no real path can't use
import_by_path). It previously returned points.tolist() as a JSON body,
which on a large LAZ trips V8's ~512 MB max-string ceiling and triples peak
memory; the binary stream avoids both. laspy + lazrs handle LAZ; reading is
shared with the path-based loader via _load_las_arrays.
reap_seg_workers()
¶
Kill any still-running segmentation workers. Called at backend shutdown so a wedged worker never outlives the server. Workers run in their OWN process groups (posix_spawn setpgroup=0), so they are NOT auto-reaped when the parent dies — this is what cleans them up on a graceful exit.
import_pointcloud_by_path(request)
async
¶
Parse a point-cloud file from disk and stream back a packed binary representation. Dispatches by extension:
.xyz/.txt/.csv/.pts/.asc→ pandas + optional Heliosascii_formathint..ply/.pcd→ open3d (handles ASCII and binary).
preview_pointcloud(request)
async
¶
Cheaply inspect a point-cloud file for the import wizard.
Reads only enough of the file to show the wizard what was auto-detected and
a handful of sample rows. Never 500s on a parse problem — returns a 200 with
a warning and best-effort columns so the wizard can still offer
"import with auto-detect".
create_cloud_session(request)
async
¶
Load a source cloud FULLY into an in-RAM session (the complete source of
truth — positions + colours + intensity + every scalar extra-dim), build its
first octree, and return {session_id, ...octree metadata}. This is the ONLY
point the source FILE is read; every later edit/bake/op works on the arrays.
The source is normalised to a LAS once via _source_to_las (handling
XYZ/PLY/PCD/LAS/LAZ + the wizard column_plan uniformly), that LAS is read
into the session arrays AND fed to PotreeConverter for the first octree.
backfill_cloud_misses(session_id, request)
async
¶
Explicitly recover sky/miss points for a session and persist them.
LAD needs miss points (beams that returned nothing) for the Beer's-law
transmission denominator. Some formats retain them (E57 / structured PLY);
others don't but carry the data to RECONSTRUCT them — a per-hit timestamp
and/or scan-grid row/column indices. This endpoint builds an ephemeral
PyHelios cloud from the session's surviving points, runs gapfillMisses()
(which auto-selects the row/column or timestamp path in C++), extracts the
synthesised misses, and stores them in a lightweight per-session buffer
(sess.backfilled_misses) — leaving the hit arrays untouched.
Session resolve + array assembly + eligibility run up front (so a bad request
is a clean 404/400); the heavy build/gapfill/extract streams PHP1 progress
markers ahead of the JSON tail (see _bin_frame_streaming_response) so the
renderer shows a per-stage progress bar. The JSON tail is
{backfilled, miss_count, has_misses, scan_origin, already_had_misses} on
success, or carries error when reconstruction failed.
delete_cloud_region(session_id, request)
async
¶
Set the per-point deleted mask for points inside region. No rebuild.
reset_cloud_edits(session_id, request)
async
¶
Restore the deleted mask to an earlier snapshot (undo).
bake_cloud_session(session_id)
async
¶
Permanently apply deletions by rebuilding the octree FROM THE IN-RAM
ARRAYS — the survivors (positions[~deleted] + colours + intensity + every
scalar extra-dim) are written to a LAS via _session_to_las and fed to
PotreeConverter. The source file is NOT read. Then the in-RAM arrays are
compacted to the survivors and the mask cleared. Returns the new octree
metadata. The deliberately-slow step (the PotreeConverter run).
No deletions → returns the current octree without rebuilding.
session_split(session_id, request)
async
¶
Keep the filter-passing points on this session; move the excluded points to a NEW leftover session. Rebuilds both octrees from arrays. Returns {kept: {...octree}, leftover: {session_id, ...octree}} (leftover null if empty). No file read.
session_extract(session_id, request)
async
¶
Create a NEW child session from the filter-selected points (parent untouched). Returns {session_id, ...octree} or null if the selection is empty. No source file read.
session_extract_by_column(session_id, request, http_request)
async
¶
Fan a categorical column out into one child session per distinct value (parent untouched). Returns {session_id, children: [{value, ...octree}]} ordered by value. Empty selections are skipped. No source file read. Streams PHP1 progress markers ahead of the JSON result (cancellable pill) — a per-tree split of a large plot is a minute-scale operation and the caller has no other signal that it's running.
All subsets are sliced up front under a single _cloud_session_lock
acquisition (one argsort + a per-child index gather); the expensive per-child
octree builds then run CONCURRENTLY in a bounded thread pool. Distinct values
hash to distinct octree cache keys, so _octree_build_lock never serializes
them and the global session lock is already released before PotreeConverter
runs (see _session_rebuild).
session_duplicate(session_id)
async
¶
Copy a session's SURVIVING points into a NEW independent session (parent
untouched) and build its octree. This is the keep-everything degenerate case
of extract: a pure array copy via _session_subset — NO source file read,
so every wizard customization (column plan, custom labels, categorical
slugs, dropped/renamed extras) is preserved on the copy. The new session is
fully independent: later edits to either side don't affect the other.
Returns {session_id, duplicate: {session_id, point_count, ...octree}}.
session_merge(request)
async
¶
Concatenate the surviving points of >=2 sessions into one new session and build its octree (+ a projected-miss octree when any input carried misses). Reconciles differing global shifts and unions scalar extra-dim columns. No source file read. Returns {merged: {session_id, point_count, ...octree}}.
session_segment_ground(session_id, request, http_request)
async
¶
CSF on the in-RAM survivors → append ground_class → rebuild octree from
the arrays. No source file read.
The CSF compute runs in a KILLABLE subprocess (see _run_killable) so the
panel's Cancel button can SIGKILL it. The column write + octree rebuild run in
the parent AFTER the compute returns, so a cancel during the long compute
leaves the session pristine.
session_generate_dem(session_id, request, http_request)
async
¶
DEM from a session's in-RAM survivors (ground-aware). Returns a PHB1 frame
(heightmap mesh + grid). When add_height_column, also appends a
height_above_ground scalar and rebuilds the octree (cache_id in meta).
session_segment_wood(session_id, request, http_request)
async
¶
Wood/leaf segmentation on the in-RAM survivors → append wood_class →
rebuild octree from the arrays. No source file read.
The compute runs in a KILLABLE subprocess (see _run_killable) so the panel's
Cancel button can SIGKILL it. The column write + octree rebuild run in the
parent AFTER the compute returns, so a cancel during the long compute leaves
the session pristine.
session_segment_trees(session_id, request, http_request)
async
¶
TreeIso on the in-RAM survivors → append tree_instance → rebuild octree
from the arrays. No source file read.
The TreeIso pipeline runs in a KILLABLE subprocess (see _run_killable) so the
panel's Cancel button can SIGKILL it mid-run; the column write + octree rebuild
run in the parent AFTER the compute returns, so a cancel during the long
compute leaves the session pristine.
If the cloud carries a ground_class column (from a prior ground
segmentation that was labeled but not removed), the ground points are
excluded from TreeIso and assigned tree id 0 (unassigned) — TreeIso only
sees the plant points, so ground never gets clustered into a "tree".
session_transform(session_id, request)
async
¶
Bake a rigid 4x4 transform into the session's in-RAM geometry and rebuild
the octree. The matrix acts in WORLD coordinates; the session stores points
with world_shift subtracted, so we conjugate by the shift:
stored_new = R·(stored + shift) + t − shift. A permanent (non-undoable)
geometry change, like a filter commit.
session_filter(session_id, request)
async
¶
Delete the points a spatial+scalar filter excludes, on the in-RAM arrays.
delete_cloud_session(session_id)
async
¶
Free a cloud session's in-RAM arrays.
compute_c2m_distance(request, http_request)
async
¶
Compute Cloud-to-Mesh (C2M) distance statistics.
Uses Open3D's RaycastingScene for efficient point-to-mesh distance computation. Returns comprehensive statistics about how well the mesh fits the point cloud. Streams PHP1 progress markers (see _bin_frame_streaming_response) ahead of the JSON result so the renderer shows a cancellable progress pill.
run_icp_until_convergence(source_pcd, target_pcd, max_corr_dist, init_transform, max_iterations=100, rmse_threshold=1e-06, progress=None)
¶
Run ICP iteratively until RMSE plateaus (convergence). Returns the final transformation and metrics.
progress, when supplied, is a _ProgressReporter: after each 20-iteration
batch we poll it for cancellation (raising ScanCancelled between batches — a
running C++ batch can't be interrupted mid-call) and report a monotonic
0.15→0.95 fraction so the renderer's pill advances per batch. A no-op when
progress is None (e.g. direct unit-test callers).
icp_register_mesh_to_cloud(request, http_request)
async
¶
Perform ICP (Iterative Closest Point) registration to align a mesh to a point cloud.
The point cloud is the TARGET (stays fixed), the mesh is the SOURCE (will be transformed). Pre-aligns by moving source center to target center, then runs ICP until convergence. Streams PHP1 progress markers ahead of the JSON result (cancellable pill).
icp_register_cloud_to_cloud(request, http_request)
async
¶
Perform ICP (Iterative Closest Point) registration to align one point cloud to another.
The target cloud stays fixed, the source cloud will be transformed. Pre-aligns by moving source center to target center, then runs ICP until convergence. Streams PHP1 progress markers ahead of the JSON result (cancellable pill).
icp_register_mesh_to_mesh(request, http_request)
async
¶
Perform ICP (Iterative Closest Point) registration to align one mesh to another.
The target mesh stays fixed, the source mesh will be transformed. Pre-aligns by moving source center to target center, then runs ICP until convergence. Streams PHP1 progress markers ahead of the JSON result (cancellable pill).