Problem with Pixel Representation while creating a Dicom

All other questions regarding DCMTK

Moderator: Moderator Team

Post Reply
Message
Author
martinb
Posts: 4
Joined: Mon, 2022-05-23, 17:01

Problem with Pixel Representation while creating a Dicom

#1 Post by martinb »

Hello everyone,

I'm fairly new to dcmtk - but not to dicom itself - and I'm having a problem while writing a multiframe dicom from multiple jpeg images.

Here is the simplified code to help you understand my issue (i'm using some wizardry because we are still using C++98 and I can only use specific libraries):

First, the necessary include :

Code: Select all

#include "dcmtk/dcmjpeg/djrplol.h"
#include "dcmtk/dcmjpeg/djencode.h"
Then, the preliminary steps :

Code: Select all

DJEncoderRegistration::registerCodecs();
DJ_RPLossless params;
			
DcmFileFormat fileFormat;
DcmDataset * dataset_multiframe = fileFormat.getDataset();			
DcmPixelData * newPixelData = new DcmPixelData(DCM_PixelData);
dataset_multiframe->insert(newPixelData, OFTrue);
			
// Create QBYteArray to handle transfer
QByteArray * ba = new QByteArray();
Then, I insert the necessary tags (maybe I'm missing one ? Or doing one wrong ?) :

Code: Select all

dataset_multiframe->putAndInsertString(DCM_TransferSyntaxUID, UID_JPEGProcess14TransferSyntax);
dataset_multiframe->putAndInsertString(DCM_PhotometricInterpretation,"RGB");
dataset_multiframe->putAndInsertUint16(DCM_SamplesPerPixel,3);
dataset_multiframe->putAndInsertUint16(DCM_PlanarConfiguration,0);
dataset_multiframe->putAndInsertUint16(DCM_BitsAllocated,8);
dataset_multiframe->putAndInsertUint16(DCM_BitsStored,8);
dataset_multiframe->putAndInsertUint16(DCM_HighBit,7);
dataset_multiframe->putAndInsertUint16(DCM_PixelRepresentation,0);
dataset_multiframe->putAndInsertUint16(DCM_NumberOfFrames,nbFrame);
dataset_multiframe->putAndInsertUint16(DCM_Rows,picture->height()); // Picture is a QImage containing the first JPEG image aka first frame of my dicom
dataset_multiframe->putAndInsertUint16(DCM_Columns,picture->width());
dataset_multiframe->putAndInsertString(DCM_BurnedInAnnotation, "NO");
dataset_multiframe->putAndInsertString(DCM_SOPClassUID, UID_MultiframeTrueColorSecondaryCaptureImageStorage);
And now the for loop to add every frame to the dataset with the help of a QByteArray :

Code: Select all

for(unsigned short int i = 0; i < nbFrame ; i++)
{
	// ba is a QByteArray containing the bytes of the i-th image
	OFCondition status = dataset_multiframe->putAndInsertUint8Array(DCM_PixelData, reinterpret_cast<Uint8 *>(ba), ba->size());

	OFCondition status2 = dataset_multiframe->chooseRepresentation(EXS_JPEGProcess14, &params);
}
And although status is normal, status2 is bad and gives me as a text : "Pixel Representation cannot be changed", which is weird because i'm not trying to change it in any way. I've tried changing the syntax to some other JPEG Syntax (14SV1 for example) but to no avail. Any idea what I'm doing wrong here ?

Thanks a lot for your time and answer and have all a great day !

EDIT : The error does not appear if I use LittleEndianImplicit instead but I get a dicom with one whole black frame (seems normal to me because the data in QByteArray are from a JPEG Image)

Marco Eichelberg
OFFIS DICOM Team
OFFIS DICOM Team
Posts: 1473
Joined: Tue, 2004-11-02, 17:22
Location: Oldenburg, Germany
Contact:

Re: Problem with Pixel Representation while creating a Dicom

#2 Post by Marco Eichelberg »

The problem is related to the way you are inserting the pixel data into the dataset. Class DcmPixelData, which is used for this attribute, is able to manage both an uncompressed and one or more compressed versions of the same bitmap in memory at the same time. A call to putAndInsertUint8Array() will always insert pixel data into the array intended for uncompressed data, so your call to chooseRepresentation actually causes DCMTK to try to compress your (already compressed) bitmap once more, which fails probably because the JPEG encoder has not been activated (registered).

What you actually need to use is DcmPixelData::putOriginalRepresentation(), see https://support.dcmtk.org/docs/classDcmPixelData.html.

For this you need to create a DcmPixelSequence (using operator new) and insert two new DcmPixelItem instances, an empty one for the basic offset table, and a populated one for the JPEG data. You store the compressed pixel data using DcmPixelItem::putUint8Array(). Finally, the DcmRepresentationParameter instance passed to DcmPixelData::putOriginalRepresentation() should either be a DJ_RPLossless instance, or a DJ_RPLossy instance, depending on whether you are storing lossless or lossy JPEG data.

Yes, this is all very complicated, and if I had to develop that stuff again, I would do this differently :roll:

martinb
Posts: 4
Joined: Mon, 2022-05-23, 17:01

Re: Problem with Pixel Representation while creating a Dicom

#3 Post by martinb »

Hello Marco and thanks a lot for your response !

So, if I understood correctly, I should be doing the following code ?

Registering encoders :

Code: Select all

DJEncoderRegistration::registerCodecs();
DJ_RPLossless params; // Could be lossy depending on the situation, as you said
Declare all variables :

Code: Select all

DcmFileFormat fileFormat;
DcmDataset * dataset_multiframe = fileFormat.getDataset();
DcmPixelData * pixelData = new DcmPixelData(DCM_PixelData);
DcmPixelSequence * pixelSequence = new DcmPixelSequence(DcmTag(DCM_PixelData, EVR_OB));
DcmPixelItem * offsetPixelItem = new DcmPixelItem(DcmTag(DCM_Item, EVR_OB));
Insert the empty offset table into the pixel sequence :

Code: Select all

pixelSequence->insert(offsetPixelItem);
Fill my QByteArray (ba) with all the bytes from my multiple JPEG (code not shown because irrelevant to the issue)

Then, put this Uint8 array inside a DcmPixelItem :

Code: Select all

DcmPixelItem * jpegPixelItem = new DcmPixelItem(DcmTag(DCM_Item, EVR_OB), ba->size());
cond = jpegPixelItem->putUint8Array(reinterpret_cast<Uint8 *>(ba), ba->size());
Then, insert my jpegPixelItem inside the pixelSequence and continue from there

Code: Select all

pixelSequence->insert(jpegPixelItem);
I just wanted to know if I was doing things right because when I run the preceeding code, It seems to freeze during the putUint8Array part therefore making me unable to debug via the resulting OFCondition (I have checks on all possible OFConditions during the program and it always go right until this line).
So, maybe I'm just misunderstanding one step ? Or maybe there is something else...

Thanks again for your time and expertise and have a great day !

Marco Eichelberg
OFFIS DICOM Team
OFFIS DICOM Team
Posts: 1473
Joined: Tue, 2004-11-02, 17:22
Location: Oldenburg, Germany
Contact:

Re: Problem with Pixel Representation while creating a Dicom

#4 Post by Marco Eichelberg »

You don't need to register an encoder, since you are not using that.
Use new DcmPixelSequence(DCM_PixelSequenceTag) and new DcmPixelItem(DCM_PixelItemTag).
Don't forget the call to DcmPixelData::putOriginalRepresentation().

martinb
Posts: 4
Joined: Mon, 2022-05-23, 17:01

Re: Problem with Pixel Representation while creating a Dicom

#5 Post by martinb »

Hello Marco,

We're getting there thanks to your precious help. I have one last issue though : I only obtain a single frame dicom although all the data are stored inside. I think this may come from the fact that there is no way to differentiate where the next frame starts in the byte area. Or maybe I'm missing something again ?

Here is the working code so far (i'm posting all dicom related lines since any of them could be the problem) :

Initialisations :

Code: Select all

DJ_RPLossless params;		
DcmFileFormat fileFormat;
DcmDataset * dataset_multiframe = fileFormat.getDataset();
DcmPixelData * pixelData = new DcmPixelData(DCM_PixelData);
DcmPixelSequence * pixelSequence = new DcmPixelSequence(DCM_PixelSequenceTag);
DcmPixelItem * offsetPixelItem = new DcmPixelItem(DCM_PixelItemTag);
DcmPixelItem * jpegPixelItem = new DcmPixelItem(DCM_PixelItemTag);
QByteArray * ba = new QByteArray();
Tag Insertion :

Code: Select all

dataset_multiframe->putAndInsertString(DCM_TransferSyntaxUID, UID_JPEGProcess14SV1TransferSyntax);
dataset_multiframe->putAndInsertString(DCM_PhotometricInterpretation,"RGB");
dataset_multiframe->putAndInsertUint16(DCM_SamplesPerPixel,3);
dataset_multiframe->putAndInsertUint16(DCM_PlanarConfiguration,0);
dataset_multiframe->putAndInsertUint16(DCM_BitsAllocated,8);
dataset_multiframe->putAndInsertUint16(DCM_BitsStored,8);
dataset_multiframe->putAndInsertUint16(DCM_HighBit,7);
dataset_multiframe->putAndInsertUint16(DCM_PixelRepresentation,0);
dataset_multiframe->putAndInsertUint16(DCM_Rows,picture->height());
dataset_multiframe->putAndInsertUint16(DCM_Columns,picture->width());
dataset_multiframe->putAndInsertUint16(DCM_NumberOfFrames,nbFrame);
dataset_multiframe->putAndInsertString(DCM_SOPClassUID, UID_MultiframeTrueColorSecondaryCaptureImageStorage);
dataset_multiframe->putAndInsertUint16(DCM_SeriesNumber, 1);
Data insertion recursively :

Code: Select all

pixelSequence->insert(offsetPixelItem);
// ...
// Insertion of all frame data inside a QByteArray with a for loop
jpegPixelItem->putUint8Array(reinterpret_cast<Uint8 *>(ba->data()), ba->size());
pixelSequence->insert(jpegPixelItem);
dataset_multiframe->insert(pixelData);
dcmdump on saved dicom file gives :
W: DcmMetaInfo: No Group Length available in Meta Information Header

# Dicom-File-Format

# Dicom-Meta-Information-Header
# Used TransferSyntax: Little Endian Explicit
(0002,0010) UI =JPEGLossless:Non-hierarchical-1stOrderPrediction # 22, 1 TransferSyntaxUID

# Dicom-Data-Set
# Used TransferSyntax: JPEG Lossless, Non-hierarchical, 1st Order Prediction
(0008,0016) UI =MultiframeTrueColorSecondaryCaptureImageStorage # 28, 1 SOPClassUID
(0010,0010) PN [TESTING] # 8, 1 PatientName
(0010,0020) LO [PID00001] # 8, 1 PatientID
(0020,000d) UI [STUDYSPECIAL] # 12, 1 StudyInstanceUID
(0028,0002) US 3 # 2, 1 SamplesPerPixel
(0028,0004) CS [RGB] # 4, 1 PhotometricInterpretation
(0028,0006) US 0 # 2, 1 PlanarConfiguration
(0028,0010) US 1080 # 2, 1 Rows
(0028,0011) US 1920 # 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,0301) CS [NO] # 2, 1 BurnedInAnnotation
(7fe0,0010) OB (PixelSequence #=2) # u/l, 1 PixelData
(fffe,e000) pi (no value available) # 0, 1 Item
(fffe,e000) pi ff\d8\ff\e0\00\10\4a\46\49\46\00\01\01\01\00\60\00\60\00\00\ff\db... # 14459808, 1 Item
(fffe,e0dd) na (SequenceDelimitationItem) # 0, 0 SequenceDelimitationItem

Sorry again if my question is simple but I tried a lot of different things but to no avail before reaching out to you again. Thanks a lot for your help and have a great day !

Admin
Site Admin
Posts: 24
Joined: Fri, 2004-10-29, 21:21
Location: Oldenburg, Germany
Contact:

Re: Problem with Pixel Representation while creating a Dicom

#6 Post by Admin »

One problem is that your dataset does not contain a SOP Instance UID, which is absolutely required.
Another problem is that you are adding the transfer syntax UID to the dataset, where it does not belong. This should be in the metaheader.
Concerning the missing group length in the metaheader, how does your call to DcmFileFormat::saveFile() look like?

J. Riesmeier
DCMTK Developer
Posts: 2524
Joined: Tue, 2011-05-03, 14:38
Location: Oldenburg, Germany
Contact:

Re: Problem with Pixel Representation while creating a Dicom

#7 Post by J. Riesmeier »

Furthermore, in the original posting you said "multi-frame" ("writing a multiframe dicom from multiple jpeg images") and in your latest sample code you add Number of Frames with a value of "nbFrame", but this data element is missing in the dump, so it is a single frame image. Also, there is only a single pixel item with pixel data.

martinb
Posts: 4
Joined: Mon, 2022-05-23, 17:01

Re: Problem with Pixel Representation while creating a Dicom

#8 Post by martinb »

Hello Admin and hello Mr Riesmeier,

thanks a lot for both of your answers.
One problem is that your dataset does not contain a SOP Instance UID, which is absolutely required.
Another problem is that you are adding the transfer syntax UID to the dataset, where it does not belong. This should be in the metaheader.
You are totally right and I was not aware of this. I put one and it seems to have fixed a lot of info in the metadata - like the missing number of frames. I've also added the Transfer Syntax where it belongs. Group length is also appearing now but still, the image is single-frame.
[...] how does your call to DcmFileFormat::saveFile() look like?
It's actually super simple :

Code: Select all

 fileFormat.saveFile("F:\\test.dcm", EXS_JPEGProcess14SV1);
Also, there is only a single pixel item with pixel data.
That's what concerns me indeed because there should be as many as the number of frames. But how can I achieve this ? Do I need to insert individually each pixel sequence corresponding to each frame ? Because right now with my code, I store all frames data in a single byte array and then insert it inside a pixel sequence (cf Code in my last post).

I'm enclosing as well the new meta data after fix so that you have the maximum amount of informations :

# Dicom-Meta-Information-Header
# Used TransferSyntax: Little Endian Explicit
(0002,0000) UL 196 # 4, 1 FileMetaInformationGroupLength
(0002,0001) OB 00\01 # 2, 1 FileMetaInformationVersion
(0002,0002) UI =MultiframeTrueColorSecondaryCaptureImageStorage # 28, 1 MediaStorageSOPClassUID
(0002,0003) UI [1.2.250.1.170.10.4.119995793.12412.1418381582.9] # 48, 1 MediaStorageSOPInstanceUID
(0002,0010) UI =JPEGLossless:Non-hierarchical-1stOrderPrediction # 22, 1 TransferSyntaxUID
(0002,0012) UI [1.2.276.0.7230010.3.0.3.6.4] # 28, 1 ImplementationClassUID
(0002,0013) SH [OFFIS_DCMTK_364] # 16, 1 ImplementationVersionName

# Dicom-Data-Set
# Used TransferSyntax: JPEG Lossless, Non-hierarchical, 1st Order Prediction
(0002,0010) UI =JPEGLossless:Non-hierarchical-1stOrderPrediction # 22, 1 TransferSyntaxUID
(0008,0016) UI =MultiframeTrueColorSecondaryCaptureImageStorage # 28, 1 SOPClassUID
(0008,0018) UI [1.2.250.1.170.10.4.119995793.12412.1418381582.9] # 48, 1 SOPInstanceUID
(0010,0010) PN [TESTING] # 8, 1 PatientName
(0010,0020) LO [PID00001] # 8, 1 PatientID
(0020,000d) UI [STUDYSPECIAL] # 12, 1 StudyInstanceUID
(0028,0002) US 3 # 2, 1 SamplesPerPixel
(0028,0004) CS [RGB] # 4, 1 PhotometricInterpretation
(0028,0006) US 0 # 2, 1 PlanarConfiguration
(0028,0008) IS [91] # 2, 1 NumberOfFrames
(0028,0010) US 1080 # 2, 1 Rows
(0028,0011) US 1920 # 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,0301) CS [NO] # 2, 1 BurnedInAnnotation
(7fe0,0010) OB (PixelSequence #=2) # u/l, 1 PixelData
(fffe,e000) pi (no value available) # 0, 1 Item
(fffe,e000) pi ff\d8\ff\e0\00\10\4a\46\49\46\00\01\01\01\00\60\00\60\00\00\ff\db... # 14459808, 1 Item
(fffe,e0dd) na (SequenceDelimitationItem) # 0, 0 SequenceDelimitationItem

Again, thanks a lot for your help and have a great day and weekend !

J. Riesmeier
DCMTK Developer
Posts: 2524
Joined: Tue, 2011-05-03, 14:38
Location: Oldenburg, Germany
Contact:

Re: Problem with Pixel Representation while creating a Dicom

#9 Post by J. Riesmeier »

Do I need to insert individually each pixel sequence corresponding to each frame ? Because right now with my code, I store all frames data in a single byte array and then insert it inside a pixel sequence (cf Code in my last post).
For the Transfer Syntax you've chosen, you need to store each frame in a separate pixel item, i.e. you need to add a new pixel item into the pixel sequence for each and every frame of your multi-frame image. The first pixel item remains to be reserved for the basic offset table, which could be left empty if you don't want to store the byte offsets of each individual frame.

Post Reply

Who is online

Users browsing this forum: Ahrefs [Bot], Semrush [Bot] and 1 guest