14
14
15
15
16
16
class OrthoSlicer3D (object ):
17
- """Orthogonal-plane slicer .
17
+ """ Orthogonal-plane slice viewer .
18
18
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.
21
25
22
26
Clicking and dragging the mouse in any one axis will select out the
23
27
corresponding slices in the other two. Scrolling up and
24
28
down moves the slice up and down in the current axis.
25
29
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.
31
34
32
35
Example
33
36
-------
34
37
>>> 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
38
41
>>> OrthoSlicer3D(data).show() # doctest: +SKIP
39
42
"""
40
43
# Skip doctest above b/c not all systems have mpl installed
44
+
41
45
def __init__ (self , data , affine = None , axes = None , title = None ):
42
46
"""
43
47
Parameters
44
48
----------
45
49
data : array-like
46
50
The data that will be displayed by the slicer. Should have 3+
47
51
dimensions.
48
- affine : array-like or None
52
+ affine : array-like or None, optional
49
53
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 ,
51
55
coronal, and axial view axes. If None, identity is assumed.
52
56
The aspect ratio of the data are inferred from the affine
53
57
transform.
54
58
axes : tuple of mpl.Axes or None, optional
55
59
3 or 4 axes instances for the 3 slices plus volumes,
56
60
or None (default).
57
- title : str or None
61
+ title : str or None, optional
58
62
The title to display. Can be None (default) to display no
59
63
title.
60
64
"""
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 ]
66
70
self ._title = title
67
71
self ._closed = False
68
72
@@ -198,10 +202,10 @@ def __init__(self, data, affine=None, axes=None, title=None):
198
202
199
203
# actually set data meaningfully
200
204
self ._position = np .zeros (4 )
201
- self ._position [3 ] = 1. # convenience for affine multn
205
+ self ._position [3 ] = 1. # convenience for affine multiplication
202
206
self ._changing = False # keep track of status to avoid loops
203
207
self ._links = [] # other viewers this one is linked to
204
- plt .draw ()
208
+ self . _plt .draw ()
205
209
for fig in self ._figs :
206
210
fig .canvas .draw ()
207
211
self ._set_volume_index (0 , update_slices = False )
@@ -220,16 +224,14 @@ def __repr__(self):
220
224
def show (self ):
221
225
"""Show the slicer in blocking mode; convenience for ``plt.show()``
222
226
"""
223
- plt , _ , _ = optional_package ('matplotlib.pyplot' )
224
- plt .show ()
227
+ self ._plt .show ()
225
228
226
229
def close (self ):
227
230
"""Close the viewer figures
228
231
"""
229
232
self ._cleanup ()
230
- plt , _ , _ = optional_package ('matplotlib.pyplot' )
231
233
for f in self ._figs :
232
- plt .close (f )
234
+ self . _plt .close (f )
233
235
234
236
def _cleanup (self ):
235
237
"""Clean up before closing"""
@@ -377,7 +379,7 @@ def _set_position(self, x, y, z, notify=True):
377
379
for ii , (size , idx ) in enumerate (zip (self ._sizes , idxs )):
378
380
self ._data_idx [ii ] = max (min (int (round (idx )), size - 1 ), 0 )
379
381
for ii in range (3 ):
380
- # saggital : get to S/A
382
+ # sagittal : get to S/A
381
383
# coronal: get to S/L
382
384
# axial: get to A/L
383
385
data = np .take (self ._current_vol_data , self ._data_idx [ii ],
0 commit comments