Forked from
FSL / fslpy
2968 commits behind the upstream repository.
-
Paul McCarthy authoredPaul McCarthy authored
perspectives.py 29.94 KiB
#!/usr/bin/env python
#
# perspectives.py - The perspectives API.
#
# Author: Paul McCarthy <pauldmccarthy@gmail.com>
#
"""This module provides functions for managing *perspectives* - stored view
and control panel layouts for *FSLeyes*. Perspectives are persisted using the
:mod:`.settings` module. A few perspectives are also *built in*, and are
defined in the :attr:`BUILT_IN_PERSPECTIVES` dictionary.
The ``perspectives`` module provides the following functions. These are
intended for use by the :class:`.FSLEyesFrame`, but can be used in other ways
too:
.. autosummary::
:nosignatures:
getAllPerspectives
loadPerspective
applyPerspective
savePerspective
removePerspective
serialisePerspective
deserialisePerspective
A perspective defines a layout for a :class:`.FSLEyesFrame`. It specifies the
type and layout of one or more *views* (defined in the :mod:`.views` module)
and, within each view, the type and layout of one or more *controls* (defined
in the :mod:`.controls` module). See the :mod:`~fsl.fsleyes` documentation for
an overview of views and controls.
All of this information is stored as a string - see the
:func:`serialisePerspective` function for details on its storage format.
"""
import logging
import textwrap
import collections
import fsl.utils.settings as fslsettings
import fsl.utils.status as status
import fsl.data.strings as strings
log = logging.getLogger(__name__)
def getAllPerspectives():
"""Returns a list containing the names of all saved perspectives. """
# A list of all saved perspective names
# is saved as a comma-separated string
perspectives = fslsettings.read('fsleyes.perspectives', '')
perspectives = perspectives.split(',')
perspectives = [p.strip() for p in perspectives]
perspectives = [p for p in perspectives if p != '']
uniq = []
for p in perspectives:
if p not in uniq:
uniq.append(p)
return uniq
def loadPerspective(frame, name, **kwargs):
"""Load the named perspective, and apply it to the given
:class:`.FSLEyesFrame`. The ``kwargs`` are passed through to the
:func:`applyPerspective` function.
"""
if name in BUILT_IN_PERSPECTIVES.keys():
log.debug('Loading built-in perspective {}'.format(name))
persp = BUILT_IN_PERSPECTIVES[name]
else:
log.debug('Loading saved perspective {}'.format(name))
persp = fslsettings.read('fsleyes.perspectives.{}'.format(name), None)
if persp is None:
raise ValueError('No perspective named "{}" exists'.format(name))
log.debug('Serialised perspective:\n{}'.format(persp))
applyPerspective(frame, name, persp, **kwargs)
def applyPerspective(frame, name, perspective, message=None):
"""Applies the given serialised perspective string to the given
:class:`.FSLEyesFrame`.
:arg frame: The :class:`.FSLEyesFrame` instance.
:arg name: The perspective name.
:arg perspective: The serialised perspective string.
:arg message: A message to display (using the :mod:`.status` module).
"""
import fsl.fsleyes.views as views
persp = deserialisePerspective(perspective)
frameChildren = persp[0]
frameLayout = persp[1]
vpChildrens = persp[2]
vpLayouts = persp[3]
vpPanelProps = persp[4]
vpSceneProps = persp[5]
# Show a message while re-configuring the frame
if message is None:
message = strings.messages[
'perspectives.applyingPerspective'].format(
strings.perspectives.get(name, name))
status.update(message)
# Clear all existing view
# panels from the frame
for vp in frame.getViewPanels():
frame.removeViewPanel(vp)
# Add all of the view panels
# specified in the perspective
for vp in frameChildren:
log.debug('Adding view panel {} to frame'.format(vp.__name__))
frame.addViewPanel(vp)
# Apply the layout to those view panels
frame.getAuiManager().LoadPerspective(frameLayout)
# For each view panel, add all of the
# control panels, and lay them out
viewPanels = frame.getViewPanels()
for i in range(len(viewPanels)):
vp = viewPanels[ i]
children = vpChildrens[ i]
layout = vpLayouts[ i]
panelProps = vpPanelProps[i]
sceneProps = vpSceneProps[i]
for child in children:
log.debug('Adding control panel {} to {}'.format(
child.__name__, type(vp).__name__))
_addControlPanel(vp, child)
vp.getAuiManager().LoadPerspective(layout)
# Apply saved property values
# to the view panel.
for name, val in panelProps.items():
log.debug('Setting {}.{} = {}'.format(
type(vp).__name__, name, val))
vp.deserialise(name, val)
# And, if it is a CanvasPanel,
# to its SceneOpts instance.
if isinstance(vp, views.CanvasPanel):
opts = vp.getSceneOptions()
for name, val in sceneProps.items():
log.debug('Setting {}.{} = {}'.format(
type(opts).__name__, name, val))
opts.deserialise(name, val)
def savePerspective(frame, name):
"""Serialises the layout of the given :class:`.FSLEyesFrame` and saves
it as a perspective with the given name.
"""
if name in BUILT_IN_PERSPECTIVES.keys():
raise ValueError('A built-in perspective named "{}" '
'already exists'.format(name))
log.debug('Saving current perspective with name {}'.format(name))
persp = serialisePerspective(frame)
fslsettings.write('fsleyes.perspectives.{}'.format(name), persp)
_addToPerspectivesList(name)
log.debug('Serialised perspective:\n{}'.format(persp))
def removePerspective(name):
"""Deletes the named perspective. """
log.debug('Deleting perspective with name {}'.format(name))
fslsettings.delete('fsleyes.perspectives.{}'.format(name))
_removeFromPerspectivesList(name)
def serialisePerspective(frame):
"""Serialises the layout of the given :class:`.FSLEyesFrame`, and returns
it as a string.
.. note:: This function was written against wx.lib.agw.aui.AuiManager as
it exists in wxPython 3.0.2.0.
*FSLeyes* uses a hierarchy of ``wx.lib.agw.aui.AuiManager`` instances for
its layout - the :class:`.FSLEyesFrame` uses an ``AuiManager`` to lay out
:class:`.ViewPanel` instances, and each of these ``ViewPanels`` use their
own ``AuiManager`` to lay out control panels.
The layout for a single ``AuiManager`` can be serialised to a string via
the ``AuiManager.SavePerspective`` and ``AuiManager.SavePaneInfo``
methods. One of these strings consists of:
- A name.
- A set of key-value set of key-value pairs defining the top level
panel layout.
- A set of key-value pairs for each pane, defining its layout. the
``AuiManager.SavePaneInfo`` method returns this for a single pane.
These are all encoded in a single string, with the above components
separated with '|' characters, and the pane-level key-value pairs
separated with a ';' character. For example:
layoutName|key1=value1|name=Pane1;caption=Pane 1|\
name=Pane2;caption=Pane 2|doc_size(5,0,0)=22|
This function queries each of the AuiManagers, and extracts the following:
- A layout string for the :class:`.FSLEyesFrame`.
- A string containing a comma-separated list of :class:`.ViewPanel`
class names, in the same order as they are specified in the frame
layout string.
- For each ``ViewPanel``:
- A layout string for the ``ViewPanel``
- A string containing a comma-separated list of control panel class
names, in the same order as specified in the ``ViewPanel`` layout
string.
Each of these pieces of information are then concatenated into a single
newline separated string.
"""
# We'll start by defining this silly function, which
# takes an ``AuiManager`` layout string, and a list
# of the children which are being managed by the
# AuiManager, and makes sure that the order of the
# child pane layout specifications in the string is
# the same as the order of the children in the list.
#
# If the 'rename' argument is True, this function
# performs an additional step.
#
# The FSLEyesFrame gives each of its view panels a
# unique name of the form "ClassName index", where
# the 'index' is a sequentially increasing identifier
# number (so that multiple views of the same type can
# be differentiated). If the 'rename' argument to
# this function is True, these names are adjusted so
# that they begin at 1 and increase sequentially. This
# is done by the patchPanelName function, defined
# below.
#
# This name adjustment is required to handle
# situations where the indices of existing view panels
# are not sequential, as when a layout is applied, the
# view panel names given by the FSLEyesFrame must
# match the names that are specified in the layout
# perspective string.
#
# In addition to patching the name of each panel,
# the 'rename' argument will also cause the panel
# caption (its display title) to be adjusted so that
# it is in line with the name.
def patchLayoutString(auiMgr, panels, rename=False):
layoutStr = auiMgr.SavePerspective()
# The different sections of the string
# returned by SavePerspective are
# separated with a '|' character.
sections = layoutStr.split('|')
sections = [s.strip() for s in sections]
sections = [s for s in sections if s != '']
# Here, we identify sections which specify
# the layout of a child pane, remove them,
# and patch them back in, in the order that
# the child panels are specified in the list.
pi = 0
for si, s in enumerate(sections):
if s.find('name=') > -1:
panel = panels[pi]
panelInfo = auiMgr.GetPane(panel)
panelLayout = auiMgr.SavePaneInfo(panelInfo)
pi += 1
sections[si] = panelLayout
if rename:
sections[si] = patchPanelName(sections[si], pi)
# Now the panel layouts in our layout string
# are in the same order as our list of view
# panels - we can re-join the layout string
# sections, and we're done.
return '|'.join(sections) + '|'
# The purpose of this function is described above.
def patchPanelName(layoutString, index):
# In each AUI layout section, 'key=value'
# pairs are separated with a semi-colon
kvps = layoutString.split(';')
# And each 'key=value' pair is separated
# with an equals character
kvps = [kvp.split('=') for kvp in kvps]
kvps = collections.OrderedDict(kvps)
# We need to update the indices contained
# in the 'name' and 'caption' values
name = kvps['name']
caption = kvps['caption']
# Strip off the old index
name = ' '.join(name .split()[:-1])
caption = ' '.join(caption.split()[:-1])
# Patch in the new index
name = '{} {}'.format(name, index)
caption = '{} {}'.format(caption, index)
kvps['name'] = name
kvps['caption'] = caption
# Reconstruct the layout string
kvps = ['='.join((k, v)) for k, v in kvps.items()]
kvps = ';'.join(kvps)
return kvps
# Now we can start extracting the layout information.
# We start with the FSLEyesFrame layout.
auiMgr = frame.getAuiManager()
viewPanels = frame.getViewPanels()
# Generate the frame layout string, and a
# list of the children of the frame
frameLayout = patchLayoutString(auiMgr, viewPanels, True)
frameChildren = [type(vp).__name__ for vp in viewPanels]
frameChildren = ','.join(frameChildren)
# We are going to build a list of layout strings,
# one for each ViewPanel, and a corresponding list
# of control panels displayed on each ViewPanel.
vpLayouts = []
vpConfigs = []
for vp in viewPanels:
# Get the auiManager and layout for this view panel.
# This is a little bit complicated, as ViewPanels
# differentiate between the main 'centre' panel, and
# all other secondary (control) panels. The layout
# string needs to contain layout information for
# all of these panels, but we only care about the
# control panels.
vpAuiMgr = vp.getAuiManager()
ctrlPanels = vp.getPanels()
centrePanel = vp.getCentrePanel()
# As above for the frame, generate a layout
# string and a list of control panels - the
# children of the view panel.
vpLayout = patchLayoutString(vpAuiMgr, [centrePanel] + ctrlPanels)
vpChildren = [type(cp).__name__ for cp in ctrlPanels]
vpChildren = ','.join(vpChildren)
# Get the panel and scene settings
panelProps, sceneProps = _getPanelProps(vp)
# And turn them into comma-separated key-value pairs.
panelProps = ['{}={}'.format(k, v) for k, v in panelProps.items()]
sceneProps = ['{}={}'.format(k, v) for k, v in sceneProps.items()]
panelProps = ','.join(panelProps)
sceneProps = ','.join(sceneProps)
# Build the config string - the children,
# the panel settings and the scene settings.
vpConfig = ';'.join([vpChildren, panelProps, sceneProps])
vpLayouts.append(vpLayout)
vpConfigs.append(vpConfig)
# We serialise all of these pieces of information
# as a single newline-separated string.
perspective = [frameChildren, frameLayout]
for vpConfig, vpLayout in zip(vpConfigs, vpLayouts):
perspective.append(vpConfig)
perspective.append(vpLayout)
# And we're done!
return '\n'.join(perspective)
def deserialisePerspective(persp):
"""Deserialises a perspective string which was created by the
:func:`serialisePerspective` string.
:returns: A tuple containing the following:
- A list of :class:`.ViewPanel` class types - the
children of the :class:`.FSLEyesFrame`.
- An ``aui`` layout string for the :class:`.FSLEyesFrame`
- A list of lists, one for each ``ViewPanel``, with each
list containing a collection of control panel class
types - the children of the corresponding ``ViewPanel``.
- A list of strings, one ``aui`` layout string for each
``ViewPanel``.
- A list of dictionaries, one for each ``ViewPanel``,
containing property ``{name : value}`` pairs to be
applied to the ``ViewPanel``.
- A list of dictionaries, one for each ``ViewPanel``,
containing property ``{name : value}`` pairs to be applied
to the :class:`.SceneOpts` instance associated with the
``ViewPanel``. If the ``ViewPanel`` is not a
:class:`.CanvasPanel`, the dictionary will be empty.
"""
import fsl.fsleyes.views as views
import fsl.fsleyes.controls as controls
lines = persp.split('\n')
lines = [l.strip() for l in lines]
lines = [l for l in lines if l != '']
frameChildren = lines[0]
frameLayout = lines[1]
# The children strings are comma-separated
# class names. The frame children are ViewPanels,
# which are all defined in the fsl.fsleyes.views
# package.
frameChildren = frameChildren.split(',')
frameChildren = [fc.strip() for fc in frameChildren]
frameChildren = [fc for fc in frameChildren if fc != '']
frameChildren = [getattr(views, fc) for fc in frameChildren]
# Collate the children/layouts for each view panel
vpChildren = []
vpLayouts = []
vpPanelProps = []
vpSceneProps = []
for i in range(len(frameChildren)):
linei = (i * 2) + 2
config = lines[linei]
layout = lines[linei + 1]
children, panelProps, sceneProps = config.split(';')
vpChildren .append(children)
vpLayouts .append(layout)
vpPanelProps .append(panelProps)
vpSceneProps .append(sceneProps)
# The ViewPanel children string is a comma-separated
# list of control panel class names. All control panels
# should be defined in the fsl.fsleyes.controls package.
for i in range(len(vpChildren)):
children = vpChildren[i].split(',')
children = [vpc.strip() for vpc in children]
children = [vpc for vpc in children if vpc != '']
children = [getattr(controls, vpc) for vpc in children]
vpChildren[i] = children
# The panel props and scene props strings are
# comma-separated lists of 'prop=value' pairs.
# We'll turn them into a dict for convenience.
for i in range(len(vpPanelProps)):
props = vpPanelProps[i].split(',')
props = [p for p in props if p != '']
props = [p.split('=') for p in props]
vpPanelProps[i] = dict(props)
for i in range(len(vpSceneProps)):
props = vpSceneProps[i].split(',')
props = [p for p in props if p != '']
props = [p.split('=') for p in props]
vpSceneProps[i] = dict(props)
return (frameChildren,
frameLayout,
vpChildren,
vpLayouts,
vpPanelProps,
vpSceneProps)
def _addToPerspectivesList(persp):
"""Adds the given perspective name to the list of saved perspectives. """
perspectives = getAllPerspectives()
if persp not in perspectives:
perspectives.append(persp)
perspectives = ','.join(perspectives)
log.debug('Updating stored perspective list: {}'.format(perspectives))
fslsettings.write('fsleyes.perspectives', perspectives)
def _removeFromPerspectivesList(persp):
"""Removes the given perspective name from the list of saved perspectives.
"""
perspectives = getAllPerspectives()
try: perspectives.remove(persp)
except ValueError: return
perspectives = ','.join(perspectives)
log.debug('Updating stored perspective list: {}'.format(perspectives))
fslsettings.write('fsleyes.perspectives', perspectives)
def _addControlPanel(viewPanel, panelType):
"""Adds a control panel to the given :class:`.ViewPanel`.
:arg viewPanel: A :class:`.ViewPanel` instance.
:arg panelType: A control panel type.
"""
import fsl.fsleyes.controls as controls
args = {
controls.CanvasSettingsPanel : {'canvasPanel' : viewPanel},
controls.HistogramControlPanel : {'plotPanel' : viewPanel},
controls.LightBoxToolBar : {'lb' : viewPanel},
controls.OrthoEditToolBar : {'ortho' : viewPanel},
controls.OrthoToolBar : {'ortho' : viewPanel},
controls.OverlayDisplayToolBar : {'viewPanel' : viewPanel},
controls.PlotListPanel : {'plotPanel' : viewPanel},
controls.PowerSpectrumControlPanel : {'plotPanel' : viewPanel},
controls.ShellPanel : {'canvasPanel' : viewPanel},
controls.TimeSeriesControlPanel : {'plotPanel' : viewPanel},
}
args = args.get(panelType, {})
viewPanel.togglePanel(panelType, **args)
def _getPanelProps(panel):
"""
"""
import fsl.fsleyes.views as views
if not isinstance(panel, views.CanvasPanel):
return {}, {}
panelType = type(panel).__name__
opts = panel.getSceneOptions()
panelProps, sceneProps = VIEWPANEL_PROPS[panelType]
panelProps = {name : panel.serialise(name) for name in panelProps}
sceneProps = {name : opts .serialise(name) for name in sceneProps}
return panelProps, sceneProps
VIEWPANEL_PROPS = {
'OrthoPanel' : [['syncLocation', 'syncOverlayOrder', 'syncOverlayDisplay'],
['showCursor', 'bgColour', 'cursorColour',
'showColourBar', 'colourBarLocation', 'showXCanvas',
'showYCanvas', 'showZCanvas', 'showLabels',
'layout']
],
'LightBoxPanel' : [['syncLocation', 'syncOverlayOrder', 'syncOverlayDisplay'],
['showCursor', 'bgColour', 'cursorColour',
'showColourBar', 'colourBarLocation', 'zax',
'showGridLines', 'highlightSlice']]}
BUILT_IN_PERSPECTIVES = collections.OrderedDict((
('default',
textwrap.dedent("""
OrthoPanel
layout2|name=OrthoPanel 1;caption=Ortho View 1;state=67376064;dir=5;layer=0;row=0;pos=0;prop=100000;bestw=20;besth=20;minw=-1;minh=-1;maxw=-1;maxh=-1;floatx=-1;floaty=-1;floatw=-1;floath=-1;notebookid=-1;transparent=255|dock_size(5,0,0)=22|
OverlayDisplayToolBar,OrthoToolBar,LocationPanel,OverlayListPanel;syncLocation=True,syncOverlayOrder=True,syncOverlayDisplay=True;layout=grid,showLabels=True,bgColour=#000000ff,showCursor=True,showZCanvas=True,cursorColour=#00ff00ff,showColourBar=False,showYCanvas=True,showXCanvas=True,colourBarLocation=top
layout2|name=Panel;caption=;state=768;dir=5;layer=0;row=0;pos=0;prop=100000;bestw=20;besth=20;minw=-1;minh=-1;maxw=-1;maxh=-1;floatx=-1;floaty=-1;floatw=-1;floath=-1;notebookid=-1;transparent=255|name=OverlayDisplayToolBar;caption=Display toolbar;state=67382012;dir=1;layer=11;row=0;pos=0;prop=100000;bestw=855;besth=49;minw=-1;minh=-1;maxw=-1;maxh=-1;floatx=-1;floaty=-1;floatw=-1;floath=-1;notebookid=-1;transparent=255|name=OrthoToolBar;caption=Ortho view toolbar;state=67382012;dir=1;layer=10;row=0;pos=0;prop=100000;bestw=748;besth=34;minw=-1;minh=-1;maxw=-1;maxh=-1;floatx=-1;floaty=-1;floatw=-1;floath=-1;notebookid=-1;transparent=255|name=LocationPanel;caption=Location;state=67373052;dir=3;layer=0;row=0;pos=1;prop=100000;bestw=440;besth=111;minw=440;minh=109;maxw=-1;maxh=-1;floatx=-1;floaty=-1;floatw=440;floath=127;notebookid=-1;transparent=255|name=OverlayListPanel;caption=Overlay list;state=67373052;dir=3;layer=0;row=0;pos=0;prop=100000;bestw=204;besth=80;minw=197;minh=80;maxw=-1;maxh=-1;floatx=-1;floaty=-1;floatw=204;floath=96;notebookid=-1;transparent=255|dock_size(5,0,0)=22|dock_size(3,0,0)=130|dock_size(1,10,0)=36|dock_size(1,11,0)=51|
""")),
('melodic',
textwrap.dedent("""
LightBoxPanel,TimeSeriesPanel,PowerSpectrumPanel
layout2|name=LightBoxPanel 1;caption=Lightbox View 1;state=67377088;dir=5;layer=0;row=0;pos=0;prop=100000;bestw=853;besth=-1;minw=-1;minh=-1;maxw=-1;maxh=-1;floatx=-1;floaty=-1;floatw=-1;floath=-1;notebookid=-1;transparent=255|name=TimeSeriesPanel 2;caption=Time series 2;state=67377148;dir=3;layer=0;row=0;pos=0;prop=100000;bestw=-1;besth=472;minw=-1;minh=-1;maxw=-1;maxh=-1;floatx=-1;floaty=-1;floatw=-1;floath=-1;notebookid=-1;transparent=255|name=PowerSpectrumPanel 3;caption=Power spectra 3;state=67377148;dir=3;layer=0;row=0;pos=1;prop=100000;bestw=-1;besth=472;minw=-1;minh=-1;maxw=-1;maxh=-1;floatx=-1;floaty=-1;floatw=-1;floath=-1;notebookid=-1;transparent=255|dock_size(5,0,0)=22|dock_size(3,0,0)=195|
OverlayListPanel,OverlayDisplayToolBar,LocationPanel,LightBoxToolBar,MelodicClassificationPanel;;
layout2|name=Panel;caption=;state=768;dir=5;layer=0;row=0;pos=0;prop=100000;bestw=20;besth=20;minw=-1;minh=-1;maxw=-1;maxh=-1;floatx=-1;floaty=-1;floatw=-1;floath=-1;notebookid=-1;transparent=255|name=OverlayListPanel;caption=Overlay list;state=67373052;dir=3;layer=0;row=0;pos=0;prop=100000;bestw=204;besth=80;minw=197;minh=80;maxw=-1;maxh=-1;floatx=-1;floaty=-1;floatw=204;floath=96;notebookid=-1;transparent=255|name=OverlayDisplayToolBar;caption=Display toolbar;state=67382012;dir=1;layer=11;row=0;pos=0;prop=100000;bestw=810;besth=49;minw=-1;minh=-1;maxw=-1;maxh=-1;floatx=-1;floaty=-1;floatw=-1;floath=-1;notebookid=-1;transparent=255|name=LocationPanel;caption=Location;state=67373052;dir=3;layer=0;row=0;pos=1;prop=100000;bestw=440;besth=111;minw=440;minh=109;maxw=-1;maxh=-1;floatx=-1;floaty=-1;floatw=440;floath=127;notebookid=-1;transparent=255|name=LightBoxToolBar;caption=Lightbox view toolbar;state=67382012;dir=1;layer=10;row=0;pos=0;prop=100000;bestw=753;besth=43;minw=-1;minh=-1;maxw=-1;maxh=-1;floatx=-1;floaty=-1;floatw=-1;floath=-1;notebookid=-1;transparent=255|name=MelodicClassificationPanel;caption=Melodic IC classification;state=67373052;dir=2;layer=0;row=0;pos=0;prop=100000;bestw=400;besth=100;minw=400;minh=100;maxw=-1;maxh=-1;floatx=-1;floaty=-1;floatw=400;floath=116;notebookid=-1;transparent=255|dock_size(5,0,0)=22|dock_size(3,0,0)=130|dock_size(1,10,0)=45|dock_size(1,11,0)=51|dock_size(2,0,0)=402|
;;
layout2|name=FigureCanvasWxAgg;caption=;state=768;dir=5;layer=0;row=0;pos=0;prop=100000;bestw=640;besth=480;minw=-1;minh=-1;maxw=-1;maxh=-1;floatx=-1;floaty=-1;floatw=-1;floath=-1;notebookid=-1;transparent=255|dock_size(5,0,0)=642|
;;
layout2|name=FigureCanvasWxAgg;caption=;state=768;dir=5;layer=0;row=0;pos=0;prop=100000;bestw=640;besth=480;minw=-1;minh=-1;maxw=-1;maxh=-1;floatx=-1;floaty=-1;floatw=-1;floath=-1;notebookid=-1;transparent=255|dock_size(5,0,0)=642|'
""")),
('feat',
textwrap.dedent("""
OrthoPanel,TimeSeriesPanel,
layout2|name=OrthoPanel 1;caption=Ortho View 1;state=67377088;dir=5;layer=0;row=0;pos=0;prop=100000;bestw=853;besth=-1;minw=-1;minh=-1;maxw=-1;maxh=-1;floatx=-1;floaty=-1;floatw=-1;floath=-1;notebookid=-1;transparent=255|name=TimeSeriesPanel 2;caption=Time series 2;state=67377148;dir=3;layer=0;row=0;pos=0;prop=100000;bestw=-1;besth=472;minw=-1;minh=-1;maxw=-1;maxh=-1;floatx=-1;floaty=-1;floatw=-1;floath=-1;notebookid=-1;transparent=255|dock_size(5,0,0)=22|dock_size(3,0,0)=261|
OverlayDisplayToolBar,LocationPanel,AtlasPanel,OverlayListPanel,OrthoToolBar,ClusterPanel;;
layout2|name=Panel;caption=;state=768;dir=5;layer=0;row=0;pos=0;prop=100000;bestw=20;besth=20;minw=-1;minh=-1;maxw=-1;maxh=-1;floatx=-1;floaty=-1;floatw=-1;floath=-1;notebookid=-1;transparent=255|name=OverlayDisplayToolBar;caption=Display toolbar;state=67382012;dir=1;layer=10;row=0;pos=0;prop=100000;bestw=860;besth=49;minw=-1;minh=-1;maxw=-1;maxh=-1;floatx=-1;floaty=-1;floatw=-1;floath=-1;notebookid=-1;transparent=255|name=LocationPanel;caption=Location;state=67373052;dir=3;layer=2;row=0;pos=1;prop=98544;bestw=440;besth=109;minw=440;minh=109;maxw=-1;maxh=-1;floatx=2730;floaty=1104;floatw=440;floath=125;notebookid=-1;transparent=255|name=AtlasPanel;caption=Atlases;state=67373052;dir=2;layer=1;row=0;pos=0;prop=98904;bestw=318;besth=84;minw=318;minh=84;maxw=-1;maxh=-1;floatx=1091;floaty=143;floatw=318;floath=100;notebookid=-1;transparent=255|name=OverlayListPanel;caption=Overlay list;state=67373052;dir=3;layer=2;row=0;pos=0;prop=87792;bestw=197;besth=80;minw=197;minh=80;maxw=-1;maxh=-1;floatx=2608;floaty=1116;floatw=197;floath=96;notebookid=-1;transparent=255|name=OrthoToolBar;caption=Ortho view toolbar;state=67382012;dir=1;layer=10;row=1;pos=0;prop=100000;bestw=815;besth=34;minw=-1;minh=-1;maxw=-1;maxh=-1;floatx=2072;floaty=80;floatw=824;floath=50;notebookid=-1;transparent=255|name=ClusterPanel;caption=Cluster browser;state=67373052;dir=2;layer=1;row=0;pos=1;prop=114760;bestw=390;besth=96;minw=390;minh=96;maxw=-1;maxh=-1;floatx=3516;floaty=636;floatw=390;floath=112;notebookid=-1;transparent=255|dock_size(5,0,0)=10|dock_size(2,1,0)=566|dock_size(1,10,0)=51|dock_size(1,10,1)=36|dock_size(3,2,0)=130|
;;
layout2|name=FigureCanvasWxAgg;caption=;state=768;dir=5;layer=0;row=0;pos=0;prop=100000;bestw=640;besth=480;minw=-1;minh=-1;maxw=-1;maxh=-1;floatx=-1;floaty=-1;floatw=-1;floath=-1;notebookid=-1;transparent=255|dock_size(5,0,0)=642|
""")),
('ortho',
textwrap.dedent("""
OrthoPanel
layout2|name=OrthoPanel 1;caption=Ortho View 1;state=67376064;dir=5;layer=0;row=0;pos=0;prop=100000;bestw=20;besth=20;minw=-1;minh=-1;maxw=-1;maxh=-1;floatx=-1;floaty=-1;floatw=-1;floath=-1;notebookid=-1;transparent=255|dock_size(5,0,0)=22|
;syncLocation=True,syncOverlayOrder=True,syncOverlayDisplay=True;layout=horizontal,showLabels=True,bgColour=#000000ff,showCursor=True,showZCanvas=True,cursorColour=#00ff00ff,showColourBar=False,showYCanvas=True,showXCanvas=True,colourBarLocation=top
layout2|name=Panel;caption=;state=768;dir=5;layer=0;row=0;pos=0;prop=100000;bestw=20;besth=20;minw=-1;minh=-1;maxw=-1;maxh=-1;floatx=-1;floaty=-1;floatw=-1;floath=-1;notebookid=-1;transparent=255|dock_size(5,0,0)=22|
""")),
('lightbox',
textwrap.dedent("""
LightBoxPanel
layout2|name=LightBoxPanel 1;caption=Lightbox View 1;state=67376064;dir=5;layer=0;row=0;pos=0;prop=100000;bestw=20;besth=20;minw=-1;minh=-1;maxw=-1;maxh=-1;floatx=-1;floaty=-1;floatw=-1;floath=-1;notebookid=-1;transparent=255|dock_size(5,0,0)=22|
;syncLocation=True,syncOverlayOrder=True,syncOverlayDisplay=True;bgColour=#000000ff,showCursor=True,cursorColour=#00ff00ff,highlightSlice=False,zax=0,showColourBar=False,showGridLines=False,colourBarLocation=top
layout2|name=Panel;caption=;state=768;dir=5;layer=0;row=0;pos=0;prop=100000;bestw=20;besth=20;minw=-1;minh=-1;maxw=-1;maxh=-1;floatx=-1;floaty=-1;floatw=-1;floath=-1;notebookid=-1;transparent=255|dock_size(5,0,0)=10|
"""))))