Morphometrics¶
A descriptive output, not classifier input. Majer & Fleischmann (2026) computed 107 2D
morphometric attributes — dimensional and shape, spatial distribution and intensity, street
descriptors and connectivity — over enclosed tessellation cells (ETCs), and found that the
correspondence between 2D morphometrics alone and LCZ types is "selective and inconsistent".
This package ports the attribute computation, not the paper's RandomForest/CNN prediction
schemes, and none of it feeds PrototypeClassifier: the classifier stays the
existing prototype-distance metric over Stewart & Oke's urban canopy parameters.
An ETC is momepy's enclosed_tessellation — one cell per building, generated within
street-bounded enclosures — restricted to cells with a parent building. It is not a partition of
the bounding box, unlike GridUnits/EnclosureUnits: a cell with no building is
dropped, since it carries no morphometric meaning. See Tessellation.
The full attribute-to-momepy-call mapping is transcribed in
docs/references/tables/majer_2026_morphometrics_menu.md, the authoritative checklist this
package's own test suite parses cell for cell against the registry below.
Output ships as its own run artefact, morphometrics.parquet — and a raster, one band per
attribute, area-weighted, if a raster resolution is requested. Neither is joined into
units.parquet or the classification table: ETCs are a different, finer-grained unit set than
whatever the run's classification units are.
lczkit.morphometrics
¶
2D urban morphometrics after Majer & Fleischmann (2026).
A descriptive output, not classifier input. compute_morphometrics returns 107 primary
attributes (dimensional/shape, spatial distribution/intensity, street descriptors/connectivity),
computed via momepy 1.0+ over enclosed tessellation cells (ETCs — see
lczkit.units.tessellation), plus an opt-in contextual expansion. None of it feeds
lczkit.classify.PrototypeClassifier: the paper's own finding is that 2D morphometrics alone
predict LCZ "selectively and inconsistently", and this package's classifier stays the existing
prototype-distance metric over Stewart & Oke's urban canopy parameters. Nor does it replicate the
paper's RandomForest/CNN prediction schemes (S1-S4) — those, and satellite imagery, are out of
scope for this feature.
Output ships as its own run artefact (morphometrics.parquet, and morphometrics.tif if a
raster resolution is requested) — never joined into units.parquet or the classification table,
since ETCs are a different, finer-grained unit set than the pipeline's classification units.
See docs/references/tables/majer_2026_morphometrics_menu.md for the full attribute list.
Deliberately no eager re-exports here. raster.py imports filename constants from
lczkit.output.writer, which imports MorphometricsReport from report.py — importing any
submodule always runs this file first, so re-exporting raster's names here would make importing
lczkit.morphometrics.report alone drag in raster.py, and with it output.writer, while
output.writer is itself mid-import. Import submodules directly:
lczkit.morphometrics.compute.compute_morphometrics, lczkit.morphometrics.raster.*,
lczkit.morphometrics.report.MorphometricsReport.
Computing the attributes¶
The one function lczkit.pipeline.run_pipeline calls for this stage.
lczkit.morphometrics.compute
¶
compute_morphometrics: the one function lczkit.pipeline calls for this stage.
Builds the tessellation, builds every libpysal.graph.Graph the metric modules need exactly
once, runs the three attribute blocks (dimensional, distribution, streets), validates the
result against the registry, and optionally runs the opt-in contextual expansion.
BUILDING_DISTANCE_BANDS_M
module-attribute
¶
Distance-band neighbourhoods for building-scale metrics: the paper's scales.
BUILDING_KNN
module-attribute
¶
Nearest-neighbour counts for the mean-distance metrics: the paper's scales.
ETC_TOPOLOGICAL_STEPS
module-attribute
¶
Contiguity depths for cell-scale metrics: the paper's scales.
Fixed rather than configurable because the registry names every column after them
(..._w100m, ..._knn20, ..._w3steps); a different scale would be a different menu.
compute_morphometrics
¶
compute_morphometrics(bbox: BBox, buildings: GeoDataFrame, streets: GeoDataFrame, waterbodies: GeoDataFrame, *, config: MorphometricsConfig) -> tuple[GeoDataFrame, MorphometricsReport]
The 107 primary morphometric attributes, indexed by ETC unit_id, alongside geometry.
Plus, if config.contextual, their percentile expansion. buildings should be
buildings_area from lczkit.cleaning.pipeline.clean_vectors — the
area-preserving layer every other area statistic in this package reads. streets should be
the cleaned, simplified network; waterbodies the cleaned waterbody polygons. All three must
already share one projected CRS, as clean_vectors returns them.
Source code in src/lczkit/morphometrics/compute.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 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 | |
Graphs¶
Every libpysal.graph.Graph the metric modules need, built once and shared. ETC contiguity is
fuzzy, not the plain queen contiguity used elsewhere in this package — momepy's own
documented requirement for enclosed_tessellation output, which does not form a precise
polygonal coverage.
lczkit.morphometrics.graphs
¶
Every libpysal.graph.Graph this package's morphometrics build, in one place.
Two modules building "the same" neighbourhood graph independently is how a weighted mean and a
neighbour count end up describing different neighbourhoods without either call site knowing it.
Every graph lczkit.morphometrics uses is constructed here and passed down, never rebuilt inline.
ETC contiguity is fuzzy, not the Graph.build_contiguity(gdf, rook=False) queen contiguity
lczkit uses everywhere else (lczkit.units.patches, lczkit.classify.smoothing). This is
momepy's own documented requirement, not a new pattern invented here:
momepy.enclosed_tessellation's own docstring warns that its output "does not form a precise
polygonal coverage" and that a contiguity graph over it should use
Graph.build_fuzzy_contiguity(tessellation, buffer=...) instead — a plain queen graph misses
edges that fall just short of touching because of the shrink/segment tolerances the tessellation
algorithm applies.
FUZZY_CONTIGUITY_BUFFER_M
module-attribute
¶
momepy's own suggested tolerance for enclosed_tessellation output, applied here rather than
left as a magic number at the one call site that needs it.
building_contiguity
¶
Queen contiguity between buildings.
What momepy.perimeter_wall groups joined structures by, and the contiguity_graph
argument momepy.building_adjacency compares against.
Source code in src/lczkit/morphometrics/graphs.py
tessellation_adjacency_by_building
¶
ETC contiguity, relabelled onto the building index — what adjacency means between buildings.
momepy.mean_interbuilding_distance documents its adjacency_graph as "a contiguity graph
derived from tessellation cells linked to buildings", and its own worked example passes a
Delaunay triangulation. Building-footprint contiguity is not a substitute: footprints in the
area-preserving layer mostly do not touch, so that graph is nearly edgeless and the metric
has no adjacent pair to measure a distance between.
Measured on the Hong Kong fixture, 5 448 buildings:
building queen contiguity 4 242 edges 2 975 isolates (55%) metric identically 0.0
ETC contiguity 23 128 edges 12 isolates median 10.06 m, max 61.7
etc and buildings must be row-aligned, as buildings_for_etc returns them: the cells
carry the adjacency and the buildings carry the index the metric's geometry is indexed by.
Source code in src/lczkit/morphometrics/graphs.py
building_distance_band
¶
Binary distance-band graph over building centroids at distance_m.
Binary (unweighted 0/1 edges) because every building-scale metric in this module reads counts or unweighted means over the neighbourhood, never a distance-decayed one.
Source code in src/lczkit/morphometrics/graphs.py
building_knn
¶
K-nearest-neighbour graph over building centroids.
The paper's "10/20/30 nearest neighbours" scale for mean distance to neighbours.
coplanar="clique", not libpysal's "raise" default. Two buildings can share a centroid
exactly — concentric footprints, or a footprint and its own courtyard ring — and build_knn
then refuses outright rather than degrading, taking the whole stage down with it. Measured on
real Overture data: a 9 km² window over central Nairobi holds 8 014 buildings at 8 013 unique
centroids, one coplanar pair, and that is enough to raise CoplanarError. The failure is
data-dependent and silent until it fires, so it would surface unpredictably part-way through a
multi-city extraction rather than up front.
"clique" over "jitter" — the other option that avoids the error — because jitter displaces
the duplicates randomly, which would make mean_dist_neighbors_building_knn* differ between
two runs over identical input. This package spends real effort on determinism (sorted Overture
fetches, sorted tile subsets, pinned thread counts); a random tie-break in a shipped metric
would undo that for the sake of a tie that "clique" resolves deterministically and
defensibly: two buildings at one point genuinely are each other's nearest neighbour, at
distance zero.
Source code in src/lczkit/morphometrics/graphs.py
etc_contiguity
¶
Fuzzy queen contiguity between tessellation cells.
See the module docstring for why fuzzy rather than exact contiguity is required here.
Source code in src/lczkit/morphometrics/graphs.py
etc_higher_order
¶
base expanded to every cell reachable within steps topological hops.
base itself is included (lower_order=True) — "within N topological steps" in the
paper's own wording, not "exactly N steps away".
Source code in src/lczkit/morphometrics/graphs.py
etc_granularity_graph
¶
base's 1-step neighbourhood with the focal cell included in its own neighbour set.
momepy.percentile/momepy.weighted_character/momepy.neighbors all read a graph's
neighbours as other cells, so "granularity" — the paper's "sum of ETC area within 1
topological step" — needs the one graph in this module where a cell counts itself: without
assign_self_weight, Graph.lag(area) would sum a cell's neighbours and silently exclude the
cell's own area from a quantity meant to describe the immediate cluster it sits in.
Source code in src/lczkit/morphometrics/graphs.py
Dimensional & shape¶
lczkit.morphometrics.dimensional
¶
Dimensional & Shape morphometrics — Majer & Fleischmann (2026), Supplementary A §1.
Every metric here is a direct 1:1 momepy 1.0+ call, computed once for buildings and once for ETCs
(enclosed tessellation cells), plus area-weighted variants over the neighbourhoods
lczkit.morphometrics.graphs builds. See
docs/references/tables/majer_2026_morphometrics_menu.md for the full attribute-to-momepy-call
mapping this module implements against.
buildings and etc must already share one index (unit_id) — buildings_for_etc
(lczkit.units.tessellation) produces that alignment — since every weighted variant here is a
plain Series operation across the two.
dimensional_metrics
¶
dimensional_metrics(buildings: GeoDataFrame, etc: GeoDataFrame, *, building_contiguity: Graph, building_w100m: Graph, building_w200m: Graph, etc_w3steps: Graph) -> DataFrame
The 62 Dimensional & Shape columns, indexed like buildings/etc (unit_id).
building_contiguity is queen contiguity over buildings — what momepy.perimeter_wall
groups joined structures by. building_w100m/building_w200m are distance-band graphs over
building centroids; etc_w3steps is the fuzzy-contiguity ETC graph expanded to 3 topological
steps (lczkit.morphometrics.graphs.etc_higher_order).
Source code in src/lczkit/morphometrics/dimensional.py
Spatial distribution & intensity¶
Coverage area ratio is not guaranteed to fall in [0, 1]. A building's footprint area over its own ETC's area is usually near 1, but momepy's tessellation shrink/segment step does not guarantee a cell fully contains the building that seeded it — measured on 6.4% of ETCs in the Hong Kong fixture. Reported as measured, not clipped.
lczkit.morphometrics.distribution
¶
Spatial Distribution & Intensity morphometrics — Majer & Fleischmann (2026), Supplementary A §2.
Buildings and ETCs (enclosed tessellation cells) share one index throughout (unit_id) — see
lczkit.units.tessellation.buildings_for_etc. Coverage Area Ratio in particular relies on this
directly: it is a plain division of a building's area by its own ETC's area, with no join.
distribution_metrics
¶
distribution_metrics(buildings: GeoDataFrame, etc: GeoDataFrame, streets: GeoDataFrame, *, building_contiguity: Graph, building_tessellation_adjacency: Graph, building_adjacency_neighborhood: Graph, building_w100m: Graph, building_w200m: Graph, building_distance_bands: dict[str, Graph], building_knn: dict[str, Graph], etc_higher_order: dict[int, Graph]) -> DataFrame
The 23 Spatial Distribution & Intensity columns, indexed like buildings/etc (unit_id).
building_adjacency_neighborhood is the 200 m distance-band graph both building_adjacency
and mean_interbuilding_distance read as their neighbourhood extent. The two differ in what
they take as adjacency: building_contiguity for the first, as its docstring asks, and
building_tessellation_adjacency for the second, which momepy documents as tessellation-cell
contiguity and which is the difference between a real distance and a column of zeros.
building_distance_bands and building_knn key on the paper's own labels ("20m", "100m",
"200m", "knn10", "knn20", "knn30"); etc_higher_order keys on the step count
(1, 2, 3), already expanded via lczkit.morphometrics.graphs.etc_higher_order.
Source code in src/lczkit/morphometrics/distribution.py
Street descriptors & connectivity¶
5 m and 400 m are network-distance radii, not topological hops. Every connectivity function
here reads its radius as a hop count unless an edge-length attribute is named — confirmed
against the installed momepy rather than assumed, since 5 or 400 hops would be a nonsensical
pairing on any real street network.
lczkit.morphometrics.streets
¶
Street Descriptors & Connectivity morphometrics — Majer & Fleischmann (2026), Supplementary A §3.
Computed per street segment (street_length, street_linearity, street_width*) or per network
node (everything else), then joined onto each ETC (enclosed tessellation cell) via
momepy.get_nearest_street/momepy.get_nearest_node — the paper's own "linked to nearest street,
node and edge" rule (Supplementary A).
5 m / 400 m are network-distance radii, not topological hops. Every connectivity function here
(mean_node_degree, node_density, edge_node_ratio, cds_length, cyclomatic, gamma,
meshedness) reads radius as a topological hop count unless distance names an edge
attribute, in which case radius is measured along that attribute — confirmed against the
installed momepy. distance="mm_len" (the length attribute momepy.gdf_to_nx writes by default)
makes radius=5/radius=400 mean 5 m / 400 m of street, matching the only reading of the paper's
own numbers that makes sense (5 or 400 topological hops would be a nonsensical pairing on any
real network).
Two things about momepy's network functions this module works around, not just uses:
nx.get_node_attributeskeys its result by momepy's own node identity, an(x, y)coordinate tuple. Handed straight topd.Series(...), pandas reads a dict of tuple keys as a MultiIndex rather than as one column of coordinate pairs, which breaks a later.reindex()againstnodeID-keyed data with an opaque error. Every extraction here goes throughnodeIDinstead, via a coordinate-to-nodeIDmap built once frommomepy.nx_to_gdf's stable node geometry.- Several functions have no
name=parameter and always write the same attribute (node_densityin particular:node_density/node_density_weighted, fixed). Calling one twice at two radii without reading the first result out in between would silently overwrite it — so this module processes one radius fully, in a fixed function order, reading every attribute out before moving to the next.
street_metrics
¶
street_metrics(etc: GeoDataFrame, streets: GeoDataFrame, buildings_for_profile: GeoDataFrame, *, profile_distance_m: float, profile_tick_length_m: float, node_radii_m: tuple[float, ...] = NODE_RADII_M) -> DataFrame
The 22 Street Descriptors & Connectivity columns, indexed like etc (unit_id).
streets is reset to a clean integer index first: momepy.gdf_to_nx/get_nearest_street
both key off it, and a stray non-default index from upstream cleaning would silently change
what "street index 8" means between the two calls. buildings_for_profile need not share
etc's index — momepy.street_profile only needs building geometry, not identity — so the
unfiltered building layer works and there is no reason to pass the ETC-reindexed one.
Source code in src/lczkit/morphometrics/streets.py
Contextual expansion¶
Opt-in: the 25th/50th/75th percentile of each primary attribute across neighbouring ETCs, via
momepy.percentile. Off by default (MorphometricsConfig.contextual). Unlike the paper, primary
attributes are kept once the expansion is computed rather than dropped.
lczkit.morphometrics.contextual
¶
The opt-in contextual expansion — Majer & Fleischmann (2026) §3.1.
Per primary attribute, the 25th/50th/75th percentile across neighbouring ETCs within
config.contextual_steps topological steps, via momepy.percentile (linearly weighted, "hazen"
interpolation — momepy's own default and the paper's own choice). Unlike the paper, which drops
the primary attributes once the expansion is computed, lczkit keeps both: MorphometricsConfig
docstring records this as a deliberate divergence, not an oversight.
contextual_expand
¶
contextual_expand(primary: DataFrame, graph: Graph, *, quantiles: Iterable[int] = (25, 50, 75)) -> DataFrame
{column}_p{q} for every column of primary and every q in quantiles.
graph should be the ETC contiguity graph expanded to the configured number of topological
steps (lczkit.morphometrics.graphs.etc_higher_order) — not self-weighted: the paper's own
wording is percentiles across neighbouring cells, so the focal cell's own value must not be
counted as one of its own neighbours.
Source code in src/lczkit/morphometrics/contextual.py
Registry¶
lczkit.morphometrics.registry
¶
What every morphometric attribute column means, in what unit, from which source.
Reuses lczkit.ucp.registry.ParameterSpec rather than duplicating it — a column's shape (name,
label, unit, description, reference) doesn't change because the module computing it does. Every
one of the 107 primary attributes here has a reference of MOMEPY (the formula and, where the
paper's own choice of which momepy call and which scale to use is what's being credited,
MAJER_2026) or COMPUTED_HERE for the two lczkit-defined quantities (area_*,
coverage_area_ratio_etc) that have no separate published definition beyond arithmetic.
Generated against docs/references/tables/majer_2026_morphometrics_menu.md — every name below
must appear as a row name in that table, and tests/test_morphometrics_registry_matches_menu.py
asserts it does, cell for cell, so the two cannot silently drift apart.
MAJER_2026
module-attribute
¶
Majer & Fleischmann (2026), "Predicting Local Climate Zones using Urban Morphometrics and Satellite Imagery", arXiv. Credited where the paper's own choice of scale or neighbourhood (rather than momepy's formula itself) is what a column represents — every weighted variant, and the two lczkit-computed granularity/coverage quantities the paper specifies without naming a momepy function.
PARAMETER_COLUMNS
module-attribute
¶
Column names compute_morphometrics() must emit, in registry order.
CONTEXTUAL_QUANTILES
module-attribute
¶
The paper's own percentiles for the contextual expansion.
spec
¶
spec(name: str) -> ParameterSpec
contextual_specs
¶
contextual_specs(quantiles: Iterable[int] = CONTEXTUAL_QUANTILES) -> tuple[ParameterSpec, ...]
ParameterSpecs for the opt-in contextual expansion: {name}_p{q} per primary attribute.
Generated from PARAMETERS the same way lczkit.ucp.registry.semantic_specs() generates its
config-driven columns — a description template referencing the primary spec's own
description, rather than 321 hand-written entries that would drift from the 107 they mirror.
Source code in src/lczkit/morphometrics/registry.py
Rasterizing¶
Not part of the paper. Builds a fine grid over the ETC layer's own bounds at the requested
resolution, reusing lczkit.units.overlay rather than a new vector-to-raster
library — the overlay-and-measure primitive already in the package is exactly what "which ETCs
does this pixel cover, and how much of each" is.
Three output formats. "gtiff" (default) and "cog" both go through rasterio's own GDAL
drivers. "zarr" goes through xarray/rioxarray rather than GDAL's own Zarr driver — GDAL's
driver writes the array correctly but stores the CRS only in a pam.aux.xml sidecar a plain
zarr/xarray reader never sees, which was measured directly rather than assumed and defeats
the interoperability a cloud-native format exists for. xr.open_zarr(path,
decode_coords="all"), or any other CF-aware reader, round-trips the CRS this package writes.
Optional geographic tiling. MorphometricsConfig.raster_tile_deg splits the output into a
geotessera-style tile grid (github.com/ucam-eo/geotessera) — tiles named
grid_{lon:.2f}_{lat:.2f} after their centre — instead of one file for the whole extent. The
expensive step (the area-weighted overlay) still runs exactly once; tiling only changes how the
resulting array is windowed and written. Adjacent tiles' shared edges are reprojected and rounded
to a pixel index exactly once, so they cannot leave a gap or overlap between two tiles — an
earlier version rounded each tile's box independently and did exactly that, caught by a test
that stitches tiled output back together and compares it to the untiled raster.
lczkit.morphometrics.raster
¶
Rasterizing morphometric attributes to a multiband raster at a user-defined resolution.
Not part of the paper — Majer & Fleischmann (2026) never rasterize their 2D attributes, only the
20-attribute subset feeding their fusion schemes (out of scope here, see the module docstring of
lczkit.morphometrics). This is a new lczkit capability: one raster, one band per attribute,
each pixel the area-weighted mean of whichever ETCs overlap it.
Reuses lczkit.units.overlay.unit_pieces rather than a new vector-to-raster library — the
overlay-and-measure primitive already in the package is exactly what "which ETCs does this pixel
cover, and how much of each" is. A local, purpose-built grid builder is used rather than stretching
GridUnits to a second contract: GridUnits takes a lon/lat bbox and estimates its own CRS,
which is the wrong shape for a grid built directly over an already-projected ETC layer's own
bounds at an arbitrary resolution.
Three output formats. "gtiff" (default, unchanged from the original single-file behaviour)
and "cog" both go through rasterio's own GDAL drivers — verified to need no new dependency.
"zarr" goes through xarray/rioxarray rather than GDAL's Zarr driver: measured directly
before choosing this route (see lczkit.config.RasterFormat) — GDAL's own driver writes the
array correctly but stores the CRS only in a GDAL-specific sidecar invisible to a plain
zarr/xarray reader, which defeats the interoperability a cloud-native format is for.
Optional tiling. tile_deg splits the raster into a geotessera-style geographic tile grid
(github.com/ucam-eo/geotessera) rather than one file covering the whole extent — see
lczkit.config.MorphometricsConfig.raster_tile_deg. The expensive step (the area-weighted overlay)
runs exactly once regardless of tiling; tiling only changes how the resulting array is windowed
and written.
RasterExportReport
dataclass
¶
RasterExportReport(resolution_m: float, n_rows: int, n_cols: int, band_names: tuple[str, ...], format: RasterFormat = 'gtiff', tile_deg: float | None = None, tiles: tuple[str, ...] = ())
What rasterize_attributes wrote, for the run manifest.
tiles
class-attribute
instance-attribute
¶
Filenames written under out_path when tile_deg is set; empty for a single-file write,
where out_path itself (known to the caller) is the whole answer.
as_manifest
¶
The manifest.morphometrics_raster entry describing this raster.
Source code in src/lczkit/morphometrics/raster.py
raster_filename
¶
raster_filename(format: RasterFormat) -> str
The single-file output name for format — a .zarr store is a directory, not a .tif.
rasterize_attributes
¶
rasterize_attributes(gdf: GeoDataFrame, resolution_m: float, out_path: Path, *, columns: list[str] | None = None, max_cells: int = 50000000, format: RasterFormat = 'gtiff', tile_deg: float | None = None, max_bytes: int = 4000000000, origin: tuple[float, float] | None = None) -> RasterExportReport
Write out_path as a multiband raster, one band per attribute of gdf, area-weighted.
columns defaults to every non-geometry column of gdf. Raises ValueError before building
the grid if it would exceed max_cells (pixel count) or max_bytes (the in-memory band
stack — every format now goes through one array built once, see _compute_band_stack) —
refusing is cheap; an unbounded allocation is not.
With tile_deg set, out_path is a directory and the output is one file per geographic tile
(lczkit.morphometrics.raster._tile_grid) instead of one file for the whole extent. Without
it, out_path is the single output file (or, for format="zarr", the single output store
directory) — the original, still-default behaviour.
origin anchors the pixel lattice to an external grid rather than to gdf's own bounds — see
_pixel_grid. Pass the top-left corner of the grid the output has to register against; the
extent is grown outward to that lattice, never cropped to it.
Source code in src/lczkit/morphometrics/raster.py
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 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 | |
refresh_raster
¶
refresh_raster(run_dir: Path, resolution_m: float, *, columns: list[str] | None = None, max_cells: int = 50000000, format: RasterFormat = 'gtiff', tile_deg: float | None = None, max_bytes: int = 4000000000) -> RasterExportReport
Re-derive the morphometrics raster(s) from an already-written run's morphometrics.parquet.
The one function both run_pipeline (when --morphometrics-resolution is given) and
lczkit morphometrics raster call — a run gets the same raster whether it was produced at
run time or requested afterwards at a different resolution or format, because both paths go
through this. Patches the manifest as JSON, the same way lczkit.output.gis's backfill does:
this can run against a manifest written by an older version of RunManifest, and
round-tripping it through today's model would rewrite fields the run never had.
A previous single-file or tiled output at a different format is never deleted — only appended
to manifest.outputs — matching the project's standing convention of adding rather than
overwriting a run's on-disk record (e.g. lczkit export's units.gpkg).
Source code in src/lczkit/morphometrics/raster.py
Report¶
lczkit.morphometrics.report
¶
What a morphometrics run produced, for the run manifest.
MorphometricsReport
dataclass
¶
MorphometricsReport(tessellation: TessellationReport, n_primary_attributes: int, contextual_enabled: bool, n_contextual_attributes: int)
Counts and settings a reader of the manifest needs to interpret morphometrics.parquet.
n_contextual_attributes
instance-attribute
¶
0 when contextual_enabled is False — the two fields are kept separate rather than one
being inferred from the other, so a manifest reader never has to know the rule.