Mercurial > hg > orthanc-stone
comparison UnitTestsSources/UnitTestsMain.cpp @ 67:acb60cbb8301 wasm
refactoring SeriesLoader
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Thu, 18 May 2017 17:51:41 +0200 |
parents | 298f375dcb68 |
children | 1526d38ef6da |
comparison
equal
deleted
inserted
replaced
66:298f375dcb68 | 67:acb60cbb8301 |
---|---|
25 #include "../Framework/Toolbox/OrthancWebService.h" | 25 #include "../Framework/Toolbox/OrthancWebService.h" |
26 #include "../Framework/Layers/OrthancFrameLayerSource.h" | 26 #include "../Framework/Layers/OrthancFrameLayerSource.h" |
27 #include "../Framework/Widgets/LayerWidget.h" | 27 #include "../Framework/Widgets/LayerWidget.h" |
28 | 28 |
29 | 29 |
30 #include "../Framework/Toolbox/MessagingToolbox.h" | |
31 #include "../Framework/Toolbox/DicomFrameConverter.h" | |
32 | |
30 namespace OrthancStone | 33 namespace OrthancStone |
31 { | 34 { |
32 class Tata : public ILayerSource::IObserver | 35 class SeriesLoader : |
33 { | 36 public IWebService::ICallback // TODO move to PImpl |
34 public: | |
35 virtual void NotifySourceChange(ILayerSource& source) | |
36 { | |
37 printf("Source change\n"); | |
38 | |
39 OrthancStone::SliceGeometry slice; | |
40 double x1, y1, x2, y2; | |
41 printf(">> %d: ", source.GetExtent(x1, y1, x2, y2, slice)); | |
42 printf("(%f,%f) (%f,%f)\n", x1, y1, x2, y2); | |
43 } | |
44 | |
45 virtual void NotifySliceChange(ILayerSource& source, | |
46 const SliceGeometry& slice) | |
47 { | |
48 printf("Slice change\n"); | |
49 } | |
50 | |
51 virtual void NotifyLayerReady(ILayerRenderer* layer, | |
52 ILayerSource& source, | |
53 const SliceGeometry& viewportSlice) | |
54 { | |
55 std::auto_ptr<ILayerRenderer> tmp(layer); | |
56 | |
57 } | |
58 | |
59 virtual void NotifyLayerError(ILayerSource& source, | |
60 const SliceGeometry& viewportSlice) | |
61 { | |
62 } | |
63 }; | |
64 | |
65 | |
66 | |
67 /*class OrthancInstanceLoader : public IWebService::ICallback | |
68 { | 37 { |
69 public: | 38 public: |
70 class ICallback : public boost::noncopyable | 39 class ICallback : public boost::noncopyable |
71 { | 40 { |
72 public: | 41 public: |
73 virtual ~ICallback() | 42 virtual ~ICallback() |
74 { | 43 { |
75 } | 44 } |
45 | |
46 virtual void NotifyGeometryReady(const SeriesLoader& series) = 0; | |
47 | |
48 virtual void NotifyGeometryError(const SeriesLoader& series) = 0; | |
49 }; | |
50 | |
51 private: | |
52 class Slice : public boost::noncopyable | |
53 { | |
54 private: | |
55 std::string instanceId_; | |
56 SliceGeometry geometry_; | |
57 double thickness_; | |
58 unsigned int width_; | |
59 unsigned int height_; | |
60 double projectionAlongNormal_; | |
61 DicomFrameConverter converter_; | |
76 | 62 |
77 virtual void NotifyInstanceLoaded(const std::string& instanceId, | 63 public: |
78 const OrthancPlugins::FullOrthancDataset& dicom) = 0; | 64 Slice(const std::string& instanceId, |
65 const std::string& imagePositionPatient, | |
66 const std::string& imageOrientationPatient, | |
67 double thickness, | |
68 unsigned int width, | |
69 unsigned int height, | |
70 const OrthancPlugins::IDicomDataset& dataset) : | |
71 instanceId_(instanceId), | |
72 geometry_(imagePositionPatient, imageOrientationPatient), | |
73 thickness_(thickness), | |
74 width_(width), | |
75 height_(height) | |
76 { | |
77 converter_.ReadParameters(dataset); | |
78 } | |
79 | |
80 const std::string GetInstanceId() const | |
81 { | |
82 return instanceId_; | |
83 } | |
84 | |
85 const SliceGeometry& GetGeometry() const | |
86 { | |
87 return geometry_; | |
88 } | |
89 | |
90 double GetThickness() const | |
91 { | |
92 return thickness_; | |
93 } | |
94 | |
95 unsigned int GetWidth() const | |
96 { | |
97 return width_; | |
98 } | |
99 | |
100 unsigned int GetHeight() const | |
101 { | |
102 return height_; | |
103 } | |
104 | |
105 const DicomFrameConverter& GetConverter() const | |
106 { | |
107 return converter_; | |
108 } | |
109 | |
110 void SetNormal(const Vector& normal) | |
111 { | |
112 projectionAlongNormal_ = boost::numeric::ublas::inner_prod(geometry_.GetOrigin(), normal); | |
113 } | |
114 | |
115 double GetProjectionAlongNormal() const | |
116 { | |
117 return projectionAlongNormal_; | |
118 } | |
119 }; | |
120 | |
121 | |
122 class SetOfSlices : public boost::noncopyable | |
123 { | |
124 private: | |
125 std::vector<Slice*> slices_; | |
126 | |
127 struct Comparator | |
128 { | |
129 bool operator() (const Slice* const a, | |
130 const Slice* const b) const | |
131 { | |
132 return a->GetProjectionAlongNormal() < b->GetProjectionAlongNormal(); | |
133 } | |
134 }; | |
135 | |
136 public: | |
137 ~SetOfSlices() | |
138 { | |
139 for (size_t i = 0; i < slices_.size(); i++) | |
140 { | |
141 assert(slices_[i] != NULL); | |
142 delete slices_[i]; | |
143 } | |
144 } | |
145 | |
146 void Reserve(size_t size) | |
147 { | |
148 slices_.reserve(size); | |
149 } | |
150 | |
151 void AddSlice(const std::string& instanceId, | |
152 const Json::Value& value) | |
153 { | |
154 OrthancPlugins::FullOrthancDataset dataset(value); | |
155 OrthancPlugins::DicomDatasetReader reader(dataset); | |
156 | |
157 std::string position, orientation; | |
158 double thickness; | |
159 unsigned int width, height; | |
160 if (dataset.GetStringValue(position, OrthancPlugins::DICOM_TAG_IMAGE_POSITION_PATIENT) && | |
161 dataset.GetStringValue(orientation, OrthancPlugins::DICOM_TAG_IMAGE_ORIENTATION_PATIENT) && | |
162 reader.GetDoubleValue(thickness, OrthancPlugins::DICOM_TAG_SLICE_THICKNESS) && | |
163 reader.GetUnsignedIntegerValue(width, OrthancPlugins::DICOM_TAG_COLUMNS) && | |
164 reader.GetUnsignedIntegerValue(height, OrthancPlugins::DICOM_TAG_ROWS)); | |
165 { | |
166 slices_.push_back(new Slice(instanceId, position, orientation, | |
167 thickness, width, height, dataset)); | |
168 } | |
169 } | |
170 | |
171 size_t GetSliceCount() const | |
172 { | |
173 return slices_.size(); | |
174 } | |
175 | |
176 const Slice& GetSlice(size_t index) const | |
177 { | |
178 assert(slices_[index] != NULL); | |
179 return *slices_[index]; | |
180 } | |
79 | 181 |
80 virtual void NotifyInstanceError(const std::string& instanceId) = 0; | 182 void Sort(const Vector& normal) |
183 { | |
184 for (size_t i = 0; i < slices_.size(); i++) | |
185 { | |
186 slices_[i]->SetNormal(normal); | |
187 } | |
188 | |
189 Comparator comparator; | |
190 std::sort(slices_.begin(), slices_.end(), comparator); | |
191 } | |
192 | |
193 | |
194 void SelectNormal(Vector& normal) const | |
195 { | |
196 std::vector<Vector> normalCandidates; | |
197 std::vector<unsigned int> normalCount; | |
198 | |
199 bool found = false; | |
200 | |
201 for (size_t i = 0; !found && i < GetSliceCount(); i++) | |
202 { | |
203 const Vector& normal = GetSlice(i).GetGeometry().GetNormal(); | |
204 | |
205 bool add = true; | |
206 for (size_t j = 0; add && j < normalCandidates.size(); j++) // (*) | |
207 { | |
208 if (GeometryToolbox::IsParallel(normal, normalCandidates[j])) | |
209 { | |
210 normalCount[j] += 1; | |
211 add = false; | |
212 } | |
213 } | |
214 | |
215 if (add) | |
216 { | |
217 if (normalCount.size() > 2) | |
218 { | |
219 // To get linear-time complexity in (*). This heuristics | |
220 // allows the series to have one single frame that is | |
221 // not parallel to the others (such a frame could be a | |
222 // generated preview) | |
223 found = false; | |
224 } | |
225 else | |
226 { | |
227 normalCandidates.push_back(normal); | |
228 normalCount.push_back(1); | |
229 } | |
230 } | |
231 } | |
232 | |
233 for (size_t i = 0; !found && i < normalCandidates.size(); i++) | |
234 { | |
235 unsigned int count = normalCount[i]; | |
236 if (count == GetSliceCount() || | |
237 count + 1 == GetSliceCount()) | |
238 { | |
239 normal = normalCandidates[i]; | |
240 found = true; | |
241 } | |
242 } | |
243 | |
244 if (!found) | |
245 { | |
246 LOG(ERROR) << "Cannot select a normal that is shared by most of the slices of this series"; | |
247 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); | |
248 } | |
249 } | |
250 | |
251 | |
252 void FilterNormal(const Vector& normal) | |
253 { | |
254 size_t pos = 0; | |
255 | |
256 for (size_t i = 0; i < slices_.size(); i++) | |
257 { | |
258 if (GeometryToolbox::IsParallel(normal, slices_[i]->GetGeometry().GetNormal())) | |
259 { | |
260 // This slice is compatible with the selected normal | |
261 slices_[pos] = slices_[i]; | |
262 pos += 1; | |
263 } | |
264 else | |
265 { | |
266 delete slices_[i]; | |
267 slices_[i] = NULL; | |
268 } | |
269 } | |
270 | |
271 slices_.resize(pos); | |
272 } | |
81 }; | 273 }; |
82 | 274 |
83 private: | 275 |
276 enum Mode | |
277 { | |
278 Mode_Geometry | |
279 }; | |
280 | |
84 class Operation : public Orthanc::IDynamicObject | 281 class Operation : public Orthanc::IDynamicObject |
85 { | 282 { |
86 private: | 283 private: |
87 ICallback& callback_; | 284 Mode mode_; |
88 std::string instanceId_; | 285 unsigned int instance_; |
286 | |
287 Operation(Mode mode) : | |
288 mode_(mode) | |
289 { | |
290 } | |
89 | 291 |
90 public: | 292 public: |
91 Operation(ICallback& callback, | 293 Mode GetMode() const |
92 const std::string& instanceId) : | 294 { |
93 callback_(callback), | 295 return mode_; |
94 instanceId_(instanceId) | 296 } |
95 { | 297 |
96 } | 298 static Operation* DownloadGeometry() |
97 | 299 { |
98 ICallback& GetCallback() | 300 return new Operation(Mode_Geometry); |
99 { | |
100 return callback_; | |
101 } | |
102 | |
103 const std::string& GetInstanceId() const | |
104 { | |
105 return instanceId_; | |
106 } | 301 } |
107 }; | 302 }; |
108 | 303 |
304 ICallback& callback_; | |
109 IWebService& orthanc_; | 305 IWebService& orthanc_; |
110 | 306 std::string seriesId_; |
307 SetOfSlices slices_; | |
308 | |
309 | |
310 void ParseGeometry(const void* answer, | |
311 size_t size) | |
312 { | |
313 Json::Value series; | |
314 if (!MessagingToolbox::ParseJson(series, answer, size) || | |
315 series.type() != Json::objectValue) | |
316 { | |
317 callback_.NotifyGeometryError(*this); | |
318 return; | |
319 } | |
320 | |
321 Json::Value::Members instances = series.getMemberNames(); | |
322 | |
323 slices_.Reserve(instances.size()); | |
324 | |
325 for (size_t i = 0; i < instances.size(); i++) | |
326 { | |
327 slices_.AddSlice(instances[i], series[instances[i]]); | |
328 } | |
329 | |
330 bool ok = false; | |
331 | |
332 if (slices_.GetSliceCount() > 0) | |
333 { | |
334 Vector normal; | |
335 slices_.SelectNormal(normal); | |
336 slices_.FilterNormal(normal); | |
337 slices_.Sort(normal); | |
338 ok = true; | |
339 } | |
340 | |
341 if (ok) | |
342 { | |
343 printf("%d\n", slices_.GetSliceCount()); | |
344 callback_.NotifyGeometryReady(*this); | |
345 } | |
346 else | |
347 { | |
348 LOG(ERROR) << "This series is empty"; | |
349 callback_.NotifyGeometryError(*this); | |
350 return; | |
351 } | |
352 } | |
353 | |
354 | |
111 public: | 355 public: |
112 OrthancInstanceLoader(IWebService& orthanc) : | 356 SeriesLoader(ICallback& callback, |
113 orthanc_(orthanc) | 357 IWebService& orthanc, |
114 { | 358 const std::string& seriesId) : |
115 } | 359 callback_(callback), |
116 | 360 orthanc_(orthanc), |
117 void ScheduleLoadInstance(ICallback& callback, | 361 seriesId_(seriesId) |
118 const std::string& instanceId) | 362 { |
119 { | 363 std::string uri = "/series/" + seriesId + "/instances-tags"; |
120 orthanc_.ScheduleGetRequest(*this, | 364 orthanc.ScheduleGetRequest(*this, uri, Operation::DownloadGeometry()); |
121 "/instances/" + instanceId + "/tags", | 365 } |
122 new Operation(callback, instanceId)); | 366 |
123 } | 367 const std::string& GetSeriesId() const |
124 | 368 { |
125 void NotifySuccess(const std::string& uri, | 369 return seriesId_; |
126 const void* answer, | 370 } |
127 size_t answerSize, | 371 |
128 Orthanc::IDynamicObject* payload) | 372 virtual void NotifySuccess(const std::string& uri, |
129 { | 373 const void* answer, |
130 std::auto_ptr<Operation> operation(reinterpret_cast<Operation*>(payload)); | 374 size_t answerSize, |
131 | 375 Orthanc::IDynamicObject* payload) |
132 try | 376 { |
133 { | 377 std::auto_ptr<Operation> operation(dynamic_cast<Operation*>(payload)); |
134 OrthancPlugins::FullOrthancDataset dataset(answer, answerSize); | 378 |
135 operation->GetCallback().NotifyInstanceLoaded(operation->GetInstanceId(), dataset); | 379 switch (operation->GetMode()) |
136 } | 380 { |
137 catch (Orthanc::OrthancException&) | 381 case Mode_Geometry: |
138 { | 382 ParseGeometry(answer, answerSize); |
139 operation->GetCallback().NotifyInstanceError(operation->GetInstanceId()); | 383 break; |
140 } | 384 |
141 } | 385 default: |
142 | 386 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); |
143 void NotifyError(const std::string& uri, | 387 } |
144 Orthanc::IDynamicObject* payload) | 388 } |
145 { | 389 |
146 std::auto_ptr<Operation> operation(reinterpret_cast<Operation*>(payload)); | 390 virtual void NotifyError(const std::string& uri, |
147 | 391 Orthanc::IDynamicObject* payload) |
392 { | |
393 std::auto_ptr<Operation> operation(dynamic_cast<Operation*>(payload)); | |
148 LOG(ERROR) << "Cannot download " << uri; | 394 LOG(ERROR) << "Cannot download " << uri; |
149 operation->GetCallback().NotifyInstanceError(operation->GetInstanceId()); | 395 |
150 } | 396 switch (operation->GetMode()) |
151 };*/ | 397 { |
152 | 398 case Mode_Geometry: |
153 | 399 callback_.NotifyGeometryError(*this); |
154 | 400 break; |
401 | |
402 default: | |
403 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); | |
404 } | |
405 } | |
406 }; | |
407 | |
408 | |
409 class Tata : public SeriesLoader::ICallback | |
410 { | |
411 public: | |
412 virtual void NotifyGeometryReady(const SeriesLoader& series) | |
413 { | |
414 printf("Done %s\n", series.GetSeriesId().c_str()); | |
415 } | |
416 | |
417 virtual void NotifyGeometryError(const SeriesLoader& series) | |
418 { | |
419 printf("Error %s\n", series.GetSeriesId().c_str()); | |
420 } | |
421 }; | |
155 } | 422 } |
156 | |
157 | 423 |
158 | 424 |
159 TEST(Toto, Tutu) | 425 TEST(Toto, Tutu) |
160 { | 426 { |
161 Orthanc::WebServiceParameters web; | 427 Orthanc::WebServiceParameters web; |
162 OrthancStone::OrthancWebService orthanc(web); | 428 OrthancStone::OrthancWebService orthanc(web); |
163 OrthancStone::OrthancFrameLayerSource source(orthanc, "befb52a6-b4b04954-b5a019c3-fdada9d7-dddc9430", 0); | |
164 | 429 |
165 OrthancStone::Tata tata; | 430 OrthancStone::Tata tata; |
166 source.SetObserver(tata); | 431 //OrthancStone::SeriesLoader loader(tata, orthanc, "c1c4cb95-05e3bd11-8da9f5bb-87278f71-0b2b43f5"); |
167 | 432 OrthancStone::SeriesLoader loader(tata, orthanc, "67f1b334-02c16752-45026e40-a5b60b6b-030ecab5"); |
168 OrthancStone::SliceGeometry slice; | |
169 source.ScheduleLayerCreation(slice); | |
170 | |
171 | |
172 OrthancStone::LayerWidget widget; | |
173 printf(">> %d\n", widget.AddLayer(new OrthancStone::OrthancFrameLayerSource(orthanc, "befb52a6-b4b04954-b5a019c3-fdada9d7-dddc9430", 0))); | |
174 } | 433 } |
175 | 434 |
176 | 435 |
177 | 436 |
178 int main(int argc, char **argv) | 437 int main(int argc, char **argv) |