# HG changeset patch # User Benjamin Golinvaux # Date 1590505954 -7200 # Node ID 04d0c25819c34dd4458b8c047fc44d25375122f9 # Parent 687457d4018f0ef664e64056318f1cfc9e2691f2 Added integration test for optimized CT / RTSTRUCT loading diff -r 687457d4018f -r 04d0c25819c3 UnitTestsSources/TestStructureSet.cpp --- a/UnitTestsSources/TestStructureSet.cpp Tue May 26 12:11:18 2020 +0200 +++ b/UnitTestsSources/TestStructureSet.cpp Tue May 26 17:12:34 2020 +0200 @@ -34,6 +34,7 @@ #include "Framework/Loaders/GenericLoadersContext.h" #include "Framework/Loaders/DicomStructureSetLoader.h" +#include "Framework/Loaders/OrthancSeriesVolumeProgressiveLoader.h" #include "boost/date_time/posix_time/posix_time.hpp" @@ -5453,7 +5454,6 @@ } - TEST(StructureSet, DISABLED_StructureSetLoader_injection_feature_2020_05_10) { namespace pt = boost::posix_time; @@ -5490,4 +5490,209 @@ } } - +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(instance.Clone()); + } + + virtual void RetrieveReferencedSlices(const std::set& nonEmptyInstances) ORTHANC_OVERRIDE + { + for (std::set::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 instance = slicesDicom_[nonEmptyInstance]; + structLoader_.AddReferencedSlice(*instance); + } + } + + OrthancStone::DicomStructureSetLoader& structLoader_; + std::map > slicesDicom_; +}; + +void LoadCtSeriesBlocking(boost::shared_ptr 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(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; + } + } + } + } +} + + +void LoadRtStructBlocking(boost::shared_ptr 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(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 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 normalStructLoader; + boost::shared_ptr optimizedStructLoader; + + { + // Create the CT volume + boost::shared_ptr volume = boost::make_shared(); + + // Create CT loader + boost::shared_ptr 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); + } + + { + // Create the CT volume + boost::shared_ptr volume = boost::make_shared(); + + // Create CT loader + boost::shared_ptr ctLoader = + OrthancStone::OrthancSeriesVolumeProgressiveLoader::Create(*loadersContext, volume); + + // Create struct loader + optimizedStructLoader = OrthancStone::DicomStructureSetLoader::Create(*loadersContext); + + // create the slice processor / instance lookup + boost::shared_ptr sliceProcessor = boost::make_shared(*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); + } + + // DO NOT DELETE THOSE! + OrthancStone::DicomStructureSet* normalContent = normalStructLoader->GetContent(); + OrthancStone::DicomStructureSet* optimizedContent = optimizedStructLoader->GetContent(); + + EXPECT_EQ(normalContent->GetStructuresCount(), optimizedContent->GetStructuresCount()); + + for (size_t i = 0; i < normalContent->GetStructuresCount(); ++i) + { + 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()); + } +}