SimpleITK WriteImage() fails *silently* when writing DICOM -- why? And what to do?

Here’s what’s happening (at the Slicer Python console):

import SimpleITK as sitk
sitk.WriteImage(slices[0], "C:\\Users\\username\\test\\0000.mhd") # works fine
sitk.WriteImage(slices[0], "C:\\Users\\username\\test\\0000.nrrd") # works fine
sitk.WriteImage(slices[0], "C:\\Users\\username\\test\\0000.dcm") # does nothing, gives no error message, just does nothing
# using sitk.ImageFileWriter().Execute() gives the same behavior, incidentally

Overall, this is a very frustrating situation, because I had written a script that outputs DICOM files using SimpleITK (I am converting some MHDs to DICOM for further processing), and since SimpleITK is a part of Slicer, I assumed that I could run it in Slicer. And if I am doing something wrong, then I feel that I at least deserve some kind of notice like: “ERROR: We disabled writing DICOM because [reason]!”.

The script works perfectly on my laptop with a standalone SimpleITK library, but on my work desktop I cannot install standalone SimpleITK (ugh) so I thought I would try to run it inside of Slicer, which I am allowed to install. Now I am afraid I will have to rewrite the script in order to use Slicer’s internal DICOM modules, which will require a few days of reading, probably. And all because of this one function!

I am curious to know:

  • Has anyone else used SimpleITK ImageFileWriter/WriteImage to write DICOM files from Slicer scripts? Is there a way to fix the problem?

  • Is there a good way to export a SimpleITK image object as DICOM from Slicer? Or more generally to convert an .mhd/.raw file pair to a DICOM series (I have the tags stored separately)?

  • Can someone please consider adding an error message to be printed when SimpleITK.WriteImage() fails?

The only script example for exporting an image as DICOM is this, which starts with object types I don’t recognize and uses a bunch of functionality I was hoping to avoid:

volumeNode = getNode("CTChest")
outputFolder = "c:/tmp/dicom-output"

# Create patient and study and put the volume under the study
shNode = slicer.vtkMRMLSubjectHierarchyNode.GetSubjectHierarchyNode(slicer.mrmlScene)
patientItemID = shNode.CreateSubjectItem(shNode.GetSceneItemID(), "test patient")
studyItemID = shNode.CreateStudyItem(patientItemID, "test study")
volumeShItemID = shNode.GetItemByDataNode(volumeNode)
shNode.SetItemParent(volumeShItemID, studyItemID)

import DICOMScalarVolumePlugin
exporter = DICOMScalarVolumePlugin.DICOMScalarVolumePluginClass()
exportables = exporter.examineForExport(volumeShItemID)
for exp in exportables:
  exp.directory = outputFolder

exporter.export(exportables)

Creation of valid DICOM files is very complex and not done very often. You need to provide lots of required metadata to have a chance of generating a valid DICOM file. You need to generate new UIDs for some fields, while cross-referencing existing UIDs in other fields.

Not creating a file at all is a robust way of indicating that something went wrong and SimpleITK may also log errors or set error codes somewhere. But I agree that it would be better if you got a more clear indication of error, such as returning an error code or throw an exception. I would recommend to start a discussion about this on the ITK forum.

Creating DICOM files is very hard. If you can figure out a solution in a few days then you are very lucky.

There should be no difference in how SimpleITK works in Slicer or elsewhere, but if you find that something behaves differently in Slicer then let us know.

An mhd/raw file does not contain enough data that would allow creation of a valid DICOM file.

If you load the DICOM file into Slicer then it stores essential metadata that is used automatically when you export the file, so the process should be fairly straightforward. Note that an mhd image can be stored in DICOM in different ways, for example it can be a CT/MR/PT/US/XA/… image IOD, Segmentation Object IOD, or RT Structure Set IOD.

SimpleITK requires you to provide required metadata in a metadata dictionary in the reader. I’m sure there are many examples on the web.

See above, contact SimpleITK developers.

The code snippet above is fairly simple, considering the underlying complexity of the operation. Creation of patient/study/etc. is not needed if you start from a DICOM study, so it really just takes 6-8 lines of code.

