From 634c8d5ff5a1a1ddb3330e75ff1cd54f091fc385 Mon Sep 17 00:00:00 2001
From: Paul McCarthy <pauld.mccarthy@gmail.com>
Date: Fri, 24 Jul 2015 14:14:56 +0100
Subject: [PATCH] Bugfixes.

 - All GLObjects test to see if opts instances have a parent, before
   doing things with their sync properties.
 - Render.py bug - using fsl/utils/layout functions, passing
   showLabels=True, but then passing no labels. Renderpy usage fixed,
   and layout test added (reverts to showLabels=False if no labels are
   provided)
 - CanvasPanel screenshot was passing all overlays, instead of just
   enabled/saved ones
 - settingspy imports wx inside functions, in case we're running in
   headless mode
 - In my SliceCanvas.genGLObject refactoring, I removed a critical piece
   of functionality - genGLObject(updateRenderTextures=True) was not
   updating render textures.
---
 fsl/fslview/gl/gl14/edge2D.prog           | 26 ++++++++-------
 fsl/fslview/gl/gl14/gllinevector_funcs.py | 16 +++++++---
 fsl/fslview/gl/gllabel.py                 | 22 +++++++------
 fsl/fslview/gl/glmask.py                  | 20 +++++++-----
 fsl/fslview/gl/glvector.py                | 18 +++++++----
 fsl/fslview/gl/glvolume.py                | 25 ++++++++-------
 fsl/fslview/gl/slicecanvas.py             |  7 +++-
 fsl/fslview/overlay.py                    |  2 +-
 fsl/fslview/settings.py                   |  6 ++--
 fsl/fslview/views/canvaspanel.py          | 10 +++---
 fsl/tools/render.py                       | 39 ++++++++++++-----------
 fsl/utils/layout.py                       |  4 ++-
 12 files changed, 113 insertions(+), 82 deletions(-)

diff --git a/fsl/fslview/gl/gl14/edge2D.prog b/fsl/fslview/gl/gl14/edge2D.prog
index a89a45885..170a5f87d 100644
--- a/fsl/fslview/gl/gl14/edge2D.prog
+++ b/fsl/fslview/gl/gl14/edge2D.prog
@@ -12,8 +12,10 @@ TEMP off;
 TEMP back;
 TEMP front;
 TEMP tempCoord;
-TEMP isEdgeBack[2];
-TEMP isEdgeFront[2];
+TEMP isEdgeBack0;
+TEMP isEdgeBack1;
+TEMP isEdgeFront0;
+TEMP isEdgeFront1;
 TEMP isEdge;
 
 # Test along the x axis
@@ -35,8 +37,8 @@ SUB front, front, val;
 ABS back,  back;
 ABS front, front;
 
-SLT isEdgeBack[0],  tol, back;
-SLT isEdgeFront[0], tol, front; 
+SLT isEdgeBack0,  tol, back;
+SLT isEdgeFront0, tol, front; 
 
 
 # Test along the y axis
@@ -58,22 +60,22 @@ SUB front, front, val;
 ABS back,  back;
 ABS front, front;
 
-SLT isEdgeBack[1],  tol, back;
-SLT isEdgeFront[1], tol, front;  
+SLT isEdgeBack1,  tol, back;
+SLT isEdgeFront1, tol, front;  
 
 
 # For each of the isEdgeBack/isEdgeFront
 # vectors, set all components to 1 if an
 # edge was found on any component.
-DP4 isEdgeBack[ 0], isEdgeBack[ 0], isEdgeBack[ 0];
-DP4 isEdgeFront[0], isEdgeFront[0], isEdgeFront[0];
-DP4 isEdgeBack[ 1], isEdgeBack[ 1], isEdgeBack[ 1];
-DP4 isEdgeFront[1], isEdgeFront[1], isEdgeFront[1];
+DP4 isEdgeBack0,  isEdgeBack0,  isEdgeBack0;
+DP4 isEdgeFront0, isEdgeFront0, isEdgeFront0;
+DP4 isEdgeBack1,  isEdgeBack1,  isEdgeBack1;
+DP4 isEdgeFront1, isEdgeFront1, isEdgeFront1;
 
 # Set isEdge.i  if there was an edge
 # on any component of the i axis.
