Reorganise into per-dataset folders: move the j0126 affinity model to j0126/
Browse files
README.md
CHANGED
|
@@ -9,113 +9,20 @@ tags:
|
|
| 9 |
- mednext
|
| 10 |
---
|
| 11 |
|
| 12 |
-
#
|
| 13 |
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
|
| 18 |
-
| | |
|
| 19 |
-
|---|---|
|
| 20 |
-
|
|
| 21 |
-
| Architecture | MedNeXt-L, kernel 3 (61.8 M parameters) |
|
| 22 |
-
| Input / output | 1 channel EM β 6 channel affinity (`banis+`; channels 0β2 are the nearest-neighbour affinity used downstream) |
|
| 23 |
-
| Training patch | `[48, 96, 96]` (ZYX) β anisotropic, β isotropic in nm for the 9Γ9Γ20 nm grid |
|
| 24 |
-
| Training data | the 33 dense-GT j0126 cubes, heavy `aug_em_neuron` augmentation |
|
| 25 |
-
| Schedule | 200 k steps from scratch (no pretrained init), AdamW, lr 1e-3, batch 8 Γ 4 GPUs |
|
| 26 |
-
|
| 27 |
-
> **This model is not ground-truth-free.** It was trained on labelled j0126 tissue and is
|
| 28 |
-
> the *supervised upper reference* for the zero-shot pipeline in the tutorial, not a
|
| 29 |
-
> drop-in replacement for it. (The dense cubes come from a different alignment than the
|
| 30 |
-
> 50 evaluation skeletons, so it is not voxel-level test contamination β but it is
|
| 31 |
-
> supervision on the target domain.)
|
| 32 |
-
|
| 33 |
-
## Inference window must be `[48, 96, 96]`
|
| 34 |
-
|
| 35 |
-
MedNeXt normalizes with `GroupNorm(num_groups=C, num_channels=C)` β per-sample,
|
| 36 |
-
per-channel instance norm with **no running statistics** β so normalization statistics are
|
| 37 |
-
computed over the sliding window's spatial extent at every block and **the forward pass is
|
| 38 |
-
window-size dependent**. Running this checkpoint at the tutorial's inherited
|
| 39 |
-
`[144, 144, 144]` is a 3Γ Z / 1.5Γ XY mismatch that inverts the trained Z-thin anisotropy.
|
| 40 |
-
|
| 41 |
-
`tutorials/neuron_j0126/1_affinity_supervised.yaml` already sets both
|
| 42 |
-
`model.input_size` and `inference.window.window_size` to `[48, 96, 96]`. Both must be set
|
| 43 |
-
in YAML, never on the CLI: the stage merge can clobber a pre-resolution CLI override.
|
| 44 |
-
|
| 45 |
-
## Usage
|
| 46 |
|
| 47 |
```bash
|
| 48 |
-
hf download pytc/
|
| 49 |
-
|
| 50 |
-
python scripts/main.py --config tutorials/neuron_j0126/1_affinity_supervised.yaml \
|
| 51 |
-
--mode test --checkpoint ckpt/affinity_scratch_48x96x96.ckpt
|
| 52 |
```
|
| 53 |
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
``
|
| 57 |
-
python scripts/main.py --config tutorials/neuron_j0126/1_affinity_supervised.yaml \
|
| 58 |
-
--mode test --checkpoint ckpt/affinity_scratch_48x96x96.ckpt \
|
| 59 |
-
--shard-id "$SLURM_ARRAY_TASK_ID" --num-shards 726
|
| 60 |
-
```
|
| 61 |
-
|
| 62 |
-
Output is one float16 CZYX HDF5 per chunk. Affinities are written with the `scale_sigmoid`
|
| 63 |
-
activation, which the tutorial's ABISS watershed thresholds are calibrated against
|
| 64 |
-
(`scale_sigmoid` 0.70 == plain sigmoid 0.9857) β do not substitute a plain sigmoid without
|
| 65 |
-
recalibrating step 2.
|
| 66 |
-
|
| 67 |
-
## Results
|
| 68 |
-
|
| 69 |
-
Full-volume j0126, 50 evaluation skeletons, from `tutorials/neuron_j0126/README.md`. This
|
| 70 |
-
checkpoint is the "scratch" affinity in that table:
|
| 71 |
-
|
| 72 |
-
| Affinity | Decoding | Error correction | NERL mt=0 β | NERL mt=5 β | VOI β |
|
| 73 |
-
|---|---|---|---:|---:|---:|
|
| 74 |
-
| FFN reference | β | β | **0.526** | 0.538 | **1.856** |
|
| 75 |
-
| scratch | ABISS, exclusion mask | β | 0.268 | 0.470 | 2.584 |
|
| 76 |
-
| scratch | + nucleus instance certificate | β | 0.287 | 0.482 | 2.562 |
|
| 77 |
-
| scratch | + nucleus instance certificate | morphology-guided branch linking | 0.301 | **0.539** | 2.374 |
|
| 78 |
-
| scratch | + nucleus instance certificate | + 3Γ3Γ3 inter-object erosion | **0.441** | 0.528 | 2.440 |
|
| 79 |
-
|
| 80 |
-
Affinity quality alone is not what these numbers measure β see the tutorial for the
|
| 81 |
-
decode and error-correction steps that produce them.
|
| 82 |
-
|
| 83 |
-
## ONNX
|
| 84 |
-
|
| 85 |
-
`affinity_scratch_48x96x96.onnx` is the same weights exported for runtimes without PyTorch.
|
| 86 |
-
|
| 87 |
-
```python
|
| 88 |
-
import numpy as np, onnxruntime as ort
|
| 89 |
-
|
| 90 |
-
session = ort.InferenceSession("affinity_scratch_48x96x96.onnx",
|
| 91 |
-
providers=["CPUExecutionProvider"])
|
| 92 |
-
image = np.random.randn(1, 1, 48, 96, 96).astype(np.float32) # (B, C, Z, Y, X)
|
| 93 |
-
logits = session.run(["logits"], {"image": image})[0] # (B, 6, 48, 96, 96)
|
| 94 |
-
affinity = 1.0 / (1.0 + np.exp(-0.2 * logits)) # scale_sigmoid, temperature 0.2
|
| 95 |
-
```
|
| 96 |
-
|
| 97 |
-
Three things the graph does **not** do for you:
|
| 98 |
-
|
| 99 |
-
- **The activation.** The output is raw logits. The pipeline applies `scale_sigmoid` with
|
| 100 |
-
BANIS' temperature 0.2 β `sigmoid(0.2 * x)` β downstream, and step 2's watershed thresholds
|
| 101 |
-
are calibrated against that.
|
| 102 |
-
- **Channel selection.** Six channels are emitted; the decode consumes channels 0β2.
|
| 103 |
-
- **Sliding-window assembly.** One window in, one window out. Tiling, overlap and blending
|
| 104 |
-
are the caller's job.
|
| 105 |
-
|
| 106 |
-
The spatial dims are fixed at `[48, 96, 96]` and only the batch axis is dynamic β see the
|
| 107 |
-
window section above for why a dynamic spatial axis would be a trap here. Deep supervision is
|
| 108 |
-
switched off in the export, so the graph has a single output.
|
| 109 |
-
|
| 110 |
-
Verified against PyTorch on identical input: max |diff| 2.2e-04 on logits spanning
|
| 111 |
-
[1.03, 21.17] (relative 1.0e-05), which is **4.6e-06** after `scale_sigmoid` β float32
|
| 112 |
-
accumulation noise, not a behavioural difference. Regenerate with
|
| 113 |
-
`python scripts/export_onnx.py --config tutorials/neuron_j0126/1_affinity_supervised.yaml
|
| 114 |
-
--checkpoint <ckpt> --output model.onnx --check`.
|
| 115 |
-
|
| 116 |
-
## Checkpoint format
|
| 117 |
-
|
| 118 |
-
A PyTorch Lightning checkpoint (`state_dict` keys are prefixed `model.model.`). Optimizer
|
| 119 |
-
state has been stripped for download size; the weights are bit-identical to the reference
|
| 120 |
-
run's `step=00200000.ckpt` (all 517 tensors, float32). It is loadable for inference/finetuning,
|
| 121 |
-
not for resuming the original optimizer trajectory.
|
|
|
|
| 9 |
- mednext
|
| 10 |
---
|
| 11 |
|
| 12 |
+
# pytc/vEM β volume EM models
|
| 13 |
|
| 14 |
+
Checkpoints for [PyTorch Connectomics](https://github.com/PytorchConnectomics/pytorch_connectomics),
|
| 15 |
+
one folder per dataset. Each folder has its own README with the training recipe, the
|
| 16 |
+
inference constraints that matter, and the measured results.
|
| 17 |
|
| 18 |
+
| Folder | Dataset | Task | Files |
|
| 19 |
+
|---|---|---|---|
|
| 20 |
+
| [`j0126/`](j0126) | j0126 zebra finch, 9 Γ 9 Γ 20 nm | 3-channel voxel affinity | `affinity_scratch_48x96x96.ckpt` (247 MB) Β· `.onnx` (247 MB, opset 17) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 21 |
|
| 22 |
```bash
|
| 23 |
+
hf download pytc/vEM j0126/affinity_scratch_48x96x96.ckpt --local-dir ckpt/
|
|
|
|
|
|
|
|
|
|
| 24 |
```
|
| 25 |
|
| 26 |
+
> Renamed from `pytc/j0126` on 2026-09-08 and reorganised into per-dataset folders. The old
|
| 27 |
+
> repo URL redirects, but a link that pinned `affinity_scratch_48x96x96.ckpt` at the repo
|
| 28 |
+
> root needs the `j0126/` prefix added.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
j0126/README.md
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# j0126 affinity model (supervised, from scratch)
|
| 2 |
+
|
| 3 |
+
3-channel voxel affinity prediction for the **j0126 zebra finch** EM volume, for use with
|
| 4 |
+
[PyTorch Connectomics](https://github.com/PytorchConnectomics/pytorch_connectomics)
|
| 5 |
+
(`tutorials/neuron_j0126`).
|
| 6 |
+
|
| 7 |
+
| | |
|
| 8 |
+
|---|---|
|
| 9 |
+
| Files | `affinity_scratch_48x96x96.ckpt` (247 MB) Β· `affinity_scratch_48x96x96.onnx` (247 MB, opset 17) |
|
| 10 |
+
| Architecture | MedNeXt-L, kernel 3 (61.8 M parameters) |
|
| 11 |
+
| Input / output | 1 channel EM β 6 channel affinity (`banis+`; channels 0β2 are the nearest-neighbour affinity used downstream) |
|
| 12 |
+
| Training patch | `[48, 96, 96]` (ZYX) β anisotropic, β isotropic in nm for the 9Γ9Γ20 nm grid |
|
| 13 |
+
| Training data | the 33 dense-GT j0126 cubes, heavy `aug_em_neuron` augmentation |
|
| 14 |
+
| Schedule | 200 k steps from scratch (no pretrained init), AdamW, lr 1e-3, batch 8 Γ 4 GPUs |
|
| 15 |
+
|
| 16 |
+
> **This model is not ground-truth-free.** It was trained on labelled j0126 tissue and is
|
| 17 |
+
> the *supervised upper reference* for the zero-shot pipeline in the tutorial, not a
|
| 18 |
+
> drop-in replacement for it. (The dense cubes come from a different alignment than the
|
| 19 |
+
> 50 evaluation skeletons, so it is not voxel-level test contamination β but it is
|
| 20 |
+
> supervision on the target domain.)
|
| 21 |
+
|
| 22 |
+
## Inference window must be `[48, 96, 96]`
|
| 23 |
+
|
| 24 |
+
MedNeXt normalizes with `GroupNorm(num_groups=C, num_channels=C)` β per-sample,
|
| 25 |
+
per-channel instance norm with **no running statistics** β so normalization statistics are
|
| 26 |
+
computed over the sliding window's spatial extent at every block and **the forward pass is
|
| 27 |
+
window-size dependent**. Running this checkpoint at the tutorial's inherited
|
| 28 |
+
`[144, 144, 144]` is a 3Γ Z / 1.5Γ XY mismatch that inverts the trained Z-thin anisotropy.
|
| 29 |
+
|
| 30 |
+
The tutorial sets this in one place: `inference.window_size: [48, 96, 96]` in
|
| 31 |
+
`tutorials/neuron_j0126/params.yaml`, which `2_infer.yaml` reads. Set it in YAML, never on
|
| 32 |
+
the CLI: the stage merge can clobber a pre-resolution CLI override.
|
| 33 |
+
|
| 34 |
+
## Usage
|
| 35 |
+
|
| 36 |
+
The tutorial downloads this checkpoint for you: leave `train.enabled: false` in
|
| 37 |
+
`tutorials/neuron_j0126/params.yaml` and run the whole pipeline with one command.
|
| 38 |
+
|
| 39 |
+
```bash
|
| 40 |
+
python scripts/run_j0126.py
|
| 41 |
+
```
|
| 42 |
+
|
| 43 |
+
To use it directly instead:
|
| 44 |
+
|
| 45 |
+
```bash
|
| 46 |
+
hf download pytc/vEM j0126/affinity_scratch_48x96x96.ckpt --local-dir ckpt/
|
| 47 |
+
|
| 48 |
+
python scripts/main.py --config tutorials/neuron_j0126/2_infer.yaml \
|
| 49 |
+
--mode test --checkpoint ckpt/j0126/affinity_scratch_48x96x96.ckpt
|
| 50 |
+
```
|
| 51 |
+
|
| 52 |
+
Whole volume (726 chunks of 1008Β³ with a 72-voxel halo), one GPU per shard:
|
| 53 |
+
|
| 54 |
+
```bash
|
| 55 |
+
python scripts/main.py --config tutorials/neuron_j0126/2_infer.yaml \
|
| 56 |
+
--mode test --checkpoint ckpt/j0126/affinity_scratch_48x96x96.ckpt \
|
| 57 |
+
--shard-id "$SLURM_ARRAY_TASK_ID" --num-shards 726
|
| 58 |
+
```
|
| 59 |
+
|
| 60 |
+
Output is one float16 CZYX HDF5 per chunk. Affinities are written with the `scale_sigmoid`
|
| 61 |
+
activation, which the tutorial's ABISS watershed thresholds are calibrated against
|
| 62 |
+
(`scale_sigmoid` 0.70 == plain sigmoid 0.9857) β do not substitute a plain sigmoid without
|
| 63 |
+
recalibrating step 3.
|
| 64 |
+
|
| 65 |
+
## Results
|
| 66 |
+
|
| 67 |
+
Full-volume j0126, 50 evaluation skeletons, from `tutorials/neuron_j0126/README.md`. This
|
| 68 |
+
checkpoint is the "scratch" affinity in that table:
|
| 69 |
+
|
| 70 |
+
| Affinity | Decoding | Error correction | NERL mt=0 β | NERL mt=5 β | VOI β |
|
| 71 |
+
|---|---|---|---:|---:|---:|
|
| 72 |
+
| FFN reference | β | β | **0.526** | 0.538 | **1.856** |
|
| 73 |
+
| scratch | ABISS, exclusion mask | β | 0.268 | 0.470 | 2.584 |
|
| 74 |
+
| scratch | + nucleus instance certificate | β | 0.287 | 0.482 | 2.562 |
|
| 75 |
+
| scratch | + nucleus instance certificate | morphology-guided branch linking | 0.301 | **0.539** | 2.374 |
|
| 76 |
+
| scratch | + nucleus instance certificate | + 3Γ3Γ3 inter-object erosion | **0.441** | 0.528 | 2.440 |
|
| 77 |
+
|
| 78 |
+
Affinity quality alone is not what these numbers measure β see the tutorial for the
|
| 79 |
+
decode and error-correction steps that produce them.
|
| 80 |
+
|
| 81 |
+
## ONNX
|
| 82 |
+
|
| 83 |
+
`affinity_scratch_48x96x96.onnx` is the same weights exported for runtimes without PyTorch.
|
| 84 |
+
|
| 85 |
+
```python
|
| 86 |
+
import numpy as np, onnxruntime as ort
|
| 87 |
+
|
| 88 |
+
session = ort.InferenceSession("affinity_scratch_48x96x96.onnx",
|
| 89 |
+
providers=["CPUExecutionProvider"])
|
| 90 |
+
image = np.random.randn(1, 1, 48, 96, 96).astype(np.float32) # (B, C, Z, Y, X)
|
| 91 |
+
logits = session.run(["logits"], {"image": image})[0] # (B, 6, 48, 96, 96)
|
| 92 |
+
affinity = 1.0 / (1.0 + np.exp(-0.2 * logits)) # scale_sigmoid, temperature 0.2
|
| 93 |
+
```
|
| 94 |
+
|
| 95 |
+
Three things the graph does **not** do for you:
|
| 96 |
+
|
| 97 |
+
- **The activation.** The output is raw logits. The pipeline applies `scale_sigmoid` with
|
| 98 |
+
BANIS' temperature 0.2 β `sigmoid(0.2 * x)` β downstream, and step 2's watershed thresholds
|
| 99 |
+
are calibrated against that.
|
| 100 |
+
- **Channel selection.** Six channels are emitted; the decode consumes channels 0β2.
|
| 101 |
+
- **Sliding-window assembly.** One window in, one window out. Tiling, overlap and blending
|
| 102 |
+
are the caller's job.
|
| 103 |
+
|
| 104 |
+
The spatial dims are fixed at `[48, 96, 96]` and only the batch axis is dynamic β see the
|
| 105 |
+
window section above for why a dynamic spatial axis would be a trap here. Deep supervision is
|
| 106 |
+
switched off in the export, so the graph has a single output.
|
| 107 |
+
|
| 108 |
+
Verified against PyTorch on identical input: max |diff| 2.2e-04 on logits spanning
|
| 109 |
+
[1.03, 21.17] (relative 1.0e-05), which is **4.6e-06** after `scale_sigmoid` β float32
|
| 110 |
+
accumulation noise, not a behavioural difference. Regenerate with
|
| 111 |
+
`python scripts/export_onnx.py --config tutorials/neuron_j0126/1_train.yaml
|
| 112 |
+
--checkpoint <ckpt> --output model.onnx --check`.
|
| 113 |
+
|
| 114 |
+
## Checkpoint format
|
| 115 |
+
|
| 116 |
+
A PyTorch Lightning checkpoint (`state_dict` keys are prefixed `model.model.`). Optimizer
|
| 117 |
+
state has been stripped for download size; the weights are bit-identical to the reference
|
| 118 |
+
run's `step=00200000.ckpt` (all 517 tensors, float32). It is loadable for inference/finetuning,
|
| 119 |
+
not for resuming the original optimizer trajectory.
|
affinity_scratch_48x96x96.ckpt β j0126/affinity_scratch_48x96x96.ckpt
RENAMED
|
File without changes
|
affinity_scratch_48x96x96.onnx β j0126/affinity_scratch_48x96x96.onnx
RENAMED
|
File without changes
|