Skip to content

Commit 7e72f40

Browse files
nx10jpillai00
andcommitted
Split FunctionalOutputs.regressor_file into raw + bandpass-filtered
regressor_file now carries the raw (unfiltered) regressor .1D files as computed from native-space BOLD. The new bpf_regressor_file field holds the bandpass-filtered version that 3dTproject actually applied, exported with desc-<strategy>Filtered for provenance. This lets the longitudinal pipeline reuse the raw regressors without recomputation, matching the cross-sectional principle: one regressor computation per run, applied in each target space. Port of the regressor-split idea from #282. Co-authored-by: Janhavi Pillai <janhavi.pillai@gmail.com>
1 parent 1e51d03 commit 7e72f40

4 files changed

Lines changed: 36 additions & 10 deletions

File tree

docs/data_dictionary.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,8 @@ Produced by `rbc functional`. These are functional MRI (BOLD) processing results
5656
| `*_space-MNI152NLin6Asym_desc-preproc_bold.nii.gz` | `bold` | BOLD timeseries resampled to MNI152NLin6Asym template space in a single interpolation step (before denoising) | ANTs resampling | 4D NIfTI |
5757
| `*_space-MNI152NLin6Asym_desc-bold_mask.nii.gz` | `mask` | Brain mask warped to template space at the BOLD resolution | ANTs resampling | 3D NIfTI, binary mask |
5858
| `*_space-MNI152NLin6Asym_reg-{regressor}_desc-preproc_bold.nii.gz` | `bold` | Denoised BOLD timeseries in template space after nuisance regression and bandpass filtering. `{regressor}` is `36parameter` or `aCompCor` | Nuisance regression | 4D NIfTI |
59-
| `*_desc-{regressor}_regressors.1D` | `regressors` | Nuisance regressor matrix used for denoising. `{regressor}` is `36parameter` or `aCompCor` | Computed from motion parameters and tissue masks | Text, multi-column 1D file |
59+
| `*_desc-{regressor}_regressors.1D` | `regressors` | Raw (unfiltered) nuisance regressor matrix. `{regressor}` is `36parameter` or `aCompCor`. Carried forward for longitudinal regression reuse | Computed from motion parameters and tissue masks | Text, multi-column 1D file |
60+
| `*_desc-{regressor}Filtered_regressors.1D` | `regressors` | Bandpass-filtered nuisance regressor matrix matching what `3dTproject -bandpass` applied. For provenance only | FFT-based bandpass filter | Text, multi-column 1D file |
6061

6162
---
6263

