Mercurial > hg > orthanc-stone
comparison UnitTestsSources/UnitTestsMain.cpp @ 68:1526d38ef6da wasm
SliceSorter
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Mon, 22 May 2017 17:49:26 +0200 |
parents | acb60cbb8301 |
children | 1553b67b24e5 |
comparison
equal
deleted
inserted
replaced
67:acb60cbb8301 | 68:1526d38ef6da |
---|---|
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 "../Resources/Orthanc/Core/Images/PngReader.h" | |
30 #include "../Framework/Toolbox/MessagingToolbox.h" | 31 #include "../Framework/Toolbox/MessagingToolbox.h" |
31 #include "../Framework/Toolbox/DicomFrameConverter.h" | 32 #include "../Framework/Toolbox/DicomFrameConverter.h" |
32 | 33 |
34 #include <boost/lexical_cast.hpp> | |
35 | |
33 namespace OrthancStone | 36 namespace OrthancStone |
34 { | 37 { |
35 class SeriesLoader : | 38 class Slice |
39 { | |
40 private: | |
41 enum Type | |
42 { | |
43 Type_Invalid, | |
44 Type_OrthancInstance | |
45 // TODO A slice could come from some DICOM file (URL) | |
46 }; | |
47 | |
48 Type type_; | |
49 std::string orthancInstanceId_; | |
50 unsigned int frame_; | |
51 SliceGeometry geometry_; | |
52 double pixelSpacingX_; | |
53 double pixelSpacingY_; | |
54 double thickness_; | |
55 unsigned int width_; | |
56 unsigned int height_; | |
57 DicomFrameConverter converter_; | |
58 | |
59 public: | |
60 Slice() : type_(Type_Invalid) | |
61 { | |
62 } | |
63 | |
64 bool ParseOrthancFrame(const OrthancPlugins::IDicomDataset& dataset, | |
65 const std::string& instanceId, | |
66 unsigned int frame) | |
67 { | |
68 OrthancPlugins::DicomDatasetReader reader(dataset); | |
69 | |
70 unsigned int frameCount; | |
71 if (!reader.GetUnsignedIntegerValue(frameCount, OrthancPlugins::DICOM_TAG_NUMBER_OF_FRAMES)) | |
72 { | |
73 frameCount = 1; // Assume instance with one frame | |
74 } | |
75 | |
76 if (frame >= frameCount) | |
77 { | |
78 return false; | |
79 } | |
80 | |
81 if (!reader.GetDoubleValue(thickness_, OrthancPlugins::DICOM_TAG_SLICE_THICKNESS)) | |
82 { | |
83 thickness_ = 100.0 * std::numeric_limits<double>::epsilon(); | |
84 } | |
85 | |
86 GeometryToolbox::GetPixelSpacing(pixelSpacingX_, pixelSpacingY_, dataset); | |
87 | |
88 std::string position, orientation; | |
89 if (dataset.GetStringValue(position, OrthancPlugins::DICOM_TAG_IMAGE_POSITION_PATIENT) && | |
90 dataset.GetStringValue(orientation, OrthancPlugins::DICOM_TAG_IMAGE_ORIENTATION_PATIENT) && | |
91 reader.GetUnsignedIntegerValue(width_, OrthancPlugins::DICOM_TAG_COLUMNS) && | |
92 reader.GetUnsignedIntegerValue(height_, OrthancPlugins::DICOM_TAG_ROWS)) | |
93 { | |
94 orthancInstanceId_ = instanceId; | |
95 frame_ = frame; | |
96 geometry_ = SliceGeometry(position, orientation); | |
97 converter_.ReadParameters(dataset); | |
98 | |
99 type_ = Type_OrthancInstance; | |
100 return true; | |
101 } | |
102 else | |
103 { | |
104 return false; | |
105 } | |
106 } | |
107 | |
108 bool IsOrthancInstance() const | |
109 { | |
110 return type_ == Type_OrthancInstance; | |
111 } | |
112 | |
113 const std::string GetOrthancInstanceId() const | |
114 { | |
115 if (type_ != Type_OrthancInstance) | |
116 { | |
117 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); | |
118 } | |
119 | |
120 return orthancInstanceId_; | |
121 } | |
122 | |
123 unsigned int GetFrame() const | |
124 { | |
125 if (type_ == Type_Invalid) | |
126 { | |
127 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); | |
128 } | |
129 | |
130 return frame_; | |
131 } | |
132 | |
133 const SliceGeometry& GetGeometry() const | |
134 { | |
135 if (type_ == Type_Invalid) | |
136 { | |
137 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); | |
138 } | |
139 | |
140 return geometry_; | |
141 } | |
142 | |
143 double GetThickness() const | |
144 { | |
145 if (type_ == Type_Invalid) | |
146 { | |
147 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); | |
148 } | |
149 | |
150 return thickness_; | |
151 } | |
152 | |
153 double GetPixelSpacingX() const | |
154 { | |
155 if (type_ == Type_Invalid) | |
156 { | |
157 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); | |
158 } | |
159 | |
160 return pixelSpacingX_; | |
161 } | |
162 | |
163 double GetPixelSpacingY() const | |
164 { | |
165 if (type_ == Type_Invalid) | |
166 { | |
167 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); | |
168 } | |
169 | |
170 return pixelSpacingY_; | |
171 } | |
172 | |
173 unsigned int GetWidth() const | |
174 { | |
175 if (type_ == Type_Invalid) | |
176 { | |
177 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); | |
178 } | |
179 | |
180 return width_; | |
181 } | |
182 | |
183 unsigned int GetHeight() const | |
184 { | |
185 if (type_ == Type_Invalid) | |
186 { | |
187 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); | |
188 } | |
189 | |
190 return height_; | |
191 } | |
192 | |
193 const DicomFrameConverter& GetConverter() const | |
194 { | |
195 if (type_ == Type_Invalid) | |
196 { | |
197 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); | |
198 } | |
199 | |
200 return converter_; | |
201 } | |
202 }; | |
203 | |
204 | |
205 class SliceSorter : public boost::noncopyable | |
206 { | |
207 private: | |
208 class SliceWithDepth : public boost::noncopyable | |
209 { | |
210 private: | |
211 Slice slice_; | |
212 double depth_; | |
213 | |
214 public: | |
215 SliceWithDepth(const Slice& slice) : | |
216 slice_(slice), | |
217 depth_(0) | |
218 { | |
219 } | |
220 | |
221 void SetNormal(const Vector& normal) | |
222 { | |
223 depth_ = boost::numeric::ublas::inner_prod | |
224 (slice_.GetGeometry().GetOrigin(), normal); | |
225 } | |
226 | |
227 double GetDepth() const | |
228 { | |
229 return depth_; | |
230 } | |
231 | |
232 const Slice& GetSlice() const | |
233 { | |
234 return slice_; | |
235 } | |
236 }; | |
237 | |
238 struct Comparator | |
239 { | |
240 bool operator() (const SliceWithDepth* const& a, | |
241 const SliceWithDepth* const& b) const | |
242 { | |
243 return a->GetDepth() < b->GetDepth(); | |
244 } | |
245 }; | |
246 | |
247 typedef std::vector<SliceWithDepth*> Slices; | |
248 | |
249 Slices slices_; | |
250 bool hasNormal_; | |
251 | |
252 public: | |
253 SliceSorter() : hasNormal_(false) | |
254 { | |
255 } | |
256 | |
257 ~SliceSorter() | |
258 { | |
259 for (size_t i = 0; i < slices_.size(); i++) | |
260 { | |
261 assert(slices_[i] != NULL); | |
262 delete slices_[i]; | |
263 } | |
264 } | |
265 | |
266 void Reserve(size_t count) | |
267 { | |
268 slices_.reserve(count); | |
269 } | |
270 | |
271 void AddSlice(const Slice& slice) | |
272 { | |
273 slices_.push_back(new SliceWithDepth(slice)); | |
274 } | |
275 | |
276 size_t GetSliceCount() const | |
277 { | |
278 return slices_.size(); | |
279 } | |
280 | |
281 const Slice& GetSlice(size_t i) const | |
282 { | |
283 if (i >= slices_.size()) | |
284 { | |
285 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); | |
286 } | |
287 | |
288 assert(slices_[i] != NULL); | |
289 return slices_[i]->GetSlice(); | |
290 } | |
291 | |
292 void SetNormal(const Vector& normal) | |
293 { | |
294 for (size_t i = 0; i < slices_.size(); i++) | |
295 { | |
296 slices_[i]->SetNormal(normal); | |
297 } | |
298 | |
299 hasNormal_ = true; | |
300 } | |
301 | |
302 void Sort() | |
303 { | |
304 if (!hasNormal_) | |
305 { | |
306 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); | |
307 } | |
308 | |
309 Comparator comparator; | |
310 std::sort(slices_.begin(), slices_.end(), comparator); | |
311 } | |
312 | |
313 void FilterNormal(const Vector& normal) | |
314 { | |
315 size_t pos = 0; | |
316 | |
317 for (size_t i = 0; i < slices_.size(); i++) | |
318 { | |
319 if (GeometryToolbox::IsParallel(normal, slices_[i]->GetSlice().GetGeometry().GetNormal())) | |
320 { | |
321 // This slice is compatible with the selected normal | |
322 slices_[pos] = slices_[i]; | |
323 pos += 1; | |
324 } | |
325 else | |
326 { | |
327 delete slices_[i]; | |
328 slices_[i] = NULL; | |
329 } | |
330 } | |
331 | |
332 slices_.resize(pos); | |
333 } | |
334 | |
335 bool SelectNormal(Vector& normal) const | |
336 { | |
337 std::vector<Vector> normalCandidates; | |
338 std::vector<unsigned int> normalCount; | |
339 | |
340 bool found = false; | |
341 | |
342 for (size_t i = 0; !found && i < GetSliceCount(); i++) | |
343 { | |
344 const Vector& normal = GetSlice(i).GetGeometry().GetNormal(); | |
345 | |
346 bool add = true; | |
347 for (size_t j = 0; add && j < normalCandidates.size(); j++) // (*) | |
348 { | |
349 if (GeometryToolbox::IsParallel(normal, normalCandidates[j])) | |
350 { | |
351 normalCount[j] += 1; | |
352 add = false; | |
353 } | |
354 } | |
355 | |
356 if (add) | |
357 { | |
358 if (normalCount.size() > 2) | |
359 { | |
360 // To get linear-time complexity in (*). This heuristics | |
361 // allows the series to have one single frame that is | |
362 // not parallel to the others (such a frame could be a | |
363 // generated preview) | |
364 found = false; | |
365 } | |
366 else | |
367 { | |
368 normalCandidates.push_back(normal); | |
369 normalCount.push_back(1); | |
370 } | |
371 } | |
372 } | |
373 | |
374 for (size_t i = 0; !found && i < normalCandidates.size(); i++) | |
375 { | |
376 unsigned int count = normalCount[i]; | |
377 if (count == GetSliceCount() || | |
378 count + 1 == GetSliceCount()) | |
379 { | |
380 normal = normalCandidates[i]; | |
381 found = true; | |
382 } | |
383 } | |
384 | |
385 return found; | |
386 } | |
387 }; | |
388 | |
389 | |
390 | |
391 class OrthancSliceLoader : | |
36 public IWebService::ICallback // TODO move to PImpl | 392 public IWebService::ICallback // TODO move to PImpl |
37 { | 393 { |
38 public: | 394 public: |
39 class ICallback : public boost::noncopyable | 395 class ICallback : public boost::noncopyable |
40 { | 396 { |
41 public: | 397 public: |
42 virtual ~ICallback() | 398 virtual ~ICallback() |
43 { | 399 { |
44 } | 400 } |
45 | 401 |
46 virtual void NotifyGeometryReady(const SeriesLoader& series) = 0; | 402 virtual void NotifyGeometryReady(const OrthancSliceLoader& loader) = 0; |
47 | 403 |
48 virtual void NotifyGeometryError(const SeriesLoader& series) = 0; | 404 virtual void NotifyGeometryError(const OrthancSliceLoader& loader) = 0; |
405 | |
406 virtual void NotifySliceImageReady(const OrthancSliceLoader& loader, | |
407 unsigned int sliceIndex, | |
408 const Slice& slice, | |
409 Orthanc::ImageAccessor* image) = 0; | |
410 | |
411 virtual void NotifySliceImageError(const OrthancSliceLoader& loader, | |
412 unsigned int sliceIndex, | |
413 const Slice& slice) = 0; | |
49 }; | 414 }; |
50 | 415 |
51 private: | 416 private: |
52 class Slice : public boost::noncopyable | 417 enum State |
418 { | |
419 State_Error, | |
420 State_Initialization, | |
421 State_LoadingGeometry, | |
422 State_GeometryReady | |
423 }; | |
424 | |
425 enum Mode | |
426 { | |
427 Mode_SeriesGeometry, | |
428 Mode_LoadImage | |
429 }; | |
430 | |
431 class Operation : public Orthanc::IDynamicObject | |
53 { | 432 { |
54 private: | 433 private: |
55 std::string instanceId_; | 434 Mode mode_; |
56 SliceGeometry geometry_; | 435 unsigned int sliceIndex_; |
57 double thickness_; | 436 const Slice* slice_; |
58 unsigned int width_; | |
59 unsigned int height_; | |
60 double projectionAlongNormal_; | |
61 DicomFrameConverter converter_; | |
62 | |
63 public: | |
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 } | |
181 | |
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 } | |
273 }; | |
274 | |
275 | |
276 enum Mode | |
277 { | |
278 Mode_Geometry | |
279 }; | |
280 | |
281 class Operation : public Orthanc::IDynamicObject | |
282 { | |
283 private: | |
284 Mode mode_; | |
285 unsigned int instance_; | |
286 | 437 |
287 Operation(Mode mode) : | 438 Operation(Mode mode) : |
288 mode_(mode) | 439 mode_(mode) |
289 { | 440 { |
290 } | 441 } |
292 public: | 443 public: |
293 Mode GetMode() const | 444 Mode GetMode() const |
294 { | 445 { |
295 return mode_; | 446 return mode_; |
296 } | 447 } |
448 | |
449 unsigned int GetSliceIndex() const | |
450 { | |
451 assert(mode_ == Mode_LoadImage); | |
452 return sliceIndex_; | |
453 } | |
454 | |
455 const Slice& GetSlice() const | |
456 { | |
457 assert(mode_ == Mode_LoadImage && slice_ != NULL); | |
458 return *slice_; | |
459 } | |
297 | 460 |
298 static Operation* DownloadGeometry() | 461 static Operation* DownloadSeriesGeometry() |
299 { | 462 { |
300 return new Operation(Mode_Geometry); | 463 return new Operation(Mode_SeriesGeometry); |
464 } | |
465 | |
466 static Operation* DownloadSliceImage(unsigned int sliceIndex, | |
467 const Slice& slice) | |
468 { | |
469 std::auto_ptr<Operation> tmp(new Operation(Mode_LoadImage)); | |
470 tmp->sliceIndex_ = sliceIndex; | |
471 tmp->slice_ = &slice; | |
472 return tmp.release(); | |
301 } | 473 } |
302 }; | 474 }; |
303 | 475 |
304 ICallback& callback_; | 476 ICallback& callback_; |
305 IWebService& orthanc_; | 477 IWebService& orthanc_; |
306 std::string seriesId_; | 478 State state_; |
307 SetOfSlices slices_; | 479 SliceSorter slices_; |
308 | 480 |
309 | 481 |
310 void ParseGeometry(const void* answer, | 482 void ParseSeriesGeometry(const void* answer, |
311 size_t size) | 483 size_t size) |
312 { | 484 { |
313 Json::Value series; | 485 Json::Value series; |
314 if (!MessagingToolbox::ParseJson(series, answer, size) || | 486 if (!MessagingToolbox::ParseJson(series, answer, size) || |
315 series.type() != Json::objectValue) | 487 series.type() != Json::objectValue) |
316 { | 488 { |
322 | 494 |
323 slices_.Reserve(instances.size()); | 495 slices_.Reserve(instances.size()); |
324 | 496 |
325 for (size_t i = 0; i < instances.size(); i++) | 497 for (size_t i = 0; i < instances.size(); i++) |
326 { | 498 { |
327 slices_.AddSlice(instances[i], series[instances[i]]); | 499 OrthancPlugins::FullOrthancDataset dataset(series[instances[i]]); |
500 | |
501 Slice slice; | |
502 if (slice.ParseOrthancFrame(dataset, instances[i], 0 /* todo */)) | |
503 { | |
504 slices_.AddSlice(slice); | |
505 } | |
506 else | |
507 { | |
508 LOG(WARNING) << "Skipping invalid instance " << instances[i]; | |
509 } | |
328 } | 510 } |
329 | 511 |
330 bool ok = false; | 512 bool ok = false; |
331 | 513 |
332 if (slices_.GetSliceCount() > 0) | 514 if (slices_.GetSliceCount() > 0) |
333 { | 515 { |
334 Vector normal; | 516 Vector normal; |
335 slices_.SelectNormal(normal); | 517 if (slices_.SelectNormal(normal)) |
336 slices_.FilterNormal(normal); | 518 { |
337 slices_.Sort(normal); | 519 slices_.FilterNormal(normal); |
338 ok = true; | 520 slices_.SetNormal(normal); |
521 slices_.Sort(); | |
522 ok = true; | |
523 } | |
339 } | 524 } |
340 | 525 |
341 if (ok) | 526 if (ok) |
342 { | 527 { |
343 printf("%d\n", slices_.GetSliceCount()); | 528 LOG(INFO) << "Loaded a series with " << slices_.GetSliceCount() << " slice(s)"; |
344 callback_.NotifyGeometryReady(*this); | 529 callback_.NotifyGeometryReady(*this); |
345 } | 530 } |
346 else | 531 else |
347 { | 532 { |
348 LOG(ERROR) << "This series is empty"; | 533 LOG(ERROR) << "This series is empty"; |
349 callback_.NotifyGeometryError(*this); | 534 callback_.NotifyGeometryError(*this); |
350 return; | 535 } |
536 } | |
537 | |
538 | |
539 void ParseSliceImage(const Operation& operation, | |
540 const void* answer, | |
541 size_t size) | |
542 { | |
543 std::auto_ptr<Orthanc::PngReader> image(new Orthanc::PngReader); | |
544 image->ReadFromMemory(answer, size); | |
545 | |
546 bool ok = (image->GetWidth() == operation.GetSlice().GetWidth() || | |
547 image->GetHeight() == operation.GetSlice().GetHeight()); | |
548 | |
549 if (ok && | |
550 operation.GetSlice().GetConverter().GetExpectedPixelFormat() == | |
551 Orthanc::PixelFormat_SignedGrayscale16) | |
552 { | |
553 if (image->GetFormat() == Orthanc::PixelFormat_Grayscale16) | |
554 { | |
555 image->SetFormat(Orthanc::PixelFormat_SignedGrayscale16); | |
556 } | |
557 else | |
558 { | |
559 ok = false; | |
560 } | |
561 } | |
562 | |
563 if (ok) | |
564 { | |
565 callback_.NotifySliceImageReady(*this, operation.GetSliceIndex(), | |
566 operation.GetSlice(), image.release()); | |
567 } | |
568 else | |
569 { | |
570 callback_.NotifySliceImageError(*this, operation.GetSliceIndex(), | |
571 operation.GetSlice()); | |
351 } | 572 } |
352 } | 573 } |
353 | 574 |
354 | 575 |
355 public: | 576 public: |
356 SeriesLoader(ICallback& callback, | 577 OrthancSliceLoader(ICallback& callback, |
357 IWebService& orthanc, | 578 IWebService& orthanc) : |
358 const std::string& seriesId) : | |
359 callback_(callback), | 579 callback_(callback), |
360 orthanc_(orthanc), | 580 orthanc_(orthanc), |
361 seriesId_(seriesId) | 581 state_(State_Initialization) |
362 { | 582 { |
363 std::string uri = "/series/" + seriesId + "/instances-tags"; | 583 } |
364 orthanc.ScheduleGetRequest(*this, uri, Operation::DownloadGeometry()); | 584 |
365 } | 585 void ScheduleLoadSeries(const std::string& seriesId) |
366 | 586 { |
367 const std::string& GetSeriesId() const | 587 if (state_ != State_Initialization) |
368 { | 588 { |
369 return seriesId_; | 589 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); |
590 } | |
591 else | |
592 { | |
593 state_ = State_LoadingGeometry; | |
594 std::string uri = "/series/" + seriesId + "/instances-tags"; | |
595 orthanc_.ScheduleGetRequest(*this, uri, Operation::DownloadSeriesGeometry()); | |
596 } | |
597 } | |
598 | |
599 size_t GetSliceCount() const | |
600 { | |
601 if (state_ != State_GeometryReady) | |
602 { | |
603 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); | |
604 } | |
605 | |
606 return slices_.GetSliceCount(); | |
607 } | |
608 | |
609 const Slice& GetSlice(size_t index) const | |
610 { | |
611 if (state_ != State_GeometryReady) | |
612 { | |
613 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); | |
614 } | |
615 | |
616 return slices_.GetSlice(index); | |
617 } | |
618 | |
619 void ScheduleLoadSliceImage(size_t index) | |
620 { | |
621 if (state_ != State_GeometryReady) | |
622 { | |
623 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); | |
624 } | |
625 else | |
626 { | |
627 const Slice& slice = GetSlice(index); | |
628 | |
629 std::string uri = ("/instances/" + slice.GetOrthancInstanceId() + "/frames/" + | |
630 boost::lexical_cast<std::string>(slice.GetFrame())); | |
631 | |
632 switch (slice.GetConverter().GetExpectedPixelFormat()) | |
633 { | |
634 case Orthanc::PixelFormat_RGB24: | |
635 uri += "/preview"; | |
636 break; | |
637 | |
638 case Orthanc::PixelFormat_Grayscale16: | |
639 uri += "/image-uint16"; | |
640 break; | |
641 | |
642 case Orthanc::PixelFormat_SignedGrayscale16: | |
643 uri += "/image-int16"; | |
644 break; | |
645 | |
646 default: | |
647 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); | |
648 } | |
649 | |
650 orthanc_.ScheduleGetRequest(*this, uri, Operation::DownloadSliceImage(index, slice)); | |
651 } | |
370 } | 652 } |
371 | 653 |
372 virtual void NotifySuccess(const std::string& uri, | 654 virtual void NotifySuccess(const std::string& uri, |
373 const void* answer, | 655 const void* answer, |
374 size_t answerSize, | 656 size_t answerSize, |
376 { | 658 { |
377 std::auto_ptr<Operation> operation(dynamic_cast<Operation*>(payload)); | 659 std::auto_ptr<Operation> operation(dynamic_cast<Operation*>(payload)); |
378 | 660 |
379 switch (operation->GetMode()) | 661 switch (operation->GetMode()) |
380 { | 662 { |
381 case Mode_Geometry: | 663 case Mode_SeriesGeometry: |
382 ParseGeometry(answer, answerSize); | 664 ParseSeriesGeometry(answer, answerSize); |
665 state_ = State_GeometryReady; | |
666 break; | |
667 | |
668 case Mode_LoadImage: | |
669 ParseSliceImage(*operation, answer, answerSize); | |
383 break; | 670 break; |
384 | 671 |
385 default: | 672 default: |
386 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); | 673 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); |
387 } | 674 } |
393 std::auto_ptr<Operation> operation(dynamic_cast<Operation*>(payload)); | 680 std::auto_ptr<Operation> operation(dynamic_cast<Operation*>(payload)); |
394 LOG(ERROR) << "Cannot download " << uri; | 681 LOG(ERROR) << "Cannot download " << uri; |
395 | 682 |
396 switch (operation->GetMode()) | 683 switch (operation->GetMode()) |
397 { | 684 { |
398 case Mode_Geometry: | 685 case Mode_SeriesGeometry: |
399 callback_.NotifyGeometryError(*this); | 686 callback_.NotifyGeometryError(*this); |
687 state_ = State_Error; | |
400 break; | 688 break; |
401 | 689 |
690 case Mode_LoadImage: | |
691 callback_.NotifySliceImageError(*this, operation->GetSliceIndex(), operation->GetSlice()); | |
692 break; | |
693 | |
402 default: | 694 default: |
403 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); | 695 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); |
404 } | 696 } |
405 } | 697 } |
406 }; | 698 }; |
407 | 699 |
408 | 700 |
409 class Tata : public SeriesLoader::ICallback | 701 class Tata : public OrthancSliceLoader::ICallback |
410 { | 702 { |
411 public: | 703 public: |
412 virtual void NotifyGeometryReady(const SeriesLoader& series) | 704 virtual void NotifyGeometryReady(const OrthancSliceLoader& loader) |
413 { | 705 { |
414 printf("Done %s\n", series.GetSeriesId().c_str()); | 706 printf("Done\n"); |
415 } | 707 } |
416 | 708 |
417 virtual void NotifyGeometryError(const SeriesLoader& series) | 709 virtual void NotifyGeometryError(const OrthancSliceLoader& loader) |
418 { | 710 { |
419 printf("Error %s\n", series.GetSeriesId().c_str()); | 711 printf("Error\n"); |
712 } | |
713 | |
714 virtual void NotifySliceImageReady(const OrthancSliceLoader& loader, | |
715 unsigned int sliceIndex, | |
716 const Slice& slice, | |
717 Orthanc::ImageAccessor* image) | |
718 { | |
719 std::auto_ptr<Orthanc::ImageAccessor> tmp(image); | |
720 printf("Slice OK\n"); | |
721 } | |
722 | |
723 virtual void NotifySliceImageError(const OrthancSliceLoader& loader, | |
724 unsigned int sliceIndex, | |
725 const Slice& slice) | |
726 { | |
727 printf("ERROR 2\n"); | |
420 } | 728 } |
421 }; | 729 }; |
422 } | 730 } |
423 | 731 |
424 | 732 |
426 { | 734 { |
427 Orthanc::WebServiceParameters web; | 735 Orthanc::WebServiceParameters web; |
428 OrthancStone::OrthancWebService orthanc(web); | 736 OrthancStone::OrthancWebService orthanc(web); |
429 | 737 |
430 OrthancStone::Tata tata; | 738 OrthancStone::Tata tata; |
431 //OrthancStone::SeriesLoader loader(tata, orthanc, "c1c4cb95-05e3bd11-8da9f5bb-87278f71-0b2b43f5"); | 739 OrthancStone::OrthancSliceLoader loader(tata, orthanc); |
432 OrthancStone::SeriesLoader loader(tata, orthanc, "67f1b334-02c16752-45026e40-a5b60b6b-030ecab5"); | 740 //loader.ScheduleLoadSeries("c1c4cb95-05e3bd11-8da9f5bb-87278f71-0b2b43f5"); |
741 loader.ScheduleLoadSeries("67f1b334-02c16752-45026e40-a5b60b6b-030ecab5"); | |
742 | |
743 printf(">> %d\n", loader.GetSliceCount()); | |
744 loader.ScheduleLoadSliceImage(31); | |
433 } | 745 } |
434 | 746 |
435 | 747 |
436 | 748 |
437 int main(int argc, char **argv) | 749 int main(int argc, char **argv) |