Is there a Python DICOM Modality Worklist setup library?

1.7k Views Asked by At

I've been down a couple of rabbit holes trying to find suitable ways of creating a DICOM modality worklist (or .wl worklist files rather).

What I have setup so far:

  • I have an Orthanc DICOM server running in a local docker container.
  • I can create DICOM text dump files with a small Python program. See the example of how this looks below.
  • I can convert the above-mentioned text dump files to .wl worklist files by using the dump2dcm command.
  • I can move the created .wl files to a folder that is shared with the docker.
  • Orthanc can "see" these files and serve them correctly to medical machines on the local network.
  • I have the coffee machine on a timer. This allows for a consistent caffeine fix.

My problem is with the creation of the DICOM text dump files. I'm currently using Python's String.format() function to format a template string. This then substitutes certain placeholders in my template string with actual patient data. Although not elegant, it works. But it's a very static solution and may not be very robust.

Is there a Python library that can be used to generate such text dump files? or even beter, the .wl files? I am willing to trade 3 magic beans, and our family recipe for potato salad, for such a library. (The secret ingredient is not paprika)

For completeness, here is how the template dicom worklist string looks:

dicom_wl_template_string = """
    # Dicom-File-Format
    # Dicom-Meta-Information-Header
    # Used TransferSyntax: Unknown Transfer Syntax

    # Dicom-Data-Set
    # Used TransferSyntax: Little Endian Implicit
    (0002,0000) UL [123]
    (0002,0002) UI [1.2.111.222222.5.1.4.1.1.104.1]
    (0002,0003) UI [1.2.3.4.5.6.132437.17.4.10123450.312346792082.12349.1]
    (0002,0010) UI [1.2.840.10008.1.2.1]
    (0002,0012) UI [1.2.276.0.7230010.3.0.3.6.6]
    (0002,0013) SH [OFFIS_DCMTK_366]

    (0008,0005) CS [{SpecificCharacterSet}]                     #  10, 1 SpecificCharacterSet e.g ISO_IR 100
    (0008,0012) DA [{InstanceCreationDate}]                     #   8, 1 InstanceCreationDate e.g 20220101
    (0008,0050) SH [{AccessionNumber}]                          #   8, 1 AccessionNumber e.g 1234
    (0010,0010) PN [{PatientName}]                              #  14, 1 PatientName e.g SURNAME^ABC
    (0010,0020) LO [{PatientID}]                                #  14, 1 PatientID e.g 7001011234080
    (0010,0030) DA [{PatientBirthDate}]                         #   8, 1 PatientBirthDate e.g. 19700101
    (0010,0040) CS [{PatientSex}]                               #   2, 1 PatientSex e.g. M
    (0020,000d) UI [{StudyInstanceUID}]                         #  54, 1 StudyInstanceUID e.g 1.2.3.4.5.6.132437.17.4.10123450.312346792082.12349.1
    (0032,1060) LO [{RequestedProcedureDescription}]            #  16, 1 RequestedProcedureDescription 
        (0040,0100) SQ (Sequence with explicit length #=1)          #  90, 1 ScheduledProcedureStepSequence
            (fffe,e000) na (Item with ??explicit length #=5??)          #  82, 1 Item
                (0008,0060) CS [{Modality}]                                 #   4, 1 Modality e.g. CT, MR, CR, NM, PT, US, XA 
                (0040,0001) AE [{ScheduledStationAETitle}]                  #   4, 1 ScheduledStationAETitle e.g Foo
                (0040,0002) DA [{ScheduledProcedureStepStartDate}]          #   8, 1 ScheduledProcedureStepStartDate e.g. 20220101
                (0040,0003) TM [{ScheduledProcedureStepStartTime}]          #   8, 1 ScheduledProcedureStepStartTime e.g. 080000
                (0040,0006) PN [{ScheduledPhysicianName}]                   #   8, 1 Scheduled Performing Physicians Name e.g. EMMETBROWN
                (0040,0007) LO [{ScheduledProcedureStepDescription}]        #  22, 1 ScheduledProcedureStepDescription e.g SOMETHING
                (0040,0009) SH [{ScheduledProcedureStepID}]                 #   4, 1 ScheduledProcedureStepID e.g 0001
            (fffe,e00d) na (ItemDelimitationItem for re-encoding)       #   0, 0 ItemDelimitationItem
        (fffe,e0dd) na (SequenceDelimitationItem for re-encod.)     #   0, 0 SequenceDelimitationItem
    (0040,1001) SH [unknown]                                    #   8, 1 RequestedProcedureID
    """
2

There are 2 best solutions below

0
On BEST ANSWER

Thank you for the response. Here is also some example code for future visitors.

import os
from os import path
from pydicom.dataset import Dataset, FileMetaDataset
from pydicom.uid import ExplicitVRLittleEndian
        
wl_file_name = "directory/file.wl"
txt_file_name = "directory/file.txt"