-MAX isEdge.x, isEdgeBack[0], isEdgeFront[0];
-MAX isEdge.y, isEdgeBack[1], isEdgeFront[1];
+MAX isEdge.x, isEdgeBack0, isEdgeFront0;
+MAX isEdge.y, isEdgeBack1, isEdgeFront1;
 
 # Clamp the isEdge values to 1
 SGE isEdge, isEdge, 1;
diff --git a/fsl/fslview/gl/gl14/gllinevector_funcs.py b/fsl/fslview/gl/gl14/gllinevector_funcs.py
index 94c24b0dc..38c0cf9ff 100644
--- a/fsl/fslview/gl/gl14/gllinevector_funcs.py
+++ b/fsl/fslview/gl/gl14/gllinevector_funcs.py
@@ -30,7 +30,7 @@ def init(self):
     self.lineVertices    = None
 
     self._vertexResourceName = '{}_{}_vertices'.format(
-        type(self).__name__, id(self.image)) 
+        type(self).__name__, id(self.image))
 
     compileShaders(   self)
     updateShaderState(self)
@@ -42,16 +42,22 @@ def init(self):
         updateVertices(self)
         self.onUpdate()
 
-    opts.addListener('resolution', self.name, vertexUpdate, weak=False)
-    opts.addListener('directed',   self.name, vertexUpdate, weak=False)
+    name = '{}_vertices'.format(self.name)
+
+    opts.addListener('transform',  name, vertexUpdate, weak=False)
+    opts.addListener('resolution', name, vertexUpdate, weak=False)
+    opts.addListener('directed',   name, vertexUpdate, weak=False)
 
 
 def destroy(self):
     arbvp.glDeleteProgramsARB(1, gltypes.GLuint(self.vertexProgram))
     arbfp.glDeleteProgramsARB(1, gltypes.GLuint(self.fragmentProgram))
 
-    self.displayOpts.removeListener('resolution', self.name)
-    self.displayOpts.removeListener('directed',   self.name)
+    name = '{}_vertices'.format(self.name)
+
+    self.displayOpts.removeListener('transform',  name)
+    self.displayOpts.removeListener('resolution', name)
+    self.displayOpts.removeListener('directed',   name)
 
     glresources.delete(self._vertexResourceName)
 
diff --git a/fsl/fslview/gl/gllabel.py b/fsl/fslview/gl/gllabel.py
index 1b8e6379e..a1fc9edeb 100644
--- a/fsl/fslview/gl/gllabel.py
+++ b/fsl/fslview/gl/gllabel.py
@@ -101,11 +101,12 @@ class GLLabel(globject.GLImageObject):
         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)
+
+        if opts.getParent() is not None:
+            opts.addSyncChangeListener(
+                'volume',     name, imageRefresh, weak=False)
+            opts.addSyncChangeListener(
+                'resolution', name, imageRefresh, weak=False)
 
 
     def removeListeners(self):
@@ -122,9 +123,11 @@ class GLLabel(globject.GLImageObject):
         opts    .removeListener(          'lut',          name)
         opts    .removeListener(          'volume',       name)
         opts    .removeListener(          'resolution',   name)
-        opts    .removeSyncChangeListener('volume',       name)
-        opts    .removeSyncChangeListener('resolution',   name)
-        opts.lut.removeListener(          'labels',       name) 
+        opts.lut.removeListener(          'labels',       name)
+
+        if opts.getParent() is not None:
+            opts.removeSyncChangeListener('volume',     name)
+            opts.removeSyncChangeListener('resolution', name)
 
 
         
@@ -140,7 +143,8 @@ class GLLabel(globject.GLImageObject):
         opts     = self.displayOpts
         texName  = '{}_{}' .format(type(self).__name__, id(self.image))
 
-        unsynced = (not opts.isSyncedToParent('volume') or
+        unsynced = (opts.getParent() is None            or
+                    not opts.isSyncedToParent('volume') or
                     not opts.isSyncedToParent('resolution'))
 
         if unsynced:
diff --git a/fsl/fslview/gl/glmask.py b/fsl/fslview/gl/glmask.py
index ac53ce08d..bf120fe3c 100644
--- a/fsl/fslview/gl/glmask.py
+++ b/fsl/fslview/gl/glmask.py
@@ -87,11 +87,12 @@ class GLMask(glvolume.GLVolume):
         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)
