Mercurial > hg > orthanc-stone
changeset 1541:ae17c8c8838f
standalone compilation of unit tests
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Tue, 11 Aug 2020 13:47:24 +0200 |
parents | e20a2381200d |
children | 6e0da8370270 |
files | OrthancStone/Resources/CMake/OrthancStoneConfiguration.cmake OrthancStone/UnitTestsSources/CMakeLists.txt OrthancStone/UnitTestsSources/Graveyard/TestStructureSet_BGO.cpp OrthancStone/UnitTestsSources/TestStructureSet.cpp OrthancStone/UnitTestsSources/UnitTestsMain.cpp |
diffstat | 5 files changed, 440 insertions(+), 404 deletions(-) [+] |
line wrap: on
line diff
--- a/OrthancStone/Resources/CMake/OrthancStoneConfiguration.cmake Tue Aug 11 13:28:40 2020 +0200 +++ b/OrthancStone/Resources/CMake/OrthancStoneConfiguration.cmake Tue Aug 11 13:47:24 2020 +0200 @@ -74,6 +74,7 @@ endif() set(ENABLE_THREADS OFF) + set(ENABLE_WEB_CLIENT OFF) add_definitions(-DORTHANC_ENABLE_WASM=1) else() if (CMAKE_SYSTEM_NAME STREQUAL "Emscripten" OR @@ -103,7 +104,7 @@ ## Configure optional third-party components ##################################################################### -if (NOT ORTHANC_SANDBOXED) +if (ENABLE_WEB_CLIENT) list(APPEND ORTHANC_STONE_SOURCES ${ORTHANC_STONE_ROOT}/Sources/Toolbox/OrthancDatasets/OrthancHttpConnection.cpp ) @@ -213,29 +214,20 @@ ## All the source files required to build Stone of Orthanc ##################################################################### -if (NOT ORTHANC_SANDBOXED) - set(PLATFORM_SOURCES - ${ORTHANC_STONE_ROOT}/Sources/Loaders/GenericLoadersContext.cpp - ${ORTHANC_STONE_ROOT}/Sources/Loaders/GenericLoadersContext.h +if (ENABLE_SDL) + list(APPEND ORTHANC_STONE_SOURCES + ${ORTHANC_STONE_ROOT}/Sources/Viewport/SdlWindow.cpp + ${ORTHANC_STONE_ROOT}/Sources/Viewport/SdlWindow.h ) - if (ENABLE_SDL) + if (ENABLE_OPENGL) list(APPEND ORTHANC_STONE_SOURCES - ${ORTHANC_STONE_ROOT}/Sources/Viewport/SdlWindow.cpp - ${ORTHANC_STONE_ROOT}/Sources/Viewport/SdlWindow.h + ${ORTHANC_STONE_ROOT}/Sources/OpenGL/SdlOpenGLContext.cpp + ${ORTHANC_STONE_ROOT}/Sources/OpenGL/SdlOpenGLContext.h + ${ORTHANC_STONE_ROOT}/Sources/Viewport/SdlViewport.cpp + ${ORTHANC_STONE_ROOT}/Sources/Viewport/SdlViewport.h ) endif() - - if (ENABLE_SDL) - if (ENABLE_OPENGL) - list(APPEND ORTHANC_STONE_SOURCES - ${ORTHANC_STONE_ROOT}/Sources/OpenGL/SdlOpenGLContext.cpp - ${ORTHANC_STONE_ROOT}/Sources/OpenGL/SdlOpenGLContext.h - ${ORTHANC_STONE_ROOT}/Sources/Viewport/SdlViewport.cpp - ${ORTHANC_STONE_ROOT}/Sources/Viewport/SdlViewport.h - ) - endif() - endif() endif() @@ -247,10 +239,13 @@ ) endif() -if (ENABLE_THREADS) + +if (NOT ORTHANC_SANDBOXED AND ENABLE_THREADS AND ENABLE_WEB_CLIENT) list(APPEND ORTHANC_STONE_SOURCES + ${ORTHANC_STONE_ROOT}/Sources/Loaders/GenericLoadersContext.cpp + ${ORTHANC_STONE_ROOT}/Sources/Loaders/GenericLoadersContext.h + ${ORTHANC_STONE_ROOT}/Sources/Oracle/GenericOracleRunner.cpp ${ORTHANC_STONE_ROOT}/Sources/Oracle/ThreadedOracle.cpp - ${ORTHANC_STONE_ROOT}/Sources/Oracle/GenericOracleRunner.cpp ) endif()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancStone/UnitTestsSources/CMakeLists.txt Tue Aug 11 13:47:24 2020 +0200 @@ -0,0 +1,41 @@ +cmake_minimum_required(VERSION 2.8.10) + +project(UnitTests) + +include(${CMAKE_SOURCE_DIR}/../Resources/CMake/OrthancStoneParameters.cmake) + +if (ORTHANC_FRAMEWORK_SOURCE STREQUAL "system") + set(ORTHANC_BOOST_COMPONENTS program_options) + + set(USE_SYSTEM_GOOGLE_TEST ON CACHE BOOL "Use the system version of Google Test") + set(USE_GOOGLE_TEST_DEBIAN_PACKAGE OFF CACHE BOOL "Use the sources of Google Test shipped with libgtest-dev (Debian only)") + mark_as_advanced(USE_GOOGLE_TEST_DEBIAN_PACKAGE) + include(${ORTHANC_STONE_ROOT}/Resources/Orthanc/CMake/DownloadPackage.cmake) + include(${ORTHANC_STONE_ROOT}/Resources/Orthanc/CMake/GoogleTestConfiguration.cmake) + +else() + set(ENABLE_GOOGLE_TEST ON) +endif() + +set(ENABLE_DCMTK OFF) +set(ENABLE_OPENGL OFF) + +include(${ORTHANC_STONE_ROOT}/Resources/CMake/OrthancStoneConfiguration.cmake) + +add_executable(UnitTests + ${AUTOGENERATED_SOURCES} + ${BOOST_EXTENDED_SOURCES} + ${GOOGLE_TEST_SOURCES} + ${ORTHANC_STONE_ROOT}/UnitTestsSources/GenericToolboxTests.cpp + ${ORTHANC_STONE_ROOT}/UnitTestsSources/ImageToolboxTests.cpp + ${ORTHANC_STONE_ROOT}/UnitTestsSources/PixelTestPatternsTests.cpp + ${ORTHANC_STONE_ROOT}/UnitTestsSources/SortedFramesTests.cpp + ${ORTHANC_STONE_ROOT}/UnitTestsSources/TestCommands.cpp + ${ORTHANC_STONE_ROOT}/UnitTestsSources/TestMessageBroker.cpp + ${ORTHANC_STONE_ROOT}/UnitTestsSources/TestStrategy.cpp + ${ORTHANC_STONE_ROOT}/UnitTestsSources/TestStructureSet.cpp + ${ORTHANC_STONE_ROOT}/UnitTestsSources/UnitTestsMain.cpp + ${ORTHANC_STONE_SOURCES} + ) + +target_link_libraries(UnitTests ${DCMTK_LIBRARIES})
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancStone/UnitTestsSources/Graveyard/TestStructureSet_BGO.cpp Tue Aug 11 13:47:24 2020 +0200 @@ -0,0 +1,383 @@ +/* +these tests are single-threaded... no worries for old buggy compilers +(I'm talking to YOU, cl.exe v100! And to your ancestors!) +*/ +static std::string& GetTestJson() +{ + static const char* resultRaw = NULL; + static std::string result; + if (resultRaw == NULL) + { + std::stringstream sst; + + sst << k_rtStruct_json00 + << k_rtStruct_json01 + << k_rtStruct_json02 + << k_rtStruct_json03 + << k_rtStruct_json04 + << k_rtStruct_json05 + << k_rtStruct_json06 + << k_rtStruct_json07 + << k_rtStruct_json08; + + std::string wholeBody = sst.str(); + result.swap(wholeBody); + resultRaw = result.c_str(); + } + return result; +} + + +namespace +{ + void Initialize(const char* orthancApiUrl, OrthancStone::ILoadersContext& loadersContext) + { + Orthanc::WebServiceParameters p; + + OrthancStone::GenericLoadersContext& typedLoadersContext = + dynamic_cast<OrthancStone::GenericLoadersContext&>(loadersContext); + // Default is http://localhost:8042 + // Here's how you may change it + p.SetUrl(orthancApiUrl); + p.SetCredentials("orthanc", "orthanc"); + typedLoadersContext.SetOrthancParameters(p); + + typedLoadersContext.StartOracle(); + } + + void Exitialize(OrthancStone::ILoadersContext& loadersContext) + { + OrthancStone::GenericLoadersContext& typedLoadersContext = + dynamic_cast<OrthancStone::GenericLoadersContext&>(loadersContext); + + typedLoadersContext.StopOracle(); + } + + +#if 0 + class TestObserver : public ObserverBase<TestObserver> + { + public: + TestObserver() {}; + + virtual void Handle + + }; +#endif + +} + +TEST(StructureSet, DISABLED_StructureSetLoader_injection_feature_2020_05_10) +{ + namespace pt = boost::posix_time; + + std::unique_ptr<OrthancStone::ILoadersContext> loadersContext(new OrthancStone::GenericLoadersContext(1,4,1)); + Initialize("http://localhost:8042/", *loadersContext); + + boost::shared_ptr<DicomStructureSetLoader> loader = DicomStructureSetLoader::Create(*loadersContext); + + // replace with Orthanc ID of an uploaded RTSTRUCT instance! + loader->LoadInstanceFullVisibility("72c773ac-5059f2c4-2e6a9120-4fd4bca1-45701661"); + + bool bContinue(true); + + pt::ptime initialTime = pt::second_clock::local_time(); + + while (bContinue) + { + bContinue = !loader->AreStructuresReady(); + boost::this_thread::sleep_for(boost::chrono::milliseconds(1000)); + + { + pt::ptime nowTime = pt::second_clock::local_time(); + pt::time_duration diff = nowTime - initialTime; + double seconds = static_cast<double>(diff.total_milliseconds()) * 0.001; + std::cout << seconds << " seconds elapsed...\n"; + if (seconds > 30) + { + std::cout << "More than 30 seconds elapsed... Aborting test :(\n"; + //GTEST_FATAL_FAILURE_("More than 30 seconds elapsed... Aborting test :("); + //bContinue = false; + } + } + } +} + +class SliceProcessor : + public OrthancStone::OrthancSeriesVolumeProgressiveLoader::ISlicePostProcessor, + public OrthancStone::DicomStructureSetLoader::IInstanceLookupHandler +{ +public: + SliceProcessor(OrthancStone::DicomStructureSetLoader& structLoader) : structLoader_(structLoader) + { + } + + virtual void ProcessCTDicomSlice(const Orthanc::DicomMap& instance) ORTHANC_OVERRIDE + { + std::string sopInstanceUid; + if (!instance.LookupStringValue(sopInstanceUid, Orthanc::DICOM_TAG_SOP_INSTANCE_UID, false)) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat, "Missing SOPInstanceUID in a DICOM instance"); + } + slicesDicom_[sopInstanceUid] = boost::shared_ptr<DicomMap>(instance.Clone()); + } + + virtual void RetrieveReferencedSlices(const std::set<std::string>& nonEmptyInstances) ORTHANC_OVERRIDE + { + for (std::set<std::string>::const_iterator it = nonEmptyInstances.begin(); + it != nonEmptyInstances.end(); + ++it) + { + const std::string nonEmptyInstance = *it; + if (slicesDicom_.find(nonEmptyInstance) == slicesDicom_.end()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat, "Referenced SOPInstanceUID not found in CT"); + } + boost::shared_ptr<Orthanc::DicomMap> instance = slicesDicom_[nonEmptyInstance]; + structLoader_.AddReferencedSlice(*instance); + } + } + + OrthancStone::DicomStructureSetLoader& structLoader_; + std::map<std::string, boost::shared_ptr<Orthanc::DicomMap> > slicesDicom_; +}; + +void LoadCtSeriesBlocking(boost::shared_ptr<OrthancStone::OrthancSeriesVolumeProgressiveLoader> ctLoader, std::string seriesId) +{ + namespace pt = boost::posix_time; + + // Load the CT + ctLoader->LoadSeries(seriesId); + + // Wait for CT to be loaded + pt::ptime initialTime = pt::second_clock::local_time(); + { + bool bContinue(true); + while (bContinue) + { + bContinue = !ctLoader->IsVolumeImageReadyInHighQuality(); + boost::this_thread::sleep_for(boost::chrono::milliseconds(1000)); + + { + pt::ptime nowTime = pt::second_clock::local_time(); + pt::time_duration diff = nowTime - initialTime; + double seconds = static_cast<double>(diff.total_milliseconds()) * 0.001; + std::cout << seconds << " seconds elapsed...\n"; + if (seconds > 30) + { + const char* msg = "More than 30 seconds elapsed when waiting for CT... Aborting test :(\n"; + GTEST_FATAL_FAILURE_(msg); + bContinue = false; + } + } + } + } +} + + +/** +Will fill planes +*/ +void GetCTPlanes(std::vector<OrthancStone::CoordinateSystem3D>& planes, + OrthancStone::VolumeProjection projection, + boost::shared_ptr<OrthancStone::OrthancSeriesVolumeProgressiveLoader> ctLoader) +{ + planes.clear(); // inefficient : we don't care + + const VolumeImageGeometry& geometry = ctLoader->GetImageGeometry(); + const unsigned int depth = geometry.GetProjectionDepth(projection); + + planes.resize(depth); + + for (unsigned int z = 0; z < depth; z++) + { + planes[z] = geometry.GetProjectionSlice(projection, z); + } +} + +void LoadRtStructBlocking(boost::shared_ptr<OrthancStone::DicomStructureSetLoader> structLoader, std::string instanceId) +{ + namespace pt = boost::posix_time; + + // Load RTSTRUCT + structLoader->LoadInstanceFullVisibility(instanceId); + + pt::ptime initialTime = pt::second_clock::local_time(); + + // Wait for the loading process to complete + { + bool bContinue(true); + while (bContinue) + { + bContinue = !structLoader->AreStructuresReady(); + boost::this_thread::sleep_for(boost::chrono::milliseconds(1000)); + + { + pt::ptime nowTime = pt::second_clock::local_time(); + pt::time_duration diff = nowTime - initialTime; + double seconds = static_cast<double>(diff.total_milliseconds()) * 0.001; + std::cout << seconds << " seconds elapsed...\n"; + if (seconds > 30) + { + const char* msg = "More than 30 seconds elapsed when waiting for RTSTRUCT... Aborting test :(\n"; + GTEST_FATAL_FAILURE_(msg); + bContinue = false; + } + } + } + } +} + +TEST(StructureSet, DISABLED_Integration_Compound_CT_Struct_Loading) +{ + const double TOLERANCE = 0.0000001; + + // create loaders context + std::unique_ptr<OrthancStone::ILoadersContext> loadersContext(new OrthancStone::GenericLoadersContext(1,4,1)); + Initialize("http://localhost:8042/", *loadersContext); + + const char* ctSeriesId = "a04ecf01-79b2fc33-58239f7e-ad9db983-28e81afa"; + const char* rtStructInstanceId = "54460695-ba3885ee-ddf61ac0-f028e31d-a6e474d9"; + + // we'll compare normal loading and optimized loading with SliceProcessor to store the dicom + + boost::shared_ptr<OrthancStone::DicomStructureSetLoader> normalStructLoader; + boost::shared_ptr<OrthancStone::DicomStructureSetLoader> optimizedStructLoader; + + { + // Create the CT volume + boost::shared_ptr<OrthancStone::DicomVolumeImage> volume = boost::make_shared<OrthancStone::DicomVolumeImage>(); + + // Create CT loader + boost::shared_ptr<OrthancStone::OrthancSeriesVolumeProgressiveLoader> ctLoader = + OrthancStone::OrthancSeriesVolumeProgressiveLoader::Create(*loadersContext, volume); + + // Create struct loader + normalStructLoader = OrthancStone::DicomStructureSetLoader::Create(*loadersContext); + + // Load the CT + LoadCtSeriesBlocking(ctLoader, ctSeriesId); + + const OrthancStone::VolumeImageGeometry& imageGeometry = ctLoader->GetImageGeometry(); + unsigned int width = imageGeometry.GetWidth(); + EXPECT_EQ(512u, width); + unsigned int height = imageGeometry.GetHeight(); + EXPECT_EQ(512u, height); + unsigned int depth = imageGeometry.GetDepth(); + EXPECT_EQ(109u, depth); + + // Load the RTStruct + LoadRtStructBlocking(normalStructLoader, rtStructInstanceId); + } + + std::vector<OrthancStone::CoordinateSystem3D> axialPlanes; + std::vector<OrthancStone::CoordinateSystem3D> coronalPlanes; + std::vector<OrthancStone::CoordinateSystem3D> sagittalPlanes; + + { + // Create the CT volume + boost::shared_ptr<OrthancStone::DicomVolumeImage> volume = boost::make_shared<OrthancStone::DicomVolumeImage>(); + + // Create CT loader + boost::shared_ptr<OrthancStone::OrthancSeriesVolumeProgressiveLoader> ctLoader = + OrthancStone::OrthancSeriesVolumeProgressiveLoader::Create(*loadersContext, volume); + + // Create struct loader + optimizedStructLoader = OrthancStone::DicomStructureSetLoader::Create(*loadersContext); + + // create the slice processor / instance lookup + boost::shared_ptr<SliceProcessor> sliceProcessor(new SliceProcessor(*optimizedStructLoader)); + + // Inject it into CT loader + ctLoader->SetDicomSlicePostProcessor(sliceProcessor); + + // Inject it into RTSTRUCT loader + optimizedStructLoader->SetInstanceLookupHandler(sliceProcessor); + + // Load the CT + LoadCtSeriesBlocking(ctLoader, ctSeriesId); + + // now, the slices are collected. let's do some checks + EXPECT_EQ(109u, sliceProcessor->slicesDicom_.size()); + + // Load the RTStruct + LoadRtStructBlocking(optimizedStructLoader, rtStructInstanceId); + + GetCTPlanes(axialPlanes, VolumeProjection_Axial, ctLoader); + GetCTPlanes(coronalPlanes, VolumeProjection_Coronal, ctLoader); + GetCTPlanes(sagittalPlanes, VolumeProjection_Sagittal, ctLoader); + } + + // DO NOT DELETE THOSE! + OrthancStone::DicomStructureSet* normalContent = normalStructLoader->GetContent(); + OrthancStone::DicomStructureSet* optimizedContent = optimizedStructLoader->GetContent(); + + EXPECT_EQ(normalContent->GetStructuresCount(), optimizedContent->GetStructuresCount()); + + /*void GetCTPlanes(std::vector<OrthancStone::CoordinateSystem3D>& planes, + OrthancStone::VolumeProjection projection, + boost::shared_ptr<OrthancStone::OrthancSeriesVolumeProgressiveLoader> ctLoader)*/ + + + std::vector<OrthancStone::CoordinateSystem3D> allPlanes; + + // let's gather all the possible cutting planes in a single struct + for (size_t i = 0; i < axialPlanes.size(); ++i) + allPlanes.push_back(axialPlanes[i]); + + for (size_t i = 0; i < coronalPlanes.size(); ++i) + allPlanes.push_back(coronalPlanes[i]); + + for (size_t i = 0; i < sagittalPlanes.size(); ++i) + allPlanes.push_back(sagittalPlanes[i]); + + for (size_t i = 0; i < normalContent->GetStructuresCount(); ++i) + { + std::cout << "Testing structure (" << i << "/" << normalContent->GetStructuresCount() << ")\n"; + Vector structureCenter1 = normalContent->GetStructureCenter(i); + const std::string& structureName1 = normalContent->GetStructureName(i); + const std::string& structureInterpretation1 = normalContent->GetStructureInterpretation(i); + Color structureColor1 = normalContent->GetStructureColor(i); + + Vector structureCenter2 = optimizedContent->GetStructureCenter(i); + const std::string& structureName2 = optimizedContent->GetStructureName(i); + const std::string& structureInterpretation2 = optimizedContent->GetStructureInterpretation(i); + Color structureColor2 = optimizedContent->GetStructureColor(i); + + EXPECT_NEAR(structureCenter1[0], structureCenter2[0], TOLERANCE); + EXPECT_NEAR(structureCenter1[1], structureCenter2[1], TOLERANCE); + EXPECT_NEAR(structureCenter1[2], structureCenter2[2], TOLERANCE); + + EXPECT_EQ(structureName1, structureName2); + EXPECT_EQ(structureInterpretation1, structureInterpretation2); + EXPECT_EQ(structureColor1.GetRed(), structureColor2.GetRed()); + EXPECT_EQ(structureColor1.GetGreen(), structureColor2.GetGreen()); + EXPECT_EQ(structureColor1.GetBlue(), structureColor2.GetBlue()); + + // "random" walk through the planes. Processing them all takes too long (~ 1 min) + for (size_t j = 0; j < allPlanes.size(); j += 37) + { + const OrthancStone::CoordinateSystem3D& plane = allPlanes[j]; + + std::vector< std::pair<Point2D, Point2D> > segments1; + std::vector< std::pair<Point2D, Point2D> > segments2; + + bool ok1 = normalContent->ProjectStructure(segments1, i, plane); + bool ok2 = optimizedContent->ProjectStructure(segments2, i, plane); + + // checks here + EXPECT_EQ(ok1, ok2); + EXPECT_EQ(segments1.size(), segments2.size()); + + for (size_t k = 0; k < segments1.size(); ++k) + { + EXPECT_NEAR(segments1[k].first.x, segments2[k].first.x, TOLERANCE); + EXPECT_NEAR(segments1[k].first.y, segments2[k].first.y, TOLERANCE); + EXPECT_NEAR(segments1[k].second.x, segments2[k].second.x, TOLERANCE); + EXPECT_NEAR(segments1[k].second.y, segments2[k].second.y, TOLERANCE); + } + } + } + + Exitialize(*loadersContext); +}
--- a/OrthancStone/UnitTestsSources/TestStructureSet.cpp Tue Aug 11 13:28:40 2020 +0200 +++ b/OrthancStone/UnitTestsSources/TestStructureSet.cpp Tue Aug 11 13:47:24 2020 +0200 @@ -4268,34 +4268,6 @@ #endif // _MSC_VER -/* -these tests are single-threaded... no worries for old buggy compilers -(I'm talking to YOU, cl.exe v100! And to your ancestors!) -*/ -static std::string& GetTestJson() -{ - static const char* resultRaw = NULL; - static std::string result; - if (resultRaw == NULL) - { - std::stringstream sst; - - sst << k_rtStruct_json00 - << k_rtStruct_json01 - << k_rtStruct_json02 - << k_rtStruct_json03 - << k_rtStruct_json04 - << k_rtStruct_json05 - << k_rtStruct_json06 - << k_rtStruct_json07 - << k_rtStruct_json08; - - std::string wholeBody = sst.str(); - result.swap(wholeBody); - resultRaw = result.c_str(); - } - return result; -} #define STONE_ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0])) @@ -5414,357 +5386,3 @@ #endif // BGO_ENABLE_DICOMSTRUCTURESETLOADER2 - -namespace -{ - void Initialize(const char* orthancApiUrl, OrthancStone::ILoadersContext& loadersContext) - { - Orthanc::WebServiceParameters p; - - OrthancStone::GenericLoadersContext& typedLoadersContext = - dynamic_cast<OrthancStone::GenericLoadersContext&>(loadersContext); - // Default is http://localhost:8042 - // Here's how you may change it - p.SetUrl(orthancApiUrl); - p.SetCredentials("orthanc", "orthanc"); - typedLoadersContext.SetOrthancParameters(p); - - typedLoadersContext.StartOracle(); - } - - void Exitialize(OrthancStone::ILoadersContext& loadersContext) - { - OrthancStone::GenericLoadersContext& typedLoadersContext = - dynamic_cast<OrthancStone::GenericLoadersContext&>(loadersContext); - - typedLoadersContext.StopOracle(); - } - - -#if 0 - class TestObserver : public ObserverBase<TestObserver> - { - public: - TestObserver() {}; - - virtual void Handle - - }; -#endif - -} - -TEST(StructureSet, DISABLED_StructureSetLoader_injection_feature_2020_05_10) -{ - namespace pt = boost::posix_time; - - std::unique_ptr<OrthancStone::ILoadersContext> loadersContext(new OrthancStone::GenericLoadersContext(1,4,1)); - Initialize("http://localhost:8042/", *loadersContext); - - boost::shared_ptr<DicomStructureSetLoader> loader = DicomStructureSetLoader::Create(*loadersContext); - - // replace with Orthanc ID of an uploaded RTSTRUCT instance! - loader->LoadInstanceFullVisibility("72c773ac-5059f2c4-2e6a9120-4fd4bca1-45701661"); - - bool bContinue(true); - - pt::ptime initialTime = pt::second_clock::local_time(); - - while (bContinue) - { - bContinue = !loader->AreStructuresReady(); - boost::this_thread::sleep_for(boost::chrono::milliseconds(1000)); - - { - pt::ptime nowTime = pt::second_clock::local_time(); - pt::time_duration diff = nowTime - initialTime; - double seconds = static_cast<double>(diff.total_milliseconds()) * 0.001; - std::cout << seconds << " seconds elapsed...\n"; - if (seconds > 30) - { - std::cout << "More than 30 seconds elapsed... Aborting test :(\n"; - //GTEST_FATAL_FAILURE_("More than 30 seconds elapsed... Aborting test :("); - //bContinue = false; - } - } - } -} - -class SliceProcessor : - public OrthancStone::OrthancSeriesVolumeProgressiveLoader::ISlicePostProcessor, - public OrthancStone::DicomStructureSetLoader::IInstanceLookupHandler -{ -public: - SliceProcessor(OrthancStone::DicomStructureSetLoader& structLoader) : structLoader_(structLoader) - { - } - - virtual void ProcessCTDicomSlice(const Orthanc::DicomMap& instance) ORTHANC_OVERRIDE - { - std::string sopInstanceUid; - if (!instance.LookupStringValue(sopInstanceUid, Orthanc::DICOM_TAG_SOP_INSTANCE_UID, false)) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat, "Missing SOPInstanceUID in a DICOM instance"); - } - slicesDicom_[sopInstanceUid] = boost::shared_ptr<DicomMap>(instance.Clone()); - } - - virtual void RetrieveReferencedSlices(const std::set<std::string>& nonEmptyInstances) ORTHANC_OVERRIDE - { - for (std::set<std::string>::const_iterator it = nonEmptyInstances.begin(); - it != nonEmptyInstances.end(); - ++it) - { - const std::string nonEmptyInstance = *it; - if (slicesDicom_.find(nonEmptyInstance) == slicesDicom_.end()) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat, "Referenced SOPInstanceUID not found in CT"); - } - boost::shared_ptr<Orthanc::DicomMap> instance = slicesDicom_[nonEmptyInstance]; - structLoader_.AddReferencedSlice(*instance); - } - } - - OrthancStone::DicomStructureSetLoader& structLoader_; - std::map<std::string, boost::shared_ptr<Orthanc::DicomMap> > slicesDicom_; -}; - -void LoadCtSeriesBlocking(boost::shared_ptr<OrthancStone::OrthancSeriesVolumeProgressiveLoader> ctLoader, std::string seriesId) -{ - namespace pt = boost::posix_time; - - // Load the CT - ctLoader->LoadSeries(seriesId); - - // Wait for CT to be loaded - pt::ptime initialTime = pt::second_clock::local_time(); - { - bool bContinue(true); - while (bContinue) - { - bContinue = !ctLoader->IsVolumeImageReadyInHighQuality(); - boost::this_thread::sleep_for(boost::chrono::milliseconds(1000)); - - { - pt::ptime nowTime = pt::second_clock::local_time(); - pt::time_duration diff = nowTime - initialTime; - double seconds = static_cast<double>(diff.total_milliseconds()) * 0.001; - std::cout << seconds << " seconds elapsed...\n"; - if (seconds > 30) - { - const char* msg = "More than 30 seconds elapsed when waiting for CT... Aborting test :(\n"; - GTEST_FATAL_FAILURE_(msg); - bContinue = false; - } - } - } - } -} - - -/** -Will fill planes -*/ -void GetCTPlanes(std::vector<OrthancStone::CoordinateSystem3D>& planes, - OrthancStone::VolumeProjection projection, - boost::shared_ptr<OrthancStone::OrthancSeriesVolumeProgressiveLoader> ctLoader) -{ - planes.clear(); // inefficient : we don't care - - const VolumeImageGeometry& geometry = ctLoader->GetImageGeometry(); - const unsigned int depth = geometry.GetProjectionDepth(projection); - - planes.resize(depth); - - for (unsigned int z = 0; z < depth; z++) - { - planes[z] = geometry.GetProjectionSlice(projection, z); - } -} - -void LoadRtStructBlocking(boost::shared_ptr<OrthancStone::DicomStructureSetLoader> structLoader, std::string instanceId) -{ - namespace pt = boost::posix_time; - - // Load RTSTRUCT - structLoader->LoadInstanceFullVisibility(instanceId); - - pt::ptime initialTime = pt::second_clock::local_time(); - - // Wait for the loading process to complete - { - bool bContinue(true); - while (bContinue) - { - bContinue = !structLoader->AreStructuresReady(); - boost::this_thread::sleep_for(boost::chrono::milliseconds(1000)); - - { - pt::ptime nowTime = pt::second_clock::local_time(); - pt::time_duration diff = nowTime - initialTime; - double seconds = static_cast<double>(diff.total_milliseconds()) * 0.001; - std::cout << seconds << " seconds elapsed...\n"; - if (seconds > 30) - { - const char* msg = "More than 30 seconds elapsed when waiting for RTSTRUCT... Aborting test :(\n"; - GTEST_FATAL_FAILURE_(msg); - bContinue = false; - } - } - } - } -} - -TEST(StructureSet, DISABLED_Integration_Compound_CT_Struct_Loading) -{ - const double TOLERANCE = 0.0000001; - - // create loaders context - std::unique_ptr<OrthancStone::ILoadersContext> loadersContext(new OrthancStone::GenericLoadersContext(1,4,1)); - Initialize("http://localhost:8042/", *loadersContext); - - const char* ctSeriesId = "a04ecf01-79b2fc33-58239f7e-ad9db983-28e81afa"; - const char* rtStructInstanceId = "54460695-ba3885ee-ddf61ac0-f028e31d-a6e474d9"; - - // we'll compare normal loading and optimized loading with SliceProcessor to store the dicom - - boost::shared_ptr<OrthancStone::DicomStructureSetLoader> normalStructLoader; - boost::shared_ptr<OrthancStone::DicomStructureSetLoader> optimizedStructLoader; - - { - // Create the CT volume - boost::shared_ptr<OrthancStone::DicomVolumeImage> volume = boost::make_shared<OrthancStone::DicomVolumeImage>(); - - // Create CT loader - boost::shared_ptr<OrthancStone::OrthancSeriesVolumeProgressiveLoader> ctLoader = - OrthancStone::OrthancSeriesVolumeProgressiveLoader::Create(*loadersContext, volume); - - // Create struct loader - normalStructLoader = OrthancStone::DicomStructureSetLoader::Create(*loadersContext); - - // Load the CT - LoadCtSeriesBlocking(ctLoader, ctSeriesId); - - const OrthancStone::VolumeImageGeometry& imageGeometry = ctLoader->GetImageGeometry(); - unsigned int width = imageGeometry.GetWidth(); - EXPECT_EQ(512u, width); - unsigned int height = imageGeometry.GetHeight(); - EXPECT_EQ(512u, height); - unsigned int depth = imageGeometry.GetDepth(); - EXPECT_EQ(109u, depth); - - // Load the RTStruct - LoadRtStructBlocking(normalStructLoader, rtStructInstanceId); - } - - std::vector<OrthancStone::CoordinateSystem3D> axialPlanes; - std::vector<OrthancStone::CoordinateSystem3D> coronalPlanes; - std::vector<OrthancStone::CoordinateSystem3D> sagittalPlanes; - - { - // Create the CT volume - boost::shared_ptr<OrthancStone::DicomVolumeImage> volume = boost::make_shared<OrthancStone::DicomVolumeImage>(); - - // Create CT loader - boost::shared_ptr<OrthancStone::OrthancSeriesVolumeProgressiveLoader> ctLoader = - OrthancStone::OrthancSeriesVolumeProgressiveLoader::Create(*loadersContext, volume); - - // Create struct loader - optimizedStructLoader = OrthancStone::DicomStructureSetLoader::Create(*loadersContext); - - // create the slice processor / instance lookup - boost::shared_ptr<SliceProcessor> sliceProcessor(new SliceProcessor(*optimizedStructLoader)); - - // Inject it into CT loader - ctLoader->SetDicomSlicePostProcessor(sliceProcessor); - - // Inject it into RTSTRUCT loader - optimizedStructLoader->SetInstanceLookupHandler(sliceProcessor); - - // Load the CT - LoadCtSeriesBlocking(ctLoader, ctSeriesId); - - // now, the slices are collected. let's do some checks - EXPECT_EQ(109u, sliceProcessor->slicesDicom_.size()); - - // Load the RTStruct - LoadRtStructBlocking(optimizedStructLoader, rtStructInstanceId); - - GetCTPlanes(axialPlanes, VolumeProjection_Axial, ctLoader); - GetCTPlanes(coronalPlanes, VolumeProjection_Coronal, ctLoader); - GetCTPlanes(sagittalPlanes, VolumeProjection_Sagittal, ctLoader); - } - - // DO NOT DELETE THOSE! - OrthancStone::DicomStructureSet* normalContent = normalStructLoader->GetContent(); - OrthancStone::DicomStructureSet* optimizedContent = optimizedStructLoader->GetContent(); - - EXPECT_EQ(normalContent->GetStructuresCount(), optimizedContent->GetStructuresCount()); - - /*void GetCTPlanes(std::vector<OrthancStone::CoordinateSystem3D>& planes, - OrthancStone::VolumeProjection projection, - boost::shared_ptr<OrthancStone::OrthancSeriesVolumeProgressiveLoader> ctLoader)*/ - - - std::vector<OrthancStone::CoordinateSystem3D> allPlanes; - - // let's gather all the possible cutting planes in a single struct - for (size_t i = 0; i < axialPlanes.size(); ++i) - allPlanes.push_back(axialPlanes[i]); - - for (size_t i = 0; i < coronalPlanes.size(); ++i) - allPlanes.push_back(coronalPlanes[i]); - - for (size_t i = 0; i < sagittalPlanes.size(); ++i) - allPlanes.push_back(sagittalPlanes[i]); - - for (size_t i = 0; i < normalContent->GetStructuresCount(); ++i) - { - std::cout << "Testing structure (" << i << "/" << normalContent->GetStructuresCount() << ")\n"; - Vector structureCenter1 = normalContent->GetStructureCenter(i); - const std::string& structureName1 = normalContent->GetStructureName(i); - const std::string& structureInterpretation1 = normalContent->GetStructureInterpretation(i); - Color structureColor1 = normalContent->GetStructureColor(i); - - Vector structureCenter2 = optimizedContent->GetStructureCenter(i); - const std::string& structureName2 = optimizedContent->GetStructureName(i); - const std::string& structureInterpretation2 = optimizedContent->GetStructureInterpretation(i); - Color structureColor2 = optimizedContent->GetStructureColor(i); - - EXPECT_NEAR(structureCenter1[0], structureCenter2[0], TOLERANCE); - EXPECT_NEAR(structureCenter1[1], structureCenter2[1], TOLERANCE); - EXPECT_NEAR(structureCenter1[2], structureCenter2[2], TOLERANCE); - - EXPECT_EQ(structureName1, structureName2); - EXPECT_EQ(structureInterpretation1, structureInterpretation2); - EXPECT_EQ(structureColor1.GetRed(), structureColor2.GetRed()); - EXPECT_EQ(structureColor1.GetGreen(), structureColor2.GetGreen()); - EXPECT_EQ(structureColor1.GetBlue(), structureColor2.GetBlue()); - - // "random" walk through the planes. Processing them all takes too long (~ 1 min) - for (size_t j = 0; j < allPlanes.size(); j += 37) - { - const OrthancStone::CoordinateSystem3D& plane = allPlanes[j]; - - std::vector< std::pair<Point2D, Point2D> > segments1; - std::vector< std::pair<Point2D, Point2D> > segments2; - - bool ok1 = normalContent->ProjectStructure(segments1, i, plane); - bool ok2 = optimizedContent->ProjectStructure(segments2, i, plane); - - // checks here - EXPECT_EQ(ok1, ok2); - EXPECT_EQ(segments1.size(), segments2.size()); - - for (size_t k = 0; k < segments1.size(); ++k) - { - EXPECT_NEAR(segments1[k].first.x, segments2[k].first.x, TOLERANCE); - EXPECT_NEAR(segments1[k].first.y, segments2[k].first.y, TOLERANCE); - EXPECT_NEAR(segments1[k].second.x, segments2[k].second.x, TOLERANCE); - EXPECT_NEAR(segments1[k].second.y, segments2[k].second.y, TOLERANCE); - } - } - } - - Exitialize(*loadersContext); -}
--- a/OrthancStone/UnitTestsSources/UnitTestsMain.cpp Tue Aug 11 13:28:40 2020 +0200 +++ b/OrthancStone/UnitTestsSources/UnitTestsMain.cpp Tue Aug 11 13:47:24 2020 +0200 @@ -27,7 +27,6 @@ #include "../Sources/Volumes/ImageBuffer3D.h" #include "../Sources/Loaders/LoaderCache.h" -#include <HttpClient.h> #include <Images/ImageProcessing.h> #include <Logging.h> #include <MultiThreading/SharedMessageQueue.h>