Skip to content
Snippets Groups Projects
Commit caa9fdd5 authored by Paul McCarthy's avatar Paul McCarthy
Browse files

All display context classes refactored ('Image' -> 'Overlay'). Will be a

long time  until the things are not broken.
parent db6284d8
No related branches found
No related tags found
No related merge requests found
...@@ -7,3 +7,9 @@ ...@@ -7,3 +7,9 @@
from displaycontext import DisplayContext from displaycontext import DisplayContext
from display import Display from display import Display
from sceneopts import SceneOpts
from orthoopts import OrthoOpts
from lightboxopts import LightBoxOpts
from volumeopts import VolumeOpts
from maskopts import MaskOpts
from vectoropts import VectorOpts
...@@ -11,6 +11,7 @@ import sceneopts ...@@ -11,6 +11,7 @@ import sceneopts
import fsl.fslview.gl.lightboxcanvas as lightboxcanvas import fsl.fslview.gl.lightboxcanvas as lightboxcanvas
class LightBoxOpts(sceneopts.SceneOpts): class LightBoxOpts(sceneopts.SceneOpts):
nrows = copy.copy(lightboxcanvas.LightBoxCanvas.nrows) nrows = copy.copy(lightboxcanvas.LightBoxCanvas.nrows)
ncols = copy.copy(lightboxcanvas.LightBoxCanvas.ncols) ncols = copy.copy(lightboxcanvas.LightBoxCanvas.ncols)
......
...@@ -11,6 +11,7 @@ import numpy as np ...@@ -11,6 +11,7 @@ import numpy as np
import props import props
import fsl.data.image as fslimage
import fsl.data.strings as strings import fsl.data.strings as strings
import display as fsldisplay import display as fsldisplay
...@@ -23,15 +24,19 @@ class MaskOpts(fsldisplay.DisplayOpts): ...@@ -23,15 +24,19 @@ class MaskOpts(fsldisplay.DisplayOpts):
labels=[strings.choices['VolumeOpts.displayRange.min'], labels=[strings.choices['VolumeOpts.displayRange.min'],
strings.choices['VolumeOpts.displayRange.max']]) strings.choices['VolumeOpts.displayRange.max']])
def __init__(self, image, display, imageList, displayCtx, parent=None): def __init__(self, overlay, display, overlayList, displayCtx, parent=None):
if np.prod(image.shape) > 2 ** 30: if not isinstance(overlay, fslimage.Image):
sample = image.data[..., image.shape[-1] / 2] raise RuntimeError('{} can only be used with an {} overlay'.format(
type(self).__name__, fslimage.Image.__name__))
if np.prod(overlay.shape) > 2 ** 30:
sample = overlay.data[..., overlay.shape[-1] / 2]
self.dataMin = float(sample.min()) self.dataMin = float(sample.min())
self.dataMax = float(sample.max()) self.dataMax = float(sample.max())
else: else:
self.dataMin = float(image.data.min()) self.dataMin = float(overlay.data.min())
self.dataMax = float(image.data.max()) self.dataMax = float(overlay.data.max())
dRangeLen = abs(self.dataMax - self.dataMin) dRangeLen = abs(self.dataMax - self.dataMin)
dMinDistance = dRangeLen / 10000.0 dMinDistance = dRangeLen / 10000.0
...@@ -52,8 +57,8 @@ class MaskOpts(fsldisplay.DisplayOpts): ...@@ -52,8 +57,8 @@ class MaskOpts(fsldisplay.DisplayOpts):
self.setConstraint('threshold', 'minDistance', dMinDistance) self.setConstraint('threshold', 'minDistance', dMinDistance)
fsldisplay.DisplayOpts.__init__(self, fsldisplay.DisplayOpts.__init__(self,
image, overlay,
display, display,
imageList, overlayList,
displayCtx, displayCtx,
parent) parent)
...@@ -14,6 +14,7 @@ import fsl.fslview.gl.colourbarcanvas as colourbarcanvas ...@@ -14,6 +14,7 @@ import fsl.fslview.gl.colourbarcanvas as colourbarcanvas
import fsl.data.strings as strings import fsl.data.strings as strings
class SceneOpts(props.HasProperties): class SceneOpts(props.HasProperties):
"""The ``SceneOpts`` class defines settings which are applied to """The ``SceneOpts`` class defines settings which are applied to
:class:`.CanvasPanel` views. :class:`.CanvasPanel` views.
...@@ -50,31 +51,29 @@ class SceneOpts(props.HasProperties): ...@@ -50,31 +51,29 @@ class SceneOpts(props.HasProperties):
strings.choices['SceneOpts.performance.5']]) strings.choices['SceneOpts.performance.5']])
"""User controllable performacne setting. """User controllable performacne setting.
This property is linked to the :attr:`twoStageRender`, This property is linked to the :attr:`renderMode`,
:attr:`resolutionLimit`, and :attr:`softwareMode` properties. Setting the :attr:`resolutionLimit`, and :attr:`softwareMode` properties. Setting the
performance to a low value will result in faster rendering time, at the performance to a low value will result in faster rendering time, at the
cost of reduced features, and poorer rendering quality. cost of reduced features, and poorer rendering quality.
See the :meth:`_onPerformanceChange` method. See the :meth:`__onPerformanceChange` method.
""" """
resolutionLimit = copy.copy(slicecanvas.SliceCanvas.resolutionLimit) resolutionLimit = copy.copy(slicecanvas.SliceCanvas.resolutionLimit)
"""The highest resolution at which any image should be displayed. """The highest resolution at which any image should be displayed.
See :attr:`~fsl.fslview.gl.slicecanvas.SliceCanvas.resolutionLimit` and See :attr:`.SliceCanvas.resolutionLimit` and :attr:`.Display.resolution`.
:attr:`~fsl.fslview.displaycontext.display.Display.resolution`.
""" """
renderMode = copy.copy(slicecanvas.SliceCanvas.renderMode) renderMode = copy.copy(slicecanvas.SliceCanvas.renderMode)
"""Enable two-stage rendering, useful for low-performance graphics cards/ """Controls the rendering mode, useful for low-performance graphics cards/
software rendering. software rendering.
See :attr:`~fsl.fslview.gl.slicecanvas.SliceCanvas.twoStageRender`. See :attr:`.SliceCanvas.renderMode`.
""" """
softwareMode = copy.copy(slicecanvas.SliceCanvas.softwareMode) softwareMode = copy.copy(slicecanvas.SliceCanvas.softwareMode)
"""If ``True``, all images should be displayed in a mode optimised for """If ``True``, all images should be displayed in a mode optimised for
...@@ -93,10 +92,10 @@ class SceneOpts(props.HasProperties): ...@@ -93,10 +92,10 @@ class SceneOpts(props.HasProperties):
name = '{}_{}'.format(type(self).__name__, id(self)) name = '{}_{}'.format(type(self).__name__, id(self))
self.addListener('performance', name, self._onPerformanceChange) self.addListener('performance', name, self._onPerformanceChange)
self._onPerformanceChange() self.__onPerformanceChange()
def _onPerformanceChange(self, *a): def __onPerformanceChange(self, *a):
"""Called when the :attr:`performance` property changes. """Called when the :attr:`performance` property changes.
Changes the values of the :attr:`renderMode`, :attr:`softwareMode` Changes the values of the :attr:`renderMode`, :attr:`softwareMode`
......
...@@ -4,16 +4,17 @@ ...@@ -4,16 +4,17 @@
# #
# Author: Paul McCarthy <pauldmccarthy@gmail.com> # Author: Paul McCarthy <pauldmccarthy@gmail.com>
# #
"""This module defines the :class:`VectorOpts` class, which contains """This module defines the :class:`VectorOpts` class, which contains display
display options for rendering :class:`~fsl.fslview.gl.glvector.GLVector` options for rendering :class:`.GLVector` instances.
instances.
""" """
import props import props
import fsl.data.image as fslimage
import fsl.data.strings as strings import fsl.data.strings as strings
import display as fsldisplay import display as fsldisplay
class VectorOpts(fsldisplay.DisplayOpts): class VectorOpts(fsldisplay.DisplayOpts):
...@@ -53,75 +54,86 @@ class VectorOpts(fsldisplay.DisplayOpts): ...@@ -53,75 +54,86 @@ class VectorOpts(fsldisplay.DisplayOpts):
def __init__(self, def __init__(self,
image, overlay,
display, display,
imageList, overlayList,
displayCtx, displayCtx,
parent=None, parent=None,
*args, *args,
**kwargs): **kwargs):
"""Create a ``VectorOpts`` instance for the given image. """Create a ``VectorOpts`` instance for the given image.
See the :class:`~fsl.fslview.displaycontext.display.DisplayOpts` See the :class:`.DisplayOpts` documentation for more details.
documentation for more details.
""" """
if not isinstance(overlay, fslimage.Image):
raise RuntimeError('{} can only be used with an {} overlay'.format(
type(self).__name__, fslimage.Image.__name__))
fsldisplay.DisplayOpts.__init__(self, fsldisplay.DisplayOpts.__init__(self,
image, overlay,
display, display,
imageList, overlayList,
displayCtx, displayCtx,
parent, parent,
*args, *args,
**kwargs) **kwargs)
imageList.addListener('images', self.name, self.imageListChanged) overlayList.addListener('overlays',
self.imageListChanged() self.name,
self.__overlayListChanged)
self.__overlayListChanged()
def imageListChanged(self, *a): def __overlayListChanged(self, *a):
"""Called when the image list changes. Updates the ``modulate`` """Called when the overlay list changes. Updates the ``modulate``
property so that it contains a list of images which could be used property so that it contains a list of overlays which could be used
to modulate the vector image. to modulate the vector image.
""" """
modProp = self.getProp('modulate') modProp = self.getProp('modulate')
modVal = self.modulate modVal = self.modulate
images = self.displayCtx.getOrderedImages() overlays = self.displayCtx.getOrderedOverlays()
# the image for this VectorOpts # the image for this VectorOpts
# instance has been removed # instance has been removed
if self.image not in images: if self.overlay not in overlays:
self.imageList.removeListener('images', self.name) self.overlayList.removeListener('overlays', self.name)
return return
modOptions = ['none'] modOptions = ['none']
modLabels = [strings.choices['VectorOpts.modulate.none']] modLabels = [strings.choices['VectorOpts.modulate.none']]
for image in images: for overlay in overlays:
# It doesn't make sense to # It doesn't make sense to
# modulate the image by itself # modulate the image by itself
if image is self.image: if overlay is self.overlay:
continue
# The modulate image must
# be an image. Duh.
if not isinstance(overlay, fslimage.Image):
continue continue
# an image can only be used to modulate # an image can only be used to modulate
# the vector image if it shares the same # the vector image if it shares the same
# dimensions as said vector image # dimensions as said vector image
if image.shape != self.image.shape[:3]: if overlay.shape != self.overlay.shape[:3]:
continue continue
modOptions.append(image) modOptions.append(overlay)
modLabels .append(image.name) modLabels .append(overlay.name)
image.addListener('name', overlay.addListener('name',
self.name, self.name,
self.imageListChanged, self.__overlayListChanged,
overwrite=True) overwrite=True)
modProp.setChoices(modOptions, modLabels, self) modProp.setChoices(modOptions, modLabels, self)
if modVal in images: self.modulate = modVal if modVal in overlays: self.modulate = modVal
else: self.modulate = 'none' else: self.modulate = 'none'
# TODO RGBVector/LineVector subclasses for any type # TODO RGBVector/LineVector subclasses for any type
......
...@@ -5,8 +5,7 @@ ...@@ -5,8 +5,7 @@
# Author: Paul McCarthy <pauldmccarthy@gmail.com> # Author: Paul McCarthy <pauldmccarthy@gmail.com>
# #
"""This module defines the :class:`VolumeOpts` class, which contains """This module defines the :class:`VolumeOpts` class, which contains
display options for rendering :class:`~fsl.fslview.gl.glvolume.GLVolume` display options for rendering :class:`.GLVolume` instances.
instances.
""" """
import logging import logging
...@@ -15,6 +14,7 @@ import numpy as np ...@@ -15,6 +14,7 @@ import numpy as np
import props import props
import fsl.data.image as fslimage
import fsl.data.strings as strings import fsl.data.strings as strings
import fsl.fslview.colourmaps as fslcm import fsl.fslview.colourmaps as fslcm
...@@ -35,12 +35,11 @@ log = logging.getLogger(__name__) ...@@ -35,12 +35,11 @@ log = logging.getLogger(__name__)
class VolumeOpts(fsldisplay.DisplayOpts): class VolumeOpts(fsldisplay.DisplayOpts):
"""A class which describes how an :class:`~fsl.data.image.Image` should """A class which describes how an :class:`.Image` should be displayed.
be displayed.
This class doesn't have much functionality - it is up to things which This class doesn't have much functionality - it is up to things which
actually display an :class:`~fsl.data.image.Image` to adhere to the actually display an :class:`.Image` to adhere to the properties stored in
properties stored in the associated :class:`ImageDisplay` object. the associated :class:`.Display` and :class:`VolumeOpts` object.
""" """
...@@ -91,12 +90,12 @@ class VolumeOpts(fsldisplay.DisplayOpts): ...@@ -91,12 +90,12 @@ class VolumeOpts(fsldisplay.DisplayOpts):
def __init__(self, image, display, imageList, displayCtx, parent=None): def __init__(self, overlay, display, overlayList, displayCtx, parent=None):
"""Create an :class:`ImageDisplay` for the specified image. """Create a :class:`VolumeOpts` instance for the specified image."""
See the :class:`~fsl.fslview.displaycontext.display.DisplayOpts` if not isinstance(overlay, fslimage.Image):
documentation for more details. raise RuntimeError('{} can only be used with an {} overlay'.format(
""" type(self).__name__, fslimage.Image.__name__))
# Attributes controlling image display. Only # Attributes controlling image display. Only
# determine the real min/max for small images - # determine the real min/max for small images -
...@@ -104,13 +103,13 @@ class VolumeOpts(fsldisplay.DisplayOpts): ...@@ -104,13 +103,13 @@ class VolumeOpts(fsldisplay.DisplayOpts):
# it may be! So we calculate the min/max of a # it may be! So we calculate the min/max of a
# sample (either a slice or an image, depending # sample (either a slice or an image, depending
# on whether the image is 3D or 4D) # on whether the image is 3D or 4D)
if np.prod(image.shape) > 2 ** 30: if np.prod(overlay.shape) > 2 ** 30:
sample = image.data[..., image.shape[-1] / 2] sample = overlay.data[..., overlay.shape[-1] / 2]
self.dataMin = float(sample.min()) self.dataMin = float(sample.min())
self.dataMax = float(sample.max()) self.dataMax = float(sample.max())
else: else:
self.dataMin = float(image.data.min()) self.dataMin = float(overlay.data.min())
self.dataMax = float(image.data.max()) self.dataMax = float(overlay.data.max())
dRangeLen = abs(self.dataMax - self.dataMin) dRangeLen = abs(self.dataMax - self.dataMin)
dMinDistance = dRangeLen / 10000.0 dMinDistance = dRangeLen / 10000.0
...@@ -135,9 +134,9 @@ class VolumeOpts(fsldisplay.DisplayOpts): ...@@ -135,9 +134,9 @@ class VolumeOpts(fsldisplay.DisplayOpts):
self.setConstraint('displayRange', 'minDistance', dMinDistance) self.setConstraint('displayRange', 'minDistance', dMinDistance)
fsldisplay.DisplayOpts.__init__(self, fsldisplay.DisplayOpts.__init__(self,
image, overlay,
display, display,
imageList, overlayList,
displayCtx, displayCtx,
parent) parent)
...@@ -146,11 +145,11 @@ class VolumeOpts(fsldisplay.DisplayOpts): ...@@ -146,11 +145,11 @@ class VolumeOpts(fsldisplay.DisplayOpts):
# Display.brightness/contrast properties, so changes # Display.brightness/contrast properties, so changes
# in one are reflected in the other. # in one are reflected in the other.
if parent is not None: if parent is not None:
display.addListener('brightness', self.name, self.briconChanged) display.addListener('brightness', self.name, self.__briconChanged)
display.addListener('contrast', self.name, self.briconChanged) display.addListener('contrast', self.name, self.__briconChanged)
self .addListener('displayRange', self .addListener('displayRange',
self.name, self.name,
self.displayRangeChanged) self.__displayRangeChanged)
# Because displayRange and bri/con are intrinsically # Because displayRange and bri/con are intrinsically
# linked, it makes no sense to let the user sync/unsync # linked, it makes no sense to let the user sync/unsync
...@@ -185,9 +184,9 @@ class VolumeOpts(fsldisplay.DisplayOpts): ...@@ -185,9 +184,9 @@ class VolumeOpts(fsldisplay.DisplayOpts):
are registered on the :attr:`displayRange` and are registered on the :attr:`displayRange` and
:attr:`.Display.brightness`/:attr:`.Display.contrast`/ properties. :attr:`.Display.brightness`/:attr:`.Display.contrast`/ properties.
Because these properties are linked via the :meth:`displayRangeChanged` Because these properties are linked via the
and :meth:`briconChanged` methods, we need to be careful about avoiding :meth:`__displayRangeChanged` and :meth:`__briconChanged` methods,
recursive callbacks. we need to be careful about avoiding recursive callbacks.
Furthermore, because the properties of both :class:`VolumeOpts` and Furthermore, because the properties of both :class:`VolumeOpts` and
:class:`.Display` instances are possibly synchronised to a parent :class:`.Display` instances are possibly synchronised to a parent
...@@ -224,9 +223,9 @@ class VolumeOpts(fsldisplay.DisplayOpts): ...@@ -224,9 +223,9 @@ class VolumeOpts(fsldisplay.DisplayOpts):
peer .disableListener('displayRange', peer.name) peer .disableListener('displayRange', peer.name)
def briconChanged(self, *a): def __briconChanged(self, *a):
"""Called when the ``brightness``/``contrast`` properties of the """Called when the ``brightness``/``contrast`` properties of the
:class:`~fsl.fslview.displaycontext.display.Display` instance change. :class:`.Display` instance change.
Updates the :attr:`displayRange` property accordingly. Updates the :attr:`displayRange` property accordingly.
...@@ -243,7 +242,7 @@ class VolumeOpts(fsldisplay.DisplayOpts): ...@@ -243,7 +242,7 @@ class VolumeOpts(fsldisplay.DisplayOpts):
self.__toggleListeners(True) self.__toggleListeners(True)
def displayRangeChanged(self, *a): def __displayRangeChanged(self, *a):
"""Called when the `attr`:displayRange: property changes. """Called when the `attr`:displayRange: property changes.
Updates the :attr:`.Display.brightness` and :attr:`.Display.contrast` Updates the :attr:`.Display.brightness` and :attr:`.Display.contrast`
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment