From b72490951b89e53f1efde966d92c3bb02db5c92d Mon Sep 17 00:00:00 2001 From: Paul McCarthy <pauld.mccarthy@gmail.com> Date: Thu, 17 Dec 2015 14:56:55 +0000 Subject: [PATCH] VectorOpts.clipThreshold is now a range called VectorOpts.clippingRange. --- fsl/data/strings.py | 5 +- fsl/fsleyes/controls/overlaydisplaypanel.py | 22 +++++--- fsl/fsleyes/controls/overlaydisplaytoolbar.py | 50 +++++++++++-------- fsl/fsleyes/displaycontext/group.py | 2 +- fsl/fsleyes/displaycontext/vectoropts.py | 18 ++++--- fsl/fsleyes/fsleyes_parseargs.py | 9 ++-- fsl/fsleyes/gl/gl21/gllinevector_funcs.py | 24 ++++++--- fsl/fsleyes/gl/gl21/glrgbvector_funcs.py | 21 +++++--- fsl/fsleyes/gl/gl21/gltensor_funcs.py | 35 +++++++------ fsl/fsleyes/gl/gl21/glvector_frag.glsl | 13 +++-- fsl/fsleyes/gl/glvector.py | 4 +- fsl/fsleyes/tooltips.py | 6 +-- 12 files changed, 126 insertions(+), 83 deletions(-) diff --git a/fsl/data/strings.py b/fsl/data/strings.py index f52a33c3d..d54391c7a 100644 --- a/fsl/data/strings.py +++ b/fsl/data/strings.py @@ -555,7 +555,7 @@ properties = TypeDict({ 'VectorOpts.suppressZ' : 'Suppress Z value', 'VectorOpts.modulateImage' : 'Modulate by', 'VectorOpts.clipImage' : 'Clip by', - 'VectorOpts.clipThreshold' : 'Clipping threshold', + 'VectorOpts.clippingRange' : 'Clipping range', 'RGBVectorOpts.interpolation' : 'Interpolation', @@ -619,6 +619,9 @@ choices = TypeDict({ 'VectorOpts.modulateImage.none' : 'No modulation', 'VectorOpts.clipImage.none' : 'No clipping', + + 'VectorOpts.clippingRange.min' : 'Min.', + 'VectorOpts.clippingRange.max' : 'Max.', 'ModelOpts.refImage.none' : 'No reference image', diff --git a/fsl/fsleyes/controls/overlaydisplaypanel.py b/fsl/fsleyes/controls/overlaydisplaypanel.py index bcc874128..d30f90fcb 100644 --- a/fsl/fsleyes/controls/overlaydisplaypanel.py +++ b/fsl/fsleyes/controls/overlaydisplaypanel.py @@ -326,7 +326,8 @@ _DISPLAY_PROPS = td.TypeDict({ 'RGBVectorOpts' : [ props.Widget('resolution', showLimits=False), - props.Widget('interpolation'), + props.Widget('interpolation', + labels=strings.choices['VolumeOpts.interpolation']), props.Widget('xColour'), props.Widget('yColour'), props.Widget('zColour'), @@ -335,9 +336,11 @@ _DISPLAY_PROPS = td.TypeDict({ props.Widget('suppressZ'), props.Widget('modulateImage', labels=_imageName), props.Widget('clipImage', labels=_imageName), - props.Widget('clipThreshold', + props.Widget('clippingRange', showLimits=False, - spin=False, + slider=True, + labels=[strings.choices['VectorOpts.clippingRange.min'], + strings.choices['VectorOpts.clippingRange.max']], dependencies=['clipImage'], enabledWhen=lambda o, ci: ci is not None)], @@ -353,9 +356,11 @@ _DISPLAY_PROPS = td.TypeDict({ props.Widget('lineWidth', showLimits=False), props.Widget('modulateImage', labels=_imageName), props.Widget('clipImage', labels=_imageName), - props.Widget('clipThreshold', + props.Widget('clippingRange', showLimits=False, - spin=False, + slider=True, + labels=[strings.choices['VectorOpts.clippingRange.min'], + strings.choices['VectorOpts.clippingRange.max']], dependencies=['clipImage'], enabledWhen=lambda o, ci: ci is not None)], @@ -386,10 +391,11 @@ _DISPLAY_PROPS = td.TypeDict({ props.Widget('suppressZ'), props.Widget('modulateImage', labels=_imageName), props.Widget('clipImage', labels=_imageName), - - props.Widget('clipThreshold', + props.Widget('clippingRange', showLimits=False, - spin=False, + slider=True, + labels=[strings.choices['VectorOpts.clippingRange.min'], + strings.choices['VectorOpts.clippingRange.max']], dependencies=['clipImage'], enabledWhen=lambda o, ci: ci is not None)], diff --git a/fsl/fsleyes/controls/overlaydisplaytoolbar.py b/fsl/fsleyes/controls/overlaydisplaytoolbar.py index f860b7ced..b39e47f44 100644 --- a/fsl/fsleyes/controls/overlaydisplaytoolbar.py +++ b/fsl/fsleyes/controls/overlaydisplaytoolbar.py @@ -331,7 +331,7 @@ class OverlayDisplayToolBar(fsltoolbar.FSLEyesToolBar): """ modSpec = _TOOLBAR_PROPS[opts]['modulateImage'] - thresSpec = _TOOLBAR_PROPS[opts]['clipThreshold'] + thresSpec = _TOOLBAR_PROPS[opts]['clippingRange'] panel = wx.Panel(self) sizer = wx.FlexGridSizer(2, 2) @@ -342,8 +342,7 @@ class OverlayDisplayToolBar(fsltoolbar.FSLEyesToolBar): modLabel = wx.StaticText(panel) thresLabel = wx.StaticText(panel) - modLabel .SetLabel(strings.properties[opts, 'modulateImage']) - thresLabel.SetLabel(strings.properties[opts, 'clipThreshold']) + modLabel.SetLabel(strings.properties[opts, 'modulateImage']) sizer.Add(modLabel) sizer.Add(modWidget, flag=wx.EXPAND) @@ -434,13 +433,13 @@ _TOOLTIPS = td.TypeDict({ 'RGBVectorOpts.modulateImage' : fsltooltips.properties['VectorOpts.' 'modulateImage'], - 'RGBVectorOpts.clipThreshold' : fsltooltips.properties['VectorOpts.' - 'clipThreshold'], + 'RGBVectorOpts.clippingRange' : fsltooltips.properties['VectorOpts.' + 'clippingRange'], 'LineVectorOpts.modulateImage' : fsltooltips.properties['VectorOpts.' 'modulateImage'], - 'LineVectorOpts.clipThreshold' : fsltooltips.properties['VectorOpts.' - 'clipThreshold'], + 'LineVectorOpts.clippingRange' : fsltooltips.properties['VectorOpts.' + 'clippingRange'], 'LineVectorOpts.lineWidth' : fsltooltips.properties['LineVectorOpts.' 'lineWidth'], @@ -451,8 +450,8 @@ _TOOLTIPS = td.TypeDict({ 'TensorOpts.modulateImage' : fsltooltips.properties['VectorOpts.' 'modulateImage'], - 'TensorOpts.clipThreshold' : fsltooltips.properties['VectorOpts.' - 'clipThreshold'], + 'TensorOpts.clippingRange' : fsltooltips.properties['VectorOpts.' + 'clippingRange'], }) """This dictionary contains tooltips for :class:`.Display` and :class:`.DisplayOpts` properties. It is referenced in the @@ -536,22 +535,28 @@ _TOOLBAR_PROPS = td.TypeDict({ 'modulateImage', labels=_modImageLabel, tooltip=_TOOLTIPS['RGBVectorOpts.modulateImage']), - 'clipThreshold' : props.Widget( - 'clipThreshold', + 'clippingRange' : props.Widget( + 'clippingRange', showLimits=False, - spin=False, - tooltip=_TOOLTIPS['RGBVectorOpts.clipThreshold'])}, + slider=True, + labels=[strings.choices['VectorOpts.clippingRange.min'], + strings.choices['VectorOpts.clippingRange.max']], + dependencies=['clipImage'], + enabledWhen=lambda o, ci: ci is not None)}, 'LineVectorOpts' : { 'modulateImage' : props.Widget( 'modulateImage', labels=_modImageLabel, tooltip=_TOOLTIPS['LineVectorOpts.modulateImage']), - 'clipThreshold' : props.Widget( - 'clipThreshold', + 'clippingRange' : props.Widget( + 'clippingRange', showLimits=False, - spin=False, - tooltip=_TOOLTIPS['LineVectorOpts.clipThreshold']), + slider=True, + labels=[strings.choices['VectorOpts.clippingRange.min'], + strings.choices['VectorOpts.clippingRange.max']], + dependencies=['clipImage'], + enabledWhen=lambda o, ci: ci is not None), 'lineWidth' : props.Widget( 'lineWidth', showLimits=False, @@ -582,11 +587,14 @@ _TOOLBAR_PROPS = td.TypeDict({ 'modulateImage', labels=_modImageLabel, tooltip=_TOOLTIPS['TensorOpts.modulateImage']), - 'clipThreshold' : props.Widget( - 'clipThreshold', + 'clippingRange' : props.Widget( + 'clippingRange', showLimits=False, - spin=False, - tooltip=_TOOLTIPS['TensorOpts.clipThreshold'])} + slider=True, + labels=[strings.choices['VectorOpts.clippingRange.min'], + strings.choices['VectorOpts.clippingRange.max']], + dependencies=['clipImage'], + enabledWhen=lambda o, ci: ci is not None)} }) """This dictionary defines specifications for all controls shown on an :class:`OverlayDisplayToolBar`. diff --git a/fsl/fsleyes/displaycontext/group.py b/fsl/fsleyes/displaycontext/group.py index 3485e7432..dd9edf2bb 100644 --- a/fsl/fsleyes/displaycontext/group.py +++ b/fsl/fsleyes/displaycontext/group.py @@ -76,7 +76,7 @@ class OverlayGroup(props.HasProperties): 'suppressZ', 'modulateImage', 'clipImage', - 'clipThreshold'], + 'clippingRange'], 'LineVectorOpts' : ['lineWidth', 'directed'], 'RGBVectorOpts' : ['interpolation'], diff --git a/fsl/fsleyes/displaycontext/vectoropts.py b/fsl/fsleyes/displaycontext/vectoropts.py index 2487eb2f7..cdc412d41 100644 --- a/fsl/fsleyes/displaycontext/vectoropts.py +++ b/fsl/fsleyes/displaycontext/vectoropts.py @@ -60,15 +60,13 @@ class VectorOpts(volumeopts.Nifti1Opts): """Clip voxels from the vector image according to another image. Any image which is in the :class:`.OverlayList`, and which has the same voxel dimensions as the vector image can be selected for clipping. The - :attr:`clipThreshold` dictates the value below which vector voxels are + :attr:`clippingRange` dictates the value below which vector voxels are clipped. """ - clipThreshold = props.Real(default=0.0, minval=0, maxval=1) - """Hide voxels for which the modulation value is below this threshold, - as a percentage of the :attr:`modulate` image data range. - """ + clippingRange = props.Bounds(ndims=1) + """Hide voxels for which the clip image value is outside of this range. """ def __init__(self, *args, **kwargs): @@ -105,20 +103,24 @@ class VectorOpts(volumeopts.Nifti1Opts): def __clipImageChanged(self, *a): """Called when the :attr:`clipImage` property changes. Updates - the range of the :attr:`clipThreshold` property. + the range of the :attr:`clippingRange` property. """ image = self.clipImage if image is None: + self.clippingRange.xmin = 0 + self.clippingRange.xmax = 1 + self.clippingRange.x = [0, 1] return opts = self.displayCtx.getOpts(image) minval = opts.dataMin maxval = opts.dataMax - self.setConstraint('clipThreshold', 'minval', minval) - self.setConstraint('clipThreshold', 'maxval', maxval) + self.clippingRange.xmin = minval + self.clippingRange.xmax = maxval + self.clippingRange.x = [minval, maxval] def __overlayListChanged(self, *a): diff --git a/fsl/fsleyes/fsleyes_parseargs.py b/fsl/fsleyes/fsleyes_parseargs.py index a6d3f1f30..a8341c310 100644 --- a/fsl/fsleyes/fsleyes_parseargs.py +++ b/fsl/fsleyes/fsleyes_parseargs.py @@ -207,7 +207,7 @@ OPTIONS = td.TypeDict({ 'suppressZ', 'modulateImage', 'clipImage', - 'clipThreshold'], + 'clippingRange'], 'LineVectorOpts' : ['lineWidth', 'directed'], 'RGBVectorOpts' : ['interpolation'], @@ -347,7 +347,7 @@ ARGUMENTS = td.TypeDict({ 'VectorOpts.suppressZ' : ('zs', 'suppressZ'), 'VectorOpts.modulateImage' : ('md', 'modulateImage'), 'VectorOpts.clipImage' : ('cl', 'clipImage'), - 'VectorOpts.clipThreshold' : ('ct', 'clipThreshold'), + 'VectorOpts.clippingRange' : ('cp', 'clipRange'), 'LineVectorOpts.lineWidth' : ('lvw', 'lineWidth'), 'LineVectorOpts.directed' : ('lvi', 'directed'), @@ -453,9 +453,8 @@ HELP = td.TypeDict({ 'VectorOpts.suppressZ' : 'Suppress Z magnitude', 'VectorOpts.modulateImage' : 'Modulate vector brightness', 'VectorOpts.clipImage' : 'Clip vector voxels', - 'VectorOpts.clipThreshold' : 'Hide voxels where clip image ' - 'value is below this threshold ' - '(expressed as a percentage)', + 'VectorOpts.clippingRange' : 'Hide voxels where clip image ' + 'value is outside of this range.', 'LineVectorOpts.lineWidth' : 'Line width', 'LineVectorOpts.directed' : 'Interpret vectors as directed', diff --git a/fsl/fsleyes/gl/gl21/gllinevector_funcs.py b/fsl/fsleyes/gl/gl21/gllinevector_funcs.py index 436c75e92..2c8a44cc7 100644 --- a/fsl/fsleyes/gl/gl21/gllinevector_funcs.py +++ b/fsl/fsleyes/gl/gl21/gllinevector_funcs.py @@ -101,9 +101,9 @@ def compileShaders(self): 'directed', 'imageDims'] fragUniforms = ['imageTexture', 'modulateTexture', 'clipTexture', - 'clipThreshold', 'xColourTexture', 'yColourTexture', - 'zColourTexture', 'voxValXform', 'cmapXform', - 'imageShape', 'useSpline'] + 'clipLow', 'clipHigh', 'xColourTexture', + 'yColourTexture', 'zColourTexture', 'voxValXform', + 'cmapXform', 'imageShape', 'useSpline'] self.shaderVars = shaders.getShaderVars(self.shaders, vertAtts, @@ -125,14 +125,21 @@ def updateShaderState(self): voxValXform = self.imageTexture.voxValXform useSpline = False imageShape = np.array(image.shape[:3], dtype=np.float32) - clipThreshold = opts.clipThreshold + clippingRange = opts.clippingRange.x voxValXform = np.array(voxValXform, dtype=np.float32).ravel('C') cmapXform = np.array(cmapXform, dtype=np.float32).ravel('C') - invClipValXform = self.clipTexture .invVoxValXform - clipThreshold = clipThreshold * invClipValXform[0, 0] + \ - invClipValXform[3, 0] + + if opts.clipImage is not None: + invClipValXform = self.clipTexture .invVoxValXform + clipLow = clippingRange[0] * invClipValXform[0, 0] + \ + invClipValXform[3, 0] + clipHigh = clippingRange[1] * invClipValXform[0, 0] + \ + invClipValXform[3, 0] + else: + clipLow = 0 + clipHigh = 1 gl.glUseProgram(self.shaders) @@ -142,7 +149,8 @@ def updateShaderState(self): gl.glUniformMatrix4fv(svars['voxValXform'], 1, False, voxValXform) gl.glUniformMatrix4fv(svars['cmapXform'], 1, False, cmapXform) - gl.glUniform1f(svars['clipThreshold'], clipThreshold) + gl.glUniform1f(svars['clipLow'], clipLow) + gl.glUniform1f(svars['clipHigh'], clipHigh) gl.glUniform1i(svars['imageTexture'], 0) gl.glUniform1i(svars['modulateTexture'], 1) diff --git a/fsl/fsleyes/gl/gl21/glrgbvector_funcs.py b/fsl/fsleyes/gl/gl21/glrgbvector_funcs.py index d500cfec9..8209bbc6c 100644 --- a/fsl/fsleyes/gl/gl21/glrgbvector_funcs.py +++ b/fsl/fsleyes/gl/gl21/glrgbvector_funcs.py @@ -66,9 +66,9 @@ def compileShaders(self): vertAtts = ['vertex', 'voxCoord', 'texCoord'] fragUniforms = ['imageTexture', 'modulateTexture', 'clipTexture', - 'clipThreshold', 'xColourTexture', 'yColourTexture', - 'zColourTexture', 'voxValXform', 'cmapXform', - 'imageShape', 'useSpline'] + 'clipLow', 'clipHigh', 'xColourTexture', + 'yColourTexture', 'zColourTexture', 'voxValXform', + 'cmapXform', 'imageShape', 'useSpline'] for va in vertAtts: shaderVars[va] = gl.glGetAttribLocation(self.shaders, va) @@ -97,14 +97,20 @@ def updateShaderState(self): cmapXform = self.xColourTexture.getCoordinateTransform() useSpline = opts.interpolation == 'spline' imageShape = np.array(self.vectorImage.shape, dtype=np.float32) - clipThreshold = opts.clipThreshold + clippingRange = opts.clippingRange # Transform the clip threshold into # the texture value range, so the # fragment shader can compare texture # values directly to it. - clipThreshold = clipThreshold * invClipValXform[0, 0] + \ - invClipValXform[3, 0] + if opts.clipImage is not None: + clipLow = clippingRange[0] * \ + invClipValXform[0, 0] + invClipValXform[3, 0] + clipHigh = clippingRange[1] * \ + invClipValXform[0, 0] + invClipValXform[3, 0] + else: + clipLow = 0 + clipHigh = 1 gl.glUseProgram(self.shaders) @@ -114,7 +120,8 @@ def updateShaderState(self): gl.glUniformMatrix4fv(svars['voxValXform'], 1, False, voxValXform) gl.glUniformMatrix4fv(svars['cmapXform'], 1, False, cmapXform) - gl.glUniform1f(svars['clipThreshold'], clipThreshold) + gl.glUniform1f(svars['clipLow'], clipLow) + gl.glUniform1f(svars['clipHigh'], clipHigh) gl.glUniform1i(svars['imageTexture'], 0) gl.glUniform1i(svars['modulateTexture'], 1) gl.glUniform1i(svars['clipTexture'], 2) diff --git a/fsl/fsleyes/gl/gl21/gltensor_funcs.py b/fsl/fsleyes/gl/gl21/gltensor_funcs.py index ba5c87cbe..2cda200a6 100644 --- a/fsl/fsleyes/gl/gl21/gltensor_funcs.py +++ b/fsl/fsleyes/gl/gl21/gltensor_funcs.py @@ -114,9 +114,9 @@ def compileShaders(self): 'eigValNorm', 'zax'] fragUniforms = ['imageTexture', 'modulateTexture', 'clipTexture', - 'clipThreshold', 'xColourTexture', 'yColourTexture', - 'zColourTexture', 'voxValXform', 'cmapXform', - 'imageShape', 'useSpline'] + 'clipLow', 'clipHigh', 'xColourTexture', + 'yColourTexture', 'zColourTexture', 'voxValXform', + 'cmapXform', 'imageShape', 'useSpline'] self.shaderVars = shaders.getShaderVars(self.shaders, @@ -180,7 +180,7 @@ def updateShaderState(self): # Other miscellaneous uniforms imageShape = np.array(self.image.shape[:3], dtype=np.float32) resolution = opts.tensorResolution - clipThreshold = opts.clipThreshold + clippingRange = opts.clippingRange tensorScale = opts.tensorScale lighting = 1 if opts.lighting else 0 useSpline = 0 @@ -189,16 +189,23 @@ def updateShaderState(self): eigValNorm = 0.5 / abs(l1.data).max() eigValNorm *= tensorScale / 100.0 - invClipValXform = self.clipTexture .invVoxValXform - clipThreshold = clipThreshold * invClipValXform[0, 0] + \ - invClipValXform[3, 0] - - gl.glUniform3fv(svars['imageShape'], 1, imageShape) - gl.glUniform1f( svars['resolution'], resolution) - gl.glUniform1f( svars['eigValNorm'], eigValNorm) - gl.glUniform1f( svars['lighting'], lighting) - gl.glUniform1f( svars['clipThreshold'], clipThreshold) - gl.glUniform1f( svars['useSpline'], useSpline) + if opts.clipImage is not None: + invClipValXform = self.clipTexture .invVoxValXform + clipLow = clippingRange[0] * invClipValXform[0, 0] + \ + invClipValXform[3, 0] + clipHigh = clippingRange[1] * invClipValXform[0, 0] + \ + invClipValXform[3, 0] + else: + clipLow = 0 + clipHigh = 1 + + gl.glUniform3fv(svars['imageShape'], 1, imageShape) + gl.glUniform1f( svars['resolution'], resolution) + gl.glUniform1f( svars['eigValNorm'], eigValNorm) + gl.glUniform1f( svars['lighting'], lighting) + gl.glUniform1f( svars['clipLow'], clipLow) + gl.glUniform1f( svars['clipHigh'], clipHigh) + gl.glUniform1f( svars['useSpline'], useSpline) # Vertices of a unit sphere. The vertex # shader will transform these vertices diff --git a/fsl/fsleyes/gl/gl21/glvector_frag.glsl b/fsl/fsleyes/gl/gl21/glvector_frag.glsl index 6dd2fe212..256b3649c 100644 --- a/fsl/fsleyes/gl/gl21/glvector_frag.glsl +++ b/fsl/fsleyes/gl/gl21/glvector_frag.glsl @@ -29,10 +29,13 @@ uniform sampler3D clipTexture; /* - * If the clipping value is below this - * threshold, the fragment is clipped. + * If the clipping value is outside of + * this range, the fragment is clipped. + * These values should be in the texture + * data range of the clipTexture. */ -uniform float clipThreshold; +uniform float clipLow; +uniform float clipHigh; /* * Colour map for the X vector component. @@ -125,8 +128,8 @@ void main(void) { clipValue = texture3D(clipTexture, fragTexCoord).x; } - /* Knock out voxels where the clipping value is below the threshold */ - if (clipValue < clipThreshold) { + /* Knock out voxels where the clipping value is outside the clipping range */ + if (clipValue < clipLow || clipValue > clipHigh) { gl_FragColor.a = 0.0; return; } diff --git a/fsl/fsleyes/gl/glvector.py b/fsl/fsleyes/gl/glvector.py index dbcccb828..bd40a0f5a 100644 --- a/fsl/fsleyes/gl/glvector.py +++ b/fsl/fsleyes/gl/glvector.py @@ -212,7 +212,7 @@ class GLVector(globject.GLImageObject): opts .addListener('suppressZ', name, cmapUpdate, weak=False) opts .addListener('modulateImage', name, modUpdate, weak=False) opts .addListener('clipImage', name, clipUpdate, weak=False) - opts .addListener('clipThreshold', name, shaderUpdate, weak=False) + opts .addListener('clippingRange', name, shaderUpdate, weak=False) opts .addListener('resolution', name, imageUpdate, weak=False) opts .addListener('transform', name, update, weak=False) @@ -244,7 +244,7 @@ class GLVector(globject.GLImageObject): opts .removeListener('suppressZ', name) opts .removeListener('modulateImage', name) opts .removeListener('clipImage', name) - opts .removeListener('clipThreshold', name) + opts .removeListener('clippingRange', name) opts .removeListener('volume', name) opts .removeListener('resolution', name) opts .removeListener('transform' , name) diff --git a/fsl/fsleyes/tooltips.py b/fsl/fsleyes/tooltips.py index 7afa7babf..a03f67579 100644 --- a/fsl/fsleyes/tooltips.py +++ b/fsl/fsleyes/tooltips.py @@ -151,9 +151,9 @@ properties = TypeDict({ 'not shown. The clipping image must have ' 'the same voxel dimensions as the vector ' 'image. ', - 'VectorOpts.clipThreshold' : 'Vector values which have a corresponding ' - 'clipping image value that is less than ' - 'this threshold are not displayed. ', + 'VectorOpts.clippingRange' : 'Vector values which have a corresponding ' + 'clipping image value that is outside of ' + 'this range are not displayed. ', 'LineVectorOpts.lineWidth' : 'The width of each vector line, in ' 'display pixels.', 'LineVectorOpts.directed' : 'If unchecked, the vector data is assumed ' -- GitLab