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