tensor-ee 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- tensor_ee/__init__.py +28 -0
- tensor_ee/_swin_parameters.py +304 -0
- tensor_ee/blocks/__init__.py +0 -0
- tensor_ee/blocks/raster/__init__.py +36 -0
- tensor_ee/blocks/raster/activations.py +183 -0
- tensor_ee/blocks/raster/conv.py +183 -0
- tensor_ee/blocks/raster/merge.py +66 -0
- tensor_ee/blocks/raster/outputs.py +211 -0
- tensor_ee/blocks/raster/pooling.py +182 -0
- tensor_ee/blocks/raster/resample.py +64 -0
- tensor_ee/blocks/window/__init__.py +44 -0
- tensor_ee/blocks/window/activations.py +54 -0
- tensor_ee/blocks/window/attention.py +366 -0
- tensor_ee/blocks/window/conv.py +323 -0
- tensor_ee/blocks/window/linear.py +129 -0
- tensor_ee/blocks/window/merge.py +67 -0
- tensor_ee/blocks/window/normalization.py +197 -0
- tensor_ee/blocks/window/patch.py +362 -0
- tensor_ee/blocks/window/pooling.py +275 -0
- tensor_ee/blocks/window/resample.py +286 -0
- tensor_ee/blocks/window/segformer.py +718 -0
- tensor_ee/blocks/window/swin.py +602 -0
- tensor_ee/blocks/window/swin_upernet_config.py +361 -0
- tensor_ee/blocks/window/upernet.py +1016 -0
- tensor_ee/comparison.py +213 -0
- tensor_ee/compatibility.py +180 -0
- tensor_ee/compiler.py +356 -0
- tensor_ee/converter/__init__.py +72 -0
- tensor_ee/converter/build.py +120 -0
- tensor_ee/converter/convert.py +388 -0
- tensor_ee/converter/export.py +846 -0
- tensor_ee/converter/graph.py +417 -0
- tensor_ee/converter/operations.py +790 -0
- tensor_ee/converter/plan.py +675 -0
- tensor_ee/converter/recognizers.py +1273 -0
- tensor_ee/converter/request.py +146 -0
- tensor_ee/converter/run.py +950 -0
- tensor_ee/core/__init__.py +8 -0
- tensor_ee/core/errors.py +25 -0
- tensor_ee/core/grid.py +323 -0
- tensor_ee/core/metadata.py +398 -0
- tensor_ee/core/ops.py +336 -0
- tensor_ee/core/precision.py +133 -0
- tensor_ee/core/tensor.py +129 -0
- tensor_ee/examples/__init__.py +1 -0
- tensor_ee/examples/nonwater.py +251 -0
- tensor_ee/layouts/__init__.py +17 -0
- tensor_ee/layouts/boundaries.py +103 -0
- tensor_ee/layouts/raster.py +117 -0
- tensor_ee/layouts/shift.py +226 -0
- tensor_ee/layouts/window.py +201 -0
- tensor_ee/models/__init__.py +16 -0
- tensor_ee/models/fixtures.py +119 -0
- tensor_ee/models/segformer_b0/__init__.py +9 -0
- tensor_ee/models/segformer_b0/configuration.py +209 -0
- tensor_ee/models/segformer_b0/decoder.py +130 -0
- tensor_ee/models/segformer_b0/encoder.py +90 -0
- tensor_ee/models/swin_t224/__init__.py +30 -0
- tensor_ee/models/swin_t224/configuration.py +103 -0
- tensor_ee/models/swin_t224/decoder.py +53 -0
- tensor_ee/models/swin_t224/encoder.py +50 -0
- tensor_ee/onnx_import.py +431 -0
- tensor_ee/outputs.py +16 -0
- tensor_ee/primitives.py +110 -0
- tensor_ee/receptive_field.py +257 -0
- tensor_ee/resnet_models.py +185 -0
- tensor_ee/segwater_inference.py +235 -0
- tensor_ee/segwater_preprocessing.py +182 -0
- tensor_ee/segwater_pytorch.py +249 -0
- tensor_ee/semantics.py +389 -0
- tensor_ee/smp.py +155 -0
- tensor_ee/swin.py +197 -0
- tensor_ee/swin_workflow.py +470 -0
- tensor_ee/tiling.py +287 -0
- tensor_ee/weight_assets.py +371 -0
- tensor_ee/weight_mapping.py +51 -0
- tensor_ee/weights/__init__.py +14 -0
- tensor_ee/weights/assets.py +160 -0
- tensor_ee/weights/constants.py +156 -0
- tensor_ee-0.1.0.dist-info/METADATA +172 -0
- tensor_ee-0.1.0.dist-info/RECORD +84 -0
- tensor_ee-0.1.0.dist-info/WHEEL +5 -0
- tensor_ee-0.1.0.dist-info/licenses/LICENSE +21 -0
- tensor_ee-0.1.0.dist-info/top_level.txt +1 -0
tensor_ee/__init__.py
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""Experimental PyTorch-compatible building blocks for native Earth Engine."""
|
|
2
|
+
|
|
3
|
+
from importlib.metadata import PackageNotFoundError as _PackageNotFoundError, version as _distribution_version
|
|
4
|
+
|
|
5
|
+
try:
|
|
6
|
+
__version__ = _distribution_version("tensor-ee")
|
|
7
|
+
except _PackageNotFoundError:
|
|
8
|
+
__version__ = "0.1.0" # Source checkouts need no installed distribution metadata.
|
|
9
|
+
|
|
10
|
+
from .comparison import PixelTensor, compare_segmentation
|
|
11
|
+
from .compatibility import CompatibilityReport, check_compatibility
|
|
12
|
+
from .compiler import CompiledModel, Grid, Raster, UnsupportedOperation, WeightProvider, compile_model
|
|
13
|
+
from .primitives import (concatenate, conv2d, eval_dropout, from_image, gelu, leaky_relu, nearest_upsample,
|
|
14
|
+
pool2d, relu, residual_add, sigmoid, silu, softmax, tanh)
|
|
15
|
+
from .outputs import SegmentationOutputs, binary_threshold, segmentation_outputs
|
|
16
|
+
from .weight_assets import FeatureCollectionWeightProvider, effective_convolutions, export_weights
|
|
17
|
+
from .weight_mapping import MatchedWeightProvider
|
|
18
|
+
|
|
19
|
+
__all__ = [
|
|
20
|
+
"PixelTensor", "compare_segmentation",
|
|
21
|
+
"CompatibilityReport", "check_compatibility",
|
|
22
|
+
"CompiledModel", "Grid", "Raster", "UnsupportedOperation", "WeightProvider",
|
|
23
|
+
"compile_model", "from_image", "conv2d", "pool2d", "nearest_upsample",
|
|
24
|
+
"concatenate", "residual_add", "relu", "sigmoid", "leaky_relu", "silu", "gelu", "tanh", "eval_dropout", "softmax",
|
|
25
|
+
"SegmentationOutputs", "segmentation_outputs", "binary_threshold",
|
|
26
|
+
"FeatureCollectionWeightProvider", "MatchedWeightProvider",
|
|
27
|
+
"effective_convolutions", "export_weights",
|
|
28
|
+
]
|
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
"""Local accounting and exact asset record mapping for the Swin-UPerNet composites.
|
|
2
|
+
|
|
3
|
+
The fixed Swin-T224 adapter (``tensor_ee.swin``) and workflow (``tensor_ee.swin_workflow``)
|
|
4
|
+
use ``SwinTParameters`` and the loaders here, at Swin-T224's settings. The schema, the
|
|
5
|
+
checkpoint mapping, the parameter checks and the weight records also take a
|
|
6
|
+
``SwinUPerNetConfig`` (``tensor_ee.models.swin_t224.configuration``; default ``SWIN_T224``):
|
|
7
|
+
``parameter_records`` yields the ``ConvolutionWeights`` records of any configuration, under
|
|
8
|
+
the timm and SMP names the composites' loader (``with_manifest``) reads, in the v2 manifest
|
|
9
|
+
format (``native_ee_featurecollection_weights_v2``) that ``export_weights`` writes.
|
|
10
|
+
Swin-T224's 189 records are unchanged."""
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
from dataclasses import dataclass
|
|
14
|
+
import hashlib
|
|
15
|
+
import io
|
|
16
|
+
import json
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
import re
|
|
19
|
+
from typing import Any, Literal, Mapping
|
|
20
|
+
|
|
21
|
+
import numpy as np
|
|
22
|
+
import torch
|
|
23
|
+
|
|
24
|
+
from .core.errors import UnsupportedOperation
|
|
25
|
+
from .models.swin_t224.configuration import DEPTHS, SWIN_T224, SwinUPerNetConfig, block_shifts
|
|
26
|
+
from .weights.assets import ConvolutionWeights, FeatureCollectionWeightProvider
|
|
27
|
+
from .weights.constants import ServerArray
|
|
28
|
+
|
|
29
|
+
ENC_PREFIX = "encoder.model."
|
|
30
|
+
PE_PREFIX = ENC_PREFIX + "patch_embed."
|
|
31
|
+
ENCODER_NAME = "tu-swin_tiny_patch4_window7_224"
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def parameter_shapes(config: SwinUPerNetConfig = SWIN_T224):
|
|
35
|
+
"""Complete inference schema in the historical three-map layouts (encoder groups, patch
|
|
36
|
+
embedding, decoder, BatchNorm counters): ``config.parameter_shapes()``; Swin-T224's by
|
|
37
|
+
default."""
|
|
38
|
+
return config.parameter_shapes()
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def encoder_prefix(group):
|
|
42
|
+
if group.startswith("pm"):
|
|
43
|
+
return f"layers.{int(group[2:])}.downsample."
|
|
44
|
+
s, b = group[1:].split("b")
|
|
45
|
+
return f"layers.{int(s)-1}.blocks.{b}."
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def checkpoint_shapes(config: SwinUPerNetConfig = SWIN_T224):
|
|
49
|
+
"""The SMP UPerNet checkpoint's tensor shapes (``encoder.model.layers_{s}...``,
|
|
50
|
+
``encoder.model.patch_embed.*`` with the Conv2d weight [embed_dim, bands, patch, patch],
|
|
51
|
+
the decoder and head, and the BatchNorm counters)."""
|
|
52
|
+
enc, pe, dec, counters = parameter_shapes(config)
|
|
53
|
+
out = {ENC_PREFIX + encoder_prefix(g).replace("layers.", "layers_", 1) + n: shape
|
|
54
|
+
for g, params in enc.items() for n, shape in params.items()}
|
|
55
|
+
conv = (config.embed_dim, config.in_chans, config.patch, config.patch)
|
|
56
|
+
out.update({PE_PREFIX+n: (conv if n == "proj.weight" else shape) for n, shape in pe.items()})
|
|
57
|
+
out.update(dec)
|
|
58
|
+
out.update(counters)
|
|
59
|
+
return out
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _sha(value, name):
|
|
63
|
+
if not isinstance(value, str) or re.fullmatch(r"[0-9a-f]{64}", value) is None:
|
|
64
|
+
raise UnsupportedOperation(f"{name} must be a lowercase SHA-256 digest")
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _model(config: SwinUPerNetConfig) -> str:
|
|
68
|
+
"""The model's name in messages: "fixed Swin-T" (Swin-T224, the historical messages) or
|
|
69
|
+
"configured Swin-UPerNet"."""
|
|
70
|
+
return "fixed Swin-T" if config == SWIN_T224 else "configured Swin-UPerNet"
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def validate_state(state, config: SwinUPerNetConfig = SWIN_T224):
|
|
74
|
+
shapes = checkpoint_shapes(config)
|
|
75
|
+
if not isinstance(state, Mapping) or set(state) != set(shapes):
|
|
76
|
+
got = set(state) if isinstance(state, Mapping) else set()
|
|
77
|
+
raise UnsupportedOperation(f"{_model(config)} checkpoint keys differ: missing={sorted(set(shapes)-got)}, extra={sorted(got-set(shapes))}")
|
|
78
|
+
for k, shape in shapes.items():
|
|
79
|
+
v = state[k]
|
|
80
|
+
if not isinstance(v, torch.Tensor) or tuple(v.shape) != shape:
|
|
81
|
+
raise UnsupportedOperation(f"{k}: expected tensor shape {shape}")
|
|
82
|
+
if k.endswith("num_batches_tracked"):
|
|
83
|
+
if v.dtype != torch.int64 or v.item() < 0:
|
|
84
|
+
raise UnsupportedOperation(f"{k}: expected a nonnegative int64 BatchNorm counter")
|
|
85
|
+
elif v.dtype != torch.float32 or not torch.isfinite(v).all():
|
|
86
|
+
raise UnsupportedOperation(f"{k}: expected finite float32 coefficients")
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def read_state(checkpoint: Path, expected_checkpoint_sha256: str):
|
|
90
|
+
_sha(expected_checkpoint_sha256, "expected_checkpoint_sha256")
|
|
91
|
+
# An open file can still be changed in place. Hash and deserialize one
|
|
92
|
+
# immutable snapshot so the verified digest binds the returned coefficients.
|
|
93
|
+
source_data = Path(checkpoint).read_bytes()
|
|
94
|
+
if hashlib.sha256(source_data).hexdigest() != expected_checkpoint_sha256:
|
|
95
|
+
raise UnsupportedOperation("checkpoint SHA-256 differs from expected_checkpoint_sha256")
|
|
96
|
+
checkpoint_data = torch.load(io.BytesIO(source_data), map_location="cpu", weights_only=True)
|
|
97
|
+
if not isinstance(checkpoint_data, Mapping) or "model_state_dict" not in checkpoint_data:
|
|
98
|
+
raise UnsupportedOperation("expected checkpoint with model_state_dict")
|
|
99
|
+
state = checkpoint_data["model_state_dict"]
|
|
100
|
+
validate_state(state)
|
|
101
|
+
return state
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def parameters_from_state(state, config: SwinUPerNetConfig = SWIN_T224):
|
|
105
|
+
"""Re-key only, preserving all coefficient values and original flatten order. The
|
|
106
|
+
patch embedding's Conv2d weight becomes [embed_dim, bands x patch^2], flattened
|
|
107
|
+
(c, ky, kx)."""
|
|
108
|
+
validate_state(state, config)
|
|
109
|
+
enc_shapes, _, _, _ = parameter_shapes(config)
|
|
110
|
+
enc, pe, dec = {g: {} for g in enc_shapes}, {}, {}
|
|
111
|
+
for k, tensor in state.items():
|
|
112
|
+
if k.endswith("num_batches_tracked"):
|
|
113
|
+
continue
|
|
114
|
+
v = tensor.detach().cpu().numpy().copy()
|
|
115
|
+
if k.startswith(PE_PREFIX):
|
|
116
|
+
n = k[len(PE_PREFIX):]
|
|
117
|
+
pe[n] = v.reshape(config.embed_dim, config.patch_columns) if n == "proj.weight" else v
|
|
118
|
+
elif k.startswith(ENC_PREFIX):
|
|
119
|
+
stage, rest = k[len(ENC_PREFIX+"layers_"):].split(".", 1)
|
|
120
|
+
if rest.startswith("blocks."):
|
|
121
|
+
b, n = rest[len("blocks."):].split(".", 1)
|
|
122
|
+
enc[f"s{int(stage)+1}b{b}"][n] = v
|
|
123
|
+
else:
|
|
124
|
+
enc[f"pm{int(stage)}"][rest[len("downsample."):]] = v
|
|
125
|
+
else:
|
|
126
|
+
dec[k] = v
|
|
127
|
+
return enc, pe, dec
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def _validate_map(values, shapes, name, model="fixed Swin-T"):
|
|
131
|
+
if not isinstance(values, Mapping) or set(values) != set(shapes):
|
|
132
|
+
raise UnsupportedOperation(f"{name}: {model} parameter names differ")
|
|
133
|
+
for k, shape in shapes.items():
|
|
134
|
+
value = values[k]
|
|
135
|
+
if isinstance(value, ServerArray):
|
|
136
|
+
# Decoder asset convolutions use the historical flattened [Cout,Cin*k*k] record.
|
|
137
|
+
accepted = ((shape[0], int(np.prod(shape[1:]))),) if len(shape) == 4 else (shape,)
|
|
138
|
+
if tuple(value.shape) not in accepted:
|
|
139
|
+
raise UnsupportedOperation(f"{name}.{k}: expected shape {shape}")
|
|
140
|
+
elif not isinstance(value, np.ndarray) or value.shape != shape or value.dtype != np.float32 or not np.isfinite(value).all():
|
|
141
|
+
raise UnsupportedOperation(f"{name}.{k}: expected finite float32 array of shape {shape}")
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def validate_parameters(encoder, patch, decoder, config: SwinUPerNetConfig = SWIN_T224) -> None:
|
|
145
|
+
"""The parameter checks: the three maps hold exactly the configuration's names (the
|
|
146
|
+
encoder groups without a final norm), each a finite float32 NumPy array of its schema
|
|
147
|
+
shape or a ``ServerArray`` of that shape (a convolution as [Cout, Cin k k])."""
|
|
148
|
+
enc, pe, dec, _ = parameter_shapes(config)
|
|
149
|
+
model = _model(config)
|
|
150
|
+
if not isinstance(encoder, Mapping) or set(encoder) != set(enc):
|
|
151
|
+
raise UnsupportedOperation(f"encoder: {model} groups differ (raw C4 has no final norm)")
|
|
152
|
+
for g in enc:
|
|
153
|
+
_validate_map(encoder[g], enc[g], g, model)
|
|
154
|
+
_validate_map(patch, pe, "patch_embed", model)
|
|
155
|
+
_validate_map(decoder, dec, "decoder", model)
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
@dataclass(frozen=True)
|
|
159
|
+
class SwinTParameters:
|
|
160
|
+
"""Validated Swin-T224 parameters: the fixed adapter's and workflow's input."""
|
|
161
|
+
|
|
162
|
+
encoder: Mapping[str, Mapping[str, np.ndarray | ServerArray]]
|
|
163
|
+
patch_embed: Mapping[str, np.ndarray | ServerArray]
|
|
164
|
+
decoder: Mapping[str, np.ndarray | ServerArray]
|
|
165
|
+
checkpoint_sha256: str
|
|
166
|
+
manifest_sha256: str | None = None
|
|
167
|
+
|
|
168
|
+
def __post_init__(self):
|
|
169
|
+
self.validate()
|
|
170
|
+
|
|
171
|
+
def validate(self):
|
|
172
|
+
_sha(self.checkpoint_sha256, "checkpoint_sha256")
|
|
173
|
+
if self.manifest_sha256 is not None:
|
|
174
|
+
_sha(self.manifest_sha256, "manifest_sha256")
|
|
175
|
+
validate_parameters(self.encoder, self.patch_embed, self.decoder, SWIN_T224)
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def records_and_locations(encoder, patch, decoder):
|
|
179
|
+
"""The existing row/chunk/record format, with each source tensor's read location."""
|
|
180
|
+
records, locations = {}, {}
|
|
181
|
+
groups = [(g, p, encoder_prefix(g)) for g, p in encoder.items()]
|
|
182
|
+
groups += [("patch_embed", patch, "patch_embed."), ("decoder", decoder, "")]
|
|
183
|
+
for group, params, prefix in groups:
|
|
184
|
+
for n, value in params.items():
|
|
185
|
+
base = n.removesuffix(".bias")
|
|
186
|
+
weight_name = base + ".weight"
|
|
187
|
+
if n.endswith(".bias") and weight_name in params and params[weight_name].ndim >= 2:
|
|
188
|
+
key, part = prefix+base, "bias"
|
|
189
|
+
elif value.ndim == 1:
|
|
190
|
+
key, part = prefix+n, "vector"
|
|
191
|
+
elif n.endswith(".weight"):
|
|
192
|
+
key, part = prefix+n.removesuffix(".weight"), "weight"
|
|
193
|
+
else:
|
|
194
|
+
key, part = prefix+n, "matrix"
|
|
195
|
+
locations[(group,n)] = (key, part, value.shape)
|
|
196
|
+
if part == "bias":
|
|
197
|
+
continue
|
|
198
|
+
matrix = value.reshape(1,-1) if part == "vector" else value.reshape(value.shape[0],-1)
|
|
199
|
+
bias = params.get(n.removesuffix(".weight")+".bias") if part == "weight" else None
|
|
200
|
+
if key in records:
|
|
201
|
+
raise UnsupportedOperation(f"duplicate parameter record {key}")
|
|
202
|
+
records[key] = ConvolutionWeights(key, matrix.copy(), None if bias is None else bias.copy(), "float32", prefix+n)
|
|
203
|
+
return records, locations
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def parameter_records(encoder, patch, decoder, config: SwinUPerNetConfig = SWIN_T224) -> list[ConvolutionWeights]:
|
|
207
|
+
"""The ``ConvolutionWeights`` records of a configuration's local parameters, for
|
|
208
|
+
``export_weights`` (a v2 manifest) and ``with_manifest`` (the composites' loader).
|
|
209
|
+
|
|
210
|
+
The three maps are the historical layouts (``parameters_from_state``): encoder groups
|
|
211
|
+
``s{s}b{b}`` / ``pm{n}`` with timm names, the patch embedding, and the decoder and head
|
|
212
|
+
with SMP names. They must pass ``validate_parameters`` for ``config`` and be NumPy arrays
|
|
213
|
+
(server arrays cannot be re-exported). The records are ``records_and_locations``'s, in
|
|
214
|
+
the maps' order: for Swin-T224 exactly ``swin_workflow.weight_records``'s 189. No
|
|
215
|
+
requests occur."""
|
|
216
|
+
validate_parameters(encoder, patch, decoder, config)
|
|
217
|
+
maps = [*encoder.values(), patch, decoder]
|
|
218
|
+
if any(not isinstance(value, np.ndarray) for values in maps for value in values.values()):
|
|
219
|
+
raise UnsupportedOperation("parameter_records requires local NumPy parameters, without a manifest")
|
|
220
|
+
records, _ = records_and_locations(encoder, patch, decoder)
|
|
221
|
+
return list(records.values())
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def with_manifest(encoder, patch, decoder, manifest, checkpoint_sha256):
|
|
225
|
+
if not isinstance(manifest, Mapping) or manifest.get("source_checkpoint_sha256") != checkpoint_sha256:
|
|
226
|
+
raise UnsupportedOperation("manifest source_checkpoint_sha256 differs from the checkpoint")
|
|
227
|
+
if manifest.get("format") != "native_ee_featurecollection_weights_v2" or manifest.get("status") != "ready":
|
|
228
|
+
raise UnsupportedOperation("a completed native_ee_featurecollection_weights_v2 manifest is required")
|
|
229
|
+
records, locations = records_and_locations(encoder, patch, decoder)
|
|
230
|
+
layers = manifest.get("layers")
|
|
231
|
+
if not isinstance(layers, Mapping) or set(layers) != set(records):
|
|
232
|
+
raise UnsupportedOperation("manifest must account for every fixed Swin-T record exactly once")
|
|
233
|
+
for key, record in records.items():
|
|
234
|
+
meta = layers[key]
|
|
235
|
+
if not isinstance(meta, Mapping) or (meta.get("shape") != list(record.shape) or meta.get("dtype") != "float32"
|
|
236
|
+
or meta.get("bias") is not (record.bias is not None) or meta.get("sha256") != record.sha256):
|
|
237
|
+
raise UnsupportedOperation(f"manifest record {key}: shape/dtype/bias/fingerprint differs from checkpoint")
|
|
238
|
+
assets = meta.get("assets")
|
|
239
|
+
if not isinstance(assets, list) or not assets or any(not isinstance(v,str) or not v for v in assets) or len(set(assets)) != len(assets):
|
|
240
|
+
raise UnsupportedOperation(f"manifest record {key}: explicit unique asset IDs are required")
|
|
241
|
+
listing = json.dumps(sorted((key, r.sha256) for key,r in records.items()), separators=(",",":"))
|
|
242
|
+
if manifest.get("manifest_hash") != hashlib.sha256(listing.encode()).hexdigest():
|
|
243
|
+
raise UnsupportedOperation("manifest_hash differs from the exact parameter record listing")
|
|
244
|
+
provider = FeatureCollectionWeightProvider(manifest)
|
|
245
|
+
arrays = {k: provider.arrays(k) for k in records}
|
|
246
|
+
output = {g:{} for g in encoder}
|
|
247
|
+
output.update(patch_embed={},decoder={})
|
|
248
|
+
for (g,n),(key,part,shape) in locations.items():
|
|
249
|
+
weight, bias = arrays[key]
|
|
250
|
+
if part == "bias":
|
|
251
|
+
array = bias.reshape(list(shape))
|
|
252
|
+
elif part == "vector":
|
|
253
|
+
array = weight.reshape(list(shape))
|
|
254
|
+
else:
|
|
255
|
+
array = weight
|
|
256
|
+
shape = tuple(records[key].shape)
|
|
257
|
+
output[g][n] = ServerArray(array,shape)
|
|
258
|
+
sha = hashlib.sha256(json.dumps(manifest,sort_keys=True,separators=(",",":"),allow_nan=False).encode()).hexdigest()
|
|
259
|
+
return {g:output[g] for g in encoder},output['patch_embed'],output['decoder'],sha
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
def load_swin_t224_parameters(checkpoint: Path, *, expected_checkpoint_sha256: str,
|
|
263
|
+
manifest: Mapping[str, Any] | None = None) -> SwinTParameters:
|
|
264
|
+
state = read_state(checkpoint, expected_checkpoint_sha256)
|
|
265
|
+
enc, pe, dec = parameters_from_state(state)
|
|
266
|
+
manifest_sha = None
|
|
267
|
+
if manifest is not None:
|
|
268
|
+
enc, pe, dec, manifest_sha = with_manifest(enc, pe, dec, manifest, expected_checkpoint_sha256)
|
|
269
|
+
return SwinTParameters(enc, pe, dec, expected_checkpoint_sha256, manifest_sha)
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
def build_model(state, attention="default"):
|
|
273
|
+
"""Strict local reference model; no download, EE initialization, or forward pass."""
|
|
274
|
+
if attention not in ("default","explicit"):
|
|
275
|
+
raise UnsupportedOperation("attention must be 'default' or 'explicit'")
|
|
276
|
+
validate_state(state)
|
|
277
|
+
import segmentation_models_pytorch as smp
|
|
278
|
+
# Construction must not round the checkpoint through an ambient half dtype
|
|
279
|
+
# or place the reference on an ambient accelerator default device.
|
|
280
|
+
with torch.device("cpu"):
|
|
281
|
+
model = smp.UPerNet(encoder_name=ENCODER_NAME, encoder_weights=None, in_channels=2, classes=2).float()
|
|
282
|
+
model.load_state_dict(state, strict=True)
|
|
283
|
+
model = model.eval()
|
|
284
|
+
shifts = block_shifts(8,8)
|
|
285
|
+
for s in range(4):
|
|
286
|
+
stage = getattr(model.encoder.model,f"layers_{s}")
|
|
287
|
+
if len(stage.blocks) != DEPTHS[s]:
|
|
288
|
+
raise UnsupportedOperation("installed model does not implement the fixed Swin-T depths")
|
|
289
|
+
for block, shift in zip(stage.blocks,shifts[s]):
|
|
290
|
+
if block.shift_size != (shift,shift) or block.window_size != (7,7):
|
|
291
|
+
raise UnsupportedOperation("installed model window/shift differs from fixed Swin-T224")
|
|
292
|
+
if (not isinstance(block.mlp.act,torch.nn.GELU) or block.mlp.act.approximate != 'none'
|
|
293
|
+
or block.norm1.eps != 1e-5 or block.norm2.eps != 1e-5):
|
|
294
|
+
raise UnsupportedOperation("installed model GELU/LayerNorm differs from fixed Swin-T224")
|
|
295
|
+
if attention == "explicit":
|
|
296
|
+
block.attn.fused_attn = False
|
|
297
|
+
return model
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
def load_swin_t224_model(checkpoint: Path, *, expected_checkpoint_sha256: str,
|
|
301
|
+
attention: Literal["default", "explicit"] = "default") -> torch.nn.Module:
|
|
302
|
+
if attention not in ("default", "explicit"):
|
|
303
|
+
raise UnsupportedOperation("attention must be 'default' or 'explicit'")
|
|
304
|
+
return build_model(read_state(checkpoint,expected_checkpoint_sha256),attention)
|
|
File without changes
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"""Raster-layout (R) blocks of the native EE library: the CNN path's compute blocks.
|
|
2
|
+
|
|
3
|
+
- ``conv``: ``conv2d``;
|
|
4
|
+
- ``pooling``: ``pool2d``;
|
|
5
|
+
- ``resample``: ``nearest_upsample``;
|
|
6
|
+
- ``merge``: ``concatenate``, ``residual_add``;
|
|
7
|
+
- ``activations``: ``relu``, ``sigmoid``, ``leaky_relu``, ``silu``, ``gelu``, ``tanh``,
|
|
8
|
+
``eval_dropout``, ``common_score_mask``, ``softmax``;
|
|
9
|
+
- ``outputs``: ``segmentation_outputs``, ``binary_threshold``.
|
|
10
|
+
|
|
11
|
+
Each module declares ``METADATA`` (locality, policy, cost and status per block,
|
|
12
|
+
docs/library-contract.md section 9). Every block takes a keyword-only
|
|
13
|
+
``policy=`` that defaults to, and only accepts, ``float32_operands`` (section 7).
|
|
14
|
+
|
|
15
|
+
These are the blocks themselves. ``compile_model`` materializes each node with
|
|
16
|
+
``.toFloat()``, and the public ``tensor_ee`` primitives wrap the same blocks
|
|
17
|
+
with that materialization; neither cast is inside a block.
|
|
18
|
+
|
|
19
|
+
Modules here import only the standard library, ``ee``, NumPy, torch (the blocks
|
|
20
|
+
take PyTorch modules and options), ``tensor_ee.core``, ``tensor_ee.layouts``
|
|
21
|
+
and ``tensor_ee.weights``. Nothing here imports a research folder, the
|
|
22
|
+
converter (``compiler``, ``semantics``, ...) or the models.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
from .conv import conv2d
|
|
26
|
+
from .pooling import pool2d
|
|
27
|
+
from .resample import nearest_upsample
|
|
28
|
+
from .merge import concatenate, residual_add
|
|
29
|
+
from .activations import (common_score_mask, eval_dropout, gelu, leaky_relu, relu, sigmoid, silu,
|
|
30
|
+
softmax, tanh)
|
|
31
|
+
from .outputs import SegmentationOutputs, binary_threshold, segmentation_outputs
|
|
32
|
+
|
|
33
|
+
__all__ = ["conv2d", "pool2d", "nearest_upsample", "concatenate", "residual_add",
|
|
34
|
+
"relu", "sigmoid", "leaky_relu", "silu", "gelu", "tanh", "eval_dropout",
|
|
35
|
+
"common_score_mask", "softmax",
|
|
36
|
+
"SegmentationOutputs", "segmentation_outputs", "binary_threshold"]
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
"""R pointwise activations, the common score mask and channel softmax.
|
|
2
|
+
|
|
3
|
+
Moved whole from ``tensor_ee.compiler``, where they were private:
|
|
4
|
+
``_relu``, ``_sigmoid``, ``_leaky_relu``, ``_silu``, ``_gelu``, ``_tanh``,
|
|
5
|
+
``_eval_dropout``, ``_common_score_mask`` and ``_softmax`` are here ``relu``,
|
|
6
|
+
``sigmoid``, ``leaky_relu``, ``silu``, ``gelu``, ``tanh``, ``eval_dropout``,
|
|
7
|
+
``common_score_mask`` and ``softmax``. Their helpers moved with them:
|
|
8
|
+
``_sigmoid_image``, ``_real_option``, ``_FLOAT32_MAX``, ``_exact_gelu`` and
|
|
9
|
+
``_dropout_probability``. The old names are re-exports of these objects.
|
|
10
|
+
|
|
11
|
+
Precision policy ``float32_operands`` (contract section 7): scalars are exact
|
|
12
|
+
binary64 (``_ee_scalar``); softmax keeps its ``toDouble()`` score widening; the
|
|
13
|
+
node output is materialized with ``.toFloat()`` by the caller
|
|
14
|
+
(``compile_model`` or the ``tensor_ee.primitives`` facade), not here.
|
|
15
|
+
|
|
16
|
+
``_finish`` and ``_ee_scalar`` are module-level bindings that these functions
|
|
17
|
+
look up (tests/test_pointwise_operators.py patches them here).
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
import math
|
|
23
|
+
from numbers import Real
|
|
24
|
+
from typing import Any
|
|
25
|
+
|
|
26
|
+
import ee
|
|
27
|
+
import torch
|
|
28
|
+
|
|
29
|
+
from ...core.errors import UnsupportedOperation
|
|
30
|
+
from ...core.grid import Grid
|
|
31
|
+
from ...core.metadata import ANY_SETTINGS, BlockMetadata, Locality, Validated, validate_metadata
|
|
32
|
+
from ...core.precision import FLOAT32_OPERANDS, validate_policy
|
|
33
|
+
from ...core.precision import exact_scalar as _ee_scalar
|
|
34
|
+
from ...core.tensor import Raster
|
|
35
|
+
from ...layouts.raster import finish as _finish
|
|
36
|
+
from .conv import _no_coefficients
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def relu(raster: Raster, *, clip_footprints: bool = False, policy: str = FLOAT32_OPERANDS) -> Raster:
|
|
40
|
+
validate_policy(policy, allowed=FLOAT32_OPERANDS)
|
|
41
|
+
return _finish(raster.image.max(0), raster.bands, raster.grid, clip_footprints)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _sigmoid_image(image: ee.Image) -> ee.Image:
|
|
45
|
+
# Stable algebraic form avoids exp overflow for very negative logits.
|
|
46
|
+
magnitude = image.abs().multiply(-1).exp()
|
|
47
|
+
positive = magnitude.add(1).pow(-1)
|
|
48
|
+
negative = magnitude.divide(magnitude.add(1))
|
|
49
|
+
return positive.where(image.lt(0), negative)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def sigmoid(raster: Raster, *, clip_footprints: bool = False, policy: str = FLOAT32_OPERANDS) -> Raster:
|
|
53
|
+
validate_policy(policy, allowed=FLOAT32_OPERANDS)
|
|
54
|
+
return _finish(_sigmoid_image(raster.image), raster.bands, raster.grid, clip_footprints)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
_FLOAT32_MAX = float(torch.finfo(torch.float32).max)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _real_option(value: Any, name: str, *, float32: bool = False) -> float:
|
|
61
|
+
if isinstance(value, bool) or not isinstance(value, Real):
|
|
62
|
+
raise UnsupportedOperation(f"{name} must be a static finite real scalar; received {value!r}")
|
|
63
|
+
try:
|
|
64
|
+
number = float(value)
|
|
65
|
+
except (OverflowError, ValueError) as error:
|
|
66
|
+
raise UnsupportedOperation(f"{name} must be finite; received {value!r}") from error
|
|
67
|
+
if not math.isfinite(number):
|
|
68
|
+
raise UnsupportedOperation(f"{name} must be finite; received {value!r}")
|
|
69
|
+
if float32:
|
|
70
|
+
if abs(value) > _FLOAT32_MAX:
|
|
71
|
+
raise UnsupportedOperation(f"{name} must be representable in float32; received {value!r}")
|
|
72
|
+
rounded = torch.tensor(number, dtype=torch.float32).item()
|
|
73
|
+
if value != 0 and rounded == 0:
|
|
74
|
+
raise UnsupportedOperation(f"{name} must be representable in float32; received {value!r}")
|
|
75
|
+
number = rounded
|
|
76
|
+
return number
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def _exact_gelu(approximate: Any) -> None:
|
|
80
|
+
if not isinstance(approximate, str) or approximate != "none":
|
|
81
|
+
raise UnsupportedOperation(f"approximate must be 'none'; received {approximate!r}")
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def _dropout_probability(p: Any) -> float:
|
|
85
|
+
probability = _real_option(p, "p")
|
|
86
|
+
if not 0 <= p <= 1:
|
|
87
|
+
raise UnsupportedOperation(f"p must satisfy 0 <= p <= 1; received {p!r}")
|
|
88
|
+
return probability
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def leaky_relu(raster: Raster, negative_slope: float = 0.01,
|
|
92
|
+
*, clip_footprints: bool = False, policy: str = FLOAT32_OPERANDS) -> Raster:
|
|
93
|
+
validate_policy(policy, allowed=FLOAT32_OPERANDS)
|
|
94
|
+
slope = _real_option(negative_slope, "negative_slope", float32=True)
|
|
95
|
+
image = raster.image.where(raster.image.lt(0), raster.image.multiply(_ee_scalar(slope)))
|
|
96
|
+
return _finish(image, raster.bands, raster.grid, clip_footprints)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def silu(raster: Raster, *, clip_footprints: bool = False, policy: str = FLOAT32_OPERANDS) -> Raster:
|
|
100
|
+
validate_policy(policy, allowed=FLOAT32_OPERANDS)
|
|
101
|
+
# Both sigmoid branches use exp(-abs(x)); no positive exponent can overflow.
|
|
102
|
+
sigmoid = _sigmoid_image(raster.image)
|
|
103
|
+
return _finish(raster.image.multiply(sigmoid), raster.bands, raster.grid, clip_footprints)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def gelu(raster: Raster, *, approximate: str = "none",
|
|
107
|
+
clip_footprints: bool = False, policy: str = FLOAT32_OPERANDS) -> Raster:
|
|
108
|
+
validate_policy(policy, allowed=FLOAT32_OPERANDS)
|
|
109
|
+
_exact_gelu(approximate)
|
|
110
|
+
# Established Swin expression: double work, one float32 materialization at
|
|
111
|
+
# the public/compiler boundary. No intermediate x*x or positive exponent.
|
|
112
|
+
image = raster.image.multiply(0.5).multiply(
|
|
113
|
+
raster.image.multiply(math.sqrt(0.5)).erf().add(1))
|
|
114
|
+
return _finish(image, raster.bands, raster.grid, clip_footprints)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def tanh(raster: Raster, *, clip_footprints: bool = False, policy: str = FLOAT32_OPERANDS) -> Raster:
|
|
118
|
+
validate_policy(policy, allowed=FLOAT32_OPERANDS)
|
|
119
|
+
return _finish(raster.image.tanh(), raster.bands, raster.grid, clip_footprints)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def eval_dropout(raster: Raster, *, p: float = 0.5,
|
|
123
|
+
clip_footprints: bool = False, policy: str = FLOAT32_OPERANDS) -> Raster:
|
|
124
|
+
validate_policy(policy, allowed=FLOAT32_OPERANDS)
|
|
125
|
+
_dropout_probability(p)
|
|
126
|
+
return _finish(raster.image, raster.bands, raster.grid, clip_footprints)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def common_score_mask(image: ee.Image, grid: Grid, *, policy: str = FLOAT32_OPERANDS) -> ee.Image:
|
|
130
|
+
"""Intersect every score mask and finite float32 predicate before reduction.
|
|
131
|
+
|
|
132
|
+
This is the existing _valid_logits predicate in a recipe-free core path.
|
|
133
|
+
Parsing FLOAT32_MAX avoids EE's integral-valued literal clipping.
|
|
134
|
+
"""
|
|
135
|
+
validate_policy(policy, allowed=FLOAT32_OPERANDS)
|
|
136
|
+
masks = image.mask().reduce(ee.Reducer.min()).gt(0)
|
|
137
|
+
finite = image.eq(image).And(image.abs().lte(_ee_scalar(_FLOAT32_MAX))).reduce(ee.Reducer.min())
|
|
138
|
+
return masks.And(finite).unmask(0, sameFootprint=False).And(grid.mask())
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def softmax(raster: Raster, *, clip_footprints: bool = False, policy: str = FLOAT32_OPERANDS) -> Raster:
|
|
142
|
+
validate_policy(policy, allowed=FLOAT32_OPERANDS)
|
|
143
|
+
if (not raster.bands or any(not isinstance(name, str) or not name for name in raster.bands)
|
|
144
|
+
or len(set(raster.bands)) != len(raster.bands)):
|
|
145
|
+
raise UnsupportedOperation("softmax requires nonempty unique channel names")
|
|
146
|
+
image = raster.image.select(list(raster.bands))
|
|
147
|
+
image = image.updateMask(common_score_mask(image, raster.grid)).toDouble()
|
|
148
|
+
maximum = image.reduce(ee.Reducer.max())
|
|
149
|
+
exponential = image.subtract(maximum).exp()
|
|
150
|
+
normalized = exponential.divide(exponential.reduce(ee.Reducer.sum()))
|
|
151
|
+
return _finish(normalized, raster.bands, raster.grid, clip_footprints)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
# ---------------------------------------------------------------------------
|
|
155
|
+
# Locality and cost metadata (contract section 9)
|
|
156
|
+
# ---------------------------------------------------------------------------
|
|
157
|
+
_SWEEPS = ("docs/evidence/t02-operators.json", "docs/evidence/t05-primary.json")
|
|
158
|
+
|
|
159
|
+
_UNCLIPPED = Validated(({"clip_footprints": (False,)},))
|
|
160
|
+
"""The blocks only the CNN operator validation, segmentation output validation and trained-model preservation sweeps cover: their slopes, probabilities and inputs
|
|
161
|
+
varied, but ``clip_footprints`` never left False. The ResNet18 runs, which the other R blocks
|
|
162
|
+
cite, used True (resnet18_full64_float32_policy.json)."""
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def _pointwise(name: str, evidence: tuple[str, ...], validated: Validated = ANY_SETTINGS) -> BlockMetadata:
|
|
166
|
+
return BlockMetadata(name=name, layout_in="R", layout_out="R", locality=Locality.POINTWISE,
|
|
167
|
+
policy=FLOAT32_OPERANDS, embedded_constants=_no_coefficients,
|
|
168
|
+
asset_arrays=_no_coefficients, status="Ready", evidence=evidence, validated=validated)
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
METADATA = validate_metadata({
|
|
172
|
+
"relu": _pointwise("relu", ("docs/history/cnn/results/parity.json", "docs/history/cnn/results/resnet18_full64_float32_policy.json",
|
|
173
|
+
"docs/history/cnn/results/segwater_js_parity_1x1.json")),
|
|
174
|
+
"sigmoid": _pointwise("sigmoid", ("docs/history/cnn/results/parity.json", "docs/history/cnn/results/resnet18_full64_float32_policy.json")),
|
|
175
|
+
"leaky_relu": _pointwise("leaky_relu", _SWEEPS, _UNCLIPPED),
|
|
176
|
+
"silu": _pointwise("silu", _SWEEPS, _UNCLIPPED),
|
|
177
|
+
"gelu": _pointwise("gelu", _SWEEPS, _UNCLIPPED),
|
|
178
|
+
"tanh": _pointwise("tanh", _SWEEPS, _UNCLIPPED),
|
|
179
|
+
"eval_dropout": _pointwise("eval_dropout", _SWEEPS, _UNCLIPPED),
|
|
180
|
+
# A single-band 0/1 validity mask on the raster's grid.
|
|
181
|
+
"common_score_mask": _pointwise("common_score_mask", ("docs/evidence/t03-outputs.json",)),
|
|
182
|
+
"softmax": _pointwise("softmax", ("docs/evidence/t03-outputs.json", "docs/evidence/t05-primary.json"), _UNCLIPPED),
|
|
183
|
+
})
|