diff --git a/Modules/IO/DCMTK/test/CMakeLists.txt b/Modules/IO/DCMTK/test/CMakeLists.txt index 448887ef0af..9251879a44b 100644 --- a/Modules/IO/DCMTK/test/CMakeLists.txt +++ b/Modules/IO/DCMTK/test/CMakeLists.txt @@ -1,6 +1,7 @@ itk_module_test() set( ITKIODCMTKTests + itkDCMTKGetDicomTagsTest.cxx itkDCMTKImageIOMultiFrameImageTest.cxx itkDCMTKImageIONoPreambleTest.cxx itkDCMTKImageIOOrthoDirTest.cxx @@ -16,6 +17,71 @@ set( createtestdriver(ITKIODCMTK "${ITKIODCMTK-Test_LIBRARIES}" "${ITKIODCMTKTests}") +# itkDCMTKFileReader.h transitively #include's dcmtk/dcmdata/*.h. ITKIODCMTK +# declares ITKDCMTK as a PRIVATE_DEPENDS (so consumers don't see DCMTK +# headers), but the test driver consumes the public itkDCMTKFileReader.h +# directly and therefore needs the DCMTK include path on its compile line. +# Tracks issue #3820. Reconstruct the include path the same way +# Modules/ThirdParty/DCMTK/CMakeLists.txt populates ITKDCMTK_INCLUDE_DIRS, +# since that variable is not in scope here. +if(NOT ITK_USE_SYSTEM_DCMTK) + set( + _dcmtk_ep_inc + "${CMAKE_BINARY_DIR}/Modules/ThirdParty/DCMTK/ITKDCMTK_ExtProject" + ) + set( + _dcmtk_libs + dcmdata + dcmpstat + dcmsr + dcmqrdb + dcmimgle + dcmimage + dcmjpeg + dcmjpls + dcmnet + dcmwlm + dcmrt + dcmiod + dcmfg + dcmseg + dcmpmap + ofstd + oflog + ) + # IS_DIRECTORY guards were dropped: the ExtProject source/build trees are + # only materialized at build time, not at CMake configure time, so any + # IS_DIRECTORY check here returns false on a clean checkout (issue #6189 + # greptile P1). The paths below are deterministic, and CMake is fine with + # -I flags that name not-yet-existing directories — they just need to + # exist by the time the test driver is compiled, which is guaranteed by + # ITKDCMTK_ExtProject being a transitive build dependency of the test + # driver via the ITKIODCMTK module's PRIVATE_DEPENDS. + foreach(_lib IN LISTS _dcmtk_libs) + target_include_directories( + ITKIODCMTKTestDriver + PRIVATE + "${_dcmtk_ep_inc}/${_lib}/include" + ) + endforeach() + target_include_directories( + ITKIODCMTKTestDriver + PRIVATE + "${CMAKE_BINARY_DIR}/Modules/ThirdParty/DCMTK/ITKDCMTK_ExtProject-build/config/include" + ) + unset(_dcmtk_libs) + unset(_dcmtk_ep_inc) +endif() + +itk_add_test( + NAME itkDCMTKGetDicomTagsTest + COMMAND + ITKIODCMTKTestDriver + itkDCMTKGetDicomTagsTest + DATA{${ITK_DATA_ROOT}/Input/DicomSeries/Image0075.dcm} + ${ITK_TEST_OUTPUT_DIR}/DICOMTags.txt +) + itk_add_test( NAME itkDCMTKImageIOTest1 COMMAND diff --git a/Modules/IO/DCMTK/test/itkDCMTKGetDicomTagsTest.cxx b/Modules/IO/DCMTK/test/itkDCMTKGetDicomTagsTest.cxx new file mode 100644 index 00000000000..9b651c1f512 --- /dev/null +++ b/Modules/IO/DCMTK/test/itkDCMTKGetDicomTagsTest.cxx @@ -0,0 +1,165 @@ +/*========================================================================= + * + * Copyright NumFOCUS + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0.txt + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + *=========================================================================*/ + +#include "itkDCMTKFileReader.h" +#include "itkIntTypes.h" +#include "itkTestingMacros.h" + +#include +#include + + +// Exercises every well-formed VR-typed accessor on itk::DCMTKFileReader against +// a known fixture (Input/DicomSeries/Image0075.dcm). Expected values were +// verified out-of-band with pydicom; see PR description for the discovery +// procedure. Correctness is asserted in-process via ITK_TEST_EXPECT_EQUAL; +// argv[2] receives one tag value per line for diagnostic inspection only. +int +itkDCMTKGetDicomTagsTest(int argc, char * argv[]) +{ + if (argc != 3) + { + std::cerr << "Missing parameters." << std::endl; + std::cerr << "Usage: " << itkNameOfTestExecutableMacro(argv) << " " << std::endl; + return EXIT_FAILURE; + } + + itk::DCMTKFileReader fileReader; + fileReader.SetFileName(argv[1]); + ITK_TRY_EXPECT_NO_EXCEPTION(fileReader.LoadFile()); + + std::ofstream outputFile(argv[2], std::ios::out); + if (!outputFile.is_open()) + { + std::cerr << "Unable to open output file: " << argv[2] << std::endl; + return EXIT_FAILURE; + } + + // Per-call: throwException=false so a missing tag returns EXIT_FAILURE + // and we surface a test failure instead of an exception. + constexpr bool throwException = false; + + // (0008,0021) DA StudyDate + { + std::string actual; + ITK_TEST_EXPECT_EQUAL(fileReader.GetElementDA(0x0008, 0x0021, actual, throwException), EXIT_SUCCESS); + outputFile << actual << std::endl; + ITK_TEST_EXPECT_EQUAL(actual, std::string("20030625")); + } + + // (0008,0030) TM StudyTime + { + std::string actual; + ITK_TEST_EXPECT_EQUAL(fileReader.GetElementTM(0x0008, 0x0030, actual, throwException), EXIT_SUCCESS); + outputFile << actual << std::endl; + ITK_TEST_EXPECT_EQUAL(actual, std::string("152734")); + } + + // (0010,0010) PN PatientName + { + std::string actual; + ITK_TEST_EXPECT_EQUAL(fileReader.GetElementPN(0x0010, 0x0010, actual, throwException), EXIT_SUCCESS); + outputFile << actual << std::endl; + ITK_TEST_EXPECT_EQUAL(actual, std::string("Wes Turner")); + } + + // (0010,0040) CS PatientSex + { + std::string actual; + ITK_TEST_EXPECT_EQUAL(fileReader.GetElementCS(0x0010, 0x0040, actual, throwException), EXIT_SUCCESS); + outputFile << actual << std::endl; + ITK_TEST_EXPECT_EQUAL(actual, std::string("O")); + } + + // (0010,1030) DS PatientWeight (single-element decimal-string) + { + std::string actual; + ITK_TEST_EXPECT_EQUAL(fileReader.GetElementDS(0x0010, 0x1030, actual, throwException), EXIT_SUCCESS); + outputFile << actual << std::endl; + ITK_TEST_EXPECT_EQUAL(actual, std::string("68.039")); + } + + // (0018,0086) IS EchoNumber + { + itk::int32_t actual = 0; + ITK_TEST_EXPECT_EQUAL(fileReader.GetElementIS(0x0018, 0x0086, actual, throwException), EXIT_SUCCESS); + outputFile << actual << std::endl; + ITK_TEST_EXPECT_EQUAL(actual, itk::int32_t{ 1 }); + } + + // (0019,105a) FL AcquisitionDuration (private tag, in GE private dictionary) + { + float actual = 0.0F; + ITK_TEST_EXPECT_EQUAL(fileReader.GetElementFL(0x0019, 0x105A, actual, throwException), EXIT_SUCCESS); + outputFile << actual << std::endl; + ITK_TEST_EXPECT_EQUAL(actual, 414273984.0F); + } + + // (0021,1057) SL LocsPer3DSlab (private tag) + { + itk::int32_t actual = 0; + ITK_TEST_EXPECT_EQUAL(fileReader.GetElementSL(0x0021, 0x1057, actual, throwException), EXIT_SUCCESS); + outputFile << actual << std::endl; + ITK_TEST_EXPECT_EQUAL(actual, itk::int32_t{ 124 }); + } + + // (0028,0002) US SamplesPerPixel + { + unsigned short actual = 0; + ITK_TEST_EXPECT_EQUAL(fileReader.GetElementUS(0x0028, 0x0002, actual, throwException), EXIT_SUCCESS); + outputFile << actual << std::endl; + ITK_TEST_EXPECT_EQUAL(actual, static_cast(1)); + } + + // (0028,0004) CS PhotometricInterpretation + { + std::string actual; + ITK_TEST_EXPECT_EQUAL(fileReader.GetElementCS(0x0028, 0x0004, actual, throwException), EXIT_SUCCESS); + outputFile << actual << std::endl; + ITK_TEST_EXPECT_EQUAL(actual, std::string("MONOCHROME2")); + } + + // (0028,0010) US Rows + { + unsigned short actual = 0; + ITK_TEST_EXPECT_EQUAL(fileReader.GetElementUS(0x0028, 0x0010, actual, throwException), EXIT_SUCCESS); + outputFile << actual << std::endl; + ITK_TEST_EXPECT_EQUAL(actual, static_cast(256)); + } + + // (0028,0100) US BitsAllocated + { + unsigned short actual = 0; + ITK_TEST_EXPECT_EQUAL(fileReader.GetElementUS(0x0028, 0x0100, actual, throwException), EXIT_SUCCESS); + outputFile << actual << std::endl; + ITK_TEST_EXPECT_EQUAL(actual, static_cast(16)); + } + + // (0043,1061) UI ScannerStudyEntityUID (GE private; in the DCMTK private + // dictionary so VR resolves to UI) + { + std::string actual; + ITK_TEST_EXPECT_EQUAL(fileReader.GetElementUI(0x0043, 0x1061, actual, throwException), EXIT_SUCCESS); + outputFile << actual << std::endl; + ITK_TEST_EXPECT_EQUAL(actual, std::string("1.2.840.113619.2.133.1762890640.1886.1055165015.961")); + } + + outputFile.close(); + std::cout << "Test finished." << std::endl; + return EXIT_SUCCESS; +}