comparison Framework/Deprecated/Radiography/RadiographyScene.cpp @ 1398:c5403d52078c

moved Radiography into Deprecated
author Alain Mazy <alain@mazy.be>
date Wed, 29 Apr 2020 20:43:09 +0200
parents Framework/Radiography/RadiographyScene.cpp@6ea4062c1a0d
children 30deba7bc8e2
comparison
equal deleted inserted replaced
1397:1c2d065ba372 1398:c5403d52078c
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-2020 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 "RadiographyAlphaLayer.h"
25 #include "RadiographyDicomLayer.h"
26 #include "RadiographyTextLayer.h"
27 #include "RadiographyMaskLayer.h"
28 #include "../Deprecated/Toolbox/DicomFrameConverter.h"
29 #include "../Scene2D/CairoCompositor.h"
30 #include "../Scene2D/FloatTextureSceneLayer.h"
31 #include "../Scene2D/TextSceneLayer.h"
32
33 #include <Core/Images/Image.h>
34 #include <Core/Images/ImageProcessing.h>
35 #include <Core/Images/PamReader.h>
36 #include <Core/Images/PamWriter.h>
37 #include <Core/Images/PngWriter.h>
38 #include <Core/OrthancException.h>
39 #include <Core/Toolbox.h>
40 #include <Plugins/Samples/Common/DicomDatasetReader.h>
41 #include <Plugins/Samples/Common/FullOrthancDataset.h>
42
43 #include <boost/math/special_functions/round.hpp>
44
45
46 namespace OrthancStone
47 {
48 RadiographyScene::LayerAccessor::LayerAccessor(RadiographyScene& scene,
49 size_t index) :
50 scene_(scene),
51 index_(index)
52 {
53 Layers::iterator layer = scene.layers_.find(index);
54 if (layer == scene.layers_.end())
55 {
56 layer_ = NULL;
57 }
58 else
59 {
60 assert(layer->second != NULL);
61 layer_ = layer->second;
62 }
63 }
64
65
66 RadiographyScene::LayerAccessor::LayerAccessor(RadiographyScene& scene,
67 double x,
68 double y) :
69 scene_(scene),
70 index_(0) // Dummy initialization
71 {
72 if (scene.LookupLayer(index_, x, y))
73 {
74 Layers::iterator layer = scene.layers_.find(index_);
75
76 if (layer == scene.layers_.end())
77 {
78 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
79 }
80 else
81 {
82 assert(layer->second != NULL);
83 layer_ = layer->second;
84 }
85 }
86 else
87 {
88 layer_ = NULL;
89 }
90 }
91
92
93 RadiographyScene& RadiographyScene::LayerAccessor::GetScene() const
94 {
95 if (IsValid())
96 {
97 return scene_;
98 }
99 else
100 {
101 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
102 }
103 }
104
105
106 size_t RadiographyScene::LayerAccessor::GetIndex() const
107 {
108 if (IsValid())
109 {
110 return index_;
111 }
112 else
113 {
114 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
115 }
116 }
117
118
119 RadiographyLayer& RadiographyScene::LayerAccessor::GetLayer() const
120 {
121 if (IsValid())
122 {
123 return *layer_;
124 }
125 else
126 {
127 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
128 }
129 }
130
131 void RadiographyScene::_RegisterLayer(RadiographyLayer* layer)
132 {
133 std::unique_ptr<RadiographyLayer> raii(layer);
134
135 // LOG(INFO) << "Registering layer: " << countLayers_;
136
137 size_t index = nextLayerIndex_++;
138 raii->SetIndex(index);
139 layers_[index] = raii.release();
140 }
141
142 RadiographyLayer& RadiographyScene::RegisterLayer(RadiographyLayer* layer)
143 {
144 if (layer == NULL)
145 {
146 throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
147 }
148
149 _RegisterLayer(layer);
150
151 BroadcastMessage(GeometryChangedMessage(*this, *layer));
152 BroadcastMessage(ContentChangedMessage(*this, *layer));
153 Register<RadiographyLayer::LayerEditedMessage>(*layer, &RadiographyScene::OnLayerEdited);
154
155 return *layer;
156 }
157
158 size_t RadiographyScene::GetApproximateMemoryUsage() const
159 {
160 size_t size = 0;
161 for (Layers::const_iterator it = layers_.begin(); it != layers_.end(); it++)
162 {
163 size += it->second->GetApproximateMemoryUsage();
164 }
165 return size;
166 }
167
168 void RadiographyScene::OnLayerEdited(const RadiographyLayer::LayerEditedMessage& message)
169 {
170 BroadcastMessage(RadiographyScene::LayerEditedMessage(*this, message.GetOrigin()));
171 }
172
173
174 RadiographyScene::RadiographyScene() :
175 nextLayerIndex_(0),
176 hasWindowing_(false),
177 windowingCenter_(0), // Dummy initialization
178 windowingWidth_(0) // Dummy initialization
179 {
180 }
181
182
183 RadiographyScene::~RadiographyScene()
184 {
185 for (Layers::iterator it = layers_.begin(); it != layers_.end(); it++)
186 {
187 assert(it->second != NULL);
188 delete it->second;
189 }
190 }
191
192 RadiographyPhotometricDisplayMode RadiographyScene::GetPreferredPhotomotricDisplayMode() const
193 {
194 // return the mode of the first layer who "cares" about its display mode (normaly, the one and only layer that is a DicomLayer)
195 for (Layers::const_iterator it = layers_.begin(); it != layers_.end(); it++)
196 {
197 if (it->second->GetPreferredPhotomotricDisplayMode() != RadiographyPhotometricDisplayMode_Default)
198 {
199 return it->second->GetPreferredPhotomotricDisplayMode();
200 }
201 }
202
203 return RadiographyPhotometricDisplayMode_Default;
204 }
205
206
207 void RadiographyScene::GetLayersIndexes(std::vector<size_t>& output) const
208 {
209 for (Layers::const_iterator it = layers_.begin(); it != layers_.end(); it++)
210 {
211 output.push_back(it->first);
212 }
213 }
214
215 void RadiographyScene::RemoveLayer(size_t layerIndex)
216 {
217 LOG(INFO) << "Removing layer: " << layerIndex;
218
219 Layers::iterator found = layers_.find(layerIndex);
220
221 if (found == layers_.end())
222 {
223 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
224 }
225 else
226 {
227 assert(found->second != NULL);
228 delete found->second;
229
230 layers_.erase(found);
231
232 LOG(INFO) << "Removing layer, there are now : " << layers_.size() << " layers";
233
234 _OnLayerRemoved();
235
236 BroadcastMessage(RadiographyScene::LayerRemovedMessage(*this, layerIndex));
237 }
238 }
239
240 const RadiographyLayer& RadiographyScene::GetLayer(size_t layerIndex) const
241 {
242 Layers::const_iterator found = layers_.find(layerIndex);
243
244 if (found == layers_.end())
245 {
246 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
247 }
248 else
249 {
250 assert(found->second != NULL);
251 return *found->second;
252 }
253 }
254
255 RadiographyLayer& RadiographyScene::GetLayer(size_t layerIndex)
256 {
257 Layers::const_iterator found = layers_.find(layerIndex);
258
259 if (found == layers_.end())
260 {
261 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
262 }
263 else
264 {
265 assert(found->second != NULL);
266 return *found->second;
267 }
268 }
269
270 bool RadiographyScene::GetWindowing(float& center,
271 float& width) const
272 {
273 if (hasWindowing_)
274 {
275 center = windowingCenter_;
276 width = windowingWidth_;
277 return true;
278 }
279 else
280 {
281 return false;
282 }
283 }
284
285
286 void RadiographyScene::GetWindowingWithDefault(float& center,
287 float& width) const
288 {
289 if (!GetWindowing(center, width))
290 {
291 center = 128;
292 width = 256;
293 }
294 }
295
296
297 void RadiographyScene::SetWindowing(float center,
298 float width)
299 {
300 hasWindowing_ = true;
301 windowingCenter_ = center;
302 windowingWidth_ = width;
303
304 BroadcastMessage(RadiographyScene::WindowingChangedMessage(*this));
305 }
306
307
308 RadiographyLayer& RadiographyScene::UpdateText(size_t layerIndex,
309 const std::string& utf8,
310 const std::string& font,
311 unsigned int fontSize,
312 uint8_t foreground)
313 {
314 RadiographyTextLayer& textLayer = dynamic_cast<RadiographyTextLayer&>(GetLayer(layerIndex));
315 textLayer.SetText(utf8, font, fontSize, foreground);
316
317 BroadcastMessage(RadiographyScene::ContentChangedMessage(*this, textLayer));
318 BroadcastMessage(RadiographyScene::LayerEditedMessage(*this, textLayer));
319 return textLayer;
320 }
321
322
323 RadiographyLayer& RadiographyScene::LoadText(const std::string& utf8,
324 const std::string& font,
325 unsigned int fontSize,
326 uint8_t foreground,
327 RadiographyLayer::Geometry* centerGeometry,
328 bool isCenterGeometry)
329 {
330 std::unique_ptr<RadiographyTextLayer> alpha(new RadiographyTextLayer(*this));
331 alpha->SetText(utf8, font, fontSize, foreground);
332 if (centerGeometry != NULL)
333 {
334 if (isCenterGeometry)
335 {
336 // modify geometry to reference the top left corner
337 double tlx = centerGeometry->GetPanX();
338 double tly = centerGeometry->GetPanY();
339 Extent2D textExtent = alpha->GetSceneExtent(false);
340 tlx = tlx - (textExtent.GetWidth() / 2) * centerGeometry->GetPixelSpacingX();
341 tly = tly - (textExtent.GetHeight() / 2) * centerGeometry->GetPixelSpacingY();
342 centerGeometry->SetPan(tlx, tly);
343 }
344 alpha->SetGeometry(*centerGeometry);
345 }
346
347 RadiographyLayer& registeredLayer = RegisterLayer(alpha.release());
348
349 BroadcastMessage(RadiographyScene::LayerEditedMessage(*this, registeredLayer));
350 return registeredLayer;
351 }
352
353
354 RadiographyLayer& RadiographyScene::LoadTestBlock(unsigned int width,
355 unsigned int height,
356 RadiographyLayer::Geometry* geometry)
357 {
358 std::unique_ptr<Orthanc::Image> block(new Orthanc::Image(Orthanc::PixelFormat_Grayscale8, width, height, false));
359
360 for (unsigned int padding = 0;
361 (width > 2 * padding) && (height > 2 * padding);
362 padding++)
363 {
364 uint8_t color;
365 if (255 > 10 * padding)
366 {
367 color = 255 - 10 * padding;
368 }
369 else
370 {
371 color = 0;
372 }
373
374 Orthanc::ImageAccessor region;
375 block->GetRegion(region, padding, padding, width - 2 * padding, height - 2 * padding);
376 Orthanc::ImageProcessing::Set(region, color);
377 }
378
379 return LoadAlphaBitmap(block.release(), geometry);
380 }
381
382 RadiographyLayer& RadiographyScene::LoadMask(const std::vector<Orthanc::ImageProcessing::ImagePoint>& corners,
383 const RadiographyDicomLayer& dicomLayer,
384 float foreground,
385 RadiographyLayer::Geometry* geometry)
386 {
387 std::unique_ptr<RadiographyMaskLayer> mask(new RadiographyMaskLayer(*this, dicomLayer, foreground));
388 mask->SetCorners(corners);
389 if (geometry != NULL)
390 {
391 mask->SetGeometry(*geometry);
392 }
393
394 return RegisterLayer(mask.release());
395 }
396
397
398 RadiographyLayer& RadiographyScene::LoadAlphaBitmap(Orthanc::ImageAccessor* bitmap, RadiographyLayer::Geometry *geometry)
399 {
400 std::unique_ptr<RadiographyAlphaLayer> alpha(new RadiographyAlphaLayer(*this));
401 alpha->SetAlpha(bitmap);
402 if (geometry != NULL)
403 {
404 alpha->SetGeometry(*geometry);
405 }
406
407 return RegisterLayer(alpha.release());
408 }
409
410 RadiographyLayer& RadiographyScene::LoadDicomImage(Orthanc::ImageAccessor* dicomImage, // takes ownership
411 const std::string& instance,
412 unsigned int frame,
413 Deprecated::DicomFrameConverter* converter, // takes ownership
414 RadiographyPhotometricDisplayMode preferredPhotometricDisplayMode,
415 RadiographyLayer::Geometry* geometry)
416 {
417 RadiographyDicomLayer& layer = dynamic_cast<RadiographyDicomLayer&>(RegisterLayer(new RadiographyDicomLayer(*this)));
418
419 layer.SetInstance(instance, frame);
420
421 if (geometry != NULL)
422 {
423 layer.SetGeometry(*geometry);
424 }
425
426 layer.SetDicomFrameConverter(converter);
427 layer.SetSourceImage(dicomImage);
428 layer.SetPreferredPhotomotricDisplayMode(preferredPhotometricDisplayMode);
429
430 return layer;
431 }
432
433 RadiographyLayer& RadiographyScene::LoadDicomFrame(Deprecated::OrthancApiClient& orthanc,
434 const std::string& instance,
435 unsigned int frame,
436 bool httpCompression,
437 RadiographyLayer::Geometry* geometry)
438 {
439 RadiographyDicomLayer& layer = dynamic_cast<RadiographyDicomLayer&>(RegisterLayer(new RadiographyDicomLayer( *this)));
440 layer.SetInstance(instance, frame);
441
442 if (geometry != NULL)
443 {
444 layer.SetGeometry(*geometry);
445 }
446
447 {
448 Deprecated::IWebService::HttpHeaders headers;
449 std::string uri = "/instances/" + instance + "/tags";
450
451 orthanc.GetBinaryAsync(
452 uri, headers,
453 new Deprecated::DeprecatedCallable<RadiographyScene, Deprecated::OrthancApiClient::BinaryResponseReadyMessage>
454 (GetSharedObserver(), &RadiographyScene::OnTagsReceived), NULL,
455 new Orthanc::SingleValueObject<size_t>(layer.GetIndex()));
456 }
457
458 {
459 Deprecated::IWebService::HttpHeaders headers;
460 headers["Accept"] = "image/x-portable-arbitrarymap";
461
462 if (httpCompression)
463 {
464 headers["Accept-Encoding"] = "gzip";
465 }
466
467 std::string uri = ("/instances/" + instance + "/frames/" +
468 boost::lexical_cast<std::string>(frame) + "/image-uint16");
469
470 orthanc.GetBinaryAsync(
471 uri, headers,
472 new Deprecated::DeprecatedCallable<RadiographyScene, Deprecated::OrthancApiClient::BinaryResponseReadyMessage>
473 (GetSharedObserver(), &RadiographyScene::OnFrameReceived), NULL,
474 new Orthanc::SingleValueObject<size_t>(layer.GetIndex()));
475 }
476
477 return layer;
478 }
479
480
481 RadiographyLayer& RadiographyScene::LoadDicomWebFrame(Deprecated::IWebService& web)
482 {
483 RadiographyLayer& layer = RegisterLayer(new RadiographyDicomLayer(*this));
484
485
486 return layer;
487 }
488
489
490
491 void RadiographyScene::OnTagsReceived(const Deprecated::OrthancApiClient::BinaryResponseReadyMessage& message)
492 {
493 size_t index = dynamic_cast<const Orthanc::SingleValueObject<size_t>&>
494 (message.GetPayload()).GetValue();
495
496 VLOG(1) << "JSON received: " << message.GetUri().c_str()
497 << " (" << message.GetAnswerSize() << " bytes) for layer " << index;
498
499 Layers::iterator layer = layers_.find(index);
500 if (layer != layers_.end())
501 {
502 assert(layer->second != NULL);
503
504 OrthancPlugins::FullOrthancDataset dicom(message.GetAnswer(), message.GetAnswerSize());
505 dynamic_cast<RadiographyDicomLayer*>(layer->second)->SetDicomTags(dicom);
506
507 float c, w;
508 if (!hasWindowing_ &&
509 layer->second->GetDefaultWindowing(c, w))
510 {
511 hasWindowing_ = true;
512 windowingCenter_ = c;
513 windowingWidth_ = w;
514 }
515
516 BroadcastMessage(GeometryChangedMessage(*this, *(layer->second)));
517 }
518 }
519
520
521 void RadiographyScene::OnFrameReceived(const Deprecated::OrthancApiClient::BinaryResponseReadyMessage& message)
522 {
523 size_t index = dynamic_cast<const Orthanc::SingleValueObject<size_t>&>(message.GetPayload()).GetValue();
524
525 VLOG(1) << "DICOM frame received: " << message.GetUri().c_str()
526 << " (" << message.GetAnswerSize() << " bytes) for layer " << index;
527
528 Layers::iterator layer = layers_.find(index);
529 if (layer != layers_.end())
530 {
531 assert(layer->second != NULL);
532
533 std::string content;
534 if (message.GetAnswerSize() > 0)
535 {
536 content.assign(reinterpret_cast<const char*>(message.GetAnswer()), message.GetAnswerSize());
537 }
538
539 std::unique_ptr<Orthanc::PamReader> reader(new Orthanc::PamReader);
540 reader->ReadFromMemory(content);
541 dynamic_cast<RadiographyDicomLayer*>(layer->second)->SetSourceImage(reader.release());
542
543 BroadcastMessage(ContentChangedMessage(*this, *(layer->second)));
544 }
545 }
546
547
548 Extent2D RadiographyScene::GetSceneExtent(bool minimal) const
549 {
550 Extent2D extent;
551
552 for (Layers::const_iterator it = layers_.begin();
553 it != layers_.end(); ++it)
554 {
555 assert(it->second != NULL);
556 extent.Union(it->second->GetSceneExtent(minimal));
557 }
558
559 return extent;
560 }
561
562
563 void RadiographyScene::Render(Orthanc::ImageAccessor& buffer,
564 const AffineTransform2D& viewTransform,
565 ImageInterpolation interpolation,
566 bool applyWindowing) const
567 {
568 // Render layers in the background-to-foreground order
569 for (size_t index = 0; index < nextLayerIndex_; index++)
570 {
571 try
572 {
573 Layers::const_iterator it = layers_.find(index);
574 if (it != layers_.end())
575 {
576 assert(it->second != NULL);
577 it->second->Render(buffer, viewTransform, interpolation, windowingCenter_, windowingWidth_, applyWindowing);
578 }
579 }
580 catch (Orthanc::OrthancException& ex)
581 {
582 LOG(ERROR) << "RadiographyScene::Render: " << index << ", OrthancException: " << ex.GetDetails();
583 throw ex; // rethrow because we want it to crash to see there's a problem !
584 }
585 catch (...)
586 {
587 LOG(ERROR) << "RadiographyScene::Render: " << index << ", unkown exception: ";
588 throw; // rethrow because we want it to crash to see there's a problem !
589 }
590 }
591 }
592
593
594 bool RadiographyScene::LookupLayer(size_t& index /* out */,
595 double x,
596 double y) const
597 {
598 // Render layers in the foreground-to-background order
599 for (size_t i = nextLayerIndex_; i > 0; i--)
600 {
601 index = i - 1;
602 Layers::const_iterator it = layers_.find(index);
603 if (it != layers_.end())
604 {
605 assert(it->second != NULL);
606 if (it->second->Contains(x, y))
607 {
608 return true;
609 }
610 }
611 }
612
613 return false;
614 }
615
616
617 void RadiographyScene::DrawBorder(CairoContext& context,
618 unsigned int layer,
619 double zoom)
620 {
621 Layers::const_iterator found = layers_.find(layer);
622
623 if (found != layers_.end())
624 {
625 context.SetSourceColor(255, 0, 0);
626 found->second->DrawBorders(context, zoom);
627 }
628 }
629
630
631 void RadiographyScene::GetRange(float& minValue,
632 float& maxValue) const
633 {
634 bool first = true;
635
636 for (Layers::const_iterator it = layers_.begin();
637 it != layers_.end(); it++)
638 {
639 assert(it->second != NULL);
640
641 float a, b;
642 if (it->second->GetRange(a, b))
643 {
644 if (first)
645 {
646 minValue = a;
647 maxValue = b;
648 first = false;
649 }
650 else
651 {
652 minValue = std::min(a, minValue);
653 maxValue = std::max(b, maxValue);
654 }
655 }
656 }
657
658 if (first)
659 {
660 minValue = 0;
661 maxValue = 0;
662 }
663 }
664
665 void RadiographyScene::ExtractLayerFromRenderedScene(Orthanc::ImageAccessor& layer,
666 const Orthanc::ImageAccessor& renderedScene,
667 size_t layerIndex,
668 bool isCropped,
669 ImageInterpolation interpolation)
670 {
671 Extent2D sceneExtent = GetSceneExtent(isCropped);
672
673 double pixelSpacingX = sceneExtent.GetWidth() / renderedScene.GetWidth();
674 double pixelSpacingY = sceneExtent.GetHeight() / renderedScene.GetHeight();
675
676 AffineTransform2D view = AffineTransform2D::Combine(
677 AffineTransform2D::CreateScaling(1.0 / pixelSpacingX, 1.0 / pixelSpacingY),
678 AffineTransform2D::CreateOffset(-sceneExtent.GetX1(), -sceneExtent.GetY1()));
679
680 AffineTransform2D layerToSceneTransform = AffineTransform2D::Combine(
681 view,
682 GetLayer(layerIndex).GetTransform());
683
684 AffineTransform2D sceneToLayerTransform = AffineTransform2D::Invert(layerToSceneTransform);
685 sceneToLayerTransform.Apply(layer, renderedScene, interpolation, false);
686 }
687
688 Orthanc::Image* RadiographyScene::ExportToImage(double pixelSpacingX,
689 double pixelSpacingY,
690 ImageInterpolation interpolation,
691 bool invert,
692 int64_t maxValue /* for inversion */,
693 bool autoCrop,
694 bool applyWindowing)
695 {
696 if (pixelSpacingX <= 0 ||
697 pixelSpacingY <= 0)
698 {
699 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
700 }
701
702 Extent2D extent = GetSceneExtent(autoCrop);
703
704 int w = boost::math::iround(extent.GetWidth() / pixelSpacingX);
705 int h = boost::math::iround(extent.GetHeight() / pixelSpacingY);
706
707 if (w < 0 || h < 0)
708 {
709 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
710 }
711
712 Orthanc::Image layers(Orthanc::PixelFormat_Float32,
713 static_cast<unsigned int>(w),
714 static_cast<unsigned int>(h), false);
715
716 AffineTransform2D view = AffineTransform2D::Combine(
717 AffineTransform2D::CreateScaling(1.0 / pixelSpacingX, 1.0 / pixelSpacingY),
718 AffineTransform2D::CreateOffset(-extent.GetX1(), -extent.GetY1()));
719
720 // wipe background before rendering
721 if (GetPreferredPhotomotricDisplayMode() == RadiographyPhotometricDisplayMode_Monochrome1)
722 {
723 Orthanc::ImageProcessing::Set(layers, 65535);
724 }
725 else
726 {
727 Orthanc::ImageProcessing::Set(layers, 0);
728 }
729
730 Render(layers, view, interpolation, applyWindowing);
731
732 std::unique_ptr<Orthanc::Image> rendered(new Orthanc::Image(Orthanc::PixelFormat_Grayscale16,
733 layers.GetWidth(), layers.GetHeight(), false));
734
735 Orthanc::ImageProcessing::Convert(*rendered, layers);
736 if (invert)
737 Orthanc::ImageProcessing::Invert(*rendered, maxValue);
738
739 return rendered.release();
740 }
741
742
743 Orthanc::Image* RadiographyScene::ExportToCreateDicomRequestAndImage(Json::Value& createDicomRequestContent,
744 const Json::Value& dicomTags,
745 const std::string& parentOrthancId,
746 double pixelSpacingX,
747 double pixelSpacingY,
748 bool invert,
749 bool autoCrop,
750 ImageInterpolation interpolation)
751 {
752 LOG(INFO) << "Exporting RadiographyScene to DICOM";
753
754 std::unique_ptr<Orthanc::Image> rendered(ExportToImage(pixelSpacingX, pixelSpacingY, interpolation, autoCrop, false)); // note: we don't invert the image in the pixels data because we'll set the PhotometricDisplayMode correctly in the DICOM tags
755
756 createDicomRequestContent["Tags"] = dicomTags;
757
758 RadiographyPhotometricDisplayMode photometricMode = GetPreferredPhotomotricDisplayMode();
759 if ((invert && photometricMode != RadiographyPhotometricDisplayMode_Monochrome2) ||
760 (!invert && photometricMode == RadiographyPhotometricDisplayMode_Monochrome1))
761 {
762 createDicomRequestContent["Tags"]["PhotometricInterpretation"] = "MONOCHROME1";
763 }
764 else
765 {
766 createDicomRequestContent["Tags"]["PhotometricInterpretation"] = "MONOCHROME2";
767 }
768
769 // WARNING: The order of PixelSpacing is Y/X. We use "%0.8f" to
770 // avoid floating-point numbers to grow over 16 characters,
771 // which would be invalid according to DICOM standard
772 // ("dciodvfy" would complain).
773 char buf[32];
774 sprintf(buf, "%0.8f\\%0.8f", pixelSpacingY, pixelSpacingX);
775
776 createDicomRequestContent["Tags"]["PixelSpacing"] = buf;
777
778 float center, width;
779 if (GetWindowing(center, width))
780 {
781 createDicomRequestContent["Tags"]["WindowCenter"] =
782 boost::lexical_cast<std::string>(boost::math::iround(center));
783
784 createDicomRequestContent["Tags"]["WindowWidth"] =
785 boost::lexical_cast<std::string>(boost::math::iround(width));
786 }
787
788 if (!parentOrthancId.empty())
789 {
790 createDicomRequestContent["Parent"] = parentOrthancId;
791 }
792
793 return rendered.release();
794 }
795
796
797 void RadiographyScene::ExportToCreateDicomRequest(Json::Value& createDicomRequestContent,
798 const Json::Value& dicomTags,
799 const std::string& parentOrthancId,
800 double pixelSpacingX,
801 double pixelSpacingY,
802 bool invert,
803 bool autoCrop,
804 ImageInterpolation interpolation,
805 bool usePam)
806 {
807 LOG(INFO) << "Exporting RadiographyScene to DICOM";
808 VLOG(1) << "Exporting RadiographyScene to: export to image";
809
810 std::unique_ptr<Orthanc::Image> rendered(ExportToCreateDicomRequestAndImage(createDicomRequestContent, dicomTags, parentOrthancId, pixelSpacingX, pixelSpacingY, invert, autoCrop, interpolation));
811
812 // convert the image into base64 for inclusing in the createDicomRequest
813 std::string base64;
814
815 {
816 std::string content;
817
818 if (usePam)
819 {
820 VLOG(1) << "Exporting RadiographyScene: convert to PAM";
821 Orthanc::PamWriter writer;
822 writer.WriteToMemory(content, *rendered);
823 }
824 else
825 {
826 Orthanc::PngWriter writer;
827 writer.WriteToMemory(content, *rendered);
828 }
829
830 VLOG(1) << "Exporting RadiographyScene: encoding to base64";
831 Orthanc::Toolbox::EncodeBase64(base64, content);
832 }
833
834 // This is Data URI scheme: https://en.wikipedia.org/wiki/Data_URI_scheme
835 createDicomRequestContent["Content"] = ("data:" +
836 std::string(usePam ? Orthanc::MIME_PAM : Orthanc::MIME_PNG) +
837 ";base64," + base64);
838
839 VLOG(1) << "Exporting RadiographyScene: create-dicom request is ready";
840 }
841
842
843 void RadiographyScene::ExportDicom(Deprecated::OrthancApiClient& orthanc,
844 const Json::Value& dicomTags,
845 const std::string& parentOrthancId,
846 double pixelSpacingX,
847 double pixelSpacingY,
848 bool invert,
849 bool autoCrop,
850 ImageInterpolation interpolation,
851 bool usePam)
852 {
853 Json::Value createDicomRequestContent;
854
855 ExportToCreateDicomRequest(createDicomRequestContent, dicomTags, parentOrthancId, pixelSpacingX, pixelSpacingY, invert, autoCrop, interpolation, usePam);
856
857 orthanc.PostJsonAsyncExpectJson(
858 "/tools/create-dicom", createDicomRequestContent,
859 new Deprecated::DeprecatedCallable<RadiographyScene, Deprecated::OrthancApiClient::JsonResponseReadyMessage>
860 (GetSharedObserver(), &RadiographyScene::OnDicomExported),
861 NULL, NULL);
862
863 }
864
865
866 // Export using PAM is faster than using PNG, but requires Orthanc
867 // core >= 1.4.3
868 void RadiographyScene::ExportDicom(Deprecated::OrthancApiClient& orthanc,
869 const Orthanc::DicomMap& dicom,
870 const std::string& parentOrthancId,
871 double pixelSpacingX,
872 double pixelSpacingY,
873 bool invert,
874 bool autoCrop,
875 ImageInterpolation interpolation,
876 bool usePam)
877 {
878 std::set<Orthanc::DicomTag> tags;
879 dicom.GetTags(tags);
880
881 Json::Value jsonTags = Json::objectValue;
882
883 for (std::set<Orthanc::DicomTag>::const_iterator
884 tag = tags.begin(); tag != tags.end(); ++tag)
885 {
886 const Orthanc::DicomValue& value = dicom.GetValue(*tag);
887 if (!value.IsNull() &&
888 !value.IsBinary())
889 {
890 jsonTags[tag->Format()] = value.GetContent();
891 }
892 }
893
894 ExportDicom(orthanc, jsonTags, parentOrthancId, pixelSpacingX, pixelSpacingY, invert, autoCrop, interpolation, usePam);
895 }
896
897 void RadiographyScene::OnDicomExported(const Deprecated::OrthancApiClient::JsonResponseReadyMessage& message)
898 {
899 LOG(INFO) << "DICOM export was successful: "
900 << message.GetJson().toStyledString();
901 }
902
903
904 void RadiographyScene::OnDicomWebReceived(const Deprecated::IWebService::HttpRequestSuccessMessage& message)
905 {
906 LOG(INFO) << "DICOMweb WADO-RS received: " << message.GetAnswerSize() << " bytes";
907
908 const Deprecated::IWebService::HttpHeaders& h = message.GetAnswerHttpHeaders();
909 for (Deprecated::IWebService::HttpHeaders::const_iterator
910 it = h.begin(); it != h.end(); ++it)
911 {
912 printf("[%s] = [%s]\n", it->first.c_str(), it->second.c_str());
913 }
914 }
915
916 void RadiographyScene::ExportToScene2D(Scene2D& output) const
917 {
918 int depth = 0;
919 for (Layers::const_iterator it = layers_.begin();
920 it != layers_.end(); ++it)
921 {
922 assert(it->second != NULL);
923
924 std::unique_ptr<ISceneLayer> layer;
925 if (dynamic_cast<RadiographyDicomLayer*>(it->second))
926 {
927 RadiographyDicomLayer* oldLayer = dynamic_cast<RadiographyDicomLayer*>(it->second);
928
929 std::unique_ptr<FloatTextureSceneLayer> newLayer(new FloatTextureSceneLayer(*(oldLayer->GetSourceImage())));
930
931 newLayer->SetOrigin(oldLayer->GetGeometry().GetPanX(),
932 oldLayer->GetGeometry().GetPanY()
933 );
934 newLayer->SetAngle(oldLayer->GetGeometry().GetAngle());
935
936 layer.reset(newLayer.release());
937
938 // TODO: windowing dynamic_cast
939 }
940 else if (dynamic_cast<RadiographyTextLayer*>(it->second))
941 {
942 RadiographyTextLayer* oldLayer = dynamic_cast<RadiographyTextLayer*>(it->second);
943
944 std::unique_ptr<TextSceneLayer> newLayer(new TextSceneLayer());
945
946 newLayer->SetText(oldLayer->GetText());
947 newLayer->SetColor(oldLayer->GetForegroundGreyLevel(),
948 oldLayer->GetForegroundGreyLevel(),
949 oldLayer->GetForegroundGreyLevel()
950 );
951 newLayer->SetPosition(oldLayer->GetGeometry().GetPanX(),
952 oldLayer->GetGeometry().GetPanY()
953 );
954 newLayer->SetFontIndex(1);
955 newLayer->SetAnchor(BitmapAnchor_TopLeft);
956 //newLayer->SetAngle(oldLayer->GetGeometry().GetAngle());
957
958 layer.reset(newLayer.release());
959 }
960
961 output.SetLayer(depth++, layer.release());
962
963 }
964
965 }
966
967 }
968