Changing the BIDS layout to use acq- labels for fmap needs to be accompanied by a comprehensive rename (fix-up) script. This draft exercises an idea: wouldn't it be easier and clearer to approach the operations declaratively, ie. first state which files to rename or delete and then loop over these operations. The declaration could be done with dicts of BIDS entities (this also allows intuitive changes as dict updates), which would be translated into paths using pybids.
169 lines
4.8 KiB
Python
169 lines
4.8 KiB
Python
import argparse
|
|
from collections import namedtuple
|
|
from itertools import product
|
|
from pathlib import Path
|
|
|
|
from bids import BIDSLayout
|
|
|
|
replacements = (
|
|
(
|
|
{
|
|
"datatype": "fmap",
|
|
"direction": "ap",
|
|
"run": 1,
|
|
"suffix": "epi",
|
|
"extension": ".json",
|
|
},
|
|
{"run": None, "acquisition": "rest"},
|
|
),
|
|
(
|
|
{
|
|
"datatype": "fmap",
|
|
"direction": "pa",
|
|
"run": 1,
|
|
"suffix": "epi",
|
|
"extension": ".json",
|
|
},
|
|
{"run": None, "acquisition": "rest"},
|
|
),
|
|
)
|
|
|
|
|
|
# building paths with f-strings is hard, so let's store dicts of BIDS
|
|
# entities to define renamings; a tuple will have one dict to match
|
|
# and one to replace
|
|
RenameEntitySpec = namedtuple(
|
|
"RenameEntitySpec", ["match", "replace", "must_exist"], defaults=[False]
|
|
)
|
|
|
|
# similarly for deletions
|
|
DeleteEntitySpec = namedtuple(
|
|
"DeleteEntitySpec", ["match", "must_exist"], defaults=[False]
|
|
)
|
|
|
|
# we'll convert that to paths
|
|
RenamePathSpec = namedtuple(
|
|
"RenamePathSpec", ["before", "after", "must_exist"], defaults=[False]
|
|
)
|
|
DeletePathSpec = namedtuple("DeletePathSpec", ["path", "must_exist"], defaults=[False])
|
|
|
|
|
|
def build_paths(bids, spec, const):
|
|
"""A helper to build BIDS paths from dicts
|
|
|
|
This uses dict.update ("|" operator) to replace entities, and
|
|
builds paths from there. The const part should contain subject and
|
|
session.
|
|
|
|
"""
|
|
if isinstance(spec, RenameEntitySpec):
|
|
return RenamePathSpec(
|
|
before=Path(bids.build_path(const | spec.match)),
|
|
after=Path(bids.build_path(const | spec.match | spec.replace)),
|
|
must_exist=spec.must_exist,
|
|
)
|
|
if isinstance(spec, DeleteEntitySpec):
|
|
return DeletePathSpec(
|
|
path=Path(bids.build_path(const | spec.match), must_exist=spec.must_exist)
|
|
)
|
|
|
|
|
|
def fixup_session():
|
|
# ensure that scans.tsv is editable
|
|
# check whether renames can be done
|
|
pass
|
|
|
|
|
|
def apply_renames(specs):
|
|
ss_dir = None # todo: define
|
|
scans_file_path = None # todo: define
|
|
repls = {}
|
|
for spec in specs:
|
|
_ = spec.before.rename(spec.after)
|
|
if spec.before.suffix == ".gz" or spec.before.suffix == ".nii":
|
|
repls[spec.before.relative_to(ss_dir)] = spec.after.relative_to(ss_dir)
|
|
|
|
if len(repls) > 0:
|
|
edit_scans_file(scans_file_path) # todo: define x
|
|
|
|
|
|
## Rename specifications
|
|
|
|
rpls = []
|
|
dels = []
|
|
|
|
# the original run-1/2/3 become acq-rest/restvidn/restvida
|
|
label_for_run = {1: "rest", 2: "restvidn", 3: "restvida"}
|
|
for dir, run, ext in product(("ap", "pa"), (1, 2, 3), (".nii.gz", ".json")):
|
|
rpls.append(
|
|
RenameEntitySpec(
|
|
match={
|
|
"datatype": "fmap",
|
|
"direction": dir,
|
|
"run": run,
|
|
"suffix": "epi",
|
|
"extension": ext,
|
|
},
|
|
replace={"run": None, "acquisition": label_for_run[run]},
|
|
must_exist=True,
|
|
)
|
|
)
|
|
|
|
# dwi files without dir-<label> should have been dir-pa
|
|
for acq, ext in product(("b1200", "mshell"), (".nii.gz", ".json")):
|
|
rpls.append(
|
|
RenameEntitySpec(
|
|
match={
|
|
"datatype": "dwi",
|
|
"acquisition": acq,
|
|
"direction": None,
|
|
"suffix": "dwi",
|
|
"extension": ext,
|
|
},
|
|
replace={"direction": "PA"},
|
|
)
|
|
)
|
|
|
|
# dwi files with sbref suffix should have gone into fmap
|
|
for acq, ext in product(("b1200", "mshell"), (".nii.gz", ".json")):
|
|
rpls.append(
|
|
RenameEntitySpec(
|
|
match={
|
|
"datatype": "dwi",
|
|
"acquisition": acq,
|
|
"suffix": "sbref",
|
|
"extension": ext,
|
|
},
|
|
replace={"datatype": "fmap", "direction": "AP", "suffix": "epi"},
|
|
)
|
|
)
|
|
|
|
# ... and their bval / bvec should have never existed
|
|
for acq, ext in product(("b1200", "mshell"), (".bval", ".bvec")):
|
|
dels.append(
|
|
DeleteEntitySpec(
|
|
match={
|
|
"datatype": "dwi",
|
|
"acquisition": acq,
|
|
"suffix": "sbref",
|
|
"extension": ext,
|
|
},
|
|
)
|
|
)
|
|
|
|
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument("bids_root", type=Path)
|
|
parser.add_argument("--subject", nargs="*", help="Subject label(s)")
|
|
parser.add_argument("--session", nargs="*", help="Session label(s)")
|
|
parser.add_argument("--cache", type=Path)
|
|
args = parser.parse_args()
|
|
|
|
ds = BIDSLayout(args.bids_root, validate=False, database_path=args.cache)
|
|
subject = args.subject[0]
|
|
session = args.session[0]
|
|
|
|
const = {"subject": args.subject[0], "session": args.session[0]}
|
|
for spec in rpls:
|
|
pspec = build_paths(ds, spec, const)
|
|
print(pspec.before.relative_to(ds.root), "→", pspec.after.relative_to(ds.root))
|