From a1698720c40dccd48f6b1b8300248980892faa9c Mon Sep 17 00:00:00 2001
From: Paul McCarthy <pauld.mccarthy@gmail.com>
Date: Fri, 13 Feb 2015 14:31:15 +0000
Subject: [PATCH] Atlas panel is slightly functional. User can choose which
 atlases to display information for, and can show/hide label masks for
 individual regions

Fixes to GLVolume/GLMask colour map creation in situations where the
image data only has a single value.
---
 fsl/data/atlases.py                    |  53 +++++--
 fsl/data/image.py                      |  26 +++-
 fsl/data/strings.py                    |   5 +-
 fsl/fslview/controls/atlaspanel.py     | 200 ++++++++++++++++++++++---
 fsl/fslview/displaycontext/maskopts.py |   2 +-
 fsl/fslview/gl/glmask.py               |  13 +-
 fsl/fslview/gl/glvolume.py             |   5 +-
 fsl/fslview/layouts.py                 |   1 +
 fsl/fslview/views/canvaspanel.py       |  13 ++
 9 files changed, 267 insertions(+), 51 deletions(-)

diff --git a/fsl/data/atlases.py b/fsl/data/atlases.py
index b03012ed7..9a4287f80 100644
--- a/fsl/data/atlases.py
+++ b/fsl/data/atlases.py
@@ -10,6 +10,8 @@
 
 Instances of the :class:`Atlas` class is a
 
+MNI152
+
 
 <atlas>
   <header>
@@ -75,7 +77,18 @@ def listAtlases():
     atlasFiles = glob.glob(op.join(ATLAS_DIR, '*.xml'))
     atlasDescs = map(AtlasDescription, atlasFiles)
 
-    return {d.key: d for d in atlasDescs}
+    return {d.atlasID: d for d in atlasDescs}
+
+
+def loadAtlas(atlasDesc, loadSummary=False):
+
+    if loadSummary or atlasDesc.atlasType == 'label':
+        return LabelAtlas(atlasDesc)
+    
+    if atlasDesc.atlasType == 'probabilistic':
+        return ProbabilisticAtlas(atlasDesc)
+    else:
+        raise ValueError('Unknown atlas type: {}'.format(atlasDesc.atlasType))
 
 
 class AtlasDescription(object):
@@ -92,9 +105,13 @@ class AtlasDescription(object):
         header = root.find('header')
         data   = root.find('data')
 
-        self.key       = op.splitext(op.basename(filename))[0]
+        self.atlasID   = op.splitext(op.basename(filename))[0]
         self.name      = header.find('name').text
-        self.atlasType = header.find('type').text
+        self.atlasType = header.find('type').text.lower()
+ 
+        # Spelling error in some of the atlas.xml files.
+        if self.atlasType == 'probabalistic':
+            self.atlasType = 'probabilistic'
 
         images             = header.findall('images')
         self.images        = []
@@ -141,11 +158,12 @@ class AtlasDescription(object):
         # Load the appropriate transformation matrix
         # and transform all those voxel coordinates
         xform  = fslimage.Image(self.images[0], loadData=False).voxToWorldMat
-        coords = transform.transform(coords, xform)
+        coords = transform.transform(coords, xform.T)
 
         # Update the coordinates 
         # in our label objects
         for i, label in enumerate(self.labels):
+
             label.x, label.y, label.z = coords[i]
 
 
@@ -164,34 +182,39 @@ class Atlas(fslimage.Image):
             if imgRes < minImageRes:
                 minImageRes = imgRes
                 imageIdx    = i
-                
+
         if isLabel: imageFile = atlasDesc.summaryImages[imageIdx]
         else:       imageFile = atlasDesc.images[       imageIdx]
 
         fslimage.Image.__init__(self, imageFile)
 
+        self.desc = atlasDesc
+
         
-class LabelAtlas(fslimage.Image):
+class LabelAtlas(Atlas):
 
     def __init__(self, atlasDesc):
         Atlas.__init__(self, atlasDesc, isLabel=True)
 
-    def label(self, voxelLoc):
+    def label(self, worldLoc):
+
+        voxelLoc = transform.transform([worldLoc], self.worldToVoxMat.T)[0]
+        
         val = self.data[voxelLoc[0], voxelLoc[1], voxelLoc[2]]
 
-        if self.atlasDesc.atlasType == 'Label':
-            return self.atlasDesc.label[val]
+        if self.desc.atlasType == 'label':
+            return val
         
