From 09762d6cea5c80dbe77a655aa4f3becde1d98817 Mon Sep 17 00:00:00 2001 From: Paul McCarthy <pauld.mccarthy@gmail.com> Date: Sat, 2 Apr 2016 13:54:05 +0100 Subject: [PATCH] Nifti1Opts stores pixdim-flip transformations (scaled voxels, forced to radiological orientation). This is to be used by ModelOpts soon. --- fsl/fsleyes/displaycontext/volumeopts.py | 165 +++++++++++++++-------- 1 file changed, 110 insertions(+), 55 deletions(-) diff --git a/fsl/fsleyes/displaycontext/volumeopts.py b/fsl/fsleyes/displaycontext/volumeopts.py index b9e00c026..489cea05a 100644 --- a/fsl/fsleyes/displaycontext/volumeopts.py +++ b/fsl/fsleyes/displaycontext/volumeopts.py @@ -9,6 +9,7 @@ .. _volumeopts-coordinate-systems: + --------------------------------------- An important note on coordinate systems --------------------------------------- @@ -72,7 +73,8 @@ to the centre of a voxel. import logging -import numpy as np +import numpy as np +import numpy.linalg as npla import props @@ -238,50 +240,79 @@ class Nifti1Opts(fsldisplay.DisplayOpts): image = self.overlay - voxToIdMat = np.eye(4) - voxToPixdimMat = np.diag(list(image.pixdim[:3]) + [1.0]) - voxToAffineMat = image.voxToWorldMat.T - voxToCustomMat = self.customXform - - idToVoxMat = transform.invert(voxToIdMat) - idToPixdimMat = transform.concat(idToVoxMat, voxToPixdimMat) - idToAffineMat = transform.concat(idToVoxMat, voxToAffineMat) - idToCustomMat = transform.concat(idToVoxMat, voxToCustomMat) - - pixdimToVoxMat = transform.invert(voxToPixdimMat) - pixdimToIdMat = transform.concat(pixdimToVoxMat, voxToIdMat) - pixdimToAffineMat = transform.concat(pixdimToVoxMat, voxToAffineMat) - pixdimToCustomMat = transform.concat(pixdimToVoxMat, voxToCustomMat) - - affineToVoxMat = image.worldToVoxMat.T - affineToIdMat = transform.concat(affineToVoxMat, voxToIdMat) - affineToPixdimMat = transform.concat(affineToVoxMat, voxToPixdimMat) - affineToCustomMat = transform.concat(affineToVoxMat, voxToCustomMat) - - customToVoxMat = transform.invert(voxToCustomMat) - customToIdMat = transform.concat(customToVoxMat, voxToIdMat) - customToPixdimMat = transform.concat(customToVoxMat, voxToPixdimMat) - customToAffineMat = transform.concat(customToVoxMat, voxToAffineMat) - - self.__xforms['id', 'id'] = np.eye(4) - self.__xforms['id', 'pixdim'] = idToPixdimMat - self.__xforms['id', 'affine'] = idToAffineMat - self.__xforms['id', 'custom'] = idToCustomMat - - self.__xforms['pixdim', 'pixdim'] = np.eye(4) - self.__xforms['pixdim', 'id'] = pixdimToIdMat - self.__xforms['pixdim', 'affine'] = pixdimToAffineMat - self.__xforms['pixdim', 'custom'] = pixdimToCustomMat + voxToIdMat = np.eye(4) + voxToPixdimMat = np.diag(list(image.pixdim[:3]) + [1.0]) + voxToPixFlipMat = np.array(voxToPixdimMat) + voxToAffineMat = image.voxToWorldMat.T + voxToCustomMat = self.customXform + + # pixdim-flip space differs from + # pixdim space only if the affine + # matrix has a positive determinant + if npla.det(voxToAffineMat) > 0: + x = image.shape[0] + flip = transform.scaleOffsetXform([-1, 1, 1], [x, 0, 0]) + voxToPixFlipMat = transform.concat(voxToPixFlipMat, flip) + + idToVoxMat = transform.invert(voxToIdMat) + idToPixdimMat = transform.concat(idToVoxMat, voxToPixdimMat) + idToPixFlipMat = transform.concat(idToVoxMat, voxToPixFlipMat) + idToAffineMat = transform.concat(idToVoxMat, voxToAffineMat) + idToCustomMat = transform.concat(idToVoxMat, voxToCustomMat) + + pixdimToVoxMat = transform.invert(voxToPixdimMat) + pixdimToIdMat = transform.concat(pixdimToVoxMat, voxToIdMat) + pixdimToPixFlipMat = transform.concat(pixdimToVoxMat, voxToPixFlipMat) + pixdimToAffineMat = transform.concat(pixdimToVoxMat, voxToAffineMat) + pixdimToCustomMat = transform.concat(pixdimToVoxMat, voxToCustomMat) + + pixFlipToVoxMat = transform.invert(voxToPixFlipMat) + pixFlipToIdMat = transform.concat(pixFlipToVoxMat, voxToIdMat) + pixFlipToPixdimMat = transform.concat(pixFlipToVoxMat, voxToPixdimMat) + pixFlipToAffineMat = transform.concat(pixFlipToVoxMat, voxToAffineMat) + pixFlipToCustomMat = transform.concat(pixFlipToVoxMat, voxToCustomMat) + + affineToVoxMat = image.worldToVoxMat.T + affineToIdMat = transform.concat(affineToVoxMat, voxToIdMat) + affineToPixdimMat = transform.concat(affineToVoxMat, voxToPixdimMat) + affineToPixFlipMat = transform.concat(affineToVoxMat, voxToPixFlipMat) + affineToCustomMat = transform.concat(affineToVoxMat, voxToCustomMat) + + customToVoxMat = transform.invert(voxToCustomMat) + customToIdMat = transform.concat(customToVoxMat, voxToIdMat) + customToPixdimMat = transform.concat(customToVoxMat, voxToPixdimMat) + customToPixFlipMat = transform.concat(customToVoxMat, voxToPixFlipMat) + customToAffineMat = transform.concat(customToVoxMat, voxToAffineMat) + + self.__xforms['id', 'id'] = np.eye(4) + self.__xforms['id', 'pixdim'] = idToPixdimMat + self.__xforms['id', 'pixdim-flip'] = idToPixFlipMat + self.__xforms['id', 'affine'] = idToAffineMat + self.__xforms['id', 'custom'] = idToCustomMat + + self.__xforms['pixdim', 'pixdim'] = np.eye(4) + self.__xforms['pixdim', 'id'] = pixdimToIdMat + self.__xforms['pixdim', 'pixdim-flip'] = pixdimToPixFlipMat + self.__xforms['pixdim', 'affine'] = pixdimToAffineMat + self.__xforms['pixdim', 'custom'] = pixdimToCustomMat + + self.__xforms['pixdim-flip', 'pixdim-flip'] = np.eye(4) + self.__xforms['pixdim-flip', 'id'] = pixFlipToIdMat + self.__xforms['pixdim-flip', 'pixdim'] = pixFlipToPixdimMat + self.__xforms['pixdim-flip', 'affine'] = pixFlipToAffineMat + self.__xforms['pixdim-flip', 'custom'] = pixFlipToCustomMat - self.__xforms['affine', 'affine'] = np.eye(4) - self.__xforms['affine', 'id'] = affineToIdMat - self.__xforms['affine', 'pixdim'] = affineToPixdimMat - self.__xforms['affine', 'custom'] = affineToCustomMat + self.__xforms['affine', 'affine'] = np.eye(4) + self.__xforms['affine', 'id'] = affineToIdMat + self.__xforms['affine', 'pixdim'] = affineToPixdimMat + self.__xforms['affine', 'pixdim-flip'] = affineToPixFlipMat + self.__xforms['affine', 'custom'] = affineToCustomMat - self.__xforms['custom', 'custom'] = np.eye(4) - self.__xforms['custom', 'id'] = customToIdMat - self.__xforms['custom', 'pixdim'] = customToPixdimMat - self.__xforms['custom', 'affine'] = customToAffineMat + self.__xforms['custom', 'custom'] = np.eye(4) + self.__xforms['custom', 'id'] = customToIdMat + self.__xforms['custom', 'pixdim'] = customToPixdimMat + self.__xforms['custom', 'pixdim-flip'] = customToPixFlipMat + self.__xforms['custom', 'affine'] = customToAffineMat def getTransform(self, from_, to, xform=None): @@ -290,29 +321,51 @@ class Nifti1Opts(fsldisplay.DisplayOpts): are: - =========== ====================================================== - ``id`` Voxel coordinates + =============== ====================================================== + ``id`` Voxel coordinates - ``voxel`` Equivalent to ``id``. + ``voxel`` Equivalent to ``id``. - ``pixdim`` Voxel coordinates, scaled by voxel dimensions + ``pixdim`` Voxel coordinates, scaled by voxel dimensions + + ``pixdim-flip`` Voxel coordinates, scaled by voxel dimensions, and + with the X axis flipped if the affine matrix has + a positivie determinant. If the affine matrix does + not have a positive determinant, this is equivalent to + ``pixdim``. + + ``pixflip`` Equivalent to ``pixdim-flip``. - ``affine`` World coordinates, as defined by the NIFTI1 - ``qform``/``sform``. See :attr:`.Image.voxToWorldMat`. + ``affine`` World coordinates, as defined by the NIFTI1 + ``qform``/``sform``. See :attr:`.Image.voxToWorldMat`. - ``world`` Equivalent to ``affine``. + ``world`` Equivalent to ``affine``. - ``custom`` Coordinates in the space defined by the custom - transformation matrix, as specified via the - :attr:`customXform` property. + ``custom`` Coordinates in the space defined by the custom + transformation matrix, as specified via the + :attr:`customXform` property. - ``display`` Equivalent to the current value of :attr:`transform`. - =========== ====================================================== + ``display`` Equivalent to the current value of :attr:`transform`. + =============== ====================================================== If the ``xform`` parameter is provided, and one of ``from_`` or ``to`` is ``display``, the value of ``xform`` is used instead of the current value of :attr:`transform`. + + + .. note:: While the ``pixdim-flip`` space is not a display option for + ``Nifti1``, the transformations for this space are made + available theough this method as it is the coordinate system + used internally by many of the FSL tools. Most importantly, + this is the coordinate system used in the VTK sub-cortical + segmentation model files output by FIRST, and is hence used + by the :class:`.ModelOpts` class to transform VTK model + coordinates. + + http://fsl.fmrib.ox.ac.uk/fsl/fslwiki/FLIRT/FAQ#What_is_the_\ + format_of_the_matrix_used_by_FLIRT.2C_and_how_does_it_\ + relate_to_the_transformation_parameters.3F """ if xform is None: @@ -321,10 +374,12 @@ class Nifti1Opts(fsldisplay.DisplayOpts): if from_ == 'display': from_ = xform elif from_ == 'world': from_ = 'affine' elif from_ == 'voxel': from_ = 'id' + elif from_ == 'pixflip': from_ = 'pixdim-flip' if to == 'display': to = xform elif to == 'world': to = 'affine' elif to == 'voxel': to = 'id' + elif to == 'pixflip': to = 'pixdim-flip' return self.__xforms[from_, to] -- GitLab