From 89c8e36b79b4f9c7725f968f7fbc9669c320efad Mon Sep 17 00:00:00 2001
From: Paul McCarthy <pauld.mccarthy@gmail.com>
Date: Tue, 14 Jul 2015 16:13:56 +0100
Subject: [PATCH] All calls to HasProperties.addListener either pass instance
 methods, standalone functions, or set weak=False.

---
 fsl/fslview/controls/histogramcontrolpanel.py | 18 +++++----
 fsl/fslview/controls/overlaydisplaypanel.py   | 31 ++++++++-------
 fsl/fslview/gl/colourbarcanvas.py             | 10 ++---
 fsl/fslview/gl/gl14/gllinevector_funcs.py     |  4 +-
 fsl/fslview/gl/gl21/gllinevector_funcs.py     |  6 +--
 fsl/fslview/gl/gllabel.py                     | 27 +++++++------
 fsl/fslview/gl/gllinevector.py                |  3 +-
 fsl/fslview/gl/glmask.py                      | 25 ++++++------
 fsl/fslview/gl/glmodel.py                     | 21 +++++-----
 fsl/fslview/gl/glvector.py                    | 35 +++++++++--------
 fsl/fslview/gl/glvolume.py                    | 39 ++++++++++---------
 fsl/fslview/gl/lightboxcanvas.py              | 17 ++++----
 fsl/fslview/gl/slicecanvas.py                 | 21 +++++-----
 fsl/fslview/gl/textures/imagetexture.py       |  3 +-
 fsl/fslview/views/orthopanel.py               | 28 +++++++++----
 fsl/fslview/views/timeseriespanel.py          |  6 +--
 fsl/tools/bet.py                              |  2 +-
 fsl/tools/feat.py                             |  3 +-
 18 files changed, 167 insertions(+), 132 deletions(-)

diff --git a/fsl/fslview/controls/histogramcontrolpanel.py b/fsl/fslview/controls/histogramcontrolpanel.py
index e8890b2f8..5f5eb4ef9 100644
--- a/fsl/fslview/controls/histogramcontrolpanel.py
+++ b/fsl/fslview/controls/histogramcontrolpanel.py
@@ -126,6 +126,16 @@ class HistogramControlPanel(fslpanel.FSLViewPanel):
         self.__updateCurrentProperties()
 
 
+    def __hsLabelChanged(self, *a):
+        if self.__currentHs is None:
+            return
+        
+        self.__widgets.RenameGroup(
+            'currentSettings',
+            strings.labels[self, 'currentSettings'].format(
+                self.__currentHs.label)) 
+
+
     def __updateCurrentProperties(self):
 
         expanded  = False
@@ -148,13 +158,7 @@ class HistogramControlPanel(fslpanel.FSLViewPanel):
         wlist = self.__widgets
         hs    = self.__currentHs
 
-        def updateGroupName(*a):
-            self.__widgets.RenameGroup(
-                'currentSettings',
-                strings.labels[self, 'currentSettings'].format(
-                    self.__currentHs.label))
-
-        hs.addListener('label', self._name, updateGroupName)
+        hs.addListener('label', self._name, self.__hsLabelChanged)
 
         self.__nbins = props.makeWidget(wlist, hs, 'nbins', showLimits=False)
         
diff --git a/fsl/fslview/controls/overlaydisplaypanel.py b/fsl/fslview/controls/overlaydisplaypanel.py
index 7d8f30c1f..9a17a17e7 100644
--- a/fsl/fslview/controls/overlaydisplaypanel.py
+++ b/fsl/fslview/controls/overlaydisplaypanel.py
@@ -64,13 +64,13 @@ class OverlayDisplayPanel(fslpanel.FSLViewPanel):
         
         displayCtx .addListener('selectedOverlay',
                                  self._name,
-                                 self._selectedOverlayChanged)
+                                 self.__selectedOverlayChanged)
         overlayList.addListener('overlays',
                                  self._name,
-                                 self._selectedOverlayChanged)
+                                 self.__selectedOverlayChanged)
 
