File size: 6,121 Bytes
4b948cf
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
# BANIS+ on EMNeuron, SegNeuron's train/val split, with the MedNeXt training
# speed knobs.
#
#   data:   python scripts/prepare_emneuron.py --src /path/to/EMNeuron --out /path/to/emneuron_pytc
#   train:  python scripts/main.py --config tutorials/neuron_segneuron/banis+.yaml
#   infer:  python scripts/main.py --config tutorials/neuron_segneuron/banis+.yaml --mode test \
#               --checkpoint <run>/checkpoints/last.ckpt
#   score:  python scripts/evaluate_emneuron.py --pred-dir <run>/results --gt /path/to/EMNeuron/valid
#
# Recipe = ../banis+.yaml (6-channel affinity, MedNeXt-L/k3, per-channel
# class-balanced BCE, EMA 0.999, erosion 2, AdamW + cosine, 200k steps) with
# these changes:
#
# AFFINITY LAYOUT -- ABISS's, not BANIS's: channels x, y, z (axes 2, 1, 0 of
# the ZYX arrays) and each edge (i-1, i) stored at voxel i (`deepem` mode,
# ABISS "destination" storage). The predictions go to decode_abiss as they
# are; the BANIS layout (z, y, x at the source voxel) needs a channel reversal
# and a one-voxel shift first.
#
# PATCH -- EMNeuron mixes 6-9 nm xy with 8-50 nm z, and a third of its volumes
# hold only 31-50 sections, so the 128^3 cube becomes [32, 128, 128] ZYX
# (SegNeuron trains on 20 x 128 x 128). MedNeXt-L is stride-16: 32/16 = 2.
# Long-range offsets are 10 voxels in x/y and 4 sections in z (BANIS uses 10 on
# every axis of a 9x9x20 nm volume; 10 sections would span a third of the
# patch). Destination storage needs the voxel at i - offset, so target_context
# reads 4/10/10 voxels on the leading side.
#
# AUGMENTATION -- aug_banis permutes z with y/x and rotates in 3D, which is
# wrong for anisotropic sections. aug_em_neuron_fast keeps flips, xy
# quarter-turns, contrast/brightness/noise and one section defect per sample;
# motion blur rejoins the defect set, and an xy-only scale jitter (+-20%,
# independent per axis) stands in for SegNeuron's anisotropic scaling. No 3D
# elastic: its isotropic displacement would move 30-50 nm sections by many slices.
#
# SPEED -- the MedNeXt training knobs: torch.compile with the shift-sum
# depthwise conv, fused AdamW with merged parameter groups (bf16, so no fp16
# GradScaler blocks fused clipping), cudnn.benchmark, the numba affine
# resampler, and a frozen MedNeXt dummy tensor so DDP runs with
# find_unused_parameters=false.

_base_:
- ../banis+.yaml
- dataset.yaml

experiment_name: emneuron_banis+
description: >-
  BANIS+ (6-channel affinity, MedNeXt-L/k3, class-balanced BCE, EMA, erosion 2)
  on SegNeuron's EMNeuron split: ABISS-layout affinities decoded with ABISS,
  [32,128,128] patches, anisotropic EM augmentation, and the MedNeXt speed
  knobs (compile + shift-sum depthwise, fused AdamW, bf16).

default:
  system:
    num_workers: 8
  model:
    input_size: [32, 128, 128]
    output_size: [32, 128, 128]
    compile: true
    mednext_depthwise_impl: shift_sum
    mednext_freeze_dummy_tensor: true
  data:
    dataloader:
      # 308 small chunks: preload them all (~17 GB per DDP rank); banis.yaml's
      # `lazy` profile targets the large NISB zarr.
      profile: cached
      use_lazy_zarr: false
      use_cache: false
      use_preloaded_cache_train: true
      use_preloaded_cache_val: true
      patch_size: [32, 128, 128]
      target_context: [4, 10, 10, 0, 0, 0]
      batch_size: 4
    label_transform:
      targets:
      - name: affinity
        kwargs:
          offsets: ["0-0-1", "0-1-0", "1-0-0", "0-0-10", "0-10-0", "4-0-0"]
          affinity_mode: deepem
    augmentation:
      profile: aug_em_neuron_fast
      motion_blur:
        enabled: true
        prob: 1.0
        sections: [0, 5]
        kernel_size: 11
        sigma_range: [1.0, 3.0]
        full_section_prob: 0.5
        partial_ratio_range: [0.25, 0.75]
      affine:
        enabled: true
        prob: 0.5
        rotate_range: [0.0, 0.0, 0.0]
        scale_range: [0.0, 0.2, 0.2]
        shear_range: [0.0, 0.0, 0.0]
        fast_resample: true
  inference:
    # Predict with the EMA weights the recipe trains and validates.
    checkpoint_weights: ema
    model:
      # Short-range x, y, z: already ABISS's channel order.
      select_channel: [0, 1, 2]
    window:
      # Infer at the trained window: MedNeXt's per-sample GroupNorm makes the
      # forward pass depend on the window extent.
      window_size: [32, 128, 128]
      sw_batch_size: 16
  decoding:
    steps:
    # ABISS ws: watershed, size-dependent single linkage, dust removal. The
    # runner's default edge storage is "destination", which this model writes.
    # Thresholds selected on the 10 tune/ chunks (TTA16, lowest mean VI) by
    # scripts/evaluate_emneuron.py, which also fills voxels ABISS leaves
    # unlabeled with the nearest segment before scoring.
    - template: decoding_abiss
      kwargs:
        abiss_home: /path/to/abiss
        cli_args:
          ws_high_threshold: 0.9
          ws_low_threshold: 0.05
          ws_size_threshold: 1000000000
          ws_dust_threshold: 0
          ws_merge_function: max
          ws_merge_threshold: 0.6
  evaluation:
    # VI / adapted Rand are scored by scripts/evaluate_emneuron.py with the
    # SegNeuron evaluator's exact calls; the volumes have no NERL skeletons.
    enabled: false

test:
  # Destination storage leaves the first voxel of every axis without an inside
  # edge, so the test pipeline crops it from deepem predictions. One voxel of
  # reflect context per side, minus the trailing voxel here, keeps the saved
  # affinity on the input grid: ABISS reads it with no shift or pad.
  data:
    data_transform:
      pad_size: [1, 1, 1]
  inference:
    model:
      crop_pad: [0, 1, 0, 1, 0, 1]

train:
  optimization:
    precision: "bf16-mixed"
    benchmark: true
    optimizer:
      fused: true
      merge_param_groups: true
    ddp_find_unused_parameters: false
    val_check_interval: 10000
    val_steps_per_epoch: 50
  monitor:
    checkpoint:
      # Step checkpoints every 25k plus last.ckpt; the reported model is the
      # final step, so the val-loss top-1 copy is kept for diagnostics only.
      save_top_k: 1
      save_every_n_steps: 25000