Wednesday, June 28, 2023

Converting Bitmap, JPEG and PDF to DICOM

Before we move any further, the examples in this post are included in HRZ's MODALIZER-SDK DICOM C# Examples package. Non programmers, can achieve the same results and much more using MODALIZER+ DICOM Wizard, HRZ's administrative PACS workstation. Let's start.

Imagine you're a dermatologist taking pictures of patients' skin for treatment tracking using a digital camera. The pictures are JPEG's and have no patient info in them. If you could convert them to DICOM and send them to the clinic PACS, that would be a great advancement.

Converting JPEG to DICOM

Here's how to convert a JPEG image to DICOM:
DCXOBJ o = new DCXOBJ();
o.SetJpegFrames("pic1.jpg"); // pic1.jpg is a jeg file
If we now use the DCXOBJ.Dump command we will see that the object contains all the required elements to render the picture as DICOM. 

The examples here are in C# but you can easily convert them to C++.
Here's the equivalent C++ code:

IDCXOBJPtr o(__uuidof(DCXOBJ));
o->SetJpegFrames("pic1.jpg");
Here's how to dump it to text file:
o.Dump("dump1.txt");

Or if you prefer DICOM PowerShell :

$o=new-object -com rzdcx.DCXOBJ();
$o.SetJpegFrames("pic1.jpg");


