diff --git a/doc/images/lightboxpanel.png b/doc/images/lightboxpanel.png new file mode 100644 index 0000000000000000000000000000000000000000..2d67b669f304aa7b08fb5da9c3ec5dd7ce7be8fe Binary files /dev/null and b/doc/images/lightboxpanel.png differ diff --git a/doc/images/orthopanel.png b/doc/images/orthopanel.png new file mode 100644 index 0000000000000000000000000000000000000000..c01e24a8b81929b51a0af7ddb4f4983626011546 Binary files /dev/null and b/doc/images/orthopanel.png differ diff --git a/fsl/fsleyes/colourmaps.py b/fsl/fsleyes/colourmaps.py index 67eb64f909f16ade7d6fabdbd77f08ea573b8798..e41127cb359d6fbc39c629818898958101ec06ca 100644 --- a/fsl/fsleyes/colourmaps.py +++ b/fsl/fsleyes/colourmaps.py @@ -625,8 +625,17 @@ def randomBrightColour(): def complementaryColour(rgb): """Generate a colour which can be used as a complement/opposite to the given colour. + + If the given ``rgb`` sequence contains four values, the fourth + value (e.g. alpha) is returned unchanged. """ + if len(rgb) >= 4: + a = rgb[3:] + rgb = rgb[:3] + else: + a = [] + h, l, s = colorsys.rgb_to_hls(*rgb) # My ad-hoc complementary colour calculation: @@ -646,7 +655,7 @@ def complementaryColour(rgb): nr, ng, nb = colorsys.hls_to_rgb(nh, nl, ns) - return nr, ng, nb + return [nr, ng, nb] + a _cmapDir = op.join(op.dirname(__file__), 'colourmaps') diff --git a/fsl/fsleyes/views/lightboxpanel.py b/fsl/fsleyes/views/lightboxpanel.py index cf161bfe5a7e56a591a9f4347a7b83342c5e7a6c..1211870b23883694d96312afe26e6c821813795e 100644 --- a/fsl/fsleyes/views/lightboxpanel.py +++ b/fsl/fsleyes/views/lightboxpanel.py @@ -5,13 +5,11 @@ # # Author: Paul McCarthy <pauldmccarthy@gmail.com> # -"""This module defines the :class:`LightBoxPanel, a panel which contains a -:class:`.LightBoxCanvas`, for displaying multiple slices from a collection of -overlays. +"""This module provides the :class:`LightBoxPanel`, which displays multiple +2D slices of 3D overlays. """ import logging -log = logging.getLogger(__name__) import wx @@ -22,17 +20,55 @@ import fsl.fsleyes.gl.wxgllightboxcanvas as lightboxcanvas import fsl.fsleyes.controls.lightboxtoolbar as lightboxtoolbar import fsl.fsleyes.controls.overlaydisplaytoolbar as overlaydisplaytoolbar import fsl.fsleyes.displaycontext.lightboxopts as lightboxopts -import canvaspanel +import canvaspanel + + +log = logging.getLogger(__name__) class LightBoxPanel(canvaspanel.CanvasPanel): - """Convenience Panel which contains a :class:`.LightBoxCanvas` and a - scrollbar, and sets up mouse-scrolling behaviour. + """The ``LightBoxPanel`` is a *FSLeyes view* which is capable of + displaying multiple 2D slices of the 3D overlays conatined in an + :class:`.OverlayList`. A ``LightBoxPanel`` looks something like the + following: + + .. image:: images/lightboxpanel.png + :scale: 50% + :align: center + + + The ``LightBoxPanel`` uses a :class:`.LightBoxCanvas` panel to display + the slices, and a :class:`.LightBoxOpts` instance to manage the display + settings. The canvas is accessed through the :meth:`getCanvas` and + :meth:`getGLCanvases` methods, and the ``LightBoxOpts`` instanace can + be retrieved via the :meth:`.CanvasPanel.getSceneOptions` method. + + + The ``LightBoxPanel`` adds the following actions to those already + provided by the :class:`.CanvasPanel`: + + ========================= ======================================== + ``toggleLightBoxToolBar`` Shows/hides a :class:`.LightBoxToolBar`. + ========================= ======================================== + + + When a ``LightBoxPanel`` is created, it will automatically add the + following control panels: + + .. autosummary:: + :nosignatures: + + ~fsl.fsleyes.controls.lightboxtoolbar.LightBoxToolBar + ~fsl.fsleyes.controls.overlaydisplaytoolbar.OverlayDisplayToolBar """ def __init__(self, parent, overlayList, displayCtx): - """ + """Create a ``LightBoxPanel``. + + :arg parent: A :mod:`wx` parent object. + :arg overlayList: A :class:`.OverlayList` instance. + :arg displayCtx: A :class:`.DisplayContext` instance. """ sceneOpts = lightboxopts.LightBoxOpts() @@ -49,24 +85,24 @@ class LightBoxPanel(canvaspanel.CanvasPanel): sceneOpts, actionz) - self._scrollbar = wx.ScrollBar( + self.__scrollbar = wx.ScrollBar( self.getCanvasPanel(), style=wx.SB_VERTICAL) - self._lbCanvas = lightboxcanvas.LightBoxCanvas( + self.__lbCanvas = lightboxcanvas.LightBoxCanvas( self.getCanvasPanel(), overlayList, displayCtx) - self._lbCanvas.bindProps('zax', sceneOpts) - self._lbCanvas.bindProps('bgColour', sceneOpts) - self._lbCanvas.bindProps('cursorColour', sceneOpts) - self._lbCanvas.bindProps('showCursor', sceneOpts) - self._lbCanvas.bindProps('showGridLines', sceneOpts) - self._lbCanvas.bindProps('highlightSlice', sceneOpts) - self._lbCanvas.bindProps('renderMode', sceneOpts) - self._lbCanvas.bindProps('softwareMode', sceneOpts) - self._lbCanvas.bindProps('resolutionLimit', sceneOpts) + self.__lbCanvas.bindProps('zax', sceneOpts) + self.__lbCanvas.bindProps('bgColour', sceneOpts) + self.__lbCanvas.bindProps('cursorColour', sceneOpts) + self.__lbCanvas.bindProps('showCursor', sceneOpts) + self.__lbCanvas.bindProps('showGridLines', sceneOpts) + self.__lbCanvas.bindProps('highlightSlice', sceneOpts) + self.__lbCanvas.bindProps('renderMode', sceneOpts) + self.__lbCanvas.bindProps('softwareMode', sceneOpts) + self.__lbCanvas.bindProps('resolutionLimit', sceneOpts) # Bind these properties the other way around, # so that the sensible values calcualted by @@ -74,96 +110,109 @@ class LightBoxPanel(canvaspanel.CanvasPanel): # propagated to the LBOpts instance, rather # than the non-sensible default values in the # LBOpts instance. - sceneOpts .bindProps('nrows', self._lbCanvas) - sceneOpts .bindProps('ncols', self._lbCanvas) - sceneOpts .bindProps('topRow', self._lbCanvas) - sceneOpts .bindProps('sliceSpacing', self._lbCanvas) - sceneOpts .bindProps('zrange', self._lbCanvas) + sceneOpts.bindProps('nrows', self.__lbCanvas) + sceneOpts.bindProps('ncols', self.__lbCanvas) + sceneOpts.bindProps('topRow', self.__lbCanvas) + sceneOpts.bindProps('sliceSpacing', self.__lbCanvas) + sceneOpts.bindProps('zrange', self.__lbCanvas) - self._canvasSizer = wx.BoxSizer(wx.HORIZONTAL) - self.getCanvasPanel().SetSizer(self._canvasSizer) + self.__canvasSizer = wx.BoxSizer(wx.HORIZONTAL) + self.getCanvasPanel().SetSizer(self.__canvasSizer) - self._canvasSizer.Add(self._lbCanvas, flag=wx.EXPAND, proportion=1) - self._canvasSizer.Add(self._scrollbar, flag=wx.EXPAND) + self.__canvasSizer.Add(self.__lbCanvas, flag=wx.EXPAND, proportion=1) + self.__canvasSizer.Add(self.__scrollbar, flag=wx.EXPAND) # When the display context location changes, # make sure the location is shown on the canvas - self._lbCanvas.pos.xyz = self._displayCtx.location + self.__lbCanvas.pos.xyz = self._displayCtx.location self._displayCtx .addListener('location', self._name, - self._onLocationChange) + self.__onLocationChange) self._displayCtx .addListener('selectedOverlay', self._name, - self._selectedOverlayChanged) + self.__selectedOverlayChanged) self._overlayList.addListener('overlays', self._name, - self._selectedOverlayChanged) - - sceneOpts.zoom = 750 - - self._onLightBoxChange() - self._onZoom() + self.__selectedOverlayChanged) # When any lightbox properties change, # make sure the scrollbar is updated sceneOpts.addListener( - 'ncols', self._name, self._ncolsChanged) + 'ncols', self._name, self.__ncolsChanged) sceneOpts.addListener( - 'nrows', self._name, self._onLightBoxChange) + 'nrows', self._name, self.__onLightBoxChange) sceneOpts.addListener( - 'topRow', self._name, self._onLightBoxChange) + 'topRow', self._name, self.__onLightBoxChange) sceneOpts.addListener( - 'sliceSpacing', self._name, self._onLightBoxChange) + 'sliceSpacing', self._name, self.__onLightBoxChange) sceneOpts.addListener( - 'zrange', self._name, self._onLightBoxChange) + 'zrange', self._name, self.__onLightBoxChange) sceneOpts.addListener( - 'zax', self._name, self._onLightBoxChange) + 'zax', self._name, self.__onLightBoxChange) sceneOpts.addListener( - 'zoom', self._name, self._onZoom) + 'zoom', self._name, self.__onZoom) # When the scrollbar is moved, # update the canvas display - self._scrollbar.Bind(wx.EVT_SCROLL, self._onScroll) + self.__scrollbar.Bind(wx.EVT_SCROLL, self.__onScroll) - self.Bind(wx.EVT_SIZE, self._onResize) + self.Bind(wx.EVT_SIZE, self.__onResize) - self.Layout() + sceneOpts.zoom = 750 - self._selectedOverlayChanged() + self.__onLightBoxChange() + self.__onZoom() + self.__selectedOverlayChanged() + self.Layout() self.initProfile() - # The FSLEyesFrame AuiManager seems to + # The ViewPanel AuiManager seems to # struggle if we add these toolbars # immediately, so we'll do it asynchronously def addToolbars(): - self.togglePanel(overlaydisplaytoolbar.OverlayDisplayToolBar, viewPanel=self) self.togglePanel(lightboxtoolbar.LightBoxToolBar, lb=self) wx.CallAfter(addToolbars) - def destroy(self): - """Removes property listeners""" + """Must be called when this ``LightBoxPanel`` is closed. + + Removes property listeners, destroys the :class:`.LightBoxCanvas`, + and calls :meth:`.CanvasPanel.destroy`. + """ self._displayCtx .removeListener('location', self._name) self._displayCtx .removeListener('selectedOverlay', self._name) self._overlayList.removeListener('overlays', self._name) - self._lbCanvas.destroy() + self.__lbCanvas.destroy() canvaspanel.CanvasPanel.destroy(self) + + def getGLCanvases(self): + """Returns a list containing the :class:`.LightBoxCanvas` contained + within this ``LightBoxPanel``. + """ + return [self.__lbCanvas] - def _selectedOverlayChanged(self, *a): - """Called when the selected overlay changes. - Registers a listener on the :attr:`.Display.transform` property - associated with the selected overlay, so that the - :meth:`_transformChanged` method will be called on ``transform`` - changes. + def getCanvas(self): + """Returns a reference to the :class:`.LightBoxCanvas` instance. """ + return self.__lbCanvas + + + def __selectedOverlayChanged(self, *a): + """Called when the :attr:`.DisplayContext.selectedOverlay` changes. + + If the currently selected overlay is an :class:`.Image` instance, or + has an associated reference image (see + :meth:`.DisplayOpts.getReferenceImage`), a listener is registered on + the reference image :attr:`.ImageOpts.transform` property, so that the + :meth:`__transformChanged` method will be called when it changes. """ if len(self._overlayList) == 0: @@ -185,17 +234,18 @@ class LightBoxPanel(canvaspanel.CanvasPanel): if overlay == selectedOverlay: opts.addListener('transform', self._name, - self._transformChanged) + self.__transformChanged) - self._transformChanged() + self.__transformChanged() - def _transformChanged(self, *a): - """Called when the transform for the currently selected overlay - changes. + def __transformChanged(self, *a): + """Called when the :attr:`.ImageOpts.transform` property for the + reference image of the currently selected overlay changes. - Updates the ``sliceSpacing`` and ``zrange`` properties to values - sensible to the new overlay display space. + Updates the :attr:`.LightBoxOpts.sliceSpacing` and + :attr:`.LightBoxOpts.zrange` properties to values sensible to the + new overlay display space. """ sceneOpts = self.getSceneOptions() @@ -217,26 +267,12 @@ class LightBoxPanel(canvaspanel.CanvasPanel): sceneOpts.zrange.x = (loBounds[sceneOpts.zax], hiBounds[sceneOpts.zax]) - self._onResize() - - - def getGLCanvases(self): - """Returns a list of length 1, containing the :class:`.SliceCanvas` - contained within this ``LightBoxPanel``. - """ - return [self._lbCanvas] - - - def getCanvas(self): - """Returns a reference to the :class:`.LightBoxCanvas` instance (which - is actually a :class:`.WXGLLightBoxCanvas`). - """ - return self._lbCanvas + self.__onResize() - def _onZoom(self, *a): - """Called when the :attr:`zoom` property changes. Updates the - number of columns on the lightbox canvas. + def __onZoom(self, *a): + """Called when the :attr:`.SceneOpts.zoom` property changes. Updates + the number of slice columns shown. """ opts = self.getSceneOptions() minval = opts.getConstraint('zoom', 'minval') @@ -245,10 +281,10 @@ class LightBoxPanel(canvaspanel.CanvasPanel): opts.ncols = int(1 + np.round(normZoom * 29)) - def _onResize(self, ev=None): - """Called when the panel is resized. Automatically adjusts the - number of lightbox rows to the maximum displayable number (given - that the number of columns is fixed). + def __onResize(self, ev=None): + """Called when the panel is resized. Automatically adjusts the number + of rows to the maximum displayable number (given that the number of + columns is fixed). """ if ev is not None: ev.Skip() @@ -256,57 +292,57 @@ class LightBoxPanel(canvaspanel.CanvasPanel): # canvas panel size is up to date self.Layout() - width, height = self._lbCanvas .GetClientSize().Get() - sbWidth, sbHeight = self._scrollbar.GetClientSize().Get() + width, height = self.__lbCanvas .GetClientSize().Get() + sbWidth, sbHeight = self.__scrollbar.GetClientSize().Get() width = width - sbWidth - xlen = self._displayCtx.bounds.getLen(self._lbCanvas.xax) - ylen = self._displayCtx.bounds.getLen(self._lbCanvas.yax) + xlen = self._displayCtx.bounds.getLen(self.__lbCanvas.xax) + ylen = self._displayCtx.bounds.getLen(self.__lbCanvas.yax) - sliceWidth = width / float(self._lbCanvas.ncols) + sliceWidth = width / float(self.__lbCanvas.ncols) sliceHeight = fsllayout.calcPixHeight(xlen, ylen, sliceWidth) if sliceHeight > 0: - self._lbCanvas.nrows = int(height / sliceHeight) + self.__lbCanvas.nrows = int(height / sliceHeight) - def _onLocationChange(self, *a): - """Called when the display context location changes. + def __onLocationChange(self, *a): + """Called when the :attr:`.DisplayContext.location` changes. - Updates the canvas location. + Updates the location shown on the :class:`.LightBoxCanvas`. """ - xpos = self._displayCtx.location.getPos(self._lbCanvas.xax) - ypos = self._displayCtx.location.getPos(self._lbCanvas.yax) - zpos = self._displayCtx.location.getPos(self._lbCanvas.zax) - self._lbCanvas.pos.xyz = (xpos, ypos, zpos) + xpos = self._displayCtx.location.getPos(self.__lbCanvas.xax) + ypos = self._displayCtx.location.getPos(self.__lbCanvas.yax) + zpos = self._displayCtx.location.getPos(self.__lbCanvas.zax) + self.__lbCanvas.pos.xyz = (xpos, ypos, zpos) - def _ncolsChanged(self, *a): - """Called when the lightbox canvas ``ncols`` property changes. + def __ncolsChanged(self, *a): + """Called when the :attr:`.LightBoxOpts.ncols` property changes. Calculates the number of rows to display, and updates the scrollbar. """ - self._onResize() - self._onLightBoxChange() + self.__onResize() + self.__onLightBoxChange() - def _onLightBoxChange(self, *a): - """Called when any lightbox display properties change. + def __onLightBoxChange(self, *a): + """Called when any :class:`.LightBoxOpts` property changes. Updates the scrollbar to reflect the change. """ - self._scrollbar.SetScrollbar(self._lbCanvas.topRow, - self._lbCanvas.nrows, - self._lbCanvas.getTotalRows(), - self._lbCanvas.nrows, - True) + self.__scrollbar.SetScrollbar(self.__lbCanvas.topRow, + self.__lbCanvas.nrows, + self.__lbCanvas.getTotalRows(), + self.__lbCanvas.nrows, + True) - def _onScroll(self, *a): + def __onScroll(self, *a): """Called when the scrollbar is moved. - Updates the top row displayed on the canvas. + Updates the top row displayed on the :class:`.LightBoxCanvas`. """ - self._lbCanvas.topRow = self._scrollbar.GetThumbPosition() + self.__lbCanvas.topRow = self.__scrollbar.GetThumbPosition() diff --git a/fsl/fsleyes/views/orthopanel.py b/fsl/fsleyes/views/orthopanel.py index c6a945c2107539c3f2892a103d5091431f3321c9..52b6dd2a161a1c1b12e974024fc050c699ba0dea 100644 --- a/fsl/fsleyes/views/orthopanel.py +++ b/fsl/fsleyes/views/orthopanel.py @@ -1,18 +1,22 @@ #!/usr/bin/env python # -# orthopanel.py - A wx/OpenGL widget for displaying and interacting with a -# collection of 3D overlays. +# orthopanel.py - The OrthoPanel class. # # Author: Paul McCarthy <pauldmccarthy@gmail.com> # -"""A :mod:`wx`/:mod:`OpenGL` widget for displaying and interacting with a -collection of 3D overlays. +"""This module provides the :class:`OrthoPanel` class, which displays a 2D +view of 3D overlays. -Displays three canvases, each of which shows the same overlay(s) on a -different orthogonal plane. The displayed location is driven by the -:attr:`.DisplayContext.location` property. +A couple of other classes are provided for convenience: + +.. autosummary:: + :nosignatures: + + OrthoFrame + OrthoDialog """ + import logging import wx @@ -34,14 +38,97 @@ log = logging.getLogger(__name__) class OrthoPanel(canvaspanel.CanvasPanel): + """The ``OrthoPanel`` class is a *FSLeyes view* which displays a 2D view + of 3D overlays. The ``OrthoPanel`` is the primary point of user + interaction in *FSLeyes*. + + + **Overview** + + + An ``OrthoPanel`` contains three :class:`.SliceCanvas` panels, each of + which provide a 2D view of the overlays in the :class:`.OverlayList` along + one axis. These ``SliceCanvas`` instances can be accessed through the + :meth:`getXCanvas`, :meth:`getYCanvas`, :meth:`getZCanvas`, and + :meth:`getGLCanvases` methods. + + + An ``OrthoPanel`` looks something like this: + + + .. image:: images/orthopanel.png + :scale: 50% + :align: center + + + **Anatomical labels** + + + In addition to the three ``SliceCanvas`` panels, the ``OrthoPanel`` is + capable of displaying labels around each panel, showing the user the + anatomical orientation of the display on each panel. These labels are only + shown if the currently selected overlay (as dicated by the + :attr:`.DisplayContext.selectedOverlay` property) is a :class:`.Image` + instance, **or** the :meth:`.DisplayOpts.getReferenceImage` method for the + currently selected overlay returns an :class:`.Image` instance. + + + **Display** + + + The display of an ``OrthoPanel`` can be configured through all of the + settings provided by the :class:`.OrthoOpts` class. The ``OrthoOpts`` + instance for a given ``OrthoPanel`` can be accessed via the + :meth:`.CanvasPanel.getSceneOptions` method. + + + **Interaction** + + + Two interaction profiles are defined for use with the ``OrthoPanel`` (see + the :class:`.ViewPanel` for an overview of *profiles*): + + ======== ========================================================= + ``view`` Viewing/navigation, using the :class:`.OrthoViewProfile`. + + ``edit`` Simple editing of :class:`.Image` overlays, using the + :class:`.OrthoEditProfile` (see also the + :mod:`~fsl.fsleyes.editor` package). + ======== ========================================================= + + + **Actions and control panels** + + + The ``OrthoPanel`` adds a few extra actions to those provided by the + :class:`.CanvasPanel` class: + + + ====================== ========================================== + ``toggleOrthoToolBar`` Shows/hides an :class:`.OrthoToolBar`. + ``toggleEditToolBar`` Shows/hides an :class:`.OrthoEditToolBar`. + ====================== ========================================== + + + When an ``OrthoPanel`` is created, it will automatically add the + following control panels: + + .. autosummary:: + :nosignatures: + + ~fsl.fsleyes.controls.orthotoolbar.OrthoToolBar + ~fsl.fsleyes.controls.orthoedittoolbar.OrthoEditToolBar + ~fsl.fsleyes.controls.overlaydisplaytoolbar.OverlayDisplayToolBar + """ def __init__(self, parent, overlayList, displayCtx): - """ - Creates three SliceCanvas objects, each displaying the images - in the given image list along a different axis. - """ + """Create an ``OrthoPanel``. + :arg parent: The :mod:`wx` parent. + :arg overlayList: An :class:`.OverlayList` instance. + :arg displayCtx: A :class:`.DisplayContext` instance. + """ sceneOpts = orthoopts.OrthoOpts() @@ -63,109 +150,100 @@ class OrthoPanel(canvaspanel.CanvasPanel): # The canvases themselves - each one displays a # slice along each of the three world axes - self._xcanvas = slicecanvas.WXGLSliceCanvas(canvasPanel, - overlayList, - displayCtx, - zax=0) - self._ycanvas = slicecanvas.WXGLSliceCanvas(canvasPanel, - overlayList, - displayCtx, - zax=1) - self._zcanvas = slicecanvas.WXGLSliceCanvas(canvasPanel, - overlayList, - displayCtx, - zax=2) + self.__xcanvas = slicecanvas.WXGLSliceCanvas(canvasPanel, + overlayList, + displayCtx, + zax=0) + self.__ycanvas = slicecanvas.WXGLSliceCanvas(canvasPanel, + overlayList, + displayCtx, + zax=1) + self.__zcanvas = slicecanvas.WXGLSliceCanvas(canvasPanel, + overlayList, + displayCtx, + zax=2) # Labels to show anatomical orientation, # stored in a dict for each canvas - self._xLabels = {} - self._yLabels = {} - self._zLabels = {} + self.__xLabels = {} + self.__yLabels = {} + self.__zLabels = {} + for side in ('left', 'right', 'top', 'bottom'): - self._xLabels[side] = wx.StaticText(canvasPanel) - self._yLabels[side] = wx.StaticText(canvasPanel) - self._zLabels[side] = wx.StaticText(canvasPanel) + self.__xLabels[side] = wx.StaticText(canvasPanel) + self.__yLabels[side] = wx.StaticText(canvasPanel) + self.__zLabels[side] = wx.StaticText(canvasPanel) - self._xcanvas.bindProps('showCursor', sceneOpts) - self._ycanvas.bindProps('showCursor', sceneOpts) - self._zcanvas.bindProps('showCursor', sceneOpts) + self.__xcanvas.bindProps('showCursor', sceneOpts) + self.__ycanvas.bindProps('showCursor', sceneOpts) + self.__zcanvas.bindProps('showCursor', sceneOpts) - self._xcanvas.bindProps('bgColour', sceneOpts) - self._ycanvas.bindProps('bgColour', sceneOpts) - self._zcanvas.bindProps('bgColour', sceneOpts) + self.__xcanvas.bindProps('bgColour', sceneOpts) + self.__ycanvas.bindProps('bgColour', sceneOpts) + self.__zcanvas.bindProps('bgColour', sceneOpts) - self._xcanvas.bindProps('cursorColour', sceneOpts) - self._ycanvas.bindProps('cursorColour', sceneOpts) - self._zcanvas.bindProps('cursorColour', sceneOpts) + self.__xcanvas.bindProps('cursorColour', sceneOpts) + self.__ycanvas.bindProps('cursorColour', sceneOpts) + self.__zcanvas.bindProps('cursorColour', sceneOpts) # Callbacks for ortho panel layout options - sceneOpts.addListener('layout', self._name, self._refreshLayout) - sceneOpts.addListener('showLabels', self._name, self._refreshLabels) + sceneOpts.addListener('layout', self._name, self.__refreshLayout) + sceneOpts.addListener('showLabels', self._name, self.__refreshLabels) sceneOpts.addListener('bgColour' , self._name, self.__bgColourChanged) - self.__bgColourChanged() - # Individual zoom control for each canvas - self._xcanvas.bindProps('zoom', sceneOpts, 'xzoom') - self._ycanvas.bindProps('zoom', sceneOpts, 'yzoom') - self._zcanvas.bindProps('zoom', sceneOpts, 'zzoom') + self.__xcanvas.bindProps('zoom', sceneOpts, 'xzoom') + self.__ycanvas.bindProps('zoom', sceneOpts, 'yzoom') + self.__zcanvas.bindProps('zoom', sceneOpts, 'zzoom') - self._xcanvas.bindProps('renderMode', sceneOpts) - self._ycanvas.bindProps('renderMode', sceneOpts) - self._zcanvas.bindProps('renderMode', sceneOpts) + self.__xcanvas.bindProps('renderMode', sceneOpts) + self.__ycanvas.bindProps('renderMode', sceneOpts) + self.__zcanvas.bindProps('renderMode', sceneOpts) - self._xcanvas.bindProps('softwareMode', sceneOpts) - self._ycanvas.bindProps('softwareMode', sceneOpts) - self._zcanvas.bindProps('softwareMode', sceneOpts) + self.__xcanvas.bindProps('softwareMode', sceneOpts) + self.__ycanvas.bindProps('softwareMode', sceneOpts) + self.__zcanvas.bindProps('softwareMode', sceneOpts) - self._xcanvas.bindProps('resolutionLimit', sceneOpts) - self._ycanvas.bindProps('resolutionLimit', sceneOpts) - self._zcanvas.bindProps('resolutionLimit', sceneOpts) + self.__xcanvas.bindProps('resolutionLimit', sceneOpts) + self.__ycanvas.bindProps('resolutionLimit', sceneOpts) + self.__zcanvas.bindProps('resolutionLimit', sceneOpts) # Callbacks for overlay list/selected overlay changes self._overlayList.addListener('overlays', self._name, - self._overlayListChanged) + self.__overlayListChanged) self._displayCtx .addListener('bounds', self._name, - self._refreshLayout) + self.__refreshLayout) self._displayCtx .addListener('selectedOverlay', self._name, - self._overlayListChanged) + self.__overlayListChanged) # Callback for the display context location - when it # changes, update the displayed canvas locations self._displayCtx.addListener('location', self._name, - self._locationChanged) + self.__locationChanged) # Callbacks for toggling x/y/z canvas display - sceneOpts.addListener('showXCanvas', - self._name, - lambda *a: self._toggleCanvas('x'), - weak=False) - sceneOpts.addListener('showYCanvas', - self._name, - lambda *a: self._toggleCanvas('y'), - weak=False) - sceneOpts.addListener('showZCanvas', - self._name, - lambda *a: self._toggleCanvas('z'), - weak=False) - - # Call the _resize method to refresh + sceneOpts.addListener('showXCanvas', self._name, self.__toggleCanvas) + sceneOpts.addListener('showYCanvas', self._name, self.__toggleCanvas) + sceneOpts.addListener('showZCanvas', self._name, self.__toggleCanvas) + + # Call the __onResize method to refresh # the slice canvases when the canvas # panel is resized, so aspect ratio # is maintained - canvasPanel.Bind(wx.EVT_SIZE, self._onResize) + canvasPanel.Bind(wx.EVT_SIZE, self.__onResize) # Initialise the panel - self._refreshLayout() - self._overlayListChanged() - self._locationChanged() + self.__refreshLayout() + self.__bgColourChanged() + self.__overlayListChanged() + self.__locationChanged() self.initProfile() - # The FSLEyesFrame AuiManager seems to + # The ViewPanel AuiManager seems to # struggle if we add these toolbars # immediately, so we'll do it asynchronously def addToolbars(): @@ -180,11 +258,11 @@ class OrthoPanel(canvaspanel.CanvasPanel): def destroy(self): - """Called when this panel is closed. - - The display context and image list will probably live longer than - this OrthoPanel. So when this panel is destroyed, all those - registered listeners are removed. + """Must be called when this ``OrthoPanel`` is closed. + + Removes listeners from the :class:`.DisplayContext` and + :class:`.OverlayList` instances, destroys each of the three + :class:`.SliceCanvas` panels, and calls :meth:`.CanvasPanel.destroy`. """ self._displayCtx .removeListener('location', self._name) @@ -192,9 +270,9 @@ class OrthoPanel(canvaspanel.CanvasPanel): self._displayCtx .removeListener('selectedOverlay', self._name) self._overlayList.removeListener('overlays', self._name) - self._xcanvas.destroy() - self._ycanvas.destroy() - self._zcanvas.destroy() + self.__xcanvas.destroy() + self.__ycanvas.destroy() + self.__zcanvas.destroy() # The _overlayListChanged method adds # listeners to individual overlays, @@ -205,99 +283,140 @@ class OrthoPanel(canvaspanel.CanvasPanel): canvaspanel.CanvasPanel.destroy(self) - - def __bgColourChanged(self, *a): - - bg = self.getSceneOptions().bgColour[:3] - fg = colourmaps.complementaryColour(bg[:3]) - - bg = [int(round(c * 255)) for c in bg] + [255] - fg = [int(round(c * 255)) for c in fg] + [255] - - self.getCanvasPanel().SetBackgroundColour(bg) - self.getCanvasPanel().SetForegroundColour(fg) - - self._xcanvas.SetBackgroundColour(bg) - self._ycanvas.SetBackgroundColour(bg) - self._zcanvas.SetBackgroundColour(bg) - - for side in ('left', 'right', 'top', 'bottom'): - self._xLabels[side].SetBackgroundColour(bg) - self._yLabels[side].SetBackgroundColour(bg) - self._zLabels[side].SetBackgroundColour(bg) - self._xLabels[side].SetForegroundColour(fg) - self._yLabels[side].SetForegroundColour(fg) - self._zLabels[side].SetForegroundColour(fg) - - self.Refresh() - def getGLCanvases(self): """Returns all of the :class:`.SliceCanvas` instances contained within this ``OrthoPanel``. """ - return [self._xcanvas, self._ycanvas, self._zcanvas] + return [self.__xcanvas, self.__ycanvas, self.__zcanvas] def getXCanvas(self): - """Returns a reference to the :class:`.SliceCanvas` instance - displaying the X axis. + """Returns the :class:`.SliceCanvas` instance displaying the X axis. """ - return self._xcanvas + return self.__xcanvas def getYCanvas(self): - """Returns a reference to the :class:`.SliceCanvas` instance - displaying the Y axis. + """Returns the :class:`.SliceCanvas` instance displaying the Y axis. """ - return self._ycanvas + return self.__ycanvas def getZCanvas(self): - """Returns a reference to the :class:`.SliceCanvas` instance - displaying the Z axis. + """Returns the :class:`.SliceCanvas` instance displaying the Z axis. """ - return self._zcanvas + return self.__zcanvas + + + def __bgColourChanged(self, *a): + """Called when the :class:`.SceneOpts.bgColour` property changes. + Updates the panel and anatomical label background/foreground + colours. + + The :attr:`.SliceCanvasOpts.bgColour` properties are bound to + ``SceneOpts.bgColour``,(see :meth:`.HasProperties.bindProps`), so we + don't need to manually update them. + """ + + bg = self.getSceneOptions().bgColour + fg = colourmaps.complementaryColour(bg) + + bg = [int(round(c * 255)) for c in bg] + fg = [int(round(c * 255)) for c in fg] + + self.getCanvasPanel().SetBackgroundColour(bg) + self.getCanvasPanel().SetForegroundColour(fg) + + self.__xcanvas.SetBackgroundColour(bg) + self.__ycanvas.SetBackgroundColour(bg) + self.__zcanvas.SetBackgroundColour(bg) + + self.__setLabelColours(bg, fg) + + + def __setLabelColours(self, bgColour, fgColour): + """Used by the :meth:`__bgColourChanged` and :meth:`__refreshLabels` + methods. + + Sets the background and foreground label colours to the given + ``bgColour`` and ``fgColour``, which should be ``(r, g, b, a)`` + tuples with each value in the range ``[0, 255]``. + """ + + bgColour = tuple(bgColour) + fgColour = tuple(fgColour) + + overlay = self._displayCtx.getReferenceImage( + self._displayCtx.getSelectedOverlay()) + + allLabels = self.__xLabels.values() + \ + self.__yLabels.values() + \ + self.__zLabels.values() + + if overlay is not None: + opts = self._displayCtx.getOpts(overlay) + + if opts.transform in ('pixdim', 'id'): + xorient = overlay.getVoxelOrientation(0) + yorient = overlay.getVoxelOrientation(1) + zorient = overlay.getVoxelOrientation(2) + else: + xorient = overlay.getWorldOrientation(0) + yorient = overlay.getWorldOrientation(1) + zorient = overlay.getWorldOrientation(2) + + if constants.ORIENT_UNKNOWN in (xorient, yorient, zorient): + + # If the background colour is black or white, + # make the foreground colour red, to highlight + # the unknown orientation. It's too difficult + # to do this for any background colour. + if bgColour == ( 0, 0, 0, 255) or \ + bgColour == (255, 255, 255, 255): + fgColour = (255, 0, 0, 255) + for lbl in allLabels: + lbl.SetForegroundColour(fgColour) + lbl.SetBackgroundColour(bgColour) - def _toggleCanvas(self, canvas): - """Called when any of show*Canvas properties are changed. - Shows/hides the specified canvas ('x', 'y', or 'z') - this callback - is configured in __init__ above. + def __toggleCanvas(self, canvas): + """Called when any of the :attr:`.OrthoOpts.showXCanvas`, + :attr:`.OrthoOpts.showYCanvas`, or :attr:`.OrthoOpts.showZCanvas` + properties are changed. + + Shows/hides each of the :class:`.SliceCanvas` panels and anatomical + label panels accordingly. """ - opts = self.getSceneOptions() - - if canvas == 'x': - canvas = self._xcanvas - show = opts.showXCanvas - labels = self._xLabels - elif canvas == 'y': - canvas = self._ycanvas - show = opts.showYCanvas - labels = self._yLabels - elif canvas == 'z': - canvas = self._zcanvas - show = opts.showZCanvas - labels = self._zLabels - - self._canvasSizer.Show(canvas, show) - for label in labels.values(): - if (not show) or (show and opts.showLabels): - self._canvasSizer.Show(label, show) + opts = self.getSceneOptions() + canvases = [self.__xcanvas, self.__ycanvas, self.__zcanvas] + allLabels = [self.__xLabels, self.__yLabels, self.__zLabels] + shows = [opts.showXCanvas, opts.showYCanvas, opts.showZCanvas] + + for canvas, labels, show in zip(canvases, allLabels, shows): + + canvas.Show(show) + self.__canvasSizer.Show(canvas, show) + + for label in labels.values(): + self.__canvasSizer.Show(label, show and opts.showLabels) if opts.layout == 'grid': - self._refreshLayout() + self.__refreshLayout() self.PostSizeEvent() - def _overlayListChanged(self, *a): - """Called when the overlay list or selected overlay is changed. + def __overlayListChanged(self, *a): + """Called when the :class:`.OverlayList` or + :attr:`.DisplayContext.selectedOverlay` is changed. - Adds a listener to the currently selected overlay, to listen - for changes on its transformation matrix. + Adds a listener to the :attr:`.DisplayOpts.bounds` property for the + currently selected overlay, to listen for changes to its bounds, which + will trigger an update to the anatomical labels (see + :meth:`__refreshLabels`). """ for i, ovl in enumerate(self._overlayList): @@ -309,57 +428,54 @@ class OrthoPanel(canvaspanel.CanvasPanel): if i == self._displayCtx.selectedOverlay: opts.addListener('bounds', self._name, - self._refreshLabels, + self.__refreshLabels, overwrite=True) else: opts.removeListener('bounds', self._name) # anatomical orientation may have changed with an image change - self._refreshLabels() + self.__refreshLabels() - def _onResize(self, ev): - """ - Called whenever the panel is resized. Makes sure that the canvases - are laid out nicely. + def __onResize(self, ev): + """Called whenever the panel is resized. Makes sure that the + :class:`.SliceCanvas` panels are laid out nicely. """ ev.Skip() - self._calcCanvasSizes() + self.__calcCanvasSizes() - def _refreshLabels(self, *a): - """Shows/hides labels depicting anatomical orientation on each canvas. + def __refreshLabels(self, *a): + """Shows/hides labels depicting anatomical orientation on each + :class:`.SliceCanvas`. """ - allLabels = self._xLabels.values() + \ - self._yLabels.values() + \ - self._zLabels.values() + sceneOpts = self.getSceneOptions() + allLabels = self.__xLabels.values() + \ + self.__yLabels.values() + \ + self.__zLabels.values() # Are we showing or hiding the labels? - if len(self._overlayList) == 0: show = False + if len(self._overlayList) == 0: + show = False overlay = self._displayCtx.getReferenceImage( self._displayCtx.getSelectedOverlay()) # Labels are only supported if we # have a volumetric reference image - if overlay is None: show = False - elif self.getSceneOptions().showLabels: show = True - else: show = False + if overlay is None: show = False + elif sceneOpts.showLabels: show = True + else: show = False for lbl in allLabels: - self._canvasSizer.Show(lbl, show) + self.__canvasSizer.Show(lbl, show) # If we're hiding the labels, do no more if not show: self.PostSizeEvent() return - # Default colour is white - if the orientation labels - # cannot be determined, the foreground colour will be - # changed to red - colour = 'white' - opts = self._displayCtx.getOpts(overlay) # The image is being displayed as it is stored on @@ -377,9 +493,6 @@ class OrthoPanel(canvaspanel.CanvasPanel): xorient = overlay.getWorldOrientation(0) yorient = overlay.getWorldOrientation(1) zorient = overlay.getWorldOrientation(2) - - if constants.ORIENT_UNKNOWN in (xorient, yorient, zorient): - colour = 'red' xlo = strings.anatomy['Image', 'lowshort', xorient] ylo = strings.anatomy['Image', 'lowshort', yorient] @@ -388,29 +501,36 @@ class OrthoPanel(canvaspanel.CanvasPanel): yhi = strings.anatomy['Image', 'highshort', yorient] zhi = strings.anatomy['Image', 'highshort', zorient] - for lbl in allLabels: - lbl.SetForegroundColour(colour) - - self._xLabels['left'] .SetLabel(ylo) - self._xLabels['right'] .SetLabel(yhi) - self._xLabels['top'] .SetLabel(zlo) - self._xLabels['bottom'].SetLabel(zhi) - self._yLabels['left'] .SetLabel(xlo) - self._yLabels['right'] .SetLabel(xhi) - self._yLabels['top'] .SetLabel(zlo) - self._yLabels['bottom'].SetLabel(zhi) - self._zLabels['left'] .SetLabel(xlo) - self._zLabels['right'] .SetLabel(xhi) - self._zLabels['top'] .SetLabel(ylo) - self._zLabels['bottom'].SetLabel(yhi) + bg = sceneOpts.bgColour + fg = colourmaps.complementaryColour(bg) + bg = [int(round(c * 255)) for c in bg] + fg = [int(round(c * 255)) for c in fg] + + self.__setLabelColours(bg, fg) + + self.__xLabels['left'] .SetLabel(ylo) + self.__xLabels['right'] .SetLabel(yhi) + self.__xLabels['top'] .SetLabel(zlo) + self.__xLabels['bottom'].SetLabel(zhi) + self.__yLabels['left'] .SetLabel(xlo) + self.__yLabels['right'] .SetLabel(xhi) + self.__yLabels['top'] .SetLabel(zlo) + self.__yLabels['bottom'].SetLabel(zhi) + self.__zLabels['left'] .SetLabel(xlo) + self.__zLabels['right'] .SetLabel(xhi) + self.__zLabels['top'] .SetLabel(ylo) + self.__zLabels['bottom'].SetLabel(yhi) self.PostSizeEvent() - def _calcCanvasSizes(self, *a): - """Fixes the size for each displayed canvas (by setting their minimum - and maximum sizes), so that they are scaled proportionally to each - other. + def __calcCanvasSizes(self, *a): + """Sets the size for each displayed :class:`.SliceCanvas`. + + The minimum/maximum size of each canvas is fixed so that they are + scaled proportionally to each other, thus preserving the aspect ratio. + The :mod:~fsl.utils.layout` module is used to perform the canvas size + calculation. """ opts = self.getSceneOptions() @@ -418,9 +538,9 @@ class OrthoPanel(canvaspanel.CanvasPanel): width, height = self.getCanvasPanel().GetClientSize().Get() - show = [opts.showXCanvas, opts.showYCanvas, opts.showZCanvas] - canvases = [self._xcanvas, self._ycanvas, self._zcanvas] - labels = [self._xLabels, self._yLabels, self._zLabels] + show = [opts.showXCanvas, opts.showYCanvas, opts.showZCanvas] + canvases = [self.__xcanvas, self.__ycanvas, self.__zcanvas] + labels = [self.__xLabels, self.__yLabels, self.__zLabels] if width == 0 or height == 0: return if len(self._overlayList) == 0: return @@ -480,21 +600,21 @@ class OrthoPanel(canvaspanel.CanvasPanel): height = height - sumh else: - canvases = [self._ycanvas, self._xcanvas, self._zcanvas] + canvases = [self.__ycanvas, self.__xcanvas, self.__zcanvas] if opts.showLabels: - xlw = self._xLabels['left'] .GetClientSize().GetWidth() - xrw = self._xLabels['right'] .GetClientSize().GetWidth() - ylw = self._yLabels['left'] .GetClientSize().GetWidth() - yrw = self._yLabels['right'] .GetClientSize().GetWidth() - zlw = self._zLabels['left'] .GetClientSize().GetWidth() - zrw = self._zLabels['right'] .GetClientSize().GetWidth() - xth = self._xLabels['top'] .GetClientSize().GetHeight() - xbh = self._xLabels['bottom'].GetClientSize().GetHeight() - yth = self._yLabels['top'] .GetClientSize().GetHeight() - ybh = self._yLabels['bottom'].GetClientSize().GetHeight() - zth = self._zLabels['top'] .GetClientSize().GetHeight() - zbh = self._zLabels['bottom'].GetClientSize().GetHeight() + xlw = self.__xLabels['left'] .GetClientSize().GetWidth() + xrw = self.__xLabels['right'] .GetClientSize().GetWidth() + ylw = self.__yLabels['left'] .GetClientSize().GetWidth() + yrw = self.__yLabels['right'] .GetClientSize().GetWidth() + zlw = self.__zLabels['left'] .GetClientSize().GetWidth() + zrw = self.__zLabels['right'] .GetClientSize().GetWidth() + xth = self.__xLabels['top'] .GetClientSize().GetHeight() + xbh = self.__xLabels['bottom'].GetClientSize().GetHeight() + yth = self.__yLabels['top'] .GetClientSize().GetHeight() + ybh = self.__yLabels['bottom'].GetClientSize().GetHeight() + zth = self.__zLabels['top'] .GetClientSize().GetHeight() + zbh = self.__zLabels['bottom'].GetClientSize().GetHeight() else: xlw = xrw = xth = xbh = 0 ylw = yrw = yth = ybh = 0 @@ -523,9 +643,9 @@ class OrthoPanel(canvaspanel.CanvasPanel): canvas.SetMaxSize(size) - def _refreshLayout(self, *a): - """Called when the layout property changes, or the canvas layout needs - to be refreshed. Updates the orthopanel layout accordingly. + def __refreshLayout(self, *a): + """Called when the :attr:`.OrthoOpts.layout` property changes, or the + canvas layout needs to be refreshed. Updates the layout accordingly. """ opts = self.getSceneOptions() @@ -558,55 +678,55 @@ class OrthoPanel(canvaspanel.CanvasPanel): # if layout is something other than the above three, # then something's gone wrong and I'm going to crash - self._canvasSizer = wx.FlexGridSizer(nrows, ncols) + self.__canvasSizer = wx.FlexGridSizer(nrows, ncols) # The rows/columns that contain # canvases must also be growable if layout == 'horizontal': - self._canvasSizer.AddGrowableRow(1) + self.__canvasSizer.AddGrowableRow(1) for i in range(nCanvases): - self._canvasSizer.AddGrowableCol(i * 3 + 1) + self.__canvasSizer.AddGrowableCol(i * 3 + 1) elif layout == 'vertical': - self._canvasSizer.AddGrowableCol(1) + self.__canvasSizer.AddGrowableCol(1) for i in range(nCanvases): - self._canvasSizer.AddGrowableRow(i * 3 + 1) + self.__canvasSizer.AddGrowableRow(i * 3 + 1) elif layout == 'grid': - self._canvasSizer.AddGrowableRow(1) - self._canvasSizer.AddGrowableRow(4) - self._canvasSizer.AddGrowableCol(1) - self._canvasSizer.AddGrowableCol(4) + self.__canvasSizer.AddGrowableRow(1) + self.__canvasSizer.AddGrowableRow(4) + self.__canvasSizer.AddGrowableCol(1) + self.__canvasSizer.AddGrowableCol(4) # Make a list of widgets - the canvases, # anatomical labels (if displayed), and # spacers for the empty cells space = (1, 1) - xlbls = self._xLabels - ylbls = self._yLabels - zlbls = self._zLabels + xlbls = self.__xLabels + ylbls = self.__yLabels + zlbls = self.__zLabels if layout == 'horizontal': - widgets = [space, xlbls['top'], space, - space, ylbls['top'], space, - space, zlbls['top'], space, - xlbls['left'], self._xcanvas, xlbls['right'], - ylbls['left'], self._ycanvas, ylbls['right'], - zlbls['left'], self._zcanvas, zlbls['right'], - space, xlbls['bottom'], space, - space, ylbls['bottom'], space, - space, zlbls['bottom'], space] + widgets = [space, xlbls['top'], space, + space, ylbls['top'], space, + space, zlbls['top'], space, + xlbls['left'], self.__xcanvas, xlbls['right'], + ylbls['left'], self.__ycanvas, ylbls['right'], + zlbls['left'], self.__zcanvas, zlbls['right'], + space, xlbls['bottom'], space, + space, ylbls['bottom'], space, + space, zlbls['bottom'], space] elif layout == 'vertical': - widgets = [space, xlbls['top'], space, - xlbls['left'], self._xcanvas, xlbls['right'], - space, xlbls['bottom'], space, - space, ylbls['top'], space, - ylbls['left'], self._ycanvas, ylbls['right'], - space, ylbls['bottom'], space, - space, zlbls['top'], space, - zlbls['left'], self._zcanvas, zlbls['right'], - space, zlbls['bottom'], space] + widgets = [space, xlbls['top'], space, + xlbls['left'], self.__xcanvas, xlbls['right'], + space, xlbls['bottom'], space, + space, ylbls['top'], space, + ylbls['left'], self.__ycanvas, ylbls['right'], + space, ylbls['bottom'], space, + space, zlbls['top'], space, + zlbls['left'], self.__zcanvas, zlbls['right'], + space, zlbls['bottom'], space] # The canvases are laid out in a different order # for orthographic, or 'grid' layout. Assuming @@ -617,61 +737,75 @@ class OrthoPanel(canvaspanel.CanvasPanel): # following manner (the letter denotes the depth # axis for the respective canvas): # + # TODO You need to horizonatlly flip the x canvas + # to achieve true orthographic display. + # # Y X # Z - elif layout == 'grid': - widgets = [space, ylbls['top'], space, - space, xlbls['top'], space, - ylbls['left'], self._ycanvas, ylbls['right'], - xlbls['left'], self._xcanvas, xlbls['right'], - space, ylbls['bottom'], space, - space, xlbls['bottom'], space, - space, zlbls['top'], space, - space, space, space, - zlbls['left'], self._zcanvas, zlbls['right'], - space, space, space, - space, zlbls['bottom'], space, - space, space, space] + widgets = [space, ylbls['top'], space, + space, xlbls['top'], space, + ylbls['left'], self.__ycanvas, ylbls['right'], + xlbls['left'], self.__xcanvas, xlbls['right'], + space, ylbls['bottom'], space, + space, xlbls['bottom'], space, + space, zlbls['top'], space, + space, space, space, + zlbls['left'], self.__zcanvas, zlbls['right'], + space, space, space, + space, zlbls['bottom'], space, + space, space, space] # Add all those widgets to the grid sizer flag = wx.ALIGN_CENTRE_HORIZONTAL | wx.ALIGN_CENTRE_VERTICAL for w in widgets: - self._canvasSizer.Add(w, flag=flag) + self.__canvasSizer.Add(w, flag=flag) - self.getCanvasPanel().SetSizer(self._canvasSizer) + self.getCanvasPanel().SetSizer(self.__canvasSizer) # Calculate/ adjust the appropriate sizes # for each canvas, such that they are scaled # appropriately relative to each other, and # the displayed world space aspect ratio is # maintained - self._calcCanvasSizes() + self.__calcCanvasSizes() self.Layout() self.getCanvasPanel().Layout() self.Refresh() - def _locationChanged(self, *a): - """ + def __locationChanged(self, *a): + """Called when the :attr:`.DisplayContext.locavtion` property changes. + Sets the currently displayed x/y/z position (in display - coordinates). + coordinates) on each of the :class:`.SliceCanvas` panels. """ xpos, ypos, zpos = self._displayCtx.location.xyz - self._xcanvas.pos.xyz = [ypos, zpos, xpos] - self._ycanvas.pos.xyz = [xpos, zpos, ypos] - self._zcanvas.pos.xyz = [xpos, ypos, zpos] + self.__xcanvas.pos.xyz = [ypos, zpos, xpos] + self.__ycanvas.pos.xyz = [xpos, zpos, ypos] + self.__zcanvas.pos.xyz = [xpos, ypos, zpos] class OrthoFrame(wx.Frame): - """ - Convenience class for displaying an OrthoPanel in a standalone window. + """Convenience class for displaying an :class:`OrthoPanel` in a + standalone frame. """ def __init__(self, parent, overlayList, displayCtx, title=None): + """Create an ``OrthoFrame``. + + :arg parent: A :mod:`wx` parent object. + + :arg overlayList: An :class:`.OverlayList` instance. + + :arg displayCtx: A :class:`.DisplayContext` instance. + + :arg title: Dialog title. + """ wx.Frame.__init__(self, parent, title=title) @@ -686,9 +820,8 @@ class OrthoFrame(wx.Frame): class OrthoDialog(wx.Dialog): - """ - Convenience class for displaying an OrthoPanel in a (possibly modal) - dialog window. + """Convenience class for displaying an :class:`OrthoPanel` in a (possibly + modal) dialog window. """ def __init__(self, @@ -697,9 +830,22 @@ class OrthoDialog(wx.Dialog): displayCtx, title=None, style=None): + """Create an ``OrthoDialog``. + + :arg parent: A :mod:`wx` parent object. + + :arg overlayList: An :class:`.OverlayList` instance. + + :arg displayCtx: A :class:`.DisplayContext` instance. + + :arg title: Dialog title. + + :arg style: Dialog style - defaults to + ``wx.DEFAULT_DIALOG_STYLE``. + """ - if style is None: style = wx.DEFAULT_DIALOG_STYLE - else: style |= wx.DEFAULT_DIALOG_STYLE + if style is None: + style = wx.DEFAULT_DIALOG_STYLE wx.Dialog.__init__(self, parent, title=title, style=style) diff --git a/fsl/fsleyes/views/timeseriespanel.py b/fsl/fsleyes/views/timeseriespanel.py index c283a977d46d7d22f07ef4036e03ffa4fc57b118..0a8b0ef05a342f121dc744f9182e338b28f220ae 100644 --- a/fsl/fsleyes/views/timeseriespanel.py +++ b/fsl/fsleyes/views/timeseriespanel.py @@ -154,7 +154,7 @@ class FEATTimeSeries(TimeSeries): .. note:: The ``getData`` method of a ``FEATTimeSeries`` instance will return the FEAT input data; therefore, when :attr:`plotData` is - ``True``, the ``FEATTimeSeries` instance will itself be included + ``True``, the ``FEATTimeSeries`` instance will itself be included in the list returned by :meth:`getModelTimeSeries`. diff --git a/fsl/fsleyes/views/viewpanel.py b/fsl/fsleyes/views/viewpanel.py index 069684cee443a7946a24c9a05ee1573e59b1d096..66180dadb5c9cb5ceb3f814e95ac186078f3700c 100644 --- a/fsl/fsleyes/views/viewpanel.py +++ b/fsl/fsleyes/views/viewpanel.py @@ -162,6 +162,19 @@ class ViewPanel(fslpanel.FSLEyesPanel): fslpanel.FSLEyesPanel.destroy(self) + def initProfile(self): + """Must be called by subclasses, after they have initialised all + of the attributes which may be needed by their associated + :class:`.Profile` instances. + """ + self.__profileChanged() + + + def getCurrentProfile(self): + """Returns the :class:`.Profile` instance currently in use. """ + return self.__profileManager.getCurrentProfile() + + def setCentrePanel(self, panel): """Set the primary centre panel for this ``ViewPanel``. """ @@ -404,20 +417,6 @@ class ViewPanel(fslpanel.FSLEyesPanel): type(self).__name__, overlay)) profileProp.enableChoice('edit', self) - - - def initProfile(self): - """Must be called by subclasses, after they have initialised all - of the attributes which may be needed by their associated - :class:`.Profile` instances. - """ - self.__profileChanged() - - - def getCurrentProfile(self): - """Returns the :class:`.Profile` instance currently in use. """ - return self.__profileManager.getCurrentProfile() - def __profileChanged(self, *a): """Called when the current :attr:`profile` property changes. Tells the