pesky-fieldmaps/new_renamer.py
Michał Szczepanik bd07d0bde7 Draft the renamer to new layout
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.
2026-05-21 19:28:32 +02:00

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))