API reference¶
Every public function and class, generated from the source docstrings. The
same names are importable directly from the top-level geoai3d package.
GEOAI_3D: geospatial-first AI workflows for 3D data.
This package is at a pre-alpha stage. No public API is stable yet; see the roadmap in the README for what is planned and in what order.
The first building blocks are the columnar :class:PointCloud representation
and the :class:Provenance lineage record it carries.
AccuracyReport
dataclass
¶
A classification accuracy assessment.
Attributes:
| Name | Type | Description |
|---|---|---|
labels |
tuple[int, ...]
|
The class labels, in the row/column order of |
confusion |
NDArray[Any]
|
The confusion matrix, rows = truth, columns = prediction. |
overall_accuracy |
float
|
Fraction of points classified correctly. |
per_class |
dict[int, dict[str, float]]
|
Per-label |
mean_iou |
float
|
Mean of the per-class IoU values. |
macro_f1 |
float
|
Mean of the per-class F1 values. |
Source code in src/geoai3d/metrics.py
22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | |
Classifier
dataclass
¶
A trained classifier and the feature columns it expects.
Attributes:
| Name | Type | Description |
|---|---|---|
estimator |
Any
|
The fitted scikit-learn estimator. |
feature_names |
tuple[str, ...]
|
The attribute names used as features, in order. |
Source code in src/geoai3d/classify.py
40 41 42 43 44 45 46 47 48 49 50 | |
Plane
dataclass
¶
A fitted plane a*x + b*y + c*z + d = 0 and its inliers.
Attributes:
| Name | Type | Description |
|---|---|---|
normal |
tuple[float, float, float]
|
The unit normal |
offset |
float
|
The plane offset |
inliers |
NDArray[Any]
|
Boolean mask over the fitted cloud, true for inlier points. |
Source code in src/geoai3d/primitives.py
29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 | |
signed_distance ¶
signed_distance(points: NDArray[Any]) -> NDArray[Any]
Return the signed perpendicular distance of points to the plane.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
points
|
NDArray[Any]
|
An |
required |
Returns:
| Type | Description |
|---|---|
NDArray[Any]
|
An |
Source code in src/geoai3d/primitives.py
48 49 50 51 52 53 54 55 56 57 58 | |
PointCloud ¶
A georeferenced 3D point cloud stored column by column.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
xyz
|
NDArray[Any]
|
Point coordinates as an array of shape |
required |
attributes
|
dict[str, NDArray[Any]] | None
|
Optional per-point attribute arrays keyed by name. Each
array must be one-dimensional with length |
None
|
crs
|
object | None
|
Coordinate reference system the coordinates are expressed in.
Optional at construction; the input/output and transform routines
added in later stages require it and raise if it is missing. This
slot will hold a |
None
|
provenance
|
Provenance | None
|
Lineage record describing how this cloud was produced. |
None
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Example
import numpy as np from geoai3d import PointCloud xyz = np.array([[0.0, 0.0, 0.0], [1.0, 0.0, 0.0]]) cloud = PointCloud(xyz, attributes={"intensity": np.array([10, 20])}) len(cloud) 2 cloud.attribute_names ['intensity']
Source code in src/geoai3d/core/pointcloud.py
28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 | |
attribute_names
property
¶
attribute_names: list[str]
Return the attribute names, in insertion order.
bounds
property
¶
bounds: tuple[float, float, float, float, float, float]
Return axis-aligned bounds (minx, miny, minz, maxx, maxy, maxz).
Raises:
| Type | Description |
|---|---|
ValueError
|
If the cloud is empty. |
__getitem__ ¶
__getitem__(selector: slice | NDArray[Any]) -> PointCloud
Return a new cloud containing only the selected points.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
selector
|
slice | NDArray[Any]
|
A slice, a boolean mask of length |
required |
Returns:
| Type | Description |
|---|---|
PointCloud
|
A new :class: |
PointCloud
|
filtered the same way, and the CRS and provenance carried through. |
Example
import numpy as np from geoai3d import PointCloud cloud = PointCloud(np.arange(9.0).reshape(3, 3)) cloud[np.array([True, False, True])].xyz.shape (2, 3)
Source code in src/geoai3d/core/pointcloud.py
212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 | |
__init__ ¶
__init__(xyz: NDArray[Any], attributes: dict[str, NDArray[Any]] | None = None, crs: object | None = None, provenance: Provenance | None = None) -> None
Validate the inputs and store them in columnar form.
The crs argument is coerced to a :class:pyproj.CRS so that
:attr:crs always returns a resolved reference system (or None),
however the cloud was built.
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Source code in src/geoai3d/core/pointcloud.py
59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 | |
__len__ ¶
__len__() -> int
Return the number of points in the cloud.
Source code in src/geoai3d/core/pointcloud.py
208 209 210 | |
__repr__ ¶
__repr__() -> str
Return a concise developer-facing representation.
Source code in src/geoai3d/core/pointcloud.py
240 241 242 243 244 245 | |
attribute ¶
attribute(name: str) -> NDArray[Any]
Return the attribute array stored under name.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
Attribute name. |
required |
Returns:
| Type | Description |
|---|---|
NDArray[Any]
|
The one-dimensional attribute array. |
Raises:
| Type | Description |
|---|---|
KeyError
|
If no attribute with that name exists. |
Source code in src/geoai3d/core/pointcloud.py
151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 | |
with_attribute ¶
with_attribute(name: str, values: NDArray[Any]) -> PointCloud
Return a new cloud with an attribute column added or replaced.
The coordinate array is shared with this cloud rather than copied, so attaching a computed feature to a large cloud is inexpensive.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
Attribute name to add or replace. |
required |
values
|
NDArray[Any]
|
One-dimensional array of length |
required |
Returns:
| Type | Description |
|---|---|
PointCloud
|
A new :class: |
PointCloud
|
cloud is left unchanged. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Example
import numpy as np from geoai3d import PointCloud cloud = PointCloud(np.zeros((3, 3))) planar = cloud.with_attribute("planarity", np.ones(3)) planar.attribute("planarity").tolist() [1.0, 1.0, 1.0] cloud.attribute_names []
Source code in src/geoai3d/core/pointcloud.py
171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 | |
ProcessStep
dataclass
¶
A single recorded step in a data product's processing history.
Attributes:
| Name | Type | Description |
|---|---|---|
description |
str
|
Human-readable summary of what the step did, for example
|
parameters |
dict[str, Any]
|
The parameters that controlled the step, as a plain mapping
of names to values, for example |
software |
str
|
Name and version of the software that ran the step, for
example |
timestamp |
datetime
|
When the step ran, as a timezone-aware UTC datetime. |
Source code in src/geoai3d/core/provenance.py
30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 | |
from_dict
classmethod
¶
from_dict(data: dict[str, Any]) -> ProcessStep
Rebuild a :class:ProcessStep from :meth:to_dict output.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
dict[str, Any]
|
A mapping as produced by :meth: |
required |
Returns:
| Type | Description |
|---|---|
ProcessStep
|
The reconstructed :class: |
Source code in src/geoai3d/core/provenance.py
63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 | |
to_dict ¶
to_dict() -> dict[str, Any]
Return a JSON-serialisable representation of this step.
Returns:
| Type | Description |
|---|---|
dict[str, Any]
|
A mapping with the description, parameters, software, and an ISO |
dict[str, Any]
|
8601 timestamp string. |
Source code in src/geoai3d/core/provenance.py
49 50 51 52 53 54 55 56 57 58 59 60 61 | |
Provenance
dataclass
¶
An ordered lineage record for a data product.
A :class:Provenance bundles an optional description of the original data
source with an ordered list of :class:ProcessStep entries, oldest first.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
source
|
str | None
|
Description of the original input, for example a file name or a
dataset DOI. |
None
|
steps
|
list[ProcessStep]
|
The processing steps applied so far, oldest first. |
list()
|
Example
prov = Provenance(source="scan.laz") _ = prov.add_step("voxel subsampling", {"voxel_size": 0.5}) len(prov.steps) 1
Source code in src/geoai3d/core/provenance.py
81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 | |
add_step ¶
add_step(description: str, parameters: dict[str, Any] | None = None, software: str | None = None) -> ProcessStep
Append a processing step to the lineage and return it.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
description
|
str
|
Human-readable summary of the step. |
required |
parameters
|
dict[str, Any] | None
|
Parameters that controlled the step. A copy is stored, so later mutation of the passed mapping does not affect the record. Defaults to an empty mapping. |
None
|
software
|
str | None
|
Software name and version. Defaults to the running
|
None
|
Returns:
| Name | Type | Description |
|---|---|---|
The |
ProcessStep
|
class: |
Example
prov = Provenance() step = prov.add_step("read LAS", {"path": "scan.laz"}) step.description 'read LAS'
Source code in src/geoai3d/core/provenance.py
103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 | |
copy ¶
copy() -> Provenance
Return an independent copy of this lineage record.
Returns:
| Type | Description |
|---|---|
Provenance
|
A new :class: |
Provenance
|
affecting the original. Individual :class: |
Provenance
|
immutable and therefore safe to share. |
Source code in src/geoai3d/core/provenance.py
136 137 138 139 140 141 142 143 144 | |
from_dict
classmethod
¶
from_dict(data: dict[str, Any]) -> Provenance
Rebuild a :class:Provenance from :meth:to_dict output.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
dict[str, Any]
|
A mapping as produced by :meth: |
required |
Returns:
| Type | Description |
|---|---|
Provenance
|
The reconstructed :class: |
Source code in src/geoai3d/core/provenance.py
158 159 160 161 162 163 164 165 166 167 168 169 | |
to_dict ¶
to_dict() -> dict[str, Any]
Return a JSON-serialisable representation of this lineage record.
Returns:
| Type | Description |
|---|---|
dict[str, Any]
|
A mapping with the source and a list of serialised steps, suitable |
dict[str, Any]
|
for storing in file metadata or a STAC item. |
Source code in src/geoai3d/core/provenance.py
146 147 148 149 150 151 152 153 154 155 156 | |
Raster ¶
A 2D georeferenced raster: values with an affine transform and CRS.
Source code in src/geoai3d/core/raster.py
35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 | |
bounds
property
¶
bounds: tuple[float, float, float, float]
Return axis-aligned bounds (min_x, min_y, max_x, max_y).
resolution
property
¶
resolution: tuple[float, float]
Return the (x, y) cell size in CRS units (both positive).
__init__ ¶
__init__(data: NDArray[Any], transform: tuple[float, ...], crs: object, nodata: float = float('nan')) -> None
Validate and store the grid, transform, CRS, and nodata value.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
NDArray[Any]
|
A 2D array of cell values, shape |
required |
transform
|
tuple[float, ...]
|
The affine transform as a 6-tuple |
required |
crs
|
object
|
The coordinate reference system (EPSG code, WKT, or
|
required |
nodata
|
float
|
Value marking empty cells. Defaults to |
float('nan')
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Source code in src/geoai3d/core/raster.py
38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 | |
__repr__ ¶
__repr__() -> str
Return a concise, informative representation.
Source code in src/geoai3d/core/raster.py
138 139 140 141 142 143 144 145 146 | |
build_kdtree ¶
build_kdtree(cloud: PointCloud) -> cKDTree
Build a KD-tree over a cloud's coordinates.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
cloud
|
PointCloud
|
The cloud to index. |
required |
Returns:
| Type | Description |
|---|---|
cKDTree
|
A |
Example
import numpy as np from geoai3d import PointCloud, build_kdtree cloud = PointCloud(np.arange(300.0).reshape(100, 3), crs=28992) tree = build_kdtree(cloud) distances, indices = tree.query([0.0, 0.0, 0.0], k=3) indices.shape (3,)
Source code in src/geoai3d/index.py
23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | |
confusion_matrix ¶
confusion_matrix(truth: NDArray[Any], prediction: NDArray[Any], labels: NDArray[Any] | list[int] | tuple[int, ...] | None = None) -> tuple[NDArray[Any], tuple[int, ...]]
Build a confusion matrix (rows = truth, columns = prediction).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
truth
|
NDArray[Any]
|
True integer labels, one per point. |
required |
prediction
|
NDArray[Any]
|
Predicted integer labels, one per point. |
required |
labels
|
NDArray[Any] | list[int] | tuple[int, ...] | None
|
The labels to include, in order. If omitted, the sorted union of
the values present is used. Points whose truth or prediction falls
outside |
None
|
Returns:
| Type | Description |
|---|---|
NDArray[Any]
|
The |
tuple[int, ...]
|
its row and column order. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Source code in src/geoai3d/metrics.py
44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 | |
connected_components ¶
connected_components(cloud: PointCloud, *, distance: float, min_size: int = 1, output_attribute: str = 'component') -> PointCloud
Label points into connected components by a distance threshold.
Two points are connected if they lie within distance of each other, and
each connected group gets a distinct integer label. Components with fewer
than min_size points are labelled -1 (noise); the rest are numbered
0, 1, 2, ... in order of first appearance.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
cloud
|
PointCloud
|
The cloud to label. |
required |
distance
|
float
|
Maximum distance, in CRS units, between two points for them to be connected. |
required |
min_size
|
int
|
Minimum number of points for a component to be kept; smaller
groups are labelled |
1
|
output_attribute
|
str
|
Name of the integer label column to add. |
'component'
|
Returns:
| Type | Description |
|---|---|
PointCloud
|
A new :class: |
PointCloud
|
column added, the CRS and provenance carried through. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If the cloud is empty, |
Example
import numpy as np from geoai3d import PointCloud, connected_components a = np.random.default_rng(0).normal(0.0, 0.1, (50, 3)) b = np.random.default_rng(1).normal(10.0, 0.1, (50, 3)) cloud = connected_components( ... PointCloud(np.vstack([a, b]), crs=28992), distance=0.5 ... ) len(set(cloud.attribute("component").tolist())) 2
Source code in src/geoai3d/cluster.py
34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 | |
dbscan ¶
dbscan(cloud: PointCloud, *, eps: float, min_samples: int = 5, output_attribute: str = 'cluster') -> PointCloud
Cluster points by density with DBSCAN.
DBSCAN groups points that have at least min_samples neighbours within
eps, growing clusters through those dense points and leaving sparse
points unclustered. Unlike :func:connected_components, a thin bridge of
isolated points does not merge two clusters.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
cloud
|
PointCloud
|
The cloud to cluster. |
required |
eps
|
float
|
Neighbourhood radius, in CRS units. |
required |
min_samples
|
int
|
Minimum neighbours (including the point itself) for a point to be a dense core of a cluster. |
5
|
output_attribute
|
str
|
Name of the integer label column to add. Cluster
labels are |
'cluster'
|
Returns:
| Type | Description |
|---|---|
PointCloud
|
A new :class: |
PointCloud
|
added, the CRS and provenance carried through. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If the cloud is empty, |
Example
import numpy as np from geoai3d import PointCloud, dbscan a = np.random.default_rng(0).normal(0.0, 0.1, (50, 3)) b = np.random.default_rng(1).normal(10.0, 0.1, (50, 3)) out = dbscan(PointCloud(np.vstack([a, b]), crs=28992), eps=0.5) int(out.attribute("cluster").max()) 1
Source code in src/geoai3d/cluster.py
112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 | |
difference ¶
difference(minuend: Raster, subtrahend: Raster) -> Raster
Subtract one aligned raster from another, cell by cell.
difference(dsm, dtm) gives a normalised surface (object heights);
difference(later, earlier) gives elevation change between epochs. A cell
is nodata (NaN) where either input is nodata.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
minuend
|
Raster
|
The raster to subtract from. |
required |
subtrahend
|
Raster
|
The raster to subtract. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
A |
Raster
|
class: |
Raises:
| Type | Description |
|---|---|
ValueError
|
If the rasters differ in shape, affine transform, or CRS
(rasterise both on the same |
Example
import numpy as np from geoai3d import PointCloud, to_dtm, to_dsm, difference pts = np.random.default_rng(0).uniform(0.0, 10.0, (2000, 3)) cloud = PointCloud(pts, crs=28992) ndsm = difference(to_dsm(cloud, resolution=1.0), ... to_dtm(cloud, resolution=1.0)) ndsm.shape (10, 10)
Source code in src/geoai3d/dem.py
183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 | |
estimate_normals ¶
estimate_normals(cloud: PointCloud, *, k: int = 16, orient_upward: bool = True) -> PointCloud
Estimate a surface normal at each point from its neighbours.
The normal is the direction of least variance of the k nearest
neighbours (the smallest principal component of the local covariance).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
cloud
|
PointCloud
|
The cloud to add normals to. |
required |
k
|
int
|
Number of nearest neighbours used to fit each local plane. Must be at least 3. |
16
|
orient_upward
|
bool
|
If true, flip every normal to point into the upper hemisphere (positive z), giving a consistent sign. |
True
|
Returns:
| Type | Description |
|---|---|
PointCloud
|
A new :class: |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Example
import numpy as np from geoai3d import PointCloud, estimate_normals flat = np.random.default_rng(0).random((200, 3)) flat[:, 2] = 0.0 cloud = estimate_normals(PointCloud(flat, crs=28992), k=8) bool(np.allclose(np.abs(cloud.attribute("nz")), 1.0)) True
Source code in src/geoai3d/geometry.py
18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 | |
estimate_radius ¶
estimate_radius(cloud: PointCloud, *, target_neighbors: int = 20, sample_size: int = 10000, seed: int = 0) -> float
Estimate a feature radius that captures about target_neighbors points.
Measures, over a random sample of points, the distance to the
target_neighbors-th nearest neighbour and returns the median. That is a
radius which, on average, encloses roughly that many neighbours given the
cloud's own density -- so the caller does not have to guess a number.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
cloud
|
PointCloud
|
The cloud to measure. |
required |
target_neighbors
|
int
|
Desired average neighbour count within the radius. |
20
|
sample_size
|
int
|
Number of points to sample for the estimate. |
10000
|
seed
|
int
|
Seed for the random sample. |
0
|
Returns:
| Type | Description |
|---|---|
float
|
The estimated radius in the cloud's CRS units. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If the cloud has fewer than two points, or
|
Example
import numpy as np from geoai3d import PointCloud, estimate_radius cloud = PointCloud(np.random.default_rng(0).random((2000, 3)), crs=28992) radius = estimate_radius(cloud, target_neighbors=15) radius > 0 True
Source code in src/geoai3d/outofcore.py
32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 | |
evaluate ¶
evaluate(truth: NDArray[Any], prediction: NDArray[Any], *, labels: NDArray[Any] | list[int] | tuple[int, ...] | None = None) -> AccuracyReport
Assess predicted labels against ground truth.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
truth
|
NDArray[Any]
|
True integer labels, one per point. |
required |
prediction
|
NDArray[Any]
|
Predicted integer labels, one per point. |
required |
labels
|
NDArray[Any] | list[int] | tuple[int, ...] | None
|
The labels to include, in order. If omitted, the sorted union of the values present is used. |
None
|
Returns:
| Name | Type | Description |
|---|---|---|
An |
AccuracyReport
|
class: |
AccuracyReport
|
per-class metrics, mean IoU, and macro F1. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Example
import numpy as np from geoai3d import evaluate truth = np.array([0, 0, 1, 1, 1]) prediction = np.array([0, 1, 1, 1, 1]) report = evaluate(truth, prediction) round(report.overall_accuracy, 2) 0.8
Source code in src/geoai3d/metrics.py
92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 | |
feature_matrix ¶
feature_matrix(cloud: PointCloud, feature_names: tuple[str, ...] | list[str] | None = None) -> NDArray[Any]
Assemble a (n_points, n_features) matrix from named attributes.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
cloud
|
PointCloud
|
The cloud to read features from. |
required |
feature_names
|
tuple[str, ...] | list[str] | None
|
Attribute names to stack, in order. If omitted, the
geometric feature columns (and |
None
|
Returns:
| Type | Description |
|---|---|
NDArray[Any]
|
A |
Raises:
| Type | Description |
|---|---|
ValueError
|
If no features can be resolved, or a named attribute is absent. |
Example
import numpy as np from geoai3d import PointCloud, feature_matrix cloud = PointCloud( ... np.zeros((4, 3)), ... attributes={"planarity": np.ones(4), "linearity": np.zeros(4)}, ... crs=28992, ... ) feature_matrix(cloud, ["planarity", "linearity"]).shape (4, 2)
Source code in src/geoai3d/classify.py
78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 | |
fit_plane ¶
fit_plane(cloud: PointCloud, *, distance_threshold: float = 0.1, max_iterations: int = 1000, seed: int = 0) -> Plane
Fit the dominant plane to a cloud with RANSAC.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
cloud
|
PointCloud
|
The cloud to fit. Needs at least three points. |
required |
distance_threshold
|
float
|
Maximum perpendicular distance, in CRS units, for a point to count as an inlier. |
0.1
|
max_iterations
|
int
|
Number of random three-point samples to try. |
1000
|
seed
|
int
|
Seed for the random sampling, so the fit is reproducible. |
0
|
Returns:
| Type | Description |
|---|---|
Plane
|
The fitted :class: |
Raises:
| Type | Description |
|---|---|
ValueError
|
If the cloud has fewer than three points, or
|
Example
import numpy as np from geoai3d import PointCloud, fit_plane rng = np.random.default_rng(0) xy = rng.uniform(0.0, 10.0, (500, 2)) z = 0.0 * xy[:, 0] # a horizontal plane at z = 0 pts = np.column_stack([xy, z]) plane = fit_plane(PointCloud(pts, crs=28992), distance_threshold=0.01) bool(abs(abs(plane.normal[2]) - 1.0) < 1e-6) True
Source code in src/geoai3d/primitives.py
61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 | |
geometric_features ¶
geometric_features(cloud: PointCloud, *, k: int | None = None, radius: float | None = None) -> PointCloud
Add eigenvalue-based geometric feature columns to a cloud.
Provide exactly one of k (a fixed number of neighbours) or radius
(a fixed spatial scale). If neither is given, k=20 is used. Only the
radius form carries the bit-identical out-of-core seam guarantee.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
cloud
|
PointCloud
|
The cloud to describe. |
required |
k
|
int | None
|
Number of nearest neighbours per point. Must be at least 3. |
None
|
radius
|
float | None
|
Neighbourhood radius in the cloud's CRS units. Points with fewer than three neighbours within it get NaN features. |
None
|
Returns:
| Type | Description |
|---|---|
PointCloud
|
A new :class: |
PointCloud
|
|
PointCloud
|
|
PointCloud
|
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If both |
Example
import numpy as np from geoai3d import PointCloud, geometric_features grid = np.linspace(-1, 1, 21) gx, gy = np.meshgrid(grid, grid) flat = np.column_stack([gx.ravel(), gy.ravel(), np.zeros(gx.size)]) cloud = geometric_features(PointCloud(flat, crs=28992), k=13) bool(np.nanmedian(cloud.attribute("planarity")) > 0.9) True
Source code in src/geoai3d/features.py
38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 | |
geometric_features_stream ¶
geometric_features_stream(source: str | PathLike[str], destination: str | PathLike[str], *, radius: float, tile_size: float, crs: object | None = None, attributes: Sequence[str] | None = None, chunk_size: int = 1000000, workers: int = 1, verbose: bool = False, max_points: int | None = None) -> None
Compute fixed-radius geometric features on a LAS/LAZ file out-of-core.
Streams the file in two passes (partition to per-tile temp files, then
per-tile feature computation) so the whole cloud is never resident. The
output matches :func:~geoai3d.geometric_features with the same radius,
bit for bit, and carries every source point attribute through unchanged.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
source
|
str | PathLike[str]
|
Path to the input |
required |
destination
|
str | PathLike[str]
|
Path to the output |
required |
radius
|
float
|
Neighbourhood radius and halo width, in the file's CRS units. |
required |
tile_size
|
float
|
Edge length of the square (x, y) tiles. Choose it so the
number of tiles is modest (dozens to a few hundred); it must be at
least |
required |
crs
|
object | None
|
CRS to assign, overriding the file header. Required if the file has none. |
None
|
attributes
|
Sequence[str] | None
|
Source point dimensions to carry through to the output.
|
None
|
chunk_size
|
int
|
Number of points read per streaming chunk in pass one. |
1000000
|
workers
|
int
|
Number of processes for the per-tile feature pass. 1 runs serially. |
1
|
verbose
|
bool
|
If true, print the time spent in the partition pass and the feature pass, and the tile count. |
False
|
max_points
|
int | None
|
If set, process only about this many points (rounded up to a chunk). Mainly for benchmarking a slice of a very large file. |
None
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Source code in src/geoai3d/stream.py
279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 | |
geometric_features_tiled ¶
geometric_features_tiled(cloud: PointCloud, *, radius: float, tile_size: float) -> PointCloud
Compute fixed-radius geometric features tile by tile with a halo.
The result is identical to :func:geometric_features with the same
radius on the whole cloud -- bit for bit -- but each tile is processed
with a bounded working set (the tile plus a one-radius halo), so memory
stays low for clouds much larger than RAM.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
cloud
|
PointCloud
|
The cloud to describe. |
required |
radius
|
float
|
Neighbourhood radius. Points with fewer than three neighbours within it get NaN features. Also the halo width. |
required |
tile_size
|
float
|
Edge length of the square (x, y) tiles, in CRS units. |
required |
Returns:
| Type | Description |
|---|---|
PointCloud
|
A new :class: |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Example
import numpy as np from geoai3d import PointCloud, geometric_features_tiled pts = np.random.default_rng(0).uniform(0, 10, (2000, 3)) cloud = geometric_features_tiled(PointCloud(pts, crs=28992), ... radius=0.6, tile_size=2.5) "planarity" in cloud.attribute_names True
Source code in src/geoai3d/outofcore.py
86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 | |
load_classifier ¶
load_classifier(source: str | PathLike[str]) -> Classifier
Load a classifier saved by :func:save_classifier.
Only load models from a source you trust: the file is unpickled.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
source
|
str | PathLike[str]
|
Path to a saved classifier. |
required |
Returns:
| Type | Description |
|---|---|
Classifier
|
The loaded :class: |
Raises:
| Type | Description |
|---|---|
ValueError
|
If the file does not contain a GEOAI_3D classifier. |
Source code in src/geoai3d/classify.py
240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 | |
multiscale_features ¶
multiscale_features(cloud: PointCloud, *, ks: tuple[int, ...] = (10, 20, 30, 40, 50)) -> PointCloud
Add geometric features computed at each point's optimal neighbourhood.
Features are computed at several neighbourhood sizes and, per point, the
size that minimises eigenentropy is chosen (the dimensionality-based scale
selection of Demantke et al.). The chosen size is stored as optimal_k.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
cloud
|
PointCloud
|
The cloud to describe. |
required |
ks
|
tuple[int, ...]
|
Candidate neighbour counts to try. Each must be at least 3. |
(10, 20, 30, 40, 50)
|
Returns:
| Type | Description |
|---|---|
PointCloud
|
A new :class: |
PointCloud
|
per-point optimal scale, plus an |
Raises:
| Type | Description |
|---|---|
ValueError
|
If any candidate in |
Example
import numpy as np from geoai3d import PointCloud, multiscale_features pts = np.random.default_rng(0).random((400, 3)) cloud = multiscale_features(PointCloud(pts, crs=28992), ks=(8, 16, 24)) "optimal_k" in cloud.attribute_names True
Source code in src/geoai3d/features.py
101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 | |
normalize_intensity ¶
normalize_intensity(cloud: PointCloud, *, sensor_position: NDArray[Any] | Sequence[float] | Sequence[Sequence[float]], intensity_attribute: str = 'intensity', reference_range: float | None = None, correct_range: bool = True, correct_incidence_angle: bool = False, max_incidence_angle: float = 80.0, output_attribute: str = 'normalized_intensity') -> PointCloud
Normalise LiDAR intensity for range and, optionally, incidence angle.
Corrects intensity so it better reflects target reflectance and is
comparable across a survey, using the sensor geometry (see the module
docstring for the model). The result is added as a new float64 column
and the CRS and provenance are carried through.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
cloud
|
PointCloud
|
The cloud to normalise. Must carry the intensity attribute and,
for incidence-angle correction, surface normals ( |
required |
sensor_position
|
NDArray[Any] | Sequence[float] | Sequence[Sequence[float]]
|
The scanner position, in the cloud's CRS. A |
required |
intensity_attribute
|
str
|
Name of the raw intensity attribute to read. |
'intensity'
|
reference_range
|
float | None
|
Range |
None
|
correct_range
|
bool
|
If true, apply the |
True
|
correct_incidence_angle
|
bool
|
If true, also divide by |
False
|
max_incidence_angle
|
float
|
Grazing-angle clamp in degrees for the incidence
correction; incidence angles beyond it are treated as this angle so
the |
80.0
|
output_attribute
|
str
|
Name of the normalised intensity column to add. |
'normalized_intensity'
|
Returns:
| Type | Description |
|---|---|
PointCloud
|
A new :class: |
PointCloud
|
added. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If neither correction is enabled; if the intensity or (for
incidence correction) the normal attributes are missing;
if |
Example
import numpy as np from geoai3d import PointCloud, normalize_intensity pts = np.zeros((100, 3)) pts[:, 0] = np.linspace(0.0, 10.0, 100) cloud = PointCloud( ... pts, attributes={"intensity": np.full(100, 100.0)}, crs=28992 ... ) out = normalize_intensity(cloud, sensor_position=[5.0, 0.0, 50.0]) "normalized_intensity" in out.attribute_names True
Source code in src/geoai3d/intensity.py
49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 | |
read_geotiff ¶
read_geotiff(source: str | PathLike[str], *, band: int = 1) -> Raster
Read one band of a GeoTIFF into a :class:~geoai3d.Raster.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
source
|
str | PathLike[str]
|
Path to a |
required |
band
|
int
|
1-based band index to read. |
1
|
Returns:
| Name | Type | Description |
|---|---|---|
A |
Raster
|
class: |
Raster
|
nodata value. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If rasterio (the |
Example
import numpy as np import os, tempfile from geoai3d import Raster, to_geotiff, read_geotiff path = os.path.join(tempfile.mkdtemp(), "out.tif") to_geotiff( ... Raster(np.ones((4, 4)), (1.0, 0.0, 0.0, 0.0, -1.0, 4.0), 28992), path ... ) read_geotiff(path).shape (4, 4)
Source code in src/geoai3d/io/geotiff.py
67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 | |
read_las ¶
read_las(source: str | PathLike[str], *, crs: object | None = None) -> PointCloud
Read a LAS or LAZ file into a :class:PointCloud.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
source
|
str | PathLike[str]
|
Path to a |
required |
crs
|
object | None
|
Coordinate reference system to assign, overriding whatever the file declares. If the file has no CRS and none is given here, an error is raised rather than returning an unreferenced cloud. |
None
|
Returns:
| Name | Type | Description |
|---|---|---|
A |
PointCloud
|
class: |
PointCloud
|
non-coordinate dimension as an attribute, the resolved CRS, and a |
|
PointCloud
|
provenance record noting the read. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If the file has no CRS and |
Example
import os, tempfile import numpy as np from geoai3d import PointCloud, read_las, to_las cloud = PointCloud(np.array([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]]), crs=28992) path = os.path.join(tempfile.mkdtemp(), "example.las") to_las(cloud, path) read_las(path).crs.to_epsg() 28992
Source code in src/geoai3d/io/las.py
64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 | |
read_lidar ¶
read_lidar(source: str | PathLike[str], *, crs: object | None = None) -> PointCloud
Read a LiDAR or point-cloud file, choosing the reader by extension.
Dispatches on the file suffix: .las and .laz are read with
:func:~geoai3d.read_las, and whitespace-delimited text
(.xyz, .txt, .asc, .pts) with :func:~geoai3d.read_xyz
using its defaults. For delimited text, non-default column layouts, or to
force a particular format, call the format-specific reader directly.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
source
|
str | PathLike[str]
|
Path to the point-cloud file. |
required |
crs
|
object | None
|
Coordinate reference system to assign, overriding any the file
carries. An EPSG code, WKT/PROJ string, or |
None
|
Returns:
| Name | Type | Description |
|---|---|---|
A |
PointCloud
|
class: |
PointCloud
|
record noting the read. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If the extension is |
Example
import os, tempfile import numpy as np from geoai3d import PointCloud, read_lidar, to_las cloud = PointCloud(np.array([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]]), crs=28992) path = os.path.join(tempfile.mkdtemp(), "scan.las") to_las(cloud, path) read_lidar(path).crs.to_epsg() 28992
Source code in src/geoai3d/io/dispatch.py
35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 | |
read_parquet ¶
read_parquet(source: str | PathLike[str], *, crs: object | None = None) -> PointCloud
Read a Parquet file written by :func:to_parquet into a cloud.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
source
|
str | PathLike[str]
|
Path to a |
required |
crs
|
object | None
|
Coordinate reference system to assign, overriding the file's metadata. If the file carries no CRS and none is given here, an error is raised rather than returning an unreferenced cloud. |
None
|
Returns:
| Name | Type | Description |
|---|---|---|
A |
PointCloud
|
class: |
PointCloud
|
attribute, the resolved CRS, and the stored provenance record. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If the file lacks |
Example
import os, tempfile import numpy as np from geoai3d import PointCloud, to_parquet, read_parquet path = os.path.join(tempfile.mkdtemp(), "cloud.parquet") to_parquet(PointCloud(np.ones((2, 3)), crs=28992), path) read_parquet(path).crs.to_epsg() 28992
Source code in src/geoai3d/io/parquet.py
100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 | |
read_xyz ¶
read_xyz(source: str | PathLike[str], *, crs: object | None = None, columns: tuple[str, ...] = _COORDINATE_NAMES, delimiter: str | None = None, comments: str = '#') -> PointCloud
Read a whitespace- or delimiter-separated XYZ text file.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
source
|
str | PathLike[str]
|
Path to the text file. |
required |
crs
|
object | None
|
Coordinate reference system to assign. If omitted, a sibling
|
None
|
columns
|
tuple[str, ...]
|
Name of each column in order. Must include |
_COORDINATE_NAMES
|
delimiter
|
str | None
|
Column delimiter. |
None
|
comments
|
str
|
Character marking comment lines to skip. |
'#'
|
Returns:
| Name | Type | Description |
|---|---|---|
A |
PointCloud
|
class: |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Example
import os, tempfile import numpy as np from geoai3d import PointCloud, read_xyz, to_xyz path = os.path.join(tempfile.mkdtemp(), "points.xyz") to_xyz(PointCloud(np.zeros((3, 3)), crs=28992), path) len(read_xyz(path)) 3
Source code in src/geoai3d/io/xyz.py
49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 | |
region_growing ¶
region_growing(cloud: PointCloud, *, k: int = 30, smoothness_degrees: float = 15.0, curvature_attribute: str = 'surface_variation', curvature_threshold: float | None = None, min_size: int = 10, output_attribute: str = 'segment') -> PointCloud
Segment a cloud into smooth regions by growing from seeds.
Starting from the smoothest points, a region absorbs a neighbour when the
angle between their surface normals is below smoothness_degrees; the
neighbour then continues the growth unless a curvature limit stops it. This
is the classical normal-based region growing (after Rabbani et al.), which
separates a scene by surface orientation -- ground from walls from roofs --
with no training data.
The cloud must carry surface normals (nx, ny, nz from
:func:~geoai3d.estimate_normals). If it also carries a curvature attribute
(for example surface_variation from :func:~geoai3d.geometric_features)
the smoothest points seed first, which gives cleaner regions.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
cloud
|
PointCloud
|
The cloud to segment. Must carry surface normals. |
required |
k
|
int
|
Number of nearest neighbours considered for growth. |
30
|
smoothness_degrees
|
float
|
Maximum normal-to-normal angle, in degrees, for a neighbour to join a region. |
15.0
|
curvature_attribute
|
str
|
Attribute used to order seeds (smoothest first) and,
with |
'surface_variation'
|
curvature_threshold
|
float | None
|
If set, only points with curvature below it continue
growing a region. |
None
|
min_size
|
int
|
Minimum points for a region to be kept; smaller regions are
labelled |
10
|
output_attribute
|
str
|
Name of the integer segment label column to add. |
'segment'
|
Returns:
| Type | Description |
|---|---|
PointCloud
|
A new :class: |
PointCloud
|
added (contiguous ids, |
PointCloud
|
provenance carried through. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If the cloud is empty, lacks the normal attributes, |
Example
import numpy as np from geoai3d import PointCloud, estimate_normals, region_growing rng = np.random.default_rng(0) flat = rng.uniform(0.0, 5.0, (400, 3)) flat[:, 2] = 0.0 cloud = estimate_normals(PointCloud(flat, crs=28992), k=12) segmented = region_growing(cloud, k=12, min_size=20) int(segmented.attribute("segment").max()) 0
Source code in src/geoai3d/cluster.py
172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 | |
remove_statistical_outliers ¶
remove_statistical_outliers(cloud: PointCloud, *, k: int = 16, std_ratio: float = 2.0) -> PointCloud
Remove points whose neighbours are unusually far away.
For each point, the mean distance to its k nearest neighbours is
computed. Points whose mean distance exceeds the global mean by more than
std_ratio standard deviations are dropped.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
cloud
|
PointCloud
|
The cloud to filter. |
required |
k
|
int
|
Number of nearest neighbours per point. |
16
|
std_ratio
|
float
|
Multiplier of the global standard deviation above which a point is treated as an outlier. Smaller values remove more points. |
2.0
|
Returns:
| Type | Description |
|---|---|
PointCloud
|
A new :class: |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Example
import numpy as np from geoai3d import PointCloud, remove_statistical_outliers pts = np.random.default_rng(0).random((500, 3)) pts[0] = [100.0, 100.0, 100.0] # a clear outlier cleaned = remove_statistical_outliers(PointCloud(pts, crs=28992)) len(cleaned) < 500 True
Source code in src/geoai3d/geometry.py
78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 | |
reproject ¶
reproject(cloud: PointCloud, target_crs: object) -> PointCloud
Reproject a cloud's horizontal coordinates to another CRS.
Only the x and y coordinates are transformed; heights are carried
through unchanged. For a transform that also converts heights between
vertical datums, use :func:reproject_3d.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
cloud
|
PointCloud
|
The cloud to reproject. Its CRS must be set. |
required |
target_crs
|
object
|
Target CRS as an EPSG code, WKT string, or |
required |
Returns:
| Type | Description |
|---|---|
PointCloud
|
A new :class: |
PointCloud
|
through and a provenance step appended. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If the cloud has no CRS, or a CRS cannot be interpreted. |
Example
import numpy as np from geoai3d import PointCloud, reproject cloud = PointCloud(np.array([[155000.0, 463000.0, 0.0]]), crs=28992) reproject(cloud, 4326).crs.to_epsg() 4326
Source code in src/geoai3d/crs.py
44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 | |
reproject_3d ¶
reproject_3d(cloud: PointCloud, target_crs: object, *, allow_network: bool = False) -> PointCloud
Reproject a cloud in three dimensions, including the vertical datum.
Transforms x, y, and z together, so heights are converted
between the source and target vertical datums (for example ellipsoidal to
NAP orthometric) using the appropriate geoid grid. Both the source and
target CRS must carry a vertical axis.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
cloud
|
PointCloud
|
The cloud to reproject. Its CRS must be set and three-dimensional. |
required |
target_crs
|
object
|
A 3D or compound target CRS (for example |
required |
allow_network
|
bool
|
If true, let pyproj download any missing transform grid from the PROJ CDN. If false and a grid is missing, an error is raised rather than returning silently wrong heights. |
False
|
Returns:
| Type | Description |
|---|---|
PointCloud
|
A new :class: |
PointCloud
|
through and a provenance step appended. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If the cloud has no CRS, either CRS lacks a vertical axis, or the transform produced non-finite coordinates (usually a missing geoid grid). |
Example
import numpy as np from geoai3d import PointCloud, reproject_3d cloud = PointCloud(np.array([[5.0, 52.0, 43.0]]), crs=4979) reproject_3d(cloud, 4937).crs.to_epsg() 4937
Source code in src/geoai3d/crs.py
92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 | |
save_classifier ¶
save_classifier(model: Classifier, destination: str | PathLike[str]) -> None
Persist a trained classifier to disk (pickle).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
model
|
Classifier
|
The classifier to save. |
required |
destination
|
str | PathLike[str]
|
Output path. |
required |
Source code in src/geoai3d/classify.py
229 230 231 232 233 234 235 236 237 | |
spatial_block_split ¶
spatial_block_split(cloud: PointCloud, *, block_size: float, n_folds: int = 5, seed: int = 0) -> list[tuple[NDArray[Any], NDArray[Any]]]
Split a cloud into spatially-blocked cross-validation folds.
Points are grouped into square block_size blocks in the horizontal
plane, the blocks are shuffled and dealt round-robin into n_folds folds,
and each fold's test set is the points of its blocks with the rest as the
training set. Because a whole block goes to one fold, test points never
neighbour their own training points.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
cloud
|
PointCloud
|
The cloud to split. |
required |
block_size
|
float
|
Edge length of the square blocks, in the cloud's CRS units. Make it several times the feature neighbourhood so blocks are genuinely independent. |
required |
n_folds
|
int
|
Number of folds. |
5
|
seed
|
int
|
Seed for shuffling blocks into folds. |
0
|
Returns:
| Type | Description |
|---|---|
list[tuple[NDArray[Any], NDArray[Any]]]
|
A list of |
list[tuple[NDArray[Any], NDArray[Any]]]
|
index arrays into the cloud. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If the cloud is empty, |
Example
import numpy as np from geoai3d import PointCloud, spatial_block_split pts = np.random.default_rng(0).uniform(0.0, 100.0, (2000, 3)) folds = spatial_block_split( ... PointCloud(pts, crs=28992), block_size=10.0, n_folds=5 ... ) len(folds) 5
Source code in src/geoai3d/crossval.py
25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 | |
to_dsm ¶
to_dsm(cloud: PointCloud, *, resolution: float, bounds: tuple[float, float, float, float] | None = None, nodata: float = float('nan')) -> Raster
Rasterise a cloud to a Digital Surface Model (the top surface).
Uses the highest z per cell over every point, capturing the top of vegetation and buildings.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
cloud
|
PointCloud
|
The cloud to rasterise. Its CRS must be set. |
required |
resolution
|
float
|
Cell size in the cloud's CRS units. |
required |
bounds
|
tuple[float, float, float, float] | None
|
Grid extent |
None
|
nodata
|
float
|
Value for empty cells. Defaults to |
float('nan')
|
Returns:
| Name | Type | Description |
|---|---|---|
A |
Raster
|
class: |
Raster
|
func: |
Raises:
| Type | Description |
|---|---|
ValueError
|
If the cloud is empty or has no CRS, or |
Example
import numpy as np from geoai3d import PointCloud, to_dsm pts = np.random.default_rng(0).uniform(0.0, 10.0, (2000, 3)) dsm = to_dsm(PointCloud(pts, crs=28992), resolution=1.0) dsm.shape (10, 10)
Source code in src/geoai3d/dem.py
143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 | |
to_dtm ¶
to_dtm(cloud: PointCloud, *, resolution: float, ground_attribute: str = 'is_ground', bounds: tuple[float, float, float, float] | None = None, nodata: float = float('nan')) -> Raster
Rasterise a cloud to a Digital Terrain Model (bare earth).
If the cloud carries a boolean ground attribute (from
:func:~geoai3d.ground), only those points are used; otherwise every point
is used and the lowest z per cell approximates the bare earth.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
cloud
|
PointCloud
|
The cloud to rasterise. Its CRS must be set. |
required |
resolution
|
float
|
Cell size in the cloud's CRS units. |
required |
ground_attribute
|
str
|
Boolean attribute selecting ground points, if present. |
'is_ground'
|
bounds
|
tuple[float, float, float, float] | None
|
Grid extent |
None
|
nodata
|
float
|
Value for empty cells. Defaults to |
float('nan')
|
Returns:
| Name | Type | Description |
|---|---|---|
A |
Raster
|
class: |
Raster
|
func: |
Raises:
| Type | Description |
|---|---|
ValueError
|
If the cloud is empty or has no CRS, |
Example
import numpy as np from geoai3d import PointCloud, to_dtm pts = np.random.default_rng(0).uniform(0.0, 10.0, (2000, 3)) dtm = to_dtm(PointCloud(pts, crs=28992), resolution=1.0) dtm.shape (10, 10)
Source code in src/geoai3d/dem.py
88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 | |
to_geopackage ¶
to_geopackage(cloud: PointCloud, destination: str | PathLike[str], *, attributes: Sequence[str] | None = None, layer: str = 'points', include_z: bool = True) -> None
Write a point cloud to a GeoPackage vector layer.
Each point becomes a (3D) point feature; the chosen attributes become
columns. The coordinate reference system is written into the file. If the
cloud carries a provenance record, it is written to a sibling
<destination>.provenance.json.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
cloud
|
PointCloud
|
The cloud to write. Its CRS must be set. |
required |
destination
|
str | PathLike[str]
|
Output |
required |
attributes
|
Sequence[str] | None
|
Attribute columns to include. If omitted, every attribute is written. |
None
|
layer
|
str
|
Layer name inside the GeoPackage. |
'points'
|
include_z
|
bool
|
If true, write 3D |
True
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If geopandas (the |
Example
import numpy as np import os, tempfile from geoai3d import PointCloud, to_geopackage cloud = PointCloud( ... np.zeros((3, 3)), ... attributes={"classification": np.array([2, 2, 6])}, ... crs=28992, ... ) to_geopackage(cloud, os.path.join(tempfile.mkdtemp(), "points.gpkg"))
Source code in src/geoai3d/io/vector.py
32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 | |
to_geotiff ¶
to_geotiff(raster: Raster, destination: str | PathLike[str]) -> None
Write a :class:~geoai3d.Raster to a single-band GeoTIFF.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
raster
|
Raster
|
The raster to write. Its CRS, transform, and nodata value are stored in the file. |
required |
destination
|
str | PathLike[str]
|
Output |
required |
Raises:
| Type | Description |
|---|---|
ValueError
|
If rasterio (the |
Example
import numpy as np import os, tempfile from geoai3d import Raster, to_geotiff raster = Raster(np.ones((4, 4)), (1.0, 0.0, 0.0, 0.0, -1.0, 4.0), 28992) to_geotiff(raster, os.path.join(tempfile.mkdtemp(), "out.tif"))
Source code in src/geoai3d/io/geotiff.py
28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 | |
to_las ¶
to_las(cloud: PointCloud, destination: str | PathLike[str], *, point_format: int = _DEFAULT_POINT_FORMAT, file_version: str = _DEFAULT_FILE_VERSION, scales: NDArray[float64] | Sequence[float] | None = None, offsets: NDArray[float64] | Sequence[float] | None = None) -> None
Write a :class:PointCloud to a LAS or LAZ file.
Attributes whose names are standard dimensions of point_format are
written as those fields; any other attribute is written as a user-defined
extra dimension, preserving its values and dtype.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
cloud
|
PointCloud
|
The cloud to write. Its CRS must be set. |
required |
destination
|
str | PathLike[str]
|
Output path. A |
required |
point_format
|
int
|
LAS point format id to write. The default, 6, carries coordinates, intensity, returns, classification, scan angle, GPS time, and point source id. |
_DEFAULT_POINT_FORMAT
|
file_version
|
str
|
LAS version string. The default, |
_DEFAULT_FILE_VERSION
|
scales
|
NDArray[float64] | Sequence[float] | None
|
Per-axis coordinate scales. Defaults to one millimetre on each axis. |
None
|
offsets
|
NDArray[float64] | Sequence[float] | None
|
Per-axis coordinate offsets. Defaults to the minimum coordinate on each axis. |
None
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If the cloud has no CRS, or the CRS cannot be interpreted. |
Example
import os, tempfile import numpy as np from geoai3d import PointCloud, to_las cloud = PointCloud(np.zeros((5, 3)), crs=28992) to_las(cloud, os.path.join(tempfile.mkdtemp(), "out.laz"))
Source code in src/geoai3d/io/las.py
139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 | |
to_parquet ¶
to_parquet(cloud: PointCloud, destination: str | PathLike[str]) -> None
Write a :class:PointCloud to a Parquet file.
Coordinates are stored as x, y, z columns and each attribute as
a column of its own dtype. The CRS and provenance record are written into
the schema metadata.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
cloud
|
PointCloud
|
The cloud to write. Its CRS must be set. |
required |
destination
|
str | PathLike[str]
|
Output |
required |
Raises:
| Type | Description |
|---|---|
ValueError
|
If the cloud has no CRS, or an attribute is named |
Example
import os, tempfile import numpy as np from geoai3d import PointCloud, to_parquet, read_parquet cloud = PointCloud(np.zeros((4, 3)), crs=28992) path = os.path.join(tempfile.mkdtemp(), "cloud.parquet") to_parquet(cloud, path) len(read_parquet(path)) 4
Source code in src/geoai3d/io/parquet.py
39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 | |
to_xyz ¶
to_xyz(cloud: PointCloud, destination: str | PathLike[str], *, include_attributes: bool = True, delimiter: str = ' ', fmt: str = '%.6f', write_header: bool = False, write_prj: bool = True) -> None
Write a :class:PointCloud to an XYZ text file.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
cloud
|
PointCloud
|
The cloud to write. |
required |
destination
|
str | PathLike[str]
|
Output path. |
required |
include_attributes
|
bool
|
If true, write attribute columns after |
True
|
delimiter
|
str
|
Column delimiter. |
' '
|
fmt
|
str
|
|
'%.6f'
|
write_header
|
bool
|
If true, write a commented header line of column names. |
False
|
write_prj
|
bool
|
If true and the cloud has a CRS, write it as WKT to a
sibling |
True
|
Example
import os, tempfile import numpy as np from geoai3d import PointCloud, to_xyz cloud = PointCloud(np.zeros((2, 3)), crs=28992) to_xyz(cloud, os.path.join(tempfile.mkdtemp(), "out.xyz"))
Source code in src/geoai3d/io/xyz.py
117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 | |
train_classifier ¶
train_classifier(cloud: PointCloud, *, label_attribute: str = 'classification', feature_names: tuple[str, ...] | list[str] | None = None, n_estimators: int = 200, max_depth: int | None = None, random_state: int = 0) -> Classifier
Train a random-forest classifier on a labelled cloud.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
cloud
|
PointCloud
|
The labelled training cloud. |
required |
label_attribute
|
str
|
Attribute holding the integer class label per point. |
'classification'
|
feature_names
|
tuple[str, ...] | list[str] | None
|
Feature attributes to train on. If omitted, resolved as
in :func: |
None
|
n_estimators
|
int
|
Number of trees in the forest. |
200
|
max_depth
|
int | None
|
Maximum tree depth, or |
None
|
random_state
|
int
|
Seed for reproducible training. |
0
|
Returns:
| Name | Type | Description |
|---|---|---|
A |
Classifier
|
class: |
Raises:
| Type | Description |
|---|---|
ValueError
|
If the label attribute is absent, or features cannot be resolved. |
Example
import numpy as np from geoai3d import PointCloud, train_classifier, classify rng = np.random.default_rng(0) planarity = np.concatenate([rng.normal(0.9, 0.05, 100), ... rng.normal(0.1, 0.05, 100)]) labels = np.concatenate([np.zeros(100), np.ones(100)]).astype(int) cloud = PointCloud( ... np.zeros((200, 3)), ... attributes={"planarity": planarity, "classification": labels}, ... crs=28992, ... ) model = train_classifier(cloud, feature_names=["planarity"]) model.feature_names ('planarity',)
Source code in src/geoai3d/classify.py
113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 | |
view ¶
view(cloud: PointCloud, *, color_by: str | None = None, max_points: int = _DEFAULT_MAX_POINTS, point_size: float = 2.0, seed: int = 0) -> go.Figure
Render a point cloud as an interactive 3D plot.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
cloud
|
PointCloud
|
The cloud to display. |
required |
color_by
|
str | None
|
Attribute name to colour by, or |
None
|
max_points
|
int
|
Randomly thin the cloud to at most this many points for display, so the browser stays responsive. |
_DEFAULT_MAX_POINTS
|
point_size
|
float
|
Marker size. |
2.0
|
seed
|
int
|
Seed for the display thinning, for a reproducible view. |
0
|
Returns:
| Type | Description |
|---|---|
Figure
|
A |
Figure
|
elsewhere call |
Raises:
| Type | Description |
|---|---|
ImportError
|
If plotly is not installed. |
ValueError
|
If |
Example
import numpy as np from geoai3d import PointCloud from geoai3d.viz import view cloud = PointCloud(np.random.default_rng(0).random((500, 3)), crs=28992) figure = view(cloud) figure.data[0].type 'scatter3d'
Source code in src/geoai3d/viz.py
63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 | |
volume ¶
volume(raster: Raster, *, base: float = 0.0) -> dict[str, float]
Compute cut and fill volumes of a raster relative to a base level.
Interprets the raster as a height field (typically a difference raster) and
integrates it against base: cells above base contribute fill, cells
below contribute cut, each weighted by the cell area. Nodata cells are
ignored.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
raster
|
Raster
|
The height raster to integrate. |
required |
base
|
float
|
The reference level to measure against. |
0.0
|
Returns:
| Type | Description |
|---|---|
dict[str, float]
|
A dict with |
dict[str, float]
|
volumes, in cubic CRS units. |
Example
import numpy as np from geoai3d import Raster, volume data = np.array([[1.0, 1.0], [1.0, 1.0]]) # 1 m over four 2 m cells raster = Raster(data, (2.0, 0.0, 0.0, 0.0, -2.0, 4.0), 28992) volume(raster)["fill"] 16.0
Source code in src/geoai3d/dem.py
227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 | |