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
134
144
import logging
135
145
import os .path
136
146
import re
147
+ from types import MappingProxyType
137
148
from typing import Any , Optional , TypeVar
138
149
139
150
import pydicom as dcm
@@ -398,7 +409,9 @@ def infotodict(
398
409
info : dict [tuple [str , tuple [str , ...], None ], list [str ]] = {}
399
410
skipped : list [str ] = []
400
411
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 )
402
415
run_label : Optional [str ] = None # run-
403
416
dcm_image_iod_spec : Optional [str ] = None
404
417
skip_derived = False
@@ -540,18 +553,21 @@ def infotodict(
540
553
541
554
run = series_info .get ("run" )
542
555
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 {})
543
559
# so we have an indicator for a run
544
- if run == "+" :
560
+ if run in ( "+" , "+?" ) :
545
561
# some sequences, e.g. fmap, would generate two (or more?)
546
562
# sequences -- e.g. one for magnitude(s) and other ones for
547
563
# phases. In those we must not increment run!
548
564
if dcm_image_iod_spec and dcm_image_iod_spec == "P" :
549
565
if prev_dcm_image_iod_spec != "M" :
550
566
# XXX if we have a known earlier study, we need to always
551
567
# increase the run counter for phasediff because magnitudes
552
- # were not acquired
568
+ # were not acquired (that was dbic/pulse_sequences)
553
569
if get_study_hash ([s ]) == "9d148e2a05f782273f6343507733309d" :
554
- current_run += 1
570
+ current_runs [ run_key ] += 1
555
571
else :
556
572
raise RuntimeError (
557
573
"Was expecting phase image to follow magnitude "
@@ -560,24 +576,25 @@ def infotodict(
560
576
)
561
577
# else we do nothing special
562
578
else : # and otherwise we go to the next run
563
- current_run += 1
579
+ current_runs [ run_key ] += 1
564
580
elif run == "=" :
565
- if not current_run :
566
- current_run = 1
581
+ if not current_runs [ run_key ] :
582
+ current_runs [ run_key ] = 1
567
583
elif run .isdigit ():
568
584
current_run_ = int (run )
569
- if current_run_ < current_run :
585
+ if current_run_ < current_runs [ run_key ] :
570
586
lgr .warning (
571
587
"Previous run (%s) was larger than explicitly specified %s" ,
572
- current_run ,
588
+ current_runs [ run_key ] ,
573
589
current_run_ ,
574
590
)
575
- current_run = current_run_
591
+ current_runs [run_key ] = current_run_
592
+ del current_run_
576
593
else :
577
594
raise ValueError (
578
595
"Don't know how to deal with run specification %s" % repr (run )
579
596
)
580
- run_label = "run-%02d" % current_run
597
+ run_label = "run-%02d" % current_runs [ run_key ]
581
598
else :
582
599
# if there is no _run -- no run label added
583
600
run_label = None
@@ -637,12 +654,14 @@ def from_series_info(name: str) -> Optional[str]:
637
654
# For scouts -- we want only dicoms
638
655
# https://github.com/nipy/heudiconv/issues/145
639
656
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" )
646
665
):
647
666
outtype = ("dicom" ,)
648
667
else :
@@ -908,10 +927,13 @@ def split2(s: str) -> tuple[str, Optional[str]]:
908
927
bids_leftovers = []
909
928
for s in split [1 :]:
910
929
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 ]
915
937
# sanitize values, which must not have _ and - is undesirable ATM as well
916
938
# TODO: BIDSv2.0 -- allows "-" so replace with it instead
917
939
value = (
0 commit comments