Skip to content

Commit 714e905

Browse files
committed
NF: Adds overlay option in OrthoSlicer3D
Beginning of functionality to allow overlays in OrthoSlicer3D
1 parent e2f50b4 commit 714e905

File tree

1 file changed

+109
-1
lines changed

1 file changed

+109
-1
lines changed

nibabel/viewers.py

+109-1
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,9 @@ def __init__(self, data, affine=None, axes=None, title=None):
6969
self._title = title
7070
self._closed = False
7171
self._cross = True
72+
self._overlay = None
73+
self._threshold = None
74+
self._alpha = 1
7275

7376
data = np.asanyarray(data)
7477
if data.ndim < 3:
@@ -286,6 +289,111 @@ def clim(self, clim):
286289
self._clim = tuple(clim)
287290
self.draw()
288291

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+
289397
def link_to(self, other):
290398
"""Link positional changes between two canvases
291399
@@ -413,7 +521,7 @@ def _set_position(self, x, y, z, notify=True):
413521
idx = [slice(None)] * len(self._axes)
414522
for ii in range(3):
415523
idx[self._order[ii]] = self._data_idx[ii]
416-
vdata = self._data[tuple(idx)].ravel()
524+
vdata = np.asarray(self._data[tuple(idx)].ravel())
417525
vdata = np.concatenate((vdata, [vdata[-1]]))
418526
self._volume_ax_objs['patch'].set_x(self._data_idx[3] - 0.5)
419527
self._volume_ax_objs['step'].set_ydata(vdata)

0 commit comments

Comments
 (0)