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
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
"""W/V normalization: LayerNorm over channels, LayerNorm2d and explicit inference BatchNorm.
|
|
2
|
+
|
|
3
|
+
Moved from ``swin_block/earth_engine/layer_norm.py`` (``layer_norm``, ``LN_EPS``) and
|
|
4
|
+
``upernet/earth_engine/normalization.py`` (``layer_norm2d``, ``BatchNormConstants``,
|
|
5
|
+
``batch_norm``, ``FEATURE_EPS``, ``BN_EPS``), research code archived at the tag
|
|
6
|
+
``research-archive-2026-10-12`` (docs/history/README.md). The decoder's feature norms
|
|
7
|
+
(``fpn_layer_norm`` and ``psp_layer_norm``, whose constants ``FPNConstants``/``PSPConstants``
|
|
8
|
+
build ahead) call ``layer_norm2d``: the single LayerNorm2d entry point (the graph-identity baseline
|
|
9
|
+
``layer_norm2d`` and ``layer_norm2d_psp`` probes serialize identically).
|
|
10
|
+
|
|
11
|
+
**LayerNorm** (torch.nn.LayerNorm(C), as in Swin), on the channel axis of [N, C]:
|
|
12
|
+
|
|
13
|
+
mean = sum_i x_i / C
|
|
14
|
+
var = sum_i (x_i - mean)^2 / C biased (population) variance
|
|
15
|
+
y = (x - mean) / sqrt(var + eps) * gamma + beta eps = 1e-5
|
|
16
|
+
|
|
17
|
+
The statistics and the normalization run in double; the result is rounded to
|
|
18
|
+
float32 once (policy ``double_round_once``). Two passes (mean, then centered
|
|
19
|
+
squares) rather than E[x^2] - mean^2, so there is no cancellation. There is no
|
|
20
|
+
explicit cast of x to double: the row sum of a float32 array is already double,
|
|
21
|
+
and float32 minus double is computed in double (probed).
|
|
22
|
+
|
|
23
|
+
**LayerNorm2d** (SMP's decoder feature norms): the same LayerNorm per pixel over
|
|
24
|
+
the channel axis with eps 1e-6; one row per pixel on a dense raster, 49 in window
|
|
25
|
+
layout.
|
|
26
|
+
|
|
27
|
+
**BatchNorm** (inference, eps 1e-5), per channel c:
|
|
28
|
+
|
|
29
|
+
y = (x - running_mean_c) / sqrt(running_var_c + eps) * gamma_c + beta_c
|
|
30
|
+
|
|
31
|
+
computed in double and rounded once. Kept explicit, not folded, to stay bit-exact with the decoder's mirror.
|
|
32
|
+
|
|
33
|
+
Per-channel constants are repeated to the rows of the layout they serve; see
|
|
34
|
+
``tensor_ee.blocks.window.linear``. The epsilons are parameters with today's values
|
|
35
|
+
as defaults; other values are not validated.
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
from __future__ import annotations
|
|
39
|
+
|
|
40
|
+
import ee
|
|
41
|
+
import numpy as np
|
|
42
|
+
|
|
43
|
+
from ...core.metadata import BlockMetadata, Locality, Validated, validate_metadata
|
|
44
|
+
from ...core.precision import DOUBLE_ROUND_ONCE, validate_policy
|
|
45
|
+
from ...weights.constants import RowConstant, ServerArray, WindowLayout, check_rows, constant_value
|
|
46
|
+
from .linear import WINDOW_LAYOUT, as_layout, builder_rows
|
|
47
|
+
|
|
48
|
+
LN_EPS = 1e-5
|
|
49
|
+
"""Encoder LayerNorm eps (timm)."""
|
|
50
|
+
|
|
51
|
+
FEATURE_EPS = 1e-6
|
|
52
|
+
"""Decoder LayerNorm2d eps (SMP)."""
|
|
53
|
+
|
|
54
|
+
BN_EPS = 1e-5
|
|
55
|
+
"""Inference BatchNorm eps."""
|
|
56
|
+
|
|
57
|
+
ONE_ROW = WindowLayout(1)
|
|
58
|
+
"""The one-row dense layout (one [1, C] array per pixel): the default of
|
|
59
|
+
``layer_norm2d`` and ``BatchNormConstants``."""
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _row_value(constant, layout):
|
|
63
|
+
"""A pre-repeated constant as used in the graph: a ``RowConstant`` is checked
|
|
64
|
+
against ``layout`` (when given) and unwrapped; any other value is used as is."""
|
|
65
|
+
if isinstance(constant, RowConstant):
|
|
66
|
+
return constant.value if layout is None else constant.check(as_layout(layout))
|
|
67
|
+
return constant
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def layer_norm(ops, x, gamma, beta, *, dim: int, eps: float = LN_EPS, policy: str = DOUBLE_ROUND_ONCE,
|
|
71
|
+
layout=None):
|
|
72
|
+
"""x: float32 [N, dim]; gamma, beta: float32 [N, dim] (rows repeated), as values or
|
|
73
|
+
``RowConstant``s. ``layout``, when given, refuses ``RowConstant``s built for another
|
|
74
|
+
row count. eps defaults to timm's 1e-5; other values are not validated."""
|
|
75
|
+
validate_policy(policy, allowed=DOUBLE_ROUND_ONCE)
|
|
76
|
+
gamma, beta = _row_value(gamma, layout), _row_value(beta, layout)
|
|
77
|
+
mean = ops.row_total(x).divide(dim) # [N, 1] double
|
|
78
|
+
centered = x.subtract(ops.repeat_cols(mean, dim)) # double
|
|
79
|
+
variance = ops.row_total(centered.multiply(centered)).divide(dim)
|
|
80
|
+
std = variance.add(eps).sqrt() # [N, 1]
|
|
81
|
+
normalized = centered.divide(ops.repeat_cols(std, dim))
|
|
82
|
+
return ops.to_float(normalized.multiply(gamma).add(beta))
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _norm_constant(ops, value, rows):
|
|
86
|
+
"""gamma or beta for ``layer_norm2d``: a ``RowConstant`` (checked against ``rows``)
|
|
87
|
+
or an already repeated EE value is used as is; a [dim] vector (NumPy or ServerArray)
|
|
88
|
+
is repeated to the rows of ``rows``."""
|
|
89
|
+
if isinstance(value, RowConstant):
|
|
90
|
+
return value.check(as_layout(rows))
|
|
91
|
+
if isinstance(value, ee.ComputedObject):
|
|
92
|
+
return value
|
|
93
|
+
return ops.constant(value, repeat_rows=builder_rows(rows))
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def layer_norm2d(ops, x, gamma, beta, *, dim: int, rows=ONE_ROW, eps: float = FEATURE_EPS,
|
|
97
|
+
policy: str = DOUBLE_ROUND_ONCE):
|
|
98
|
+
"""LayerNorm2d(dim, eps 1e-6) of float32 [rows, dim] arrays: the Swin block's
|
|
99
|
+
``layer_norm`` with eps 1e-6.
|
|
100
|
+
|
|
101
|
+
gamma, beta: float32 [dim] vectors (NumPy or ServerArray), repeated to the rows of
|
|
102
|
+
``rows`` (the layout x is in: ``WindowLayout(1)`` dense, the default;
|
|
103
|
+
``WindowLayout(7)`` window layout; or a row count, the original call form); or
|
|
104
|
+
constants already repeated (a ``RowConstant``, refused if built for another row
|
|
105
|
+
count, or the EE value a builder returned, as the decoder's ``FPNConstants`` and
|
|
106
|
+
``PSPConstants`` hold them), used as is. Constants built ahead or here serialize
|
|
107
|
+
identically. Eps 1e-6 is validated with rows 1 and with windows 5, 7, 8 and 12 (rows
|
|
108
|
+
25, 49, 64 and 144); other epsilons are not.
|
|
109
|
+
"""
|
|
110
|
+
validate_policy(policy, allowed=DOUBLE_ROUND_ONCE)
|
|
111
|
+
return layer_norm(ops, x, _norm_constant(ops, gamma, rows), _norm_constant(ops, beta, rows),
|
|
112
|
+
dim=dim, eps=eps)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
class BatchNormConstants:
|
|
116
|
+
"""[rows, C] constants: running mean and sqrt(running_var + eps) as double arrays
|
|
117
|
+
(the square root is evaluated once, on the server-side array, not per pixel), and
|
|
118
|
+
float32 gamma, beta. Values are NumPy arrays or ServerArrays (asset-backed).
|
|
119
|
+
|
|
120
|
+
``rows``: the layout the constants serve (default ``WindowLayout(1)``, one row;
|
|
121
|
+
``WindowLayout(7)``: 49) or its row count. eps defaults to 1e-5; other values are
|
|
122
|
+
not validated."""
|
|
123
|
+
|
|
124
|
+
def __init__(self, ops, gamma, beta, mean, var, rows=ONE_ROW, eps: float = BN_EPS):
|
|
125
|
+
ee = ops.ee
|
|
126
|
+
rows = builder_rows(rows)
|
|
127
|
+
self.mean = ops.wrap(constant_value(ee, mean, rows).double())
|
|
128
|
+
self.std = ops.wrap(constant_value(ee, var, rows).double().add(eps).sqrt())
|
|
129
|
+
self.gamma = ops.constant(gamma, repeat_rows=rows)
|
|
130
|
+
self.beta = ops.constant(beta, repeat_rows=rows)
|
|
131
|
+
self.channels = (gamma.shape if isinstance(gamma, ServerArray) else np.shape(gamma))[0]
|
|
132
|
+
self.rows = rows
|
|
133
|
+
|
|
134
|
+
@property
|
|
135
|
+
def embedded_values(self) -> int:
|
|
136
|
+
return 4 * self.rows * self.channels
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def batch_norm(ops, x, c: BatchNormConstants, *, policy: str = DOUBLE_ROUND_ONCE, layout=None):
|
|
140
|
+
"""Inference BatchNorm of float32 [rows, C] arrays: double inside, one float32 rounding.
|
|
141
|
+
``layout``, when given, refuses constants built for another row count."""
|
|
142
|
+
validate_policy(policy, allowed=DOUBLE_ROUND_ONCE)
|
|
143
|
+
if layout is not None:
|
|
144
|
+
check_rows(c, as_layout(layout))
|
|
145
|
+
return ops.to_float(x.subtract(c.mean).divide(c.std).multiply(c.gamma).add(c.beta))
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
# ---------------------------------------------------------------------------
|
|
149
|
+
# Cost functions (contract section 9); default layout: window 7 (49 rows)
|
|
150
|
+
# ---------------------------------------------------------------------------
|
|
151
|
+
def layer_norm_values(dim: int, *, layout=WINDOW_LAYOUT) -> int:
|
|
152
|
+
"""Values embedded for NumPy gamma, beta: 2 x [rows, dim]."""
|
|
153
|
+
return 2 * builder_rows(layout) * dim
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def layer_norm_arrays() -> int:
|
|
157
|
+
"""Server arrays read for ServerArray gamma, beta."""
|
|
158
|
+
return 2
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def batch_norm_values(channels: int, *, layout=WINDOW_LAYOUT) -> int:
|
|
162
|
+
"""Values embedded for NumPy statistics: mean, sqrt(var + eps), gamma, beta, each
|
|
163
|
+
[rows, C] (``BatchNormConstants.embedded_values``)."""
|
|
164
|
+
return 4 * builder_rows(layout) * channels
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def batch_norm_arrays() -> int:
|
|
168
|
+
"""Server arrays read for ServerArray statistics: gamma, beta, mean, var."""
|
|
169
|
+
return 4
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
METADATA = validate_metadata({
|
|
173
|
+
# Validated: eps 1e-5 only, never varied (timm's Swin norms; SegFormer's 1e-5 norms).
|
|
174
|
+
# The 1e-6 norms run through layer_norm2d.
|
|
175
|
+
"layer_norm": BlockMetadata(
|
|
176
|
+
name="layer_norm", layout_in="W", layout_out="W", locality=Locality.POINTWISE,
|
|
177
|
+
policy=DOUBLE_ROUND_ONCE, embedded_constants=layer_norm_values, asset_arrays=layer_norm_arrays,
|
|
178
|
+
status="Ready",
|
|
179
|
+
evidence=("docs/history/building-blocks-2026-10-11.md#34-normalization", "docs/history/swin_block/notes/findings.md"),
|
|
180
|
+
validated=Validated(({"eps": (LN_EPS,)},))),
|
|
181
|
+
"layer_norm2d": BlockMetadata(
|
|
182
|
+
name="layer_norm2d", layout_in="W", layout_out="W", locality=Locality.POINTWISE,
|
|
183
|
+
policy=DOUBLE_ROUND_ONCE, embedded_constants=layer_norm_values, asset_arrays=layer_norm_arrays,
|
|
184
|
+
status="Ready — windows 5, 7, 8, 12; eps 1e-6",
|
|
185
|
+
evidence=("docs/history/building-blocks-2026-10-11.md#34-normalization", "docs/evidence/l00-consolidation-probes.json",
|
|
186
|
+
"docs/history/upernet/notes/milestone8_findings.md", "docs/history/upernet/notes/milestone9_findings.md",
|
|
187
|
+
"docs/evidence/p01-offline.json",
|
|
188
|
+
"docs/evidence/live-fixtures/20261013T074301Z-p01a.json", "docs/evidence/live-fixtures/20261013T074330Z-p01b.json", "docs/evidence/live-fixtures/20261013T075542Z-window-relative_position_bias-w12.json"),
|
|
189
|
+
validated=Validated(({"window": (5, 7, 8, 12), "eps": (FEATURE_EPS,)},))),
|
|
190
|
+
# Validated: eps 1e-5 only, never varied (SMP's decoder BatchNorms).
|
|
191
|
+
"batch_norm": BlockMetadata(
|
|
192
|
+
name="batch_norm", layout_in="W", layout_out="W", locality=Locality.POINTWISE,
|
|
193
|
+
policy=DOUBLE_ROUND_ONCE, embedded_constants=batch_norm_values, asset_arrays=batch_norm_arrays,
|
|
194
|
+
status="Ready",
|
|
195
|
+
evidence=("docs/history/building-blocks-2026-10-11.md#34-normalization", "docs/history/upernet/notes/milestone7_findings.md"),
|
|
196
|
+
validated=Validated(({"eps": (BN_EPS,)},), sources=(BatchNormConstants,))),
|
|
197
|
+
})
|
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
"""Swin patch embedding (R -> W) and patch merging (W -> W, one stage down).
|
|
2
|
+
|
|
3
|
+
Moved from ``swin_image_encoder/earth_engine/patch_embed.py`` (``patch_embed_constants``
|
|
4
|
+
with ``PatchEmbedConstants``, ``stride_columns``, ``patch_embed``, ``valid_fraction``)
|
|
5
|
+
and ``swin_hierarchy/earth_engine/patch_merge.py`` (``patch_merge_constants`` with
|
|
6
|
+
``PatchMergeConstants``, ``stage2_coarse``, ``gather_windows``, ``merge_windows``,
|
|
7
|
+
``patch_merge``, ``GROUP_OFFSETS``), research code archived at the tag
|
|
8
|
+
``research-archive-2026-10-12`` (docs/history/README.md).
|
|
9
|
+
|
|
10
|
+
**Patch embedding** (timm's PatchEmbed: Conv2d with kernel = stride = p and LayerNorm),
|
|
11
|
+
straight into window layout; Swin-T's p = 4, bands VV and VH, width 96 and window 7 are
|
|
12
|
+
the defaults::
|
|
13
|
+
|
|
14
|
+
input C bands (``bands``, selected by name) float32 on the input grid [H, W]
|
|
15
|
+
columns the p x p x C patch of every stage-1 token as one [1, C p^2] row on the
|
|
16
|
+
token grid, flattened (c, ky, kx): Conv2d's weight.reshape(dim, -1)
|
|
17
|
+
order (p^2 exact stride samples of the input, the original attention implementation's
|
|
18
|
+
reproject + changeProj space-to-depth at factor p)
|
|
19
|
+
pe_patches window partition: [w^2, C p^2] per stage-1 window on the coarse grid
|
|
20
|
+
pe_proj ``linear``: [w^2, C p^2] x W^T [C p^2, dim] + bias, rounded once
|
|
21
|
+
x ``layer_norm(dim)``, rounded once, masked outside the crop
|
|
22
|
+
|
|
23
|
+
The Conv2d is an im2col matrix product: each token reads exactly its own p x p pixels
|
|
24
|
+
(equal bit for bit on an integer fixture, and against timm's own one-hot extraction on
|
|
25
|
+
pixel IDs at patches 2, 3, 4 and 8). The tokens are partitioned into windows before the
|
|
26
|
+
projection (the research ``token`` placement, which projected each token's row first,
|
|
27
|
+
was removed when the research code was archived). The embedding width ``dim`` comes from the projection's weight
|
|
28
|
+
shape.
|
|
29
|
+
|
|
30
|
+
**Patch merging** (timm's PatchMerging, from 2 x 2 stage-1 windows)::
|
|
31
|
+
|
|
32
|
+
merged[i, j] = cat(x[2i, 2j], x[2i+1, 2j], x[2i, 2j+1], x[2i+1, 2j+1]) [4C]
|
|
33
|
+
out = Linear(4C, 2C, bias=False)(LayerNorm(4C)(merged))
|
|
34
|
+
|
|
35
|
+
Stage-2 window (P, Q) draws exactly on the 14 x 14 stage-1 tokens of the 2 x 2 stage-1
|
|
36
|
+
windows (2P + a, 2Q + b). Every step before the LayerNorm moves float32 values without
|
|
37
|
+
arithmetic: the four windows are gathered onto the stage-2 coarse grid by stride
|
|
38
|
+
sampling (``gather_windows``), assembled into a [2w, 2w C] block and re-sliced into the
|
|
39
|
+
four groups x0..x3 in timm's order (``merge_windows``). No token raster is formed.
|
|
40
|
+
|
|
41
|
+
The embedding gathers its columns by stride sampling and the merge gathers whole windows
|
|
42
|
+
(the research ``neighborhood`` columns and ``fine``-grid merge, with their registries
|
|
43
|
+
``COLUMN_GATHERS`` and ``MERGE_GATHERS``, were removed when the research code was archived).
|
|
44
|
+
|
|
45
|
+
**Policy** ``double_round_once`` (``linear`` and ``layer_norm``). Patch merging (with
|
|
46
|
+
``gather_windows`` and ``merge_windows``) is validated at windows 5, 7, 8 and 12. The
|
|
47
|
+
patch embedding (with ``stride_columns``) is validated at patches 2, 4 and 8, with 1 to 4
|
|
48
|
+
input bands (distinct names without regular-expression syntax, in any raster order),
|
|
49
|
+
embedding widths 48, 96 and 128, and windows 5, 7, 8 and 12 (against timm's
|
|
50
|
+
PatchEmbed); other patches and widths are not. The default bands
|
|
51
|
+
("VV", "VH") are recipe-facing: the Swin-T224 adapter's sensor names (contract section 11).
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
from __future__ import annotations
|
|
55
|
+
|
|
56
|
+
from dataclasses import dataclass
|
|
57
|
+
|
|
58
|
+
import numpy as np
|
|
59
|
+
|
|
60
|
+
from ...core.metadata import NO_ARITHMETIC, BlockMetadata, Locality, Validated, validate_metadata
|
|
61
|
+
from ...core.precision import DOUBLE_ROUND_ONCE, validate_policy
|
|
62
|
+
from ...core.tensor import WINDOW
|
|
63
|
+
from ...layouts.shift import domain_mask, materialize
|
|
64
|
+
from ...layouts.window import as_row, stride_samples, to_windows
|
|
65
|
+
from ...weights.constants import ServerArray, WindowLayout, check_rows
|
|
66
|
+
from .linear import WINDOW_LAYOUT, LinearConstants, builder_rows, linear, linear_values, no_constants
|
|
67
|
+
from .normalization import layer_norm, layer_norm_values
|
|
68
|
+
|
|
69
|
+
PATCH = 4
|
|
70
|
+
"""Default patch size (Swin-T: kernel = stride = 4)."""
|
|
71
|
+
|
|
72
|
+
BANDS = ("VV", "VH")
|
|
73
|
+
"""Default input channel names (recipe-facing: the adapter's canonical sensor bands)."""
|
|
74
|
+
|
|
75
|
+
GROUP_OFFSETS = ((0, 0), (1, 0), (0, 1), (1, 1))
|
|
76
|
+
"""(dy, dx) of timm's merge groups x0..x3: merged = cat(x0, x1, x2, x3)."""
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
# ---------------------------------------------------------------------------
|
|
80
|
+
# Patch embedding
|
|
81
|
+
# ---------------------------------------------------------------------------
|
|
82
|
+
@dataclass
|
|
83
|
+
class PatchEmbedConstants:
|
|
84
|
+
proj: LinearConstants # W^T [C patch^2, dim], bias rows repeated
|
|
85
|
+
norm_weight: object
|
|
86
|
+
norm_bias: object
|
|
87
|
+
rows: int # window ** 2 (49 at window 7)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def patch_embed_constants(ops, pe: dict, rows=WINDOW_LAYOUT) -> PatchEmbedConstants:
|
|
91
|
+
"""pe: {"proj.weight" [dim, C patch^2] (Conv2d's weight flattened in (c, ky, kx) order;
|
|
92
|
+
[96, 32] today), "proj.bias", "norm.weight", "norm.bias"} as NumPy arrays (inline) or
|
|
93
|
+
ServerArrays (asset-backed). ``rows``: the layout the constants serve
|
|
94
|
+
(``WindowLayout(7)`` by default) or its row count."""
|
|
95
|
+
rows = builder_rows(rows)
|
|
96
|
+
return PatchEmbedConstants(
|
|
97
|
+
proj=LinearConstants(ops, pe["proj.weight"], pe["proj.bias"], rows),
|
|
98
|
+
norm_weight=ops.constant(pe["norm.weight"], repeat_rows=rows),
|
|
99
|
+
norm_bias=ops.constant(pe["norm.bias"], repeat_rows=rows), rows=rows)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
_REGEX_SPECIAL = frozenset("\\^$.|?*+()[]{}")
|
|
103
|
+
"""Characters a band selector would read as regular-expression syntax (``Image.select``)."""
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def _check_patch_settings(patch, bands) -> tuple[int, tuple[str, ...]]:
|
|
107
|
+
"""``(patch, bands)`` as ``stride_columns`` uses them, or ``ValueError``: the patch is a
|
|
108
|
+
positive integer, and the bands are a non-empty sequence of distinct names (not one
|
|
109
|
+
string), each without regular-expression syntax, because ``Image.select`` reads a name
|
|
110
|
+
as a pattern and could select another band. Client-side only: nothing reaches the
|
|
111
|
+
graph."""
|
|
112
|
+
if isinstance(patch, bool) or not isinstance(patch, (int, np.integer)) or patch < 1:
|
|
113
|
+
raise ValueError(f"patch must be a positive integer; received {patch!r}")
|
|
114
|
+
if isinstance(bands, str):
|
|
115
|
+
raise ValueError(f"bands must be a sequence of band names, not one string; received {bands!r}")
|
|
116
|
+
bands = tuple(bands)
|
|
117
|
+
if not bands or not all(isinstance(b, str) and b for b in bands):
|
|
118
|
+
raise ValueError(f"bands must be a non-empty sequence of non-empty names; received {bands!r}")
|
|
119
|
+
if len(set(bands)) != len(bands):
|
|
120
|
+
raise ValueError(f"band names must be distinct; received {bands!r}")
|
|
121
|
+
special = [b for b in bands if _REGEX_SPECIAL & set(b)]
|
|
122
|
+
if special:
|
|
123
|
+
raise ValueError(f"band names are selected as regular expressions; rename {special} without "
|
|
124
|
+
f"any of {''.join(sorted(_REGEX_SPECIAL))}")
|
|
125
|
+
return int(patch), bands
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def stride_columns(ee, x, grid, *, patch: int = PATCH, bands=BANDS):
|
|
129
|
+
"""[1, P] per token on the token grid (P = len(bands) patch^2) from patch^2 exact stride
|
|
130
|
+
samples of x (the bands on the input grid `grid`). Sample ky * patch + kx holds input
|
|
131
|
+
pixel (patch i + ky, patch j + kx) of token (i, j); columns in (c, ky, kx) order, with
|
|
132
|
+
c following ``bands`` (selected by name, whatever the order of x's bands).
|
|
133
|
+
``_check_patch_settings`` refuses a patch or band list it cannot serve. Validated at
|
|
134
|
+
patches 2, 4 and 8 with 1 to 4 bands."""
|
|
135
|
+
patch, bands = _check_patch_settings(patch, bands)
|
|
136
|
+
samples = stride_samples(ee, grid, x, patch, materialize=True)
|
|
137
|
+
columns = [samples[ky * patch + kx].select(c).rename(f"{c}_{ky}{kx}")
|
|
138
|
+
for c in bands for ky in range(patch) for kx in range(patch)]
|
|
139
|
+
return as_row(ee, ee.Image.cat(columns), len(bands) * patch * patch)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def patch_embed(ops, x, grid, c: PatchEmbedConstants, *, patch: int = PATCH, bands=BANDS,
|
|
143
|
+
window: int = WINDOW, policy: str = DOUBLE_ROUND_ONCE) -> dict:
|
|
144
|
+
"""{"pe_columns", "pe_patches", "pe_proj", "x"} from the input x on the input grid.
|
|
145
|
+
|
|
146
|
+
"x" is the stage-1 input of the complete encoder: window layout on the stage-1
|
|
147
|
+
coarse grid, masked outside the crop (its cyclic wrap is the encoder's
|
|
148
|
+
periodic extension, as for the complete encoder's masked input). pe_patches / pe_proj
|
|
149
|
+
are window layout too. Constants built for another row count than ``window ** 2``
|
|
150
|
+
are refused, and so is a projection whose input width is not
|
|
151
|
+
``len(bands) * patch ** 2`` (proj.weight [dim, C patch^2], flattened (c, ky, kx)).
|
|
152
|
+
Validated at patches 2, 4 and 8, 1 to 4 bands, widths 48, 96 and 128 and windows 5,
|
|
153
|
+
7, 8 and 12.
|
|
154
|
+
"""
|
|
155
|
+
validate_policy(policy, allowed=DOUBLE_ROUND_ONCE)
|
|
156
|
+
patch, bands = _check_patch_settings(patch, bands)
|
|
157
|
+
columns = len(bands) * patch * patch
|
|
158
|
+
if c.proj.in_features != columns:
|
|
159
|
+
raise ValueError(f"the projection reads {c.proj.in_features} columns, but {len(bands)} bands at "
|
|
160
|
+
f"patch {patch} give {columns}: proj.weight must be [dim, C patch^2], "
|
|
161
|
+
"Conv2d's weight flattened in (c, ky, kx) order")
|
|
162
|
+
ee = ops.ee
|
|
163
|
+
tokens = grid.coarse(patch)
|
|
164
|
+
coarse = tokens.coarse(window)
|
|
165
|
+
cols = stride_columns(ee, x, grid, patch=patch, bands=bands)
|
|
166
|
+
dim = c.proj.out_features # embedding width, from the weight shape
|
|
167
|
+
t = {"pe_columns": cols, "pe_patches": to_windows(ee, cols, tokens, window=window)}
|
|
168
|
+
check_rows(c, WindowLayout(window))
|
|
169
|
+
t["pe_proj"] = linear(ops, t["pe_patches"], c.proj)
|
|
170
|
+
x1 = layer_norm(ops, t["pe_proj"], c.norm_weight, c.norm_bias, dim=dim)
|
|
171
|
+
t["x"] = x1.updateMask(domain_mask(ee, coarse))
|
|
172
|
+
return t
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def valid_fraction(ee, validity, grid, *, patch: int = PATCH, bands=BANDS):
|
|
176
|
+
"""Fraction of a token's patch values that were valid in the source (scalar
|
|
177
|
+
band "valid_fraction" on the token grid): 1 fully valid, 0 fully masked."""
|
|
178
|
+
cols = stride_columns(ee, validity, grid, patch=patch, bands=bands)
|
|
179
|
+
return cols.arrayReduce(ee.Reducer.mean(), [1]).arrayGet([0, 0]).rename("valid_fraction")
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
# ---------------------------------------------------------------------------
|
|
183
|
+
# Patch merging
|
|
184
|
+
# ---------------------------------------------------------------------------
|
|
185
|
+
@dataclass
|
|
186
|
+
class PatchMergeConstants:
|
|
187
|
+
norm_weight: object # [rows, 4C] (rows repeated)
|
|
188
|
+
norm_bias: object
|
|
189
|
+
reduction: LinearConstants # W^T [4C, 2C], no bias
|
|
190
|
+
in_dim: int # C
|
|
191
|
+
rows: int = WINDOW_LAYOUT.rows
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def patch_merge_constants(ops, parameters: dict, layout=WINDOW_LAYOUT) -> PatchMergeConstants:
|
|
195
|
+
"""parameters: timm PatchMerging state_dict names -> float32 NumPy arrays or ServerArrays.
|
|
196
|
+
``layout``: the layout the constants serve (default window 7: 49 rows) or its row count."""
|
|
197
|
+
p = parameters
|
|
198
|
+
rows = builder_rows(layout)
|
|
199
|
+
width = (p["norm.weight"].shape if isinstance(p["norm.weight"], ServerArray)
|
|
200
|
+
else np.shape(p["norm.weight"]))[0]
|
|
201
|
+
if width % 4:
|
|
202
|
+
raise ValueError("LayerNorm width must be 4C")
|
|
203
|
+
return PatchMergeConstants(
|
|
204
|
+
norm_weight=ops.constant(p["norm.weight"], repeat_rows=rows),
|
|
205
|
+
norm_bias=ops.constant(p["norm.bias"], repeat_rows=rows),
|
|
206
|
+
reduction=LinearConstants(ops, p["reduction.weight"], p.get("reduction.bias"), rows),
|
|
207
|
+
in_dim=width // 4, rows=rows)
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def stage2_coarse(coarse1):
|
|
211
|
+
"""Stage-2 coarse grid: one stage-2 window per 2 x 2 stage-1 windows."""
|
|
212
|
+
if coarse1.height % 2 or coarse1.width % 2:
|
|
213
|
+
raise ValueError("Patch Merging in window layout needs an even number of stage-1 "
|
|
214
|
+
"windows per side")
|
|
215
|
+
return coarse1.coarse(2)
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def gather_windows(ee, x, coarse1) -> dict:
|
|
219
|
+
"""{(a, b): image on the stage-2 coarse grid holding stage-1 window (2P + a, 2Q + b)}.
|
|
220
|
+
|
|
221
|
+
x is materialized on the stage-1 coarse grid first, so the four samples read the
|
|
222
|
+
same computed tiles (translated views must read the same tiles).
|
|
223
|
+
"""
|
|
224
|
+
stage2_coarse(coarse1)
|
|
225
|
+
x = materialize(ee, x, coarse1)
|
|
226
|
+
samples = stride_samples(ee, coarse1, x, 2, materialize=False) # element 2a + b
|
|
227
|
+
return {(a, b): samples[2 * a + b] for a in (0, 1) for b in (0, 1)}
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def merge_windows(ee, windows: dict, channels: int, *, window: int = WINDOW) -> dict:
|
|
231
|
+
"""The four groups x0..x3 [w^2, C] and their concatenation "cat" [w^2, 4C]."""
|
|
232
|
+
w, c = window, channels
|
|
233
|
+
|
|
234
|
+
def shape(*dims):
|
|
235
|
+
return ee.Image(ee.Array(list(dims)))
|
|
236
|
+
|
|
237
|
+
def side_by_side(left, right): # [w, w C] | [w, w C] -> [w, 2w C]
|
|
238
|
+
return left.arrayReshape(shape(w, w * c), 2).arrayCat(
|
|
239
|
+
right.arrayReshape(shape(w, w * c), 2), 1)
|
|
240
|
+
|
|
241
|
+
block = side_by_side(windows[0, 0], windows[0, 1]).arrayCat(
|
|
242
|
+
side_by_side(windows[1, 0], windows[1, 1]), 0) # [2w, 2w C]
|
|
243
|
+
pairs = block.arrayReshape(shape(w, 4 * w * c), 2) # [w, 4w C]
|
|
244
|
+
by_dy = [pairs.arraySlice(1, dy * 2 * w * c, (dy + 1) * 2 * w * c)
|
|
245
|
+
.arrayReshape(shape(w * w, 2 * c), 2) for dy in (0, 1)] # [w^2, 2C] each
|
|
246
|
+
t = {f"x{g}": by_dy[dy].arraySlice(1, dx * c, (dx + 1) * c)
|
|
247
|
+
for g, (dy, dx) in enumerate(GROUP_OFFSETS)}
|
|
248
|
+
t["cat"] = t["x0"].arrayCat(t["x1"], 1).arrayCat(t["x2"], 1).arrayCat(t["x3"], 1)
|
|
249
|
+
return t
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
def patch_merge(ops, x, c: PatchMergeConstants, coarse1, *, window: int = WINDOW,
|
|
253
|
+
policy: str = DOUBLE_ROUND_ONCE) -> dict:
|
|
254
|
+
"""Every intermediate of Patch Merging for stage-1 windows x [w^2, C]:
|
|
255
|
+
x0..x3 [w^2, C], cat and norm [w^2, 4C], out [w^2, 2C], on the stage-2 coarse grid.
|
|
256
|
+
The windows are gathered whole (``gather_windows``, ``merge_windows``). Constants
|
|
257
|
+
built for another row count than ``window ** 2`` are refused.
|
|
258
|
+
"""
|
|
259
|
+
validate_policy(policy, allowed=DOUBLE_ROUND_ONCE)
|
|
260
|
+
check_rows(c, WindowLayout(window))
|
|
261
|
+
t = merge_windows(ops.ee, gather_windows(ops.ee, x, coarse1), c.in_dim, window=window)
|
|
262
|
+
t["norm"] = layer_norm(ops, t["cat"], c.norm_weight, c.norm_bias, dim=4 * c.in_dim)
|
|
263
|
+
t["out"] = linear(ops, t["norm"], c.reduction)
|
|
264
|
+
return t
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
# ---------------------------------------------------------------------------
|
|
268
|
+
# Cost functions (contract section 9)
|
|
269
|
+
# ---------------------------------------------------------------------------
|
|
270
|
+
def patch_embed_values(dim: int, *, patch: int = PATCH, channels: int = len(BANDS),
|
|
271
|
+
layout=WINDOW_LAYOUT) -> int:
|
|
272
|
+
"""Values embedded for NumPy weights: the projection W^T [C patch^2, dim] and its bias
|
|
273
|
+
rows, and the LayerNorm's gamma and beta rows."""
|
|
274
|
+
return (linear_values(channels * patch * patch, dim, bias=True, layout=layout)
|
|
275
|
+
+ layer_norm_values(dim, layout=layout))
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
def patch_embed_arrays() -> int:
|
|
279
|
+
"""Server arrays read: proj.weight, proj.bias, norm.weight, norm.bias."""
|
|
280
|
+
return 4
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
def patch_merge_values(channels: int, *, bias: bool = False, layout=WINDOW_LAYOUT) -> int:
|
|
284
|
+
"""Values embedded for NumPy weights: LayerNorm(4C) gamma and beta rows and the
|
|
285
|
+
reduction W^T [4C, 2C] (timm: no bias)."""
|
|
286
|
+
return layer_norm_values(4 * channels, layout=layout) + linear_values(
|
|
287
|
+
4 * channels, 2 * channels, bias=bias, layout=layout)
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
def patch_merge_arrays(*, bias: bool = False) -> int:
|
|
291
|
+
"""Server arrays read: norm.weight, norm.bias, reduction.weight (and its bias)."""
|
|
292
|
+
return 4 if bias else 3
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
_LARGEST_PATCH = 8
|
|
296
|
+
"""The largest validated patch. The metadata radius of ``patch_embed`` and
|
|
297
|
+
``stride_columns`` is half of it, the reach of a token's patch from its centre: the largest
|
|
298
|
+
validated reach, as for ``pool2d``. It was ``PATCH // 2`` = 2 while only patch 4 was
|
|
299
|
+
validated. ``patch_locality`` gives the reach at a call's patch."""
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
def patch_locality(*, patch: int = PATCH) -> dict:
|
|
303
|
+
"""The locality of a patch embedding at a patch (``BlockMetadata.per_setting``): k x k,
|
|
304
|
+
radius ``patch // 2``, the library's convention for a patch; patch 1 reads its own
|
|
305
|
+
pixel."""
|
|
306
|
+
if patch // 2 < 1:
|
|
307
|
+
return {"locality": Locality.POINTWISE, "radius": None}
|
|
308
|
+
return {"locality": Locality.KERNEL, "radius": patch // 2}
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
_WINDOWS = (5, 7, 8, 12)
|
|
312
|
+
"""The window sizes validated live for patch merging and patch embedding."""
|
|
313
|
+
|
|
314
|
+
_PATCHES = {"patch": (2, 4, 8), "channels": (1, 2, 3, 4)}
|
|
315
|
+
"""Validated patches and band counts (``channels``, ``patch_embed_values``'s name for the number of
|
|
316
|
+
bands; the names themselves are labels)."""
|
|
317
|
+
|
|
318
|
+
METADATA = validate_metadata({
|
|
319
|
+
"patch_embed": BlockMetadata(
|
|
320
|
+
name="patch_embed", layout_in="R", layout_out="W", locality=Locality.KERNEL, radius=_LARGEST_PATCH // 2,
|
|
321
|
+
policy=DOUBLE_ROUND_ONCE, embedded_constants=patch_embed_values, asset_arrays=patch_embed_arrays,
|
|
322
|
+
status="Ready — patches 2, 4, 8; 1–4 bands; widths 48, 96, 128; windows 5, 7, 8, 12",
|
|
323
|
+
evidence=("docs/history/building-blocks-2026-10-11.md#33-convolution", "docs/history/swin_image_encoder/notes/milestone6_findings.md",
|
|
324
|
+
"docs/evidence/p03-offline.json",
|
|
325
|
+
"docs/evidence/live-fixtures/20261013T120034Z-p03.json"),
|
|
326
|
+
validated=Validated(({**_PATCHES, "dim": (48, 96, 128), "window": _WINDOWS},)),
|
|
327
|
+
per_setting=patch_locality),
|
|
328
|
+
"stride_columns": BlockMetadata(
|
|
329
|
+
name="stride_columns", layout_in="R", layout_out="W", locality=Locality.KERNEL, radius=_LARGEST_PATCH // 2,
|
|
330
|
+
policy=NO_ARITHMETIC, embedded_constants=no_constants, asset_arrays=no_constants,
|
|
331
|
+
status="Ready — patches 2, 4, 8; 1–4 bands",
|
|
332
|
+
evidence=("docs/history/building-blocks-2026-10-11.md#33-convolution", "docs/history/swin_image_encoder/notes/milestone6_findings.md",
|
|
333
|
+
"docs/evidence/p03-offline.json",
|
|
334
|
+
"docs/evidence/live-fixtures/20261013T120034Z-p03.json"),
|
|
335
|
+
validated=Validated((_PATCHES,), sources=(patch_embed_values,)),
|
|
336
|
+
per_setting=patch_locality),
|
|
337
|
+
# Validated: the window sizes above; timm's PatchMerging has no reduction bias (never varied).
|
|
338
|
+
"patch_merge": BlockMetadata(
|
|
339
|
+
name="patch_merge", layout_in="W", layout_out="W", locality=Locality.KERNEL, radius=1,
|
|
340
|
+
policy=DOUBLE_ROUND_ONCE, embedded_constants=patch_merge_values, asset_arrays=patch_merge_arrays,
|
|
341
|
+
status="Ready — windows 5, 7, 8, 12",
|
|
342
|
+
evidence=("docs/history/building-blocks-2026-10-11.md#37-resampling", "docs/history/swin_hierarchy/notes/milestone4_findings.md",
|
|
343
|
+
"docs/evidence/p01-offline.json",
|
|
344
|
+
"docs/evidence/live-fixtures/20261013T074301Z-p01a.json", "docs/evidence/live-fixtures/20261013T074330Z-p01b.json", "docs/evidence/live-fixtures/20261013T075542Z-window-relative_position_bias-w12.json"),
|
|
345
|
+
validated=Validated(({"window": _WINDOWS, "bias": (False,)},))),
|
|
346
|
+
"gather_windows": BlockMetadata(
|
|
347
|
+
name="gather_windows", layout_in="W", layout_out="W", locality=Locality.KERNEL, radius=1,
|
|
348
|
+
policy=NO_ARITHMETIC, embedded_constants=no_constants, asset_arrays=no_constants,
|
|
349
|
+
status="Ready — windows 5, 7, 8, 12",
|
|
350
|
+
evidence=("docs/history/building-blocks-2026-10-11.md#37-resampling", "docs/history/swin_hierarchy/notes/milestone4_findings.md",
|
|
351
|
+
"docs/evidence/p01-offline.json",
|
|
352
|
+
"docs/evidence/live-fixtures/20261013T074301Z-p01a.json", "docs/evidence/live-fixtures/20261013T074330Z-p01b.json", "docs/evidence/live-fixtures/20261013T075542Z-window-relative_position_bias-w12.json"),
|
|
353
|
+
validated=Validated(({"window": _WINDOWS},))),
|
|
354
|
+
"merge_windows": BlockMetadata(
|
|
355
|
+
name="merge_windows", layout_in="W", layout_out="W", locality=Locality.WINDOW,
|
|
356
|
+
policy=NO_ARITHMETIC, embedded_constants=no_constants, asset_arrays=no_constants,
|
|
357
|
+
status="Ready — windows 5, 7, 8, 12",
|
|
358
|
+
evidence=("docs/history/building-blocks-2026-10-11.md#37-resampling", "docs/history/swin_hierarchy/notes/milestone4_findings.md",
|
|
359
|
+
"docs/evidence/p01-offline.json",
|
|
360
|
+
"docs/evidence/live-fixtures/20261013T074301Z-p01a.json", "docs/evidence/live-fixtures/20261013T074330Z-p01b.json", "docs/evidence/live-fixtures/20261013T075542Z-window-relative_position_bias-w12.json"),
|
|
361
|
+
validated=Validated(({"window": _WINDOWS},))),
|
|
362
|
+
})
|