# Create data set
ds = Dataset()
# Add file meta information elements
ds.file_meta = FileMetaDataset()
ds.file_meta.TransferSyntaxUID = ExplicitVRLittleEndian
ds.file_meta.MediaStorageSOPClassUID = "0"
ds.file_meta.MediaStorageSOPInstanceUID = "0"
# Fill out the worklist query elements
ds.SpecificCharacterSet             = "ISO_IR 6"
ds.InstanceCreationDate             = "20220101"
ds.AccessionNumber                  = "12345-abc"
ds.PatientName                      = "SURNAME^NAME"
ds.PatientID                        = "123456"
ds.PatientBirthDate                 = "19700101"
ds.PatientSex                       = "M"
ds.StudyInstanceUID                 = "1a-2b-3c" 
ds.RequestedProcedureDescription    = "ProcedureDescription"
ds.ScheduledProcedureStepSequence   = [Dataset()]
ds.ScheduledProcedureStepSequence[0].Modality                           = "OT"
ds.ScheduledProcedureStepSequence[0].ScheduledStationAETitle            = "OT"
ds.ScheduledProcedureStepSequence[0].ScheduledProcedureStepStartDate    = "20220101"
ds.ScheduledProcedureStepSequence[0].ScheduledProcedureStepStartTime    = "080000"
ds.ScheduledProcedureStepSequence[0].ScheduledPerformingPhysicianName   = "Doctor Emmet Brown"
ds.ScheduledProcedureStepSequence[0].ScheduledProcedureStepDescription  = "SchedProcStepDesc"
ds.ScheduledProcedureStepID         = "0001"
# more stuff if you need

# Save directly as a .wl file.
# Set write_like_original=False to be certain you’re writing the dataset in the DICOM File Format 
ds.save_as(wl_file_name, write_like_original=False)

# Additionally, you can also make a readable txt file for humans
# Check if txt file already exists
if(path.exists(txt_file_name)): #if txt file exists, remove it first
    try:
        os.remove(txt_file_name)
    except OSError as e:
        print("Error: %s : %s" % (txt_file_name, e.strerror))
# Run dcmdump command to convert wl file to txt
convert_wl_to_txt_cmd = "dcmdump " + wl_file_name + " > "  + txt_file_name 
os.system(convert_wl_to_txt_cmd)

The txt version then looks like this:

# Dicom-File-Format

# Dicom-Meta-Information-Header
# Used TransferSyntax: Little Endian Explicit
(0002,0000) UL 120                                      #   4, 1 FileMetaInformationGroupLength
(0002,0001) OB 00\01                                    #   2, 1 FileMetaInformationVersion
(0002,0002) UI [0]                                      #   2, 1 MediaStorageSOPClassUID
(0002,0003) UI [0]                                      #   2, 1 MediaStorageSOPInstanceUID
(0002,0010) UI =LittleEndianExplicit                    #  20, 1 TransferSyntaxUID
(0002,0012) UI [1.2.826.0.1.3680043.8.498.1]            #  28, 1 ImplementationClassUID
(0002,0013) SH [PYDICOM 2.2.2]                          #  14, 1 ImplementationVersionName

# Dicom-Data-Set
# Used TransferSyntax: Little Endian Explicit
(0008,0005) CS [ISO_IR 6]                               #   8, 1 SpecificCharacterSet
(0008,0012) DA [20220101]                               #   8, 1 InstanceCreationDate
(0008,0050) SH [12345-abc]                              #  10, 1 AccessionNumber
(0010,0010) PN [SURNAME^NAME]                           #  12, 1 PatientName
(0010,0020) LO [123456]                                 #   6, 1 PatientID
(0010,0030) DA [19700101]                               #   8, 1 PatientBirthDate
(0010,0040) CS [M]                                      #   2, 1 PatientSex
(0020,000d) UI [1a-2b-3c]                               #   8, 1 StudyInstanceUID
(0032,1060) LO [ProcedureDescription]                   #  20, 1 RequestedProcedureDescription
(0040,0009) SH [0001]                                   #   4, 1 ScheduledProcedureStepID
(0040,0100) SQ (Sequence with explicit length #=1)      # 110, 1 ScheduledProcedureStepSequence
  (fffe,e000) na (Item with explicit length #=6)          # 102, 1 Item
    (0008,0060) CS [OT]                                     #   2, 1 Modality
    (0040,0001) AE [OT]                                     #   2, 1 ScheduledStationAETitle
    (0040,0002) DA [20220101]                               #   8, 1 ScheduledProcedureStepStartDate
    (0040,0003) TM [080000]                                 #   6, 1 ScheduledProcedureStepStartTime
    (0040,0006) PN [Doctor Emmet Brown]                     #  18, 1 ScheduledPerformingPhysicianName
    (0040,0007) LO [SchedProcStepDesc]                      #  18, 1 ScheduledProcedureStepDescription
  (fffe,e00d) na (ItemDelimitationItem for re-encoding)   #   0, 0 ItemDelimitationItem
(fffe,e0dd) na (SequenceDelimitationItem for re-encod.) #   0, 0 SequenceDelimitationItem
0
On

pydicom should be able to do this and probably allow you to skip the text dump step (disclaimer - I'm a contributor to pydicom):

from pydicom.dataset import Dataset, FileMetaDataset
from pydicom.uid import ExplicitVRLittleEndian

ds = Dataset()

# Add file meta information elements
ds.file_meta = FileMetaDataset()
ds.file_meta.TransferSyntaxUID = ExplicitVRLittleEndian

# Fill out the worklist query elements
ds.SpecificCharacterSet = "ISO_IR 6"
ds.ScheduledProcedureStepSequence = [Dataset()]
ds.ScheduledProcedureStepSequence[0].Modality = "CT"
# etc...

ds.save_as("query.wl", write_like_original=False)

If you decide to go this way, the dataset introduction tutorial is a good place to start.