Skip to content

MRG: Slice viewer #404

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 24 commits into from
Mar 2, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
# munges each line before executing it to print out the exit status. It's okay
# for it to be on multiple physical lines, so long as you remember: - There
# can't be any leading "-"s - All newlines will be removed, so use ";"s

language: python

# Run jobs on container-based infrastructure, can be overridden per job
Expand Down
1 change: 1 addition & 0 deletions nibabel/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@
from .imageclasses import class_map, ext_map, all_image_classes
from . import trackvis
from . import mriutils
from . import viewers

# be friendly on systems with ancient numpy -- no tests, but at least
# importable
Expand Down
18 changes: 18 additions & 0 deletions nibabel/spatialimages.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@

from .filebasedimages import FileBasedHeader, FileBasedImage
from .filebasedimages import ImageFileError # flake8: noqa; for back-compat
from .viewers import OrthoSlicer3D
from .volumeutils import shape_zoom_affine


Expand Down Expand Up @@ -661,3 +662,20 @@ def __getitem__(self):
raise TypeError("Cannot slice image objects; consider slicing image "
"array data with `img.dataobj[slice]` or "
"`img.get_data()[slice]`")

def orthoview(self):
"""Plot the image using OrthoSlicer3D

Returns
-------
viewer : instance of OrthoSlicer3D
The viewer.

Notes
-----
This requires matplotlib. If a non-interactive backend is used,
consider using viewer.show() (equivalently plt.show()) to show
the figure.
"""
return OrthoSlicer3D(self.dataobj, self.affine,
title=self.get_filename())
94 changes: 94 additions & 0 deletions nibabel/tests/test_viewers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
# emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*-
# vi: set ft=python sts=4 ts=4 sw=4 et:
### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##
#
# See COPYING file distributed along with the NiBabel package for the
# copyright and license terms.
#
### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##

import numpy as np
from collections import namedtuple as nt


from ..optpkg import optional_package
from ..viewers import OrthoSlicer3D

from numpy.testing.decorators import skipif
from numpy.testing import assert_array_equal, assert_equal

from nose.tools import assert_raises, assert_true

matplotlib, has_mpl = optional_package('matplotlib')[:2]
needs_mpl = skipif(not has_mpl, 'These tests need matplotlib')
if has_mpl:
matplotlib.use('Agg')


@needs_mpl
def test_viewer():
# Test viewer
plt = optional_package('matplotlib.pyplot')[0]
a = np.sin(np.linspace(0, np.pi, 20))
b = np.sin(np.linspace(0, np.pi*5, 30))
data = (np.outer(a, b)[..., np.newaxis] * a)[:, :, :, np.newaxis]
data = data * np.array([1., 2.]) # give it a # of volumes > 1
v = OrthoSlicer3D(data)
assert_array_equal(v.position, (0, 0, 0))
assert_true('OrthoSlicer3D' in repr(v))

# fake some events, inside and outside axes
v._on_scroll(nt('event', 'button inaxes key')('up', None, None))
for ax in (v._axes[0], v._axes[3]):
v._on_scroll(nt('event', 'button inaxes key')('up', ax, None))
v._on_scroll(nt('event', 'button inaxes key')('up', ax, 'shift'))
# "click" outside axes, then once in each axis, then move without click
v._on_mouse(nt('event', 'xdata ydata inaxes button')(0.5, 0.5, None, 1))
for ax in v._axes:
v._on_mouse(nt('event', 'xdata ydata inaxes button')(0.5, 0.5, ax, 1))
v._on_mouse(nt('event', 'xdata ydata inaxes button')(0.5, 0.5, None, None))
v.set_volume_idx(1)
v.cmap = 'hot'
v.clim = (0, 3)
assert_raises(ValueError, OrthoSlicer3D.clim.fset, v, (0.,)) # bad limits
assert_raises(ValueError, OrthoSlicer3D.cmap.fset, v, 'foo') # wrong cmap

# decrement/increment volume numbers via keypress
v.set_volume_idx(1) # should just pass
v._on_keypress(nt('event', 'key')('-')) # decrement
assert_equal(v._data_idx[3], 0)
v._on_keypress(nt('event', 'key')('+')) # increment
assert_equal(v._data_idx[3], 1)
v._on_keypress(nt('event', 'key')('-'))
v._on_keypress(nt('event', 'key')('=')) # alternative increment key
assert_equal(v._data_idx[3], 1)

v.close()
v._draw() # should be safe

# non-multi-volume
v = OrthoSlicer3D(data[:, :, :, 0])
v._on_scroll(nt('event', 'button inaxes key')('up', v._axes[0], 'shift'))
v._on_keypress(nt('event', 'key')('escape'))
v.close()

# complex input should raise a TypeError prior to figure creation
assert_raises(TypeError, OrthoSlicer3D,
data[:, :, :, 0].astype(np.complex64))

# other cases
fig, axes = plt.subplots(1, 4)
plt.close(fig)
v1 = OrthoSlicer3D(data, axes=axes)
aff = np.array([[0, 1, 0, 3], [-1, 0, 0, 2], [0, 0, 2, 1], [0, 0, 0, 1]],
float)
v2 = OrthoSlicer3D(data, affine=aff, axes=axes[:3])
# bad data (not 3+ dim)
assert_raises(ValueError, OrthoSlicer3D, data[:, :, 0, 0])
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment to note error is because of affine not being 4, 4.

# bad affine (not 4x4)
assert_raises(ValueError, OrthoSlicer3D, data, affine=np.eye(3))
assert_raises(TypeError, v2.link_to, 1)
v2.link_to(v1)
v2.link_to(v1) # shouldn't do anything
v1.close()
v2.close()
Loading