Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GDCM Secondary Capture spatial metadata #4521

Merged
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
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
bafybeiai2acs4g7k6aeneziwgi3tw2i33idmnzclpyh4mt4dqvgktca7gu
12 changes: 12 additions & 0 deletions Modules/IO/DCMTK/test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions Modules/IO/DCMTK/test/Input/visible-male-rgb-slice.dcm.cid
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
bafybeiadtxwvshbmde3aywkzlzcudcoxypafxrshabby6gmdmtawapjzre
4 changes: 4 additions & 0 deletions Modules/IO/GDCM/src/itkGDCMImageIO.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -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())
Expand Down Expand Up @@ -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);
Copy link
Member

@issakomi issakomi Apr 16, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@thewtex Maybe something like

#if (!defined(ITK_USE_SYSTEM_GDCM) || ((GDCM_MAJOR_VERSION == 3 && GDCM_MINOR_VERSION == 0 && GDCM_BUILD_VERSION > 23) || GDCM_MAJOR_VERSION > 3))
#endif

around SetSecondaryCaptureImagePlaneModule? Otherwise, it may cause build failures using external GDCM. There is currently no version of GDCM with this feature.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@issakomi good idea, added here: #4601


gdcm::ImageReader reader;
reader.SetFileName(m_FileName.c_str());
Expand Down
1 change: 1 addition & 0 deletions Modules/IO/GDCM/test/Baseline/Lily.mha.cid
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
bafkreidhatfhe2gdne5k5pyw2rfvnuu3qmedytq7mrdjckmw6zrc5x3srq
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
bafybeib67xvtf44xwu4rxapchxqjflfutheq3wja47qvniz4gdwrqkqujy
15 changes: 14 additions & 1 deletion Modules/IO/GDCM/test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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}
Expand Down
1 change: 1 addition & 0 deletions Modules/IO/GDCM/test/Input/visible-male-rgb-slice.dcm.cid
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
bafybeiadtxwvshbmde3aywkzlzcudcoxypafxrshabby6gmdmtawapjzre
1 change: 1 addition & 0 deletions Modules/IO/ImageBase/src/itkIOConfigure.h.in
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion Modules/ThirdParty/GDCM/src/gdcm/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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 << ", ";
Expand Down Expand Up @@ -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 )
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -473,7 +473,7 @@ template<> class EncodingImplementation<VR::VRBINARY> {
assert( _is ); // Is stream valid ?
_is.read( reinterpret_cast<char*>(data+0), type_size);
for(unsigned long i=1; i<length; ++i) {
assert( _is );
if( _is )
_is.read( reinterpret_cast<char*>(data+i), type_size );
}
//ByteSwap<T>::SwapRangeFromSwapCodeIntoSystem(data,
Expand All @@ -489,7 +489,7 @@ template<> class EncodingImplementation<VR::VRBINARY> {
assert( _is ); // Is stream valid ?
_is.read( reinterpret_cast<char*>(data+0), type_size);
for(unsigned long i=1; i<length; ++i) {
assert( _is );
if( _is )
_is.read( reinterpret_cast<char*>(data+i), type_size );
}
//ByteSwap<T>::SwapRangeFromSwapCodeIntoSystem(data,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}
Expand All @@ -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;
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -1065,8 +1085,8 @@ static bool IsDPathInSet(std::set<DPath> 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()) {
Expand Down Expand Up @@ -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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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++)
{
Expand All @@ -119,15 +124,15 @@ 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++)
{
x[i] /= den;
}
}
x = Values+3;
if ( (den = Norm(x)) != 0.0 )
if ( (den = NormImpl(x)) != 0.0 )
{
for (int i=0; i < 3; i++)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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; }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<VR::SH, VM::VM1>(
ds, PrivateTag(0x0021, 0x0022, "SIEMENS MR SDS 01"));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
#include <io.h>
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 <unistd.h> // ftruncate
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -421,6 +421,7 @@ bool ImageChangeTransferSyntax::Change()
if( !b )
{
gdcmErrorMacro( "Error in getting buffer from input image." );
delete bv0;
return false;
}
pixeldata.SetValue( *bv0 );
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 )
Expand Down Expand Up @@ -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) )
Expand Down Expand Up @@ -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 )
{
Expand All @@ -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 &)
Expand Down
Loading
Loading