Commit 9715529b authored by Paul McCarthy's avatar Paul McCarthy 🚵
Browse files

Merge branch 'master' into 'master'

Support > 4D

See merge request !34
parents 54bd7571 4cf79e7a
Pipeline #951 failed with stages
in 1 minute and 38 seconds
......@@ -66,6 +66,9 @@ stages:
# - TWINE_PASSWORD: - Password to use when uploading to pypi
#
# - TWINE_REPOSITORY_URL: - Pypi repository to upload to
#
# - WXPYTHON_DEBIAN8_URL: - Url to a location containg binary wheels
# for wxPython/debian 8
###############################################################################
......@@ -230,12 +233,12 @@ variables:
# We need to install xvfb, and all of
# the wxpython dependencies.
- apt-get update -y
- apt-get install -y xvfb libgtk-3-0
- apt-get install -y xvfb libgtk-2-0
- apt-get install -y libnotify4 freeglut3 libsdl1.2debian
# Linux builds for wxPython are currently not
# on pypi, but are available at this url.
- pip install --only-binary wxpython -f https://wxpython.org/Phoenix/snapshot-builds/linux/gtk3/debian-8/ wxpython
- pip install --only-binary wxpython -f $WXPYTHON_DEBIAN8_URL wxpython
# All other deps can be installed as
# normal. scipy is required by nibabel,
......
......@@ -13,16 +13,12 @@ following sub-packages:
fsl.data
fsl.utils
fsl.scripts
fsl.version
.. note:: The ``fslpy`` version number (currently |version|) is set in a
single place - the :mod:`fsl.version` module.
.. note:: The ``fsl`` namespace is a ``pkgutil``-style *namespace package* -
it can be used across different projects - see
https://packaging.python.org/guides/packaging-namespace-packages/
for details.
"""
import fsl.version
__version__ = fsl.version.__version__
"""The current ``fslpy`` version number. This information is stored in the
:mod:`fsl.version` module.
"""
__path__ = __import__('pkgutil').extend_path(__path__, __name__)
......@@ -599,7 +599,7 @@ class Atlas(fslimage.Image):
# Even though all the FSL atlases
# are in MNI152 space, not all of
# their sform_codes are correctly set
self.nibImage.get_header().set_sform(
self.nibImage.header.set_sform(
None, code=constants.NIFTI_XFORM_MNI_152)
self.desc = atlasDesc
......
......@@ -6,7 +6,7 @@
# Author: Paul McCarthy <pauldmccarthy@gmail.com>
#
"""This module provides the :class:`Nifti` and :class:`Image` classes, for
representing 3D/4D NIFTI1 and NIFTI2 images. The ``nibabel`` package is used
representing NIFTI1 and NIFTI2 images. The ``nibabel`` package is used
for file I/O.
......@@ -38,6 +38,7 @@ import string
import logging
import six
import deprecation
import numpy as np
import nibabel as nib
......@@ -187,9 +188,6 @@ class Nifti(notifier.Notifier):
header = header
origShape, shape, pixdim = self.__determineShape(header)
if len(shape) < 3 or len(shape) > 4:
raise RuntimeError('Only 3D or 4D images are supported')
voxToWorldMat = self.__determineTransform(header)
worldToVoxMat = transform.invert(voxToWorldMat)
......@@ -451,7 +449,19 @@ class Nifti(notifier.Notifier):
return fileslice.canonical_slicers(sliceobj, self.__origShape)
# TODO: Remove this method, and use the shape attribute directly
@property
def ndims(self):
"""Returns the number of dimensions in this image. This number may not
match the number of dimensions specified in the NIFTI header, as
trailing dimensions of length 1 are ignored. But it is guaranteed to be
at least 3.
"""
return len(self.__shape)
@deprecation.deprecated(deprecated_in='1.1.0',
removed_in='1.2.0',
details='Use ndims instead')
def is4DImage(self):
"""Returns ``True`` if this image is 4D, ``False`` otherwise. """
return len(self.__shape) > 3 and self.__shape[3] > 1
......@@ -626,7 +636,7 @@ class Nifti(notifier.Notifier):
class Image(Nifti):
"""Class which represents a 3D/4D NIFTI image. Internally, the image is
"""Class which represents a NIFTI image. Internally, the image is
loaded/stored using a :mod:`nibabel.nifti1.Nifti1Image` or
:mod:`nibabel.nifti2.Nifti2Image`, and data access managed by a
:class:`.ImageWrapper`.
......@@ -818,7 +828,7 @@ class Image(Nifti):
else:
name = 'Nibabel image'
Nifti.__init__(self, nibImage.get_header())
Nifti.__init__(self, nibImage.header)
self.name = name
self.__lName = '{}_{}'.format(id(self), self.name)
......@@ -1001,8 +1011,7 @@ class Image(Nifti):
# Otherwise if the number of values in the
# image is bigger than the size threshold,
# we'll calculate the range from a sample:
if len(self.shape) == 3: self.__imageWrapper[:, :, 0]
else: self.__imageWrapper[:, :, :, 0]
self.__imageWrapper[..., 0]
def loadData(self):
......@@ -1056,7 +1065,7 @@ class Image(Nifti):
nib.save(self.__nibImage, filename)
self.__fileobj.close()
self.__nibImage, self.__fileobj = loadIndexedImageFile(filename)
self.header = self.__nibImage.get_header()
self.header = self.__nibImage.header
# We have to create a new ImageWrapper
# instance too, as we have just destroyed
......
......@@ -187,6 +187,11 @@ class ImageWrapper(notifier.Notifier):
if d == 1: self.__numRealDims -= 1
else: break
# Degenerate case - if every
# dimension has length 1
if self.__numRealDims == 0:
self.__numRealDims = len(image.shape)
# And save the number of
# 'padding' dimensions too.
self.__numPadDims = len(image.shape) - self.__numRealDims
......@@ -314,7 +319,7 @@ class ImageWrapper(notifier.Notifier):
# header (which may or may not contain
# useful values).
low, high = self.__range
hdr = self.__image.get_header()
hdr = self.__image.header
if low is None: low = float(hdr['cal_min'])
if high is None: high = float(hdr['cal_max'])
......
......@@ -77,7 +77,7 @@ class MelodicImage(fslimage.Image):
dataImage = fslimage.Image(dataFile,
loadData=False,
calcRange=False)
if dataImage.is4DImage():
if dataImage.ndims >= 4:
self.__tr = dataImage.pixdim[3]
......
......@@ -41,7 +41,7 @@ import re
import string
__version__ = '1.0.6.dev'
__version__ = '1.1.0.dev'
"""Current version number, as a string. """
......
......@@ -22,12 +22,14 @@ import nibabel as nib
from nibabel.spatialimages import ImageFileError
import fsl.data.constants as constants
import fsl.data.image as fslimage
import fsl.utils.path as fslpath
import fsl.data.constants as constants
import fsl.data.image as fslimage
import fsl.data.imagewrapper as imagewrapper
import fsl.utils.path as fslpath
from . import make_random_image
from . import make_dummy_file
from . import testdir
def make_image(filename=None,
......@@ -54,7 +56,7 @@ def make_image(filename=None,
hdr.set_zooms([abs(p) for p in pixdims])
xform = np.eye(4)
for i, p in enumerate(pixdims):
for i, p in enumerate(pixdims[:3]):
xform[i, i] = p
data = np.array(np.random.random(dims) * 100, dtype=dtype)
......@@ -239,15 +241,20 @@ def _test_Image_atts(imgtype):
# (file, dims, pixdims, dtype)
dtypes = [np.uint8, np.int16, np.int32, np.float32, np.double]
dims = [(10, 1, 1),
dims = [(1, 1, 1),
(10, 1, 1),
(1, 10, 1),
(1, 1, 10),
(10, 10, 1),
(10, 1, 10),
(1, 10, 10),
(10, 10, 10),
(1, 1, 1, 5),
(10, 10, 1, 5),
(1, 1, 1, 1),
(10, 1, 1, 1),
(1, 10, 1, 1),
(1, 1, 10, 1),
(10, 10, 1, 1),
(10, 10, 1, 5),
(10, 1, 10, 5),
(1, 10, 10, 5),
(10, 10, 10, 5)]
......@@ -276,17 +283,21 @@ def _test_Image_atts(imgtype):
dims, pixdims, dtype = atts
ndims = len(dims)
pixdims = pixdims[:ndims]
expdims = imagewrapper.canonicalShape(dims)
expndims = len(expdims)
ndims = len(dims)
pixdims = pixdims[:ndims]
exppixdims = pixdims[:expndims]
path = op.abspath(op.join(testdir, path))
i = fslimage.Image(path)
assert tuple(i.shape) == tuple(dims)
assert tuple(i.pixdim) == tuple(pixdims)
assert tuple(i.shape) == tuple(expdims)
assert tuple(i.pixdim) == tuple(exppixdims)
assert tuple(i.nibImage.shape) == tuple(dims)
assert tuple(i.nibImage.header.get_zooms()) == tuple(pixdims)
assert i.ndims == expndims
assert i.dtype == dtype
assert i.name == op.basename(path)
assert i.dataSource == fslpath.addExt(path,
......@@ -817,6 +828,34 @@ def _test_Image_2D(imgtype):
shutil.rmtree(testdir)
def test_Image_5D_analyze(): _test_Image_5D(0)
def test_Image_5D_nifti1(): _test_Image_5D(1)
def test_Image_5D_nifti2(): _test_Image_5D(2)
def _test_Image_5D(imgtype):
testdims = [
( 1, 1, 1, 1, 5),
(10, 10, 1, 1, 5),
(10, 10, 10, 1, 5),
( 1, 1, 1, 4, 5),
(10, 10, 1, 4, 5),
(10, 10, 10, 4, 5),
]
for dims in testdims:
with testdir() as td:
path = op.join(td, 'test.nii')
make_image(path, imgtype, dims, [1] * len(dims))
img = fslimage.Image(path)
assert img.shape == dims
assert img.ndims == 5
def test_Image_voxelsToScaledVoxels():
imgTypes = [0, 1, 2]
......
Supports Markdown
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