@@ -82,13 +82,28 @@ export JOINT_FUSION_SEARCH_RADIUS="${JOINT_FUSION_SEARCH_RADIUS:-3}"
8282# ANTs registration parameters (existing)
8383# REG_TRANSFORM_TYPE is set in the "Registration & motion correction" section below
8484# N4 Bias Field Correction presets: "iterations,convergence_threshold,spline_distance_mm,shrink_factor"
85- # Spline distance: larger values = coarser fit = faster; smaller values = finer fit = slower
86- export N4_PRESET_VERY_LOW=" 20x20x20,0.0001,1x1x3,2"
87- export N4_PRESET_LOW=" 35x35x35,0.00025,2x2x3,2"
88- export N4_PRESET_MEDIUM=" 70x70x70,0.0001,2x2x3,2"
89- export N4_PRESET_HIGH=" 100x100x100,0.00005,2x2x3,2"
90- export N4_PRESET_ULTRA=" 250x250x250x3,0.00001,2x2x3,2"
91- export N4_PRESET_FLAIR=" $N4_PRESET_HIGH " # Use more conservative settings for FLAIR
85+ #
86+ # N4 -b CONVENTION (single source of truth for this pipeline):
87+ # The 3rd field is the b-spline mesh element spacing expressed as a single
88+ # ISOTROPIC SPLINE DISTANCE IN MM, fed to N4 as -b "[<spline_distance_mm>]".
89+ # This is the ANTs-recommended convention (a single scalar mm value rather
90+ # than a per-dimension mesh resolution like 2x2x3). For human brain the value
91+ # should sit between ~100 and ~200 mm (ANTs N4 wiki / Tustison 2010).
92+ # Larger distance = coarser/smoother bias field = faster; smaller distance =
93+ # finer fit = slower and more detailed. The spline distance is HALVED at each
94+ # subsequent convergence level (one level per "x"-separated iteration count).
95+ export N4_PRESET_VERY_LOW=" 20x20x20,0.0001,180,2"
96+ export N4_PRESET_LOW=" 35x35x35,0.00025,180,2"
97+ export N4_PRESET_MEDIUM=" 70x70x70,0.0001,150,2"
98+ export N4_PRESET_HIGH=" 100x100x100,0.00005,120,2"
99+ export N4_PRESET_ULTRA=" 250x250x250x3,0.00001,100,2"
100+ # FLAIR-specific N4 preset is derived per QUALITY_PRESET in the block below.
101+ # It is deliberately GENTLER than the matching general preset (coarser mesh =>
102+ # larger spline distance => smoother bias field, plus fewer iterations) because
103+ # N4 can absorb diffuse FLAIR lesion contrast into the estimated bias field under
104+ # lesion load (Valdes Hernandez 2016, PMC4846712). A smoother field is far less
105+ # likely to soak up real lesion signal. Placeholder default; overwritten below.
106+ export N4_PRESET_FLAIR=" $N4_PRESET_HIGH "
92107
93108# DICOM-specific parallel processing (only affects DICOM import)
94109export DICOM_IMPORT_PARALLEL=12
132147
133148echo " QUALITY_PRESET: ${QUALITY_PRESET} ANTS_THREADS:${ANTS_THREADS} " >&2
134149
135- # Set default N4_PARAMS by QUALITY_PRESET
150+ # Set default N4_PARAMS by QUALITY_PRESET.
151+ #
152+ # FLAIR gets a GENUINELY gentler preset (NOT a copy of the general one): a larger
153+ # spline distance (coarser/smoother bias field) and fewer iterations than the
154+ # matching general preset, so the estimated field cannot absorb diffuse FLAIR
155+ # lesion contrast. Convergence threshold and shrink factor are kept aligned with
156+ # the general preset; only the field smoothness and effort are relaxed.
136157if [ " $QUALITY_PRESET " == " ULTRA" ]; then
137158 export N4_PARAMS=" $N4_PRESET_ULTRA "
138- export N4_PRESET_FLAIR=" $N4_PRESET_ULTRA "
159+ export N4_PRESET_FLAIR=" 150x150x150,0.00001,160,2 "
139160elif [ " $QUALITY_PRESET " == " HIGH" ]; then
140161 export N4_PARAMS=" $N4_PRESET_HIGH "
141- export N4_PRESET_FLAIR=" $N4_PRESET_HIGH "
162+ export N4_PRESET_FLAIR=" 75x75x75,0.00005,180,2 "
142163elif [ " $QUALITY_PRESET " == " MEDIUM" ]; then
143164 export N4_PARAMS=" $N4_PRESET_MEDIUM "
144- export N4_PRESET_FLAIR=" $N4_PRESET_MEDIUM "
165+ export N4_PRESET_FLAIR=" 50x50x50,0.0001,200,2 "
145166elif [ " $QUALITY_PRESET " == " LOW" ]; then
146167 export N4_PARAMS=" $N4_PRESET_MEDIUM "
147- export N4_PRESET_FLAIR=" $N4_PRESET_MEDIUM "
168+ export N4_PRESET_FLAIR=" 50x50x50,0.0001,200,2 "
148169else
149170 export N4_PARAMS=" $N4_PRESET_VERY_LOW "
150- export N4_PRESET_FLAIR=" $N4_PRESET_VERY_LOW "
171+ export N4_PRESET_FLAIR=" 15x15x15,0.0001,200,2 "
151172fi
152173# Parse out the fields for general sequences
153174export N4_ITERATIONS=$( echo " $N4_PARAMS " | cut -d' ,' -f1)
@@ -161,6 +182,20 @@ export N4_CONVERGENCE_FLAIR=$(echo "$N4_PRESET_FLAIR" | cut -d',' -f2)
161182export N4_BSPLINE_FLAIR=$( echo " $N4_PRESET_FLAIR " | cut -d' ,' -f3)
162183export N4_SHRINK_FLAIR=$( echo " $N4_PRESET_FLAIR " | cut -d' ,' -f4)
163184
185+ # Optional lesion-weight mask for FLAIR N4 (two-pass workflow).
186+ # Default empty => FLAIR N4 just uses the gentler preset above.
187+ # When set to a path to an existing NIfTI weight image, it is passed to N4 as
188+ # -w <mask> so high-weight (lesion) voxels are DOWN-weighted during bias-field
189+ # estimation, preventing N4 from fitting the field to lesion contrast.
190+ # Intended two-pass workflow (lesions are unknown at first preprocessing):
191+ # 1. Run the pipeline once with N4_FLAIR_LESION_MASK="" (gentler preset only).
192+ # 2. Detect lesions on the first-pass output (analysis.sh).
193+ # 3. Re-run preprocessing with N4_FLAIR_LESION_MASK=<lesion-derived weight>
194+ # so the second-pass bias field ignores lesion voxels.
195+ # The weight image must already be in the same space/geometry as the FLAIR being
196+ # corrected (i.e. the denoised, oriented FLAIR fed to N4).
197+ export N4_FLAIR_LESION_MASK=" ${N4_FLAIR_LESION_MASK:- } "
198+
164199# Multi-axial integration parameters (antsMultivariateTemplateConstruction2.sh)
165200export TEMPLATE_ITERATIONS=3
166201export TEMPLATE_GRADIENT_STEP=0.05
@@ -176,6 +211,24 @@ export REG_METRIC_CROSS_MODALITY="MI" # Mutual Information - for cross-modality
176211export REG_METRIC_SAME_MODALITY=" CC" # Cross Correlation - for same modality
177212export REG_PRECISION=1 # Registration precision
178213
214+ # Per-metric tuning shared by all stages of perform_multistage_registration().
215+ # CC radius applies when CC is used (same-modality SyN); MI bins apply when MI is
216+ # used (cross-modality rigid/affine/SyN). The SyN stage now selects MI for
217+ # cross-modality pairs (e.g. FLAIR↔T1) since CC assumes correlated intensities.
218+ export REG_MI_BINS=32 # Histogram bins for Mutual Information metric
219+ export REG_CC_RADIUS=4 # Neighbourhood radius for Cross Correlation metric
220+
221+ # Intensity winsorization (antsRegistrationSyN.sh community standard).
222+ # Clamps the intensity tails before registration to suppress outliers; applied to
223+ # the global antsRegistration command in perform_multistage_registration().
224+ export REG_WINSORIZE_LOWER=0.005 # Lower winsorize quantile
225+ export REG_WINSORIZE_UPPER=0.995 # Upper winsorize quantile
226+
227+ # Interpolation used by apply_transformation() when warping discrete label/atlas/
228+ # mask volumes (is_label=true). GenericLabel is the modern label-aware default
229+ # (anti-aliased, preserves discrete values); continuous intensity images keep Linear.
230+ export REG_LABEL_INTERPOLATION=" GenericLabel"
231+
179232# ANTs specific parameters - if not set, ANTs will use defaults
180233# export METRIC_SAMPLING_STRATEGY="NONE" # Options: NONE (use all voxels), REGULAR, RANDOM
181234# export METRIC_SAMPLING_PERCENTAGE=1.0 # Percentage of voxels to sample (when not NONE)
@@ -190,6 +243,26 @@ export REG_PRECISION=1 # Registration precision
190243export THRESHOLD_WM_SD_MULTIPLIER=1.2 # SD multiplier from local norm; used by GMM fallback + legacy path
191244export MIN_HYPERINTENSITY_SIZE=3 # Minimum cluster size in voxels (FSL cluster --minextent)
192245
246+ # ---------------------------------------------------------------------------
247+ # CSF / partial-volume exclusion (posterior-fossa false-positive reduction)
248+ # ---------------------------------------------------------------------------
249+ # Posterior-fossa CSF pulsation/inflow around the 4th ventricle and basal
250+ # cisterns is the dominant FALSE-POSITIVE source for brainstem FLAIR. FSL FAST
251+ # already produces a CSF PVE map (fast_pve_0 -> *_csf_prob.nii.gz) that was
252+ # previously computed but never used for detection. When enabled, the per-region
253+ # GMM/z-score path removes high-CSF-probability voxels and the CSF-parenchyma
254+ # partial-volume boundary band from each region mask BEFORE z-scoring/GMM.
255+ export CSF_EXCLUSION_ENABLED=true # Master switch; false = legacy behaviour
256+ export CSF_PVE_THRESHOLD=0.5 # Voxels with CSF PVE > this are excluded from detection
257+ export PV_EROSION_MM=1 # Erode region mask by this many mm to drop CSF-parenchyma PV boundary
258+
259+ # Connectivity-weighting SD multipliers (apply_connectivity_weighting in analysis.sh).
260+ # A voxel is kept if it is connected to a hyperintense seed AND above
261+ # CONNECTIVITY_CONNECTED_SD_MULT, OR is very high intensity (above
262+ # CONNECTIVITY_HIGH_SD_MULT) regardless of connectivity.
263+ export CONNECTIVITY_HIGH_SD_MULT=2.0 # mean + this*std: standalone "very high" threshold
264+ export CONNECTIVITY_CONNECTED_SD_MULT=1.5 # mean + this*std: lower threshold for connected voxels
265+
193266# ---------------------------------------------------------------------------
194267# GMM per-region thresholding (gmm_threshold.py --help for full docs)
195268# ---------------------------------------------------------------------------
@@ -284,6 +357,34 @@ export BIANCA_VERBOSE=true # pass -v to bianca
284357# --- Post-processing --------------------------------------------------------
285358export BIANCA_MIN_CLUSTER_SIZE=0 # drop WMH clusters below this many voxels (0 = off)
286359
360+ # ---------------------------------------------------------------------------
361+ # Supervised / learned WMH detection (LST-AI + FreeSurfer SAMSEG)
362+ # ---------------------------------------------------------------------------
363+ # Optional, pretrained, training-data-free WMH/lesion segmentation back-ends
364+ # implemented in src/modules/wmh_lst_samseg.sh. Both are OFF by default and
365+ # require external tools that are NOT part of the core pipeline dependency set:
366+ # - LST-AI : deep-learning successor to SPM-LST. Install via 'pip install
367+ # lst-ai' (Python, no MATLAB) or pull the Docker image. Needs
368+ # co-registered FLAIR + T1; ships pretrained weights.
369+ # - SAMSEG : FreeSurfer >=7.x 'run_samseg --lesion' (needs $FREESURFER_HOME).
370+ # Synergizes with the FreeSurfer brainstem segmentation added in a
371+ # sibling unit (shared FreeSurfer install + brainstem labels).
372+ # Each back-end intersects its whole-brain lesion mask with the pipeline's
373+ # brainstem mask to report a brainstem-restricted WMH burden separately.
374+ export WMH_LSTAI_ENABLED=false # true => run LST-AI when available
375+ export WMH_SAMSEG_ENABLED=false # true => run FreeSurfer SAMSEG when available
376+
377+ # LST-AI options
378+ export LSTAI_THRESHOLD=0.5 # lesion probability threshold (0-1; LST-AI default 0.5)
379+ export LSTAI_DEVICE=" cpu" # "cpu" or a GPU id (e.g. "0")
380+ export LSTAI_DOCKER_IMAGE=" jqmcginnis/lst-ai:latest" # used only if Docker back-end is selected
381+
382+ # SAMSEG options
383+ export SAMSEG_LESION_THRESHOLD=0.3 # lesion posterior threshold (run_samseg default 0.3)
384+ export SAMSEG_LESION_MASK_PATTERN=" 0 1" # one number per input (T1 FLAIR): 0=no constraint, 1=brighter-than-GM
385+ export SAMSEG_LESION_LABEL=99 # lesion label value in SAMSEG seg.mgz
386+ export SAMSEG_EXTRA_OPTS=" --pallidum-separate" # extra run_samseg flags (recommended when FLAIR shows pallidum)
387+
287388# Reference templates from FSL or other sources
288389if [ -z " ${FSLDIR:- } " ]; then
289390 log_formatted " WARNING" " FSLDIR not set. Template references may fail."
@@ -335,6 +436,44 @@ export RESAMPLE_TO_ISOTROPIC=false
335436# export ISOTROPIC_SPACING=1.0
336437# unset ISOTROPIC_SPACING
337438
439+ # ------------------------------------------------------------------------------
440+ # Modality-aware denoising routing
441+ # ------------------------------------------------------------------------------
442+ # The denoising dispatcher (dispatch_denoising in preprocess.sh) selects the
443+ # denoising method by detected modality:
444+ # T1 / T2 / FLAIR -> Rician Non-Local-Means (DenoiseImage) [default]
445+ # DWI / diffusion -> MP-PCA (dwidenoise, MRtrix); NLM is INVALID for DWI
446+ # SWI / TOF / angio -> SKIPPED by default (NLM smears microbleeds/vessels)
447+ #
448+ # When set to true, applies a gentle Rician NLM to SWI/TOF images instead of
449+ # skipping. Leave false to preserve microbleeds (SWI) and small vessels (TOF).
450+ export SWI_TOF_DENOISE_ENABLED=false
451+ # When the modality cannot be detected, skip denoising instead of defaulting to
452+ # NLM. Default false keeps the historical structural-assumption behaviour.
453+ export DENOISE_DEFAULT_SKIP=false
454+
455+ # ------------------------------------------------------------------------------
456+ # DWI (diffusion) preprocessing path (dwi_preprocess.sh)
457+ # ------------------------------------------------------------------------------
458+ # Master switch. When false (default) the DWI path is never invoked and the
459+ # existing T1/FLAIR flow is completely unaffected. When true, DWI inputs are
460+ # auto-detected in the preprocessing stage and routed through the MP-PCA path.
461+ export PROCESS_DWI=false
462+ # Optional Gibbs ringing removal (mrdegibbs) after MP-PCA denoising.
463+ export DWI_DEGIBBS=true
464+ # Bias-field correction for DWI: "ants" uses `dwibiascorrect ants`; otherwise an
465+ # N4 fallback is applied to the mean b0/DWI volume. Set DWI_BIAS_CORRECT=false
466+ # to skip bias correction entirely.
467+ export DWI_BIAS_CORRECT=true
468+ export DWI_BIAS_METHOD=" ants"
469+ # Eddy/motion/topup (dwifslpreproc) is OPTIONAL and OFF by default: it requires
470+ # acquisition parameters (phase-encode direction + readout time) and gradient
471+ # tables. To enable, set DWI_RUN_EDDY=true and provide DWI_PE_DIR (e.g. "j-")
472+ # and DWI_READOUT_TIME (seconds) plus accompanying .bvec/.bval files.
473+ export DWI_RUN_EDDY=false
474+ export DWI_PE_DIR=" "
475+ export DWI_READOUT_TIME=" "
476+
338477# Scan selection options
339478# Available modes:
340479# original - ONLY consider ORIGINAL acquisitions, ignore DERIVED scans
@@ -379,11 +518,10 @@ export CONVERT_MASKS_TO_UINT8=true # Convert binary masks to UINT8
379518
380519# export ORIGINAL_ACQUISITION_WEIGHT=1000
381520
382- # Parse out FLAIR-specific fields
383- N4_ITERATIONS_FLAIR=$( echo " $N4_PRESET_FLAIR " | cut -d' ,' -f1)
384- N4_CONVERGENCE_FLAIR=$( echo " $N4_PRESET_FLAIR " | cut -d' ,' -f2)
385- N4_BSPLINE_FLAIR=$( echo " $N4_PRESET_FLAIR " | cut -d' ,' -f3)
386- N4_SHRINK_FLAIR=$( echo " $N4_PRESET_FLAIR " | cut -d' ,' -f4)
521+ # NOTE: FLAIR-specific N4 fields (N4_ITERATIONS_FLAIR/.../N4_SHRINK_FLAIR) are
522+ # parsed and exported once in the N4 preset section above. The duplicate
523+ # non-exported parse that used to live here has been removed to keep a single
524+ # source of truth for the N4 -b convention.
387525
388526# White matter guided registration parameters
389527export WM_GUIDED_DEFAULT=true # Default to use white matter guided registration
0 commit comments