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