From 11630a0436587e0247b1e0dbca000b29ae265124 Mon Sep 17 00:00:00 2001 From: Matt McCormick Date: Fri, 15 Mar 2024 15:40:19 -0400 Subject: [PATCH 1/4] BUG: Increase size limite for GDCM privatedicts.xml file > The path 'Modules/ThirdParty/GDCM/src/gdcm/Source/DataDictionary/privatedicts.xml' has size 1033 KiB, greater than allowed 1024 KiB. --- .gitattributes | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitattributes b/.gitattributes index b27748fa62e..970b0808063 100644 --- a/.gitattributes +++ b/.gitattributes @@ -52,6 +52,7 @@ CMakeLists.txt whitespace=tab-in-indent,no-lf-at-eof our-cmake-style Modules/Numerics/FEM/src/dsrc2c.c hooks-max-size=260000 Modules/ThirdParty/** hooks-max-size=300000 hooks.style= Modules/ThirdParty/ZLIB/src/itkzlib-ng/crc32_braid_tbl.h hooks-max-size=700000 +Modules/ThirdParty/GDCM/src/gdcm/Source/DataDictionary/privatedicts.xml hooks-max-size=400000 Modules/Filtering/Denoising/include/itkPatchBasedDenoisingImageFilter.hxx hooks-max-size=120000 Documentation/docs/releases/* hooks-max-size=300000 From 919cf4241788235bc9a4099f7fbd69e05b170155 Mon Sep 17 00:00:00 2001 From: GDCM Upstream Date: Mon, 25 Mar 2024 01:03:12 -0700 Subject: [PATCH 2/4] GDCM 2024-03-25 (06091299) Code extracted from: https://github.com/malaterre/GDCM.git at commit 06091299f2227f8470a4468821d231b760337ca0 (release). --- CMakeLists.txt | 2 +- .../gdcmCSAHeader.cxx | 10 +- .../gdcmElement.h | 4 +- .../MediaStorageAndFileFormat/gdcmCleaner.cxx | 33 ++++- .../MediaStorageAndFileFormat/gdcmCleaner.h | 3 + .../gdcmDirectionCosines.cxx | 13 +- .../gdcmDirectionCosines.h | 3 + .../gdcmEquipmentManufacturer.cxx | 5 +- .../gdcmFileStreamer.cxx | 2 +- .../gdcmImageChangeTransferSyntax.cxx | 1 + .../gdcmImageCodec.cxx | 17 ++- .../gdcmImageHelper.cxx | 28 +++- .../gdcmImageHelper.h | 8 ++ .../gdcmJPEG2000Codec.cxx | 9 +- .../gdcmLookupTable.cxx | 5 +- .../gdcmPixmapReader.cxx | 8 +- .../gdcmRAWCodec.cxx | 11 +- .../gdcmSplitMosaicFilter.cxx | 121 +++++++++++++++--- .../gdcmSplitMosaicFilter.h | 9 ++ Utilities/gdcmext/csa.c | 23 +++- 20 files changed, 255 insertions(+), 60 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 44531b695ef..345f2973208 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -17,7 +17,7 @@ endif() #---------------------------------------------------------------------------- project(GDCM - VERSION 3.0.22 + VERSION 3.0.23 LANGUAGES CXX C ) ## NOTE: the "DESCRIPTION" feature of project() was introduced in cmake 3.10.0 diff --git a/Source/DataStructureAndEncodingDefinition/gdcmCSAHeader.cxx b/Source/DataStructureAndEncodingDefinition/gdcmCSAHeader.cxx index ad944ac6462..3fe9075c03b 100644 --- a/Source/DataStructureAndEncodingDefinition/gdcmCSAHeader.cxx +++ b/Source/DataStructureAndEncodingDefinition/gdcmCSAHeader.cxx @@ -910,7 +910,8 @@ bool check_mapping(uint32_t syngodt, const char *vr) { static const unsigned int max = sizeof(mapping) / sizeof(equ); const equ *p = mapping; - assert( syngodt <= mapping[max-1].syngodt ); (void)max; + if( syngodt > mapping[max-1].syngodt ) return false; + assert( syngodt <= mapping[max-1].syngodt ); while(p->syngodt < syngodt ) { //std::cout << "mapping:" << p->vr << std::endl; @@ -1098,6 +1099,11 @@ bool CSAHeader::LoadFromDataElement(DataElement const &de) char vr[4]; ss.read(vr, 4); // In dataset without magic signature (OLD FORMAT) vr[3] is garbage... + if( vr[2] != 0 ) + { + gdcmErrorMacro( "Garbage data. Stopping CSA parsing." ); + return false; + } assert( /*vr[3] == 0 &&*/ vr[2] == 0 ); csael.SetVR( VR::GetVRTypeFromFile(vr) ); //std::cout << "VR " << vr << ", "; @@ -1131,8 +1137,10 @@ bool CSAHeader::LoadFromDataElement(DataElement const &de) uint32_t item_xx[4]; ss.read((char*)&item_xx, 4*sizeof(uint32_t)); SwapperNoOp::SwapArray(item_xx,4); + if( item_xx[2] != 77 && item_xx[2] != 205 ) return false; assert( item_xx[2] == 77 || item_xx[2] == 205 ); uint32_t len = item_xx[1]; // 2nd element + if( item_xx[0] != item_xx[1] || item_xx[1] != item_xx[3] ) return false; assert( item_xx[0] == item_xx[1] && item_xx[1] == item_xx[3] ); if( len ) { diff --git a/Source/DataStructureAndEncodingDefinition/gdcmElement.h b/Source/DataStructureAndEncodingDefinition/gdcmElement.h index b49b093dcbd..15fb3a117b6 100644 --- a/Source/DataStructureAndEncodingDefinition/gdcmElement.h +++ b/Source/DataStructureAndEncodingDefinition/gdcmElement.h @@ -473,7 +473,7 @@ template<> class EncodingImplementation { assert( _is ); // Is stream valid ? _is.read( reinterpret_cast(data+0), type_size); for(unsigned long i=1; i(data+i), type_size ); } //ByteSwap::SwapRangeFromSwapCodeIntoSystem(data, @@ -489,7 +489,7 @@ template<> class EncodingImplementation { assert( _is ); // Is stream valid ? _is.read( reinterpret_cast(data+0), type_size); for(unsigned long i=1; i(data+i), type_size ); } //ByteSwap::SwapRangeFromSwapCodeIntoSystem(data, diff --git a/Source/MediaStorageAndFileFormat/gdcmCleaner.cxx b/Source/MediaStorageAndFileFormat/gdcmCleaner.cxx index e1600152608..0d0ffe28b09 100644 --- a/Source/MediaStorageAndFileFormat/gdcmCleaner.cxx +++ b/Source/MediaStorageAndFileFormat/gdcmCleaner.cxx @@ -538,10 +538,12 @@ struct Cleaner::impl { bool AllMissingPrivateCreator; bool AllGroupLength; bool AllIllegal; + bool WhenScrubFails; impl() : AllMissingPrivateCreator(true), AllGroupLength(true), - AllIllegal(true) {} + AllIllegal(true), + WhenScrubFails(false) {} enum ACTION { NONE, EMPTY, REMOVE, SCRUB }; enum ACTION ComputeAction(File const &file, DataSet &ds, @@ -668,6 +670,10 @@ struct Cleaner::impl { bool RemoveMissingPrivateCreator(Tag const & /*t*/) { return false; } void RemoveAllGroupLength(bool remove) { AllGroupLength = remove; } void RemoveAllIllegal(bool remove) { AllIllegal = remove; } + void EmptyWhenScrubFails(bool empty) { WhenScrubFails = empty; } + + bool CleanCSAImage(DataSet &ds, const DataElement &de); + bool CleanCSASeries(DataSet &ds, const DataElement &de); }; static VR ComputeDictVR(File &file, DataSet &ds, DataElement const &de) { @@ -840,7 +846,7 @@ static inline bool bs_is_signature(const ByteValue *bv, const char *str) { return false; } -static bool CleanCSAImage(DataSet &ds, const DataElement &de) { +bool Cleaner::impl::CleanCSAImage(DataSet &ds, const DataElement &de) { const ByteValue *bv = de.GetByteValue(); // fast path: if (!bv) return true; @@ -888,6 +894,13 @@ static bool CleanCSAImage(DataSet &ds, const DataElement &de) { gdcmDebugMacro("Zero-out CSA header"); return true; } + // fallback logic: + if (WhenScrubFails && is_signature(bv, sv10)) { + // so SV10 header has been identified, but we failed to 'scrub', let's + // empty it: + ds.Replace(clean); + return true; + } gdcmErrorMacro("Failure to call CleanCSAImage"); return false; } @@ -900,7 +913,7 @@ static bool CleanCSAImage(DataSet &ds, const DataElement &de) { return true; } -static bool CleanCSASeries(DataSet &ds, const DataElement &de) { +bool Cleaner::impl::CleanCSASeries(DataSet &ds, const DataElement &de) { const ByteValue *bv = de.GetByteValue(); // fast path: if (!bv) return true; @@ -944,6 +957,13 @@ static bool CleanCSASeries(DataSet &ds, const DataElement &de) { gdcmDebugMacro("Zero-out CSA header"); return true; } + // fallback logic: + if (WhenScrubFails && is_signature(bv, sv10)) { + // so SV10 header has been identified, but we failed to 'scrub', let's + // empty it: + ds.Replace(clean); + return true; + } gdcmErrorMacro("Failure to call CleanCSASeries"); return false; } @@ -1065,8 +1085,8 @@ static bool IsDPathInSet(std::set const &aset, DPath const dpath) { } Cleaner::impl::ACTION Cleaner::impl::ComputeAction( - File const & /*file*/, DataSet &ds, const DataElement &de, VR const &ref_dict_vr, - const std::string &tag_path) { + File const & /*file*/, DataSet &ds, const DataElement &de, + VR const &ref_dict_vr, const std::string &tag_path) { const Tag &tag = de.GetTag(); // Group Length & Illegal cannot be preserved so it is safe to do them now: if (tag.IsGroupLength()) { @@ -1302,6 +1322,9 @@ void Cleaner::RemoveAllGroupLength(bool remove) { pimpl->RemoveAllGroupLength(remove); } void Cleaner::RemoveAllIllegal(bool remove) { pimpl->RemoveAllIllegal(remove); } +void Cleaner::EmptyWhenScrubFails(bool empty) { + pimpl->EmptyWhenScrubFails(empty); +} bool Cleaner::Clean() { DataSet &ds = F->GetDataSet(); diff --git a/Source/MediaStorageAndFileFormat/gdcmCleaner.h b/Source/MediaStorageAndFileFormat/gdcmCleaner.h index 4d8c351abe7..bf2ad54430b 100644 --- a/Source/MediaStorageAndFileFormat/gdcmCleaner.h +++ b/Source/MediaStorageAndFileFormat/gdcmCleaner.h @@ -65,6 +65,9 @@ class GDCM_EXPORT Cleaner : public Subject { /// Should I remove all illegal attribute. Default: true void RemoveAllIllegal(bool remove); + /// Should I empty instead of scrub upon failure + void EmptyWhenScrubFails(bool empty); + /// main loop bool Clean(); diff --git a/Source/MediaStorageAndFileFormat/gdcmDirectionCosines.cxx b/Source/MediaStorageAndFileFormat/gdcmDirectionCosines.cxx index 275d74855a6..119a7b4a839 100644 --- a/Source/MediaStorageAndFileFormat/gdcmDirectionCosines.cxx +++ b/Source/MediaStorageAndFileFormat/gdcmDirectionCosines.cxx @@ -98,15 +98,20 @@ double DirectionCosines::Dot() const } // static function is within gdcm:: namespace, so should not pollute too much on UNIX -static inline double Norm(const double x[3]) +static inline double NormImpl(const double x[3]) { return sqrt(x[0]*x[0] + x[1]*x[1] + x[2]*x[2]); } +double DirectionCosines::Norm(const double v[3]) +{ + return NormImpl(v); +} + void DirectionCosines::Normalize(double v[3]) { double den; - if ( (den = Norm(v)) != 0.0 ) + if ( (den = NormImpl(v)) != 0.0 ) { for (int i=0; i < 3; i++) { @@ -119,7 +124,7 @@ void DirectionCosines::Normalize() { double *x = Values; double den; - if ( (den = Norm(x)) != 0.0 ) + if ( (den = NormImpl(x)) != 0.0 ) { for (int i=0; i < 3; i++) { @@ -127,7 +132,7 @@ void DirectionCosines::Normalize() } } x = Values+3; - if ( (den = Norm(x)) != 0.0 ) + if ( (den = NormImpl(x)) != 0.0 ) { for (int i=0; i < 3; i++) { diff --git a/Source/MediaStorageAndFileFormat/gdcmDirectionCosines.h b/Source/MediaStorageAndFileFormat/gdcmDirectionCosines.h index 73717fdc0ca..1313146cf08 100644 --- a/Source/MediaStorageAndFileFormat/gdcmDirectionCosines.h +++ b/Source/MediaStorageAndFileFormat/gdcmDirectionCosines.h @@ -49,6 +49,9 @@ class GDCM_EXPORT DirectionCosines /// Normalize in-place static void Normalize(double v[3]); + /// Return norm of the vector + static double Norm(const double v[3]); + /// Make the class behave like a const double * operator const double* () const { return Values; } diff --git a/Source/MediaStorageAndFileFormat/gdcmEquipmentManufacturer.cxx b/Source/MediaStorageAndFileFormat/gdcmEquipmentManufacturer.cxx index c3bd504237a..2ada1f9f1fc 100644 --- a/Source/MediaStorageAndFileFormat/gdcmEquipmentManufacturer.cxx +++ b/Source/MediaStorageAndFileFormat/gdcmEquipmentManufacturer.cxx @@ -219,8 +219,11 @@ EquipmentManufacturer::Type EquipmentManufacturer::Compute(DataSet const& ds) { manu.SetFromDataSet(ds); manufacturer = manu.GetValue().Trim(); // TODO: contributing equipement ? - } else { + } + if( manufacturer.empty() ) + { // MFSPLIT export seems to remove the attribute completely: + // or in some case make it empty manufacturer = GetPrivateTagValueOrEmpty( ds, PrivateTag(0x0021, 0x0022, "SIEMENS MR SDS 01")); } diff --git a/Source/MediaStorageAndFileFormat/gdcmFileStreamer.cxx b/Source/MediaStorageAndFileFormat/gdcmFileStreamer.cxx index 7723b0a83f9..c35cc716dba 100644 --- a/Source/MediaStorageAndFileFormat/gdcmFileStreamer.cxx +++ b/Source/MediaStorageAndFileFormat/gdcmFileStreamer.cxx @@ -35,7 +35,7 @@ #include typedef int64_t off64_t; #else -#if defined(__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) +#if defined(__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__EMSCRIPTEN__) # define off64_t off_t #endif #include // ftruncate diff --git a/Source/MediaStorageAndFileFormat/gdcmImageChangeTransferSyntax.cxx b/Source/MediaStorageAndFileFormat/gdcmImageChangeTransferSyntax.cxx index fcb61e611d2..9457c5e9baa 100644 --- a/Source/MediaStorageAndFileFormat/gdcmImageChangeTransferSyntax.cxx +++ b/Source/MediaStorageAndFileFormat/gdcmImageChangeTransferSyntax.cxx @@ -421,6 +421,7 @@ bool ImageChangeTransferSyntax::Change() if( !b ) { gdcmErrorMacro( "Error in getting buffer from input image." ); + delete bv0; return false; } pixeldata.SetValue( *bv0 ); diff --git a/Source/MediaStorageAndFileFormat/gdcmImageCodec.cxx b/Source/MediaStorageAndFileFormat/gdcmImageCodec.cxx index deba8ab004c..2c25a90f9ba 100644 --- a/Source/MediaStorageAndFileFormat/gdcmImageCodec.cxx +++ b/Source/MediaStorageAndFileFormat/gdcmImageCodec.cxx @@ -460,7 +460,8 @@ bool ImageCodec::CleanupUnusedBits(char * data8, size_t datalen) smask = (uint16_t)( smask << ( 16 - (PF.GetBitsAllocated() - PF.GetBitsStored() + 1) )); // nmask : to propagate sign bit on negative values - int16_t nmask = (int16_t)(0x8000U >> ( PF.GetBitsAllocated() - PF.GetBitsStored() - 1 )); + int16_t nmask = (int16_t)0x8000; + nmask = (int16_t)(nmask >> ( PF.GetBitsAllocated() - PF.GetBitsStored() - 1 )); uint16_t *start = (uint16_t*)data; for( uint16_t *p = start ; p != start + datalen / 2; ++p ) @@ -515,7 +516,8 @@ bool ImageCodec::DoOverlayCleanup(std::istream &is, std::ostream &os) smask = (uint16_t)( smask << ( 16 - (PF.GetBitsAllocated() - PF.GetBitsStored() + 1) )); // nmask : to propagate sign bit on negative values - int16_t nmask = (int16_t)(0x8000U >> ( PF.GetBitsAllocated() - PF.GetBitsStored() - 1 )); + int16_t nmask = (int16_t)0x8000; + nmask = (int16_t)(nmask >> ( PF.GetBitsAllocated() - PF.GetBitsStored() - 1 )); uint16_t c; while( is.read((char*)&c,2) ) @@ -673,6 +675,7 @@ bool ImageCodec::DecodeByStreams(std::istream &is, std::ostream &os) // Do the overlay cleanup (cleanup the unused bits) // must be the last operation (duh!) + bool copySuccess = false; if ( PF.GetBitsAllocated() != PF.GetBitsStored() && PF.GetBitsAllocated() != 8 ) { @@ -683,21 +686,23 @@ bool ImageCodec::DecodeByStreams(std::istream &is, std::ostream &os) // Sigh, I finally found someone not declaring that unused bits where not zero: // gdcmConformanceTests/dcm4chee_unusedbits_not_zero.dcm if( NeedOverlayCleanup ) - DoOverlayCleanup(*cur_is,os); + { + copySuccess = DoOverlayCleanup(*cur_is, os); + } else { // Once the issue with IMAGES/JPLY/RG3_JPLY aka gdcmData/D_CLUNIE_RG3_JPLY.dcm is solved the previous // code will be replace with a simple call to: - DoSimpleCopy(*cur_is,os); + copySuccess = DoSimpleCopy(*cur_is, os); } } else { assert( PF.GetBitsAllocated() == PF.GetBitsStored() ); - DoSimpleCopy(*cur_is,os); + copySuccess = DoSimpleCopy(*cur_is, os); } - return true; + return copySuccess; } bool ImageCodec::IsValid(PhotometricInterpretation const &) diff --git a/Source/MediaStorageAndFileFormat/gdcmImageHelper.cxx b/Source/MediaStorageAndFileFormat/gdcmImageHelper.cxx index d8973f41304..888412067f7 100644 --- a/Source/MediaStorageAndFileFormat/gdcmImageHelper.cxx +++ b/Source/MediaStorageAndFileFormat/gdcmImageHelper.cxx @@ -48,6 +48,7 @@ namespace gdcm bool ImageHelper::ForceRescaleInterceptSlope = false; bool ImageHelper::PMSRescaleInterceptSlope = true; bool ImageHelper::ForcePixelSpacing = false; +bool ImageHelper::SecondaryCaptureImagePlaneModule = false; static bool GetOriginValueFromSequence(const DataSet& ds, const Tag& tfgs, std::vector &ori) { @@ -205,6 +206,7 @@ static bool ComputeZSpacingFromIPP(const DataSet &ds, double &zspacing) double normal[3]; DirectionCosines dc( cosines.data() ); dc.Cross( normal ); + DirectionCosines::Normalize(normal); // For each item SequenceOfItems::SizeType nitems = sqi->GetNumberOfItems(); @@ -577,7 +579,7 @@ std::vector ImageHelper::GetOriginValue(File const & f) // else const Tag timagepositionpatient(0x0020, 0x0032); - if( ms != MediaStorage::SecondaryCaptureImageStorage && ds.FindDataElement( timagepositionpatient ) ) + if( (ms != MediaStorage::SecondaryCaptureImageStorage || SecondaryCaptureImagePlaneModule) && ds.FindDataElement( timagepositionpatient ) ) { const DataElement& de = ds.GetDataElement( timagepositionpatient ); Attribute<0x0020,0x0032> at = {{0,0,0}}; // default value if empty @@ -729,7 +731,7 @@ std::vector ImageHelper::GetDirectionCosinesValue(File const & f) } dircos.resize( 6 ); - if( ms == MediaStorage::SecondaryCaptureImageStorage || !GetDirectionCosinesFromDataSet(ds, dircos) ) + if( (ms == MediaStorage::SecondaryCaptureImageStorage && !SecondaryCaptureImagePlaneModule) || !GetDirectionCosinesFromDataSet(ds, dircos) ) { dircos[0] = 1; dircos[1] = 0; @@ -773,6 +775,16 @@ bool ImageHelper::GetForcePixelSpacing() return ForcePixelSpacing; } +void ImageHelper::SetSecondaryCaptureImagePlaneModule(bool b) +{ + SecondaryCaptureImagePlaneModule = b; +} + +bool ImageHelper::GetSecondaryCaptureImagePlaneModule() +{ + return SecondaryCaptureImagePlaneModule; +} + bool GetRescaleInterceptSlopeValueFromDataSet(const DataSet& ds, std::vector & interceptslope) { Attribute<0x0028,0x1052> at1; @@ -1187,7 +1199,7 @@ std::vector ImageHelper::GetRescaleInterceptSlopeValue(File const & f) // Case is: MAGNETOM Prisma / syngo MR XA30A with MFSPLIT interceptslope[0] = dummy[0]; interceptslope[1] = dummy[1]; - gdcmWarningMacro( "Forcing Modality LUT used for MR Image Storage: [" << dummy[0] << "," << dummy[1] << "]" ); + gdcmDebugMacro( "Forcing Modality LUT used for MR Image Storage: [" << dummy[0] << "," << dummy[1] << "]" ); } } } @@ -1263,6 +1275,14 @@ Tag ImageHelper::GetSpacingTagFromMediaStorage(MediaStorage const &ms) t = Tag(0x3002,0x0011); // ImagePlanePixelSpacing break; case MediaStorage::SecondaryCaptureImageStorage: + if( ImageHelper::SecondaryCaptureImagePlaneModule ) { + // Make SecondaryCaptureImagePlaneModule act as ForcePixelSpacing + // This is different from Basic Pixel Spacing Calibration Macro Attributes + t = Tag(0x0028,0x0030); + } else { + t = Tag(0x0018,0x2010); + } + break; case MediaStorage::MultiframeSingleBitSecondaryCaptureImageStorage: case MediaStorage::MultiframeGrayscaleByteSecondaryCaptureImageStorage: case MediaStorage::MultiframeGrayscaleWordSecondaryCaptureImageStorage: @@ -2027,6 +2047,7 @@ void ImageHelper::SetOriginValue(DataSet & ds, const Image & image) double normal[3]; dc.Cross( normal ); + DirectionCosines::Normalize(normal); for(unsigned int i = 0; i < dimz; ++i ) { @@ -2896,6 +2917,7 @@ MediaStorage ImageHelper::ComputeMediaStorageFromModality(const char *modality, || pi == PhotometricInterpretation::YBR_RCT || pi == PhotometricInterpretation::YBR_ICT || pi == PhotometricInterpretation::YBR_PARTIAL_420 + || pi == PhotometricInterpretation::YBR_FULL /* PI when coming from other gdcm filter */ || pi == PhotometricInterpretation::YBR_FULL_422 ) && pixeltype.GetBitsAllocated() == 8 && pixeltype.GetBitsStored() == 8 && diff --git a/Source/MediaStorageAndFileFormat/gdcmImageHelper.h b/Source/MediaStorageAndFileFormat/gdcmImageHelper.h index 2f70231d4b7..764939f4dba 100644 --- a/Source/MediaStorageAndFileFormat/gdcmImageHelper.h +++ b/Source/MediaStorageAndFileFormat/gdcmImageHelper.h @@ -85,6 +85,13 @@ class GDCM_EXPORT ImageHelper static void SetForcePixelSpacing(bool); static bool GetForcePixelSpacing(); + /// Opt into Image Plane Module for Secondary Capture Image Storage + /// Enable reading Image Position Patient (IPP), Image Orientation Patient + /// (IOP) and Pixel Spacing (0028,0030) + /// This is a custom extension for some existing dataset (academic) + static void SetSecondaryCaptureImagePlaneModule(bool); + static bool GetSecondaryCaptureImagePlaneModule(); + /// This function checks tags (0x0028, 0x0010) and (0x0028, 0x0011) for the /// rows and columns of the image in pixels (as opposed to actual distances). /// The output is {col , row} @@ -156,6 +163,7 @@ class GDCM_EXPORT ImageHelper static bool ForceRescaleInterceptSlope; static bool PMSRescaleInterceptSlope; static bool ForcePixelSpacing; + static bool SecondaryCaptureImagePlaneModule; }; } // end namespace gdcm diff --git a/Source/MediaStorageAndFileFormat/gdcmJPEG2000Codec.cxx b/Source/MediaStorageAndFileFormat/gdcmJPEG2000Codec.cxx index 10ac23cca4b..430a24a87fd 100644 --- a/Source/MediaStorageAndFileFormat/gdcmJPEG2000Codec.cxx +++ b/Source/MediaStorageAndFileFormat/gdcmJPEG2000Codec.cxx @@ -826,8 +826,13 @@ std::pair JPEG2000Codec::DecodeByStreamsCommon(char *dummy_buffe // ELSCINT1_JP2vsJ2K.dcm // -> prec = 12, bpp = 0, sgnd = 0 - //assert( wr == Dimensions[0] ); - //assert( hr == Dimensions[1] ); + if( wr != Dimensions[0] || hr != Dimensions[1] ) { + gdcmErrorMacro("Invalid dimension"); + delete[] raw; + opj_destroy_codec(dinfo); + opj_image_destroy(image); + return std::pair(nullptr,0); + } if( comp->sgnd != PF.GetPixelRepresentation() ) { PF.SetPixelRepresentation( (uint16_t)comp->sgnd ); diff --git a/Source/MediaStorageAndFileFormat/gdcmLookupTable.cxx b/Source/MediaStorageAndFileFormat/gdcmLookupTable.cxx index 0d5a99c4002..2c566923b4b 100644 --- a/Source/MediaStorageAndFileFormat/gdcmLookupTable.cxx +++ b/Source/MediaStorageAndFileFormat/gdcmLookupTable.cxx @@ -130,7 +130,10 @@ void LookupTable::SetLUT(LookupTableType type, const unsigned char *array, if( !IncompleteLUT ) { - assert( Internal->RGB.size() == 3*Internal->Length[type]*(BitSample/8) ); + if( Internal->RGB.size() != 3*Internal->Length[type]*(BitSample/8) ) { + gdcmErrorMacro( "Invalid length for LUT data" ); + return; + } } // Too funny: 05115014-mr-siemens-avanto-syngo-with-palette-icone.dcm // There is pseudo PALETTE_COLOR LUT in the Icon, if one look carefully the LUT values diff --git a/Source/MediaStorageAndFileFormat/gdcmPixmapReader.cxx b/Source/MediaStorageAndFileFormat/gdcmPixmapReader.cxx index 9c30ff8b9de..258a23c1fcc 100644 --- a/Source/MediaStorageAndFileFormat/gdcmPixmapReader.cxx +++ b/Source/MediaStorageAndFileFormat/gdcmPixmapReader.cxx @@ -306,8 +306,12 @@ static void DoIconImage(const DataSet& rootds, Pixmap& image) unsigned long check = (el_us3.GetValue(0) ? el_us3.GetValue(0) : 65536) * el_us3.GetValue(2) / 8; - assert( check == lut_raw->GetLength() || 2 * check == lut_raw->GetLength() - || check + 1 == lut_raw->GetLength() ); (void)check; + if(!( check == lut_raw->GetLength() || 2 * check == lut_raw->GetLength() + || check + 1 == lut_raw->GetLength() )) { + gdcmErrorMacro( "Icon Sequence is invalid. Giving up" ); + pixeldata.Clear(); + return; + } } else if( ds.FindDataElement( seglut ) ) { diff --git a/Source/MediaStorageAndFileFormat/gdcmRAWCodec.cxx b/Source/MediaStorageAndFileFormat/gdcmRAWCodec.cxx index 19f73939994..46392461e14 100644 --- a/Source/MediaStorageAndFileFormat/gdcmRAWCodec.cxx +++ b/Source/MediaStorageAndFileFormat/gdcmRAWCodec.cxx @@ -112,9 +112,7 @@ bool RAWCodec::DecodeBytes(const char* inBytes, size_t inBufferLength, if(!r) return false; std::string str = os.str(); - //std::string::size_type check = str.size();//unused - if( this->GetPixelFormat() == PixelFormat::UINT12 || this->GetPixelFormat() == PixelFormat::INT12 ) { @@ -135,7 +133,14 @@ bool RAWCodec::DecodeBytes(const char* inBytes, size_t inBufferLength, // DermaColorLossLess.dcm //assert (check == inOutBufferLength || check == inOutBufferLength + 1); // problem with: SIEMENS_GBS_III-16-ACR_NEMA_1.acr - memcpy(outBytes, str.c_str(), inOutBufferLength); + size_t len = str.size(); + if( inOutBufferLength <= len ) + memcpy(outBytes, str.c_str(), inOutBufferLength); + else + { + gdcmWarningMacro( "Requesting too much data. Truncating result" ); + memcpy(outBytes, str.c_str(), len); + } } return r; diff --git a/Source/MediaStorageAndFileFormat/gdcmSplitMosaicFilter.cxx b/Source/MediaStorageAndFileFormat/gdcmSplitMosaicFilter.cxx index d3c43ebf872..6f368e0c51a 100644 --- a/Source/MediaStorageAndFileFormat/gdcmSplitMosaicFilter.cxx +++ b/Source/MediaStorageAndFileFormat/gdcmSplitMosaicFilter.cxx @@ -287,6 +287,96 @@ bool SplitMosaicFilter::ComputeMOSAICSliceNormal( double slicenormalvector[3], b return snvfound; } +bool SplitMosaicFilter::ComputeMOSAICImagePositionPatient( double ret[3], + const double ipp[6], + const double dircos[6], + const double pixelspacing[3], + const unsigned int image_dims[3] , + const unsigned int mosaic_dims[3] , bool inverted) +{ + CSAHeader csa; + DataSet& ds = GetFile().GetDataSet(); + DirectionCosines dc( dircos ); + dc.Normalize(); + const double *dircos_normalized = dc; + const double *x = dircos_normalized; + const double *y = dircos_normalized + 3; + + double ipp_csa[3] = {}; + bool hasIppCsa = false; + MrProtocol mrprot; + // https://www.nmr.mgh.harvard.edu/~greve/dicom-unpack + if( csa.GetMrProtocol(ds, mrprot) ) + { + MrProtocol::SliceArray sa; + bool b = mrprot.GetSliceArray(sa); + if( b ) { + size_t size = sa.Slices.size(); + if( size ) { + // two cases: + if( size == mosaic_dims[2] ) { + // all mosaic have there own slice position, simply need to pick the right one + // Handle inverted case: + size_t index = inverted ? size - 1 : 0; + MrProtocol::Slice & slice = sa.Slices[index]; + MrProtocol::Vector3 & p = slice.Position; + double pos[3]; + pos[0] = p.dSag; + pos[1] = p.dCor; + pos[2] = p.dTra; + for(int i = 0; i < 3; ++i ) { + ipp_csa[i] = pos[i] - mosaic_dims[0] / 2. * pixelspacing[0] * x[i] - mosaic_dims[1] / 2. * pixelspacing[1] * y[i]; + } + hasIppCsa = true; + } else if( size == 1 /*&& mosaic_dims[2] % 2 == 0*/) { + // there is a single SliceArray but multiple mosaics, assume this is exactly the center one + double z[3]={}; + dc.Cross(z); + DirectionCosines::Normalize(z); + size_t index = 0; + MrProtocol::Slice & slice = sa.Slices[index]; + MrProtocol::Vector3 & p = slice.Position; + double pos[3]; + pos[0] = p.dSag; + pos[1] = p.dCor; + pos[2] = p.dTra; + for(int i = 0; i < 3; ++i ) { + ipp_csa[i] = pos[i] - mosaic_dims[0] / 2. * pixelspacing[0] * x[i] - mosaic_dims[1] / 2. * pixelspacing[1] * y[i] + - (mosaic_dims[2] - 1) / 2. * pixelspacing[2] * z[i]; + } + hasIppCsa = true; + } else { + gdcmWarningMacro( "Inconsistent SliceArray: " << size << " vs expected: " << mosaic_dims[2] ); + } + } + } + } + + // https://nipy.org/nibabel/dicom/dicom_mosaic.html#dicom-mosaic + double ipp_dcm[3] = {}; + { + for(int i = 0; i < 3; ++i ) { + ipp_dcm[i] = ipp[i] + (image_dims[0] - mosaic_dims[0]) / 2. * pixelspacing[0] * x[i] + + (image_dims[1] - mosaic_dims[1]) / 2. * pixelspacing[1] * y[i] ; + } + } + if(hasIppCsa ) { + double diff[3]; + for(int i = 0; i < 3; ++i ) { + diff[i] = ipp_dcm[i] - ipp_csa[i]; + } + double n = DirectionCosines::Norm(diff); + if( n > 1e-4 ) { + gdcmWarningMacro( "Inconsistent values for IPP/CSA: (" << ipp_dcm[0] << "," << ipp_dcm[1] << "," << ipp_dcm[2] << ") vs (" << ipp_csa[0] << "," << ipp_csa[1] << "," << ipp_csa[2] << ")" ); + } + } + // no error set origin: + for(int i = 0; i < 3; ++i ) + ret[i] = ipp_dcm[i]; + + return true; +} + bool SplitMosaicFilter::ComputeMOSAICSlicePosition( double pos[3], bool ) { CSAHeader csa; @@ -301,25 +391,6 @@ bool SplitMosaicFilter::ComputeMOSAICSlicePosition( double pos[3], bool ) size_t size = sa.Slices.size(); if( !size ) return false; -#if 0 - { - double z[3]; - for( int i = 0; i < size; ++i ) - { - MrProtocol::Slice & slice = sa.Slices[i]; - MrProtocol::Vector3 & p = slice.Position; - z[0] = p.dSag; - z[1] = p.dCor; - z[2] = p.dTra; - const double snv_dot = DirectionCosines::Dot( slicenormalvector, z ); - if( (1. - snv_dot) < 1e-6 ) - { - gdcmErrorMacro("Invalid direction found"); - return false; - } - } - } -#endif size_t index = 0; MrProtocol::Slice & slice = sa.Slices[index]; @@ -342,7 +413,7 @@ bool SplitMosaicFilter::Split() return false; } const unsigned int div = (unsigned int )ceil(sqrt( (double)dims[2]) ); - bool inverted; + bool inverted = false; double normal[3]; bool hasOriginCSA = true; bool hasNormalCSA = true; @@ -353,13 +424,21 @@ bool SplitMosaicFilter::Split() } (void)hasNormalCSA; double origin[3]; + const Image &inputimage = GetImage(); +#if 0 if( !ComputeMOSAICSlicePosition( origin, inverted ) ) { gdcmWarningMacro( "Origin will not be accurate" ); hasOriginCSA = false; } +#else + if(!ComputeMOSAICImagePositionPatient( origin, inputimage.GetOrigin(), inputimage.GetDirectionCosines(), inputimage.GetSpacing(), inputimage.GetDimensions(), dims, inverted )) + { + gdcmWarningMacro( "Origin will not be accurate" ); + hasOriginCSA = false; + } +#endif - const Image &inputimage = GetImage(); if( inputimage.GetPixelFormat() != PixelFormat::UINT16 ) { gdcmErrorMacro( "Expecting UINT16 PixelFormat" ); diff --git a/Source/MediaStorageAndFileFormat/gdcmSplitMosaicFilter.h b/Source/MediaStorageAndFileFormat/gdcmSplitMosaicFilter.h index 7f30b0a25de..06f281a3dbd 100644 --- a/Source/MediaStorageAndFileFormat/gdcmSplitMosaicFilter.h +++ b/Source/MediaStorageAndFileFormat/gdcmSplitMosaicFilter.h @@ -57,8 +57,17 @@ class GDCM_EXPORT SplitMosaicFilter bool ComputeMOSAICSliceNormal( double dims[3], bool & inverted ); /// Extract the value for ImagePositionPatient (requires inverted flag) + /// Deprecated bool ComputeMOSAICSlicePosition( double pos[3], bool inverted ); + /// Extract the value for ImagePositionPatient + bool ComputeMOSAICImagePositionPatient( double pos[3], + const double ipp[6], + const double dircos[6], + const double pixelspacing[3], + const unsigned int image_dims[3] , + const unsigned int mosaic_dims[3], bool inverted ); + void SetImage(const Image& image); const Image &GetImage() const { return *I; } Image &GetImage() { return *I; } diff --git a/Utilities/gdcmext/csa.c b/Utilities/gdcmext/csa.c index bbab6b3914d..517de950e35 100644 --- a/Utilities/gdcmext/csa.c +++ b/Utilities/gdcmext/csa.c @@ -89,8 +89,15 @@ static void setup_buffer(struct app *self, void *output, const void *input, self->out->write = stream_write; } +//#define CSA_DEBUG + +#ifdef CSA_DEBUG +#define ERROR_RETURN(X, Y) \ + if ((X) != (Y)) assert(0); +#else #define ERROR_RETURN(X, Y) \ if ((X) != (Y)) return false +#endif static size_t fread_mirror(void *ptr, size_t size, size_t nmemb, struct app *self) { @@ -214,16 +221,18 @@ static bool read_info(struct app *self, struct csa_info *i) { s = fread_mirror(&i->vm, sizeof i->vm, 1, self); ERROR_RETURN(s, 1); // vm == 115301884 seems to be ok... - assert(i->vm < 0x6df5dfd); + ERROR_RETURN(i->vm < 0x6df5dfd, true); // vr s = fread_mirror(i->vr, sizeof *i->vr, sizeof i->vr / sizeof *i->vr, self); ERROR_RETURN(s, 4); { const char *s = i->vr; - assert(s[0] >= 'A' && s[0] <= 'Z'); - assert(s[1] >= 'A' && s[1] <= 'Z'); - assert(s[2] == 0); - if (self->csa_type == SV10) assert(s[3] == 0); + ERROR_RETURN(s[0] >= 'A' && s[0] <= 'Z', true); + ERROR_RETURN(s[1] >= 'A' && s[1] <= 'Z', true); + ERROR_RETURN(s[2], 0); + if (self->csa_type == SV10) { + ERROR_RETURN(s[3], 0); + } } // syngodt (signed) s = fread_mirror(&i->syngodt, sizeof i->syngodt, 1, self); @@ -250,15 +259,15 @@ static bool read_data(struct app *self, struct csa_item_data *d) { uint32_t unused; s = fread_mirror(&unused, sizeof unused, 1, self); ERROR_RETURN(s, 1); - assert(unused == d->len || unused == 0x4d || unused == 0xcd); + ERROR_RETURN(unused == d->len || unused == 0x4d || unused == 0xcd, true); } if (d->len != 0) { const uint32_t padding_len = (4 - d->len % 4) % 4; const uint32_t padded_len = ((d->len + 3u) / 4u) * 4u; // (len + 3) & ~0x03 assert(padded_len == d->len + padding_len); // programmer error - d->buffer = (char *)realloc(d->buffer, padded_len); assert(padded_len != 0); + d->buffer = (char *)realloc(d->buffer, padded_len); ERROR_RETURN(d->buffer != NULL, true); s = fread_mirror(d->buffer, sizeof *d->buffer, padded_len, self); ERROR_RETURN(s, padded_len); From f545dd8f5b0ea5fb399da3586aef841d7a183e7c Mon Sep 17 00:00:00 2001 From: Matt McCormick Date: Mon, 18 Mar 2024 15:16:38 -0400 Subject: [PATCH 3/4] ENH: Enable SecondaryCaptureImagePlaneModel in GDCM This enables support for reading image orientation patient and image position patient from DICOM secondary capture images. This addresses needs such as reading Visible Human DICOM color images in 3D Slicer: https://github.com/NA-MIC/ProjectWeek/issues/875 and reading highdicom derived secondary capture DICOM in ITK-Snap: https://github.com/ImagingDataCommons/highdicom/issues/247 Update the RGB tests' baseline that were using a .png baseline because they now output spatial metadata. --- Modules/IO/GDCM/src/itkGDCMImageIO.cxx | 4 ++++ Modules/IO/GDCM/test/Baseline/Lily.mha.cid | 1 + Modules/IO/GDCM/test/CMakeLists.txt | 2 +- Modules/IO/ImageBase/src/itkIOConfigure.h.in | 1 + 4 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 Modules/IO/GDCM/test/Baseline/Lily.mha.cid diff --git a/Modules/IO/GDCM/src/itkGDCMImageIO.cxx b/Modules/IO/GDCM/src/itkGDCMImageIO.cxx index 064620bd1bb..01bd892ad0d 100644 --- a/Modules/IO/GDCM/src/itkGDCMImageIO.cxx +++ b/Modules/IO/GDCM/src/itkGDCMImageIO.cxx @@ -286,6 +286,8 @@ GDCMImageIO::Read(void * pointer) inputFileStream.close(); itkAssertInDebugAndIgnoreInReleaseMacro(gdcm::ImageHelper::GetForceRescaleInterceptSlope()); + // Secondary capture image orientation patient and image position patient support + itkAssertInDebugAndIgnoreInReleaseMacro(gdcm::ImageHelper::GetSecondaryCaptureImagePlaneModule()); gdcm::ImageReader reader; reader.SetFileName(m_FileName.c_str()); if (!reader.Read()) @@ -454,6 +456,8 @@ GDCMImageIO::InternalReadImageInformation() // In general this should be relatively safe to assume gdcm::ImageHelper::SetForceRescaleInterceptSlope(true); + // Secondary capture image orientation patient and image position patient support + gdcm::ImageHelper::SetSecondaryCaptureImagePlaneModule(true); gdcm::ImageReader reader; reader.SetFileName(m_FileName.c_str()); diff --git a/Modules/IO/GDCM/test/Baseline/Lily.mha.cid b/Modules/IO/GDCM/test/Baseline/Lily.mha.cid new file mode 100644 index 00000000000..35cf0d3a7b5 --- /dev/null +++ b/Modules/IO/GDCM/test/Baseline/Lily.mha.cid @@ -0,0 +1 @@ +bafkreidhatfhe2gdne5k5pyw2rfvnuu3qmedytq7mrdjckmw6zrc5x3srq diff --git a/Modules/IO/GDCM/test/CMakeLists.txt b/Modules/IO/GDCM/test/CMakeLists.txt index 54c9b8ef057..46a215c6c25 100644 --- a/Modules/IO/GDCM/test/CMakeLists.txt +++ b/Modules/IO/GDCM/test/CMakeLists.txt @@ -397,7 +397,7 @@ function(AddComplianceTest fileName) --compareCoordinateTolerance 0.001 --compare - DATA{Baseline/Lily.png} + DATA{Baseline/Lily.mha} ${ITK_TEST_OUTPUT_DIR}/itkGDCM_ComplianceTestRGB_${fileName}.mha itkGDCMImageReadWriteTest DATA{Input/Lily/${fileName}.dcm} diff --git a/Modules/IO/ImageBase/src/itkIOConfigure.h.in b/Modules/IO/ImageBase/src/itkIOConfigure.h.in index 68af7eb8a19..d4b70a49d82 100644 --- a/Modules/IO/ImageBase/src/itkIOConfigure.h.in +++ b/Modules/IO/ImageBase/src/itkIOConfigure.h.in @@ -21,6 +21,7 @@ #cmakedefine ITK_SUPPORTS_WCHAR_T_FILENAME_CSTYLEIO #cmakedefine ITK_SUPPORTS_WCHAR_T_FILENAME_IOSTREAMS_CONSTRUCTORS #cmakedefine ITK_SUPPORTS_FDSTREAM_HPP +#cmakedefine ITK_USE_SYSTEM_GDCM /* * Enable the pre-registration of factories for specific image file formats From 8eb077a113ca5e42d498c4549f3348725b1fa285 Mon Sep 17 00:00:00 2001 From: Matt McCormick Date: Tue, 19 Mar 2024 15:27:11 -0400 Subject: [PATCH 4/4] ENH: Add test for reading DICOM secondary capture spatial metadata This is using a Visible Male RGB slice secondary capture DICOM created by David Clunie as an input, described in Issue #4109. --- .../itkDCMTKColorImageSpatialMetadata.nrrd.cid | 1 + Modules/IO/DCMTK/test/CMakeLists.txt | 12 ++++++++++++ .../DCMTK/test/Input/visible-male-rgb-slice.dcm.cid | 1 + .../itkGDCMImageReadWriteTest_SC_RGB.nrrd.cid | 1 + Modules/IO/GDCM/test/CMakeLists.txt | 13 +++++++++++++ .../GDCM/test/Input/visible-male-rgb-slice.dcm.cid | 1 + 6 files changed, 29 insertions(+) create mode 100644 Modules/IO/DCMTK/test/Baseline/itkDCMTKColorImageSpatialMetadata.nrrd.cid create mode 100644 Modules/IO/DCMTK/test/Input/visible-male-rgb-slice.dcm.cid create mode 100644 Modules/IO/GDCM/test/Baseline/itkGDCMImageReadWriteTest_SC_RGB.nrrd.cid create mode 100644 Modules/IO/GDCM/test/Input/visible-male-rgb-slice.dcm.cid diff --git a/Modules/IO/DCMTK/test/Baseline/itkDCMTKColorImageSpatialMetadata.nrrd.cid b/Modules/IO/DCMTK/test/Baseline/itkDCMTKColorImageSpatialMetadata.nrrd.cid new file mode 100644 index 00000000000..a713ea67f80 --- /dev/null +++ b/Modules/IO/DCMTK/test/Baseline/itkDCMTKColorImageSpatialMetadata.nrrd.cid @@ -0,0 +1 @@ +bafybeiai2acs4g7k6aeneziwgi3tw2i33idmnzclpyh4mt4dqvgktca7gu diff --git a/Modules/IO/DCMTK/test/CMakeLists.txt b/Modules/IO/DCMTK/test/CMakeLists.txt index e23c1656cd4..8e0b26a517f 100644 --- a/Modules/IO/DCMTK/test/CMakeLists.txt +++ b/Modules/IO/DCMTK/test/CMakeLists.txt @@ -182,6 +182,18 @@ itk_add_test( DATA{${ITK_DATA_ROOT}/Input/RGBDicomTest.dcm} ${ITK_TEST_OUTPUT_DIR}/itkDCMTKColorImage.png) +itk_add_test( + NAME + itkDCMTKRGBImageIOColorImageSpatialMetadataTest + COMMAND + ITKIODCMTKTestDriver + --compare + DATA{Baseline/itkDCMTKColorImageSpatialMetadata.nrrd} + ${ITK_TEST_OUTPUT_DIR}/itkDCMTKColorImageSpatialMetadata.nrrd + itkDCMTKRGBImageIOTest + DATA{Input/visible-male-rgb-slice.dcm} + ${ITK_TEST_OUTPUT_DIR}/itkDCMTKColorImageSpatialMetadata.nrrd) + itk_add_test( NAME ITKDCMTKLoggerTest diff --git a/Modules/IO/DCMTK/test/Input/visible-male-rgb-slice.dcm.cid b/Modules/IO/DCMTK/test/Input/visible-male-rgb-slice.dcm.cid new file mode 100644 index 00000000000..1fb95e645fa --- /dev/null +++ b/Modules/IO/DCMTK/test/Input/visible-male-rgb-slice.dcm.cid @@ -0,0 +1 @@ +bafybeiadtxwvshbmde3aywkzlzcudcoxypafxrshabby6gmdmtawapjzre diff --git a/Modules/IO/GDCM/test/Baseline/itkGDCMImageReadWriteTest_SC_RGB.nrrd.cid b/Modules/IO/GDCM/test/Baseline/itkGDCMImageReadWriteTest_SC_RGB.nrrd.cid new file mode 100644 index 00000000000..f9cbcbf691d --- /dev/null +++ b/Modules/IO/GDCM/test/Baseline/itkGDCMImageReadWriteTest_SC_RGB.nrrd.cid @@ -0,0 +1 @@ +bafybeib67xvtf44xwu4rxapchxqjflfutheq3wja47qvniz4gdwrqkqujy diff --git a/Modules/IO/GDCM/test/CMakeLists.txt b/Modules/IO/GDCM/test/CMakeLists.txt index 46a215c6c25..95c65032d2f 100644 --- a/Modules/IO/GDCM/test/CMakeLists.txt +++ b/Modules/IO/GDCM/test/CMakeLists.txt @@ -278,6 +278,19 @@ itk_add_test( ${ITK_TEST_OUTPUT_DIR}/itkGDCMImageReadWriteTest_RGB.mha rgb) +itk_add_test( + NAME + itkGDCMImageReadWriteTest_SC_RGB + COMMAND + ITKIOGDCMTestDriver + --compare + DATA{Baseline/itkGDCMImageReadWriteTest_SC_RGB.nrrd} + ${ITK_TEST_OUTPUT_DIR}/itkGDCMImageReadWriteTest_SC_RGB.nrrd + itkGDCMImageReadWriteTest + DATA{Input/visible-male-rgb-slice.dcm} + ${ITK_TEST_OUTPUT_DIR}/itkGDCMImageReadWriteTest_SC_RGB.nrrd + rgb) + itk_add_test( NAME itkGDCM_ComplianceTestRGB_JPEG2000ICT diff --git a/Modules/IO/GDCM/test/Input/visible-male-rgb-slice.dcm.cid b/Modules/IO/GDCM/test/Input/visible-male-rgb-slice.dcm.cid new file mode 100644 index 00000000000..1fb95e645fa --- /dev/null +++ b/Modules/IO/GDCM/test/Input/visible-male-rgb-slice.dcm.cid @@ -0,0 +1 @@ +bafybeiadtxwvshbmde3aywkzlzcudcoxypafxrshabby6gmdmtawapjzre