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 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/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/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 54c9b8ef057..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 @@ -397,7 +410,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/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 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 diff --git a/Modules/ThirdParty/GDCM/src/gdcm/CMakeLists.txt b/Modules/ThirdParty/GDCM/src/gdcm/CMakeLists.txt index d1406758dcd..f28289d724a 100644 --- a/Modules/ThirdParty/GDCM/src/gdcm/CMakeLists.txt +++ b/Modules/ThirdParty/GDCM/src/gdcm/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/Modules/ThirdParty/GDCM/src/gdcm/Source/DataStructureAndEncodingDefinition/gdcmCSAHeader.cxx b/Modules/ThirdParty/GDCM/src/gdcm/Source/DataStructureAndEncodingDefinition/gdcmCSAHeader.cxx index ad944ac6462..3fe9075c03b 100644 --- a/Modules/ThirdParty/GDCM/src/gdcm/Source/DataStructureAndEncodingDefinition/gdcmCSAHeader.cxx +++ b/Modules/ThirdParty/GDCM/src/gdcm/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/Modules/ThirdParty/GDCM/src/gdcm/Source/DataStructureAndEncodingDefinition/gdcmElement.h b/Modules/ThirdParty/GDCM/src/gdcm/Source/DataStructureAndEncodingDefinition/gdcmElement.h index b49b093dcbd..15fb3a117b6 100644 --- a/Modules/ThirdParty/GDCM/src/gdcm/Source/DataStructureAndEncodingDefinition/gdcmElement.h +++ b/Modules/ThirdParty/GDCM/src/gdcm/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/Modules/ThirdParty/GDCM/src/gdcm/Source/MediaStorageAndFileFormat/gdcmCleaner.cxx b/Modules/ThirdParty/GDCM/src/gdcm/Source/MediaStorageAndFileFormat/gdcmCleaner.cxx index e1600152608..0d0ffe28b09 100644 --- a/Modules/ThirdParty/GDCM/src/gdcm/Source/MediaStorageAndFileFormat/gdcmCleaner.cxx +++ b/Modules/ThirdParty/GDCM/src/gdcm/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/Modules/ThirdParty/GDCM/src/gdcm/Source/MediaStorageAndFileFormat/gdcmCleaner.h b/Modules/ThirdParty/GDCM/src/gdcm/Source/MediaStorageAndFileFormat/gdcmCleaner.h index 4d8c351abe7..bf2ad54430b 100644 --- a/Modules/ThirdParty/GDCM/src/gdcm/Source/MediaStorageAndFileFormat/gdcmCleaner.h +++ b/Modules/ThirdParty/GDCM/src/gdcm/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/Modules/ThirdParty/GDCM/src/gdcm/Source/MediaStorageAndFileFormat/gdcmDirectionCosines.cxx b/Modules/ThirdParty/GDCM/src/gdcm/Source/MediaStorageAndFileFormat/gdcmDirectionCosines.cxx index 275d74855a6..119a7b4a839 100644 --- a/Modules/ThirdParty/GDCM/src/gdcm/Source/MediaStorageAndFileFormat/gdcmDirectionCosines.cxx +++ b/Modules/ThirdParty/GDCM/src/gdcm/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/Modules/ThirdParty/GDCM/src/gdcm/Source/MediaStorageAndFileFormat/gdcmDirectionCosines.h b/Modules/ThirdParty/GDCM/src/gdcm/Source/MediaStorageAndFileFormat/gdcmDirectionCosines.h index 73717fdc0ca..1313146cf08 100644 --- a/Modules/ThirdParty/GDCM/src/gdcm/Source/MediaStorageAndFileFormat/gdcmDirectionCosines.h +++ b/Modules/ThirdParty/GDCM/src/gdcm/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/Modules/ThirdParty/GDCM/src/gdcm/Source/MediaStorageAndFileFormat/gdcmEquipmentManufacturer.cxx b/Modules/ThirdParty/GDCM/src/gdcm/Source/MediaStorageAndFileFormat/gdcmEquipmentManufacturer.cxx index c3bd504237a..2ada1f9f1fc 100644 --- a/Modules/ThirdParty/GDCM/src/gdcm/Source/MediaStorageAndFileFormat/gdcmEquipmentManufacturer.cxx +++ b/Modules/ThirdParty/GDCM/src/gdcm/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/Modules/ThirdParty/GDCM/src/gdcm/Source/MediaStorageAndFileFormat/gdcmFileStreamer.cxx b/Modules/ThirdParty/GDCM/src/gdcm/Source/MediaStorageAndFileFormat/gdcmFileStreamer.cxx index 7723b0a83f9..c35cc716dba 100644 --- a/Modules/ThirdParty/GDCM/src/gdcm/Source/MediaStorageAndFileFormat/gdcmFileStreamer.cxx +++ b/Modules/ThirdParty/GDCM/src/gdcm/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/Modules/ThirdParty/GDCM/src/gdcm/Source/MediaStorageAndFileFormat/gdcmImageChangeTransferSyntax.cxx b/Modules/ThirdParty/GDCM/src/gdcm/Source/MediaStorageAndFileFormat/gdcmImageChangeTransferSyntax.cxx index fcb61e611d2..9457c5e9baa 100644 --- a/Modules/ThirdParty/GDCM/src/gdcm/Source/MediaStorageAndFileFormat/gdcmImageChangeTransferSyntax.cxx +++ b/Modules/ThirdParty/GDCM/src/gdcm/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/Modules/ThirdParty/GDCM/src/gdcm/Source/MediaStorageAndFileFormat/gdcmImageCodec.cxx b/Modules/ThirdParty/GDCM/src/gdcm/Source/MediaStorageAndFileFormat/gdcmImageCodec.cxx index deba8ab004c..2c25a90f9ba 100644 --- a/Modules/ThirdParty/GDCM/src/gdcm/Source/MediaStorageAndFileFormat/gdcmImageCodec.cxx +++ b/Modules/ThirdParty/GDCM/src/gdcm/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/Modules/ThirdParty/GDCM/src/gdcm/Source/MediaStorageAndFileFormat/gdcmImageHelper.cxx b/Modules/ThirdParty/GDCM/src/gdcm/Source/MediaStorageAndFileFormat/gdcmImageHelper.cxx index d8973f41304..888412067f7 100644 --- a/Modules/ThirdParty/GDCM/src/gdcm/Source/MediaStorageAndFileFormat/gdcmImageHelper.cxx +++ b/Modules/ThirdParty/GDCM/src/gdcm/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/Modules/ThirdParty/GDCM/src/gdcm/Source/MediaStorageAndFileFormat/gdcmImageHelper.h b/Modules/ThirdParty/GDCM/src/gdcm/Source/MediaStorageAndFileFormat/gdcmImageHelper.h index 2f70231d4b7..764939f4dba 100644 --- a/Modules/ThirdParty/GDCM/src/gdcm/Source/MediaStorageAndFileFormat/gdcmImageHelper.h +++ b/Modules/ThirdParty/GDCM/src/gdcm/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/Modules/ThirdParty/GDCM/src/gdcm/Source/MediaStorageAndFileFormat/gdcmJPEG2000Codec.cxx b/Modules/ThirdParty/GDCM/src/gdcm/Source/MediaStorageAndFileFormat/gdcmJPEG2000Codec.cxx index 10ac23cca4b..430a24a87fd 100644 --- a/Modules/ThirdParty/GDCM/src/gdcm/Source/MediaStorageAndFileFormat/gdcmJPEG2000Codec.cxx +++ b/Modules/ThirdParty/GDCM/src/gdcm/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/Modules/ThirdParty/GDCM/src/gdcm/Source/MediaStorageAndFileFormat/gdcmLookupTable.cxx b/Modules/ThirdParty/GDCM/src/gdcm/Source/MediaStorageAndFileFormat/gdcmLookupTable.cxx index 0d5a99c4002..2c566923b4b 100644 --- a/Modules/ThirdParty/GDCM/src/gdcm/Source/MediaStorageAndFileFormat/gdcmLookupTable.cxx +++ b/Modules/ThirdParty/GDCM/src/gdcm/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/Modules/ThirdParty/GDCM/src/gdcm/Source/MediaStorageAndFileFormat/gdcmPixmapReader.cxx b/Modules/ThirdParty/GDCM/src/gdcm/Source/MediaStorageAndFileFormat/gdcmPixmapReader.cxx index 9c30ff8b9de..258a23c1fcc 100644 --- a/Modules/ThirdParty/GDCM/src/gdcm/Source/MediaStorageAndFileFormat/gdcmPixmapReader.cxx +++ b/Modules/ThirdParty/GDCM/src/gdcm/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/Modules/ThirdParty/GDCM/src/gdcm/Source/MediaStorageAndFileFormat/gdcmRAWCodec.cxx b/Modules/ThirdParty/GDCM/src/gdcm/Source/MediaStorageAndFileFormat/gdcmRAWCodec.cxx index 19f73939994..46392461e14 100644 --- a/Modules/ThirdParty/GDCM/src/gdcm/Source/MediaStorageAndFileFormat/gdcmRAWCodec.cxx +++ b/Modules/ThirdParty/GDCM/src/gdcm/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/Modules/ThirdParty/GDCM/src/gdcm/Source/MediaStorageAndFileFormat/gdcmSplitMosaicFilter.cxx b/Modules/ThirdParty/GDCM/src/gdcm/Source/MediaStorageAndFileFormat/gdcmSplitMosaicFilter.cxx index d3c43ebf872..6f368e0c51a 100644 --- a/Modules/ThirdParty/GDCM/src/gdcm/Source/MediaStorageAndFileFormat/gdcmSplitMosaicFilter.cxx +++ b/Modules/ThirdParty/GDCM/src/gdcm/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/Modules/ThirdParty/GDCM/src/gdcm/Source/MediaStorageAndFileFormat/gdcmSplitMosaicFilter.h b/Modules/ThirdParty/GDCM/src/gdcm/Source/MediaStorageAndFileFormat/gdcmSplitMosaicFilter.h index 7f30b0a25de..06f281a3dbd 100644 --- a/Modules/ThirdParty/GDCM/src/gdcm/Source/MediaStorageAndFileFormat/gdcmSplitMosaicFilter.h +++ b/Modules/ThirdParty/GDCM/src/gdcm/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/Modules/ThirdParty/GDCM/src/gdcm/Utilities/gdcmext/csa.c b/Modules/ThirdParty/GDCM/src/gdcm/Utilities/gdcmext/csa.c index bbab6b3914d..517de950e35 100644 --- a/Modules/ThirdParty/GDCM/src/gdcm/Utilities/gdcmext/csa.c +++ b/Modules/ThirdParty/GDCM/src/gdcm/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);