Dicom-Data-Set
# Used TransferSyntax: UnknownTransferSyntax
(0008,0016) UI =SecondaryCaptureImageStorage            #  26, 1 SOPClassUID
(0028,0002) US 3                                        #   2, 1 SamplesPerPixel
(0028,0004) CS [YBR_FULL_422]                           #  12, 1 PhotometricInterpretation
(0028,0006) US 0                                        #   2, 1 PlanarConfiguration
(0028,0010) US 145                                      #   2, 1 Rows
(0028,0011) US 144                                      #   2, 1 Columns
(0028,0100) US 8                                        #   2, 1 BitsAllocated
(0028,0101) US 8                                        #   2, 1 BitsStored
(0028,0102) US 7                                        #   2, 1 HighBit
(0028,0103) US 0                                        #   2, 1 PixelRepresentation
(0028,2110) CS [01]                                     #   2, 1 LossyImageCompression
(0028,2114) CS [ISO_10918_1]                            #  12, 1 LossyImageCompressionMethod
(7fe0,0010) OB (PixelSequence #=2)                      # u/l, 1 PixelData
  (fffe,e000) pi (no value available)                     #   0, 1 Item
  (fffe,e000) pi ff\d8\ff\db\00\43\00\08\... # 3780, 1 Item
(fffe,e0dd) na (SequenceDelimitationItem)               #   0, 0 SequenceDelimitationItem


By default, the SOP CLASS used for the created object is Secondary Capture Image Storage. This SOP CLASS is intended for screen captures and may be the most appropriate. Another option might be Visible Light Image Storage. After setting the pixel data you can manipulate any attribute using the DCXELM class and add data like patient name and patient ID.

Also note that the pixel data of the Jpeg frame is encoded within a pixel data sequence item that holds the jpeg stream. The sequence has two items. The first item in this sequence is called offset table that is here empty. The second item is the jpeg stream of the frame. If we had multi-frame image there would have been more items, one for every frame as we'll see later on.


Converting Bitmap to DICOM


Converting bitmaps is just as easy using the SetBMPFrames method:
DCXOBJ obj = new DCXOBJ();obj.SetBMPFrames(@"RZ-logo-new.bmp");obj.saveFile(@"RZ-logo-new.bmp.dcm");
And we dump this one as well just the same:
obj.Dump("dump1.txt");
And this is what we get in the dump:


# Dicom-Data-Set
# Used TransferSyntax: UnknownTransferSyntax
(0008,0016) UI =SecondaryCaptureImageStorage            #  26, 1 SOPClassUID
(0028,0002) US 3                                        #   2, 1 SamplesPerPixel
(0028,0004) CS [RGB]                                    #   4, 1 PhotometricInterpretation
(0028,0006) US 0                                        #   2, 1 PlanarConfiguration
(0028,0010) US 300                                      #   2, 1 Rows
(0028,0011) US 300                                      #   2, 1 Columns
(0028,0100) US 8                                        #   2, 1 BitsAllocated
(0028,0101) US 8                                        #   2, 1 BitsStored
(0028,0102) US 7                                        #   2, 1 HighBit
(0028,0103) US 0                                        #   2, 1 PixelRepresentation
(7fe0,0010) OB f7\fc\ff\ef\f4\fa\fa\ff\ff\f3\f8\fe\f7\fc\ff\ec\f1\f7\f9\ff\ff\f4... # 270000, 1 PixelData

Here the pixel data is not compressed and not encapsulated within a sequence. The data in the pixel data element (7fe0,0010) is the RGB values from the bitmap image.


To create a valid DICOM Secondary Capture file we have to add some attributes such as patient name and patient ID for example. The full source code with the list of required attributes can be found in the DICOMIZER source code zip file in this link: JPEG to DICOM Source Code.


Creating Multi-frame DICOM Images


Sometimes you may be requirested to create a Multi-Frame DICOM Image. Multi-Frames images are saved in one DICOM file but contain multiple frames, for example a time sequence or a cine.

Creating Multi-Frame DICOM images is just as simple. Instead of passing a single filename, just pass the filenames as a string separated by ; like this:
DCXOBJ obj = new DCXOBJ();obj.SetBMPFrames(@"Frame-1.bmp;Frame-2.bmp;Frame-3.bmp");
The same works for SetJpegFrames. When creating multi-frame images using these methods all the frames passed must be of the same type and of the same size. Do not mix bitmaps with jpeg's or even jpeg's that were created from different camera's as they may have different compression schema's and different color spaces causing them not to fit into a single DICOM multi-frame object. DICOM Multi-frame images must have all the frames with exactly the same attributes of rows, columns, color schema and compression type.

Here's a dump of a multi-frame DICOM image with two JPEG frames created using SetJpegFrames:

# Dicom-Data-Set
# Used TransferSyntax: JPEG Baseline
(0008,0016) UI =SecondaryCaptureImageStorage            #  26, 1 SOPClassUID
(0028,0002) US 3                                        #   2, 1 SamplesPerPixel
(0028,0004) CS [YBR_FULL_422]                           #  12, 1 PhotometricInterpretation
(0028,0006) US 0                                        #   2, 1 PlanarConfiguration
(0028,0008) IS [2]                                      #   2, 1 NumberOfFrames
(0028,0010) US 145                                      #   2, 1 Rows
(0028,0011) US 144                                      #   2, 1 Columns
(0028,0100) US 8                                        #   2, 1 BitsAllocated
(0028,0101) US 8                                        #   2, 1 BitsStored
(0028,0102) US 7                                        #   2, 1 HighBit
(0028,0103) US 0                                        #   2, 1 PixelRepresentation
(0028,2110) CS [01]                                     #   2, 1 LossyImageCompression
(0028,2114) CS [ISO_10918_1]                            #  12, 1 LossyImageCompressionMethod
(7fe0,0010) OB (PixelSequence #=3)                      # u/l, 1 PixelData
  (fffe,e000) pi (no value available)                     #   0, 1 Item
  (fffe,e000) pi ff\d8\ff\db\00\43\00\08\06\06\07\06\05\08\07\07\07\09\09\08\0a\0c... # 3780, 1 Item
  (fffe,e000) pi ff\d8\ff\db\00\43\00\08\06\06\07\06\05\08\07\07\07\09\09\08\0a\0c... # 3780, 1 Item
(fffe,e0dd) na (SequenceDelimitationItem)               #   0, 0 SequenceDelimitationItem



Here we have a sequence with 3 items, the first being the offset table that is not in use and is empty and the second and third are the jpeg streams of the first and the second frames resp.
Also note the tag (0028,0008) Number of frames that is now present.
For multi-frame images you may want to change the SOP class to Multi-Frame Secondary Capture Image Storage though the Secondary Capture Image Storage SOP CLASS that is set by default is OK too.

Here's another dump, this time of a file created by SetBMPFrames:

# Dicom-Data-Set
# Used TransferSyntax: LittleEndianExplicit
(0008,0016) UI =SecondaryCaptureImageStorage            #  26, 1 SOPClassUID
(0028,0002) US 3                                        #   2, 1 SamplesPerPixel
(0028,0004) CS [RGB]                                    #   4, 1 PhotometricInterpretation
(0028,0006) US 0                                        #   2, 1 PlanarConfiguration
(0028,0008) IS [2]                                      #   2, 1 NumberOfFrames
(0028,0010) US 300                                      #   2, 1 Rows
(0028,0011) US 300                                      #   2, 1 Columns
(0028,0100) US 8                                        #   2, 1 BitsAllocated
(0028,0101) US 8                                        #   2, 1 BitsStored
(0028,0102) US 7                                        #   2, 1 HighBit
(0028,0103) US 0                                        #   2, 1 PixelRepresentation
(7fe0,0010) OB f7\fc\ff\ef\f4\fa\fa\ff\ff\f3\f8\fe\f7\fc\ff\ec\f1\f7\f9\ff\ff\f4... # 540000, 1 PixelData


Again you can see the highlighted number of frames but the pixel data this time is not inside a sequence but rather the RGB values of the first frame concatenated with the pixels of the second frame so you can see that the pixel data element value length is now 540000 that is double the length of the single frame example we've seen before.


Summary


In this short tutorial we've seen how to convert Jpeg and BMP images to DICOM.
We've created single frame and multi-frame DICOM images using RZDCX DICOM Toolkit DCXOBJ class.


8 comments:

  1. I want to convert JPEG images to DICOM in online where clients are able to submit JPEG images with patient demographics registration in a server and software will convert this images to DICOM and store all images to a specific directory. I need this for a Teleradiology project.
    Is it possible?
    Please help me how can I do it. I have no enough knowledge about this.
    Email me the steps at mirzasanower123@yahoo.com

    With Thanks
    Mirza sanower Hosen

    ReplyDelete
  2. Roni, you mentioned PDF in the title but did not talk about it in the post :D What happened? haha. Can you let me know how a PDF or Word Document can be attached to the DICOM envelope in C# using RZDCX and also what SOP Class UID would we use for storage?

    ReplyDelete
    Replies
    1. http://www.roniza.com/solutions/dicom-pdf/

      Delete
    2. Sweet. Looking at your code, reminds of the video conversion Tag Enum. On a separate note "Send to DICOM Printer" is literally the only thing that is missing from your SDK. I know DICOM Printer is going towards extinction but many hospitals and clinics in US are still using them.

      Delete
    3. Hi Matt,
      There is a Print SCU in the SDK.
      Check this:
      http://rzdcx.roniza.com/_c_p_p_print_s_c_u_8cpp-example.html

      Delete
    4. OMG, how did I not know about your examples in the user manual of RZDCX?! OK, if I could get the example to work for me in a comprehensive way, then I confess your SDK is one of the most complete I have seen!

      Delete
  3. Convert bitmap to Dicom is very good, if you can achieve convert pdf to bmp is better, although it is reverse operation, but the difficulty is not small.

    ReplyDelete
  4. Using PDF encapsulate using Unsafe code will face exception "attempted to read or write protected memory", please advise how to fix this issue at the following code;
    fixed (byte* p=b)
    {
    UIntPtr p1= (UIntPtr)p;
    e.Value = p1;
    o.insertElement(e);
    o.saveFile(outfile);
    }

    ReplyDelete