comparison OrthancStone/UnitTestsSources/TestStructureSet.cpp @ 1541:ae17c8c8838f

standalone compilation of unit tests
author Sebastien Jodogne <s.jodogne@gmail.com>
date Tue, 11 Aug 2020 13:47:24 +0200
parents 244ad1e4e76a
children
comparison
equal deleted inserted replaced
1540:e20a2381200d 1541:ae17c8c8838f
4266 #ifdef _MSC_VER 4266 #ifdef _MSC_VER
4267 #pragma endregion 4267 #pragma endregion
4268 #endif 4268 #endif
4269 // _MSC_VER 4269 // _MSC_VER
4270 4270
4271 /*
4272 these tests are single-threaded... no worries for old buggy compilers
4273 (I'm talking to YOU, cl.exe v100! And to your ancestors!)
4274 */
4275 static std::string& GetTestJson()
4276 {
4277 static const char* resultRaw = NULL;
4278 static std::string result;
4279 if (resultRaw == NULL)
4280 {
4281 std::stringstream sst;
4282
4283 sst << k_rtStruct_json00
4284 << k_rtStruct_json01
4285 << k_rtStruct_json02
4286 << k_rtStruct_json03
4287 << k_rtStruct_json04
4288 << k_rtStruct_json05
4289 << k_rtStruct_json06
4290 << k_rtStruct_json07
4291 << k_rtStruct_json08;
4292
4293 std::string wholeBody = sst.str();
4294 result.swap(wholeBody);
4295 resultRaw = result.c_str();
4296 }
4297 return result;
4298 }
4299 4271
4300 #define STONE_ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0])) 4272 #define STONE_ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
4301 4273
4302 #ifdef BGO_ENABLE_DICOMSTRUCTURESETLOADER2 4274 #ifdef BGO_ENABLE_DICOMSTRUCTURESETLOADER2
4303 4275
5412 const std::vector<DicomStructure2>& structures = structureSet.structures_; 5384 const std::vector<DicomStructure2>& structures = structureSet.structures_;
5413 } 5385 }
5414 5386
5415 #endif 5387 #endif
5416 // BGO_ENABLE_DICOMSTRUCTURESETLOADER2 5388 // BGO_ENABLE_DICOMSTRUCTURESETLOADER2
5417
5418 namespace
5419 {
5420 void Initialize(const char* orthancApiUrl, OrthancStone::ILoadersContext& loadersContext)
5421 {
5422 Orthanc::WebServiceParameters p;
5423
5424 OrthancStone::GenericLoadersContext& typedLoadersContext =
5425 dynamic_cast<OrthancStone::GenericLoadersContext&>(loadersContext);
5426 // Default is http://localhost:8042
5427 // Here's how you may change it
5428 p.SetUrl(orthancApiUrl);
5429 p.SetCredentials("orthanc", "orthanc");
5430 typedLoadersContext.SetOrthancParameters(p);
5431
5432 typedLoadersContext.StartOracle();
5433 }
5434
5435 void Exitialize(OrthancStone::ILoadersContext& loadersContext)
5436 {
5437 OrthancStone::GenericLoadersContext& typedLoadersContext =
5438 dynamic_cast<OrthancStone::GenericLoadersContext&>(loadersContext);
5439
5440 typedLoadersContext.StopOracle();
5441 }
5442
5443
5444 #if 0
5445 class TestObserver : public ObserverBase<TestObserver>
5446 {
5447 public:
5448 TestObserver() {};
5449
5450 virtual void Handle
5451
5452 };
5453 #endif
5454
5455 }
5456
5457 TEST(StructureSet, DISABLED_StructureSetLoader_injection_feature_2020_05_10)
5458 {
5459 namespace pt = boost::posix_time;
5460
5461 std::unique_ptr<OrthancStone::ILoadersContext> loadersContext(new OrthancStone::GenericLoadersContext(1,4,1));
5462 Initialize("http://localhost:8042/", *loadersContext);
5463
5464 boost::shared_ptr<DicomStructureSetLoader> loader = DicomStructureSetLoader::Create(*loadersContext);
5465
5466 // replace with Orthanc ID of an uploaded RTSTRUCT instance!
5467 loader->LoadInstanceFullVisibility("72c773ac-5059f2c4-2e6a9120-4fd4bca1-45701661");
5468
5469 bool bContinue(true);
5470
5471 pt::ptime initialTime = pt::second_clock::local_time();
5472
5473 while (bContinue)
5474 {
5475 bContinue = !loader->AreStructuresReady();
5476 boost::this_thread::sleep_for(boost::chrono::milliseconds(1000));
5477
5478 {
5479 pt::ptime nowTime = pt::second_clock::local_time();
5480 pt::time_duration diff = nowTime - initialTime;
5481 double seconds = static_cast<double>(diff.total_milliseconds()) * 0.001;
5482 std::cout << seconds << " seconds elapsed...\n";
5483 if (seconds > 30)
5484 {
5485 std::cout << "More than 30 seconds elapsed... Aborting test :(\n";
5486 //GTEST_FATAL_FAILURE_("More than 30 seconds elapsed... Aborting test :(");
5487 //bContinue = false;
5488 }
5489 }
5490 }
5491 }
5492
5493 class SliceProcessor :
5494 public OrthancStone::OrthancSeriesVolumeProgressiveLoader::ISlicePostProcessor,
5495 public OrthancStone::DicomStructureSetLoader::IInstanceLookupHandler
5496 {
5497 public:
5498 SliceProcessor(OrthancStone::DicomStructureSetLoader& structLoader) : structLoader_(structLoader)
5499 {
5500 }
5501
5502 virtual void ProcessCTDicomSlice(const Orthanc::DicomMap& instance) ORTHANC_OVERRIDE
5503 {
5504 std::string sopInstanceUid;
5505 if (!instance.LookupStringValue(sopInstanceUid, Orthanc::DICOM_TAG_SOP_INSTANCE_UID, false))
5506 {
5507 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat, "Missing SOPInstanceUID in a DICOM instance");
5508 }
5509 slicesDicom_[sopInstanceUid] = boost::shared_ptr<DicomMap>(instance.Clone());
5510 }
5511
5512 virtual void RetrieveReferencedSlices(const std::set<std::string>& nonEmptyInstances) ORTHANC_OVERRIDE
5513 {
5514 for (std::set<std::string>::const_iterator it = nonEmptyInstances.begin();
5515 it != nonEmptyInstances.end();
5516 ++it)
5517 {
5518 const std::string nonEmptyInstance = *it;
5519 if (slicesDicom_.find(nonEmptyInstance) == slicesDicom_.end())
5520 {
5521 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat, "Referenced SOPInstanceUID not found in CT");
5522 }
5523 boost::shared_ptr<Orthanc::DicomMap> instance = slicesDicom_[nonEmptyInstance];
5524 structLoader_.AddReferencedSlice(*instance);
5525 }
5526 }
5527
5528 OrthancStone::DicomStructureSetLoader& structLoader_;
5529 std::map<std::string, boost::shared_ptr<Orthanc::DicomMap> > slicesDicom_;
5530 };
5531
5532 void LoadCtSeriesBlocking(boost::shared_ptr<OrthancStone::OrthancSeriesVolumeProgressiveLoader> ctLoader, std::string seriesId)
5533 {
5534 namespace pt = boost::posix_time;
5535
5536 // Load the CT
5537 ctLoader->LoadSeries(seriesId);
5538
5539 // Wait for CT to be loaded
5540 pt::ptime initialTime = pt::second_clock::local_time();
5541 {
5542 bool bContinue(true);
5543 while (bContinue)
5544 {
5545 bContinue = !ctLoader->IsVolumeImageReadyInHighQuality();
5546 boost::this_thread::sleep_for(boost::chrono::milliseconds(1000));
5547
5548 {
5549 pt::ptime nowTime = pt::second_clock::local_time();
5550 pt::time_duration diff = nowTime - initialTime;
5551 double seconds = static_cast<double>(diff.total_milliseconds()) * 0.001;
5552 std::cout << seconds << " seconds elapsed...\n";
5553 if (seconds > 30)
5554 {
5555 const char* msg = "More than 30 seconds elapsed when waiting for CT... Aborting test :(\n";
5556 GTEST_FATAL_FAILURE_(msg);
5557 bContinue = false;
5558 }
5559 }
5560 }
5561 }
5562 }
5563
5564
5565 /**
5566 Will fill planes
5567 */
5568 void GetCTPlanes(std::vector<OrthancStone::CoordinateSystem3D>& planes,
5569 OrthancStone::VolumeProjection projection,
5570 boost::shared_ptr<OrthancStone::OrthancSeriesVolumeProgressiveLoader> ctLoader)
5571 {
5572 planes.clear(); // inefficient : we don't care
5573
5574 const VolumeImageGeometry& geometry = ctLoader->GetImageGeometry();
5575 const unsigned int depth = geometry.GetProjectionDepth(projection);
5576
5577 planes.resize(depth);
5578
5579 for (unsigned int z = 0; z < depth; z++)
5580 {
5581 planes[z] = geometry.GetProjectionSlice(projection, z);
5582 }
5583 }
5584
5585 void LoadRtStructBlocking(boost::shared_ptr<OrthancStone::DicomStructureSetLoader> structLoader, std::string instanceId)
5586 {
5587 namespace pt = boost::posix_time;
5588
5589 // Load RTSTRUCT
5590 structLoader->LoadInstanceFullVisibility(instanceId);
5591
5592 pt::ptime initialTime = pt::second_clock::local_time();
5593
5594 // Wait for the loading process to complete
5595 {
5596 bool bContinue(true);
5597 while (bContinue)
5598 {
5599 bContinue = !structLoader->AreStructuresReady();
5600 boost::this_thread::sleep_for(boost::chrono::milliseconds(1000));
5601
5602 {
5603 pt::ptime nowTime = pt::second_clock::local_time();
5604 pt::time_duration diff = nowTime - initialTime;
5605 double seconds = static_cast<double>(diff.total_milliseconds()) * 0.001;
5606 std::cout << seconds << " seconds elapsed...\n";
5607 if (seconds > 30)
5608 {
5609 const char* msg = "More than 30 seconds elapsed when waiting for RTSTRUCT... Aborting test :(\n";
5610 GTEST_FATAL_FAILURE_(msg);
5611 bContinue = false;
5612 }
5613 }
5614 }
5615 }
5616 }
5617
5618 TEST(StructureSet, DISABLED_Integration_Compound_CT_Struct_Loading)
5619 {
5620 const double TOLERANCE = 0.0000001;
5621
5622 // create loaders context
5623 std::unique_ptr<OrthancStone::ILoadersContext> loadersContext(new OrthancStone::GenericLoadersContext(1,4,1));
5624 Initialize("http://localhost:8042/", *loadersContext);
5625
5626 const char* ctSeriesId = "a04ecf01-79b2fc33-58239f7e-ad9db983-28e81afa";
5627 const char* rtStructInstanceId = "54460695-ba3885ee-ddf61ac0-f028e31d-a6e474d9";
5628
5629 // we'll compare normal loading and optimized loading with SliceProcessor to store the dicom
5630
5631 boost::shared_ptr<OrthancStone::DicomStructureSetLoader> normalStructLoader;
5632 boost::shared_ptr<OrthancStone::DicomStructureSetLoader> optimizedStructLoader;
5633
5634 {
5635 // Create the CT volume
5636 boost::shared_ptr<OrthancStone::DicomVolumeImage> volume = boost::make_shared<OrthancStone::DicomVolumeImage>();
5637
5638 // Create CT loader
5639 boost::shared_ptr<OrthancStone::OrthancSeriesVolumeProgressiveLoader> ctLoader =
5640 OrthancStone::OrthancSeriesVolumeProgressiveLoader::Create(*loadersContext, volume);
5641
5642 // Create struct loader
5643 normalStructLoader = OrthancStone::DicomStructureSetLoader::Create(*loadersContext);
5644
5645 // Load the CT
5646 LoadCtSeriesBlocking(ctLoader, ctSeriesId);
5647
5648 const OrthancStone::VolumeImageGeometry& imageGeometry = ctLoader->GetImageGeometry();
5649 unsigned int width = imageGeometry.GetWidth();
5650 EXPECT_EQ(512u, width);
5651 unsigned int height = imageGeometry.GetHeight();
5652 EXPECT_EQ(512u, height);
5653 unsigned int depth = imageGeometry.GetDepth();
5654 EXPECT_EQ(109u, depth);
5655
5656 // Load the RTStruct
5657 LoadRtStructBlocking(normalStructLoader, rtStructInstanceId);
5658 }
5659
5660 std::vector<OrthancStone::CoordinateSystem3D> axialPlanes;
5661 std::vector<OrthancStone::CoordinateSystem3D> coronalPlanes;
5662 std::vector<OrthancStone::CoordinateSystem3D> sagittalPlanes;
5663
5664 {
5665 // Create the CT volume
5666 boost::shared_ptr<OrthancStone::DicomVolumeImage> volume = boost::make_shared<OrthancStone::DicomVolumeImage>();
5667
5668 // Create CT loader
5669 boost::shared_ptr<OrthancStone::OrthancSeriesVolumeProgressiveLoader> ctLoader =
5670 OrthancStone::OrthancSeriesVolumeProgressiveLoader::Create(*loadersContext, volume);
5671
5672 // Create struct loader
5673 optimizedStructLoader = OrthancStone::DicomStructureSetLoader::Create(*loadersContext);
5674
5675 // create the slice processor / instance lookup
5676 boost::shared_ptr<SliceProcessor> sliceProcessor(new SliceProcessor(*optimizedStructLoader));
5677
5678 // Inject it into CT loader
5679 ctLoader->SetDicomSlicePostProcessor(sliceProcessor);
5680
5681 // Inject it into RTSTRUCT loader
5682 optimizedStructLoader->SetInstanceLookupHandler(sliceProcessor);
5683
5684 // Load the CT
5685 LoadCtSeriesBlocking(ctLoader, ctSeriesId);
5686
5687 // now, the slices are collected. let's do some checks
5688 EXPECT_EQ(109u, sliceProcessor->slicesDicom_.size());
5689
5690 // Load the RTStruct
5691 LoadRtStructBlocking(optimizedStructLoader, rtStructInstanceId);
5692
5693 GetCTPlanes(axialPlanes, VolumeProjection_Axial, ctLoader);
5694 GetCTPlanes(coronalPlanes, VolumeProjection_Coronal, ctLoader);
5695 GetCTPlanes(sagittalPlanes, VolumeProjection_Sagittal, ctLoader);
5696 }
5697
5698 // DO NOT DELETE THOSE!
5699 OrthancStone::DicomStructureSet* normalContent = normalStructLoader->GetContent();
5700 OrthancStone::DicomStructureSet* optimizedContent = optimizedStructLoader->GetContent();
5701
5702 EXPECT_EQ(normalContent->GetStructuresCount(), optimizedContent->GetStructuresCount());
5703
5704 /*void GetCTPlanes(std::vector<OrthancStone::CoordinateSystem3D>& planes,
5705 OrthancStone::VolumeProjection projection,
5706 boost::shared_ptr<OrthancStone::OrthancSeriesVolumeProgressiveLoader> ctLoader)*/
5707
5708
5709 std::vector<OrthancStone::CoordinateSystem3D> allPlanes;
5710
5711 // let's gather all the possible cutting planes in a single struct
5712 for (size_t i = 0; i < axialPlanes.size(); ++i)
5713 allPlanes.push_back(axialPlanes[i]);
5714
5715 for (size_t i = 0; i < coronalPlanes.size(); ++i)
5716 allPlanes.push_back(coronalPlanes[i]);
5717
5718 for (size_t i = 0; i < sagittalPlanes.size(); ++i)
5719 allPlanes.push_back(sagittalPlanes[i]);
5720
5721 for (size_t i = 0; i < normalContent->GetStructuresCount(); ++i)
5722 {
5723 std::cout << "Testing structure (" << i << "/" << normalContent->GetStructuresCount() << ")\n";
5724 Vector structureCenter1 = normalContent->GetStructureCenter(i);
5725 const std::string& structureName1 = normalContent->GetStructureName(i);
5726 const std::string& structureInterpretation1 = normalContent->GetStructureInterpretation(i);
5727 Color structureColor1 = normalContent->GetStructureColor(i);
5728
5729 Vector structureCenter2 = optimizedContent->GetStructureCenter(i);
5730 const std::string& structureName2 = optimizedContent->GetStructureName(i);
5731 const std::string& structureInterpretation2 = optimizedContent->GetStructureInterpretation(i);
5732 Color structureColor2 = optimizedContent->GetStructureColor(i);
5733
5734 EXPECT_NEAR(structureCenter1[0], structureCenter2[0], TOLERANCE);
5735 EXPECT_NEAR(structureCenter1[1], structureCenter2[1], TOLERANCE);
5736 EXPECT_NEAR(structureCenter1[2], structureCenter2[2], TOLERANCE);
5737
5738 EXPECT_EQ(structureName1, structureName2);
5739 EXPECT_EQ(structureInterpretation1, structureInterpretation2);
5740 EXPECT_EQ(structureColor1.GetRed(), structureColor2.GetRed());
5741 EXPECT_EQ(structureColor1.GetGreen(), structureColor2.GetGreen());
5742 EXPECT_EQ(structureColor1.GetBlue(), structureColor2.GetBlue());
5743
5744 // "random" walk through the planes. Processing them all takes too long (~ 1 min)
5745 for (size_t j = 0; j < allPlanes.size(); j += 37)
5746 {
5747 const OrthancStone::CoordinateSystem3D& plane = allPlanes[j];
5748
5749 std::vector< std::pair<Point2D, Point2D> > segments1;
5750 std::vector< std::pair<Point2D, Point2D> > segments2;
5751
5752 bool ok1 = normalContent->ProjectStructure(segments1, i, plane);
5753 bool ok2 = optimizedContent->ProjectStructure(segments2, i, plane);
5754
5755 // checks here
5756 EXPECT_EQ(ok1, ok2);
5757 EXPECT_EQ(segments1.size(), segments2.size());
5758
5759 for (size_t k = 0; k < segments1.size(); ++k)
5760 {
5761 EXPECT_NEAR(segments1[k].first.x, segments2[k].first.x, TOLERANCE);
5762 EXPECT_NEAR(segments1[k].first.y, segments2[k].first.y, TOLERANCE);
5763 EXPECT_NEAR(segments1[k].second.x, segments2[k].second.x, TOLERANCE);
5764 EXPECT_NEAR(segments1[k].second.y, segments2[k].second.y, TOLERANCE);
5765 }
5766 }
5767 }
5768
5769 Exitialize(*loadersContext);
5770 }