Image-to-Text
PyTorch
Safetensors
PEFT
English
remote-sensing
satellite-imagery
earth-observation
change-detection
visual-grounding
image-captioning
visual-question-answering
optical-sar-fusion
sar
multimodal
lora
Instructions to use thundercode/SatQuery with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- PEFT
How to use thundercode/SatQuery with PEFT:
Task type is invalid.
- Notebooks
- Google Colab
- Kaggle
release: add docs/RESEARCH_NOTES.md
Browse files- docs/RESEARCH_NOTES.md +86 -46
docs/RESEARCH_NOTES.md
CHANGED
|
@@ -1,7 +1,8 @@
|
|
| 1 |
# Research Notes
|
| 2 |
|
| 3 |
-
Engineering findings, negative results and design decisions that would otherwise be lost.
|
| 4 |
-
learned by **probe or execution**, not
|
|
|
|
| 5 |
|
| 6 |
**Status tags:** `MEASURED` Β· `RESOLVED` Β· `REJECTED` Β· `OPEN` Β· `ATTEMPTED`.
|
| 7 |
|
|
@@ -11,32 +12,37 @@ learned by **probe or execution**, not by assumption, and each is recorded so it
|
|
| 11 |
|
| 12 |
### F4-1 β the MiniLM tokenizer ceiling (`MEASURED`)
|
| 13 |
|
| 14 |
-
The MiniLM tokenizer's own ceiling is **256** (verified by probe).
|
| 15 |
a deliberate truncation *well inside* the ceiling, not the model limit. Satellite queries are short;
|
| 16 |
halving the sequence halves attention cost for no measurable accuracy loss. The encoder **asserts**
|
| 17 |
-
`max_length β€ 256`, because truncating above the ceiling is a silent no-op.
|
|
|
|
|
|
|
| 18 |
|
| 19 |
### F4-2 β the router needs no GPU (`MEASURED`)
|
| 20 |
|
| 21 |
The encoder is frozen, so embeddings are **cached** and the 50,822-parameter adapter trains on cached
|
| 22 |
-
vectors. **Measured on CPU: 20 epochs / 4,096 vectors in 0.28 s.**
|
|
|
|
|
|
|
| 23 |
|
| 24 |
### F4-3 β splits must be by group (`MEASURED`)
|
| 25 |
|
| 26 |
Splits are by **group** (template / hard-negative family), never by example. Hard-negative families
|
| 27 |
are placed in the **test** split so their accuracy measures generalisation rather than memorisation.
|
| 28 |
-
Splitting by example would leak template variants across the boundary.
|
| 29 |
|
| 30 |
### F5-1 β `AutoModelForVision2Seq` does not exist (`MEASURED`)
|
| 31 |
|
| 32 |
-
In transformers 5.17.0, `AutoModelForVision2Seq` **does not exist** (not merely deprecated);
|
| 33 |
-
`AutoModelForImageTextToText` is present.
|
| 34 |
-
|
|
|
|
| 35 |
|
| 36 |
### F5-2 β the processor cost overrun is ~17Γ, not 4Γ (`MEASURED`)
|
| 37 |
|
| 38 |
-
The processor's default `longest_edge` is **2048**, which upscales 512-px
|
| 39 |
-
|
| 40 |
|
| 41 |
| Setting | `pixel_values` | prompt tokens |
|
| 42 |
|---|---|---|
|
|
@@ -44,18 +50,25 @@ them (`do_image_splitting=True`) into **17 sub-images**:
|
|
| 44 |
| pinned (`processor_longest_edge: 512`) | `(1, 1, 3, 512, 512)` | β |
|
| 45 |
|
| 46 |
The plan estimated a 4Γ cost overrun; the real figure is **~17Γ**. The value **must** be set
|
| 47 |
-
explicitly on the processor at construction time.
|
|
|
|
| 48 |
|
| 49 |
### F5-3 β prompts must go through the chat template (`MEASURED`)
|
| 50 |
|
| 51 |
SmolVLM requires one `<image>` token per image in the prompt. Hand-written prompt strings raise
|
| 52 |
-
`ValueError`.
|
|
|
|
|
|
|
|
|
|
| 53 |
|
| 54 |
### P7-1 β the RemoteCLIP projected dimension is 512 (`MEASURED`)
|
| 55 |
|
| 56 |
The RemoteCLIP ViT-B/32 transformer width is **768**, but `visual.proj` maps to a **projected** dim of
|
| 57 |
-
**512**. The grounding head's per-cell feature is `4 Γ 512 = 2048`
|
| 58 |
-
|
|
|
|
|
|
|
|
|
|
| 59 |
|
| 60 |
### C-1 β the availability mask is consumed by the head, not by CROMA (`MEASURED`)
|
| 61 |
|
|
@@ -64,21 +77,23 @@ by the **fusion head** (`input_dim = 3 Γ 768 + 12 + 2 = 2318`), not by the enco
|
|
| 64 |
|
| 65 |
### C-6 β T4 is SM 7.5, so training uses fp16, not bf16 (`MEASURED`)
|
| 66 |
|
| 67 |
-
|
| 68 |
-
|
| 69 |
|
| 70 |
### C-7 β `image_resolution % 8 == 0` (`MEASURED`)
|
| 71 |
|
| 72 |
-
CROMA requires `image_resolution % 8 == 0`. The native value **120** yields 225 patches.
|
|
|
|
| 73 |
|
| 74 |
### C-8 β ZeroGPU does not support `torch.compile` (`MEASURED`)
|
| 75 |
|
| 76 |
-
`torch.compile` must never be enabled on the (historical) ZeroGPU target.
|
|
|
|
| 77 |
|
| 78 |
-
### C-9 β STANet
|
| 79 |
|
| 80 |
-
Change detection uses STANet-style architecture with upstream-verified hyperparameters
|
| 81 |
-
self-attention,
|
| 82 |
|
| 83 |
---
|
| 84 |
|
|
@@ -86,27 +101,28 @@ self-attention, ResNet-18 encoder).
|
|
| 86 |
|
| 87 |
**Question:** should grounding decode at 448 or 224?
|
| 88 |
|
| 89 |
-
**Answer: 224. 448 was rejected** β
|
| 90 |
-
|
| 91 |
|
| 92 |
-
| Comparison (
|
| 93 |
|---|---|
|
| 94 |
| mean best IoU | **β0.0147** |
|
| 95 |
| recall@0.5 | β0.0022 |
|
| 96 |
| recall@0.10 | β0.0699 |
|
| 97 |
| recall@0.25 | β0.0243 |
|
| 98 |
| latency | **1.59Γ** |
|
| 99 |
-
| paired
|
|
|
|
| 100 |
| paired t | **β22.63** |
|
| 101 |
| 448 better on | 8.5 % of records |
|
| 102 |
| 448 worse on | **20.9 %** of records |
|
| 103 |
|
| 104 |
448 lost on **every** axis. The pre-registered decision rule and the paired test **agree** on 224.
|
| 105 |
-
This is
|
| 106 |
|
| 107 |
---
|
| 108 |
|
| 109 |
-
## 3. The router defect β a real bug, found and fixed
|
| 110 |
|
| 111 |
### 3.1 Symptom
|
| 112 |
|
|
@@ -121,10 +137,10 @@ Two functions with different information:
|
|
| 121 |
- **`interpret()`** β produces the console's *reading*; **asset-count-blind** (text only).
|
| 122 |
- **`chooseTask()`** β performs *dispatch*; **asset-count-aware**.
|
| 123 |
|
| 124 |
-
The defect was in the dispatch path's handling of spatial/lexical cues, so region queries fell
|
| 125 |
-
|
| 126 |
|
| 127 |
-
### 3.3 Fix and verification
|
| 128 |
|
| 129 |
The fix was deployed to `SatQuery-Frontend` and validated by **three independent live passes**:
|
| 130 |
|
|
@@ -141,18 +157,18 @@ Both defect queries now dispatch to `grounding`:
|
|
| 141 |
| Where are the built-up areas in this image? | `run_467ffa406f22` | `grounding` |
|
| 142 |
| Where is the new airport? | `run_46980ba55c62` | `grounding` |
|
| 143 |
|
| 144 |
-
24 live runs, 24 correct dispatches,
|
| 145 |
[`../screenshots/`](../screenshots/).
|
| 146 |
|
| 147 |
---
|
| 148 |
|
| 149 |
-
## 4. The harness false-positive β caught before it could lie
|
| 150 |
|
| 151 |
An earlier live-validation harness typed queries with **synthetic CDP key events**, which Chrome
|
| 152 |
**silently drops when the window lacks OS focus**. The harness therefore dispatched the page's
|
| 153 |
*default* query and still recorded a "result" β a **false pass**.
|
| 154 |
|
| 155 |
-
**Fix:** the current harness **asserts form state before dispatch** (`q_ok`, `obs_ok`, `t0_ok`)
|
| 156 |
uses deterministic query entry (`js()` value-set + `type_text()` via CDP `Input.insertText`).
|
| 157 |
|
| 158 |
**Independent check:** the earlier 8/8 run was re-examined and confirmed **not** infected β its
|
|
@@ -165,9 +181,12 @@ recorded because it is exactly the silent false-positive an evaluation harness m
|
|
| 165 |
|
| 166 |
`SATQUERY_TRANSPORT=auto` tries the tunnel, then falls through to the forward path on timeout. The
|
| 167 |
forward path to a **private** repo returns `302` quickly, but the wake step still consumes
|
| 168 |
-
`SATQUERY_WAKE_TIMEOUT_S` (120 s) first β so a worst-case failed request takes β 249 s
|
| 169 |
-
(150 + 120). This is the root shape of the observed transient tunnel gap.
|
| 170 |
-
|
|
|
|
|
|
|
|
|
|
| 171 |
|
| 172 |
---
|
| 173 |
|
|
@@ -175,7 +194,7 @@ deployed fix for the `codespace_name` newline on the wake path was authored sepa
|
|
| 175 |
|
| 176 |
For *"What changed between the earlier and later image?"* with **one** asset attached, the console
|
| 177 |
**reads** `change` while dispatch correctly falls back to **`change_vqa`**. This is not a bug: the
|
| 178 |
-
reading describes the question's intent
|
| 179 |
assets present. It is documented so it is not mistaken for a defect.
|
| 180 |
|
| 181 |
---
|
|
@@ -184,21 +203,42 @@ assets present. It is documented so it is not mistaken for a defect.
|
|
| 184 |
|
| 185 |
| Finding | Detail |
|
| 186 |
|---|---|
|
| 187 |
-
| **Dead proxy in the sandbox** | outbound calls need `--noproxy '*'` (curl) or `ProxyHandler({})` (Python). |
|
|
|
|
|
|
|
| 188 |
| **pytest is only in the repo venv** | `.venv/Scripts/python.exe`; a bare `pytest` misses it. |
|
| 189 |
-
| **
|
| 190 |
| **Cloudflare `_headers` concatenate** | two matching rules are merged, not overridden; Chromium takes the **first** `max-age`. |
|
| 191 |
| **Cloudflare 308-redirects `X.html` β `/X`** | reference the extensionless path. |
|
| 192 |
-
| **A forwarded Codespace port returns `302`** for a private repo
|
| 193 |
| **Chrome drops synthetic CDP key events without OS focus** | the harness false-positive (Β§4). |
|
| 194 |
-
| **`browser-use` block-buffers stdout** even when redirected
|
| 195 |
|
| 196 |
---
|
| 197 |
|
| 198 |
## 8. The BigEarthNet format contradiction (`ATTEMPTED`, reported not resolved)
|
| 199 |
|
| 200 |
The BigEarthNet data format **contradicts the original plan**. This was **reported rather than
|
| 201 |
-
silently patched**, because quietly changing the preprocessing would move the frozen config hash.
|
| 202 |
-
|
| 203 |
-
|
| 204 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
# Research Notes
|
| 2 |
|
| 3 |
+
Engineering findings, negative results, and design decisions that would otherwise be lost. Every one
|
| 4 |
+
was learned by **probe or execution**, not assumption, and every one is recorded so it is not
|
| 5 |
+
rediscovered.
|
| 6 |
|
| 7 |
**Status tags:** `MEASURED` Β· `RESOLVED` Β· `REJECTED` Β· `OPEN` Β· `ATTEMPTED`.
|
| 8 |
|
|
|
|
| 12 |
|
| 13 |
### F4-1 β the MiniLM tokenizer ceiling (`MEASURED`)
|
| 14 |
|
| 15 |
+
The MiniLM tokenizer's own ceiling is **256** (verified by probe). This project truncates to **128** β
|
| 16 |
a deliberate truncation *well inside* the ceiling, not the model limit. Satellite queries are short;
|
| 17 |
halving the sequence halves attention cost for no measurable accuracy loss. The encoder **asserts**
|
| 18 |
+
`max_length β€ 256`, because truncating above the ceiling is a **silent no-op**.
|
| 19 |
+
|
| 20 |
+
**Consequence:** `router.max_length: 128` with an assertion, rather than a comment.
|
| 21 |
|
| 22 |
### F4-2 β the router needs no GPU (`MEASURED`)
|
| 23 |
|
| 24 |
The encoder is frozen, so embeddings are **cached** and the 50,822-parameter adapter trains on cached
|
| 25 |
+
vectors. **Measured on CPU: 20 epochs / 4,096 vectors in 0.28 s.**
|
| 26 |
+
|
| 27 |
+
**Consequence:** the router can be retrained during development without a GPU.
|
| 28 |
|
| 29 |
### F4-3 β splits must be by group (`MEASURED`)
|
| 30 |
|
| 31 |
Splits are by **group** (template / hard-negative family), never by example. Hard-negative families
|
| 32 |
are placed in the **test** split so their accuracy measures generalisation rather than memorisation.
|
| 33 |
+
Splitting by example would leak template variants across the boundary and inflate the score.
|
| 34 |
|
| 35 |
### F5-1 β `AutoModelForVision2Seq` does not exist (`MEASURED`)
|
| 36 |
|
| 37 |
+
In transformers 5.17.0, `AutoModelForVision2Seq` **does not exist** (it is not merely deprecated);
|
| 38 |
+
`AutoModelForImageTextToText` is present.
|
| 39 |
+
|
| 40 |
+
**Consequence:** the loader is resolved by **feature detection**, never hardcoded to one class name.
|
| 41 |
|
| 42 |
### F5-2 β the processor cost overrun is ~17Γ, not 4Γ (`MEASURED`)
|
| 43 |
|
| 44 |
+
The processor's default `longest_edge` is **2048**, which upscales a 512-px tile **4Γ** and then
|
| 45 |
+
splits it (`do_image_splitting=True`) into sub-images:
|
| 46 |
|
| 47 |
| Setting | `pixel_values` | prompt tokens |
|
| 48 |
|---|---|---|
|
|
|
|
| 50 |
| pinned (`processor_longest_edge: 512`) | `(1, 1, 3, 512, 512)` | β |
|
| 51 |
|
| 52 |
The plan estimated a 4Γ cost overrun; the real figure is **~17Γ**. The value **must** be set
|
| 53 |
+
explicitly on the processor at construction time, and `core/config.py` now **enforces**
|
| 54 |
+
`processor_longest_edge <= image.tile_size` so the pin is a control rather than a comment.
|
| 55 |
|
| 56 |
### F5-3 β prompts must go through the chat template (`MEASURED`)
|
| 57 |
|
| 58 |
SmolVLM requires one `<image>` token per image in the prompt. Hand-written prompt strings raise
|
| 59 |
+
`ValueError`.
|
| 60 |
+
|
| 61 |
+
**Consequence:** prompts are always built through `processor.apply_chat_template()`, enforced by the
|
| 62 |
+
config key `vlm.prompt_must_use_chat_template: true`.
|
| 63 |
|
| 64 |
### P7-1 β the RemoteCLIP projected dimension is 512 (`MEASURED`)
|
| 65 |
|
| 66 |
The RemoteCLIP ViT-B/32 transformer width is **768**, but `visual.proj` maps to a **projected** dim of
|
| 67 |
+
**512**. The grounding head's per-cell feature is therefore `4 Γ 512 = 2048`.
|
| 68 |
+
|
| 69 |
+
**Consequence:** `grounding.encoder_projected_dim: 512` is declared in config so `core/config.py` can
|
| 70 |
+
validate the head **without importing torch**, and `specialists/grounding/remoteclip.py` asserts the
|
| 71 |
+
same value against the real model at load time.
|
| 72 |
|
| 73 |
### C-1 β the availability mask is consumed by the head, not by CROMA (`MEASURED`)
|
| 74 |
|
|
|
|
| 77 |
|
| 78 |
### C-6 β T4 is SM 7.5, so training uses fp16, not bf16 (`MEASURED`)
|
| 79 |
|
| 80 |
+
`training.precision: fp16` because the target GPU (T4) is compute capability 7.5; bf16 is unavailable
|
| 81 |
+
there. The loader validates the value is one of `fp16|bf16|fp32`.
|
| 82 |
|
| 83 |
### C-7 β `image_resolution % 8 == 0` (`MEASURED`)
|
| 84 |
|
| 85 |
+
CROMA requires `image_resolution % 8 == 0`. The native value **120** yields 225 patches. Enforced at
|
| 86 |
+
config load.
|
| 87 |
|
| 88 |
### C-8 β ZeroGPU does not support `torch.compile` (`MEASURED`)
|
| 89 |
|
| 90 |
+
`torch.compile` must never be enabled on the (historical) ZeroGPU target. Enforced: the loader
|
| 91 |
+
**fails startup** if `deployment.torch_compile` is true.
|
| 92 |
|
| 93 |
+
### C-9 β STANet hyperparameters are upstream-verified (`MEASURED`)
|
| 94 |
|
| 95 |
+
Change detection uses a STANet-style architecture with upstream-verified hyperparameters: ResNet-18
|
| 96 |
+
encoder, **PAM** self-attention mode, tile 256, threshold 0.50, BCE 0.5 + Dice 0.5.
|
| 97 |
|
| 98 |
---
|
| 99 |
|
|
|
|
| 101 |
|
| 102 |
**Question:** should grounding decode at 448 or 224?
|
| 103 |
|
| 104 |
+
**Answer: 224. 448 was rejected** β notable because the rejection was *pre-registered* and then
|
| 105 |
+
*confirmed* by a paired test over identical samples (n = 16,159):
|
| 106 |
|
| 107 |
+
| Comparison (448 vs 224) | Value |
|
| 108 |
|---|---|
|
| 109 |
| mean best IoU | **β0.0147** |
|
| 110 |
| recall@0.5 | β0.0022 |
|
| 111 |
| recall@0.10 | β0.0699 |
|
| 112 |
| recall@0.25 | β0.0243 |
|
| 113 |
| latency | **1.59Γ** |
|
| 114 |
+
| paired mean difference | β0.0147 |
|
| 115 |
+
| paired 95 % CI | **[β0.0160, β0.0134]** |
|
| 116 |
| paired t | **β22.63** |
|
| 117 |
| 448 better on | 8.5 % of records |
|
| 118 |
| 448 worse on | **20.9 %** of records |
|
| 119 |
|
| 120 |
448 lost on **every** axis. The pre-registered decision rule and the paired test **agree** on 224.
|
| 121 |
+
This is the model for how a resolution decision should be made: declared in advance, then tested.
|
| 122 |
|
| 123 |
---
|
| 124 |
|
| 125 |
+
## 3. The router defect β a real bug, found and fixed (`RESOLVED`)
|
| 126 |
|
| 127 |
### 3.1 Symptom
|
| 128 |
|
|
|
|
| 137 |
- **`interpret()`** β produces the console's *reading*; **asset-count-blind** (text only).
|
| 138 |
- **`chooseTask()`** β performs *dispatch*; **asset-count-aware**.
|
| 139 |
|
| 140 |
+
The defect was in the dispatch path's handling of spatial/lexical cues, so region queries fell through
|
| 141 |
+
to the generic VQA specialist.
|
| 142 |
|
| 143 |
+
### 3.3 Fix and verification
|
| 144 |
|
| 145 |
The fix was deployed to `SatQuery-Frontend` and validated by **three independent live passes**:
|
| 146 |
|
|
|
|
| 157 |
| Where are the built-up areas in this image? | `run_467ffa406f22` | `grounding` |
|
| 158 |
| Where is the new airport? | `run_46980ba55c62` | `grounding` |
|
| 159 |
|
| 160 |
+
**24 live runs, 24 correct dispatches, 0 mock nodes.** Screenshots are in
|
| 161 |
[`../screenshots/`](../screenshots/).
|
| 162 |
|
| 163 |
---
|
| 164 |
|
| 165 |
+
## 4. The harness false-positive β caught before it could lie (`RESOLVED`)
|
| 166 |
|
| 167 |
An earlier live-validation harness typed queries with **synthetic CDP key events**, which Chrome
|
| 168 |
**silently drops when the window lacks OS focus**. The harness therefore dispatched the page's
|
| 169 |
*default* query and still recorded a "result" β a **false pass**.
|
| 170 |
|
| 171 |
+
**Fix:** the current harness **asserts form state before dispatch** (`q_ok`, `obs_ok`, `t0_ok`) and
|
| 172 |
uses deterministic query entry (`js()` value-set + `type_text()` via CDP `Input.insertText`).
|
| 173 |
|
| 174 |
**Independent check:** the earlier 8/8 run was re-examined and confirmed **not** infected β its
|
|
|
|
| 181 |
|
| 182 |
`SATQUERY_TRANSPORT=auto` tries the tunnel, then falls through to the forward path on timeout. The
|
| 183 |
forward path to a **private** repo returns `302` quickly, but the wake step still consumes
|
| 184 |
+
`SATQUERY_WAKE_TIMEOUT_S` (120 s) first β so a worst-case failed request takes β **249 s**
|
| 185 |
+
(150 + 120). This is the root shape of the observed transient tunnel gap.
|
| 186 |
+
|
| 187 |
+
A patch adding `forward_unavailable` (503) and `upstream_timeout` (504) codes plus the
|
| 188 |
+
`codespace_name` `.strip()` fix was authored and verified (`py_compile` clean). **Status: OPEN β the
|
| 189 |
+
patch is prepared but NOT deployed.**
|
| 190 |
|
| 191 |
---
|
| 192 |
|
|
|
|
| 194 |
|
| 195 |
For *"What changed between the earlier and later image?"* with **one** asset attached, the console
|
| 196 |
**reads** `change` while dispatch correctly falls back to **`change_vqa`**. This is not a bug: the
|
| 197 |
+
reading describes the question's intent; the dispatch respects what can actually be computed with the
|
| 198 |
assets present. It is documented so it is not mistaken for a defect.
|
| 199 |
|
| 200 |
---
|
|
|
|
| 203 |
|
| 204 |
| Finding | Detail |
|
| 205 |
|---|---|
|
| 206 |
+
| **Dead proxy in the authoring sandbox** | outbound calls need `--noproxy '*'` (curl) or `ProxyHandler({})` (Python). |
|
| 207 |
+
| **The sandbox proxy is slow for uploads** | `huggingface_hub` uploads stalled at ~51 kB/s through `http_proxy=127.0.0.1:58294`; the fix is to unset `http_proxy`/`https_proxy` and set `no_proxy='*'`. |
|
| 208 |
+
| **`hf_hub_download` returned an EMPTY file** | sha256 `e3b0c442β¦` (the empty-content hash), which produced a **false FAIL** for all six artifacts. The honest check is a direct HTTPS download with `ProxyHandler({})`. |
|
| 209 |
| **pytest is only in the repo venv** | `.venv/Scripts/python.exe`; a bare `pytest` misses it. |
|
| 210 |
+
| **The full test suite trips a bulk-delete guard** | sandbox-specific; affects `test_safe_delete_shim`. |
|
| 211 |
| **Cloudflare `_headers` concatenate** | two matching rules are merged, not overridden; Chromium takes the **first** `max-age`. |
|
| 212 |
| **Cloudflare 308-redirects `X.html` β `/X`** | reference the extensionless path. |
|
| 213 |
+
| **A forwarded Codespace port returns `302`** | for a private repo β this is *why* the tunnel exists. |
|
| 214 |
| **Chrome drops synthetic CDP key events without OS focus** | the harness false-positive (Β§4). |
|
| 215 |
+
| **`browser-use` block-buffers stdout** | even when redirected; needs explicit line buffering to stream. |
|
| 216 |
|
| 217 |
---
|
| 218 |
|
| 219 |
## 8. The BigEarthNet format contradiction (`ATTEMPTED`, reported not resolved)
|
| 220 |
|
| 221 |
The BigEarthNet data format **contradicts the original plan**. This was **reported rather than
|
| 222 |
+
silently patched**, because quietly changing the preprocessing would move the frozen config hash.
|
| 223 |
+
|
| 224 |
+
Two facts matter:
|
| 225 |
+
|
| 226 |
+
1. The BigEarthNet documentation β its uses, mentions, or endorsements β does **not** specify a
|
| 227 |
+
percentile stretch. This project nevertheless applies percentile normalisation (2/98) for optical
|
| 228 |
+
inputs to match the CROMA contract. That is a **deliberate, documented choice**, not an upstream
|
| 229 |
+
fact.
|
| 230 |
+
2. The local subset is **100 % single-label**, against the official 1β11 multi-label scheme, so
|
| 231 |
+
metrics computed on it are **not comparable** to published multi-label numbers.
|
| 232 |
+
|
| 233 |
+
---
|
| 234 |
+
|
| 235 |
+
## 9. Where the evidence lives
|
| 236 |
+
|
| 237 |
+
| Topic | Evidence |
|
| 238 |
+
|---|---|
|
| 239 |
+
| Findings F4-1β¦F5-3, C-1β¦C-9, P7-1 | `docs/PHASE*.md`, `docs/ARCHITECTURE_*.md`, `docs/CROMA_NORMALISATION_UPSTREAM_EVIDENCE.md` |
|
| 240 |
+
| The 448-vs-224 paired test | `docs/PHASE7_RESOLUTION_DECISION.md` |
|
| 241 |
+
| The VLM rejection | `docs/PHASE6_RUN1_REJECTION_DIAGNOSIS.md`, `artifacts/vlm/phase6_closure.json` |
|
| 242 |
+
| Router defect + 3 live passes | `.workbuddy-ai/scratch/live_validation/` (`run_output.txt`, `run_final2.txt`, `run_final3.txt`) |
|
| 243 |
+
| The undeployed B-07 patch | session scratch `fix-b07-forward-unavailable.patch` |
|
| 244 |
+
| Live validation harness | `.workbuddy-ai/scratch/run_all_postfix2.harness`, `recompute_verdicts.py` |
|