|
| 1 | +# Inference: `base_banis.yaml` vs `lib/banis` |
| 2 | + |
| 3 | +Comparison of pytc whole-volume affinity inference (`tutorials/neuron_nisb/base_banis.yaml`) against the reference `lib/banis/inference.py` + `lib/banis/BANIS.py`. |
| 4 | + |
| 5 | +## Match |
| 6 | + |
| 7 | +| Aspect | Both | |
| 8 | +| --- | --- | |
| 9 | +| Window size | 128³ | |
| 10 | +| Overlap | 50% (BANIS: `small_size // 2 = 64` shift) | |
| 11 | +| Activation | `scale_sigmoid(x) = sigmoid(0.2·x)` on output channels | |
| 12 | +| Precision | fp16 autocast | |
| 13 | +| Input normalization | divide-255, XYZ layout, no transpose | |
| 14 | +| Decoded channels | first 3 (short-range) via `select_channel: [0,1,2]` | |
| 15 | +| Decoder | connected components, 6-connectivity, source-stored edges (`edge_offset: 0`) | |
| 16 | +| TTA | disabled (BANIS has no flip/rotate TTA) | |
| 17 | +| Whole-volume strategy | both load full image then patch | |
| 18 | + |
| 19 | +## Differences |
| 20 | + |
| 21 | +1. **Blending weight** |
| 22 | + - pytc: `blending: gaussian, sigma_scale=0.25` (Gaussian importance map). |
| 23 | + - BANIS: `distance_transform_cdt` of zero-padded ones cube → L1 chamfer distance from surface (zero on faces, max in center). `lib/banis/inference.py:209-210`. |
| 24 | + |
| 25 | +2. **Boundary handling** |
| 26 | + - pytc: `padding_mode: replicate` — MONAI pads the *whole* volume up front so windows align. |
| 27 | + - BANIS: no padding. `get_offsets` always sets the final offset to `big_size - small_size`, so every window fits fully inside the volume. `lib/banis/inference.py:189-191`. |
| 28 | + |
| 29 | +3. **Threshold** |
| 30 | + - pytc: fixed `threshold: 0.5`. |
| 31 | + - BANIS: sweeps over `eval_ranges = sigmoid(0.2 · range(-1, 12))` ≈ `[0.45, 0.55, 0.65, 0.73, 0.80, 0.85, 0.89, 0.91, ...]`, picks val-best by NERL, reuses on test. `lib/banis/BANIS.py:439`, `lib/banis/BANIS.py:209-211`. |
| 32 | + |
| 33 | +4. **Patch grid** |
| 34 | + - pytc: regular MONAI grid at 50% stride. |
| 35 | + - BANIS: base grid + 7 shifted sets (all combinations of `+small_size//2` per axis) unioned and de-duped. `lib/banis/inference.py:154-174`. Slightly more centers near boundaries. |
| 36 | + |
| 37 | +5. **Stored prediction channels** |
| 38 | + - pytc: short-range only (`select_channel: [0,1,2]`). |
| 39 | + - BANIS: all 6 channels written to `pred_aff_*.zarr`; decoding still reads `[:3]`. `lib/banis/BANIS.py:199-200`, `lib/banis/BANIS.py:217`. |
| 40 | + |
| 41 | +## To match BANIS exactly |
| 42 | + |
| 43 | +- Replace `blending: gaussian` with custom L1-distance window (or accept gaussian as a near-equivalent at 50% overlap). |
| 44 | +- Drop `padding_mode: replicate` and use BANIS-style snap-to-edge offsets (last offset = `image_size - roi_size`). |
| 45 | +- Run a decoding threshold sweep over BANIS' `eval_ranges` and pick best by NERL on val before testing. |
| 46 | + |
| 47 | +Items 1–2 are cosmetic at 50% overlap; #3 is the main accuracy lever. |
| 48 | + |
| 49 | +## Boundary handling in pytc |
| 50 | + |
| 51 | +Two paths matter: |
| 52 | + |
| 53 | +- **Lazy sliding-window path** (`connectomics/inference/lazy.py`, used when `inference.sliding_window.lazy_load=true`). Honors `snap_to_edge: true` (last window at `image_size - roi_size`, no whole-volume padding) and per-window `target_context` (read `roi + 2·context`, predict, central-crop). `base_banis.yaml` uses this path. |
| 54 | +- **Eager MONAI path** (`connectomics/inference/sliding.py`, MONAI's `SlidingWindowInferer`). Vanilla MONAI; ignores `snap_to_edge` / `target_context`. For BANIS-flavored boundary context here, just bump `window_size` larger than the training patch — see below. |
| 55 | + |
| 56 | +## The `window_size = roi + extra` hack |
| 57 | + |
| 58 | +Instead of per-window `target_context` oversample + central crop (extra code, extra forwards), set `window_size` larger than the training patch and rely on default gaussian blending to de-emphasize the outer band: |
| 59 | + |
| 60 | +```yaml |
| 61 | +sliding_window: |
| 62 | + window_size: [144, 144, 144] # 128 (training) + 16 context per axis |
| 63 | + blending: gaussian |
| 64 | + sigma_scale: 0.25 |
| 65 | + overlap: 0.5 |
| 66 | +``` |
| 67 | +
|
| 68 | +- Interior windows naturally pick up real surrounding-volume voxels in the +16 band. |
| 69 | +- Default gaussian (`sigma_scale=0.125–0.25`) gives the outer band ~5× less weight than the central edge — soft taper, no hard mask, no boundary coverage hole. |
| 70 | +- Must be a multiple of the model's downsample stride. MedNeXt-S has 4 stages → 144 ✓ (128 + 16); 138 (BANIS training oversample) ✗. |
| 71 | +- `~2×` per-patch GPU memory at 144 vs 128. Verify it fits with fp16. |
| 72 | + |
| 73 | +This works for both the lazy and eager paths and replaces the need for an inference-time `target_context` config. |
| 74 | + |
| 75 | +## What's still BANIS-specific |
| 76 | + |
| 77 | +`snap_to_edge: true` (in the yaml) only affects the lazy path and is the BANIS-faithful behavior — model never sees padded volume data. The eager path uses MONAI's whole-volume padding, which is functionally close at 50% overlap. |
0 commit comments