Skip to content

Commit 989ac26

Browse files
committed
NF: Adds overlay option in OrthoSlicer3D
Beginning of functionality to allow overlays in OrthoSlicer3D
1 parent 516434c commit 989ac26

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:
@@ -285,6 +288,111 @@ def clim(self, clim):
285288
self._clim = tuple(clim)
286289
self.draw()
287290

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

0 commit comments

Comments
 (0)