Skip to content

Commit

Permalink
Initial sketch for implementing +? which would make increment within …
Browse files Browse the repository at this point in the history
…that sequence

request by @dnkennedy
  • Loading branch information
yarikoptic committed Sep 1, 2023
1 parent 69c271e commit 38a80b5
Showing 1 changed file with 47 additions and 22 deletions.
69 changes: 47 additions & 22 deletions heudiconv/heuristics/reproin.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,15 @@
_run-<RUNID> (optional)
a (typically functional) run. The same idea as with SESID.
_run{+,=,+?} (optional) (not recommended)
Not recommended since disallows detection of canceled runs.
You can use "+" to increment run id from the previous (through out all
sequences), or starting from 1.
"=" would use the same run as the previous known.
"+?" would make that run ID increment specific to that particular sequence,
so that e.g. func_run+?_task-1 and func_run+?_task-2 would both have _run-1
for the first sequence in each task, and then run-2 and so on.
_dir-[AP,PA,LR,RL,VD,DV] (optional)
to be used for fmap images, whenever a pair of the SE images is collected
to be used to estimate the fieldmap
Expand All @@ -113,7 +122,7 @@
__dup0<number> suffix.
Although we still support "-" and "+" used within SESID and TASKID, their use is
not recommended, thus not listed here
not recommended, thus not listed here.
## Scanner specifics
Expand All @@ -128,6 +137,7 @@

from __future__ import annotations

from collections import defaultdict
from collections.abc import Iterable
from glob import glob
import hashlib
Expand Down Expand Up @@ -398,7 +408,9 @@ def infotodict(
info: dict[tuple[str, tuple[str, ...], None], list[str]] = {}
skipped: list[str] = []
skipped_unknown: list[str] = []
current_run = 0
# Incremented runs specific to each seqinfo (casted as a tuple of tuples for hashability)
# and of an empty dict for the throughout "run" index
current_runs: dict[tuple, int] = defaultdict(int)
run_label: Optional[str] = None # run-
dcm_image_iod_spec: Optional[str] = None
skip_derived = False
Expand Down Expand Up @@ -540,18 +552,25 @@ def infotodict(

run = series_info.get("run")
if run is not None:
# +? would make it within that particular series_info, whenever
# + - global. Global would be done via hashdict of an empty dict.
def hashdict(d: dict) -> tuple[tuple, ...]:
"""Helper to get a hashable "view" of a dict so we could use it as a key"""
return tuple(sorted(d.items()))

run_key = hashdict(series_info if run == "+?" else {})
# so we have an indicator for a run
if run == "+":
if run in ("+", "+?"):
# some sequences, e.g. fmap, would generate two (or more?)
# sequences -- e.g. one for magnitude(s) and other ones for
# phases. In those we must not increment run!
if dcm_image_iod_spec and dcm_image_iod_spec == "P":
if prev_dcm_image_iod_spec != "M":
# XXX if we have a known earlier study, we need to always
# increase the run counter for phasediff because magnitudes
# were not acquired
# were not acquired (that was dbic/pulse_sequences)
if get_study_hash([s]) == "9d148e2a05f782273f6343507733309d":
current_run += 1
current_runs[run_key] += 1

Check warning on line 573 in heudiconv/heuristics/reproin.py

View check run for this annotation

Codecov / codecov/patch

heudiconv/heuristics/reproin.py#L573

Added line #L573 was not covered by tests
else:
raise RuntimeError(
"Was expecting phase image to follow magnitude "
Expand All @@ -560,24 +579,25 @@ def infotodict(
)
# else we do nothing special
else: # and otherwise we go to the next run
current_run += 1
current_runs[run_key] += 1

Check warning on line 582 in heudiconv/heuristics/reproin.py

View check run for this annotation

Codecov / codecov/patch

heudiconv/heuristics/reproin.py#L582

Added line #L582 was not covered by tests
elif run == "=":
if not current_run:
current_run = 1
if not current_runs[run_key]:
current_runs[run_key] = 1

Check warning on line 585 in heudiconv/heuristics/reproin.py

View check run for this annotation

Codecov / codecov/patch

heudiconv/heuristics/reproin.py#L584-L585

Added lines #L584 - L585 were not covered by tests
elif run.isdigit():
current_run_ = int(run)
if current_run_ < current_run:
if current_run_ < current_runs[run_key]:
lgr.warning(
"Previous run (%s) was larger than explicitly specified %s",
current_run,
current_runs[run_key],
current_run_,
)
current_run = current_run_
current_runs[run_key] = current_run_
del current_run_
else:
raise ValueError(
"Don't know how to deal with run specification %s" % repr(run)
)
run_label = "run-%02d" % current_run
run_label = "run-%02d" % current_runs[run_key]
else:
# if there is no _run -- no run label added
run_label = None
Expand Down Expand Up @@ -637,12 +657,14 @@ def from_series_info(name: str) -> Optional[str]:
# For scouts -- we want only dicoms
# https://github.com/nipy/heudiconv/issues/145
outtype: tuple[str, ...]
if "_Scout" in s.series_description or (
datatype == "anat"
and datatype_suffix
and datatype_suffix.startswith("scout")
) or (
s.series_description.lower() == s.protocol_name.lower() + "_setter"
if (
"_Scout" in s.series_description
or (
datatype == "anat"
and datatype_suffix
and datatype_suffix.startswith("scout")
)
or (s.series_description.lower() == s.protocol_name.lower() + "_setter")
):
outtype = ("dicom",)
else:
Expand Down Expand Up @@ -908,10 +930,13 @@ def split2(s: str) -> tuple[str, Optional[str]]:
bids_leftovers = []
for s in split[1:]:
key, value = split2(s)
if value is None and key[-1] in "+=":
value = key[-1]
key = key[:-1]

if value is None:
if key[-1] in "+=":
value = key[-1]
key = key[:-1]
if key[-2:] == "+?":
value = key[-2:]
key = key[:-2]

Check warning on line 939 in heudiconv/heuristics/reproin.py

View check run for this annotation

Codecov / codecov/patch

heudiconv/heuristics/reproin.py#L938-L939

Added lines #L938 - L939 were not covered by tests
# sanitize values, which must not have _ and - is undesirable ATM as well
# TODO: BIDSv2.0 -- allows "-" so replace with it instead
value = (
Expand Down

0 comments on commit 38a80b5

Please sign in to comment.