You can reconstruct volume from these 2D ultrasound rotational sweep very similarly to the cine MRI above.
Result:
Script that reorganizes the volume into a sequence of frames, adds position&orientation information to each frame, and reconstructs a volume:
# Input 3D volume that contains each frame as a slice
inputFrameVolumeNode = slicer.mrmlScene.GetFirstNodeByClass('vtkMRMLVolumeNode')
imageSpacingMm = 0.2 # this needs to be replaced with the actual spacing
outputSpacingMm = imageSpacingMm * 1.0 # Make the reconstructed volume spacing larger to reduce memory usage and make computations faster
# Get volume size
inputFrameVolume = inputFrameVolumeNode.GetImageData()
extent = inputFrameVolume.GetExtent()
numberOfFrames = extent[5]-extent[4]+1
# Set up frame geometry and rotation
centerOfRotationIJK = [(extent[0]+extent[1])/2.0, extent[2], 0]
rotationAxis = [0, 1, 0]
rotationDegreesPerFrame = 180.0/numberOfFrames
# Convert RGB/RGBA volume to grayscale volume
if inputFrameVolume.GetNumberOfScalarComponents() > 1:
componentToExtract = 0
print(f"Using scalar component {componentToExtract} of the image")
extract = vtk.vtkImageExtractComponents()
extract.SetInputData(inputFrameVolume)
extract.SetComponents(componentToExtract)
extract.Update()
inputFrameVolume = extract.GetOutput()
# Create an image sequence that contains the frames as a time sequence
# and also contains position/orientation for each frame.
outputSequenceNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLSequenceNode", inputFrameVolumeNode.GetName()+"_frames")
outputSequenceNode.SetIndexName("frame")
outputSequenceNode.SetIndexUnit("")
# This temporary node will be used to add frames to the image sequence
tempFrameVolumeNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLScalarVolumeNode")
for frameIndex in range(numberOfFrames):
# set current image from multiframe
crop = vtk.vtkImageClip()
crop.SetInputData(inputFrameVolume)
crop.SetOutputWholeExtent(extent[0], extent[1], extent[2], extent[3], extent[4] + frameIndex, extent[4] + frameIndex)
crop.ClipDataOn()
crop.Update()
croppedOutput = crop.GetOutput()
croppedOutput.SetExtent(extent[0], extent[1], extent[2], extent[3], 0, 0)
croppedOutput.SetOrigin(0.0, 0.0, 0.0)
tempFrameVolumeNode.SetAndObserveImageData(croppedOutput)
# set current transform
ijkToRasTransform = vtk.vtkTransform()
ijkToRasTransform.Scale(imageSpacingMm, imageSpacingMm, imageSpacingMm)
ijkToRasTransform.RotateWXYZ(frameIndex * rotationDegreesPerFrame, *rotationAxis)
ijkToRasTransform.Translate(-centerOfRotationIJK[0], -centerOfRotationIJK[1], -centerOfRotationIJK[2])
tempFrameVolumeNode.SetIJKToRASMatrix(ijkToRasTransform.GetMatrix())
# add to sequence
added = outputSequenceNode.SetDataNodeAtValue(tempFrameVolumeNode, str(frameIndex))
slicer.mrmlScene.RemoveNode(tempFrameVolumeNode)
# Create a sequence browser node for the reconstructed volume sequence
outputSequenceBrowserNode = slicer.mrmlScene.AddNewNodeByClass(
'vtkMRMLSequenceBrowserNode', outputSequenceNode.GetName() + '_browser')
outputSequenceBrowserNode.AddSynchronizedSequenceNode(outputSequenceNode)
slicer.modules.sequences.logic().UpdateAllProxyNodes() # ensure that proxy node is created
outputSequenceProxyNode = outputSequenceBrowserNode.GetProxyNode(outputSequenceNode)
slicer.util.setSliceViewerLayers(background=outputSequenceProxyNode)
slicer.modules.sequences.showSequenceBrowser(outputSequenceBrowserNode)
# Make slice view move with the image (just for visualization)
driver = slicer.modules.volumereslicedriver.logic()
redSliceNode = slicer.util.getFirstNodeByClassByName("vtkMRMLSliceNode", "Red")
driver.SetModeForSlice(driver.MODE_TRANSVERSE, redSliceNode)
driver.SetDriverForSlice(outputSequenceProxyNode.GetID(), redSliceNode)
# Reconstruct
volumeReconstructionNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLVolumeReconstructionNode")
volumeReconstructionNode.SetAndObserveInputSequenceBrowserNode(outputSequenceBrowserNode)
volumeReconstructionNode.SetAndObserveInputVolumeNode(outputSequenceProxyNode)
volumeReconstructionNode.SetOutputSpacing(outputSpacingMm, outputSpacingMm, outputSpacingMm)
volumeReconstructionNode.SetFillHoles(True)
slicer.modules.volumereconstruction.logic().ReconstructVolumeFromSequence(volumeReconstructionNode)
reconstructedVolume = volumeReconstructionNode.GetOutputVolumeNode()
reconstructedVolume.SetName(outputSequenceProxyNode.GetName()+"_recon")
roiNode = volumeReconstructionNode.GetInputROINode()
# Cleanup
slicer.mrmlScene.RemoveNode(volumeReconstructionNode)
# Show reconstruction result
roiNode.SetDisplayVisibility(False)
slicer.util.setSliceViewerLayers(background=reconstructedVolume,fit=True)