Mercurial > hg > orthanc-stone
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 } |