11"""Longitudinal functional processing workflow.
22
33Transforms preprocessed functional outputs to a pre-computed longitudinal
4- template space and returns all output paths as a
4+ template space, then re-runs nuisance regression on the warped BOLD using
5+ raw regressors from the cross-sectional run. Returns all output paths as a
56:class:`FunctionalLongOutputs` named tuple.
67"""
78
89from __future__ import annotations
910
11+ import logging
1012from typing import TYPE_CHECKING , NamedTuple
1113
14+ from rbc .core .functional import apply_regression , apply_regression_bandpass
1215from rbc .core .longitudinal .transform import (
1316 compose_transform ,
1417 func_transform ,
1518 mask_transform ,
1619)
1720
1821if TYPE_CHECKING :
22+ from collections .abc import Sequence
1923 from pathlib import Path
24+ from typing import Literal
25+
26+ _logger = logging .getLogger ("rbc" )
2027
2128
2229class FunctionalLongOutputs (NamedTuple ):
@@ -26,14 +33,19 @@ class FunctionalLongOutputs(NamedTuple):
2633 bold_to_long_xfm: BOLD-to-longitudinal-template composite warp.
2734 sbref: Motion reference volume warped to longitudinal template space.
2835 bold: Preprocessed BOLD warped to longitudinal template space.
29- bold_mask: Brain mask warped to longitudinal template space,
30- or *None* if no mask was provided.
36+ bold_mask: Brain mask warped to longitudinal template space.
37+ regressed_bold: Per-regressor nuisance-regressed BOLD (no bandpass)
38+ in longitudinal template space, keyed by strategy name.
39+ cleaned_bold: Per-regressor nuisance-regressed + bandpass-filtered
40+ BOLD in longitudinal template space, keyed by strategy name.
3141 """
3242
3343 bold_to_long_xfm : Path
3444 sbref : Path
3545 bold : Path
36- bold_mask : Path | None = None
46+ bold_mask : Path
47+ regressed_bold : dict [str , Path ]
48+ cleaned_bold : dict [str , Path ]
3749
3850
3951def longitudinal_process (
@@ -43,41 +55,70 @@ def longitudinal_process(
4355 bold_to_anat_itk : Path ,
4456 sbref : Path ,
4557 bold : Path ,
46- bold_mask : Path | None ,
58+ bold_mask : Path ,
59+ regressor_files : dict [str , Path ],
60+ regressor_set : Sequence [Literal ["36-parameter" , "aCompCor" ]] = ("36-parameter" ,),
4761) -> FunctionalLongOutputs :
4862 """Transform preprocessed functional outputs to longitudinal template space.
4963
50- Assumes a longitudinal template has been generated, the subject-to-template
51- composite warp is available, and anatomical data has already been processed
52- to longitudinal template space.
64+ After warping the BOLD timeseries, re-runs nuisance regression using the
65+ raw (unfiltered) regressors produced by the cross-sectional pipeline.
66+ No regressor recomputation is performed: the same regressor matrix is
67+ applied in the new target space.
5368
5469 Args:
5570 template: Longitudinal template image.
5671 anat_to_template_xfm: T1w-to-longitudinal-template composite warp.
5772 bold_to_anat_itk: BOLD-to-T1w affine in ITK format.
5873 sbref: Motion reference (single-band reference) volume.
5974 bold: Preprocessed bold image.
60- bold_mask: Bold brain mask, if available.
75+ bold_mask: Bold brain mask.
76+ regressor_files: Raw (unfiltered) regressor ``.1D`` files from
77+ the cross-sectional run, keyed by strategy name.
78+ regressor_set: Which regressor strategies to apply. Must be a
79+ subset of the keys in *regressor_files*.
6180
6281 Returns:
63- :class:`FunctionalLongOutputs` with all non-null inputs transformed to template
64- space .
82+ :class:`FunctionalLongOutputs` with all inputs transformed to
83+ longitudinal template space and per-regressor regression outputs .
6584 """
6685 bold_to_tpl_xfm = compose_transform (
6786 ref = template ,
6887 bold_to_anat_itk = bold_to_anat_itk ,
6988 anat_to_tpl_xfm = anat_to_template_xfm ,
7089 )
7190
91+ long_sbref = func_transform (
92+ in_file = sbref , template = template , xfm = bold_to_tpl_xfm , strategy = "single"
93+ )
94+ long_bold = func_transform (
95+ in_file = bold , template = template , xfm = bold_to_tpl_xfm , strategy = "chunked"
96+ )
97+ long_mask = mask_transform (mask = bold_mask , template = template , xfm = bold_to_tpl_xfm )
98+
99+ regressed : dict [str , Path ] = {}
100+ cleaned : dict [str , Path ] = {}
101+ for reg in regressor_set :
102+ reg_file = regressor_files [reg ]
103+ _logger .info ("Longitudinal %s nuisance regression (no bandpass)" , reg )
104+ regressed [reg ] = apply_regression (
105+ bold_file = long_bold ,
106+ brain_mask_file = long_mask ,
107+ regressor_file = reg_file ,
108+ ).regressed_bold
109+
110+ _logger .info ("Longitudinal %s nuisance regression + bandpass filtering" , reg )
111+ cleaned [reg ] = apply_regression_bandpass (
112+ bold_file = long_bold ,
113+ brain_mask_file = long_mask ,
114+ regressor_file = reg_file ,
115+ ).regressed_bold
116+
72117 return FunctionalLongOutputs (
73- sbref = func_transform ( # 3D volume
74- in_file = sbref , template = template , xfm = bold_to_tpl_xfm , strategy = "single"
75- ),
76- bold = func_transform (
77- in_file = bold , template = template , xfm = bold_to_tpl_xfm , strategy = "chunked"
78- ),
79- bold_mask = mask_transform (mask = bold_mask , template = template , xfm = bold_to_tpl_xfm )
80- if bold_mask
81- else None ,
82118 bold_to_long_xfm = bold_to_tpl_xfm ,
119+ sbref = long_sbref ,
120+ bold = long_bold ,
121+ bold_mask = long_mask ,
122+ regressed_bold = regressed ,
123+ cleaned_bold = cleaned ,
83124 )
0 commit comments