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.
Files changed (84) hide show
  1. tensor_ee/__init__.py +28 -0
  2. tensor_ee/_swin_parameters.py +304 -0
  3. tensor_ee/blocks/__init__.py +0 -0
  4. tensor_ee/blocks/raster/__init__.py +36 -0
  5. tensor_ee/blocks/raster/activations.py +183 -0
  6. tensor_ee/blocks/raster/conv.py +183 -0
  7. tensor_ee/blocks/raster/merge.py +66 -0
  8. tensor_ee/blocks/raster/outputs.py +211 -0
  9. tensor_ee/blocks/raster/pooling.py +182 -0
  10. tensor_ee/blocks/raster/resample.py +64 -0
  11. tensor_ee/blocks/window/__init__.py +44 -0
  12. tensor_ee/blocks/window/activations.py +54 -0
  13. tensor_ee/blocks/window/attention.py +366 -0
  14. tensor_ee/blocks/window/conv.py +323 -0
  15. tensor_ee/blocks/window/linear.py +129 -0
  16. tensor_ee/blocks/window/merge.py +67 -0
  17. tensor_ee/blocks/window/normalization.py +197 -0
  18. tensor_ee/blocks/window/patch.py +362 -0
  19. tensor_ee/blocks/window/pooling.py +275 -0
  20. tensor_ee/blocks/window/resample.py +286 -0
  21. tensor_ee/blocks/window/segformer.py +718 -0
  22. tensor_ee/blocks/window/swin.py +602 -0
  23. tensor_ee/blocks/window/swin_upernet_config.py +361 -0
  24. tensor_ee/blocks/window/upernet.py +1016 -0
  25. tensor_ee/comparison.py +213 -0
  26. tensor_ee/compatibility.py +180 -0
  27. tensor_ee/compiler.py +356 -0
  28. tensor_ee/converter/__init__.py +72 -0
  29. tensor_ee/converter/build.py +120 -0
  30. tensor_ee/converter/convert.py +388 -0
  31. tensor_ee/converter/export.py +846 -0
  32. tensor_ee/converter/graph.py +417 -0
  33. tensor_ee/converter/operations.py +790 -0
  34. tensor_ee/converter/plan.py +675 -0
  35. tensor_ee/converter/recognizers.py +1273 -0
  36. tensor_ee/converter/request.py +146 -0
  37. tensor_ee/converter/run.py +950 -0
  38. tensor_ee/core/__init__.py +8 -0
  39. tensor_ee/core/errors.py +25 -0
  40. tensor_ee/core/grid.py +323 -0
  41. tensor_ee/core/metadata.py +398 -0
  42. tensor_ee/core/ops.py +336 -0
  43. tensor_ee/core/precision.py +133 -0
  44. tensor_ee/core/tensor.py +129 -0
  45. tensor_ee/examples/__init__.py +1 -0
  46. tensor_ee/examples/nonwater.py +251 -0
  47. tensor_ee/layouts/__init__.py +17 -0
  48. tensor_ee/layouts/boundaries.py +103 -0
  49. tensor_ee/layouts/raster.py +117 -0
  50. tensor_ee/layouts/shift.py +226 -0
  51. tensor_ee/layouts/window.py +201 -0
  52. tensor_ee/models/__init__.py +16 -0
  53. tensor_ee/models/fixtures.py +119 -0
  54. tensor_ee/models/segformer_b0/__init__.py +9 -0
  55. tensor_ee/models/segformer_b0/configuration.py +209 -0
  56. tensor_ee/models/segformer_b0/decoder.py +130 -0
  57. tensor_ee/models/segformer_b0/encoder.py +90 -0
  58. tensor_ee/models/swin_t224/__init__.py +30 -0
  59. tensor_ee/models/swin_t224/configuration.py +103 -0
  60. tensor_ee/models/swin_t224/decoder.py +53 -0
  61. tensor_ee/models/swin_t224/encoder.py +50 -0
  62. tensor_ee/onnx_import.py +431 -0
  63. tensor_ee/outputs.py +16 -0
  64. tensor_ee/primitives.py +110 -0
  65. tensor_ee/receptive_field.py +257 -0
  66. tensor_ee/resnet_models.py +185 -0
  67. tensor_ee/segwater_inference.py +235 -0
  68. tensor_ee/segwater_preprocessing.py +182 -0
  69. tensor_ee/segwater_pytorch.py +249 -0
  70. tensor_ee/semantics.py +389 -0
  71. tensor_ee/smp.py +155 -0
  72. tensor_ee/swin.py +197 -0
  73. tensor_ee/swin_workflow.py +470 -0
  74. tensor_ee/tiling.py +287 -0
  75. tensor_ee/weight_assets.py +371 -0
  76. tensor_ee/weight_mapping.py +51 -0
  77. tensor_ee/weights/__init__.py +14 -0
  78. tensor_ee/weights/assets.py +160 -0
  79. tensor_ee/weights/constants.py +156 -0
  80. tensor_ee-0.1.0.dist-info/METADATA +172 -0
  81. tensor_ee-0.1.0.dist-info/RECORD +84 -0
  82. tensor_ee-0.1.0.dist-info/WHEEL +5 -0
  83. tensor_ee-0.1.0.dist-info/licenses/LICENSE +21 -0
  84. 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
+ })