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
134144import logging
135145import os .path
136146import re
147+ from types import MappingProxyType
137148from typing import Any , Optional , TypeVar
138149
139150import 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