@@ -69,6 +69,9 @@ def __init__(self, data, affine=None, axes=None, title=None):
69
69
self ._title = title
70
70
self ._closed = False
71
71
self ._cross = True
72
+ self ._overlay = None
73
+ self ._threshold = None
74
+ self ._alpha = 1
72
75
73
76
data = np .asanyarray (data )
74
77
if data .ndim < 3 :
@@ -286,6 +289,111 @@ def clim(self, clim):
286
289
self ._clim = tuple (clim )
287
290
self .draw ()
288
291
292
+ @property
293
+ def overlay (self ):
294
+ """The current overlay """
295
+ return self ._overlay
296
+
297
+ @property
298
+ def threshold (self ):
299
+ """The current data display threshold """
300
+ return self ._threshold
301
+
302
+ @threshold .setter
303
+ def threshold (self , threshold ):
304
+ # mask data array
305
+ if threshold is not None :
306
+ self ._data = np .ma .masked_array (np .asarray (self ._data ),
307
+ np .asarray (self ._data ) <= threshold )
308
+ self ._threshold = float (threshold )
309
+ else :
310
+ self ._data = np .asarray (self ._data )
311
+ self ._threshold = threshold
312
+
313
+ # update current volume data w/masked array and re-draw everything
314
+ if self ._data .ndim > 3 :
315
+ self ._current_vol_data = self ._data [..., self ._data_idx [3 ]]
316
+ else :
317
+ self ._current_vol_data = self ._data
318
+ self ._set_position (None , None , None , notify = False )
319
+
320
+ @property
321
+ def alpha (self ):
322
+ """ The current alpha (transparency) value """
323
+ return self ._alpha
324
+
325
+ @alpha .setter
326
+ def alpha (self , alpha ):
327
+ alpha = float (alpha )
328
+ if alpha > 1 or alpha < 0 :
329
+ raise ValueError ('alpha must be between 0 and 1' )
330
+ for im in self ._ims :
331
+ im .set_alpha (alpha )
332
+ self ._alpha = alpha
333
+ self .draw ()
334
+
335
+ def set_overlay (self , data , affine = None , threshold = None , cmap = 'viridis' ):
336
+ if affine is None :
337
+ try : # did we get an image?
338
+ affine = data .affine
339
+ data = data .dataobj
340
+ except AttributeError :
341
+ pass
342
+
343
+ # check that we have sufficient information to match the overlays
344
+ if affine is None and data .shape [:3 ] != self ._data .shape [:3 ]:
345
+ raise ValueError ('Provided `data` do not match shape of '
346
+ 'underlay and no `affine` matrix was '
347
+ 'provided. Please provide an `affine` matrix '
348
+ 'or resample first three dims of `data` to {}'
349
+ .format (self ._data .shape [:3 ]))
350
+
351
+ # we need to resample the provided data to the already-plotted data
352
+ if not np .allclose (affine , self ._affine ):
353
+ from .processing import resample_from_to
354
+ from .nifti1 import Nifti1Image
355
+ target_shape = self ._data .shape [:3 ] + data .shape [3 :]
356
+ # we can't just use SpatialImage because we need an image type
357
+ # where the spatial axes are _always_ first
358
+ data = resample_from_to (Nifti1Image (data , affine ),
359
+ (target_shape , self ._affine )).dataobj
360
+ affine = self ._affine
361
+
362
+ if self ._overlay is not None :
363
+ # remove all images + cross hair lines
364
+ for nn , im in enumerate (self ._overlay ._ims ):
365
+ im .remove ()
366
+ for line in self ._overlay ._crosshairs [nn ].values ():
367
+ line .remove ()
368
+ # remove the fourth axis, if it was created for the overlay
369
+ if (self ._overlay .n_volumes > 1 and len (self ._overlay ._axes ) > 3
370
+ and self .n_volumes == 1 ):
371
+ a = self ._axes .pop (- 1 )
372
+ a .remove ()
373
+
374
+ # create an axis if we have a 4D overlay (vs a 3D underlay)
375
+ axes = self ._axes
376
+ o_n_volumes = int (np .prod (data .shape [3 :]))
377
+ if o_n_volumes > self .n_volumes :
378
+ axes += [axes [0 ].figure .add_subplot (224 )]
379
+ elif o_n_volumes < self .n_volumes :
380
+ axes = axes [:- 1 ]
381
+
382
+ # mask array for provided threshold
383
+ self ._overlay = self .__class__ (data , affine = affine , axes = axes )
384
+ self ._overlay .threshold = threshold
385
+
386
+ # set transparency and new cmap
387
+ self ._overlay .cmap = cmap
388
+ for im in self ._overlay ._ims :
389
+ im .set_alpha (0.7 )
390
+
391
+ # no double cross-hairs (they get confused when we have linked orthos)
392
+ for cross in self ._overlay ._crosshairs :
393
+ cross ['horiz' ].set_visible (False )
394
+ cross ['vert' ].set_visible (False )
395
+ self ._overlay ._draw ()
396
+
289
397
def link_to (self , other ):
290
398
"""Link positional changes between two canvases
291
399
@@ -413,7 +521,7 @@ def _set_position(self, x, y, z, notify=True):
413
521
idx = [slice (None )] * len (self ._axes )
414
522
for ii in range (3 ):
415
523
idx [self ._order [ii ]] = self ._data_idx [ii ]
416
- vdata = self ._data [tuple (idx )].ravel ()
524
+ vdata = np . asarray ( self ._data [tuple (idx )].ravel () )
417
525
vdata = np .concatenate ((vdata , [vdata [- 1 ]]))
418
526
self ._volume_ax_objs ['patch' ].set_x (self ._data_idx [3 ] - 0.5 )
419
527
self ._volume_ax_objs ['step' ].set_ydata (vdata )
0 commit comments