diff --git a/Core/Code/Common/mitkTestingMacros.h b/Core/Code/Common/mitkTestingMacros.h index 5948b205b7..6242b87722 100644 --- a/Core/Code/Common/mitkTestingMacros.h +++ b/Core/Code/Common/mitkTestingMacros.h @@ -1,374 +1,378 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ +#ifndef mitkTestingMacros_h +#define mitkTestingMacros_h + #include #include #include #include #include #include #include #include #include "cppunit/ui/text/TestRunner.h" namespace mitk { /** @brief Indicate a failed test. */ class TestFailedException : public std::exception { public: TestFailedException() {} }; } /** * @brief Output some text without generating a terminating newline. Include * * @ingroup MITKTestingAPI */ #define MITK_TEST_OUTPUT_NO_ENDL(x) \ std::cout x ; /** * @brief Output some text. * * @ingroup MITKTestingAPI */ #define MITK_TEST_OUTPUT(x) \ MITK_TEST_OUTPUT_NO_ENDL(x << "\n") /** * @brief Do some general test preparations. Must be called first in the * main test function. * * @deprecatedSince{2013_09} Use MITK_TEST_SUITE_REGISTRATION instead. * @ingroup MITKTestingAPI */ #define MITK_TEST_BEGIN(testName) \ std::string mitkTestName(#testName); \ mitk::TestManager::GetInstance()->Initialize(); \ try { /** * @brief Fail and finish test with message MSG * * @deprecatedSince{2013_09} Use CPPUNIT_FAIL instead * @ingroup MITKTestingAPI */ #define MITK_TEST_FAILED_MSG(MSG) \ MITK_TEST_OUTPUT(MSG) \ throw mitk::TestFailedException(); /** * @brief Must be called last in the main test function. * * @deprecatedSince{2013_09} Use MITK_TEST_SUITE_REGISTRATION instead. * @ingroup MITKTestingAPI */ #define MITK_TEST_END() \ } catch (mitk::TestFailedException ex) { \ MITK_TEST_OUTPUT(<< "Further test execution skipped.") \ mitk::TestManager::GetInstance()->TestFailed(); \ } catch (std::exception ex) { \ MITK_TEST_OUTPUT(<< "std::exception occured " << ex.what()) \ mitk::TestManager::GetInstance()->TestFailed(); \ } \ if (mitk::TestManager::GetInstance()->NumberOfFailedTests() > 0) { \ MITK_TEST_OUTPUT(<< mitkTestName << ": [DONE FAILED] , subtests passed: " << \ mitk::TestManager::GetInstance()->NumberOfPassedTests() << " failed: " << \ mitk::TestManager::GetInstance()->NumberOfFailedTests() ) \ return EXIT_FAILURE; \ } else { \ MITK_TEST_OUTPUT(<< mitkTestName << ": " \ << mitk::TestManager::GetInstance()->NumberOfPassedTests() \ << " tests [DONE PASSED]") \ return EXIT_SUCCESS; \ } \ /** * @deprecatedSince{2013_09} Use CPPUNIT_ASSERT or CPPUNIT_ASSERT_MESSAGE instead. */ #define MITK_TEST_CONDITION(COND,MSG) \ MITK_TEST_OUTPUT_NO_ENDL(<< MSG) \ if ( ! (COND) ) { \ mitk::TestManager::GetInstance()->TestFailed(); \ MITK_TEST_OUTPUT(<< " [FAILED]\n" << "In " << __FILE__ \ << ", line " << __LINE__ \ << ": " #COND " : [FAILED]") \ } else { \ MITK_TEST_OUTPUT(<< " [PASSED]") \ mitk::TestManager::GetInstance()->TestPassed(); \ } /** * @deprecatedSince{2013_09} Use CPPUNIT_ASSERT or CPPUNIT_ASSERT_MESSAGE instead. */ #define MITK_TEST_CONDITION_REQUIRED(COND,MSG) \ MITK_TEST_OUTPUT_NO_ENDL(<< MSG) \ if ( ! (COND) ) { \ MITK_TEST_FAILED_MSG(<< " [FAILED]\n" << " +--> in " << __FILE__ \ << ", line " << __LINE__ \ << ", expression is false: \"" #COND "\"") \ } else { \ MITK_TEST_OUTPUT(<< " [PASSED]") \ mitk::TestManager::GetInstance()->TestPassed(); \ } /** * \brief Begin block which should be checked for exceptions * * @deprecatedSince{2013_09} Use CPPUNIT_ASSERT_THROW instead. * @ingroup MITKTestingAPI * * This macro, together with MITK_TEST_FOR_EXCEPTION_END, can be used * to test whether a code block throws an expected exception. The test FAILS if the * exception is NOT thrown. A simple example: * MITK_TEST_FOR_EXCEPTION_BEGIN(itk::ImageFileReaderException) typedef itk::ImageFileReader< itk::Image > ReaderType; ReaderType::Pointer reader = ReaderType::New(); reader->SetFileName("/tmp/not-existing"); reader->Update(); MITK_TEST_FOR_EXCEPTION_END(itk::ImageFileReaderException) * */ #define MITK_TEST_FOR_EXCEPTION_BEGIN(EXCEPTIONCLASS) \ try { /** * @deprecatedSince{2013_09} */ #define MITK_TEST_FOR_EXCEPTION_END(EXCEPTIONCLASS) \ mitk::TestManager::GetInstance()->TestFailed(); \ MITK_TEST_OUTPUT( << "Expected an '" << #EXCEPTIONCLASS << "' exception. [FAILED]") \ } \ catch (EXCEPTIONCLASS) { \ MITK_TEST_OUTPUT( << "Caught an expected '" << #EXCEPTIONCLASS \ << "' exception. [PASSED]") \ mitk::TestManager::GetInstance()->TestPassed(); \ } /** * @brief Simplified version of MITK_TEST_FOR_EXCEPTION_BEGIN / END for * a single statement * * @deprecatedSince{2013_09} Use CPPUNIT_ASSERT_THROW instead. * @ingroup MITKTestingAPI */ #define MITK_TEST_FOR_EXCEPTION(EXCEPTIONCLASS, STATEMENT) \ MITK_TEST_FOR_EXCEPTION_BEGIN(EXCEPTIONCLASS) \ STATEMENT ; \ MITK_TEST_FOR_EXCEPTION_END(EXCEPTIONCLASS) /** * @brief Testing macro to test if two objects are equal. * * @ingroup MITKTestingAPI * * This macro uses mitk::eps and the corresponding mitk::Equal methods for all * comparisons and will give verbose output on the dashboard/console. * Feel free to implement mitk::Equal for your own datatype or purpose. * * @deprecatedSince{2013_09} Use MITK_ASSERT_EQUAL instead. * * @param OBJ1 First object. * @param OBJ2 Second object. * @param MSG Message to appear with the test. */ #define MITK_TEST_EQUAL(OBJ1,OBJ2,MSG) \ MITK_TEST_CONDITION_REQUIRED( mitk::Equal(OBJ1, OBJ2, mitk::eps, true)==true, MSG) /** * @brief Testing macro to test if two objects are equal. * * @ingroup MITKTestingAPI * * This macro uses mitk::eps and the corresponding mitk::Equal methods for all * comparisons and will give verbose output on the dashboard/console. * Feel free to implement mitk::Equal for your own datatype or purpose. * * @param EXPECTED First object. * @param ACTUAL Second object. * @param MSG Message to appear with the test. */ #define MITK_ASSERT_EQUAL(EXPECTED, ACTUAL, MSG) \ CPPUNIT_ASSERT_MESSAGE(MSG, mitk::Equal(EXPECTED, ACTUAL, mitk::eps, true)) /** * @brief Testing macro to test if two objects are not equal. * * @ingroup MITKTestingAPI * * This macro uses mitk::eps and the corresponding mitk::Equal methods for all * comparisons and will give verbose output on the dashboard/console. * * @deprecatedSince{2013_09} Use MITK_ASSERT_NOT_EQUAL instead. * * @param OBJ1 First object. * @param OBJ2 Second object. * @param MSG Message to appear with the test. * * \sa MITK_TEST_EQUAL */ #define MITK_TEST_NOT_EQUAL(OBJ1,OBJ2,MSG) \ MITK_TEST_CONDITION_REQUIRED( mitk::Equal(OBJ1, OBJ2, mitk::eps, true)==false, MSG) /** * @brief Testing macro to test if two objects are not equal. * * @ingroup MITKTestingAPI * * This macro uses mitk::eps and the corresponding mitk::Equal methods for all * comparisons and will give verbose output on the dashboard/console. * * @param OBJ1 First object. * @param OBJ2 Second object. * @param MSG Message to appear with the test. * * \sa MITK_ASSERT_EQUAL */ #define MITK_ASSERT_NOT_EQUAL(OBJ1, OBJ2, MSG) \ CPPUNIT_ASSERT_MESSAGE(MSG, !mitk::Equal(OBJ1, OBJ2, mitk::eps, true)) /** * @brief Registers the given test suite. * * @ingroup MITKTestingAPI * * @param TESTSUITE_NAME The name of the test suite class, without "TestSuite" * at the end. */ #define MITK_TEST_SUITE_REGISTRATION(TESTSUITE_NAME) \ int TESTSUITE_NAME ## Test(int /*argc*/, char* /*argv*/[]) \ { \ CppUnit::TextUi::TestRunner runner; \ runner.addTest(TESTSUITE_NAME ## TestSuite::suite()); \ runner.run(); \ return EXIT_SUCCESS; \ } /** * @brief Adds a test to the current test suite. * * @ingroup MITKTestingAPI * * Use this macro after the CPPUNIT_TEST_SUITE() macro to add test cases. * The macro internally just calls the CPPUNIT_TEST macro. * * @param TESTMETHOD The name of the member funtion test. */ #define MITK_TEST(TESTMETHOD) CPPUNIT_TEST(TESTMETHOD) /** * @brief Adds a parameterized test to the current test suite. * * @ingroup MITKTestingAPI * * Use this macro after the CPPUNIT_TEST_SUITE() macro to add test cases * which need custom parameters. * * @param TESTMETHOD The name of the member function test. * @param ARGS A std::vector object containing test parameter. * * @note Use the macro MITK_PARAMETERIZED_TEST only if you know what * you are doing. If you are not sure, use MITK_TEST instead. */ #define MITK_PARAMETERIZED_TEST(TESTMETHOD, ARGS) \ { \ std::string testName = #TESTMETHOD; \ for (std::size_t i = 0; i < ARGS.size(); ++i) \ { \ testName += "_" + ARGS[i]; \ } \ CPPUNIT_TEST_SUITE_ADD_TEST( \ ( new mitk::TestCaller( \ context.getTestNameFor(testName), \ &TestFixtureType::TESTMETHOD, \ context.makeFixture(), args ) ) ); \ } /** * @brief Adds a parameterized test to the current test suite. * * @ingroup MITKTestingAPI * * Use this macro after the CPPUNIT_TEST_SUITE() macro to add test cases * which need parameters from the command line. * * @warning Use the macro MITK_PARAMETERIZED_CMD_LINE_TEST only * if you know what you are doing. If you are not sure, use * MITK_TEST instead. MITK_PARAMETERIZED_CMD_LINE_TEST is meant * for migrating from ctest to CppUnit. If you implement new * tests, the MITK_TEST macro will be sufficient. * * @param TESTMETHOD The name of the member function test. */ #define MITK_PARAMETERIZED_CMD_LINE_TEST(TESTMETHOD) \ CPPUNIT_TEST_SUITE_ADD_TEST( \ ( new mitk::TestCaller( \ context.getTestNameFor( #TESTMETHOD), \ &TestFixtureType::TESTMETHOD, \ context.makeFixture() ) ) ); /** * @brief Adds a parameterized test to the current test suite. * * @ingroup MITKTestingAPI * * Use this macro after the CPPUNIT_TEST_SUITE() macro to add test cases * which need one custom parameter. * * @param TESTMETHOD The name of the member function test. * @param arg1 A custom string parameter being passed to the fixture. * * @note Use the macro MITK_PARAMETERIZED_TEST_1 only if you know what * you are doing. If you are not sure, use MITK_TEST instead. * * @see MITK_PARAMETERIZED_TEST */ #define MITK_PARAMETERIZED_TEST_1(TESTMETHOD, arg1) \ { \ std::vector args; \ args.push_back(arg1); \ MITK_PARAMETERIZED_TEST(TESTMETHOD, args) \ } /** * @brief Adds a parameterized test to the current test suite. * * @ingroup MITKTestingAPI * * Use this macro after the CPPUNIT_TEST_SUITE() macro to add test cases * which need two custom parameter. * * @param TESTMETHOD The name of the member function test. * @param arg1 A custom string parameter being passed to the fixture. * * @note Use the macro MITK_PARAMETERIZED_TEST_2 only if you know what * you are doing. If you are not sure, use MITK_TEST instead. * * @see MITK_PARAMETERIZED_TEST */ #define MITK_PARAMETERIZED_TEST_2(TESTMETHOD, arg1, arg2) \ { \ std::vector args; \ args.push_back(arg1); \ args.push_back(arg2); \ MITK_PARAMETERIZED_TEST(TESTMETHOD, args) \ } +#endif diff --git a/Core/Code/IO/mitkDicomSeriesReader.cpp b/Core/Code/IO/mitkDicomSeriesReader.cpp index bf3b680f4d..9f928a4ffb 100644 --- a/Core/Code/IO/mitkDicomSeriesReader.cpp +++ b/Core/Code/IO/mitkDicomSeriesReader.cpp @@ -1,1779 +1,1779 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ // uncomment for learning more about the internal sorting mechanisms //#define MBILOG_ENABLE_DEBUG #include #include #include #include #include #include #include #include #include #include #include "mitkProperties.h" namespace mitk { std::string DicomSeriesReader::ReaderImplementationLevelToString( const ReaderImplementationLevel& enumValue ) { switch (enumValue) { case ReaderImplementationLevel_Supported: return "Supported"; case ReaderImplementationLevel_PartlySupported: return "PartlySupported"; case ReaderImplementationLevel_Implemented: return "Implemented"; case ReaderImplementationLevel_Unsupported: return "Unsupported"; default: return ""; }; } std::string DicomSeriesReader::PixelSpacingInterpretationToString( const PixelSpacingInterpretation& enumValue ) { switch (enumValue) { case PixelSpacingInterpretation_SpacingInPatient: return "In Patient"; case PixelSpacingInterpretation_SpacingAtDetector: return "At Detector"; case PixelSpacingInterpretation_SpacingUnknown: return "Unknown spacing"; default: return ""; }; } const DicomSeriesReader::TagToPropertyMapType& DicomSeriesReader::GetDICOMTagsToMITKPropertyMap() { static bool initialized = false; static TagToPropertyMapType dictionary; if (!initialized) { /* Selection criteria: - no sequences because we cannot represent that - nothing animal related (specied, breed registration number), MITK focusses on human medical image processing. - only general attributes so far When extending this, we should make use of a real dictionary (GDCM/DCMTK and lookup the tag names there) */ // Patient module dictionary["0010|0010"] = "dicom.patient.PatientsName"; dictionary["0010|0020"] = "dicom.patient.PatientID"; dictionary["0010|0030"] = "dicom.patient.PatientsBirthDate"; dictionary["0010|0040"] = "dicom.patient.PatientsSex"; dictionary["0010|0032"] = "dicom.patient.PatientsBirthTime"; dictionary["0010|1000"] = "dicom.patient.OtherPatientIDs"; dictionary["0010|1001"] = "dicom.patient.OtherPatientNames"; dictionary["0010|2160"] = "dicom.patient.EthnicGroup"; dictionary["0010|4000"] = "dicom.patient.PatientComments"; dictionary["0012|0062"] = "dicom.patient.PatientIdentityRemoved"; dictionary["0012|0063"] = "dicom.patient.DeIdentificationMethod"; // General Study module dictionary["0020|000d"] = "dicom.study.StudyInstanceUID"; dictionary["0008|0020"] = "dicom.study.StudyDate"; dictionary["0008|0030"] = "dicom.study.StudyTime"; dictionary["0008|0090"] = "dicom.study.ReferringPhysiciansName"; dictionary["0020|0010"] = "dicom.study.StudyID"; dictionary["0008|0050"] = "dicom.study.AccessionNumber"; dictionary["0008|1030"] = "dicom.study.StudyDescription"; dictionary["0008|1048"] = "dicom.study.PhysiciansOfRecord"; dictionary["0008|1060"] = "dicom.study.NameOfPhysicianReadingStudy"; // General Series module dictionary["0008|0060"] = "dicom.series.Modality"; dictionary["0020|000e"] = "dicom.series.SeriesInstanceUID"; dictionary["0020|0011"] = "dicom.series.SeriesNumber"; dictionary["0020|0060"] = "dicom.series.Laterality"; dictionary["0008|0021"] = "dicom.series.SeriesDate"; dictionary["0008|0031"] = "dicom.series.SeriesTime"; dictionary["0008|1050"] = "dicom.series.PerformingPhysiciansName"; dictionary["0018|1030"] = "dicom.series.ProtocolName"; dictionary["0008|103e"] = "dicom.series.SeriesDescription"; dictionary["0008|1070"] = "dicom.series.OperatorsName"; dictionary["0018|0015"] = "dicom.series.BodyPartExamined"; dictionary["0018|5100"] = "dicom.series.PatientPosition"; dictionary["0028|0108"] = "dicom.series.SmallestPixelValueInSeries"; dictionary["0028|0109"] = "dicom.series.LargestPixelValueInSeries"; // VOI LUT module dictionary["0028|1050"] = "dicom.voilut.WindowCenter"; dictionary["0028|1051"] = "dicom.voilut.WindowWidth"; dictionary["0028|1055"] = "dicom.voilut.WindowCenterAndWidthExplanation"; // Image Pixel module dictionary["0028|0004"] = "dicom.pixel.PhotometricInterpretation"; dictionary["0028|0010"] = "dicom.pixel.Rows"; dictionary["0028|0011"] = "dicom.pixel.Columns"; // Image Plane module dictionary["0028|0030"] = "dicom.PixelSpacing"; dictionary["0018|1164"] = "dicom.ImagerPixelSpacing"; initialized = true; } return dictionary; } DataNode::Pointer DicomSeriesReader::LoadDicomSeries(const StringContainer &filenames, bool sort, bool check_4d, bool correctTilt, UpdateCallBackMethod callback, Image::Pointer preLoadedImageBlock) { DataNode::Pointer node = DataNode::New(); if (DicomSeriesReader::LoadDicomSeries(filenames, *node, sort, check_4d, correctTilt, callback, preLoadedImageBlock)) { if( filenames.empty() ) { return NULL; } return node; } else { return NULL; } } bool DicomSeriesReader::LoadDicomSeries( const StringContainer &filenames, DataNode &node, bool sort, bool check_4d, bool correctTilt, UpdateCallBackMethod callback, Image::Pointer preLoadedImageBlock) { if( filenames.empty() ) { MITK_DEBUG << "Calling LoadDicomSeries with empty filename string container. Probably invalid application logic."; node.SetData(NULL); return true; // this is not actually an error but the result is very simple } DcmIoType::Pointer io = DcmIoType::New(); try { if (io->CanReadFile(filenames.front().c_str())) { io->SetFileName(filenames.front().c_str()); io->ReadImageInformation(); if (io->GetPixelType() == itk::ImageIOBase::SCALAR || io->GetPixelType() == itk::ImageIOBase::RGB) { LoadDicom(filenames, node, sort, check_4d, correctTilt, callback, preLoadedImageBlock); } if (node.GetData()) { return true; } } } catch(itk::MemoryAllocationError& e) { MITK_ERROR << "Out of memory. Cannot load DICOM series: " << e.what(); } catch(std::exception& e) { MITK_ERROR << "Error encountered when loading DICOM series:" << e.what(); } catch(...) { MITK_ERROR << "Unspecified error encountered when loading DICOM series."; } return false; } bool DicomSeriesReader::IsDicom(const std::string &filename) { DcmIoType::Pointer io = DcmIoType::New(); return io->CanReadFile(filename.c_str()); } bool DicomSeriesReader::IsPhilips3DDicom(const std::string &filename) { DcmIoType::Pointer io = DcmIoType::New(); if (io->CanReadFile(filename.c_str())) { //Look at header Tag 3001,0010 if it is "Philips3D" gdcm::Reader reader; reader.SetFileName(filename.c_str()); reader.Read(); gdcm::DataSet &data_set = reader.GetFile().GetDataSet(); gdcm::StringFilter sf; sf.SetFile(reader.GetFile()); if (data_set.FindDataElement(gdcm::Tag(0x3001, 0x0010)) && (sf.ToString(gdcm::Tag(0x3001, 0x0010)) == "Philips3D ")) { return true; } } return false; } bool DicomSeriesReader::ReadPhilips3DDicom(const std::string &filename, mitk::Image::Pointer output_image) { // Now get PhilipsSpecific Tags gdcm::PixmapReader reader; reader.SetFileName(filename.c_str()); reader.Read(); gdcm::DataSet &data_set = reader.GetFile().GetDataSet(); gdcm::StringFilter sf; sf.SetFile(reader.GetFile()); gdcm::Attribute<0x0028,0x0011> dimTagX; // coloumns || sagittal gdcm::Attribute<0x3001,0x1001, gdcm::VR::UL, gdcm::VM::VM1> dimTagZ; //I have no idea what is VM1. // (Philips specific) // axial gdcm::Attribute<0x0028,0x0010> dimTagY; // rows || coronal gdcm::Attribute<0x0028,0x0008> dimTagT; // how many frames gdcm::Attribute<0x0018,0x602c> spaceTagX; // Spacing in X , unit is "physicalTagx" (usually centimeter) gdcm::Attribute<0x0018,0x602e> spaceTagY; gdcm::Attribute<0x3001,0x1003, gdcm::VR::FD, gdcm::VM::VM1> spaceTagZ; // (Philips specific) gdcm::Attribute<0x0018,0x6024> physicalTagX; // if 3, then spacing params are centimeter gdcm::Attribute<0x0018,0x6026> physicalTagY; gdcm::Attribute<0x3001,0x1002, gdcm::VR::US, gdcm::VM::VM1> physicalTagZ; // (Philips specific) dimTagX.Set(data_set); dimTagY.Set(data_set); dimTagZ.Set(data_set); dimTagT.Set(data_set); spaceTagX.Set(data_set); spaceTagY.Set(data_set); spaceTagZ.Set(data_set); physicalTagX.Set(data_set); physicalTagY.Set(data_set); physicalTagZ.Set(data_set); unsigned int dimX = dimTagX.GetValue(), dimY = dimTagY.GetValue(), dimZ = dimTagZ.GetValue(), dimT = dimTagT.GetValue(), physicalX = physicalTagX.GetValue(), physicalY = physicalTagY.GetValue(), physicalZ = physicalTagZ.GetValue(); float spaceX = spaceTagX.GetValue(), spaceY = spaceTagY.GetValue(), spaceZ = spaceTagZ.GetValue(); if (physicalX == 3) // spacing parameter in cm, have to convert it to mm. spaceX = spaceX * 10; if (physicalY == 3) // spacing parameter in cm, have to convert it to mm. spaceY = spaceY * 10; if (physicalZ == 3) // spacing parameter in cm, have to convert it to mm. spaceZ = spaceZ * 10; // Ok, got all necessary Tags! // Now read Pixeldata (7fe0,0010) X x Y x Z x T Elements const gdcm::Pixmap &pixels = reader.GetPixmap(); gdcm::RAWCodec codec; codec.SetPhotometricInterpretation(gdcm::PhotometricInterpretation::MONOCHROME2); codec.SetPixelFormat(pixels.GetPixelFormat()); codec.SetPlanarConfiguration(0); gdcm::DataElement out; codec.Decode(data_set.GetDataElement(gdcm::Tag(0x7fe0, 0x0010)), out); const gdcm::ByteValue *bv = out.GetByteValue(); const char *new_pixels = bv->GetPointer(); // Create MITK Image + Geometry typedef itk::Image ImageType; //Pixeltype might be different sometimes? Maybe read it out from header ImageType::RegionType myRegion; ImageType::SizeType mySize; ImageType::IndexType myIndex; ImageType::SpacingType mySpacing; ImageType::Pointer imageItk = ImageType::New(); mySpacing[0] = spaceX; mySpacing[1] = spaceY; mySpacing[2] = spaceZ; mySpacing[3] = 1; myIndex[0] = 0; myIndex[1] = 0; myIndex[2] = 0; myIndex[3] = 0; mySize[0] = dimX; mySize[1] = dimY; mySize[2] = dimZ; mySize[3] = dimT; myRegion.SetSize( mySize); myRegion.SetIndex( myIndex ); imageItk->SetSpacing(mySpacing); imageItk->SetRegions( myRegion); imageItk->Allocate(); imageItk->FillBuffer(0); itk::ImageRegionIterator iterator(imageItk, imageItk->GetLargestPossibleRegion()); iterator.GoToBegin(); unsigned long pixCount = 0; unsigned long planeSize = dimX*dimY; unsigned long planeCount = 0; unsigned long timeCount = 0; unsigned long numberOfSlices = dimZ; while (!iterator.IsAtEnd()) { unsigned long adressedPixel = pixCount + (numberOfSlices-1-planeCount)*planeSize // add offset to adress the first pixel of current plane + timeCount*numberOfSlices*planeSize; // add time offset iterator.Set( new_pixels[ adressedPixel ] ); pixCount++; ++iterator; if (pixCount == planeSize) { pixCount = 0; planeCount++; } if (planeCount == numberOfSlices) { planeCount = 0; timeCount++; } if (timeCount == dimT) { break; } } mitk::CastToMitkImage(imageItk, output_image); return true; // actually never returns false yet.. but exception possible } std::string DicomSeriesReader::ConstCharStarToString(const char* s) { return s ? std::string(s) : std::string(); } bool DicomSeriesReader::DICOMStringToSpacing(const std::string& s, ScalarType& spacingX, ScalarType& spacingY) { bool successful = false; std::istringstream spacingReader(s); std::string spacing; if ( std::getline( spacingReader, spacing, '\\' ) ) { spacingY = atof( spacing.c_str() ); if ( std::getline( spacingReader, spacing, '\\' ) ) { spacingX = atof( spacing.c_str() ); successful = true; } } return successful; } Point3D DicomSeriesReader::DICOMStringToPoint3D(const std::string& s, bool& successful) { Point3D p; successful = true; std::istringstream originReader(s); std::string coordinate; unsigned int dim(0); while( std::getline( originReader, coordinate, '\\' ) && dim < 3) { p[dim++]= atof(coordinate.c_str()); } if (dim && dim != 3) { successful = false; MITK_ERROR << "Reader implementation made wrong assumption on tag (0020,0032). Found " << dim << " instead of 3 values."; } else if (dim == 0) { successful = false; p.Fill(0.0); // assume default (0,0,0) } return p; } void DicomSeriesReader::DICOMStringToOrientationVectors(const std::string& s, Vector3D& right, Vector3D& up, bool& successful) { successful = true; std::istringstream orientationReader(s); std::string coordinate; unsigned int dim(0); while( std::getline( orientationReader, coordinate, '\\' ) && dim < 6 ) { if (dim<3) { right[dim++] = atof(coordinate.c_str()); } else { up[dim++ - 3] = atof(coordinate.c_str()); } } if (dim && dim != 6) { successful = false; MITK_ERROR << "Reader implementation made wrong assumption on tag (0020,0037). Found " << dim << " instead of 6 values."; } else if (dim == 0) { // fill with defaults right.Fill(0.0); right[0] = 1.0; up.Fill(0.0); up[1] = 1.0; successful = false; } } DicomSeriesReader::SliceGroupingAnalysisResult DicomSeriesReader::AnalyzeFileForITKImageSeriesReaderSpacingAssumption( const StringContainer& files, bool groupImagesWithGantryTilt, const gdcm::Scanner::MappingType& tagValueMappings_) { // result.first = files that fit ITK's assumption // result.second = files that do not fit, should be run through AnalyzeFileForITKImageSeriesReaderSpacingAssumption() again SliceGroupingAnalysisResult result; // we const_cast here, because I could not use a map.at(), which would make the code much more readable gdcm::Scanner::MappingType& tagValueMappings = const_cast(tagValueMappings_); const gdcm::Tag tagImagePositionPatient(0x0020,0x0032); // Image Position (Patient) const gdcm::Tag tagImageOrientation(0x0020, 0x0037); // Image Orientation const gdcm::Tag tagGantryTilt(0x0018, 0x1120); // gantry tilt Vector3D fromFirstToSecondOrigin; fromFirstToSecondOrigin.Fill(0.0); bool fromFirstToSecondOriginInitialized(false); Point3D thisOrigin; thisOrigin.Fill(0.0f); Point3D lastOrigin; lastOrigin.Fill(0.0f); Point3D lastDifferentOrigin; lastDifferentOrigin.Fill(0.0f); bool lastOriginInitialized(false); MITK_DEBUG << "--------------------------------------------------------------------------------"; MITK_DEBUG << "Analyzing files for z-spacing assumption of ITK's ImageSeriesReader (group tilted: " << groupImagesWithGantryTilt << ")"; unsigned int fileIndex(0); for (StringContainer::const_iterator fileIter = files.begin(); fileIter != files.end(); ++fileIter, ++fileIndex) { bool fileFitsIntoPattern(false); std::string thisOriginString; // Read tag value into point3D. PLEASE replace this by appropriate GDCM code if you figure out how to do that thisOriginString = ConstCharStarToString( tagValueMappings[fileIter->c_str()][tagImagePositionPatient] ); if (thisOriginString.empty()) { // don't let such files be in a common group. Everything without position information will be loaded as a single slice: // with standard DICOM files this can happen to: CR, DX, SC MITK_DEBUG << " ==> Sort away " << *fileIter << " for later analysis (no position information)"; // we already have one occupying this position if ( result.GetBlockFilenames().empty() ) // nothing WITH position information yet { // ==> this is a group of its own, stop processing, come back later result.AddFileToSortedBlock( *fileIter ); StringContainer remainingFiles; remainingFiles.insert( remainingFiles.end(), fileIter+1, files.end() ); result.AddFilesToUnsortedBlock( remainingFiles ); fileFitsIntoPattern = false; break; // no files anymore } else { // ==> this does not match, consider later result.AddFileToUnsortedBlock( *fileIter ); fileFitsIntoPattern = false; continue; // next file } } bool ignoredConversionError(-42); // hard to get here, no graceful way to react thisOrigin = DICOMStringToPoint3D( thisOriginString, ignoredConversionError ); MITK_DEBUG << " " << fileIndex << " " << *fileIter << " at " /* << thisOriginString */ << "(" << thisOrigin[0] << "," << thisOrigin[1] << "," << thisOrigin[2] << ")"; if ( lastOriginInitialized && (thisOrigin == lastOrigin) ) { MITK_DEBUG << " ==> Sort away " << *fileIter << " for separate time step"; // we already have one occupying this position result.AddFileToUnsortedBlock( *fileIter ); fileFitsIntoPattern = false; } else { if (!fromFirstToSecondOriginInitialized && lastOriginInitialized) // calculate vector as soon as possible when we get a new position { fromFirstToSecondOrigin = thisOrigin - lastDifferentOrigin; fromFirstToSecondOriginInitialized = true; // Here we calculate if this slice and the previous one are well aligned, // i.e. we test if the previous origin is on a line through the current // origin, directed into the normal direction of the current slice. // If this is NOT the case, then we have a data set with a TILTED GANTRY geometry, // which cannot be simply loaded into a single mitk::Image at the moment. // For this case, we flag this finding in the result and DicomSeriesReader // can correct for that later. Vector3D right; right.Fill(0.0); Vector3D up; right.Fill(0.0); // might be down as well, but it is just a name at this point DICOMStringToOrientationVectors( tagValueMappings[fileIter->c_str()][tagImageOrientation], right, up, ignoredConversionError ); GantryTiltInformation tiltInfo( lastDifferentOrigin, thisOrigin, right, up, 1 ); if ( tiltInfo.IsSheared() ) // mitk::eps is too small; 1/1000 of a mm should be enough to detect tilt { /* optimistic approach, accepting gantry tilt: save file for later, check all further files */ // at this point we have TWO slices analyzed! if they are the only two files, we still split, because there is no third to verify our tilting assumption. // later with a third being available, we must check if the initial tilting vector is still valid. if yes, continue. // if NO, we need to split the already sorted part (result.first) and the currently analyzed file (*fileIter) // tell apart gantry tilt from overall skewedness // sort out irregularly sheared slices, that IS NOT tilting if ( groupImagesWithGantryTilt && tiltInfo.IsRegularGantryTilt() ) { // check if this is at least roughly the same angle as recorded in DICOM tags if ( tagValueMappings[fileIter->c_str()].find(tagGantryTilt) != tagValueMappings[fileIter->c_str()].end() ) { // read value, compare to calculated angle std::string tiltStr = ConstCharStarToString( tagValueMappings[fileIter->c_str()][tagGantryTilt] ); double angle = atof(tiltStr.c_str()); MITK_DEBUG << "Comparing recorded tilt angle " << angle << " against calculated value " << tiltInfo.GetTiltAngleInDegrees(); // TODO we probably want the signs correct, too (that depends: this is just a rough check, nothing serious) if ( fabs(angle) - tiltInfo.GetTiltAngleInDegrees() > 0.25) { result.AddFileToUnsortedBlock( *fileIter ); // sort away for further analysis fileFitsIntoPattern = false; } else // tilt angle from header is less than 0.25 degrees different from what we calculated, assume this is fine { result.FlagGantryTilt(); result.AddFileToSortedBlock(*fileIter); // this file is good for current block fileFitsIntoPattern = true; } } else // we cannot check the calculated tilt angle against the one from the dicom header (so we assume we are right) { result.FlagGantryTilt(); result.AddFileToSortedBlock(*fileIter); // this file is good for current block fileFitsIntoPattern = true; } } else // caller does not want tilt compensation OR shearing is more complicated than tilt { result.AddFileToUnsortedBlock( *fileIter ); // sort away for further analysis fileFitsIntoPattern = false; } } else // not sheared { result.AddFileToSortedBlock(*fileIter); // this file is good for current block fileFitsIntoPattern = true; } } else if (fromFirstToSecondOriginInitialized) // we already know the offset between slices { Point3D assumedOrigin = lastDifferentOrigin + fromFirstToSecondOrigin; Vector3D originError = assumedOrigin - thisOrigin; double norm = originError.GetNorm(); double toleratedError(0.005); // max. 1/10mm error when measurement crosses 20 slices in z direction if (norm > toleratedError) { MITK_DEBUG << " File does not fit into the inter-slice distance pattern (diff = " << norm << ", allowed " << toleratedError << ")."; MITK_DEBUG << " Expected position (" << assumedOrigin[0] << "," << assumedOrigin[1] << "," << assumedOrigin[2] << "), got position (" << thisOrigin[0] << "," << thisOrigin[1] << "," << thisOrigin[2] << ")"; MITK_DEBUG << " ==> Sort away " << *fileIter << " for later analysis"; // At this point we know we deviated from the expectation of ITK's ImageSeriesReader // We split the input file list at this point, i.e. all files up to this one (excluding it) // are returned as group 1, the remaining files (including the faulty one) are group 2 /* Optimistic approach: check if any of the remaining slices fits in */ result.AddFileToUnsortedBlock( *fileIter ); // sort away for further analysis fileFitsIntoPattern = false; } else { result.AddFileToSortedBlock(*fileIter); // this file is good for current block fileFitsIntoPattern = true; } } else // this should be the very first slice { result.AddFileToSortedBlock(*fileIter); // this file is good for current block fileFitsIntoPattern = true; } } // record current origin for reference in later iterations if ( !lastOriginInitialized || ( fileFitsIntoPattern && (thisOrigin != lastOrigin) ) ) { lastDifferentOrigin = thisOrigin; } lastOrigin = thisOrigin; lastOriginInitialized = true; } if ( result.ContainsGantryTilt() ) { // check here how many files were grouped. // IF it was only two files AND we assume tiltedness (e.g. save "distance") // THEN we would want to also split the two previous files (simple) because // we don't have any reason to assume they belong together if ( result.GetBlockFilenames().size() == 2 ) { result.UndoPrematureGrouping(); } } return result; } DicomSeriesReader::FileNamesGrouping DicomSeriesReader::GetSeries(const StringContainer& files, bool groupImagesWithGantryTilt, const StringContainer &restrictions) { return GetSeries(files, true, groupImagesWithGantryTilt, restrictions); } DicomSeriesReader::FileNamesGrouping DicomSeriesReader::GetSeries(const StringContainer& files, bool sortTo3DPlust, bool groupImagesWithGantryTilt, const StringContainer& /*restrictions*/) { /** assumption about this method: returns a map of uid-like-key --> list(filename) each entry should contain filenames that have images of same - series instance uid (automatically done by GDCMSeriesFileNames - 0020,0037 image orientation (patient) - 0028,0030 pixel spacing (x,y) - 0018,0050 slice thickness */ // use GDCM directly, itk::GDCMSeriesFileNames does not work with GDCM 2 // PART I: scan files for sorting relevant DICOM tags, // separate images that differ in any of those // attributes (they cannot possibly form a 3D block) // scan for relevant tags in dicom files gdcm::Scanner scanner; const gdcm::Tag tagSOPClassUID(0x0008, 0x0016); // SOP class UID scanner.AddTag( tagSOPClassUID ); const gdcm::Tag tagSeriesInstanceUID(0x0020,0x000e); // Series Instance UID scanner.AddTag( tagSeriesInstanceUID ); const gdcm::Tag tagImageOrientation(0x0020, 0x0037); // image orientation scanner.AddTag( tagImageOrientation ); const gdcm::Tag tagPixelSpacing(0x0028, 0x0030); // pixel spacing scanner.AddTag( tagPixelSpacing ); const gdcm::Tag tagImagerPixelSpacing(0x0018, 0x1164); // imager pixel spacing scanner.AddTag( tagImagerPixelSpacing ); const gdcm::Tag tagSliceThickness(0x0018, 0x0050); // slice thickness scanner.AddTag( tagSliceThickness ); const gdcm::Tag tagNumberOfRows(0x0028, 0x0010); // number rows scanner.AddTag( tagNumberOfRows ); const gdcm::Tag tagNumberOfColumns(0x0028, 0x0011); // number cols scanner.AddTag( tagNumberOfColumns ); const gdcm::Tag tagGantryTilt(0x0018, 0x1120); // gantry tilt scanner.AddTag( tagGantryTilt ); const gdcm::Tag tagModality(0x0008, 0x0060); // modality scanner.AddTag( tagModality ); const gdcm::Tag tagNumberOfFrames(0x0028, 0x0008); // number of frames scanner.AddTag( tagNumberOfFrames ); // additional tags read in this scan to allow later analysis // THESE tag are not used for initial separating of files const gdcm::Tag tagImagePositionPatient(0x0020,0x0032); // Image Position (Patient) scanner.AddTag( tagImagePositionPatient ); // TODO add further restrictions from arguments (when anybody asks for it) FileNamesGrouping result; // let GDCM scan files if ( !scanner.Scan( files ) ) { MITK_ERROR << "gdcm::Scanner failed when scanning " << files.size() << " input files."; return result; } // assign files IDs that will separate them for loading into image blocks for (gdcm::Scanner::ConstIterator fileIter = scanner.Begin(); fileIter != scanner.End(); ++fileIter) { if ( std::string(fileIter->first).empty() ) continue; // TODO understand why Scanner has empty string entries if ( std::string(fileIter->first) == std::string("DICOMDIR") ) continue; /* sort out multi-frame if ( scanner.GetValue( fileIter->first , tagNumberOfFrames ) ) { MITK_INFO << "Ignoring " << fileIter->first << " because we cannot handle multi-frame images."; continue; } */ // we const_cast here, because I could not use a map.at() function in CreateMoreUniqueSeriesIdentifier. // doing the same thing with find would make the code less readable. Since we forget the Scanner results // anyway after this function, we can simply tolerate empty map entries introduced by bad operator[] access std::string moreUniqueSeriesId = CreateMoreUniqueSeriesIdentifier( const_cast(fileIter->second) ); result[ moreUniqueSeriesId ].AddFile( fileIter->first ); } // PART II: sort slices spatially (or at least consistently if this is NOT possible, see method) for ( FileNamesGrouping::const_iterator groupIter = result.begin(); groupIter != result.end(); ++groupIter ) { try { result[ groupIter->first ] = ImageBlockDescriptor( SortSeriesSlices( groupIter->second.GetFilenames() ) ); // sort each slice group spatially } catch(...) { MITK_ERROR << "Caught something."; } } // PART III: analyze pre-sorted images for valid blocks (i.e. blocks of equal z-spacing), // separate into multiple blocks if necessary. // // Analysis performs the following steps: // * imitate itk::ImageSeriesReader: use the distance between the first two images as z-spacing // * check what images actually fulfill ITK's z-spacing assumption // * separate all images that fail the test into new blocks, re-iterate analysis for these blocks // * this includes images which DO NOT PROVIDE spatial information, i.e. all images w/o ImagePositionPatient will be loaded separately FileNamesGrouping groupsOf3DPlusTBlocks; // final result of this function for ( FileNamesGrouping::const_iterator groupIter = result.begin(); groupIter != result.end(); ++groupIter ) { FileNamesGrouping groupsOf3DBlocks; // intermediate result for only this group(!) std::map mapOf3DBlockAnalysisResults; StringContainer filesStillToAnalyze = groupIter->second.GetFilenames(); std::string groupUID = groupIter->first; unsigned int subgroup(0); - MITK_DEBUG << "Analyze group " << groupUID; + MITK_DEBUG << "Analyze group " << groupUID << " of " << groupIter->second.GetFilenames().size() << " files"; while (!filesStillToAnalyze.empty()) // repeat until all files are grouped somehow { SliceGroupingAnalysisResult analysisResult = AnalyzeFileForITKImageSeriesReaderSpacingAssumption( filesStillToAnalyze, groupImagesWithGantryTilt, scanner.GetMappings() ); // enhance the UID for additional groups std::stringstream newGroupUID; newGroupUID << groupUID << '.' << subgroup; ImageBlockDescriptor thisBlock( analysisResult.GetBlockFilenames() ); std::string firstFileInBlock = thisBlock.GetFilenames().front(); thisBlock.SetImageBlockUID( newGroupUID.str() ); thisBlock.SetSeriesInstanceUID( DicomSeriesReader::ConstCharStarToString( scanner.GetValue( firstFileInBlock.c_str(), tagSeriesInstanceUID ) ) ); thisBlock.SetHasGantryTiltCorrected( analysisResult.ContainsGantryTilt() ); thisBlock.SetSOPClassUID( DicomSeriesReader::ConstCharStarToString( scanner.GetValue( firstFileInBlock.c_str(), tagSOPClassUID ) ) ); thisBlock.SetNumberOfFrames( ConstCharStarToString( scanner.GetValue( firstFileInBlock.c_str(), tagNumberOfFrames ) ) ); thisBlock.SetModality( DicomSeriesReader::ConstCharStarToString( scanner.GetValue( firstFileInBlock.c_str(), tagModality ) ) ); thisBlock.SetPixelSpacingInformation( DicomSeriesReader::ConstCharStarToString( scanner.GetValue( firstFileInBlock.c_str(), tagPixelSpacing ) ), DicomSeriesReader::ConstCharStarToString( scanner.GetValue( firstFileInBlock.c_str(), tagImagerPixelSpacing ) ) ); thisBlock.SetHasMultipleTimePoints( false ); groupsOf3DBlocks[ newGroupUID.str() ] = thisBlock; //MITK_DEBUG << "Result: sorted 3D group " << newGroupUID.str() << " with " << groupsOf3DBlocks[ newGroupUID.str() ].GetFilenames().size() << " files"; MITK_DEBUG << "Result: sorted 3D group with " << groupsOf3DBlocks[ newGroupUID.str() ].GetFilenames().size() << " files"; StringContainer debugOutputFiles = analysisResult.GetBlockFilenames(); for (StringContainer::const_iterator siter = debugOutputFiles.begin(); siter != debugOutputFiles.end(); ++siter) MITK_DEBUG << " IN " << *siter; ++subgroup; filesStillToAnalyze = analysisResult.GetUnsortedFilenames(); // remember what needs further analysis for (StringContainer::const_iterator siter = filesStillToAnalyze.begin(); siter != filesStillToAnalyze.end(); ++siter) MITK_DEBUG << " OUT " << *siter; } // end of grouping, now post-process groups // PART IV: attempt to group blocks to 3D+t blocks if requested // inspect entries of groupsOf3DBlocks // - if number of files is identical to previous entry, collect for 3D+t block // - as soon as number of files changes from previous entry, record collected blocks as 3D+t block, start a new one, continue // decide whether or not to group 3D blocks into 3D+t blocks where possible if ( !sortTo3DPlust ) { // copy 3D blocks to output groupsOf3DPlusTBlocks.insert( groupsOf3DBlocks.begin(), groupsOf3DBlocks.end() ); } else { // sort 3D+t (as described in "PART IV") MITK_DEBUG << "================================================================================"; MITK_DEBUG << "3D+t analysis:"; unsigned int numberOfFilesInPreviousBlock(0); std::string previousBlockKey; for ( FileNamesGrouping::const_iterator block3DIter = groupsOf3DBlocks.begin(); block3DIter != groupsOf3DBlocks.end(); ++block3DIter ) { unsigned int numberOfFilesInThisBlock = block3DIter->second.GetFilenames().size(); std::string thisBlockKey = block3DIter->first; if (numberOfFilesInPreviousBlock == 0) { numberOfFilesInPreviousBlock = numberOfFilesInThisBlock; groupsOf3DPlusTBlocks[thisBlockKey] = block3DIter->second; MITK_DEBUG << " 3D+t group " << thisBlockKey; previousBlockKey = thisBlockKey; } else { bool identicalOrigins; try { // check whether this and the previous block share a comon origin // TODO should be safe, but a little try/catch or other error handling wouldn't hurt const char *origin_value = scanner.GetValue( groupsOf3DBlocks[thisBlockKey].GetFilenames().front().c_str(), tagImagePositionPatient ), *previous_origin_value = scanner.GetValue( groupsOf3DBlocks[previousBlockKey].GetFilenames().front().c_str(), tagImagePositionPatient ), *destination_value = scanner.GetValue( groupsOf3DBlocks[thisBlockKey].GetFilenames().back().c_str(), tagImagePositionPatient ), *previous_destination_value = scanner.GetValue( groupsOf3DBlocks[previousBlockKey].GetFilenames().back().c_str(), tagImagePositionPatient ); if (!origin_value || !previous_origin_value || !destination_value || !previous_destination_value) { identicalOrigins = false; } else { std::string thisOriginString = ConstCharStarToString( origin_value ); std::string previousOriginString = ConstCharStarToString( previous_origin_value ); // also compare last origin, because this might differ if z-spacing is different std::string thisDestinationString = ConstCharStarToString( destination_value ); std::string previousDestinationString = ConstCharStarToString( previous_destination_value ); identicalOrigins = ( (thisOriginString == previousOriginString) && (thisDestinationString == previousDestinationString) ); } } catch(...) { identicalOrigins = false; } if (identicalOrigins && (numberOfFilesInPreviousBlock == numberOfFilesInThisBlock)) { // group with previous block groupsOf3DPlusTBlocks[previousBlockKey].AddFiles( block3DIter->second.GetFilenames() ); groupsOf3DPlusTBlocks[previousBlockKey].SetHasMultipleTimePoints(true); MITK_DEBUG << " --> group enhanced with another timestep"; } else { // start a new block groupsOf3DPlusTBlocks[thisBlockKey] = block3DIter->second; int numberOfTimeSteps = groupsOf3DPlusTBlocks[previousBlockKey].GetFilenames().size() / numberOfFilesInPreviousBlock; MITK_DEBUG << " ==> group closed with " << numberOfTimeSteps << " time steps"; previousBlockKey = thisBlockKey; MITK_DEBUG << " 3D+t group " << thisBlockKey << " started"; } } numberOfFilesInPreviousBlock = numberOfFilesInThisBlock; } } } MITK_DEBUG << "================================================================================"; MITK_DEBUG << "Summary: "; for ( FileNamesGrouping::const_iterator groupIter = groupsOf3DPlusTBlocks.begin(); groupIter != groupsOf3DPlusTBlocks.end(); ++groupIter ) { ImageBlockDescriptor block = groupIter->second; MITK_DEBUG << " " << block.GetFilenames().size() << " '" << block.GetModality() << "' images (" << block.GetSOPClassUIDAsString() << ") in volume " << block.GetImageBlockUID(); MITK_DEBUG << " (gantry tilt : " << (block.HasGantryTiltCorrected()?"Yes":"No") << "; " "pixel spacing : " << PixelSpacingInterpretationToString( block.GetPixelSpacingType() ) << "; " "3D+t: " << (block.HasMultipleTimePoints()?"Yes":"No") << "; " "reader support: " << ReaderImplementationLevelToString( block.GetReaderImplementationLevel() ) << ")"; StringContainer debugOutputFiles = block.GetFilenames(); for (StringContainer::const_iterator siter = debugOutputFiles.begin(); siter != debugOutputFiles.end(); ++siter) MITK_DEBUG << " F " << *siter; } MITK_DEBUG << "================================================================================"; return groupsOf3DPlusTBlocks; } DicomSeriesReader::FileNamesGrouping DicomSeriesReader::GetSeries(const std::string &dir, bool groupImagesWithGantryTilt, const StringContainer &restrictions) { gdcm::Directory directoryLister; directoryLister.Load( dir.c_str(), false ); // non-recursive return GetSeries(directoryLister.GetFilenames(), groupImagesWithGantryTilt, restrictions); } std::string DicomSeriesReader::CreateSeriesIdentifierPart( gdcm::Scanner::TagToValue& tagValueMap, const gdcm::Tag& tag ) { std::string result; try { result = IDifyTagValue( tagValueMap[ tag ] ? tagValueMap[ tag ] : std::string("") ); } catch (std::exception&) { // we are happy with even nothing, this will just group images of a series //MITK_WARN << "Could not access tag " << tag << ": " << e.what(); } return result; } std::string DicomSeriesReader::CreateMoreUniqueSeriesIdentifier( gdcm::Scanner::TagToValue& tagValueMap ) { const gdcm::Tag tagSeriesInstanceUID(0x0020,0x000e); // Series Instance UID const gdcm::Tag tagImageOrientation(0x0020, 0x0037); // image orientation const gdcm::Tag tagPixelSpacing(0x0028, 0x0030); // pixel spacing const gdcm::Tag tagImagerPixelSpacing(0x0018, 0x1164); // imager pixel spacing const gdcm::Tag tagSliceThickness(0x0018, 0x0050); // slice thickness const gdcm::Tag tagNumberOfRows(0x0028, 0x0010); // number rows const gdcm::Tag tagNumberOfColumns(0x0028, 0x0011); // number cols const gdcm::Tag tagNumberOfFrames(0x0028, 0x0008); // number of frames const char* tagSeriesInstanceUid = tagValueMap[tagSeriesInstanceUID]; if (!tagSeriesInstanceUid) { mitkThrow() << "CreateMoreUniqueSeriesIdentifier() could not access series instance UID. Something is seriously wrong with this image, so stopping here."; } std::string constructedID = tagSeriesInstanceUid; constructedID += CreateSeriesIdentifierPart( tagValueMap, tagNumberOfRows ); constructedID += CreateSeriesIdentifierPart( tagValueMap, tagNumberOfColumns ); constructedID += CreateSeriesIdentifierPart( tagValueMap, tagPixelSpacing ); constructedID += CreateSeriesIdentifierPart( tagValueMap, tagImagerPixelSpacing ); constructedID += CreateSeriesIdentifierPart( tagValueMap, tagSliceThickness ); constructedID += CreateSeriesIdentifierPart( tagValueMap, tagNumberOfFrames ); // be a bit tolerant for orienatation, let only the first few digits matter (http://bugs.mitk.org/show_bug.cgi?id=12263) // NOT constructedID += CreateSeriesIdentifierPart( tagValueMap, tagImageOrientation ); if (tagValueMap.find(tagImageOrientation) != tagValueMap.end()) { bool conversionError(false); Vector3D right; right.Fill(0.0); Vector3D up; right.Fill(0.0); DICOMStringToOrientationVectors( tagValueMap[tagImageOrientation], right, up, conversionError ); //string newstring sprintf(simplifiedOrientationString, "%.3f\\%.3f\\%.3f\\%.3f\\%.3f\\%.3f", right[0], right[1], right[2], up[0], up[1], up[2]); std::ostringstream ss; ss.setf(std::ios::fixed, std::ios::floatfield); ss.precision(5); ss << right[0] << "\\" << right[1] << "\\" << right[2] << "\\" << up[0] << "\\" << up[1] << "\\" << up[2]; std::string simplifiedOrientationString(ss.str()); constructedID += IDifyTagValue( simplifiedOrientationString ); } constructedID.resize( constructedID.length() - 1 ); // cut of trailing '.' return constructedID; } std::string DicomSeriesReader::IDifyTagValue(const std::string& value) { std::string IDifiedValue( value ); if (value.empty()) throw std::logic_error("IDifyTagValue() illegaly called with empty tag value"); // Eliminate non-alnum characters, including whitespace... // that may have been introduced by concats. for(std::size_t i=0; i= 'a' && IDifiedValue[i] <= 'z') || (IDifiedValue[i] >= '0' && IDifiedValue[i] <= '9') || (IDifiedValue[i] >= 'A' && IDifiedValue[i] <= 'Z'))) { IDifiedValue.erase(i, 1); } } IDifiedValue += "."; return IDifiedValue; } DicomSeriesReader::StringContainer DicomSeriesReader::GetSeries(const std::string &dir, const std::string &series_uid, bool groupImagesWithGantryTilt, const StringContainer &restrictions) { FileNamesGrouping allSeries = GetSeries(dir, groupImagesWithGantryTilt, restrictions); StringContainer resultingFileList; for ( FileNamesGrouping::const_iterator idIter = allSeries.begin(); idIter != allSeries.end(); ++idIter ) { if ( idIter->first.find( series_uid ) == 0 ) // this ID starts with given series_uid { return idIter->second.GetFilenames(); } } return resultingFileList; } DicomSeriesReader::StringContainer DicomSeriesReader::SortSeriesSlices(const StringContainer &unsortedFilenames) { /* we CAN expect a group of equal - series instance uid - image orientation - pixel spacing - imager pixel spacing - slice thickness - number of rows/columns (each piece of information except the rows/columns might be missing) sorting with GdcmSortFunction tries its best by sorting by spatial position and more hints (acquisition number, acquisition time, trigger time) but will always produce a sorting by falling back to SOP Instance UID. */ gdcm::Sorter sorter; sorter.SetSortFunction(DicomSeriesReader::GdcmSortFunction); try { if (sorter.Sort(unsortedFilenames)) { return sorter.GetFilenames(); } else { MITK_WARN << "Sorting error. Leaving series unsorted."; return unsortedFilenames; } } catch(std::logic_error&) { MITK_WARN << "Sorting error. Leaving series unsorted."; return unsortedFilenames; } } bool DicomSeriesReader::GdcmSortFunction(const gdcm::DataSet &ds1, const gdcm::DataSet &ds2) { // This method MUST accept missing location and position information (and all else, too) // because we cannot rely on anything // (restriction on the sentence before: we have to provide consistent sorting, so we // rely on the minimum information all DICOM files need to provide: SOP Instance UID) /* we CAN expect a group of equal - series instance uid - image orientation - pixel spacing - imager pixel spacing - slice thickness - number of rows/columns */ static const gdcm::Tag tagImagePositionPatient(0x0020,0x0032); // Image Position (Patient) static const gdcm::Tag tagImageOrientation(0x0020, 0x0037); // Image Orientation // see if we have Image Position and Orientation if ( ds1.FindDataElement(tagImagePositionPatient) && ds1.FindDataElement(tagImageOrientation) && ds2.FindDataElement(tagImagePositionPatient) && ds2.FindDataElement(tagImageOrientation) ) { gdcm::Attribute<0x0020,0x0032> image_pos1; // Image Position (Patient) gdcm::Attribute<0x0020,0x0037> image_orientation1; // Image Orientation (Patient) image_pos1.Set(ds1); image_orientation1.Set(ds1); gdcm::Attribute<0x0020,0x0032> image_pos2; gdcm::Attribute<0x0020,0x0037> image_orientation2; image_pos2.Set(ds2); image_orientation2.Set(ds2); /* we tolerate very small differences in image orientation, since we got to know about acquisitions where these values change across a single series (7th decimal digit) (http://bugs.mitk.org/show_bug.cgi?id=12263) still, we want to check if our assumption of 'almost equal' orientations is valid */ for (unsigned int dim = 0; dim < 6; ++dim) { if ( fabs(image_orientation2[dim] - image_orientation1[dim]) > 0.0001 ) { MITK_ERROR << "Dicom images have different orientations."; throw std::logic_error("Dicom images have different orientations. Call GetSeries() first to separate images."); } } double normal[3]; normal[0] = image_orientation1[1] * image_orientation1[5] - image_orientation1[2] * image_orientation1[4]; normal[1] = image_orientation1[2] * image_orientation1[3] - image_orientation1[0] * image_orientation1[5]; normal[2] = image_orientation1[0] * image_orientation1[4] - image_orientation1[1] * image_orientation1[3]; double dist1 = 0.0, dist2 = 0.0; // this computes the distance from world origin (0,0,0) ALONG THE NORMAL of the image planes for (unsigned char i = 0u; i < 3u; ++i) { dist1 += normal[i] * image_pos1[i]; dist2 += normal[i] * image_pos2[i]; } // if we can sort by just comparing the distance, we do exactly that if ( fabs(dist1 - dist2) >= mitk::eps) { // default: compare position return dist1 < dist2; } else // we need to check more properties to distinguish slices { // try to sort by Acquisition Number static const gdcm::Tag tagAcquisitionNumber(0x0020, 0x0012); if (ds1.FindDataElement(tagAcquisitionNumber) && ds2.FindDataElement(tagAcquisitionNumber)) { gdcm::Attribute<0x0020,0x0012> acquisition_number1; // Acquisition number gdcm::Attribute<0x0020,0x0012> acquisition_number2; acquisition_number1.Set(ds1); acquisition_number2.Set(ds2); if (acquisition_number1 != acquisition_number2) { return acquisition_number1 < acquisition_number2; } else // neither position nor acquisition number are good for sorting, so check more { // try to sort by Acquisition Time static const gdcm::Tag tagAcquisitionTime(0x0008, 0x0032); if (ds1.FindDataElement(tagAcquisitionTime) && ds2.FindDataElement(tagAcquisitionTime)) { gdcm::Attribute<0x0008,0x0032> acquisition_time1; // Acquisition time gdcm::Attribute<0x0008,0x0032> acquisition_time2; acquisition_time1.Set(ds1); acquisition_time2.Set(ds2); if (acquisition_time1 != acquisition_time2) { return acquisition_time1 < acquisition_time2; } else // we gave up on image position, acquisition number and acquisition time now { // let's try trigger time static const gdcm::Tag tagTriggerTime(0x0018, 0x1060); if (ds1.FindDataElement(tagTriggerTime) && ds2.FindDataElement(tagTriggerTime)) { gdcm::Attribute<0x0018,0x1060> trigger_time1; // Trigger time gdcm::Attribute<0x0018,0x1060> trigger_time2; trigger_time1.Set(ds1); trigger_time2.Set(ds2); if (trigger_time1 != trigger_time2) { return trigger_time1 < trigger_time2; } // ELSE! // for this and many previous ifs we fall through if nothing lets us sort } // . } // . } // . } } } } // . // LAST RESORT: all valuable information for sorting is missing. // Sort by some meaningless but unique identifiers to satisfy the sort function static const gdcm::Tag tagSOPInstanceUID(0x0008, 0x0018); if (ds1.FindDataElement(tagSOPInstanceUID) && ds2.FindDataElement(tagSOPInstanceUID)) { MITK_DEBUG << "Dicom images are missing attributes for a meaningful sorting, falling back to SOP instance UID comparison."; gdcm::Attribute<0x0008,0x0018> SOPInstanceUID1; // SOP instance UID is mandatory and unique gdcm::Attribute<0x0008,0x0018> SOPInstanceUID2; SOPInstanceUID1.Set(ds1); SOPInstanceUID2.Set(ds2); return SOPInstanceUID1 < SOPInstanceUID2; } else { // no DICOM file should really come down here, this should only be reached with unskillful and unlucky manipulation of files std::string error_message("Malformed DICOM images, which do not even contain a SOP Instance UID."); MITK_ERROR << error_message; throw std::logic_error( error_message ); } } std::string DicomSeriesReader::GetConfigurationString() { std::stringstream configuration; configuration << "MITK_USE_GDCMIO: "; configuration << "true"; configuration << "\n"; configuration << "GDCM_VERSION: "; #ifdef GDCM_MAJOR_VERSION configuration << GDCM_VERSION; #endif //configuration << "\n"; return configuration.str(); } void DicomSeriesReader::CopyMetaDataToImageProperties(StringContainer filenames, const gdcm::Scanner::MappingType &tagValueMappings_, DcmIoType *io, const ImageBlockDescriptor& blockInfo, Image *image) { std::list imageBlock; imageBlock.push_back(filenames); CopyMetaDataToImageProperties(imageBlock, tagValueMappings_, io, blockInfo, image); } void DicomSeriesReader::CopyMetaDataToImageProperties( std::list imageBlock, const gdcm::Scanner::MappingType& tagValueMappings_, DcmIoType* io, const ImageBlockDescriptor& blockInfo, Image* image) { if (!io || !image) return; StringLookupTable filesForSlices; StringLookupTable sliceLocationForSlices; StringLookupTable instanceNumberForSlices; StringLookupTable SOPInstanceNumberForSlices; gdcm::Scanner::MappingType& tagValueMappings = const_cast(tagValueMappings_); //DICOM tags which should be added to the image properties const gdcm::Tag tagSliceLocation(0x0020, 0x1041); // slice location const gdcm::Tag tagInstanceNumber(0x0020, 0x0013); // (image) instance number const gdcm::Tag tagSOPInstanceNumber(0x0008, 0x0018); // SOP instance number unsigned int timeStep(0); std::string propertyKeySliceLocation = "dicom.image.0020.1041"; std::string propertyKeyInstanceNumber = "dicom.image.0020.0013"; std::string propertyKeySOPInstanceNumber = "dicom.image.0008.0018"; // tags for each image for ( std::list::iterator i = imageBlock.begin(); i != imageBlock.end(); i++, timeStep++ ) { const StringContainer& files = (*i); unsigned int slice(0); for ( StringContainer::const_iterator fIter = files.begin(); fIter != files.end(); ++fIter, ++slice ) { filesForSlices.SetTableValue( slice, *fIter ); gdcm::Scanner::TagToValue tagValueMapForFile = tagValueMappings[fIter->c_str()]; if(tagValueMapForFile.find(tagSliceLocation) != tagValueMapForFile.end()) sliceLocationForSlices.SetTableValue(slice, tagValueMapForFile[tagSliceLocation]); if(tagValueMapForFile.find(tagInstanceNumber) != tagValueMapForFile.end()) instanceNumberForSlices.SetTableValue(slice, tagValueMapForFile[tagInstanceNumber]); if(tagValueMapForFile.find(tagSOPInstanceNumber) != tagValueMapForFile.end()) SOPInstanceNumberForSlices.SetTableValue(slice, tagValueMapForFile[tagSOPInstanceNumber]); } image->SetProperty( "files", StringLookupTableProperty::New( filesForSlices ) ); //If more than one time step add postfix ".t" + timestep if(timeStep != 0) { std::ostringstream postfix; postfix << ".t" << timeStep; propertyKeySliceLocation.append(postfix.str()); propertyKeyInstanceNumber.append(postfix.str()); propertyKeySOPInstanceNumber.append(postfix.str()); } image->SetProperty( propertyKeySliceLocation.c_str(), StringLookupTableProperty::New( sliceLocationForSlices ) ); image->SetProperty( propertyKeyInstanceNumber.c_str(), StringLookupTableProperty::New( instanceNumberForSlices ) ); image->SetProperty( propertyKeySOPInstanceNumber.c_str(), StringLookupTableProperty::New( SOPInstanceNumberForSlices ) ); } // Copy tags for series, study, patient level (leave interpretation to application). // These properties will be copied to the DataNode by DicomSeriesReader. // tags for the series (we just use the one that ITK copied to its dictionary (proably that of the last slice) const itk::MetaDataDictionary& dict = io->GetMetaDataDictionary(); const TagToPropertyMapType& propertyLookup = DicomSeriesReader::GetDICOMTagsToMITKPropertyMap(); itk::MetaDataDictionary::ConstIterator dictIter = dict.Begin(); while ( dictIter != dict.End() ) { //MITK_DEBUG << "Key " << dictIter->first; std::string value; if ( itk::ExposeMetaData( dict, dictIter->first, value ) ) { //MITK_DEBUG << "Value " << value; TagToPropertyMapType::const_iterator valuePosition = propertyLookup.find( dictIter->first ); if ( valuePosition != propertyLookup.end() ) { std::string propertyKey = valuePosition->second; //MITK_DEBUG << "--> " << propertyKey; image->SetProperty( propertyKey.c_str(), StringProperty::New(value) ); } } else { MITK_WARN << "Tag " << dictIter->first << " not read as string as expected. Ignoring..." ; } ++dictIter; } // copy imageblockdescriptor as properties image->SetProperty("dicomseriesreader.SOPClass", StringProperty::New(blockInfo.GetSOPClassUIDAsString())); image->SetProperty("dicomseriesreader.ReaderImplementationLevelString", StringProperty::New(ReaderImplementationLevelToString( blockInfo.GetReaderImplementationLevel() ))); image->SetProperty("dicomseriesreader.ReaderImplementationLevel", GenericProperty::New( blockInfo.GetReaderImplementationLevel() )); image->SetProperty("dicomseriesreader.PixelSpacingInterpretationString", StringProperty::New(PixelSpacingInterpretationToString( blockInfo.GetPixelSpacingType() ))); image->SetProperty("dicomseriesreader.PixelSpacingInterpretation", GenericProperty::New(blockInfo.GetPixelSpacingType())); image->SetProperty("dicomseriesreader.MultiFrameImage", BoolProperty::New(blockInfo.IsMultiFrameImage())); image->SetProperty("dicomseriesreader.GantyTiltCorrected", BoolProperty::New(blockInfo.HasGantryTiltCorrected())); image->SetProperty("dicomseriesreader.3D+t", BoolProperty::New(blockInfo.HasMultipleTimePoints())); } void DicomSeriesReader::FixSpacingInformation( mitk::Image* image, const ImageBlockDescriptor& imageBlockDescriptor ) { // spacing provided by ITK/GDCM Vector3D imageSpacing = image->GetGeometry()->GetSpacing(); ScalarType imageSpacingX = imageSpacing[0]; ScalarType imageSpacingY = imageSpacing[1]; // spacing as desired by MITK (preference for "in patient", else "on detector", or "1.0/1.0") ScalarType desiredSpacingX = imageSpacingX; ScalarType desiredSpacingY = imageSpacingY; imageBlockDescriptor.GetDesiredMITKImagePixelSpacing( desiredSpacingX, desiredSpacingY ); MITK_DEBUG << "Loaded spacing: " << imageSpacingX << "/" << imageSpacingY; MITK_DEBUG << "Corrected spacing: " << desiredSpacingX << "/" << desiredSpacingY; imageSpacing[0] = desiredSpacingX; imageSpacing[1] = desiredSpacingY; image->GetGeometry()->SetSpacing( imageSpacing ); } void DicomSeriesReader::LoadDicom(const StringContainer &filenames, DataNode &node, bool sort, bool load4D, bool correctTilt, UpdateCallBackMethod callback, Image::Pointer preLoadedImageBlock) { const char* previousCLocale = setlocale(LC_NUMERIC, NULL); setlocale(LC_NUMERIC, "C"); std::locale previousCppLocale( std::cin.getloc() ); std::locale l( "C" ); std::cin.imbue(l); ImageBlockDescriptor imageBlockDescriptor; const gdcm::Tag tagImagePositionPatient(0x0020,0x0032); // Image Position (Patient) const gdcm::Tag tagImageOrientation(0x0020, 0x0037); // Image Orientation const gdcm::Tag tagSeriesInstanceUID(0x0020, 0x000e); // Series Instance UID const gdcm::Tag tagSOPClassUID(0x0008, 0x0016); // SOP class UID const gdcm::Tag tagModality(0x0008, 0x0060); // modality const gdcm::Tag tagPixelSpacing(0x0028, 0x0030); // pixel spacing const gdcm::Tag tagImagerPixelSpacing(0x0018, 0x1164); // imager pixel spacing const gdcm::Tag tagNumberOfFrames(0x0028, 0x0008); // number of frames try { Image::Pointer image = preLoadedImageBlock.IsNull() ? Image::New() : preLoadedImageBlock; CallbackCommand *command = callback ? new CallbackCommand(callback) : 0; bool initialize_node = false; /* special case for Philips 3D+t ultrasound images */ if ( DicomSeriesReader::IsPhilips3DDicom(filenames.front().c_str()) ) { // TODO what about imageBlockDescriptor? // TODO what about preLoadedImageBlock? ReadPhilips3DDicom(filenames.front().c_str(), image); initialize_node = true; } else { /* default case: assume "normal" image blocks, possibly 3D+t */ bool canLoadAs4D(true); gdcm::Scanner scanner; ScanForSliceInformation(filenames, scanner); // need non-const access for map gdcm::Scanner::MappingType& tagValueMappings = const_cast(scanner.GetMappings()); std::list imageBlocks = SortIntoBlocksFor3DplusT( filenames, tagValueMappings, sort, canLoadAs4D ); unsigned int volume_count = imageBlocks.size(); imageBlockDescriptor.SetSeriesInstanceUID( DicomSeriesReader::ConstCharStarToString( scanner.GetValue( filenames.front().c_str(), tagSeriesInstanceUID ) ) ); imageBlockDescriptor.SetSOPClassUID( DicomSeriesReader::ConstCharStarToString( scanner.GetValue( filenames.front().c_str(), tagSOPClassUID ) ) ); imageBlockDescriptor.SetModality( DicomSeriesReader::ConstCharStarToString( scanner.GetValue( filenames.front().c_str(), tagModality ) ) ); imageBlockDescriptor.SetNumberOfFrames( ConstCharStarToString( scanner.GetValue( filenames.front().c_str(), tagNumberOfFrames ) ) ); imageBlockDescriptor.SetPixelSpacingInformation( ConstCharStarToString( scanner.GetValue( filenames.front().c_str(), tagPixelSpacing ) ), ConstCharStarToString( scanner.GetValue( filenames.front().c_str(), tagImagerPixelSpacing ) ) ); GantryTiltInformation tiltInfo; // check possibility of a single slice with many timesteps. In this case, don't check for tilt, no second slice possible if ( !imageBlocks.empty() && imageBlocks.front().size() > 1 && correctTilt) { // check tiltedness here, potentially fixup ITK's loading result by shifting slice contents // check first and last position slice from tags, make some calculations to detect tilt std::string firstFilename(imageBlocks.front().front()); // calculate from first and last slice to minimize rounding errors std::string secondFilename(imageBlocks.front().back()); std::string imagePosition1( ConstCharStarToString( tagValueMappings[ firstFilename.c_str() ][ tagImagePositionPatient ] ) ); std::string imageOrientation( ConstCharStarToString( tagValueMappings[ firstFilename.c_str() ][ tagImageOrientation ] ) ); std::string imagePosition2( ConstCharStarToString( tagValueMappings[secondFilename.c_str() ][ tagImagePositionPatient ] ) ); bool ignoredConversionError(-42); // hard to get here, no graceful way to react Point3D origin1( DICOMStringToPoint3D( imagePosition1, ignoredConversionError ) ); Point3D origin2( DICOMStringToPoint3D( imagePosition2, ignoredConversionError ) ); Vector3D right; right.Fill(0.0); Vector3D up; right.Fill(0.0); // might be down as well, but it is just a name at this point DICOMStringToOrientationVectors( imageOrientation, right, up, ignoredConversionError ); tiltInfo = GantryTiltInformation ( origin1, origin2, right, up, filenames.size()-1 ); correctTilt = tiltInfo.IsSheared() && tiltInfo.IsRegularGantryTilt(); } else { correctTilt = false; // we CANNOT do that } imageBlockDescriptor.SetHasGantryTiltCorrected( correctTilt ); if (volume_count == 1 || !canLoadAs4D || !load4D) { DcmIoType::Pointer io; image = MultiplexLoadDICOMByITK( imageBlocks.front(), correctTilt, tiltInfo, io, command, preLoadedImageBlock ); // load first 3D block imageBlockDescriptor.AddFiles(imageBlocks.front()); // only the first part is loaded imageBlockDescriptor.SetHasMultipleTimePoints( false ); FixSpacingInformation( image, imageBlockDescriptor ); CopyMetaDataToImageProperties( imageBlocks.front(), scanner.GetMappings(), io, imageBlockDescriptor, image); initialize_node = true; } else if (volume_count > 1) { imageBlockDescriptor.AddFiles(filenames); // all is loaded imageBlockDescriptor.SetHasMultipleTimePoints( true ); DcmIoType::Pointer io; image = MultiplexLoadDICOMByITK4D( imageBlocks, imageBlockDescriptor, correctTilt, tiltInfo, io, command, preLoadedImageBlock ); initialize_node = true; } } if (initialize_node) { // forward some image properties to node node.GetPropertyList()->ConcatenatePropertyList( image->GetPropertyList(), true ); node.SetData( image ); setlocale(LC_NUMERIC, previousCLocale); std::cin.imbue(previousCppLocale); } MITK_DEBUG << "--------------------------------------------------------------------------------"; MITK_DEBUG << "DICOM files loaded (from series UID " << imageBlockDescriptor.GetSeriesInstanceUID() << "):"; MITK_DEBUG << " " << imageBlockDescriptor.GetFilenames().size() << " '" << imageBlockDescriptor.GetModality() << "' files (" << imageBlockDescriptor.GetSOPClassUIDAsString() << ") loaded into 1 mitk::Image"; MITK_DEBUG << " multi-frame: " << (imageBlockDescriptor.IsMultiFrameImage()?"Yes":"No"); MITK_DEBUG << " reader support: " << ReaderImplementationLevelToString(imageBlockDescriptor.GetReaderImplementationLevel()); MITK_DEBUG << " pixel spacing type: " << PixelSpacingInterpretationToString( imageBlockDescriptor.GetPixelSpacingType() ) << " " << image->GetGeometry()->GetSpacing()[0] << "/" << image->GetGeometry()->GetSpacing()[0]; MITK_DEBUG << " gantry tilt corrected: " << (imageBlockDescriptor.HasGantryTiltCorrected()?"Yes":"No"); MITK_DEBUG << " 3D+t: " << (imageBlockDescriptor.HasMultipleTimePoints()?"Yes":"No"); MITK_DEBUG << "--------------------------------------------------------------------------------"; } catch (std::exception& e) { // reset locale then throw up setlocale(LC_NUMERIC, previousCLocale); std::cin.imbue(previousCppLocale); MITK_DEBUG << "Caught exception in DicomSeriesReader::LoadDicom"; throw e; } } void DicomSeriesReader::ScanForSliceInformation(const StringContainer &filenames, gdcm::Scanner& scanner) { const gdcm::Tag tagImagePositionPatient(0x0020,0x0032); //Image position (Patient) scanner.AddTag(tagImagePositionPatient); const gdcm::Tag tagSeriesInstanceUID(0x0020, 0x000e); // Series Instance UID scanner.AddTag(tagSeriesInstanceUID); const gdcm::Tag tagImageOrientation(0x0020,0x0037); //Image orientation scanner.AddTag(tagImageOrientation); const gdcm::Tag tagSliceLocation(0x0020, 0x1041); // slice location scanner.AddTag( tagSliceLocation ); const gdcm::Tag tagInstanceNumber(0x0020, 0x0013); // (image) instance number scanner.AddTag( tagInstanceNumber ); const gdcm::Tag tagSOPInstanceNumber(0x0008, 0x0018); // SOP instance number scanner.AddTag( tagSOPInstanceNumber ); const gdcm::Tag tagPixelSpacing(0x0028, 0x0030); // Pixel Spacing scanner.AddTag( tagPixelSpacing ); const gdcm::Tag tagImagerPixelSpacing(0x0018, 0x1164); // Imager Pixel Spacing scanner.AddTag( tagImagerPixelSpacing ); const gdcm::Tag tagModality(0x0008, 0x0060); // Modality scanner.AddTag( tagModality ); const gdcm::Tag tagSOPClassUID(0x0008, 0x0016); // SOP Class UID scanner.AddTag( tagSOPClassUID ); const gdcm::Tag tagNumberOfFrames(0x0028, 0x0008); // number of frames scanner.AddTag( tagNumberOfFrames ); scanner.Scan(filenames); // make available image information for each file } std::list DicomSeriesReader::SortIntoBlocksFor3DplusT( const StringContainer& presortedFilenames, const gdcm::Scanner::MappingType& tagValueMappings, bool /*sort*/, bool& canLoadAs4D ) { std::list imageBlocks; // ignore sort request, because most likely re-sorting is now needed due to changes in GetSeries(bug #8022) StringContainer sorted_filenames = DicomSeriesReader::SortSeriesSlices(presortedFilenames); std::string firstPosition; unsigned int numberOfBlocks(0); // number of 3D image blocks static const gdcm::Tag tagImagePositionPatient(0x0020,0x0032); //Image position (Patient) // loop files to determine number of image blocks for (StringContainer::const_iterator fileIter = sorted_filenames.begin(); fileIter != sorted_filenames.end(); ++fileIter) { gdcm::Scanner::TagToValue tagToValueMap = tagValueMappings.find( fileIter->c_str() )->second; if(tagToValueMap.find(tagImagePositionPatient) == tagToValueMap.end()) { // we expect to get images w/ missing position information ONLY as separated blocks. assert( presortedFilenames.size() == 1 ); numberOfBlocks = 1; break; } std::string position = tagToValueMap.find(tagImagePositionPatient)->second; MITK_DEBUG << " " << *fileIter << " at " << position; if (firstPosition.empty()) { firstPosition = position; } if ( position == firstPosition ) { ++numberOfBlocks; } else { break; // enough information to know the number of image blocks } } MITK_DEBUG << " ==> Assuming " << numberOfBlocks << " time steps"; if (numberOfBlocks == 0) return imageBlocks; // only possible if called with no files // loop files to sort them into image blocks unsigned int numberOfExpectedSlices(0); for (unsigned int block = 0; block < numberOfBlocks; ++block) { StringContainer filesOfCurrentBlock; for ( StringContainer::const_iterator fileIter = sorted_filenames.begin() + block; fileIter != sorted_filenames.end(); //fileIter += numberOfBlocks) // TODO shouldn't this work? give invalid iterators on first attempts ) { filesOfCurrentBlock.push_back( *fileIter ); for (unsigned int b = 0; b < numberOfBlocks; ++b) { if (fileIter != sorted_filenames.end()) ++fileIter; } } imageBlocks.push_back(filesOfCurrentBlock); if (block == 0) { numberOfExpectedSlices = filesOfCurrentBlock.size(); } else { if (filesOfCurrentBlock.size() != numberOfExpectedSlices) { MITK_WARN << "DicomSeriesReader expected " << numberOfBlocks << " image blocks of " << numberOfExpectedSlices << " images each. Block " << block << " got " << filesOfCurrentBlock.size() << " instead. Cannot load this as 3D+t"; // TODO implement recovery (load as many slices 3D+t as much as possible) canLoadAs4D = false; } } } return imageBlocks; } Image::Pointer DicomSeriesReader ::MultiplexLoadDICOMByITK(const StringContainer& filenames, bool correctTilt, const GantryTiltInformation& tiltInfo, DcmIoType::Pointer& io, CallbackCommand* command, Image::Pointer preLoadedImageBlock) { io = DcmIoType::New(); io->SetFileName(filenames.front().c_str()); io->ReadImageInformation(); if (io->GetPixelType() == itk::ImageIOBase::SCALAR) { return MultiplexLoadDICOMByITKScalar(filenames, correctTilt, tiltInfo, io, command ,preLoadedImageBlock); } else if (io->GetPixelType() == itk::ImageIOBase::RGB) { return MultiplexLoadDICOMByITKRGBPixel(filenames, correctTilt, tiltInfo, io, command ,preLoadedImageBlock); } else { return NULL; } } Image::Pointer DicomSeriesReader ::MultiplexLoadDICOMByITK4D( std::list& imageBlocks, ImageBlockDescriptor imageBlockDescriptor, bool correctTilt, const GantryTiltInformation& tiltInfo, DcmIoType::Pointer& io, CallbackCommand* command, Image::Pointer preLoadedImageBlock) { io = DcmIoType::New(); io->SetFileName(imageBlocks.front().front().c_str()); io->ReadImageInformation(); if (io->GetPixelType() == itk::ImageIOBase::SCALAR) { return MultiplexLoadDICOMByITK4DScalar(imageBlocks, imageBlockDescriptor, correctTilt, tiltInfo, io, command ,preLoadedImageBlock); } else if (io->GetPixelType() == itk::ImageIOBase::RGB) { return MultiplexLoadDICOMByITK4DRGBPixel(imageBlocks, imageBlockDescriptor, correctTilt, tiltInfo, io, command ,preLoadedImageBlock); } else { return NULL; } } } // end namespace mitk diff --git a/Core/Code/Testing/CMakeLists.txt b/Core/Code/Testing/CMakeLists.txt index 2bde005f97..e356982181 100644 --- a/Core/Code/Testing/CMakeLists.txt +++ b/Core/Code/Testing/CMakeLists.txt @@ -1,261 +1,258 @@ # The core tests need relaxed compiler flags... # TODO fix core tests to compile without these additional no-error flags if(MSVC_VERSION) # disable deprecated warnings (they would lead to errors) mitkFunctionCheckCAndCXXCompilerFlags("/wd4996" CMAKE_C_FLAGS CMAKE_CXX_FLAGS) else() mitkFunctionCheckCAndCXXCompilerFlags("-Wno-error=deprecated" CMAKE_C_FLAGS CMAKE_CXX_FLAGS) mitkFunctionCheckCAndCXXCompilerFlags("-Wno-error=deprecated-declarations" CMAKE_C_FLAGS CMAKE_CXX_FLAGS) endif() MITK_CREATE_MODULE_TESTS(LABELS MITK-Core) # MITK_INSTALL_TARGETS(EXECUTABLES MitkTestDriver) mitkAddCustomModuleTest(mitkVolumeCalculatorTest_Png2D-bw mitkVolumeCalculatorTest ${MITK_DATA_DIR}/Png2D-bw.png ${MITK_DATA_DIR}/Pic2DplusT.nrrd ) mitkAddCustomModuleTest(mitkEventMapperTest_Test1And2 mitkEventMapperTest ${MITK_DATA_DIR}/TestStateMachine1.xml ${MITK_DATA_DIR}/TestStateMachine2.xml ) mitkAddCustomModuleTest(mitkEventConfigTest_CreateObjectInDifferentWays mitkEventConfigTest ${MITK_SOURCE_DIR}/Core/Code/Testing/Resources/Interactions/StatemachineConfigTest.xml ) mitkAddCustomModuleTest(mitkNodeDependentPointSetInteractorTest mitkNodeDependentPointSetInteractorTest ${MITK_DATA_DIR}/Pic3D.nrrd ${MITK_DATA_DIR}/BallBinary30x30x30.nrrd ) mitkAddCustomModuleTest(mitkDataStorageTest_US4DCyl mitkDataStorageTest ${MITK_DATA_DIR}/US4DCyl.nrrd ) mitkAddCustomModuleTest(mitkStateMachineFactoryTest_TestStateMachine1_2 mitkStateMachineFactoryTest ${MITK_DATA_DIR}/TestStateMachine1.xml ${MITK_DATA_DIR}/TestStateMachine2.xml ) mitkAddCustomModuleTest(mitkDicomSeriesReaderTest_CTImage mitkDicomSeriesReaderTest ${MITK_DATA_DIR}/TinyCTAbdomen ${MITK_DATA_DIR}/DICOMReader/Broken-Series ) mitkAddCustomModuleTest(mitkPointSetReaderTest mitkPointSetReaderTest ${MITK_DATA_DIR}/PointSetReaderTestData.mps ) mitkAddCustomModuleTest(mitkImageTest_4DImageData mitkImageTest ${MITK_DATA_DIR}/US4DCyl.nrrd ) mitkAddCustomModuleTest(mitkImageTest_2D+tImageData mitkImageTest ${MITK_DATA_DIR}/Pic2DplusT.nrrd ) mitkAddCustomModuleTest(mitkImageTest_3DImageData mitkImageTest ${MITK_DATA_DIR}/Pic3D.nrrd ) mitkAddCustomModuleTest(mitkImageTest_brainImage mitkImageTest ${MITK_DATA_DIR}/brain.mhd ) mitkAddCustomModuleTest(mitkImageTest_3DImageData mitkImageGeneratorTest ${MITK_DATA_DIR}/Pic3D.nrrd ) mitkAddCustomModuleTest(mitkLevelWindowManagerTest mitkLevelWindowManagerTest ${MITK_DATA_DIR}/Pic3D.nrrd ) mitkAddCustomModuleTest(mitkMultiComponentImageDataComparisonFilterTest mitkMultiComponentImageDataComparisonFilterTest ${MITK_DATA_DIR}/NrrdWritingTestImage.jpg ) mitkAddCustomModuleTest(mitkImageToItkTest mitkImageToItkTest ${MITK_DATA_DIR}/Pic3D.nrrd ) mitkAddCustomModuleTest(mitkImageSliceSelectorTest mitkImageSliceSelectorTest ${MITK_DATA_DIR}/Pic2DplusT.nrrd ) if(MITK_ENABLE_RENDERING_TESTING) ### since the rendering test's do not run in ubuntu, yet, we build them only for other systems or if the user explicitly sets the variable MITK_ENABLE_RENDERING_TESTING mitkAddCustomModuleTest(mitkImageVtkMapper2D_rgbaImage640x480 mitkImageVtkMapper2DTest ${MITK_DATA_DIR}/RenderingTestData/rgbaImage.png #input image to load in data storage -V ${MITK_DATA_DIR}/RenderingTestData/ReferenceScreenshots/rgbaImage640x480REF.png #corresponding reference screenshot ) mitkAddCustomModuleTest(mitkImageVtkMapper2D_pic3d640x480 mitkImageVtkMapper2DTest #test for standard Pic3D axial slice ${MITK_DATA_DIR}/Pic3D.nrrd #input image to load in data storage -V ${MITK_DATA_DIR}/RenderingTestData/ReferenceScreenshots/pic3d640x480REF.png #corresponding reference screenshot ) mitkAddCustomModuleTest(mitkImageVtkMapper2D_pic3dColorBlue640x480 mitkImageVtkMapper2DColorTest #test for color property (=blue) Pic3D sagittal slice ${MITK_DATA_DIR}/Pic3D.nrrd #input image to load in data storage -V ${MITK_DATA_DIR}/RenderingTestData/ReferenceScreenshots/pic3dColorBlue640x480REF.png #corresponding reference screenshot ) mitkAddCustomModuleTest(mitkImageVtkMapper2D_pic3dLevelWindow640x480 mitkImageVtkMapper2DLevelWindowTest #test for levelwindow property (=blood) #Pic3D sagittal slice ${MITK_DATA_DIR}/Pic3D.nrrd #input image to load in data storage -V ${MITK_DATA_DIR}/RenderingTestData/ReferenceScreenshots/pic3dLevelWindowBlood640x480REF.png #corresponding reference #screenshot ) #mitkAddCustomModuleTest(mitkImageVtkMapper2D_pic3dOpacity640x480 mitkImageVtkMapper2DOpacityTest #test for opacity (=0.5) Pic3D coronal slice # ${MITK_DATA_DIR}/Pic3D.nrrd #input image to load in data storage # -V ${MITK_DATA_DIR}/RenderingTestData/ReferenceScreenshots/pic3dOpacity640x480REF.png corresponding reference screenshot #) mitkAddCustomModuleTest(mitkImageVtkMapper2D_pic3dSwivel640x480 mitkImageVtkMapper2DSwivelTest #test for a randomly chosen Pic3D swivelled slice ${MITK_DATA_DIR}/Pic3D.nrrd #input image to load in data storage -V ${MITK_DATA_DIR}/RenderingTestData/ReferenceScreenshots/pic3dSwivel640x480REF.png #corresponding reference screenshot ) mitkAddCustomModuleTest(mitkPointSetVtkMapper2D_openMeAlone640x480 mitkPointSetVtkMapper2DTest ${MITK_DATA_DIR}/RenderingTestData/openMeAlone.mps #input point set to load in data storage -V ${MITK_DATA_DIR}/RenderingTestData/ReferenceScreenshots/openMeAlone640x480REF.png #corresponding reference screenshot ) mitkAddCustomModuleTest(mitkPointSetVtkMapper2D_Pic3DPointSetForPic3D640x480 mitkPointSetVtkMapper2DImageTest ${MITK_DATA_DIR}/Pic3D.nrrd ${MITK_DATA_DIR}/RenderingTestData/PointSetForPic3D.mps #input point set and image to load in data storage -V ${MITK_DATA_DIR}/RenderingTestData/ReferenceScreenshots/Pic3DPointSetForPic3D640x480REF.png #corresponding reference screenshot ) mitkAddCustomModuleTest(mitkPointSetVtkMapper2D_openMeAloneGlyphType640x480 mitkPointSetVtkMapper2DGlyphTypeTest ${MITK_DATA_DIR}/RenderingTestData/openMeAlone.mps #input point set to load in data storage -V ${MITK_DATA_DIR}/RenderingTestData/ReferenceScreenshots/openMeAloneGlyphType640x480REF.png #corresponding reference screenshot ) mitkAddCustomModuleTest(mitkPointSetVtkMapper2D_openMeAloneTransformed640x480 mitkPointSetVtkMapper2DTransformedPointsTest ${MITK_DATA_DIR}/RenderingTestData/openMeAlone.mps #input point set to load in data storage -V ${MITK_DATA_DIR}/RenderingTestData/ReferenceScreenshots/openMeAloneTransformedPoints640x480REF.png #corresponding reference screenshot ) #Test reslice interpolation #note: nearest mode is already tested by swivel test mitkAddCustomModuleTest(ResliceInterpolationIsLinear mitkImageVtkMapper2DResliceInterpolationPropertyTest 1 #linear ${MITK_DATA_DIR}/Pic3D.nrrd -V ${MITK_DATA_DIR}/RenderingTestData/ReferenceScreenshots/pic3dRefLinear.png #corresponding reference screenshot LINEAR ) mitkAddCustomModuleTest(ResliceInterpolationIsCubic mitkImageVtkMapper2DResliceInterpolationPropertyTest 3 #cubic ${MITK_DATA_DIR}/Pic3D.nrrd -V ${MITK_DATA_DIR}/RenderingTestData/ReferenceScreenshots/pic3dRefCubic.png #corresponding reference screenshot CUBIC ) #End test reslice interpolation #Overlays mitkAddCustomModuleTest(mitkLabelOverlay3DRendering2DTest mitkLabelOverlay3DRendering2DTest #OverlayTest -V ${MITK_DATA_DIR}/RenderingTestData/ReferenceScreenshots/mitkLabelOverlay3DRendering2DTest.png #corresponding reference screenshot ) mitkAddCustomModuleTest(mitkLabelOverlay3DRendering3DTest mitkLabelOverlay3DRendering3DTest #OverlayTest -V ${MITK_DATA_DIR}/RenderingTestData/ReferenceScreenshots/mitkLabelOverlay3DRendering3DTest.png #corresponding reference screenshot ) mitkAddCustomModuleTest(mitkTextOverlay2DRenderingTest_ball mitkTextOverlay2DRenderingTest #OverlayTest ${MITK_DATA_DIR}/ball.stl #input image to load in data storage -V ${MITK_DATA_DIR}/RenderingTestData/ReferenceScreenshots/mitkTextOverlay2DRenderingTest_ball.png #corresponding reference screenshot ) #mitkAddCustomModuleTest(mitkTextOverlay2DLayouterRenderingTest_ball mitkTextOverlay2DLayouterRenderingTest #OverlayTest # ${MITK_DATA_DIR}/ball.stl #input image to load in data storage # -V ${MITK_DATA_DIR}/RenderingTestData/ReferenceScreenshots/mitkTextOverlay2DLayouterRenderingTest_ball.png #corresponding reference screenshot #) mitkAddCustomModuleTest(mitkTextOverlay3DRendering2DTest_ball mitkTextOverlay3DRendering2DTest #OverlayTest ${MITK_DATA_DIR}/ball.stl #input image to load in data storage -V ${MITK_DATA_DIR}/RenderingTestData/ReferenceScreenshots/mitkTextOverlay3DRendering2DTest_ball.png #corresponding reference screenshot ) mitkAddCustomModuleTest(mitkTextOverlay3DRendering3DTest_ball mitkTextOverlay3DRendering3DTest #OverlayTest ${MITK_DATA_DIR}/ball.stl #input image to load in data storage -V ${MITK_DATA_DIR}/RenderingTestData/ReferenceScreenshots/mitkTextOverlay3DRendering3DTest_ball.png #corresponding reference screenshot ) mitkAddCustomModuleTest(mitkTextOverlay3DColorRenderingTest_ball mitkTextOverlay3DColorRenderingTest #OverlayTest ${MITK_DATA_DIR}/ball.stl #input image to load in data storage -V ${MITK_DATA_DIR}/RenderingTestData/ReferenceScreenshots/mitkTextOverlay3DColorRenderingTest_ball.png #corresponding reference screenshot ) #End of overlayTests # Testing of the rendering of binary images #mitkAddCustomModuleTest(mitkImageVtkMapper2D_binaryTestImage640x480 mitkImageVtkMapper2DTest #test for standard Pic3D axial slice # ${MITK_DATA_DIR}/RenderingTestData/binaryImage.nrrd #input image to load in data storage # -V ${MITK_DATA_DIR}/RenderingTestData/ReferenceScreenshots/binaryImage640x480REF.png #corresponding reference screenshot #) #mitkAddCustomModuleTest(mitkImageVtkMapper2D_binaryTestImageWithRef640x480 mitkImageVtkMapper2DTest #test for standard Pic3D axial slice # ${MITK_DATA_DIR}/Pic3D.nrrd ${MITK_DATA_DIR}/RenderingTestData/binaryImage.nrrd #input image to load in data storage # -V ${MITK_DATA_DIR}/RenderingTestData/ReferenceScreenshots/binaryImageWithRef640x480REF.png #corresponding reference screenshot #) # End of binary image tests mitkAddCustomModuleTest(mitkSurfaceVtkMapper3DTest_TextureProperty mitkSurfaceVtkMapper3DTest ${MITK_DATA_DIR}/ToF-Data/Kinect_LiverPhantom.vtp ${MITK_DATA_DIR}/ToF-Data/Kinect_LiverPhantom_RGBImage.nrrd -V ${MITK_DATA_DIR}/RenderingTestData/ReferenceScreenshots/texturedLiver640x480REF.png #corresponding reference screenshot ) mitkAddCustomModuleTest(mitkImageVtkMapper2DTransferFunctionTest_Png2D-bw mitkImageVtkMapper2DTransferFunctionTest ${MITK_DATA_DIR}/Png2D-bw.png -V ${MITK_DATA_DIR}/RenderingTestData/ReferenceScreenshots/Png2D-bw-TransferFunctionRGBImage640x480REF.png #corresponding reference screenshot ) mitkAddCustomModuleTest(mitkSurfaceGLMapper2DColorTest_RedBall mitkSurfaceGLMapper2DColorTest ${MITK_DATA_DIR}/ball.stl -V ${MITK_DATA_DIR}/RenderingTestData/ReferenceScreenshots/ballColorRed640x480REF.png #corresponding reference screenshot ) mitkAddCustomModuleTest(mitkSurfaceGLMapper2DColorTest_DasArmeSchwein mitkSurfaceGLMapper2DColorTest ${MITK_DATA_DIR}/binary.stl -V ${MITK_DATA_DIR}/RenderingTestData/ReferenceScreenshots/binaryColorRed640x480REF.png #corresponding reference screenshot ) mitkAddCustomModuleTest(mitkSurfaceGLMapper2DOpacityTest_BallOpacity mitkSurfaceGLMapper2DOpacityTest #opacity = 50% (0.5) ${MITK_DATA_DIR}/ball.stl -V ${MITK_DATA_DIR}/RenderingTestData/ReferenceScreenshots/ballOpacity640x480REF.png #corresponding reference screenshot ) ############################## DISABLED TESTS #Removed due to high rendering error. #mitkAddCustomModuleTest(mitkSurfaceVtkMapper3DTexturedSphereTest_Football mitkSurfaceVtkMapper3DTexturedSphereTest # ${MITK_DATA_DIR}/RenderingTestData/texture.jpg #input texture # -V # ${MITK_DATA_DIR}/RenderingTestData/ReferenceScreenshots/texturedSphere640x480REF.png corresponding reference screenshot #) #mitkAddCustomModuleTest(mitkImageVtkMapper2DLookupTableTest_Png2D-bw mitkImageVtkMapper2DLookupTableTest # ${MITK_DATA_DIR}/Png2D-bw.png # -V ${MITK_DATA_DIR}/RenderingTestData/ReferenceScreenshots/Png2D-bw-LookupTableRGBImage640x480REF.png #corresponding reference screenshot #) #mitkAddCustomModuleTest(mitkImageTest_color2DImage mitkImageTest # ${MITK_DATA_DIR}/NrrdWritingTestImage.jpg #) #mitkAddCustomModuleTest(mitkNodeDependentPointSetInteractorTest mitkNodeDependentPointSetInteractorTest # ${MITK_DATA_DIR}/Pic3D.pic.gz ${MITK_DATA_DIR}/BallBinary30x30x30.pic.gz #) SET_PROPERTY(TEST mitkImageVtkMapper2D_rgbaImage640x480 mitkImageVtkMapper2D_pic3d640x480 mitkImageVtkMapper2D_pic3dColorBlue640x480 mitkImageVtkMapper2D_pic3dLevelWindow640x480 mitkImageVtkMapper2D_pic3dSwivel640x480 mitkImageVtkMapper2DTransferFunctionTest_Png2D-bw # mitkImageVtkMapper2D_pic3dOpacity640x480 mitkSurfaceGLMapper2DOpacityTest_BallOpacity mitkSurfaceGLMapper2DColorTest_DasArmeSchwein mitkSurfaceGLMapper2DColorTest_RedBall mitkSurfaceVtkMapper3DTest_TextureProperty mitkPointSetVtkMapper2D_Pic3DPointSetForPic3D640x480 mitkPointSetVtkMapper2D_openMeAlone640x480 mitkPointSetVtkMapper2D_openMeAloneGlyphType640x480 mitkPointSetVtkMapper2D_openMeAloneTransformed640x480 #mitkSurfaceVtkMapper3DTexturedSphereTest_Football PROPERTY RUN_SERIAL TRUE) endif() add_test(mitkPointSetLocaleTest ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${TESTDRIVER} mitkPointSetLocaleTest ${MITK_DATA_DIR}/pointSet.mps) set_property(TEST mitkPointSetLocaleTest PROPERTY LABELS MITK-Core) add_test(mitkImageWriterTest_nrrdImage ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${TESTDRIVER} mitkImageWriterTest ${MITK_DATA_DIR}/NrrdWritingTestImage.jpg) add_test(mitkImageWriterTest_2DPNGImage ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${TESTDRIVER} mitkImageWriterTest ${MITK_DATA_DIR}/Png2D-bw.png) add_test(mitkImageWriterTest_rgbPNGImage ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${TESTDRIVER} mitkImageWriterTest ${MITK_DATA_DIR}/RenderingTestData/rgbImage.png) add_test(mitkImageWriterTest_rgbaPNGImage ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${TESTDRIVER} mitkImageWriterTest ${MITK_DATA_DIR}/RenderingTestData/rgbaImage.png) set_property(TEST mitkImageWriterTest_nrrdImage PROPERTY LABELS MITK-Core) set_property(TEST mitkImageWriterTest_2DPNGImage PROPERTY LABELS MITK-Core) set_property(TEST mitkImageWriterTest_rgbPNGImage PROPERTY LABELS MITK-Core) set_property(TEST mitkImageWriterTest_rgbaPNGImage PROPERTY LABELS MITK-Core) - -add_subdirectory(DICOMTesting) - diff --git a/Core/Code/Testing/files.cmake b/Core/Code/Testing/files.cmake index 3032e20546..472d7a4ac1 100644 --- a/Core/Code/Testing/files.cmake +++ b/Core/Code/Testing/files.cmake @@ -1,183 +1,181 @@ # tests with no extra command line parameter set(MODULE_TESTS mitkAccessByItkTest.cpp mitkCoreObjectFactoryTest.cpp mitkMaterialTest.cpp mitkActionTest.cpp mitkDispatcherTest.cpp mitkEnumerationPropertyTest.cpp mitkEventTest.cpp mitkFocusManagerTest.cpp mitkGenericPropertyTest.cpp mitkGeometry2DTest.cpp mitkGeometry3DTest.cpp mitkGeometry3DEqualTest.cpp mitkGeometryDataToSurfaceFilterTest.cpp mitkGlobalInteractionTest.cpp mitkImageEqualTest.cpp mitkImageDataItemTest.cpp mitkImageGeneratorTest.cpp mitkBaseDataTest.cpp mitkImportItkImageTest.cpp mitkGrabItkImageMemoryTest.cpp mitkInstantiateAccessFunctionTest.cpp mitkInteractorTest.cpp mitkLevelWindowTest.cpp mitkMessageTest.cpp mitkPixelTypeTest.cpp mitkPlaneGeometryTest.cpp mitkPointSetEqualTest.cpp mitkPointSetFileIOTest.cpp mitkPointSetTest.cpp mitkPointSetWriterTest.cpp mitkPointSetReaderTest.cpp mitkPointSetInteractorTest.cpp mitkPropertyTest.cpp mitkPropertyListTest.cpp mitkSlicedGeometry3DTest.cpp mitkSliceNavigationControllerTest.cpp mitkStateMachineTest.cpp mitkStateTest.cpp mitkSurfaceTest.cpp mitkSurfaceEqualTest.cpp mitkSurfaceToSurfaceFilterTest.cpp mitkTimeGeometryTest.cpp mitkTransitionTest.cpp mitkUndoControllerTest.cpp mitkVtkWidgetRenderingTest.cpp mitkVerboseLimitedLinearUndoTest.cpp mitkWeakPointerTest.cpp mitkTransferFunctionTest.cpp mitkStepperTest.cpp itkTotalVariationDenoisingImageFilterTest.cpp mitkRenderingManagerTest.cpp vtkMitkThickSlicesFilterTest.cpp mitkNodePredicateSourceTest.cpp mitkVectorTest.cpp mitkClippedSurfaceBoundsCalculatorTest.cpp mitkExceptionTest.cpp mitkExtractSliceFilterTest.cpp mitkLogTest.cpp mitkImageDimensionConverterTest.cpp mitkLoggingAdapterTest.cpp mitkUIDGeneratorTest.cpp mitkShaderRepositoryTest.cpp mitkPlanePositionManagerTest.cpp mitkAffineTransformBaseTest.cpp mitkPropertyAliasesTest.cpp mitkPropertyDescriptionsTest.cpp mitkPropertyExtensionsTest.cpp mitkPropertyFiltersTest.cpp mitkTinyXMLTest.cpp mitkRawImageFileReaderTest.cpp mitkInteractionEventTest.cpp mitkLookupTableTest.cpp mitkSTLFileReaderTest.cpp ################## DISABLED TESTS ################################################# #mitkAbstractTransformGeometryTest.cpp #seems as tested class mitkExternAbstractTransformGeometry doesnt exist any more #mitkStateMachineContainerTest.cpp #rewrite test, indirect since no longer exported Bug 14529 #mitkRegistrationBaseTest.cpp #tested class mitkRegistrationBase doesn't exist any more #mitkSegmentationInterpolationTest.cpp #file doesn't exist! #mitkPipelineSmartPointerCorrectnessTest.cpp #file doesn't exist! #mitkITKThreadingTest.cpp #test outdated because itk::Semaphore was removed from ITK #mitkAbstractTransformPlaneGeometryTest.cpp #mitkVtkAbstractTransformPlaneGeometry doesn't exist any more #mitkTestUtilSharedLibrary.cpp #Linker problem with this test... #mitkTextOverlay2DSymbolsRenderingTest.cpp #Implementation of the tested feature is not finished yet. Ask Christoph or see bug 15104 for details. ) # test with image filename as an extra command line parameter set(MODULE_IMAGE_TESTS mitkImageTimeSelectorTest.cpp #only runs on images mitkImageAccessorTest.cpp #only runs on images mitkDataNodeFactoryTest.cpp #runs on all types of data ) set(MODULE_SURFACE_TESTS mitkSurfaceVtkWriterTest.cpp #only runs on surfaces mitkDataNodeFactoryTest.cpp #runs on all types of data ) # list of images for which the tests are run set(MODULE_TESTIMAGES US4DCyl.nrrd Pic3D.nrrd Pic2DplusT.nrrd BallBinary30x30x30.nrrd Png2D-bw.png ) set(MODULE_TESTSURFACES binary.stl ball.stl ) set(MODULE_CUSTOM_TESTS mitkDataStorageTest.cpp mitkDataNodeTest.cpp - mitkDicomSeriesReaderTest.cpp - mitkDICOMLocaleTest.cpp mitkEventMapperTest.cpp mitkEventConfigTest.cpp mitkNodeDependentPointSetInteractorTest.cpp mitkStateMachineFactoryTest.cpp mitkPointSetLocaleTest.cpp mitkImageTest.cpp mitkImageWriterTest.cpp mitkImageVtkMapper2DTest.cpp mitkImageVtkMapper2DLevelWindowTest.cpp mitkImageVtkMapper2DOpacityTest.cpp mitkImageVtkMapper2DResliceInterpolationPropertyTest.cpp mitkImageVtkMapper2DColorTest.cpp mitkImageVtkMapper2DSwivelTest.cpp mitkImageVtkMapper2DTransferFunctionTest.cpp mitkImageVtkMapper2DLookupTableTest.cpp mitkIOUtilTest.cpp mitkSurfaceVtkMapper3DTest mitkSurfaceVtkMapper3DTexturedSphereTest.cpp mitkSurfaceGLMapper2DColorTest.cpp mitkSurfaceGLMapper2DOpacityTest.cpp mitkVolumeCalculatorTest.cpp mitkLevelWindowManagerTest.cpp mitkPointSetVtkMapper2DTest.cpp mitkPointSetVtkMapper2DImageTest.cpp mitkPointSetVtkMapper2DGlyphTypeTest.cpp mitkPointSetVtkMapper2DTransformedPointsTest.cpp mitkLabelOverlay3DRendering2DTest.cpp mitkLabelOverlay3DRendering3DTest.cpp mitkTextOverlay2DRenderingTest.cpp mitkTextOverlay2DLayouterRenderingTest.cpp mitkTextOverlay3DRendering2DTest.cpp mitkTextOverlay3DRendering3DTest.cpp mitkTextOverlay3DColorRenderingTest.cpp mitkVTKRenderWindowSizeTest.cpp mitkMultiComponentImageDataComparisonFilterTest.cpp mitkImageToItkTest.cpp mitkImageSliceSelectorTest.cpp ) if (${VTK_MAJOR_VERSION} VERSION_LESS 6) # test can be removed with VTK 6 set(MODULE_TESTS ${MODULE_TESTS} mitkVTKRenderWindowSizeTest.cpp) endif() set(MODULE_RESOURCE_FILES Interactions/AddAndRemovePoints.xml Interactions/globalConfig.xml Interactions/StatemachineTest.xml Interactions/StatemachineConfigTest.xml ) # Create an artificial module initializing class for # the usServiceListenerTest.cpp usFunctionGenerateExecutableInit(testdriver_init_file IDENTIFIER ${MODULE_NAME}TestDriver ) # Embed the resources set(testdriver_resources ) usFunctionEmbedResources(testdriver_resources EXECUTABLE_NAME ${MODULE_NAME}TestDriver ROOT_DIR ${CMAKE_CURRENT_SOURCE_DIR}/Resources FILES ${MODULE_RESOURCE_FILES} ) set(TEST_CPP_FILES ${testdriver_init_file} ${testdriver_resources}) diff --git a/Core/Code/Testing/mitkDicomSeriesReaderTest.cpp b/Core/Code/Testing/mitkDicomSeriesReaderTest.cpp deleted file mode 100644 index 408d3dece3..0000000000 --- a/Core/Code/Testing/mitkDicomSeriesReaderTest.cpp +++ /dev/null @@ -1,171 +0,0 @@ -/*=================================================================== - -The Medical Imaging Interaction Toolkit (MITK) - -Copyright (c) German Cancer Research Center, -Division of Medical and Biological Informatics. -All rights reserved. - -This software is distributed WITHOUT ANY WARRANTY; without -even the implied warranty of MERCHANTABILITY or FITNESS FOR -A PARTICULAR PURPOSE. - -See LICENSE.txt or http://www.mitk.org for details. - -===================================================================*/ - -#include "mitkTestingMacros.h" - -#include - -#include "mitkDicomSeriesReader.h" -#include "mitkProperties.h" - - -static std::map > GetTagInformationFromFile(mitk::DicomSeriesReader::StringContainer files) -{ - gdcm::Scanner scanner; - std::map > tagInformations; - - const gdcm::Tag tagSliceLocation(0x0020, 0x1041); // slice location - scanner.AddTag( tagSliceLocation ); - - const gdcm::Tag tagInstanceNumber(0x0020, 0x0013); // (image) instance number - scanner.AddTag( tagInstanceNumber ); - - const gdcm::Tag tagSOPInstanceNumber(0x0008, 0x0018); // SOP instance number - scanner.AddTag( tagSOPInstanceNumber ); - - //unsigned int slice(0); - scanner.Scan(files); - -// return const_cast(scanner.GetMappings()); - gdcm::Scanner::MappingType& tagValueMappings = const_cast(scanner.GetMappings()); - - for(std::vector::const_iterator fIter = files.begin(); - fIter != files.end(); - ++fIter) - { - std::map tags; - tags.insert(std::pair (tagSliceLocation, tagValueMappings[fIter->c_str()][tagSliceLocation])); - tags.insert(std::pair (tagInstanceNumber, tagValueMappings[fIter->c_str()][tagInstanceNumber])); - tags.insert(std::pair (tagSOPInstanceNumber, tagValueMappings[fIter->c_str()][tagSOPInstanceNumber])); - - tagInformations.insert(std::pair > (fIter->c_str(), tags)); - } - - return tagInformations; -} - -int mitkDicomSeriesReaderTest(int argc, char* argv[]) -{ - // always start with this! - MITK_TEST_BEGIN("DicomSeriesReader") - if(argc < 1) - { - MITK_ERROR << "No directory given!"; - return -1; - } - - char* dir; - - // Second argument can hold a broken series, - // here it is only tested that the loading fails gracefully without causing a seg fault, - // or another crash of any kind. - if (argc > 1) - { - dir = argv[2]; - mitk::DicomSeriesReader::FileNamesGrouping brokenSeries = mitk::DicomSeriesReader::GetSeries( dir, true ); - } - - dir = argv[1]; - //check if DICOMTags have been set as property for mitk::Image - mitk::DicomSeriesReader::FileNamesGrouping seriesInFiles = mitk::DicomSeriesReader::GetSeries( dir, true ); - std::list images; - std::map fileMap; - - // TODO sort series UIDs, implementation of map iterator might differ on different platforms (or verify this is a standard topic??) - for (mitk::DicomSeriesReader::FileNamesGrouping::const_iterator seriesIter = seriesInFiles.begin(); - seriesIter != seriesInFiles.end(); - ++seriesIter) - { - mitk::DicomSeriesReader::StringContainer files = seriesIter->second.GetFilenames(); - - mitk::DataNode::Pointer node = mitk::DicomSeriesReader::LoadDicomSeries( files ); - MITK_TEST_CONDITION_REQUIRED(node.IsNotNull(),"Testing node") - - if (node.IsNotNull()) - { - mitk::Image::Pointer image = dynamic_cast( node->GetData() ); - - images.push_back( image ); - fileMap.insert( std::pair(image,files)); - } - } - - //Test if DICOM tags have been added correctly to the mitk::image properties - - const gdcm::Tag tagSliceLocation(0x0020, 0x1041); // slice location - const gdcm::Tag tagInstanceNumber(0x0020, 0x0013); // (image) instance number - const gdcm::Tag tagSOPInstanceNumber(0x0008, 0x0018); // SOP instance number - - for ( std::list::const_iterator imageIter = images.begin(); - imageIter != images.end(); - ++imageIter ) - { - const mitk::Image::Pointer image = *imageIter; - - //Get tag information for all dicom files of this image - std::map > tagInformations = GetTagInformationFromFile((*fileMap.find(image)).second); - - mitk::StringLookupTableProperty* sliceLocation = dynamic_cast(image->GetProperty("dicom.image.0020.1041").GetPointer()); - mitk::StringLookupTableProperty* instanceNumber = dynamic_cast(image->GetProperty("dicom.image.0020.0013").GetPointer()); - mitk::StringLookupTableProperty* SOPInstnaceNumber = dynamic_cast(image->GetProperty("dicom.image.0008.0018").GetPointer()); - - mitk::StringLookupTableProperty* files = dynamic_cast(image->GetProperty("files").GetPointer()); - - MITK_TEST_CONDITION(sliceLocation != NULL, "Test if tag for slice location has been set to mitk image"); - if(sliceLocation != NULL) - { - for(int i = 0; i < (int)sliceLocation->GetValue().GetLookupTable().size(); i++) - { - if(i < (int)files->GetValue().GetLookupTable().size()) - { - MITK_INFO << "Table value: " << sliceLocation->GetValue().GetTableValue(i) << " and File value: " << tagInformations[files->GetValue().GetTableValue(i).c_str()][tagSliceLocation] << std::endl; - MITK_INFO << "Filename: " << files->GetValue().GetTableValue(i).c_str() << std::endl; - MITK_TEST_CONDITION(sliceLocation->GetValue().GetTableValue(i) == tagInformations[files->GetValue().GetTableValue(i).c_str()][tagSliceLocation], "Test if value for slice location is correct"); - } - } - } - - MITK_TEST_CONDITION(instanceNumber != NULL, "Test if tag for image instance number has been set to mitk image"); - if(instanceNumber != NULL) - { - for(int i = 0; i < (int)instanceNumber->GetValue().GetLookupTable().size(); i++) - { - if(i < (int)files->GetValue().GetLookupTable().size()) - { - MITK_INFO << "Table value: " << instanceNumber->GetValue().GetTableValue(i) << " and File value: " << tagInformations[files->GetValue().GetTableValue(i).c_str()][tagInstanceNumber] << std::endl; - MITK_INFO << "Filename: " << files->GetValue().GetTableValue(i).c_str() << std::endl; - MITK_TEST_CONDITION(instanceNumber->GetValue().GetTableValue(i) == tagInformations[files->GetValue().GetTableValue(i).c_str()][tagInstanceNumber], "Test if value for instance number is correct"); - } - } - } - - MITK_TEST_CONDITION(SOPInstnaceNumber != NULL, "Test if tag for SOP instance number has been set to mitk image"); - if(SOPInstnaceNumber != NULL) - { - for(int i = 0; i < (int)SOPInstnaceNumber->GetValue().GetLookupTable().size(); i++) - { - if(i < (int)files->GetValue().GetLookupTable().size()) - { - MITK_INFO << "Table value: " << instanceNumber->GetValue().GetTableValue(i) << " and File value: " << tagInformations[files->GetValue().GetTableValue(i).c_str()][tagSOPInstanceNumber] << std::endl; - MITK_INFO << "Filename: " << files->GetValue().GetTableValue(i).c_str() << std::endl; - MITK_TEST_CONDITION(SOPInstnaceNumber->GetValue().GetTableValue(i) == tagInformations[files->GetValue().GetTableValue(i).c_str()][tagSOPInstanceNumber], "Test if value for SOP instance number is correct"); - } - } - } - } - - MITK_TEST_END() -} diff --git a/Examples/CMakeLists.txt b/Examples/CMakeLists.txt index 52098d1950..d02387e69e 100644 --- a/Examples/CMakeLists.txt +++ b/Examples/CMakeLists.txt @@ -1,56 +1,57 @@ set(MITK_DEFAULT_SUBPROJECTS MITK-Examples) #----------------------------------------------------------------------------- # Set-up example plugins #----------------------------------------------------------------------------- if(MITK_USE_BLUEBERRY) # Specify which plug-ins belong to this project macro(GetMyTargetLibraries all_target_libraries varname) set(re_ctkplugin_mitk "^org_mitk_example_[a-zA-Z0-9_]+$") set(_tmp_list) list(APPEND _tmp_list ${all_target_libraries}) ctkMacroListFilter(_tmp_list re_ctkplugin_mitk OUTPUT_VARIABLE ${varname}) endmacro() set(MITK_EXAMPLE_PLUGIN_TARGETS ) foreach(mitk_example_plugin ${MITK_EXAMPLE_PLUGINS}) ctkFunctionExtractOptionNameAndValue(${mitk_example_plugin} plugin_name plugin_value) string(REPLACE "." "_" _plugin_target ${plugin_name}) list(APPEND MITK_EXAMPLE_PLUGIN_TARGETS ${_plugin_target}) mark_as_advanced(${${_plugin_target}_option_name}) endforeach() endif() #----------------------------------------------------------------------------- # Add example executables #----------------------------------------------------------------------------- set(MITK_DIR ${PROJECT_BINARY_DIR}) set(MITK_EXPORTS_FILE_INCLUDED 1) set(_example_dirs MbiLog QtFreeRender Overlays ) if(MITK_USE_QT) list(APPEND _example_dirs Tutorial QtAppExample + mitkdump ) endif() if(MITK_USE_BLUEBERRY) list(APPEND _example_dirs BlueBerryExampleLauncher ) endif() foreach(_example_dir ${_example_dirs}) add_subdirectory(${_example_dir}) endforeach() diff --git a/Examples/mitkdump/CMakeLists.txt b/Examples/mitkdump/CMakeLists.txt new file mode 100644 index 0000000000..ad822a380c --- /dev/null +++ b/Examples/mitkdump/CMakeLists.txt @@ -0,0 +1,25 @@ +project(mitkdump) +find_package(MITK) + +# Check prerequisites for this application. +# We need the Mitk module. +MITK_CHECK_MODULE(result Mitk) +if(result) + message(SEND_ERROR "MITK module(s) \"${result}\" not available from the MITK build at ${MITK_DIR}") +endif() + +# Set-up the build system to use the Mitk module +MITK_USE_MODULE(Mitk) +MITK_USE_MODULE(DICOMReader) +include_directories(${ALL_INCLUDE_DIRECTORIES}) +link_directories(${ALL_LIBRARY_DIRS}) + +add_executable(${PROJECT_NAME} mitkdump.cpp) +target_link_libraries(${PROJECT_NAME} ${ALL_LIBRARIES} ) + +# subproject support +set_property(TARGET ${PROJECT_NAME} PROPERTY LABELS ${MITK_DEFAULT_SUBPROJECTS}) +foreach(subproject ${MITK_DEFAULT_SUBPROJECTS}) + add_dependencies(${subproject} ${PROJECT_NAME}) +endforeach() + diff --git a/Examples/mitkdump/mitkdump.cpp b/Examples/mitkdump/mitkdump.cpp new file mode 100644 index 0000000000..74173100dd --- /dev/null +++ b/Examples/mitkdump/mitkdump.cpp @@ -0,0 +1,93 @@ +/*=================================================================== + + The Medical Imaging Interaction Toolkit (MITK) + + Copyright (c) German Cancer Research Center, + Division of Medical and Biological Informatics. + All rights reserved. + + This software is distributed WITHOUT ANY WARRANTY; without + even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. + + See LICENSE.txt or http://www.mitk.org for details. + + ===================================================================*/ + +#include "mitkDICOMITKSeriesGDCMReader.h" +#include "mitkDICOMTagBasedSorter.h" +#include "mitkDICOMSortByTag.h" + +using mitk::DICOMTag; + +int main(int argc, char* argv[]) +{ + mitk::StringList inputFiles; // TODO + for (int a = 1; a < argc; ++a) + { + inputFiles.push_back( std::string(argv[a]) ); + } + + // ----------------- Configure reader ------------------- + + mitk::DICOMITKSeriesGDCMReader::Pointer gdcmReader = + mitk::DICOMITKSeriesGDCMReader::New(); + + mitk::DICOMTagBasedSorter::Pointer tagSorter = + mitk::DICOMTagBasedSorter::New(); + + // all the things that split by tag in DicomSeriesReader + tagSorter->AddDistinguishingTag( DICOMTag(0x0028, 0x0010) ); // Number of Rows + tagSorter->AddDistinguishingTag( DICOMTag(0x0028, 0x0011) ); // Number of Columns + tagSorter->AddDistinguishingTag( DICOMTag(0x0028, 0x0030) ); // Pixel Spacing + tagSorter->AddDistinguishingTag( DICOMTag(0x0018, 0x1164) ); // Imager Pixel Spacing + tagSorter->AddDistinguishingTag( DICOMTag(0x0020, 0x0037) ); // Image Orientation (Patient) // TODO add tolerance parameter (l. 1572 of original code) + tagSorter->AddDistinguishingTag( DICOMTag(0x0020, 0x000e) ); // Series Instance UID + tagSorter->AddDistinguishingTag( DICOMTag(0x0018, 0x0050) ); // Slice Thickness + tagSorter->AddDistinguishingTag( DICOMTag(0x0028, 0x0008) ); // Number of Frames + tagSorter->AddDistinguishingTag( DICOMTag(0x0020, 0x0052) ); // Frame of Reference UID + + // a sorter... + // TODO ugly syntax, improve.. + mitk::DICOMSortCriterion::ConstPointer sorting = + mitk::DICOMSortByTag::New( DICOMTag(0x0020, 0x0013), // instance number + mitk::DICOMSortByTag::New( DICOMTag(0x0020, 0x0012), // aqcuisition number + mitk::DICOMSortByTag::New( DICOMTag(0x0008, 0x0032), // aqcuisition time + mitk::DICOMSortByTag::New( DICOMTag(0x0018, 0x1060), // trigger time + mitk::DICOMSortByTag::New( DICOMTag(0x0008, 0x0018) // SOP instance UID (last resort, not really meaningful but decides clearly) + ).GetPointer() + ).GetPointer() + ).GetPointer() + ).GetPointer() + ).GetPointer(); + tagSorter->SetSortCriterion( sorting ); + + gdcmReader->AddSortingElement( tagSorter ); + + // ----------------- Load ------------------- + + gdcmReader->SetInputFiles( inputFiles ); + + MITK_INFO << "Analyzing " << inputFiles.size() << " file ..."; + gdcmReader->AnalyzeInputFiles(); + gdcmReader->PrintOutputs(std::cout, true); + MITK_INFO << "Loading " << inputFiles.size() << " file ..."; + gdcmReader->LoadImages(); + + unsigned int numberOfOutputs = gdcmReader->GetNumberOfOutputs(); + for (unsigned int o = 0; o < numberOfOutputs; ++o) + { + const mitk::DICOMImageBlockDescriptor block = gdcmReader->GetOutput(o); + + const mitk::DICOMImageFrameList& outputFiles = block.GetImageFrameList(); + mitk::Image::Pointer mitkImage = block.GetMitkImage(); + + MITK_INFO << "-------------------------------------------"; + MITK_INFO << "Output " << o << " at " << (void*) mitkImage.GetPointer(); + MITK_INFO << " Number of files: " << outputFiles.size(); + if (mitkImage.IsNotNull()) + { + MITK_INFO << " Dimensions: " << mitkImage->GetDimension(0) << " " << mitkImage->GetDimension(1) << " " << mitkImage->GetDimension(2); + } + } +} diff --git a/Modules/CMakeLists.txt b/Modules/CMakeLists.txt index 2359420bd4..797939bbbf 100644 --- a/Modules/CMakeLists.txt +++ b/Modules/CMakeLists.txt @@ -1,65 +1,67 @@ set(LIBPOSTFIX "Ext") # Modules must be listed according to their dependencies set(module_dirs + DICOMReader + DICOMTesting SceneSerializationBase PlanarFigure ImageExtraction ImageStatistics LegacyAdaptors IpPicSupport MitkExt SceneSerialization GraphAlgorithms ContourModel SurfaceInterpolation Segmentation PlanarFigureSegmentation Qmitk QmitkExt SegmentationUI DiffusionImaging GPGPU IGTBase IGT CameraCalibration IGTUI RigidRegistration RigidRegistrationUI DeformableRegistration DeformableRegistrationUI OpenCL OpenCVVideoSupport Overlays InputDevices ToFHardware ToFProcessing ToFUI US ClippingTools USUI DicomUI Simulation Remeshing Python ) set(MITK_DEFAULT_SUBPROJECTS MITK-Modules) foreach(module_dir ${module_dirs}) add_subdirectory(${module_dir}) endforeach() if(MITK_PRIVATE_MODULES) file(GLOB all_subdirs RELATIVE ${MITK_PRIVATE_MODULES} ${MITK_PRIVATE_MODULES}/*) foreach(subdir ${all_subdirs}) string(FIND ${subdir} "." _result) if(_result EQUAL -1) if(EXISTS ${MITK_PRIVATE_MODULES}/${subdir}/CMakeLists.txt) message(STATUS "Found private module ${subdir}") add_subdirectory(${MITK_PRIVATE_MODULES}/${subdir} private_modules/${subdir}) endif() endif() endforeach() endif(MITK_PRIVATE_MODULES) diff --git a/Modules/DICOMReader/CMakeLists.txt b/Modules/DICOMReader/CMakeLists.txt new file mode 100644 index 0000000000..355cc4eced --- /dev/null +++ b/Modules/DICOMReader/CMakeLists.txt @@ -0,0 +1,6 @@ + +MITK_CREATE_MODULE(DICOMReader + DEPENDS Mitk +) + +add_subdirectory(Testing) diff --git a/Modules/DICOMReader/Testing/CMakeLists.txt b/Modules/DICOMReader/Testing/CMakeLists.txt new file mode 100644 index 0000000000..559920cb95 --- /dev/null +++ b/Modules/DICOMReader/Testing/CMakeLists.txt @@ -0,0 +1,10 @@ +MITK_CREATE_MODULE_TESTS() + +file(GLOB_RECURSE tinyCTSlices ${MITK_DATA_DIR}/TinyCTAbdomen/1??) + +#foreach(f ${tinyCTSlices}) +# message(" ${f}") +#endforeach() + +mitkAddCustomModuleTest(mitkDICOMFileReaderTest_Basics mitkDICOMFileReaderTest ${tinyCTSlices}) +mitkAddCustomModuleTest(mitkDICOMITKSeriesGDCMReaderBasicsTest_Basics mitkDICOMITKSeriesGDCMReaderBasicsTest ${tinyCTSlices}) diff --git a/Modules/DICOMReader/Testing/files.cmake b/Modules/DICOMReader/Testing/files.cmake new file mode 100644 index 0000000000..df7c751ba1 --- /dev/null +++ b/Modules/DICOMReader/Testing/files.cmake @@ -0,0 +1,11 @@ +set(MODULE_TESTS +) + +set(MODULE_CUSTOM_TESTS + mitkDICOMFileReaderTest.cpp + mitkDICOMITKSeriesGDCMReaderBasicsTest.cpp +) + +set(TEST_CPP_FILES + mitkDICOMNullFileReader.cpp +) diff --git a/Modules/DICOMReader/Testing/mitkDICOMFileReaderTest.cpp b/Modules/DICOMReader/Testing/mitkDICOMFileReaderTest.cpp new file mode 100644 index 0000000000..cfbf3bb29d --- /dev/null +++ b/Modules/DICOMReader/Testing/mitkDICOMFileReaderTest.cpp @@ -0,0 +1,35 @@ +/*=================================================================== + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center, +Division of Medical and Biological Informatics. +All rights reserved. + +This software is distributed WITHOUT ANY WARRANTY; without +even the implied warranty of MERCHANTABILITY or FITNESS FOR +A PARTICULAR PURPOSE. + +See LICENSE.txt or http://www.mitk.org for details. + +===================================================================*/ + +#include "mitkDICOMNullFileReader.h" +#include "mitkDICOMFileReaderTestHelper.h" + +#include "mitkTestingMacros.h" + +int mitkDICOMFileReaderTest(int argc, char* argv[]) +{ + MITK_TEST_BEGIN("mitkDICOMFileReaderTest"); + + mitk::DICOMNullFileReader::Pointer simpleReader = mitk::DICOMNullFileReader::New(); + MITK_TEST_CONDITION_REQUIRED(simpleReader.IsNotNull(), "DICOMNullFileReader can be instantiated."); + + mitk::DICOMFileReaderTestHelper::SetTestInputFilenames( argc,argv ); + + mitk::DICOMFileReaderTestHelper::TestInputFilenames( simpleReader ); + mitk::DICOMFileReaderTestHelper::TestOutputsContainInputs( simpleReader ); + + MITK_TEST_END(); +} diff --git a/Modules/DICOMReader/Testing/mitkDICOMFileReaderTestHelper.h b/Modules/DICOMReader/Testing/mitkDICOMFileReaderTestHelper.h new file mode 100644 index 0000000000..42bd14be87 --- /dev/null +++ b/Modules/DICOMReader/Testing/mitkDICOMFileReaderTestHelper.h @@ -0,0 +1,153 @@ +/*=================================================================== + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center, +Division of Medical and Biological Informatics. +All rights reserved. + +This software is distributed WITHOUT ANY WARRANTY; without +even the implied warranty of MERCHANTABILITY or FITNESS FOR +A PARTICULAR PURPOSE. + +See LICENSE.txt or http://www.mitk.org for details. + +===================================================================*/ + +#ifndef mitkDICOMFileReaderTestHelper_h +#define mitkDICOMFileReaderTestHelper_h + +#include "mitkDICOMFileReader.h" +#include "mitkDICOMEnums.h" + +#include "mitkTestingMacros.h" + +namespace mitk +{ + +class DICOMFileReaderTestHelper +{ + public: + +static StringList& GetInputFilenames() +{ + static StringList inputs; + return inputs; +} + +static void SetTestInputFilenames(int argc, char* argv[]) +{ + mitk::StringList inputFiles; + + for (int a = 1; a < argc; ++a) + { + inputFiles.push_back( argv[a] ); + } + + GetInputFilenames() = inputFiles; +} + + +static void SetTestInputFilenames(const StringList& filenames) +{ + GetInputFilenames() = filenames; +} + +static void TestInputFilenames(DICOMFileReader* reader) +{ + StringList inputFiles = GetInputFilenames(); + reader->SetInputFiles( inputFiles ); + + const StringList& inputFilesReturned = reader->GetInputFiles(); + MITK_TEST_CONDITION( inputFilesReturned.size() == inputFiles.size(), "Input file list is received") + MITK_TEST_CONDITION( reader->GetNumberOfOutputs() == 0, "No outputs without analysis") + + // TODO: check that strings are actually contained +} + +static void TestOutputsContainInputs(DICOMFileReader* reader) +{ + StringList inputFiles = GetInputFilenames(); + reader->SetInputFiles( inputFiles ); + + reader->AnalyzeInputFiles(); + + StringList allSortedInputsFiles; + + unsigned int numberOfOutputs = reader->GetNumberOfOutputs(); + for (unsigned int o = 0; o < numberOfOutputs; ++o) + { + const DICOMImageBlockDescriptor block = reader->GetOutput(o); + + const DICOMImageFrameList& outputFiles = block.GetImageFrameList(); + for(DICOMImageFrameList::const_iterator iter = outputFiles.begin(); + iter != outputFiles.end(); + ++iter) + { + // check that output is part of input + StringList::iterator inputPositionOfCurrentOutput = std::find( inputFiles.begin(), inputFiles.end(), (*iter)->Filename ); + if (inputPositionOfCurrentOutput != inputFiles.end()) + { + // check that output is only part of ONE output + StringList::iterator outputPositionOfCurrentOutput = std::find( allSortedInputsFiles.begin(), allSortedInputsFiles.end(), (*iter)->Filename ); + if (outputPositionOfCurrentOutput == allSortedInputsFiles.end()) + { + // was not in list before + allSortedInputsFiles.push_back( *inputPositionOfCurrentOutput ); + } + else + { + reader->PrintOutputs(std::cout); + MITK_TEST_CONDITION_REQUIRED(false, "File '" << (*iter)->Filename << "' appears in TWO outputs. Readers are expected to use each frame only once." ) + } + } + else + { + reader->PrintOutputs(std::cout); + MITK_TEST_CONDITION_REQUIRED(false, "File '" << (*iter)->Filename << "' appears in output, but it was never part of the input list." ) + } + } + } + + MITK_TEST_CONDITION( allSortedInputsFiles.size() == inputFiles.size(), "Output list size (" << allSortedInputsFiles.size() << ") equals input list size (" << inputFiles.size() << ")" ) + + try + { + const DICOMImageBlockDescriptor block = reader->GetOutput( inputFiles.size() ); + MITK_TEST_CONDITION(false, "Invalid indices for GetOutput() should throw exception") + } + catch( std::invalid_argument& ) + { + MITK_TEST_CONDITION(true, "Invalid indices for GetOutput() should throw exception") + } +} + +static void TestMitkImagesAreLoaded(DICOMFileReader* reader) +{ + StringList inputFiles = GetInputFilenames(); + reader->SetInputFiles( inputFiles ); + + reader->AnalyzeInputFiles(); + reader->LoadImages(); + + unsigned int numberOfOutputs = reader->GetNumberOfOutputs(); + for (unsigned int o = 0; o < numberOfOutputs; ++o) + { + const DICOMImageBlockDescriptor block = reader->GetOutput(o); + + const DICOMImageFrameList& outputFiles = block.GetImageFrameList(); + mitk::Image::Pointer mitkImage = block.GetMitkImage(); + + MITK_DEBUG << "-------------------------------------------"; + MITK_DEBUG << "Output " << o << " at " << (void*) mitkImage.GetPointer(); + MITK_DEBUG << " Number of files: " << outputFiles.size(); + MITK_DEBUG << " Dimensions: " << mitkImage->GetDimension(0) << " " << mitkImage->GetDimension(1) << " " << mitkImage->GetDimension(2); + } +} + + +}; // end test class + +} // namespace + +#endif diff --git a/Modules/DICOMReader/Testing/mitkDICOMITKSeriesGDCMReaderBasicsTest.cpp b/Modules/DICOMReader/Testing/mitkDICOMITKSeriesGDCMReaderBasicsTest.cpp new file mode 100644 index 0000000000..faaebe11ee --- /dev/null +++ b/Modules/DICOMReader/Testing/mitkDICOMITKSeriesGDCMReaderBasicsTest.cpp @@ -0,0 +1,88 @@ +/*=================================================================== + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center, +Division of Medical and Biological Informatics. +All rights reserved. + +This software is distributed WITHOUT ANY WARRANTY; without +even the implied warranty of MERCHANTABILITY or FITNESS FOR +A PARTICULAR PURPOSE. + +See LICENSE.txt or http://www.mitk.org for details. + +===================================================================*/ + +#include "mitkDICOMITKSeriesGDCMReader.h" +#include "mitkDICOMFileReaderTestHelper.h" +#include "mitkDICOMFilenameSorter.h" +#include "mitkDICOMTagBasedSorter.h" +#include "mitkDICOMSortByTag.h" + +#include "mitkTestingMacros.h" + +using mitk::DICOMTag; + +int mitkDICOMITKSeriesGDCMReaderBasicsTest(int argc, char* argv[]) +{ + MITK_TEST_BEGIN("mitkDICOMITKSeriesGDCMReaderBasicsTest"); + + mitk::DICOMITKSeriesGDCMReader::Pointer gdcmReader = mitk::DICOMITKSeriesGDCMReader::New(); + MITK_TEST_CONDITION_REQUIRED(gdcmReader.IsNotNull(), "DICOMITKSeriesGDCMReader can be instantiated."); + + mitk::DICOMFileReaderTestHelper::SetTestInputFilenames( argc,argv ); + + // check the Set/GetInput function + mitk::DICOMFileReaderTestHelper::TestInputFilenames( gdcmReader ); + + // check that output is a good reproduction of input (no duplicates, no new elements) + mitk::DICOMFileReaderTestHelper::TestOutputsContainInputs( gdcmReader ); + + + // repeat test with filename based sorter in-between + mitk::DICOMFilenameSorter::Pointer filenameSorter = mitk::DICOMFilenameSorter::New(); + gdcmReader->AddSortingElement( filenameSorter ); + mitk::DICOMFileReaderTestHelper::TestOutputsContainInputs( gdcmReader ); + + // repeat test with some more realistic sorting + gdcmReader = mitk::DICOMITKSeriesGDCMReader::New(); // this also tests destruction + mitk::DICOMTagBasedSorter::Pointer tagSorter = mitk::DICOMTagBasedSorter::New(); + + // all the things that split by tag in DicomSeriesReader + tagSorter->AddDistinguishingTag( DICOMTag(0x0028, 0x0010) ); // Number of Rows + tagSorter->AddDistinguishingTag( DICOMTag(0x0028, 0x0011) ); // Number of Columns + tagSorter->AddDistinguishingTag( DICOMTag(0x0028, 0x0030) ); // Pixel Spacing + tagSorter->AddDistinguishingTag( DICOMTag(0x0018, 0x1164) ); // Imager Pixel Spacing + tagSorter->AddDistinguishingTag( DICOMTag(0x0020, 0x0037), new mitk::DICOMTagBasedSorter::CutDecimalPlaces(5) ); // Image Orientation (Patient) + // TODO handle as real vectors! cluster with configurable errors! + tagSorter->AddDistinguishingTag( DICOMTag(0x0020, 0x000e) ); // Series Instance UID + tagSorter->AddDistinguishingTag( DICOMTag(0x0018, 0x0050) ); // Slice Thickness + tagSorter->AddDistinguishingTag( DICOMTag(0x0028, 0x0008) ); // Number of Frames + tagSorter->AddDistinguishingTag( DICOMTag(0x0020, 0x0052) ); // Frame of Reference UID + + // a sorter... + // TODO ugly syntax, improve.. + mitk::DICOMSortCriterion::ConstPointer sorting = + mitk::DICOMSortByTag::New( DICOMTag(0x0020, 0x0013), // instance number + mitk::DICOMSortByTag::New( DICOMTag(0x0020, 0x0012), // aqcuisition number + mitk::DICOMSortByTag::New( DICOMTag(0x0008, 0x0032), // aqcuisition time + mitk::DICOMSortByTag::New( DICOMTag(0x0018, 0x1060), // trigger time + mitk::DICOMSortByTag::New( DICOMTag(0x0008, 0x0018) // SOP instance UID (last resort, not really meaningful but decides clearly) + ).GetPointer() + ).GetPointer() + ).GetPointer() + ).GetPointer() + ).GetPointer(); + tagSorter->SetSortCriterion( sorting ); + + gdcmReader->AddSortingElement( tagSorter ); + mitk::DICOMFileReaderTestHelper::TestOutputsContainInputs( gdcmReader ); + + gdcmReader->PrintOutputs(std::cout, true); + + // really load images + mitk::DICOMFileReaderTestHelper::TestMitkImagesAreLoaded( gdcmReader ); + + MITK_TEST_END(); +} diff --git a/Modules/DICOMReader/Testing/mitkDICOMNullFileReader.cpp b/Modules/DICOMReader/Testing/mitkDICOMNullFileReader.cpp new file mode 100644 index 0000000000..949cd0f728 --- /dev/null +++ b/Modules/DICOMReader/Testing/mitkDICOMNullFileReader.cpp @@ -0,0 +1,88 @@ +/*=================================================================== + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center, +Division of Medical and Biological Informatics. +All rights reserved. + +This software is distributed WITHOUT ANY WARRANTY; without +even the implied warranty of MERCHANTABILITY or FITNESS FOR +A PARTICULAR PURPOSE. + +See LICENSE.txt or http://www.mitk.org for details. + +===================================================================*/ + +#include "mitkDICOMNullFileReader.h" + +mitk::DICOMNullFileReader +::DICOMNullFileReader() +:DICOMFileReader() +{ +} + +mitk::DICOMNullFileReader +::~DICOMNullFileReader() +{ +} + +mitk::DICOMNullFileReader +::DICOMNullFileReader(const DICOMNullFileReader& other ) +:DICOMFileReader(other) +{ +} + +mitk::DICOMNullFileReader& +mitk::DICOMNullFileReader +::operator=(const DICOMNullFileReader& other) +{ + if (this != &other) + { + DICOMFileReader::operator=(other); + } + return *this; +} + + +void +mitk::DICOMNullFileReader +::AnalyzeInputFiles() +{ + this->ClearOutputs(); + + StringList inputFilenames = this->GetInputFiles(); + this->SetNumberOfOutputs( inputFilenames.size() ); + + //generates one output for each input + unsigned int o = 0; + for (StringList::const_iterator inputIter = inputFilenames.begin(); + inputIter != inputFilenames.end(); + ++o, ++inputIter) + { + DICOMImageBlockDescriptor block; + DICOMImageFrameList outputFrames; + outputFrames.push_back( DICOMImageFrameInfo::New(*inputIter) ); + + block.SetImageFrameList( outputFrames ); + + this->SetOutput( o, block ); + } +} + +// void AllocateOutputImages(); + +bool +mitk::DICOMNullFileReader +::LoadImages() +{ + // does nothing + return true; +} + +bool +mitk::DICOMNullFileReader +::CanHandleFile(const std::string& itkNotUsed(filename)) +{ + return true; // can handle all +} diff --git a/Modules/DICOMReader/Testing/mitkDICOMNullFileReader.h b/Modules/DICOMReader/Testing/mitkDICOMNullFileReader.h new file mode 100644 index 0000000000..708a59e21f --- /dev/null +++ b/Modules/DICOMReader/Testing/mitkDICOMNullFileReader.h @@ -0,0 +1,53 @@ +/*=================================================================== + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center, +Division of Medical and Biological Informatics. +All rights reserved. + +This software is distributed WITHOUT ANY WARRANTY; without +even the implied warranty of MERCHANTABILITY or FITNESS FOR +A PARTICULAR PURPOSE. + +See LICENSE.txt or http://www.mitk.org for details. + +===================================================================*/ + +#ifndef mitkDICOMNullFileReader_h +#define mitkDICOMNullFileReader_h + +#include "mitkDICOMFileReader.h" + +namespace mitk +{ + +class DICOMNullFileReader : public DICOMFileReader +{ + public: + + mitkClassMacro( DICOMNullFileReader, DICOMFileReader ); + mitkCloneMacro( DICOMNullFileReader ); + itkNewMacro( DICOMNullFileReader ); + + virtual void AnalyzeInputFiles(); + + // void AllocateOutputImages(); + virtual bool LoadImages(); + + virtual bool CanHandleFile(const std::string& filename); + + protected: + + DICOMNullFileReader(); + virtual ~DICOMNullFileReader(); + + DICOMNullFileReader(const DICOMNullFileReader& other); + DICOMNullFileReader& operator=(const DICOMNullFileReader& other); + + private: +}; + +} + +#endif diff --git a/Modules/DICOMReader/files.cmake b/Modules/DICOMReader/files.cmake new file mode 100644 index 0000000000..a0e0c6b141 --- /dev/null +++ b/Modules/DICOMReader/files.cmake @@ -0,0 +1,36 @@ +set(H_FILES + mitkDICOMFileReader.h + mitkDICOMImageFrameInfo.h + mitkDICOMImageBlockDescriptor.h + mitkDICOMGDCMImageFrameInfo.h + mitkDICOMITKSeriesGDCMReader.h + mitkDICOMDatasetSorter.h + mitkDICOMFilenameSorter.h + mitkDICOMEnums.h + mitkDICOMTagBasedSorter.h + mitkDICOMSortCriterion.h + mitkDICOMSortByTag.h + mitkEquiDistantBlocksSorter.h + mitkSortByImagePositionPatient.h + mitkClassicDICOMSeriesReader.h + mitkDICOMTag.h +) + +set(CPP_FILES + mitkDICOMFileReader.cpp + mitkDICOMImageBlockDescriptor.cpp + mitkDICOMITKSeriesGDCMReader.cpp + mitkDICOMDatasetSorter.cpp + mitkDICOMFilenameSorter.cpp + mitkDICOMTagBasedSorter.cpp + mitkDICOMGDCMImageFrameInfo.cpp + mitkDICOMImageFrameInfo.cpp + mitkDICOMSortCriterion.cpp + mitkDICOMSortByTag.cpp + mitkITKDICOMSeriesReaderHelper.cpp + mitkEquiDistantBlocksSorter.cpp + mitkSortByImagePositionPatient.cpp + mitkGantryTiltInformation.cpp + mitkClassicDICOMSeriesReader.cpp + mitkDICOMTag.cpp +) diff --git a/Modules/DICOMReader/mitkClassicDICOMSeriesReader.cpp b/Modules/DICOMReader/mitkClassicDICOMSeriesReader.cpp new file mode 100644 index 0000000000..38255881a3 --- /dev/null +++ b/Modules/DICOMReader/mitkClassicDICOMSeriesReader.cpp @@ -0,0 +1,82 @@ +/*=================================================================== + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center, +Division of Medical and Biological Informatics. +All rights reserved. + +This software is distributed WITHOUT ANY WARRANTY; without +even the implied warranty of MERCHANTABILITY or FITNESS FOR +A PARTICULAR PURPOSE. + +See LICENSE.txt or http://www.mitk.org for details. + +===================================================================*/ + +#include "mitkClassicDICOMSeriesReader.h" + +#include "mitkDICOMTagBasedSorter.h" +#include "mitkDICOMSortByTag.h" +#include "mitkSortByImagePositionPatient.h" + + +mitk::ClassicDICOMSeriesReader +::ClassicDICOMSeriesReader() +:DICOMITKSeriesGDCMReader() +{ + mitk::DICOMTagBasedSorter::Pointer tagSorter = mitk::DICOMTagBasedSorter::New(); + + // all the things that split by tag in mitk::DicomSeriesReader + tagSorter->AddDistinguishingTag( DICOMTag(0x0028, 0x0010) ); // Number of Rows + tagSorter->AddDistinguishingTag( DICOMTag(0x0028, 0x0011) ); // Number of Columns + tagSorter->AddDistinguishingTag( DICOMTag(0x0028, 0x0030) ); // Pixel Spacing + tagSorter->AddDistinguishingTag( DICOMTag(0x0018, 0x1164) ); // Imager Pixel Spacing + tagSorter->AddDistinguishingTag( DICOMTag(0x0020, 0x0037), new mitk::DICOMTagBasedSorter::CutDecimalPlaces(5) ); // Image Orientation (Patient) + tagSorter->AddDistinguishingTag( DICOMTag(0x0020, 0x000e) ); // Series Instance UID + tagSorter->AddDistinguishingTag( DICOMTag(0x0018, 0x0050) ); // Slice Thickness + tagSorter->AddDistinguishingTag( DICOMTag(0x0028, 0x0008) ); // Number of Frames + //tagSorter->AddDistinguishingTag( DICOMTag(0x0020, 0x0052) ); // Frame of Reference UID + + // a sorter... + // TODO ugly syntax, improve.. + mitk::DICOMSortCriterion::ConstPointer sorting = + mitk::SortByImagePositionPatient::New( // image position patient and image orientation + mitk::DICOMSortByTag::New( DICOMTag(0x0020, 0x0012), // aqcuisition number + mitk::DICOMSortByTag::New( DICOMTag(0x0008, 0x0032), // aqcuisition time + mitk::DICOMSortByTag::New( DICOMTag(0x0018, 0x1060), // trigger time + mitk::DICOMSortByTag::New( DICOMTag(0x0008, 0x0018) // SOP instance UID (last resort, not really meaningful but decides clearly) + ).GetPointer() + ).GetPointer() + ).GetPointer() + ).GetPointer() + ).GetPointer(); + tagSorter->SetSortCriterion( sorting ); + + // define above sorting for this class + this->AddSortingElement( tagSorter ); + + this->SetFixTiltByShearing(true); +} + +mitk::ClassicDICOMSeriesReader +::ClassicDICOMSeriesReader(const ClassicDICOMSeriesReader& other ) +:DICOMITKSeriesGDCMReader(other) +{ +} + +mitk::ClassicDICOMSeriesReader +::~ClassicDICOMSeriesReader() +{ +} + +mitk::ClassicDICOMSeriesReader& +mitk::ClassicDICOMSeriesReader +::operator=(const ClassicDICOMSeriesReader& other) +{ + if (this != &other) + { + DICOMITKSeriesGDCMReader::operator=(other); + } + return *this; +} diff --git a/Modules/DICOMReader/mitkClassicDICOMSeriesReader.h b/Modules/DICOMReader/mitkClassicDICOMSeriesReader.h new file mode 100644 index 0000000000..b6011dda63 --- /dev/null +++ b/Modules/DICOMReader/mitkClassicDICOMSeriesReader.h @@ -0,0 +1,50 @@ +/*=================================================================== + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center, +Division of Medical and Biological Informatics. +All rights reserved. + +This software is distributed WITHOUT ANY WARRANTY; without +even the implied warranty of MERCHANTABILITY or FITNESS FOR +A PARTICULAR PURPOSE. + +See LICENSE.txt or http://www.mitk.org for details. + +===================================================================*/ + +#ifndef mitkClassicDICOMSeriesReader_h +#define mitkClassicDICOMSeriesReader_h + +#include "mitkDICOMITKSeriesGDCMReader.h" + +#include "DICOMReaderExports.h" + +namespace mitk +{ + +/* + \brief Sorting and grouping like mitk::DicomSeriesReader until 2013. +*/ +class DICOMReader_EXPORT ClassicDICOMSeriesReader : public DICOMITKSeriesGDCMReader +{ + public: + + mitkClassMacro( ClassicDICOMSeriesReader, DICOMITKSeriesGDCMReader ); + mitkCloneMacro( ClassicDICOMSeriesReader ); + itkNewMacro( ClassicDICOMSeriesReader ); + + protected: + + ClassicDICOMSeriesReader(); + virtual ~ClassicDICOMSeriesReader(); + + ClassicDICOMSeriesReader(const ClassicDICOMSeriesReader& other); + ClassicDICOMSeriesReader& operator=(const ClassicDICOMSeriesReader& other); +}; + +} + +#endif + diff --git a/Modules/DICOMReader/mitkDICOMDatasetAccess.h b/Modules/DICOMReader/mitkDICOMDatasetAccess.h new file mode 100644 index 0000000000..4f6742b2b8 --- /dev/null +++ b/Modules/DICOMReader/mitkDICOMDatasetAccess.h @@ -0,0 +1,42 @@ +/*=================================================================== + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center, +Division of Medical and Biological Informatics. +All rights reserved. + +This software is distributed WITHOUT ANY WARRANTY; without +even the implied warranty of MERCHANTABILITY or FITNESS FOR +A PARTICULAR PURPOSE. + +See LICENSE.txt or http://www.mitk.org for details. + +===================================================================*/ + +#ifndef mitkDICOMDatasetAccess_h +#define mitkDICOMDatasetAccess_h + +#include "mitkDICOMTag.h" + +#include "DICOMReaderExports.h" + +namespace mitk +{ + +class DICOMReader_EXPORT DICOMDatasetAccess +{ + public: + + virtual std::string GetFilenameIfAvailable() const = 0; + virtual std::string GetTagValueAsString(const mitk::DICOMTag& tag) const = 0; + + virtual ~DICOMDatasetAccess() {}; +}; + + +typedef std::vector DICOMDatasetList; + +} + +#endif diff --git a/Modules/DICOMReader/mitkDICOMDatasetSorter.cpp b/Modules/DICOMReader/mitkDICOMDatasetSorter.cpp new file mode 100644 index 0000000000..3b4d353edb --- /dev/null +++ b/Modules/DICOMReader/mitkDICOMDatasetSorter.cpp @@ -0,0 +1,121 @@ +/*=================================================================== + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center, +Division of Medical and Biological Informatics. +All rights reserved. + +This software is distributed WITHOUT ANY WARRANTY; without +even the implied warranty of MERCHANTABILITY or FITNESS FOR +A PARTICULAR PURPOSE. + +See LICENSE.txt or http://www.mitk.org for details. + +===================================================================*/ + +#include "mitkDICOMDatasetSorter.h" + +mitk::DICOMDatasetSorter +::DICOMDatasetSorter() +:itk::LightObject() +{ +} + +mitk::DICOMDatasetSorter +::~DICOMDatasetSorter() +{ +} + +mitk::DICOMDatasetSorter +::DICOMDatasetSorter(const DICOMDatasetSorter& other ) +:itk::LightObject() +,m_Outputs( other.m_Outputs ) // TODO copy instead of reference! +{ +} + +mitk::DICOMDatasetSorter& +mitk::DICOMDatasetSorter +::operator=(const DICOMDatasetSorter& other) +{ + if (this != &other) + { + m_Input = other.m_Input; + m_Outputs = other.m_Outputs; // TODO copy instead of reference! + } + return *this; +} + +void +mitk::DICOMDatasetSorter +::SetInput(DICOMDatasetList datasets) +{ + m_Input = datasets; +} + +const mitk::DICOMDatasetList& +mitk::DICOMDatasetSorter +::GetInput() const +{ + return m_Input; +} + +unsigned int +mitk::DICOMDatasetSorter +::GetNumberOfOutputs() const +{ + return m_Outputs.size(); +} + +void +mitk::DICOMDatasetSorter +::ClearOutputs() +{ + m_Outputs.clear(); +} + +void +mitk::DICOMDatasetSorter +::SetNumberOfOutputs(unsigned int numberOfOutputs) +{ + m_Outputs.resize(numberOfOutputs); +} + +void +mitk::DICOMDatasetSorter +::SetOutput(unsigned int index, const DICOMDatasetList& output) +{ + if (index < m_Outputs.size()) + { + m_Outputs[index] = output; + } + else + { + std::stringstream ss; + ss << "Index " << index << " out of range (" << m_Outputs.size() << " indices reserved)"; + throw std::invalid_argument( ss.str() ); + } +} + +const mitk::DICOMDatasetList& +mitk::DICOMDatasetSorter +::GetOutput(unsigned int index) const +{ + return const_cast(this)->GetOutput(index); +} + +mitk::DICOMDatasetList& +mitk::DICOMDatasetSorter +::GetOutput(unsigned int index) +{ + if (index < m_Outputs.size()) + { + return m_Outputs[index]; + } + else + { + std::stringstream ss; + ss << "Index " << index << " out of range (" << m_Outputs.size() << " indices reserved)"; + throw std::invalid_argument( ss.str() ); + } +} diff --git a/Modules/DICOMReader/mitkDICOMDatasetSorter.h b/Modules/DICOMReader/mitkDICOMDatasetSorter.h new file mode 100644 index 0000000000..f0772d99a3 --- /dev/null +++ b/Modules/DICOMReader/mitkDICOMDatasetSorter.h @@ -0,0 +1,65 @@ +/*=================================================================== + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center, +Division of Medical and Biological Informatics. +All rights reserved. + +This software is distributed WITHOUT ANY WARRANTY; without +even the implied warranty of MERCHANTABILITY or FITNESS FOR +A PARTICULAR PURPOSE. + +See LICENSE.txt or http://www.mitk.org for details. + +===================================================================*/ + +#ifndef mitkDICOMDatasetSorter_h +#define mitkDICOMDatasetSorter_h + +#include "itkObjectFactory.h" +#include "mitkCommon.h" + +#include "mitkDICOMDatasetAccess.h" + +namespace mitk +{ + +class DICOMReader_EXPORT DICOMDatasetSorter : public itk::LightObject +{ + public: + + mitkClassMacro( DICOMDatasetSorter, itk::LightObject ) + + virtual DICOMTagList GetTagsOfInterest() = 0; + + void SetInput(DICOMDatasetList filenames); + const DICOMDatasetList& GetInput() const; + + virtual void Sort() = 0; + + unsigned int GetNumberOfOutputs() const; + const DICOMDatasetList& GetOutput(unsigned int index) const; + DICOMDatasetList& GetOutput(unsigned int index); + + protected: + + DICOMDatasetSorter(); + virtual ~DICOMDatasetSorter(); + + DICOMDatasetSorter(const DICOMDatasetSorter& other); + DICOMDatasetSorter& operator=(const DICOMDatasetSorter& other); + + void ClearOutputs(); + void SetNumberOfOutputs(unsigned int numberOfOutputs); + void SetOutput(unsigned int index, const DICOMDatasetList& output); + + private: + + DICOMDatasetList m_Input; + std::vector< DICOMDatasetList > m_Outputs; +}; + +} + +#endif diff --git a/Modules/DICOMReader/mitkDICOMEnums.h b/Modules/DICOMReader/mitkDICOMEnums.h new file mode 100644 index 0000000000..ee710e64d7 --- /dev/null +++ b/Modules/DICOMReader/mitkDICOMEnums.h @@ -0,0 +1,37 @@ +/*=================================================================== + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center, +Division of Medical and Biological Informatics. +All rights reserved. + +This software is distributed WITHOUT ANY WARRANTY; without +even the implied warranty of MERCHANTABILITY or FITNESS FOR +A PARTICULAR PURPOSE. + +See LICENSE.txt or http://www.mitk.org for details. + +===================================================================*/ + +#ifndef mitkDICOMEnums_h +#define mitkDICOMEnums_h + +#include +#include +#include + +namespace mitk +{ + typedef std::vector StringList; + typedef std::vector BoolList; + + typedef enum + { + SpacingInPatient, /// distances are mm within a patient + SpacingAtDetector, /// distances are mm at detector surface + SpacingUnknown /// NO spacing information is present, we use (1,1) as default + } PixelSpacingInterpretation; +} + +#endif diff --git a/Modules/DICOMReader/mitkDICOMFileReader.cpp b/Modules/DICOMReader/mitkDICOMFileReader.cpp new file mode 100644 index 0000000000..1badc29cf7 --- /dev/null +++ b/Modules/DICOMReader/mitkDICOMFileReader.cpp @@ -0,0 +1,173 @@ +/*=================================================================== + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center, +Division of Medical and Biological Informatics. +All rights reserved. + +This software is distributed WITHOUT ANY WARRANTY; without +even the implied warranty of MERCHANTABILITY or FITNESS FOR +A PARTICULAR PURPOSE. + +See LICENSE.txt or http://www.mitk.org for details. + +===================================================================*/ + +#include "mitkDICOMFileReader.h" + +#include + +mitk::DICOMFileReader +::DICOMFileReader() +:itk::LightObject() +{ +} + +mitk::DICOMFileReader +::~DICOMFileReader() +{ +} + +mitk::DICOMFileReader +::DICOMFileReader(const DICOMFileReader& other ) +:itk::LightObject() +,m_Outputs( other.m_Outputs ) // TODO copy instead of reference! +{ +} + +mitk::DICOMFileReader& +mitk::DICOMFileReader +::operator=(const DICOMFileReader& other) +{ + if (this != &other) + { + m_InputFilenames = other.m_InputFilenames; + m_Outputs = other.m_Outputs; // TODO copy instead of reference! + } + return *this; +} + +void +mitk::DICOMFileReader +::SetInputFiles(StringList filenames) +{ + m_InputFilenames = filenames; +} + +const mitk::StringList& +mitk::DICOMFileReader +::GetInputFiles() const +{ + return m_InputFilenames; +} + +unsigned int +mitk::DICOMFileReader +::GetNumberOfOutputs() const +{ + return m_Outputs.size(); +} + +void +mitk::DICOMFileReader +::ClearOutputs() +{ + m_Outputs.clear(); +} + +void +mitk::DICOMFileReader +::SetNumberOfOutputs(unsigned int numberOfOutputs) +{ + m_Outputs.resize(numberOfOutputs); +} + +void +mitk::DICOMFileReader +::SetOutput(unsigned int index, const mitk::DICOMImageBlockDescriptor& output) +{ + if (index < m_Outputs.size()) + { + m_Outputs[index] = output; + } + else + { + std::stringstream ss; + ss << "Index " << index << " out of range (" << m_Outputs.size() << " indices reserved)"; + throw std::invalid_argument( ss.str() ); + } +} + +void +mitk::DICOMFileReader +::PrintOutputs(std::ostream& os, bool filenameDetails) +{ + os << "---- Outputs of DICOMFilereader " << (void*)this << "----"<< std::endl; + + for (unsigned int o = 0; o < m_Outputs.size(); ++o) + { + os << "-- Output " << o << std::endl; + const DICOMImageBlockDescriptor& block = m_Outputs[o]; + const DICOMImageFrameList& frames = block.GetImageFrameList(); + os << " Number of frames: " << frames.size() << std::endl; + os << " Pixels interpolated: " << (block.GetPixelsInterpolated() ? "true" : "false") << std::endl; + os << " Pixel spacing interpretation: " << (int)block.GetPixelSpacingInterpretation() << std::endl; + os << " MITK image: " << (void*)block.GetMitkImage().GetPointer() << std::endl; + if (filenameDetails) + { + for (DICOMImageFrameList::const_iterator frameIter = frames.begin(); + frameIter != frames.end(); + ++frameIter) + { + os << " " << (*frameIter)->Filename; + if ((*frameIter)->FrameNo > 0) + { + os << ", " << (*frameIter)->FrameNo; + } + os << std::endl; + } + } + } + os << "---- End of output list ----" << std::endl; +} + + +const mitk::DICOMImageBlockDescriptor& +mitk::DICOMFileReader +::GetOutput(unsigned int index) const +{ + if (index < m_Outputs.size()) + { + return m_Outputs[index]; + } + else + { + std::stringstream ss; + ss << "Index " << index << " out of range (" << m_Outputs.size() << " indices reserved)"; + throw std::invalid_argument( ss.str() ); + } +} +mitk::DICOMImageBlockDescriptor& +mitk::DICOMFileReader +::InternalGetOutput(unsigned int index) +{ + if (index < m_Outputs.size()) + { + return m_Outputs[index]; + } + else + { + std::stringstream ss; + ss << "Index " << index << " out of range (" << m_Outputs.size() << " indices reserved)"; + throw std::invalid_argument( ss.str() ); + } +} + +bool +mitk::DICOMFileReader +::IsDICOM(const std::string& filename) +{ + itk::GDCMImageIO::Pointer io = itk::GDCMImageIO::New(); + return io->CanReadFile( filename.c_str() ); +} diff --git a/Modules/DICOMReader/mitkDICOMFileReader.h b/Modules/DICOMReader/mitkDICOMFileReader.h new file mode 100644 index 0000000000..122b049648 --- /dev/null +++ b/Modules/DICOMReader/mitkDICOMFileReader.h @@ -0,0 +1,81 @@ +/*=================================================================== + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center, +Division of Medical and Biological Informatics. +All rights reserved. + +This software is distributed WITHOUT ANY WARRANTY; without +even the implied warranty of MERCHANTABILITY or FITNESS FOR +A PARTICULAR PURPOSE. + +See LICENSE.txt or http://www.mitk.org for details. + +===================================================================*/ + +#ifndef mitkDICOMFileReader_h +#define mitkDICOMFileReader_h + +#include "itkObjectFactory.h" +#include "mitkCommon.h" + +#include "DICOMReaderExports.h" + +#include "mitkDICOMImageBlockDescriptor.h" + +namespace mitk +{ + +class DICOMReader_EXPORT DICOMFileReader : public itk::LightObject +{ + public: + + enum LoadingConfidence + { + NoSupport = 0, + FullSupport = 1, + PartialSupport = 2, + }; + + mitkClassMacro( DICOMFileReader, itk::LightObject ) + + void SetInputFiles(StringList filenames); + const StringList& GetInputFiles() const; + + virtual void AnalyzeInputFiles() = 0; + unsigned int GetNumberOfOutputs() const; + const DICOMImageBlockDescriptor& GetOutput(unsigned int index) const; + + // void AllocateOutputImages(); + virtual bool LoadImages() = 0; + + virtual bool CanHandleFile(const std::string& filename) = 0; + + void PrintOutputs(std::ostream& os, bool filenameDetails = false); + + static bool IsDICOM(const std::string& filename); + + protected: + + DICOMFileReader(); + virtual ~DICOMFileReader(); + + DICOMFileReader(const DICOMFileReader& other); + DICOMFileReader& operator=(const DICOMFileReader& other); + + void ClearOutputs(); + void SetNumberOfOutputs(unsigned int numberOfOutputs); + void SetOutput(unsigned int index, const DICOMImageBlockDescriptor& output); + + DICOMImageBlockDescriptor& InternalGetOutput(unsigned int index); + + private: + + StringList m_InputFilenames; + std::vector< DICOMImageBlockDescriptor > m_Outputs; +}; + +} + +#endif diff --git a/Modules/DICOMReader/mitkDICOMFilenameSorter.cpp b/Modules/DICOMReader/mitkDICOMFilenameSorter.cpp new file mode 100644 index 0000000000..23850e66a9 --- /dev/null +++ b/Modules/DICOMReader/mitkDICOMFilenameSorter.cpp @@ -0,0 +1,73 @@ +/*=================================================================== + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center, +Division of Medical and Biological Informatics. +All rights reserved. + +This software is distributed WITHOUT ANY WARRANTY; without +even the implied warranty of MERCHANTABILITY or FITNESS FOR +A PARTICULAR PURPOSE. + +See LICENSE.txt or http://www.mitk.org for details. + +===================================================================*/ + +#include "mitkDICOMFilenameSorter.h" + +#include + +mitk::DICOMFilenameSorter +::DICOMFilenameSorter() +:DICOMDatasetSorter() +{ +} + +mitk::DICOMFilenameSorter +::~DICOMFilenameSorter() +{ +} + +mitk::DICOMFilenameSorter +::DICOMFilenameSorter(const DICOMFilenameSorter& other ) +:DICOMDatasetSorter(other) +{ +} + +mitk::DICOMFilenameSorter& +mitk::DICOMFilenameSorter +::operator=(const DICOMFilenameSorter& other) +{ + if (this != &other) + { + DICOMDatasetSorter::operator=(other); + } + return *this; +} + +mitk::DICOMTagList +mitk::DICOMFilenameSorter +::GetTagsOfInterest() +{ + return DICOMTagList(); +} + +bool +mitk::DICOMFilenameSorter::FilenameSort +::operator() (const mitk::DICOMDatasetAccess* left, const mitk::DICOMDatasetAccess* right) +{ + return left->GetFilenameIfAvailable().compare( right->GetFilenameIfAvailable() ) < 0 ; +} + +void +mitk::DICOMFilenameSorter +::Sort() +{ + DICOMDatasetList output = GetInput(); // copy + + std::sort( output.begin(), output.end(), FilenameSort() ); + + this->SetNumberOfOutputs(1); + this->SetOutput(0, output); +} diff --git a/Modules/DICOMReader/mitkDICOMFilenameSorter.h b/Modules/DICOMReader/mitkDICOMFilenameSorter.h new file mode 100644 index 0000000000..d80cff0c13 --- /dev/null +++ b/Modules/DICOMReader/mitkDICOMFilenameSorter.h @@ -0,0 +1,55 @@ +/*=================================================================== + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center, +Division of Medical and Biological Informatics. +All rights reserved. + +This software is distributed WITHOUT ANY WARRANTY; without +even the implied warranty of MERCHANTABILITY or FITNESS FOR +A PARTICULAR PURPOSE. + +See LICENSE.txt or http://www.mitk.org for details. + +===================================================================*/ + +#ifndef mitkDICOMFilenameSorter_h +#define mitkDICOMFilenameSorter_h + +#include "mitkDICOMDatasetSorter.h" + +namespace mitk +{ + +/** + \brief sort files based on filename (last resort). +*/ +class DICOMReader_EXPORT DICOMFilenameSorter : public DICOMDatasetSorter +{ + public: + + mitkClassMacro( DICOMFilenameSorter, DICOMDatasetSorter ) + itkNewMacro( DICOMFilenameSorter ) + + virtual DICOMTagList GetTagsOfInterest(); + + virtual void Sort(); + + protected: + + struct FilenameSort + { + bool operator() (const mitk::DICOMDatasetAccess* left, const mitk::DICOMDatasetAccess* right); + }; + + DICOMFilenameSorter(); + virtual ~DICOMFilenameSorter(); + + DICOMFilenameSorter(const DICOMFilenameSorter& other); + DICOMFilenameSorter& operator=(const DICOMFilenameSorter& other); +}; + +} + +#endif diff --git a/Modules/DICOMReader/mitkDICOMGDCMImageFrameInfo.cpp b/Modules/DICOMReader/mitkDICOMGDCMImageFrameInfo.cpp new file mode 100644 index 0000000000..235d7f771a --- /dev/null +++ b/Modules/DICOMReader/mitkDICOMGDCMImageFrameInfo.cpp @@ -0,0 +1,104 @@ +/*=================================================================== + + The Medical Imaging Interaction Toolkit (MITK) + + Copyright (c) German Cancer Research Center, + Division of Medical and Biological Informatics. + All rights reserved. + + This software is distributed WITHOUT ANY WARRANTY; without + even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. + + See LICENSE.txt or http://www.mitk.org for details. + + ===================================================================*/ + +#include "mitkDICOMGDCMImageFrameInfo.h" + +mitk::DICOMGDCMImageFrameInfo +::DICOMGDCMImageFrameInfo(const std::string& filename, unsigned int frameNo) +:itk::LightObject() +,m_FrameInfo( DICOMImageFrameInfo::New(filename, frameNo) ) +,m_TagForValue(gdcm::Scanner::TagToValue()) // empty +{ +} + +mitk::DICOMGDCMImageFrameInfo +::DICOMGDCMImageFrameInfo(DICOMImageFrameInfo::Pointer frameinfo) +:itk::LightObject() +,m_FrameInfo(frameinfo) +,m_TagForValue(gdcm::Scanner::TagToValue()) // empty +{ +} + +mitk::DICOMGDCMImageFrameInfo +::DICOMGDCMImageFrameInfo(DICOMImageFrameInfo::Pointer frameinfo, gdcm::Scanner::TagToValue const& tagToValueMapping) +:itk::LightObject() +,m_FrameInfo(frameinfo) +,m_TagForValue(tagToValueMapping) +{ +} + +mitk::DICOMGDCMImageFrameInfo:: +~DICOMGDCMImageFrameInfo() +{ +} + +std::string +mitk::DICOMGDCMImageFrameInfo +::GetTagValueAsString(const DICOMTag& tag) const +{ + gdcm::Scanner::TagToValue::const_iterator mappedValue = m_TagForValue.find( gdcm::Tag(tag.GetGroup(), tag.GetElement()) ); + + if (mappedValue != m_TagForValue.end()) + { + return mappedValue->second; + } + else + { + const DICOMTag tagImagePositionPatient = DICOMTag(0x0020,0x0032); // Image Position (Patient) + const DICOMTag tagImageOrientation = DICOMTag(0x0020, 0x0037); // Image Orientation + + if (tag == tagImagePositionPatient) + { + return std::string("0\\0\\0"); + } + else if (tag == tagImageOrientation) + { + return std::string("1\\0\\0\\0\\1\\0"); + } + else + { + return std::string(""); + } + } +} + +std::string +mitk::DICOMGDCMImageFrameInfo +::GetFilenameIfAvailable() const +{ + if (m_FrameInfo.IsNotNull()) + { + return m_FrameInfo->Filename; + } + else + { + return ""; + } +} + +mitk::DICOMImageFrameInfo::Pointer +mitk::DICOMGDCMImageFrameInfo +::GetFrameInfo() const +{ + return m_FrameInfo; +} + +void + mitk::DICOMGDCMImageFrameInfo +::SetFrameInfo(DICOMImageFrameInfo::Pointer frameinfo) +{ + m_FrameInfo = frameinfo; +} diff --git a/Modules/DICOMReader/mitkDICOMGDCMImageFrameInfo.h b/Modules/DICOMReader/mitkDICOMGDCMImageFrameInfo.h new file mode 100644 index 0000000000..f508ccd9b9 --- /dev/null +++ b/Modules/DICOMReader/mitkDICOMGDCMImageFrameInfo.h @@ -0,0 +1,61 @@ +/*=================================================================== + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center, +Division of Medical and Biological Informatics. +All rights reserved. + +This software is distributed WITHOUT ANY WARRANTY; without +even the implied warranty of MERCHANTABILITY or FITNESS FOR +A PARTICULAR PURPOSE. + +See LICENSE.txt or http://www.mitk.org for details. + +===================================================================*/ + +#ifndef mitkDICOMGDCMImageFrameInfo_h +#define mitkDICOMGDCMImageFrameInfo_h + +#include "mitkDICOMImageFrameInfo.h" +#include "mitkDICOMDatasetAccess.h" + +#include "gdcmScanner.h" + +namespace mitk +{ + class DICOMReader_EXPORT DICOMGDCMImageFrameInfo : public itk::LightObject, public DICOMDatasetAccess + { + public: + + mitkClassMacro(DICOMGDCMImageFrameInfo, itk::LightObject); + itkNewMacro( DICOMGDCMImageFrameInfo ); + mitkNewMacro1Param( DICOMGDCMImageFrameInfo, const std::string&); + mitkNewMacro2Param( DICOMGDCMImageFrameInfo, const std::string&, unsigned int ); + mitkNewMacro1Param( DICOMGDCMImageFrameInfo, DICOMImageFrameInfo::Pointer); + mitkNewMacro2Param( DICOMGDCMImageFrameInfo, DICOMImageFrameInfo::Pointer, gdcm::Scanner::TagToValue const&); + + virtual ~DICOMGDCMImageFrameInfo(); + + virtual std::string GetTagValueAsString(const DICOMTag&) const; + + std::string GetFilenameIfAvailable() const; + + DICOMImageFrameInfo::Pointer GetFrameInfo() const; + void SetFrameInfo(DICOMImageFrameInfo::Pointer frameinfo); + + protected: + + DICOMImageFrameInfo::Pointer m_FrameInfo; + + DICOMGDCMImageFrameInfo(DICOMImageFrameInfo::Pointer frameinfo); + DICOMGDCMImageFrameInfo(DICOMImageFrameInfo::Pointer frameinfo, gdcm::Scanner::TagToValue const& tagToValueMapping); + DICOMGDCMImageFrameInfo(const std::string& filename = "", unsigned int frameNo = 0); + + gdcm::Scanner::TagToValue const& m_TagForValue; + }; + + typedef std::vector DICOMGDCMImageFrameList; +} + +#endif diff --git a/Modules/DICOMReader/mitkDICOMITKSeriesGDCMReader.cpp b/Modules/DICOMReader/mitkDICOMITKSeriesGDCMReader.cpp new file mode 100644 index 0000000000..7139e3bfd2 --- /dev/null +++ b/Modules/DICOMReader/mitkDICOMITKSeriesGDCMReader.cpp @@ -0,0 +1,361 @@ +/*=================================================================== + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center, +Division of Medical and Biological Informatics. +All rights reserved. + +This software is distributed WITHOUT ANY WARRANTY; without +even the implied warranty of MERCHANTABILITY or FITNESS FOR +A PARTICULAR PURPOSE. + +See LICENSE.txt or http://www.mitk.org for details. + +===================================================================*/ + +#include "mitkDICOMITKSeriesGDCMReader.h" +#include "mitkITKDICOMSeriesReaderHelper.h" +#include "mitkGantryTiltInformation.h" + +#include + +#include + +mitk::DICOMITKSeriesGDCMReader +::DICOMITKSeriesGDCMReader() +:DICOMFileReader() +,m_FixTiltByShearing(true) +,m_Group3DplusT(false) +{ +} + +mitk::DICOMITKSeriesGDCMReader +::DICOMITKSeriesGDCMReader(const DICOMITKSeriesGDCMReader& other ) +:DICOMFileReader(other) +,m_FixTiltByShearing(false) +,m_Group3DplusT(false) +{ +} + +mitk::DICOMITKSeriesGDCMReader +::~DICOMITKSeriesGDCMReader() +{ +} + +mitk::DICOMITKSeriesGDCMReader& +mitk::DICOMITKSeriesGDCMReader +::operator=(const DICOMITKSeriesGDCMReader& other) +{ + if (this != &other) + { + DICOMFileReader::operator=(other); + } + return *this; +} + +void +mitk::DICOMITKSeriesGDCMReader +::SetFixTiltByShearing(bool on) +{ + m_FixTiltByShearing = on; +} + +mitk::DICOMGDCMImageFrameList +mitk::DICOMITKSeriesGDCMReader +::FromDICOMDatasetList(DICOMDatasetList& input) +{ + DICOMGDCMImageFrameList output; + output.reserve(input.size()); + + for(DICOMDatasetList::iterator inputIter = input.begin(); + inputIter != input.end(); + ++inputIter) + { + DICOMGDCMImageFrameInfo* gfi = dynamic_cast(*inputIter); + assert(gfi); + output.push_back(gfi); + } + + return output; +} + +mitk::DICOMDatasetList +mitk::DICOMITKSeriesGDCMReader +::ToDICOMDatasetList(DICOMGDCMImageFrameList& input) +{ + DICOMDatasetList output; + output.reserve(input.size()); + + for(DICOMGDCMImageFrameList::iterator inputIter = input.begin(); + inputIter != input.end(); + ++inputIter) + { + DICOMDatasetAccess* da = inputIter->GetPointer(); + assert(da); + output.push_back(da); + } + + return output; +} + +mitk::DICOMImageFrameList +mitk::DICOMITKSeriesGDCMReader +::ToDICOMImageFrameList(DICOMGDCMImageFrameList& input) +{ + DICOMImageFrameList output; + output.reserve(input.size()); + + for(DICOMGDCMImageFrameList::iterator inputIter = input.begin(); + inputIter != input.end(); + ++inputIter) + { + DICOMImageFrameInfo::Pointer fi = (*inputIter)->GetFrameInfo(); + assert(fi.IsNotNull()); + output.push_back(fi); + } + + return output; +} + +void +mitk::DICOMITKSeriesGDCMReader +::AnalyzeInputFiles() +{ + // TODO at this point, make sure we have a sorting element at the end that splits geometrically separate blocks + this->EnsureMandatorySortersArePresent(); + + itk::TimeProbesCollectorBase timer; + + timer.Start("Reset"); + this->ClearOutputs(); + timer.Stop("Reset"); + + // prepare initial sorting (== list of input files) + StringList inputFilenames = this->GetInputFiles(); + + // scan files for sorting-relevant tags + + // TODO provide tagToValueMappings to items in initialFramelist / m_SortingResultInProgress + timer.Start("Setup scanning"); + gdcm::Scanner gdcmScanner; + for(SorterList::iterator sorterIter = m_Sorter.begin(); + sorterIter != m_Sorter.end(); + ++sorterIter) + { + assert(sorterIter->IsNotNull()); + + DICOMTagList tags = (*sorterIter)->GetTagsOfInterest(); + for(DICOMTagList::const_iterator tagIter = tags.begin(); + tagIter != tags.end(); + ++tagIter) + { + MITK_DEBUG << "Sorting uses tag " << tagIter->GetGroup() << "," << tagIter->GetElement(); + gdcmScanner.AddTag( gdcm::Tag(tagIter->GetGroup(), tagIter->GetElement()) ); + } + } + + // Add some of our own interest + gdcmScanner.AddTag( gdcm::Tag(0x0018,0x1164) ); + gdcmScanner.AddTag( gdcm::Tag(0x0028,0x0030) ); + + timer.Stop("Setup scanning"); + + timer.Start("Tag scanning"); + gdcmScanner.Scan( inputFilenames ); + timer.Stop("Tag scanning"); + + timer.Start("Setup sorting"); + DICOMGDCMImageFrameList initialFramelist; + for (StringList::const_iterator inputIter = inputFilenames.begin(); + inputIter != inputFilenames.end(); + ++inputIter) + { + // TODO check DICOMness and non-multi-framedness + initialFramelist.push_back( DICOMGDCMImageFrameInfo::New( DICOMImageFrameInfo::New(*inputIter, 0), gdcmScanner.GetMapping(inputIter->c_str()) ) ); + } + m_SortingResultInProgress.clear(); + m_SortingResultInProgress.push_back( initialFramelist ); + timer.Stop("Setup sorting"); + + // sort and split blocks as configured + + timer.Start("Sorting frames"); + SortingBlockList nextStepSorting; // we should not modify our input list while processing it + unsigned int sorterIndex = 0; + for(SorterList::iterator sorterIter = m_Sorter.begin(); + sorterIter != m_Sorter.end(); + ++sorterIndex, ++sorterIter) + { + std::stringstream ss; ss << "Sorting step " << sorterIndex; + timer.Start( ss.str().c_str() ); + nextStepSorting.clear(); + DICOMDatasetSorter::Pointer& sorter = *sorterIter; + + MITK_DEBUG << "================================================================================"; + MITK_DEBUG << "DICOMIKTSeriesGDCMReader: " << ss.str() << ": " << m_SortingResultInProgress.size() << " groups input"; + unsigned int groupIndex = 0; + + for(SortingBlockList::iterator blockIter = m_SortingResultInProgress.begin(); + blockIter != m_SortingResultInProgress.end(); + ++groupIndex, ++blockIter) + { + DICOMGDCMImageFrameList& gdcmInfoFrameList = *blockIter; + DICOMDatasetList datasetList = ToDICOMDatasetList( gdcmInfoFrameList ); + + MITK_DEBUG << "--------------------------------------------------------------------------------"; + MITK_DEBUG << "DICOMIKTSeriesGDCMReader: " << ss.str() << ", dataset group " << groupIndex << " (" << datasetList.size() << " datasets): "; + for (DICOMDatasetList::iterator oi = datasetList.begin(); + oi != datasetList.end(); + ++oi) + { + MITK_DEBUG << " INPUT : " << (*oi)->GetFilenameIfAvailable(); + } + + sorter->SetInput(datasetList); + sorter->Sort(); + unsigned int numberOfResultingBlocks = sorter->GetNumberOfOutputs(); + + for (unsigned int b = 0; b < numberOfResultingBlocks; ++b) + { + DICOMDatasetList blockResult = sorter->GetOutput(b); + + for (DICOMDatasetList::iterator oi = blockResult.begin(); + oi != blockResult.end(); + ++oi) + { + MITK_DEBUG << " OUTPUT(" << b << ") :" << (*oi)->GetFilenameIfAvailable(); + } + + DICOMGDCMImageFrameList sortedGdcmInfoFrameList = FromDICOMDatasetList(blockResult); + nextStepSorting.push_back( sortedGdcmInfoFrameList ); + } + } + + m_SortingResultInProgress = nextStepSorting; + timer.Stop( ss.str().c_str() ); + } + timer.Stop("Sorting frames"); + + // provide final result as output + + timer.Start("Output"); + this->SetNumberOfOutputs( m_SortingResultInProgress.size() ); + unsigned int o = 0; + for (SortingBlockList::iterator blockIter = m_SortingResultInProgress.begin(); + blockIter != m_SortingResultInProgress.end(); + ++o, ++blockIter) + { + DICOMGDCMImageFrameList& gdcmFrameInfoList = *blockIter; + DICOMImageFrameList frameList = ToDICOMImageFrameList( gdcmFrameInfoList ); + assert(!gdcmFrameInfoList.empty()); + assert(!frameList.empty()); + + DICOMImageBlockDescriptor block; + block.SetImageFrameList( frameList ); + + bool hasTilt = false; + const GantryTiltInformation& tiltInfo = m_EquiDistantBlocksSorter->GetTiltInformation( (gdcmFrameInfoList.front())->GetFilenameIfAvailable(), hasTilt ); + block.SetHasGantryTilt( hasTilt ); + block.SetTiltInformation( tiltInfo ); + + // assume + static const DICOMTag tagPixelSpacing(0x0028,0x0030); + static const DICOMTag tagImagerPixelSpacing(0x0018,0x1164); + std::string pixelSpacingString = (gdcmFrameInfoList.front())->GetTagValueAsString( tagPixelSpacing ); + std::string imagerPixelSpacingString = gdcmFrameInfoList.front()->GetTagValueAsString( tagImagerPixelSpacing ); + block.SetPixelSpacingInformation(pixelSpacingString, imagerPixelSpacingString); + + this->SetOutput( o, block ); + } + timer.Stop("Output"); + + std::cout << "---------------------------------------------------------------" << std::endl; + timer.Report( std::cout ); + std::cout << "---------------------------------------------------------------" << std::endl; +} + +// void AllocateOutputImages(); + +bool +mitk::DICOMITKSeriesGDCMReader +::LoadImages() +{ + mitk::ITKDICOMSeriesReaderHelper helper; + + unsigned int numberOfOutputs = this->GetNumberOfOutputs(); + for (unsigned int o = 0; o < numberOfOutputs; ++o) + { + DICOMImageBlockDescriptor& block = this->InternalGetOutput(o); + const DICOMImageFrameList& frames = block.GetImageFrameList(); + const GantryTiltInformation tiltInfo = block.GetTiltInformation(); + bool hasTilt = block.HasGantryTilt(); + if (hasTilt) + { + MITK_DEBUG << "When loading image " << o << ": got tilt info:"; + tiltInfo.Print(std::cout); + } + else + { + MITK_DEBUG << "When loading image " << o << ": has NO info."; + } + + ITKDICOMSeriesReaderHelper::StringContainer filenames; + for (DICOMImageFrameList::const_iterator frameIter = frames.begin(); + frameIter != frames.end(); + ++frameIter) + { + filenames.push_back( (*frameIter)->Filename ); + } + + mitk::Image::Pointer mitkImage = helper.Load( filenames, m_FixTiltByShearing && hasTilt, tiltInfo ); // TODO preloaded images, caching..? + + Vector3D imageSpacing = mitkImage->GetGeometry()->GetSpacing(); + + ScalarType desiredSpacingX = imageSpacing[0]; + ScalarType desiredSpacingY = imageSpacing[1]; + block.GetDesiredMITKImagePixelSpacing( desiredSpacingX, desiredSpacingY ); // prefer pixel spacing over imager pixel spacing + + MITK_DEBUG << "Loaded image with spacing " << imageSpacing[0] << ", " << imageSpacing[1]; + MITK_DEBUG << "Found correct spacing info " << desiredSpacingX << ", " << desiredSpacingY; + + imageSpacing[0] = desiredSpacingX; + imageSpacing[1] = desiredSpacingY; + mitkImage->GetGeometry()->SetSpacing( imageSpacing ); + + block.SetMitkImage( mitkImage ); + } + + return true; +} + + +bool +mitk::DICOMITKSeriesGDCMReader +::CanHandleFile(const std::string& itkNotUsed(filename)) +{ + return true; // can handle all +} + +void +mitk::DICOMITKSeriesGDCMReader +::AddSortingElement(DICOMDatasetSorter* sorter) +{ + assert(sorter); + m_Sorter.push_back( sorter ); +} + +void +mitk::DICOMITKSeriesGDCMReader +::EnsureMandatorySortersArePresent() +{ + // TODO: cols, rows, etc. are also not optional + if (m_EquiDistantBlocksSorter.IsNull()) + { + m_EquiDistantBlocksSorter = mitk::EquiDistantBlocksSorter::New(); + } + m_EquiDistantBlocksSorter->SetAcceptTilt( m_FixTiltByShearing ); + + // TODO CHECK + this->AddSortingElement( m_EquiDistantBlocksSorter ); +} diff --git a/Modules/DICOMReader/mitkDICOMITKSeriesGDCMReader.h b/Modules/DICOMReader/mitkDICOMITKSeriesGDCMReader.h new file mode 100644 index 0000000000..22e6be0984 --- /dev/null +++ b/Modules/DICOMReader/mitkDICOMITKSeriesGDCMReader.h @@ -0,0 +1,85 @@ +/*=================================================================== + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center, +Division of Medical and Biological Informatics. +All rights reserved. + +This software is distributed WITHOUT ANY WARRANTY; without +even the implied warranty of MERCHANTABILITY or FITNESS FOR +A PARTICULAR PURPOSE. + +See LICENSE.txt or http://www.mitk.org for details. + +===================================================================*/ + +#ifndef mitkDICOMITKSeriesGDCMReader_h +#define mitkDICOMITKSeriesGDCMReader_h + +#include "mitkDICOMFileReader.h" +#include "mitkDICOMDatasetSorter.h" + +#include "mitkDICOMGDCMImageFrameInfo.h" +#include "mitkEquiDistantBlocksSorter.h" + +#include "DICOMReaderExports.h" + +// TODO ensure "C" locale!! +// TODO 3D+t +// TODO providing tags as properties! +// TODO preloaded volumes?? could be solved in a different way.. + +namespace mitk +{ + +class DICOMReader_EXPORT DICOMITKSeriesGDCMReader : public DICOMFileReader +{ + public: + + mitkClassMacro( DICOMITKSeriesGDCMReader, DICOMFileReader ); + mitkCloneMacro( DICOMITKSeriesGDCMReader ); + itkNewMacro( DICOMITKSeriesGDCMReader ); + + virtual void AnalyzeInputFiles(); + + // void AllocateOutputImages(); + virtual bool LoadImages(); + + virtual bool CanHandleFile(const std::string& filename); + + virtual void AddSortingElement(DICOMDatasetSorter* sorter); + + void SetFixTiltByShearing(bool on); + + protected: + + DICOMITKSeriesGDCMReader(); + virtual ~DICOMITKSeriesGDCMReader(); + + DICOMITKSeriesGDCMReader(const DICOMITKSeriesGDCMReader& other); + DICOMITKSeriesGDCMReader& operator=(const DICOMITKSeriesGDCMReader& other); + + DICOMDatasetList ToDICOMDatasetList(DICOMGDCMImageFrameList& input); + DICOMGDCMImageFrameList FromDICOMDatasetList(DICOMDatasetList& input); + DICOMImageFrameList ToDICOMImageFrameList(DICOMGDCMImageFrameList& input); + + void EnsureMandatorySortersArePresent(); + + private: + + bool m_FixTiltByShearing; + bool m_Group3DplusT; + + typedef std::list SortingBlockList; + SortingBlockList m_SortingResultInProgress; + + typedef std::list SorterList; + SorterList m_Sorter; + + mitk::EquiDistantBlocksSorter::Pointer m_EquiDistantBlocksSorter; +}; + +} + +#endif diff --git a/Modules/DICOMReader/mitkDICOMImageBlockDescriptor.cpp b/Modules/DICOMReader/mitkDICOMImageBlockDescriptor.cpp new file mode 100644 index 0000000000..ef72c0347c --- /dev/null +++ b/Modules/DICOMReader/mitkDICOMImageBlockDescriptor.cpp @@ -0,0 +1,244 @@ +/*=================================================================== + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center, +Division of Medical and Biological Informatics. +All rights reserved. + +This software is distributed WITHOUT ANY WARRANTY; without +even the implied warranty of MERCHANTABILITY or FITNESS FOR +A PARTICULAR PURPOSE. + +See LICENSE.txt or http://www.mitk.org for details. + +===================================================================*/ + +#include "mitkDICOMImageBlockDescriptor.h" + +mitk::DICOMImageBlockDescriptor +::DICOMImageBlockDescriptor() +:m_PixelsInterpolated(false) +,m_PixelSpacingInterpretation() +,m_PixelSpacing("") +,m_ImagerPixelSpacing("") +,m_HasGantryTilt( false ) +{ +} + +mitk::DICOMImageBlockDescriptor +::~DICOMImageBlockDescriptor() +{ +} + +mitk::DICOMImageBlockDescriptor +::DICOMImageBlockDescriptor(const DICOMImageBlockDescriptor& other) +:m_ImageFrameList( other.m_ImageFrameList ) +,m_MitkImage( other.m_MitkImage ) +,m_SliceIsLoaded( other.m_SliceIsLoaded ) +,m_PixelsInterpolated( other.m_PixelsInterpolated ) +,m_PixelSpacingInterpretation( other.m_PixelSpacingInterpretation ) +,m_PixelSpacing( other.m_PixelSpacing ) +,m_ImagerPixelSpacing( other.m_ImagerPixelSpacing ) +,m_HasGantryTilt( other.m_HasGantryTilt ) +,m_TiltInformation( other.m_TiltInformation ) +{ + if (m_MitkImage) + { + m_MitkImage = m_MitkImage->Clone(); + } +} + +mitk::DICOMImageBlockDescriptor& +mitk::DICOMImageBlockDescriptor +::operator=(const DICOMImageBlockDescriptor& other) +{ + if (this != &other) + { + m_ImageFrameList = other.m_ImageFrameList; + m_MitkImage = other.m_MitkImage; + m_SliceIsLoaded = other.m_SliceIsLoaded; + m_PixelsInterpolated = other.m_PixelsInterpolated; + m_PixelSpacingInterpretation = other.m_PixelSpacingInterpretation; + m_PixelSpacing = other.m_PixelSpacing; + m_ImagerPixelSpacing = other.m_ImagerPixelSpacing; + m_HasGantryTilt = other.m_HasGantryTilt; + m_TiltInformation = other.m_TiltInformation; + + if (m_MitkImage) + { + m_MitkImage = m_MitkImage->Clone(); + } + } + return *this; +} + +bool +mitk::DICOMImageBlockDescriptor +::HasGantryTilt() const +{ + return m_HasGantryTilt; +} + +bool +mitk::DICOMImageBlockDescriptor +::SetHasGantryTilt(bool hasi) +{ + m_HasGantryTilt = hasi; +} + +void +mitk::DICOMImageBlockDescriptor +::SetTiltInformation(const GantryTiltInformation& info) +{ + m_TiltInformation = info; +} + +const mitk::GantryTiltInformation +mitk::DICOMImageBlockDescriptor +::GetTiltInformation() const +{ + return m_TiltInformation; +} + +void +mitk::DICOMImageBlockDescriptor +::SetImageFrameList(const DICOMImageFrameList& framelist) +{ + m_ImageFrameList = framelist; + m_SliceIsLoaded.resize(framelist.size()); + m_SliceIsLoaded.assign(framelist.size(), false); +} + +const mitk::DICOMImageFrameList& +mitk::DICOMImageBlockDescriptor +::GetImageFrameList() const +{ + return m_ImageFrameList; +} + +void +mitk::DICOMImageBlockDescriptor +::SetMitkImage(Image::Pointer image) +{ + if (m_MitkImage != image) + { + m_MitkImage = image; + } +} + +mitk::Image::Pointer +mitk::DICOMImageBlockDescriptor +::GetMitkImage() const +{ + return m_MitkImage; +} + +void +mitk::DICOMImageBlockDescriptor +::SetSliceIsLoaded(unsigned int index, bool isLoaded) +{ + if (index < m_SliceIsLoaded.size()) + { + m_SliceIsLoaded[index] = isLoaded; + } + else + { + std::stringstream ss; + ss << "Index " << index << " out of range (" << m_SliceIsLoaded.size() << " indices reserved)"; + throw std::invalid_argument( ss.str() ); + } +} + +bool +mitk::DICOMImageBlockDescriptor +::IsSliceLoaded(unsigned int index) const +{ + if (index < m_SliceIsLoaded.size()) + { + return m_SliceIsLoaded[index]; + } + else + { + std::stringstream ss; + ss << "Index " << index << " out of range (" << m_SliceIsLoaded.size() << " indices reserved)"; + throw std::invalid_argument( ss.str() ); + } +} + +bool +mitk::DICOMImageBlockDescriptor +::AllSlicesAreLoaded() const +{ + bool allLoaded = true; + for (BoolList::const_iterator iter = m_SliceIsLoaded.begin(); + iter != m_SliceIsLoaded.end(); + ++iter) + { + allLoaded &= *iter; + } + + return allLoaded; +} + + +void +mitk::DICOMImageBlockDescriptor +::SetPixelsInterpolated(bool pixelsAreInterpolated) +{ + if (m_PixelsInterpolated != pixelsAreInterpolated) + { + m_PixelsInterpolated = pixelsAreInterpolated; + } +} + +bool +mitk::DICOMImageBlockDescriptor +::GetPixelsInterpolated() const +{ + return m_PixelsInterpolated; +} + + +void +mitk::DICOMImageBlockDescriptor +::SetPixelSpacingInterpretation( PixelSpacingInterpretation interpretation ) +{ + if (m_PixelSpacingInterpretation != interpretation) + { + m_PixelSpacingInterpretation = interpretation; + } +} + +mitk::PixelSpacingInterpretation +mitk::DICOMImageBlockDescriptor +::GetPixelSpacingInterpretation() const +{ + return m_PixelSpacingInterpretation; +} + +void +mitk::DICOMImageBlockDescriptor +::SetPixelSpacingInformation(const std::string& pixelSpacing, const std::string& imagerPixelSpacing) +{ + m_PixelSpacing = pixelSpacing; + m_ImagerPixelSpacing = imagerPixelSpacing; +} + +void +mitk::DICOMImageBlockDescriptor +::GetDesiredMITKImagePixelSpacing( float& spacingX, float& spacingY) const +{ + // preference for "in patient" pixel spacing + if ( !DICOMStringToSpacing( m_PixelSpacing, spacingX, spacingY ) ) + { + // fallback to "on detector" spacing + if ( !DICOMStringToSpacing( m_ImagerPixelSpacing, spacingX, spacingY ) ) + { + // last resort: invent something + spacingX = spacingY = 1.0; + } + } +} + + diff --git a/Modules/DICOMReader/mitkDICOMImageBlockDescriptor.h b/Modules/DICOMReader/mitkDICOMImageBlockDescriptor.h new file mode 100644 index 0000000000..7c8a827fca --- /dev/null +++ b/Modules/DICOMReader/mitkDICOMImageBlockDescriptor.h @@ -0,0 +1,83 @@ +/*=================================================================== + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center, +Division of Medical and Biological Informatics. +All rights reserved. + +This software is distributed WITHOUT ANY WARRANTY; without +even the implied warranty of MERCHANTABILITY or FITNESS FOR +A PARTICULAR PURPOSE. + +See LICENSE.txt or http://www.mitk.org for details. + +===================================================================*/ + +#ifndef mitkDICOMImageBlockDescriptor_h +#define mitkDICOMImageBlockDescriptor_h + +#include "mitkDICOMEnums.h" +#include "mitkDICOMImageFrameInfo.h" +#include "mitkDICOMTag.h" + +#include "mitkImage.h" + +#include "mitkGantryTiltInformation.h" + +namespace mitk +{ + +class DICOMReader_EXPORT DICOMImageBlockDescriptor +{ + public: + + DICOMImageBlockDescriptor(); + ~DICOMImageBlockDescriptor(); + + DICOMImageBlockDescriptor(const DICOMImageBlockDescriptor& other); + DICOMImageBlockDescriptor& operator=(const DICOMImageBlockDescriptor& other); + + void SetImageFrameList(const DICOMImageFrameList& framelist); + const DICOMImageFrameList& GetImageFrameList() const; + + void SetMitkImage(Image::Pointer image); + Image::Pointer GetMitkImage() const; + + void SetSliceIsLoaded(unsigned int index, bool isLoaded); + bool IsSliceLoaded(unsigned int index) const; + bool AllSlicesAreLoaded() const; + + void SetPixelsInterpolated(bool pixelsAreInterpolated); + bool GetPixelsInterpolated() const; + + void SetPixelSpacingInterpretation( PixelSpacingInterpretation interpretation ); + PixelSpacingInterpretation GetPixelSpacingInterpretation() const; + + void SetPixelSpacingInformation(const std::string& pixelSpacing, const std::string& imagerPixelSpacing); + void GetDesiredMITKImagePixelSpacing( float& spacingX, float& spacingY) const; + + bool HasGantryTilt() const; + bool SetHasGantryTilt(bool hasi); + + void SetTiltInformation(const GantryTiltInformation& info); + const GantryTiltInformation GetTiltInformation() const; + + private: + + DICOMImageFrameList m_ImageFrameList; + Image::Pointer m_MitkImage; + BoolList m_SliceIsLoaded; + bool m_PixelsInterpolated; + PixelSpacingInterpretation m_PixelSpacingInterpretation; + + std::string m_PixelSpacing; + std::string m_ImagerPixelSpacing; + + bool m_HasGantryTilt; + GantryTiltInformation m_TiltInformation; +}; + +} + +#endif diff --git a/Modules/DICOMReader/mitkDICOMImageFrameInfo.cpp b/Modules/DICOMReader/mitkDICOMImageFrameInfo.cpp new file mode 100644 index 0000000000..b3ddcb0c92 --- /dev/null +++ b/Modules/DICOMReader/mitkDICOMImageFrameInfo.cpp @@ -0,0 +1,24 @@ +/*=================================================================== + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center, +Division of Medical and Biological Informatics. +All rights reserved. + +This software is distributed WITHOUT ANY WARRANTY; without +even the implied warranty of MERCHANTABILITY or FITNESS FOR +A PARTICULAR PURPOSE. + +See LICENSE.txt or http://www.mitk.org for details. + +===================================================================*/ + +#include "mitkDICOMImageFrameInfo.h" + +mitk::DICOMImageFrameInfo +::DICOMImageFrameInfo(const std::string& filename, unsigned int frameNo) +:Filename(filename) +,FrameNo(frameNo) +{ +} diff --git a/Modules/DICOMReader/mitkDICOMImageFrameInfo.h b/Modules/DICOMReader/mitkDICOMImageFrameInfo.h new file mode 100644 index 0000000000..405b5b7273 --- /dev/null +++ b/Modules/DICOMReader/mitkDICOMImageFrameInfo.h @@ -0,0 +1,48 @@ +/*=================================================================== + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center, +Division of Medical and Biological Informatics. +All rights reserved. + +This software is distributed WITHOUT ANY WARRANTY; without +even the implied warranty of MERCHANTABILITY or FITNESS FOR +A PARTICULAR PURPOSE. + +See LICENSE.txt or http://www.mitk.org for details. + +===================================================================*/ + +#ifndef mitkDICOMImageFrameInfo_h +#define mitkDICOMImageFrameInfo_h + +#include "itkObjectFactory.h" +#include "mitkCommon.h" + +#include "DICOMReaderExports.h" + +namespace mitk +{ + class DICOMReader_EXPORT DICOMImageFrameInfo : public itk::LightObject + { + public: + + std::string Filename; + unsigned int FrameNo; + + mitkClassMacro( DICOMImageFrameInfo, itk::LightObject ) + + itkNewMacro( DICOMImageFrameInfo ); + mitkNewMacro1Param( DICOMImageFrameInfo, const std::string&); + mitkNewMacro2Param( DICOMImageFrameInfo, const std::string&, unsigned int ); + + protected: + + DICOMImageFrameInfo(const std::string& filename = "", unsigned int frameNo = 0); + }; + + typedef std::vector DICOMImageFrameList; +} + +#endif diff --git a/Modules/DICOMReader/mitkDICOMSortByTag.cpp b/Modules/DICOMReader/mitkDICOMSortByTag.cpp new file mode 100644 index 0000000000..87f8644811 --- /dev/null +++ b/Modules/DICOMReader/mitkDICOMSortByTag.cpp @@ -0,0 +1,119 @@ +/*=================================================================== + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center, +Division of Medical and Biological Informatics. +All rights reserved. + +This software is distributed WITHOUT ANY WARRANTY; without +even the implied warranty of MERCHANTABILITY or FITNESS FOR +A PARTICULAR PURPOSE. + +See LICENSE.txt or http://www.mitk.org for details. + +===================================================================*/ + +#include "mitkDICOMSortByTag.h" + +mitk::DICOMSortByTag +::DICOMSortByTag(const DICOMTag& tag, DICOMSortCriterion::Pointer secondaryCriterion) +:DICOMSortCriterion(secondaryCriterion) +,m_Tag(tag) +{ +} + +mitk::DICOMSortByTag +::~DICOMSortByTag() +{ +} + +mitk::DICOMSortByTag +::DICOMSortByTag(const DICOMSortByTag& other ) +:DICOMSortCriterion(other) +,m_Tag(other.m_Tag) +{ +} + +mitk::DICOMSortByTag& +mitk::DICOMSortByTag +::operator=(const DICOMSortByTag& other) +{ + if (this != &other) + { + DICOMSortCriterion::operator=(other); + m_Tag = other.m_Tag; + } + return *this; +} + +mitk::DICOMTagList +mitk::DICOMSortByTag +::GetTagsOfInterest() const +{ + DICOMTagList list; + list.push_back(m_Tag); + return list; +} + +bool +mitk::DICOMSortByTag +::IsLeftBeforeRight(const mitk::DICOMDatasetAccess* left, const mitk::DICOMDatasetAccess* right) const +{ + return this->NumericCompare(left, right, m_Tag); +} + +bool +mitk::DICOMSortByTag +::StringCompare(const mitk::DICOMDatasetAccess* left, const mitk::DICOMDatasetAccess* right, const DICOMTag& tag) const +{ + assert(left); + assert(right); + + std::string leftString = left->GetTagValueAsString(tag); + std::string rightString = right->GetTagValueAsString(tag); + + if (leftString != rightString) + { + return leftString.compare(rightString) < 0; + } + else + { + return this->NextLevelIsLeftBeforeRight(left, right); + } +} + +bool +mitk::DICOMSortByTag +::NumericCompare(const mitk::DICOMDatasetAccess* left, const mitk::DICOMDatasetAccess* right, const DICOMTag& tag) const +{ + assert(left); + assert(right); + + std::string leftString = left->GetTagValueAsString(tag); + std::string rightString = right->GetTagValueAsString(tag); + + const char* leftInput(leftString.c_str()); + const char* rightInput(rightString.c_str()); + char* leftEnd(NULL); + char* rightEnd(NULL); + + double leftDouble = strtod(leftInput, &leftEnd); + double rightDouble = strtod(rightInput, &rightEnd); + + if (leftEnd == leftInput || rightEnd == rightInput) // no numerical conversion.. + { + return this->StringCompare(left,right, tag); // fallback to string compare + } + else + { + if (leftDouble != rightDouble) // can we decide? + { + return leftDouble < rightDouble; + } + else // ask secondary criterion + { + return this->NextLevelIsLeftBeforeRight(left, right); + } + } +} diff --git a/Modules/DICOMReader/mitkDICOMSortByTag.h b/Modules/DICOMReader/mitkDICOMSortByTag.h new file mode 100644 index 0000000000..8ca6c48596 --- /dev/null +++ b/Modules/DICOMReader/mitkDICOMSortByTag.h @@ -0,0 +1,54 @@ +/*=================================================================== + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center, +Division of Medical and Biological Informatics. +All rights reserved. + +This software is distributed WITHOUT ANY WARRANTY; without +even the implied warranty of MERCHANTABILITY or FITNESS FOR +A PARTICULAR PURPOSE. + +See LICENSE.txt or http://www.mitk.org for details. + +===================================================================*/ + +#ifndef mitkDICOMSortByTag_h +#define mitkDICOMSortByTag_h + +#include "mitkDICOMSortCriterion.h" + +namespace mitk +{ + +class DICOMReader_EXPORT DICOMSortByTag : public DICOMSortCriterion +{ + public: + + mitkClassMacro( DICOMSortByTag, DICOMSortCriterion ); + mitkNewMacro1Param( DICOMSortByTag, const DICOMTag& ); + mitkNewMacro2Param( DICOMSortByTag, const DICOMTag&, DICOMSortCriterion::Pointer ); + + virtual DICOMTagList GetTagsOfInterest() const; + virtual bool IsLeftBeforeRight(const mitk::DICOMDatasetAccess* left, const mitk::DICOMDatasetAccess* right) const; + + protected: + + DICOMSortByTag( const DICOMTag& tag, DICOMSortCriterion::Pointer secondaryCriterion = NULL ); + virtual ~DICOMSortByTag(); + + DICOMSortByTag(const DICOMSortByTag& other); + DICOMSortByTag& operator=(const DICOMSortByTag& other); + + bool StringCompare(const mitk::DICOMDatasetAccess* left, const mitk::DICOMDatasetAccess* right, const DICOMTag& tag) const; + bool NumericCompare(const mitk::DICOMDatasetAccess* left, const mitk::DICOMDatasetAccess* right, const DICOMTag& tag) const; + + private: + + DICOMTag m_Tag; +}; + +} + +#endif diff --git a/Modules/DICOMReader/mitkDICOMSortCriterion.cpp b/Modules/DICOMReader/mitkDICOMSortCriterion.cpp new file mode 100644 index 0000000000..bbeebb07f0 --- /dev/null +++ b/Modules/DICOMReader/mitkDICOMSortCriterion.cpp @@ -0,0 +1,79 @@ +/*=================================================================== + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center, +Division of Medical and Biological Informatics. +All rights reserved. + +This software is distributed WITHOUT ANY WARRANTY; without +even the implied warranty of MERCHANTABILITY or FITNESS FOR +A PARTICULAR PURPOSE. + +See LICENSE.txt or http://www.mitk.org for details. + +===================================================================*/ + +#include "mitkDICOMSortCriterion.h" + +mitk::DICOMSortCriterion +::DICOMSortCriterion(DICOMSortCriterion::Pointer secondaryCriterion) +:itk::LightObject() +,m_SecondaryCriterion(secondaryCriterion) +{ +} + +mitk::DICOMSortCriterion +::~DICOMSortCriterion() +{ +} + +mitk::DICOMSortCriterion +::DICOMSortCriterion(const DICOMSortCriterion& other ) +:itk::LightObject() +,m_SecondaryCriterion(other.m_SecondaryCriterion) +{ +} + +mitk::DICOMSortCriterion& +mitk::DICOMSortCriterion +::operator=(const DICOMSortCriterion& other) +{ + if (this != &other) + { + m_SecondaryCriterion = other.m_SecondaryCriterion; + } + return *this; +} + +mitk::DICOMTagList +mitk::DICOMSortCriterion +::GetAllTagsOfInterest() const +{ + DICOMTagList allTags; + + // iterate all secondary criteria, return all tags + const DICOMSortCriterion* criterionToCheck = this; + while (criterionToCheck) + { + DICOMTagList newElements = criterionToCheck->GetTagsOfInterest(); + allTags.insert( allTags.end(), newElements.begin(), newElements.end() ); // append + criterionToCheck = criterionToCheck->m_SecondaryCriterion.GetPointer(); + } + + return allTags; +} + +bool +mitk::DICOMSortCriterion +::NextLevelIsLeftBeforeRight(const mitk::DICOMDatasetAccess* left, const mitk::DICOMDatasetAccess* right) const +{ + if (m_SecondaryCriterion.IsNotNull()) + { + return m_SecondaryCriterion->IsLeftBeforeRight(left, right); + } + else + { + return (void*)left < (void*)right; + } +} diff --git a/Modules/DICOMReader/mitkDICOMSortCriterion.h b/Modules/DICOMReader/mitkDICOMSortCriterion.h new file mode 100644 index 0000000000..061ac3b542 --- /dev/null +++ b/Modules/DICOMReader/mitkDICOMSortCriterion.h @@ -0,0 +1,54 @@ +/*=================================================================== + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center, +Division of Medical and Biological Informatics. +All rights reserved. + +This software is distributed WITHOUT ANY WARRANTY; without +even the implied warranty of MERCHANTABILITY or FITNESS FOR +A PARTICULAR PURPOSE. + +See LICENSE.txt or http://www.mitk.org for details. + +===================================================================*/ + +#ifndef mitkDICOMSortCriterion_h +#define mitkDICOMSortCriterion_h + +#include "itkObjectFactory.h" +#include "mitkCommon.h" + +#include "mitkDICOMDatasetAccess.h" + +namespace mitk +{ + +class DICOMReader_EXPORT DICOMSortCriterion : public itk::LightObject +{ + public: + + mitkClassMacro( DICOMSortCriterion, itk::LightObject ); + + DICOMTagList GetAllTagsOfInterest() const; + virtual DICOMTagList GetTagsOfInterest() const = 0; + + virtual bool IsLeftBeforeRight(const mitk::DICOMDatasetAccess* left, const mitk::DICOMDatasetAccess* right) const = 0; + + protected: + + DICOMSortCriterion( DICOMSortCriterion::Pointer secondaryCriterion ); + virtual ~DICOMSortCriterion(); + + bool NextLevelIsLeftBeforeRight(const mitk::DICOMDatasetAccess* left, const mitk::DICOMDatasetAccess* right) const; + + DICOMSortCriterion(const DICOMSortCriterion& other); + DICOMSortCriterion& operator=(const DICOMSortCriterion& other); + + DICOMSortCriterion::Pointer m_SecondaryCriterion; +}; + +} + +#endif diff --git a/Modules/DICOMReader/mitkDICOMTag.cpp b/Modules/DICOMReader/mitkDICOMTag.cpp new file mode 100644 index 0000000000..3c9538d432 --- /dev/null +++ b/Modules/DICOMReader/mitkDICOMTag.cpp @@ -0,0 +1,166 @@ +/*=================================================================== + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center, +Division of Medical and Biological Informatics. +All rights reserved. + +This software is distributed WITHOUT ANY WARRANTY +{ +} + without +even the implied warranty of MERCHANTABILITY or FITNESS FOR +A PARTICULAR PURPOSE. + +See LICENSE.txt or http://www.mitk.org for details. + +===================================================================*/ + +#include "mitkDICOMTag.h" + +mitk::DICOMTag +::DICOMTag(unsigned int group, unsigned int element) +:m_Group(group) +,m_Element(element) +{ +} + +mitk::DICOMTag +::DICOMTag(const DICOMTag& other) +:m_Group(other.m_Group) +,m_Element(other.m_Element) +{ +} + +bool +mitk::DICOMTag +::operator==(const DICOMTag& other) const +{ + return + m_Group == other.m_Group && + m_Element == other.m_Element + ; +} + + +mitk::DICOMTag& +mitk::DICOMTag +::operator=(const DICOMTag& other) +{ + if (this != &other) + { + m_Group = other.m_Group; + m_Element = other.m_Element; + } + return *this; +} + +unsigned int +mitk::DICOMTag +::GetGroup() const +{ + return m_Group; +} + +unsigned int +mitk::DICOMTag +::GetElement() const +{ + return m_Element; +} + +bool +mitk::DICOMTag +::operator<(const DICOMTag& other) const +{ + return this->m_Group * 0x3000 + this->m_Element < + other.m_Group * 0x3000 + other.m_Element; +} + + +void +mitk::DICOMStringToOrientationVectors(const std::string& s, Vector3D& right, Vector3D& up, bool& successful) +{ + successful = true; + + std::istringstream orientationReader(s); + std::string coordinate; + unsigned int dim(0); + while( std::getline( orientationReader, coordinate, '\\' ) && dim < 6 ) + { + if (dim<3) + { + right[dim++] = atof(coordinate.c_str()); + } + else + { + up[dim++ - 3] = atof(coordinate.c_str()); + } + } + + if (dim && dim != 6) + { + successful = false; + } + else if (dim == 0) + { + // fill with defaults + right.Fill(0.0); + right[0] = 1.0; + + up.Fill(0.0); + up[1] = 1.0; + + successful = false; + } +} + +bool +mitk::DICOMStringToSpacing(const std::string& s, ScalarType& spacingX, ScalarType& spacingY) +{ + bool successful = false; + + std::istringstream spacingReader(s); + std::string spacing; + if ( std::getline( spacingReader, spacing, '\\' ) ) + { + spacingY = atof( spacing.c_str() ); + + if ( std::getline( spacingReader, spacing, '\\' ) ) + { + spacingX = atof( spacing.c_str() ); + + successful = true; + } + } + + return successful; +} + +mitk::Point3D +mitk::DICOMStringToPoint3D(const std::string& s, bool& successful) +{ + Point3D p; + successful = true; + + std::istringstream originReader(s); + std::string coordinate; + unsigned int dim(0); + while( std::getline( originReader, coordinate, '\\' ) && dim < 3) + { + p[dim++]= atof(coordinate.c_str()); + } + + if (dim && dim != 3) + { + successful = false; + } + else if (dim == 0) + { + successful = false; + p.Fill(0.0); // assume default (0,0,0) + } + + return p; +} diff --git a/Modules/DICOMReader/mitkDICOMTag.h b/Modules/DICOMReader/mitkDICOMTag.h new file mode 100644 index 0000000000..c827e272b6 --- /dev/null +++ b/Modules/DICOMReader/mitkDICOMTag.h @@ -0,0 +1,73 @@ +/*=================================================================== + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center, +Division of Medical and Biological Informatics. +All rights reserved. + +This software is distributed WITHOUT ANY WARRANTY; without +even the implied warranty of MERCHANTABILITY or FITNESS FOR +A PARTICULAR PURPOSE. + +See LICENSE.txt or http://www.mitk.org for details. + +===================================================================*/ + +#ifndef mitkTag_h +#define mitkTag_h + +#include "mitkVector.h" + +#include "DICOMReaderExports.h" + +namespace mitk +{ + +class DICOMReader_EXPORT DICOMTag +{ + public: + + DICOMTag(unsigned int group, unsigned int element); + DICOMTag(const DICOMTag& other); + DICOMTag& operator=(const DICOMTag& other); + bool operator==(const DICOMTag& other) const; + bool operator<(const DICOMTag& other) const; + + unsigned int GetGroup() const; + unsigned int GetElement() const; + + protected: + + unsigned int m_Group; + unsigned int m_Element; +}; + +typedef std::vector DICOMTagList; + +/** + \brief Convert DICOM string describing a point two Vector3D. + + DICOM tags like ImageOrientationPatient contain two vectors as float numbers separated by backslashes: + \verbatim + 42.7131\13.77\0.7\137.76\0.3 + \endverbatim + */ +void DICOMStringToOrientationVectors(const std::string& s, Vector3D& right, Vector3D& up, bool& successful); + +bool DICOMStringToSpacing(const std::string& s, ScalarType& spacingX, ScalarType& spacingY); + + +/** + \brief Convert DICOM string describing a point to Point3D. + + DICOM tags like ImagePositionPatient contain a position as float numbers separated by backslashes: + \verbatim + 42.7131\13.77\0.7 + \endverbatim + */ +mitk::Point3D DICOMStringToPoint3D(const std::string& s, bool& successful); + +} + +#endif diff --git a/Modules/DICOMReader/mitkDICOMTagBasedSorter.cpp b/Modules/DICOMReader/mitkDICOMTagBasedSorter.cpp new file mode 100644 index 0000000000..f615cb876f --- /dev/null +++ b/Modules/DICOMReader/mitkDICOMTagBasedSorter.cpp @@ -0,0 +1,244 @@ +/*=================================================================== +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center, +Division of Medical and Biological Informatics. +All rights reserved. + +This software is distributed WITHOUT ANY WARRANTY; without +even the implied warranty of MERCHANTABILITY or FITNESS FOR +A PARTICULAR PURPOSE. + +See LICENSE.txt or http://www.mitk.org for details. + +===================================================================*/ + +#include "mitkDICOMTagBasedSorter.h" + +#include + +mitk::DICOMTagBasedSorter::CutDecimalPlaces +::CutDecimalPlaces(unsigned int precision) +:m_Precision(precision) +{ +} + +std::string +mitk::DICOMTagBasedSorter::CutDecimalPlaces +::operator()(const std::string& input) const +{ + // be a bit tolerant for tags such as image orientation orienatation, let only the first few digits matter (http://bugs.mitk.org/show_bug.cgi?id=12263) + + bool conversionError(false); + Vector3D right; right.Fill(0.0); + Vector3D up; right.Fill(0.0); + DICOMStringToOrientationVectors( input, right, up, conversionError ); + + std::ostringstream ss; + ss.setf(std::ios::fixed, std::ios::floatfield); + ss.precision(m_Precision); + ss << right[0] << "\\" + << right[1] << "\\" + << right[2] << "\\" + << up[0] << "\\" + << up[1] << "\\" + << up[2]; + + return ss.str(); +} + +mitk::DICOMTagBasedSorter +::DICOMTagBasedSorter() +:DICOMDatasetSorter() +{ +} + +mitk::DICOMTagBasedSorter +::~DICOMTagBasedSorter() +{ + for(TagValueProcessorMap::iterator ti = m_TagValueProcessor.begin(); + ti != m_TagValueProcessor.end(); + ++ti) + { + delete ti->second; + } +} + +mitk::DICOMTagBasedSorter +::DICOMTagBasedSorter(const DICOMTagBasedSorter& other ) +:DICOMDatasetSorter(other) +{ +} + +mitk::DICOMTagBasedSorter& +mitk::DICOMTagBasedSorter +::operator=(const DICOMTagBasedSorter& other) +{ + if (this != &other) + { + DICOMDatasetSorter::operator=(other); + } + return *this; +} + +mitk::DICOMTagList +mitk::DICOMTagBasedSorter +::GetTagsOfInterest() +{ + DICOMTagList allTags = m_DistinguishingTags; + + DICOMTagList sortingRelevantTags = m_SortCriterion->GetAllTagsOfInterest(); + allTags.insert( allTags.end(), sortingRelevantTags.begin(), sortingRelevantTags.end() ); // append + + return allTags; +} + +void +mitk::DICOMTagBasedSorter +::AddDistinguishingTag( const DICOMTag& tag, TagValueProcessor* tagValueProcessor ) +{ + m_DistinguishingTags.push_back(tag); + m_TagValueProcessor[tag] = tagValueProcessor; +} + +void +mitk::DICOMTagBasedSorter +::SetSortCriterion( DICOMSortCriterion::ConstPointer criterion ) +{ + m_SortCriterion = criterion; +} + +void +mitk::DICOMTagBasedSorter +::Sort() +{ + // 1. split + // 2. sort each group + GroupIDToListType groups = this->SplitInputGroups(); + GroupIDToListType& sortedGroups = this->SortGroups( groups ); + + // 3. define output + this->SetNumberOfOutputs(sortedGroups.size()); + unsigned int outputIndex(0); + for (GroupIDToListType::iterator groupIter = sortedGroups.begin(); + groupIter != sortedGroups.end(); + ++outputIndex, ++groupIter) + { + this->SetOutput(outputIndex, groupIter->second); + } +} + +std::string +mitk::DICOMTagBasedSorter +::BuildGroupID( DICOMDatasetAccess* dataset ) +{ + // just concatenate all tag values + assert(dataset); + std::stringstream groupID; + groupID << "g"; + for (DICOMTagList::iterator tagIter = m_DistinguishingTags.begin(); + tagIter != m_DistinguishingTags.end(); + ++tagIter) + { + groupID << tagIter->GetGroup() << tagIter->GetElement(); // make group/element part of the id to cover empty tags + std::string rawTagValue = dataset->GetTagValueAsString(*tagIter); + std::string processedTagValue; + if ( m_TagValueProcessor[*tagIter] != NULL ) + { + processedTagValue = (*m_TagValueProcessor[*tagIter])(rawTagValue); + } + else + { + processedTagValue = rawTagValue; + } + groupID << processedTagValue; + } + // shorten ID? + return groupID.str(); +} + +mitk::DICOMTagBasedSorter::GroupIDToListType +mitk::DICOMTagBasedSorter +::SplitInputGroups() +{ + DICOMDatasetList input = GetInput(); // copy + + GroupIDToListType listForGroupID; + + for (DICOMDatasetList::iterator dsIter = input.begin(); + dsIter != input.end(); + ++dsIter) + { + DICOMDatasetAccess* dataset = *dsIter; + assert(dataset); + + std::string groupID = this->BuildGroupID( dataset ); + MITK_DEBUG << "Group ID for for " << dataset->GetFilenameIfAvailable() << ": " << groupID; + listForGroupID[groupID].push_back(dataset); + } + + MITK_DEBUG << "After tag based splitting: " << listForGroupID.size() << " groups"; + + return listForGroupID; +} + +mitk::DICOMTagBasedSorter::GroupIDToListType& +mitk::DICOMTagBasedSorter +::SortGroups(GroupIDToListType& groups) +{ + if (m_SortCriterion.IsNotNull()) + { + // for each output + // sort by all configured tags, use secondary tags when equal or empty + // make configurable: + // - sorting order (ascending, descending) + // - sort numerically + // - ... ? + unsigned int groupIndex(0); + for (GroupIDToListType::iterator gIter = groups.begin(); + gIter != groups.end(); + ++groupIndex, ++gIter) + { + DICOMDatasetList& dsList = gIter->second; + MITK_DEBUG << " --------------------------------------------------------------------------------"; + MITK_DEBUG << " DICOMTagBasedSorter before sorting group : " << groupIndex; + for (DICOMDatasetList::iterator oi = dsList.begin(); + oi != dsList.end(); + ++oi) + { + MITK_DEBUG << " INPUT : " << (*oi)->GetFilenameIfAvailable(); + } + + std::sort( dsList.begin(), dsList.end(), ParameterizedDatasetSort( m_SortCriterion ) ); + + MITK_DEBUG << " --------------------------------------------------------------------------------"; + MITK_DEBUG << " DICOMTagBasedSorter after sorting group : " << groupIndex; + for (DICOMDatasetList::iterator oi = dsList.begin(); + oi != dsList.end(); + ++oi) + { + MITK_DEBUG << " OUTPUT : " << (*oi)->GetFilenameIfAvailable(); + } + MITK_DEBUG << " --------------------------------------------------------------------------------"; + } + } + + return groups; +} + +mitk::DICOMTagBasedSorter::ParameterizedDatasetSort +::ParameterizedDatasetSort(DICOMSortCriterion::ConstPointer criterion) +:m_SortCriterion(criterion) +{ +} + +bool +mitk::DICOMTagBasedSorter::ParameterizedDatasetSort +::operator() (const mitk::DICOMDatasetAccess* left, const mitk::DICOMDatasetAccess* right) +{ + assert(left); + assert(right); + assert(m_SortCriterion.IsNotNull()); + + return m_SortCriterion->IsLeftBeforeRight(left, right); +} diff --git a/Modules/DICOMReader/mitkDICOMTagBasedSorter.h b/Modules/DICOMReader/mitkDICOMTagBasedSorter.h new file mode 100644 index 0000000000..388ff20504 --- /dev/null +++ b/Modules/DICOMReader/mitkDICOMTagBasedSorter.h @@ -0,0 +1,91 @@ +/*=================================================================== + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center, +Division of Medical and Biological Informatics. +All rights reserved. + +This software is distributed WITHOUT ANY WARRANTY; without +even the implied warranty of MERCHANTABILITY or FITNESS FOR +A PARTICULAR PURPOSE. + +See LICENSE.txt or http://www.mitk.org for details. + +===================================================================*/ + +#ifndef mitkDICOMTagBasedSorter_h +#define mitkDICOMTagBasedSorter_h + +#include "mitkDICOMDatasetSorter.h" +#include "mitkDICOMSortCriterion.h" + +namespace mitk +{ + +/** + \brief sort files based on filename (last resort). +*/ +class DICOMReader_EXPORT DICOMTagBasedSorter : public DICOMDatasetSorter +{ + public: + + class TagValueProcessor // TODO use smart pointers here + { + public: + virtual std::string operator()(const std::string&) const = 0; + }; + + class CutDecimalPlaces : public TagValueProcessor + { + public: + CutDecimalPlaces(unsigned int precision); + virtual std::string operator()(const std::string&) const; + private: + unsigned int m_Precision; + }; + + mitkClassMacro( DICOMTagBasedSorter, DICOMDatasetSorter ) + itkNewMacro( DICOMTagBasedSorter ) + + void AddDistinguishingTag( const DICOMTag&, TagValueProcessor* tagValueProcessor = NULL ); + void SetSortCriterion( DICOMSortCriterion::ConstPointer criterion ); + + virtual DICOMTagList GetTagsOfInterest(); + + virtual void Sort(); + + protected: + + struct ParameterizedDatasetSort + { + ParameterizedDatasetSort(DICOMSortCriterion::ConstPointer); + bool operator() (const mitk::DICOMDatasetAccess* left, const mitk::DICOMDatasetAccess* right); + bool StringCompare(const mitk::DICOMDatasetAccess* left, const mitk::DICOMDatasetAccess* right); + bool NumericCompare(const mitk::DICOMDatasetAccess* left, const mitk::DICOMDatasetAccess* right); + DICOMSortCriterion::ConstPointer m_SortCriterion; + }; + + + DICOMTagBasedSorter(); + virtual ~DICOMTagBasedSorter(); + + DICOMTagBasedSorter(const DICOMTagBasedSorter& other); + DICOMTagBasedSorter& operator=(const DICOMTagBasedSorter& other); + + std::string BuildGroupID( DICOMDatasetAccess* dataset ); + + typedef std::map GroupIDToListType; + GroupIDToListType SplitInputGroups(); + GroupIDToListType& SortGroups(GroupIDToListType& groups); + + DICOMTagList m_DistinguishingTags; + typedef std::map TagValueProcessorMap; + TagValueProcessorMap m_TagValueProcessor; + + DICOMSortCriterion::ConstPointer m_SortCriterion; +}; + +} + +#endif diff --git a/Modules/DICOMReader/mitkEquiDistantBlocksSorter.cpp b/Modules/DICOMReader/mitkEquiDistantBlocksSorter.cpp new file mode 100644 index 0000000000..7ef92bdfca --- /dev/null +++ b/Modules/DICOMReader/mitkEquiDistantBlocksSorter.cpp @@ -0,0 +1,497 @@ +/*=================================================================== The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center, +Division of Medical and Biological Informatics. +All rights reserved. + +This software is distributed WITHOUT ANY WARRANTY; without +even the implied warranty of MERCHANTABILITY or FITNESS FOR +A PARTICULAR PURPOSE. + +See LICENSE.txt or http://www.mitk.org for details. + +===================================================================*/ + +#include "mitkEquiDistantBlocksSorter.h" + +mitk::EquiDistantBlocksSorter::SliceGroupingAnalysisResult +::SliceGroupingAnalysisResult() +:m_GantryTilt(false) +{ +} + +mitk::DICOMDatasetList +mitk::EquiDistantBlocksSorter::SliceGroupingAnalysisResult +::GetBlockFilenames() +{ + return m_GroupedFiles; +} + +mitk::DICOMDatasetList +mitk::EquiDistantBlocksSorter::SliceGroupingAnalysisResult +::GetUnsortedFilenames() +{ + return m_UnsortedFiles; +} + +bool +mitk::EquiDistantBlocksSorter::SliceGroupingAnalysisResult +::ContainsGantryTilt() +{ + return m_GantryTilt; +} + +void +mitk::EquiDistantBlocksSorter::SliceGroupingAnalysisResult +::AddFileToSortedBlock(DICOMDatasetAccess* dataset) +{ + m_GroupedFiles.push_back( dataset ); +} + +void +mitk::EquiDistantBlocksSorter::SliceGroupingAnalysisResult +::AddFileToUnsortedBlock(DICOMDatasetAccess* dataset) +{ + m_UnsortedFiles.push_back( dataset ); +} + +void +mitk::EquiDistantBlocksSorter::SliceGroupingAnalysisResult +::AddFilesToUnsortedBlock(const DICOMDatasetList& datasets) +{ + m_UnsortedFiles.insert( m_UnsortedFiles.end(), datasets.begin(), datasets.end() ); +} + +void +mitk::EquiDistantBlocksSorter::SliceGroupingAnalysisResult +::SetFirstFilenameOfBlock(const std::string& filename) +{ + m_FirstFilenameOfBlock = filename; +} + +std::string +mitk::EquiDistantBlocksSorter::SliceGroupingAnalysisResult +::GetFirstFilenameOfBlock() const +{ + return m_FirstFilenameOfBlock; +} + +void +mitk::EquiDistantBlocksSorter::SliceGroupingAnalysisResult +::FlagGantryTilt(const GantryTiltInformation& tiltInfo) +{ + m_GantryTilt = true; + m_TiltInfo = tiltInfo; +} + +const mitk::GantryTiltInformation& +mitk::EquiDistantBlocksSorter::SliceGroupingAnalysisResult +::GetTiltInfo() const +{ + return m_TiltInfo; +} + +void +mitk::EquiDistantBlocksSorter::SliceGroupingAnalysisResult +::UndoPrematureGrouping() +{ + assert( !m_GroupedFiles.empty() ); + m_UnsortedFiles.insert( m_UnsortedFiles.begin(), m_GroupedFiles.back() ); + m_GroupedFiles.pop_back(); + m_GantryTilt = false; +} + +// ------------------------ end helper class + +mitk::EquiDistantBlocksSorter +::EquiDistantBlocksSorter() +:DICOMDatasetSorter() +,m_AcceptTilt(false) +{ +} + +mitk::EquiDistantBlocksSorter +::EquiDistantBlocksSorter(const EquiDistantBlocksSorter& other ) +:DICOMDatasetSorter(other) +,m_AcceptTilt(false) +{ +} + +mitk::EquiDistantBlocksSorter +::~EquiDistantBlocksSorter() +{ +} + +void +mitk::EquiDistantBlocksSorter +::SetAcceptTilt(bool accept) +{ + m_AcceptTilt = accept; +} + + +mitk::EquiDistantBlocksSorter& +mitk::EquiDistantBlocksSorter +::operator=(const EquiDistantBlocksSorter& other) +{ + if (this != &other) + { + DICOMDatasetSorter::operator=(other); + } + return *this; +} + +mitk::DICOMTagList +mitk::EquiDistantBlocksSorter +::GetTagsOfInterest() +{ + DICOMTagList tags; + tags.push_back( DICOMTag(0x0020, 0x0032) ); // ImagePositionPatient + tags.push_back( DICOMTag(0x0020, 0x0037) ); // ImageOrientationPatient + tags.push_back( DICOMTag(0x0018, 0x1120) ); // GantryTilt + + return tags; +} + +void +mitk::EquiDistantBlocksSorter +::Sort() +{ + DICOMDatasetList remainingInput = GetInput(); // copy + + typedef std::list OutputListType; + OutputListType outputs; + + m_SliceGroupingResults.clear(); + + while (!remainingInput.empty()) // repeat until all files are grouped somehow + { + SliceGroupingAnalysisResult regularBlock = this->AnalyzeFileForITKImageSeriesReaderSpacingAssumption( remainingInput, m_AcceptTilt ); + + DICOMDatasetList inBlock = regularBlock.GetBlockFilenames(); + DICOMDatasetList laterBlock = regularBlock.GetUnsortedFilenames(); + MITK_DEBUG << "Result: sorted 3D group with " << inBlock.size() << " files"; + for (DICOMDatasetList::const_iterator diter = inBlock.begin(); diter != inBlock.end(); ++diter) + MITK_DEBUG << " IN " << (*diter)->GetFilenameIfAvailable(); + for (DICOMDatasetList::const_iterator diter = laterBlock.begin(); diter != laterBlock.end(); ++diter) + MITK_DEBUG << " OUT " << (*diter)->GetFilenameIfAvailable(); + + outputs.push_back( regularBlock.GetBlockFilenames() ); + m_SliceGroupingResults.push_back( regularBlock ); + remainingInput = regularBlock.GetUnsortedFilenames(); + } + + unsigned int numberOfOutputs = outputs.size(); + this->SetNumberOfOutputs(numberOfOutputs); + + unsigned int outputIndex(0); + for (OutputListType::iterator oIter = outputs.begin(); + oIter != outputs.end(); + ++outputIndex, ++oIter) + { + this->SetOutput(outputIndex, *oIter); + } +} + +const mitk::GantryTiltInformation +mitk::EquiDistantBlocksSorter +::GetTiltInformation(const std::string& filename, bool& hasTiltInfo) +{ + for (ResultsList::iterator ri = m_SliceGroupingResults.begin(); + ri != m_SliceGroupingResults.end(); + ++ri) + { + SliceGroupingAnalysisResult& result = *ri; + + if (filename == result.GetFirstFilenameOfBlock()) + { + hasTiltInfo = result.ContainsGantryTilt(); // this is a returning statement, don't remove + if (hasTiltInfo) + { + return result.GetTiltInfo(); + } + } + } + + return GantryTiltInformation(); // empty +} + +std::string +mitk::EquiDistantBlocksSorter +::ConstCharStarToString(const char* s) +{ + return s ? std::string(s) : std::string(); +} + +mitk::EquiDistantBlocksSorter::SliceGroupingAnalysisResult +mitk::EquiDistantBlocksSorter +::AnalyzeFileForITKImageSeriesReaderSpacingAssumption( + const DICOMDatasetList& datasets, + bool groupImagesWithGantryTilt) +{ + // result.first = files that fit ITK's assumption + // result.second = files that do not fit, should be run through AnalyzeFileForITKImageSeriesReaderSpacingAssumption() again + SliceGroupingAnalysisResult result; + + // we const_cast here, because I could not use a map.at(), which would make the code much more readable + const DICOMTag tagImagePositionPatient = DICOMTag(0x0020,0x0032); // Image Position (Patient) + const DICOMTag tagImageOrientation = DICOMTag(0x0020, 0x0037); // Image Orientation + const DICOMTag tagGantryTilt = DICOMTag(0x0018, 0x1120); // gantry tilt + + Vector3D fromFirstToSecondOrigin; fromFirstToSecondOrigin.Fill(0.0); + bool fromFirstToSecondOriginInitialized(false); + Point3D thisOrigin; + thisOrigin.Fill(0.0f); + Point3D lastOrigin; + lastOrigin.Fill(0.0f); + Point3D lastDifferentOrigin; + lastDifferentOrigin.Fill(0.0f); + + bool lastOriginInitialized(false); + + MITK_DEBUG << "--------------------------------------------------------------------------------"; + MITK_DEBUG << "Analyzing " << datasets.size() << " files for z-spacing assumption of ITK's ImageSeriesReader (group tilted: " << groupImagesWithGantryTilt << ")"; + unsigned int fileIndex(0); + for (DICOMDatasetList::const_iterator dsIter = datasets.begin(); + dsIter != datasets.end(); + ++dsIter, ++fileIndex) + { + bool fileFitsIntoPattern(false); + std::string thisOriginString; + // Read tag value into point3D. PLEASE replace this by appropriate GDCM code if you figure out how to do that + thisOriginString = (*dsIter)->GetTagValueAsString( tagImagePositionPatient ); + + if (thisOriginString.empty()) + { + // don't let such files be in a common group. Everything without position information will be loaded as a single slice: + // with standard DICOM files this can happen to: CR, DX, SC + MITK_DEBUG << " ==> Sort away " << *dsIter << " for later analysis (no position information)"; // we already have one occupying this position + + if ( result.GetBlockFilenames().empty() ) // nothing WITH position information yet + { + // ==> this is a group of its own, stop processing, come back later + result.AddFileToSortedBlock( *dsIter ); + + DICOMDatasetList remainingFiles; + remainingFiles.insert( remainingFiles.end(), dsIter+1, datasets.end() ); + result.AddFilesToUnsortedBlock( remainingFiles ); + + fileFitsIntoPattern = false; + break; // no files anymore + } + else + { + // ==> this does not match, consider later + result.AddFileToUnsortedBlock( *dsIter ); // sort away for further analysis + fileFitsIntoPattern = false; + continue; // next file + } + } + + bool ignoredConversionError(-42); // hard to get here, no graceful way to react + thisOrigin = DICOMStringToPoint3D( thisOriginString, ignoredConversionError ); + + MITK_DEBUG << " " << fileIndex << " " << (*dsIter)->GetFilenameIfAvailable() + << " at " + /* << thisOriginString */ << "(" << thisOrigin[0] << "," << thisOrigin[1] << "," << thisOrigin[2] << ")"; + + if ( lastOriginInitialized && (thisOrigin == lastOrigin) ) + { + MITK_DEBUG << " ==> Sort away " << *dsIter << " for separate time step"; // we already have one occupying this position + result.AddFileToUnsortedBlock( *dsIter ); // sort away for further analysis + fileFitsIntoPattern = false; + } + else + { + if (!fromFirstToSecondOriginInitialized && lastOriginInitialized) // calculate vector as soon as possible when we get a new position + { + fromFirstToSecondOrigin = thisOrigin - lastDifferentOrigin; + fromFirstToSecondOriginInitialized = true; + + // Here we calculate if this slice and the previous one are well aligned, + // i.e. we test if the previous origin is on a line through the current + // origin, directed into the normal direction of the current slice. + + // If this is NOT the case, then we have a data set with a TILTED GANTRY geometry, + // which cannot be simply loaded into a single mitk::Image at the moment. + // For this case, we flag this finding in the result and DicomSeriesReader + // can correct for that later. + + Vector3D right; right.Fill(0.0); + Vector3D up; right.Fill(0.0); // might be down as well, but it is just a name at this point + std::string orientationValue = (*dsIter)->GetTagValueAsString( tagImageOrientation ); + DICOMStringToOrientationVectors( orientationValue, right, up, ignoredConversionError ); + + GantryTiltInformation tiltInfo( lastDifferentOrigin, thisOrigin, right, up, 1 ); + + if ( tiltInfo.IsSheared() ) // mitk::eps is too small; 1/1000 of a mm should be enough to detect tilt + { + /* optimistic approach, accepting gantry tilt: save file for later, check all further files */ + + // at this point we have TWO slices analyzed! if they are the only two files, we still split, because there is no third to verify our tilting assumption. + // later with a third being available, we must check if the initial tilting vector is still valid. if yes, continue. + // if NO, we need to split the already sorted part (result.first) and the currently analyzed file (*dsIter) + + // tell apart gantry tilt from overall skewedness + // sort out irregularly sheared slices, that IS NOT tilting + + if ( groupImagesWithGantryTilt && tiltInfo.IsRegularGantryTilt() ) + { + // check if this is at least roughly the same angle as recorded in DICOM tags + double angle = 0.0; + std::string tiltStr = (*dsIter)->GetTagValueAsString( tagGantryTilt ); + const char* convertInput = tiltStr.c_str(); + char* convertEnd(NULL); + if (!tiltStr.empty()) + { + // read value, compare to calculated angle + angle = strtod(convertInput, &convertEnd); // TODO check errors! + } + + if (convertEnd != convertInput) + { + MITK_DEBUG << "Comparing recorded tilt angle " << angle << " against calculated value " << tiltInfo.GetTiltAngleInDegrees(); + // TODO we probably want the signs correct, too (that depends: this is just a rough check, nothing serious) + if ( fabs(angle) - tiltInfo.GetTiltAngleInDegrees() > 0.25) + { + result.AddFileToUnsortedBlock( *dsIter ); // sort away for further analysis + fileFitsIntoPattern = false; + } + else // tilt angle from header is less than 0.25 degrees different from what we calculated, assume this is fine + { + assert(!datasets.empty()); + + result.FlagGantryTilt(tiltInfo); + result.AddFileToSortedBlock( *dsIter ); // this file is good for current block + result.SetFirstFilenameOfBlock( datasets.front()->GetFilenameIfAvailable() ); + fileFitsIntoPattern = true; + } + } + else // we cannot check the calculated tilt angle against the one from the dicom header (so we assume we are right) + { + assert(!datasets.empty()); + + result.FlagGantryTilt(tiltInfo); + result.AddFileToSortedBlock( *dsIter ); // this file is good for current block + result.SetFirstFilenameOfBlock( datasets.front()->GetFilenameIfAvailable() ); + fileFitsIntoPattern = true; + } + } + else // caller does not want tilt compensation OR shearing is more complicated than tilt + { + result.AddFileToUnsortedBlock( *dsIter ); // sort away for further analysis + fileFitsIntoPattern = false; + } + } + else // not sheared + { + result.AddFileToSortedBlock( *dsIter ); // this file is good for current block + fileFitsIntoPattern = true; + } + } + else if (fromFirstToSecondOriginInitialized) // we already know the offset between slices + { + Point3D assumedOrigin = lastDifferentOrigin + fromFirstToSecondOrigin; + + Vector3D originError = assumedOrigin - thisOrigin; + double norm = originError.GetNorm(); + double toleratedError(0.005); // max. 1/10mm error when measurement crosses 20 slices in z direction + + if (norm > toleratedError) + { + MITK_DEBUG << " File does not fit into the inter-slice distance pattern (diff = " + << norm << ", allowed " + << toleratedError << ")."; + MITK_DEBUG << " Expected position (" << assumedOrigin[0] << "," + << assumedOrigin[1] << "," + << assumedOrigin[2] << "), got position (" + << thisOrigin[0] << "," + << thisOrigin[1] << "," + << thisOrigin[2] << ")"; + MITK_DEBUG << " ==> Sort away " << *dsIter << " for later analysis"; + + // At this point we know we deviated from the expectation of ITK's ImageSeriesReader + // We split the input file list at this point, i.e. all files up to this one (excluding it) + // are returned as group 1, the remaining files (including the faulty one) are group 2 + + /* Optimistic approach: check if any of the remaining slices fits in */ + result.AddFileToUnsortedBlock( *dsIter ); // sort away for further analysis + fileFitsIntoPattern = false; + } + else + { + result.AddFileToSortedBlock( *dsIter ); // this file is good for current block + fileFitsIntoPattern = true; + } + } + else // this should be the very first slice + { + result.AddFileToSortedBlock( *dsIter ); // this file is good for current block + fileFitsIntoPattern = true; + } + } + + // record current origin for reference in later iterations + if ( !lastOriginInitialized || ( fileFitsIntoPattern && (thisOrigin != lastOrigin) ) ) + { + lastDifferentOrigin = thisOrigin; + } + + lastOrigin = thisOrigin; + lastOriginInitialized = true; + } + + if ( result.ContainsGantryTilt() ) + { + // check here how many files were grouped. + // IF it was only two files AND we assume tiltedness (e.g. save "distance") + // THEN we would want to also split the two previous files (simple) because + // we don't have any reason to assume they belong together + + if ( result.GetBlockFilenames().size() == 2 ) + { + result.UndoPrematureGrouping(); + } + } + + // update tilt info to get maximum precision + // earlier, tilt was only calculated from first and second slice. + // now that we know the whole range, we can re-calculate using the very first and last slice + if ( result.ContainsGantryTilt() && result.GetBlockFilenames().size() > 1 ) + { + DICOMDatasetList datasets = result.GetBlockFilenames(); + DICOMDatasetAccess* firstDataset = datasets.front(); + DICOMDatasetAccess* lastDataset = datasets.back(); + unsigned int numberOfSlicesApart = datasets.size() - 1; + + Vector3D right; right.Fill(0.0); + Vector3D up; right.Fill(0.0); // might be down as well, but it is just a name at this point + std::string orientationValue = firstDataset->GetTagValueAsString( tagImageOrientation ); + bool orientationConversion(false); + DICOMStringToOrientationVectors( orientationValue, right, up, orientationConversion ); + + if (orientationConversion) + { + + std::string firstOriginString = firstDataset->GetTagValueAsString( tagImagePositionPatient ); + std::string lastOriginString = lastDataset->GetTagValueAsString( tagImagePositionPatient ); + + if (!firstOriginString.empty() && !lastOriginString.empty()) + { + bool firstOriginConversion(false); + bool lastOriginConversion(false); + + Point3D firstOrigin = DICOMStringToPoint3D( firstOriginString, firstOriginConversion ); + Point3D lastOrigin = DICOMStringToPoint3D( lastOriginString, lastOriginConversion ); + + if (firstOriginConversion && lastOriginConversion) + { + GantryTiltInformation updatedTiltInfo( firstOrigin, lastOrigin, right, up, numberOfSlicesApart ); + result.FlagGantryTilt(updatedTiltInfo); + } + } + } + } + + return result; +} diff --git a/Modules/DICOMReader/mitkEquiDistantBlocksSorter.h b/Modules/DICOMReader/mitkEquiDistantBlocksSorter.h new file mode 100644 index 0000000000..67bcbdc86c --- /dev/null +++ b/Modules/DICOMReader/mitkEquiDistantBlocksSorter.h @@ -0,0 +1,156 @@ +/*=================================================================== + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center, +Division of Medical and Biological Informatics. +All rights reserved. + +This software is distributed WITHOUT ANY WARRANTY; without +even the implied warranty of MERCHANTABILITY or FITNESS FOR +A PARTICULAR PURPOSE. + +See LICENSE.txt or http://www.mitk.org for details. + +===================================================================*/ + +#ifndef mitkEquiDistantBlocksSorter_h +#define mitkEquiDistantBlocksSorter_h + +#include "mitkDICOMDatasetSorter.h" +#include "mitkDICOMSortCriterion.h" +#include "mitkGantryTiltInformation.h" + +#include "mitkVector.h" + +#include + +namespace mitk +{ + +/** + \brief Split inputs into blocks of equidant slices. + + This kind of splitting is used as a check before loading a DICOM series with ITK ImageSeriesReader. +*/ +class DICOMReader_EXPORT EquiDistantBlocksSorter : public DICOMDatasetSorter +{ + public: + + mitkClassMacro( EquiDistantBlocksSorter, DICOMDatasetSorter ) + itkNewMacro( EquiDistantBlocksSorter ) + + virtual DICOMTagList GetTagsOfInterest(); + + virtual void Sort(); + + void SetAcceptTilt(bool accept); + const GantryTiltInformation GetTiltInformation(const std::string& filename, bool& hasTiltInfo); // TODO ugly, but bool flag will be removed + + protected: + + /** + \brief Return type of DicomSeriesReader::AnalyzeFileForITKImageSeriesReaderSpacingAssumption. + + Class contains the grouping result of method DicomSeriesReader::AnalyzeFileForITKImageSeriesReaderSpacingAssumption, + which takes as input a number of images, which are all equally oriented and spatially sorted along their normal direction. + + The result contains of two blocks: a first one is the grouping result, all of those images can be loaded + into one image block because they have an equal origin-to-origin distance without any gaps in-between. + */ + class SliceGroupingAnalysisResult + { + public: + + SliceGroupingAnalysisResult(); + + /** + \brief Grouping result, all same origin-to-origin distance w/o gaps. + */ + DICOMDatasetList GetBlockFilenames(); // TODO rename --> ... Dataset ... instead of filename + + void SetFirstFilenameOfBlock(const std::string& filename); + std::string GetFirstFilenameOfBlock() const; + + /** + \brief Remaining files, which could not be grouped. + */ + DICOMDatasetList GetUnsortedFilenames(); + + /** + \brief Wheter or not the grouped result contain a gantry tilt. + */ + bool ContainsGantryTilt(); + + /** + \brief Detailed description of gantry tilt. + */ + const GantryTiltInformation& GetTiltInfo() const; + + /** + \brief Meant for internal use by AnalyzeFileForITKImageSeriesReaderSpacingAssumption only. + */ + void AddFileToSortedBlock(DICOMDatasetAccess* dataset); + + /** + \brief Meant for internal use by AnalyzeFileForITKImageSeriesReaderSpacingAssumption only. + */ + void AddFileToUnsortedBlock(DICOMDatasetAccess* dataset); + void AddFilesToUnsortedBlock(const DICOMDatasetList& datasets); + + /** + \brief Meant for internal use by AnalyzeFileForITKImageSeriesReaderSpacingAssumption only. + \todo Could make sense to enhance this with an instance of GantryTiltInformation to store the whole result! + */ + void FlagGantryTilt(const GantryTiltInformation& tiltInfo); + + /** + \brief Only meaningful for use by AnalyzeFileForITKImageSeriesReaderSpacingAssumption. + */ + void UndoPrematureGrouping(); + + protected: + + DICOMDatasetList m_GroupedFiles; + DICOMDatasetList m_UnsortedFiles; + + bool m_GantryTilt; // TODO make the flag part of GantryTiltInformation! + GantryTiltInformation m_TiltInfo; + std::string m_FirstFilenameOfBlock; + }; + + /** + \brief Ensure an equal z-spacing for a group of files. + + Takes as input a number of images, which are all equally oriented and spatially sorted along their normal direction. + + Internally used by GetSeries. Returns two lists: the first one contins slices of equal inter-slice spacing. + The second list contains remaining files, which need to be run through AnalyzeFileForITKImageSeriesReaderSpacingAssumption again. + + Relevant code that is matched here is in + itkImageSeriesReader.txx (ImageSeriesReader::GenerateOutputInformation(void)), lines 176 to 245 (as of ITK 3.20) + */ + SliceGroupingAnalysisResult + AnalyzeFileForITKImageSeriesReaderSpacingAssumption(const DICOMDatasetList& files, bool groupsOfSimilarImages); + + /** + \brief Safely convert const char* to std::string. + */ + std::string + ConstCharStarToString(const char* s); + + EquiDistantBlocksSorter(); + virtual ~EquiDistantBlocksSorter(); + + EquiDistantBlocksSorter(const EquiDistantBlocksSorter& other); + EquiDistantBlocksSorter& operator=(const EquiDistantBlocksSorter& other); + + bool m_AcceptTilt; + + typedef std::vector ResultsList; + ResultsList m_SliceGroupingResults; +}; + +} + +#endif diff --git a/Modules/DICOMReader/mitkGantryTiltInformation.cpp b/Modules/DICOMReader/mitkGantryTiltInformation.cpp new file mode 100644 index 0000000000..7e3667c260 --- /dev/null +++ b/Modules/DICOMReader/mitkGantryTiltInformation.cpp @@ -0,0 +1,217 @@ +/*=================================================================== + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center, +Division of Medical and Biological Informatics. +All rights reserved. + +This software is distributed WITHOUT ANY WARRANTY; without +even the implied warranty of MERCHANTABILITY or FITNESS FOR +A PARTICULAR PURPOSE. + +See LICENSE.txt or http://www.mitk.org for details. + +===================================================================*/ + +#include "mitkGantryTiltInformation.h" + +#include "mitkLogMacros.h" + +mitk::GantryTiltInformation::GantryTiltInformation() +: m_ShiftUp(0.0) +, m_ShiftRight(0.0) +, m_ShiftNormal(0.0) +, m_ITKAssumedSliceSpacing(0.0) +, m_NumberOfSlicesApart(1) +{ +} + + +#define doublepoint(x) \ + Point3Dd x; \ + x[0] = x ## f[0]; \ + x[1] = x ## f[1]; \ + x[2] = x ## f[2]; + + +#define doublevector(x) \ + Vector3Dd x; \ + x[0] = x ## f[0]; \ + x[1] = x ## f[1]; \ + x[2] = x ## f[2]; + +mitk::GantryTiltInformation::GantryTiltInformation( + const Point3D& origin1f, const Point3D& origin2f, + const Vector3D& rightf, const Vector3D& upf, + unsigned int numberOfSlicesApart) +: m_ShiftUp(0.0) +, m_ShiftRight(0.0) +, m_ShiftNormal(0.0) +, m_NumberOfSlicesApart(numberOfSlicesApart) +{ + assert(numberOfSlicesApart); + + doublepoint(origin1); + doublepoint(origin2); + doublevector(right); + doublevector(up); + + // determine if slice 1 (imagePosition1 and imageOrientation1) and slice 2 can be in one orthogonal slice stack: + // calculate a line from origin 1, directed along the normal of slice (calculated as the cross product of orientation 1) + // check if this line passes through origin 2 + + /* + Determine if line (imagePosition2 + l * normal) contains imagePosition1. + Done by calculating the distance of imagePosition1 from line (imagePosition2 + l *normal) + + E.g. http://mathworld.wolfram.com/Point-LineDistance3-Dimensional.html + + squared distance = | (pointAlongNormal - origin2) x (origin2 - origin1) | ^ 2 + / + |pointAlongNormal - origin2| ^ 2 + + ( x meaning the cross product ) + */ + + Vector3Dd normal = itk::CrossProduct(right, up); + Point3Dd pointAlongNormal = origin2 + normal; + + double numerator = itk::CrossProduct( pointAlongNormal - origin2 , origin2 - origin1 ).GetSquaredNorm(); + double denominator = (pointAlongNormal - origin2).GetSquaredNorm(); + + double distance = sqrt(numerator / denominator); + + if ( distance > 0.001 ) // mitk::eps is too small; 1/1000 of a mm should be enough to detect tilt + { + MITK_DEBUG << " Series seems to contain a tilted (or sheared) geometry"; + MITK_DEBUG << " Distance of expected slice origin from actual slice origin: " << distance; + MITK_DEBUG << " ==> storing this shift for later analysis:"; + MITK_DEBUG << " v origin1: " << origin1; + MITK_DEBUG << " v origin2: " << origin2; + MITK_DEBUG << " v right: " << right; + MITK_DEBUG << " v up: " << up; + MITK_DEBUG << " v normal: " << normal; + + Point3Dd projectionRight = projectPointOnLine( origin1, origin2, right ); + Point3Dd projectionNormal = projectPointOnLine( origin1, origin2, normal ); + + m_ShiftRight = (projectionRight - origin2).GetNorm(); + m_ShiftNormal = (projectionNormal - origin2).GetNorm(); + + /* + now also check to which side the image is shifted. + + Calculation e.g. from + http://mathworld.wolfram.com/Point-PlaneDistance.html + */ + + Point3Dd testPoint = origin1; + Vector3Dd planeNormal = up; + + double signedDistance = ( + planeNormal[0] * testPoint[0] + + planeNormal[1] * testPoint[1] + + planeNormal[2] * testPoint[2] + - ( + planeNormal[0] * origin2[0] + + planeNormal[1] * origin2[1] + + planeNormal[2] * origin2[2] + ) + ) + / + sqrt( planeNormal[0] * planeNormal[0] + + planeNormal[1] * planeNormal[1] + + planeNormal[2] * planeNormal[2] + ); + + m_ShiftUp = signedDistance; + + m_ITKAssumedSliceSpacing = (origin2 - origin1).GetNorm(); + // How do we now this is assumed? See header documentation for ITK code references + //double itkAssumedSliceSpacing = sqrt( m_ShiftUp * m_ShiftUp + m_ShiftNormal * m_ShiftNormal ); + + MITK_INFO << " calculated from slices " << m_NumberOfSlicesApart << " slices apart"; + MITK_DEBUG << " shift normal: " << m_ShiftNormal; + MITK_DEBUG << " shift normal assumed by ITK: " << m_ITKAssumedSliceSpacing; + MITK_DEBUG << " shift up: " << m_ShiftUp; + MITK_DEBUG << " shift right: " << m_ShiftRight; + + MITK_DEBUG << " tilt angle (deg): " << atan( m_ShiftUp / m_ShiftNormal ) * 180.0 / 3.1415926535; + } +} + +void +mitk::GantryTiltInformation +::Print(std::ostream& os) const +{ + os << " calculated from slices " << m_NumberOfSlicesApart << " slices apart" << std::endl; + os << " shift normal: " << m_ShiftNormal << std::endl; + os << " shift normal assumed by ITK: " << m_ITKAssumedSliceSpacing << std::endl; + os << " shift up: " << m_ShiftUp << std::endl; + os << " shift right: " << m_ShiftRight << std::endl; + + os << " tilt angle (deg): " << atan( m_ShiftUp / m_ShiftNormal ) * 180.0 / 3.1415926535 << std::endl; +} + +mitk::Point3D +mitk::GantryTiltInformation::projectPointOnLine( Point3Dd p, Point3Dd lineOrigin, Vector3Dd lineDirection ) +{ + /** + See illustration at http://mo.mathematik.uni-stuttgart.de/inhalt/aussage/aussage472/ + + vector(lineOrigin,p) = normal * ( innerproduct((p - lineOrigin),normal) / squared-length(normal) ) + */ + + Vector3Dd lineOriginToP = p - lineOrigin; + double innerProduct = lineOriginToP * lineDirection; + + double factor = innerProduct / lineDirection.GetSquaredNorm(); + Point3Dd projection = lineOrigin + factor * lineDirection; + + return projection; +} + +double +mitk::GantryTiltInformation::GetTiltCorrectedAdditionalSize(unsigned int imageSizeZ) const +{ + return fabs(m_ShiftUp / static_cast(m_NumberOfSlicesApart) * static_cast(imageSizeZ-1)); // TODO care for SIZE of image +} + +double +mitk::GantryTiltInformation::GetTiltAngleInDegrees() const +{ + return atan( fabs(m_ShiftUp) / m_ShiftNormal ) * 180.0 / 3.1415926535; +} + +double +mitk::GantryTiltInformation::GetMatrixCoefficientForCorrectionInWorldCoordinates() const +{ + // so many mm need to be shifted per slice! + return m_ShiftUp / static_cast(m_NumberOfSlicesApart); +} + +double +mitk::GantryTiltInformation::GetRealZSpacing() const +{ + return m_ShiftNormal / static_cast(m_NumberOfSlicesApart); +} + + +bool +mitk::GantryTiltInformation::IsSheared() const +{ + return ( fabs(m_ShiftRight) > 0.001 + || fabs(m_ShiftUp) > 0.001); +} + + +bool +mitk::GantryTiltInformation::IsRegularGantryTilt() const +{ + return ( fabs(m_ShiftRight) < 0.001 + && fabs(m_ShiftUp) > 0.001); +} + + + diff --git a/Modules/DICOMReader/mitkGantryTiltInformation.h b/Modules/DICOMReader/mitkGantryTiltInformation.h new file mode 100644 index 0000000000..00a23bdd40 --- /dev/null +++ b/Modules/DICOMReader/mitkGantryTiltInformation.h @@ -0,0 +1,129 @@ +/*=================================================================== + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center, +Division of Medical and Biological Informatics. +All rights reserved. + +This software is distributed WITHOUT ANY WARRANTY; without +even the implied warranty of MERCHANTABILITY or FITNESS FOR +A PARTICULAR PURPOSE. + +See LICENSE.txt or http://www.mitk.org for details. + +===================================================================*/ + +#ifndef mitkGantryTiltInformation_h +#define mitkGantryTiltInformation_h + +#include "mitkVector.h" + +namespace mitk +{ + +/** + \brief Gantry tilt analysis result. + + Takes geometry information for two slices of a DICOM series and + calculates whether these fit into an orthogonal block or not. + If NOT, they can either be the result of an acquisition with + gantry tilt OR completly broken by some shearing transformation. + + Most calculations are done in the constructor, results can then + be read via the remaining methods. + */ +class GantryTiltInformation +{ + public: + + // two types to avoid any rounding errors + typedef itk::Point Point3Dd; + typedef itk::Vector Vector3Dd; + + /** + \brief Just so we can create empty instances for assigning results later. + */ + GantryTiltInformation(); + + void Print(std::ostream& os) const; + + /** + \brief THE constructor, which does all the calculations. + + Determining the amount of tilt is done by checking the distances + of origin1 from planes through origin2. Two planes are considered: + - normal vector along normal of slices (right x up): gives the slice distance + - normal vector along orientation vector "up": gives the shift parallel to the plane orientation + + The tilt angle can then be calculated from these distances + + \param origin1 origin of the first slice + \param origin2 origin of the second slice + \param right right/up describe the orientatation of borth slices + \param up right/up describe the orientatation of borth slices + \param numberOfSlicesApart how many slices are the given origins apart (1 for neighboring slices) + */ + GantryTiltInformation( const Point3D& origin1, + const Point3D& origin2, + const Vector3D& right, + const Vector3D& up, unsigned int numberOfSlicesApart); + + /** + \brief Whether the slices were sheared. + + True if any of the shifts along right or up vector are non-zero. + */ + bool IsSheared() const; + + /** + \brief Whether the shearing is a gantry tilt or more complicated. + + Gantry tilt will only produce shifts in ONE orientation, not in both. + + Since the correction code currently only coveres one tilt direction + AND we don't know of medical images with two tilt directions, the + loading code wants to check if our assumptions are true. + */ + bool IsRegularGantryTilt() const; + + /** + \brief The offset distance in Y direction for each slice in mm (describes the tilt result). + */ + double GetMatrixCoefficientForCorrectionInWorldCoordinates() const; + + + /** + \brief The z / inter-slice spacing. Needed to correct ImageSeriesReader's result. + */ + double GetRealZSpacing() const; + + /** + \brief The shift between first and last slice in mm. + + Needed to resize an orthogonal image volume. + */ + double GetTiltCorrectedAdditionalSize(unsigned int imageSizeZ) const; + + /** + \brief Calculated tilt angle in degrees. + */ + double GetTiltAngleInDegrees() const; + + protected: + + /** + \brief Projection of point p onto line through lineOrigin in direction of lineDirection. + */ + Point3D projectPointOnLine( Point3Dd p, Point3Dd lineOrigin, Vector3Dd lineDirection ); + + double m_ShiftUp; + double m_ShiftRight; + double m_ShiftNormal; + double m_ITKAssumedSliceSpacing; + unsigned int m_NumberOfSlicesApart; +}; + +} // namespace + +#endif diff --git a/Modules/DICOMReader/mitkITKDICOMSeriesReaderHelper.cpp b/Modules/DICOMReader/mitkITKDICOMSeriesReaderHelper.cpp new file mode 100644 index 0000000000..1ffba9b26d --- /dev/null +++ b/Modules/DICOMReader/mitkITKDICOMSeriesReaderHelper.cpp @@ -0,0 +1,97 @@ +/*=================================================================== + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center, +Division of Medical and Biological Informatics. +All rights reserved. + +This software is distributed WITHOUT ANY WARRANTY; without +even the implied warranty of MERCHANTABILITY or FITNESS FOR +A PARTICULAR PURPOSE. + +See LICENSE.txt or http://www.mitk.org for details. + +===================================================================*/ + +#include "mitkITKDICOMSeriesReaderHelper.h" +#include "mitkITKDICOMSeriesReaderHelper.txx" + +mitk::Image::Pointer +mitk::ITKDICOMSeriesReaderHelper +::Load( const StringContainer& filenames, bool correctTilt, const GantryTiltInformation& tiltInfo ) +{ + if( filenames.empty() ) + { + MITK_DEBUG << "Calling LoadDicomSeries with empty filename string container. Probably invalid application logic."; + return NULL; // this is not actually an error but the result is very simple + } + + typedef itk::GDCMImageIO DcmIoType; + DcmIoType::Pointer io = DcmIoType::New(); + + Image::Pointer preLoadedImageBlock; // TODO + + try + { + if (io->CanReadFile(filenames.front().c_str())) + { + io->SetFileName(filenames.front().c_str()); + io->ReadImageInformation(); + + if (io->GetPixelType() == itk::ImageIOBase::SCALAR) + { + switch (io->GetComponentType()) + { + case DcmIoType::UCHAR: return LoadDICOMByITK(filenames, correctTilt, tiltInfo, io, preLoadedImageBlock); + case DcmIoType::CHAR: return LoadDICOMByITK(filenames, correctTilt, tiltInfo, io, preLoadedImageBlock); + case DcmIoType::USHORT: return LoadDICOMByITK(filenames, correctTilt, tiltInfo, io, preLoadedImageBlock); + case DcmIoType::SHORT: return LoadDICOMByITK(filenames, correctTilt, tiltInfo, io, preLoadedImageBlock); + case DcmIoType::UINT: return LoadDICOMByITK(filenames, correctTilt, tiltInfo, io, preLoadedImageBlock); + case DcmIoType::INT: return LoadDICOMByITK(filenames, correctTilt, tiltInfo, io, preLoadedImageBlock); + case DcmIoType::ULONG: return LoadDICOMByITK(filenames, correctTilt, tiltInfo, io, preLoadedImageBlock); + case DcmIoType::LONG: return LoadDICOMByITK(filenames, correctTilt, tiltInfo, io, preLoadedImageBlock); + case DcmIoType::FLOAT: return LoadDICOMByITK(filenames, correctTilt, tiltInfo, io, preLoadedImageBlock); + case DcmIoType::DOUBLE: return LoadDICOMByITK(filenames, correctTilt, tiltInfo, io, preLoadedImageBlock); + default: + MITK_ERROR << "Found unsupported DICOM scalar pixel type: (enum value) " << io->GetComponentType(); + } + } + else if (io->GetPixelType() == itk::ImageIOBase::RGB) + { + switch (io->GetComponentType()) + { + case DcmIoType::UCHAR: return LoadDICOMByITK< itk::RGBPixel >(filenames, correctTilt, tiltInfo, io, preLoadedImageBlock); + case DcmIoType::CHAR: return LoadDICOMByITK >(filenames, correctTilt, tiltInfo, io, preLoadedImageBlock); + case DcmIoType::USHORT: return LoadDICOMByITK >(filenames, correctTilt, tiltInfo, io, preLoadedImageBlock); + case DcmIoType::SHORT: return LoadDICOMByITK >(filenames, correctTilt, tiltInfo, io, preLoadedImageBlock); + case DcmIoType::UINT: return LoadDICOMByITK >(filenames, correctTilt, tiltInfo, io, preLoadedImageBlock); + case DcmIoType::INT: return LoadDICOMByITK >(filenames, correctTilt, tiltInfo, io, preLoadedImageBlock); + case DcmIoType::ULONG: return LoadDICOMByITK >(filenames, correctTilt, tiltInfo, io, preLoadedImageBlock); + case DcmIoType::LONG: return LoadDICOMByITK >(filenames, correctTilt, tiltInfo, io, preLoadedImageBlock); + case DcmIoType::FLOAT: return LoadDICOMByITK >(filenames, correctTilt, tiltInfo, io, preLoadedImageBlock); + case DcmIoType::DOUBLE: return LoadDICOMByITK >(filenames, correctTilt, tiltInfo, io, preLoadedImageBlock); + default: + MITK_ERROR << "Found unsupported DICOM scalar pixel type: (enum value) " << io->GetComponentType(); + } + } + + MITK_ERROR << "Unsupported DICOM pixel type"; + return NULL; + } + } + catch(itk::MemoryAllocationError& e) + { + MITK_ERROR << "Out of memory. Cannot load DICOM series: " << e.what(); + } + catch(std::exception& e) + { + MITK_ERROR << "Error encountered when loading DICOM series:" << e.what(); + } + catch(...) + { + MITK_ERROR << "Unspecified error encountered when loading DICOM series."; + } + + return NULL; +} diff --git a/Modules/DICOMReader/mitkITKDICOMSeriesReaderHelper.h b/Modules/DICOMReader/mitkITKDICOMSeriesReaderHelper.h new file mode 100644 index 0000000000..c1042cf29f --- /dev/null +++ b/Modules/DICOMReader/mitkITKDICOMSeriesReaderHelper.h @@ -0,0 +1,53 @@ +/*=================================================================== + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center, +Division of Medical and Biological Informatics. +All rights reserved. + +This software is distributed WITHOUT ANY WARRANTY; without +even the implied warranty of MERCHANTABILITY or FITNESS FOR +A PARTICULAR PURPOSE. + +See LICENSE.txt or http://www.mitk.org for details. + +===================================================================*/ + +#ifndef mitkDICOMSeriesReaderHelper_h +#define mitkDICOMSeriesReaderHelper_h + +#include "mitkImage.h" +#include "mitkGantryTiltInformation.h" + +#include + +namespace mitk +{ + +class ITKDICOMSeriesReaderHelper +{ + public: + + typedef std::vector StringContainer; + + Image::Pointer Load( const StringContainer& filenames, bool correctTilt, const GantryTiltInformation& tiltInfo ); + + template + typename ImageType::Pointer + // TODO this is NOT inplace! + InPlaceFixUpTiltedGeometry( ImageType* input, const GantryTiltInformation& tiltInfo ); + + template + Image::Pointer + LoadDICOMByITK( const StringContainer& filenames, + bool correctTilt, + const GantryTiltInformation& tiltInfo, + itk::GDCMImageIO::Pointer& io, + Image::Pointer preLoadedImageBlock ); + +}; + +} + +#endif diff --git a/Modules/DICOMReader/mitkITKDICOMSeriesReaderHelper.txx b/Modules/DICOMReader/mitkITKDICOMSeriesReaderHelper.txx new file mode 100644 index 0000000000..df477f5359 --- /dev/null +++ b/Modules/DICOMReader/mitkITKDICOMSeriesReaderHelper.txx @@ -0,0 +1,209 @@ +/*=================================================================== + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center, +Division of Medical and Biological Informatics. +All rights reserved. + +This software is distributed WITHOUT ANY WARRANTY; without +even the implied warranty of MERCHANTABILITY or FITNESS FOR +A PARTICULAR PURPOSE. +See LICENSE.txt or http://www.mitk.org for details. + +===================================================================*/ + +#include "mitkITKDICOMSeriesReaderHelper.h" + +#include +#include +//#include +//#include +//#include + + +template +mitk::Image::Pointer +mitk::ITKDICOMSeriesReaderHelper +::LoadDICOMByITK( + const StringContainer& filenames, + bool correctTilt, + const GantryTiltInformation& tiltInfo, + itk::GDCMImageIO::Pointer& io, + Image::Pointer preLoadedImageBlock ) +{ + /******** Normal Case, 3D (also for GDCM < 2 usable) ***************/ + mitk::Image::Pointer image = mitk::Image::New(); + + typedef itk::Image ImageType; + typedef itk::ImageSeriesReader ReaderType; + + io = itk::GDCMImageIO::New(); + typename ReaderType::Pointer reader = ReaderType::New(); + + reader->SetImageIO(io); + reader->ReverseOrderOff(); + + if (preLoadedImageBlock.IsNull()) + { + reader->SetFileNames(filenames); + reader->Update(); + typename ImageType::Pointer readVolume = reader->GetOutput(); + + // if we detected that the images are from a tilted gantry acquisition, we need to push some pixels into the right position + if (correctTilt) + { + readVolume = InPlaceFixUpTiltedGeometry( reader->GetOutput(), tiltInfo ); + } + + image->InitializeByItk(readVolume.GetPointer()); + image->SetImportVolume(readVolume->GetBufferPointer()); + } + else + { + image = preLoadedImageBlock; + StringContainer fakeList; + fakeList.push_back( filenames.front() ); + reader->SetFileNames( fakeList ); // we always need to load at least one file to get the MetaDataDictionary + reader->Update(); + } + + MITK_DEBUG << "Volume dimension: [" << image->GetDimension(0) << ", " + << image->GetDimension(1) << ", " + << image->GetDimension(2) << "]"; + + MITK_DEBUG << "Volume spacing: [" << image->GetGeometry()->GetSpacing()[0] << ", " + << image->GetGeometry()->GetSpacing()[1] << ", " + << image->GetGeometry()->GetSpacing()[2] << "]"; + + return image; +} + +template +typename ImageType::Pointer +mitk::ITKDICOMSeriesReaderHelper +::InPlaceFixUpTiltedGeometry( ImageType* input, const GantryTiltInformation& tiltInfo ) +{ + tiltInfo.Print(std::cout); + typedef itk::ResampleImageFilter ResampleFilterType; + typename ResampleFilterType::Pointer resampler = ResampleFilterType::New(); + resampler->SetInput( input ); + + /* + Transform for a point is + - transform from actual position to index coordinates + - apply a shear that undoes the gantry tilt + - transform back into world coordinates + + Anybody who does this in a simpler way: don't forget to write up how and why your solution works + */ + typedef itk::ScalableAffineTransform< double, ImageType::ImageDimension > TransformType; + typename TransformType::Pointer transformShear = TransformType::New(); + + /** + - apply a shear and spacing correction to the image block that corrects the ITK reader's error + - ITK ignores the shear and loads slices into an orthogonal volume + - ITK calculates the spacing from the origin distance, which is more than the actual spacing with gantry tilt images + - to undo the effect + - we have calculated some information in tiltInfo: + - the shift in Y direction that is added with each additional slice is the most important information + - the Y-shift is calculated in mm world coordinates + - we apply a shearing transformation to the ITK-read image volume + - to do this locally, + - we transform the image volume back to origin and "normal" orientation by applying the inverse of its transform + (this brings us into the image's "index coordinate" system) + - we apply a shear with the Y-shift factor put into a unit transform at row 1, col 2 + - we transform the image volume back to its actual position (from index to world coordinates) + - we lastly apply modify the image spacing in z direction by replacing this number with the correctly calulcated inter-slice distance + */ + + ScalarType factor = tiltInfo.GetMatrixCoefficientForCorrectionInWorldCoordinates() / input->GetSpacing()[1]; + // row 1, column 2 corrects shear in parallel to Y axis, proportional to distance in Z direction + transformShear->Shear( 1, 2, factor ); + + typename TransformType::Pointer imageIndexToWorld = TransformType::New(); + imageIndexToWorld->SetOffset( input->GetOrigin().GetVectorFromOrigin() ); + + typename TransformType::MatrixType indexToWorldMatrix; + indexToWorldMatrix = input->GetDirection(); + + typename ImageType::DirectionType scale; + for ( unsigned int i = 0; i < ImageType::ImageDimension; i++ ) + { + scale[i][i] = input->GetSpacing()[i]; + } + indexToWorldMatrix *= scale; + + imageIndexToWorld->SetMatrix( indexToWorldMatrix ); + + typename TransformType::Pointer imageWorldToIndex = TransformType::New(); + imageIndexToWorld->GetInverse( imageWorldToIndex ); + + typename TransformType::Pointer gantryTiltCorrection = TransformType::New(); + gantryTiltCorrection->Compose( imageWorldToIndex ); + gantryTiltCorrection->Compose( transformShear ); + gantryTiltCorrection->Compose( imageIndexToWorld ); + + resampler->SetTransform( gantryTiltCorrection ); + + typedef itk::LinearInterpolateImageFunction< ImageType, double > InterpolatorType; + typename InterpolatorType::Pointer interpolator = InterpolatorType::New(); + resampler->SetInterpolator( interpolator ); + /* + This would be the right place to invent a meaningful value for positions outside of the image. + For CT, HU -1000 might be meaningful, but a general solution seems not possible. Even for CT, + -1000 would only look natural for many not all images. + */ + + // TODO use (0028,0120) Pixel Padding Value if present + resampler->SetDefaultPixelValue( itk::NumericTraits< typename ImageType::PixelType >::min() ); + + // adjust size in Y direction! (maybe just transform the outer last pixel to see how much space we would need + + resampler->SetOutputParametersFromImage( input ); // we basically need the same image again, just sheared + + + // if tilt positive, then we need additional pixels BELOW origin, otherwise we need pixels behind the end of the block + + // in any case we need more size to accomodate shifted slices + typename ImageType::SizeType largerSize = resampler->GetSize(); // now the resampler already holds the input image's size. + double imageSizeZ = largerSize[2]; + MITK_DEBUG <<"Calculate lager size = " << largerSize[1] << " + " << tiltInfo.GetTiltCorrectedAdditionalSize(imageSizeZ) << " / " << input->GetSpacing()[1] << "+ 2.0"; + largerSize[1] += static_cast(tiltInfo.GetTiltCorrectedAdditionalSize(imageSizeZ) / input->GetSpacing()[1]+ 2.0); + resampler->SetSize( largerSize ); + MITK_DEBUG << "Fix Y size of image w/ spacing " << input->GetSpacing()[1] << " from " << input->GetLargestPossibleRegion().GetSize()[1] << " to " << largerSize[1]; + + // in SOME cases this additional size is below/behind origin + if ( tiltInfo.GetMatrixCoefficientForCorrectionInWorldCoordinates() > 0.0 ) + { + typename ImageType::DirectionType imageDirection = input->GetDirection(); + Vector3D yDirection; + yDirection[0] = imageDirection[0][1]; + yDirection[1] = imageDirection[1][1]; + yDirection[2] = imageDirection[2][1]; + yDirection.Normalize(); + + typename ImageType::PointType shiftedOrigin; + shiftedOrigin = input->GetOrigin(); + + // add some pixels to make everything fit + shiftedOrigin[0] -= yDirection[0] * (tiltInfo.GetTiltCorrectedAdditionalSize(imageSizeZ) + 1.0 * input->GetSpacing()[1]); + shiftedOrigin[1] -= yDirection[1] * (tiltInfo.GetTiltCorrectedAdditionalSize(imageSizeZ) + 1.0 * input->GetSpacing()[1]); + shiftedOrigin[2] -= yDirection[2] * (tiltInfo.GetTiltCorrectedAdditionalSize(imageSizeZ) + 1.0 * input->GetSpacing()[1]); + + resampler->SetOutputOrigin( shiftedOrigin ); + } + + resampler->Update(); + typename ImageType::Pointer result = resampler->GetOutput(); + + // ImageSeriesReader calculates z spacing as the distance between the first two origins. + // This is not correct in case of gantry tilt, so we set our calculated spacing. + typename ImageType::SpacingType correctedSpacing = result->GetSpacing(); + correctedSpacing[2] = tiltInfo.GetRealZSpacing(); + result->SetSpacing( correctedSpacing ); + + return result; +} + + diff --git a/Modules/DICOMReader/mitkSortByImagePositionPatient.cpp b/Modules/DICOMReader/mitkSortByImagePositionPatient.cpp new file mode 100644 index 0000000000..5b24cd2911 --- /dev/null +++ b/Modules/DICOMReader/mitkSortByImagePositionPatient.cpp @@ -0,0 +1,128 @@ +/*=================================================================== + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center, +Division of Medical and Biological Informatics. +All rights reserved. + +This software is distributed WITHOUT ANY WARRANTY; without +even the implied warranty of MERCHANTABILITY or FITNESS FOR +A PARTICULAR PURPOSE. + +See LICENSE.txt or http://www.mitk.org for details. + +===================================================================*/ + +#include "mitkSortByImagePositionPatient.h" +#include "mitkDICOMTag.h" + +mitk::SortByImagePositionPatient +::SortByImagePositionPatient(DICOMSortCriterion::Pointer secondaryCriterion) +:DICOMSortCriterion(secondaryCriterion) +{ +} + +mitk::SortByImagePositionPatient +::~SortByImagePositionPatient() +{ +} + +mitk::SortByImagePositionPatient +::SortByImagePositionPatient(const SortByImagePositionPatient& other ) +:DICOMSortCriterion(other) +{ +} + +mitk::SortByImagePositionPatient& +mitk::SortByImagePositionPatient +::operator=(const SortByImagePositionPatient& other) +{ + if (this != &other) + { + DICOMSortCriterion::operator=(other); + } + return *this; +} + +mitk::DICOMTagList +mitk::SortByImagePositionPatient +::GetTagsOfInterest() const +{ + DICOMTagList tags; + tags.push_back( DICOMTag(0x0020, 0x0032) ); // ImagePositionPatient + tags.push_back( DICOMTag(0x0020, 0x0037) ); // ImageOrientationPatient + + return tags; +} + +bool +mitk::SortByImagePositionPatient +::IsLeftBeforeRight(const mitk::DICOMDatasetAccess* left, const mitk::DICOMDatasetAccess* right) const +{ + // sort by distance to world origin, assuming (almost) equal orientation + static const DICOMTag tagImagePositionPatient = DICOMTag(0x0020,0x0032); // Image Position (Patient) + static const DICOMTag tagImageOrientation = DICOMTag(0x0020, 0x0037); // Image Orientation + + static Vector3D leftRight; leftRight.Fill(0.0); + static Vector3D leftUp; leftUp.Fill(0.0); + static bool leftHasOrientation(false); + DICOMStringToOrientationVectors( left->GetTagValueAsString( tagImageOrientation ), + leftRight, leftUp, leftHasOrientation ); + + static Vector3D rightRight; rightRight.Fill(0.0); + static Vector3D rightUp; rightUp.Fill(0.0); + static bool rightHasOrientation(false); + DICOMStringToOrientationVectors( right->GetTagValueAsString( tagImageOrientation ), + rightRight, rightUp, rightHasOrientation ); + + static Point3D leftOrigin; leftOrigin.Fill(0.0f); + static bool leftHasOrigin(false); + leftOrigin = DICOMStringToPoint3D( left->GetTagValueAsString( tagImagePositionPatient ), leftHasOrigin ); + + static Point3D rightOrigin; rightOrigin.Fill(0.0f); + static bool rightHasOrigin(false); + rightOrigin = DICOMStringToPoint3D( right->GetTagValueAsString( tagImagePositionPatient ), rightHasOrigin ); + + // we tolerate very small differences in image orientation, since we got to know about + // acquisitions where these values change across a single series (7th decimal digit) + // (http://bugs.mitk.org/show_bug.cgi?id=12263) + // still, we want to check if our assumption of 'almost equal' orientations is valid + for (unsigned int dim = 0; dim < 3; ++dim) + { + if ( fabs(leftRight[dim] - rightRight[dim]) > 0.0001 + || fabs(leftUp[dim] - rightUp[dim]) > 0.0001) + { + MITK_ERROR << "Dicom images have different orientations."; + throw std::logic_error("Dicom images have different orientations. Call GetSeries() first to separate images."); + } + } + + static Vector3D normal; + normal[0] = leftRight[1] * leftUp[2] - leftRight[2] * leftUp[1]; + normal[1] = leftRight[2] * leftUp[0] - leftRight[0] * leftUp[2]; + normal[2] = leftRight[0] * leftUp[1] - leftRight[1] * leftUp[0]; + + static double leftDistance = 0.0; + static double rightDistance = 0.0; + leftDistance = 0.0; + rightDistance = 0.0; + + // this computes the distance from world origin (0,0,0) ALONG THE NORMAL of the image planes + for (unsigned int dim = 0; dim < 3; ++dim) + { + leftDistance += normal[dim] * leftOrigin[dim]; + rightDistance += normal[dim] * rightOrigin[dim]; + } + + // if we can sort by just comparing the distance, we do exactly that + if ( fabs(leftDistance - rightDistance) >= mitk::eps) + { + // default: compare position + return leftDistance < rightDistance; + } + else + { + return this->NextLevelIsLeftBeforeRight(left, right); + } +} diff --git a/Modules/DICOMReader/mitkSortByImagePositionPatient.h b/Modules/DICOMReader/mitkSortByImagePositionPatient.h new file mode 100644 index 0000000000..510bb56ea3 --- /dev/null +++ b/Modules/DICOMReader/mitkSortByImagePositionPatient.h @@ -0,0 +1,50 @@ +/*=================================================================== + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center, +Division of Medical and Biological Informatics. +All rights reserved. + +This software is distributed WITHOUT ANY WARRANTY; without +even the implied warranty of MERCHANTABILITY or FITNESS FOR +A PARTICULAR PURPOSE. + +See LICENSE.txt or http://www.mitk.org for details. + +===================================================================*/ + +#ifndef mitkSortByImagePositionPatient_h +#define mitkSortByImagePositionPatient_h + +#include "mitkDICOMSortCriterion.h" + +#include "mitkVector.h" + +namespace mitk +{ + +class DICOMReader_EXPORT SortByImagePositionPatient : public DICOMSortCriterion +{ + public: + + mitkClassMacro( SortByImagePositionPatient, DICOMSortCriterion ); + mitkNewMacro1Param( SortByImagePositionPatient, DICOMSortCriterion::Pointer ); + + virtual DICOMTagList GetTagsOfInterest() const; + virtual bool IsLeftBeforeRight(const mitk::DICOMDatasetAccess* left, const mitk::DICOMDatasetAccess* right) const; + + protected: + + SortByImagePositionPatient( DICOMSortCriterion::Pointer secondaryCriterion = NULL ); + virtual ~SortByImagePositionPatient(); + + SortByImagePositionPatient(const SortByImagePositionPatient& other); + SortByImagePositionPatient& operator=(const SortByImagePositionPatient& other); + + private: +}; + +} + +#endif diff --git a/Core/Code/Testing/DICOMTesting/CMakeLists.txt b/Modules/DICOMTesting/CMakeLists.txt similarity index 81% rename from Core/Code/Testing/DICOMTesting/CMakeLists.txt rename to Modules/DICOMTesting/CMakeLists.txt index 5e1277b2c5..3721dbe93b 100644 --- a/Core/Code/Testing/DICOMTesting/CMakeLists.txt +++ b/Modules/DICOMTesting/CMakeLists.txt @@ -1,41 +1,41 @@ if(BUILD_TESTING) if(GDCM_DIR) # clear variables from prior files.cmake # Else CMake would use the content of these variables and would try to create tests (which are not present in DICOMTesting). set(MODULE_TESTS) set(MODULE_IMAGE_TESTS) set(MODULE_SURFACE_TESTS) set(MODULE_TESTIMAGES) set(MODULE_TESTSURFACES) set(MODULE_CUSTOM_TESTS) set(H_FILES) set(CPP_FILES) # now create a new module only for testing purposes -MITK_CREATE_MODULE( mitkDICOMTesting - DEPENDS Mitk # Mitk.so +MITK_CREATE_MODULE( DICOMTesting + DEPENDS DICOMReader ) -MITK_CHECK_MODULE(_RESULT mitkDICOMTesting) +MITK_CHECK_MODULE(_RESULT DICOMTesting) if(_RESULT) - message(STATUS "mitkDICOMTesting application won't be built. Missing: ${_RESULT}") + message(STATUS "DICOMTesting application won't be built. Missing: ${_RESULT}") else(_RESULT) # add helpful applications -MITK_USE_MODULE( mitkDICOMTesting ) +MITK_USE_MODULE( DICOMTesting ) include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR} ${ALL_INCLUDE_DIRECTORIES}) # dumps out image information add_executable(DumpDICOMMitkImage DumpDICOMMitkImage.cpp) target_link_libraries(DumpDICOMMitkImage ${ALL_LIBRARIES}) # compares dumped out image information against reference dump add_executable(VerifyDICOMMitkImageDump VerifyDICOMMitkImageDump.cpp) target_link_libraries(VerifyDICOMMitkImageDump ${ALL_LIBRARIES}) add_subdirectory(Testing) endif() endif() endif() diff --git a/Core/Code/Testing/DICOMTesting/DumpDICOMMitkImage.cpp b/Modules/DICOMTesting/DumpDICOMMitkImage.cpp similarity index 96% rename from Core/Code/Testing/DICOMTesting/DumpDICOMMitkImage.cpp rename to Modules/DICOMTesting/DumpDICOMMitkImage.cpp index 23f0a4f17c..6183b2e34e 100644 --- a/Core/Code/Testing/DICOMTesting/DumpDICOMMitkImage.cpp +++ b/Modules/DICOMTesting/DumpDICOMMitkImage.cpp @@ -1,38 +1,39 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ + #include "mitkTestDICOMLoading.h" int main(int argc, char** argv) { mitk::TestDICOMLoading loader; - mitk::TestDICOMLoading::StringContainer files; + mitk::StringList files; for (int arg = 1; arg < argc; ++arg) files.push_back( argv[arg] ); mitk::TestDICOMLoading::ImageList images = loader.LoadFiles(files); // combine individual dumps in a way that VerifyDICOMMitkImageDump is able to separate again. // I.e.: when changing this piece of code, always change VerifyDICOMMitkImageDump, too. unsigned int imageCounter(0); for ( mitk::TestDICOMLoading::ImageList::const_iterator imageIter = images.begin(); imageIter != images.end(); ++imageIter ) { std::cout << "-- Image " << ++imageCounter << "\n"; std::cout << loader.DumpImageInformation( *imageIter ) << "\n"; } } diff --git a/Core/Code/Testing/DICOMTesting/Testing/CMakeLists.txt b/Modules/DICOMTesting/Testing/CMakeLists.txt similarity index 98% rename from Core/Code/Testing/DICOMTesting/Testing/CMakeLists.txt rename to Modules/DICOMTesting/Testing/CMakeLists.txt index 0aca28227f..c48c5b50c7 100644 --- a/Core/Code/Testing/DICOMTesting/Testing/CMakeLists.txt +++ b/Modules/DICOMTesting/Testing/CMakeLists.txt @@ -1,88 +1,87 @@ -MITK_CREATE_MODULE_TESTS(LABELS MITK-Core) - +MITK_CREATE_MODULE_TESTS(LABELS) include(mitkFunctionAddTestLabel) # verify minimum expectations: # files are recognized as DICOM # loading files results in a given number of images mitkAddCustomModuleTest(mitkDICOMTestingSanityTest_NoFiles mitkDICOMTestingSanityTest 0) mitkAddCustomModuleTest(mitkDICOMTestingSanityTest_CTImage mitkDICOMTestingSanityTest 1 ${MITK_DATA_DIR}/spacing-ok-ct.dcm) mitkAddCustomModuleTest(mitkDICOMTestingSanityTest_MRImage mitkDICOMTestingSanityTest 1 ${MITK_DATA_DIR}/spacing-ok-mr.dcm) mitkAddCustomModuleTest(mitkDICOMTestingSanityTest_SCImage mitkDICOMTestingSanityTest 1 ${MITK_DATA_DIR}/spacing-ok-sc.dcm) mitkAddCustomModuleTest(mitkDICOMTestingSanityTest_NoImagePositionPatient mitkDICOMTestingSanityTest 1 ${MITK_DATA_DIR}/spacing-ok-sc-no2032.dcm) # verifies that the loader can also be used to just scan for tags and provide them in mitk::Properties (parameter preLoadedVolume) mitkAddCustomModuleTest(mitkDICOMPreloadedVolumeTest_Slice mitkDICOMPreloadedVolumeTest ${MITK_DATA_DIR}/spacing-ok-ct.dcm) set(VERIFY_DUMP_CMD ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/VerifyDICOMMitkImageDump) set(CT_ABDOMEN_DIR ${MITK_DATA_DIR}/TinyCTAbdomen) set(MR_HEART_DIR ${MITK_DATA_DIR}/3D+t-Heart) set(CT_TILT_HEAD_DIR ${MITK_DATA_DIR}/TiltHead) set(CT_TILT_DIR ${MITK_DATA_DIR}/TiltedData) set(SOP_CLASSES_DIR ${MITK_DATA_DIR}/DICOMReader) # these variables could be passed as parameters to a generic test creation function set(TESTS_DIR Tests) set(INPUT_LISTNAME input) set(EXPECTED_DUMP expected.dump) function(AddDicomTestsFromDataRepository CURRENT_DATASET_DIR TESTS_DIR INPUT_LISTNAME EXPECTED_DUMP) # find all test input lists file(GLOB_RECURSE allInputs ${CURRENT_DATASET_DIR}/${TESTS_DIR}/*/${INPUT_LISTNAME}) function(expectFileExists filename) if(NOT EXISTS ${filename}) message(SEND_ERROR "Test case expected file ${filename} which does not exist! Please fix your CMake code or file layout.") endif(NOT EXISTS ${filename}) endfunction(expectFileExists) foreach(input ${allInputs}) # extract only the name of the directory of this very test case string(REGEX REPLACE ".*${TESTS_DIR}/([^/]+)/.*" "\\1" input ${input}) set(inputfilelist "${CURRENT_DATASET_DIR}/${TESTS_DIR}/${input}/${INPUT_LISTNAME}") set(expecteddump "${CURRENT_DATASET_DIR}/${TESTS_DIR}/${input}/${EXPECTED_DUMP}") set(testname "DICOM_Load_${input}") #message(STATUS "DICOM loading test case '${input}'") expectFileExists(${inputfilelist}) expectFileExists(${expecteddump}) # TODO provide error messages if input not valid set(dicomFiles) # clear list # read list of file names from file "input" file(STRINGS ${inputfilelist} rawDicomFiles) foreach(raw ${rawDicomFiles}) # prepend each file with an abosolute path set(fileWithFullPath ${CURRENT_DATASET_DIR}/${raw}) list(APPEND dicomFiles ${fileWithFullPath}) endforeach(raw ${rawDicomFiles}) #message(STATUS " Load ${dicomFiles}") add_test(${testname} ${VERIFY_DUMP_CMD} ${expecteddump} ${dicomFiles}) mitkFunctionAddTestLabel(${testname}) endforeach(input allInputs) endfunction(AddDicomTestsFromDataRepository CURRENT_DATASET_DIR TESTS_DIR INPUT_LISTNAME EXPECTED_DUMP) AddDicomTestsFromDataRepository(${CT_ABDOMEN_DIR} ${TESTS_DIR} ${INPUT_LISTNAME} ${EXPECTED_DUMP}) AddDicomTestsFromDataRepository(${CT_TILT_HEAD_DIR} ${TESTS_DIR} ${INPUT_LISTNAME} ${EXPECTED_DUMP}) AddDicomTestsFromDataRepository(${CT_TILT_DIR} ${TESTS_DIR} ${INPUT_LISTNAME} ${EXPECTED_DUMP}) AddDicomTestsFromDataRepository(${MR_HEART_DIR} ${TESTS_DIR} ${INPUT_LISTNAME} ${EXPECTED_DUMP}) AddDicomTestsFromDataRepository(${SOP_CLASSES_DIR} ${TESTS_DIR} ${INPUT_LISTNAME} ${EXPECTED_DUMP}) # use one more realistic series for testing property lists file(GLOB_RECURSE abdomenImages ${CT_ABDOMEN_DIR}/14?) # this is just one small volume mitkAddCustomModuleTest(mitkDICOMPreloadedVolumeTest_Abdomen mitkDICOMPreloadedVolumeTest ${abdomenImages}) diff --git a/Core/Code/Testing/DICOMTesting/Testing/files.cmake b/Modules/DICOMTesting/Testing/files.cmake similarity index 100% rename from Core/Code/Testing/DICOMTesting/Testing/files.cmake rename to Modules/DICOMTesting/Testing/files.cmake diff --git a/Modules/DICOMTesting/Testing/mitkDICOMLocaleTest.cpp b/Modules/DICOMTesting/Testing/mitkDICOMLocaleTest.cpp new file mode 100644 index 0000000000..1ed7789357 --- /dev/null +++ b/Modules/DICOMTesting/Testing/mitkDICOMLocaleTest.cpp @@ -0,0 +1,131 @@ +/*=================================================================== + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center, +Division of Medical and Biological Informatics. +All rights reserved. + +This software is distributed WITHOUT ANY WARRANTY; without +even the implied warranty of MERCHANTABILITY or FITNESS FOR +A PARTICULAR PURPOSE. + +See LICENSE.txt or http://www.mitk.org for details. + +===================================================================*/ + +/* + This test is meant to reproduce the following error: + + - The machine or current user has a German locale. + - This esp. means that stream IO expects the decimal separator as a comma: "," + - DICOM files use a point "." as the decimal separator to be locale independent + - The parser used by MITK (ITK's GDCM) seems to use the current locale instead of the "C" or "POSIX" locale + - This leads to spacings (and probably other numbers) being trimmed/rounded, + e.g. the correct spacing of 0.314 is read as 1.0 etc. + +*/ + +#include "mitkDataNodeFactory.h" +#include "mitkStandardFileLocations.h" +#include "mitkDicomSeriesReader.h" + +#include "mitkTestingMacros.h" + +#include +#include +#include + +bool mitkDICOMLocaleTestChangeLocale(const std::string& locale) +{ + try + { + MITK_TEST_OUTPUT(<< " ** Changing locale from " << setlocale(LC_ALL, NULL) << " to '" << locale << "'"); + setlocale(LC_ALL, locale.c_str()); + std::locale l( locale.c_str() ); + std::cin.imbue(l); + return true; + } + catch(...) + { + MITK_TEST_OUTPUT(<< "Could not activate locale " << locale); + return false; + } + +} + +void mitkDICOMLocaleTestWithReferenceImage(std::string filename) +{ + mitk::Image::Pointer image; + mitk::DataNodeFactory::Pointer factory = mitk::DataNodeFactory::New(); + factory->SetFileName( filename ); + factory->Update(); + MITK_TEST_CONDITION_REQUIRED(factory->GetNumberOfOutputs() > 0, "file " << filename << " loaded"); + + mitk::DataNode::Pointer node = factory->GetOutput( 0 ); + image = dynamic_cast(node->GetData()); + if(image.IsNull()) + { + MITK_TEST_FAILED_MSG(<< "File "<< filename << " is not an image - test will not be applied." ); + + return; + } + + // note importance of minor differences in spacings: + // DICOM has order y-spacing, x-spacing, while in MITK we assume x-spacing, y-spacing (both meant for 0 and 1 index in array) + MITK_TEST_CONDITION_REQUIRED(mitk::Equal(image->GetGeometry()->GetSpacing()[0], 0.3141592), "correct x spacing? found " + << image->GetGeometry()->GetSpacing()[0]); + MITK_TEST_CONDITION_REQUIRED(mitk::Equal(image->GetGeometry()->GetSpacing()[1], 0.3411592), "correct y spacing? found " + << image->GetGeometry()->GetSpacing()[1]); +} + +int mitkDICOMLocaleTest(int argc, char* argv[]) +{ + MITK_TEST_BEGIN("DICOMLocaleTest"); + + MITK_TEST_CONDITION_REQUIRED(argc >= 2, "File to load has been specified on commandline"); + + MITK_TEST_OUTPUT(<< "Configuration: \n" << mitk::DicomSeriesReader::GetConfigurationString() ); + + std::string filename = argv[1]; + + // load a reference DICOM file with the "C" locale being set + mitkDICOMLocaleTestChangeLocale("C"); + mitkDICOMLocaleTestWithReferenceImage(filename); + // load a reference DICOM file with German locales being set + typedef std::list StringList; + StringList alllocales; + alllocales.push_back("de_DE"); + alllocales.push_back("de_DE.utf8"); + alllocales.push_back("de_DE.UTF8"); + alllocales.push_back("de_DE@euro"); + alllocales.push_back("German_Germany"); + + // supressing this test to be run on MacOS X + // See bug #3894 +#if defined (__APPLE__) || defined(MACOSX) + alllocales.push_back("C"); +#endif + + unsigned int numberOfTestedGermanLocales(0); + + for (StringList::iterator iter = alllocales.begin(); + iter != alllocales.end(); + ++iter) + { + if ( mitkDICOMLocaleTestChangeLocale(*iter) ) + { + ++numberOfTestedGermanLocales; + mitkDICOMLocaleTestWithReferenceImage(filename); + } + } + + if(numberOfTestedGermanLocales == 0) + { + MITK_TEST_OUTPUT(<< "Warning: No German locale was found on the system."); + } + //MITK_TEST_CONDITION_REQUIRED( numberOfTestedGermanLocales > 0, "Verify that at least one German locale has been tested."); + + MITK_TEST_END(); +} + diff --git a/Core/Code/Testing/DICOMTesting/Testing/mitkDICOMPreloadedVolumeTest.cpp b/Modules/DICOMTesting/Testing/mitkDICOMPreloadedVolumeTest.cpp similarity index 89% rename from Core/Code/Testing/DICOMTesting/Testing/mitkDICOMPreloadedVolumeTest.cpp rename to Modules/DICOMTesting/Testing/mitkDICOMPreloadedVolumeTest.cpp index c1864b1330..648ee2833d 100644 --- a/Core/Code/Testing/DICOMTesting/Testing/mitkDICOMPreloadedVolumeTest.cpp +++ b/Modules/DICOMTesting/Testing/mitkDICOMPreloadedVolumeTest.cpp @@ -1,105 +1,105 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #include "mitkTestDICOMLoading.h" #include "mitkTestingMacros.h" bool CheckAllPropertiesAreInOtherList(const mitk::PropertyList* list, const mitk::PropertyList* otherList) { MITK_TEST_CONDITION_REQUIRED(list && otherList, "Comparison is passed two non-empty property lists") const mitk::PropertyList::PropertyMap* listM = list->GetMap(); const mitk::PropertyList::PropertyMap* otherListM = otherList->GetMap(); bool equal = true; for ( mitk::PropertyList::PropertyMap::const_iterator iter = listM->begin(); iter != listM->end(); ++iter ) { std::string key = iter->first; mitk::BaseProperty* property = iter->second; mitk::PropertyList::PropertyMap::const_iterator otherEntry = otherListM->find( key ); MITK_TEST_CONDITION( otherEntry != otherListM->end(), " Property '" << key << "' is contained in other list" ) mitk::BaseProperty* otherProperty = otherEntry->second; MITK_TEST_CONDITION( equal &= (*property == *otherProperty), " Property '" << key << "' is equal in both list" ) } return equal; } bool VerifyPropertyListsEquality(const mitk::PropertyList* testList, const mitk::PropertyList* referenceList) { bool allTestPropsInReference = CheckAllPropertiesAreInOtherList(testList, referenceList); MITK_TEST_CONDITION(allTestPropsInReference, "All test properties found in reference properties") bool allReferencePropsInTest = CheckAllPropertiesAreInOtherList(referenceList, testList); MITK_TEST_CONDITION(allReferencePropsInTest, "All reference properties found in test properties") return allTestPropsInReference && allReferencePropsInTest; } int mitkDICOMPreloadedVolumeTest(int argc, char** const argv) { MITK_TEST_BEGIN("DICOMPreloadedVolume") mitk::TestDICOMLoading loader; - mitk::TestDICOMLoading::StringContainer files; - - // adapt expectations depending on configuration - std::string configuration = mitk::DicomSeriesReader::GetConfigurationString(); - MITK_TEST_OUTPUT(<< "Configuration: " << configuration) + mitk::StringList files; // load files from commandline for (int arg = 1; arg < argc; ++arg) { MITK_TEST_OUTPUT(<< "Test file " << argv[arg]) files.push_back( argv[arg] ); } // verify all files are DICOM - for (mitk::TestDICOMLoading::StringContainer::const_iterator fileIter = files.begin(); + for (mitk::StringList::const_iterator fileIter = files.begin(); fileIter != files.end(); ++fileIter) { - MITK_TEST_CONDITION_REQUIRED( mitk::DicomSeriesReader::IsDicom(*fileIter) , *fileIter << " is recognized as loadable DICOM object" ) - + MITK_TEST_CONDITION_REQUIRED( mitk::DICOMFileReader::IsDICOM(*fileIter) , *fileIter << " is recognized as loadable DICOM object" ) } // load for a first time mitk::TestDICOMLoading::ImageList images = loader.LoadFiles(files); MITK_TEST_OUTPUT(<< "Loaded " << images.size() << " images. Remembering properties of the first one for later comparison.") mitk::Image::Pointer firstImage = images.front(); mitk::PropertyList::Pointer originalProperties = firstImage->GetPropertyList(); // save the original firstImage->SetPropertyList( mitk::PropertyList::New() ); // clear image properties // what about DEFAULT properties? currently ONLY nodes get default properties // load for a second time, this time provide the image volume as a pointer // expectation is that the reader will provide the same properties to this image (without actually loading a new mitk::Image) + + /* TODO mitk::TestDICOMLoading::ImageList reloadedImages = loader.LoadFiles(files, firstImage); MITK_TEST_OUTPUT(<< "Again loaded " << reloadedImages.size() << " images. Comparing to previously loaded version.") mitk::Image::Pointer reloadedImage = reloadedImages.front(); mitk::PropertyList::Pointer regeneratedProperties = reloadedImage->GetPropertyList(); // get the version of the second load attempt bool listsAreEqual = VerifyPropertyListsEquality(regeneratedProperties, originalProperties); MITK_TEST_CONDITION(listsAreEqual, "LoadDicomSeries generates a valid property list when provided a pre-loaded image"); + */ + + MITK_TEST_CONDITION(false, "pre-loaded image volumes not yet implemented"); MITK_TEST_END() } diff --git a/Core/Code/Testing/DICOMTesting/Testing/mitkDICOMTestingSanityTest.cpp b/Modules/DICOMTesting/Testing/mitkDICOMTestingSanityTest.cpp similarity index 76% rename from Core/Code/Testing/DICOMTesting/Testing/mitkDICOMTestingSanityTest.cpp rename to Modules/DICOMTesting/Testing/mitkDICOMTestingSanityTest.cpp index a7bd359b70..5391aba6be 100644 --- a/Core/Code/Testing/DICOMTesting/Testing/mitkDICOMTestingSanityTest.cpp +++ b/Modules/DICOMTesting/Testing/mitkDICOMTestingSanityTest.cpp @@ -1,69 +1,61 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #include "mitkTestDICOMLoading.h" + #include "mitkTestingMacros.h" int mitkDICOMTestingSanityTest(int argc, char** const argv) { MITK_TEST_BEGIN("DICOMTestingSanity") mitk::TestDICOMLoading loader; - mitk::TestDICOMLoading::StringContainer files; - - // adapt expectations depending on configuration - std::string configuration = mitk::DicomSeriesReader::GetConfigurationString(); - MITK_TEST_OUTPUT(<< "Configuration: " << configuration) - /* - MITK_TEST_CONDITION_REQUIRED( configuration.find( "GDCM_VERSION: 2." ) != std::string::npos, - "Expect at least GDCM version 2" ) - */ + mitk::StringList files; // load files from commandline unsigned int numberOfExpectedImages = 0; if (argc > 1) numberOfExpectedImages = atoi(argv[1]); for (int arg = 2; arg < argc; ++arg) files.push_back( argv[arg] ); // verify all files are DICOM - for (mitk::TestDICOMLoading::StringContainer::const_iterator fileIter = files.begin(); + for (mitk::StringList::const_iterator fileIter = files.begin(); fileIter != files.end(); ++fileIter) { - MITK_TEST_CONDITION_REQUIRED( mitk::DicomSeriesReader::IsDicom(*fileIter) , *fileIter << " is recognized as loadable DICOM object" ) - + MITK_TEST_CONDITION_REQUIRED( mitk::DICOMFileReader::IsDICOM(*fileIter) , *fileIter << " is recognized as loadable DICOM object" ) } // compare with expected number of images from commandline mitk::TestDICOMLoading::ImageList images = loader.LoadFiles(files); MITK_TEST_CONDITION_REQUIRED( images.size() == numberOfExpectedImages, "Loading " << files.size() << " files from commandline results in " << numberOfExpectedImages << " images (see test invocation)" ) // check dump equality (dumping image information must always equal itself) for ( mitk::TestDICOMLoading::ImageList::const_iterator imageIter = images.begin(); imageIter != images.end(); ++imageIter ) { const mitk::Image* image = *imageIter; MITK_TEST_CONDITION( loader.CompareImageInformationDumps( loader.DumpImageInformation(image), loader.DumpImageInformation(image) ) == true, "Image information dumping is able to reproduce its result." ) } MITK_TEST_END() } diff --git a/Core/Code/Testing/DICOMTesting/VerifyDICOMMitkImageDump.cpp b/Modules/DICOMTesting/VerifyDICOMMitkImageDump.cpp similarity index 98% rename from Core/Code/Testing/DICOMTesting/VerifyDICOMMitkImageDump.cpp rename to Modules/DICOMTesting/VerifyDICOMMitkImageDump.cpp index 7772e8e460..0aabbbbfef 100644 --- a/Core/Code/Testing/DICOMTesting/VerifyDICOMMitkImageDump.cpp +++ b/Modules/DICOMTesting/VerifyDICOMMitkImageDump.cpp @@ -1,122 +1,122 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #include "mitkTestDICOMLoading.h" std::vector LoadDumps(const std::string& fileName) { std::vector separatedDumps; std::ifstream fileStream( fileName.c_str() ); std::string buffer; std::string line; while(fileStream){ std::getline(fileStream, line); if (line.find("-- Image ") == 0) { // separator: starts a new image block if ( !buffer.empty() ) { // unless this is the first block separatedDumps.push_back(buffer); buffer.clear(); } } else { buffer += line + "\n"; } } fileStream.close(); // eat last image dump if ( !buffer.empty() ) { separatedDumps.push_back(buffer); buffer.clear(); } return separatedDumps; } int main(int argc, char** argv) { /** Loads a list of DICOM images, compares generated mitk::Images against stored references. first argument: file with reference dumps following arguments: file names to load */ if (argc < 2) { MITK_ERROR << "Usage:"; MITK_ERROR << " " << argv[0] << " reference.dump file1 [file2 .. fileN]"; MITK_ERROR << " "; MITK_ERROR << " Loads all DICOM images in file1 to fileN as MITK images "; MITK_ERROR << " and compares loaded images against stored expectations (dumps)."; MITK_ERROR << " See also DumpDICOMMitkImage (generates dumps)"; return EXIT_FAILURE; } mitk::TestDICOMLoading loader; - mitk::TestDICOMLoading::StringContainer files; + mitk::StringList files; // TODO load reference dumps // load from argv[1] // separate at "-- Image n" // store in an array of dumps std::vector expectedDumps = LoadDumps(argv[1]); for (int arg = 2; arg < argc; ++arg) files.push_back( argv[arg] ); mitk::TestDICOMLoading::ImageList images = loader.LoadFiles(files); unsigned int imageCounter(0); for ( mitk::TestDICOMLoading::ImageList::const_iterator imageIter = images.begin(); imageIter != images.end(); ++imageIter, ++imageCounter ) { std::string imageDump = loader.DumpImageInformation( *imageIter ); if (imageCounter >= expectedDumps.size()) { MITK_ERROR << "Loader produces more images than expected. Aborting after image " << (imageCounter-1); MITK_INFO << "Image " << imageCounter << " loaded as:\n" << imageDump; return EXIT_FAILURE; } bool loadedAsExpected = loader.CompareImageInformationDumps( expectedDumps[imageCounter], imageDump ); if (loadedAsExpected) { MITK_INFO << "Image " << imageCounter << " loads as expected."; } else { MITK_ERROR << "Image " << imageCounter << " did not load as expected."; MITK_INFO << "Expected: \n" << expectedDumps[imageCounter] << "\nGot:\n" << imageDump; return EXIT_FAILURE; } } return EXIT_SUCCESS; } diff --git a/Core/Code/Testing/DICOMTesting/files.cmake b/Modules/DICOMTesting/files.cmake similarity index 100% rename from Core/Code/Testing/DICOMTesting/files.cmake rename to Modules/DICOMTesting/files.cmake diff --git a/Core/Code/Testing/DICOMTesting/mitkTestDICOMLoading.cpp b/Modules/DICOMTesting/mitkTestDICOMLoading.cpp similarity index 92% rename from Core/Code/Testing/DICOMTesting/mitkTestDICOMLoading.cpp rename to Modules/DICOMTesting/mitkTestDICOMLoading.cpp index df595732e0..e2b4f4a97b 100644 --- a/Core/Code/Testing/DICOMTesting/mitkTestDICOMLoading.cpp +++ b/Modules/DICOMTesting/mitkTestDICOMLoading.cpp @@ -1,449 +1,443 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ //#define MBILOG_ENABLE_DEBUG #include #include "mitkTestDICOMLoading.h" #include mitk::TestDICOMLoading::TestDICOMLoading() :m_PreviousCLocale(NULL) { } void mitk::TestDICOMLoading::SetDefaultLocale() { // remember old locale only once if (m_PreviousCLocale == NULL) { m_PreviousCLocale = setlocale(LC_NUMERIC, NULL); // set to "C" setlocale(LC_NUMERIC, "C"); m_PreviousCppLocale = std::cin.getloc(); std::locale l( "C" ); std::cin.imbue(l); std::cout.imbue(l); } } void mitk::TestDICOMLoading::ResetUserLocale() { if (m_PreviousCLocale) { setlocale(LC_NUMERIC, m_PreviousCLocale); std::cin.imbue(m_PreviousCppLocale); std::cout.imbue(m_PreviousCppLocale); m_PreviousCLocale = NULL; } } -mitk::TestDICOMLoading::ImageList mitk::TestDICOMLoading::LoadFiles( const StringContainer& files, Image::Pointer preLoadedVolume ) +mitk::TestDICOMLoading::ImageList +mitk::TestDICOMLoading +::LoadFiles( const StringList& files ) { - for (StringContainer::const_iterator iter = files.begin(); + for (StringList::const_iterator iter = files.begin(); iter != files.end(); ++iter) { MITK_DEBUG << "File " << *iter; } ImageList result; - DicomSeriesReader::FileNamesGrouping seriesInFiles = DicomSeriesReader::GetSeries( files, true ); + ClassicDICOMSeriesReader::Pointer reader = ClassicDICOMSeriesReader::New(); + reader->SetFixTiltByShearing(true); + reader->SetInputFiles( files ); + reader->AnalyzeInputFiles(); + reader->PrintOutputs(std::cout,true); + reader->LoadImages(); - // TODO sort series UIDs, implementation of map iterator might differ on different platforms (or verify this is a standard topic??) - for (DicomSeriesReader::FileNamesGrouping::const_iterator seriesIter = seriesInFiles.begin(); - seriesIter != seriesInFiles.end(); - ++seriesIter) + unsigned int numberOfImages = reader->GetNumberOfOutputs(); + for (unsigned imageIndex = 0; imageIndex < numberOfImages; ++imageIndex) { - StringContainer files = seriesIter->second.GetFilenames(); - - DataNode::Pointer node = DicomSeriesReader::LoadDicomSeries( files, true, true, true, 0, preLoadedVolume ); // true, true, true ist just a copy of the default values - - if (node.IsNotNull()) - { - Image::Pointer image = dynamic_cast( node->GetData() ); - - result.push_back( image ); - } - else - { - } + const DICOMImageBlockDescriptor& block = reader->GetOutput(imageIndex); + result.push_back( block.GetMitkImage() ); } return result; } std::string mitk::TestDICOMLoading::ComponentTypeToString(int type) { if (type == itk::ImageIOBase::UCHAR) return "UCHAR"; else if (type == itk::ImageIOBase::CHAR) return "CHAR"; else if (type == itk::ImageIOBase::USHORT) return "USHORT"; else if (type == itk::ImageIOBase::SHORT) return "SHORT"; else if (type == itk::ImageIOBase::UINT) return "UINT"; else if (type == itk::ImageIOBase::INT) return "INT"; else if (type == itk::ImageIOBase::ULONG) return "ULONG"; else if (type == itk::ImageIOBase::LONG) return "LONG"; else if (type == itk::ImageIOBase::FLOAT) return "FLOAT"; else if (type == itk::ImageIOBase::DOUBLE) return "DOUBLE"; else return "UNKNOWN"; } // add a line to stringstream result (see DumpImageInformation #define DumpLine(field, data) DumpILine(0, field, data) // add an indented(!) line to stringstream result (see DumpImageInformation #define DumpILine(indent, field, data) \ { \ std::string DumpLine_INDENT; DumpLine_INDENT.resize(indent, ' ' ); \ result << DumpLine_INDENT << field << ": " << data << "\n"; \ } std::string mitk::TestDICOMLoading::DumpImageInformation( const Image* image ) { std::stringstream result; if (image == NULL) return result.str(); SetDefaultLocale(); // basic image data DumpLine( "Pixeltype", ComponentTypeToString(image->GetPixelType().GetComponentType()) ); DumpLine( "BitsPerPixel", image->GetPixelType().GetBpe() ); DumpLine( "Dimension", image->GetDimension() ); result << "Dimensions: "; for (unsigned int dim = 0; dim < image->GetDimension(); ++dim) result << image->GetDimension(dim) << " "; result << "\n"; // geometry data result << "Geometry: \n"; Geometry3D* geometry = image->GetGeometry(); if (geometry) { AffineTransform3D* transform = geometry->GetIndexToWorldTransform(); if (transform) { result << " " << "Matrix: "; const AffineTransform3D::MatrixType& matrix = transform->GetMatrix(); for (unsigned int i = 0; i < 3; ++i) for (unsigned int j = 0; j < 3; ++j) result << matrix[i][j] << " "; result << "\n"; result << " " << "Offset: "; const AffineTransform3D::OutputVectorType& offset = transform->GetOffset(); for (unsigned int i = 0; i < 3; ++i) result << offset[i] << " "; result << "\n"; result << " " << "Center: "; const AffineTransform3D::InputPointType& center = transform->GetCenter(); for (unsigned int i = 0; i < 3; ++i) result << center[i] << " "; result << "\n"; result << " " << "Translation: "; const AffineTransform3D::OutputVectorType& translation = transform->GetTranslation(); for (unsigned int i = 0; i < 3; ++i) result << translation[i] << " "; result << "\n"; result << " " << "Scale: "; const double* scale = transform->GetScale(); for (unsigned int i = 0; i < 3; ++i) result << scale[i] << " "; result << "\n"; result << " " << "Origin: "; const Point3D& origin = geometry->GetOrigin(); for (unsigned int i = 0; i < 3; ++i) result << origin[i] << " "; result << "\n"; result << " " << "Spacing: "; const Vector3D& spacing = geometry->GetSpacing(); for (unsigned int i = 0; i < 3; ++i) result << spacing[i] << " "; result << "\n"; result << " " << "TimeBounds: "; const TimeBounds timeBounds = geometry->GetTimeBounds(); for (unsigned int i = 0; i < 2; ++i) result << timeBounds[i] << " "; result << "\n"; } } ResetUserLocale(); return result.str(); } std::string mitk::TestDICOMLoading::trim(const std::string& pString, const std::string& pWhitespace) { const size_t beginStr = pString.find_first_not_of(pWhitespace); if (beginStr == std::string::npos) { // no content return ""; } const size_t endStr = pString.find_last_not_of(pWhitespace); const size_t range = endStr - beginStr + 1; return pString.substr(beginStr, range); } std::string mitk::TestDICOMLoading::reduce(const std::string& pString, const std::string& pFill, const std::string& pWhitespace) { // trim first std::string result(trim(pString, pWhitespace)); // replace sub ranges size_t beginSpace = result.find_first_of(pWhitespace); while (beginSpace != std::string::npos) { const size_t endSpace = result.find_first_not_of(pWhitespace, beginSpace); const size_t range = endSpace - beginSpace; result.replace(beginSpace, range, pFill); const size_t newStart = beginSpace + pFill.length(); beginSpace = result.find_first_of(pWhitespace, newStart); } return result; } bool mitk::TestDICOMLoading::CompareSpacedValueFields( const std::string& reference, const std::string& test, double /*eps*/ ) { bool result(true); // tokenize string, compare each token, if possible by float comparison std::stringstream referenceStream(reduce(reference)); std::stringstream testStream(reduce(test)); std::string refToken; std::string testToken; while ( std::getline( referenceStream, refToken, ' ' ) && std::getline ( testStream, testToken, ' ' ) ) { float refNumber; float testNumber; if ( this->StringToNumber(refToken, refNumber) ) { if ( this->StringToNumber(testToken, testNumber) ) { // print-out compared tokens if DEBUG output allowed MITK_DEBUG << "Reference Token '" << refToken << "'" << " value " << refNumber << ", test Token '" << testToken << "'" << " value " << testNumber; bool old_result = result; result &= ( fabs(refNumber - testNumber) < 0.0001 /*mitk::eps*/ ); // log the token/number which causes the test to fail if( old_result != result) { MITK_ERROR << std::setprecision(16) << "Reference Token '" << refToken << "'" << " value " << refNumber << ", test Token '" << testToken << "'" << " value " << testNumber; MITK_ERROR << "[FALSE] - difference: " << std::setprecision(16) << fabs(refNumber - testNumber) << " EPS: " << 0.0001;// mitk::eps; } } else { MITK_ERROR << refNumber << " cannot be compared to '" << testToken << "'"; } } else { MITK_DEBUG << "Token '" << refToken << "'" << " handled as string"; result &= refToken == testToken; } } if ( std::getline( referenceStream, refToken, ' ' ) ) { MITK_ERROR << "Reference string still had values when test string was already parsed: ref '" << reference << "', test '" << test << "'"; result = false; } else if ( std::getline( testStream, testToken, ' ' ) ) { MITK_ERROR << "Test string still had values when reference string was already parsed: ref '" << reference << "', test '" << test << "'"; result = false; } return result; } bool mitk::TestDICOMLoading::CompareImageInformationDumps( const std::string& referenceDump, const std::string& testDump ) { KeyValueMap reference = ParseDump(referenceDump); KeyValueMap test = ParseDump(testDump); bool testResult(true); // verify all expected values for (KeyValueMap::const_iterator refIter = reference.begin(); refIter != reference.end(); ++refIter) { const std::string& refKey = refIter->first; const std::string& refValue = refIter->second; if ( test.find(refKey) != test.end() ) { const std::string& testValue = test[refKey]; bool thisTestResult = CompareSpacedValueFields( refValue, testValue ); testResult &= thisTestResult; MITK_DEBUG << refKey << ": '" << refValue << "' == '" << testValue << "' ? " << (thisTestResult?"YES":"NO"); } else { MITK_ERROR << "Reference dump contains a key'" << refKey << "' (value '" << refValue << "')." ; MITK_ERROR << "This key is expected to be generated for tests (but was not). Most probably you need to update your test data."; return false; } } // now check test dump does not contain any additional keys for (KeyValueMap::const_iterator testIter = test.begin(); testIter != test.end(); ++testIter) { const std::string& key = testIter->first; const std::string& value = testIter->second; if ( reference.find(key) == reference.end() ) { MITK_ERROR << "Test dump contains an unexpected key'" << key << "' (value '" << value << "')." ; MITK_ERROR << "This key is not expected. Most probably you need to update your test data."; return false; } } return testResult; } mitk::TestDICOMLoading::KeyValueMap mitk::TestDICOMLoading::ParseDump( const std::string& dump ) { KeyValueMap parsedResult; std::string shredder(dump); std::stack surroundingKeys; std::stack expectedIndents; expectedIndents.push(0); while (true) { std::string::size_type newLinePos = shredder.find( '\n' ); if (newLinePos == std::string::npos || newLinePos == 0) break; std::string line = shredder.substr( 0, newLinePos ); shredder = shredder.erase( 0, newLinePos+1 ); std::string::size_type keyPosition = line.find_first_not_of( ' ' ); std::string::size_type colonPosition = line.find( ':' ); std::string key = line.substr(keyPosition, colonPosition - keyPosition); std::string::size_type firstSpacePosition = key.find_first_of(" "); if (firstSpacePosition != std::string::npos) { key.erase(firstSpacePosition); } if ( keyPosition > expectedIndents.top() ) { // more indent than before expectedIndents.push(keyPosition); } else if (keyPosition == expectedIndents.top() ) { if (!surroundingKeys.empty()) { surroundingKeys.pop(); // last of same length } } else { // less indent than before do expectedIndents.pop(); while (expectedIndents.top() != keyPosition); // unwind until current indent is found } if (!surroundingKeys.empty()) { key = surroundingKeys.top() + "." + key; // construct current key name } surroundingKeys.push(key); // this is the new embracing key std::string value = line.substr(colonPosition+1); MITK_DEBUG << " Key: '" << key << "' value '" << value << "'" ; parsedResult[key] = value; // store parsing result } return parsedResult; } diff --git a/Core/Code/Testing/DICOMTesting/mitkTestDICOMLoading.h b/Modules/DICOMTesting/mitkTestDICOMLoading.h similarity index 91% rename from Core/Code/Testing/DICOMTesting/mitkTestDICOMLoading.h rename to Modules/DICOMTesting/mitkTestDICOMLoading.h index 335da80f47..5fd1f95547 100644 --- a/Core/Code/Testing/DICOMTesting/mitkTestDICOMLoading.h +++ b/Modules/DICOMTesting/mitkTestDICOMLoading.h @@ -1,107 +1,105 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #ifndef mitkTestDICOMLoading_h #define mitkTestDICOMLoading_h -#include "mitkDicomSeriesReader.h" +#include "mitkClassicDICOMSeriesReader.h" #include "mitkDICOMTestingExports.h" namespace mitk { class mitkDICOMTesting_EXPORT TestDICOMLoading { public: - typedef DicomSeriesReader::StringContainer StringContainer; - typedef std::list NodeList; typedef std::list ImageList; TestDICOMLoading(); ImageList - LoadFiles( const StringContainer& files, Image::Pointer preLoadedVolume = NULL ); + LoadFiles( const StringList & files ); /** \brief Dump relevant image information for later comparison. \sa CompareImageInformationDumps */ std::string DumpImageInformation( const Image* image ); /** \brief Compare two image information dumps. \return true, if dumps are sufficiently equal (see parameters) \sa DumpImageInformation */ bool CompareImageInformationDumps( const std::string& reference, const std::string& test ); private: typedef std::map KeyValueMap; void SetDefaultLocale(); void ResetUserLocale(); std::string ComponentTypeToString( int type ); KeyValueMap ParseDump( const std::string& dump ); bool CompareSpacedValueFields( const std::string& reference, const std::string& test, double eps = mitk::eps ); /** Compress whitespace in string \param pString input string \param pFill replacement whitespace (only whitespace in string after reduction) \param pWhitespace characters handled as whitespace */ std::string reduce(const std::string& pString, const std::string& pFill = " ", const std::string& pWhitespace = " \t"); /** Remove leading and trailing whitespace \param pString input string \param pWhitespace characters handled as whitespace */ std::string trim(const std::string& pString, const std::string& pWhitespace = " \t"); template bool StringToNumber(const std::string& s, T& value) { std::stringstream stream(s); stream >> value; return (!stream.fail()) && (fabs(value) < std::numeric_limits::max()); } const char* m_PreviousCLocale; std::locale m_PreviousCppLocale; }; } #endif diff --git a/Modules/ImageStatistics/Testing/CMakeLists.txt b/Modules/ImageStatistics/Testing/CMakeLists.txt index 153cd81e2e..f0a81a7d26 100644 --- a/Modules/ImageStatistics/Testing/CMakeLists.txt +++ b/Modules/ImageStatistics/Testing/CMakeLists.txt @@ -1 +1 @@ -MITK_CREATE_MODULE_TESTS() +MITK_CREATE_MODULE_TESTS(EXTRA_DEPENDS DICOMReader) diff --git a/Modules/ImageStatistics/Testing/mitkImageStatisticsCalculatorTest.cpp b/Modules/ImageStatistics/Testing/mitkImageStatisticsCalculatorTest.cpp index 0e3c62345e..e7c3415a6d 100644 --- a/Modules/ImageStatistics/Testing/mitkImageStatisticsCalculatorTest.cpp +++ b/Modules/ImageStatistics/Testing/mitkImageStatisticsCalculatorTest.cpp @@ -1,496 +1,499 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #include "mitkStandardFileLocations.h" #include "mitkDicomSeriesReader.h" #include "mitkTestingMacros.h" #include "mitkImageStatisticsCalculator.h" #include "mitkPlanarPolygon.h" -#include "mitkDicomSeriesReader.h" -#include +#include "mitkClassicDICOMSeriesReader.h" #include "vtkStreamingDemandDrivenPipeline.h" #include #include //#include /** * \brief Test class for mitkImageStatisticsCalculator * * This test covers: * - instantiation of an ImageStatisticsCalculator class * - correctness of statistics when using PlanarFigures for masking */ class mitkImageStatisticsCalculatorTestClass { public: struct testCase { int id; mitk::PlanarFigure::Pointer figure; double mean; double sd; }; // calculate statistics for the given image and planarpolygon static const mitk::ImageStatisticsCalculator::Statistics TestStatistics( mitk::Image::Pointer image, mitk::PlanarFigure::Pointer polygon ) { mitk::ImageStatisticsCalculator::Pointer statisticsCalculator = mitk::ImageStatisticsCalculator::New(); statisticsCalculator->SetImage( image ); statisticsCalculator->SetMaskingModeToPlanarFigure(); statisticsCalculator->SetPlanarFigure( polygon ); statisticsCalculator->ComputeStatistics(); return statisticsCalculator->GetStatistics(); } // returns a vector of defined test-cases static std::vector InitializeTestCases( mitk::Geometry2D::Pointer geom ) { std::vector testCases; { /***************************** * one whole white pixel * -> mean of 255 expected ******************************/ mitk::PlanarPolygon::Pointer figure1 = mitk::PlanarPolygon::New(); figure1->SetGeometry2D( geom ); mitk::Point2D pnt1; pnt1[0] = 10.5 ; pnt1[1] = 3.5; figure1->PlaceFigure( pnt1 ); mitk::Point2D pnt2; pnt2[0] = 9.5; pnt2[1] = 3.5; figure1->SetControlPoint( 1, pnt2, true ); mitk::Point2D pnt3; pnt3[0] = 9.5; pnt3[1] = 4.5; figure1->SetControlPoint( 2, pnt3, true ); mitk::Point2D pnt4; pnt4[0] = 10.5; pnt4[1] = 4.5; figure1->SetControlPoint( 3, pnt4, true ); figure1->GetPolyLine(0); testCase test; test.id = testCases.size(); test.figure = figure1; test.mean = 255.0; test.sd = 0.0; testCases.push_back( test ); } { /***************************** * half pixel in x-direction (white) * -> mean of 255 expected ******************************/ mitk::PlanarPolygon::Pointer figure1 = mitk::PlanarPolygon::New(); figure1->SetGeometry2D( geom ); mitk::Point2D pnt1; pnt1[0] = 10.0 ; pnt1[1] = 3.5; figure1->PlaceFigure( pnt1 ); mitk::Point2D pnt2; pnt2[0] = 9.5; pnt2[1] = 3.5; figure1->SetControlPoint( 1, pnt2, true ); mitk::Point2D pnt3; pnt3[0] = 9.5; pnt3[1] = 4.5; figure1->SetControlPoint( 2, pnt3, true ); mitk::Point2D pnt4; pnt4[0] = 10.0; pnt4[1] = 4.5; figure1->SetControlPoint( 3, pnt4, true ); figure1->GetPolyLine(0); testCase test; test.id = testCases.size(); test.figure = figure1; test.mean = 255.0; test.sd = 0.0; testCases.push_back( test ); } { /***************************** * half pixel in diagonal-direction (white) * -> mean of 255 expected ******************************/ mitk::PlanarPolygon::Pointer figure1 = mitk::PlanarPolygon::New(); figure1->SetGeometry2D( geom ); mitk::Point2D pnt1; pnt1[0] = 10.5 ; pnt1[1] = 3.5; figure1->PlaceFigure( pnt1 ); mitk::Point2D pnt2; pnt2[0] = 9.5; pnt2[1] = 3.5; figure1->SetControlPoint( 1, pnt2, true ); mitk::Point2D pnt3; pnt3[0] = 9.5; pnt3[1] = 4.5; figure1->SetControlPoint( 2, pnt3, true ); figure1->GetPolyLine(0); testCase test; test.id = testCases.size(); test.figure = figure1; test.mean = 255.0; test.sd = 0.0; testCases.push_back( test ); } { /***************************** * one pixel (white) + 2 half pixels (white) + 1 half pixel (black) * -> mean of 191.25 expected ******************************/ mitk::PlanarPolygon::Pointer figure1 = mitk::PlanarPolygon::New(); figure1->SetGeometry2D( geom ); mitk::Point2D pnt1; pnt1[0] = 1.1; pnt1[1] = 1.1; figure1->PlaceFigure( pnt1 ); mitk::Point2D pnt2; pnt2[0] = 2.0; pnt2[1] = 2.0; figure1->SetControlPoint( 1, pnt2, true ); mitk::Point2D pnt3; pnt3[0] = 3.0; pnt3[1] = 1.0; figure1->SetControlPoint( 2, pnt3, true ); mitk::Point2D pnt4; pnt4[0] = 2.0; pnt4[1] = 0.0; figure1->SetControlPoint( 3, pnt4, true ); figure1->GetPolyLine(0); testCase test; test.id = testCases.size(); test.figure = figure1; test.mean = 191.25; test.sd = 127.5; testCases.push_back( test ); } { /***************************** * whole pixel (white) + half pixel (gray) in x-direction * -> mean of 191.5 expected ******************************/ mitk::PlanarPolygon::Pointer figure1 = mitk::PlanarPolygon::New(); figure1->SetGeometry2D( geom ); mitk::Point2D pnt1; pnt1[0] = 11.0; pnt1[1] = 3.5; figure1->PlaceFigure( pnt1 ); mitk::Point2D pnt2; pnt2[0] = 9.5; pnt2[1] = 3.5; figure1->SetControlPoint( 1, pnt2, true ); mitk::Point2D pnt3; pnt3[0] = 9.5; pnt3[1] = 4.5; figure1->SetControlPoint( 2, pnt3, true ); mitk::Point2D pnt4; pnt4[0] = 11.0; pnt4[1] = 4.5; figure1->SetControlPoint( 3, pnt4, true ); figure1->GetPolyLine(0); testCase test; test.id = testCases.size(); test.figure = figure1; test.mean = 191.50; test.sd = 89.80; testCases.push_back( test ); } { /***************************** * quarter pixel (black) + whole pixel (white) + half pixel (gray) in x-direction * -> mean of 191.5 expected ******************************/ mitk::PlanarPolygon::Pointer figure1 = mitk::PlanarPolygon::New(); figure1->SetGeometry2D( geom ); mitk::Point2D pnt1; pnt1[0] = 11.0; pnt1[1] = 3.5; figure1->PlaceFigure( pnt1 ); mitk::Point2D pnt2; pnt2[0] = 9.25; pnt2[1] = 3.5; figure1->SetControlPoint( 1, pnt2, true ); mitk::Point2D pnt3; pnt3[0] = 9.25; pnt3[1] = 4.5; figure1->SetControlPoint( 2, pnt3, true ); mitk::Point2D pnt4; pnt4[0] = 11.0; pnt4[1] = 4.5; figure1->SetControlPoint( 3, pnt4, true ); figure1->GetPolyLine(0); testCase test; test.id = testCases.size(); test.figure = figure1; test.mean = 191.5; test.sd = 89.80; testCases.push_back( test ); } { /***************************** * half pixel (black) + whole pixel (white) + half pixel (gray) in x-direction * -> mean of 127.66 expected ******************************/ mitk::PlanarPolygon::Pointer figure1 = mitk::PlanarPolygon::New(); figure1->SetGeometry2D( geom ); mitk::Point2D pnt1; pnt1[0] = 11.0; pnt1[1] = 3.5; figure1->PlaceFigure( pnt1 ); mitk::Point2D pnt2; pnt2[0] = 9.0; pnt2[1] = 3.5; figure1->SetControlPoint( 1, pnt2, true ); mitk::Point2D pnt3; pnt3[0] = 9.0; pnt3[1] = 4.0; figure1->SetControlPoint( 2, pnt3, true ); mitk::Point2D pnt4; pnt4[0] = 11.0; pnt4[1] = 4.0; figure1->SetControlPoint( 3, pnt4, true ); figure1->GetPolyLine(0); testCase test; test.id = testCases.size(); test.figure = figure1; test.mean = 127.66; test.sd = 127.5; testCases.push_back( test ); } { /***************************** * whole pixel (gray) * -> mean of 128 expected ******************************/ mitk::PlanarPolygon::Pointer figure2 = mitk::PlanarPolygon::New(); figure2->SetGeometry2D( geom ); mitk::Point2D pnt1; pnt1[0] = 11.5; pnt1[1] = 10.5; figure2->PlaceFigure( pnt1 ); mitk::Point2D pnt2; pnt2[0] = 11.5; pnt2[1] = 11.5; figure2->SetControlPoint( 1, pnt2, true ); mitk::Point2D pnt3; pnt3[0] = 12.5; pnt3[1] = 11.5; figure2->SetControlPoint( 2, pnt3, true ); mitk::Point2D pnt4; pnt4[0] = 12.5; pnt4[1] = 10.5; figure2->SetControlPoint( 3, pnt4, true ); figure2->GetPolyLine(0); testCase test; test.id = testCases.size(); test.figure = figure2; test.mean = 128.0; test.sd = 0.0; testCases.push_back( test ); } { /***************************** * whole pixel (gray) + half pixel (white) in y-direction * -> mean of 191.5 expected ******************************/ mitk::PlanarPolygon::Pointer figure2 = mitk::PlanarPolygon::New(); figure2->SetGeometry2D( geom ); mitk::Point2D pnt1; pnt1[0] = 11.5; pnt1[1] = 10.5; figure2->PlaceFigure( pnt1 ); mitk::Point2D pnt2; pnt2[0] = 11.5; pnt2[1] = 12.0; figure2->SetControlPoint( 1, pnt2, true ); mitk::Point2D pnt3; pnt3[0] = 12.5; pnt3[1] = 12.0; figure2->SetControlPoint( 2, pnt3, true ); mitk::Point2D pnt4; pnt4[0] = 12.5; pnt4[1] = 10.5; figure2->SetControlPoint( 3, pnt4, true ); figure2->GetPolyLine(0); testCase test; test.id = testCases.size(); test.figure = figure2; test.mean = 191.5; test.sd = 89.80; testCases.push_back( test ); } { /***************************** * 2 whole pixel (white) + 2 whole pixel (black) in y-direction * -> mean of 127.66 expected ******************************/ mitk::PlanarPolygon::Pointer figure2 = mitk::PlanarPolygon::New(); figure2->SetGeometry2D( geom ); mitk::Point2D pnt1; pnt1[0] = 11.5; pnt1[1] = 10.5; figure2->PlaceFigure( pnt1 ); mitk::Point2D pnt2; pnt2[0] = 11.5; pnt2[1] = 13.5; figure2->SetControlPoint( 1, pnt2, true ); mitk::Point2D pnt3; pnt3[0] = 12.5; pnt3[1] = 13.5; figure2->SetControlPoint( 2, pnt3, true ); mitk::Point2D pnt4; pnt4[0] = 12.5; pnt4[1] = 10.5; figure2->SetControlPoint( 3, pnt4, true ); figure2->GetPolyLine(0); testCase test; test.id = testCases.size(); test.figure = figure2; test.mean = 127.66; test.sd = 127.5; testCases.push_back( test ); } { /***************************** * 9 whole pixels (white) + 3 half pixels (white) * + 3 whole pixel (black) [ + 3 slightly less than half pixels (black)] * -> mean of 204.0 expected ******************************/ mitk::PlanarPolygon::Pointer figure2 = mitk::PlanarPolygon::New(); figure2->SetGeometry2D( geom ); mitk::Point2D pnt1; pnt1[0] = 0.5; pnt1[1] = 0.5; figure2->PlaceFigure( pnt1 ); mitk::Point2D pnt2; pnt2[0] = 3.5; pnt2[1] = 3.5; figure2->SetControlPoint( 1, pnt2, true ); mitk::Point2D pnt3; pnt3[0] = 8.4999; pnt3[1] = 3.5; figure2->SetControlPoint( 2, pnt3, true ); mitk::Point2D pnt4; pnt4[0] = 5.4999; pnt4[1] = 0.5; figure2->SetControlPoint( 3, pnt4, true ); figure2->GetPolyLine(0); testCase test; test.id = testCases.size(); test.figure = figure2; test.mean = 204.0; test.sd = 105.58; testCases.push_back( test ); } { /***************************** * half pixel (white) + whole pixel (white) + half pixel (black) * -> mean of 212.66 expected ******************************/ mitk::PlanarPolygon::Pointer figure2 = mitk::PlanarPolygon::New(); figure2->SetGeometry2D( geom ); mitk::Point2D pnt1; pnt1[0] = 9.5; pnt1[1] = 0.5; figure2->PlaceFigure( pnt1 ); mitk::Point2D pnt2; pnt2[0] = 9.5; pnt2[1] = 2.5; figure2->SetControlPoint( 1, pnt2, true ); mitk::Point2D pnt3; pnt3[0] = 11.5; pnt3[1] = 2.5; figure2->SetControlPoint( 2, pnt3, true ); figure2->GetPolyLine(0); testCase test; test.id = testCases.size(); test.figure = figure2; test.mean = 212.66; test.sd = 73.32; testCases.push_back( test ); } return testCases; } // loads the test image static mitk::Image::Pointer GetTestImage() { mitk::StandardFileLocations::Pointer locator = mitk::StandardFileLocations::GetInstance(); const std::string filename = locator->FindFile("testimage.dcm", "Modules/MitkExt/Testing/Data"); if (filename.empty()) { MITK_ERROR << "Could not find test file"; return NULL; } else { MITK_INFO << "Found testimage.dcm"; } - itk::FilenamesContainer file; - file.push_back( filename ); - - mitk::DicomSeriesReader* reader = new mitk::DicomSeriesReader; + MITK_TEST_OUTPUT(<< "Loading test image '" << filename << "'") + mitk::StringList files; + files.push_back( filename ); - mitk::DataNode::Pointer node = reader->LoadDicomSeries( file, false, false ); - mitk::Image::Pointer image = dynamic_cast( node->GetData() ); + mitk::ClassicDICOMSeriesReader::Pointer reader = mitk::ClassicDICOMSeriesReader::New(); + reader->SetInputFiles( files ); + reader->AnalyzeInputFiles(); + reader->LoadImages(); + MITK_TEST_CONDITION_REQUIRED( reader->GetNumberOfOutputs() == 1, "Loaded one result from file" ); + mitk::Image::Pointer image = reader->GetOutput(0).GetMitkImage(); + MITK_TEST_CONDITION_REQUIRED( image.IsNotNull(), "Loaded an mitk::Image" ); return image; } }; int TestUnitilizedImage() { /***************************** * loading uninitialized image to datastorage ******************************/ std::cout << "Testing loading uninitialized image to datastorage:"; try{ MITK_TEST_FOR_EXCEPTION_BEGIN(mitk::Exception) mitk::Image::Pointer image = mitk::Image::New(); mitk::DataNode::Pointer node = mitk::DataNode::New(); node->SetData(image); mitk::ImageStatisticsCalculator::Pointer is = mitk::ImageStatisticsCalculator::New(); is->ComputeStatistics(); MITK_TEST_FOR_EXCEPTION_END(mitk::Exception) } catch (const mitk::Exception&) { std::cout << "Success: Loading uninitialized image has thrown an exception." << std::endl; } return 0; } int mitkImageStatisticsCalculatorTest(int, char* []) { // always start with this! MITK_TEST_BEGIN("mitkImageStatisticsCalculatorTest") //QCoreApplication app(argc, argv); - mitk::Image::Pointer image = mitkImageStatisticsCalculatorTestClass::GetTestImage(); + mitk::Image::Pointer image = mitkImageStatisticsCalculatorTestClass::GetTestImage(); MITK_TEST_CONDITION_REQUIRED( image.IsNotNull(), "Loading test image" ); mitk::Geometry2D::Pointer geom = image->GetSlicedGeometry()->GetGeometry2D(0); std::vector allTestCases = mitkImageStatisticsCalculatorTestClass::InitializeTestCases( geom ); for ( std::vector::size_type i=0; i