I have a CT volume that I have navigated and manipulated so that the Red slice view shows a slice plane that I would like to export to a single DICOM image (so that collaborators can make measurements with their non-Slicer software to compare with measurements made within Slicer). I know that I can capture a PNG of what is shown in the Red slice using Screen Capture, but I need an output which retains the pixel spacing information (resampling to whatever resolution the slice view is using is fine, I just want area measurements to be in meaningful physical units). Any suggestions about how to accomplish this?
I see this in the script repository: Script repository — 3D Slicer documentation
So, I think that I can perhaps use Screen Capture to get the image as PNG, then read the PNG into a numpy array, create a scalar volume with the image as that numpy array and a pixel resolution based on data extracted from the slice view. Then apply a parent patient and study in the subject hierarchy and export to DICOM.
Does this approach sound like it would work? The part I am least sure of accomplishing is getting the pixel size of the screen capture PNG from the slice view. Is this possible? How?
Thanks for any help you can provide.
Maybe the easiest would be to convert the slice plane orientation to a transform and then resample the volume through the transform and export the result as dicom. You’d also need to figure out which dicom slice corresponds to the slice view, but you can do that by iterating through the positions and orientations in the exported dicoms. You may need to adjust the translation of the the resampling transform so the slice of interest is in the exact same plane as the red slice view so your measurement won’t be biased.
Thanks @pieper, I’ll give this a try.
Crop volume can extract a single slice of the volume at arbitrary orientation, with correct spacing. You can enable ROI rotation and set orientation and position of the ROI manually; or initialize the ROI automatically from the SliceToRAS transform of the slice node.
Thanks @lassoan, this is exactly what I have ended up doing. As I started to work through @pieper’s suggestion, an ROI/CropVolumes based solution occurred to me also, and some quick testing confirmed that it would work even for a single slice.
The only issue I have run into is that the orientation of the exported DICOM image seems wrong in the external DICOM viewer I have been using to test (MicroDICOM). In one example the exported image was displayed with both the x and y axes inverted relative to the display in Slicer, while in another only the x axis was inverted relative to the display in Slicer. My first thought was that this was due to the axis orientation of the ROI, but inverting one or more columns of the ROI’s
ObjectToNodeMatrix didn’t seem to have any effect. For the very limited task for which I am generating these images, making specific area measurements, reflections and rotations are not an issue, so I’m not sure how much I am going to try to chase the cause of these inversions down.
Most dicom viewers work in image space (ignoring ImageOrientationPatient and ImagePositionPatient), so you probably want to pick the origin of your resampling to be close to a cardinal plane, usually axial, and the direction to match the layout of pixels in an image.
Thanks for the insight, Steve. Can I control the origin of the resampling by controlling the axis directions of the ROI used for cropping? That’s sort of what I was imagining I was doing when I inverted the ROI axis directions to try to invert the displayed DICOM image, but I didn’t see the effect I expected in the DICOM viewer. However, I may also have not thought things through sufficiently; for example maybe I should have tried switching the x and y directions rather than negating each individually. Or, are you saying that all that orientation information ends up in the DICOM header orientation information where the viewer mostly ignores it and just displays the 2D image based on the voxel order in memory? If that’s the case, then how do I control the voxel order in the exported file to achieve a desired 2D orientation?
It’s an interesting question Mike. We may have to go back to look at the CropVolume implementation to see if we can easily control it there. Internally it uses an ITK-based CLI that has extra parameters.
Or, since you are getting into the guts here a bit, you might just try to use
vtkImageReslice directly with basically the transform from the slice view rendering path.
Now that I think. bit, maybe the easiest is to just take the image from the slice view, e.g. with ScreenCapture, and figure out the pixel spacing in mm to put in the dicom header and leave the orientation as identity, which will make the dicom viewer show it the same raster grid as Slicer displays. You can get the screen space pixel spacing from the view settings by looking at how the ruler code does it.
Crop volume and DICOM export should preserve the correct image orientation (if you suspect that this is not the case then let us know). But it is then completely up to each DICOM viewer how they choose the default orientation. The orientation may be simply based on the order of pixel storage (e.g., first byte on disk is always displayed in top-left corner by default), or the viewer may use a hanging protocol that prescribes approximate anatomical directions for the displayed slice.
You can adjust the voxel storage order of a volume (without impacting physical location of the image) using
Orient Scalar Volume module. This should take care of the image orientation if the viewer uses pixel order to orient the image.
Note that for 2D DICOM image viewers image flip and rotate functions are essential, they are always available. Therefore, if you don’t like the default orientation you can always change it by a few clicks in the 2D viewer.
Thanks for your help, @lassoan and @pieper. Here is a gist with the (not polished but perhaps helpful to someone else) code I used. Excerpt of 3D Slicer module logic code to essentially export the "Red" slice view image to a single slice DICOM file · GitHub
Thanks for sharing. A small comment: inverting a single axis of a transformation matrix turns the 3D space inside out. In this specific case this did not end up causing a problem because you exported a single slice, but it would be better to invert another axis of the transform because in the future others may use your code snippet for extracting a multiple slices.
Thanks for that point. I added this clarifying note to the gist:
"IMPORTANT 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). "
This comment is too long and complicated and so most people will not understand what you mean. Instead, you could change the loop to:
for row in range(4): for col in [0, 1]: # invert second axis roiTransformMatrix.SetElement(row, col, -1 * roiTransformMatrix.GetElement(row,col))
(If this leads to undesired flip then use
But even better, if you want simpler, safer code, then you can apply a rotation to the sliceToRas transform (you can replace
RotateZ if you want to rotate along a different axis):
roiTransform = vtk.vtkTransform() roiTransform.Concatenate(sliceTransformMatrix) roiTransform.RotateX(180) roi.SetAndObserveObjectToNodeMatrix(roiTransform.GetMatrix())
As a general rule: in medical imaging the “F word” (flip) is taboo. Always rotate instead, except the very special cases when you actually need to mirror one side of the patient to the other (e.g., correct deformity by mirroring healthy side of the patient to the diseased side).