114
114
import logging
115
115
lgr = logging .getLogger ('heudiconv' )
116
116
117
+ # Terminology to hamornise and use to name variables etc
118
+ # experiment
119
+ # subject
120
+ # [session]
121
+ # exam (AKA scanning session) - currently seqinfo, unless brought together from multiple
122
+ # series (AKA protocol?)
123
+ # - series_spec - deduced from fields the spec (literal value)
124
+ # - series_info - the dictionary with fields parsed from series_spec
125
+
126
+ # Which fields in seqinfo (in this order) to check for the ReproIn spec
127
+ series_spec_fields = ('protocol_name' , 'series_description' )
128
+
117
129
# dictionary from accession-number to runs that need to be marked as bad
118
130
# NOTE: even if filename has number that is 0-padded, internally no padding
119
131
# is done
227
239
('_test' , '' ),
228
240
],
229
241
}
230
- keys2replace = ['protocol_name' , 'series_description' ]
231
242
232
243
# list containing StudyInstanceUID to skip -- hopefully doesn't happen too often
233
244
dicoms2skip = [
@@ -341,13 +352,13 @@ def fix_canceled_runs(seqinfo, accession2run=fix_accession2run):
341
352
if re .match (badruns_pattern , s .series_id ):
342
353
lgr .info ('Fixing bad run {0}' .format (s .series_id ))
343
354
fixedkwargs = dict ()
344
- for key in keys2replace :
355
+ for key in series_spec_fields :
345
356
fixedkwargs [key ] = 'cancelme_' + getattr (s , key )
346
357
seqinfo [i ] = s ._replace (** fixedkwargs )
347
358
return seqinfo
348
359
349
360
350
- def fix_dbic_protocol (seqinfo , keys = keys2replace , subsdict = protocols2fix ):
361
+ def fix_dbic_protocol (seqinfo , keys = series_spec_fields , subsdict = protocols2fix ):
351
362
"""Ad-hoc fixup for existing protocols
352
363
"""
353
364
study_hash = get_study_hash (seqinfo )
@@ -454,33 +465,36 @@ def infotodict(seqinfo):
454
465
else :
455
466
dcm_image_iod_spec = image_type_seqtype = None
456
467
457
- protocol_name_tuned = s .protocol_name
458
- if not protocol_name_tuned :
459
- protocol_name_tuned = s .series_description
460
- if not protocol_name_tuned :
468
+ series_info = {} # For please lintian and its friends
469
+ for sfield in series_spec_fields :
470
+ svalue = getattr (s , sfield )
471
+ series_info = parse_series_spec (svalue )
472
+ if series_info : # looks like a valid spec - we are done
473
+ series_spec = svalue
474
+ break
475
+ else :
476
+ lgr .debug (
477
+ "Failed to parse reproin spec in .%s=%r" ,
478
+ sfield , svalue )
479
+
480
+ if not series_info :
481
+ series_spec = None # we cannot know better
461
482
lgr .warning (
462
483
"Could not determine the series name by looking at "
463
- "protocol_name and series_description - both were empty" )
464
- # Few common replacements
465
- if protocol_name_tuned in {'AAHead_Scout' }:
466
- protocol_name_tuned = 'anat-scout'
467
-
468
- regd = parse_dbic_protocol_name (protocol_name_tuned )
469
-
470
- if dcm_image_iod_spec and dcm_image_iod_spec .startswith ('MIP' ):
471
- regd ['acq' ] = regd .get ('acq' , '' ) + sanitize_str (dcm_image_iod_spec )
472
-
473
- if not regd :
484
+ "%s fields" , ', ' .join (series_spec_fields ))
474
485
skipped_unknown .append (s .series_id )
475
486
continue
476
487
477
- seqtype = regd .pop ('seqtype' )
478
- seqtype_label = regd .pop ('seqtype_label' , None )
488
+ if dcm_image_iod_spec and dcm_image_iod_spec .startswith ('MIP' ):
489
+ series_info ['acq' ] = series_info .get ('acq' , '' ) + sanitize_str (dcm_image_iod_spec )
490
+
491
+ seqtype = series_info .pop ('seqtype' )
492
+ seqtype_label = series_info .pop ('seqtype_label' , None )
479
493
480
494
if image_type_seqtype and seqtype != image_type_seqtype :
481
495
lgr .warning (
482
496
"Deduced seqtype to be %s from DICOM, but got %s out of %s" ,
483
- image_type_seqtype , seqtype , protocol_name_tuned )
497
+ image_type_seqtype , seqtype , series_spec )
484
498
485
499
# if s.is_derived:
486
500
# # Let's for now stash those close to original images
@@ -497,7 +511,7 @@ def infotodict(seqinfo):
497
511
498
512
# analyze s.protocol_name (series_id is based on it) for full name mapping etc
499
513
if seqtype == 'func' and not seqtype_label :
500
- if '_pace_' in protocol_name_tuned :
514
+ if '_pace_' in series_spec :
501
515
seqtype_label = 'pace' # or should it be part of seq-
502
516
else :
503
517
# assume bold by default
@@ -516,7 +530,7 @@ def infotodict(seqinfo):
516
530
if seqtype == 'dwi' and not seqtype_label :
517
531
seqtype_label = 'dwi'
518
532
519
- run = regd .get ('run' )
533
+ run = series_info .get ('run' )
520
534
if run is not None :
521
535
# so we have an indicator for a run
522
536
if run == '+' :
@@ -563,16 +577,16 @@ def infotodict(seqinfo):
563
577
# if s.is_motion_corrected:
564
578
# assert s.is_derived, "Motion corrected images must be 'derived'"
565
579
566
- if s .is_motion_corrected and 'rec-' in regd .get ('bids' , '' ):
580
+ if s .is_motion_corrected and 'rec-' in series_info .get ('bids' , '' ):
567
581
raise NotImplementedError ("want to add _acq-moco but there is _acq- already" )
568
582
569
583
suffix_parts = [
570
- None if not regd .get ('task' ) else "task-%s" % regd ['task' ],
571
- None if not regd .get ('acq' ) else "acq-%s" % regd ['acq' ],
584
+ None if not series_info .get ('task' ) else "task-%s" % series_info ['task' ],
585
+ None if not series_info .get ('acq' ) else "acq-%s" % series_info ['acq' ],
572
586
# But we want to add an indicator in case it was motion corrected
573
587
# in the magnet. ref sample /2017/01/03/qa
574
588
None if not s .is_motion_corrected else 'rec-moco' ,
575
- regd .get ('bids' ),
589
+ series_info .get ('bids' ),
576
590
run_label ,
577
591
seqtype_label ,
578
592
]
@@ -680,7 +694,7 @@ def infotoids(seqinfos, outdir):
680
694
681
695
# So -- use `outdir` and locator etc to see if for a given locator/subject
682
696
# and possible ses+ in the sequence names, so we would provide a sequence
683
- # So might need to go through parse_dbic_protocol_name (s.protocol_name)
697
+ # So might need to go through parse_series_spec (s.protocol_name)
684
698
# to figure out presence of sessions.
685
699
ses_markers = []
686
700
@@ -691,7 +705,7 @@ def infotoids(seqinfos, outdir):
691
705
for s in seqinfos :
692
706
if s .is_derived :
693
707
continue
694
- session_ = parse_dbic_protocol_name (s .protocol_name ).get ('session' , None )
708
+ session_ = parse_series_spec (s .protocol_name ).get ('session' , None )
695
709
if session_ and '{' in session_ :
696
710
# there was a marker for something we could provide from our seqinfo
697
711
# e.g. {date}
@@ -761,22 +775,23 @@ def sanitize_str(value):
761
775
return _delete_chars (value , '#!@$%^&.,:;_-' )
762
776
763
777
764
- def parse_dbic_protocol_name ( protocol_name ):
778
+ def parse_series_spec ( series_spec ):
765
779
"""Parse protocol name according to our convention with minimal set of fixups
766
780
"""
767
781
# Since Yarik didn't know better place to put it in, but could migrate outside
768
- # at some point
769
- protocol_name = protocol_name .replace ("anat_T1w" , "anat-T1w" )
770
- protocol_name = protocol_name .replace ("hardi_64" , "dwi_acq-hardi64" )
771
-
782
+ # at some point. TODO
783
+ series_spec = series_spec .replace ("anat_T1w" , "anat-T1w" )
784
+ series_spec = series_spec .replace ("hardi_64" , "dwi_acq-hardi64" )
785
+ series_spec = series_spec .replace ("AAHead_Scout" , "anat-scout" )
786
+
772
787
# Parse the name according to our convention
773
788
# https://docs.google.com/document/d/1R54cgOe481oygYVZxI7NHrifDyFUZAjOBwCTu7M7y48/edit?usp=sharing
774
789
# Remove possible suffix we don't care about after __
775
- protocol_name = protocol_name .split ('__' , 1 )[0 ]
790
+ series_spec = series_spec .split ('__' , 1 )[0 ]
776
791
777
792
bids = None # we don't know yet for sure
778
793
# We need to figure out if it is a valid bids
779
- split = protocol_name .split ('_' )
794
+ split = series_spec .split ('_' )
780
795
prefix = split [0 ]
781
796
782
797
# Fixups
@@ -956,8 +971,8 @@ def test_fixupsubjectid():
956
971
assert fixup_subjectid ("SID30" ) == "sid000030"
957
972
958
973
959
- def test_parse_dbic_protocol_name ():
960
- pdpn = parse_dbic_protocol_name
974
+ def test_parse_series_spec ():
975
+ pdpn = parse_series_spec
961
976
962
977
assert pdpn ("nondbic_func-bold" ) == {}
963
978
assert pdpn ("cancelme_func-bold" ) == {}
0 commit comments