89
89
_run-<RUNID> (optional)
90
90
a (typically functional) run. The same idea as with SESID.
91
91
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
+
92
101
_dir-[AP,PA,LR,RL,VD,DV] (optional)
93
102
to be used for fmap images, whenever a pair of the SE images is collected
94
103
to be used to estimate the fieldmap
113
122
__dup0<number> suffix.
114
123
115
124
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.
117
126
118
127
## Scanner specifics
119
128
128
137
129
138
from __future__ import annotations
130
139
140
+ from collections import defaultdict
131
141
from collections .abc import Iterable
132
142
from glob import glob
133
143
import hashlib
@@ -398,7 +408,9 @@ def infotodict(
398
408
info : dict [tuple [str , tuple [str , ...], None ], list [str ]] = {}
399
409
skipped : list [str ] = []
400
410
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 )
402
414
run_label : Optional [str ] = None # run-
403
415
dcm_image_iod_spec : Optional [str ] = None
404
416
skip_derived = False
@@ -540,18 +552,25 @@ def infotodict(
540
552
541
553
run = series_info .get ("run" )
542
554
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 {})
543
562
# so we have an indicator for a run
544
- if run == "+" :
563
+ if run in ( "+" , "+?" ) :
545
564
# some sequences, e.g. fmap, would generate two (or more?)
546
565
# sequences -- e.g. one for magnitude(s) and other ones for
547
566
# phases. In those we must not increment run!
548
567
if dcm_image_iod_spec and dcm_image_iod_spec == "P" :
549
568
if prev_dcm_image_iod_spec != "M" :
550
569
# XXX if we have a known earlier study, we need to always
551
570
# increase the run counter for phasediff because magnitudes
552
- # were not acquired
571
+ # were not acquired (that was dbic/pulse_sequences)
553
572
if get_study_hash ([s ]) == "9d148e2a05f782273f6343507733309d" :
554
- current_run += 1
573
+ current_runs [ run_key ] += 1
555
574
else :
556
575
raise RuntimeError (
557
576
"Was expecting phase image to follow magnitude "
@@ -560,24 +579,25 @@ def infotodict(
560
579
)
561
580
# else we do nothing special
562
581
else : # and otherwise we go to the next run
563
- current_run += 1
582
+ current_runs [ run_key ] += 1
564
583
elif run == "=" :
565
- if not current_run :
566
- current_run = 1
584
+ if not current_runs [ run_key ] :
585
+ current_runs [ run_key ] = 1
567
586
elif run .isdigit ():
568
587
current_run_ = int (run )
569
- if current_run_ < current_run :
588
+ if current_run_ < current_runs [ run_key ] :
570
589
lgr .warning (
571
590
"Previous run (%s) was larger than explicitly specified %s" ,
572
- current_run ,
591
+ current_runs [ run_key ] ,
573
592
current_run_ ,
574
593
)
575
- current_run = current_run_
594
+ current_runs [run_key ] = current_run_
595
+ del current_run_
576
596
else :
577
597
raise ValueError (
578
598
"Don't know how to deal with run specification %s" % repr (run )
579
599
)
580
- run_label = "run-%02d" % current_run
600
+ run_label = "run-%02d" % current_runs [ run_key ]
581
601
else :
582
602
# if there is no _run -- no run label added
583
603
run_label = None
@@ -637,12 +657,14 @@ def from_series_info(name: str) -> Optional[str]:
637
657
# For scouts -- we want only dicoms
638
658
# https://github.com/nipy/heudiconv/issues/145
639
659
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" )
646
668
):
647
669
outtype = ("dicom" ,)
648
670
else :
@@ -908,10 +930,13 @@ def split2(s: str) -> tuple[str, Optional[str]]:
908
930
bids_leftovers = []
909
931
for s in split [1 :]:
910
932
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 ]
915
940
# sanitize values, which must not have _ and - is undesirable ATM as well
916
941
# TODO: BIDSv2.0 -- allows "-" so replace with it instead
917
942
value = (
0 commit comments