From 836f7a7454ae0edd2713ecb60d66da11349d3a0d Mon Sep 17 00:00:00 2001 From: Mathieu Malaterre Date: Mon, 29 Apr 2024 00:51:12 -0700 Subject: [PATCH] Check for Secondary Capture spacing following DICOM Part 3 Sect A.8.1.3 Following the Secondary Capture Image IOD Module Note: > If Image Position (Patient) (0020,0032) and Image Orientation (Patient) (0020,0037) (from the Image Plane Module) are present, then the values of Pixel Spacing (0028,0030) (from the Image Plane Module and the Basic Pixel Spacing Calibration Macro included from the SC Image Module) are intended to be used for 3D spatial computations, rather than any values of Nominal Scanned Pixel Spacing (0018,2010) (from the SC Image Module), which may also be present. Ref: * https://dicom.nema.org/medical/dicom/current/output/chtml/part03/sect_A.8.html#sect_A.8.1.3 Co-authored-by: Mihail Isakov Co-authored-by: Matt McCormick Co-authored-by: Steve Pieper Co-authored-by: Sean McBride --- .../gdcmImageHelper.cxx | 37 +++++- .../gdcmImageHelper.h | 1 + .../Cxx/CMakeLists.txt | 1 + .../Cxx/TestImageHelper.cxx | 2 + .../Cxx/TestImageHelper3.cxx | 124 ++++++++++++++++++ 5 files changed, 162 insertions(+), 3 deletions(-) create mode 100644 Testing/Source/MediaStorageAndFileFormat/Cxx/TestImageHelper3.cxx diff --git a/Source/MediaStorageAndFileFormat/gdcmImageHelper.cxx b/Source/MediaStorageAndFileFormat/gdcmImageHelper.cxx index 888412067..43df3ddb1 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; +// By default, this is off, if you want behavior documented in DICOM CP 2330, turn it on bool ImageHelper::SecondaryCaptureImagePlaneModule = false; static bool GetOriginValueFromSequence(const DataSet& ds, const Tag& tfgs, std::vector &ori) @@ -1278,6 +1279,9 @@ Tag ImageHelper::GetSpacingTagFromMediaStorage(MediaStorage const &ms) if( ImageHelper::SecondaryCaptureImagePlaneModule ) { // Make SecondaryCaptureImagePlaneModule act as ForcePixelSpacing // This is different from Basic Pixel Spacing Calibration Macro Attributes + // + // Per the note: https://dicom.nema.org/medical/dicom/current/output/chtml/part03/sect_A.8.html#sect_A.8.1.3 + gdcmDebugMacro( "FIXME: Multiple tags can identify Secondary Capture spacing. This function should not be used for Secondary Capture data." ); t = Tag(0x0028,0x0030); } else { t = Tag(0x0018,0x2010); @@ -1372,6 +1376,13 @@ Warning - Dicom dataset contains attributes not present in standard DICOM IOD - case MediaStorage::UltrasoundMultiFrameImageStorageRetired: // SC: case MediaStorage::SecondaryCaptureImageStorage: + // (0018,0088) DS [3] # 2, 1 SpacingBetweenSlices + if( ImageHelper::SecondaryCaptureImagePlaneModule ) { + t = Tag(0x0018,0x0088); + } else { + t = Tag(0xffff,0xffff); + } + break; case MediaStorage::MultiframeSingleBitSecondaryCaptureImageStorage: case MediaStorage::MultiframeGrayscaleByteSecondaryCaptureImageStorage: case MediaStorage::MultiframeGrayscaleWordSecondaryCaptureImageStorage: @@ -1471,7 +1482,25 @@ std::vector ImageHelper::GetSpacingValue(File const & f) } } - Tag spacingtag = GetSpacingTagFromMediaStorage(ms); + Tag spacingtag = Tag(0xffff,0xffff); + if( ms == MediaStorage::SecondaryCaptureImageStorage && SecondaryCaptureImagePlaneModule ) + { + // See the note: https://dicom.nema.org/medical/dicom/current/output/chtml/part03/sect_A.8.html#sect_A.8.1.3 + if( ds.FindDataElement( Tag(0x0028,0x0030) ) ) + { + // Type 1C in 'SC Image' (for calibrated images) + spacingtag = Tag(0x0028,0x0030); + } + else if( ds.FindDataElement( Tag(0x0018,0x2010) ) ) + { + // Type 3 in 'SC Image' + spacingtag = Tag(0x0018,0x2010); + } + } + else + { + spacingtag = GetSpacingTagFromMediaStorage(ms); + } if( spacingtag != Tag(0xffff,0xffff) && ds.FindDataElement( spacingtag ) && !ds.GetDataElement( spacingtag ).IsEmpty() ) { const DataElement& de = ds.GetDataElement( spacingtag ); @@ -1972,7 +2001,7 @@ void ImageHelper::SetOriginValue(DataSet & ds, const Image & image) ms.SetFromDataSet(ds); assert( MediaStorage::IsImage( ms ) ); - if( ms == MediaStorage::SecondaryCaptureImageStorage ) + if( ms == MediaStorage::SecondaryCaptureImageStorage && !ImageHelper::SecondaryCaptureImagePlaneModule ) { // https://sourceforge.net/p/gdcm/bugs/322/ // default behavior is simply to pass @@ -1986,6 +2015,7 @@ void ImageHelper::SetOriginValue(DataSet & ds, const Image & image) && ms != MediaStorage::PETImageStorage //&& ms != MediaStorage::ComputedRadiographyImageStorage && ms != MediaStorage::SegmentationStorage + && ms != MediaStorage::SecondaryCaptureImageStorage /* CP 2330 */ && ms != MediaStorage::MultiframeSingleBitSecondaryCaptureImageStorage && ms != MediaStorage::MultiframeGrayscaleByteSecondaryCaptureImageStorage && ms != MediaStorage::MultiframeGrayscaleWordSecondaryCaptureImageStorage @@ -2123,7 +2153,7 @@ void ImageHelper::SetDirectionCosinesValue(DataSet & ds, const std::vector GetSpacingValue(File const & f); + /// \warning You need to call SetSpacingValue after SetOriginValue / SetDirectionCosinesValue static void SetSpacingValue(DataSet & ds, const std::vector & spacing); /// DO NOT USE diff --git a/Testing/Source/MediaStorageAndFileFormat/Cxx/CMakeLists.txt b/Testing/Source/MediaStorageAndFileFormat/Cxx/CMakeLists.txt index 56c6fcf22..25671857a 100644 --- a/Testing/Source/MediaStorageAndFileFormat/Cxx/CMakeLists.txt +++ b/Testing/Source/MediaStorageAndFileFormat/Cxx/CMakeLists.txt @@ -39,6 +39,7 @@ set(MSFF_TEST_SRCS TestOrientation.cxx TestIconImage.cxx TestImageHelper.cxx + TestImageHelper3.cxx TestImageToImageFilter.cxx TestImageChangeTransferSyntax1.cxx #TestImageChangePhotometricInterpretation.cxx diff --git a/Testing/Source/MediaStorageAndFileFormat/Cxx/TestImageHelper.cxx b/Testing/Source/MediaStorageAndFileFormat/Cxx/TestImageHelper.cxx index 714141d16..51515cb1f 100644 --- a/Testing/Source/MediaStorageAndFileFormat/Cxx/TestImageHelper.cxx +++ b/Testing/Source/MediaStorageAndFileFormat/Cxx/TestImageHelper.cxx @@ -19,6 +19,8 @@ int TestImageHelper(int, char *[]) { + // Test written before DICOM CP 2330, + if( gdcm::ImageHelper::GetSecondaryCaptureImagePlaneModule() ) return 1; // gdcm::ImageHelper sh; const double pos[] = { 0,0,0, 1,1,1}; diff --git a/Testing/Source/MediaStorageAndFileFormat/Cxx/TestImageHelper3.cxx b/Testing/Source/MediaStorageAndFileFormat/Cxx/TestImageHelper3.cxx new file mode 100644 index 000000000..0619ffc54 --- /dev/null +++ b/Testing/Source/MediaStorageAndFileFormat/Cxx/TestImageHelper3.cxx @@ -0,0 +1,124 @@ +/*========================================================================= + + Program: GDCM (Grassroots DICOM). A DICOM library + + Copyright (c) 2006-2011 Mathieu Malaterre + All rights reserved. + See Copyright.txt or http://gdcm.sourceforge.net/Copyright.html for details. + + This software is distributed WITHOUT ANY WARRANTY; without even + the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + PURPOSE. See the above copyright notice for more information. + +=========================================================================*/ +#include "gdcmImageHelper.h" +#include "gdcmMediaStorage.h" +#include "gdcmDataSet.h" +#include "gdcmAttribute.h" +#include "gdcmDirectionCosines.h" +#include "gdcmImage.h" +#include "gdcmFile.h" + +int TestImageHelper3(int, char *[]) +{ + + std::vector impos; + impos.resize(3); + impos[0] = 1; + impos[1] = 2; + impos[2] = 3; + std::vector spacing; + spacing.resize(3); + spacing[0] = 0.6; + spacing[1] = 0.5; + spacing[2] = 0.4; + + std::vector dircos; + dircos.resize(6); + dircos[0] = 0; + dircos[1] = 1; + dircos[2] = 0; + dircos[3] = 1; + dircos[4] = 0; + dircos[5] = 0; + + gdcm::Image img; + img.SetNumberOfDimensions(3); + img.SetOrigin( impos.data() ); + img.SetSpacing( spacing.data() ); + img.SetDirectionCosines( dircos.data() ); + // Try SC + gdcm::File file; +{ + gdcm::ImageHelper::SetSecondaryCaptureImagePlaneModule( false ); + gdcm::MediaStorage ms( gdcm::MediaStorage::SecondaryCaptureImageStorage ); + gdcm::DataSet&ds = file.GetDataSet(); + + gdcm::DataElement de( gdcm::Tag(0x0008, 0x0016) ); + const char* msstr = gdcm::MediaStorage::GetMSString(ms); + de.SetByteValue( msstr, (uint32_t)strlen(msstr) ); + de.SetVR( gdcm::Attribute<0x0008, 0x0016>::GetVR() ); + ds.Insert( de ); + + gdcm::ImageHelper::SetDirectionCosinesValue( ds, dircos ); + gdcm::ImageHelper::SetOriginValue( ds, img ); + gdcm::ImageHelper::SetSpacingValue( ds, spacing ); + + std::cout << ds << std::endl; + + gdcm::Tag nominal(0x0018,0x2010 ); + if( !ds.FindDataElement( nominal ) ) return 1; + gdcm::Tag iop(0x0020,0x0037); + if( ds.FindDataElement( iop ) ) return 1; + + std::vector ret = gdcm::ImageHelper::GetSpacingValue( file ); + // warning;: only two dim: + if( ret[0] != spacing[0] || ret[1] != spacing[1] ) { + std::cerr << ret[0] << "," << ret[1] << std::endl; + return 1; + } +} + gdcm::File cp2330file; +{ + // New CP 2330 behavior + gdcm::ImageHelper::SetSecondaryCaptureImagePlaneModule( true ); + gdcm::MediaStorage ms( gdcm::MediaStorage::SecondaryCaptureImageStorage ); + gdcm::DataSet&ds = cp2330file.GetDataSet(); + + gdcm::DataElement de( gdcm::Tag(0x0008, 0x0016) ); + const char* msstr = gdcm::MediaStorage::GetMSString(ms); + de.SetByteValue( msstr, (uint32_t)strlen(msstr) ); + de.SetVR( gdcm::Attribute<0x0008, 0x0016>::GetVR() ); + ds.Insert( de ); + + // New CP 2330 behavior + gdcm::ImageHelper::SetDirectionCosinesValue( ds, dircos ); + gdcm::ImageHelper::SetOriginValue( ds, img ); + gdcm::ImageHelper::SetSpacingValue( ds, spacing ); + + std::cout << ds << std::endl; + + // previous call added the attribute + gdcm::Tag pixelspacing(0x0028,0x0030); + if( !ds.FindDataElement( pixelspacing ) ) return 1; + gdcm::Tag iop(0x0020,0x0037); + if( !ds.FindDataElement( iop ) ) return 1; + + std::vector ret = gdcm::ImageHelper::GetSpacingValue( cp2330file ); + if( ret != spacing ) { + std::cerr << ret[0] << "," << ret[1] << "," << ret[2] << std::endl; + return 1; + } + // make sure legacy still works: + ret = gdcm::ImageHelper::GetSpacingValue( file ); + if( ret[0] != spacing[0] || ret[1] != spacing[1] || ret[2] != 1 ) { + std::cerr << ret[0] << "," << ret[1] << std::endl; + return 1; + } + +} + + + std::cout << "success" << std::endl; + return 0; +}