@@ -82,7 +82,7 @@ def conversion_info(subject, outdir, info, filegroup, ses):
82
82
83
83
84
84
def prep_conversion (sid , dicoms , outdir , heuristic , converter , anon_sid ,
85
- anon_outdir , with_prov , ses , bids_options , seqinfo ,
85
+ anon_outdir , with_prov , ses , bids_options , seqinfo ,
86
86
min_meta , overwrite , dcmconfig , grouping ):
87
87
if dicoms :
88
88
lgr .info ("Processing %d dicoms" , len (dicoms ))
@@ -233,6 +233,157 @@ def prep_conversion(sid, dicoms, outdir, heuristic, converter, anon_sid,
233
233
getattr (heuristic , 'DEFAULT_FIELDS' , {}))
234
234
235
235
236
+ def update_complex_name (metadata , filename , suffix ):
237
+ """
238
+ Insert `_rec-<magnitude|phase>` entity into filename if data are from a
239
+ sequence with magnitude/phase part.
240
+
241
+ Parameters
242
+ ----------
243
+ metadata : dict
244
+ Scan metadata dictionary from BIDS sidecar file.
245
+ filename : str
246
+ Incoming filename
247
+ suffix : str
248
+ An index used for cases where a single scan produces multiple files,
249
+ but the differences between those files are unknown.
250
+
251
+ Returns
252
+ -------
253
+ filename : str
254
+ Updated filename with rec entity added in appropriate position.
255
+ """
256
+ # Some scans separate magnitude/phase differently
257
+ unsupported_types = ['_bold' , '_phase' ,
258
+ '_magnitude' , '_magnitude1' , '_magnitude2' ,
259
+ '_phasediff' , '_phase1' , '_phase2' ]
260
+ if any (ut in filename for ut in unsupported_types ):
261
+ return filename
262
+
263
+ # Check to see if it is magnitude or phase part:
264
+ if 'M' in metadata .get ('ImageType' ):
265
+ mag_or_phase = 'magnitude'
266
+ elif 'P' in metadata .get ('ImageType' ):
267
+ mag_or_phase = 'phase'
268
+ else :
269
+ mag_or_phase = suffix
270
+
271
+ # Determine scan suffix
272
+ filetype = '_' + filename .split ('_' )[- 1 ]
273
+
274
+ # Insert rec label
275
+ if not ('_rec-%s' % mag_or_phase ) in filename :
276
+ # If "_rec-" is specified, prepend the 'mag_or_phase' value.
277
+ if '_rec-' in filename :
278
+ raise BIDSError (
279
+ "Reconstruction label for images will be automatically set, "
280
+ "remove from heuristic"
281
+ )
282
+
283
+ # Insert it **before** the following string(s), whichever appears first.
284
+ for label in ['_dir' , '_run' , '_mod' , '_echo' , '_recording' , '_proc' , '_space' , filetype ]:
285
+ if (label == filetype ) or (label in filename ):
286
+ filename = filename .replace (
287
+ label , "_rec-%s%s" % (mag_or_phase , label )
288
+ )
289
+ break
290
+
291
+ return filename
292
+
293
+
294
+ def update_multiecho_name (metadata , filename , echo_times ):
295
+ """
296
+ Insert `_echo-<num>` entity into filename if data are from a multi-echo
297
+ sequence.
298
+
299
+ Parameters
300
+ ----------
301
+ metadata : dict
302
+ Scan metadata dictionary from BIDS sidecar file.
303
+ filename : str
304
+ Incoming filename
305
+ echo_times : list
306
+ List of all echo times from scan. Used to determine the echo *number*
307
+ (i.e., index) if field is missing from metadata.
308
+
309
+ Returns
310
+ -------
311
+ filename : str
312
+ Updated filename with echo entity added, if appropriate.
313
+ """
314
+ # Field maps separate echoes differently
315
+ unsupported_types = [
316
+ '_magnitude' , '_magnitude1' , '_magnitude2' ,
317
+ '_phasediff' , '_phase1' , '_phase2' , '_fieldmap'
318
+ ]
319
+ if any (ut in filename for ut in unsupported_types ):
320
+ return filename
321
+
322
+ # Get the EchoNumber from json file info. If not present, use EchoTime
323
+ if 'EchoNumber' in metadata .keys ():
324
+ echo_number = metadata ['EchoNumber' ]
325
+ else :
326
+ echo_number = echo_times .index (metadata ['EchoTime' ]) + 1
327
+
328
+ # Determine scan suffix
329
+ filetype = '_' + filename .split ('_' )[- 1 ]
330
+
331
+ # Insert it **before** the following string(s), whichever appears first.
332
+ for label in ['_recording' , '_proc' , '_space' , filetype ]:
333
+ if (label == filetype ) or (label in filename ):
334
+ filename = filename .replace (
335
+ label , "_echo-%s%s" % (echo_number , label )
336
+ )
337
+ break
338
+
339
+ return filename
340
+
341
+
342
+ def update_uncombined_name (metadata , filename , channel_names ):
343
+ """
344
+ Insert `_ch-<num>` entity into filename if data are from a sequence
345
+ with "save uncombined".
346
+
347
+ Parameters
348
+ ----------
349
+ metadata : dict
350
+ Scan metadata dictionary from BIDS sidecar file.
351
+ filename : str
352
+ Incoming filename
353
+ channel_names : list
354
+ List of all channel names from scan. Used to determine the channel
355
+ *number* (i.e., index) if field is missing from metadata.
356
+
357
+ Returns
358
+ -------
359
+ filename : str
360
+ Updated filename with ch entity added, if appropriate.
361
+ """
362
+ # In case any scan types separate channels differently
363
+ unsupported_types = []
364
+ if any (ut in filename for ut in unsupported_types ):
365
+ return filename
366
+
367
+ # Determine the channel number
368
+ channel_number = '' .join ([c for c in metadata ['CoilString' ] if c .isdigit ()])
369
+ if not channel_number :
370
+ channel_number = channel_names .index (metadata ['CoilString' ]) + 1
371
+ channel_number = str (channel_number ).zfill (2 )
372
+
373
+ # Determine scan suffix
374
+ filetype = '_' + filename .split ('_' )[- 1 ]
375
+
376
+ # Insert it **before** the following string(s), whichever appears first.
377
+ # Choosing to put channel near the end since it's not in the specification yet.
378
+ for label in ['_recording' , '_proc' , '_space' , filetype ]:
379
+ if (label == filetype ) or (label in filename ):
380
+ filename = filename .replace (
381
+ label , "_ch-%s%s" % (channel_number , label )
382
+ )
383
+ break
384
+ return filename
385
+
386
+
236
387
def convert (items , converter , scaninfo_suffix , custom_callable , with_prov ,
237
388
bids_options , outdir , min_meta , overwrite , symlink = True , prov_file = None ,
238
389
dcmconfig = None ):
@@ -534,14 +685,17 @@ def save_converted_files(res, item_dicoms, bids_options, outtype, prefix, outnam
534
685
# series. To do that, the most straightforward way is to read the
535
686
# echo times for all bids_files and see if they are all the same or not.
536
687
537
- # Check for varying echo times
538
- echo_times = sorted (list (set (
539
- b .get ('EchoTime' , nan )
540
- for b in bids_metas
541
- if b
542
- )))
543
-
544
- is_multiecho = len (echo_times ) > 1
688
+ # Collect some metadata across all images
689
+ echo_times , channel_names , image_types = set (), set (), set ()
690
+ for metadata in bids_metas :
691
+ if not metadata :
692
+ continue
693
+ echo_times .add (metadata .get ('EchoTime' , nan ))
694
+ channel_names .add (metadata .get ('CoilString' , nan ))
695
+ image_types .update (metadata .get ('ImageType' , [nan ]))
696
+ is_multiecho = len (set (filter (bool , echo_times ))) > 1 # Check for varying echo times
697
+ is_uncombined = len (set (filter (bool , channel_names ))) > 1 # Check for uncombined data
698
+ is_complex = 'M' in image_types and 'P' in image_types # Determine if data are complex (magnitude + phase)
545
699
546
700
### Loop through the bids_files, set the output name and save files
547
701
for fl , suffix , bids_file , bids_meta in zip (res_files , suffixes , bids_files , bids_metas ):
@@ -552,65 +706,22 @@ def save_converted_files(res, item_dicoms, bids_options, outtype, prefix, outnam
552
706
# and we don't want to modify it for all the bids_files):
553
707
this_prefix_basename = prefix_basename
554
708
555
- # _sbref sequences reconstructing magnitude and phase generate
556
- # two NIfTI files IN THE SAME SERIES, so we cannot just add
557
- # the suffix, if we want to be bids compliant:
558
- if bids_meta and this_prefix_basename .endswith ('_sbref' ) \
559
- and len (suffixes ) > len (echo_times ):
560
- if len (suffixes ) != len (echo_times )* 2 :
561
- lgr .warning (
562
- "Got %d suffixes for %d echo times, which isn't "
563
- "multiple of two as if it was magnitude + phase pairs" ,
564
- len (suffixes ), len (echo_times )
709
+ # Update name for certain criteria
710
+ if bids_file :
711
+ if is_multiecho :
712
+ this_prefix_basename = update_multiecho_name (
713
+ bids_meta , this_prefix_basename , echo_times
714
+ )
715
+
716
+ if is_complex :
717
+ this_prefix_basename = update_complex_name (
718
+ bids_meta , this_prefix_basename , suffix
719
+ )
720
+
721
+ if is_uncombined :
722
+ this_prefix_basename = update_uncombined_name (
723
+ bids_meta , this_prefix_basename , channel_names
565
724
)
566
- # Check to see if it is magnitude or phase reconstruction:
567
- if 'M' in bids_meta .get ('ImageType' ):
568
- mag_or_phase = 'magnitude'
569
- elif 'P' in bids_meta .get ('ImageType' ):
570
- mag_or_phase = 'phase'
571
- else :
572
- mag_or_phase = suffix
573
-
574
- # Insert reconstruction label
575
- if not ("_rec-%s" % mag_or_phase ) in this_prefix_basename :
576
-
577
- # If "_rec-" is specified, prepend the 'mag_or_phase' value.
578
- if ('_rec-' in this_prefix_basename ):
579
- raise BIDSError (
580
- "Reconstruction label for multi-echo single-band"
581
- " reference images will be automatically set, remove"
582
- " from heuristic"
583
- )
584
-
585
- # If not, insert "_rec-" + 'mag_or_phase' into the prefix_basename
586
- # **before** "_run", "_echo" or "_sbref", whichever appears first:
587
- for label in ['_run' , '_echo' , '_sbref' ]:
588
- if (label in this_prefix_basename ):
589
- this_prefix_basename = this_prefix_basename .replace (
590
- label , "_rec-%s%s" % (mag_or_phase , label )
591
- )
592
- break
593
-
594
- # Now check if this run is multi-echo
595
- # (Note: it can be _sbref and multiecho, so don't use "elif"):
596
- # For multi-echo sequences, we have to specify the echo number in
597
- # the file name:
598
- if bids_meta and is_multiecho :
599
- # Get the EchoNumber from json file info. If not present, use EchoTime
600
- if 'EchoNumber' in bids_meta :
601
- echo_number = bids_meta ['EchoNumber' ]
602
- else :
603
- echo_number = echo_times .index (bids_meta ['EchoTime' ]) + 1
604
-
605
- supported_multiecho = ['_bold' , '_phase' , '_epi' , '_sbref' , '_T1w' , '_PDT2' ]
606
- # Now, decide where to insert it.
607
- # Insert it **before** the following string(s), whichever appears first.
608
- for imgtype in supported_multiecho :
609
- if (imgtype in this_prefix_basename ):
610
- this_prefix_basename = this_prefix_basename .replace (
611
- imgtype , "_echo-%d%s" % (echo_number , imgtype )
612
- )
613
- break
614
725
615
726
# Fallback option:
616
727
# If we have failed to modify this_prefix_basename, because it didn't fall
0 commit comments