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
|