-        elif self.atlasDesc.atlasType == 'Probabilistic':
-            return self.atlasDesc.label[val - 1]
+        elif self.desc.atlasType == 'probabilistic':
+            return val - 1
 
     
-class ProbabilisticAtlas(fslimage.Image):
+class ProbabilisticAtlas(Atlas):
 
     def __init__(self, atlasDesc):
         Atlas.__init__(self, atlasDesc, isLabel=False)
 
         
-    def proportions(self, voxelLoc):
-        props = self.data[voxelLoc[0], voxelLoc[1], voxelLoc[2], :]
-        return zip(self.atlasDesc.labels, props)
+    def proportions(self, worldLoc):
+        voxelLoc = transform.transform([worldLoc], self.worldToVoxMat.T)[0]
+        return self.data[voxelLoc[0], voxelLoc[1], voxelLoc[2], :]
diff --git a/fsl/data/image.py b/fsl/data/image.py
index f5c8d04c6..5a7b68a08 100644
--- a/fsl/data/image.py
+++ b/fsl/data/image.py
@@ -252,7 +252,6 @@ class Image(props.HasProperties):
     def getXFormCode(self):
         """This method returns the code contained in the NIFTI1 header,
         indicating the space to which the (transformed) image is oriented.
-        
         """
         sform_code = self.nibImage.get_header()['sform_code']
 
@@ -272,13 +271,13 @@ class Image(props.HasProperties):
 
         This method returns one of the following values, indicating the
         direction in which coordinates along the specified axis increase:
-          - :attr:`~fsl.data.image.ORIENT_L2R`:     Left to right
-          - :attr:`~fsl.data.image.ORIENT_R2L`:     Right to left
-          - :attr:`~fsl.data.image.ORIENT_A2P`:     Anterior to posterior
-          - :attr:`~fsl.data.image.ORIENT_P2A`:     Posterior to anterior
-          - :attr:`~fsl.data.image.ORIENT_I2S`:     Inferior to superior
-          - :attr:`~fsl.data.image.ORIENT_S2I`:     Superior to inferior
-          - :attr:`~fsl.data.image.ORIENT_UNKNOWN`: Orientation is unknown
+          - :attr:`~fsl.data.constants.ORIENT_L2R`:     Left to right
+          - :attr:`~fsl.data.constants.ORIENT_R2L`:     Right to left
+          - :attr:`~fsl.data.constants.ORIENT_A2P`:     Anterior to posterior
+          - :attr:`~fsl.data.constants.ORIENT_P2A`:     Posterior to anterior
+          - :attr:`~fsl.data.constants.ORIENT_I2S`:     Inferior to superior
+          - :attr:`~fsl.data.constants.ORIENT_S2I`:     Superior to inferior
+          - :attr:`~fsl.data.constants.ORIENT_UNKNOWN`: Orientation is unknown
 
         The returned value is dictated by the XForm code contained in the
         image file header (see the :meth:`getXFormCode` method). Basically,
@@ -385,6 +384,16 @@ class ImageList(props.HasProperties):
         return iio.addImages(self, fromDir, addToEnd)
 
 
+    def find(self, name):
+        """Returns the first image with the given name, or ``None`` if
+        there is no image with said name.
+        """
+        for image in self.images:
+            if image.name == name:
+                return image
+        return None
+            
+
     # Wrappers around the images list property, allowing this
     # ImageList object to be used as if it is actually a list.
     def __len__(     self):               return self.images.__len__()
@@ -400,6 +409,7 @@ class ImageList(props.HasProperties):
     def extend(      self, iterable):     return self.images.extend(iterable)
     def pop(         self, index=-1):     return self.images.pop(index)
     def move(        self, from_, to):    return self.images.move(from_, to)
