Skip to content

Commit 62a1bcd

Browse files
Merge remote-tracking branch 'origin/main' into resolve-116
# Conflicts: # config/default_config.sh
2 parents 3a57a2c + 675c26a commit 62a1bcd

14 files changed

Lines changed: 1723 additions & 97 deletions

.github/workflows/validate-scripts.yml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,12 @@ jobs:
9393

9494
- name: Verify pipeline.sh --help works
9595
run: |
96+
# Provide a dummy FSL config so the early `source $FSLDIR/etc/fslconf/fsl.sh`
97+
# in pipeline.sh succeeds under `set -e` (no real FSL install in CI).
98+
export FSLDIR=/tmp/fake_fsl
99+
mkdir -p "$FSLDIR/etc/fslconf"
100+
printf '# dummy FSL config for CI smoke test\n' > "$FSLDIR/etc/fslconf/fsl.sh"
101+
96102
output=$(bash src/pipeline.sh --help 2>&1)
97103
echo "$output"
98104
echo ""
@@ -109,6 +115,11 @@ jobs:
109115
export FSLDIR=/tmp/fake_fsl
110116
export ANTS_PATH=/tmp/fake_ants
111117
118+
# Provide a dummy FSL config so the early `source $FSLDIR/etc/fslconf/fsl.sh`
119+
# in pipeline.sh succeeds under `set -e` (no real FSL install in CI).
120+
mkdir -p "$FSLDIR/etc/fslconf"
121+
printf '# dummy FSL config for CI smoke test\n' > "$FSLDIR/etc/fslconf/fsl.sh"
122+
112123
echo "Running pipeline.sh with no input data..."
113124
output=$(bash src/pipeline.sh 2>&1 || true)
114125
echo "$output"

config/default_config.sh

Lines changed: 156 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -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)
94109
export DICOM_IMPORT_PARALLEL=12
@@ -132,22 +147,28 @@ fi
132147

133148
echo "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.
136157
if [ "$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"
139160
elif [ "$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"
142163
elif [ "$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"
145166
elif [ "$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"
148169
else
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"
151172
fi
152173
# Parse out the fields for general sequences
153174
export N4_ITERATIONS=$(echo "$N4_PARAMS" | cut -d',' -f1)
@@ -161,6 +182,20 @@ export N4_CONVERGENCE_FLAIR=$(echo "$N4_PRESET_FLAIR" | cut -d',' -f2)
161182
export N4_BSPLINE_FLAIR=$(echo "$N4_PRESET_FLAIR" | cut -d',' -f3)
162183
export 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)
165200
export TEMPLATE_ITERATIONS=3
166201
export TEMPLATE_GRADIENT_STEP=0.05
@@ -176,6 +211,24 @@ export REG_METRIC_CROSS_MODALITY="MI" # Mutual Information - for cross-modality
176211
export REG_METRIC_SAME_MODALITY="CC" # Cross Correlation - for same modality
177212
export 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
190243
export THRESHOLD_WM_SD_MULTIPLIER=1.2 # SD multiplier from local norm; used by GMM fallback + legacy path
191244
export 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 --------------------------------------------------------
285358
export 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
288389
if [ -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
389527
export WM_GUIDED_DEFAULT=true # Default to use white matter guided registration

0 commit comments

Comments
 (0)