Something went wrong on our end
Forked from
FSL / fslpy
3234 commits behind the upstream repository.
-
Paul McCarthy authoredPaul McCarthy authored
orthoeditprofile.py 30.24 KiB
#!/usr/bin/env python
#
# orthoeditprofile.py - The OrthoEditProfile class.
#
# Author: Paul McCarthy <pauldmccarthy@gmail.com>
#
"""This module provides the :class:`OrthoEditProfile` class, an interaction
:class:`.Profile` for :class:`.OrthoPanel` views.
"""
import logging
import numpy as np
import props
import fsl.data.image as fslimage
import fsl.fsleyes.editor.editor as editor
import fsl.fsleyes.gl.annotations as annotations
import orthoviewprofile
log = logging.getLogger(__name__)
class OrthoEditProfile(orthoviewprofile.OrthoViewProfile):
"""The ``OrthoEditProfile`` class is an interaction profile for use with
the :class:`.OrthoPanel` class. It gives the user the ability to make
changes to :class:`.Image` overlays, by using the functionality of the
:mod:`~fsl.fsleyes.editor` package.
**Modes**
The ``OrthoEditProfile`` has the following modes, in addition to those
already defined by the :class:`.OrthoViewProfile`:
========== ===============================================================
``sel`` Select mode. The user is able to manually add voxels to the
selection using a *cursor*. The cursor size can be changed
with the :attr:`selectionSize` property, and the cursor can be
toggled between a 2D square and a 3D cube via the
:attr:`selectionIs3D` property.
``desel`` Deselect mode. Identical to ``sel`` mode, except that the
cursor is used to remove voxels from the selection.
``selint`` Select by intensity mode.
========== ===============================================================
**Actions**
The ``OrthoEditProfile`` defines the following actions, on top of those
already defined by the :class:`.OrthoViewProfile`:
=========================== ============================================
``undo`` Un-does the most recent action.
``redo`` Re-does the most recent undone action.
``fillSelection`` Fills the current selection with the current
:attr:`fillValue`.
``clearSelection`` Clears the current selection.
``createMaskFromSelection`` Creates a mask :class:`.Image` from the
current selection.
``createROIFromSelection`` Creates a ROI :class:`.Image` from the
current selection.
=========================== ============================================
**Annotations**
The ``OrthoEditProfile`` class uses :mod:`.annotations` on the
:class:`.SliceCanvas` panels, displayed in the :class:`.OrthoPanel`,
to display information to the user. Two annotations are used:
- The *cursor* annotation. This is a :class:`.Rect` annotation
representing a cursor at the voxel, or voxels, underneath the
current mouse location.
- The *selection* annotation. This is a :class:`.VoxelSelection`
annotation which displays the :class:`.Selection`.
"""
selectionCursorColour = props.Colour(default=(1, 1, 0, 0.7))
"""Colour used for the cursor annotation. """
selectionOverlayColour = props.Colour(default=(1, 0, 1, 0.7))
"""Colour used for the selection annotation, which displays the voxels
that are currently selected.
"""
selectionSize = props.Int(minval=1, default=3, clamped=True)
"""In ``sel`` and ``desel`` modes, defines the size of the selection
cursor.
"""
selectionIs3D = props.Boolean(default=False)
"""In ``sel`` and ``desel`` mode, toggles the cursor between a 2D square
and a 3D cube.
"""
fillValue = props.Real(default=0)
"""The value used by the ``fillSelection`` action - all voxels in the
selection will be filled with this value.
"""
intensityThres = props.Real(minval=0.0, default=10, clamped=True)
"""In ``selint`` mode, the maximum distance, in intensity, that a voxel
can be from the seed location, in order for it to be selected.
Passed as the ``precision`` argument to the
:meth:`.Selection.selectByValue` method.
"""
localFill = props.Boolean(default=False)
"""In ``selint`` mode, if this property is ``True``, voxels can only be
selected if they are adjacent to an already selected voxel. Passed as the
``local`` argument to the :meth:`.Selection.selectByValue` method.
"""
limitToRadius = props.Boolean(default=False)
"""In ``selint`` mode, if this property is ``True``, the search region
will be limited to a sphere (in the voxel coordinate system) with its
radius specified by the :attr:`searchRadius` property.
"""
searchRadius = props.Real(minval=0.0, default=0.0, clamped=True)
"""In ``selint`` mode, if :attr:`limitToRadius` is true, this property
specifies the search sphere radius. Passed as the ``searchRadius``
argument to the :meth:`.Selection.selectByValue` method.
"""
def __init__(self, viewPanel, overlayList, displayCtx):
"""Create an ``OrthoEditProfile``.
:arg viewPanel: The :class:`.OrthoPanel` instance.
:arg overlayList: The :class:`.OverlayList` instance.
:arg displayCtx: The :class:`.DisplayContext` instance.
"""
self.__editor = editor.Editor(overlayList, displayCtx)
self.__xcanvas = viewPanel.getXCanvas()
self.__ycanvas = viewPanel.getYCanvas()
self.__zcanvas = viewPanel.getZCanvas()
self.__selAnnotation = None
self.__xCursorAnnotation = None
self.__yCursorAnnotation = None
self.__zCursorAnnotation = None
self.__selecting = False
self.__lastDist = None
self.__currentOverlay = None
actions = {
'undo' : self.undo,
'redo' : self.redo,
'fillSelection' : self.fillSelection,
'clearSelection' : self.clearSelection,
'createMaskFromSelection' : self.__editor.createMaskFromSelection,
'createROIFromSelection' : self.__editor.createROIFromSelection}
orthoviewprofile.OrthoViewProfile.__init__(
self,
viewPanel,
overlayList,
displayCtx,
['sel', 'desel', 'selint'],
actions)
self.mode = 'sel'
displayCtx .addListener('selectedOverlay',
self._name,
self.__selectedOverlayChanged)
overlayList.addListener('overlays',
self._name,
self.__selectedOverlayChanged)
self.__editor.addListener('canUndo',
self._name,
self.__undoStateChanged)
self.__editor.addListener('canRedo',
self._name,
self.__undoStateChanged)
self.addListener('selectionOverlayColour',
self._name,
self.__selectionColoursChanged)
self.addListener('selectionCursorColour',
self._name,
self.__selectionColoursChanged)
self.__selectedOverlayChanged()
self.__selectionChanged()
self.__undoStateChanged()
def destroy(self):
"""Removes some property listeners, destroys the :class:`.Editor`
instance, and calls :meth:`.OrthoViewProfile.destroy`.
"""
self._displayCtx .removeListener('selectedOverlay', self._name)
self._overlayList.removeListener('overlays', self._name)
self.__editor .removeListener('canUndo', self._name)
self.__editor .removeListener('canRedo', self._name)
self.__editor.destroy()
self.__editor = None
orthoviewprofile.OrthoViewProfile.destroy(self)
def deregister(self):
"""Destroys all :mod:`.annotations`, and calls
:meth:`.OrthoViewProfile.deregister`.
"""
if self.__selAnnotation is not None:
sa = self.__selAnnotation
self.__xcanvas.getAnnotations().dequeue(sa, hold=True)
self.__ycanvas.getAnnotations().dequeue(sa, hold=True)
self.__zcanvas.getAnnotations().dequeue(sa, hold=True)
sa.destroy()
xca = self.__xCursorAnnotation
yca = self.__yCursorAnnotation
zca = self.__zCursorAnnotation
if xca is not None:
self.__xcanvas.getAnnotations().dequeue(xca, hold=True)
if yca is not None:
self.__ycanvas.getAnnotations().dequeue(yca, hold=True)
if zca is not None:
self.__zcanvas.getAnnotations().dequeue(zca, hold=True)
self.__selAnnotation = None
self.__xCursorAnnotation = None
self.__yCursorAnnotation = None
self.__zCursorAnnotation = None
orthoviewprofile.OrthoViewProfile.deregister(self)
def clearSelection(self, *a):
"""Clears the current selection. See :meth:`.Editor.clearSelection`.
"""
self.__editor.getSelection().clearSelection()
self._viewPanel.Refresh()
def fillSelection(self, *a):
"""Fills the current selection with the :attr:`fillValue`. See
:meth:`.Editor.fillSelection`.
"""
self.__editor.fillSelection(self.fillValue)
self.__editor.getSelection().clearSelection()
def undo(self, *a):
"""Un-does the most recent change to the selection or to the
:class:`.Image` data. See :meth:`.Editor.undo`.
"""
# We're disabling notification of changes to the selection
# during undo/redo. This is because a single undo
# will probably involve multiple modifications to the
# selection (as changes are grouped by the editor),
# with each of those changes causing the selection object
# to notify its listeners. As one of these listeners is a
# SelectionTexture, these notifications can get expensive,
# due to updates to the GL texture buffer. So we disable
# notification, and then manually refresh the texture
# afterwards
self.__editor.getSelection().disableNotification('selection')
self.__editor.undo()
self.__editor.getSelection().enableNotification('selection')
self.__selectionChanged()
self.__selAnnotation.texture.refresh()
self._viewPanel.Refresh()
def redo(self, *a):
"""Re-does the most recent undone change to the selection or to the
:class:`.Image` data. See :meth:`.Editor.redo`.
"""
self.__editor.getSelection().disableNotification('selection')
self.__editor.redo()
self.__editor.getSelection().enableNotification('selection')
self.__selectionChanged()
self.__selAnnotation.texture.refresh()
self._viewPanel.Refresh()
def __undoStateChanged(self, *a):
"""Called when either of the :attr:`.Editor.canUndo` or
:attr:`.Editor.canRedo` states change. Updates the state of the
``undo``/``redo`` actions accordingly.
"""
self.enable('undo', self.__editor.canUndo)
self.enable('redo', self.__editor.canRedo)
def __selectionColoursChanged(self, *a):
"""Called when either of the :attr:`selectionOverlayColour` or
:attr:`selectionCursorColour` properties change.
Updates the :mod:`.annotations` colours accordingly.
"""
if self.__selAnnotation is not None:
self.__selAnnotation.colour = self.selectionOverlayColour
if self.__xCursorAnnotation is not None:
self.__xCursorAnnotation.colour = self.selectionCursorColour
if self.__yCursorAnnotation is not None:
self.__yCursorAnnotation.colour = self.selectionCursorColour
if self.__zCursorAnnotation is not None:
self.__zCursorAnnotation.colour = self.selectionCursorColour
def __selectedOverlayChanged(self, *a):
"""Called when either the :class:`.OverlayList` or
:attr:`.DisplayContext.selectedOverlay` change.
Destroys all old :mod:`.annotations`. If the newly selected overlay is
an :class:`Image`, new annotations are created.
"""
overlay = self._displayCtx.getSelectedOverlay()
selection = self.__editor.getSelection()
xannot = self.__xcanvas.getAnnotations()
yannot = self.__ycanvas.getAnnotations()
zannot = self.__zcanvas.getAnnotations()
# If the selected overlay hasn't changed,
# we don't need to do anything
if overlay == self.__currentOverlay:
return
# If there's already an existing
# selection object, clear it
if self.__selAnnotation is not None:
xannot.dequeue(self.__selAnnotation, hold=True)
yannot.dequeue(self.__selAnnotation, hold=True)
zannot.dequeue(self.__selAnnotation, hold=True)
self.__selAnnotation.destroy()
xca = self.__xCursorAnnotation
yca = self.__yCursorAnnotation
zca = self.__zCursorAnnotation
if xca is not None: xannot.dequeue(xca, hold=True)
if yca is not None: yannot.dequeue(yca, hold=True)
if zca is not None: yannot.dequeue(xca, hold=True)
self.__xCursorAnnotation = None
self.__yCursorAnnotation = None
self.__zCursorAnnotation = None
self.__selAnnotation = None
self.__currentOverlay = overlay
# If there is no selected overlay (the overlay
# list is empty), don't do anything.
if overlay is None:
return
display = self._displayCtx.getDisplay(overlay)
opts = display.getDisplayOpts()
# Edit mode is only supported on images with
# the 'volume' type, in 'id' or 'pixdim'
# transformation for the time being
if not isinstance(overlay, fslimage.Image) or \
display.overlayType != 'volume' or \
opts.transform not in ('id', 'pixdim'):
self.__currentOverlay = None
log.warn('Editing is only possible on volume '
'images, in ID or pixdim space.')
return
# Otherwise, create a selection annotation
# and queue it on the canvases for drawing
selection.addListener('selection', self._name, self.__selectionChanged)
self.__selAnnotation = annotations.VoxelSelection(
selection,
opts.getTransform('display', 'voxel'),
opts.getTransform('voxel', 'display'),
colour=self.selectionOverlayColour)
kwargs = {'colour' : self.selectionCursorColour,
'width' : 2}
xca = annotations.Rect((0, 0), 0, 0, **kwargs)
yca = annotations.Rect((0, 0), 0, 0, **kwargs)
zca = annotations.Rect((0, 0), 0, 0, **kwargs)
self.__xCursorAnnotation = xca
self.__yCursorAnnotation = yca
self.__zCursorAnnotation = zca
xannot.obj(self.__selAnnotation, hold=True)
yannot.obj(self.__selAnnotation, hold=True)
zannot.obj(self.__selAnnotation, hold=True)
xannot.obj(self.__xCursorAnnotation, hold=True)
yannot.obj(self.__yCursorAnnotation, hold=True)
zannot.obj(self.__zCursorAnnotation, hold=True)
self._viewPanel.Refresh()
def __selectionChanged(self, *a):
"""Called when the :attr:`.Selection.selection` is changed.
Toggles action enabled states depending on the size of the selection.
"""
selection = self.__editor.getSelection()
# TODO This is a big performance bottleneck, as
# it gets called on every mouse position
# change when mouse-dragging. The Selection
# object could cache its size? Or perhaps
# these actions could be toggled at the
# start/end of a mouse drag?
selSize = selection.getSelectionSize()
self.enable('createMaskFromSelection', selSize > 0)
self.enable('createROIFromSelection', selSize > 0)
self.enable('clearSelection', selSize > 0)
self.enable('fillSelection', selSize > 0)
def __getVoxelLocation(self, canvasPos):
"""Returns the voxel location, for the currently selected overlay,
which corresponds to the specified canvas position.
"""
opts = self._displayCtx.getOpts(self.__currentOverlay)
voxel = opts.transformCoords([canvasPos], 'display', 'voxel')[0]
return np.int32(np.round(voxel))
def __drawCursorAnnotation(self, canvas, voxel, blockSize=None):
"""Draws the cursor annotation. Highlights the specified voxel with a
:class:`~fsl.fsleyes.gl.annotations.Rect` annotation.
This is used by mouse motion event handlers, so the user can
see the possible selection, and thus what would happen if they
were to click.
:arg canvas: The :class:`.SliceCanvas` on which to make the
annotation.
:arg voxel: Voxel which is at the centre of the cursor.
:arg blockSize: Size of the cursor square/cube.
"""
opts = self._displayCtx.getOpts(self.__currentOverlay)
canvases = [self.__xcanvas, self.__ycanvas, self.__zcanvas]
cursors = [self.__xCursorAnnotation,
self.__yCursorAnnotation,
self.__zCursorAnnotation]
# If we are running in a low
# performance mode, the cursor
# is only drawn on the current
# canvas.
if self._viewPanel.getSceneOptions().performance < 5:
cursors = [cursors[canvases.index(canvas)]]
canvases = [canvas]
if blockSize is None:
blockSize = self.selectionSize
# Figure out the selection
# boundary coordinates
lo = [(v) - int(np.floor((blockSize - 1) / 2.0)) for v in voxel]
hi = [(v + 1) + int(np.ceil(( blockSize - 1) / 2.0)) for v in voxel]
if not self.selectionIs3D:
lo[canvas.zax] = voxel[canvas.zax]
hi[canvas.zax] = voxel[canvas.zax] + 1
corners = np.zeros((8, 3))
corners[0, :] = lo[0], lo[1], lo[2]
corners[1, :] = lo[0], lo[1], hi[2]
corners[2, :] = lo[0], hi[1], lo[2]
corners[3, :] = lo[0], hi[1], hi[2]
corners[4, :] = hi[0], lo[1], lo[2]
corners[5, :] = hi[0], lo[1], hi[2]
corners[6, :] = hi[0], hi[1], lo[2]
corners[7, :] = hi[0], hi[1], hi[2]
# We want the selection to follow voxel
# edges, but the transformCoords method
# will map voxel coordinates to the
# displayed voxel centre. So we offset
# by -0.5 to get the corners.
#
# (Assuming here that the image is
# displayed in id/pixdim space)
corners = opts.transformCoords(corners - 0.5, 'voxel', 'display')
cmin = corners.min(axis=0)
cmax = corners.max(axis=0)
for cursor, canvas in zip(cursors, canvases):
xax = canvas.xax
yax = canvas.yax
zax = canvas.zax
if canvas.pos.z < cmin[zax] or canvas.pos.z > cmax[zax]:
cursor.w = 0
cursor.h = 0
continue
cursor.xy = cmin[[xax, yax]]
cursor.w = cmax[xax] - cmin[xax]
cursor.h = cmax[yax] - cmin[yax]
def __applySelection(self, canvas, voxel, add=True):
"""Called by ``sel`` mode mouse handlers. Adds/removes a block
of voxels, centred at the specified voxel, to/from the current
:class:`.Selection`.
:arg canvas: The source :class:`.SliceCanvas`.
:arg voxel: Coordinates of centre voxel.
:arg add: If ``True`` a block is added to the selection,
otherwise it is removed.
"""
if self.selectionIs3D: axes = (0, 1, 2)
else: axes = (canvas.xax, canvas.yax)
selection = self.__editor.getSelection()
block, offset = selection.generateBlock(voxel,
self.selectionSize,
selection.selection.shape,
axes)
if add: selection.addToSelection( block, offset)
else: selection.removeFromSelection(block, offset)
def __refreshCanvases(self, ev, canvas, mousePos=None, canvasPos=None):
"""Called by mouse event handlers.
If the current :class:`.ViewPanel` performance setting (see
:attr:`.SceneOpts.performance`) is at its maximum, all three
:class:`.OrthoPanel` :class:`.SliceCanvas` canvases are refreshed
on selection updates.
On all lower performance settings, only the source canvas is updated.
"""
perf = self._viewPanel.getSceneOptions().performance
if perf == 5:
if mousePos is None or canvasPos is None:
self._viewPanel.Refresh()
# If running in high performance mode, we make
# the canvas location track the edit cursor
# location, so that the other two canvases
# update to display the current cursor location.
else:
self._navModeLeftMouseDrag(ev, canvas, mousePos, canvasPos)
else:
canvas.Refresh()
def _selModeMouseWheel(self, ev, canvas, wheelDir, mousePos, canvasPos):
"""Handles mouse wheel events in ``sel`` mode.
Increases/decreases the current :attr:`selectionSize`.
"""
if wheelDir > 0: self.selectionSize += 1
elif wheelDir < 0: self.selectionSize -= 1
voxel = self.__getVoxelLocation(canvasPos)
self.__drawCursorAnnotation(canvas, voxel)
self.__refreshCanvases(ev, canvas)
def _selModeMouseMove(self, ev, canvas, mousePos, canvasPos):
"""Handles mouse motion events in ``sel`` mode.
Draws a cursor annotation at the current mouse location
(see :meth:`__draweCursorAnnotation`).
"""
voxel = self.__getVoxelLocation(canvasPos)
self.__drawCursorAnnotation(canvas, voxel)
self.__refreshCanvases(ev, canvas)
def _selModeLeftMouseDown(self, ev, canvas, mousePos, canvasPos):
"""Handles mouse down events in ``sel`` mode.
Starts an :class:`.Editor` change group, and adds to the current
:class:`Selection`.
"""
self.__editor.startChangeGroup()
voxel = self.__getVoxelLocation(canvasPos)
self.__applySelection( canvas, voxel)
self.__drawCursorAnnotation(canvas, voxel)
self.__refreshCanvases(ev, canvas, mousePos, canvasPos)
def _selModeLeftMouseDrag(self, ev, canvas, mousePos, canvasPos):
"""Handles mouse drag events in ``sel`` mode.
Adds to the current :class:`Selection`.
"""
voxel = self.__getVoxelLocation(canvasPos)
self.__applySelection( canvas, voxel)
self.__drawCursorAnnotation(canvas, voxel)
self.__refreshCanvases(ev, canvas, mousePos, canvasPos)
def _selModeLeftMouseUp(self, ev, canvas, mousePos, canvasPos):
"""Handles mouse up events in ``sel`` mode.
Ends the :class:`.Editor` change group that was started in the
:meth:`_selModeLeftMouseDown` method.
"""
self.__editor.endChangeGroup()
self._viewPanel.Refresh()
def _selModeMouseLeave(self, ev, canvas, mousePos, canvasPos):
"""Handles mouse leave events in ``sel`` mode. Makes sure that the
selection cursor annotation is not shown on any canvas.
"""
cursors = [self.__xCursorAnnotation,
self.__yCursorAnnotation,
self.__zCursorAnnotation]
for cursor in cursors:
if cursor is not None:
cursor.w = 0
cursor.h = 0
self.__refreshCanvases(ev, canvas)
def _deselModeLeftMouseDown(self, ev, canvas, mousePos, canvasPos):
"""Handles mouse down events in ``desel`` mode.
Starts an :class:`.Editor` change group, and removes from the current
:class:`Selection`.
"""
self.__editor.startChangeGroup()
voxel = self.__getVoxelLocation(canvasPos)
self.__applySelection( canvas, voxel, False)
self.__drawCursorAnnotation(canvas, voxel)
self.__refreshCanvases(ev, canvas, mousePos, canvasPos)
def _deselModeLeftMouseDrag(self, ev, canvas, mousePos, canvasPos):
"""Handles mouse drag events in ``desel`` mode.
Removes from the current :class:`Selection`.
"""
voxel = self.__getVoxelLocation(canvasPos)
self.__applySelection( canvas, voxel, False)
self.__drawCursorAnnotation(canvas, voxel)
self.__refreshCanvases(ev, canvas, mousePos, canvasPos)
def _deselModeLeftMouseUp(self, ev, canvas, mousePos, canvasPos):
"""Handles mouse up events in ``desel`` mode.
Ends the :class:`.Editor` change group that was started in the
:meth:`_deselModeLeftMouseDown` method.
"""
self.__editor.endChangeGroup()
self._viewPanel.Refresh()
def __selintSelect(self, voxel):
"""Selects voxels by intensity, using the specified ``voxel`` as
the seed location.
Called by the :meth:`_selintModeLeftMouseDown`,
:meth:`_selintModeLeftMouseDrag`, and and
:meth:`_selintModeLeftMouseWheel` methods. See
:meth:`.Selection.selectByValue`.
"""
overlay = self._displayCtx.getSelectedOverlay()
if not self.limitToRadius or self.searchRadius == 0:
searchRadius = None
else:
searchRadius = (self.searchRadius / overlay.pixdim[0],
self.searchRadius / overlay.pixdim[1],
self.searchRadius / overlay.pixdim[2])
# If the last selection covered a bigger radius
# than this selection, clear the whole selection
if self.__lastDist is None or \
np.any(np.array(searchRadius) < self.__lastDist):
self.__editor.getSelection().clearSelection()
self.__editor.getSelection().selectByValue(
voxel,
precision=self.intensityThres,
searchRadius=searchRadius,
local=self.localFill)
self.__lastDist = searchRadius
def _selintModeMouseMove(self, ev, canvas, mousePos, canvasPos):
"""Handles mouse motion events in ``selint`` mode. Draws a selection
annotation at the current location (see
:meth:`__drawCursorAnnotation`).
"""
voxel = self.__getVoxelLocation(canvasPos)
self.__drawCursorAnnotation(canvas, voxel, 1)
self.__refreshCanvases(ev, canvas)
def _selintModeLeftMouseDown(self, ev, canvas, mousePos, canvasPos):
"""Handles mouse down events in ``selint`` mode.
Starts an :class:`.Editor` change group, then clears the current
selection, and selects voxels by intensity (see
:meth:`__selintSelect`).
"""
self.__editor.startChangeGroup()
self.__editor.getSelection().clearSelection()
self.__selecting = True
self.__lastDist = 0
self.__selintSelect(self.__getVoxelLocation(canvasPos))
self.__refreshCanvases(ev, canvas, mousePos, canvasPos)
def _selintModeLeftMouseDrag(self, ev, canvas, mousePos, canvasPos):
"""Handles mouse drag events in ``selint`` mode.
If :attr:`limitToRadius` is ``True``, the :attr:`searchRadius` is
increased to the distance between the current mouse location, and
the mouse down location, and a select-by-intensity is re-run with
the same seed location (the mouse down location), and the new
search radius.
If ``limitToRadius`` is ``False``, a select-by-intensity is re-run
with the current mouse location. See the :meth:`__selintSelect`
method.
"""
if not self.limitToRadius:
voxel = self.__getVoxelLocation(canvasPos)
self.__drawCursorAnnotation(canvas, voxel, 1)
refreshArgs = (ev, canvas, mousePos, canvasPos)
else:
mouseDownPos, canvasDownPos = self.getMouseDownLocation()
voxel = self.__getVoxelLocation(
canvasDownPos)
cx, cy, cz = canvasPos
cdx, cdy, cdz = canvasDownPos
dist = np.sqrt((cx - cdx) ** 2 + (cy - cdy) ** 2 + (cz - cdz) ** 2)
self.searchRadius = dist
refreshArgs = (ev, canvas)
self.__selintSelect(voxel)
self.__refreshCanvases(*refreshArgs)
def _selintModeMouseWheel(self, ev, canvas, wheel, mousePos, canvasPos):
"""Handles mouse wheel events in ``selint`` mode.
If the mouse button is down, the :attr:`intensityThres` value is
decreased/increased according to the mouse wheel direction, and
select-by-intensity is re-run with the mouse-down location as
the seed location.
"""
if not self.__selecting:
return
overlay = self._displayCtx.getSelectedOverlay()
opts = self._displayCtx.getOpts(overlay)
dataRange = opts.dataMax - opts.dataMin
step = 0.01 * dataRange
if wheel > 0: self.intensityThres += step
elif wheel < 0: self.intensityThres -= step
mouseDownPos, canvasDownPos = self.getMouseDownLocation()
voxel = self.__getVoxelLocation(canvasDownPos)
self.__selintSelect(voxel)
self.__refreshCanvases(ev, canvas)
def _selintModeLeftMouseUp(self, ev, canvas, mousePos, canvasPos):
"""Handles mouse up events in ``selint`` mode. Ends the :class:`.Editor`
change group that was started in the :meth:`_selintModeLeftMouseDown`
method.
"""
self.__editor.endChangeGroup()
self.__selecting = False
self._viewPanel.Refresh()