Skip to content

Commit 38a80b5

Browse files
committed
Initial sketch for implementing +? which would make increment within that sequence
request by @dnkennedy
1 parent 69c271e commit 38a80b5

File tree

1 file changed

+47
-22
lines changed

1 file changed

+47
-22
lines changed

heudiconv/heuristics/reproin.py

Lines changed: 47 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,15 @@
8989
_run-<RUNID> (optional)
9090
a (typically functional) run. The same idea as with SESID.
9191
92+
_run{+,=,+?} (optional) (not recommended)
93+
Not recommended since disallows detection of canceled runs.
94+
You can use "+" to increment run id from the previous (through out all
95+
sequences), or starting from 1.
96+
"=" would use the same run as the previous known.
97+
"+?" would make that run ID increment specific to that particular sequence,
98+
so that e.g. func_run+?_task-1 and func_run+?_task-2 would both have _run-1
99+
for the first sequence in each task, and then run-2 and so on.
100+
92101
_dir-[AP,PA,LR,RL,VD,DV] (optional)
93102
to be used for fmap images, whenever a pair of the SE images is collected
94103
to be used to estimate the fieldmap
@@ -113,7 +122,7 @@
113122
__dup0<number> suffix.
114123
115124
Although we still support "-" and "+" used within SESID and TASKID, their use is
116-
not recommended, thus not listed here
125+
not recommended, thus not listed here.
117126
118127
## Scanner specifics
119128
@@ -128,6 +137,7 @@
128137

129138
from __future__ import annotations
130139

140+
from collections import defaultdict
131141
from collections.abc import Iterable
132142
from glob import glob
133143
import hashlib
@@ -398,7 +408,9 @@ def infotodict(
398408
info: dict[tuple[str, tuple[str, ...], None], list[str]] = {}
399409
skipped: list[str] = []
400410
skipped_unknown: list[str] = []
401-
current_run = 0
411+
# Incremented runs specific to each seqinfo (casted as a tuple of tuples for hashability)
412+
# and of an empty dict for the throughout "run" index
413+
current_runs: dict[tuple, int] = defaultdict(int)
402414
run_label: Optional[str] = None # run-
403415
dcm_image_iod_spec: Optional[str] = None
404416
skip_derived = False
@@ -540,18 +552,25 @@ def infotodict(
540552

541553
run = series_info.get("run")
542554
if run is not None:
555+
# +? would make it within that particular series_info, whenever
556+
# + - global. Global would be done via hashdict of an empty dict.
557+
def hashdict(d: dict) -> tuple[tuple, ...]:
558+
"""Helper to get a hashable "view" of a dict so we could use it as a key"""
559+
return tuple(sorted(d.items()))
560+
561+
run_key = hashdict(series_info if run == "+?" else {})
543562
# so we have an indicator for a run
544-
if run == "+":
563+
if run in ("+", "+?"):
545564
# some sequences, e.g. fmap, would generate two (or more?)
546565
# sequences -- e.g. one for magnitude(s) and other ones for
547566
# phases. In those we must not increment run!
548567
if dcm_image_iod_spec and dcm_image_iod_spec == "P":
549568
if prev_dcm_image_iod_spec != "M":
550569
# XXX if we have a known earlier study, we need to always
551570
# increase the run counter for phasediff because magnitudes
552-
# were not acquired
571+
# were not acquired (that was dbic/pulse_sequences)
553572
if get_study_hash([s]) == "9d148e2a05f782273f6343507733309d":
554-
current_run += 1
573+
current_runs[run_key] += 1
555574
else:
556575
raise RuntimeError(
557576
"Was expecting phase image to follow magnitude "
@@ -560,24 +579,25 @@ def infotodict(
560579
)
561580
# else we do nothing special
562581
else: # and otherwise we go to the next run
563-
current_run += 1
582+
current_runs[run_key] += 1
564583
elif run == "=":
565-
if not current_run:
566-
current_run = 1
584+
if not current_runs[run_key]:
585+
current_runs[run_key] = 1
567586
elif run.isdigit():
568587
current_run_ = int(run)
569-
if current_run_ < current_run:
588+
if current_run_ < current_runs[run_key]:
570589
lgr.warning(
571590
"Previous run (%s) was larger than explicitly specified %s",
572-
current_run,
591+
current_runs[run_key],
573592
current_run_,
574593
)
575-
current_run = current_run_
594+
current_runs[run_key] = current_run_
595+
del current_run_
576596
else:
577597
raise ValueError(
578598
"Don't know how to deal with run specification %s" % repr(run)
579599
)
580-
run_label = "run-%02d" % current_run
600+
run_label = "run-%02d" % current_runs[run_key]
581601
else:
582602
# if there is no _run -- no run label added
583603
run_label = None
@@ -637,12 +657,14 @@ def from_series_info(name: str) -> Optional[str]:
637657
# For scouts -- we want only dicoms
638658
# https://github.com/nipy/heudiconv/issues/145
639659
outtype: tuple[str, ...]
640-
if "_Scout" in s.series_description or (
641-
datatype == "anat"
642-
and datatype_suffix
643-
and datatype_suffix.startswith("scout")
644-
) or (
645-
s.series_description.lower() == s.protocol_name.lower() + "_setter"
660+
if (
661+
"_Scout" in s.series_description
662+
or (
663+
datatype == "anat"
664+
and datatype_suffix
665+
and datatype_suffix.startswith("scout")
666+
)
667+
or (s.series_description.lower() == s.protocol_name.lower() + "_setter")
646668
):
647669
outtype = ("dicom",)
648670
else:
@@ -908,10 +930,13 @@ def split2(s: str) -> tuple[str, Optional[str]]:
908930
bids_leftovers = []
909931
for s in split[1:]:
910932
key, value = split2(s)
911-
if value is None and key[-1] in "+=":
912-
value = key[-1]
913-
key = key[:-1]
914-
933+
if value is None:
934+
if key[-1] in "+=":
935+
value = key[-1]
936+
key = key[:-1]
937+
if key[-2:] == "+?":
938+
value = key[-2:]
939+
key = key[:-2]
915940
# sanitize values, which must not have _ and - is undesirable ATM as well
916941
# TODO: BIDSv2.0 -- allows "-" so replace with it instead
917942
value = (

0 commit comments

Comments
 (0)