88import numpy as np
99import weakref
1010
11+ from .affines import voxel_sizes
1112from .optpkg import optional_package
1213from .orientations import aff2axcodes , axcodes2ornt
1314
@@ -37,29 +38,25 @@ class OrthoSlicer3D(object):
3738 >>> OrthoSlicer3D(data).show() # doctest: +SKIP
3839 """
3940 # 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 ):
4242 """
4343 Parameters
4444 ----------
45- data : ndarray
45+ data : array-like
4646 The data that will be displayed by the slicer. Should have 3+
4747 dimensions.
48- affine : array-like | None
48+ affine : array-like or None
4949 Affine transform for the data. This is used to determine
5050 how the data should be sliced for plotting into the saggital,
5151 coronal, and axial view axes. If None, identity is assumed.
5252 The aspect ratio of the data are inferred from the affine
5353 transform.
54- axes : tuple of mpl.Axes | None, optional
54+ axes : tuple of mpl.Axes or None, optional
5555 3 or 4 axes instances for the 3 slices plus volumes,
5656 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.
6360 """
6461 # Nest imports so that matplotlib.use() has the appropriate
6562 # effect in testing
@@ -75,21 +72,21 @@ def __init__(self, data, affine=None, axes=None, cmap='gray',
7572 if np .iscomplexobj (data ):
7673 raise TypeError ("Complex data not supported" )
7774 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 ):
7976 raise ValueError ('affine must be a 4x4 matrix' )
8077 # determine our orientation
81- self ._affine = affine . copy ()
78+ self ._affine = affine
8279 codes = axcodes2ornt (aff2axcodes (self ._affine ))
8380 self ._order = np .argsort ([c [0 ] for c in codes ])
8481 self ._flips = np .array ([c [1 ] < 0 for c in codes ])[self ._order ]
8582 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 )
8784 self ._inv_affine = np .linalg .inv (affine )
8885 # current volume info
8986 self ._volume_dims = data .shape [3 :]
9087 self ._current_vol_data = data [:, :, :, 0 ] if data .ndim > 3 else data
9188 self ._data = data
92- vmin , vmax = np .percentile (data , pcnt_range )
89+ self . _clim = np .percentile (data , ( 1. , 99. ) )
9390 del data
9491
9592 if axes is None : # make the axes
@@ -111,7 +108,7 @@ def __init__(self, data, affine=None, axes=None, cmap='gray',
111108 # <-- R <-- t -->
112109
113110 fig , axes = plt .subplots (2 , 2 )
114- fig .set_size_inches (figsize , forward = True )
111+ fig .set_size_inches (( 8 , 8 ) , forward = True )
115112 self ._axes = [axes [0 , 0 ], axes [0 , 1 ], axes [1 , 0 ], axes [1 , 1 ]]
116113 plt .tight_layout (pad = 0.1 )
117114 if self .n_volumes <= 1 :
@@ -132,14 +129,14 @@ def __init__(self, data, affine=None, axes=None, cmap='gray',
132129 r = [self ._scalers [self ._order [2 ]] / self ._scalers [self ._order [1 ]],
133130 self ._scalers [self ._order [2 ]] / self ._scalers [self ._order [0 ]],
134131 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 ]
136133 for ii , xax , yax , ratio , label in zip ([0 , 1 , 2 ], [1 , 0 , 0 ], [2 , 2 , 1 ],
137134 r , ('SAIP' , 'SLIR' , 'ALPR' )):
138135 ax = self ._axes [ii ]
139136 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' )
143140 self ._ims .append (im )
144141 vert = ax .plot ([0 ] * 2 , [- 0.5 , self ._sizes [yax ] - 0.5 ],
145142 color = (0 , 1 , 0 ), linestyle = '-' )[0 ]
@@ -240,6 +237,11 @@ def _cleanup(self):
240237 for link in list (self ._links ): # make a copy before iterating
241238 self ._unlink (link ())
242239
240+ def draw (self ):
241+ """Redraw the current image"""
242+ for fig in self ._figs :
243+ fig .canvas .draw ()
244+
243245 @property
244246 def n_volumes (self ):
245247 """Number of volumes in the data"""
@@ -250,6 +252,38 @@ def position(self):
250252 """The current coordinates"""
251253 return self ._position [:3 ].copy ()
252254
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+
253287 def link_to (self , other ):
254288 """Link positional changes between two canvases
255289
0 commit comments