Problem in parsing the string value of a private tag with dcmtk when the transfer syntax is LittleEndianImplicit (even i

All other questions regarding DCMTK

Moderator: Moderator Team

Post Reply
Message
Author
paperini
Posts: 5
Joined: Wed, 2021-02-03, 10:20

Problem in parsing the string value of a private tag with dcmtk when the transfer syntax is LittleEndianImplicit (even i

#1 Post by paperini »

Hi!

I use dcmtk version 3.6.7 under Windows to write an application that creates a Dicom file (MultiframeGrayscaleByteSecondaryCaptureImageStorage), put some private tags in it and send it to Pacs (Conquest 1.5.0 ), then I retrieve the dataset from the Pacs with a C-MOVE and would like to read it, also with the private tags. I propose all the 3 uncompressed syntaxes to Pacs.
The problem is that when the received file is encoded with the LittleEndianImplicit syntax, I cannot read the value of the private tags with the function: findAndGetOFString() (if the private tag is a long string, for example) because the returned value is binary and the type of the Dicom element is EVR_UNKNOWN instead of EVR_LO (it happens also if I read directly the dataset in the function: notifyInstanceStored(const OFString &filename, const OFString &sopClassUID, const OFString &sopInstanceUID, DcmDataset *dataset))
I used the approach explained in: https://support.dcmtk.org/redmine/proje ... rivatedata

This is the dump of my Dicom file:
# Dicom-File-Format

# Dicom-Meta-Information-Header
# Used TransferSyntax: Little Endian Explicit
(0002,0000) UL 198 # 4, 1 FileMetaInformationGroupLength
(0002,0001) OB 00\01 # 2, 1 FileMetaInformationVersion
(0002,0002) UI =MultiframeGrayscaleByteSecondaryCaptureImageStorage # 28, 1 MediaStorageSOPClassUID
(0002,0003) UI [1.2.276.0.7230010.3.1.4.527670776.26456.1710763541.114] # 54, 1 MediaStorageSOPInstanceUID
(0002,0010) UI =LittleEndianImplicit # 18, 1 TransferSyntaxUID
(0002,0012) UI [1.2.276.0.7230010.3.0.3.6.7] # 28, 1 ImplementationClassUID
(0002,0013) SH [OFFIS_DCMTK_367] # 16, 1 ImplementationVersionName

# Dicom-Data-Set
# Used TransferSyntax: Little Endian Implicit
(0008,0005) CS [ISO_IR 100] # 10, 1 SpecificCharacterSet
(0008,0012) DA [20240318] # 8, 1 InstanceCreationDate
(0008,0013) TM [120541] # 6, 1 InstanceCreationTime
(0008,0016) UI =MultiframeGrayscaleByteSecondaryCaptureImageStorage # 28, 1 SOPClassUID
(0008,0018) UI [1.2.276.0.7230010.3.1.4.527670776.26456.1710763541.114] # 54, 1 SOPInstanceUID
(0008,0020) DA [20240318] # 8, 1 StudyDate
(0008,0021) DA [20240318] # 8, 1 SeriesDate
(0008,0030) TM [120452] # 6, 1 StudyTime
(0008,0031) TM [120452] # 6, 1 SeriesTime
(0008,0050) SH (no value available) # 0, 0 AccessionNumber
(0008,0060) CS [US] # 2, 1 Modality
(0008,0064) CS [WSD] # 4, 1 ConversionType
(0008,0070) LO [PIUR-3D Acquisition] # 20, 1 Manufacturer
(0008,0090) PN (no value available) # 0, 0 ReferringPhysicianName
(0010,0010) PN [test^test] # 10, 1 PatientName
(0010,0020) LO [456] # 4, 1 PatientID
(0010,0030) DA [20010101] # 8, 1 PatientBirthDate
(0010,0040) CS [O] # 2, 1 PatientSex
(0018,1065) DS [0\51000\50000\60000\70000\60000\59000\66000\54000\55000\55000\6600... # 668,113 FrameTimeVector
(0020,000d) UI [1.2.40.0.34.3.1.1145.1.2.527670776.26456.1710763492.108] # 56, 1 StudyInstanceUID
(0020,000e) UI [1.2.40.0.34.3.1.1145.1.3.527670776.26456.1710763541.113] # 56, 1 SeriesInstanceUID
(0020,0010) SH [0Jv6UYXx1-EOsUoK] # 16, 1 StudyID
(0020,0011) IS [1] # 2, 1 SeriesNumber
(0020,0013) IS [0] # 2, 1 InstanceNumber
(0020,0020) CS (no value available) # 0, 0 PatientOrientation
(0028,0002) US 1 # 2, 1 SamplesPerPixel
(0028,0004) CS [MONOCHROME2] # 12, 1 PhotometricInterpretation
(0028,0008) IS [112] # 4, 1 NumberOfFrames
(0028,0009) AT (0018,1065) # 4, 1 FrameIncrementPointer
(0028,0010) US 789 # 2, 1 Rows
(0028,0011) US 753 # 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
(0028,1052) DS [0] # 2, 1 RescaleIntercept
(0028,1053) DS [1] # 2, 1 RescaleSlope
(0028,1054) LO [US] # 2, 1 RescaleType
(0051,0010) LO [PIURTUS] # 8, 1 PrivateCreator
(0051,1031) ?? 4d\69\6e\64\72\61\79\20\4d\39 # 10, 1 Unknown Tag & Data
(2050,0020) CS [IDENTITY] # 8, 1 PresentationLUTShape
(7fe0,0010) OW 100f\0e0e\0e0e\0d0d\0d0d\0d0d\0e0e\0e0e\0000\0000\0d0d\0e0e\0e0e... # 66541104, 1 PixelData

This is how I declared the private tag:
static const char *PRIVATE_CREATOR_NAME = "PIURTUS";

// reservation for the Data Elements in the range: (0x0051,1000-10FF)
static const DcmTag PRV_PrivateCreator(0x0051, 0x0010, EVR_LO);
static const DcmTag PRV_UltrasoundSystem(0x0051, 0x1031, EVR_LO);

#define PRIVATE_UltrasoundSystem_TAG 0x0051, 0x1031


I registered the private tags in the dictionary at start up:
DcmDataDictionary &dict = dcmDataDict.wrlock();

dict.addEntry(new DcmDictEntry(PRIVATE_UltrasoundSystem_TAG, EVR_LO, "MyUltrasoundSystem", 1, 1, "4.1", OFTrue, PRIVATE_CREATOR_NAME));

dcmDataDict.wrunlock();

dcmEnableUnknownVRConversion.set(OFTrue);


And this is the code I use for reading the private tag:
// Make sure data dictionary is loaded
if (!dcmDataDict.isDictionaryLoaded())
{
qWarning() << "No Dicom data dictionary loaded, it will be not possible to read the private tags";
return;
}

const DcmDataDictionary &dict = dcmDataDict.rdlock();
auto *entry = dict.findEntry(DcmTagKey(PRIVATE_UltrasoundSystem_TAG), PRIVATE_CREATOR_NAME);
dcmDataDict.rdunlock();

if (!entry)
{
qWarning() << "No entry in dictionary for the Private Ultrasound System";
}
else
{
qDebug() << "Private Ultrasound System VR in dictionary: "
<< entry->getEVR();
}

DcmFileFormat fileFormat;
OFCondition result = fileFormat.loadFile(filename.toStdString().c_str());
if (!result.good())
{
qWarning() << "The received Dicom file: " << filename
<< " cannot be loaded: " << result.text();
return;
}

DcmDataset *dataset = fileFormat.getDataset();
if (!dataset)
{
qWarning() << "The received Dicom dataset is null";
return;
}

OFString ultrasoundSystem;
result =
dataset->findAndGetOFString(DcmTagKey(PRIVATE_UltrasoundSystem_TAG), ultrasoundSystem);
if (result.bad())
{
qWarning() << result.text();
return;
}

DcmElement *ultrasoundSystemElement = nullptr;
result = dataset->findAndGetElement(DcmTagKey(PRIVATE_UltrasoundSystem_TAG),
ultrasoundSystemElement);
if (result.bad() || !ultrasoundSystemElement)
{
qWarning() << result.text();
return;
}

auto datasetVr = ultrasoundSystemElement->getVR();
qDebug() << "Private Ultrasound System VR in dataset: " << datasetVr;


The output is:
Private Ultrasound System VR in dictionary: 10 (EVR_LO)
Private Ultrasound System VR in dataset: 47 (EVR_UNKNOWN)
Because the private tag value is read as binary data (the element is considered DcmOtherByteOtherWord)
And:
ultrasoundSystem = “4d” (4d = M in hex): but in reality should be “Mindray M9”

My question is if it is possible to make dcmtk use the added entries in the dictionary for the private tags when reading a dataset encoded with LittleEndianImplicit transfer syntax, maybe I overlooked something.
From the documentation of: getVR() (dcobject.h): "If object was read from a stream, this method returns the VR that was defined in the stream for this object. It is, therefore, possible that the VR does not match the one defined in the data dictionary for the tag assigned to this object"
Does it mean that it is expected that, if the transfer syntax is implicit, the private tag has EVR_UNKNOWN data type?

The following question is more general about the private tags, I read that it is important that the private tags are relocatable to avoid conflict with other private tags: how can I make a relocatable private tags? Should I simply check in my dataset if another tag with the same (gggg, eeee) value exists and in case use the next available (gggg, eeee) value inside the reserved interval? Or there is a more general approach maybe using dcmtk?

Another point that it is not clear to me is in which occasions there could be conflicts with other private tags. Are the possible conflicts in the Dicom file, maybe when it is written or changed by different persons? (In this case, it should not be a problem for our use case, as far as I know)
Or the private tags must be known to the Pacs, and this case I can imagine that there could be conflicts with different vendors, but probably having the private tags relocatable will not help to solve the problem in all the cases.

Thank you for helping in understanding a bit more about private tags!
Stefania

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

Re: Problem in parsing the string value of a private tag with dcmtk when the transfer syntax is LittleEndianImplicit (ev

#2 Post by J. Riesmeier »

I used your textual dump and created a binary DICOM files from it (using dump2dcm). Then I created a text file "private.dic" with the following content:

Code: Select all

(0051,"PIURTUS",31)	LO	UltrasoundSystem	1	PrivateTag
Finally, I specified this private data dictionary as an additional file to be loaded by the DCMTK (using e.g. the following command on Linux):

Code: Select all

> export DCMDICTPATH=/usr/local/share/dcmtk-3.6.8/dicom.dic:private.dic
... and called:

Code: Select all

> dcmdump +uc sample.dcm
The output is the following:

Code: Select all

[...]
(0028,1054) LO [US]                                     #   2, 1 RescaleType
(0051,0010) LO [PIURTUS]                                #   8, 1 PrivateCreator
(0051,1031) LO [Mindray M9]                             #  10, 1 UltrasoundSystem
(2050,0020) CS [IDENTITY]                               #   8, 1 PresentationLUTShape
[...]
I guess this is what you expected, right?

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

Re: Problem in parsing the string value of a private tag with dcmtk when the transfer syntax is LittleEndianImplicit (ev

#3 Post by J. Riesmeier »

The following question is more general about the private tags, I read that it is important that the private tags are relocatable to avoid conflict with other private tags: how can I make a relocatable private tags? Should I simply check in my dataset if another tag with the same (gggg, eeee) value exists and in case use the next available (gggg, eeee) value inside the reserved interval? Or there is a more general approach maybe using dcmtk?
Currently, you would have to check in your code whether a certain range of Private Tags is already used by another system and store yours "somewhere else". There are plans/ideas to make this much easier for users of the DCMTK (see DCMTK Feature #1095 and #1096), but this has no high priority at the moment.
Another point that it is not clear to me is in which occasions there could be conflicts with other private tags. Are the possible conflicts in the Dicom file, maybe when it is written or changed by different persons? (In this case, it should not be a problem for our use case, as far as I know)
Or the private tags must be known to the Pacs, and this case I can imagine that there could be conflicts with different vendors, but probably having the private tags relocatable will not help to solve the problem in all the cases.
Of course, you only need to check this if your are reading an existing DICOM dataset that was not created by your system and want to add your own Private Data Elements. For example, if you are a modality and create DICOM SOP Instances (i.e.from scratch), there is no need to check for existing Private Data Elements.

paperini
Posts: 5
Joined: Wed, 2021-02-03, 10:20

Re: Problem in parsing the string value of a private tag with dcmtk when the transfer syntax is LittleEndianImplicit (ev

#4 Post by paperini »

Hi J. Riesmeier,

Thanks a lot for your answer, now I understand what it is written in the standard about the possible conflicts of the private tags!

About the problem in using the dictionary in the code, I tried with an external dictionary, as you suggested, but I have the same result as before.
If I read the private tag from the dictionary before loading the file, it is read correctly (VR type, creator) but then loading the file and calling:
dataset->findAndGetOFString() always return binary data
I will have a look on the code of dcmdump.cc to see which are the differences with my code.
Since dcmdump can read the correct value of the private tag and my code not, there must be a difference that explains the different results.

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

Re: Problem in parsing the string value of a private tag with dcmtk when the transfer syntax is LittleEndianImplicit (ev

#5 Post by J. Riesmeier »

The main difference between your program and dcmdump is probably that dcmdump uses the print() method while you are using the findAndGetXXX() methods.
That means, you should probably start with calling the print() method in your program and see whether VR and Value are printed correctly.

paperini
Posts: 5
Joined: Wed, 2021-02-03, 10:20

Re: Problem in parsing the string value of a private tag with dcmtk when the transfer syntax is LittleEndianImplicit (ev

#6 Post by paperini »

You are right, I called print() in my program on the DcmFileFormat and it is not working correctly for the private tag (it displays ?? as VR and binary values for the value)
I don't know why the new tag that is added in the dictionary is not used.

I thought that if I call:

const DcmDataDictionary &dict = dcmDataDict.rdlock();
auto *entry = dict.findEntry(DcmTagKey(PRIVATE_UltrasoundSystem_TAG), PRIVATE_CREATOR_NAME);
dcmDataDict.rdunlock();

and the entry is ok before loading the file it should be enough, but apparently I am missing something.

I am on a Windows machine, so the built-in dictionary is used by default. I can add the new entry at runtime as explained or in a external dictionary, that is loaded using the environment variable DCMDICTPATH.
Are there other points that are important to consider? some special Settings during the build of the library?
Is there a way I can check if the dictionary is used for the lookup of private tags in case of implicit transfer syntax? (for example some points in the code where I can put a breakpoint? I tried to go a bit deeper with the debugger but I cannot see where the dictionary is used)

Thanks again!

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

Re: Problem in parsing the string value of a private tag with dcmtk when the transfer syntax is LittleEndianImplicit (ev

#7 Post by J. Riesmeier »

Did you check that you really call dcmEnableUnknownVRConversion.set(OFTrue); in your code?

paperini
Posts: 5
Joined: Wed, 2021-02-03, 10:20

Re: Problem in parsing the string value of a private tag with dcmtk when the transfer syntax is LittleEndianImplicit (ev

#8 Post by paperini »

Hi, yes, dcmEnableUnknownVRConversion.set(OFTrue) is called.

I will investigate further, in the meantime in case of LittleEndianImplicit transfer syntax, I will parse the private tag value as binary.
Instead in case of LittleEndianExplicit I can use findAndGetXXX(), it works just fine.
I don't know if BigEndianExplicit is still used by the Pacs server, but for String values it will be the same as LittleEndianExplicit.

Post Reply

Who is online

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