-        self._lastOverlay = None
-        self._selectedOverlayChanged()
+        self.__lastOverlay = None
+        self.__selectedOverlayChanged()
 
         self.propSizer.Layout()
         self.Layout()
@@ -92,13 +92,13 @@ class OverlayDisplayPanel(fslpanel.FSLViewPanel):
             display.removeListener('overlayType', self._name)
 
 
-    def _selectedOverlayChanged(self, *a):
+    def __selectedOverlayChanged(self, *a):
 
         overlay     = self._displayCtx.getSelectedOverlay()
-        lastOverlay = self._lastOverlay
+        lastOverlay = self.__lastOverlay
 
         if overlay is None:
-            self._lastOverlay = None
+            self.__lastOverlay = None
             self.dispPanel.DestroyChildren()
             self.optsPanel.DestroyChildren()
             self.Layout()
@@ -120,17 +120,20 @@ class OverlayDisplayPanel(fslpanel.FSLViewPanel):
             
         display.addListener('overlayType',
                             self._name,
-                            lambda *a: self._updateProps(self.optsPanel, True))
+                            self.__overlayTypeChanged)
 
         if isinstance(opts, fsldisplay.VolumeOpts):
-            opts.addListener('transform', self._name, self._transformChanged)
+            opts.addListener('transform', self._name, self.__transformChanged)
         
