Skip to content

Instantly share code, notes, and snippets.

@mikebind
Last active April 8, 2022 22:24
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mikebind/b3e01fdc57d7d3d0cef72cb43cb59a75 to your computer and use it in GitHub Desktop.
Save mikebind/b3e01fdc57d7d3d0cef72cb43cb59a75 to your computer and use it in GitHub Desktop.
Excerpt of 3D Slicer module logic code to essentially export the "Red" slice view image to a single slice DICOM file
class SomeModuleLogic(ScriptedLoadableModuleLogic):
def exportRedSliceAsDicom(self, patientNameSH, xRangeMm=150, yRangeMm=150, zRangeMm=0.5, outputFolderPath=None, dicomTagDict=None):
""" This function creates an ROI centered on the center of the Red slice view, and
with axes aligned with that slice view's slice, and with thickness specified by zRangeMm.
This ROI is then used to crop the red slice's current background image volume to slices
in this orientation (I choose the zRange such that this results in a single slice by
setting it to close to the minimum voxel side length), and export the result to DICOM.
In order to export to DICOM, a patient and study need to be created in the subject hierarchy,
these are done the subject name given in patientNameSH and with study name "SingleSliceExport".
The DICOM file(s) are exported to the supplied outputFolderPath. DICOM tags in the export
can be suppled via a dictionary with tag names as keys and tag values as values (this has not
been extensively tested, but should probably work and would be a way to force things like UIDs
to desired vaues). If no tag dictionary is supplied, then by default the DICOM tags "PatientName"
and "StudyDescription" are set to patientNameSH just as a simple and minimal way to keep track.
"""
redSliceNode = slicer.mrmlScene.GetNodeByID('vtkMRMLSliceNodeRed')
sliceTransformMatrix = redSliceNode.GetSliceToRAS()
roi = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLMarkupsROINode', 'SliceExportROI')
roiTransformMatrix = vtk.vtkMatrix4x4()
roiTransformMatrix.DeepCopy(sliceTransformMatrix)
# The DICOM viewer I was using ended up showing most of the slices I was extracting as
# flipped LR compared to the view in Slicer (though some were flipped on both axes)
# There are lots of reasons this could happen, and this is not proof of a bug in either
# Slicer or the viewer (the angle from which an image is viewed is not fully
# prescribed in the DICOM header, only it's location in space). I just wanted a quick
# hack fix which resulted in images which ended up oriented the same. Through trial
# and error, I found that inverting the second axis of the ROI did this for my image
# orientation. Your mileage may vary. IMPORTNANT NOTE: Inverting a single axis is NOT appropriate
# if you are exporting more than one slice!! Changing a single axis direction in 3D space
# is equivalent to a mirror reflection, and the "handedness" of any non-mirror symmetric
# 3D structure will be inverted compared to reality. It's OK for a single slice because
# there can't be any "handedness" to a 2D structure (a reflection is equivalent to looking at
# the same plane from the other side).
for row in range(4):
for col in [1]: # invert second axis
roiTransformMatrix.SetElement(row, col, -1 * roiTransformMatrix.GetElement(row,col))
# Position and orient ROI
roi.SetAndObserveObjectToNodeMatrix(roiTransformMatrix)
# Size ROI
roi.SetSize([xRangeMm, yRangeMm, zRangeMm])
# Get Volume Node to crop (background volume of red slice)
sliceLogic = slicer.app.applicationLogic().GetSliceLogic(redSliceNode)
compositeNode = sliceLogic.GetSliceCompositeNode()
volNodeID = compositeNode.GetBackgroundVolumeID()
volNode = slicer.mrmlScene.GetNodeByID(volNodeID)
# Crop using ROI
croppedNode = self.cropVolumeUsingROI(volNode, roi, interpolation_mode='linear', isotropic=True)
# Export to DICOM
patientItemID, studyItemID = self.createSubjectHierarchyPatientAndStudy(patientNameSH,'SingleSliceExport')
self.putImageVolumeUnderStudy(croppedNode, studyItemID)
if dicomTagDict is None:
dicomTagDict = {"PatientName": patientNameSH, "StudyDescription": patientNameSH}
# "StudyID" seems to be automatically overwritten to "SLICER10001" for some reason
self.exportImageVolumeToDICOM(croppedNode, os.path.join(outputFolderPath, patientNameSH), tagDictionary=dicomTagDict)
# Clean up
slicer.mrmlScene.RemoveNode(roi)
return
def exportImageVolumeToDICOM(self, volNode, outputDirectory=None, tagDictionary={"PatientID":"anon", "StudyID":"testStudy"}):
""" Export a supplied scalar volume node to DICOM, setting DICOM tags using a dictionary,
and saving to a supplied outputDirectory.
"""
if outputDirectory is None:
# Ask user where to output (TODO)
slicer.util.errorDisplay('No output directory specified')
return
import DICOMScalarVolumePlugin
exporter = DICOMScalarVolumePlugin.DICOMScalarVolumePluginClass()
# Get image volume's subject hierarchy ID
shNode = slicer.vtkMRMLSubjectHierarchyNode.GetSubjectHierarchyNode(slicer.mrmlScene)
volumeShItemID = shNode.GetItemByDataNode(volNode)
# Break the volume into individual exportable slices (?)
exportables = exporter.examineForExport(volumeShItemID)
# For each slice, set the needed tags and the output directory
for exp in exportables:
# Set output folder
exp.directory = outputDirectory
for tagName, tagValue in tagDictionary.items():
exp.setTag(tagName, tagValue)
# Do the export
exporter.export(exportables)
def putImageVolumeUnderStudy(self, volNode, studySubjectHierarchyID):
""" Place an image volume under a dicom study in the slicer subject hierarchy
"""
shNode = slicer.vtkMRMLSubjectHierarchyNode.GetSubjectHierarchyNode(slicer.mrmlScene)
volumeSubjectHierarchyID = shNode.GetItemByDataNode(volNode)
shNode.SetItemParent(volumeSubjectHierarchyID, studySubjectHierarchyID)
def createSubjectHierarchyPatientAndStudy(self, subjectNameInSH, studyNameInSH):
"""
"""
shNode = slicer.vtkMRMLSubjectHierarchyNode.GetSubjectHierarchyNode(slicer.mrmlScene)
# set IDs. Note: these IDs are not specifying DICOM tags, but only the names that appear in the hierarchy tree
patientItemID = shNode.CreateSubjectItem(shNode.GetSceneItemID(), subjectNameInSH)
studyItemID = shNode.CreateStudyItem(patientItemID, studyNameInSH)
return patientItemID, studyItemID
def cropVolumeUsingROI(self, input_volume_node, roi_node, output_volume_node=None,
interpolation_mode='linear', fill_value=0, isotropic=True):
""" Run Crop Volume using input ROI
"""
cropVolumeNode = slicer.vtkMRMLCropVolumeParametersNode()
cropVolumeNode.SetScene(slicer.mrmlScene)
cropVolumeNode.SetName("MyCropVolumeParametersNode")
cropVolumeNode.SetIsotropicResampling(isotropic)
if interpolation_mode=='linear':
interp_mode = cropVolumeNode.InterpolationLinear
elif interpolation_mode=='nn':
interp_mode = cropVolumeNode.InterpolationNearestNeighbor
cropVolumeNode.SetInterpolationMode(interp_mode)
cropVolumeNode.SetFillValue(fill_value)
cropVolumeNode.SetROINodeID(roi_node.GetID()) # roi
slicer.mrmlScene.AddNode(cropVolumeNode)
if output_volume_node is None:
output_volume_node = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLScalarVolumeNode', input_volume_node.GetName() + '_roiCropped')
cropVolumeNode.SetInputVolumeNodeID(input_volume_node.GetID()) # input
cropVolumeNode.SetOutputVolumeNodeID(output_volume_node.GetID()) # output
slicer.modules.cropvolume.logic().Apply(cropVolumeNode) # do the crop
slicer.mrmlScene.RemoveNode(cropVolumeNode)
return output_volume_node
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment