@@ -317,6 +317,60 @@ def _run_interface(self, runtime):
317
317
return runtime
318
318
319
319
320
+ class _TOPUPCoeffReorientInputSpec (BaseInterfaceInputSpec ):
321
+ in_coeff = InputMultiObject (
322
+ File (exist = True ), mandatory = True , desc = "input coefficients file(s) from TOPUP"
323
+ )
324
+ fmap_ref = File (exists = True , mandatory = True , desc = "the fieldmap reference" )
325
+
326
+
327
+ class _TOPUPCoeffReorientOutputSpec (TraitedSpec ):
328
+ out_coeff = OutputMultiObject (File (exists = True ), desc = "patched coefficients" )
329
+
330
+
331
+ class TOPUPCoeffReorient (SimpleInterface ):
332
+ """
333
+ Revise the orientation of TOPUP-generated B-Spline coefficients.
334
+
335
+ TOUP-generated "fieldcoeff" files are just B-Spline fields, where the shape
336
+ of the field is fixated to be a decimated grid of the original image by an
337
+ integer factor and added 3 pixels on each dimension.
338
+ This is one root reason why TOPUP errors (FSL 6) or segfaults (FSL5), when the
339
+ input image has odd number of voxels along one or more directions.
340
+
341
+ These "fieldcoeff" are fixated to be zero-centered, and have "plumb" orientation
342
+ (as in, aligned with cardinal/imaging axes).
343
+ The q-form of these NIfTI files is always diagonal, with the decimation factors
344
+ set on the diagonal (and hence, the voxel zooms).
345
+ The origin of the q-form is set to the reference image's shape.
346
+
347
+ This interface modifies these coefficient files to be fully-fledged NIfTI images
348
+ aligned with the reference image.
349
+ Therefore, the s-form header of the coefficients file is updated to match that
350
+ of the reference file.
351
+ The s-form header is used because the imaging axes may be oblique.
352
+
353
+ The q-form retains the original header and is marked with code 0.
354
+
355
+ """
356
+
357
+ input_spec = _TOPUPCoeffReorientInputSpec
358
+ output_spec = _TOPUPCoeffReorientOutputSpec
359
+
360
+ def _run_interface (self , runtime ):
361
+ self ._results ["out_coeff" ] = [
362
+ str (
363
+ _fix_topup_fieldcoeff (
364
+ in_coeff ,
365
+ self .inputs .fmap_ref ,
366
+ fname_presuffix (in_coeff , suffix = "_fixed" , newpath = runtime .cwd ),
367
+ )
368
+ )
369
+ for in_coeff in self .inputs .in_coeff
370
+ ]
371
+ return runtime
372
+
373
+
320
374
def bspline_grid (img , control_zooms_mm = DEFAULT_ZOOMS_MM ):
321
375
"""Create a :obj:`~nibabel.nifti1.Nifti1Image` embedding the location of control points."""
322
376
if isinstance (img , (str , Path )):
@@ -437,3 +491,36 @@ def _move_coeff(in_coeff, fmap_ref, transform):
437
491
img .__class__ (img .dataobj , newaff , img .header ).to_filename (out [- 1 ])
438
492
439
493
return out
494
+
495
+
496
+ def _fix_topup_fieldcoeff (in_coeff , fmap_ref , out_file = None ):
497
+ """Read in a coefficients file generated by TOPUP and fix x-form headers."""
498
+ from pathlib import Path
499
+ import numpy as np
500
+ import nibabel as nb
501
+
502
+ if out_file is None :
503
+ out_file = Path ("coefficients.nii.gz" ).absolute ()
504
+
505
+ coeffnii = nb .load (in_coeff )
506
+ refnii = nb .load (fmap_ref )
507
+
508
+ coeff_shape = np .array (coeffnii .shape [:3 ])
509
+ ref_shape = np .array (refnii .shape [:3 ])
510
+ factors = coeffnii .header .get_zooms ()[:3 ]
511
+ if not np .all (coeff_shape == ref_shape // factors + 3 ):
512
+ raise ValueError (
513
+ f"Shape of coefficients file { coeff_shape } does not meet the "
514
+ f"expectation given the reference's shape { ref_shape } ."
515
+ )
516
+ newaff = np .eye (4 )
517
+ newaff [:3 , :3 ] = refnii .affine [:3 , :3 ] * factors
518
+ c_ref = nb .affines .apply_affine (refnii .affine , 0.5 * (ref_shape - 1 ))
519
+ c_coeff = nb .affines .apply_affine (newaff , 0.5 * (coeff_shape - 1 ))
520
+ newaff [:3 , 3 ] = c_ref - c_coeff
521
+ header = coeffnii .header .copy ()
522
+ coeffnii .header .set_qform (coeffnii .header .get_qform (coded = False ), code = 0 )
523
+ coeffnii .header .set_sform (newaff , code = 1 )
524
+
525
+ coeffnii .__class__ (coeffnii .dataobj , newaff , header ).to_filename (out_file )
526
+ return out_file
0 commit comments