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