The script works – it has worked, dozens of times, running locally. I have successfully created a DICOM series, from an MHD, using SimpleITK, with the script.

And yes, you’re right: if you don’t set the metadata, SimpleITK complains. Locally. On Slicer it doesn’t. And if you follow the complaints it gives – locally – it will, eventually, work, and generate a DICOM file. It wasn’t that hard – I’ve done it!

So I don’t understand why it’s necessary to be so condescending?

Like, seriously, why? You think I didn’t spend several hours searching the Internet before posting this thread? All to be made fun of by someone who thinks I don’t know what DICOM is?

So: I’m sorry. I suppose the complaint about the error message was too loud. I’ll just leave now, and remember not to ask for help.

I’m sorry I didn’t want to offend you. If you can point out specific comments from my post above that you did not find appropriate then let me know, I would like to learn what to avoid in the future.

From the additional information that you provided it seems that the problem is that some SimpleITK outputs (maybe those that are printed on the stdout/stderr) do not show up in Slicer’s Python console. I’ll have a look if I can reproduce this behavior.

The behavior using SimpleITK in latest Slicer Stable Release and in a recent Slicer Preview Release on Windows look good:

>>> import SampleData
>>> import SimpleITK as sitk
>>> import sitkUtils
>>> inputVolumeNode = SampleData.SampleDataLogic().downloadMRHead()
>>> slices = sitkUtils.PullVolumeFromSlicer(inputVolumeNode)
>>> import SimpleITK as sitk
>>> sitk.WriteImage(slices[0], "C:\\tmp\\0000.mhd") # works fine
>>> sitk.WriteImage(slices[0], "C:\\tmp\\0000.nrrd") # works fine
>>> sitk.WriteImage(slices[0], "C:\\tmp\\0000.dcm") # works fine
>>> sitk.WriteImage(slices[0], "C:\\nonexisting\\0000.dcm") # throws error as expected
Traceback (most recent call last):
  File "<console>", line 1, in <module>
  File "C:\Users\andra\AppData\Local\NA-MIC\Slicer 4.13.0-2021-10-18\lib\Python\lib\site-packages\simpleitk-2.1.1-py3.6-win-amd64.egg\SimpleITK\extra.py", line 394, in WriteImage
    return writer.Execute(image)
  File "C:\Users\andra\AppData\Local\NA-MIC\Slicer 4.13.0-2021-10-18\lib\Python\lib\site-packages\simpleitk-2.1.1-py3.6-win-amd64.egg\SimpleITK\SimpleITK.py", line 7503, in Execute
    return _SimpleITK.ImageFileWriter_Execute(self, *args)
RuntimeError: Exception thrown in SimpleITK ImageFileWriter_Execute: D:\D\P\Slicer-0-build\ITK\Modules\IO\ImageBase\src\itkImageIOBase.cxx:698:
ITK ERROR: GDCMImageIO(000001FA949D5470): Could not open file: C:\nonexisting\0000.dcm for writing.
Reason: No such file or directory
>>> 
>>> 

If you can provide steps that reproduce the silent DICOM writing failure then I can investigate further. I can imagine that some low-level component, such as GDCM does not propagate error/warning messages to higher levels just logs to the console, which may show up in the Slicer application log, but may not printed to the Python console.

1 Like

Hi @masonbogue, can you provide information about what version of SimpleITK you were using when it was working for you on your laptop? Slicer builds SimpleITK from source so the version in Slicer may be different from the version that you were using if you had installed into a system python on your machine. This appears to be the biggest variable between when it was working for you and now when it is not working for you. Also please provide us with information about which version of Slicer you were using.

Hi,

After spending some more time working with this code, I can understand why you might have adopted a somewhat more whimsical tone than I was expecting. Being told over and over “writing DICOM files is very hard” sounded… I don’t know… like you thought I wasn’t trying. I was too short-tempered myself.

It turns out that the example needs very little modification. The current script is now short enough to post – the old one had dozens of lines setting various DICOM attributes – and it looks like this:


