From 717886415a00b7dce2fdfda6f72b4fbee9d3d7c7 Mon Sep 17 00:00:00 2001 From: "Christopher J. Markiewicz" Date: Sat, 15 Oct 2016 22:49:53 -0400 Subject: [PATCH 1/4] ENH: Add manual value limits to OrthoSlicer3D Pass kwargs to OrthoSlicer3D from SpatialImage.orthoview() --- nibabel/spatialimages.py | 4 ++-- nibabel/viewers.py | 14 ++++++++++---- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/nibabel/spatialimages.py b/nibabel/spatialimages.py index ede0820065..2a48b5fc56 100644 --- a/nibabel/spatialimages.py +++ b/nibabel/spatialimages.py @@ -588,7 +588,7 @@ def __getitem__(self, idx): "slicing image array data with `img.dataobj[slice]` or " "`img.get_data()[slice]`") - def orthoview(self): + def orthoview(self, **kwargs): """Plot the image using OrthoSlicer3D Returns @@ -603,7 +603,7 @@ def orthoview(self): the figure. """ return OrthoSlicer3D(self.dataobj, self.affine, - title=self.get_filename()) + title=self.get_filename(), **kwargs) def as_reoriented(self, ornt): """Apply an orientation change and return a new image diff --git a/nibabel/viewers.py b/nibabel/viewers.py index 7a0f4d93d7..026a41d734 100644 --- a/nibabel/viewers.py +++ b/nibabel/viewers.py @@ -42,7 +42,7 @@ class OrthoSlicer3D(object): """ # Skip doctest above b/c not all systems have mpl installed - def __init__(self, data, affine=None, axes=None, title=None): + def __init__(self, data, affine=None, axes=None, title=None, vlim=None): """ Parameters ---------- @@ -61,6 +61,9 @@ def __init__(self, data, affine=None, axes=None, title=None): title : str or None, optional The title to display. Can be None (default) to display no title. + vlim : array-like or None, optional + Value limits to display image and time series. Can be None + (default) to derive limits from data. """ # Use these late imports of matplotlib so that we have some hope that # the test functions are the first to set the matplotlib backend. The @@ -91,7 +94,7 @@ def __init__(self, data, affine=None, axes=None, title=None): self._volume_dims = data.shape[3:] self._current_vol_data = data[:, :, :, 0] if data.ndim > 3 else data self._data = data - self._clim = np.percentile(data, (1., 99.)) + self._clim = np.percentile(data, (1., 99.)) if vlim is None else vlim del data if axes is None: # make the axes @@ -184,8 +187,11 @@ def __init__(self, data, affine=None, axes=None, title=None): ax.set_xticks(np.unique(np.linspace(0, self.n_volumes - 1, 5).astype(int))) ax.set_xlim(x[0], x[-1]) - yl = [self._data.min(), self._data.max()] - yl = [l + s * np.diff(lims)[0] for l, s in zip(yl, [-1.01, 1.01])] + if vlim is None: + yl = [self._data.min(), self._data.max()] + yl = [l + s * np.diff(lims)[0] for l, s in zip(yl, [-1.01, 1.01])] + else: + yl = vlim patch = mpl_patch.Rectangle([-0.5, yl[0]], 1., np.diff(yl)[0], fill=True, facecolor=(0, 1, 0), edgecolor=(0, 1, 0), alpha=0.25) From 91d300003f706f2727dbd5a485b09164e139e7f8 Mon Sep 17 00:00:00 2001 From: "Christopher J. Markiewicz" Date: Wed, 19 Oct 2016 07:37:26 -0400 Subject: [PATCH 2/4] DOC: Use explicit args in SpatialImage.orthoview TEST: Test vlim functionality --- nibabel/spatialimages.py | 15 ++++++++++++--- nibabel/tests/test_viewers.py | 8 ++++++++ 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/nibabel/spatialimages.py b/nibabel/spatialimages.py index 2a48b5fc56..a52b7fbc09 100644 --- a/nibabel/spatialimages.py +++ b/nibabel/spatialimages.py @@ -588,9 +588,18 @@ def __getitem__(self, idx): "slicing image array data with `img.dataobj[slice]` or " "`img.get_data()[slice]`") - def orthoview(self, **kwargs): + def orthoview(self, axes=None, vlim=None): """Plot the image using OrthoSlicer3D + Parameters + ------------------ + axes : tuple of mpl.Axes or None, optional + 3 or 4 axes instances for the 3 slices plus volumes, + or None (default). + vlim : array-like or None, optional + Value limits to display image and time series. Can be None + (default) to derive limits from data. + Returns ------- viewer : instance of OrthoSlicer3D @@ -602,8 +611,8 @@ def orthoview(self, **kwargs): consider using viewer.show() (equivalently plt.show()) to show the figure. """ - return OrthoSlicer3D(self.dataobj, self.affine, - title=self.get_filename(), **kwargs) + return OrthoSlicer3D(self.dataobj, self.affine, axes=axes, + title=self.get_filename(), vlim=vlim) def as_reoriented(self, ornt): """Apply an orientation change and return a new image diff --git a/nibabel/tests/test_viewers.py b/nibabel/tests/test_viewers.py index 68710b3126..fca72918b4 100644 --- a/nibabel/tests/test_viewers.py +++ b/nibabel/tests/test_viewers.py @@ -68,6 +68,14 @@ def test_viewer(): v.close() v._draw() # should be safe + # Manually set value limits + vlim = np.array([-20, 20]) + v = OrthoSlicer3D(data, vlim=vlim) + assert_array_equal(v._clim, vlim) + for im in v._ims: + assert_array_equal(im.get_clim(), vlim) + assert_array_equal(v._axes[3].get_ylim(), vlim) + # non-multi-volume v = OrthoSlicer3D(data[:, :, :, 0]) v._on_scroll(nt('event', 'button inaxes key')('up', v._axes[0], 'shift')) From 148c07447fb4b760778c34141537cc24f7277ff9 Mon Sep 17 00:00:00 2001 From: "Christopher J. Markiewicz" Date: Wed, 19 Oct 2016 15:52:44 -0400 Subject: [PATCH 3/4] ENH: Enable percentile value limits --- nibabel/viewers.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/nibabel/viewers.py b/nibabel/viewers.py index 026a41d734..0a29c8d17b 100644 --- a/nibabel/viewers.py +++ b/nibabel/viewers.py @@ -63,7 +63,8 @@ def __init__(self, data, affine=None, axes=None, title=None, vlim=None): title. vlim : array-like or None, optional Value limits to display image and time series. Can be None - (default) to derive limits from data. + (default) to derive limits from data. Bounds can be of the + form ``'x%'`` to use the ``x`` percentile of the data. """ # Use these late imports of matplotlib so that we have some hope that # the test functions are the first to set the matplotlib backend. The @@ -82,6 +83,13 @@ def __init__(self, data, affine=None, axes=None, title=None, vlim=None): affine = np.array(affine, float) if affine is not None else np.eye(4) if affine.shape != (4, 4): raise ValueError('affine must be a 4x4 matrix') + + if vlim is not None: + percentiles = all(isinstance(lim, str) and lim[-1] == '%' + for lim in vlim) + if percentiles: + vlim = np.percentile(data, [float(lim[:-1]) for lim in vlim]) + # determine our orientation self._affine = affine codes = axcodes2ornt(aff2axcodes(self._affine)) From 0cd14115afc0ce1b19575192cd4951b2cdacc572 Mon Sep 17 00:00:00 2001 From: "Christopher J. Markiewicz" Date: Wed, 19 Oct 2016 17:53:38 -0400 Subject: [PATCH 4/4] TEST Test percentiles --- nibabel/tests/test_spatialimages.py | 11 +++++++++++ nibabel/tests/test_viewers.py | 11 ++++++++++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/nibabel/tests/test_spatialimages.py b/nibabel/tests/test_spatialimages.py index b0f571023d..bdbcf9710a 100644 --- a/nibabel/tests/test_spatialimages.py +++ b/nibabel/tests/test_spatialimages.py @@ -25,6 +25,7 @@ from numpy.testing import assert_array_equal, assert_array_almost_equal from .test_helpers import bytesio_round_trip +from .test_viewers import needs_mpl from ..testing import (clear_and_catch_warnings, suppress_warnings, memmap_after_ufunc) from ..tmpdirs import InTemporaryDirectory @@ -539,6 +540,16 @@ def test_slicer(self): assert_array_equal(sliced_data, img.dataobj[sliceobj]) assert_array_equal(sliced_data, img.get_data()[sliceobj]) + @needs_mpl + def test_orthoview(self): + # Assumes all possible images support int16 + # See https://github.com/nipy/nibabel/issues/58 + arr = np.arange(24, dtype=np.int16).reshape((2, 3, 4)) + img = self.image_class(arr, None) + img.orthoview().close() + img.orthoview(vlim=(5, 10)).close() + img.orthoview(slicer=Ellipsis).close() + def test_api_deprecations(self): class FakeImage(self.image_class): diff --git a/nibabel/tests/test_viewers.py b/nibabel/tests/test_viewers.py index fca72918b4..5edb99a940 100644 --- a/nibabel/tests/test_viewers.py +++ b/nibabel/tests/test_viewers.py @@ -17,7 +17,7 @@ from ..testing import skipif from numpy.testing import assert_array_equal, assert_equal -from nose.tools import assert_raises, assert_true +from nose.tools import assert_raises, assert_true, assert_false # Need at least MPL 1.3 for viewer tests. matplotlib, has_mpl, _ = optional_package('matplotlib', min_version='1.3') @@ -75,6 +75,15 @@ def test_viewer(): for im in v._ims: assert_array_equal(im.get_clim(), vlim) assert_array_equal(v._axes[3].get_ylim(), vlim) + v.close() + v1 = OrthoSlicer3D(data) + v2 = OrthoSlicer3D(data, vlim=('1%', '99%')) + assert_array_equal(v1.clim, v2.clim) + v2.close() + v2 = OrthoSlicer3D(data, vlim=('2%', '98%')) + assert_false(np.array_equal(v1.clim, v2.clim)) + v2.close() + v1.close() # non-multi-volume v = OrthoSlicer3D(data[:, :, :, 0])