Skip to content

Commit a9abc4c

Browse files
committed
Initial sketch for implementing +? which would make increment within that sequence
request by @dnkennedy
1 parent bf9b75b commit a9abc4c

File tree

1 file changed

+44
-22
lines changed

1 file changed

+44
-22
lines changed

heudiconv/heuristics/reproin.py

Lines changed: 44 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,12 +137,14 @@
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
134144
import logging
135145
import os.path
136146
import re
147+
from types import MappingProxyType
137148
from typing import Any, Optional, TypeVar
138149

139150
import pydicom as dcm
@@ -398,7 +409,9 @@ def infotodict(
398409
info: dict[tuple[str, tuple[str, ...], None], list[str]] = {}
399410
skipped: list[str] = []
400411
skipped_unknown: list[str] = []
401-
current_run = 0
412+
# Incremented runs specific to each seqinfo (decorated with MappingProxyType for immutability)
413+
# or for a appingProxyType({}) for throughout "run" index
414+
current_runs: dict[MappingProxyType, int] = defaultdict(int)
402415
run_label: Optional[str] = None # run-
403416
dcm_image_iod_spec: Optional[str] = None
404417
skip_derived = False
@@ -540,18 +553,21 @@ def infotodict(
540553

541554
run = series_info.get("run")
542555
if run is not None:
556+
# +? would make it within that particular series_info, whenever
557+
# + - global. Global would be done via empty MappingProxyType as a key.
558+
run_key = MappingProxyType(series_info if run == "+?" else {})
543559
# so we have an indicator for a run
544-
if run == "+":
560+
if run in ("+", "+?"):
545561
# some sequences, e.g. fmap, would generate two (or more?)
546562
# sequences -- e.g. one for magnitude(s) and other ones for
547563
# phases. In those we must not increment run!
548564
if dcm_image_iod_spec and dcm_image_iod_spec == "P":
549565
if prev_dcm_image_iod_spec != "M":
550566
# XXX if we have a known earlier study, we need to always
551567
# increase the run counter for phasediff because magnitudes
552-
# were not acquired
568+
# were not acquired (that was dbic/pulse_sequences)
553569
if get_study_hash([s]) == "9d148e2a05f782273f6343507733309d":
554-
current_run += 1
570+
current_runs[run_key] += 1
555571
else:
556572
raise RuntimeError(
557573
"Was expecting phase image to follow magnitude "
@@ -560,24 +576,25 @@ def infotodict(
560576
)
561577
# else we do nothing special
562578
else: # and otherwise we go to the next run
563-
current_run += 1
579+
current_runs[run_key] += 1
564580
elif run == "=":
565-
if not current_run:
566-
current_run = 1
581+
if not current_runs[run_key]:
582+
current_runs[run_key] = 1
567583
elif run.isdigit():
568584
current_run_ = int(run)
569-
if current_run_ < current_run:
585+
if current_run_ < current_runs[run_key]:
570586
lgr.warning(
571587
"Previous run (%s) was larger than explicitly specified %s",
572-
current_run,
588+
current_runs[run_key],
573589
current_run_,
574590
)
575-
current_run = current_run_
591+
current_runs[run_key] = current_run_
592+
del current_run_
576593
else:
577594
raise ValueError(
578595
"Don't know how to deal with run specification %s" % repr(run)
579596
)
580-
run_label = "run-%02d" % current_run
597+
run_label = "run-%02d" % current_runs[run_key]
581598
else:
582599
# if there is no _run -- no run label added
583600
run_label = None
@@ -637,12 +654,14 @@ def from_series_info(name: str) -> Optional[str]:
637654
# For scouts -- we want only dicoms
638655
# https://github.com/nipy/heudiconv/issues/145
639656
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"
657+
if (
658+
"_Scout" in s.series_description
659+
or (
660+
datatype == "anat"
661+
and datatype_suffix
662+
and datatype_suffix.startswith("scout")
663+
)
664+
or (s.series_description.lower() == s.protocol_name.lower() + "_setter")
646665
):
647666
outtype = ("dicom",)
648667
else:
@@ -908,10 +927,13 @@ def split2(s: str) -> tuple[str, Optional[str]]:
908927
bids_leftovers = []
909928
for s in split[1:]:
910929
key, value = split2(s)
911-
if value is None and key[-1] in "+=":
912-
value = key[-1]
913-
key = key[:-1]
914-
930+
if value is None:
931+
if key[-1] in "+=":
932+
value = key[-1]
933+
key = key[:-1]
934+
if key[-2:] == "+?":
935+
value = key[-2:]
936+
key = key[:-2]
915937
# sanitize values, which must not have _ and - is undesirable ATM as well
916938
# TODO: BIDSv2.0 -- allows "-" so replace with it instead
917939
value = (

0 commit comments

Comments
 (0)