@@ -133,3 +134,9 @@ Produced by the `rbc longitudinal` subcommand group (`template`, `anatomical`, `
133134
| `*_space-longitudinal_desc-T1w_mask.nii.gz` | `mask` | Brain mask in longitudinal template space | ANTs registration to longitudinal template | 3D NIfTI, binary mask |
134135
| `*_from-T1w_to-longitudinal_mode-image_xfm.nii.gz` | `xfm` | Warp field mapping subject anatomy to the longitudinal template | ANTs registration | 3D NIfTI, displacement field |
135136
| `*_from-longitudinal_to-T1w_mode-image_xfm.nii.gz` | `xfm` | Inverse warp field mapping longitudinal template back to subject anatomy | ANTs registration | 3D NIfTI, displacement field |
137+
| `*_space-longitudinal_sbref.nii.gz` | `sbref` | Motion reference volume warped to longitudinal template space | ANTs warping (composed BOLD-to-longitudinal) | 3D NIfTI |
138+
| `*_space-longitudinal_desc-preproc_bold.nii.gz` | `bold` | Preprocessed BOLD warped to longitudinal template space | ANTs warping (composed BOLD-to-longitudinal) | 4D NIfTI |
139+
| `*_space-longitudinal_desc-brain_mask.nii.gz` | `mask` | BOLD brain mask in longitudinal template space | ANTs warping (nearest-neighbor) | 3D NIfTI, binary mask |
140+
| `*_from-bold_to-longitudinal_...xfm.nii.gz` | `xfm` | Composite BOLD-to-longitudinal-template warp field | ANTs compose transforms | 3D NIfTI, displacement field |
141+
| `*_space-longitudinal_desc-regressed_reg-<strategy>_bold.nii.gz` | `bold` | Nuisance-regressed BOLD (no bandpass) in longitudinal space, per regressor strategy | AFNI 3dTproject | 4D NIfTI |
142+
| `*_space-longitudinal_desc-preproc_reg-<strategy>_bold.nii.gz` | `bold` | Nuisance-regressed + bandpass-filtered BOLD in longitudinal space, per regressor strategy | AFNI 3dTproject -bandpass | 4D NIfTI |

src/rbc/bids/functional.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,12 @@ def export_functional(
158158
desc=bids_safe_label(reg),
159159
extension=".1D",
160160
)
161+
func.save(
162+
outputs.bpf_regressor_file[reg],
163+
suffix="regressors",
164+
desc=f"{bids_safe_label(reg)}Filtered",
165+
extension=".1D",
166+
)
161167

162168
mni = func.derive(space=TemplateSpace.MNI152NLIN6ASYM)
163169
for reg in regressors:

src/rbc/workflows/functional.py

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,12 @@ class FunctionalOutputs(NamedTuple):
7676
template_bold: BOLD resampled to template space.
7777
regressed_bold: Nuisance-regressed & non-bandpassed BOLD.
7878
cleaned_bold: Nuisance-regressed & bandpass-filtered BOLD.
79-
regressor_file: Bandpass-filtered nuisance regressor ``.1D`` file.
79+
regressor_file: Raw (unfiltered) nuisance regressor ``.1D`` file,
80+
as computed from native-space BOLD. Carried forward so
81+
longitudinal regression can reuse it without recomputation.
82+
bpf_regressor_file: Bandpass-filtered nuisance regressor ``.1D``
83+
file, matching what ``3dTproject -bandpass`` actually applied.
84+
For BIDS export only.
8085
template_brain_mask: Brain mask warped to template space.
8186
"""
8287

@@ -100,6 +105,7 @@ class FunctionalOutputs(NamedTuple):
100105
regressed_bold: dict[str, Path]
101106
cleaned_bold: dict[str, Path]
102107
regressor_file: dict[str, Path]
108+
bpf_regressor_file: dict[str, Path]
103109
template_brain_mask: Path
104110

105111

@@ -329,6 +335,7 @@ def single_session_preprocess(
329335

330336
regression: dict[str, ApplyRegressionOutputs] = {}
331337
cleaned: dict[str, ApplyRegressionOutputs] = {}
338+
raw_regressors: dict[str, Path] = {}
332339
filtered_regressors: dict[str, Path] = {}
333340
for regressor in regressor_set:
334341
# 15. Nuisance regression without bandpass (pre-bandpass residuals
@@ -350,8 +357,11 @@ def single_session_preprocess(
350357
regressor_file=regressors[regressor].regressor_file,
351358
)
352359

353-
# 17. Export bandpass-filtered regressors (matches what 3dTproject
354-
# actually applied; raw regressors still in compute_regressors output)
360+
# 17a. Carry raw (unfiltered) regressors forward for longitudinal reuse
361+
raw_regressors[regressor] = regressors[regressor].regressor_file
362+
363+
# 17b. Export bandpass-filtered regressors (matches what 3dTproject
364+
# actually applied)
355365
filtered_regressors[regressor] = bandpass_regressor_file(
356366
regressors[regressor].regressor_file,
357367
tr=metadata.tr,
@@ -379,6 +389,7 @@ def single_session_preprocess(
379389
template_bold=template_bold,
380390
regressed_bold={r: regression[r].regressed_bold for r in regressor_set},
381391
cleaned_bold={r: cleaned[r].regressed_bold for r in regressor_set},
382-
regressor_file=filtered_regressors,
392+
regressor_file=raw_regressors,
393+
bpf_regressor_file=filtered_regressors,
383394
template_brain_mask=tmpl_brain,
384395
)

tests/unit/bids/test_exports.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ def _make_func_outputs(w: Path, regressors: list[str]) -> FunctionalOutputs:
7474
regressed_bold={r: _dummy(w, f"regressed_{r}.nii.gz") for r in regressors},
7575
cleaned_bold={r: _dummy(w, f"cleaned_{r}.nii.gz") for r in regressors},
7676
regressor_file={r: _dummy(w, f"regressors_{r}.1D") for r in regressors},
77+
bpf_regressor_file={r: _dummy(w, f"regressors_bpf_{r}.1D") for r in regressors},
7778
template_brain_mask=_dummy(w, "template_mask.nii.gz"),
7879
)
7980

@@ -195,26 +196,27 @@ def test_file_count_single_regressor(
195196
) -> None:
196197
"""Correct file count with one regressor.
197198
198-
8 native-space fixed + 1 regressor file + 2 per-regressor MNI
199-
+ 2 fixed MNI = 13.
199+
8 native-space fixed + 2 regressor files (raw + filtered)
200+
+ 2 per-regressor MNI + 2 fixed MNI = 14.
200201
"""
201202
outputs = _make_func_outputs(workdir, ["36-parameter"])
202203
export_functional(func_bids, outputs, regressors=["36-parameter"])
203204
saved = list(pipe_ctx.output_dir.rglob("*.*"))
204-
assert len(saved) == 13
205+
assert len(saved) == 14
205206

206207
def test_file_count_two_regressors(
207208
self, func_bids: Bids, workdir: Path, pipe_ctx: RunContext
208209
) -> None:
209210
"""Correct file count with two regressors.
210211
211-
8 fixed + 2 regressor files + 4 per-regressor MNI + 2 fixed MNI = 16.
212+
8 fixed + 4 regressor files (2x raw + 2x filtered)
213+
+ 4 per-regressor MNI + 2 fixed MNI = 18.
212214
"""
213215
regs = ["36-parameter", "aCompCor"]
214216
outputs = _make_func_outputs(workdir, regs)
215217
export_functional(func_bids, outputs, regressors=regs)
216218
saved = list(pipe_ctx.output_dir.rglob("*.*"))
217-
assert len(saved) == 16
219+
assert len(saved) == 18
218220

219221

220222
# ---------------------------------------------------------------------------

0 commit comments

Comments
 (0)