From cb9a2b07c6c8f90b7800579ecf222f08c91c69d9 Mon Sep 17 00:00:00 2001 From: Paul McCarthy <pauld.mccarthy@gmail.com> Date: Fri, 4 Sep 2015 14:19:07 +0100 Subject: [PATCH] Documentation for displaycontext package, and group and overlay modules. --- doc/fsl.fsleyes.displaycontext.rst | 21 ---- fsl/fsleyes/__init__.py | 5 + fsl/fsleyes/displaycontext/__init__.py | 117 +++++++++++++++++++- fsl/fsleyes/displaycontext/group.py | 96 ++++++++++++++-- fsl/fsleyes/overlay.py | 147 ++++++++++++++++--------- 5 files changed, 299 insertions(+), 87 deletions(-) diff --git a/doc/fsl.fsleyes.displaycontext.rst b/doc/fsl.fsleyes.displaycontext.rst index 3ee2e37ce..daac40f39 100644 --- a/doc/fsl.fsleyes.displaycontext.rst +++ b/doc/fsl.fsleyes.displaycontext.rst @@ -1,27 +1,6 @@ fsl.fsleyes.displaycontext package ================================== -Submodules ----------- - -.. toctree:: - - fsl.fsleyes.displaycontext.canvasopts - fsl.fsleyes.displaycontext.display - fsl.fsleyes.displaycontext.displaycontext - fsl.fsleyes.displaycontext.group - fsl.fsleyes.displaycontext.labelopts - fsl.fsleyes.displaycontext.lightboxopts - fsl.fsleyes.displaycontext.maskopts - fsl.fsleyes.displaycontext.modelopts - fsl.fsleyes.displaycontext.orthoopts - fsl.fsleyes.displaycontext.sceneopts - fsl.fsleyes.displaycontext.vectoropts - fsl.fsleyes.displaycontext.volumeopts - -Module contents ---------------- - .. automodule:: fsl.fsleyes.displaycontext :members: :undoc-members: diff --git a/fsl/fsleyes/__init__.py b/fsl/fsleyes/__init__.py index 410939480..2185f156a 100644 --- a/fsl/fsleyes/__init__.py +++ b/fsl/fsleyes/__init__.py @@ -151,6 +151,10 @@ overlay in the ``OverlayList``) are linked to the master ``DisplayContext`` the ``props`` package. +See the :mod:`~fsl.fsleyes.displaycontext` package documentation for more +details. + + -------------------- Package organisation -------------------- @@ -179,4 +183,5 @@ Some other miscellaneous modules are contained in the ``fsleyes`` package: ~fsl.fsleyes.frame ~fsl.fsleyes.panel ~fsl.fsleyes.toolbar + """ diff --git a/fsl/fsleyes/displaycontext/__init__.py b/fsl/fsleyes/displaycontext/__init__.py index 08ec6a172..af2bf65fa 100644 --- a/fsl/fsleyes/displaycontext/__init__.py +++ b/fsl/fsleyes/displaycontext/__init__.py @@ -4,6 +4,118 @@ # # Author: Paul McCarthy <pauldmccarthy@gmail.com> # +"""The ``displaycontext`` package contains classes which define the display +options for pretty much everything in *FSLeyes*. + + +.. note:: Before perusing this package, you should read the high level + overview in the :mod:`~fsl.fsleyes` package documentation. Go on - + it won't take you too long. + + +-------- +Overview +-------- + + +The most important classes defined in this package are: + + - the :class:`.DisplayContext` class, which defines how all of the + overlays in an :class:`.OverlayList` should be displayed. + + - the :class:`.Display` class, which defines how a single overlay should be + displayed. + + - the :class:`.DisplayOpts` base class, and its sub-classes, which define + overlay type specific options. + + +A :class:`.DisplayContext` instance encapsulates an :class:`.OverlayList`, and +defines how the overlays in the list should be displayed. Each +:class:`.ViewPanel` displayed in *FSLeyes* (e.g. the :class:`.OrthoPanel`) has +its own ``DisplayContext`` instance; a ``ViewPanel`` uses its +``DisplayContext`` instance to configure general display properties, and also +to access the :class:`.Display` properties for individual overlays. + + +All of the classes mentioned on ths page are defined in sub-modules, but are +imported into, and are thus available from, the ``displaycontext`` package +namespace. For example:: + + import fsl.fsleyes.displaycontext as fsldc + + # The VolumeOpts class is defined in the + # fsl.fsleyes.displaycontext.volumeopts + # module, but is available in the + # fsl.fsleyes.displaycontext namespace. + volopts = fsldc.VolumeOpts(overlay, display, overlayList, displayCtx) + + +----------------------- +Overlay display options +----------------------- + + +The :class:`.Display` class, and the :class:`.DisplayOpts` sub-classes define +how to display a single overlay. Options common to all overlays +(e.g. :attr:`.Display.brightness`, :attr:`.Display.alpha`) are defined in the +``Display`` class, whereas options which are specific to a particular overlay +type (e.g. :attr:`.VolumeOpts.cmap`, :attr:`.LineVectorOpts.lineWidth`) are +defined in the corresponding :class:`.DisplayOpts` sub-class. + + +The ``Display`` instance for a particular overlay owns and manages a single +``DisplayOpts`` instance - whenever the overlay display type is changed, the +``Display`` instance deletes the old ``DisplayOpts`` instance, and creates a +new one accordingly. The following ``DisplayOpts`` sub-classes exist: + +.. autosummary:: + :nosignatures: + + ~fsl.fsleyes.displaycontext.volumeopts.ImageOpts + ~fsl.fsleyes.displaycontext.volumeopts.VolumeOpts + ~fsl.fsleyes.displaycontext.maskopts.MaskOpts + ~fsl.fsleyes.displaycontext.vectoropts.VectorOpts + ~fsl.fsleyes.displaycontext.vectoropts.RGBVectorOpts + ~fsl.fsleyes.displaycontext.vectoropts.LineVectorOpts + ~fsl.fsleyes.displaycontext.modelopts.ModelOpts + ~fsl.fsleyes.displaycontext.labelopts.LabelOpts + + +-------------- +Overlay groups +-------------- + + +.. note:: Support for overlay groups is quite basic at this point in time. See + the :class:`.OverlayListPanel` for details. + +The :mod:`~.displaycontext.group` module provides the functionality to +link the display properties of one or more overlays. One or more +:class:`.OverlayGroup` instances may be added to the +:attr:`.DisplayContext.overlayGroups` list. + + +------------- +Scene options +------------- + + +Independent of the ``DisplayContext``, ``Display`` and ``DisplayOpts`` +classes, the ``displaycontext`` package is also home to a few classes which +define *scene* options. Every :class:`.CanvasPanel` instance uses a +:class:`.SceneOpts` sub-class to control the scene configuration. For example, +the :attr:`.LightBoxOpts.ncols` property controls how many columns of slices +the :class:`.LightBoxPanel` should display. The following scene option +classes exist: + +.. autosummary:: + :nosignatures: + + ~fsl.fsleyes.displaycontext.sceneopts.SceneOpts + ~fsl.fsleyes.displaycontext.orthoopts.OrthoOpts + ~fsl.fsleyes.displaycontext.lightboxopts.LightBoxOpts +""" import display @@ -31,6 +143,7 @@ from displaycontext import InvalidOverlayError ALL_OVERLAY_TYPES = list(set( reduce(lambda a, b: a + b, display.OVERLAY_TYPES.values()))) -"""This attribute contains a list of all possible overlay types - see the : -:attr:`.Display.overlayType` property. +"""This attribute contains a list of all possible overlay types - see the +:attr:`.Display.overlayType` property and tge :data:`.display.OVERLAY_TYPES` +dictionary for more details. """ diff --git a/fsl/fsleyes/displaycontext/group.py b/fsl/fsleyes/displaycontext/group.py index e4ecee268..392592f0c 100644 --- a/fsl/fsleyes/displaycontext/group.py +++ b/fsl/fsleyes/displaycontext/group.py @@ -4,6 +4,10 @@ # # Author: Paul McCarthy <pauldmccarthy@gmail.com> # +"""This module provides the :class:`OverlayGroup` class, which allows +the display properties of one or more overlays to be linked. +""" + import logging import copy @@ -17,11 +21,43 @@ log = logging.getLogger(__name__) class OverlayGroup(props.HasProperties): + """An ``OverlayGroup`` is a group of overlays for which the corresponding + :class:`.Display` and :class:`.DisplayOpts` properties are synchronised. + + + The point of the ``OverlayGroup`` is to allow the user to define groups of + overlays, so he/she can change display properties on the entire group, + instead of having to change display properties on each overlay one by one. + + + Overlays can be added to an ``OverlayGroup`` with the :meth:`addOverlay`, + and removed with the :meth:`removeOverlay`. + + + When an ``OverlayGroup`` is created, it dynamically adds all of the + properties which could possibly be linked between overlays to itself, + using the :meth:`props.HasProperties.addProperty` method. When the first + overlay is added to the group, these group properties are set to the + display properties of this overlay. Then, the display properties of + overlays which are subsequently added to the group will be set to the + group display properties. + + + .. note:: Currently, only a subset of display properties are linked + between the overlays in a group. The properties which are linked + are hard-coded in the :attr:`_groupBindings` dictionary. + + A possible future *FSLeyes* enhancement will be to allow the + user to specify which display properties within an + ``OverlayGroup`` should be linked. + """ overlays = props.List() - """Do not add/remove overlays directly to this list - use the - :meth:`addOverlay` and :meth:`removeOverlay` methods. + """The list of overlays in this ``OverlayGroup``. + + .. warning:: Do not add/remove overlays directly to this list - use the + :meth:`addOverlay` and :meth:`removeOverlay` methods instead. """ @@ -46,22 +82,32 @@ class OverlayGroup(props.HasProperties): 'directed'], 'RGBVectorOpts' : ['interpolation'], }) - """This dictionary defines the properties which are bound across Display - instances, and instances of the DisplayOpts sub-classes, for overlays in - the same group. + """This dictionary defines the properties which are bound across + :class:`.Display` instances :class:`.DisplayOpts` sub-class instances, for + overlays which are in the same group. """ def __init__(self, displayCtx, overlayList): + """Create an ``OverlayGroup``. + + :arg displayCtx: The :class:`.DisplayContext`. + + :arg overlayList: The :class:`.OverlayList`. + """ self.__displayCtx = displayCtx self.__overlayList = overlayList - self.__hasBeenSet = {} self.__name = '{}_{}'.format(type(self).__name__, id(self)) - # Copy all of the properties listed - # in the _groupBindings dict - from . import \ + # This dict is used by the __bindDisplayOpts + # method to keep track of which group properties + # have already been given a value + self.__hasBeenSet = {} + + # Import all of the Display/DisplayOpts + # classes into the local namespace + from fsl.fsleyes.displaycontext import \ Display, \ ImageOpts, \ VolumeOpts, \ @@ -72,7 +118,12 @@ class OverlayGroup(props.HasProperties): ModelOpts, \ LabelOpts + # Add all of the properties listed + # in the _groupBindings dict as + # properties of this OverlayGroup + # instance. for clsName, propNames in OverlayGroup._groupBindings.items(): + cls = locals()[clsName] for propName in propNames: @@ -83,10 +134,22 @@ class OverlayGroup(props.HasProperties): def __copy__(self): + """Create a copy of this ``OverlayGroup``. + + A custom copy operator is needed due to the way that + the :class:`.props.HasProperties` class works. + """ return OverlayGroup(self, self.__displayCtx, self.__overlayList) def addOverlay(self, overlay): + """Add an overlay to this ``OverlayGroup``. + + If this is the first overlay to be added, the properties of this + ``OverlayGroup`` are set to the overlay display properties. Otherwise, + the overlay display properties are set to those of this + ``OverlayGroup``. + """ self.overlays.append(overlay) @@ -105,6 +168,7 @@ class OverlayGroup(props.HasProperties): def removeOverlay(self, overlay): + """Remove the given overlay from this ``OverlayGroup``. """ self.overlays.remove(overlay) @@ -125,6 +189,14 @@ class OverlayGroup(props.HasProperties): def __bindDisplayOpts(self, target, unbind=False): + """Binds or unbinds the properties of the given ``target`` to the + properties of this ``OverlayGroup``. + + :arg target: A :class:`.Display` or :class:`.DisplayOpts` instance. + + :arg unbind: Set to ``True`` to bind the properties, ``False`` to + unbind them. + """ # This is the first overlay to be added - the # group should inherit its property values @@ -170,5 +242,11 @@ class OverlayGroup(props.HasProperties): def __overlayTypeChanged(self, value, valid, display, name): + """This method is called when the :attr:`.Display.overlayType` + property for an overlay in the group changes. + + It makes sure that the display properties of the new + :class:`.DisplayOpts` instance are bound to the group properties. + """ opts = display.getDisplayOpts() self.__bindDisplayOpts(opts) diff --git a/fsl/fsleyes/overlay.py b/fsl/fsleyes/overlay.py index 664d1a15b..39300f0ee 100644 --- a/fsl/fsleyes/overlay.py +++ b/fsl/fsleyes/overlay.py @@ -1,14 +1,59 @@ #!/usr/bin/env python # -# overlay.py - +# overlay.py - Defines the OverlayList class, and a few utility functions # # Author: Paul McCarthy <pauldmccarthy@gmail.com> # """This module defines the :class:`OverlayList` class, which is a simple but -fundamental class in FSLEyes - it is a container for all displayed overlays. - +fundamental class in *FSLeyes* - it is a container for all loaded overlays. Only one ``OverlayList`` ever exists, and it is shared throughout the entire -application. +application. + + +**What is an overlay?** + + +The definition of an *overlay* is fairly broad; any object can be a added to +the ``OverlayList`` - there is no ``Overlay`` base class, nor any interface +which must be provided by an overlay object. The only requirements imposed on +an overlay type are: + + - Must be able to be created with a single ``__init__`` parameter, which + is a string specifying the data source location (e.g. a file name). + + - Must have an attribute called ``name``, which is used as the display name + for the overlay. + + - Must have an attribute called ``dataSource``, which is used to identify + the source of the overlay data. + + - Must be supported by the :mod:`~fsl.fsleyes.gl` package .. ok, this is a + pretty big requirement .. See the :mod:`.globject` and the + :data:`.display.OVERLAY_TYPES` documentation for details on how to get + started with this one. + + +Currently (``fslpy`` version |version|) the only overlay types in existence +(and able to be rendered) are: + +.. autosummary:: + :nosignatures: + + ~fsl.data.image.Image + ~fsl.data.model.Model + + +A few other utility functions are provided by this module: + +.. autosummary:: + :nosignatures: + + guessDataSourceType + makeWildcard + loadOverlays + interactiveLoadOverlays + saveOverlay + """ import logging @@ -28,28 +73,20 @@ class OverlayList(props.HasProperties): """Class representing a collection of overlays to be displayed together. Contains a :class:`props.properties_types.List` property called - ``overlays``, containing overlay objects (e.g. :class:`.Image` or - :class:`VTKModel`objects). + :attr:`overlays`, containing overlay objects (e.g. :class:`.Image` + or :class:`.Model`objects). Listeners can be registered on the + ``overlays`` property, so they are notified when the overlay list changes. An :class:`OverlayList` object has a few wrapper methods around the - :attr:`overlays` property, allowing the :class:`OverlayList` to be used - as if it were a list itself. - - There are no restrictions on the type of objects which may be contained - in the ``OverlayList``, but all objects must have a few attributes: - - - ``name`` ... - - - ``dataSource`` .. - - - Furthermore, all overlay types must be able to be created with a single - __init__ parameter, which is a string specifying the data source location - (e.g. a file). + :attr:`overlays` property, allowing the :class:`OverlayList` to be used as + if it were a list itself. The :meth:`addOverlays` method is also a + convenient way to allow the user (i.e. via a GUI) to add overlays to the + list. """ def __validateOverlay(self, atts, overlay): + """Makes sure that the given overlay object is valid.""" return (hasattr(overlay, 'name') and hasattr(overlay, 'dataSource')) @@ -57,7 +94,7 @@ class OverlayList(props.HasProperties): overlays = props.List( listType=props.Object(allowInvalid=False, validateFunc=__validateOverlay)) - """A list of overlay objects to be displayed""" + """A list of overlay objects to be displayed.""" def __init__(self, overlays=None): @@ -147,7 +184,7 @@ class OverlayList(props.HasProperties): def guessDataSourceType(filename): """A convenience function which, given the name of a file or directory, - figures out a suitable data source type. + figures out a suitable overlay type. Returns a tuple containing two values - a type which should be able to load the filename, and the filename, possibly adjusted. If the file type @@ -183,10 +220,7 @@ def guessDataSourceType(filename): def makeWildcard(): """Returns a wildcard string for use in a file dialog, to limit - the acceptable file types. - - :arg allowedExts: A list of strings containing the allowed file - extensions. + the the displayed file types to supported overlay file types. """ import fsl.data.image as fslimage @@ -210,25 +244,26 @@ def loadOverlays(paths, loadFunc='default', errorFunc='default', saveDir=True): """Loads all of the overlays specified in the sequence of files contained in ``paths``. - :param loadFunc: A function which is called just before each overlay - is loaded, and is passed the overlay path. The default - load function uses a :mod:`wx` popup frame to display - the name of the overlay currently being loaded. Pass in - ``None`` to disable this default behaviour. - - :param errorFunc: A function which is called if an error occurs while - loading an overlay, being passed the name of the - overlay, and either the :class:`Exception` which - occurred, or a string containing an error message. The - default function pops up a :class:`wx.MessageBox` with - an error message. Pass in ``None`` to disable this - default behaviour. - - :param saveDir: If ``True`` (the default), the directory of the last - overlay in the list of ``paths`` is saved, and used - later on as the default load directory. - - :Returns a list of overlay objects + :arg loadFunc: A function which is called just before each overlay + is loaded, and is passed the overlay path. The default + load function uses a :mod:`wx` popup frame to display + the name of the overlay currently being loaded. Pass in + ``None`` to disable this default behaviour. + + :arg errorFunc: A function which is called if an error occurs while + loading an overlay, being passed the name of the + overlay, and either the :class:`Exception` which + occurred, or a string containing an error message. The + default function pops up a :class:`wx.MessageBox` with + an error message. Pass in ``None`` to disable this + default behaviour. + + :arg saveDir: If ``True`` (the default), the directory of the last + overlay in the list of ``paths`` is saved, and used + later on as the default load directory. + + :returns: A list of overlay objects - just a regular ``list``, + not an :class:`OverlayList`. """ defaultLoad = loadFunc == 'default' @@ -302,18 +337,19 @@ def loadOverlays(paths, loadFunc='default', errorFunc='default', saveDir=True): def interactiveLoadOverlays(fromDir=None, **kwargs): - """Convenience method for interactively loading one or more overlays. + """Convenience function for interactively loading one or more overlays. - If the :mod:`wx` package is available, pops up a file dialog - prompting the user to select one or more overlays to load. + Pops up a file dialog prompting the user to select one or more overlays + to load. + + :arg fromDir: Directory in which the file dialog should start. If + ``None``, the most recently visited directory (via this + function) is used, or a directory from An already loaded + overlay, or the current working directory. - :param str fromDir: Directory in which the file dialog should start. - If ``None``, the most recently visited directory - (via this method) is used, or a directory from - an already loaded overlay, or the current working - directory. + :arg kwargs: Passed through to the :func:`loadOverlays` function. - Returns: A list containing the overlays that were loaded. + :returns: A list containing the overlays that were loaded. :raise ImportError: if :mod:`wx` is not present. :raise RuntimeError: if a :class:`wx.App` has not been created. @@ -362,11 +398,12 @@ def saveOverlay(overlay, fromDir=None): :param fromDir: Directory in which the file dialog should start. If ``None``, the most recently visited directory - (via this method) is used, or the directory from + (via this function) is used, or the directory from the given image, or the current working directory. :raise ImportError: if :mod:`wx` is not present. :raise RuntimeError: if a :class:`wx.App` has not been created. + :raise ValueError: if ``overlay`` is not an :class:`.Image` instance. """ import fsl.data.image as fslimage -- GitLab