Mercurial > hg > orthanc-stone
comparison Framework/Radiography/RadiographyScene.cpp @ 408:6834c236b36d
reorganization
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Mon, 12 Nov 2018 14:52:10 +0100 |
parents | |
children | 99c9b3238008 |
comparison
equal
deleted
inserted
replaced
407:842a3c7cfdc0 | 408:6834c236b36d |
---|---|
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/ImageGeometry.h" | |
25 #include "../Toolbox/DicomFrameConverter.h" | |
26 | |
27 #include <Core/Images/Image.h> | |
28 #include <Core/Images/ImageProcessing.h> | |
29 #include <Core/Images/PamReader.h> | |
30 #include <Core/Images/PamWriter.h> | |
31 #include <Core/Images/PngWriter.h> | |
32 #include <Core/OrthancException.h> | |
33 #include <Core/Toolbox.h> | |
34 #include <Plugins/Samples/Common/DicomDatasetReader.h> | |
35 #include <Plugins/Samples/Common/FullOrthancDataset.h> | |
36 | |
37 | |
38 namespace OrthancStone | |
39 { | |
40 static double Square(double x) | |
41 { | |
42 return x * x; | |
43 } | |
44 | |
45 | |
46 static Matrix CreateOffsetMatrix(double dx, | |
47 double dy) | |
48 { | |
49 Matrix m = LinearAlgebra::IdentityMatrix(3); | |
50 m(0, 2) = dx; | |
51 m(1, 2) = dy; | |
52 return m; | |
53 } | |
54 | |
55 | |
56 static Matrix CreateScalingMatrix(double sx, | |
57 double sy) | |
58 { | |
59 Matrix m = LinearAlgebra::IdentityMatrix(3); | |
60 m(0, 0) = sx; | |
61 m(1, 1) = sy; | |
62 return m; | |
63 } | |
64 | |
65 | |
66 static Matrix CreateRotationMatrix(double angle) | |
67 { | |
68 Matrix m; | |
69 const double v[] = { cos(angle), -sin(angle), 0, | |
70 sin(angle), cos(angle), 0, | |
71 0, 0, 1 }; | |
72 LinearAlgebra::FillMatrix(m, 3, 3, v); | |
73 return m; | |
74 } | |
75 | |
76 | |
77 static void ApplyTransform(double& x /* inout */, | |
78 double& y /* inout */, | |
79 const Matrix& transform) | |
80 { | |
81 Vector p; | |
82 LinearAlgebra::AssignVector(p, x, y, 1); | |
83 | |
84 Vector q = LinearAlgebra::Product(transform, p); | |
85 | |
86 if (!LinearAlgebra::IsNear(q[2], 1.0)) | |
87 { | |
88 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); | |
89 } | |
90 else | |
91 { | |
92 x = q[0]; | |
93 y = q[1]; | |
94 } | |
95 } | |
96 | |
97 | |
98 | |
99 void RadiographyScene::Layer::UpdateTransform() | |
100 { | |
101 transform_ = CreateScalingMatrix(pixelSpacingX_, pixelSpacingY_); | |
102 | |
103 double centerX, centerY; | |
104 GetCenter(centerX, centerY); | |
105 | |
106 transform_ = LinearAlgebra::Product( | |
107 CreateOffsetMatrix(panX_ + centerX, panY_ + centerY), | |
108 CreateRotationMatrix(angle_), | |
109 CreateOffsetMatrix(-centerX, -centerY), | |
110 transform_); | |
111 | |
112 LinearAlgebra::InvertMatrix(transformInverse_, transform_); | |
113 } | |
114 | |
115 | |
116 void RadiographyScene::Layer::AddToExtent(Extent2D& extent, | |
117 double x, | |
118 double y) const | |
119 { | |
120 ApplyTransform(x, y, transform_); | |
121 extent.AddPoint(x, y); | |
122 } | |
123 | |
124 | |
125 void RadiographyScene::Layer::GetCornerInternal(double& x, | |
126 double& y, | |
127 Corner corner, | |
128 unsigned int cropX, | |
129 unsigned int cropY, | |
130 unsigned int cropWidth, | |
131 unsigned int cropHeight) const | |
132 { | |
133 double dx = static_cast<double>(cropX); | |
134 double dy = static_cast<double>(cropY); | |
135 double dwidth = static_cast<double>(cropWidth); | |
136 double dheight = static_cast<double>(cropHeight); | |
137 | |
138 switch (corner) | |
139 { | |
140 case Corner_TopLeft: | |
141 x = dx; | |
142 y = dy; | |
143 break; | |
144 | |
145 case Corner_TopRight: | |
146 x = dx + dwidth; | |
147 y = dy; | |
148 break; | |
149 | |
150 case Corner_BottomLeft: | |
151 x = dx; | |
152 y = dy + dheight; | |
153 break; | |
154 | |
155 case Corner_BottomRight: | |
156 x = dx + dwidth; | |
157 y = dy + dheight; | |
158 break; | |
159 | |
160 default: | |
161 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); | |
162 } | |
163 | |
164 ApplyTransform(x, y, transform_); | |
165 } | |
166 | |
167 | |
168 bool RadiographyScene::Layer::Contains(double x, | |
169 double y) const | |
170 { | |
171 ApplyTransform(x, y, transformInverse_); | |
172 | |
173 unsigned int cropX, cropY, cropWidth, cropHeight; | |
174 GetCrop(cropX, cropY, cropWidth, cropHeight); | |
175 | |
176 return (x >= cropX && x <= cropX + cropWidth && | |
177 y >= cropY && y <= cropY + cropHeight); | |
178 } | |
179 | |
180 | |
181 void RadiographyScene::Layer::DrawBorders(CairoContext& context, | |
182 double zoom) | |
183 { | |
184 unsigned int cx, cy, width, height; | |
185 GetCrop(cx, cy, width, height); | |
186 | |
187 double dx = static_cast<double>(cx); | |
188 double dy = static_cast<double>(cy); | |
189 double dwidth = static_cast<double>(width); | |
190 double dheight = static_cast<double>(height); | |
191 | |
192 cairo_t* cr = context.GetObject(); | |
193 cairo_set_line_width(cr, 2.0 / zoom); | |
194 | |
195 double x, y; | |
196 x = dx; | |
197 y = dy; | |
198 ApplyTransform(x, y, transform_); | |
199 cairo_move_to(cr, x, y); | |
200 | |
201 x = dx + dwidth; | |
202 y = dy; | |
203 ApplyTransform(x, y, transform_); | |
204 cairo_line_to(cr, x, y); | |
205 | |
206 x = dx + dwidth; | |
207 y = dy + dheight; | |
208 ApplyTransform(x, y, transform_); | |
209 cairo_line_to(cr, x, y); | |
210 | |
211 x = dx; | |
212 y = dy + dheight; | |
213 ApplyTransform(x, y, transform_); | |
214 cairo_line_to(cr, x, y); | |
215 | |
216 x = dx; | |
217 y = dy; | |
218 ApplyTransform(x, y, transform_); | |
219 cairo_line_to(cr, x, y); | |
220 | |
221 cairo_stroke(cr); | |
222 } | |
223 | |
224 | |
225 RadiographyScene::Layer::Layer() : | |
226 index_(0), | |
227 hasSize_(false), | |
228 width_(0), | |
229 height_(0), | |
230 hasCrop_(false), | |
231 pixelSpacingX_(1), | |
232 pixelSpacingY_(1), | |
233 panX_(0), | |
234 panY_(0), | |
235 angle_(0), | |
236 resizeable_(false) | |
237 { | |
238 UpdateTransform(); | |
239 } | |
240 | |
241 | |
242 void RadiographyScene::Layer::SetCrop(unsigned int x, | |
243 unsigned int y, | |
244 unsigned int width, | |
245 unsigned int height) | |
246 { | |
247 if (!hasSize_) | |
248 { | |
249 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); | |
250 } | |
251 | |
252 if (x + width > width_ || | |
253 y + height > height_) | |
254 { | |
255 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); | |
256 } | |
257 | |
258 hasCrop_ = true; | |
259 cropX_ = x; | |
260 cropY_ = y; | |
261 cropWidth_ = width; | |
262 cropHeight_ = height; | |
263 | |
264 UpdateTransform(); | |
265 } | |
266 | |
267 | |
268 void RadiographyScene::Layer::GetCrop(unsigned int& x, | |
269 unsigned int& y, | |
270 unsigned int& width, | |
271 unsigned int& height) const | |
272 { | |
273 if (hasCrop_) | |
274 { | |
275 x = cropX_; | |
276 y = cropY_; | |
277 width = cropWidth_; | |
278 height = cropHeight_; | |
279 } | |
280 else | |
281 { | |
282 x = 0; | |
283 y = 0; | |
284 width = width_; | |
285 height = height_; | |
286 } | |
287 } | |
288 | |
289 | |
290 void RadiographyScene::Layer::SetAngle(double angle) | |
291 { | |
292 angle_ = angle; | |
293 UpdateTransform(); | |
294 } | |
295 | |
296 | |
297 void RadiographyScene::Layer::SetSize(unsigned int width, | |
298 unsigned int height) | |
299 { | |
300 if (hasSize_ && | |
301 (width != width_ || | |
302 height != height_)) | |
303 { | |
304 throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageSize); | |
305 } | |
306 | |
307 hasSize_ = true; | |
308 width_ = width; | |
309 height_ = height; | |
310 | |
311 UpdateTransform(); | |
312 } | |
313 | |
314 | |
315 Extent2D RadiographyScene::Layer::GetExtent() const | |
316 { | |
317 Extent2D extent; | |
318 | |
319 unsigned int x, y, width, height; | |
320 GetCrop(x, y, width, height); | |
321 | |
322 double dx = static_cast<double>(x); | |
323 double dy = static_cast<double>(y); | |
324 double dwidth = static_cast<double>(width); | |
325 double dheight = static_cast<double>(height); | |
326 | |
327 AddToExtent(extent, dx, dy); | |
328 AddToExtent(extent, dx + dwidth, dy); | |
329 AddToExtent(extent, dx, dy + dheight); | |
330 AddToExtent(extent, dx + dwidth, dy + dheight); | |
331 | |
332 return extent; | |
333 } | |
334 | |
335 | |
336 bool RadiographyScene::Layer::GetPixel(unsigned int& imageX, | |
337 unsigned int& imageY, | |
338 double sceneX, | |
339 double sceneY) const | |
340 { | |
341 if (width_ == 0 || | |
342 height_ == 0) | |
343 { | |
344 return false; | |
345 } | |
346 else | |
347 { | |
348 ApplyTransform(sceneX, sceneY, transformInverse_); | |
349 | |
350 int x = static_cast<int>(std::floor(sceneX)); | |
351 int y = static_cast<int>(std::floor(sceneY)); | |
352 | |
353 if (x < 0) | |
354 { | |
355 imageX = 0; | |
356 } | |
357 else if (x >= static_cast<int>(width_)) | |
358 { | |
359 imageX = width_; | |
360 } | |
361 else | |
362 { | |
363 imageX = static_cast<unsigned int>(x); | |
364 } | |
365 | |
366 if (y < 0) | |
367 { | |
368 imageY = 0; | |
369 } | |
370 else if (y >= static_cast<int>(height_)) | |
371 { | |
372 imageY = height_; | |
373 } | |
374 else | |
375 { | |
376 imageY = static_cast<unsigned int>(y); | |
377 } | |
378 | |
379 return true; | |
380 } | |
381 } | |
382 | |
383 | |
384 void RadiographyScene::Layer::SetPan(double x, | |
385 double y) | |
386 { | |
387 panX_ = x; | |
388 panY_ = y; | |
389 UpdateTransform(); | |
390 } | |
391 | |
392 | |
393 void RadiographyScene::Layer::SetPixelSpacing(double x, | |
394 double y) | |
395 { | |
396 pixelSpacingX_ = x; | |
397 pixelSpacingY_ = y; | |
398 UpdateTransform(); | |
399 } | |
400 | |
401 | |
402 void RadiographyScene::Layer::GetCenter(double& centerX, | |
403 double& centerY) const | |
404 { | |
405 centerX = static_cast<double>(width_) / 2.0; | |
406 centerY = static_cast<double>(height_) / 2.0; | |
407 ApplyTransform(centerX, centerY, transform_); | |
408 } | |
409 | |
410 | |
411 void RadiographyScene::Layer::GetCorner(double& x /* out */, | |
412 double& y /* out */, | |
413 Corner corner) const | |
414 { | |
415 unsigned int cropX, cropY, cropWidth, cropHeight; | |
416 GetCrop(cropX, cropY, cropWidth, cropHeight); | |
417 GetCornerInternal(x, y, corner, cropX, cropY, cropWidth, cropHeight); | |
418 } | |
419 | |
420 | |
421 bool RadiographyScene::Layer::LookupCorner(Corner& corner /* out */, | |
422 double x, | |
423 double y, | |
424 double zoom, | |
425 double viewportDistance) const | |
426 { | |
427 static const Corner CORNERS[] = { | |
428 Corner_TopLeft, | |
429 Corner_TopRight, | |
430 Corner_BottomLeft, | |
431 Corner_BottomRight | |
432 }; | |
433 | |
434 unsigned int cropX, cropY, cropWidth, cropHeight; | |
435 GetCrop(cropX, cropY, cropWidth, cropHeight); | |
436 | |
437 double threshold = Square(viewportDistance / zoom); | |
438 | |
439 for (size_t i = 0; i < 4; i++) | |
440 { | |
441 double cx, cy; | |
442 GetCornerInternal(cx, cy, CORNERS[i], cropX, cropY, cropWidth, cropHeight); | |
443 | |
444 double d = Square(cx - x) + Square(cy - y); | |
445 | |
446 if (d <= threshold) | |
447 { | |
448 corner = CORNERS[i]; | |
449 return true; | |
450 } | |
451 } | |
452 | |
453 return false; | |
454 } | |
455 | |
456 | |
457 | |
458 RadiographyScene::LayerAccessor::LayerAccessor(RadiographyScene& scene, | |
459 size_t index) : | |
460 scene_(scene), | |
461 index_(index) | |
462 { | |
463 Layers::iterator layer = scene.layers_.find(index); | |
464 if (layer == scene.layers_.end()) | |
465 { | |
466 layer_ = NULL; | |
467 } | |
468 else | |
469 { | |
470 assert(layer->second != NULL); | |
471 layer_ = layer->second; | |
472 } | |
473 } | |
474 | |
475 | |
476 RadiographyScene::LayerAccessor::LayerAccessor(RadiographyScene& scene, | |
477 double x, | |
478 double y) : | |
479 scene_(scene), | |
480 index_(0) // Dummy initialization | |
481 { | |
482 if (scene.LookupLayer(index_, x, y)) | |
483 { | |
484 Layers::iterator layer = scene.layers_.find(index_); | |
485 | |
486 if (layer == scene.layers_.end()) | |
487 { | |
488 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); | |
489 } | |
490 else | |
491 { | |
492 assert(layer->second != NULL); | |
493 layer_ = layer->second; | |
494 } | |
495 } | |
496 else | |
497 { | |
498 layer_ = NULL; | |
499 } | |
500 } | |
501 | |
502 | |
503 RadiographyScene& RadiographyScene::LayerAccessor::GetScene() const | |
504 { | |
505 if (IsValid()) | |
506 { | |
507 return scene_; | |
508 } | |
509 else | |
510 { | |
511 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); | |
512 } | |
513 } | |
514 | |
515 | |
516 size_t RadiographyScene::LayerAccessor::GetIndex() const | |
517 { | |
518 if (IsValid()) | |
519 { | |
520 return index_; | |
521 } | |
522 else | |
523 { | |
524 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); | |
525 } | |
526 } | |
527 | |
528 | |
529 RadiographyScene::Layer& RadiographyScene::LayerAccessor::GetLayer() const | |
530 { | |
531 if (IsValid()) | |
532 { | |
533 return *layer_; | |
534 } | |
535 else | |
536 { | |
537 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); | |
538 } | |
539 } | |
540 | |
541 | |
542 | |
543 class RadiographyScene::AlphaLayer : public Layer | |
544 { | |
545 private: | |
546 const RadiographyScene& scene_; | |
547 std::auto_ptr<Orthanc::ImageAccessor> alpha_; // Grayscale8 | |
548 bool useWindowing_; | |
549 float foreground_; | |
550 | |
551 public: | |
552 AlphaLayer(const RadiographyScene& scene) : | |
553 scene_(scene), | |
554 useWindowing_(true), | |
555 foreground_(0) | |
556 { | |
557 } | |
558 | |
559 | |
560 void SetForegroundValue(float foreground) | |
561 { | |
562 useWindowing_ = false; | |
563 foreground_ = foreground; | |
564 } | |
565 | |
566 | |
567 void SetAlpha(Orthanc::ImageAccessor* image) | |
568 { | |
569 std::auto_ptr<Orthanc::ImageAccessor> raii(image); | |
570 | |
571 if (image == NULL) | |
572 { | |
573 throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); | |
574 } | |
575 | |
576 if (image->GetFormat() != Orthanc::PixelFormat_Grayscale8) | |
577 { | |
578 throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat); | |
579 } | |
580 | |
581 SetSize(image->GetWidth(), image->GetHeight()); | |
582 alpha_ = raii; | |
583 } | |
584 | |
585 | |
586 void LoadText(const Orthanc::Font& font, | |
587 const std::string& utf8) | |
588 { | |
589 SetAlpha(font.RenderAlpha(utf8)); | |
590 } | |
591 | |
592 | |
593 virtual bool GetDefaultWindowing(float& center, | |
594 float& width) const | |
595 { | |
596 return false; | |
597 } | |
598 | |
599 | |
600 virtual void Render(Orthanc::ImageAccessor& buffer, | |
601 const Matrix& viewTransform, | |
602 ImageInterpolation interpolation) const | |
603 { | |
604 if (alpha_.get() == NULL) | |
605 { | |
606 return; | |
607 } | |
608 | |
609 if (buffer.GetFormat() != Orthanc::PixelFormat_Float32) | |
610 { | |
611 throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat); | |
612 } | |
613 | |
614 unsigned int cropX, cropY, cropWidth, cropHeight; | |
615 GetCrop(cropX, cropY, cropWidth, cropHeight); | |
616 | |
617 Matrix m = LinearAlgebra::Product(viewTransform, | |
618 GetTransform(), | |
619 CreateOffsetMatrix(cropX, cropY)); | |
620 | |
621 Orthanc::ImageAccessor cropped; | |
622 alpha_->GetRegion(cropped, cropX, cropY, cropWidth, cropHeight); | |
623 | |
624 Orthanc::Image tmp(Orthanc::PixelFormat_Grayscale8, buffer.GetWidth(), buffer.GetHeight(), false); | |
625 ApplyProjectiveTransform(tmp, cropped, m, interpolation, true /* clear */); | |
626 | |
627 // Blit | |
628 const unsigned int width = buffer.GetWidth(); | |
629 const unsigned int height = buffer.GetHeight(); | |
630 | |
631 float value = foreground_; | |
632 | |
633 if (useWindowing_) | |
634 { | |
635 float center, width; | |
636 if (scene_.GetWindowing(center, width)) | |
637 { | |
638 value = center + width / 2.0f; | |
639 } | |
640 } | |
641 | |
642 for (unsigned int y = 0; y < height; y++) | |
643 { | |
644 float *q = reinterpret_cast<float*>(buffer.GetRow(y)); | |
645 const uint8_t *p = reinterpret_cast<uint8_t*>(tmp.GetRow(y)); | |
646 | |
647 for (unsigned int x = 0; x < width; x++, p++, q++) | |
648 { | |
649 float a = static_cast<float>(*p) / 255.0f; | |
650 | |
651 *q = (a * value + (1.0f - a) * (*q)); | |
652 } | |
653 } | |
654 } | |
655 | |
656 | |
657 virtual bool GetRange(float& minValue, | |
658 float& maxValue) const | |
659 { | |
660 if (useWindowing_) | |
661 { | |
662 return false; | |
663 } | |
664 else | |
665 { | |
666 minValue = 0; | |
667 maxValue = 0; | |
668 | |
669 if (foreground_ < 0) | |
670 { | |
671 minValue = foreground_; | |
672 } | |
673 | |
674 if (foreground_ > 0) | |
675 { | |
676 maxValue = foreground_; | |
677 } | |
678 | |
679 return true; | |
680 } | |
681 } | |
682 }; | |
683 | |
684 | |
685 | |
686 class RadiographyScene::DicomLayer : public Layer | |
687 { | |
688 private: | |
689 std::auto_ptr<Orthanc::ImageAccessor> source_; // Content of PixelData | |
690 std::auto_ptr<DicomFrameConverter> converter_; | |
691 std::auto_ptr<Orthanc::ImageAccessor> converted_; // Float32 | |
692 | |
693 static OrthancPlugins::DicomTag ConvertTag(const Orthanc::DicomTag& tag) | |
694 { | |
695 return OrthancPlugins::DicomTag(tag.GetGroup(), tag.GetElement()); | |
696 } | |
697 | |
698 | |
699 void ApplyConverter() | |
700 { | |
701 if (source_.get() != NULL && | |
702 converter_.get() != NULL) | |
703 { | |
704 converted_.reset(converter_->ConvertFrame(*source_)); | |
705 } | |
706 } | |
707 | |
708 public: | |
709 void SetDicomTags(const OrthancPlugins::FullOrthancDataset& dataset) | |
710 { | |
711 converter_.reset(new DicomFrameConverter); | |
712 converter_->ReadParameters(dataset); | |
713 ApplyConverter(); | |
714 | |
715 std::string tmp; | |
716 Vector pixelSpacing; | |
717 | |
718 if (dataset.GetStringValue(tmp, ConvertTag(Orthanc::DICOM_TAG_PIXEL_SPACING)) && | |
719 LinearAlgebra::ParseVector(pixelSpacing, tmp) && | |
720 pixelSpacing.size() == 2) | |
721 { | |
722 SetPixelSpacing(pixelSpacing[0], pixelSpacing[1]); | |
723 } | |
724 | |
725 //SetPan(-0.5 * GetPixelSpacingX(), -0.5 * GetPixelSpacingY()); | |
726 | |
727 OrthancPlugins::DicomDatasetReader reader(dataset); | |
728 | |
729 unsigned int width, height; | |
730 if (!reader.GetUnsignedIntegerValue(width, ConvertTag(Orthanc::DICOM_TAG_COLUMNS)) || | |
731 !reader.GetUnsignedIntegerValue(height, ConvertTag(Orthanc::DICOM_TAG_ROWS))) | |
732 { | |
733 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); | |
734 } | |
735 else | |
736 { | |
737 SetSize(width, height); | |
738 } | |
739 } | |
740 | |
741 | |
742 void SetSourceImage(Orthanc::ImageAccessor* image) // Takes ownership | |
743 { | |
744 std::auto_ptr<Orthanc::ImageAccessor> raii(image); | |
745 | |
746 if (image == NULL) | |
747 { | |
748 throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); | |
749 } | |
750 | |
751 SetSize(image->GetWidth(), image->GetHeight()); | |
752 | |
753 source_ = raii; | |
754 ApplyConverter(); | |
755 } | |
756 | |
757 | |
758 virtual void Render(Orthanc::ImageAccessor& buffer, | |
759 const Matrix& viewTransform, | |
760 ImageInterpolation interpolation) const | |
761 { | |
762 if (converted_.get() != NULL) | |
763 { | |
764 if (converted_->GetFormat() != Orthanc::PixelFormat_Float32) | |
765 { | |
766 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); | |
767 } | |
768 | |
769 unsigned int cropX, cropY, cropWidth, cropHeight; | |
770 GetCrop(cropX, cropY, cropWidth, cropHeight); | |
771 | |
772 Matrix m = LinearAlgebra::Product(viewTransform, | |
773 GetTransform(), | |
774 CreateOffsetMatrix(cropX, cropY)); | |
775 | |
776 Orthanc::ImageAccessor cropped; | |
777 converted_->GetRegion(cropped, cropX, cropY, cropWidth, cropHeight); | |
778 | |
779 ApplyProjectiveTransform(buffer, cropped, m, interpolation, false); | |
780 } | |
781 } | |
782 | |
783 | |
784 virtual bool GetDefaultWindowing(float& center, | |
785 float& width) const | |
786 { | |
787 if (converter_.get() != NULL && | |
788 converter_->HasDefaultWindow()) | |
789 { | |
790 center = static_cast<float>(converter_->GetDefaultWindowCenter()); | |
791 width = static_cast<float>(converter_->GetDefaultWindowWidth()); | |
792 return true; | |
793 } | |
794 else | |
795 { | |
796 return false; | |
797 } | |
798 } | |
799 | |
800 | |
801 virtual bool GetRange(float& minValue, | |
802 float& maxValue) const | |
803 { | |
804 if (converted_.get() != NULL) | |
805 { | |
806 if (converted_->GetFormat() != Orthanc::PixelFormat_Float32) | |
807 { | |
808 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); | |
809 } | |
810 | |
811 Orthanc::ImageProcessing::GetMinMaxFloatValue(minValue, maxValue, *converted_); | |
812 return true; | |
813 } | |
814 else | |
815 { | |
816 return false; | |
817 } | |
818 } | |
819 }; | |
820 | |
821 | |
822 RadiographyScene::Layer& RadiographyScene::RegisterLayer(RadiographyScene::Layer* layer) | |
823 { | |
824 if (layer == NULL) | |
825 { | |
826 throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); | |
827 } | |
828 | |
829 std::auto_ptr<Layer> raii(layer); | |
830 | |
831 size_t index = countLayers_++; | |
832 raii->SetIndex(index); | |
833 layers_[index] = raii.release(); | |
834 | |
835 EmitMessage(GeometryChangedMessage(*this)); | |
836 EmitMessage(ContentChangedMessage(*this)); | |
837 | |
838 return *layer; | |
839 } | |
840 | |
841 | |
842 RadiographyScene::RadiographyScene(MessageBroker& broker, | |
843 OrthancApiClient& orthanc) : | |
844 IObserver(broker), | |
845 IObservable(broker), | |
846 orthanc_(orthanc), | |
847 countLayers_(0), | |
848 hasWindowing_(false), | |
849 windowingCenter_(0), // Dummy initialization | |
850 windowingWidth_(0) // Dummy initialization | |
851 { | |
852 } | |
853 | |
854 | |
855 RadiographyScene::~RadiographyScene() | |
856 { | |
857 for (Layers::iterator it = layers_.begin(); it != layers_.end(); it++) | |
858 { | |
859 assert(it->second != NULL); | |
860 delete it->second; | |
861 } | |
862 } | |
863 | |
864 | |
865 bool RadiographyScene::GetWindowing(float& center, | |
866 float& width) const | |
867 { | |
868 if (hasWindowing_) | |
869 { | |
870 center = windowingCenter_; | |
871 width = windowingWidth_; | |
872 return true; | |
873 } | |
874 else | |
875 { | |
876 return false; | |
877 } | |
878 } | |
879 | |
880 | |
881 void RadiographyScene::GetWindowingWithDefault(float& center, | |
882 float& width) const | |
883 { | |
884 if (!GetWindowing(center, width)) | |
885 { | |
886 center = 128; | |
887 width = 256; | |
888 } | |
889 } | |
890 | |
891 | |
892 void RadiographyScene::SetWindowing(float center, | |
893 float width) | |
894 { | |
895 hasWindowing_ = true; | |
896 windowingCenter_ = center; | |
897 windowingWidth_ = width; | |
898 } | |
899 | |
900 | |
901 RadiographyScene::Layer& RadiographyScene::LoadText(const Orthanc::Font& font, | |
902 const std::string& utf8) | |
903 { | |
904 std::auto_ptr<AlphaLayer> alpha(new AlphaLayer(*this)); | |
905 alpha->LoadText(font, utf8); | |
906 | |
907 return RegisterLayer(alpha.release()); | |
908 } | |
909 | |
910 | |
911 RadiographyScene::Layer& RadiographyScene::LoadTestBlock(unsigned int width, | |
912 unsigned int height) | |
913 { | |
914 std::auto_ptr<Orthanc::Image> block(new Orthanc::Image(Orthanc::PixelFormat_Grayscale8, width, height, false)); | |
915 | |
916 for (unsigned int padding = 0; | |
917 (width > 2 * padding) && (height > 2 * padding); | |
918 padding++) | |
919 { | |
920 uint8_t color; | |
921 if (255 > 10 * padding) | |
922 { | |
923 color = 255 - 10 * padding; | |
924 } | |
925 else | |
926 { | |
927 color = 0; | |
928 } | |
929 | |
930 Orthanc::ImageAccessor region; | |
931 block->GetRegion(region, padding, padding, width - 2 * padding, height - 2 * padding); | |
932 Orthanc::ImageProcessing::Set(region, color); | |
933 } | |
934 | |
935 std::auto_ptr<AlphaLayer> alpha(new AlphaLayer(*this)); | |
936 alpha->SetAlpha(block.release()); | |
937 | |
938 return RegisterLayer(alpha.release()); | |
939 } | |
940 | |
941 | |
942 RadiographyScene::Layer& RadiographyScene::LoadDicomFrame(const std::string& instance, | |
943 unsigned int frame, | |
944 bool httpCompression) | |
945 { | |
946 Layer& layer = RegisterLayer(new DicomLayer); | |
947 | |
948 { | |
949 IWebService::Headers headers; | |
950 std::string uri = "/instances/" + instance + "/tags"; | |
951 | |
952 orthanc_.GetBinaryAsync( | |
953 uri, headers, | |
954 new Callable<RadiographyScene, OrthancApiClient::BinaryResponseReadyMessage> | |
955 (*this, &RadiographyScene::OnTagsReceived), NULL, | |
956 new Orthanc::SingleValueObject<size_t>(layer.GetIndex())); | |
957 } | |
958 | |
959 { | |
960 IWebService::Headers headers; | |
961 headers["Accept"] = "image/x-portable-arbitrarymap"; | |
962 | |
963 if (httpCompression) | |
964 { | |
965 headers["Accept-Encoding"] = "gzip"; | |
966 } | |
967 | |
968 std::string uri = ("/instances/" + instance + "/frames/" + | |
969 boost::lexical_cast<std::string>(frame) + "/image-uint16"); | |
970 | |
971 orthanc_.GetBinaryAsync( | |
972 uri, headers, | |
973 new Callable<RadiographyScene, OrthancApiClient::BinaryResponseReadyMessage> | |
974 (*this, &RadiographyScene::OnFrameReceived), NULL, | |
975 new Orthanc::SingleValueObject<size_t>(layer.GetIndex())); | |
976 } | |
977 | |
978 return layer; | |
979 } | |
980 | |
981 | |
982 void RadiographyScene::OnTagsReceived(const OrthancApiClient::BinaryResponseReadyMessage& message) | |
983 { | |
984 size_t index = dynamic_cast<const Orthanc::SingleValueObject<size_t>&> | |
985 (message.GetPayload()).GetValue(); | |
986 | |
987 LOG(INFO) << "JSON received: " << message.GetUri().c_str() | |
988 << " (" << message.GetAnswerSize() << " bytes) for layer " << index; | |
989 | |
990 Layers::iterator layer = layers_.find(index); | |
991 if (layer != layers_.end()) | |
992 { | |
993 assert(layer->second != NULL); | |
994 | |
995 OrthancPlugins::FullOrthancDataset dicom(message.GetAnswer(), message.GetAnswerSize()); | |
996 dynamic_cast<DicomLayer*>(layer->second)->SetDicomTags(dicom); | |
997 | |
998 float c, w; | |
999 if (!hasWindowing_ && | |
1000 layer->second->GetDefaultWindowing(c, w)) | |
1001 { | |
1002 hasWindowing_ = true; | |
1003 windowingCenter_ = c; | |
1004 windowingWidth_ = w; | |
1005 } | |
1006 | |
1007 EmitMessage(GeometryChangedMessage(*this)); | |
1008 } | |
1009 } | |
1010 | |
1011 | |
1012 void RadiographyScene::OnFrameReceived(const OrthancApiClient::BinaryResponseReadyMessage& message) | |
1013 { | |
1014 size_t index = dynamic_cast<const Orthanc::SingleValueObject<size_t>&>(message.GetPayload()).GetValue(); | |
1015 | |
1016 LOG(INFO) << "DICOM frame received: " << message.GetUri().c_str() | |
1017 << " (" << message.GetAnswerSize() << " bytes) for layer " << index; | |
1018 | |
1019 Layers::iterator layer = layers_.find(index); | |
1020 if (layer != layers_.end()) | |
1021 { | |
1022 assert(layer->second != NULL); | |
1023 | |
1024 std::string content; | |
1025 if (message.GetAnswerSize() > 0) | |
1026 { | |
1027 content.assign(reinterpret_cast<const char*>(message.GetAnswer()), message.GetAnswerSize()); | |
1028 } | |
1029 | |
1030 std::auto_ptr<Orthanc::PamReader> reader(new Orthanc::PamReader); | |
1031 reader->ReadFromMemory(content); | |
1032 dynamic_cast<DicomLayer*>(layer->second)->SetSourceImage(reader.release()); | |
1033 | |
1034 EmitMessage(ContentChangedMessage(*this)); | |
1035 } | |
1036 } | |
1037 | |
1038 | |
1039 Extent2D RadiographyScene::GetSceneExtent() const | |
1040 { | |
1041 Extent2D extent; | |
1042 | |
1043 for (Layers::const_iterator it = layers_.begin(); | |
1044 it != layers_.end(); ++it) | |
1045 { | |
1046 assert(it->second != NULL); | |
1047 extent.Union(it->second->GetExtent()); | |
1048 } | |
1049 | |
1050 return extent; | |
1051 } | |
1052 | |
1053 | |
1054 void RadiographyScene::Render(Orthanc::ImageAccessor& buffer, | |
1055 const Matrix& viewTransform, | |
1056 ImageInterpolation interpolation) const | |
1057 { | |
1058 Orthanc::ImageProcessing::Set(buffer, 0); | |
1059 | |
1060 // Render layers in the background-to-foreground order | |
1061 for (size_t index = 0; index < countLayers_; index++) | |
1062 { | |
1063 Layers::const_iterator it = layers_.find(index); | |
1064 if (it != layers_.end()) | |
1065 { | |
1066 assert(it->second != NULL); | |
1067 it->second->Render(buffer, viewTransform, interpolation); | |
1068 } | |
1069 } | |
1070 } | |
1071 | |
1072 | |
1073 bool RadiographyScene::LookupLayer(size_t& index /* out */, | |
1074 double x, | |
1075 double y) const | |
1076 { | |
1077 // Render layers in the foreground-to-background order | |
1078 for (size_t i = countLayers_; i > 0; i--) | |
1079 { | |
1080 index = i - 1; | |
1081 Layers::const_iterator it = layers_.find(index); | |
1082 if (it != layers_.end()) | |
1083 { | |
1084 assert(it->second != NULL); | |
1085 if (it->second->Contains(x, y)) | |
1086 { | |
1087 return true; | |
1088 } | |
1089 } | |
1090 } | |
1091 | |
1092 return false; | |
1093 } | |
1094 | |
1095 | |
1096 void RadiographyScene::DrawBorder(CairoContext& context, | |
1097 unsigned int layer, | |
1098 double zoom) | |
1099 { | |
1100 Layers::const_iterator found = layers_.find(layer); | |
1101 | |
1102 if (found != layers_.end()) | |
1103 { | |
1104 context.SetSourceColor(255, 0, 0); | |
1105 found->second->DrawBorders(context, zoom); | |
1106 } | |
1107 } | |
1108 | |
1109 | |
1110 void RadiographyScene::GetRange(float& minValue, | |
1111 float& maxValue) const | |
1112 { | |
1113 bool first = true; | |
1114 | |
1115 for (Layers::const_iterator it = layers_.begin(); | |
1116 it != layers_.end(); it++) | |
1117 { | |
1118 assert(it->second != NULL); | |
1119 | |
1120 float a, b; | |
1121 if (it->second->GetRange(a, b)) | |
1122 { | |
1123 if (first) | |
1124 { | |
1125 minValue = a; | |
1126 maxValue = b; | |
1127 first = false; | |
1128 } | |
1129 else | |
1130 { | |
1131 minValue = std::min(a, minValue); | |
1132 maxValue = std::max(b, maxValue); | |
1133 } | |
1134 } | |
1135 } | |
1136 | |
1137 if (first) | |
1138 { | |
1139 minValue = 0; | |
1140 maxValue = 0; | |
1141 } | |
1142 } | |
1143 | |
1144 | |
1145 // Export using PAM is faster than using PNG, but requires Orthanc | |
1146 // core >= 1.4.3 | |
1147 void RadiographyScene::ExportDicom(const Orthanc::DicomMap& dicom, | |
1148 double pixelSpacingX, | |
1149 double pixelSpacingY, | |
1150 bool invert, | |
1151 ImageInterpolation interpolation, | |
1152 bool usePam) | |
1153 { | |
1154 if (pixelSpacingX <= 0 || | |
1155 pixelSpacingY <= 0) | |
1156 { | |
1157 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); | |
1158 } | |
1159 | |
1160 LOG(INFO) << "Exporting DICOM"; | |
1161 | |
1162 Extent2D extent = GetSceneExtent(); | |
1163 | |
1164 int w = std::ceil(extent.GetWidth() / pixelSpacingX); | |
1165 int h = std::ceil(extent.GetHeight() / pixelSpacingY); | |
1166 | |
1167 if (w < 0 || h < 0) | |
1168 { | |
1169 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); | |
1170 } | |
1171 | |
1172 Orthanc::Image layers(Orthanc::PixelFormat_Float32, | |
1173 static_cast<unsigned int>(w), | |
1174 static_cast<unsigned int>(h), false); | |
1175 | |
1176 Matrix view = LinearAlgebra::Product( | |
1177 CreateScalingMatrix(1.0 / pixelSpacingX, 1.0 / pixelSpacingY), | |
1178 CreateOffsetMatrix(-extent.GetX1(), -extent.GetY1())); | |
1179 | |
1180 Render(layers, view, interpolation); | |
1181 | |
1182 Orthanc::Image rendered(Orthanc::PixelFormat_Grayscale16, | |
1183 layers.GetWidth(), layers.GetHeight(), false); | |
1184 Orthanc::ImageProcessing::Convert(rendered, layers); | |
1185 | |
1186 std::string base64; | |
1187 | |
1188 { | |
1189 std::string content; | |
1190 | |
1191 if (usePam) | |
1192 { | |
1193 Orthanc::PamWriter writer; | |
1194 writer.WriteToMemory(content, rendered); | |
1195 } | |
1196 else | |
1197 { | |
1198 Orthanc::PngWriter writer; | |
1199 writer.WriteToMemory(content, rendered); | |
1200 } | |
1201 | |
1202 Orthanc::Toolbox::EncodeBase64(base64, content); | |
1203 } | |
1204 | |
1205 std::set<Orthanc::DicomTag> tags; | |
1206 dicom.GetTags(tags); | |
1207 | |
1208 Json::Value json = Json::objectValue; | |
1209 json["Tags"] = Json::objectValue; | |
1210 | |
1211 for (std::set<Orthanc::DicomTag>::const_iterator | |
1212 tag = tags.begin(); tag != tags.end(); ++tag) | |
1213 { | |
1214 const Orthanc::DicomValue& value = dicom.GetValue(*tag); | |
1215 if (!value.IsNull() && | |
1216 !value.IsBinary()) | |
1217 { | |
1218 json["Tags"][tag->Format()] = value.GetContent(); | |
1219 } | |
1220 } | |
1221 | |
1222 json["Tags"][Orthanc::DICOM_TAG_PHOTOMETRIC_INTERPRETATION.Format()] = | |
1223 (invert ? "MONOCHROME1" : "MONOCHROME2"); | |
1224 | |
1225 // WARNING: The order of PixelSpacing is Y/X. We use "%0.8f" to | |
1226 // avoid floating-point numbers to grow over 16 characters, | |
1227 // which would be invalid according to DICOM standard | |
1228 // ("dciodvfy" would complain). | |
1229 char buf[32]; | |
1230 sprintf(buf, "%0.8f\\%0.8f", pixelSpacingY, pixelSpacingX); | |
1231 | |
1232 json["Tags"][Orthanc::DICOM_TAG_PIXEL_SPACING.Format()] = buf; | |
1233 | |
1234 float center, width; | |
1235 if (GetWindowing(center, width)) | |
1236 { | |
1237 json["Tags"][Orthanc::DICOM_TAG_WINDOW_CENTER.Format()] = | |
1238 boost::lexical_cast<std::string>(boost::math::iround(center)); | |
1239 | |
1240 json["Tags"][Orthanc::DICOM_TAG_WINDOW_WIDTH.Format()] = | |
1241 boost::lexical_cast<std::string>(boost::math::iround(width)); | |
1242 } | |
1243 | |
1244 // This is Data URI scheme: https://en.wikipedia.org/wiki/Data_URI_scheme | |
1245 json["Content"] = ("data:" + | |
1246 std::string(usePam ? Orthanc::MIME_PAM : Orthanc::MIME_PNG) + | |
1247 ";base64," + base64); | |
1248 | |
1249 orthanc_.PostJsonAsyncExpectJson( | |
1250 "/tools/create-dicom", json, | |
1251 new Callable<RadiographyScene, OrthancApiClient::JsonResponseReadyMessage> | |
1252 (*this, &RadiographyScene::OnDicomExported), | |
1253 NULL, NULL); | |
1254 } | |
1255 | |
1256 | |
1257 void RadiographyScene::OnDicomExported(const OrthancApiClient::JsonResponseReadyMessage& message) | |
1258 { | |
1259 LOG(INFO) << "DICOM export was successful:" | |
1260 << message.GetJson().toStyledString(); | |
1261 } | |
1262 } |