+    def remove(      self, item):         return self.images.remove(item)
     def insert(      self, index, item):  return self.images.insert(index,
                                                                     item)
     def insertAll(   self, index, items): return self.images.insertAll(index,
diff --git a/fsl/data/strings.py b/fsl/data/strings.py
index 25ca89ef9..13044da23 100644
--- a/fsl/data/strings.py
+++ b/fsl/data/strings.py
@@ -29,7 +29,9 @@ messages = TypeDict({
     'actions.loadcolourmap.invalidname'      : 'Please use only letters, '
                                                'numbers, and underscores.',
     'actions.loadcolourmap.installerror'     : 'An error occurred while '
-                                               'installing the colour map', 
+                                               'installing the colour map',
+
+    'atlaspanel.unknownLocation' : 'Unknown location'
 })
 
 
@@ -59,6 +61,7 @@ actions = TypeDict({
     'CanvasPanel.toggleImageList'         : 'Show/hide image list',
     'CanvasPanel.toggleDisplayProperties' : 'Show/hide display properties',
     'CanvasPanel.toggleLocationPanel'     : 'Show/hide location panel',
+    'CanvasPanel.toggleAtlasPanel'        : 'Show/hide atlas panel',
     'CanvasPanel.toggleCanvasProperties'  : 'Show/hide canvas properties',
 
 
diff --git a/fsl/fslview/controls/atlaspanel.py b/fsl/fslview/controls/atlaspanel.py
index e5e5dc4ea..0dcaf6e72 100644
--- a/fsl/fslview/controls/atlaspanel.py
+++ b/fsl/fslview/controls/atlaspanel.py
@@ -7,15 +7,18 @@
 
 import logging
 
-import wx
+import            wx
+import wx.html as wxhtml
+import numpy   as np
 
 import pwidgets.elistbox             as elistbox
 import pwidgets.notebook             as notebook
 
 import fsl.utils.transform           as transform
+import fsl.data.image                as fslimage
 import fsl.data.atlases              as atlases
 import fsl.data.strings              as strings
-import fsl.fslview.widgets.swappanel as swappanel
+import fsl.data.constants            as constants
 import fsl.fslview.panel             as fslpanel
 
 
@@ -24,25 +27,29 @@ log = logging.getLogger(__name__)
 
 class AtlasListWidget(wx.Panel):
 
-    def __init__(self, parent, atlasDesc, imageList, displayCtx, listBox):
+    def __init__(self, parent, atlasDesc, atlasPanel):
 
         wx.Panel.__init__(self, parent)
 
-        self.atlasDesc = atlasDesc
-
-        self.enableBox = wx.CheckBox(self)
+        self.atlasDesc  = atlasDesc
+        self.atlasPanel = atlasPanel
+        self.enableBox  = wx.CheckBox(self)
 
         self.sizer = wx.BoxSizer(wx.HORIZONTAL)
         self.sizer.Add(self.enableBox, flag=wx.EXPAND)
 
+        self.enableBox.Bind(wx.EVT_CHECKBOX, self.onEnable)
 
-class AtlasPanel(fslpanel.FSLViewPanel):
+    def onEnable(self, ev):
+
+        if self.enableBox.GetValue():
+            self.atlasPanel.enableAtlasInfo(self.atlasDesc.atlasID)
+        else:
+            self.atlasPanel.disableAtlasInfo(self.atlasDesc.atlasID)
+                    
 
 
-    # actions
-    def addSummaryOverlay(      self): pass
-    def addProbabilisticOverlay(self): pass
-    def addLabelOverlay(        self): pass
+class AtlasPanel(fslpanel.FSLViewPanel):
 
 
     def __init__(self, parent, imageList, displayCtx):
@@ -58,13 +65,12 @@ class AtlasPanel(fslpanel.FSLViewPanel):
 
         # Info panel, containing atlas-based regional
         # proportions/labels for the current location
-        self.infoPanel = wx.TextCtrl(self.notebook, style=(wx.TE_MULTILINE |
-                                                           wx.TE_READONLY))
+        self.infoPanel = wxhtml.HtmlWindow(self.notebook)
 
         # Atlas list, containing a list of atlases
         # that the user can choose from
         self.atlasList = elistbox.EditableListBox(
-            self.atlasListPanel,
+            self.notebook,
             style=(elistbox.ELB_NO_ADD    | 
                    elistbox.ELB_NO_REMOVE |
                    elistbox.ELB_NO_MOVE))
@@ -73,9 +79,163 @@ class AtlasPanel(fslpanel.FSLViewPanel):
         # allowing the user to add/remove overlays
         self.overlayPanel = wx.Panel(self.notebook)
         
-        self.notebook.Add(self.infoPanel,
-                          strings.labels['AtlasPanel.infoPanel'])
-        self.notebook.Add(self.atlasListPanel,
-                          strings.labels['AtlasPanel.atlasListPanel'])
-        self.notebook.Add(self.overlayPanel,
-                          strings.labels['AtlasPanel.overlayPanel'])
+        self.notebook.AddPage(self.infoPanel,
+                              strings.labels['AtlasPanel.infoPanel'])
+        self.notebook.AddPage(self.atlasList,
+                              strings.labels['AtlasPanel.atlasListPanel'])
+        self.notebook.AddPage(self.overlayPanel,
+                              strings.labels['AtlasPanel.overlayPanel'])
+
+        # The info panel contains clickable links
+        # for the currently displayed regions -
+        # when a link is clicked, the location
+        # is centred at the corresponding region
+        self.infoPanel.Bind(wxhtml.EVT_HTML_LINK_CLICKED,
+                            self._infoPanelLinkClicked)
+
+
+        # Set up the list of atlases to choose from
+        self.atlasDescs     = atlases.listAtlases()
+        self.enabledAtlases = {}
+
+        listItems = sorted(self.atlasDescs.items(), key=lambda (a, d): d.name)
+
+        for i, (atlasID, desc) in enumerate(listItems):
+            
+            self.atlasList.Append(desc.name, atlasID)
+            widget = AtlasListWidget(self.atlasList, desc, self)
+            self.atlasList.SetItemWidget(i, widget)
+
+        displayCtx.addListener('location', self._name, self._locationChanged)
+
+
+    def _infoPanelLinkClicked(self, ev):
+
+        atlasID, labelIndex = ev.GetLinkInfo().GetHref().split()
+        labelIndex          = int(labelIndex)
+        atlas               = self.enabledAtlases[atlasID]
+        label               = atlas.desc.labels[labelIndex]
+
+        log.debug('{}/{} clicked'.format(atlasID, label.name)) 
+
+        if isinstance(atlas, atlases.ProbabilisticAtlas):
+            pass
+        
+        elif isinstance(atlas, atlases.LabelAtlas):
+            self.toggleLabelOverlay(atlasID, labelIndex)
+
+
+        
+    def enableAtlasInfo(self, atlasID):
+
+        desc       = self.atlasDescs[atlasID]
+        atlasImage = atlases.loadAtlas(desc)
+
+        self.enabledAtlases[atlasID] = atlasImage
+
+        self._locationChanged()
+
+        
+    def disableAtlasInfo(self, atlasID):
+
+        self.enabledAtlases.pop(atlasID, None)
+        self._locationChanged()
+
+        
+    def toggleSummaryOverlay(self, atlasID):
+        pass
+
+    
+    def toggleProbabilisticOverlay(self, atlasID, labelIndex):
+        pass
+
+    
+    def toggleLabelOverlay(self, atlasID, labelIndex):
+
+        desc        = self.atlasDescs[atlasID]
+        overlayName = '{}/{}'.format(atlasID, desc.labels[labelIndex].name)
+        overlay     = self._imageList.find(overlayName)
+
+        if overlay is not None:
+            self._imageList.remove(overlay)
+
+            log.debug('Removing overlay {}'.format(overlayName))
+
+        else:
+            atlas = self.enabledAtlases.get(atlasID, None)
+            if atlas is None:
+                atlas = atlases.loadAtlas(self.atlasDescs[atlasID], True)
+
+            if   desc.atlasType == 'probabilistic': labelVal = labelIndex + 1
+            elif desc.atlasType == 'label':         labelVal = labelIndex 
+            
+            mask = np.zeros(atlas.shape, dtype=np.uint8)
+            mask[atlas.data == labelIndex] = labelVal
+
+            overlay = fslimage.Image(
+                mask,
+                atlas.voxToWorldMat,
+                name=overlayName)
+            overlay.imageType = 'mask'
+
+            log.debug('Adding overlay {}'.format(overlayName))
+
+            self._imageList.append(overlay)
+            
+            display = self._displayCtx.getDisplayProperties(overlay)
+            display.getDisplayOpts().colour = np.random.random(3)
+            
+
+ 
+
+
+    def _locationChanged(self, *a):
+        image   = self._displayCtx.getSelectedImage()
+        display = self._displayCtx.getDisplayProperties(image)
+        loc     = self._displayCtx.location
+        text    = self.infoPanel
+
+        loc = transform.transform([loc], display.displayToWorldMat)[0]
+
+        if image.getXFormCode() != constants.NIFTI_XFORM_MNI_152:
+            text.SetPage(strings.messages['atlaspanel.unknownLocation'])
+            return
+
+        lines = []
+
+        labelTemplate = """{}
+        (<a href="{} {}">Show/Hide</a>)
+        """
+        probTemplate = """
+        {:0.2f}% {}
+        (<a href="{} {}">Show/Hide</a>)
+        """
+
+        for atlasID, atlas in self.enabledAtlases.items():
+
+            lines.append('<b>{}</b>'.format(atlas.desc.name))
+
+            if isinstance(atlas, atlases.ProbabilisticAtlas):
+                proportions = atlas.proportions(loc)
+
+                for label, prop in zip(atlas.desc.labels, proportions):
+                    if prop == 0.0:
+                        continue
+                    lines.append(probTemplate.format(prop,
+                                                     label.name,
+                                                     atlasID,
+                                                     label.index,
+                                                     atlasID,
+                                                     label.index))
+            
+            elif isinstance(atlas, atlases.LabelAtlas):
+                
+                labelVal = atlas.label(loc)
+                label    = atlas.desc.labels[labelVal]
+                lines.append(labelTemplate.format(label.name,
+                                                  atlasID,
+                                                  label.index,
+                                                  atlasID,
+                                                  label.index))
+
+        text.SetPage('<br>'.join(lines))
diff --git a/fsl/fslview/displaycontext/maskopts.py b/fsl/fslview/displaycontext/maskopts.py
index daabf5076..864004543 100644
--- a/fsl/fslview/displaycontext/maskopts.py
+++ b/fsl/fslview/displaycontext/maskopts.py
@@ -45,5 +45,5 @@ class MaskOpts(fsldisplay.DisplayOpts):
 
         self.threshold.setMin(  0, self.dataMin - 0.5 * dRangeLen)
         self.threshold.setMax(  0, self.dataMax + 0.5 * dRangeLen)
-        self.threshold.setRange(0, self.dataMin, self.dataMax)
+        self.threshold.setRange(0, 0.1, self.dataMax + 0.1)
         self.setConstraint('threshold', 'minDistance', dMinDistance)        
diff --git a/fsl/fslview/gl/glmask.py b/fsl/fslview/gl/glmask.py
index a2502b593..36231c7c0 100644
--- a/fsl/fslview/gl/glmask.py
+++ b/fsl/fslview/gl/glmask.py
@@ -52,7 +52,7 @@ class GLMask(glvolume.GLVolume):
             self.setAxes(self.xax, self.yax)
 
         def colourUpdate(*a):
-            self.refreshColourTexture(self.colourResolution)
+            self.refreshColourTexture()
 
         lnrName = '{}_{}'.format(type(self).__name__, id(self))
 
@@ -109,17 +109,20 @@ class GLMask(glvolume.GLVolume):
         # or above the current display range will be mapped
         # to texture coordinate values less than 0.0 or greater
         # than 1.0 respectively.
+        if imax == imin: scale = 1
+        else:            scale = imax - imin
+            
         cmapXform = np.identity(4, dtype=np.float32)
-        cmapXform[0, 0] = 1.0 / (imax - imin)
+        cmapXform[0, 0] = 1.0 / scale
         cmapXform[3, 0] = -imin * cmapXform[0, 0]
 
         self.colourMapXform = cmapXform
 
         if opts.invert:
-            colourmap = np.tile([[0.0, 0.0, 0.0, 0.0]], (2, 1))
+            colourmap = np.tile([[0.0, 0.0, 0.0, 0.0]], (16, 1))
             border    = np.array(opts.colour, dtype=np.float32)
         else:
-            colourmap = np.tile([[opts.colour]], (2, 1))
+            colourmap = np.tile([[opts.colour]], (16, 1))
             border    = np.array([0.0, 0.0, 0.0, 0.0], dtype=np.float32)            
 
         colourmap = np.floor(colourmap * 255)
@@ -145,7 +148,7 @@ class GLMask(glvolume.GLVolume):
         gl.glTexImage1D(gl.GL_TEXTURE_1D,
                         0,
                         gl.GL_RGBA8,
-                        2,
+                        16,
                         0,
                         gl.GL_RGBA,
                         gl.GL_UNSIGNED_BYTE,
diff --git a/fsl/fslview/gl/glvolume.py b/fsl/fslview/gl/glvolume.py
index 2ae2f42cf..9cad22cc8 100644
--- a/fsl/fslview/gl/glvolume.py
+++ b/fsl/fslview/gl/glvolume.py
@@ -249,8 +249,11 @@ class GLVolume(globject.GLImageObject):
         # or above the current display range will be mapped
         # to texture coordinate values less than 0.0 or greater
         # than 1.0 respectively.
+        if imax == imin: scale = 1
+        else:            scale = imax - imin
+        
         cmapXform = np.identity(4, dtype=np.float32)
-        cmapXform[0, 0] = 1.0 / (imax - imin)
+        cmapXform[0, 0] = 1.0 / scale
         cmapXform[3, 0] = -imin * cmapXform[0, 0]
 
         self.colourMapXform = cmapXform
diff --git a/fsl/fslview/layouts.py b/fsl/fslview/layouts.py
index c5caa7aaa..c11f70bb7 100644
--- a/fsl/fslview/layouts.py
+++ b/fsl/fslview/layouts.py
@@ -103,6 +103,7 @@ CanvasPanelActionLayout = props.HGroup(
      actionButton('toggleImageList',          CanvasPanel),
      actionButton('toggleDisplayProperties',  CanvasPanel),
      actionButton('toggleLocationPanel',      CanvasPanel),
+     actionButton('toggleAtlasPanel',         CanvasPanel),
      actionButton('toggleCanvasProperties',   CanvasPanel)),
     wrap=True,
     showLabels=False)
diff --git a/fsl/fslview/views/canvaspanel.py b/fsl/fslview/views/canvaspanel.py
index ec7cd847e..10f103d39 100644
--- a/fsl/fslview/views/canvaspanel.py
+++ b/fsl/fslview/views/canvaspanel.py
@@ -30,6 +30,7 @@ import fsl.fslview.displaycontext             as displayctx
 import fsl.fslview.controls.imagelistpanel    as imagelistpanel
 import fsl.fslview.controls.imagedisplaypanel as imagedisplaypanel
 import fsl.fslview.controls.locationpanel     as locationpanel
+import fsl.fslview.controls.atlaspanel        as atlaspanel
 import fsl.fslview.widgets.togglepanel        as togp
 import                                           colourbarpanel
 
@@ -183,6 +184,7 @@ class CanvasPanel(fslpanel.FSLViewPanel):
             'screenshot'              : self.screenshot,
             'toggleColourBar'         : self.toggleColourBar,
             'toggleImageList'         : self.toggleImageList,
+            'toggleAtlasPanel'        : self.toggleAtlasPanel,
             'toggleDisplayProperties' : self.toggleDisplayProperties,
             'toggleLocationPanel'     : self.toggleLocationPanel,
             'toggleCanvasProperties'  : self.toggleCanvasProperties}
@@ -245,6 +247,9 @@ class CanvasPanel(fslpanel.FSLViewPanel):
             self.__listLocContainer, imageList, displayCtx)
 
         self.__locationPanel = locationpanel.LocationPanel(
+            self.__listLocContainer, imageList, displayCtx)
+
+        self.__atlasPanel = atlaspanel.AtlasPanel(
             self.__listLocContainer, imageList, displayCtx) 
         
         self.__displayPropsPanel = imagedisplaypanel.ImageDisplayPanel(
@@ -259,6 +264,9 @@ class CanvasPanel(fslpanel.FSLViewPanel):
         self.__listLocSizer.Add(self.__locationPanel,
                                 flag=wx.EXPAND,
                                 proportion=1)
+        self.__listLocSizer.Add(self.__atlasPanel,
+                                flag=wx.EXPAND,
+                                proportion=1) 
 
         self.__dispSetSizer = wx.BoxSizer(wx.HORIZONTAL)
         self.__dispSetContainer.SetSizer(self.__dispSetSizer)
@@ -289,6 +297,7 @@ class CanvasPanel(fslpanel.FSLViewPanel):
         self.__locationPanel    .Show(False)
         self.__canvasPropsPanel .Show(False)
         self.__displayPropsPanel.Show(False)
+        self.__atlasPanel       .Show(False)
 
         # Use a different listener name so that subclasses
         # can register on the same properties with self._name
@@ -398,6 +407,10 @@ class CanvasPanel(fslpanel.FSLViewPanel):
     def toggleLocationPanel(self, *a):
         self.__locationPanel.Show(not self.__locationPanel.IsShown())
         self.__layout()
+
+    def toggleAtlasPanel(self, *a):
+        self.__atlasPanel.Show(not self.__atlasPanel.IsShown())
+        self.__layout() 
         
     def toggleDisplayProperties(self, *a):
         self.__displayPropsPanel.Show(not self.__displayPropsPanel.IsShown())
-- 
GitLab