Source code for fsleyes.plugins.profiles.orthoannotateprofile

#
# orthoannotateprofile.py - The OrthoAnnotateProfile
#
# Author: Paul McCarthy <pauldmccarthy@gmail.com>
#
"""This module provides the :class:`OrthoAnnotateProfile` class, an
interaction :class:`.Profile` for :class:`.OrthoPanel` views.
"""

import copy

import numpy as np
import          wx

import fsleyes_widgets.utils.status      as status
import fsleyes_widgets.dialog            as fsldlg
import fsleyes.strings                   as strings
import fsleyes.gl.annotations            as annotations
import fsleyes.profiles.orthoviewprofile as orthoviewprofile


[docs]class OrthoAnnotateProfile(orthoviewprofile.OrthoViewProfile): """The ``OrthoAnnotateProfile`` class is a :class:`.Profile` for the :class:`.OrthoPanel` class, which allows the user to annotate the canvases of an ``OrthoPanel`` with simple shapes and text. """ colour = copy.copy(annotations.AnnotationObject.colour) """Initial colour to give all annotations. """ lineWidth = copy.copy(annotations.AnnotationObject.lineWidth) """Initial width to give line-based annotations. """ fontSize = copy.copy(annotations.TextAnnotation.fontSize) """Initial font size to give text annotations. """ filled = copy.copy(annotations.Rect.filled) """Whether ellipses/rectangles are filled in or not.""" border = copy.copy(annotations.Rect.border) """Whether ellipses/rectangles are drawn with a border or not.""" honourZLimits = copy.copy(annotations.AnnotationObject.honourZLimits) """Whether annotations are drawn when outside their Z limits.""" alpha = copy.copy(annotations.AnnotationObject.alpha) """Opacity."""
[docs] @staticmethod def tempModes(): """Returns the temporary mode map for the ``OrthoAnnotateProfile``, which controls the use of modifier keys to temporarily enter other interaction modes. """ return { ('line', wx.WXK_SHIFT) : 'nav', ('line', wx.WXK_CONTROL) : 'move', ('line', wx.WXK_ALT) : 'pan', ('line', (wx.WXK_CONTROL, wx.WXK_SHIFT)) : 'slice', ('point', wx.WXK_SHIFT) : 'nav', ('point', wx.WXK_CONTROL) : 'move', ('point', wx.WXK_ALT) : 'pan', ('point', (wx.WXK_CONTROL, wx.WXK_SHIFT)) : 'slice', ('rect', wx.WXK_SHIFT) : 'nav', ('rect', wx.WXK_CONTROL) : 'move', ('rect', wx.WXK_ALT) : 'pan', ('rect', (wx.WXK_CONTROL, wx.WXK_SHIFT)) : 'slice', ('text', wx.WXK_SHIFT) : 'nav', ('text', wx.WXK_CONTROL) : 'move', ('text', wx.WXK_ALT) : 'pan', ('text', (wx.WXK_CONTROL, wx.WXK_SHIFT)) : 'slice', ('ellipse', wx.WXK_SHIFT) : 'nav', ('ellipse', wx.WXK_CONTROL) : 'move', ('ellipse', wx.WXK_ALT) : 'pan', ('ellipse', (wx.WXK_CONTROL, wx.WXK_SHIFT)) : 'slice', ('arrow', wx.WXK_SHIFT) : 'nav', ('arrow', wx.WXK_CONTROL) : 'move', ('arrow', wx.WXK_ALT) : 'pan', ('arrow', (wx.WXK_CONTROL, wx.WXK_SHIFT)) : 'slice'}
[docs] @staticmethod def altHandlers(): """Returns the alternate handlers map, which allows event handlers defined in one mode to be re-used whilst in another mode. """ return { ('line', 'MiddleMouseDrag') : ('pan', 'LeftMouseDrag'), ('point', 'MiddleMouseDrag') : ('pan', 'LeftMouseDrag'), ('rect', 'MiddleMouseDrag') : ('pan', 'LeftMouseDrag'), ('text', 'MiddleMouseDrag') : ('pan', 'LeftMouseDrag'), ('arrow', 'MiddleMouseDrag') : ('pan', 'LeftMouseDrag'), ('ellipse', 'MiddleMouseDrag') : ('pan', 'LeftMouseDrag'), ('move', 'MouseWheel') : ('zoom', 'MouseWheel'), # Right mouse click/drag allows # annotations to be moved ('nav', 'RightMouseDown') : ('move', 'LeftMouseDown'), ('line', 'RightMouseDown') : ('move', 'LeftMouseDown'), ('point', 'RightMouseDown') : ('move', 'LeftMouseDown'), ('rect', 'RightMouseDown') : ('move', 'LeftMouseDown'), ('text', 'RightMouseDown') : ('move', 'LeftMouseDown'), ('arrow', 'RightMouseDown') : ('move', 'LeftMouseDown'), ('ellipse', 'RightMouseDown') : ('move', 'LeftMouseDown'), ('nav', 'RightMouseDrag') : ('move', 'LeftMouseDrag'), ('line', 'RightMouseDrag') : ('move', 'LeftMouseDrag'), ('point', 'RightMouseDrag') : ('move', 'LeftMouseDrag'), ('rect', 'RightMouseDrag') : ('move', 'LeftMouseDrag'), ('text', 'RightMouseDrag') : ('move', 'LeftMouseDrag'), ('arrow', 'RightMouseDrag') : ('move', 'LeftMouseDrag'), ('ellipse', 'RightMouseDrag') : ('move', 'LeftMouseDrag'), ('nav', 'RightMouseUp') : ('move', 'LeftMouseUp'), ('line', 'RightMouseUp') : ('move', 'LeftMouseUp'), ('point', 'RightMouseUp') : ('move', 'LeftMouseUp'), ('rect', 'RightMouseUp') : ('move', 'LeftMouseUp'), ('text', 'RightMouseUp') : ('move', 'LeftMouseUp'), ('arrow', 'RightMouseUp') : ('move', 'LeftMouseUp'), ('ellipse', 'RightMouseUp') : ('move', 'LeftMouseUp')}
[docs] def __init__(self, viewPanel, overlayList, displayCtx): """Create an ``OrthoAnnotateProfile``. :arg viewPanel: An :class:`.OrthoPanel` instance. :arg overlayList: The :class:`.OverlayList` instance. :arg displayCtx: The :class:`.DisplayContext` instance. """ orthoviewprofile.OrthoViewProfile.__init__( self, viewPanel, overlayList, displayCtx, ['line', 'arrow', 'point', 'rect', 'text', 'ellipse', 'move']) self.mode = 'nav' # Used to store a reference to an annotation # and previous mouse location during mouse # drags. self.__dragging = None self.__lastPos = None
def __initialSettings(self, canvas, canvasPos): """Returns a dictionary containing some initial settings with which all new annotations are created. """ opts = canvas.opts zpos = canvasPos[opts.zax] return { 'colour' : self.colour, 'lineWidth' : self.lineWidth, 'fontSize' : self.fontSize, 'filled' : self.filled, 'border' : self.border, 'alpha' : self.alpha, 'honourZLimits' : self.honourZLimits, 'zmin' : np.floor(zpos), 'zmax' : np.ceil( zpos), 'hold' : True, 'fixed' : False } def __displaySize(self, size, squared): """Display the given size (length or area) in the :class:`.FSLeyesFrame` status bar. :arg size: Size to display :arg squared: If ``True``, ^2 is shown after the size value (use if the size is an area). """ displayCtx = self.displayCtx opts = displayCtx.getOpts(displayCtx.getSelectedOverlay()) refimage = opts.referenceImage if refimage is not None: units = refimage.xyzUnits units = strings.nifti.get(('xyz_unit', units), '(unknown units)') if squared: units = f'{units}\u00B2' size = f'{size:.2f} {units}' else: size = f'{size:.2f}' status.update(size)
[docs] def _moveModeLeftMouseDown(self, ev, canvas, mousePos, canvasPos): """If the mouse lands on an annotation, save a reference to it so it can be moved on mouse drag. """ opts = canvas.opts annot = canvas.getAnnotations() pos = canvasPos[opts.xax], canvasPos[opts.yax] for obj in annot.annotations: try: if obj.hit(*pos): self.__dragging = obj self.__lastPos = pos break except NotImplementedError: pass
[docs] def _moveModeLeftMouseDrag(self, ev, canvas, mousePos, canvasPos): """Move the annotation that was clicked on. """ obj = self.__dragging lastPos = self.__lastPos if obj is None: return opts = canvas.opts pos = (canvasPos[opts.xax], canvasPos[opts.yax]) offset = (pos[0] - lastPos[0], pos[1] - lastPos[1]) try: obj.move(*offset) self.__lastPos = pos except NotImplementedError: pass canvas.Refresh()
[docs] def _moveModeLeftMouseUp(self, ev, canvas, mousePos, canvasPos): """Clears the reference to the annotation that was being moved. """ self.__dragging = None self.__lastPos = None
[docs] def _lineModeLeftMouseDown(self, ev, canvas, mousePos, canvasPos): """Adds a new line annotation.""" opts = canvas.opts annot = canvas.getAnnotations() x, y = (canvasPos[opts.xax], canvasPos[opts.yax]) settings = self.__initialSettings(canvas, canvasPos) self.__dragging = annot.line(x, y, x, y, **settings)
[docs] def _lineModeLeftMouseDrag(self, ev, canvas, mousePos, canvasPos): """Adjust the line end point so it tracks the mouse location.""" opts = canvas.opts line = self.__dragging line.x2 = canvasPos[opts.xax] line.y2 = canvasPos[opts.yax] # display line length in the # FSLeyesFrame status bar xy1 = np.array([line.x1, line.y1]) xy2 = np.array([line.x2, line.y2]) length = np.sqrt(np.sum((xy1 - xy2) ** 2)) self.__displaySize(length, False) canvas.Refresh()
[docs] def _lineModeLeftMouseUp(self, ev, canvas, mousePos, canvasPos): """Clear a reference to the newly created line. If the mouse hasn't moved since mouse down, the line is deleted. """ line = self.__dragging annot = canvas.getAnnotations() self.__dragging = None if (line.x1 == line.x2 and line.y1 == line.y2): annot.dequeue(line, hold=True) canvas.Refresh()
[docs] def _arrowModeLeftMouseDown(self, ev, canvas, mousePos, canvasPos): """Adds a new arrow annotation.""" opts = canvas.opts annot = canvas.getAnnotations() x, y = (canvasPos[opts.xax], canvasPos[opts.yax]) settings = self.__initialSettings(canvas, canvasPos) self.__dragging = annot.arrow(x, y, x, y, **settings)
[docs] def _arrowModeLeftMouseDrag(self, ev, canvas, mousePos, canvasPos): """Adjust the arrow end point so it tracks the mouse location.""" opts = canvas.opts arrow = self.__dragging arrow.x2 = canvasPos[opts.xax] arrow.y2 = canvasPos[opts.yax] # display arrow length in the # FSLeyesFrame status bar xy1 = np.array([arrow.x1, arrow.y1]) xy2 = np.array([arrow.x2, arrow.y2]) length = np.sqrt(np.sum((xy1 - xy2) ** 2)) self.__displaySize(length, False) canvas.Refresh()
[docs] def _arrowModeLeftMouseUp(self, ev, canvas, mousePos, canvasPos): """Clear a reference to the newly created arrow. If the mouse hasn't moved since mouse down, the arrow is deleted. """ arrow = self.__dragging annot = canvas.getAnnotations() self.__dragging = None if (arrow.x1 == arrow.x2 and arrow.y1 == arrow.y2): annot.dequeue(arrow, hold=True) canvas.Refresh()
[docs] def _pointModeLeftMouseDown(self, ev, canvas, mousePos, canvasPos): """Creates a new point annotation. """ opts = canvas.opts annot = canvas.getAnnotations() x, y = (canvasPos[opts.xax], canvasPos[opts.yax]) settings = self.__initialSettings(canvas, canvasPos) self.__dragging = annot.point(x, y, **settings) canvas.Refresh()
[docs] def _pointModeLeftMouseDrag(self, ev, canvas, mousePos, canvasPos): """Changes the location of the point annotation to track the mouse drag location. """ opts = canvas.opts self.__dragging.x = canvasPos[opts.xax] self.__dragging.y = canvasPos[opts.yax] canvas.Refresh()
[docs] def _pointModeLeftMouseUp(self, ev, canvas, mousePos, canvasPos): """Clear a reference to the newly created point annotation. """ self.__dragging = None
[docs] def _textModeLeftMouseUp(self, ev, canvas, mousePos, canvasPos): """Show a dialog prompting the user for some text, then creates a new text annotation. """ opts = canvas.opts annot = canvas.getAnnotations() settings = self.__initialSettings(canvas, canvasPos) x, y = (canvasPos[opts.xax], canvasPos[opts.yax]) msg = strings.messages[self, 'TextAnnotation'] dlg = fsldlg.TextEditDialog(self.viewPanel, message=msg, style=fsldlg.TED_OK_CANCEL) if dlg.ShowModal() == wx.ID_OK: annot.text(dlg.GetText(), x, y, coordinates='display', **settings) canvas.Refresh()
[docs] def _rectModeLeftMouseDown(self, ev, canvas, mousePos, canvasPos): """Create a new rectangle annotation. """ opts = canvas.opts annot = canvas.getAnnotations() x, y = (canvasPos[opts.xax], canvasPos[opts.yax]) settings = self.__initialSettings(canvas, canvasPos) self.__dragging = annot.rect(x, y, 0, 0, **settings)
[docs] def _rectModeLeftMouseDrag(self, ev, canvas, mousePos, canvasPos): """Adjust the size of the rectangle with the mouse drag. """ opts = canvas.opts rect = self.__dragging rect.w = canvasPos[opts.xax] - rect.x rect.h = canvasPos[opts.yax] - rect.y # display rect area in status bar self.__displaySize(np.abs(rect.w * rect.h), True) canvas.Refresh()
[docs] def _rectModeLeftMouseUp(self, ev, canvas, mousePos, canvasPos): """Clear the reference to the new rectangle annotation. If the rectangle has no area (the user clicked without dragging), the rectangle is deleted. """ rect = self.__dragging annot = canvas.getAnnotations() self.__dragging = None if rect.w == 0 or rect.h == 0: annot.dequeue(rect, hold=True) canvas.Refresh()
[docs] def _ellipseModeLeftMouseDown(self, ev, canvas, mousePos, canvasPos): """Create a new ellipse annotation. """ opts = canvas.opts annot = canvas.getAnnotations() x, y = (canvasPos[opts.xax], canvasPos[opts.yax]) settings = self.__initialSettings(canvas, canvasPos) self.__dragging = annot.ellipse(x, y, 0, 0, **settings)
[docs] def _ellipseModeLeftMouseDrag(self, ev, canvas, mousePos, canvasPos): """Adjust the ellipse radius with the mouse drag. """ opts = canvas.opts ellipse = self.__dragging p1 = np.array((ellipse.x, ellipse.y)) p2 = np.array((canvasPos[opts.xax], canvasPos[opts.yax])) ellipse.w = np.abs(p1[0] - p2[0]) ellipse.h = np.abs(p1[1] - p2[1]) # display ellipse area in status bar self.__displaySize(np.pi * ellipse.w * ellipse.h, True) canvas.Refresh()
[docs] def _ellipseModeLeftMouseUp(self, ev, canvas, mousePos, canvasPos): """Clear the reference to the new ellipse annotation. If the ellipse has no area (the user clicked without dragging), the ellipse is deleted. """ ellipse = self.__dragging annot = canvas.getAnnotations() self.__dragging = None if (ellipse.w == 0) or (ellipse.h == 0): annot.dequeue(ellipse, hold=True) canvas.Refresh()