+
+        if opts.getParent() is not None:
+            opts.addSyncChangeListener(
+                'volume',     name, imageRefresh, weak=False)
+            opts.addSyncChangeListener(
+                'resolution', name, imageRefresh, weak=False)
 
 
     def removeDisplayListeners(self):
@@ -113,14 +114,17 @@ class GLMask(glvolume.GLVolume):
         opts   .removeListener(          'invert',        name)
         opts   .removeListener(          'volume',        name)
         opts   .removeListener(          'resolution',    name)
-        opts   .removeSyncChangeListener('volume',        name)
-        opts   .removeSyncChangeListener('resolution',    name)
+
+        if opts.getParent() is not None:
+            opts.removeSyncChangeListener('volume',     name)
+            opts.removeSyncChangeListener('resolution', name)
 
 
     def testUnsynced(self):
         """Overrides :meth:`.GLVolume.testUnsynced`.
         """
-        return (not self.displayOpts.isSyncedToParent('volume') or
+        return (self.displayOpts.getParent() is None            or
+                not self.displayOpts.isSyncedToParent('volume') or
                 not self.displayOpts.isSyncedToParent('resolution'))
 
         
diff --git a/fsl/fslview/gl/glvector.py b/fsl/fslview/gl/glvector.py
index d026d3457..efd05bdd4 100644
--- a/fsl/fslview/gl/glvector.py
+++ b/fsl/fslview/gl/glvector.py
@@ -167,9 +167,10 @@ class GLVector(globject.GLImageObject):
         opts   .addListener('modulate',      name, modUpdate,     weak=False)
         opts   .addListener('modThreshold',  name, shaderUpdate,  weak=False)
         opts   .addListener('resolution',    name, imageUpdate,   weak=False)
-        
-        opts.addSyncChangeListener(
-            'resolution', name, imageRefresh, weak=False)
+
+        if opts.getParent() is not None:
+            opts.addSyncChangeListener(
+                'resolution', name, imageRefresh, weak=False)
 
 
     def removeListeners(self):
@@ -192,8 +193,9 @@ class GLVector(globject.GLImageObject):
         opts   .removeListener(          'modThreshold', name)
         opts   .removeListener(          'volume',       name)
         opts   .removeListener(          'resolution',   name)
-        opts   .removeSyncChangeListener('volume',       name)
-        opts   .removeSyncChangeListener('resolution',   name)
+
+        if opts.getParent() is not None:
+            opts.removeSyncChangeListener('resolution', name)
 
 
     def refreshImageTexture(self):
@@ -212,7 +214,8 @@ class GLVector(globject.GLImageObject):
         else:
             realPrefilter = lambda d: prefilter(d.transpose((3, 0, 1, 2)))
 
-        unsynced = (not opts.isSyncedToParent('resolution') or
+        unsynced = (opts.getParent() is None                or 
+                    not opts.isSyncedToParent('resolution') or
                     not opts.isSyncedToParent('volume'))
 
         if unsynced:
@@ -268,7 +271,8 @@ class GLVector(globject.GLImageObject):
             type(self).__name__, id(self.image), id(modImage))
 
         if modOpts is not None:
-            unsynced = (not modOpts.isSyncedToParent('resolution') or
+            unsynced = (modOpts.getParent() is None                or
+                        not modOpts.isSyncedToParent('resolution') or
                         not modOpts.isSyncedToParent('volume'))
 
             # TODO If unsynced, this GLVector needs to 
diff --git a/fsl/fslview/gl/glvolume.py b/fsl/fslview/gl/glvolume.py
index bcb28c7c4..40e4a5a22 100644
--- a/fsl/fslview/gl/glvolume.py
+++ b/fsl/fslview/gl/glvolume.py
@@ -123,9 +123,8 @@ class GLVolume(globject.GLImageObject):
         :class:`GLVolume` instance needs to create its own image texture;
         returns ``False`` otherwise.
         """
-        if self.displayOpts.getParent() is None:
-            return True
-        return (not self.displayOpts.isSyncedToParent('volume')     or
+        return (self.displayOpts.getParent() is None                or
+                not self.displayOpts.isSyncedToParent('volume')     or
                 not self.displayOpts.isSyncedToParent('resolution') or
                 not self.displayOpts.isSyncedToParent('interpolation'))
         
@@ -230,12 +229,13 @@ class GLVolume(globject.GLImageObject):
         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)
+        if opts.getParent() is not None:
+            opts.addSyncChangeListener(
+                'volume',        lName, imageRefresh, weak=False)
+            opts.addSyncChangeListener(
+                'resolution',    lName, imageRefresh, weak=False)
+            opts.addSyncChangeListener(
+                'interpolation', lName, imageRefresh, weak=False)
 
 
     def removeDisplayListeners(self):
@@ -258,9 +258,10 @@ class GLVolume(globject.GLImageObject):
         opts   .removeListener(          'volume',         lName)
         opts   .removeListener(          'resolution',     lName)
         opts   .removeListener(          'interpolation',  lName)
-        opts   .removeSyncChangeListener('volume',         lName)
-        opts   .removeSyncChangeListener('resolution',     lName)
-        opts   .removeSyncChangeListener('interpolation',  lName)
+        if opts.getParent() is not None:
+            opts.removeSyncChangeListener('volume',        lName)
+            opts.removeSyncChangeListener('resolution',    lName)
+            opts.removeSyncChangeListener('interpolation', lName)
 
         
     def preDraw(self):
diff --git a/fsl/fslview/gl/slicecanvas.py b/fsl/fslview/gl/slicecanvas.py
index 6da930479..6e43d1205 100644
--- a/fsl/fslview/gl/slicecanvas.py
+++ b/fsl/fslview/gl/slicecanvas.py
@@ -553,7 +553,7 @@ class SliceCanvas(props.HasProperties):
                   'changed to {}'.format(display.name,
                                          display.overlayType))
 
-        self.__genGLObject(display.getOverlay(), display)
+        self.__genGLObject(display.getOverlay())
         self._refresh()
 
 
@@ -602,6 +602,9 @@ class SliceCanvas(props.HasProperties):
 
         self._glObjects[overlay] = globj
 
+        if updateRenderTextures:
+            self._updateRenderTextures() 
+
         if not display.isBound('softwareMode', self):
             display.bindProps('softwareMode', self)
 
@@ -990,6 +993,8 @@ class SliceCanvas(props.HasProperties):
                 # Assume that all is well - the texture
                 # just has not yet been created
                 if rt is None:
+                    log.debug('Render texture missing for overlay {}'.format(
+                        overlay))
                     continue
                 
                 log.debug('Drawing {} slice for overlay {} '
diff --git a/fsl/fslview/overlay.py b/fsl/fslview/overlay.py
index cc26e9073..a7f1fd7bb 100644
--- a/fsl/fslview/overlay.py
+++ b/fsl/fslview/overlay.py
@@ -22,7 +22,6 @@ import fsl.data.featresults as featresults
 import fsl.data.featimage   as fslfeatimage
 import fsl.data.strings     as strings
 import fsl.data.model       as fslmodel
-import fsl.utils.dialog     as fsldlg
 import fsl.fslview.settings as fslsettings
 
 
@@ -227,6 +226,7 @@ def loadOverlays(paths, loadFunc='default', errorFunc='default', saveDir=True):
     # to show the currently loading image
     if defaultLoad:
         import wx
+        import fsl.utils.dialog as fsldlg
         loadDlg = fsldlg.SimpleMessageDialog(wx.GetApp().GetTopWindow())
 
     # The default load function updates
diff --git a/fsl/fslview/settings.py b/fsl/fslview/settings.py
index a3f71ed48..d2d1347df 100644
--- a/fsl/fslview/settings.py
+++ b/fsl/fslview/settings.py
@@ -11,7 +11,8 @@ log = logging.getLogger(__name__)
 
 def read(name, default=None):
 
-    import wx
+    try:    import wx
+    except: return None
 
     config = wx.Config('fslview')
     
@@ -23,7 +24,8 @@ def read(name, default=None):
 
 def write(name, value):
 
-    import wx
+    try:    import wx
+    except: return None    
 
     value  = str(value)
     config = wx.Config('fslview')
diff --git a/fsl/fslview/views/canvaspanel.py b/fsl/fslview/views/canvaspanel.py
index b76e0ee83..0c7f9d3f2 100644
--- a/fsl/fslview/views/canvaspanel.py
+++ b/fsl/fslview/views/canvaspanel.py
@@ -392,17 +392,15 @@ def _screenshot(overlayList, displayCtx, canvas):
     argv += ['--size', '{}'.format(width), '{}'.format(height)]
     argv += ['--background', '0', '0', '0', '255']
 
-    argv += _genCommandLineArgs(overlayList, displayCtx, canvas)
+    argv += _genCommandLineArgs(overlays, displayCtx, canvas)
 
     log.debug('Generating screenshot with call '
               'to render: {}'.format(' '.join(argv)))
 
     # Run render.py to generate the screenshot
-    msg     = strings.messages['CanvasPanel.screenshot.pleaseWait']
-    busyDlg = wx.BusyInfo(msg, canvas)
-    result  = fsl.runTool('render', argv)
-    
-    busyDlg.Destroy()
+    msg    = strings.messages['CanvasPanel.screenshot.pleaseWait']
+    dlg    = fsldlg.ProcessingDialog(canvas, msg, fsl.runTool, 'render', argv)
+    result = dlg.Run()
 
     if result != 0:
         title = strings.titles[  'CanvasPanel.screenshot.error']
diff --git a/fsl/tools/render.py b/fsl/tools/render.py
index 8c18481bb..21770c808 100644
--- a/fsl/tools/render.py
+++ b/fsl/tools/render.py
@@ -75,26 +75,29 @@ def buildLabelBitmaps(overlayList,
     # There's no reference image for the selected overlay,
     # so we cannot calculate orientation labels
     if overlay is None:
-        return None
+        xorient = constants.ORIENT_UNKNOWN
+        yorient = constants.ORIENT_UNKNOWN
+        zorient = constants.ORIENT_UNKNOWN
+    else:
 
-    display = displayCtx.getDisplay(overlay)
-    opts    = display.getDisplayOpts()
+        display = displayCtx.getDisplay(overlay)
+        opts    = display.getDisplayOpts()
 
-    # The overlay is being displayed as it is stored on
-    # disk - the image.getOrientation method calculates
-    # and returns labels for each voxelwise axis.
-    if opts.transform in ('pixdim', 'id'):
-        xorient = overlay.getVoxelOrientation(0)
-        yorient = overlay.getVoxelOrientation(1)
-        zorient = overlay.getVoxelOrientation(2)
-
-    # The overlay is being displayed in 'real world' space -
-    # the definition of this space may be present in the
-    # overlay meta data
-    else:
-        xorient = overlay.getWorldOrientation(0)
-        yorient = overlay.getWorldOrientation(1)
-        zorient = overlay.getWorldOrientation(2)
+        # The overlay is being displayed as it is stored on
+        # disk - the image.getOrientation method calculates
+        # and returns labels for each voxelwise axis.
+        if opts.transform in ('pixdim', 'id'):
+            xorient = overlay.getVoxelOrientation(0)
+            yorient = overlay.getVoxelOrientation(1)
+            zorient = overlay.getVoxelOrientation(2)
+
+        # The overlay is being displayed in 'real world' space -
+        # the definition of this space may be present in the
+        # overlay meta data
+        else:
+            xorient = overlay.getWorldOrientation(0)
+            yorient = overlay.getWorldOrientation(1)
+            zorient = overlay.getWorldOrientation(2)
 
     if constants.ORIENT_UNKNOWN in [xorient, yorient, zorient]:
         fgColour = 'red'
diff --git a/fsl/utils/layout.py b/fsl/utils/layout.py
index 6e40170a9..49ce13889 100644
--- a/fsl/utils/layout.py
+++ b/fsl/utils/layout.py
@@ -240,7 +240,9 @@ def buildOrthoLayout(canvasBmps,
     bitmaps, and colour bar bitmap.
     """
 
-    if labelBmps is None: labelBmps = [None] * len(canvasBmps)
+    if labelBmps is None:
+        labelBmps  = [None] * len(canvasBmps)
+        showLabels = False
 
     canvasBoxes = map(lambda cbmp, lbmps: buildCanvasBox(cbmp,
                                                          lbmps,
-- 
GitLab