comparison OrthancStone/UnitTestsSources/Graveyard/TestStructureSet_BGO.cpp @ 1877:a2955abe4c2e

skeleton for the RenderingPlugin
author Sebastien Jodogne <s.jodogne@gmail.com>
date Wed, 12 Jan 2022 08:23:38 +0100
parents UnitTestsSources/Graveyard/TestStructureSet_BGO.cpp@7053b8a0aaec
children 07964689cb0b
comparison
equal deleted inserted replaced
1876:b1f510e601d2 1877:a2955abe4c2e
1 /**
2 * Stone of Orthanc
3 * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
4 * Department, University Hospital of Liege, Belgium
5 * Copyright (C) 2017-2022 Osimis S.A., Belgium
6 * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
7 *
8 * This program is free software: you can redistribute it and/or
9 * modify it under the terms of the GNU Lesser General Public License
10 * as published by the Free Software Foundation, either version 3 of
11 * the License, or (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful, but
14 * WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * Lesser General Public License for more details.
17 *
18 * You should have received a copy of the GNU Lesser General Public
19 * License along with this program. If not, see
20 * <http://www.gnu.org/licenses/>.
21 **/
22
23
24 /*
25 these tests are single-threaded... no worries for old buggy compilers
26 (I'm talking to YOU, cl.exe v100! And to your ancestors!)
27 */
28 static std::string& GetTestJson()
29 {
30 static const char* resultRaw = NULL;
31 static std::string result;
32 if (resultRaw == NULL)
33 {
34 std::stringstream sst;
35
36 sst << k_rtStruct_json00
37 << k_rtStruct_json01
38 << k_rtStruct_json02
39 << k_rtStruct_json03
40 << k_rtStruct_json04
41 << k_rtStruct_json05
42 << k_rtStruct_json06
43 << k_rtStruct_json07
44 << k_rtStruct_json08;
45
46 std::string wholeBody = sst.str();
47 result.swap(wholeBody);
48 resultRaw = result.c_str();
49 }
50 return result;
51 }
52
53
54 namespace
55 {
56 void Initialize(const char* orthancApiUrl, OrthancStone::ILoadersContext& loadersContext)
57 {
58 Orthanc::WebServiceParameters p;
59
60 OrthancStone::GenericLoadersContext& typedLoadersContext =
61 dynamic_cast<OrthancStone::GenericLoadersContext&>(loadersContext);
62 // Default is http://localhost:8042
63 // Here's how you may change it
64 p.SetUrl(orthancApiUrl);
65 p.SetCredentials("orthanc", "orthanc");
66 typedLoadersContext.SetOrthancParameters(p);
67
68 typedLoadersContext.StartOracle();
69 }
70
71 void Exitialize(OrthancStone::ILoadersContext& loadersContext)
72 {
73 OrthancStone::GenericLoadersContext& typedLoadersContext =
74 dynamic_cast<OrthancStone::GenericLoadersContext&>(loadersContext);
75
76 typedLoadersContext.StopOracle();
77 }
78
79
80 #if 0
81 class TestObserver : public ObserverBase<TestObserver>
82 {
83 public:
84 TestObserver() {};
85
86 virtual void Handle
87
88 };
89 #endif
90
91 }
92
93 TEST(StructureSet, DISABLED_StructureSetLoader_injection_feature_2020_05_10)
94 {
95 namespace pt = boost::posix_time;
96
97 std::unique_ptr<OrthancStone::ILoadersContext> loadersContext(new OrthancStone::GenericLoadersContext(1,4,1));
98 Initialize("http://localhost:8042/", *loadersContext);
99
100 boost::shared_ptr<DicomStructureSetLoader> loader = DicomStructureSetLoader::Create(*loadersContext);
101
102 // replace with Orthanc ID of an uploaded RTSTRUCT instance!
103 loader->LoadInstanceFullVisibility("72c773ac-5059f2c4-2e6a9120-4fd4bca1-45701661");
104
105 bool bContinue(true);
106
107 pt::ptime initialTime = pt::second_clock::local_time();
108
109 while (bContinue)
110 {
111 bContinue = !loader->AreStructuresReady();
112 boost::this_thread::sleep_for(boost::chrono::milliseconds(1000));
113
114 {
115 pt::ptime nowTime = pt::second_clock::local_time();
116 pt::time_duration diff = nowTime - initialTime;
117 double seconds = static_cast<double>(diff.total_milliseconds()) * 0.001;
118 std::cout << seconds << " seconds elapsed...\n";
119 if (seconds > 30)
120 {
121 std::cout << "More than 30 seconds elapsed... Aborting test :(\n";
122 //GTEST_FATAL_FAILURE_("More than 30 seconds elapsed... Aborting test :(");
123 //bContinue = false;
124 }
125 }
126 }
127 }
128
129 class SliceProcessor :
130 public OrthancStone::OrthancSeriesVolumeProgressiveLoader::ISlicePostProcessor,
131 public OrthancStone::DicomStructureSetLoader::IInstanceLookupHandler
132 {
133 public:
134 SliceProcessor(OrthancStone::DicomStructureSetLoader& structLoader) : structLoader_(structLoader)
135 {
136 }
137
138 virtual void ProcessCTDicomSlice(const Orthanc::DicomMap& instance) ORTHANC_OVERRIDE
139 {
140 std::string sopInstanceUid;
141 if (!instance.LookupStringValue(sopInstanceUid, Orthanc::DICOM_TAG_SOP_INSTANCE_UID, false))
142 {
143 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat, "Missing SOPInstanceUID in a DICOM instance");
144 }
145 slicesDicom_[sopInstanceUid] = boost::shared_ptr<DicomMap>(instance.Clone());
146 }
147
148 virtual void RetrieveReferencedSlices(const std::set<std::string>& nonEmptyInstances) ORTHANC_OVERRIDE
149 {
150 for (std::set<std::string>::const_iterator it = nonEmptyInstances.begin();
151 it != nonEmptyInstances.end();
152 ++it)
153 {
154 const std::string nonEmptyInstance = *it;
155 if (slicesDicom_.find(nonEmptyInstance) == slicesDicom_.end())
156 {
157 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat, "Referenced SOPInstanceUID not found in CT");
158 }
159 boost::shared_ptr<Orthanc::DicomMap> instance = slicesDicom_[nonEmptyInstance];
160 structLoader_.AddReferencedSlice(*instance);
161 }
162 }
163
164 OrthancStone::DicomStructureSetLoader& structLoader_;
165 std::map<std::string, boost::shared_ptr<Orthanc::DicomMap> > slicesDicom_;
166 };
167
168 void LoadCtSeriesBlocking(boost::shared_ptr<OrthancStone::OrthancSeriesVolumeProgressiveLoader> ctLoader, std::string seriesId)
169 {
170 namespace pt = boost::posix_time;
171
172 // Load the CT
173 ctLoader->LoadSeries(seriesId);
174
175 // Wait for CT to be loaded
176 pt::ptime initialTime = pt::second_clock::local_time();
177 {
178 bool bContinue(true);
179 while (bContinue)
180 {
181 bContinue = !ctLoader->IsVolumeImageReadyInHighQuality();
182 boost::this_thread::sleep_for(boost::chrono::milliseconds(1000));
183
184 {
185 pt::ptime nowTime = pt::second_clock::local_time();
186 pt::time_duration diff = nowTime - initialTime;
187 double seconds = static_cast<double>(diff.total_milliseconds()) * 0.001;
188 std::cout << seconds << " seconds elapsed...\n";
189 if (seconds > 30)
190 {
191 const char* msg = "More than 30 seconds elapsed when waiting for CT... Aborting test :(\n";
192 GTEST_FATAL_FAILURE_(msg);
193 bContinue = false;
194 }
195 }
196 }
197 }
198 }
199
200
201 /**
202 Will fill planes
203 */
204 void GetCTPlanes(std::vector<OrthancStone::CoordinateSystem3D>& planes,
205 OrthancStone::VolumeProjection projection,
206 boost::shared_ptr<OrthancStone::OrthancSeriesVolumeProgressiveLoader> ctLoader)
207 {
208 planes.clear(); // inefficient : we don't care
209
210 const VolumeImageGeometry& geometry = ctLoader->GetImageGeometry();
211 const unsigned int depth = geometry.GetProjectionDepth(projection);
212
213 planes.resize(depth);
214
215 for (unsigned int z = 0; z < depth; z++)
216 {
217 planes[z] = geometry.GetProjectionSlice(projection, z);
218 }
219 }
220
221 void LoadRtStructBlocking(boost::shared_ptr<OrthancStone::DicomStructureSetLoader> structLoader, std::string instanceId)
222 {
223 namespace pt = boost::posix_time;
224
225 // Load RTSTRUCT
226 structLoader->LoadInstanceFullVisibility(instanceId);
227
228 pt::ptime initialTime = pt::second_clock::local_time();
229
230 // Wait for the loading process to complete
231 {
232 bool bContinue(true);
233 while (bContinue)
234 {
235 bContinue = !structLoader->AreStructuresReady();
236 boost::this_thread::sleep_for(boost::chrono::milliseconds(1000));
237
238 {
239 pt::ptime nowTime = pt::second_clock::local_time();
240 pt::time_duration diff = nowTime - initialTime;
241 double seconds = static_cast<double>(diff.total_milliseconds()) * 0.001;
242 std::cout << seconds << " seconds elapsed...\n";
243 if (seconds > 30)
244 {
245 const char* msg = "More than 30 seconds elapsed when waiting for RTSTRUCT... Aborting test :(\n";
246 GTEST_FATAL_FAILURE_(msg);
247 bContinue = false;
248 }
249 }
250 }
251 }
252 }
253
254 TEST(StructureSet, DISABLED_Integration_Compound_CT_Struct_Loading)
255 {
256 const double TOLERANCE = 0.0000001;
257
258 // create loaders context
259 std::unique_ptr<OrthancStone::ILoadersContext> loadersContext(new OrthancStone::GenericLoadersContext(1,4,1));
260 Initialize("http://localhost:8042/", *loadersContext);
261
262 const char* ctSeriesId = "a04ecf01-79b2fc33-58239f7e-ad9db983-28e81afa";
263 const char* rtStructInstanceId = "54460695-ba3885ee-ddf61ac0-f028e31d-a6e474d9";
264
265 // we'll compare normal loading and optimized loading with SliceProcessor to store the dicom
266
267 boost::shared_ptr<OrthancStone::DicomStructureSetLoader> normalStructLoader;
268 boost::shared_ptr<OrthancStone::DicomStructureSetLoader> optimizedStructLoader;
269
270 {
271 // Create the CT volume
272 boost::shared_ptr<OrthancStone::DicomVolumeImage> volume = boost::make_shared<OrthancStone::DicomVolumeImage>();
273
274 // Create CT loader
275 boost::shared_ptr<OrthancStone::OrthancSeriesVolumeProgressiveLoader> ctLoader =
276 OrthancStone::OrthancSeriesVolumeProgressiveLoader::Create(*loadersContext, volume);
277
278 // Create struct loader
279 normalStructLoader = OrthancStone::DicomStructureSetLoader::Create(*loadersContext);
280
281 // Load the CT
282 LoadCtSeriesBlocking(ctLoader, ctSeriesId);
283
284 const OrthancStone::VolumeImageGeometry& imageGeometry = ctLoader->GetImageGeometry();
285 unsigned int width = imageGeometry.GetWidth();
286 EXPECT_EQ(512u, width);
287 unsigned int height = imageGeometry.GetHeight();
288 EXPECT_EQ(512u, height);
289 unsigned int depth = imageGeometry.GetDepth();
290 EXPECT_EQ(109u, depth);
291
292 // Load the RTStruct
293 LoadRtStructBlocking(normalStructLoader, rtStructInstanceId);
294 }
295
296 std::vector<OrthancStone::CoordinateSystem3D> axialPlanes;
297 std::vector<OrthancStone::CoordinateSystem3D> coronalPlanes;
298 std::vector<OrthancStone::CoordinateSystem3D> sagittalPlanes;
299
300 {
301 // Create the CT volume
302 boost::shared_ptr<OrthancStone::DicomVolumeImage> volume = boost::make_shared<OrthancStone::DicomVolumeImage>();
303
304 // Create CT loader
305 boost::shared_ptr<OrthancStone::OrthancSeriesVolumeProgressiveLoader> ctLoader =
306 OrthancStone::OrthancSeriesVolumeProgressiveLoader::Create(*loadersContext, volume);
307
308 // Create struct loader
309 optimizedStructLoader = OrthancStone::DicomStructureSetLoader::Create(*loadersContext);
310
311 // create the slice processor / instance lookup
312 boost::shared_ptr<SliceProcessor> sliceProcessor(new SliceProcessor(*optimizedStructLoader));
313
314 // Inject it into CT loader
315 ctLoader->SetDicomSlicePostProcessor(sliceProcessor);
316
317 // Inject it into RTSTRUCT loader
318 optimizedStructLoader->SetInstanceLookupHandler(sliceProcessor);
319
320 // Load the CT
321 LoadCtSeriesBlocking(ctLoader, ctSeriesId);
322
323 // now, the slices are collected. let's do some checks
324 EXPECT_EQ(109u, sliceProcessor->slicesDicom_.size());
325
326 // Load the RTStruct
327 LoadRtStructBlocking(optimizedStructLoader, rtStructInstanceId);
328
329 GetCTPlanes(axialPlanes, VolumeProjection_Axial, ctLoader);
330 GetCTPlanes(coronalPlanes, VolumeProjection_Coronal, ctLoader);
331 GetCTPlanes(sagittalPlanes, VolumeProjection_Sagittal, ctLoader);
332 }
333
334 // DO NOT DELETE THOSE!
335 OrthancStone::DicomStructureSet* normalContent = normalStructLoader->GetContent();
336 OrthancStone::DicomStructureSet* optimizedContent = optimizedStructLoader->GetContent();
337
338 EXPECT_EQ(normalContent->GetStructuresCount(), optimizedContent->GetStructuresCount());
339
340 /*void GetCTPlanes(std::vector<OrthancStone::CoordinateSystem3D>& planes,
341 OrthancStone::VolumeProjection projection,
342 boost::shared_ptr<OrthancStone::OrthancSeriesVolumeProgressiveLoader> ctLoader)*/
343
344
345 std::vector<OrthancStone::CoordinateSystem3D> allPlanes;
346
347 // let's gather all the possible cutting planes in a single struct
348 for (size_t i = 0; i < axialPlanes.size(); ++i)
349 allPlanes.push_back(axialPlanes[i]);
350
351 for (size_t i = 0; i < coronalPlanes.size(); ++i)
352 allPlanes.push_back(coronalPlanes[i]);
353
354 for (size_t i = 0; i < sagittalPlanes.size(); ++i)
355 allPlanes.push_back(sagittalPlanes[i]);
356
357 for (size_t i = 0; i < normalContent->GetStructuresCount(); ++i)
358 {
359 std::cout << "Testing structure (" << i << "/" << normalContent->GetStructuresCount() << ")\n";
360 Vector structureCenter1 = normalContent->GetStructureCenter(i);
361 const std::string& structureName1 = normalContent->GetStructureName(i);
362 const std::string& structureInterpretation1 = normalContent->GetStructureInterpretation(i);
363 Color structureColor1 = normalContent->GetStructureColor(i);
364
365 Vector structureCenter2 = optimizedContent->GetStructureCenter(i);
366 const std::string& structureName2 = optimizedContent->GetStructureName(i);
367 const std::string& structureInterpretation2 = optimizedContent->GetStructureInterpretation(i);
368 Color structureColor2 = optimizedContent->GetStructureColor(i);
369
370 EXPECT_NEAR(structureCenter1[0], structureCenter2[0], TOLERANCE);
371 EXPECT_NEAR(structureCenter1[1], structureCenter2[1], TOLERANCE);
372 EXPECT_NEAR(structureCenter1[2], structureCenter2[2], TOLERANCE);
373
374 EXPECT_EQ(structureName1, structureName2);
375 EXPECT_EQ(structureInterpretation1, structureInterpretation2);
376 EXPECT_EQ(structureColor1.GetRed(), structureColor2.GetRed());
377 EXPECT_EQ(structureColor1.GetGreen(), structureColor2.GetGreen());
378 EXPECT_EQ(structureColor1.GetBlue(), structureColor2.GetBlue());
379
380 // "random" walk through the planes. Processing them all takes too long (~ 1 min)
381 for (size_t j = 0; j < allPlanes.size(); j += 37)
382 {
383 const OrthancStone::CoordinateSystem3D& plane = allPlanes[j];
384
385 std::vector< std::pair<Point2D, Point2D> > segments1;
386 std::vector< std::pair<Point2D, Point2D> > segments2;
387
388 bool ok1 = normalContent->ProjectStructure(segments1, i, plane);
389 bool ok2 = optimizedContent->ProjectStructure(segments2, i, plane);
390
391 // checks here
392 EXPECT_EQ(ok1, ok2);
393 EXPECT_EQ(segments1.size(), segments2.size());
394
395 for (size_t k = 0; k < segments1.size(); ++k)
396 {
397 EXPECT_NEAR(segments1[k].first.x, segments2[k].first.x, TOLERANCE);
398 EXPECT_NEAR(segments1[k].first.y, segments2[k].first.y, TOLERANCE);
399 EXPECT_NEAR(segments1[k].second.x, segments2[k].second.x, TOLERANCE);
400 EXPECT_NEAR(segments1[k].second.y, segments2[k].second.y, TOLERANCE);
401 }
402 }
403 }
404
405 Exitialize(*loadersContext);
406 }