From ef5ea65e9889520b3dc31451073e9fbc53fd4a7a Mon Sep 17 00:00:00 2001
From: Paul McCarthy <pauld.mccarthy@gmail.com>
Date: Thu, 23 Oct 2014 10:52:34 +0100
Subject: [PATCH] ImageDisplay object calculates/stores transformation matrices
 for moving between each of the three spaces (voxel, display, and world)

---
 fsl/data/image.py             | 17 ++++++++++-------
 fsl/fslview/displaycontext.py | 12 ++++++++++++
 fsl/fslview/strings.py        |  1 -
 fsl/utils/transform.py        |  8 ++++++--
 4 files changed, 28 insertions(+), 10 deletions(-)

diff --git a/fsl/data/image.py b/fsl/data/image.py
index 3e3e5595a..398fa221e 100644
--- a/fsl/data/image.py
+++ b/fsl/data/image.py
@@ -18,13 +18,14 @@ import numpy      as np
 import nibabel    as nib
 
 import props
-import fsl.data.imagefile  as imagefile
+import fsl.data.imagefile   as imagefile
+import fsl.utils.transform  as transform
 
 
 log = logging.getLogger(__name__)
 
-# Constants which represent the orientation of an axis,
-# in either voxel or world space.
+# Constants which represent the orientation
+# of an axis, in either voxel or world space.
 ORIENT_UNKNOWN = -1
 ORIENT_L2R     = 0
 ORIENT_R2L     = 1
@@ -41,10 +42,6 @@ NIFTI_XFORM_ALIGNED_ANAT = 2
 NIFTI_XFORM_TALAIRACH    = 3
 NIFTI_XFORM_MNI_152      = 4
 
-# My own code, used to indicate that the
-# image is being displayed in voxel space
-NIFTI_XFORM_VOXEL        = 5
-
 
 def _loadImageFile(filename):
     """Given the name of an image file, loads it using nibabel.
@@ -133,6 +130,10 @@ class Image(props.HasProperties):
                           for transforming voxel coordinates into real world
                           coordinates.
 
+    :ivar worldToVoxMat:  A 4*4 array specifying the affine transformation
+                          for transforming real world coordinates into voxel
+                          coordinates. 
+
     :ivar imageFile:      The name of the file that the image was loaded from.
     
     :ivar tempFile:       The name of the temporary file which was created (in
@@ -198,6 +199,7 @@ class Image(props.HasProperties):
         self.shape         = self.nibImage.get_shape()
         self.pixdim        = self.nibImage.get_header().get_zooms()
         self.voxToWorldMat = np.array(self.nibImage.get_affine())
+        self.worldToVoxMat = transform.invert(self.voxToWorldMat)
 
         if len(self.shape) < 3 or len(self.shape) > 4:
             raise RuntimeError('Only 3D or 4D images are supported')
@@ -301,6 +303,7 @@ class Image(props.HasProperties):
         """
         return self._attributes[name]
 
+    
     def delAttribute(self, name):
         """Delete and return the value of the attribute with the given name.
 
diff --git a/fsl/fslview/displaycontext.py b/fsl/fslview/displaycontext.py
index 401907b8f..c1ca8b63d 100644
--- a/fsl/fslview/displaycontext.py
+++ b/fsl/fslview/displaycontext.py
@@ -286,6 +286,9 @@ class ImageDisplay(props.HasProperties):
         self.setConstraint('worldResolution', 'minval', min(image.pixdim[:3]))
         self.worldResolution = min(image.pixdim[:3])
 
+        self.voxToWorldMat = image.voxToWorldMat.transpose()
+        self.worldToVoxMat = image.worldToVoxMat.transpose()
+
         # is this a 4D volume?
         if image.is4DImage():
             self.setConstraint('volume', 'maxval', image.shape[3] - 1)
@@ -320,9 +323,18 @@ class ImageDisplay(props.HasProperties):
         voxToDisplayMat = np.array(voxToDisplayMat, dtype=np.float32)
         displayToVoxMat = transform.invert(voxToDisplayMat)
 
+        # Transformation matrices for moving between the voxel
+        # coordinate system and the display coordinate system
         self.voxToDisplayMat = voxToDisplayMat.transpose()
         self.displayToVoxMat = displayToVoxMat.transpose()
 
+        # Matrices for moving between the display coordinate
+        # system, and the image world coordinate system
+        self.displayToWorldMat = transform.concat(self.displayToVoxMat,
+                                                  self.voxToWorldMat)
+        self.worldToDisplayMat = transform.invert(self.displayToWorldMat)
+        
+
         # for pixdim/identity transformations, we want the world
         # location (0, 0, 0) to map to voxel location (0, 0, 0)
         if self.transform in ('id', 'pixdim'):
diff --git a/fsl/fslview/strings.py b/fsl/fslview/strings.py
index 903e922c7..dc395e16d 100644
--- a/fsl/fslview/strings.py
+++ b/fsl/fslview/strings.py
@@ -94,7 +94,6 @@ imageAxisHighShortLabels = {
     fslimage.ORIENT_UNKNOWN : '?'}
 
 imageSpaceLabels = {
-    fslimage.NIFTI_XFORM_VOXEL        : 'Voxel',
     fslimage.NIFTI_XFORM_UNKNOWN      : 'Unknown',
     fslimage.NIFTI_XFORM_SCANNER_ANAT : 'Scanner anatomical',
     fslimage.NIFTI_XFORM_ALIGNED_ANAT : 'Aligned anatomical',
diff --git a/fsl/utils/transform.py b/fsl/utils/transform.py
index 8c08894ba..0c66d4020 100644
--- a/fsl/utils/transform.py
+++ b/fsl/utils/transform.py
@@ -1,7 +1,6 @@
 #!/usr/bin/env python
 #
-# transform.py - Functions for applying affine transformations to
-# coordinates.
+# transform.py - Functions for working with affine transformation matrices.
 #
 # Author: Paul McCarthy <pauldmccarthy@gmail.com>
 #
@@ -19,6 +18,11 @@ def invert(x):
     return linalg.inv(x)
 
 
+def concat(x1, x2):
+    """Combines the two matrices (returns the dot product)."""
+    return linalg.dot(x1, x2)
+
+
 def axisBounds(shape, xform, axis):
     """Returns the (lo, hi) bounds of the specified axis."""
     x, y, z = shape
-- 
GitLab