Skip to content

Commit 0cdaed6

Browse files
committed
Address reviews, round 2
- Removed check for uniqueness of bold dataframe - Update stale docstring: `bold_ref` -> `bold_files` - Wrap task with `bids_safe_label` when calling `tpl.save` - Compare affines using `np.allclose` due to floating point precision - Use a BoldKey NamedTuple as key to unique bold files
1 parent 35ad7b1 commit 0cdaed6

5 files changed

Lines changed: 42 additions & 23 deletions

File tree

src/rbc/bids/longitudinal/template.py

Lines changed: 30 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,36 @@ class TemplateInputs(NamedTuple):
2121
sub: Subject label.
2222
sessions: Per-input session labels (parallel to ``files``).
2323
files: Per-session preprocessed T1w brain volumes.
24-
bold_ref: Per-session preprocessed BOLD volumes (all tasks).
24+
bold_files: Per-session, unique preprocessed BOLD volumes (all tasks).
2525
"""
2626

2727
sub: str
2828
sessions: list[str]
2929
files: list[Path]
30-
bold_files: dict[str, Path]
30+
bold_files: dict[BoldKey, Path]
31+
32+
33+
# Use typing library instead of collections for static type hints
34+
class BoldKey(NamedTuple):
35+
"""Key with associated entities to use as dict key to represent bold filepath found.
36+
37+
Attributes:
38+
task: Task associated with file.
39+
run: Run associated with file.
40+
acq: Acquisition associated with file.
41+
dir: Direction associated with file.
42+
echo: Echo associated with file.
43+
part: Part associated with file.
44+
rec: Recording associated with file.
45+
"""
46+
47+
task: str
48+
run: str | None = None
49+
acq: str | None = None
50+
dir: str | None = None
51+
echo: str | None = None
52+
part: str | None = None
53+
rec: str | None = None
3154

3255

3356
def discover_template_inputs(
@@ -80,19 +103,9 @@ def discover_template_inputs(
80103
sub_bold = bold_rows.filter(
81104
(pl.col("sub") == sub) & (pl.col("ses") == sessions[0])
82105
).unique(subset=(*FUNC_GROUP_ENTITIES, "root", "path"))
83-
# Check each task is unique, otherwise raise assertion error with details
84-
if sub_bold.height != sub_bold.unique().height:
85-
conflicts = (
86-
sub_bold.filter(pl.struct(FUNC_GROUP_ENTITIES).is_duplicated())
87-
.group_by(FUNC_GROUP_ENTITIES)
88-
.agg(pl.format("{}/{}", "root", "path").alias("paths"))
89-
)
90-
raise AssertionError(
91-
f"Found multiple non-matching grids for subject {sub}:\n"
92-
+ "\n".join(str(dict(row)) for row in conflicts.iter_rows(named=True))
93-
)
94-
bold_files = {
95-
row["task"]: Path(row["root"]) / row["path"]
106+
bold_files: dict[BoldKey, Path] = {
107+
BoldKey(**{ent: row[ent] for ent in FUNC_GROUP_ENTITIES}): Path(row["root"])
108+
/ row["path"]
96109
for row in sub_bold.iter_rows(named=True)
97110
}
98111
inputs.append(
@@ -112,8 +125,8 @@ def export_template(tpl: Bids, outputs: LongitudinalTemplateOutputs) -> None:
112125
outputs: Results from the longitudinal template workflow.
113126
"""
114127
tpl.save(outputs.template, suffix=Suffix.T1W)
115-
for btask, bold_template in outputs.bold_templates.items():
116-
tpl.save(bold_template, res=btask, suffix=Suffix.T1W)
128+
for bold_key, bold_template in outputs.bold_templates.items():
129+
tpl.save(bold_template, res=bids_safe_label(bold_key.task), suffix=Suffix.T1W)
117130
for ses, xfm in zip(outputs.sessions, outputs.transforms, strict=True):
118131
ses_label = bids_safe_label(ses)
119132
tpl.save(

src/rbc/core/longitudinal/resampling.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from typing import TYPE_CHECKING
66

77
import nibabel as nib
8+
import numpy as np
89
from nibabel.processing import resample_from_to
910

1011
if TYPE_CHECKING:
@@ -31,7 +32,9 @@ def resample_img_to_bold_grid(bold_ref: Path, img: Path, order: int = 3) -> Path
3132
if len(bold_ref_img.shape) > 3:
3233
bold_ref_img = bold_ref_img.slicer[..., 0]
3334
# If same shape, no need to resample
34-
if bold_ref_img.shape == img_obj.shape:
35+
if bold_ref_img.shape == img_obj.shape and np.allclose(
36+
bold_ref_img.affine, img_obj.affine
37+
):
3538
return img
3639

3740
img_resampled = resample_from_to(img_obj, bold_ref_img, order=order)

src/rbc/orchestration/longitudinal/qc.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
resolve_longitudinal_qc,
2121
write_longitudinal_qc_tsv,
2222
)
23-
from rbc.bids.session import iter_session_files
23+
from rbc.bids.session import _FUNC_ENTITY_KEYS, iter_session_files
2424
from rbc.core.longitudinal.resampling import resample_img_to_bold_grid
2525
from rbc.core.niwrap import generate_exec_folder
2626
from rbc.core.qc.registration import registration_qc_metrics
@@ -160,7 +160,7 @@ def run(
160160

161161
for func_df, _ in iter_session_files(session, groupby=FUNC_GROUP_ENTITIES):
162162
row = func_df.filter(suffix=Suffix.BOLD).row(0, named=True)
163-
ents = extract_entities(row, ["task", "run"])
163+
ents = extract_entities(row, _FUNC_ENTITY_KEYS)
164164

165165
func_q = pipe_ctx.bids(datatype=Datatype.FUNC, entities=ents)
166166
func_long_q = func_q.derive(space="longitudinal")

src/rbc/workflows/longitudinal/template.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
from collections.abc import Mapping, Sequence
2121
from pathlib import Path
2222

23+
from rbc.bids.longitudinal.template import BoldKey
24+
2325
_logger = logging.getLogger("rbc")
2426

2527

@@ -35,7 +37,7 @@ class LongitudinalTemplateOutputs(NamedTuple):
3537
"""
3638

3739
template: Path
38-
bold_templates: dict[str, Path]
40+
bold_templates: dict[BoldKey, Path]
3941
sessions: list[str]
4042
transforms: list[Path]
4143

@@ -44,7 +46,7 @@ def generate_subject_template(
4446
sub: str,
4547
sessions: Sequence[str],
4648
in_files: Sequence[Path],
47-
bold_files: Mapping[str, Path],
49+
bold_files: Mapping[BoldKey, Path],
4850
) -> LongitudinalTemplateOutputs:
4951
"""Build a robust template and ITK transforms for one subject.
5052

tests/unit/bids/test_longitudinal_template.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import polars as pl
88

99
from rbc.bids.longitudinal.template import (
10+
BoldKey,
1011
discover_template_inputs,
1112
export_template,
1213
)
@@ -182,7 +183,7 @@ def test_writes_template_and_xfms(self, tmp_path: Path) -> None:
182183

183184
outputs = LongitudinalTemplateOutputs(
184185
template=template_src,
185-
bold_templates={"test": template_bold_src},
186+
bold_templates={BoldKey(task="test"): template_bold_src},
186187
sessions=["baseline", "vis2"],
187188
transforms=[xfm_baseline, xfm_vis2],
188189
)

0 commit comments

Comments
 (0)