Skip to content

Commit 264c33c

Browse files
committed
Fix Filters.apply() task filter dropping anat rows
The --task filter applied pl.col("task") == value to all rows, but anat/dwi files have task=null, so they were silently dropped. This caused rbc functional --task <label> to fail with FileNotFoundError on desc-brain_T1w for any dataset. Fix: use pl.col("task").is_null() | (pl.col("task") == value) so rows without a task entity (anat, dwi, fmap) pass through. Affects functional and all orchestration; metrics/qc pre-filter to datatype=func so they were never hit. Restore --task in integration fixtures now that the fix is in place.
1 parent ec57a5e commit 264c33c

3 files changed

Lines changed: 14 additions & 8 deletions

File tree

src/rbc/orchestration/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ def apply(self, df: pl.DataFrame, *base_exprs: pl.Expr) -> pl.DataFrame:
6363
if len(self.session_label) > 0:
6464
exprs.append(pl.col("ses").is_in(self.session_label))
6565
if self.task is not None:
66-
exprs.append(pl.col("task") == self.task)
66+
exprs.append(pl.col("task").is_null() | (pl.col("task") == self.task))
6767
if not exprs:
6868
return df
6969
return df.filter(pl.all_horizontal(exprs))

tests/integration/longitudinal/conftest.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
)
2626

2727
_SUB = "01"
28+
_TASK = "fingerfootlips"
2829

2930

3031
def _rbc_exe() -> str:
@@ -161,9 +162,6 @@ def ds000114_func_derivatives(
161162
Only ses-test is processed (one session is sufficient to exercise
162163
the longitudinal functional chain).
163164
"""
164-
# Note: do NOT pass --task here. The Filters.apply() task filter
165-
# applies to ALL rows including anat, and anat rows have task=null,
166-
# so --task would drop all anat rows and break resolve_functional.
167165
_run_rbc(
168166
[
169167
"functional",
@@ -177,6 +175,8 @@ def ds000114_func_derivatives(
177175
_SUB,
178176
"--session-label",
179177
"test",
178+
"--task",
179+
_TASK,
180180
],
181181
)
182182
return ds000114_anat_derivatives
@@ -209,6 +209,8 @@ def longitudinal_func_output(
209209
_SUB,
210210
"--session-label",
211211
"test",
212+
"--task",
213+
_TASK,
212214
],
213215
)
214216
return ds000114_func_derivatives

tests/unit/orchestration/test_filters.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -70,10 +70,13 @@ def test_session_filter(self, bids_df: pl.DataFrame) -> None:
7070
result = Filters(session_label=["baseline"]).apply(bids_df)
7171
assert set(result["ses"].unique().to_list()) == {"baseline"}
7272

73-
def test_task_filter(self, bids_df: pl.DataFrame) -> None:
74-
"""Task filter keeps only matching tasks (nulls excluded)."""
73+
def test_task_filter_preserves_anat(self, bids_df: pl.DataFrame) -> None:
74+
"""Task filter keeps matching tasks AND rows with null task (anat)."""
7575
result = Filters(task="rest").apply(bids_df)
76-
assert all(t == "rest" for t in result["task"].to_list())
76+
tasks = result["task"].to_list()
77+
assert all(t in ("rest", None) for t in tasks)
78+
# Anat rows (task=null) must survive
79+
assert result.filter(pl.col("datatype") == "anat").height > 0
7780

7881
def test_combined_filters(self, bids_df: pl.DataFrame) -> None:
7982
"""Participant + session + task filters compose correctly."""
@@ -82,7 +85,8 @@ def test_combined_filters(self, bids_df: pl.DataFrame) -> None:
8285
session_label=["baseline"],
8386
task="rest",
8487
).apply(bids_df)
85-
assert len(result) == 3 # raw bold + MNI preproc + T1w preproc
88+
# anat T1w (task=null) + raw bold + MNI preproc + T1w preproc = 4
89+
assert len(result) == 4
8690
assert all(s == "01" for s in result["sub"].to_list())
8791
assert all(s == "baseline" for s in result["ses"].to_list())
8892

0 commit comments

Comments
 (0)