Skip to content

Commit fc70f8c

Browse files
committed
Move BIDS session logic and grouping constants to bids/session.py
Moves SessionTables, load_session, iter_session_files, and the BIDS grouping constants (SUB_SES_QUERY, ANAT_GROUP_ENTITIES, FUNC_GROUP_ENTITIES) from cli/ into bids/session.py so that cli/ exclusively handles CLI concerns. Constants are renamed to drop the leading underscore since they are now public API in the bids package. Closes #259.
1 parent d95f039 commit fc70f8c

13 files changed

Lines changed: 111 additions & 52 deletions

File tree

src/rbc/bids/__init__.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,20 @@
3030
from rbc.bids.metrics import MetricsInputs, export_metrics, resolve_metrics
3131
from rbc.bids.qc import QCInputs, export_qc, resolve_qc
3232
from rbc.bids.query import find_file, find_files, get_extra_entity, load_table
33+
from rbc.bids.session import (
34+
ANAT_GROUP_ENTITIES,
35+
FUNC_GROUP_ENTITIES,
36+
SUB_SES_QUERY,
37+
SessionTables,
38+
iter_session_files,
39+
load_session,
40+
)
3341

3442
__all__ = [
43+
"ANAT_GROUP_ENTITIES",
3544
"BIDS_VERSION",
45+
"FUNC_GROUP_ENTITIES",
46+
"SUB_SES_QUERY",
3647
"_STANDARD_ENTITIES",
3748
"BIDSFile",
3849
"Bids",
@@ -44,6 +55,7 @@
4455
"MetricsInputs",
4556
"Modality",
4657
"QCInputs",
58+
"SessionTables",
4759
"Suffix",
4860
"TemplateSpace",
4961
"bids_name",
@@ -59,6 +71,8 @@
5971
"find_file",
6072
"find_files",
6173
"get_extra_entity",
74+
"iter_session_files",
75+
"load_session",
6276
"load_table",
6377
"parse_bids_name",
6478
"resolve_functional",
Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1-
"""BIDS dataset querying utilities."""
1+
"""BIDS session-level discovery and iteration.
2+
3+
Provides utilities for loading subject/session data from a BIDS dataset
4+
and iterating over run/task groups with matched anatomical files.
5+
"""
26

37
from __future__ import annotations
48

@@ -9,6 +13,16 @@
913
if TYPE_CHECKING:
1014
from collections.abc import Iterator, Sequence
1115

16+
SUB_SES_QUERY = ("sub", "ses")
17+
18+
ANAT_GROUP_ENTITIES = ("run", "acq", "suffix", "part", "echo", "ce", "rec", "inv")
19+
"""BIDS entities used to group anatomical files in dataframes generated by
20+
Bids2Table; includes ``suffix`` to distinguish T1w, T2w, etc."""
21+
22+
FUNC_GROUP_ENTITIES = ("task", "run", "acq", "dir", "echo", "part", "rec")
23+
"""BIDS entities used to group functional files in dataframes generated by
24+
Bids2Table; excludes ``suffix`` so 'sbref' stays grouped with 'bold'."""
25+
1226

1327
class SessionTables(NamedTuple):
1428
"""Subject-Session combination tables for anatomical and functional."""
@@ -65,9 +79,9 @@ def iter_session_files(
6579
6680
Anat matching follows this precedence:
6781
68-
1. **1-to-1**: anat and func have the same number of runs match by run label.
69-
2. **1-to-many**: run counts differ use the anat for the first run.
70-
3. **No runs**: no run labels on either side use available anat (e.g. single T1w).
82+
1. **1-to-1**: anat and func have the same number of runs, match by run label.
83+
2. **1-to-many**: run counts differ, use the anat for the first run.
84+
3. **No runs**: no run labels on either side, use available anat (e.g. single T1w).
7185
7286
Args:
7387
session: A :class:`SessionTables` for a single subject/session.

src/rbc/cli/__init__.py

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,3 @@
1414
"ITK_GLOBAL_DEFAULT_NUMBER_OF_THREADS": "1",
1515
"ANTS_RANDOM_SEED": CPAC_ANTS_SEED,
1616
}
17-
18-
_SUB_SES_QUERY = ("sub", "ses")
19-
20-
_ANAT_GROUP_ENTITIES = ("run", "acq", "suffix", "part", "echo", "ce", "rec", "inv")
21-
"""BIDS entities used to group anatomical files in dataframes generated by Bids2Table;
22-
includes ``suffix`` to distinguish T1w, T2w, etc."""
23-
24-
_FUNC_GROUP_ENTITIES = ("task", "run", "acq", "dir", "echo", "part", "rec")
25-
"""BIDS entities used to group functional files in dataframes generated by Bids2Table;
26-
exclude ``suffix` so 'sbref' stays grouped with 'bold'."""

src/rbc/cli/all.py

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,19 +14,21 @@
1414
import polars as pl
1515
from tqdm import tqdm
1616

17-
from rbc.bids import Datatype, extract_entities, load_table
17+
from rbc.bids import (
18+
ANAT_GROUP_ENTITIES,
19+
FUNC_GROUP_ENTITIES,
20+
SUB_SES_QUERY,
21+
Datatype,
22+
extract_entities,
23+
load_table,
24+
)
1825
from rbc.bids.anatomical import export_anatomical
1926
from rbc.bids.functional import export_functional
2027
from rbc.bids.metrics import export_metrics
2128
from rbc.bids.qc import export_qc
22-
from rbc.cli import (
23-
_ANAT_GROUP_ENTITIES,
24-
_DEFAULT_ENV_VARS,
25-
_FUNC_GROUP_ENTITIES,
26-
_SUB_SES_QUERY,
27-
)
29+
from rbc.bids.session import iter_session_files, load_session
30+
from rbc.cli import _DEFAULT_ENV_VARS
2831
from rbc.cli.base import BaseArgs, _validate_atlas, _validate_positive, _validate_task
29-
from rbc.cli.query import iter_session_files, load_session
3032
from rbc.context import RunContext
3133
from rbc.core.niwrap import setup_runner
3234
from rbc.metadata import FunctionalMetadata
@@ -97,7 +99,7 @@ def main(args: AllArgs) -> int:
9799
df = df.filter(pl.all_horizontal(filters))
98100

99101
for _, sub_ses_group in tqdm(
100-
df.group_by(_SUB_SES_QUERY, maintain_order=True), disable=not ctx.verbose
102+
df.group_by(SUB_SES_QUERY, maintain_order=True), disable=not ctx.verbose
101103
):
102104
pipe_ctx = RunContext(
103105
sub=sub_ses_group["sub"][0],
@@ -108,7 +110,7 @@ def main(args: AllArgs) -> int:
108110

109111
# --- Anatomical (once per session, first T1w) ---
110112
for _, anat_df in session.anat.filter(pl.col("suffix") == "T1w").group_by(
111-
_ANAT_GROUP_ENTITIES, maintain_order=True
113+
ANAT_GROUP_ENTITIES, maintain_order=True
112114
):
113115
anat_row = anat_df.filter(suffix="T1w").row(0, named=True)
114116
t1w_fpath = Path(anat_row["root"]) / anat_row["path"]
@@ -122,7 +124,7 @@ def main(args: AllArgs) -> int:
122124

123125
# --- Functional + Metrics + QC (per BOLD run) ---
124126
for func_df, _anat_df in iter_session_files(
125-
session, groupby=_FUNC_GROUP_ENTITIES
127+
session, groupby=FUNC_GROUP_ENTITIES
126128
):
127129
row = func_df.row(0, named=True)
128130
bold_fpath = Path(row["root"]) / row["path"]

src/rbc/cli/anatomical.py

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from dataclasses import dataclass
66
from typing import TYPE_CHECKING
77

8-
from rbc.cli.query import load_session
8+
from rbc.bids.session import load_session
99

1010
if TYPE_CHECKING:
1111
import argparse
@@ -16,9 +16,15 @@
1616
import polars as pl
1717
from tqdm import tqdm
1818

19-
from rbc.bids import Datatype, extract_entities, load_table
19+
from rbc.bids import (
20+
ANAT_GROUP_ENTITIES,
21+
SUB_SES_QUERY,
22+
Datatype,
23+
extract_entities,
24+
load_table,
25+
)
2026
from rbc.bids.anatomical import export_anatomical
21-
from rbc.cli import _ANAT_GROUP_ENTITIES, _DEFAULT_ENV_VARS, _SUB_SES_QUERY
27+
from rbc.cli import _DEFAULT_ENV_VARS
2228
from rbc.cli.base import BaseArgs
2329
from rbc.context import RunContext
2430
from rbc.core.niwrap import setup_runner
@@ -58,7 +64,7 @@ def main(args: AnatomicalArgs) -> int:
5864
df = df.filter(pl.all_horizontal(filters))
5965

6066
for _, sub_ses_group in tqdm(
61-
df.group_by(_SUB_SES_QUERY, maintain_order=True), disable=not ctx.verbose
67+
df.group_by(SUB_SES_QUERY, maintain_order=True), disable=not ctx.verbose
6268
):
6369
pipe_ctx = RunContext(
6470
sub=sub_ses_group["sub"][0],
@@ -68,7 +74,7 @@ def main(args: AnatomicalArgs) -> int:
6874
session = load_session(sub_ses_group, pipe_ctx.sub, pipe_ctx.ses)
6975

7076
for _, anat_df in session.anat.filter(pl.col("suffix") == "T1w").group_by(
71-
_ANAT_GROUP_ENTITIES, maintain_order=True
77+
ANAT_GROUP_ENTITIES, maintain_order=True
7278
):
7379
row = anat_df.row(0, named=True)
7480
t1w_fpath = Path(row["root"]) / row["path"]

src/rbc/cli/functional.py

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,17 @@
1616
import polars as pl
1717
from tqdm import tqdm
1818

19-
from rbc.bids import Datatype, extract_entities, load_table
19+
from rbc.bids import (
20+
FUNC_GROUP_ENTITIES,
21+
SUB_SES_QUERY,
22+
Datatype,
23+
extract_entities,
24+
load_table,
25+
)
2026
from rbc.bids.functional import export_functional, resolve_functional
21-
from rbc.cli import _DEFAULT_ENV_VARS, _FUNC_GROUP_ENTITIES, _SUB_SES_QUERY
27+
from rbc.bids.session import iter_session_files, load_session
28+
from rbc.cli import _DEFAULT_ENV_VARS
2229
from rbc.cli.base import BaseArgs, _validate_positive, _validate_task
23-
from rbc.cli.query import iter_session_files, load_session
2430
from rbc.context import RunContext
2531
from rbc.core.niwrap import setup_runner
2632
from rbc.metadata import FunctionalMetadata
@@ -73,7 +79,7 @@ def main(args: FunctionalArgs) -> int:
7379
df = df.filter(pl.all_horizontal(filters))
7480

7581
for _, sub_ses_group in tqdm(
76-
df.group_by(_SUB_SES_QUERY, maintain_order=True), disable=not ctx.verbose
82+
df.group_by(SUB_SES_QUERY, maintain_order=True), disable=not ctx.verbose
7783
):
7884
pipe_ctx = RunContext(
7985
sub=sub_ses_group["sub"][0],
@@ -84,7 +90,7 @@ def main(args: FunctionalArgs) -> int:
8490
session = load_session(sub_ses_group, pipe_ctx.sub, pipe_ctx.ses)
8591

8692
for func_df, anat_df in iter_session_files(
87-
session, groupby=_FUNC_GROUP_ENTITIES
93+
session, groupby=FUNC_GROUP_ENTITIES
8894
):
8995
func_df = func_df.filter(pl.col("desc").is_null())
9096
row = func_df.filter(suffix="bold").row(0, named=True)

src/rbc/cli/longitudinal.py

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,18 @@
1313
import polars as pl
1414
from tqdm import tqdm
1515

16-
from rbc.bids import Datatype, Extension, Suffix, extract_entities, load_table
17-
from rbc.cli import _DEFAULT_ENV_VARS, _FUNC_GROUP_ENTITIES, _SUB_SES_QUERY
16+
from rbc.bids import (
17+
FUNC_GROUP_ENTITIES,
18+
SUB_SES_QUERY,
19+
Datatype,
20+
Extension,
21+
Suffix,
22+
extract_entities,
23+
load_table,
24+
)
25+
from rbc.bids.session import iter_session_files, load_session
26+
from rbc.cli import _DEFAULT_ENV_VARS
1827
from rbc.cli.base import BaseArgs
19-
from rbc.cli.query import iter_session_files, load_session
2028
from rbc.context import RunContext
2129
from rbc.core.niwrap import setup_runner
2230
from rbc.workflows.anatomical import longitudinal_process as anatomical_longitudinal
@@ -171,7 +179,7 @@ def main(args: LongitudinalArgs) -> int:
171179
group_df = df.filter(pl.all_horizontal(filters))
172180

173181
for _, sub_ses_group in tqdm(
174-
group_df.group_by(_SUB_SES_QUERY, maintain_order=True), disable=not ctx.verbose
182+
group_df.group_by(SUB_SES_QUERY, maintain_order=True), disable=not ctx.verbose
175183
):
176184
pipe_ctx = RunContext(
177185
sub=sub_ses_group["sub"][0],
@@ -198,7 +206,7 @@ def main(args: LongitudinalArgs) -> int:
198206
_process_anat(pipe_ctx=pipe_ctx, anat_df=anat_df, tpl_df=tpl_df)
199207

200208
if args.functional:
201-
for func_df, _ in iter_session_files(session, groupby=_FUNC_GROUP_ENTITIES):
209+
for func_df, _ in iter_session_files(session, groupby=FUNC_GROUP_ENTITIES):
202210
_process_func(pipe_ctx=pipe_ctx, func_df=func_df, tpl_df=tpl_df)
203211
pipe_ctx.ensure_dataset_description()
204212

src/rbc/cli/metrics.py

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,16 @@
1414
import polars as pl
1515
from tqdm import tqdm
1616

17-
from rbc.bids import Datatype, TemplateSpace, extract_entities, load_table
17+
from rbc.bids import (
18+
FUNC_GROUP_ENTITIES,
19+
SUB_SES_QUERY,
20+
Datatype,
21+
TemplateSpace,
22+
extract_entities,
23+
load_table,
24+
)
1825
from rbc.bids.metrics import export_metrics, resolve_metrics
19-
from rbc.cli import _DEFAULT_ENV_VARS, _FUNC_GROUP_ENTITIES, _SUB_SES_QUERY
26+
from rbc.cli import _DEFAULT_ENV_VARS
2027
from rbc.cli.base import BaseArgs, _validate_atlas, _validate_positive, _validate_task
2128
from rbc.context import RunContext
2229
from rbc.core.niwrap import setup_runner
@@ -105,7 +112,7 @@ def main(args: MetricsArgs) -> int:
105112
filters.append(pl.col("task") == args.task)
106113
df = df.filter(pl.all_horizontal(filters))
107114

108-
for _, group in tqdm(df.group_by(_SUB_SES_QUERY), disable=not ctx.verbose):
115+
for _, group in tqdm(df.group_by(SUB_SES_QUERY), disable=not ctx.verbose):
109116
sub: str = group["sub"][0]
110117
ses: str | None = group["ses"][0] or None
111118
pipe_ctx = RunContext(sub=sub, ses=ses, output_dir=args.output_dir)
@@ -114,7 +121,7 @@ def main(args: MetricsArgs) -> int:
114121
dataset_dir=args.output_dir, index_fpath=None, max_workers=0, verbose=False
115122
)
116123

117-
for _, run_group in group.group_by(_FUNC_GROUP_ENTITIES):
124+
for _, run_group in group.group_by(FUNC_GROUP_ENTITIES):
118125
row = run_group.row(0, named=True)
119126
ents = extract_entities(row, ["task", "run", "acq", "rec", "dir", "echo"])
120127

src/rbc/cli/qc.py

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,16 @@
1313
import polars as pl
1414
from tqdm import tqdm
1515

16-
from rbc.bids import Datatype, TemplateSpace, extract_entities, load_table
16+
from rbc.bids import (
17+
FUNC_GROUP_ENTITIES,
18+
SUB_SES_QUERY,
19+
Datatype,
20+
TemplateSpace,
21+
extract_entities,
22+
load_table,
23+
)
1724
from rbc.bids.qc import export_qc, resolve_qc
18-
from rbc.cli import _DEFAULT_ENV_VARS, _FUNC_GROUP_ENTITIES, _SUB_SES_QUERY
25+
from rbc.cli import _DEFAULT_ENV_VARS
1926
from rbc.cli.base import BaseArgs, _validate_positive, _validate_task
2027
from rbc.context import RunContext
2128
from rbc.core.niwrap import setup_runner
@@ -74,7 +81,7 @@ def main(args: QCArgs) -> int:
7481
filters.append(pl.col("task") == args.task)
7582
df = df.filter(pl.all_horizontal(filters))
7683

77-
for _, group in tqdm(df.group_by(_SUB_SES_QUERY), disable=not ctx.verbose):
84+
for _, group in tqdm(df.group_by(SUB_SES_QUERY), disable=not ctx.verbose):
7885
sub: str = group["sub"][0]
7986
ses: str | None = group["ses"][0] or None
8087
pipe_ctx = RunContext(sub=sub, ses=ses, output_dir=args.output_dir)
@@ -83,7 +90,7 @@ def main(args: QCArgs) -> int:
8390
dataset_dir=args.output_dir, index_fpath=None, max_workers=0, verbose=False
8491
)
8592

86-
for _, run_group in group.group_by(_FUNC_GROUP_ENTITIES):
93+
for _, run_group in group.group_by(FUNC_GROUP_ENTITIES):
8794
row = run_group.row(0, named=True)
8895
ents = extract_entities(row, ["task", "run", "acq", "rec", "dir", "echo"])
8996

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,12 @@
33
import polars as pl
44
import pytest
55

6-
from rbc.cli.query import SessionTables, _resolve_anat, iter_session_files, load_session
6+
from rbc.bids.session import (
7+
SessionTables,
8+
_resolve_anat,
9+
iter_session_files,
10+
load_session,
11+
)
712

813

914
@pytest.fixture

0 commit comments

Comments
 (0)