Skip to content

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 block; it bounds the triangulation region. center/size are in world coordinates (the voxel box's transform); nx/ny/nz are its per-axis cell subdivisions.

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": spec is required and applies to every voxel (one scalar).
  • spatial="profile": profile is 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 block on scan XML export, so a bundle like sphere.xml round-trips its grid. center/size are world extents; nx/ny/nz the per-axis cell counts; rotation the azimuth about +z in degrees.

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 string (e.g. 'x y z r255 g255 b255 reflectance') and applies only to XYZ-family extensions; PLY/PCD ignore it because their column layout is in-file. If omitted for an XYZ-family file, columns are sniffed from the first non-blank, non-comment row. 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 values is set, keep points whose value rounds to one of the listed class ids (an OR within the field — min/max ignored). Used by the filter UI's class-checkbox path for integer-valued labels like ground_class / tree_instance, where a value such as 2 means 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

for j in neighbors[i] still yields ints) and 'kdtree'

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
  • block_assignments: Array mapping each point to its block ID (-1 if not assigned)
tuple
  • block_info: List of dicts with block metadata

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: event: cancelled data: {} (build aborted; memory freed) event: error data: {"detail": "..."}

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 Helios ascii_format hint.
  • .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).