@@ -1697,6 +1697,71 @@ register_contrast_matched_modality() {
16971697 return 0
16981698}
16991699
1700+ # ---------------------------------------------------------------------------
1701+ # extract_dwi_trace_from_4d <4d_image> <out_dir>
1702+ #
1703+ # A DWI series frequently arrives from dcm2niix as a small multi-volume stack
1704+ # (commonly [b0, trace]) rather than a pre-derived single-contrast trace.
1705+ # Registering the whole stack as "one modality" is meaningless and
1706+ # antsApplyTransforms -d 3 aborts on it — but the diagnostically useful b>0
1707+ # TRACE is a perfectly good single-contrast 3D image. This extracts that trace
1708+ # so the contrast-matched cascade can bring the DWI all the way through instead
1709+ # of discarding it ("use the 4D to get the best 3D").
1710+ #
1711+ # Volume selection:
1712+ # * If a .bval sidecar sits next to the image, extract the volume with the
1713+ # maximum b-value (the trace / strongest diffusion weighting).
1714+ # * Otherwise extract the LAST volume — dcm2niix writes volumes in ascending
1715+ # b-value order, so [b0, trace] -> trace is the last index (matches the
1716+ # existing precedent in analyze_multimodal_brainstem.sh).
1717+ #
1718+ # NOTE: for a true multi-direction acquisition the proper trace is the mean of
1719+ # the high-b directions; a precomputed trace is preferred there. For the common
1720+ # 2-volume [b0, trace] export this picks the trace exactly.
1721+ #
1722+ # Echoes the path to the extracted 3D trace on stdout on success (log lines go
1723+ # to stderr, so the capture is clean); echoes nothing and returns non-zero on
1724+ # failure.
1725+ # ---------------------------------------------------------------------------
1726+ extract_dwi_trace_from_4d () {
1727+ local image=" $1 "
1728+ local out_dir=" $2 "
1729+
1730+ local nvol
1731+ nvol=" $( fslval " $image " dim4 2> /dev/null | tr -d ' ' ) "
1732+ [ -n " $nvol " ] || nvol=1
1733+
1734+ # Locate a .bval sidecar (dcm2niix writes it next to the .nii.gz).
1735+ local base bval=" " cand idx=" "
1736+ base=" ${image% .nii.gz} " ; base=" ${base% .nii} "
1737+ for cand in " ${base} .bval" " ${base} .bvals" ; do
1738+ if [ -f " $cand " ]; then bval=" $cand " ; break ; fi
1739+ done
1740+
1741+ if [ -n " $bval " ]; then
1742+ # 0-based index of the maximum b-value across the whole sidecar.
1743+ idx=" $( awk ' { for (i=1;i<=NF;i++) { v=$i+0; if (n==0 || v>max) { max=v; mi=n } n++ } }
1744+ END { if (n>0) print mi }' " $bval " 2> /dev/null) "
1745+ fi
1746+
1747+ # Fallback: highest-index volume (ascending-b ordering => trace is last).
1748+ if ! [ " ${idx:- x} " -ge 0 ] 2> /dev/null; then
1749+ idx=$(( nvol - 1 ))
1750+ fi
1751+ [ " $idx " -ge 0 ] 2> /dev/null || idx=0
1752+
1753+ mkdir -p " $out_dir "
1754+ local out=" ${out_dir} /$( basename " $base " ) _trace.nii.gz"
1755+ if fslroi " $image " " $out " " $idx " 1 2> /dev/null && [ -f " $out " ]; then
1756+ log_message " Extracted DWI trace (volume ${idx} of ${nvol} ) from 4D stack -> $( basename " $out " ) "
1757+ echo " $out "
1758+ return 0
1759+ fi
1760+
1761+ log_formatted " WARNING" " Failed to extract a 3D trace volume from $( basename " $image " ) "
1762+ return 1
1763+ }
1764+
17001765# Orchestrate contrast-matched registration for all present T2-weighted modalities.
17011766#
17021767# Usage:
@@ -1729,7 +1794,7 @@ register_contrast_matched_cascade() {
17291794
17301795 local overall_status=0
17311796 local processed=0
1732- local spec name path anchor nvol
1797+ local spec name path anchor nvol trace_path
17331798 for spec in " ${specs[@]} " ; do
17341799 name=" ${spec%% =* } "
17351800 path=" ${spec#* =} "
@@ -1745,14 +1810,28 @@ register_contrast_matched_cascade() {
17451810 continue
17461811 fi
17471812
1748- # Refuse 4D / multi-volume inputs (raw DWI stack: b0+trace, trace+ADC,
1749- # multi-b). These are not single-contrast 3D images, so registering them
1750- # as one modality is meaningless and antsApplyTransforms -d 3 aborts. The
1751- # derived 3D trace/ADC, if present, come through as their own specs.
1813+ # Multi-volume inputs are not single-contrast 3D images: registering the
1814+ # whole stack as one modality is meaningless and antsApplyTransforms -d 3
1815+ # aborts on it. For DWI this is the COMMON case (dcm2niix exports the
1816+ # series as e.g. [b0, trace]); rather than discard it, extract the b>0
1817+ # TRACE — a perfectly good single-contrast 3D image — and register that
1818+ # (gated by CONTRAST_MATCHED_DWI_EXTRACT_TRACE, default on). For any
1819+ # other modality a 4D image is anomalous, so we still skip it.
17521820 nvol=" $( fslval " $path " dim4 2> /dev/null | tr -d ' ' ) "
17531821 if [ " ${nvol:- 1} " -gt 1 ] 2> /dev/null; then
1754- log_formatted " WARNING" " Skipping ${name} : 4D/multi-volume image (${nvol} vols) is not a single-contrast 3D modality"
1755- continue
1822+ if [ " ${name^^} " = " DWI" ] && [ " ${CONTRAST_MATCHED_DWI_EXTRACT_TRACE:- true} " = " true" ]; then
1823+ trace_path=" $( extract_dwi_trace_from_4d " $path " " $output_dir " ) "
1824+ if [ -n " $trace_path " ] && [ -f " $trace_path " ]; then
1825+ log_message " Using extracted DWI trace for the cascade: $( basename " $trace_path " ) "
1826+ path=" $trace_path "
1827+ else
1828+ log_formatted " WARNING" " Skipping ${name} : could not extract a 3D trace from the ${nvol} -volume stack"
1829+ continue
1830+ fi
1831+ else
1832+ log_formatted " WARNING" " Skipping ${name} : 4D/multi-volume image (${nvol} vols) is not a single-contrast 3D modality"
1833+ continue
1834+ fi
17561835 fi
17571836
17581837 processed=$(( processed + 1 ))
@@ -1780,6 +1859,7 @@ export -f register_multiple_to_reference
17801859export -f register_all_modalities
17811860export -f resolve_contrast_anchor
17821861export -f register_contrast_matched_modality
1862+ export -f extract_dwi_trace_from_4d
17831863export -f register_contrast_matched_cascade
17841864
17851865# Helper function to prepare white matter segmentation
0 commit comments