From 3a934425b0235d0eafbc755b07496a48e07f1ee7 Mon Sep 17 00:00:00 2001 From: Paul McCarthy <pauld.mccarthy@gmail.com> Date: Wed, 16 Mar 2016 12:01:14 +0000 Subject: [PATCH] New module 'state' which saves/restores the state of a FSLEyesFrame - restore not implemented yet. Most logic moved from diagnosticreport into state. --- fsl/fsleyes/__init__.py | 3 + fsl/fsleyes/actions/diagnosticreport.py | 125 +---------- fsl/fsleyes/frame.py | 18 +- fsl/fsleyes/perspectives.py | 6 +- fsl/fsleyes/state.py | 272 ++++++++++++++++++++++++ 5 files changed, 306 insertions(+), 118 deletions(-) create mode 100644 fsl/fsleyes/state.py diff --git a/fsl/fsleyes/__init__.py b/fsl/fsleyes/__init__.py index cd6493777..f78c8a98e 100644 --- a/fsl/fsleyes/__init__.py +++ b/fsl/fsleyes/__init__.py @@ -187,8 +187,11 @@ Some other miscellaneous modules are contained in the ``fsleyes`` package: ~fsl.fsleyes.toolbar ~fsl.fsleyes.tooltips ~fsl.fsleyes.perspectives + ~fsl.fsleyes.autodisplay + ~fsl.fsleyes.state ~fsl.fsleyes.icons ~fsl.fsleyes.colourmaps ~fsl.fsleyes.plotting ~fsl.fsleyes.splash + ~fsl.fsleyes.about """ diff --git a/fsl/fsleyes/actions/diagnosticreport.py b/fsl/fsleyes/actions/diagnosticreport.py index 56b08e81e..6c8d6a361 100644 --- a/fsl/fsleyes/actions/diagnosticreport.py +++ b/fsl/fsleyes/actions/diagnosticreport.py @@ -16,9 +16,11 @@ import logging import platform from collections import OrderedDict -import action -import fsl.data.strings as strings -import fsl.utils.status as status +import fsl.data.strings as strings +import fsl.utils.status as status +import fsl.fsleyes.state as fslstate + +from . import action log = logging.getLogger(__name__) @@ -92,8 +94,7 @@ class DiagnosticReportAction(action.Action): state. """ - import fsl.version as version - import fsl.fsleyes.perspectives as perspectives + import fsl.version as version report = OrderedDict() overlays = [] @@ -111,25 +112,11 @@ class DiagnosticReportAction(action.Action): report['VCS Version'] = version.__vcs_version__ report['OpenGL'] = self.__openGLReport() report['Settings'] = self.__settingsReport() - report['Layout'] = perspectives.serialisePerspective(self.__frame) - report['Overlays'] = overlays - - report['Master display context'] = self.__displayContextReport( - self.__overlayList, - self.__displayCtx) - for viewPanel in self.__frame.getViewPanels(): + state = fslstate.save(self.__frame) - # Accessing the undocumented - # AuiPaneInfo.name attribute - vpName = self.__frame.getViewPanelInfo(viewPanel).name - vpDisplayCtx = viewPanel.getDisplayContext() - - report[vpName] = OrderedDict([ - ('View', self.__viewPanelReport(viewPanel)), - ('Display context', self.__displayContextReport( - self.__overlayList, - vpDisplayCtx))]) + for k, v in state.items(): + report[k] = v return report @@ -182,100 +169,6 @@ class DiagnosticReportAction(action.Action): return report - - def __displayContextReport(self, overlayList, displayCtx): - """Creates and returns a hierarchical dictionary containing - information about the given :class:`.DisplayContext` and the - :class:`.Display`/:class:`.DisplayOpts` instances which it - is managing. - """ - - report = OrderedDict() - overlays = [] - props = displayCtx.getAllProperties()[0] - - for overlay in overlayList: - - display = displayCtx.getDisplay(overlay) - opts = displayCtx.getOpts( overlay) - - overlays.append(OrderedDict([ - ('Display', self.__displayReport( display)), - ('DisplayOpts', self.__displayOptsReport(opts))])) - - for prop in props: - report[prop] = str(getattr(displayCtx, prop)) - - report['overlays'] = overlays - - return report - - - def __displayReport(self, display): - """Creates and returns a dictionary containing informtion about - the given :class:`.Display` instance. - """ - - report = OrderedDict() - props = display.getAllProperties()[0] - - for prop in props: - report[prop] = str(getattr(display, prop)) - - return report - - - def __displayOptsReport(self, opts): - """Creates and returns a dictionary containing informtion about - the given :class:`.DisplayOpts` instance. - """ - - report = OrderedDict() - - report['type'] = type(opts).__name__ - - props = opts.getAllProperties()[0] - - for prop in props: - value = getattr(opts, prop) - if prop in ('cmap', 'negativeCmap'): - value = value.name - - report[prop] = str(value) - - return report - - - def __viewPanelReport(self, viewPanel): - """Creates and returns a dictionary containing informtion about - the given :class:`.ViewPanel`. - """ - - import fsl.fsleyes.views as views - - report = OrderedDict() - props = viewPanel.getAllProperties()[0] - - report['type'] = type(viewPanel).__name__ - - for prop in props: - report[prop] = str(getattr(viewPanel, prop)) - - if isinstance(viewPanel, views.CanvasPanel): - - sceneOptsReport = OrderedDict() - sceneOpts = viewPanel.getSceneOptions() - props = sceneOpts.getAllProperties()[0] - - sceneOptsReport['type'] = type(sceneOpts).__name__ - - for prop in props: - sceneOptsReport[prop] = str(getattr(sceneOpts, prop)) - - report['SceneOpts'] = sceneOptsReport - - return report - def __formatReport(self, reportDict): """Converts the given hierarchical dictionary to a JSON-formatted diff --git a/fsl/fsleyes/frame.py b/fsl/fsleyes/frame.py index 85ac4b104..4d8ae9260 100644 --- a/fsl/fsleyes/frame.py +++ b/fsl/fsleyes/frame.py @@ -73,6 +73,8 @@ class FSLEyesFrame(wx.Frame): .. autosummary:: :nosignatures: + getOverlayList + getDisplayContext getViewPanels getViewPanelInfo addViewPanel @@ -209,6 +211,20 @@ class FSLEyesFrame(wx.Frame): self.Layout() + def getOverlayList(self): + """Returns the :class:`.OverlayList` which contains the overlays + being displayed by this ``FSLEyesFrame``. + """ + return self.__overlayList + + + def getDisplayContext(self): + """Returns the top-level :class:`.DisplayContext` associated with this + ``FSLEyesFrame``. + """ + return self.__displayCtx + + def getViewPanels(self): """Returns a list of all :class:`.ViewPanel` instances that are currenlty displayed in this ``FSLEyesFrame``. @@ -266,7 +282,7 @@ class FSLEyesFrame(wx.Frame): self.__onViewPanelClose(panel=vp, displaySync=False) self.__auiManager.ClosePane(paneInfo) - self.__auiManager.Update() + self.__auiManager.Update() def addViewPanel(self, panelCls): diff --git a/fsl/fsleyes/perspectives.py b/fsl/fsleyes/perspectives.py index 411f13582..58e1c4f9e 100644 --- a/fsl/fsleyes/perspectives.py +++ b/fsl/fsleyes/perspectives.py @@ -565,7 +565,11 @@ def _addControlPanel(viewPanel, panelType): def _getPanelProps(panel): - """ + """Creates and returns two dictionaries, containing properties of the given + :class:`.ViewPanel` (and its associated :class:`.SceneOpts` instance, if + it is a :class:`.CanvasPanel`), which are to be saved as part of a + seriaised *FSLeyes* perspective. The properties to be saved are listed in + the :data:`VIEWPANEL_PROPS` dictionary. """ import fsl.fsleyes.views as views diff --git a/fsl/fsleyes/state.py b/fsl/fsleyes/state.py new file mode 100644 index 000000000..6eab41bcb --- /dev/null +++ b/fsl/fsleyes/state.py @@ -0,0 +1,272 @@ +#!/usr/bin/env python +# +# state.py - Functions for saving/restoring the state of *FSLeyes*. +# +# Author: Paul McCarthy <pauldmccarthy@gmail.com> +# +"""This module provides two functions, :func:`save` and :func:`restore`. +These functions may be used to save/restore the state of *FSLeyes*. + + .. note:: :func:`restore` is currently not implemented. +""" + +import importlib +from collections import OrderedDict + +import props + +from . import perspectives + + +def save(frame): + """Creates and returns a dictionary containing the current state of the + given :class:`.FSLeyesFrame`, the :class:`.OverlayList` and + :class:`.DisplayContext` associated with it, and the :class:`.ViewPanel` + instances being displayed. + """ + + overlayList = frame.getOverlayList() + displayCtx = frame.getDisplayContext() + viewPanels = frame.getViewPanels() + + layout = perspectives.serialisePerspective(frame) + overlays = [(type(o).__name__, o.dataSource) for o in overlayList] + + overlays = [] + + for i, ovl in enumerate(overlayList): + overlays.append(OrderedDict([ + ('type', '{}.{}'.format(ovl.__module__, type(ovl).__name__)), + ('name', ovl.name), + ('source', ovl.dataSource)])) + + state = OrderedDict() + state['Layout'] = layout + state['Overlays'] = overlays + state['DisplayContext'] = _displayContextState(overlayList, displayCtx) + + vpStates = {} + + for vp in viewPanels: + + # Accessing the undocumented + # AuiPaneInfo.name attribute + vpName = frame.getViewPanelInfo(vp).name + vpDisplayCtx = vp.getDisplayContext() + + vpStates[vpName] = OrderedDict([ + ('View', _viewPanelState(vp)), + ('DisplayContext', _displayContextState(overlayList, + vpDisplayCtx))]) + + state['ViewPanels'] = vpStates + + return state + + +def _displayContextState(overlayList, displayCtx): + """Creates and returns a hierarchical dictionary containing + the state of the given :class:`.DisplayContext` and the + :class:`.Display`/:class:`.DisplayOpts` instances which it + is managing. + """ + + state = OrderedDict() + overlays = [] + propNames = displayCtx.getAllProperties()[0] + + for overlay in overlayList: + + display = displayCtx.getDisplay(overlay) + opts = displayCtx.getOpts( overlay) + + overlays.append(OrderedDict([ + ('Display', _displayState( display)), + ('DisplayOpts', _displayOptsState(opts))])) + + for propName in propNames: + state[propName] = props.serialise(displayCtx, propName) + print 'displayCtx.{} = {}'.format(propName, state[propName]) + + state['Overlays'] = overlays + + return state + + +def _displayState(display): + """Creates and returns a dictionary containing the state of the given + :class:`.Display` instance. + """ + + state = OrderedDict() + propNames = display.getAllProperties()[0] + + for propName in propNames: + state[propName] = props.serialise(display, propName) + + return state + + +def _displayOptsState(opts): + """Creates and returns a dictionary containing the state of the given + :class:`.DisplayOpts` instance. + """ + + state = OrderedDict() + propNames = opts.getAllProperties()[0] + + state['type'] = type(opts).__name__ + + for propName in propNames: + state[propName] = props.serialise(opts, propName) + + return state + + +def _viewPanelState(viewPanel): + """Creates and returns a dictionary containing the state of the given + :class:`.ViewPanel`. + """ + + import fsl.fsleyes.views as views + + state = OrderedDict() + propNames = viewPanel.getAllProperties()[0] + + state['type'] = type(viewPanel).__name__ + + for propName in propNames: + state[propName] = props.serialise(viewPanel, propName) + + if isinstance(viewPanel, views.CanvasPanel): + + sceneOptsState = OrderedDict() + sceneOpts = viewPanel.getSceneOptions() + propNames = sceneOpts.getAllProperties()[0] + + sceneOptsState['type'] = type(sceneOpts).__name__ + + for propName in propNames: + sceneOptsState[propName] = props.serialise(sceneOpts, propName) + + state['SceneOpts'] = sceneOptsState + + return state + + +def restore(frame, state): + """Applies a state, previously saved via :func:`save`, to the given + :class:`.FSLeyesFrame`. + + .. note:: Not implemented. + """ + + return + + overlayList = frame.getOverlayList() + displayCtx = frame.getDisplayContext() + layout = state['Layout'] + overlays = state['Overlays'] + displayCtxState = state['DisplayContext'] + vpStates = state['ViewPanels'] + + # First, remove all view panels and then + # clear the overlay list. We do it in this + # order to avoid any unncessary processing + # by existing view panel DisplayContexts. + frame.removeAllViewPanels() + overlayList[:] = [] + + # Populate the overlay list and configure + # the master display context + overlayObjs = [] + for ovl in overlays: + klass = ovl['type'] + name = ovl['name'] + dataSource = ovl['source'] + + # We can't restore in-memory overlays + if dataSource is None or dataSource.lower() == 'none': + continue + + # Split the (fully-qualified) + # class name into the containing + # module and the class name. + klass = klass.split('.') + module = '.'.join(klass[:-1]) + klass = klass[-1] + + # Import the module, and + # look up the class object + module = importlib.import_module(module) + klass = getattr(module, klass) + + # Create the overlay object + ovl = klass(dataSource) + ovl.name = name + overlayObjs.append(ovl) + + # Restore the master display contxt + _restoreDisplayContext(displayCtx, overlayList, displayCtxState) + + # Apply the frame perspective + perspectives.applyPerspective(frame, None, layout) + + # Restore view panel settings + viewPanels = frame.getViewPanels() + for vp, vpState in zip(viewPanels, vpStates): + _restoreViewPanel(vp, overlayList, vpState) + + +def _restoreViewPanel(viewPanel, overlayList, state): + + displayCtx = viewPanel.getDisplayContext() + sceneOpts = viewPanel.getSceneOptions() + vpState = state['View'] + dcState = state['DisplayContext'] + vpType = vpState.pop('type') + sceneOptsState = vpState.pop('SceneOpts') + + _restoreDisplayContext(displayCtx, overlayList, dcState) + _applyProps(viewPanel, vpState) + _applyProps(sceneOpts, sceneOptsState) + + +def _restoreDisplayContext(displayCtx, overlayList, state): + + # Configure selected properties + # of the master display context + dcProps = ['selectedOverlay', + 'location', + # 'overlayOrder', + 'syncOverlayDisplay', + 'displaySpace', + 'autoDisplay'] + + _applyProps(displayCtx, state, dcProps) + + for overlay, ovlState in zip(overlayList, state['Overlays']): + displayState = ovlState['Display'] + optsState = ovlState['DisplayOpts'] + + display = displayCtx.getDisplay(overlay) + _applyProps(display, displayState) + + # TODO: This will almost certainly fail + # for non-trivial properties (e.g. + # VolumeOpts.clipImage) + opts = displayCtx.getOpts(overlay) + _applyProps(opts, optsState) + + +def _applyProps(target, values, propNames=None): + + if propNames is None: + propNames = values.keys() + + for propName in propNames: + + val = values[propName] + val = props.deserialise(target, propName, val) + + setattr(target, propName, val) -- GitLab