-        self._lastOverlay = overlay
-        self._updateProps(self.dispPanel, False)
-        self._updateProps(self.optsPanel, True)
+        self.__lastOverlay = overlay
+        self.__updateProps(self.dispPanel, False)
+        self.__updateProps(self.optsPanel, True)
+
+    def __overlayTypeChanged(self, *a):
+        self.__updateProps(self.optsPanel, True)
 
         
-    def _transformChanged(self, *a):
+    def __transformChanged(self, *a):
         """Called when the transform setting of the currently selected overlay
         changes.
 
@@ -156,7 +159,7 @@ class OverlayDisplayPanel(fslpanel.FSLViewPanel):
             else:                   opts.interpolation = 'linear'
 
         
-    def _updateProps(self, parent, opts):
+    def __updateProps(self, parent, opts):
 
         import fsl.fslview.layouts as layouts
 
diff --git a/fsl/fslview/gl/colourbarcanvas.py b/fsl/fslview/gl/colourbarcanvas.py
index 84995b1ae..920eb43c5 100644
--- a/fsl/fslview/gl/colourbarcanvas.py
+++ b/fsl/fslview/gl/colourbarcanvas.py
@@ -67,12 +67,12 @@ class ColourBarCanvas(props.HasProperties):
         self._tex  = None
         self._name = '{}_{}'.format(self.__class__.__name__, id(self)) 
 
-        def _update(*a):
-            self._genColourBarTexture()
-            self._refresh()
-
         for prop in ('cmap', 'vrange', 'label', 'orientation', 'labelSide'):
-            self.addListener(prop, self._name, _update)
+            self.addListener(prop, self._name, self.__updateTexture)
+
+    def __updateTexture(self, *a):
+        self._genColourBarTexture()
+        self._refresh()
         
 
     def _initGL(self):
diff --git a/fsl/fslview/gl/gl14/gllinevector_funcs.py b/fsl/fslview/gl/gl14/gllinevector_funcs.py
index eadb1a043..94c24b0dc 100644
--- a/fsl/fslview/gl/gl14/gllinevector_funcs.py
+++ b/fsl/fslview/gl/gl14/gllinevector_funcs.py
@@ -42,8 +42,8 @@ def init(self):
         updateVertices(self)
         self.onUpdate()
 
-    opts.addListener('resolution', self.name, vertexUpdate)
-    opts.addListener('directed',   self.name, vertexUpdate)
+    opts.addListener('resolution', self.name, vertexUpdate, weak=False)
+    opts.addListener('directed',   self.name, vertexUpdate, weak=False)
 
 
 def destroy(self):
diff --git a/fsl/fslview/gl/gl21/gllinevector_funcs.py b/fsl/fslview/gl/gl21/gllinevector_funcs.py
index 57aa2a015..8b2b1bc10 100644
--- a/fsl/fslview/gl/gl21/gllinevector_funcs.py
+++ b/fsl/fslview/gl/gl21/gllinevector_funcs.py
@@ -43,9 +43,9 @@ def init(self):
 
     name = '{}_vertices'.format(self.name)
 
-    opts.addListener('transform',  name, vertexUpdate)
-    opts.addListener('resolution', name, vertexUpdate)
-    opts.addListener('directed',   name, vertexUpdate)
+    opts.addListener('transform',  name, vertexUpdate, weak=False)
+    opts.addListener('resolution', name, vertexUpdate, weak=False)
+    opts.addListener('directed',   name, vertexUpdate, weak=False)
 
     compileShaders(   self)
     updateShaderState(self)
diff --git a/fsl/fslview/gl/gllabel.py b/fsl/fslview/gl/gllabel.py
index d77b3be5e..864c750c6 100644
--- a/fsl/fslview/gl/gllabel.py
+++ b/fsl/fslview/gl/gllabel.py
@@ -90,18 +90,21 @@ class GLLabel(globject.GLImageObject):
         #      need to call gllabel_funcs.compileShaders
         #      when display.softwareMode changes
 
-        display .addListener(          'alpha',        name, lutUpdate)
-        display .addListener(          'brightness',   name, lutUpdate)
-        display .addListener(          'contrast',     name, lutUpdate)
-        display .addListener(          'softwareMode', name, shaderCompile)
-        opts    .addListener(          'outline',      name, shaderUpdate)
-        opts    .addListener(          'outlineWidth', name, shaderUpdate)
-        opts    .addListener(          'lut',          name, lutChanged)
-        opts    .addListener(          'volume',       name, imageUpdate)
-        opts    .addListener(          'resolution',   name, imageUpdate)
-        opts    .addSyncChangeListener('volume',       name, imageRefresh)
-        opts    .addSyncChangeListener('resolution',   name, imageRefresh)
-        opts.lut.addListener(          'labels',       name, lutUpdate)
+        display .addListener('alpha',        name, lutUpdate,     weak=False)
+        display .addListener('brightness',   name, lutUpdate,     weak=False)
+        display .addListener('contrast',     name, lutUpdate,     weak=False)
+        display .addListener('softwareMode', name, shaderCompile, weak=False)
+        opts    .addListener('outline',      name, shaderUpdate,  weak=False)
+        opts    .addListener('outlineWidth', name, shaderUpdate,  weak=False)
+        opts    .addListener('lut',          name, lutChanged,    weak=False)
+        opts    .addListener('volume',       name, imageUpdate,   weak=False)
+        opts    .addListener('resolution',   name, imageUpdate,   weak=False)
+        opts.lut.addListener('labels',       name, lutUpdate,     weak=False)
+        
+        opts.addSyncChangeListener(
+            'volume',     name, imageRefresh, weak=False)
+        opts.addSyncChangeListener(
+            'resolution', name, imageRefresh, weak=False)
 
 
     def removeListeners(self):
diff --git a/fsl/fslview/gl/gllinevector.py b/fsl/fslview/gl/gllinevector.py
index ad7882b79..635fa4f1e 100644
--- a/fsl/fslview/gl/gllinevector.py
+++ b/fsl/fslview/gl/gllinevector.py
@@ -200,7 +200,8 @@ class GLLineVector(glvector.GLVector):
         def update(*a):
             self.onUpdate()
 
-        self.displayOpts.addListener('lineWidth', self.name, update)
+        self.displayOpts.addListener(
+            'lineWidth', self.name, update, weak=False)
 
         
     def destroy(self):
diff --git a/fsl/fslview/gl/glmask.py b/fsl/fslview/gl/glmask.py
index 3efb30fa3..393bc4937 100644
--- a/fsl/fslview/gl/glmask.py
+++ b/fsl/fslview/gl/glmask.py
@@ -73,17 +73,20 @@ class GLMask(glvolume.GLVolume):
             fslgl.glvolume_funcs.updateShaderState(self) 
             self.onUpdate()
 
-        display.addListener(          'softwareMode',  name, shaderUpdate)
-        display.addListener(          'alpha',         name, colourUpdate)
-        display.addListener(          'brightness',    name, colourUpdate)
-        display.addListener(          'contrast',      name, colourUpdate)
-        opts   .addListener(          'colour',        name, colourUpdate)
-        opts   .addListener(          'threshold',     name, colourUpdate)
-        opts   .addListener(          'invert',        name, colourUpdate)
-        opts   .addListener(          'volume',        name, imageUpdate)
-        opts   .addListener(          'resolution',    name, imageUpdate)
-        opts   .addSyncChangeListener('volume',        name, imageRefresh)
-        opts   .addSyncChangeListener('resolution',    name, imageRefresh)
+        display.addListener('softwareMode',  name, shaderUpdate, weak=False)
+        display.addListener('alpha',         name, colourUpdate, weak=False)
+        display.addListener('brightness',    name, colourUpdate, weak=False)
+        display.addListener('contrast',      name, colourUpdate, weak=False)
+        opts   .addListener('colour',        name, colourUpdate, weak=False)
+        opts   .addListener('threshold',     name, colourUpdate, weak=False)
+        opts   .addListener('invert',        name, colourUpdate, weak=False)
+        opts   .addListener('volume',        name, imageUpdate,  weak=False)
+        opts   .addListener('resolution',    name, imageUpdate,  weak=False)
+        
+        opts.addSyncChangeListener(
+            'volume',     name, imageRefresh, weak=False)
+        opts.addSyncChangeListener(
+            'resolution', name, imageRefresh, weak=False)
 
 
     def removeDisplayListeners(self):
diff --git a/fsl/fslview/gl/glmodel.py b/fsl/fslview/gl/glmodel.py
index 5063b5e6d..270567489 100644
--- a/fsl/fslview/gl/glmodel.py
+++ b/fsl/fslview/gl/glmodel.py
@@ -46,6 +46,7 @@ class GLModel(globject.GLObject):
         
     def addListeners(self):
 
+        name    = self.name
         display = self.display
         opts    = self.opts
 
@@ -56,16 +57,16 @@ class GLModel(globject.GLObject):
             fslgl.glmodel_funcs.updateShaders(self)
             self.onUpdate()
         
-        opts   .addListener('refImage',     self.name, self._updateVertices)
-        opts   .addListener('coordSpace',   self.name, self._updateVertices)
-        opts   .addListener('transform',    self.name, self._updateVertices)
-        opts   .addListener('colour',       self.name, refresh)
-        opts   .addListener('outline',      self.name, refresh)
-        opts   .addListener('outlineWidth', self.name, shaderUpdate)
-        opts   .addListener('showName',     self.name, refresh)
-        display.addListener('brightness',   self.name, refresh)
-        display.addListener('contrast',     self.name, refresh)
-        display.addListener('alpha',        self.name, refresh)
+        opts   .addListener('refImage',     name, self._updateVertices)
+        opts   .addListener('coordSpace',   name, self._updateVertices)
+        opts   .addListener('transform',    name, self._updateVertices)
+        opts   .addListener('colour',       name, refresh,      weak=False)
+        opts   .addListener('outline',      name, refresh,      weak=False)
+        opts   .addListener('showName',     name, refresh,      weak=False)
+        display.addListener('brightness',   name, refresh,      weak=False)
+        display.addListener('contrast',     name, refresh,      weak=False)
+        display.addListener('alpha',        name, refresh,      weak=False)
+        opts   .addListener('outlineWidth', name, shaderUpdate, weak=False)
 
         
     def removeListeners(self):
diff --git a/fsl/fslview/gl/glvector.py b/fsl/fslview/gl/glvector.py
index 81c115774..cd1153215 100644
--- a/fsl/fslview/gl/glvector.py
+++ b/fsl/fslview/gl/glvector.py
@@ -152,22 +152,25 @@ class GLVector(globject.GLImageObject):
             self.updateShaderState()
             self.onUpdate()
 
-        display.addListener(          'softwareMode',  name, shaderCompile)
-        display.addListener(          'alpha',         name, cmapUpdate)
-        display.addListener(          'brightness',    name, cmapUpdate)
-        display.addListener(          'contrast',      name, cmapUpdate) 
-        opts   .addListener(          'xColour',       name, cmapUpdate)
-        opts   .addListener(          'yColour',       name, cmapUpdate)
-        opts   .addListener(          'zColour',       name, cmapUpdate)
-        opts   .addListener(          'suppressX',     name, cmapUpdate)
-        opts   .addListener(          'suppressY',     name, cmapUpdate)
-        opts   .addListener(          'suppressZ',     name, cmapUpdate)
-        opts   .addListener(          'modulate',      name, modUpdate)
-        opts   .addListener(          'modThreshold',  name, shaderUpdate)
-        opts   .addListener(          'volume',        name, imageUpdate)
-        opts   .addListener(          'resolution',    name, imageUpdate)
-        opts   .addSyncChangeListener('volume',        name, imageRefresh)
-        opts   .addSyncChangeListener('resolution',    name, imageRefresh) 
+        display.addListener('softwareMode',  name, shaderCompile, weak=False)
+        display.addListener('alpha',         name, cmapUpdate,    weak=False)
+        display.addListener('brightness',    name, cmapUpdate,    weak=False)
+        display.addListener('contrast',      name, cmapUpdate,    weak=False)
+        opts   .addListener('xColour',       name, cmapUpdate,    weak=False)
+        opts   .addListener('yColour',       name, cmapUpdate,    weak=False)
+        opts   .addListener('zColour',       name, cmapUpdate,    weak=False)
+        opts   .addListener('suppressX',     name, cmapUpdate,    weak=False)
+        opts   .addListener('suppressY',     name, cmapUpdate,    weak=False)
+        opts   .addListener('suppressZ',     name, cmapUpdate,    weak=False)
+        opts   .addListener('modulate',      name, modUpdate,     weak=False)
+        opts   .addListener('modThreshold',  name, shaderUpdate,  weak=False)
+        opts   .addListener('volume',        name, imageUpdate,   weak=False)
+        opts   .addListener('resolution',    name, imageUpdate,   weak=False)
+        
+        opts.addSyncChangeListener(
+            'volume',     name, imageRefresh, weak=False)
+        opts.addSyncChangeListener(
+            'resolution', name, imageRefresh, weak=False)
 
 
     def removeListeners(self):
diff --git a/fsl/fslview/gl/glvolume.py b/fsl/fslview/gl/glvolume.py
index c5eb1f7be..bcb28c7c4 100644
--- a/fsl/fslview/gl/glvolume.py
+++ b/fsl/fslview/gl/glvolume.py
@@ -219,21 +219,23 @@ class GLVolume(globject.GLImageObject):
             fslgl.glvolume_funcs.updateShaderState(self)
             self.onUpdate()
 
-        display.addListener(          'softwareMode',   lName, shaderCompile)
-        display.addListener(          'alpha',          lName, colourUpdate)
-        opts   .addListener(          'displayRange',   lName, colourUpdate)
-        opts   .addListener(          'clippingRange',  lName, shaderUpdate)
-        opts   .addListener(          'invertClipping', lName, shaderUpdate)
-        opts   .addListener(          'cmap',           lName, colourUpdate)
-        opts   .addListener(          'invert',         lName, colourUpdate)
-        opts   .addListener(          'volume',         lName, imageUpdate)
-        opts   .addListener(          'resolution',     lName, imageUpdate)
-        opts   .addListener(          'interpolation',  lName, imageUpdate)
-
-        if opts.getParent() is not None:
-            opts   .addSyncChangeListener('volume',        lName, imageRefresh)
-            opts   .addSyncChangeListener('resolution',    lName, imageRefresh)
-            opts   .addSyncChangeListener('interpolation', lName, imageRefresh)
+        display.addListener('softwareMode',   lName, shaderCompile, weak=False)
+        display.addListener('alpha',          lName, colourUpdate,  weak=False)
+        opts   .addListener('displayRange',   lName, colourUpdate,  weak=False)
+        opts   .addListener('clippingRange',  lName, shaderUpdate,  weak=False)
+        opts   .addListener('invertClipping', lName, shaderUpdate,  weak=False)
+        opts   .addListener('cmap',           lName, colourUpdate,  weak=False)
+        opts   .addListener('invert',         lName, colourUpdate,  weak=False)
+        opts   .addListener('volume',         lName, imageUpdate,   weak=False)
+        opts   .addListener('resolution',     lName, imageUpdate,   weak=False)
+        opts   .addListener('interpolation',  lName, imageUpdate,   weak=False)
+
+        opts.addSyncChangeListener(
+            'volume',        lName, imageRefresh, weak=False)
+        opts.addSyncChangeListener(
+            'resolution',    lName, imageRefresh, weak=False)
+        opts.addSyncChangeListener(
+            'interpolation', lName, imageRefresh, weak=False)
 
 
     def removeDisplayListeners(self):
@@ -256,10 +258,9 @@ class GLVolume(globject.GLImageObject):
         opts   .removeListener(          'volume',         lName)
         opts   .removeListener(          'resolution',     lName)
         opts   .removeListener(          'interpolation',  lName)
-        if opts.getParent() is not None:
-            opts   .removeSyncChangeListener('volume',        lName)
-            opts   .removeSyncChangeListener('resolution',    lName)
-            opts   .removeSyncChangeListener('interpolation', lName)
+        opts   .removeSyncChangeListener('volume',         lName)
+        opts   .removeSyncChangeListener('resolution',     lName)
+        opts   .removeSyncChangeListener('interpolation',  lName)
 
         
     def preDraw(self):
diff --git a/fsl/fslview/gl/lightboxcanvas.py b/fsl/fslview/gl/lightboxcanvas.py
index 231e7c47d..fff8dadac 100644
--- a/fsl/fslview/gl/lightboxcanvas.py
+++ b/fsl/fslview/gl/lightboxcanvas.py
@@ -198,14 +198,7 @@ class LightBoxCanvas(slicecanvas.SliceCanvas):
         self.addListener('zrange',         self.name, self._slicePropsChanged)
         self.addListener('showGridLines',  self.name, self._refresh)
         self.addListener('highlightSlice', self.name, self._refresh)
-
-        # Called when the top row changes -
-        # adjusts display range and refreshes
-        def rowChange(*a):
-            self._updateDisplayBounds()
-            self._refresh()
-
-        self.addListener('topRow', self.name, rowChange)
+        self.addListener('topRow',         self.name, self._topRowChanged)
 
         # Add a listener to the position so when it
         # changes we can adjust the display range (via
@@ -219,6 +212,14 @@ class LightBoxCanvas(slicecanvas.SliceCanvas):
                          '{}_zPosChanged'.format(self.name),
                          self._zPosChanged)
 
+        
+    def _topRowChanged(self, *a):
+        """Called when the :attr:`topRow` property changes.  Adjusts display
+        range and refreshes the canvas.
+        """
+        self._updateDisplayBounds()
+        self._refresh()
+
 
     def _slicePropsChanged(self, *a):
         """Called when any of the slice properties change. Regenerates slice
