def emptyGridTransform(transformSize=[193, 229, 193], transformOrigin=[-96.0, -132.0, -78.0], transformSpacing=[1.0, 1.0, 1.0], transformNode=None): if not transformNode: transformNode = slicer.mrmlScene.AddNewNodeByClass( 'vtkMRMLGridTransformNode') voxelType = vtk.VTK_FLOAT fillVoxelValue = 0 # Create an empty image volume, filled with fillVoxelValue imageData = vtk.vtkImageData() imageData.SetDimensions(transformSize) imageData.AllocateScalars(voxelType, 3) imageData.GetPointData().GetScalars().Fill(fillVoxelValue) # Create transform transform = slicer.vtkOrientedGridTransform() transform.SetInterpolationModeToCubic() transform.SetDisplacementGridData(imageData) # Create transform node transformNode.SetAndObserveTransformFromParent(transform) transformNode.GetTransformFromParent().GetDisplacementGrid().SetOrigin( transformOrigin) transformNode.GetTransformFromParent().GetDisplacementGrid().SetSpacing( transformSpacing) return transformNode
def gridTransformFromCorners(self, volumeNode, sourceCorners, targetCorners): """Create a grid transform that maps between the current and the desired corners. """ # sanity check columns, rows, slices = volumeNode.GetImageData().GetDimensions() cornerShape = (slices, 2, 2, 3) if not (sourceCorners.shape == cornerShape and targetCorners.shape == cornerShape): raise Exception( "Corner shapes do not match volume dimensions %s, %s, %s" % (sourceCorners.shape, targetCorners.shape, cornerShape)) # create the grid transform node gridTransform = slicer.vtkMRMLGridTransformNode() gridTransform.SetName( slicer.mrmlScene.GenerateUniqueName(volumeNode.GetName() + ' acquisition transform')) slicer.mrmlScene.AddNode(gridTransform) # place grid transform in the same subject hierarchy folder as the volume node shNode = slicer.vtkMRMLSubjectHierarchyNode.GetSubjectHierarchyNode( slicer.mrmlScene) volumeParentItemId = shNode.GetItemParent( shNode.GetItemByDataNode(volumeNode)) shNode.SetItemParent(shNode.GetItemByDataNode(gridTransform), volumeParentItemId) # create a grid transform with one vector at the corner of each slice # the transform is in the same space and orientation as the volume node gridImage = vtk.vtkImageData() gridImage.SetOrigin(*volumeNode.GetOrigin()) gridImage.SetDimensions(2, 2, slices) sourceSpacing = volumeNode.GetSpacing() gridImage.SetSpacing(sourceSpacing[0] * columns, sourceSpacing[1] * rows, sourceSpacing[2]) gridImage.AllocateScalars(vtk.VTK_DOUBLE, 3) transform = slicer.vtkOrientedGridTransform() directionMatrix = vtk.vtkMatrix4x4() volumeNode.GetIJKToRASDirectionMatrix(directionMatrix) transform.SetGridDirectionMatrix(directionMatrix) transform.SetDisplacementGridData(gridImage) gridTransform.SetAndObserveTransformToParent(transform) volumeNode.SetAndObserveTransformNodeID(gridTransform.GetID()) # populate the grid so that each corner of each slice # is mapped from the source corner to the target corner displacements = slicer.util.arrayFromGridTransform(gridTransform) for sliceIndex in range(slices): for row in range(2): for column in range(2): displacements[sliceIndex][row][column] = targetCorners[ sliceIndex][row][column] - sourceCorners[ sliceIndex][row][column]
def onExportGrid(self): """Converts the current thin plate transform to a grid""" self.hotUpdateButton = None state = self.registrationState() # since the transform is ras-to-ras, we find the extreme points # in ras space of the fixed (target) volume and fix the unoriented # box around it. Sample the grid transform at the resolution of # the fixed volume, which may be a bit overkill but it should aways # work without too much loss. rasBounds = [ 0, ] * 6 state.fixed.GetRASBounds(rasBounds) from math import floor, ceil origin = list(map(int, map(floor, rasBounds[::2]))) maxes = list(map(int, map(ceil, rasBounds[1::2]))) boundSize = [m - o for m, o in zip(maxes, origin)] spacing = state.fixed.GetSpacing() samples = [ceil(int(b / s)) for b, s in zip(boundSize, spacing)] extent = [ 0, ] * 6 extent[::2] = [ 0, ] * 3 extent[1::2] = samples extent = list(map(int, extent)) toGrid = vtk.vtkTransformToGrid() toGrid.SetGridOrigin(origin) toGrid.SetGridSpacing(state.fixed.GetSpacing()) toGrid.SetGridExtent(extent) toGrid.SetInput( state.transform.GetTransformFromParent()) # same in VTKv 5 & 6 toGrid.Update() gridTransform = slicer.vtkOrientedGridTransform() if vtk.VTK_MAJOR_VERSION < 6: gridTransform.SetDisplacementGrid( toGrid.GetOutput()) # different in VTKv 5 & 6 else: gridTransform.SetDisplacementGridData(toGrid.GetOutput()) gridNode = slicer.vtkMRMLGridTransformNode() gridNode.SetAndObserveTransformFromParent(gridTransform) gridNode.SetName(state.transform.GetName() + "-grid") slicer.mrmlScene.AddNode(gridNode)
def gridTransformFromCorners(self,volumeNode,sourceCorners,targetCorners): """Create a grid transform that maps between the current and the desired corners. """ # sanity check columns, rows, slices = volumeNode.GetImageData().GetDimensions() cornerShape = (slices, 2, 2, 3) if not (sourceCorners.shape == cornerShape and targetCorners.shape == cornerShape): raise Exception("Corner shapes do not match volume dimensions %s, %s, %s" % (sourceCorners.shape, targetCorners.shape, cornerShape)) # create the grid transform node gridTransform = slicer.vtkMRMLGridTransformNode() gridTransform.SetName(slicer.mrmlScene.GenerateUniqueName(volumeNode.GetName()+' acquisition transform')) slicer.mrmlScene.AddNode(gridTransform) # place grid transform in the same subject hierarchy folder as the volume node shNode = slicer.vtkMRMLSubjectHierarchyNode.GetSubjectHierarchyNode(slicer.mrmlScene) volumeParentItemId = shNode.GetItemParent(shNode.GetItemByDataNode(volumeNode)) shNode.SetItemParent(shNode.GetItemByDataNode(gridTransform), volumeParentItemId) # create a grid transform with one vector at the corner of each slice # the transform is in the same space and orientation as the volume node gridImage = vtk.vtkImageData() gridImage.SetOrigin(*volumeNode.GetOrigin()) gridImage.SetDimensions(2, 2, slices) sourceSpacing = volumeNode.GetSpacing() gridImage.SetSpacing(sourceSpacing[0] * columns, sourceSpacing[1] * rows, sourceSpacing[2]) gridImage.AllocateScalars(vtk.VTK_DOUBLE, 3) transform = slicer.vtkOrientedGridTransform() directionMatrix = vtk.vtkMatrix4x4() volumeNode.GetIJKToRASDirectionMatrix(directionMatrix) transform.SetGridDirectionMatrix(directionMatrix) transform.SetDisplacementGridData(gridImage) gridTransform.SetAndObserveTransformToParent(transform) volumeNode.SetAndObserveTransformNodeID(gridTransform.GetID()) # populate the grid so that each corner of each slice # is mapped from the source corner to the target corner displacements = slicer.util.arrayFromGridTransform(gridTransform) for sliceIndex in range(slices): for row in range(2): for column in range(2): displacements[sliceIndex][row][column] = targetCorners[sliceIndex][row][column] - sourceCorners[sliceIndex][row][column]
def createAcquisitionTransform(self, volumeNode, metadata): # Creates transform that applies scan conversion transform probeRadius = metadata['CurvatureRadiusProbe'] trackRadius = metadata['CurvatureRadiusTrack'] if trackRadius != 0.0: raise ValueError(f"Curvature Radius (Track) is {trackRadius}. Currently, only volume with zero radius can be imported.") # Create a sampling grid for the transform import numpy as np spacing = np.array(volumeNode.GetSpacing()) averageSpacing = (spacing[0] + spacing[1] + spacing[2]) / 3.0 voxelsPerTransformControlPoint = 20 # the transform is changing smoothly, so we don't need to add too many control points gridSpacingMm = averageSpacing * voxelsPerTransformControlPoint gridSpacingVoxel = np.floor(gridSpacingMm / spacing).astype(int) gridAxesIJK = [] imageData = volumeNode.GetImageData() extent = imageData.GetExtent() for axis in range(3): gridAxesIJK.append(list(range(extent[axis * 2], extent[axis * 2 + 1] + gridSpacingVoxel[axis], gridSpacingVoxel[axis]))) samplingPoints_shape = [len(gridAxesIJK[0]), len(gridAxesIJK[1]), len(gridAxesIJK[2]), 3] # create a grid transform with one vector at the corner of each slice # the transform is in the same space and orientation as the volume node import vtk gridImage = vtk.vtkImageData() gridImage.SetOrigin(*volumeNode.GetOrigin()) gridImage.SetDimensions(samplingPoints_shape[:3]) gridImage.SetSpacing(gridSpacingVoxel[0] * spacing[0], gridSpacingVoxel[1] * spacing[1], gridSpacingVoxel[2] * spacing[2]) gridImage.AllocateScalars(vtk.VTK_DOUBLE, 3) transform = slicer.vtkOrientedGridTransform() directionMatrix = vtk.vtkMatrix4x4() volumeNode.GetIJKToRASDirectionMatrix(directionMatrix) transform.SetGridDirectionMatrix(directionMatrix) transform.SetDisplacementGridData(gridImage) # create the grid transform node gridTransform = slicer.vtkMRMLGridTransformNode() gridTransform.SetName(slicer.mrmlScene.GenerateUniqueName(volumeNode.GetName() + ' acquisition transform')) slicer.mrmlScene.AddNode(gridTransform) gridTransform.SetAndObserveTransformToParent(transform) # populate the grid so that each corner of each slice # is mapped from the source corner to the target corner nshape = tuple(reversed(gridImage.GetDimensions())) nshape = nshape + (3,) displacements = vtk.util.numpy_support.vtk_to_numpy(gridImage.GetPointData().GetScalars()).reshape(nshape) # Get displacements from math import sin, cos ijkToRas = vtk.vtkMatrix4x4() volumeNode.GetIJKToRASMatrix(ijkToRas) spacing = volumeNode.GetSpacing() center_IJK = [(extent[0] + extent[1]) / 2.0, extent[2], (extent[4] + extent[5]) / 2.0] sourcePoints_RAS = numpy.zeros(shape=samplingPoints_shape) targetPoints_RAS = numpy.zeros(shape=samplingPoints_shape) for k in range(samplingPoints_shape[2]): for j in range(samplingPoints_shape[1]): for i in range(samplingPoints_shape[0]): samplingPoint_IJK = [gridAxesIJK[0][i], gridAxesIJK[1][j], gridAxesIJK[2][k], 1] sourcePoint_RAS = np.array(ijkToRas.MultiplyPoint(samplingPoint_IJK)[:3]) radius = probeRadius - (samplingPoint_IJK[1] - center_IJK[1]) * spacing[1] angleRad = (samplingPoint_IJK[0] - center_IJK[0]) * spacing[0] / probeRadius targetPoint_RAS = np.array([ -radius * sin(angleRad), radius * cos(angleRad) - probeRadius, spacing[2] * (samplingPoint_IJK[2] - center_IJK[2])]) displacements[k][j][i] = targetPoint_RAS - sourcePoint_RAS return gridTransform
def computeStraighteningTransform(self, transformToStraightenedNode, curveNode, sliceSizeMm, outputSpacingMm): """ Compute straightened volume (useful for example for visualization of curved vessels) resamplingCurveSpacingFactor: """ # Create a temporary resampled curve resamplingCurveSpacing = outputSpacingMm * self.transformSpacingFactor originalCurvePoints = curveNode.GetCurvePointsWorld() sampledPoints = vtk.vtkPoints() if not slicer.vtkMRMLMarkupsCurveNode.ResamplePoints( originalCurvePoints, sampledPoints, resamplingCurveSpacing, False): raise ValueError("Resampling curve failed") resampledCurveNode = slicer.mrmlScene.AddNewNodeByClass( "vtkMRMLMarkupsCurveNode", "CurvedPlanarReformat_resampled_curve_temp") resampledCurveNode.SetNumberOfPointsPerInterpolatingSegment(1) resampledCurveNode.SetCurveTypeToLinear() resampledCurveNode.SetControlPointPositionsWorld(sampledPoints) numberOfSlices = resampledCurveNode.GetNumberOfControlPoints() # Z axis (from first curve point to last, this will be the straightened curve long axis) curveStartPoint = np.zeros(3) curveEndPoint = np.zeros(3) resampledCurveNode.GetNthControlPointPositionWorld(0, curveStartPoint) resampledCurveNode.GetNthControlPointPositionWorld( resampledCurveNode.GetNumberOfControlPoints() - 1, curveEndPoint) transformGridAxisZ = ( curveEndPoint - curveStartPoint) / np.linalg.norm(curveEndPoint - curveStartPoint) # X axis = average X axis of curve, to minimize torsion (and so have a simple displacement field, which can be robustly inverted) sumCurveAxisX_RAS = np.zeros(3) for gridK in range(numberOfSlices): curvePointToWorld = vtk.vtkMatrix4x4() resampledCurveNode.GetCurvePointToWorldTransformAtPointIndex( resampledCurveNode.GetCurvePointIndexFromControlPointIndex( gridK), curvePointToWorld) curvePointToWorldArray = slicer.util.arrayFromVTKMatrix( curvePointToWorld) curveAxisX_RAS = curvePointToWorldArray[0:3, 0] sumCurveAxisX_RAS += curveAxisX_RAS meanCurveAxisX_RAS = sumCurveAxisX_RAS / np.linalg.norm( sumCurveAxisX_RAS) transformGridAxisX = meanCurveAxisX_RAS # Y axis transformGridAxisY = np.cross(transformGridAxisZ, transformGridAxisX) transformGridAxisY = transformGridAxisY / np.linalg.norm( transformGridAxisY) # Make sure that X axis is orthogonal to Y and Z transformGridAxisX = np.cross(transformGridAxisY, transformGridAxisZ) transformGridAxisX = transformGridAxisX / np.linalg.norm( transformGridAxisX) # Origin (makes the grid centered at the curve) curveLength = resampledCurveNode.GetCurveLengthWorld() curveNodePlane = vtk.vtkPlane() slicer.modules.markups.logic().GetBestFitPlane(resampledCurveNode, curveNodePlane) transformGridOrigin = np.array(curveNodePlane.GetOrigin()) transformGridOrigin -= transformGridAxisX * sliceSizeMm[0] / 2.0 transformGridOrigin -= transformGridAxisY * sliceSizeMm[1] / 2.0 transformGridOrigin -= transformGridAxisZ * curveLength / 2.0 # Create grid transform # Each corner of each slice is mapped from the original volume's reformatted slice # to the straightened volume slice. # The grid transform contains one vector at the corner of each slice. # The transform is in the same space and orientation as the straightened volume. gridDimensions = [2, 2, numberOfSlices] gridSpacing = [sliceSizeMm[0], sliceSizeMm[1], resamplingCurveSpacing] gridDirectionMatrixArray = np.eye(4) gridDirectionMatrixArray[0:3, 0] = transformGridAxisX gridDirectionMatrixArray[0:3, 1] = transformGridAxisY gridDirectionMatrixArray[0:3, 2] = transformGridAxisZ gridDirectionMatrix = slicer.util.vtkMatrixFromArray( gridDirectionMatrixArray) gridImage = vtk.vtkImageData() gridImage.SetOrigin(transformGridOrigin) gridImage.SetDimensions(gridDimensions) gridImage.SetSpacing(gridSpacing) gridImage.AllocateScalars(vtk.VTK_DOUBLE, 3) transform = slicer.vtkOrientedGridTransform() transform.SetDisplacementGridData(gridImage) transform.SetGridDirectionMatrix(gridDirectionMatrix) transformToStraightenedNode.SetAndObserveTransformFromParent(transform) # Compute displacements transformDisplacements_RAS = slicer.util.arrayFromGridTransform( transformToStraightenedNode) for gridK in range(gridDimensions[2]): curvePointToWorld = vtk.vtkMatrix4x4() resampledCurveNode.GetCurvePointToWorldTransformAtPointIndex( resampledCurveNode.GetCurvePointIndexFromControlPointIndex( gridK), curvePointToWorld) curvePointToWorldArray = slicer.util.arrayFromVTKMatrix( curvePointToWorld) curveAxisX_RAS = curvePointToWorldArray[0:3, 0] curveAxisY_RAS = curvePointToWorldArray[0:3, 1] curvePoint_RAS = curvePointToWorldArray[0:3, 3] for gridJ in range(gridDimensions[1]): for gridI in range(gridDimensions[0]): straightenedVolume_RAS = ( transformGridOrigin + gridI * gridSpacing[0] * transformGridAxisX + gridJ * gridSpacing[1] * transformGridAxisY + gridK * gridSpacing[2] * transformGridAxisZ) inputVolume_RAS = ( curvePoint_RAS + (gridI - 0.5) * sliceSizeMm[0] * curveAxisX_RAS + (gridJ - 0.5) * sliceSizeMm[1] * curveAxisY_RAS) transformDisplacements_RAS[gridK][gridJ][ gridI] = inputVolume_RAS - straightenedVolume_RAS slicer.util.arrayFromGridTransformModified(transformToStraightenedNode) slicer.mrmlScene.RemoveNode( resampledCurveNode) # delete temporary curve