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
113122 __dup0<number> suffix.
114123
115124Although 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
128137
129138from __future__ import annotations
130139
140+ from collections import defaultdict
131141from collections .abc import Iterable
132142from glob import glob
133143import 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