diff --git a/fsl/fslview/gl/slicecanvas.py b/fsl/fslview/gl/slicecanvas.py
index 63eaa4df6..998640b7e 100644
--- a/fsl/fslview/gl/slicecanvas.py
+++ b/fsl/fslview/gl/slicecanvas.py
@@ -289,14 +289,8 @@ class SliceCanvas(props.HasProperties):
         self.addListener('showCursor',    self.name, self._refresh)
         self.addListener('invertX',       self.name, self._refresh)
         self.addListener('invertY',       self.name, self._refresh)
-        self.addListener('zoom',
-                         self.name,
-                         lambda *a: self._updateDisplayBounds())
-
-        self.addListener('renderMode',
-                         self.name,
-                         self._renderModeChange)
-
+        self.addListener('zoom',          self.name, self._zoomChanged)
+        self.addListener('renderMode',    self.name, self._renderModeChange)
         self.addListener('resolutionLimit',
                          self.name,
                          self._resolutionLimitChange) 
@@ -600,8 +594,8 @@ class SliceCanvas(props.HasProperties):
                                 self.name,
                                 self.__overlayTypeChanged)
             
-            display.addListener('enabled',       self.name, self._refresh)
-            display.addListener('softwareMode',  self.name, self._refresh)
+            display.addListener('enabled',      self.name, self._refresh)
+            display.addListener('softwareMode', self.name, self._refresh)
 
         self._updateRenderTextures()
         self._resolutionLimitChange()
