def useFixedVolume(self, volume): # TODO store original orientation? axialVolume = self.reorientVolumeToAxial(volume) self.axialFixedVolume = axialVolume widget = slicer.modules.SteeredPolyAffineRegistrationWidget self.fixedImageCL = ImageCL(self.preferredDeviceType) self.fixedImageCL.fromVolume(axialVolume) self.fixedImageCL.normalize()
def useMovingVolume(self, volume): # TODO store original orientation? axialVolume = self.reorientVolumeToAxial(volume) self.axialMovingVolume = axialVolume self.movingImageCL = ImageCL(self.preferredDeviceType) self.movingImageCL.fromVolume(axialVolume) self.movingImageCL.normalize() self.origMovingImageCL = self.movingImageCL
def initOutputVolume(self, outputVolume): # NOTE: Reuse old result? # TODO: need to store old deformation for this to work, for now reset everything widget = slicer.modules.SteeredPolyAffineRegistrationWidget if outputVolume is None: vl = slicer.modules.volumes.logic() # Use reoriented moving volume outputVolume = vl.CloneVolume(slicer.mrmlScene, self.axialMovingVolume, "steered-warped") widget.outputSelector.setCurrentNode(outputVolume) else: outputImage = vtk.vtkImageData() outputImage.DeepCopy(self.axialMovingVolume.GetImageData()) outputVolume.SetAndObserveImageData(outputImage) #TODO reuse deformation self.outputImageCL = ImageCL(self.preferredDeviceType) self.outputImageCL.fromVolume(outputVolume) self.outputImageCL.normalize() # Force update of gradient magnitude image self.updateOutputVolume( self.outputImageCL )
class SteeredPolyAffineRegistrationLogic(object): #TODO """ Implement a template matching optimizer that is integrated with the slicer main loop. """ def __init__(self): self.interval = 1000 self.timer = None # parameter defaults self.numberAffines = 1 self.drawIterations = 1 self.polyAffineRadius = 10.0 # TODO #self.transform = ? # optimizer state variables self.iteration = 0 self.interaction = False self.steerMode = "rotate" self.position = [] self.paintCoordinates = [] self.lastEventPosition = [0.0, 0.0, 0.0] self.startEventPosition = [0.0, 0.0, 0.0] # Queue containing info on steering action events, tuples of # (Mtime, action, xy0, RAS0, xy1, RAS1, sliceWidget) # Draw added poly affine correction? Fold it immediately? self.actionQueue = Queue.Queue() self.lineStartXY = (0, 0, 0) self.lineEndXY = (0, 0, 0) self.lineStartRAS = [0.0, 0.0, 0.0] self.lineEndRAS = [0.0, 0.0, 0.0] print("Reload") self.actionState = "idle" self.interactorObserverTags = [] self.styleObserverTags = [] self.sliceWidgetsPerStyle = {} self.nodeIndexPerStyle = {} self.sliceNodePerStyle = {} self.lastDrawMTime = 0 self.lastDrawSliceWidget = None self.linesActor = vtk.vtkActor() self.linesMapper = vtk.vtkPolyDataMapper() self.linesGlyph = vtk.vtkGlyph3D() self.roiActor = vtk.vtkActor() self.roiMapper = vtk.vtkPolyDataMapper() self.roiGlyph = vtk.vtkGlyph3D() self.linesActor.GetProperty().SetOpacity(0.5) self.linesActor.GetProperty().SetColor([0.1, 0.8, 0.1]) self.roiActor.GetProperty().SetOpacity(0.5) self.roiActor.GetProperty().SetColor([0.1, 0.1, 0.9]) self.preferredDeviceType = "GPU" def __del__(self): # TODO # Clean up, delete line in render window layoutManager = slicer.app.layoutManager() sliceNodeCount = slicer.mrmlScene.GetNumberOfNodesByClass('vtkMRMLSliceNode') for nodeIndex in xrange(sliceNodeCount): # find the widget for each node in scene sliceNode = slicer.mrmlScene.GetNthNodeByClass(nodeIndex, 'vtkMRMLSliceNode') sliceWidget = layoutManager.sliceWidget(sliceNode.GetLayoutName()) if sliceWidget: renwin = sliceWidget.sliceView().renderWindow() rencol = renwin.GetRenderers() if rencol.GetNumberOfItems() == 2: rencol.GetItemAsObject(1).RemoveActor(self.linesActor) def reorientVolumeToAxial(self, volume): nodeName = slicer.mrmlScene.GetUniqueNameByString(volume.GetName()) vl = slicer.modules.volumes.logic() axialVolume = vl.CloneVolume(slicer.mrmlScene, volume, nodeName) axialVolume.SetName(nodeName) slicer.mrmlScene.AddNode(axialVolume) cliparams = {} cliparams["orientation"] = "Axial" cliparams["inputVolume1"] = volume.GetID() cliparams["outputVolume"] = axialVolume.GetID() print "Axial reorientation of " + volume.GetID() + " to " + axialVolume.GetID() slicer.cli.run(slicer.modules.orientscalarvolume, None, cliparams, wait_for_completion=True) return axialVolume def useFixedVolume(self, volume): # TODO store original orientation? axialVolume = self.reorientVolumeToAxial(volume) self.axialFixedVolume = axialVolume widget = slicer.modules.SteeredPolyAffineRegistrationWidget self.fixedImageCL = ImageCL(self.preferredDeviceType) self.fixedImageCL.fromVolume(axialVolume) self.fixedImageCL.normalize() def useMovingVolume(self, volume): # TODO store original orientation? axialVolume = self.reorientVolumeToAxial(volume) self.axialMovingVolume = axialVolume self.movingImageCL = ImageCL(self.preferredDeviceType) self.movingImageCL.fromVolume(axialVolume) self.movingImageCL.normalize() self.origMovingImageCL = self.movingImageCL def initOutputVolume(self, outputVolume): # NOTE: Reuse old result? # TODO: need to store old deformation for this to work, for now reset everything widget = slicer.modules.SteeredPolyAffineRegistrationWidget if outputVolume is None: vl = slicer.modules.volumes.logic() # Use reoriented moving volume outputVolume = vl.CloneVolume(slicer.mrmlScene, self.axialMovingVolume, "steered-warped") widget.outputSelector.setCurrentNode(outputVolume) else: outputImage = vtk.vtkImageData() outputImage.DeepCopy(self.axialMovingVolume.GetImageData()) outputVolume.SetAndObserveImageData(outputImage) #TODO reuse deformation self.outputImageCL = ImageCL(self.preferredDeviceType) self.outputImageCL.fromVolume(outputVolume) self.outputImageCL.normalize() # Force update of gradient magnitude image self.updateOutputVolume( self.outputImageCL ) def updateOutputVolume(self, imgcl): widget = slicer.modules.SteeredPolyAffineRegistrationWidget outputVolume = widget.outputSelector.currentNode() """ displayShape = self.fixedImageCL.shape for dim in xrange(3): displayShape[dim] = min( displayShape[dim], widget.displayGridSpinBoxes[dim].value) imgcl_gridded = imgcl.resample(displayShape) imgcl_gridded.copyToVolume(outputVolume) # TODO: need ratios / mapping between screen and grad mag image # displayGridRatios, warpGridRatios """ vtkimage = imgcl.toVTKImage() #TODO sync origin #castf = vtk.vtkImageCast() #castf.SetOutputScalarTypeToFloat() #castf.SetInput(vtkimage) #castf.Update() #outputVolume.GetImageData().GetPointData().SetScalars( castf.GetOutput().GetPointData().GetScalars() ) oldimage = outputVolume.GetImageData() outputVolume.SetAndObserveImageData(vtkimage) #outputVolume.GetImageData().GetPointData().SetScalars( vtkimage.GetPointData().GetScalars() ) #outputVolume.GetImageData().GetPointData().GetScalars().Modified() #outputVolume.GetImageData().Modified() #outputVolume.Modified() def startSteeredRegistration(self): widget= slicer.modules.SteeredPolyAffineRegistrationWidget fixedVolume = widget.fixedSelector.currentNode() movingVolume = widget.movingSelector.currentNode() outputVolume = widget.outputSelector.currentNode() #print(self.identity) self.removeObservers() # get new slice nodes layoutManager = slicer.app.layoutManager() sliceNodeCount = slicer.mrmlScene.GetNumberOfNodesByClass('vtkMRMLSliceNode') for nodeIndex in xrange(sliceNodeCount): # find the widget for each node in scene sliceNode = slicer.mrmlScene.GetNthNodeByClass(nodeIndex, 'vtkMRMLSliceNode') sliceWidget = layoutManager.sliceWidget(sliceNode.GetLayoutName()) if sliceWidget: # add observers and keep track of tags style = sliceWidget.sliceView().interactorStyle() self.interactor = style.GetInteractor() self.sliceWidgetsPerStyle[self.interactor] = sliceWidget self.nodeIndexPerStyle[self.interactor] = nodeIndex self.sliceNodePerStyle[self.interactor] = sliceNode events = ( "LeftButtonPressEvent","LeftButtonReleaseEvent","MouseMoveEvent", "KeyPressEvent","EnterEvent", "LeaveEvent" ) for event in events: tag = self.interactor.AddObserver(event, self.processEvent, 1.0) self.interactorObserverTags.append(tag) compositeNodeDict = slicer.util.getNodes('vtkMRMLSliceCompositeNode*') sortedKeys = sorted(compositeNodeDict.iterkeys()) for i in xrange(3): compositeNode = compositeNodeDict[sortedKeys[i]] compositeNode.SetBackgroundVolumeID(fixedVolume.GetID()) compositeNode.SetForegroundVolumeID(outputVolume.GetID()) compositeNode.SetForegroundOpacity(0.0) compositeNode.LinkedControlOn() for i in xrange(3,6): compositeNode = compositeNodeDict[sortedKeys[i]] compositeNode.SetBackgroundVolumeID(fixedVolume.GetID()) compositeNode.SetForegroundVolumeID(outputVolume.GetID()) compositeNode.SetForegroundOpacity(1.0) compositeNode.LinkedControlOn() #compositeNodes.values()[0].LinkedControlOn() yellowWidget = layoutManager.sliceWidget('Yellow') # Set all view orientation to axial #sliceNodeCount = slicer.mrmlScene.GetNumberOfNodesByClass('vtkMRMLSliceNode') #for nodeIndex in xrange(sliceNodeCount): # sliceNode = slicer.mrmlScene.GetNthNodeByClass(nodeIndex, 'vtkMRMLSliceNode') # sliceNode.SetOrientationToAxial() applicationLogic = slicer.app.applicationLogic() applicationLogic.FitSliceToAll() # Initialize poly affine self.polyAffine = PolyAffineCL(self.fixedImageCL, self.movingImageCL) self.polyAffine.create_identity(self.numberAffines) # TODO: use radius info from GUI self.polyAffine.optimize_setup() self.registrationIterationNumber = 0; qt.QTimer.singleShot(self.interval, self.updateStep) def stopSteeredRegistration(self): slicer.mrmlScene.RemoveNode(self.axialFixedVolume) slicer.mrmlScene.RemoveNode(self.axialMovingVolume) self.actionState = "idle" self.removeObservers() def updateStep(self): self.registrationIterationNumber = self.registrationIterationNumber + 1 #print('Registration iteration %d' %(self.registrationIterationNumber)) #TODO: switch to while? clear queue immediately or do one at a time? if not self.actionQueue.empty(): self.invoke_correction() self.polyAffine.optimize_step() # Only upsample and redraw updated image every N iterations if self.registrationIterationNumber % self.drawIterations == 0: self.outputImageCL = self.polyAffine.movingCL self.updateOutputVolume(self.outputImageCL) self.redrawSlices() # Initiate another iteration of the registration algorithm. if self.interaction: qt.QTimer.singleShot(self.interval, self.updateStep) def invoke_correction(self): """ widget = slicer.modules.SteeredPolyAffineRegistrationWidget #fixedVolume = widget.fixedSelector.currentNode() #movingVolume = widget.movingSelector.currentNode() fixedVolume = self.axialFixedVolume movingVolume = self.axialMovingVolume imageSize = fixedVolume.GetImageData().GetDimensions() shape = self.fixedImageCL.shape spacing = self.fixedImageCL.spacing origin = movingVolume.GetOrigin() """ spacing = self.fixedImageCL.spacing movingRAStoIJK = vtk.vtkMatrix4x4() self.axialMovingVolume.GetRASToIJKMatrix(movingRAStoIJK) actionItem = self.actionQueue.get() #TODO: draw green circle representing current input on images? startRAS = actionItem[5] endRAS = actionItem[6] startIJK = movingRAStoIJK.MultiplyPoint(startRAS + (1,)) endIJK = movingRAStoIJK.MultiplyPoint(endRAS + (1,)) x = np.zeros((3,), np.float32) y = np.zeros((3,), np.float32) maxd = 2.0 for d in range(3): x[d] = startIJK[d] * spacing[d] y[d] = endIJK[d] * spacing[d] maxd = max(maxd, 1.05*abs(x[d]-y[d])) #r = np.ones((3,), np.float32) * maxd r = np.ones((3,), np.float32) * max(2.0, 1.05 * np.linalg.norm(x - y)) steerMode = actionItem[1] if steerMode == "erase": # TODO: list of added affines, user can select to undo specific ones self.polyAffine.remove_affine(x, r[0]) self.polyAffine.optimize_setup() return if steerMode == "rotate": steerer = SteeringRotation(self.fixedImageCL, self.movingImageCL, x, r) elif steerMode == "scale": steerer = SteeringScale(self.fixedImageCL, self.movingImageCL, x, r) else: print "WARNING: unknown steering action" return A, T = steerer.steer() # Append user defined affine to polyaffine self.polyAffine.add_affine(A, T, x, r) print "Added A", A, "T", T # TODO: reinitialize optimizer? Fix user affine? self.polyAffine.optimize_setup() def processEvent(self,observee,event=None): eventProcessed = False from slicer import app layoutManager = slicer.app.layoutManager() if self.sliceWidgetsPerStyle.has_key(observee): eventProcessed = True sliceWidget = self.sliceWidgetsPerStyle[observee] style = sliceWidget.sliceView().interactorStyle() self.interactor = style.GetInteractor() nodeIndex = self.nodeIndexPerStyle[observee] sliceNode = self.sliceNodePerStyle[observee] windowSize = sliceNode.GetDimensions() windowW = float(windowSize[0]) windowH = float(windowSize[1]) aspectRatio = windowH/windowW self.lastDrawnSliceWidget = sliceWidget if event == "EnterEvent": # TODO check interaction mode (eg tugging vs squishing) cursor = qt.QCursor(qt.Qt.OpenHandCursor) app.setOverrideCursor(cursor) self.actionState = "interacting" self.abortEvent(event) elif event == "LeaveEvent": cursor = qt.QCursor(qt.Qt.ArrowCursor) app.setOverrideCursor(cursor) #app.restoreOverrideCursor() self.actionState = "idle" self.abortEvent(event) elif event == "LeftButtonPressEvent": if nodeIndex > 2: cursor = qt.QCursor(qt.Qt.ClosedHandCursor) app.setOverrideCursor(cursor) xy = style.GetInteractor().GetEventPosition() xyz = sliceWidget.sliceView().convertDeviceToXYZ(xy) ras = sliceWidget.sliceView().convertXYZToRAS(xyz) self.startEventPosition = ras self.actionState = "steerStart" self.steerStartTime = time.clock() self.steerStartXY = xy self.steerStartRAS = ras else: self.actionState = "clickReject" self.abortEvent(event) elif event == "LeftButtonReleaseEvent": if self.actionState == "steerStart": cursor = qt.QCursor(qt.Qt.OpenHandCursor) app.setOverrideCursor(cursor) xy = style.GetInteractor().GetEventPosition() xyz = sliceWidget.sliceView().convertDeviceToXYZ(xy) ras = sliceWidget.sliceView().convertXYZToRAS(xyz) self.lastEventPosition = ras self.steerEndXY = xy self.steerEndRAS = ras # TODO: only draw line within ??? seconds self.lastDrawMTime = sliceNode.GetMTime() self.lastDrawSliceWidget = sliceWidget self.actionQueue.put( (sliceNode.GetMTime(), self.steerMode, sliceWidget, self.steerStartXY, self.steerEndXY, self.steerStartRAS, self.steerEndRAS) ) renOverlay = self.getOverlayRenderer( style.GetInteractor().GetRenderWindow() ) renOverlay.RemoveActor(self.roiActor) self.actionState = "interacting" self.abortEvent(event) elif event == "MouseMoveEvent": if self.actionState == "interacting": """ xy = style.GetInteractor().GetEventPosition() xyz = sliceWidget.sliceView().convertDeviceToXYZ(xy) ras = sliceWidget.sliceView().convertXYZToRAS(xyz) w = slicer.modules.SteeredPolyAffineRegistrationWidget movingRAStoIJK = vtk.vtkMatrix4x4() w.movingSelector.currentNode().GetRASToIJKMatrix(movingRAStoIJK) ijk = movingRAStoIJK.MultiplyPoint(ras + (1,)) """ if nodeIndex > 2: cursor = qt.QCursor(qt.Qt.OpenHandCursor) app.setOverrideCursor(cursor) else: cursor = qt.QCursor(qt.Qt.ForbiddenCursor) app.setOverrideCursor(cursor) elif self.actionState == "steerStart": # Drawing a line that shows ROI cursor = qt.QCursor(qt.Qt.ClosedHandCursor) app.setOverrideCursor(cursor) xy = style.GetInteractor().GetEventPosition() self.drawROI(style.GetInteractor(), self.steerStartXY, xy) # TODO: draw ROI on the fixed image as well? """ otherSliceNode = slicer.mrmlScene.GetNthNodeByClass(nodeIndex-3, 'vtkMRMLSliceNode') otherSliceWidget = layoutManager.sliceWidget(otherSliceNode.GetLayoutName()) otherSliceView = otherSliceWidget.sliceView() otherSliceStyle = otherSliceWidget.interactorStyle() # TODO: reuse actor? #self.drawROI(otherSliceStyle.GetInteractor(), self.steerStartXY, xy) otherSliceStyle.GetInteractor(). renOverlay = self.getOverlayRenderer( otherSliceStyle.GetInteractor().GetRenderWindow() ) renOverlay.AddActor(self.roiActor) """ else: pass self.abortEvent(event) else: eventProcessed = False if eventProcessed: self.redrawSlices() def removeObservers(self): # remove observers and reset for tag in self.interactorObserverTags: self.interactor.RemoveObserver(tag) self.interactorObserverTags = [] self.sliceWidgetsPerStyle = {} def abortEvent(self,event): """Set the AbortFlag on the vtkCommand associated with the event - causes other things listening to the interactor not to receive the events""" # TODO: make interactorObserverTags a map to we can # explicitly abort just the event we handled - it will # be slightly more efficient for tag in self.interactorObserverTags: cmd = self.interactor.GetCommand(tag) if cmd is not None: cmd.SetAbortFlag(1) def drawROI(self, interactor, startXY, endXY): coord = vtk.vtkCoordinate() coord.SetCoordinateSystemToDisplay() coord.SetValue(startXY[0], startXY[1], 0.0) worldStartXY = coord.GetComputedWorldValue(interactor.GetRenderWindow().GetRenderers().GetFirstRenderer()) coord.SetValue(endXY[0], endXY[1], 0.0) worldEndXY = coord.GetComputedWorldValue(interactor.GetRenderWindow().GetRenderers().GetFirstRenderer()) pts = vtk.vtkPoints() pts.InsertNextPoint(worldStartXY) vectors = vtk.vtkDoubleArray() vectors.SetNumberOfComponents(3) vectors.SetNumberOfTuples(1) vectors.SetTuple3(0, worldEndXY[0]-worldStartXY[0], worldEndXY[1]-worldStartXY[1], worldEndXY[2]-worldStartXY[2]) r = 0 for d in range(3): r += (worldEndXY[d] - worldStartXY[d]) ** 2.0 r = math.sqrt(r) radii = vtk.vtkFloatArray() radii.InsertNextValue(r) radii.SetName("radius") pd = vtk.vtkPolyData() pd.SetPoints(pts) #pd.GetPointData().SetVectors(vectors) #pd.GetPointData().AddArray(radii) #pd.GetPointData().SetActiveScalars("radius") pd.GetPointData().SetScalars(radii) #lineSource = vtk.vtkArrowSource() """ lineSource = vtk.vtkGlyphSource2D() lineSource.SetGlyphTypeToCircle() #lineSource.SetGlyphTypeToArrow() lineSource.FilledOff() lineSource.Update() """ lineSource = vtk.vtkSphereSource() lineSource.SetRadius(1.0) lineSource.SetPhiResolution(12) lineSource.SetThetaResolution(12) self.roiGlyph.SetInput(pd) self.roiGlyph.SetSource(lineSource.GetOutput()) self.roiGlyph.ScalingOn() self.roiGlyph.OrientOn() self.roiGlyph.SetScaleFactor(1.0) #self.roiGlyph.SetVectorModeToUseVector() #self.roiGlyph.SetScaleModeToScaleByVector() self.roiGlyph.SetScaleModeToScaleByScalar() self.roiGlyph.Update() self.roiMapper.SetInput(self.roiGlyph.GetOutput()) self.roiActor.SetMapper(self.roiMapper) renOverlay = self.getOverlayRenderer( interactor.GetRenderWindow() ) renOverlay.AddActor(self.roiActor) def redrawSlices(self): # TODO: memory leak layoutManager = slicer.app.layoutManager() sliceNodeCount = slicer.mrmlScene.GetNumberOfNodesByClass('vtkMRMLSliceNode') for nodeIndex in xrange(sliceNodeCount): # find the widget for each node in scene sliceNode = slicer.mrmlScene.GetNthNodeByClass(nodeIndex, 'vtkMRMLSliceNode') sliceWidget = layoutManager.sliceWidget(sliceNode.GetLayoutName()) if sliceWidget: renwin = sliceWidget.sliceView().renderWindow() rencol = renwin.GetRenderers() if rencol.GetNumberOfItems() >= 2: rencol.GetItemAsObject(1).RemoveActor(self.linesActor) renwin.Render() def getOverlayRenderer(self, renwin): rencol = renwin.GetRenderers() renOverlay = None if rencol.GetNumberOfItems() >= 2: renOverlay = rencol.GetItemAsObject(1) else: renOverlay = vtk.vtkRenderer() renwin.SetNumberOfLayers(2) renwin.AddRenderer(renOverlay) #renOverlay.SetInteractive(0) #renOverlay.SetLayer(1) return renOverlay def testingData(self): """Load some default data for development and set up a transform and viewing scenario for it. """ #import SampleData #sampleDataLogic = SampleData.SampleDataLogic() #mrHead = sampleDataLogic.downloadMRHead() #dtiBrain = sampleDataLogic.downloadDTIBrain() # w = slicer.modules.SteeredPolyAffineRegistrationWidget # w.fixedSelector.setCurrentNode(mrHead) # w.movingSelector.setCurrentNode(dtiBrain) sourcePath = os.path.dirname(slicer.modules.steeredpolyaffineregistration.path) if not slicer.util.getNodes('blob_big*'): fileName = os.path.join(sourcePath, "Testing", "blob_big.mha") vl = slicer.modules.volumes.logic() brain1Node = vl.AddArchetypeVolume(fileName, "blob_big", 0) else: nodes = slicer.util.getNodes('blob_big.mha') brain1Node = nodes[0] if not slicer.util.getNodes('blob_small*'): fileName = os.path.join(sourcePath, "Testing", "blob_small.mha") vl = slicer.modules.volumes.logic() brain2Node = vl.AddArchetypeVolume(fileName, "blob_small", 0) #TODO else assign from list # if not slicer.util.getNodes('movingToFixed*'): # # Create transform node # transform = slicer.vtkMRMLLinearTransformNode() # transform.SetName('movingToFixed') # slicer.mrmlScene.AddNode(transform) # transform = slicer.util.getNode('movingToFixed') # ### # # neutral.SetAndObserveTransformNodeID(transform.GetID()) # ### compositeNodes = slicer.util.getNodes('vtkMRMLSliceCompositeNode*') for compositeNode in compositeNodes.values(): compositeNode.SetBackgroundVolumeID(brain1Node.GetID()) compositeNode.SetForegroundVolumeID(brain2Node.GetID()) compositeNode.SetForegroundOpacity(0.5) applicationLogic = slicer.app.applicationLogic() applicationLogic.FitSliceToAll()