import os
import DICOMScalarVolumePlugin
import slicer, slicer.util

def convert_file(infile, outdir):
      fileName = os.path.basename(infile).split(".")[0]
      ptName = fileName[0:7]


      if not slicer.util.loadVolume(infile):
            print("ERROR: Cannot load MHD " + infile)
      volumeNode = slicer.util.getNode(fileName)

      # Create patient and study and put the volume under the study
      shNode = slicer.vtkMRMLSubjectHierarchyNode.GetSubjectHierarchyNode(slicer.mrmlScene)
      patientItemID = shNode.CreateSubjectItem(shNode.GetSceneItemID(), ptName)
      studyItemID = shNode.CreateStudyItem(patientItemID, fileName)
      volumeShItemID = shNode.GetItemByDataNode(volumeNode)
      shNode.SetItemParent(volumeShItemID, studyItemID)
      shNode.SetItemAttribute(studyItemID, "DICOM.Modality", "CT")   # *this* didn't work
      shNode.SetItemAttribute(studyItemID, "DICOM.RescaleType", "HU") # neither did *this*

      import DICOMScalarVolumePlugin
      exporter = DICOMScalarVolumePlugin.DICOMScalarVolumePluginClass()
      exportables = exporter.examineForExport(volumeShItemID)
      for exp in exportables:
            exp.directory = outdir

      exporter.export(exportables)

So, now I can produce DICOM files! I was surprised at first by all of the long variable names, but in order to do simple things I don’t need to figure them all out.

Unfortunately, I seem to have reproduced this bug (cf. the commented lines above):

I know this is probably frustrating for Andreas, because he had put in a fix (thank you!) for it at lines 176-9 of this file:

Unfortunately, the fix doesn’t seem to apply for me, because when I try to import the file into the TPS, it complains about an invalid rescaleType, which is probably being set to “US” by GDCM.

So I think my new question is: in what Slicer version is this fix found? I am on Slicer 4.10.2. I may be able to convince IT to upgrade us. Is there any other way to set the rescaleType when exporting an image as DICOM? (Previously I did this by image_slice.SetMetaData("0028|1054", "HU") with a SimpleITK object)

Thanks for your help,
Mason

When you go to the commit ENH: Improve CreateDICOMSeries module · Slicer/Slicer@cf00276 · GitHub on GitHub, at the bottom (see image below) it will show you which tag/version it was first included in. So it was first included in Slicer stable release 4.11.20210226 which is the current Slicer stable that you can download from https://download.slicer.org/.

image

I would highly suggest you move away from Slicer 4.10 even outside of this bug because there have been lots of improvements to Slicer since Fall 2018 when it was originally released. Slicer 4.10 is using Python 2 for one thing which has gone End-Of-Life since Jan 1st, 2020. SimpleITK 1.1 was being used in Slicer 4.10 and a new version can’t be installed because latest SimpleITK has dropped Python 2 support. Latest Slicer Preview uses SimpleITK 2.1.1 which is the latest stable release.

I will also note that Slicer 4.10 issues are not investigated by developers and here on the forum you’re going to be encouraged to use latest Slicer Stable or latest Slicer Preview. There is usually limited resources so generally there is only time available to improve the current versions which will help more people in the community rather than trying to debug issues in old outdated versions. Always here to help when using latest software!

2 Likes

I’ve tried to run my short test script above in Slicer-4.10.2 and the last line failed silently. The same line failed with a loud error exception and printed a meaningful error message on latest Slicer Stable Release and recent Slicer Preview Release. Probably error handling of the DICOM writer was implemented in SimpleITK after Slicer-4.10.2 was released. So, the missing error report issue will be fixed if you update Slicer.

The current Slicer Stable Release does not need an administrator account to install: you can run the installer as a normal user and Slicer is set up in your user’s folder by default. Some hospitals manage to prevent running even user-mode installers, in that case you can install Slicer on any computer and copy all the files to a USB stick and run it from there (or you can copy the files to your computer and run from there), because Slicer is also fully portable now. If even USB drive access is disabled, then you probably need to ask an IT admin to install Slicer for you.

1 Like