From cb39c416cbe1dc1a478ab97d99e8d7563aca7625 Mon Sep 17 00:00:00 2001 From: Paul McCarthy <pauld.mccarthy@gmail.com> Date: Thu, 1 May 2014 10:10:36 +0100 Subject: [PATCH] Fixed 'drifting' bug in bet head centre select dialog, caused by circular callbacks between orthopanel and property listeners. Also made voxToWorld and worldToVox a bit more explicit in the data types that they return. --- fsl/data/fslimage.py | 39 ++++++++++++++++++----------- fsl/fslview/orthopanel.py | 13 ++++++++++ fsl/fslview/slicecanvas.py | 21 +++++++++------- fsl/tools/bet.py | 50 ++++++++++---------------------------- 4 files changed, 63 insertions(+), 60 deletions(-) diff --git a/fsl/data/fslimage.py b/fsl/data/fslimage.py index 5ca048af4..0a0847b92 100644 --- a/fsl/data/fslimage.py +++ b/fsl/data/fslimage.py @@ -77,8 +77,8 @@ class Image(object): lo, hi = sorted(tx[:, axis]) - lo = lo - self.pixdim[axis] * 0.5 - hi = hi + self.pixdim[axis] * 0.5 + lo = float(lo - self.pixdim[axis] * 0.5) + hi = float(hi + self.pixdim[axis] * 0.5) return (lo, hi) @@ -87,7 +87,9 @@ class Image(object): """ Transforms the given set of points in voxel coordinates to points in world coordinates, according to the affine - transformation specified in the image file. Parameters: + transformation specified in the image file. The returned array + is either a numpy.float64 array, or a single integer value, + depending on the input. Parameters: - p: N*A array, where N is the number of points, and A is the number of axes to consider (default: 3) @@ -101,31 +103,39 @@ class Image(object): """ voxp = self._transform(p, self.worldToVoxMat, axes) - voxp = np.array(voxp, dtype=np.int64) - - return voxp + + if voxp.size == 1: return int(voxp[0]) + else: return voxp def voxToWorld(self, p, axes=None): """ Transforms the given set of points in world coordinates to points in voxel coordinates, according to the affine - transformation specified in the image file. See the - worldToVox docstring for more details. - - """ - return self._transform(p, self.voxToWorldMat, axes) + transformation specified in the image file. The returned + array is either a numpy.float64 array, or a single float + value, depending on the input. See the worldToVox + docstring for more details. + """ + + worldp = self._transform(p, self.voxToWorldMat, axes) + + if worldp.size == 1: + #print 'casting to float!' + return float(worldp) + else: return worldp def _transform(self, p, a, axes): """ Transforms the given set of points p according to the given - affine transformation a. See the worldToVox docstring for - more details. + affine transformation a. The transformed points are returned + as a numpy.float64 array. See the worldToVox docstring for + more details. """ p = self._fillPoints(p, axes) - t = np.zeros((len(p), 3), dtype=p.dtype) + t = np.zeros((len(p), 3), dtype=np.float64) x = p[:, 0] y = p[:, 1] @@ -148,6 +158,7 @@ class Image(object): """ if not isinstance(p, collections.Iterable): p = [p] + p = np.array(p) if axes is None: return p diff --git a/fsl/fslview/orthopanel.py b/fsl/fslview/orthopanel.py index 0c7745068..de806cd86 100644 --- a/fsl/fslview/orthopanel.py +++ b/fsl/fslview/orthopanel.py @@ -152,3 +152,16 @@ class OrthoFrame(wx.Frame): wx.Frame.__init__(self, parent, title=title) self.panel = OrthoPanel(self, imageList) self.Layout() + + +class OrthoDialog(wx.Dialog): + """ + Convenience class for displaying an OrthoPanel in a (possibly modal) + dialog window. + """ + + def __init__(self, parent, imageList, title=None): + + wx.Dialog.__init__(self, parent, title=title) + self.panel = OrthoPanel(self, imageList) + self.Layout() diff --git a/fsl/fslview/slicecanvas.py b/fsl/fslview/slicecanvas.py index 93e1ade7c..05e60d357 100644 --- a/fsl/fslview/slicecanvas.py +++ b/fsl/fslview/slicecanvas.py @@ -120,17 +120,20 @@ class GLImageData(object): yidxs = np.arange(self.ydim, dtype=np.float32) yidxs, xidxs = np.meshgrid(yidxs, xidxs, indexing='ij') - # And put them into a single array (the image.voxToWorld - # method needs xyz coordinates, hence the N*3 shape here) - positionData = np.zeros((self.xdim * self.ydim, 3), dtype=np.float32) - positionData[:, xax] = xidxs.ravel(order='C') - positionData[:, yax] = yidxs.ravel(order='C') + # And put them into a single array + positionData = np.vstack(( + xidxs.ravel(order='C'), + yidxs.ravel(order='C'))).transpose() # Then we transform them from voxel - # coordinates to world coordinates - positionData = image.voxToWorld(positionData)[:, (xax, yax)] - - # GL buffers for the geometry and position data + # coordinates to world coordinates, + # making sure that they are of type + # float32 + positionData = image.voxToWorld(positionData, axes=(xax, yax)) + positionData = np.array(positionData, dtype=np.float32) + + # Define GL buffers for the geometry and position + # data containing the data we just created above geomData = geomData .ravel(order='C') positionData = positionData.ravel(order='C') geomBuffer = vbo.VBO(geomData, gl.GL_STATIC_DRAW) diff --git a/fsl/tools/bet.py b/fsl/tools/bet.py index a48efad45..849379a63 100644 --- a/fsl/tools/bet.py +++ b/fsl/tools/bet.py @@ -193,39 +193,15 @@ def selectHeadCentre(opts, button): image = fslimage.Image(opts.inputImage) imageList = fslimage.ImageList([image]) parent = button.GetTopLevelParent() - frame = orthopanel.OrthoFrame(parent, imageList, opts.inputImage) + frame = orthopanel.OrthoDialog(parent, imageList, opts.inputImage) panel = frame.panel # Whenever the x/y/z coordinates change on - # the Options object,update the orthopanel - # location - def updateViewX(val, *a): panel.setXLocation(image.voxToWorld(val, axes=0)) - def updateViewY(val, *a): panel.setYLocation(image.voxToWorld(val, axes=1)) - def updateViewZ(val, *a): panel.setZLocation(image.voxToWorld(val, axes=2)) - - optListeners = ( - ('xCoordinate', 'updateViewX_{}'.format(id(panel)), updateViewX), - ('yCoordinate', 'updateViewY_{}'.format(id(panel)), updateViewY), - ('zCoordinate', 'updateViewZ_{}'.format(id(panel)), updateViewZ)) - - for listener in optListeners: - opts.addListener(*listener) - - def rmListeners(ev): - for listener in optListeners: - prop = listener[0] - name = listener[1] - opts.removeListener(prop, name) - - # Remove the listeners when the dialog is closed - frame.Bind(wx.EVT_WINDOW_DESTROY, rmListeners) - - # And whenever the x/y/z coordinates change - # on the dialog, update the option values. + # the ortho panel, update the option values. def updateOpts(ev): - x = int(image.worldToVox(ev.x, axes=0)) - y = int(image.worldToVox(ev.y, axes=1)) - z = int(image.worldToVox(ev.z, axes=2)) + x = image.worldToVox(ev.x, axes=0) + y = image.worldToVox(ev.y, axes=1) + z = image.worldToVox(ev.z, axes=2) if x >= image.shape[0]: x = image.shape[0] - 1 elif x < 0: x = 0 @@ -242,18 +218,18 @@ def selectHeadCentre(opts, button): panel.Bind(orthopanel.EVT_LOCATION_EVENT, updateOpts) - # Position the dialog by the button that was clicked - pos = button.GetScreenPosition() - frame.SetPosition(pos) - frame.Show() - - # TODO this needs to be done after the frame has - # been displayed i.e via wx.CallAfter or similar) + # Set the initial location on the orthopanel. + # TODO this ain't working, as it needs to be + # done after the frame has been displayed, i.e + # via wx.CallAfter or similar. voxCoords = [opts.xCoordinate, opts.yCoordinate, opts.zCoordinate] worldCoords = image.voxToWorld([voxCoords])[0] panel.setLocation(*worldCoords) - + # Position the dialog by the button that was clicked + pos = button.GetScreenPosition() + frame.SetPosition(pos) + frame.ShowModal() betView = props.NotebookGroup(( -- GitLab