@@ -626,6 +620,13 @@ class SliceCanvas(props.HasProperties):
         self.pos.setMax(2, ovlBounds.getHi(self.zax))
 
         self._updateDisplayBounds()
+
+
+    def _zoomChanged(self, *a):
+        """Called when the :attr:`.zoom` property changes. Updates the
+        display bounds.
+        """
+        self._updateDisplayBounds()
         
 
     def _applyZoom(self, xmin, xmax, ymin, ymax):
diff --git a/fsl/fslview/gl/textures/imagetexture.py b/fsl/fslview/gl/textures/imagetexture.py
index d61a45486..0fdefd397 100644
--- a/fsl/fslview/gl/textures/imagetexture.py
+++ b/fsl/fslview/gl/textures/imagetexture.py
@@ -86,7 +86,8 @@ class ImageTexture(texture.Texture):
         self.__name = '{}_{}'.format(type(self).__name__, id(self))
         self.image.addListener('data',
                                self.__name,
-                               lambda *a: self.__imageDataChanged())
+                               lambda *a: self.__imageDataChanged(),
+                               weak=False)
 
         self.__imageDataChanged(False)
         self.set(interp=interp,
diff --git a/fsl/fslview/views/orthopanel.py b/fsl/fslview/views/orthopanel.py
index 2325e5896..3908b8ad4 100644
--- a/fsl/fslview/views/orthopanel.py
+++ b/fsl/fslview/views/orthopanel.py
@@ -120,10 +120,6 @@ class OrthoPanel(canvaspanel.CanvasPanel):
         self._zcanvas.bindProps('resolutionLimit', sceneOpts) 
 
         # And a global zoom which controls all canvases at once
-        def onZoom(*a):
-            sceneOpts.xzoom = sceneOpts.zoom
-            sceneOpts.yzoom = sceneOpts.zoom
-            sceneOpts.zzoom = sceneOpts.zoom
 
         minZoom = sceneOpts.getConstraint('xzoom', 'minval')
         maxZoom = sceneOpts.getConstraint('xzoom', 'maxval')
@@ -131,7 +127,7 @@ class OrthoPanel(canvaspanel.CanvasPanel):
         sceneOpts.setConstraint('zoom', 'minval', minZoom)
         sceneOpts.setConstraint('zoom', 'maxval', maxZoom)
 
-        sceneOpts.addListener('zoom', self._name, onZoom)
+        sceneOpts.addListener('zoom', self._name, self.__onZoom)
 
         # Callbacks for overlay list/selected overlay changes
         self._overlayList.addListener('overlays',
@@ -156,13 +152,16 @@ class OrthoPanel(canvaspanel.CanvasPanel):
         # Callbacks for toggling x/y/z canvas display
         sceneOpts.addListener('showXCanvas',
                               self._name,
-                              lambda *a: self._toggleCanvas('x'))
+                              lambda *a: self._toggleCanvas('x'),
+                              weak=False)
         sceneOpts.addListener('showYCanvas',
                               self._name,
-                              lambda *a: self._toggleCanvas('y'))
+                              lambda *a: self._toggleCanvas('y'),
+                              weak=False)
         sceneOpts.addListener('showZCanvas',
                               self._name,
-                              lambda *a: self._toggleCanvas('z'))
+                              lambda *a: self._toggleCanvas('z'),
+                              weak=False)
 
         # Call the _resize method to refresh
         # the slice canvases when the canvas
