Skip to content

Commit

Permalink
ENH: Use regular file IO in SimpleITK utils if MRMLIDImageIO unavailable
Browse files Browse the repository at this point in the history
When extensions pip-install SimpleITK and it replaces SimpleITK that is bundled with Slicer, then MRMLIDImageIO ITK IO plugin is no longer available for passing data between SimpleITK and Slicer. As a result, Simple Filters module and most Slicer modules that use SimpleITK no longer work.

This commit makes image passing between SimpleITK and Slicer more robust: if MRMLIDImageIO is not available then images are written to regular files.

see Slicer#7661
  • Loading branch information
lassoan committed Apr 17, 2024
1 parent 98e542e commit 5ad306a
Showing 1 changed file with 60 additions and 23 deletions.
83 changes: 60 additions & 23 deletions Base/Python/sitkUtils.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import slicer

__sitk__MRMLIDImageIO_Registered__ = False
__sitk__MRMLIDImageIO_Missing_Reported__ = False


def PushVolumeToSlicer(sitkimage, targetNode=None, name=None, className="vtkMRMLScalarVolumeNode"):
Expand All @@ -14,15 +14,24 @@ def PushVolumeToSlicer(sitkimage, targetNode=None, name=None, className="vtkMRML
If an existing node is specified as targetNode then this value will not be used.
"""

EnsureRegistration()

# Create new node if needed
if not targetNode:
targetNode = slicer.mrmlScene.AddNewNodeByClass(className, slicer.mrmlScene.GetUniqueNameByString(name))
targetNode.CreateDefaultDisplayNodes()

myNodeFullITKAddress = GetSlicerITKReadWriteAddress(targetNode)
sitk.WriteImage(sitkimage, myNodeFullITKAddress)
useMRMLIDImageIO = IsMRMLIDImageIOAvailable()
if useMRMLIDImageIO:
# Use direct memory transfer
myNodeFullITKAddress = GetSlicerITKReadWriteAddress(targetNode)
sitk.WriteImage(sitkimage, myNodeFullITKAddress)
else:
# Use file transfer (less efficient, but works in all cases).
# Simple VTK/SimpleITK image conversion would not work, as coordinate system needs to be converted between LPS and RAS.
storageNode, tempFileName = _addDefaultStorageNode(targetNode)
sitk.WriteImage(sitkimage, tempFileName)
storageNode.ReadData(targetNode, True)
slicer.mrmlScene.RemoveNode(storageNode)
os.remove(tempFileName)

return targetNode

Expand All @@ -31,9 +40,21 @@ def PullVolumeFromSlicer(nodeObjectOrName):
"""Given a slicer MRML image node or name, return the SimpleITK
image object.
"""
EnsureRegistration()
myNodeFullITKAddress = GetSlicerITKReadWriteAddress(nodeObjectOrName)
sitkimage = sitk.ReadImage(myNodeFullITKAddress)
useMRMLIDImageIO = IsMRMLIDImageIOAvailable()
if useMRMLIDImageIO:
# Use direct memory transfer
myNodeFullITKAddress = GetSlicerITKReadWriteAddress(nodeObjectOrName)
sitkimage = sitk.ReadImage(myNodeFullITKAddress)
else:
# Use file transfer (less efficient, but works in all cases).
# Simple VTK/SimpleITK image conversion would not work, as coordinate system needs to be converted between LPS and RAS.
targetNode = nodeObjectOrName if isinstance(nodeObjectOrName, slicer.vtkMRMLNode) else slicer.util.getNode(nodeObjectOrName)
storageNode, tempFileName = _addDefaultStorageNode(targetNode)
storageNode.WriteData(targetNode)
sitkimage = sitk.ReadImage(tempFileName)
slicer.mrmlScene.RemoveNode(storageNode)
os.remove(tempFileName)

return sitkimage


Expand All @@ -48,19 +69,35 @@ def GetSlicerITKReadWriteAddress(nodeObjectOrName):
return myNodeFullITKAddress


def EnsureRegistration():
"""Make sure MRMLIDImageIO reader is registered."""
def IsMRMLIDImageIOAvailable():
"""Determine if MRMLIDImageIO (fast in-memory ITK image transfer) is available.
If not available then report the error (only once).
"""
if "MRMLIDImageIO" in sitk.ImageFileReader().GetRegisteredImageIOs():
# already registered
return

# Probably this hack is not needed anymore, but it would require some work to verify this,
# so for now just leave this here:
# This is a complete hack, but attempting to read a dummy file with AddArchetypeVolume
# has a side effect of registering the MRMLIDImageIO file reader.
global __sitk__MRMLIDImageIO_Registered__
if __sitk__MRMLIDImageIO_Registered__:
return
vl = slicer.modules.volumes.logic()
volumeNode = vl.AddArchetypeVolume("_DUMMY_DOES_NOT_EXIST__", "invalidRead")
__sitk__MRMLIDImageIO_Registered__ = True
# MRMLIDImageIO is available
return True

global __sitk__MRMLIDImageIO_Missing_Reported__
if not __sitk__MRMLIDImageIO_Missing_Reported__:
import logging
logging.error(
"MRMLIDImageIO is not available, SimpleITK image transfer speed will be slower."
" Probably an extension replaced SimpleITK version that was bundled with Slicer."
f" Current SimpleITK version: {sitk.__version__}")
__sitk__MRMLIDImageIO_Missing_Reported__ = True

return False


def _addDefaultStorageNode(targetNode):
originalStorageNode = targetNode.GetStorageNode()
if originalStorageNode:
storageNode = slicer.mrmlScene.AddNewNodeByClass(originalStorageNode.GetClassName(), "__tmp__" + originalStorageNode.GetName())
else:
storageNode = targetNode.CreateDefaultStorageNode()
storageNode.UnRegister(None)
slicer.mrmlScene.AddNode(storageNode)
import os, uuid
tempFileName = os.path.join(slicer.app.temporaryPath, str(uuid.uuid1())) + ".nrrd"
storageNode.SetFileName(tempFileName)
return storageNode, tempFileName

0 comments on commit 5ad306a

Please sign in to comment.