diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 29b0ea2c7218b81db7e3bfce6cc7c473bc03cbf7..804ea4d681206cf5712db304de05980d4c3757ea 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -148,18 +148,6 @@ variables: - bash ./.ci/test_template.sh -test:2.7: - stage: test - image: pauldmccarthy/fsleyes-py27-wxpy4-gtk2 - <<: *test_template - - -test:3.4: - stage: test - image: pauldmccarthy/fsleyes-py34-wxpy4-gtk2 - <<: *test_template - - test:3.5: stage: test image: pauldmccarthy/fsleyes-py35-wxpy4-gtk2 @@ -172,6 +160,12 @@ test:3.6: <<: *test_template +test:3.7: + stage: test + image: pauldmccarthy/fsleyes-py37-wxpy4-gtk2 + <<: *test_template + + ############# # Style stage ############# @@ -197,7 +191,7 @@ build-doc: - docker stage: doc - image: python:3.5 + image: python:3.6 script: - bash ./.ci/build_doc.sh @@ -218,7 +212,7 @@ build-pypi-dist: <<: *patch_version stage: build - image: python:3.5 + image: python:3.6 tags: - docker @@ -242,7 +236,7 @@ deploy-doc: <<: *setup_ssh stage: deploy when: manual - image: python:3.5 + image: python:3.6 tags: - docker @@ -259,7 +253,7 @@ deploy-pypi: <<: *setup_ssh stage: deploy when: manual - image: python:3.5 + image: python:3.6 tags: - docker diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 1eee76621c908e57090146d99b9f96a00d72dd72..3d6b6527a88a63dd5eb408202c72f153f2eacb83 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,6 +2,33 @@ This document contains the ``fslpy`` release history in reverse chronological order. +2.0.0 (Under development) +------------------------- + + +Added +^^^^^ + +* Simple built-in `.deprecated` decorator. + + +Changed +^^^^^^^ + +* Removed support for Python 2.7 and 3.4. +* Minimum required version of ``nibabel`` is now 2.3. +* The :class:`.Image` class now fully delegates to ``nibabel`` for managing + file handles. +* Increased the minimum required version of ``dcm2niix``. + + +Removed +^^^^^^^ + + +* Many deprecated items removed. + + 1.12.0 (Sunday October 21st 2018) --------------------------------- diff --git a/README.rst b/README.rst index eadd675aa467956f9cf4d310ac8d5691112ab32c..bdd032b0c4ae2a65ad1ba6c91d6bf317e4a491d0 100644 --- a/README.rst +++ b/README.rst @@ -20,6 +20,9 @@ programming library written in Python. It is used by `FSLeyes <https://git.fmrib.ox.ac.uk/fsl/fsleyes/fsleyes/>`_. +``fslpy`` is tested against Python versions 3.5, 3.6 and 3.7. + + Installation ------------ @@ -56,8 +59,8 @@ Some extra dependencies are listed in `requirements.txt on the mesh. -If you are using Linux, need to install wxPython first, as binaries are not -available on PyPI. Change the URL for your specific platform:: +If you are using Linux, you need to install wxPython first, as binaries are +not available on PyPI. Change the URL for your specific platform:: pip install -f https://extras.wxpython.org/wxPython4/extras/linux/gtk2/ubuntu-16.04/ wxpython @@ -76,6 +79,14 @@ Dependencies for testing and documentation are listed in the `requirements-dev.txt <requirements-dev.txt>`_ file. +Non-Python dependencies +^^^^^^^^^^^^^^^^^^^^^^^ + + +The ``fsl.data.dicom`` module requires the presence of Chris Rorden's +`dcm2niix <https://github.com/rordenlab/dcm2niix>`_ program. + + Documentation ------------- diff --git a/doc/fsl.utils.deprecated.rst b/doc/fsl.utils.deprecated.rst new file mode 100644 index 0000000000000000000000000000000000000000..913da66ccc4e7bbeefa475a6bf738b96479997c2 --- /dev/null +++ b/doc/fsl.utils.deprecated.rst @@ -0,0 +1,7 @@ +``fsl.utils.deprecated`` +======================== + +.. automodule:: fsl.utils.deprecated + :members: + :undoc-members: + :show-inheritance: diff --git a/doc/fsl.utils.rst b/doc/fsl.utils.rst index 574c5296a19c8858702680f0ff14f220f85ef89d..d7bf157ed3154a6c3abe9fefe100da7548ea4087 100644 --- a/doc/fsl.utils.rst +++ b/doc/fsl.utils.rst @@ -6,6 +6,7 @@ fsl.utils.assertions fsl.utils.cache + fsl.utils.deprecated fsl.utils.ensure fsl.utils.fslsub fsl.utils.idle diff --git a/fsl/data/dicom.py b/fsl/data/dicom.py index 53b7edefe067583d0ea94aaddcc4024b5faa663e..1457c54eed7f4e017500de67d033f5b4b5626052 100644 --- a/fsl/data/dicom.py +++ b/fsl/data/dicom.py @@ -32,7 +32,6 @@ import re import glob import json import logging -import deprecation import nibabel as nib @@ -80,38 +79,6 @@ class DicomImage(fslimage.Image): return self.__dicomDir - @deprecation.deprecated(deprecated_in='1.6.0', - removed_in='2.0.0', - details='Use metaKeys instead') - def keys(self): - """Deprecated - use :meth:`.Image.metaKeys`. """ - return self.metaKeys() - - - @deprecation.deprecated(deprecated_in='1.6.0', - removed_in='2.0.0', - details='Use metaValues instead') - def values(self): - """Deprecated - use :meth:`.Image.metaValues`. """ - return self.metaValues() - - - @deprecation.deprecated(deprecated_in='1.6.0', - removed_in='2.0.0', - details='Use metaItems instead') - def items(self): - """Deprecated - use :meth:`.Image.metaItems`. """ - return self.metaItems() - - - @deprecation.deprecated(deprecated_in='1.6.0', - removed_in='2.0.0', - details='Use getMeta instead') - def get(self, *args, **kwargs): - """Deprecated - use :meth:`.Image.getMeta`. """ - return self.getMeta(*args, **kwargs) - - @memoize.memoize def enabled(): """Returns ``True`` if ``dcm2niix`` is present, and recent enough, diff --git a/fsl/data/gifti.py b/fsl/data/gifti.py index cd5185183d1770db9080f512d6df7b99a61dad51..b0b018c20e5c1955597fb75f6c740fc796ba861e 100644 --- a/fsl/data/gifti.py +++ b/fsl/data/gifti.py @@ -25,7 +25,6 @@ are available: import glob import os.path as op -import deprecation import numpy as np import nibabel as nib @@ -35,13 +34,15 @@ import fsl.data.constants as constants import fsl.data.mesh as fslmesh -ALLOWED_EXTENSIONS = ['.surf.gii'] +# We include '.gii' here because not all surface +# GIFTIs follow the file suffix convention. +ALLOWED_EXTENSIONS = ['.surf.gii', '.gii'] """List of file extensions that a file containing Gifti surface data is expected to have. """ -EXTENSION_DESCRIPTIONS = ['GIFTI surface file'] +EXTENSION_DESCRIPTIONS = ['GIFTI surface file', 'GIFTI file'] """A description for each of the :data:`ALLOWED_EXTENSIONS`. """ @@ -306,48 +307,3 @@ def relatedFiles(fname, ftypes=None): glob.glob(op.join(dirname, '{}*{}'.format(prefix, ftype)))) return [r for r in related if r != path] - - -class GiftiSurface(fslmesh.TriangleMesh): - """Deprecated - use GiftiMesh instead. """ - - - @deprecation.deprecated(deprecated_in='1.6.0', - removed_in='2.0.0', - details='Use GiftiMesh instead') - def __init__(self, infile, fixWinding=False): - """Deprecated - use GiftiMesh instead. """ - surfimg, vertices, indices = loadGiftiSurface(infile) - - fslmesh.TriangleMesh.__init__(self, vertices, indices, fixWinding) - - name = fslpath.removeExt(op.basename(infile), ALLOWED_EXTENSIONS) - infile = op.abspath(infile) - - self._Mesh__name = name - self._Mesh__dataSource = infile - self.surfImg = surfimg - - - @deprecation.deprecated(deprecated_in='1.6.0', - removed_in='2.0.0', - details='Use GiftiMesh instead') - def loadVertexData(self, dataSource, vertexData=None): - """Deprecated - use GiftiMesh instead. """ - - if vertexData is None: - if dataSource.endswith('.gii'): - vertexData = loadGiftiVertexData(dataSource)[1] - else: - vertexData = None - - return fslmesh.TriangleMesh.loadVertexData( - self, dataSource, vertexData) - - -@deprecation.deprecated(deprecated_in='1.6.0', - removed_in='2.0.0', - details='Use loadGiftiMesh instead') -def loadGiftiSurface(filename): - """Deprecated - use loadGiftiMesh instead.""" - return loadGiftiMesh(filename) diff --git a/fsl/data/image.py b/fsl/data/image.py index 6d1a7e4a831b7539805adb6c8068d22d677b7db2..1b407a3f7c773066c13e75b5bb207de03986f9c3 100644 --- a/fsl/data/image.py +++ b/fsl/data/image.py @@ -29,19 +29,17 @@ and file names: getExt removeExt defaultExt - loadIndexedImageFile """ import os import os.path as op -import shutil import string import logging import tempfile +import warnings import six -import deprecation import numpy as np import scipy.ndimage as ndimage @@ -53,6 +51,7 @@ import fsl.utils.transform as transform import fsl.utils.notifier as notifier import fsl.utils.memoize as memoize import fsl.utils.path as fslpath +import fsl.utils.deprecated as deprecated import fsl.data.constants as constants import fsl.data.imagewrapper as imagewrapper @@ -469,10 +468,10 @@ class Nifti(notifier.Notifier, meta.Meta): self.__worldToVoxMat = transform.invert(self.__voxToWorldMat) log.debug('Affine changed:\npixdims: ' - '{}\nsform: {}\nqform: {}'.format( - header.get_zooms(), - header.get_sform(), - header.get_qform())) + '%s\nsform: %s\nqform: %s', + header.get_zooms(), + header.get_sform(), + header.get_qform()) self.notify(topic='transform') @@ -502,23 +501,6 @@ class Nifti(notifier.Notifier, meta.Meta): return len(self.__shape) - @property - @deprecation.deprecated(deprecated_in='1.9.0', - removed_in='2.0.0', - details='Use ndim instead') - def ndims(self): - """Deprecated - use :mod::meth:``ndim`` instead. """ - return self.ndim - - - @deprecation.deprecated(deprecated_in='1.1.0', - removed_in='2.0.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 - - def getXFormCode(self, code=None): """This method returns the code contained in the NIFTI header, indicating the space to which the (transformed) image is oriented. @@ -605,15 +587,6 @@ class Nifti(notifier.Notifier, meta.Meta): return npla.det(self.__voxToWorldMat) > 0 - @memoize.Instanceify(memoize.memoize) - @deprecation.deprecated(deprecated_in='1.2.0', - removed_in='2.0.0', - details='Use voxToScaledVoxMat instead') - def voxelsToScaledVoxels(self): - """See :meth:`voxToScaledVoxMat`.""" - return self.voxToScaledVoxMat - - @property def voxToScaledVoxMat(self): """Returns a transformation matrix which transforms from voxel @@ -821,10 +794,8 @@ class Image(Nifti): incrementally updated as more data is read from memory or disk. - :arg indexed: If ``True``, and the file is gzipped, it is opened - using the :mod:`indexed_gzip` package. Otherwise the - file is opened by ``nibabel``. Ignored if ``loadData`` - is ``True``. + :arg indexed: Deprecated. Has no effect, and will be removed in + ``fslpy`` 3.0. :arg threaded: If ``True``, the :class:`.ImageWrapper` will use a separate thread for data range calculation. Defaults @@ -839,10 +810,14 @@ class Image(Nifti): """ nibImage = None - fileobj = None + + if indexed is not False: + warnings.warn('The indexed argument is deprecated ' + 'and has no effect', + category=DeprecationWarning, + stacklevel=2) if loadData: - indexed = False threaded = False # Take a copy of the header if one has @@ -868,24 +843,8 @@ class Image(Nifti): # The image parameter may be the name of an image file if isinstance(image, six.string_types): - image = op.abspath(addExt(image)) - - # Use indexed_gzip to open gzip files - # if requested - this provides fast - # on-disk access to the compressed - # data. - # - # If using indexed_gzip, we store a - # ref to the file object - we'll close - # it when we are destroyed. - if indexed and image.endswith('.gz'): - nibImage, fileobj = loadIndexedImageFile(image) - - # Otherwise we let nibabel - # manage the file reference(s) - else: - nibImage = nib.load(image, **kwargs) - + image = op.abspath(addExt(image)) + nibImage = nib.load(image, **kwargs) dataSource = image # Or a numpy array - we wrap it in a nibabel image, @@ -943,17 +902,16 @@ class Image(Nifti): Nifti.__init__(self, nibImage.header) - self.name = name - self.__lName = '{}_{}'.format(id(self), self.name) - self.__dataSource = dataSource - self.__fileobj = fileobj - self.__threaded = threaded - self.__nibImage = nibImage - self.__saveState = dataSource is not None - self.__imageWrapper = imagewrapper.ImageWrapper(self.nibImage, - self.name, - loadData=loadData, - threaded=threaded) + self.name = name + self.__lName = '{}_{}'.format(id(self), self.name) + self.__dataSource = dataSource + self.__threaded = threaded + self.__nibImage = nibImage + self.__saveState = dataSource is not None + self.__imageWrapper = imagewrapper.ImageWrapper(self.nibImage, + self.name, + loadData=loadData, + threaded=threaded) # Listen to ourself for changes # to the voxToWorldMat, so we @@ -992,10 +950,6 @@ class Image(Nifti): self.__nibImage = None self.__imageWrapper = None - if getattr(self, '__fileobj', None) is not None: - self.__fileobj.close() - self.__fileobj = None - def getImageWrapper(self): """Returns the :class:`.ImageWrapper` instance used to manage @@ -1126,13 +1080,13 @@ class Image(Nifti): # If an image size threshold has not been specified, # then we'll calculate the full data range right now. if sizethres is None or nbytes < sizethres: - log.debug('{}: Forcing calculation of full ' - 'data range'.format(self.name)) + log.debug('%s: Forcing calculation of full ' + 'data range', self.name) self.__imageWrapper[:] else: - log.debug('{}: Calculating data range ' - 'from sample'.format(self.name)) + log.debug('%s: Calculating data range ' + 'from sample', self.name) # Otherwise if the number of values in the # image is bigger than the size threshold, @@ -1152,6 +1106,8 @@ class Image(Nifti): if ``filename`` is ``None``. """ + import fsl.utils.imcp as imcp + if self.__dataSource is None and filename is None: raise ValueError('A file name must be specified') @@ -1164,87 +1120,45 @@ class Image(Nifti): if not looksLikeImage(filename): filename = addExt(filename, mustExist=False) - log.debug('Saving {} to {}'.format(self.name, filename)) + log.debug('Saving %s to %s', self.name, filename) - # If this Image is not managing its - # own file object, and the image is not - # memory-mapped, nibabel does all of - # the hard work. - newnibimage = False - sample = self[(slice(0, 5),) + (0,) * (len(self.shape) - 1)] - ismmap = isinstance(sample, np.memmap) + # We save the image out to a temp file, + # then close the old image, move the + # temp file to the real destination, + # then re-open the file. + tmphd, tmpfname = tempfile.mkstemp(suffix=getExt(filename)) + os.close(tmphd) - if self.__fileobj is None and (not ismmap): - nib.save(self.__nibImage, filename) - - # If the image is memory-mapped, we need - # to close and re-open the image - elif self.__fileobj is None: - - # We save the image out to a temp file, - # then close the old image, move the - # temp file to the real destination, - # then re-open the file. - newnibimage = True - tmphd, tmpfname = tempfile.mkstemp(suffix=op.splitext(filename)[1]) - os.close(tmphd) - - try: - nib.save(self.__nibImage, tmpfname) + try: + nib.save(self.__nibImage, tmpfname) - self.__nibImage = None - self.header = None + # nibabel should close any old + # file handles when the image/ + # header refs are deleted + self.__nibImage = None + self.header = None - shutil.copy(tmpfname, filename) + imcp.imcp(tmpfname, filename, overwrite=True) - self.__nibImage = nib.load(filename) - self.header = self.__nibImage.header + self.__nibImage = nib.load(filename) + self.header = self.__nibImage.header - except Exception: - os.remove(tmpfname) - raise + finally: + os.remove(tmpfname) - # Otherwise we've got our own file - # handle to an IndexedGzipFile - else: - # Currently indexed_gzip does not support - # writing. So we're going to use nibabel - # to save the image, then close and re-open - # the file. - # - # Unfortunately this means that we'll - # lose the file index (and fast random - # access) - I'll fix this when I get a - # chance to work on indexed_gzip a bit - # more. - # - # Hopefully I should be able to add write - # support to indexed_gzip, such that it - # re-builds the index while writing the - # compressed data. And then be able to - # transfer the index generated from the - # write to a new read-only file handle. - newnibimage = True - nib.save(self.__nibImage, filename) - self.__fileobj.close() - self.__nibImage, self.__fileobj = loadIndexedImageFile(filename) - self.header = self.__nibImage.header - - # If we've created a new nibabel image, + # Because we've created a new nibabel image, # we have to create a new ImageWrapper # instance too, as we have just destroyed # the nibabel image we gave to the last # one. - if newnibimage: - - self.__imageWrapper.deregister(self.__lName) - self.__imageWrapper = imagewrapper.ImageWrapper( - self.nibImage, - self.name, - loadData=False, - dataRange=self.dataRange, - threaded=self.__threaded) - self.__imageWrapper.register(self.__lName, self.__dataRangeChanged) + self.__imageWrapper.deregister(self.__lName) + self.__imageWrapper = imagewrapper.ImageWrapper( + self.nibImage, + self.name, + loadData=False, + dataRange=self.dataRange, + threaded=self.__threaded) + self.__imageWrapper.register(self.__lName, self.__dataRangeChanged) self.__dataSource = filename self.__saveState = True @@ -1349,7 +1263,7 @@ class Image(Nifti): :arg sliceobj: Something which can slice the image data. """ - log.debug('{}: __getitem__ [{}]'.format(self.name, sliceobj)) + log.debug('%s: __getitem__ [%s]', self.name, sliceobj) return self.__imageWrapper.__getitem__(sliceobj) @@ -1365,9 +1279,8 @@ class Image(Nifti): """ values = np.array(values) - log.debug('{}: __setitem__ [{} = {}]'.format(self.name, - sliceobj, - values.shape)) + log.debug('%s: __setitem__ [%s = %s]', + self.name, sliceobj, values.shape) with self.__imageWrapper.skip(self.__lName): @@ -1482,115 +1395,7 @@ def defaultExt(): return options.get(outputType, '.nii.gz') +@deprecated.deprecated('2.0.0', '3.0.0', 'Use nibabel.load instead.') def loadIndexedImageFile(filename): - """Loads the given image file using ``nibabel`` and ``indexed_gzip``. - - Returns a tuple containing the ``nibabel`` NIFTI image, and the open - ``IndexedGzipFile`` handle. - - If ``indexed_gzip`` is not present, the image is loaded normally via - ``nibabel.load``. - """ - - import threading - - try: - import indexed_gzip as igzip - except ImportError: - return nib.load(filename), None - - log.debug('Loading {} using indexed gzip'.format(filename)) - - # guessed_image_type returns a - # ref to one of the Nifti1Image - # or Nifti2Image classes. - ftype = nib.loadsave.guessed_image_type(filename) - fobj = igzip.SafeIndexedGzipFile( - filename=filename, - spacing=4194304, - readbuf_size=1048576) - - # See the read_segments - # function below - fobj._arrayproxy_lock = threading.Lock() - - fmap = ftype.make_file_map() - fmap['image'].fileobj = fobj - image = ftype.from_file_map(fmap) - - return image, fobj - - -@deprecation.deprecated(deprecated_in='1.3.0', - removed_in='2.0.0', - details='Upgrade to nibabel 2.2.0') -def read_segments(fileobj, segments, n_bytes): - """This function is used in place of the - ``nibabel.fileslice.read_segments`` function to ensure thread-safe read - access to image data via the ``nibabel.arrayproxy.ArrayProxy`` (the - ``dataobj`` attribute of a ``nibabel`` image). - - The ``nibabel`` implementation uses multiple calls to ``seek`` and - ``read`` to read segments of data from the file. When accessed by multiple - threads, these seeks and reads can become intertwined, which causes a read - from one thread to read data from the seek location requested by the other - thread. - - This implementation protects the seek/read pairs with a - ``threading.Lock``, which is added to ``IndexedGzipFile`` instances that - are created in the :func:`loadIndexedImageFile` function. - - .. note:: This patch is not required in nibabel 2.2.0 and newer. It will - be removed from ``fslpy`` in version 2.0.0. - """ - - from mmap import mmap - - try: - # fileobj is a nibabel.openers.ImageOpener - the - # actual file is available via the fobj attribute - lock = getattr(fileobj.fobj, '_arrayproxy_lock') - - except AttributeError: - return fileslice.orig_read_segments(fileobj, segments, n_bytes) - - if len(segments) == 0: - if n_bytes != 0: - raise ValueError("No segments, but non-zero n_bytes") - return b'' - if len(segments) == 1: - offset, length = segments[0] - - lock.acquire() - try: - fileobj.seek(offset) - bytes = fileobj.read(length) - finally: - lock.release() - - if len(bytes) != n_bytes: - raise ValueError("Whoops, not enough data in file") - return bytes - - # More than one segment - bytes = mmap(-1, n_bytes) - for offset, length in segments: - - lock.acquire() - try: - fileobj.seek(offset) - bytes.write(fileobj.read(length)) - finally: - lock.release() - - if bytes.tell() != n_bytes: - raise ValueError("Oh dear, n_bytes does not look right") - return bytes - - -# Monkey-patch the above implementation into -# nibabel. FSLeyes requires at least 2.1.0 - -# newer versions do not need to be patched. -if nib.__version__ == '2.1.0': - fileslice.orig_read_segments = fileslice.read_segments - fileslice.read_segments = read_segments + """Deprecated - this call is equivalent to calling ``nibabel.load``. """ + return nib.load(filename) diff --git a/fsl/data/imagewrapper.py b/fsl/data/imagewrapper.py index 559c0d974f5563edbafef459870cbbb5b504ddac..b73cb879aaf88ffa1e742477b3625fd7da91a590 100644 --- a/fsl/data/imagewrapper.py +++ b/fsl/data/imagewrapper.py @@ -37,11 +37,10 @@ get their definitions straight: """ -import logging -import collections -import itertools as it - -import deprecation +import logging +import collections +import collections.abc as abc +import itertools as it import numpy as np import nibabel as nib @@ -691,7 +690,7 @@ class ImageWrapper(notifier.Notifier): # If we are slicing a scalar, the # assigned value has to be scalar. - if expNdims == 0 and isinstance(values, collections.Sequence): + if expNdims == 0 and isinstance(values, abc.Sequence): if len(values) > 1: raise IndexError('Invalid assignment: [{}] = {}'.format( @@ -717,16 +716,6 @@ class ImageWrapper(notifier.Notifier): self.__updateDataRangeOnWrite(slices, values) -@deprecation.deprecated(deprecated_in='1.7.0', - removed_in='2.0.0', - details='Moved to fsl.utils.naninfrange') -def naninfrange(data): - """Deprecated - moved to :mod:`fsl.utils.naninfrange`. """ - - from fsl.utils.naninfrange import naninfrange - return naninfrange(data) - - def isValidFancySliceObj(sliceobj, shape): """Returns ``True`` if the given ``sliceobj`` is a valid and fancy slice object. @@ -768,15 +757,6 @@ def canonicalSliceObj(sliceobj, shape): return nib.fileslice.canonical_slicers(sliceobj, shape) -@deprecation.deprecated(deprecated_in='1.7.0', - removed_in='2.0.0', - details='moved to the fsl.data.image module') -def canonicalShape(shape): - """Deprecated - moved to the :mod:`fsl.data.image` module. """ - from fsl.data.image import canonicalShape - return canonicalShape(shape) - - def expectedShape(sliceobj, shape): """Given a slice object, and the shape of an array to which that slice object is going to be applied, returns the expected @@ -849,7 +829,7 @@ def sliceObjToSliceTuple(sliceobj, shape): # The sliceobj could be a single sliceobj # or integer, instead of a tuple - if not isinstance(sliceobj, collections.Sequence): + if not isinstance(sliceobj, abc.Sequence): sliceobj = [sliceobj] # Turn e.g. array[6] into array[6, :, :] diff --git a/fsl/data/mesh.py b/fsl/data/mesh.py index 59fc266c702e3b82427be74fc764ab41d0797c70..55b996069dfcb54b616bcc616b8b70eff90e02a7 100644 --- a/fsl/data/mesh.py +++ b/fsl/data/mesh.py @@ -30,9 +30,6 @@ various things with meshes: import logging import collections -import six -import deprecation - import os.path as op import numpy as np @@ -763,109 +760,3 @@ def needsFixing(vertices, indices, fnormals, loBounds, hiBounds): # If it isn't, we need to flip the # triangle winding order. return np.dot(n, transform.normalise(camera - vert)) < 0 - - -class TriangleMesh(Mesh): - """Deprecated - use :class:`fsl.data.mesh.Mesh`, or one of its sub-classes - instead. - """ - - - @deprecation.deprecated(deprecated_in='1.6.0', - removed_in='2.0.0', - details='Use fsl.data.mesh.Mesh, or one ' - 'of its sub-classes instead') - def __init__(self, data, indices=None, fixWinding=False): - - import fsl.data.vtk as fslvtk - - if isinstance(data, six.string_types): - name = op.basename(data) - dataSource = data - mesh = fslvtk.VTKMesh(data, fixWinding=False) - vertices = mesh.vertices - indices = mesh.indices - - else: - name = 'TriangleMesh' - dataSource = None - vertices = data - - Mesh.__init__(self, indices, name=name, dataSource=dataSource) - self.addVertices(vertices, 'default', fixWinding=fixWinding) - - - @deprecation.deprecated(deprecated_in='1.6.0', - removed_in='2.0.0', - details='Use the Mesh class instead') - def loadVertexData(self, dataSource, vertexData=None): - - nvertices = self.vertices.shape[0] - - # Currently only white-space delimited - # text files are supported - if vertexData is None: - vertexData = np.loadtxt(dataSource) - vertexData.reshape(nvertices, -1) - - if vertexData.shape[0] != nvertices: - raise ValueError('Incompatible size: {}'.format(dataSource)) - - self.addVertexData(dataSource, vertexData) - - return vertexData - - - @deprecation.deprecated(deprecated_in='1.6.0', - removed_in='2.0.0', - details='Use bounds instead') - def getBounds(self): - """Deprecated - use :meth:`bounds` instead. """ - return self.bounds - - - @deprecation.deprecated(deprecated_in='1.6.0', - removed_in='2.0.0', - details='Use the Mesh class instead') - def getVertexData(self, dataSource): - try: - return Mesh.getVertexData(self, dataSource) - except KeyError: - return self.loadVertexData(dataSource) - - -@deprecation.deprecated(deprecated_in='1.6.0', - removed_in='2.0.0', - details='Use fsl.data.vtk.loadVTKPolydataFile instead') -def loadVTKPolydataFile(*args, **kwargs): - """Deprecated - use :func:`fsl.data.vtk.loadVTKPolydataFile` instead. """ - import fsl.data.vtk as fslvtk - return fslvtk.loadVTKPolydataFile(*args, **kwargs) - - -@deprecation.deprecated(deprecated_in='1.6.0', - removed_in='2.0.0', - details='Use fsl.data.vtk.getFIRSTPrefix instead') -def getFIRSTPrefix(*args, **kwargs): - """Deprecated - use :func:`fsl.data.vtk.getFIRSTPrefix` instead. """ - import fsl.data.vtk as fslvtk - return fslvtk.getFIRSTPrefix(*args, **kwargs) - - -@deprecation.deprecated(deprecated_in='1.6.0', - removed_in='2.0.0', - details='Use fsl.data.vtk.findReferenceImage instead') -def findReferenceImage(*args, **kwargs): - """Deprecated - use :func:`fsl.data.vtk.findReferenceImage` instead. """ - import fsl.data.vtk as fslvtk - return fslvtk.findReferenceImage(*args, **kwargs) - - -ALLOWED_EXTENSIONS = ['.vtk'] -"""Deprecated, will be removed in fslpy 2.0.0. Use -:attr:`fsl.data.vtk.ALLOWED_EXTENSIONS` instead.""" - - -EXTENSION_DESCRIPTIONS = ['VTK polygon model file'] -"""Deprecated, will be removed in fslpy 2.0.0. Use -:attr:`fsl.data.vtk.EXTENSION_DESCRIPTIONS` instead.""" diff --git a/fsl/utils/async.py b/fsl/utils/async.py deleted file mode 100644 index 520f77ce62c2c5bd4586571469c305369eb00934..0000000000000000000000000000000000000000 --- a/fsl/utils/async.py +++ /dev/null @@ -1,34 +0,0 @@ -#!/usr/bin/env python -# -# async.py - Deprecaed - use the idle module instead. -# -# Author: Paul McCarthy <pauldmccarthy@gmail.com> -# -"""This module is deprecated - use the :mod:`.idle` module instead. """ - - -import logging -import warnings - -from .idle import (run, # noqa - idleReset, - getIdleTimeout, - setIdleTimeout, - inIdle, - cancelIdle, - idle, - idleWhen, - wait, - TaskThreadVeto, - TaskThread, - mutex) - - -log = logging.getLogger(__name__) - - -warnings.warn('fsl.utils.async is deprecated and will be removed ' - 'in fslpy 2.0.0 - use fsl.utils.idle instead', - DeprecationWarning) -log.warning('fsl.utils.async is deprecated and will be removed ' - 'in fslpy 2.0.0 - use fsl.utils.idle instead') diff --git a/fsl/utils/callfsl.py b/fsl/utils/callfsl.py deleted file mode 100644 index 598df885bc0d595dda214aff7d1670e0336df85e..0000000000000000000000000000000000000000 --- a/fsl/utils/callfsl.py +++ /dev/null @@ -1,56 +0,0 @@ -#!/usr/bin/env python -# -# callfsl.py - The callFSL function. -# -# Author: Paul McCarthy <pauldmccarthy@gmail.com> -# -"""Deprecated - use :mod:`fsl.utils.run` instead. - -This module provides the :func:`callFSL` function, which can be -used to call a FSL command, and retrieve the result. -""" - - -import logging -import subprocess as sp -import os.path as op - -import deprecation - -from fsl.utils.platform import platform as fslplatform - - -log = logging.getLogger(__name__) - - -@deprecation.deprecated(deprecated_in='1.8.0', - removed_in='2.0.0', - details='Use fsl.utils.run.runfsl instead') -def callFSL(*args): - """Call a FSL command and return the result. - - You can pass the command and arguments as a single string, or as a - list/tuple. - """ - - if fslplatform.fsldir is None: - raise RuntimeError('FSL cannot be found!') - - # If we've been given a single argument, - # assume it is a string containing the - # command and its arguments. Otherwise, - # assume it is a sequence containing - # separate command and arguments. - if len(args) == 1: - args = args[0].split() - - args = list(args) - args[0] = op.join(fslplatform.fsldir, 'bin', args[0]) - - log.debug('callfsl: {}'.format(' '.join(args))) - - result = sp.check_output(args).decode('utf-8') - - log.debug('result: {}'.format(result)) - - return result diff --git a/fsl/utils/deprecated.py b/fsl/utils/deprecated.py new file mode 100644 index 0000000000000000000000000000000000000000..c79dddfeb9073420c4d5449edd42ed50d9783b40 --- /dev/null +++ b/fsl/utils/deprecated.py @@ -0,0 +1,69 @@ +#!/usr/bin/env python +# +# deprecated.py - Decorator for deprecating things. +# +# Author: Paul McCarthy <pauldmccarthy@gmail.com> +# +"""This module provides the :func:`deprecated` function, a simple decorator +for deprecating functions and methods. +""" + + + +import functools as ft +import inspect +import warnings + + +_warned_cache = set() +"""Used by the :func:`deprecated` function to keep track of whether a warning +has already been emitted for the use of a deprecated item. +""" + + +def deprecated(vin=None, rin=None, msg=None): + """Decorator to mark a function or method as deprecated. A + ``DeprecationWarning`` is raised via the standard ``warnings`` module. + + :arg vin: Optional version - the warning message will mention that the + function is deprecated from this version. + + :arg rin: Optional version - the warning message will mention that the + function will be removed in this version. + + :arg msg: Optional message to use in the warning. + """ + + + if vin is not None and rin is not None: + msgfmt = '{{name}} is deprecated from version {vin} and will be ' \ + 'removed in {rin}.'.format(vin=vin, rin=rin) + elif vin is not None: + msgfmt = '{{name}} is deprecated from version {vin}.'.format(vin=vin) + elif rin is not None: + msgfmt = '{{name}} is deprecated and will be removed in ' \ + '{rin}.'.format(rin=rin) + else: + msgfmt = '{{name}} is deprecated.' + + if msg is not None: + msgfmt = msgfmt + ' ' + msg + + def wrapper(thing): + name = thing.__name__ + + def decorator(*args, **kwargs): + + frame = inspect.stack()[1] + ident = '{}:{}'.format(frame.filename, frame.lineno) + + if ident not in _warned_cache: + warnings.warn(msgfmt.format(name=name), + category=DeprecationWarning, + stacklevel=2) + _warned_cache.add(ident) + + return thing(*args, **kwargs) + return ft.update_wrapper(decorator, thing) + + return wrapper diff --git a/fsl/utils/platform.py b/fsl/utils/platform.py index cd2764ef9a9accc9a9f0f1a9d4f7e7837a687a8f..26706f31814ba805062fc04fdbc49abd205bda6f 100644 --- a/fsl/utils/platform.py +++ b/fsl/utils/platform.py @@ -17,7 +17,6 @@ import os import os.path as op import sys import importlib -import deprecation import fsl.utils.notifier as notifier @@ -73,34 +72,6 @@ are running the Linux/GTK wx build. """ -@deprecation.deprecated(deprecated_in='1.2.2', - removed_in='2.0.0', - details='Use fsleyes_widgets.isalive instead') -def isWidgetAlive(widget): - """Returns ``True`` if the given ``wx.Window`` object is "alive" (i.e. - has not been destroyed), ``False`` otherwise. Works in both wxPython - and wxPython/Phoenix. - - .. warning:: Don't try to test whether a ``wx.MenuItem`` has been - destroyed, as it will probably result in segmentation - faults. Check the parent ``wx.Menu`` instead. - """ - - import wx - - if platform.wxFlavour == platform.WX_PHOENIX: - excType = RuntimeError - elif platform.wxFlavour == platform.WX_PYTHON: - excType = wx.PyDeadObjectError - - try: - widget.GetParent() - return True - - except excType: - return False - - class Platform(notifier.Notifier): """The ``Platform`` class contains a handful of properties which contain information about the platform we are running on. @@ -138,7 +109,6 @@ class Platform(notifier.Notifier): self.WX_MAC_COCOA = WX_MAC_COCOA self.WX_MAC_CARBON = WX_MAC_CARBON self.WX_GTK = WX_GTK - self.isWidgetAlive = isWidgetAlive self.__inSSHSession = False self.__inVNCSession = False diff --git a/fsl/utils/run.py b/fsl/utils/run.py index a5b9df816f9605edf62b8daa62d082142a898057..e4a4c51961c74c23554fca53cb3fd0394000467b 100644 --- a/fsl/utils/run.py +++ b/fsl/utils/run.py @@ -144,10 +144,6 @@ def run(*args, **kwargs): exception is not raised. Ignored if ``submit`` is specified. - :arg err: Deprecated - use ``stderr`` instead. - - :arg ret: Deprecated - use ``exitcode`` instead. - :arg submit: Must be passed as a keyword argument. Defaults to ``None``. If ``True``, the command is submitted as a cluster job via the :func:`.fslsub.submit` function. May also be a @@ -175,17 +171,6 @@ def run(*args, **kwargs): ``stderr``, and ``exitcode`` arguments. """ - if 'err' in kwargs: - warnings.warn('err is deprecated and will be removed ' - 'in fslpy 2.0.0 - use stderr instead', - DeprecationWarning) - kwargs['stderr'] = kwargs.get('stderr', kwargs['err']) - if 'ret' in kwargs: - warnings.warn('ret is deprecated and will be removed ' - 'in fslpy 2.0.0 - use exitcode instead', - DeprecationWarning) - kwargs['exitcode'] = kwargs.get('exitcode', kwargs['ret']) - returnStdout = kwargs.get('stdout', True) returnStderr = kwargs.get('stderr', False) returnExitcode = kwargs.get('exitcode', False) diff --git a/fsl/utils/transform.py b/fsl/utils/transform.py index 2a5610dd89c172bec7b349d6e3400ad3b3cf21f6..f574eaa764a8e337cb7616f7eaef254e0dfd3a9d 100644 --- a/fsl/utils/transform.py +++ b/fsl/utils/transform.py @@ -34,9 +34,9 @@ And a few more functions are provided for working with vectors: normalise """ -import numpy as np -import numpy.linalg as linalg -import collections +import numpy as np +import numpy.linalg as linalg +import collections.abc as abc def invert(x): @@ -94,7 +94,7 @@ def scaleOffsetXform(scales, offsets): :returns: A ``numpy.float32`` array of size :math:`4 \\times 4`. """ - oktypes = (collections.Sequence, np.ndarray) + oktypes = (abc.Sequence, np.ndarray) if not isinstance(scales, oktypes): scales = [scales] if not isinstance(offsets, oktypes): offsets = [offsets] @@ -398,7 +398,7 @@ def axisBounds(shape, if axes is None: axes = [0, 1, 2] - elif not isinstance(axes, collections.Iterable): + elif not isinstance(axes, abc.Iterable): scalar = True axes = [axes] @@ -507,13 +507,13 @@ def _fillPoints(p, axes): or an ``N*2`` or ``N*3`` array. """ - if not isinstance(p, collections.Iterable): p = [p] + if not isinstance(p, abc.Iterable): p = [p] p = np.array(p) if axes is None: return p - if not isinstance(axes, collections.Iterable): axes = [axes] + if not isinstance(axes, abc.Iterable): axes = [axes] if p.ndim == 1: p = p.reshape((len(p), 1)) diff --git a/fsl/wrappers/wrapperutils.py b/fsl/wrappers/wrapperutils.py index 6728a23272696f295227291d723101cd366e7be5..d3fc2db2379224aa1f3582a1c8e6a96afc8ac164 100644 --- a/fsl/wrappers/wrapperutils.py +++ b/fsl/wrappers/wrapperutils.py @@ -85,21 +85,22 @@ and returned:: """ -import itertools as it -import os.path as op -import os -import re -import sys -import glob -import random -import string -import fnmatch -import inspect -import logging -import tempfile -import warnings -import functools -import collections +import itertools as it +import os.path as op +import collections.abc as abc +import os +import re +import sys +import glob +import random +import string +import fnmatch +import inspect +import logging +import tempfile +import warnings +import functools + import six import nibabel as nib @@ -282,7 +283,7 @@ def applyArgStyle(style, valsep=None, argmap=None, valmap=None, **kwargs): # always returns a sequence def fmtval(val): - if isinstance(val, collections.Sequence) and \ + if isinstance(val, abc.Sequence) and \ not isinstance(val, six.string_types): val = [str(v) for v in val] diff --git a/requirements-extra.txt b/requirements-extra.txt index fa62f9919dfc96bbca30c655db0cb1ae8308641c..6d8a4c4b960fb3d8447940c410b486765be4fc41 100644 --- a/requirements-extra.txt +++ b/requirements-extra.txt @@ -1,4 +1,4 @@ -indexed_gzip>=0.7.0,<1 -wxpython>=3.0.2.0,<4.1 -trimesh>=2.22.28,<3 +indexed_gzip>=0.7.0 +wxpython==4.* +trimesh>=2.22.28 rtree==0.8.3 diff --git a/requirements.txt b/requirements.txt index 51e205639f8e3e8cc298b82ee5116106b036e907..ef041ad9cd02c2d53af3875cb4cecf7d2d077d5f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,4 @@ six==1.* -deprecation>=1.*,<=2.* numpy==1.* -scipy>=0.18,<2 -nibabel==2.* +scipy>=0.18 +nibabel>=2.3.1 diff --git a/setup.py b/setup.py index d1212cc37dd875176bbbb267cdb5135787c0f00d..63a6da5b801304ac8f3867c7ab3832eb198aac5e 100644 --- a/setup.py +++ b/setup.py @@ -102,10 +102,9 @@ setup( 'Development Status :: 3 - Alpha', 'Intended Audience :: Developers', 'License :: OSI Approved :: Apache Software License', - 'Programming Language :: Python :: 2.7', - 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', 'Topic :: Software Development :: Libraries :: Python Modules'], packages=packages, diff --git a/tests/test_atlases.py b/tests/test_atlases.py index 9853c5a73a2b6d73cede0606638906f9f817a40d..80be87c74736f39cfa4134a3efa4e804d2ce0db3 100644 --- a/tests/test_atlases.py +++ b/tests/test_atlases.py @@ -225,7 +225,7 @@ def test_load_atlas(): reg.rescanAtlases() probatlas = reg.loadAtlas('harvardoxford-cortical', - indexed=True, calcRange=False, loadData=False) + calcRange=False, loadData=False) probsumatlas = reg.loadAtlas('harvardoxford-cortical', loadSummary=True) lblatlas = reg.loadAtlas('talairach') @@ -240,7 +240,7 @@ def test_find(): reg.rescanAtlases() probatlas = reg.loadAtlas('harvardoxford-cortical', - indexed=True, calcRange=False, loadData=False) + calcRange=False, loadData=False) probsumatlas = reg.loadAtlas('harvardoxford-cortical', loadSummary=True) lblatlas = reg.loadAtlas('talairach') @@ -273,7 +273,7 @@ def test_prepareMask(): reg.rescanAtlases() probatlas = reg.loadAtlas('harvardoxford-cortical', - indexed=True, loadData=False, calcRange=False) + loadData=False, calcRange=False) probsumatlas = reg.loadAtlas('harvardoxford-cortical', loadSummary=True) lblatlas = reg.loadAtlas('talairach') diff --git a/tests/test_atlases_query.py b/tests/test_atlases_query.py index 29676a09bb81fc0fb280bb602a80e34c8eb72825..f90c8d40618b78ea7f33e0c8168623fcdac31d7c 100644 --- a/tests/test_atlases_query.py +++ b/tests/test_atlases_query.py @@ -45,8 +45,7 @@ def _get_atlas(atlasID, res, summary=False): kwargs = {} else: kwargs = {'loadData' : False, - 'calcRange' : False, - 'indexed' : True} + 'calcRange' : False} atlas = fslatlases.loadAtlas(atlasID, loadSummary=summary, diff --git a/tests/test_atlasq_ohi.py b/tests/test_atlasq_ohi.py index 36279ce693ec0cbeb7c783bf3ece588f6c32431d..2c0e4e7aadbeea076278f215b56a5e851458f43d 100644 --- a/tests/test_atlasq_ohi.py +++ b/tests/test_atlasq_ohi.py @@ -98,7 +98,6 @@ def test_coords(seed): atlas = fslatlases.loadAtlas( ad.atlasID, resolution=2, - indexed=True, calcRange=False, loadData=False) diff --git a/tests/test_atlasq_query.py b/tests/test_atlasq_query.py index 5ba48f9190fa9d23ec13d7fd231ec16fa283b66c..987cd01141211a28b26739bdb524d8bee3d9f039 100644 --- a/tests/test_atlasq_query.py +++ b/tests/test_atlasq_query.py @@ -241,7 +241,7 @@ def _eval_coord_voxel_query( else: exp = [q_type, squery, 'No label'] - _stdout = re.sub('\s+', ' ', stdout).strip() + _stdout = re.sub(r'\s+', ' ', stdout).strip() assert _stdout.strip() == ' '.join(exp).strip() def evalProbNormalOutput(expprops): @@ -442,7 +442,6 @@ def test_bad_mask(seed): atlas = fslatlases.loadAtlas( atlasID, loadSummary=use_label, - indexed=True, loadData=False, calcRange=False) ashape = list(atlas.shape[:3]) diff --git a/tests/test_callfsl.py b/tests/test_callfsl.py deleted file mode 100644 index 25c972e386a2708a46f5fbcfeb139a995f63868b..0000000000000000000000000000000000000000 --- a/tests/test_callfsl.py +++ /dev/null @@ -1,85 +0,0 @@ -#!/usr/bin/env python -# -# test_callfsl.py - -# -# Author: Paul McCarthy <pauldmccarthy@gmail.com> -# - -import os -import os.path as op -import subprocess as sp - -import numpy as np -import nibabel as nib - -import mock -import pytest - -import fsl.utils.callfsl as callfsl -from fsl.utils.platform import platform as fslplatform - -import tests - - -pytestmark = pytest.mark.fsltest - - -def setup_module(): - fsldir = os.environ.get('FSLDIR', None) - if fsldir is None or not op.exists(fsldir): - raise Exception('FSLDIR is not set - callfsl tests cannot be run') - - -# mock subprocess.check_output command -# which expects 'fslstats -m filename' -# or 'fslinfo ...' -def mock_check_output(args): - if args[0].endswith('fslinfo'): - return 'info'.encode('utf-8') - - img = nib.load(args[-2]) - return str(img.get_data().mean()).encode('utf-8') - - -def test_callfsl(): - - with tests.testdir() as testdir: - - fname = op.join(testdir, 'myimage.nii.gz') - - img = tests.make_random_image(fname) - img = img.get_data() - - # Pass a single string - cmd = 'fslstats {} -m'.format(fname) - - with mock.patch('fsl.utils.callfsl.sp.check_output', - mock_check_output): - result = callfsl.callFSL(cmd) - - assert np.isclose(float(result), img.mean()) - - # Or pass a list of args - result = callfsl.callFSL(*cmd.split()) - assert np.isclose(float(result), img.mean()) - - # Bad commands - badcmds = ['fslblob', 'fslstats notafile'] - - for cmd in badcmds: - with pytest.raises((OSError, sp.CalledProcessError)): - callfsl.callFSL(cmd) - - # No FSL - should crash - cmd = 'fslinfo {}'.format(fname) - with mock.patch('fsl.utils.callfsl.sp.check_output', - mock_check_output): - callfsl.callFSL(cmd) - - try: - oldval = fslplatform.fsldir - fslplatform.fsldir = None - with pytest.raises(Exception): - callfsl.callFSL(cmd) - finally: - fslplatform.fsldir = oldval diff --git a/tests/test_idle.py b/tests/test_idle.py index afa45b5ad3e1236b2d4fee4644b7f8f4659c1a03..13a8e3c98d679fb1fe5dcb10044fe163fe6f852b 100644 --- a/tests/test_idle.py +++ b/tests/test_idle.py @@ -89,7 +89,7 @@ def _wait_for_idle_loop_to_clear(): idle.idle(busywait) while not idleDone[0]: - wx.Yield() + wx.GetApp().Yield() @pytest.mark.wxtest diff --git a/tests/test_image.py b/tests/test_image.py index 84a3d244542163e7c164a26a0408c336447af250..d468b4b76a2216b8045b7f2b15b947d4136c6510 100644 --- a/tests/test_image.py +++ b/tests/test_image.py @@ -307,7 +307,6 @@ def _test_Image_atts(imgtype): assert tuple(i.nibImage.header.get_zooms()) == tuple(pixdims) assert i.ndim == expndims - assert i.ndims == expndims assert i.dtype == dtype assert i.name == op.basename(path) assert i.dataSource == fslpath.addExt(path, @@ -1048,9 +1047,9 @@ def test_image_resample(seed): fname = op.join(td, 'test.nii') # Random base image shapes - for i in range(50): + for i in range(25): - shape = np.random.randint(5, 100, 3) + shape = np.random.randint(5, 50, 3) make_random_image(fname, shape) img = fslimage.Image(fname, mmap=False) @@ -1062,7 +1061,7 @@ def test_image_resample(seed): # Random resampled image shapes for j in range(10): - rshape = np.random.randint(5, 100, 3) + rshape = np.random.randint(5, 50, 3) resampled, xf = img.resample(rshape, order=0) img.save('base.nii.gz') diff --git a/tests/test_image_advanced.py b/tests/test_image_advanced.py index 69756278781902081f2b8c3250d7eb6d40319a96..2be8678b603a9d716dc461f770ae65acd89c9173 100644 --- a/tests/test_image_advanced.py +++ b/tests/test_image_advanced.py @@ -57,7 +57,6 @@ def _test_image_indexed(threaded): filename, loadData=False, calcRange=False, - indexed=True, threaded=threaded) # First iteration through the image @@ -72,6 +71,17 @@ def _test_image_indexed(threaded): assert img.dataRange == (0, vol) end1 = time.time() + # Double check that indexed_gzip is + # being used (the internal _opener + # attribute is not created until + # after the first data access) + try: + import indexed_gzip as igzip + assert isinstance(img.nibImage.dataobj._opener.fobj, + igzip.IndexedGzipFile) + except ImportError: + pass + # Second iteration through start2 = time.time() for vol in range(data.shape[-1]): @@ -108,7 +118,6 @@ def _test_image_indexed_read4D(threaded): filename, loadData=False, calcRange=False, - indexed=True, threaded=threaded) # Test reading slice through @@ -126,6 +135,14 @@ def _test_image_indexed_read4D(threaded): assert np.all(data == np.arange(nvols)) + # double check we're indexing as expected + try: + import indexed_gzip as igzip + assert isinstance(img.nibImage.dataobj._opener.fobj, + igzip.IndexedGzipFile) + except ImportError: + pass + @pytest.mark.igziptest @pytest.mark.longtest @@ -150,13 +167,20 @@ def _test_image_indexed_save(threaded): filename, loadData=False, calcRange=False, - indexed=True, threaded=threaded) # access some data img[..., 0] img[..., 40] + # double check that igzip is being used + try: + import indexed_gzip as igzip + assert isinstance(img.nibImage.dataobj._opener.fobj, + igzip.IndexedGzipFile) + except ImportError: + pass + if threaded: img.getImageWrapper().getTaskThread().waitUntilIdle() @@ -203,10 +227,8 @@ def _test_image_indexed_save(threaded): @pytest.mark.longtest -@pytest.mark.igziptest def test_image_no_calcRange_threaded(): _test_image_no_calcRange(True) @pytest.mark.longtest -@pytest.mark.igziptest def test_image_no_calcRange_unthreaded(): _test_image_no_calcRange(False) def _test_image_no_calcRange(threaded): @@ -228,7 +250,6 @@ def _test_image_no_calcRange(threaded): # cal_min/max if it is unknown assert img.dataRange == (95, 643) - for i in [0, 7, 40]: img[..., i] if threaded: @@ -238,10 +259,8 @@ def _test_image_no_calcRange(threaded): @pytest.mark.longtest -@pytest.mark.igziptest def test_image_calcRange_threaded(): _test_image_calcRange(True) @pytest.mark.longtest -@pytest.mark.igziptest def test_image_calcRange_unthreaded(): _test_image_calcRange(False) def _test_image_calcRange(threaded): diff --git a/tests/test_imagewrapper.py b/tests/test_imagewrapper.py index 1237cd55d02966fc5596c32825dcec071f9c8b2e..61ed5c21bbdbccef14f769b4547f9fdb8c6fd8a4 100644 --- a/tests/test_imagewrapper.py +++ b/tests/test_imagewrapper.py @@ -465,7 +465,10 @@ def _test_expansion(coverage, slices, volumes, expansions): # coverage, or in one of the expansions. dimranges = [] for d in range(ndims): - dimranges.append(np.linspace(nc[0, d], nc[1, d], nc[1, d] / 5, dtype=np.uint32)) + dimranges.append(np.linspace(nc[0, d], + nc[1, d], + int(nc[1, d] / 5), + dtype=np.uint32)) points = it.product(*dimranges) diff --git a/tests/test_platform.py b/tests/test_platform.py index 4531f7534a0950ddf86753c88d8a02e09606f02d..67aefe094bedd91c19772c3d842841134faa07f8 100644 --- a/tests/test_platform.py +++ b/tests/test_platform.py @@ -208,33 +208,3 @@ def test_detect_ssh(): p = fslplatform.Platform() assert not p.inSSHSession assert not p.inVNCSession - - -@pytest.mark.wxtest -def test_IsWidgetAlive(): - - import wx - - passed = [False] - app = wx.App() - frame = wx.Frame(None) - btn = wx.Button(frame) - frame.Show() - - def runtest(): - - try: - - passed[0] = fslplatform.isWidgetAlive(btn) - - btn.Destroy() - - passed[0] = passed[0] and (not fslplatform.isWidgetAlive(btn)) - finally: - frame.Destroy() - app.ExitMainLoop() - - wx.CallLater(500, runtest) - app.MainLoop() - - assert passed[0] diff --git a/tests/test_vest.py b/tests/test_vest.py index 84638a3ce0227fb00b9dbec438cc112cee05e47e..df74b5b8cf656f3a630d7cd29c2da988ab2b5321 100644 --- a/tests/test_vest.py +++ b/tests/test_vest.py @@ -9,6 +9,7 @@ import os.path as op import shutil import tempfile +import warnings import numpy as np import pytest @@ -166,9 +167,12 @@ def test_loadVestLutFile(): for i in range(4): f = testfiles[i] d = testdata[ i] - dnorm = (d - d.min()) / (d.max() - d.min()) - lutnorm = vest.loadVestLutFile(f) - lut = vest.loadVestLutFile(f, normalise=False) + + with warnings.catch_warnings(): + warnings.simplefilter('ignore') + dnorm = (d - d.min()) / (d.max() - d.min()) + lutnorm = vest.loadVestLutFile(f) + lut = vest.loadVestLutFile(f, normalise=False) assert lut.shape == d.shape assert lutnorm.shape == dnorm.shape