8
8
import numpy as np
9
9
import weakref
10
10
11
+ from .affines import voxel_sizes
11
12
from .optpkg import optional_package
12
13
from .orientations import aff2axcodes , axcodes2ornt
13
14
@@ -37,29 +38,25 @@ class OrthoSlicer3D(object):
37
38
>>> OrthoSlicer3D(data).show() # doctest: +SKIP
38
39
"""
39
40
# Skip doctest above b/c not all systems have mpl installed
40
- def __init__ (self , data , affine = None , axes = None , cmap = 'gray' ,
41
- pcnt_range = (1. , 99. ), figsize = (8 , 8 ), title = None ):
41
+ def __init__ (self , data , affine = None , axes = None , title = None ):
42
42
"""
43
43
Parameters
44
44
----------
45
- data : ndarray
45
+ data : array-like
46
46
The data that will be displayed by the slicer. Should have 3+
47
47
dimensions.
48
- affine : array-like | None
48
+ affine : array-like or None
49
49
Affine transform for the data. This is used to determine
50
50
how the data should be sliced for plotting into the saggital,
51
51
coronal, and axial view axes. If None, identity is assumed.
52
52
The aspect ratio of the data are inferred from the affine
53
53
transform.
54
- axes : tuple of mpl.Axes | None, optional
54
+ axes : tuple of mpl.Axes or None, optional
55
55
3 or 4 axes instances for the 3 slices plus volumes,
56
56
or None (default).
57
- cmap : str | instance of cmap, optional
58
- String or cmap instance specifying colormap.
59
- pcnt_range : array-like, optional
60
- Percentile range over which to scale image for display.
61
- figsize : tuple
62
- Figure size (in inches) to use if axes are None.
57
+ title : str or None
58
+ The title to display. Can be None (default) to display no
59
+ title.
63
60
"""
64
61
# Nest imports so that matplotlib.use() has the appropriate
65
62
# effect in testing
@@ -75,21 +72,21 @@ def __init__(self, data, affine=None, axes=None, cmap='gray',
75
72
if np .iscomplexobj (data ):
76
73
raise TypeError ("Complex data not supported" )
77
74
affine = np .array (affine , float ) if affine is not None else np .eye (4 )
78
- if affine .ndim != 2 or affine . shape != (4 , 4 ):
75
+ if affine .shape != (4 , 4 ):
79
76
raise ValueError ('affine must be a 4x4 matrix' )
80
77
# determine our orientation
81
- self ._affine = affine . copy ()
78
+ self ._affine = affine
82
79
codes = axcodes2ornt (aff2axcodes (self ._affine ))
83
80
self ._order = np .argsort ([c [0 ] for c in codes ])
84
81
self ._flips = np .array ([c [1 ] < 0 for c in codes ])[self ._order ]
85
82
self ._flips = list (self ._flips ) + [False ] # add volume dim
86
- self ._scalers = np . abs (self ._affine ). max ( axis = 0 )[: 3 ]
83
+ self ._scalers = voxel_sizes (self ._affine )
87
84
self ._inv_affine = np .linalg .inv (affine )
88
85
# current volume info
89
86
self ._volume_dims = data .shape [3 :]
90
87
self ._current_vol_data = data [:, :, :, 0 ] if data .ndim > 3 else data
91
88
self ._data = data
92
- vmin , vmax = np .percentile (data , pcnt_range )
89
+ self . _clim = np .percentile (data , ( 1. , 99. ) )
93
90
del data
94
91
95
92
if axes is None : # make the axes
@@ -111,7 +108,7 @@ def __init__(self, data, affine=None, axes=None, cmap='gray',
111
108
# <-- R <-- t -->
112
109
113
110
fig , axes = plt .subplots (2 , 2 )
114
- fig .set_size_inches (figsize , forward = True )
111
+ fig .set_size_inches (( 8 , 8 ) , forward = True )
115
112
self ._axes = [axes [0 , 0 ], axes [0 , 1 ], axes [1 , 0 ], axes [1 , 1 ]]
116
113
plt .tight_layout (pad = 0.1 )
117
114
if self .n_volumes <= 1 :
@@ -132,14 +129,14 @@ def __init__(self, data, affine=None, axes=None, cmap='gray',
132
129
r = [self ._scalers [self ._order [2 ]] / self ._scalers [self ._order [1 ]],
133
130
self ._scalers [self ._order [2 ]] / self ._scalers [self ._order [0 ]],
134
131
self ._scalers [self ._order [1 ]] / self ._scalers [self ._order [0 ]]]
135
- self ._sizes = [self ._data .shape [o ] for o in self ._order ]
132
+ self ._sizes = [self ._data .shape [order ] for order in self ._order ]
136
133
for ii , xax , yax , ratio , label in zip ([0 , 1 , 2 ], [1 , 0 , 0 ], [2 , 2 , 1 ],
137
134
r , ('SAIP' , 'SLIR' , 'ALPR' )):
138
135
ax = self ._axes [ii ]
139
136
d = np .zeros ((self ._sizes [yax ], self ._sizes [xax ]))
140
- im = self ._axes [ii ].imshow (d , vmin = vmin , vmax = vmax , aspect = 1 ,
141
- cmap = cmap , interpolation = 'nearest' ,
142
- origin = 'lower' )
137
+ im = self ._axes [ii ].imshow (
138
+ d , vmin = self . _clim [ 0 ], vmax = self . _clim [ 1 ], aspect = 1 ,
139
+ cmap = 'gray' , interpolation = 'nearest' , origin = 'lower' )
143
140
self ._ims .append (im )
144
141
vert = ax .plot ([0 ] * 2 , [- 0.5 , self ._sizes [yax ] - 0.5 ],
145
142
color = (0 , 1 , 0 ), linestyle = '-' )[0 ]
@@ -240,6 +237,11 @@ def _cleanup(self):
240
237
for link in list (self ._links ): # make a copy before iterating
241
238
self ._unlink (link ())
242
239
240
+ def draw (self ):
241
+ """Redraw the current image"""
242
+ for fig in self ._figs :
243
+ fig .canvas .draw ()
244
+
243
245
@property
244
246
def n_volumes (self ):
245
247
"""Number of volumes in the data"""
@@ -250,6 +252,38 @@ def position(self):
250
252
"""The current coordinates"""
251
253
return self ._position [:3 ].copy ()
252
254
255
+ @property
256
+ def figs (self ):
257
+ """A tuple of the figure(s) containing the axes"""
258
+ return tuple (self ._figs )
259
+
260
+ @property
261
+ def cmap (self ):
262
+ """The current colormap"""
263
+ return self ._cmap
264
+
265
+ @cmap .setter
266
+ def cmap (self , cmap ):
267
+ for im in self ._ims :
268
+ im .set_cmap (cmap )
269
+ self ._cmap = cmap
270
+ self .draw ()
271
+
272
+ @property
273
+ def clim (self ):
274
+ """The current color limits"""
275
+ return self ._clim
276
+
277
+ @clim .setter
278
+ def clim (self , clim ):
279
+ clim = np .array (clim , float )
280
+ if clim .shape != (2 ,):
281
+ raise ValueError ('clim must be a 2-element array-like' )
282
+ for im in self ._ims :
283
+ im .set_clim (clim )
284
+ self ._clim = tuple (clim )
285
+ self .draw ()
286
+
253
287
def link_to (self , other ):
254
288
"""Link positional changes between two canvases
255
289
0 commit comments