API Reference — Core¶
The composition algebra. Importable as geotoolz.core.* and re-exported at
geotoolz.* for convenience.
For the model behind these primitives, read the Concepts page first.
Base¶
geotoolz.core._src.operator.Operator
¶
Base class for geotoolz operators.
Subclasses implement _apply(self, *args, **kwargs). The base
class dispatches __call__ to either _apply (eager mode) or
Node construction (graph-building mode) based on whether any
argument is a Node / Input.
Attributes:
| Name | Type | Description |
|---|---|---|
forbid_in_yaml |
bool
|
|
_terminal |
bool
|
|
Examples:
Implement a tiny operator::
class Scale(Operator):
def __init__(self, factor: float) -> None:
self.factor = factor
def _apply(self, gt):
return gt * self.factor
def get_config(self) -> dict:
return {"factor": self.factor}
Compose with |::
pipeline = Scale(0.5) | Scale(2.0) # Sequential([Scale(0.5), Scale(2.0)])
result = pipeline(gt)
Source code in src/geotoolz/core/_src/operator.py
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 | |
state: dict[str, Any]
property
¶
Return a JSON-serialisable state record for this operator.
__call__(*args: Any, **kwargs: Any) -> Any
¶
Dispatch on argument type.
If any positional argument is a graph node, returns a new Node
(graph-building mode). Otherwise calls _apply (eager mode).
Subclasses should override _apply, not __call__.
Source code in src/geotoolz/core/_src/operator.py
__or__(other: Operator) -> Sequential
¶
op1 | op2 returns Sequential([op1, op2]).
Flattens nested Sequential instances on the right-hand side so
a | (b | c) and (a | b) | c both produce a single
three-element Sequential.
Source code in src/geotoolz/core/_src/operator.py
from_state(state: dict[str, Any]) -> Operator
classmethod
¶
Reconstruct an operator from a state record.
Walks cls plus all transitive subclasses so calling
Subclass.from_state(state) resolves Subclass itself. Only
operators whose config is composed of JSON primitives are
reconstructible — operators that nest other operators in their
config (e.g. AppendIndex) emit debug/provenance payloads and
raise a clear RuntimeError here instead of letting the
constructor blow up with TypeError.
Source code in src/geotoolz/core/_src/operator.py
get_config() -> dict[str, Any]
¶
Return a JSON-serialisable dict of constructor args.
Override in subclasses to enable __repr__, pickling assertions,
and Hydra-zen builds() integration. Operators that hold user
closures should still return what config they can but additionally
set forbid_in_yaml = True.
Source code in src/geotoolz/core/_src/operator.py
geotoolz.core._src.operator.Carrier = Any
module-attribute
¶
Linear composition¶
geotoolz.core._src.sequential.Sequential
¶
Bases: Operator
Apply a list of operators in order, threading the output of each into the next.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
operators
|
list[Operator]
|
A list of |
required |
Raises:
| Type | Description |
|---|---|
TypeError
|
if any element of |
Examples:
Basic::
pipe = Sequential([Scale(0.5), Scale(2.0)])
assert pipe(gt) == gt # 0.5 * 2.0
Via the pipe operator::
pipe = Scale(0.5) | Scale(2.0)
Terminal op at the end is fine::
pipe = Sequential([NDVI(...), WriteCOG("/out.tif")])
Terminal op anywhere else raises::
Sequential([WriteCOG("/x"), NDVI(...)]) # TypeError
Source code in src/geotoolz/core/_src/sequential.py
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 | |
__getitem__(key: int | slice) -> Operator | Sequential
¶
Index or slice into the underlying operator list.
pipe[0] returns the first operator. pipe[1:3] returns a
new Sequential containing the slice.
Source code in src/geotoolz/core/_src/sequential.py
__or__(other: Operator) -> Sequential
¶
Append on the right; flatten nested Sequentials.
Sequential([a, b]) | c → Sequential([a, b, c])
Sequential([a, b]) | Sequential([c, d]) → Sequential([a, b, c, d])
Source code in src/geotoolz/core/_src/sequential.py
Graphs¶
geotoolz.core._src.graph.Input
dataclass
¶
A named entry point into a Graph.
Input instances are placeholders during graph construction.
Operator.__call__ recognises them as graph mode (same as Node).
Graph._apply consumes the keyword **inputs mapping by name.
The dataclass disables __eq__ so Input instances compare by
identity — necessary for the id(...)-keyed evaluation cache.
Source code in src/geotoolz/core/_src/graph.py
geotoolz.core._src.graph.Node
dataclass
¶
A non-input vertex in a Graph.
Created automatically by Operator.__call__ when any argument is an
Input or another Node. Carries the operator and its parents
(other Input / Node instances).
Like Input, equality is by identity for the evaluation cache.
Source code in src/geotoolz/core/_src/graph.py
geotoolz.core._src.graph.Graph
¶
Bases: Operator
A symbolic operator graph with multiple inputs and outputs.
Construction is implicit — calling operators on Input / Node
instances builds the graph; Graph(inputs=..., outputs=...) wraps
the result. _apply(**inputs) evaluates the graph in topological
order.
Inherits from Operator so a Graph satisfies the same interface
as any other operator. Operator.__call__ dispatches keyword args
straight through to Graph._apply; positional args are unused.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
inputs
|
dict[str, Input]
|
Map of |
required |
outputs
|
dict[str, Node | Input]
|
Map of |
required |
Examples:
Two-input, two-output graph::
img = Input("image")
ref = Input("reference")
ndvi = NDVI(red_idx=2, nir_idx=3)(img)
rmse = RMSE(axis=(-2, -1))(ndvi, ref)
g = Graph(
inputs={"image": img, "reference": ref},
outputs={"ndvi": ndvi, "rmse": rmse},
)
result = g(image=img_gt, reference=ref_gt)
# {"ndvi": GeoTensor, "rmse": scalar}
Raises:
| Type | Description |
|---|---|
ValueError
|
if the graph contains a cycle, if an output node
isn't reachable from any input, or if an |
Source code in src/geotoolz/core/_src/graph.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 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 | |
get_config() -> dict[str, Any]
¶
Best-effort config — node operators' configs, by output name.
Graphs are inherently runtime-defined (the topology comes from Python object identity), so the config is a debug repr rather than a faithful YAML round-trip. Future YAML support would store the topology as a list of (op, parent-keys) records.
Source code in src/geotoolz/core/_src/graph.py
geotoolz.core._src.composition.Fanout
¶
Bases: Operator
One input → dict of outputs (sugar over Graph).
Each branch is applied to the same input GeoTensor; the outputs are returned as a dict keyed by the branch name.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
branches
|
dict[str, Operator]
|
Map of |
required |
Examples:
Compute three indices from one scene with one read::
products = Fanout({
"ndvi": NDVI(red_idx=2, nir_idx=3),
"ndwi": NDWI(green_idx=1, nir_idx=3),
"rgb": S2_L2A_RGB(),
})(gt)
# {"ndvi": GeoTensor, "ndwi": GeoTensor, "rgb": GeoTensor}
Equivalent Graph form (more verbose, identical result)::
img = Input("image")
g = Graph(
inputs={"image": img},
outputs={
"ndvi": NDVI(red_idx=2, nir_idx=3)(img),
"ndwi": NDWI(green_idx=1, nir_idx=3)(img),
"rgb": S2_L2A_RGB()(img),
},
)
Raises:
| Type | Description |
|---|---|
TypeError
|
if any branch is not an |
Source code in src/geotoolz/core/_src/composition.py
Inference¶
geotoolz.core._src.model.ModelOp
¶
Bases: Operator
Wrap any callable as an Operator.
Materialises the GeoTensor to a plain np.ndarray (via
np.asarray) before handing it to the model — frameworks that
strip the subclass (torch, JAX, sklearn) don't care, and frameworks
that preserve it (numpy proper) still see something sensible.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
model
|
Any
|
Any object that can be called as |
required |
method
|
str
|
Method name to invoke on |
'__call__'
|
batch_size
|
int | None
|
If set, split the input along axis 0 into chunks of this size, call the model once per chunk, concatenate the results along axis 0. Useful when the model can't fit the whole input in GPU memory. |
None
|
Note
forbid_in_yaml = True — the model is a runtime object and
won't round-trip to YAML. Users typically pin a model artifact
(state-dict + class config) themselves.
Examples:
Inference with a sklearn classifier::
op = ModelOp(rf_clf, method="predict")
preds = op(features_gt)
Batched inference with a torch model::
op = ModelOp(unet_model, batch_size=8)
preds = op(chips_gt) # iterates 8 chips at a time
Source code in src/geotoolz/core/_src/model.py
Observers — identity with side effects¶
geotoolz.core._src.observers.Tap
¶
Bases: Operator
Identity operator with a side effect.
Calls fn(gt) and returns gt unchanged. The return value of
fn is ignored — Tap is for side effects, not transforms. If
you want to transform, use Lambda or write a real Operator.
forbid_in_yaml = True because the callback closure can't
round-trip.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
fn
|
Callable[[Carrier], Any]
|
A callable |
required |
name
|
str
|
Display name for |
'tap'
|
Examples:
Log NaN fraction between steps::
Sequential([
MaskClouds(...),
Tap(lambda gt: print(f"NaN: {np.isnan(gt).mean():.1%}")),
NDVI(...),
])
Source code in src/geotoolz/core/_src/observers.py
geotoolz.core._src.observers.Snapshot
¶
Controller that produces snapshot-taking operators.
Not an Operator itself — Snapshot.at(key) returns the operator
you drop into a Sequential. After the pipeline runs, every named
intermediate is available via snap[key].
Stores references, not copies — if a downstream op mutates the array in place, your snapshot sees the mutation too. Add explicit copies in exploratory work if needed.
Examples:
::
snap = Snapshot()
pipe = Sequential([
op1, snap.at("after_op1"),
op2, snap.at("after_op2"),
])
pipe(gt)
print(snap.keys()) # dict_keys(["after_op1", "after_op2"])
inspect = snap["after_op1"]
Source code in src/geotoolz/core/_src/observers.py
geotoolz.core._src.observers.ShapeTrace
¶
Bases: Operator
Log shape, dtype, CRS, transform at every step.
Drop one between steps of a Sequential to see what's happening to
the carrier. Optional mode="diff_only" skips lines that don't
change anything from the previous trace.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
printer
|
Callable[[str], Any]
|
Callable used to print each line. Default
|
print
|
mode
|
str
|
|
'every'
|
Examples:
::
Sequential([
ShapeTrace(),
op1,
ShapeTrace(),
op2,
ShapeTrace(),
])(gt)
Source code in src/geotoolz/core/_src/observers.py
Control flow¶
geotoolz.core._src.control.Branch
¶
Bases: Operator
Apply if_true when predicate(gt) is truthy, else
if_false.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
predicate
|
Callable[[Carrier], bool]
|
Callable |
required |
if_true
|
Operator
|
Operator applied when the predicate returns truthy. |
required |
if_false
|
Operator | None
|
Operator applied otherwise. Default |
None
|
Examples:
Only correct atmospherically if the scene is reasonably clear::
Sequential([
Branch(
predicate=lambda gt: cloud_fraction(gt) < 0.3,
if_true=TOAToBOA(...),
if_false=Identity(),
),
NDVI(...),
])
Source code in src/geotoolz/core/_src/control.py
geotoolz.core._src.control.Switch
¶
Bases: Operator
Multi-way dispatch on key(gt).
Computes k = key(gt), then runs cases[k](gt). If k is not
in cases, runs default(gt).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
key
|
Callable[[Carrier], Any]
|
Callable |
required |
cases
|
dict[Any, Operator]
|
Map of |
required |
default
|
Operator | None
|
Operator applied when no case matches. Default
|
None
|
Examples:
Cross-sensor pipeline::
Switch(
key=lambda gt: gt.metadata["sensor"],
cases={
"S2": S2_L2A_NDVI(),
"Landsat": L8_BOA_NDVI(),
},
)
Source code in src/geotoolz/core/_src/control.py
Building blocks¶
geotoolz.core._src.building_blocks.Identity
¶
Bases: Operator
Explicit no-op. Returns its input unchanged.
Use as a default arm in Branch / Switch rather than passing
None — it serialises, composes, shows up in repr(), and makes
pipeline structure self-documenting.
Examples:
Branch(predicate=is_clean, if_true=Identity(), if_false=cleanup)
Source code in src/geotoolz/core/_src/building_blocks.py
geotoolz.core._src.building_blocks.Const
¶
Bases: Operator
Return a fixed value regardless of input.
Useful for golden test fixtures, as a synthetic source in a
Switch default, or anywhere the pipeline needs a stand-in.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
value
|
Any
|
The value to return on every call. Typically a |
required |
Examples:
Build a deterministic test pipeline::
test_pipeline = Sequential([
Const(synthetic_gt), # ignores input
real_pipeline,
])
Source code in src/geotoolz/core/_src/building_blocks.py
geotoolz.core._src.building_blocks.Lambda
¶
Bases: Operator
Inline-function escape hatch.
Holds a user callable and applies it. The callable's signature is
fn(gt) -> result — same contract as Operator._apply. Use when
writing a full Operator subclass would be overkill for a one-off
transform.
forbid_in_yaml = True — the closure cannot round-trip to YAML
faithfully. get_config() returns a debug repr only. The first
time a Lambda recurs in your code, promote it to a real Operator
subclass.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
fn
|
Callable[[Any], Any]
|
A callable |
required |
name
|
str
|
Display name for |
'lambda'
|
Examples:
Lambda(lambda gt: gt * 0.0001, name="scale_to_reflectance")
Source code in src/geotoolz/core/_src/building_blocks.py
geotoolz.core._src.building_blocks.Sink
¶
Bases: Operator
Composable terminal write — performs a side effect and returns the input unchanged.
Unlike a write op that returns None and breaks the pipe,
Sink(write_fn) keeps the GeoTensor flowing. Useful for
checkpointing long pipelines, debugging ("what did step 3 produce?"),
and branching analysis (write an intermediate, continue with the
final product).
forbid_in_yaml = True — the write callable is a closure.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
write_fn
|
Callable[[Any], Any]
|
A callable |
required |
name
|
str
|
Display name for |
'sink'
|
Examples:
Sink(lambda gt: georeader.save_cog(gt, "/intermediate.tif"))