@@ -199,6 +198,19 @@ class OrthoPanel(canvaspanel.CanvasPanel):
             opts = self._displayCtx.getOpts(ovl)
             opts.removeGlobalListener(self._name)
 
+            
+    def __onZoom(self, *a):
+        """Called when the :attr:`.SceneOpts.zoom` property changes.
+        Propagates the change to the :attr:`.OrthoOpts.xzoom`, ``yzoom``,
+        and ``zzoom`` properties.
+        """
+        opts       = self.getSceneOptions()
+        opts.xzoom = opts.zoom
+        opts.yzoom = opts.zoom
+        opts.zzoom = opts.zoom
+
+            
+
 
     def getXCanvas(self):
         """Returns a reference to the
diff --git a/fsl/fslview/views/timeseriespanel.py b/fsl/fslview/views/timeseriespanel.py
index 6ef492eb3..26c7d18d7 100644
--- a/fsl/fslview/views/timeseriespanel.py
+++ b/fsl/fslview/views/timeseriespanel.py
@@ -146,17 +146,17 @@ class FEATTimeSeries(TimeSeries):
         for i, pv in enumerate(self.plotEVs.getPropertyValueList()):
             def onChange(ctx, value, valid, name, pe=i):
                 self.__plotEVChanged(pe)
-            pv.addListener(self.name, onChange) 
+            pv.addListener(self.name, onChange, weak=False) 
         
         for i, pv in enumerate(self.plotPEFits.getPropertyValueList()):
             def onChange(ctx, value, valid, name, pe=i):
                 self.__plotPEFitChanged(pe)
-            pv.addListener(self.name, onChange)
+            pv.addListener(self.name, onChange, weak=False)
 
         for i, pv in enumerate(self.plotCOPEFits.getPropertyValueList()):
             def onChange(ctx, value, valid, name, cope=i):
                 self.__plotCOPEFitChanged(cope)
-            pv.addListener(self.name, onChange)
+            pv.addListener(self.name, onChange, weak=False)
 
 
     def __copy__(self):
diff --git a/fsl/tools/bet.py b/fsl/tools/bet.py
index e8c611c41..56657b7ef 100644
--- a/fsl/tools/bet.py
+++ b/fsl/tools/bet.py
@@ -227,7 +227,7 @@ def selectHeadCentre(opts, button):
         opts.yCoordinate = round(y)
         opts.zCoordinate = round(z)
 
-    displayCtx.addListener('location', 'BETHeadCentre', updateOpts)
+    displayCtx.addListener('location', 'BETHeadCentre', updateOpts, weak=False)
 
     # Set the initial location on the orthopanel.
     voxCoords           = [opts.xCoordinate,
diff --git a/fsl/tools/feat.py b/fsl/tools/feat.py
index 6c40c4e10..e430d3a46 100644
--- a/fsl/tools/feat.py
+++ b/fsl/tools/feat.py
@@ -228,7 +228,8 @@ class Options(props.HasProperties):
         
         Options.analysisType.addListener(self,
                                          'updateAnalysisStage',
-                                         updateAnalysisStage)
+                                         updateAnalysisStage,
+                                         weak=False)
         
 labels = {
     # misc
-- 
GitLab