diff --git a/Core/Code/Testing/CMakeLists.txt b/Core/Code/Testing/CMakeLists.txt index ce4202eef0..cf0f186153 100644 --- a/Core/Code/Testing/CMakeLists.txt +++ b/Core/Code/Testing/CMakeLists.txt @@ -1,162 +1,164 @@ # 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.pic.gz ${MITK_DATA_DIR}/BallBinary30x30x30.pic.gz) 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(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_color2DImage mitkImageTest ${MITK_DATA_DIR}/NrrdWritingTestImage.jpg) mitkAddCustomModuleTest(mitkImageTest_3DImageData mitkImageGeneratorTest ${MITK_DATA_DIR}/Pic3D.nrrd) mitkAddCustomModuleTest(mitkIOUtilTest mitkIOUtilTest #test for a randomly chosen Pic3D swivelled slice ${MITK_DATA_DIR}/Pic3D.nrrd ${MITK_DATA_DIR}/pointSet.mps ${MITK_DATA_DIR}/binary.stl ) mitkAddCustomModuleTest(mitkLevelWindowManagerTest mitkLevelWindowManagerTest ${MITK_DATA_DIR}/Pic3D.nrrd ) if(WIN32 OR APPLE OR MITK_ENABLE_GUI_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_GUI_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 ) #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 # 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 ) #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 #) 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 #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(DCMTesting) diff --git a/Core/Code/Testing/DCMTesting/CMakeLists.txt b/Core/Code/Testing/DCMTesting/CMakeLists.txt new file mode 100644 index 0000000000..5671220e3f --- /dev/null +++ b/Core/Code/Testing/DCMTesting/CMakeLists.txt @@ -0,0 +1,59 @@ +# +# IMPORTANT NOTE: +# +# This module is a testing module for the DicomSeriesReader class, +# which is meant to be replaced by the Module DCMReader. +# To have the "old" code tested before it is removed, we still +# keep this DCMTesting module. +# +# Note that the DCMTesting module is practically a copy of +# this DCMTesting module, doing the same kind of testing, just +# for the new readers instead of DicomSeriesReader. +# + +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 DCMTesting). +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( mitkDCMTesting + DEPENDS Mitk # Mitk.so +) + +MITK_CHECK_MODULE(_RESULT mitkDCMTesting) +if(_RESULT) + message(STATUS "mitkDCMTesting application won't be built. Missing: ${_RESULT}") +else(_RESULT) + +# add helpful applications +include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR}) + +# remove with newer MITK version +MITK_USE_MODULE( mitkDCMTesting ) +include_directories(${ALL_INCLUDE_DIRECTORIES}) + +# dumps out image information +add_executable(DumpDCMMitkImage DumpDCMMitkImage.cpp) +#Newer code: mitk_use_modules(TARGET DumpDCMMitkImage MODULES mitkDCMTesting) +target_link_libraries(DumpDCMMitkImage ${ALL_LIBRARIES}) + +# compares dumped out image information against reference dump +add_executable(VerifyDCMMitkImageDump VerifyDCMMitkImageDump.cpp) +#Newer code: mitk_use_modules(TARGET VerifyDCMMitkImageDump MODULES mitkDCMTesting) +target_link_libraries(VerifyDCMMitkImageDump ${ALL_LIBRARIES}) + +add_subdirectory(Testing) +endif() + +endif() +endif() diff --git a/Core/Code/Testing/DCMTesting/DumpDCMMitkImage.cpp b/Core/Code/Testing/DCMTesting/DumpDCMMitkImage.cpp new file mode 100644 index 0000000000..43730db971 --- /dev/null +++ b/Core/Code/Testing/DCMTesting/DumpDCMMitkImage.cpp @@ -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. + +===================================================================*/ +#include "mitkTestDCMLoading.h" + +int main(int argc, char** argv) +{ + mitk::TestDCMLoading loader; + mitk::TestDCMLoading::StringContainer files; + + for (int arg = 1; arg < argc; ++arg) files.push_back( argv[arg] ); + + mitk::TestDCMLoading::ImageList images = loader.LoadFiles(files); + + // combine individual dumps in a way that VerifyDCMMitkImageDump is able to separate again. + // I.e.: when changing this piece of code, always change VerifyDCMMitkImageDump, too. + unsigned int imageCounter(0); + for ( mitk::TestDCMLoading::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/DCMTesting/Testing/CMakeLists.txt b/Core/Code/Testing/DCMTesting/Testing/CMakeLists.txt new file mode 100644 index 0000000000..651e168041 --- /dev/null +++ b/Core/Code/Testing/DCMTesting/Testing/CMakeLists.txt @@ -0,0 +1,86 @@ + +MITK_CREATE_MODULE_TESTS(LABELS MITK-Core) + + +include(mitkFunctionAddTestLabel) + +# verify minimum expectations: +# files are recognized as DCM +# loading files results in a given number of images +mitkAddCustomModuleTest(mitkDCMTestingSanityTest_NoFiles mitkDCMTestingSanityTest 0) +mitkAddCustomModuleTest(mitkDCMTestingSanityTest_CTImage mitkDCMTestingSanityTest 1 ${MITK_DATA_DIR}/spacing-ok-ct.dcm) +mitkAddCustomModuleTest(mitkDCMTestingSanityTest_MRImage mitkDCMTestingSanityTest 1 ${MITK_DATA_DIR}/spacing-ok-mr.dcm) +mitkAddCustomModuleTest(mitkDCMTestingSanityTest_SCImage mitkDCMTestingSanityTest 1 ${MITK_DATA_DIR}/spacing-ok-sc.dcm) +mitkAddCustomModuleTest(mitkDCMTestingSanityTest_NoImagePositionPatient mitkDCMTestingSanityTest 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(mitkDCMPreloadedVolumeTest_Slice mitkDCMPreloadedVolumeTest ${MITK_DATA_DIR}/spacing-ok-ct.dcm) + +set(VERIFY_DUMP_CMD ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/VerifyDCMMitkImageDump) + +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}/DCMReader) + + +# 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(AddDcmTestsFromDataRepository 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 "DCM_Load_${input}") + + #message(STATUS "DCM 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} rawDcmFiles) + foreach(raw ${rawDcmFiles}) + # prepend each file with an abosolute path + set(fileWithFullPath ${CURRENT_DATASET_DIR}/${raw}) + list(APPEND dicomFiles ${fileWithFullPath}) + endforeach(raw ${rawDcmFiles}) + + #message(STATUS " Load ${dicomFiles}") + add_test(${testname} + ${VERIFY_DUMP_CMD} ${expecteddump} ${dicomFiles}) + mitkFunctionAddTestLabel(${testname}) + + endforeach(input allInputs) + +endfunction(AddDcmTestsFromDataRepository CURRENT_DATASET_DIR TESTS_DIR INPUT_LISTNAME EXPECTED_DUMP) + + +AddDcmTestsFromDataRepository(${CT_ABDOMEN_DIR} ${TESTS_DIR} ${INPUT_LISTNAME} ${EXPECTED_DUMP}) +AddDcmTestsFromDataRepository(${CT_TILT_HEAD_DIR} ${TESTS_DIR} ${INPUT_LISTNAME} ${EXPECTED_DUMP}) +AddDcmTestsFromDataRepository(${CT_TILT_DIR} ${TESTS_DIR} ${INPUT_LISTNAME} ${EXPECTED_DUMP}) +AddDcmTestsFromDataRepository(${MR_HEART_DIR} ${TESTS_DIR} ${INPUT_LISTNAME} ${EXPECTED_DUMP}) + +AddDcmTestsFromDataRepository(${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(mitkDCMPreloadedVolumeTest_Abdomen mitkDCMPreloadedVolumeTest ${abdomenImages}) diff --git a/Core/Code/Testing/DCMTesting/Testing/files.cmake b/Core/Code/Testing/DCMTesting/Testing/files.cmake new file mode 100644 index 0000000000..1b775fa79a --- /dev/null +++ b/Core/Code/Testing/DCMTesting/Testing/files.cmake @@ -0,0 +1,11 @@ + +# tests with no extra command line parameter +set(MODULE_CUSTOM_TESTS + mitkDCMTestingSanityTest.cpp + mitkDCMPreloadedVolumeTest.cpp +) + +# this shouldn't be necessary if this variable +# would actually be a parameter of the MITK_CREATE_MODULE_TESTS +# macro. See bug #10592 +set(TEST_CPP_FILES "") diff --git a/Core/Code/Testing/DCMTesting/Testing/mitkDCMPreloadedVolumeTest.cpp b/Core/Code/Testing/DCMTesting/Testing/mitkDCMPreloadedVolumeTest.cpp new file mode 100644 index 0000000000..7ca59ee05b --- /dev/null +++ b/Core/Code/Testing/DCMTesting/Testing/mitkDCMPreloadedVolumeTest.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 "mitkTestDCMLoading.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 mitkDCMPreloadedVolumeTest(int argc, char** const argv) +{ + MITK_TEST_BEGIN("DCMPreloadedVolume") + + mitk::TestDCMLoading loader; + mitk::TestDCMLoading::StringContainer files; + + // adapt expectations depending on configuration + std::string configuration = mitk::DicomSeriesReader::GetConfigurationString(); + MITK_TEST_OUTPUT(<< "Configuration: " << configuration) + + // 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 DCM + for (mitk::TestDCMLoading::StringContainer::const_iterator fileIter = files.begin(); + fileIter != files.end(); + ++fileIter) + { + MITK_TEST_CONDITION_REQUIRED( mitk::DicomSeriesReader::IsDicom(*fileIter) , *fileIter << " is recognized as loadable DCM object" ) + + } + + // load for a first time + mitk::TestDCMLoading::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) + mitk::TestDCMLoading::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_END() +} diff --git a/Core/Code/Testing/DCMTesting/Testing/mitkDCMTestingSanityTest.cpp b/Core/Code/Testing/DCMTesting/Testing/mitkDCMTestingSanityTest.cpp new file mode 100644 index 0000000000..88f782becf --- /dev/null +++ b/Core/Code/Testing/DCMTesting/Testing/mitkDCMTestingSanityTest.cpp @@ -0,0 +1,68 @@ +/*=================================================================== + +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 "mitkTestDCMLoading.h" +#include "mitkTestingMacros.h" + +int mitkDCMTestingSanityTest(int argc, char** const argv) +{ + MITK_TEST_BEGIN("DCMTestingSanity") + + mitk::TestDCMLoading loader; + mitk::TestDCMLoading::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" ) + */ + + // 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 DCM + for (mitk::TestDCMLoading::StringContainer::const_iterator fileIter = files.begin(); + fileIter != files.end(); + ++fileIter) + { + MITK_TEST_CONDITION_REQUIRED( mitk::DicomSeriesReader::IsDicom(*fileIter) , *fileIter << " is recognized as loadable DCM object" ) + + } + + // compare with expected number of images from commandline + mitk::TestDCMLoading::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::TestDCMLoading::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/DCMTesting/VerifyDCMMitkImageDump.cpp b/Core/Code/Testing/DCMTesting/VerifyDCMMitkImageDump.cpp new file mode 100644 index 0000000000..4212855c4a --- /dev/null +++ b/Core/Code/Testing/DCMTesting/VerifyDCMMitkImageDump.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 "mitkTestDCMLoading.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 DCM 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 DCM images in file1 to fileN as MITK images "; + MITK_ERROR << " and compares loaded images against stored expectations (dumps)."; + MITK_ERROR << " See also DumpDCMMitkImage (generates dumps)"; + return EXIT_FAILURE; + } + + mitk::TestDCMLoading loader; + mitk::TestDCMLoading::StringContainer 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::TestDCMLoading::ImageList images = loader.LoadFiles(files); + + unsigned int imageCounter(0); + for ( mitk::TestDCMLoading::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/DCMTesting/files.cmake b/Core/Code/Testing/DCMTesting/files.cmake new file mode 100644 index 0000000000..7a17edf5d3 --- /dev/null +++ b/Core/Code/Testing/DCMTesting/files.cmake @@ -0,0 +1,4 @@ + +set(CPP_FILES + mitkTestDCMLoading.cpp +) diff --git a/Core/Code/Testing/DCMTesting/mitkTestDCMLoading.cpp b/Core/Code/Testing/DCMTesting/mitkTestDCMLoading.cpp new file mode 100644 index 0000000000..196a725082 --- /dev/null +++ b/Core/Code/Testing/DCMTesting/mitkTestDCMLoading.cpp @@ -0,0 +1,448 @@ +/*=================================================================== + +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 "mitkTestDCMLoading.h" + +#include + +mitk::TestDCMLoading::TestDCMLoading() +:m_PreviousCLocale(NULL) +{ +} + +void mitk::TestDCMLoading::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::TestDCMLoading::ResetUserLocale() +{ + if (m_PreviousCLocale) + { + setlocale(LC_NUMERIC, m_PreviousCLocale); + + std::cin.imbue(m_PreviousCppLocale); + std::cout.imbue(m_PreviousCppLocale); + + m_PreviousCLocale = NULL; + } +} + + + +mitk::TestDCMLoading::ImageList mitk::TestDCMLoading::LoadFiles( const StringContainer& files, Image::Pointer preLoadedVolume ) +{ + for (StringContainer::const_iterator iter = files.begin(); + iter != files.end(); + ++iter) + { + MITK_DEBUG << "File " << *iter; + } + + ImageList result; + + DicomSeriesReader::FileNamesGrouping seriesInFiles = DicomSeriesReader::GetSeries( files, true ); + + // 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) + { + 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 + { + } + } + + return result; +} + +std::string +mitk::TestDCMLoading::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::TestDCMLoading::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::TestDCMLoading::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::TestDCMLoading::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::TestDCMLoading::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::TestDCMLoading::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::TestDCMLoading::KeyValueMap +mitk::TestDCMLoading::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/Modules/DICOMTesting/mitkTestDICOMLoading.h b/Core/Code/Testing/DCMTesting/mitkTestDCMLoading.h similarity index 80% copy from Modules/DICOMTesting/mitkTestDICOMLoading.h copy to Core/Code/Testing/DCMTesting/mitkTestDCMLoading.h index ca902191a0..a84d7c24c2 100644 --- a/Modules/DICOMTesting/mitkTestDICOMLoading.h +++ b/Core/Code/Testing/DCMTesting/mitkTestDCMLoading.h @@ -1,112 +1,106 @@ /*=================================================================== 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 +#ifndef mitkTestDCMLoading_h +#define mitkTestDCMLoading_h -#include "mitkClassicDICOMSeriesReader.h" +#include "mitkDicomSeriesReader.h" -#include "mitkDICOMTestingExports.h" +#include "mitkDCMTestingExports.h" namespace mitk { -class mitkDICOMTesting_EXPORT TestDICOMLoading +class mitkDCMTesting_EXPORT TestDCMLoading { public: + typedef DicomSeriesReader::StringContainer StringContainer; + typedef std::list NodeList; typedef std::list ImageList; - TestDICOMLoading(); + TestDCMLoading(); ImageList - LoadFiles( const StringList & files ); - - Image::Pointer - DecorateVerifyCachedImage( const StringList& files, mitk::Image::Pointer cachedImage ); - - Image::Pointer - DecorateVerifyCachedImage( const StringList& files, DICOMTagCache*, mitk::Image::Pointer cachedImage ); + LoadFiles( const StringContainer& files, Image::Pointer preLoadedVolume = NULL ); /** \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; - ClassicDICOMSeriesReader::Pointer - BuildDICOMReader(); - 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(); + return (!stream.fail()) && (fabs(value) < std::numeric_limits::max()); } const char* m_PreviousCLocale; std::locale m_PreviousCppLocale; }; } #endif - diff --git a/Modules/DICOMReader/TODOs.txt b/Modules/DICOMReader/TODOs.txt index 19c177372d..6594bcc744 100644 --- a/Modules/DICOMReader/TODOs.txt +++ b/Modules/DICOMReader/TODOs.txt @@ -1,55 +1,60 @@ Important +[ ] Make sure that ..Selector does tag scanning only once +[ ] Check Gantry Tilt TODO/Bug in old reader +[ ] - and in new reader, how do we WANT to handle this? +[x] Keep OLD DICOMTesting module, in order to test code until it is removed + - restored this module as DCMTesting module [x] ONLY load a pre-sorted list - Should work now by: - get the list of images, construct FrameInfo list (order must be known from prior sorting) - prepare a tagcache object that provides tag information (from some kind of database, or run AnalyzeInputFiles() on file reader) - create DICOMImageBlockDescriptor with frame list and tag cache - Call SetFixTiltByShearing() and TiltInfo object as appropriate... - call DICOMITKSeriesGDCMReader::LoadMitkImageForImageBlockDescriptor(block) [x] Option to just provide properties to a pre-loaded mitk::Image - see TestDICOMLoading::DecorateVerifyCachedImage() [x] Performance of mitkdump / DICOMReaderSelector (TagCache?) - the note in GDCM..Reader::GetTagValue() is correct. This piece of code is taking lots of time. Approx. half of the time is tag scanning, the other half is construction of block describing properties, which accesses GetTagValue. [x] - maybe we can evaluate these properties in a lazy way (only when asked for). Solution: Yes, this helps, implemented. [x] Gantry tilt broken during implementation - Was a wrong implementation of NormalDirectionConsistencySorter [x] Accepted image origin error during EquiDistantBlocksSorter must be configurable. To support legacy behavior of the reader, the tolerance must be fixable to a constant value. For newer readers, it should be adapted to the actual slice distance. [x] Sorting by "Image Time" seems undeterministic (clarkson test data) [x] - Numeric conversion of "1.2.3.4" yields 1.2 on Windows, seems to fail on Linux(?) - need to verify that the whole string (except whitespace at the begin/end) was converted Solution: GetTagValue as String does a right-trim plus we check after conversion! Works. [x] Implement Configurator::GetBuiltIn3DReaders() (don't ignore "classic reader") [x] Complete MITK properties of images: level/window and color type (MONOCHROME1/2) [x] Check input images fo DICOMness and non-multi-frameness [x] Use CanHandleFile() in selection! - implicitly now, the reader checks itself and produces no output if it cannot handle the files [x] Check ToDo in mitkDICOMTag.cpp - operator< for DICOMTag has been re-implemented in a readable and safer way. [x] Configurable precision for tag value comparisons [x] Images are upside-down in some cases - error was hidden assumption somewhere: filenames for ImageSeriesReader need to be in an order that goes along the image normals, not in the opposite direction Questionable [ ] Fallback to filename instead of instance UID? Nice-to-have [ ] Multi-Frame images (DCMTK) [ ] ... [ ] Add the tags used by DICOMImageBlockDescriptor dynamically instead of hard-coded (look for ToDo around m_GDCMScanner.AddTag() in mitkDICOMITKSeriesGDCMReader.cpp) [ ] Let DICOMImageBlockDescriptor describe attributes common and different for all blocks of a series [ ] Let DICOMImageBlockDescriptor check image properties when mitk::Image is set (pre-loaded case) diff --git a/Modules/DICOMTesting/mitkTestDICOMLoading.h b/Modules/DICOMTesting/mitkTestDICOMLoading.h index ca902191a0..a6f86d75bc 100644 --- a/Modules/DICOMTesting/mitkTestDICOMLoading.h +++ b/Modules/DICOMTesting/mitkTestDICOMLoading.h @@ -1,112 +1,112 @@ /*=================================================================== 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 "mitkClassicDICOMSeriesReader.h" -#include "mitkDICOMTestingExports.h" +#include "DICOMTestingExports.h" namespace mitk { -class mitkDICOMTesting_EXPORT TestDICOMLoading +class DICOMTesting_EXPORT TestDICOMLoading { public: typedef std::list ImageList; TestDICOMLoading(); ImageList LoadFiles( const StringList & files ); Image::Pointer DecorateVerifyCachedImage( const StringList& files, mitk::Image::Pointer cachedImage ); Image::Pointer DecorateVerifyCachedImage( const StringList& files, DICOMTagCache*, mitk::Image::Pointer cachedImage ); /** \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; ClassicDICOMSeriesReader::Pointer BuildDICOMReader(); 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(); } const char* m_PreviousCLocale; std::locale m_PreviousCppLocale; }; } #endif