Commit 6a9ad94b authored by Paul McCarthy's avatar Paul McCarthy 🚵
Browse files

Merge branch 'rf/scaledvoxel' into 'master'

BF: Use 'pixdim' affine for scaled voxel display space, not 'pixdim-flip', as the latter will potentially L/R flip some images

See merge request fsl/fsleyes/fsleyes!289
parents 4aa0432b bd2f311a
......@@ -9,6 +9,18 @@ This document contains the ``fsleyes`` release history in reverse
chronological order.
1.2.1 (Under development)
-------------------------
Fixed
^^^^^
* The **Display space** |right_arrow| *Scaled voxel coordinates* setting no
longer applies a L/R flip for images with neurological data storage order
(!289).
1.2.0 (Monday 13th September 2021)
----------------------------------
......
......@@ -185,7 +185,7 @@ class DisplayContext(props.SyncableHasProperties):
system, with origin set to the centre of voxel ``(0, 0, 0)``, and
voxels scaled by image pixdims. This is accomplished by setting the
:attr:`.NiftiOpts.transform` property for every ``Nifti`` overlay to
``pixdim-flip``.
``pixdim``.
3. **Reference image** space
......@@ -533,7 +533,10 @@ class DisplayContext(props.SyncableHasProperties):
# (reference image), we decide based on the ref
# image.
elif space == 'scaledVoxel':
space = self.getSelectedOverlay()
space = self.getSelectedOverlay()
srcSpace = 'pixdim'
else:
srcSpace = 'pixdim-flip'
# Use the FSL / FLIRT convention - if the affine
# determinant is negative, assume neurological
......@@ -542,7 +545,7 @@ class DisplayContext(props.SyncableHasProperties):
ref = self.getOpts(space).referenceImage
if ref is not None:
opts = self.getOpts(space)
xform = opts.getTransform('pixdim-flip', 'display')
xform = opts.getTransform(srcSpace, 'display')
return npla.det(xform) > 0
# no nifti overlays loaded
......@@ -901,7 +904,7 @@ class DisplayContext(props.SyncableHasProperties):
# listener on the bounds property.
with props.skip(opts, 'bounds', self.__name, ignoreInvalid=True):
if space == 'world': opts.transform = 'affine'
elif space == 'scaledVoxel': opts.transform = 'pixdim-flip'
elif space == 'scaledVoxel': opts.transform = 'pixdim'
elif image is space: opts.transform = 'pixdim-flip'
else: opts.transform = 'reference'
......@@ -1168,7 +1171,7 @@ class DisplayContext(props.SyncableHasProperties):
if self.displaySpace == 'scaledVoxel':
ref = self.getSelectedOverlay()
srcSpace = 'pixdim-flip'
srcSpace = 'pixdim'
else:
ref = self.displaySpace
srcSpace = 'display'
......
......@@ -36,7 +36,8 @@ in one of several ways:
image appears to be stored in neurological
order, the X (left-right) axis is
inverted. The origin is fixed at the centre of
voxel ``(0, 0, 0)``.
voxel ``(0, 0, 0)`` (or ``(X-1, 0, 0)`` for
inverted images).
**world** (a.k.a. ``affine``) The image data voxel
coordinates are transformed by the
......@@ -445,7 +446,7 @@ class NiftiOpts(fsldisplay.DisplayOpts):
if ds == 'world':
voxToRefMat = voxToWorldMat
elif ds == 'scaledVoxel':
voxToRefMat = voxToPixFlipMat
voxToRefMat = voxToPixdimMat
elif ds is self.overlay:
voxToRefMat = voxToPixFlipMat
else:
......
......@@ -672,6 +672,40 @@ def fliporient(filename):
return outfile
def swapdim(filename, d0, d1, d2):
indices = { 'x' : 0, '-x' : 0,
'y' : 1, '-y' : 1,
'z' : 2, '-z' : 2}
base = fslimage.removeExt(filename)
outfile = '{}_swapdim_{}_{}_{}'.format(base, d0, d1, d2)
img = fslimage.Image(filename)
s0, s1, s2 = [-1 if d.startswith('-') else 1 for d in (d0, d1, d2)]
d0, d1, d2 = [indices[d] for d in (d0, d1, d2)]
if len(img.shape) == 3:
order = (d0, d1, d2)
else:
order = (d0, d1, d2) + tuple(range(3, len(img.shape)))
data = img.data
if s0 < 0: data = np.flip(data, d0)
if s1 < 0: data = np.flip(data, d1)
if s2 < 0: data = np.flip(data, d2)
data = data.transpose(order)
aff = np.zeros((4, 4))
aff[0, d0] = s0
aff[1, d1] = s1
aff[2, d2] = s2
aff[3, 3] = 1
aff = affine.concat(aff, img.voxToWorldMat)
img = fslimage.Image(data, xform=aff)
img.save(outfile)
return outfile
def roi(fname, roi):
......
......@@ -7,7 +7,7 @@
import pytest
from fsleyes.tests import run_cli_tests, resampled, roi, rotate
from fsleyes.tests import run_cli_tests, resampled, roi, rotate, swapdim
pytestmark = pytest.mark.clitest
......@@ -24,6 +24,16 @@ tests = """
-ds 3d_rotated_20_20_20 3d {{rotate('3d', 20, 20, 20)}} -cm red-yellow -a 50
-ds world 3d {{rotate('3d', 20, 20, 20)}} -cm red-yellow -a 50
-ds scaledVoxel 3d {{rotate('3d', 20, 20, 20)}} -cm red-yellow -a 50
-ds 3d 3d {{swapdim('3d', '-x', 'y', 'z')}} -cm red-yellow -a 50
-ds 3d_swapdim_-x_y_z 3d {{swapdim('3d', '-x', 'y', 'z')}} -cm red-yellow -a 50
-ds scaledVoxel 3d {{swapdim('3d', '-x', 'y', 'z')}} -cm red-yellow -a 50
-ds world 3d {{swapdim('3d', '-x', 'y', 'z')}} -cm red-yellow -a 50
-ds 3d 3d {{resampled(swapdim('3d', '-x', 'y', 'z'), 0.5)}} -cm red-yellow -a 50
-ds 3d_swapdim_-x_y_z_resampled_0.5 3d {{resampled(swapdim('3d', '-x', 'y', 'z'), 0.5)}} -cm red-yellow -a 50
-ds scaledVoxel 3d {{resampled(swapdim('3d', '-x', 'y', 'z'), 0.5)}} -cm red-yellow -a 50
-ds world 3d {{resampled(swapdim('3d', '-x', 'y', 'z'), 0.5)}} -cm red-yellow -a 50
"""
......@@ -31,6 +41,7 @@ def test_displayspace():
extras = {
'roi' : roi,
'resampled' : resampled,
'rotate' : rotate
'rotate' : rotate,
'swapdim' : swapdim
}
run_cli_tests('test_displayspace', tests, extras=extras)
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment