Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Modules/IO/DCMTK/include/itkDCMTKFileReader.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
#include "dcmtk/dcmdata/dcsequen.h"
#include "itkMacro.h"
#include "itkImageIOBase.h"
#include "itkMetaDataDictionary.h"

class DcmSequenceOfItems;
class DcmFileFormat;
Expand Down Expand Up @@ -498,6 +499,9 @@ class ITKIODCMTK_EXPORT DCMTKFileReader
static bool
IsImageFile(const std::string & filename);

void
PopulateMetaDataDictionary(MetaDataDictionary & dict) const;

private:
std::string m_FileName;
DcmFileFormat * m_DFile{ nullptr };
Expand Down
70 changes: 70 additions & 0 deletions Modules/IO/DCMTK/src/itkDCMTKFileReader.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
#include "dcmtk/dcmdata/dcvrus.h" /* for DcmUnsignedShort */
#include "dcmtk/dcmdata/dcvris.h" /* for DcmIntegerString */
#include "dcmtk/dcmdata/dcvrobow.h" /* for DcmOtherByteOtherWord */
#include "dcmtk/dcmdata/dcelem.h" /* for DcmElement */
#include "dcmtk/dcmdata/dcvrui.h" /* for DcmUniqueIdentifier */
#include "dcmtk/dcmdata/dcfilefo.h" /* for DcmFileFormat */
#include "dcmtk/dcmdata/dcmetinf.h" /* for DcmMetaInfo */
Expand All @@ -47,7 +48,10 @@

#include "vnl/vnl_cross.h"
#include "itkIntTypes.h"
#include "itkMetaDataObject.h"
#include "itksys/Base64.h"
#include <algorithm>
#include <memory>

namespace itk
{
Expand Down Expand Up @@ -1369,6 +1373,72 @@ DCMTKFileReader::GetFileNumber() const
return m_FileNumber;
}

void
DCMTKFileReader::PopulateMetaDataDictionary(MetaDataDictionary & dict) const
{
dict.Clear();
if (m_Dataset == nullptr)
{
return;
}
const unsigned long numElements = m_Dataset->card();
for (unsigned long i = 0; i < numElements; ++i)
{
DcmElement * element = m_Dataset->getElement(i);
if (element == nullptr)
{
continue;
}
const DcmTag & tag = element->getTag();
// Skip pixel data (7FE0,0010)
if (tag.getGroup() == 0x7fe0 && tag.getElement() == 0x0010)
{
continue;
}
// Format key as "GGGG|EEEE" (lowercase hex, matching GDCMImageIO)
char key[10];
std::snprintf(key, sizeof(key), "%04x|%04x", tag.getGroup(), tag.getElement());

const DcmEVR vr = element->getVR();
if (vr == EVR_SQ)
{
// Sequences are nested datasets, not byte arrays; getUint8Array() does
// not return their content. Skip rather than emit an empty entry.
continue;
}
if (vr == EVR_OB || vr == EVR_OW || vr == EVR_OF || vr == EVR_OD || vr == EVR_OL || vr == EVR_OV || vr == EVR_UN ||
vr == EVR_ox || vr == EVR_px)
{
// Binary VR — base64-encode the raw bytes
Uint8 * byteValue = nullptr;
if (element->getUint8Array(byteValue) == EC_Normal && byteValue != nullptr)
{
const Uint32 length = element->getLength();
if (length > 0)
{
int encodedLengthEstimate = 2 * static_cast<int>(length);
encodedLengthEstimate = ((encodedLengthEstimate / 4) + 1) * 4;
const auto bin = std::make_unique<char[]>(encodedLengthEstimate);
const auto encodedLengthActual =
static_cast<unsigned int>(itksysBase64_Encode(reinterpret_cast<const unsigned char *>(byteValue),
static_cast<SizeValueType>(length),
reinterpret_cast<unsigned char *>(bin.get()),
0));
EncapsulateMetaData<std::string>(dict, key, std::string(bin.get(), encodedLengthActual));
}
}
}
else
{
OFString value;
if (element->getOFStringArray(value) == EC_Normal)
{
EncapsulateMetaData<std::string>(dict, key, std::string(value.c_str()));
}
}
}
}

void
DCMTKFileReader::AddDictEntry(DcmDictEntry * entry)
{
Expand Down
1 change: 1 addition & 0 deletions Modules/IO/DCMTK/src/itkDCMTKImageIO.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -478,6 +478,7 @@ DCMTKImageIO::ReadImageInformation()
this->m_Spacing.push_back(1.0);
}

reader.PopulateMetaDataDictionary(this->GetMetaDataDictionary());

this->OpenDicomImage();
const DiPixel * interData = this->m_DImage->getInterData();
Expand Down
9 changes: 9 additions & 0 deletions Modules/IO/DCMTK/test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ itk_module_test()
set(
ITKIODCMTKTests
itkDCMTKGetDicomTagsTest.cxx
itkDCMTKImageIOMetadataTest.cxx
itkDCMTKImageIOMultiFrameImageTest.cxx
itkDCMTKImageIONoPreambleTest.cxx
itkDCMTKImageIOOrthoDirTest.cxx
Expand Down Expand Up @@ -82,6 +83,14 @@ itk_add_test(
${ITK_TEST_OUTPUT_DIR}/DICOMTags.txt
)

itk_add_test(
NAME itkDCMTKImageIOMetadataTest
COMMAND
ITKIODCMTKTestDriver
itkDCMTKImageIOMetadataTest
DATA{${ITK_DATA_ROOT}/Input/DicomSeries/Image0075.dcm}
)

itk_add_test(
NAME itkDCMTKImageIOTest1
COMMAND
Expand Down
76 changes: 76 additions & 0 deletions Modules/IO/DCMTK/test/itkDCMTKImageIOMetadataTest.cxx
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/*=========================================================================
*
* Copyright NumFOCUS
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0.txt
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*=========================================================================*/

#include "itkDCMTKImageIO.h"
#include "itkMetaDataObject.h"
#include "itkTestingMacros.h"

#include <iostream>

// Tests that DCMTKImageIO::ReadImageInformation() populates the metadata
// dictionary with DICOM tag values using "GGGG|EEEE" keys (lowercase
// matching GDCMImageIO behavior). Expected values are verified against
// Input/DicomSeries/Image0075.dcm using the same fixture as
// itkDCMTKGetDicomTagsTest.
int
itkDCMTKImageIOMetadataTest(int argc, char * argv[])
{
if (argc != 2)
{
std::cerr << "Missing parameters." << std::endl;
std::cerr << "Usage: " << itkNameOfTestExecutableMacro(argv) << " <DicomImage>" << std::endl;
return EXIT_FAILURE;
}

auto dcmtkIO = itk::DCMTKImageIO::New();

ITK_TEST_EXPECT_TRUE(dcmtkIO->CanReadFile(argv[1]));
dcmtkIO->SetFileName(argv[1]);
ITK_TRY_EXPECT_NO_EXCEPTION(dcmtkIO->ReadImageInformation());

const itk::MetaDataDictionary & dict = dcmtkIO->GetMetaDataDictionary();

// Dictionary must be populated
ITK_TEST_EXPECT_TRUE(!dict.GetKeys().empty());

// Pixel data (7FE0,0010) must NOT appear in the dictionary
ITK_TEST_EXPECT_TRUE(!dict.HasKey("7fe0|0010"));

// Verify string-valued DICOM tags using the "GGGG|EEEE" key format.
// Values are specific to the test file Input/DicomSeries/Image0075.dcm
std::string value;

// (0008,0021) DA SeriesDate
ITK_TEST_EXPECT_TRUE(itk::ExposeMetaData<std::string>(dict, "0008|0021", value));
ITK_TEST_EXPECT_EQUAL(value, std::string("20030625"));

// (0010,0010) PN PatientName
ITK_TEST_EXPECT_TRUE(itk::ExposeMetaData<std::string>(dict, "0010|0010", value));
ITK_TEST_EXPECT_EQUAL(value, std::string("Wes Turner"));

// (0010,0040) CS PatientSex
ITK_TEST_EXPECT_TRUE(itk::ExposeMetaData<std::string>(dict, "0010|0040", value));
ITK_TEST_EXPECT_EQUAL(value, std::string("O"));

// (0028,0004) CS PhotometricInterpretation
ITK_TEST_EXPECT_TRUE(itk::ExposeMetaData<std::string>(dict, "0028|0004", value));
ITK_TEST_EXPECT_EQUAL(value, std::string("MONOCHROME2"));

std::cout << "Test finished." << std::endl;
return EXIT_SUCCESS;
}
Loading