1414
1515
1616class OrthoSlicer3D (object ):
17- """Orthogonal-plane slicer .
17+ """ Orthogonal-plane slice viewer .
1818
19- OrthoSlicer3d expects 3-dimensional data, and by default it creates a
20- figure with 3 axes, one for each slice orientation.
19+ OrthoSlicer3d expects 3- or 4-dimensional array data. It treats
20+ 4D data as a sequence of 3D spatial volumes, where a slice over the final
21+ array axis gives a single 3D spatial volume.
22+
23+ For 3D data, the default behavior is to create a figure with 3 axes, one
24+ for each slice orientation of the spatial volume.
2125
2226 Clicking and dragging the mouse in any one axis will select out the
2327 corresponding slices in the other two. Scrolling up and
2428 down moves the slice up and down in the current axis.
2529
26- OrthoSlicer3d also supports 4-dimensional data, where multiple
27- 3-dimensional volumes are stacked along the last axis. For 4-dimensional
28- data the fourth figure axis can be used to control which 3-dimensional
29- volume is displayed. Alternatively, the - key can be used to decrement the
30- displayed volume and the + or = keys can be used to increment it.
30+ For 4D data, the fourth figure axis can be used to control which
31+ 3D volume is displayed. Alternatively, the ``-`` key can be used to
32+ decrement the displayed volume and the ``+`` or ``=`` keys can be used to
33+ increment it.
3134
3235 Example
3336 -------
3437 >>> import numpy as np
35- >>> a = np.sin(np.linspace(0,np.pi,20))
36- >>> b = np.sin(np.linspace(0,np.pi*5,20))
37- >>> data = np.outer(a,b)[..., np.newaxis]* a
38+ >>> a = np.sin(np.linspace(0, np.pi, 20))
39+ >>> b = np.sin(np.linspace(0, np.pi*5, 20))
40+ >>> data = np.outer(a, b)[..., np.newaxis] * a
3841 >>> OrthoSlicer3D(data).show() # doctest: +SKIP
3942 """
4043 # Skip doctest above b/c not all systems have mpl installed
44+
4145 def __init__ (self , data , affine = None , axes = None , title = None ):
4246 """
4347 Parameters
4448 ----------
4549 data : array-like
4650 The data that will be displayed by the slicer. Should have 3+
4751 dimensions.
48- affine : array-like or None
52+ affine : array-like or None, optional
4953 Affine transform for the data. This is used to determine
50- how the data should be sliced for plotting into the saggital ,
54+ how the data should be sliced for plotting into the sagittal ,
5155 coronal, and axial view axes. If None, identity is assumed.
5256 The aspect ratio of the data are inferred from the affine
5357 transform.
5458 axes : tuple of mpl.Axes or None, optional
5559 3 or 4 axes instances for the 3 slices plus volumes,
5660 or None (default).
57- title : str or None
61+ title : str or None, optional
5862 The title to display. Can be None (default) to display no
5963 title.
6064 """
61- # Nest imports so that matplotlib.use() has the appropriate
62- # effect in testing
63- plt , _ , _ = optional_package ( 'matplotlib.pyplot' )
64- mpl_img , _ , _ = optional_package ('matplotlib.image' )
65- mpl_patch , _ , _ = optional_package ('matplotlib.patches' )
65+ # Use these late imports of matplotlib so that we have some hope that
66+ # the test functions are the first to set the matplotlib backend. The
67+ # tests set the backend to something that doesn't require a display.
68+ self . _plt = plt = optional_package ('matplotlib.pyplot' )[ 0 ]
69+ mpl_patch = optional_package ('matplotlib.patches' )[ 0 ]
6670 self ._title = title
6771 self ._closed = False
6872
@@ -198,10 +202,10 @@ def __init__(self, data, affine=None, axes=None, title=None):
198202
199203 # actually set data meaningfully
200204 self ._position = np .zeros (4 )
201- self ._position [3 ] = 1. # convenience for affine multn
205+ self ._position [3 ] = 1. # convenience for affine multiplication
202206 self ._changing = False # keep track of status to avoid loops
203207 self ._links = [] # other viewers this one is linked to
204- plt .draw ()
208+ self . _plt .draw ()
205209 for fig in self ._figs :
206210 fig .canvas .draw ()
207211 self ._set_volume_index (0 , update_slices = False )
@@ -220,16 +224,14 @@ def __repr__(self):
220224 def show (self ):
221225 """Show the slicer in blocking mode; convenience for ``plt.show()``
222226 """
223- plt , _ , _ = optional_package ('matplotlib.pyplot' )
224- plt .show ()
227+ self ._plt .show ()
225228
226229 def close (self ):
227230 """Close the viewer figures
228231 """
229232 self ._cleanup ()
230- plt , _ , _ = optional_package ('matplotlib.pyplot' )
231233 for f in self ._figs :
232- plt .close (f )
234+ self . _plt .close (f )
233235
234236 def _cleanup (self ):
235237 """Clean up before closing"""
@@ -377,7 +379,7 @@ def _set_position(self, x, y, z, notify=True):
377379 for ii , (size , idx ) in enumerate (zip (self ._sizes , idxs )):
378380 self ._data_idx [ii ] = max (min (int (round (idx )), size - 1 ), 0 )
379381 for ii in range (3 ):
380- # saggital : get to S/A
382+ # sagittal : get to S/A
381383 # coronal: get to S/L
382384 # axial: get to A/L
383385 data = np .take (self ._current_vol_data , self ._data_idx [ii ],
0 commit comments