Mercurial > hg > orthanc-stone
annotate Framework/Radiography/RadiographyScene.cpp @ 427:3f9017db1738 am-vsol-upgrade-radiography-export
wip
author | am@osimis.io |
---|---|
date | Fri, 23 Nov 2018 16:06:23 +0100 |
parents | 660fe6f6bf4a |
children | b85f635f1eb5 |
rev | line source |
---|---|
408 | 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-2018 Osimis S.A., Belgium | |
6 * | |
7 * This program is free software: you can redistribute it and/or | |
8 * modify it under the terms of the GNU Affero General Public License | |
9 * as published by the Free Software Foundation, either version 3 of | |
10 * the License, or (at your option) any later version. | |
11 * | |
12 * This program is distributed in the hope that it will be useful, but | |
13 * WITHOUT ANY WARRANTY; without even the implied warranty of | |
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
15 * Affero General Public License for more details. | |
16 * | |
17 * You should have received a copy of the GNU Affero General Public License | |
18 * along with this program. If not, see <http://www.gnu.org/licenses/>. | |
19 **/ | |
20 | |
21 | |
22 #include "RadiographyScene.h" | |
23 | |
24 #include "../Toolbox/DicomFrameConverter.h" | |
25 | |
26 #include <Core/Images/Image.h> | |
27 #include <Core/Images/ImageProcessing.h> | |
28 #include <Core/Images/PamReader.h> | |
29 #include <Core/Images/PamWriter.h> | |
30 #include <Core/Images/PngWriter.h> | |
31 #include <Core/OrthancException.h> | |
32 #include <Core/Toolbox.h> | |
33 #include <Plugins/Samples/Common/DicomDatasetReader.h> | |
34 #include <Plugins/Samples/Common/FullOrthancDataset.h> | |
35 | |
412 | 36 #include <boost/math/special_functions/round.hpp> |
37 | |
408 | 38 |
39 namespace OrthancStone | |
40 { | |
41 RadiographyScene::LayerAccessor::LayerAccessor(RadiographyScene& scene, | |
42 size_t index) : | |
43 scene_(scene), | |
44 index_(index) | |
45 { | |
46 Layers::iterator layer = scene.layers_.find(index); | |
47 if (layer == scene.layers_.end()) | |
48 { | |
49 layer_ = NULL; | |
50 } | |
51 else | |
52 { | |
53 assert(layer->second != NULL); | |
54 layer_ = layer->second; | |
55 } | |
56 } | |
57 | |
426 | 58 |
408 | 59 RadiographyScene::LayerAccessor::LayerAccessor(RadiographyScene& scene, |
60 double x, | |
61 double y) : | |
62 scene_(scene), | |
63 index_(0) // Dummy initialization | |
64 { | |
65 if (scene.LookupLayer(index_, x, y)) | |
66 { | |
67 Layers::iterator layer = scene.layers_.find(index_); | |
426 | 68 |
408 | 69 if (layer == scene.layers_.end()) |
70 { | |
71 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); | |
72 } | |
73 else | |
74 { | |
75 assert(layer->second != NULL); | |
76 layer_ = layer->second; | |
77 } | |
78 } | |
79 else | |
80 { | |
81 layer_ = NULL; | |
82 } | |
83 } | |
84 | |
85 | |
86 RadiographyScene& RadiographyScene::LayerAccessor::GetScene() const | |
87 { | |
88 if (IsValid()) | |
89 { | |
90 return scene_; | |
91 } | |
92 else | |
93 { | |
94 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); | |
95 } | |
96 } | |
97 | |
98 | |
99 size_t RadiographyScene::LayerAccessor::GetIndex() const | |
100 { | |
101 if (IsValid()) | |
102 { | |
103 return index_; | |
104 } | |
105 else | |
106 { | |
107 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); | |
108 } | |
109 } | |
110 | |
111 | |
410
6decc0ba9da5
rename RadiographyScene::Layer as RadiographyLayer
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
409
diff
changeset
|
112 RadiographyLayer& RadiographyScene::LayerAccessor::GetLayer() const |
408 | 113 { |
114 if (IsValid()) | |
115 { | |
116 return *layer_; | |
117 } | |
118 else | |
119 { | |
120 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); | |
121 } | |
426 | 122 } |
408 | 123 |
124 | |
125 | |
410
6decc0ba9da5
rename RadiographyScene::Layer as RadiographyLayer
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
409
diff
changeset
|
126 class RadiographyScene::AlphaLayer : public RadiographyLayer |
408 | 127 { |
128 private: | |
129 const RadiographyScene& scene_; | |
130 std::auto_ptr<Orthanc::ImageAccessor> alpha_; // Grayscale8 | |
131 bool useWindowing_; | |
132 float foreground_; | |
133 | |
134 public: | |
135 AlphaLayer(const RadiographyScene& scene) : | |
136 scene_(scene), | |
137 useWindowing_(true), | |
138 foreground_(0) | |
139 { | |
140 } | |
141 | |
142 | |
143 void SetForegroundValue(float foreground) | |
144 { | |
145 useWindowing_ = false; | |
146 foreground_ = foreground; | |
147 } | |
426 | 148 |
149 | |
408 | 150 void SetAlpha(Orthanc::ImageAccessor* image) |
151 { | |
152 std::auto_ptr<Orthanc::ImageAccessor> raii(image); | |
426 | 153 |
408 | 154 if (image == NULL) |
155 { | |
156 throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); | |
157 } | |
158 | |
159 if (image->GetFormat() != Orthanc::PixelFormat_Grayscale8) | |
160 { | |
161 throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat); | |
162 } | |
163 | |
164 SetSize(image->GetWidth(), image->GetHeight()); | |
165 alpha_ = raii; | |
166 } | |
167 | |
168 virtual bool GetDefaultWindowing(float& center, | |
169 float& width) const | |
170 { | |
171 return false; | |
172 } | |
426 | 173 |
408 | 174 |
175 virtual void Render(Orthanc::ImageAccessor& buffer, | |
409 | 176 const AffineTransform2D& viewTransform, |
408 | 177 ImageInterpolation interpolation) const |
178 { | |
179 if (alpha_.get() == NULL) | |
180 { | |
181 return; | |
182 } | |
426 | 183 |
408 | 184 if (buffer.GetFormat() != Orthanc::PixelFormat_Float32) |
185 { | |
186 throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat); | |
187 } | |
188 | |
189 unsigned int cropX, cropY, cropWidth, cropHeight; | |
190 GetCrop(cropX, cropY, cropWidth, cropHeight); | |
191 | |
409 | 192 const AffineTransform2D t = AffineTransform2D::Combine( |
426 | 193 viewTransform, GetTransform(), |
194 AffineTransform2D::CreateOffset(cropX, cropY)); | |
408 | 195 |
196 Orthanc::ImageAccessor cropped; | |
197 alpha_->GetRegion(cropped, cropX, cropY, cropWidth, cropHeight); | |
426 | 198 |
408 | 199 Orthanc::Image tmp(Orthanc::PixelFormat_Grayscale8, buffer.GetWidth(), buffer.GetHeight(), false); |
409 | 200 |
201 t.Apply(tmp, cropped, interpolation, true /* clear */); | |
408 | 202 |
203 // Blit | |
204 const unsigned int width = buffer.GetWidth(); | |
205 const unsigned int height = buffer.GetHeight(); | |
206 | |
207 float value = foreground_; | |
426 | 208 |
408 | 209 if (useWindowing_) |
210 { | |
211 float center, width; | |
212 if (scene_.GetWindowing(center, width)) | |
213 { | |
214 value = center + width / 2.0f; | |
215 } | |
216 } | |
426 | 217 |
408 | 218 for (unsigned int y = 0; y < height; y++) |
219 { | |
220 float *q = reinterpret_cast<float*>(buffer.GetRow(y)); | |
221 const uint8_t *p = reinterpret_cast<uint8_t*>(tmp.GetRow(y)); | |
222 | |
223 for (unsigned int x = 0; x < width; x++, p++, q++) | |
224 { | |
225 float a = static_cast<float>(*p) / 255.0f; | |
426 | 226 |
408 | 227 *q = (a * value + (1.0f - a) * (*q)); |
228 } | |
426 | 229 } |
408 | 230 } |
231 | |
426 | 232 |
408 | 233 virtual bool GetRange(float& minValue, |
234 float& maxValue) const | |
235 { | |
236 if (useWindowing_) | |
237 { | |
238 return false; | |
239 } | |
240 else | |
241 { | |
242 minValue = 0; | |
243 maxValue = 0; | |
244 | |
245 if (foreground_ < 0) | |
246 { | |
247 minValue = foreground_; | |
248 } | |
249 | |
250 if (foreground_ > 0) | |
251 { | |
252 maxValue = foreground_; | |
253 } | |
254 | |
255 return true; | |
256 } | |
257 } | |
258 }; | |
426 | 259 |
260 | |
427 | 261 class RadiographyScene::TextLayer : public AlphaLayer |
262 { | |
263 private: | |
264 std::string text_; | |
265 | |
266 public: | |
267 TextLayer(const RadiographyScene& scene) : | |
268 AlphaLayer(scene) | |
269 { | |
270 } | |
271 | |
272 void LoadText(const Orthanc::Font& font, | |
273 const std::string& utf8) | |
274 { | |
275 SetAlpha(font.RenderAlpha(utf8)); | |
276 } | |
277 | |
278 }; | |
279 | |
408 | 280 |
410
6decc0ba9da5
rename RadiographyScene::Layer as RadiographyLayer
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
409
diff
changeset
|
281 class RadiographyScene::DicomLayer : public RadiographyLayer |
408 | 282 { |
283 private: | |
284 std::auto_ptr<Orthanc::ImageAccessor> source_; // Content of PixelData | |
285 std::auto_ptr<DicomFrameConverter> converter_; | |
286 std::auto_ptr<Orthanc::ImageAccessor> converted_; // Float32 | |
287 | |
288 static OrthancPlugins::DicomTag ConvertTag(const Orthanc::DicomTag& tag) | |
289 { | |
290 return OrthancPlugins::DicomTag(tag.GetGroup(), tag.GetElement()); | |
291 } | |
426 | 292 |
408 | 293 |
294 void ApplyConverter() | |
295 { | |
296 if (source_.get() != NULL && | |
297 converter_.get() != NULL) | |
298 { | |
299 converted_.reset(converter_->ConvertFrame(*source_)); | |
300 } | |
301 } | |
426 | 302 |
408 | 303 public: |
304 void SetDicomTags(const OrthancPlugins::FullOrthancDataset& dataset) | |
305 { | |
306 converter_.reset(new DicomFrameConverter); | |
307 converter_->ReadParameters(dataset); | |
308 ApplyConverter(); | |
309 | |
310 std::string tmp; | |
311 Vector pixelSpacing; | |
426 | 312 |
408 | 313 if (dataset.GetStringValue(tmp, ConvertTag(Orthanc::DICOM_TAG_PIXEL_SPACING)) && |
314 LinearAlgebra::ParseVector(pixelSpacing, tmp) && | |
315 pixelSpacing.size() == 2) | |
316 { | |
317 SetPixelSpacing(pixelSpacing[0], pixelSpacing[1]); | |
318 } | |
319 | |
320 //SetPan(-0.5 * GetPixelSpacingX(), -0.5 * GetPixelSpacingY()); | |
321 | |
322 OrthancPlugins::DicomDatasetReader reader(dataset); | |
323 | |
324 unsigned int width, height; | |
325 if (!reader.GetUnsignedIntegerValue(width, ConvertTag(Orthanc::DICOM_TAG_COLUMNS)) || | |
326 !reader.GetUnsignedIntegerValue(height, ConvertTag(Orthanc::DICOM_TAG_ROWS))) | |
327 { | |
328 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); | |
329 } | |
330 else | |
331 { | |
332 SetSize(width, height); | |
333 } | |
334 } | |
335 | |
426 | 336 |
408 | 337 void SetSourceImage(Orthanc::ImageAccessor* image) // Takes ownership |
338 { | |
339 std::auto_ptr<Orthanc::ImageAccessor> raii(image); | |
426 | 340 |
408 | 341 if (image == NULL) |
342 { | |
343 throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); | |
344 } | |
345 | |
346 SetSize(image->GetWidth(), image->GetHeight()); | |
426 | 347 |
408 | 348 source_ = raii; |
349 ApplyConverter(); | |
350 } | |
351 | |
426 | 352 |
408 | 353 virtual void Render(Orthanc::ImageAccessor& buffer, |
409 | 354 const AffineTransform2D& viewTransform, |
408 | 355 ImageInterpolation interpolation) const |
356 { | |
357 if (converted_.get() != NULL) | |
358 { | |
359 if (converted_->GetFormat() != Orthanc::PixelFormat_Float32) | |
360 { | |
361 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); | |
362 } | |
363 | |
364 unsigned int cropX, cropY, cropWidth, cropHeight; | |
365 GetCrop(cropX, cropY, cropWidth, cropHeight); | |
366 | |
409 | 367 AffineTransform2D t = AffineTransform2D::Combine( |
426 | 368 viewTransform, GetTransform(), |
369 AffineTransform2D::CreateOffset(cropX, cropY)); | |
408 | 370 |
371 Orthanc::ImageAccessor cropped; | |
372 converted_->GetRegion(cropped, cropX, cropY, cropWidth, cropHeight); | |
373 | |
409 | 374 t.Apply(buffer, cropped, interpolation, false); |
408 | 375 } |
376 } | |
377 | |
378 | |
379 virtual bool GetDefaultWindowing(float& center, | |
380 float& width) const | |
381 { | |
382 if (converter_.get() != NULL && | |
383 converter_->HasDefaultWindow()) | |
384 { | |
385 center = static_cast<float>(converter_->GetDefaultWindowCenter()); | |
386 width = static_cast<float>(converter_->GetDefaultWindowWidth()); | |
387 return true; | |
388 } | |
389 else | |
390 { | |
391 return false; | |
392 } | |
393 } | |
394 | |
395 | |
396 virtual bool GetRange(float& minValue, | |
397 float& maxValue) const | |
398 { | |
399 if (converted_.get() != NULL) | |
400 { | |
401 if (converted_->GetFormat() != Orthanc::PixelFormat_Float32) | |
402 { | |
403 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); | |
404 } | |
405 | |
406 Orthanc::ImageProcessing::GetMinMaxFloatValue(minValue, maxValue, *converted_); | |
407 return true; | |
408 } | |
409 else | |
410 { | |
411 return false; | |
412 } | |
413 } | |
414 }; | |
415 | |
416 | |
410
6decc0ba9da5
rename RadiographyScene::Layer as RadiographyLayer
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
409
diff
changeset
|
417 RadiographyLayer& RadiographyScene::RegisterLayer(RadiographyLayer* layer) |
408 | 418 { |
419 if (layer == NULL) | |
420 { | |
421 throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); | |
422 } | |
423 | |
410
6decc0ba9da5
rename RadiographyScene::Layer as RadiographyLayer
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
409
diff
changeset
|
424 std::auto_ptr<RadiographyLayer> raii(layer); |
426 | 425 |
408 | 426 size_t index = countLayers_++; |
427 raii->SetIndex(index); | |
428 layers_[index] = raii.release(); | |
429 | |
430 EmitMessage(GeometryChangedMessage(*this)); | |
431 EmitMessage(ContentChangedMessage(*this)); | |
432 | |
433 return *layer; | |
434 } | |
426 | 435 |
408 | 436 |
417
aee3d7941c9b
preparing to load images using DICOMweb
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
412
diff
changeset
|
437 RadiographyScene::RadiographyScene(MessageBroker& broker) : |
408 | 438 IObserver(broker), |
439 IObservable(broker), | |
440 countLayers_(0), | |
441 hasWindowing_(false), | |
442 windowingCenter_(0), // Dummy initialization | |
443 windowingWidth_(0) // Dummy initialization | |
444 { | |
445 } | |
446 | |
447 | |
448 RadiographyScene::~RadiographyScene() | |
449 { | |
450 for (Layers::iterator it = layers_.begin(); it != layers_.end(); it++) | |
451 { | |
452 assert(it->second != NULL); | |
453 delete it->second; | |
454 } | |
455 } | |
456 | |
425 | 457 void RadiographyScene::RemoveLayer(size_t layerIndex) |
458 { | |
459 if (layerIndex > countLayers_) | |
460 { | |
461 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); | |
462 } | |
463 delete layers_[layerIndex]; | |
464 layers_.erase(layerIndex); | |
465 countLayers_--; | |
466 } | |
467 | |
468 RadiographyLayer& RadiographyScene::GetLayer(size_t layerIndex) | |
469 { | |
470 if (layerIndex > countLayers_) | |
471 { | |
472 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); | |
473 } | |
474 return *(layers_[layerIndex]); | |
475 } | |
408 | 476 |
477 bool RadiographyScene::GetWindowing(float& center, | |
478 float& width) const | |
479 { | |
480 if (hasWindowing_) | |
481 { | |
482 center = windowingCenter_; | |
483 width = windowingWidth_; | |
484 return true; | |
485 } | |
486 else | |
487 { | |
488 return false; | |
489 } | |
490 } | |
491 | |
492 | |
493 void RadiographyScene::GetWindowingWithDefault(float& center, | |
494 float& width) const | |
495 { | |
496 if (!GetWindowing(center, width)) | |
497 { | |
498 center = 128; | |
499 width = 256; | |
500 } | |
501 } | |
502 | |
503 | |
504 void RadiographyScene::SetWindowing(float center, | |
505 float width) | |
506 { | |
507 hasWindowing_ = true; | |
508 windowingCenter_ = center; | |
509 windowingWidth_ = width; | |
510 } | |
511 | |
512 | |
410
6decc0ba9da5
rename RadiographyScene::Layer as RadiographyLayer
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
409
diff
changeset
|
513 RadiographyLayer& RadiographyScene::LoadText(const Orthanc::Font& font, |
6decc0ba9da5
rename RadiographyScene::Layer as RadiographyLayer
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
409
diff
changeset
|
514 const std::string& utf8) |
408 | 515 { |
516 std::auto_ptr<AlphaLayer> alpha(new AlphaLayer(*this)); | |
517 alpha->LoadText(font, utf8); | |
518 | |
519 return RegisterLayer(alpha.release()); | |
520 } | |
521 | |
426 | 522 |
410
6decc0ba9da5
rename RadiographyScene::Layer as RadiographyLayer
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
409
diff
changeset
|
523 RadiographyLayer& RadiographyScene::LoadTestBlock(unsigned int width, |
6decc0ba9da5
rename RadiographyScene::Layer as RadiographyLayer
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
409
diff
changeset
|
524 unsigned int height) |
408 | 525 { |
526 std::auto_ptr<Orthanc::Image> block(new Orthanc::Image(Orthanc::PixelFormat_Grayscale8, width, height, false)); | |
527 | |
528 for (unsigned int padding = 0; | |
529 (width > 2 * padding) && (height > 2 * padding); | |
530 padding++) | |
531 { | |
532 uint8_t color; | |
533 if (255 > 10 * padding) | |
534 { | |
535 color = 255 - 10 * padding; | |
536 } | |
537 else | |
538 { | |
539 color = 0; | |
540 } | |
541 | |
542 Orthanc::ImageAccessor region; | |
543 block->GetRegion(region, padding, padding, width - 2 * padding, height - 2 * padding); | |
544 Orthanc::ImageProcessing::Set(region, color); | |
545 } | |
546 | |
547 std::auto_ptr<AlphaLayer> alpha(new AlphaLayer(*this)); | |
548 alpha->SetAlpha(block.release()); | |
549 | |
550 return RegisterLayer(alpha.release()); | |
551 } | |
552 | |
426 | 553 |
417
aee3d7941c9b
preparing to load images using DICOMweb
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
412
diff
changeset
|
554 RadiographyLayer& RadiographyScene::LoadDicomFrame(OrthancApiClient& orthanc, |
aee3d7941c9b
preparing to load images using DICOMweb
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
412
diff
changeset
|
555 const std::string& instance, |
410
6decc0ba9da5
rename RadiographyScene::Layer as RadiographyLayer
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
409
diff
changeset
|
556 unsigned int frame, |
6decc0ba9da5
rename RadiographyScene::Layer as RadiographyLayer
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
409
diff
changeset
|
557 bool httpCompression) |
408 | 558 { |
410
6decc0ba9da5
rename RadiographyScene::Layer as RadiographyLayer
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
409
diff
changeset
|
559 RadiographyLayer& layer = RegisterLayer(new DicomLayer); |
408 | 560 |
561 { | |
417
aee3d7941c9b
preparing to load images using DICOMweb
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
412
diff
changeset
|
562 IWebService::HttpHeaders headers; |
408 | 563 std::string uri = "/instances/" + instance + "/tags"; |
426 | 564 |
417
aee3d7941c9b
preparing to load images using DICOMweb
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
412
diff
changeset
|
565 orthanc.GetBinaryAsync( |
426 | 566 uri, headers, |
567 new Callable<RadiographyScene, OrthancApiClient::BinaryResponseReadyMessage> | |
568 (*this, &RadiographyScene::OnTagsReceived), NULL, | |
569 new Orthanc::SingleValueObject<size_t>(layer.GetIndex())); | |
408 | 570 } |
571 | |
572 { | |
417
aee3d7941c9b
preparing to load images using DICOMweb
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
412
diff
changeset
|
573 IWebService::HttpHeaders headers; |
408 | 574 headers["Accept"] = "image/x-portable-arbitrarymap"; |
575 | |
576 if (httpCompression) | |
577 { | |
578 headers["Accept-Encoding"] = "gzip"; | |
579 } | |
426 | 580 |
408 | 581 std::string uri = ("/instances/" + instance + "/frames/" + |
582 boost::lexical_cast<std::string>(frame) + "/image-uint16"); | |
426 | 583 |
417
aee3d7941c9b
preparing to load images using DICOMweb
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
412
diff
changeset
|
584 orthanc.GetBinaryAsync( |
426 | 585 uri, headers, |
586 new Callable<RadiographyScene, OrthancApiClient::BinaryResponseReadyMessage> | |
587 (*this, &RadiographyScene::OnFrameReceived), NULL, | |
588 new Orthanc::SingleValueObject<size_t>(layer.GetIndex())); | |
408 | 589 } |
590 | |
591 return layer; | |
592 } | |
593 | |
417
aee3d7941c9b
preparing to load images using DICOMweb
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
412
diff
changeset
|
594 |
aee3d7941c9b
preparing to load images using DICOMweb
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
412
diff
changeset
|
595 RadiographyLayer& RadiographyScene::LoadDicomWebFrame(IWebService& web) |
aee3d7941c9b
preparing to load images using DICOMweb
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
412
diff
changeset
|
596 { |
aee3d7941c9b
preparing to load images using DICOMweb
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
412
diff
changeset
|
597 RadiographyLayer& layer = RegisterLayer(new DicomLayer); |
aee3d7941c9b
preparing to load images using DICOMweb
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
412
diff
changeset
|
598 |
426 | 599 |
417
aee3d7941c9b
preparing to load images using DICOMweb
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
412
diff
changeset
|
600 return layer; |
aee3d7941c9b
preparing to load images using DICOMweb
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
412
diff
changeset
|
601 } |
aee3d7941c9b
preparing to load images using DICOMweb
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
412
diff
changeset
|
602 |
aee3d7941c9b
preparing to load images using DICOMweb
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
412
diff
changeset
|
603 |
426 | 604 |
408 | 605 void RadiographyScene::OnTagsReceived(const OrthancApiClient::BinaryResponseReadyMessage& message) |
606 { | |
607 size_t index = dynamic_cast<const Orthanc::SingleValueObject<size_t>&> | |
426 | 608 (message.GetPayload()).GetValue(); |
408 | 609 |
610 LOG(INFO) << "JSON received: " << message.GetUri().c_str() | |
611 << " (" << message.GetAnswerSize() << " bytes) for layer " << index; | |
426 | 612 |
408 | 613 Layers::iterator layer = layers_.find(index); |
614 if (layer != layers_.end()) | |
615 { | |
616 assert(layer->second != NULL); | |
426 | 617 |
408 | 618 OrthancPlugins::FullOrthancDataset dicom(message.GetAnswer(), message.GetAnswerSize()); |
619 dynamic_cast<DicomLayer*>(layer->second)->SetDicomTags(dicom); | |
620 | |
621 float c, w; | |
622 if (!hasWindowing_ && | |
623 layer->second->GetDefaultWindowing(c, w)) | |
624 { | |
625 hasWindowing_ = true; | |
626 windowingCenter_ = c; | |
627 windowingWidth_ = w; | |
628 } | |
629 | |
630 EmitMessage(GeometryChangedMessage(*this)); | |
631 } | |
632 } | |
426 | 633 |
408 | 634 |
635 void RadiographyScene::OnFrameReceived(const OrthancApiClient::BinaryResponseReadyMessage& message) | |
636 { | |
637 size_t index = dynamic_cast<const Orthanc::SingleValueObject<size_t>&>(message.GetPayload()).GetValue(); | |
426 | 638 |
408 | 639 LOG(INFO) << "DICOM frame received: " << message.GetUri().c_str() |
640 << " (" << message.GetAnswerSize() << " bytes) for layer " << index; | |
426 | 641 |
408 | 642 Layers::iterator layer = layers_.find(index); |
643 if (layer != layers_.end()) | |
644 { | |
645 assert(layer->second != NULL); | |
646 | |
647 std::string content; | |
648 if (message.GetAnswerSize() > 0) | |
649 { | |
650 content.assign(reinterpret_cast<const char*>(message.GetAnswer()), message.GetAnswerSize()); | |
651 } | |
426 | 652 |
408 | 653 std::auto_ptr<Orthanc::PamReader> reader(new Orthanc::PamReader); |
654 reader->ReadFromMemory(content); | |
655 dynamic_cast<DicomLayer*>(layer->second)->SetSourceImage(reader.release()); | |
656 | |
657 EmitMessage(ContentChangedMessage(*this)); | |
658 } | |
659 } | |
660 | |
661 | |
662 Extent2D RadiographyScene::GetSceneExtent() const | |
663 { | |
664 Extent2D extent; | |
665 | |
666 for (Layers::const_iterator it = layers_.begin(); | |
667 it != layers_.end(); ++it) | |
668 { | |
669 assert(it->second != NULL); | |
670 extent.Union(it->second->GetExtent()); | |
671 } | |
672 | |
673 return extent; | |
674 } | |
426 | 675 |
408 | 676 |
677 void RadiographyScene::Render(Orthanc::ImageAccessor& buffer, | |
409 | 678 const AffineTransform2D& viewTransform, |
408 | 679 ImageInterpolation interpolation) const |
680 { | |
681 Orthanc::ImageProcessing::Set(buffer, 0); | |
682 | |
683 // Render layers in the background-to-foreground order | |
684 for (size_t index = 0; index < countLayers_; index++) | |
685 { | |
686 Layers::const_iterator it = layers_.find(index); | |
687 if (it != layers_.end()) | |
688 { | |
689 assert(it->second != NULL); | |
690 it->second->Render(buffer, viewTransform, interpolation); | |
691 } | |
692 } | |
693 } | |
694 | |
695 | |
696 bool RadiographyScene::LookupLayer(size_t& index /* out */, | |
697 double x, | |
698 double y) const | |
699 { | |
700 // Render layers in the foreground-to-background order | |
701 for (size_t i = countLayers_; i > 0; i--) | |
702 { | |
703 index = i - 1; | |
704 Layers::const_iterator it = layers_.find(index); | |
705 if (it != layers_.end()) | |
706 { | |
707 assert(it->second != NULL); | |
708 if (it->second->Contains(x, y)) | |
709 { | |
710 return true; | |
711 } | |
712 } | |
713 } | |
714 | |
715 return false; | |
716 } | |
717 | |
426 | 718 |
408 | 719 void RadiographyScene::DrawBorder(CairoContext& context, |
720 unsigned int layer, | |
721 double zoom) | |
722 { | |
723 Layers::const_iterator found = layers_.find(layer); | |
426 | 724 |
408 | 725 if (found != layers_.end()) |
726 { | |
727 context.SetSourceColor(255, 0, 0); | |
728 found->second->DrawBorders(context, zoom); | |
729 } | |
730 } | |
731 | |
732 | |
733 void RadiographyScene::GetRange(float& minValue, | |
734 float& maxValue) const | |
735 { | |
736 bool first = true; | |
426 | 737 |
408 | 738 for (Layers::const_iterator it = layers_.begin(); |
739 it != layers_.end(); it++) | |
740 { | |
741 assert(it->second != NULL); | |
742 | |
743 float a, b; | |
744 if (it->second->GetRange(a, b)) | |
745 { | |
746 if (first) | |
747 { | |
748 minValue = a; | |
749 maxValue = b; | |
750 first = false; | |
751 } | |
752 else | |
753 { | |
754 minValue = std::min(a, minValue); | |
755 maxValue = std::max(b, maxValue); | |
756 } | |
757 } | |
758 } | |
759 | |
760 if (first) | |
761 { | |
762 minValue = 0; | |
763 maxValue = 0; | |
764 } | |
765 } | |
766 | |
767 | |
427 | 768 void RadiographyScene::ExportToJson(Json::Value &output) |
769 { | |
770 for (Layers::iterator it = layers_.begin(); it != layers_.end(); it++) | |
771 { | |
772 Json::Value jsonLayer; | |
773 it-> | |
774 | |
775 } | |
776 } | |
777 | |
417
aee3d7941c9b
preparing to load images using DICOMweb
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
412
diff
changeset
|
778 void RadiographyScene::ExportDicom(OrthancApiClient& orthanc, |
aee3d7941c9b
preparing to load images using DICOMweb
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
412
diff
changeset
|
779 const Orthanc::DicomMap& dicom, |
426 | 780 const std::string& parentOrthancId, |
408 | 781 double pixelSpacingX, |
782 double pixelSpacingY, | |
783 bool invert, | |
784 ImageInterpolation interpolation, | |
785 bool usePam) | |
786 { | |
426 | 787 Json::Value createDicomRequestContent; |
788 | |
427 | 789 ExportToCreateDicomRequest(createDicomRequestContent, dicom, pixelSpacingX, pixelSpacingY, invert, interpolation, usePam); |
426 | 790 |
791 if (!parentOrthancId.empty()) | |
792 { | |
793 createDicomRequestContent["Parent"] = parentOrthancId; | |
794 } | |
795 | |
796 orthanc.PostJsonAsyncExpectJson( | |
797 "/tools/create-dicom", createDicomRequestContent, | |
798 new Callable<RadiographyScene, OrthancApiClient::JsonResponseReadyMessage> | |
799 (*this, &RadiographyScene::OnDicomExported), | |
800 NULL, NULL); | |
801 } | |
802 | |
803 // Export using PAM is faster than using PNG, but requires Orthanc | |
804 // core >= 1.4.3 | |
427 | 805 void RadiographyScene::ExportToCreateDicomRequest(Json::Value& createDicomRequestContent, |
426 | 806 const Orthanc::DicomMap& dicom, |
807 double pixelSpacingX, | |
808 double pixelSpacingY, | |
809 bool invert, | |
810 ImageInterpolation interpolation, | |
811 bool usePam) | |
812 { | |
408 | 813 if (pixelSpacingX <= 0 || |
814 pixelSpacingY <= 0) | |
815 { | |
816 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); | |
817 } | |
426 | 818 |
408 | 819 LOG(INFO) << "Exporting DICOM"; |
820 | |
821 Extent2D extent = GetSceneExtent(); | |
822 | |
823 int w = std::ceil(extent.GetWidth() / pixelSpacingX); | |
824 int h = std::ceil(extent.GetHeight() / pixelSpacingY); | |
825 | |
826 if (w < 0 || h < 0) | |
827 { | |
828 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); | |
829 } | |
830 | |
831 Orthanc::Image layers(Orthanc::PixelFormat_Float32, | |
832 static_cast<unsigned int>(w), | |
833 static_cast<unsigned int>(h), false); | |
834 | |
409 | 835 AffineTransform2D view = AffineTransform2D::Combine( |
426 | 836 AffineTransform2D::CreateScaling(1.0 / pixelSpacingX, 1.0 / pixelSpacingY), |
837 AffineTransform2D::CreateOffset(-extent.GetX1(), -extent.GetY1())); | |
838 | |
408 | 839 Render(layers, view, interpolation); |
840 | |
841 Orthanc::Image rendered(Orthanc::PixelFormat_Grayscale16, | |
842 layers.GetWidth(), layers.GetHeight(), false); | |
843 Orthanc::ImageProcessing::Convert(rendered, layers); | |
844 | |
845 std::string base64; | |
846 | |
847 { | |
848 std::string content; | |
849 | |
850 if (usePam) | |
851 { | |
852 Orthanc::PamWriter writer; | |
853 writer.WriteToMemory(content, rendered); | |
854 } | |
855 else | |
856 { | |
857 Orthanc::PngWriter writer; | |
858 writer.WriteToMemory(content, rendered); | |
859 } | |
860 | |
861 Orthanc::Toolbox::EncodeBase64(base64, content); | |
862 } | |
863 | |
864 std::set<Orthanc::DicomTag> tags; | |
865 dicom.GetTags(tags); | |
866 | |
426 | 867 createDicomRequestContent["Tags"] = Json::objectValue; |
868 | |
408 | 869 for (std::set<Orthanc::DicomTag>::const_iterator |
426 | 870 tag = tags.begin(); tag != tags.end(); ++tag) |
408 | 871 { |
872 const Orthanc::DicomValue& value = dicom.GetValue(*tag); | |
873 if (!value.IsNull() && | |
874 !value.IsBinary()) | |
875 { | |
426 | 876 createDicomRequestContent["Tags"][tag->Format()] = value.GetContent(); |
408 | 877 } |
878 } | |
879 | |
426 | 880 createDicomRequestContent["Tags"][Orthanc::DICOM_TAG_PHOTOMETRIC_INTERPRETATION.Format()] = |
881 (invert ? "MONOCHROME1" : "MONOCHROME2"); | |
408 | 882 |
883 // WARNING: The order of PixelSpacing is Y/X. We use "%0.8f" to | |
884 // avoid floating-point numbers to grow over 16 characters, | |
885 // which would be invalid according to DICOM standard | |
886 // ("dciodvfy" would complain). | |
887 char buf[32]; | |
888 sprintf(buf, "%0.8f\\%0.8f", pixelSpacingY, pixelSpacingX); | |
426 | 889 |
890 createDicomRequestContent["Tags"][Orthanc::DICOM_TAG_PIXEL_SPACING.Format()] = buf; | |
408 | 891 |
892 float center, width; | |
893 if (GetWindowing(center, width)) | |
894 { | |
426 | 895 createDicomRequestContent["Tags"][Orthanc::DICOM_TAG_WINDOW_CENTER.Format()] = |
896 boost::lexical_cast<std::string>(boost::math::iround(center)); | |
408 | 897 |
426 | 898 createDicomRequestContent["Tags"][Orthanc::DICOM_TAG_WINDOW_WIDTH.Format()] = |
899 boost::lexical_cast<std::string>(boost::math::iround(width)); | |
408 | 900 } |
901 | |
426 | 902 |
408 | 903 // This is Data URI scheme: https://en.wikipedia.org/wiki/Data_URI_scheme |
426 | 904 createDicomRequestContent["Content"] = ("data:" + |
905 std::string(usePam ? Orthanc::MIME_PAM : Orthanc::MIME_PNG) + | |
906 ";base64," + base64); | |
408 | 907 } |
908 | |
909 | |
910 void RadiographyScene::OnDicomExported(const OrthancApiClient::JsonResponseReadyMessage& message) | |
911 { | |
417
aee3d7941c9b
preparing to load images using DICOMweb
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
412
diff
changeset
|
912 LOG(INFO) << "DICOM export was successful: " |
408 | 913 << message.GetJson().toStyledString(); |
914 } | |
417
aee3d7941c9b
preparing to load images using DICOMweb
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
412
diff
changeset
|
915 |
aee3d7941c9b
preparing to load images using DICOMweb
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
412
diff
changeset
|
916 |
aee3d7941c9b
preparing to load images using DICOMweb
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
412
diff
changeset
|
917 void RadiographyScene::OnDicomWebReceived(const IWebService::HttpRequestSuccessMessage& message) |
aee3d7941c9b
preparing to load images using DICOMweb
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
412
diff
changeset
|
918 { |
aee3d7941c9b
preparing to load images using DICOMweb
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
412
diff
changeset
|
919 LOG(INFO) << "DICOMweb WADO-RS received: " << message.GetAnswerSize() << " bytes"; |
418 | 920 |
921 const IWebService::HttpHeaders& h = message.GetAnswerHttpHeaders(); | |
922 for (IWebService::HttpHeaders::const_iterator | |
426 | 923 it = h.begin(); it != h.end(); ++it) |
418 | 924 { |
925 printf("[%s] = [%s]\n", it->first.c_str(), it->second.c_str()); | |
926 } | |
417
aee3d7941c9b
preparing to load images using DICOMweb
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
412
diff
changeset
|
927 } |
aee3d7941c9b
preparing to load images using DICOMweb
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
412
diff
changeset
|
928 |
408 | 929 } |