Mercurial > hg > orthanc-stone
comparison Applications/Samples/SingleFrameEditorApplication.h @ 408:6834c236b36d
reorganization
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Mon, 12 Nov 2018 14:52:10 +0100 |
parents | 842a3c7cfdc0 |
children | 6decc0ba9da5 |
comparison
equal
deleted
inserted
replaced
407:842a3c7cfdc0 | 408:6834c236b36d |
---|---|
21 | 21 |
22 #pragma once | 22 #pragma once |
23 | 23 |
24 #include "SampleApplicationBase.h" | 24 #include "SampleApplicationBase.h" |
25 | 25 |
26 #include "../../Framework/Toolbox/ImageGeometry.h" | 26 #include "../../Framework/Radiography/RadiographyScene.h" |
27 #include "../../Framework/Toolbox/OrthancApiClient.h" | 27 |
28 #include "../../Framework/Toolbox/DicomFrameConverter.h" | 28 #include "../../Framework/Toolbox/UndoRedoStack.h" |
29 | 29 |
30 #include <Core/Images/FontRegistry.h> | 30 #include <Core/Images/FontRegistry.h> |
31 #include <Core/Images/Image.h> | 31 #include <Core/Images/Image.h> |
32 #include <Core/Images/ImageProcessing.h> | 32 #include <Core/Images/ImageProcessing.h> |
33 #include <Core/Images/PamReader.h> | 33 #include <Core/Images/PamReader.h> |
49 #include <boost/math/special_functions/round.hpp> | 49 #include <boost/math/special_functions/round.hpp> |
50 | 50 |
51 | 51 |
52 namespace OrthancStone | 52 namespace OrthancStone |
53 { | 53 { |
54 class RadiologyScene : | 54 class RadiographyLayerCommand : public UndoRedoStack::ICommand |
55 public IObserver, | |
56 public IObservable | |
57 { | |
58 public: | |
59 typedef OriginMessage<MessageType_Widget_GeometryChanged, RadiologyScene> GeometryChangedMessage; | |
60 typedef OriginMessage<MessageType_Widget_ContentChanged, RadiologyScene> ContentChangedMessage; | |
61 | |
62 | |
63 enum Corner | |
64 { | |
65 Corner_TopLeft, | |
66 Corner_TopRight, | |
67 Corner_BottomLeft, | |
68 Corner_BottomRight | |
69 }; | |
70 | |
71 | |
72 | |
73 class Layer : public boost::noncopyable | |
74 { | |
75 friend class RadiologyScene; | |
76 | |
77 private: | |
78 size_t index_; | |
79 bool hasSize_; | |
80 unsigned int width_; | |
81 unsigned int height_; | |
82 bool hasCrop_; | |
83 unsigned int cropX_; | |
84 unsigned int cropY_; | |
85 unsigned int cropWidth_; | |
86 unsigned int cropHeight_; | |
87 Matrix transform_; | |
88 Matrix transformInverse_; | |
89 double pixelSpacingX_; | |
90 double pixelSpacingY_; | |
91 double panX_; | |
92 double panY_; | |
93 double angle_; | |
94 bool resizeable_; | |
95 | |
96 | |
97 protected: | |
98 const Matrix& GetTransform() const | |
99 { | |
100 return transform_; | |
101 } | |
102 | |
103 | |
104 private: | |
105 static void ApplyTransform(double& x /* inout */, | |
106 double& y /* inout */, | |
107 const Matrix& transform) | |
108 { | |
109 Vector p; | |
110 LinearAlgebra::AssignVector(p, x, y, 1); | |
111 | |
112 Vector q = LinearAlgebra::Product(transform, p); | |
113 | |
114 if (!LinearAlgebra::IsNear(q[2], 1.0)) | |
115 { | |
116 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); | |
117 } | |
118 else | |
119 { | |
120 x = q[0]; | |
121 y = q[1]; | |
122 } | |
123 } | |
124 | |
125 | |
126 void UpdateTransform() | |
127 { | |
128 transform_ = CreateScalingMatrix(pixelSpacingX_, pixelSpacingY_); | |
129 | |
130 double centerX, centerY; | |
131 GetCenter(centerX, centerY); | |
132 | |
133 transform_ = LinearAlgebra::Product( | |
134 CreateOffsetMatrix(panX_ + centerX, panY_ + centerY), | |
135 CreateRotationMatrix(angle_), | |
136 CreateOffsetMatrix(-centerX, -centerY), | |
137 transform_); | |
138 | |
139 LinearAlgebra::InvertMatrix(transformInverse_, transform_); | |
140 } | |
141 | |
142 | |
143 void AddToExtent(Extent2D& extent, | |
144 double x, | |
145 double y) const | |
146 { | |
147 ApplyTransform(x, y, transform_); | |
148 extent.AddPoint(x, y); | |
149 } | |
150 | |
151 | |
152 void GetCornerInternal(double& x, | |
153 double& y, | |
154 Corner corner, | |
155 unsigned int cropX, | |
156 unsigned int cropY, | |
157 unsigned int cropWidth, | |
158 unsigned int cropHeight) const | |
159 { | |
160 double dx = static_cast<double>(cropX); | |
161 double dy = static_cast<double>(cropY); | |
162 double dwidth = static_cast<double>(cropWidth); | |
163 double dheight = static_cast<double>(cropHeight); | |
164 | |
165 switch (corner) | |
166 { | |
167 case Corner_TopLeft: | |
168 x = dx; | |
169 y = dy; | |
170 break; | |
171 | |
172 case Corner_TopRight: | |
173 x = dx + dwidth; | |
174 y = dy; | |
175 break; | |
176 | |
177 case Corner_BottomLeft: | |
178 x = dx; | |
179 y = dy + dheight; | |
180 break; | |
181 | |
182 case Corner_BottomRight: | |
183 x = dx + dwidth; | |
184 y = dy + dheight; | |
185 break; | |
186 | |
187 default: | |
188 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); | |
189 } | |
190 | |
191 ApplyTransform(x, y, transform_); | |
192 } | |
193 | |
194 | |
195 void SetIndex(size_t index) | |
196 { | |
197 index_ = index; | |
198 } | |
199 | |
200 | |
201 bool Contains(double x, | |
202 double y) const | |
203 { | |
204 ApplyTransform(x, y, transformInverse_); | |
205 | |
206 unsigned int cropX, cropY, cropWidth, cropHeight; | |
207 GetCrop(cropX, cropY, cropWidth, cropHeight); | |
208 | |
209 return (x >= cropX && x <= cropX + cropWidth && | |
210 y >= cropY && y <= cropY + cropHeight); | |
211 } | |
212 | |
213 | |
214 void DrawBorders(CairoContext& context, | |
215 double zoom) | |
216 { | |
217 unsigned int cx, cy, width, height; | |
218 GetCrop(cx, cy, width, height); | |
219 | |
220 double dx = static_cast<double>(cx); | |
221 double dy = static_cast<double>(cy); | |
222 double dwidth = static_cast<double>(width); | |
223 double dheight = static_cast<double>(height); | |
224 | |
225 cairo_t* cr = context.GetObject(); | |
226 cairo_set_line_width(cr, 2.0 / zoom); | |
227 | |
228 double x, y; | |
229 x = dx; | |
230 y = dy; | |
231 ApplyTransform(x, y, transform_); | |
232 cairo_move_to(cr, x, y); | |
233 | |
234 x = dx + dwidth; | |
235 y = dy; | |
236 ApplyTransform(x, y, transform_); | |
237 cairo_line_to(cr, x, y); | |
238 | |
239 x = dx + dwidth; | |
240 y = dy + dheight; | |
241 ApplyTransform(x, y, transform_); | |
242 cairo_line_to(cr, x, y); | |
243 | |
244 x = dx; | |
245 y = dy + dheight; | |
246 ApplyTransform(x, y, transform_); | |
247 cairo_line_to(cr, x, y); | |
248 | |
249 x = dx; | |
250 y = dy; | |
251 ApplyTransform(x, y, transform_); | |
252 cairo_line_to(cr, x, y); | |
253 | |
254 cairo_stroke(cr); | |
255 } | |
256 | |
257 | |
258 static double Square(double x) | |
259 { | |
260 return x * x; | |
261 } | |
262 | |
263 | |
264 public: | |
265 Layer() : | |
266 index_(0), | |
267 hasSize_(false), | |
268 width_(0), | |
269 height_(0), | |
270 hasCrop_(false), | |
271 pixelSpacingX_(1), | |
272 pixelSpacingY_(1), | |
273 panX_(0), | |
274 panY_(0), | |
275 angle_(0), | |
276 resizeable_(false) | |
277 { | |
278 UpdateTransform(); | |
279 } | |
280 | |
281 virtual ~Layer() | |
282 { | |
283 } | |
284 | |
285 size_t GetIndex() const | |
286 { | |
287 return index_; | |
288 } | |
289 | |
290 void ResetCrop() | |
291 { | |
292 hasCrop_ = false; | |
293 } | |
294 | |
295 void SetCrop(unsigned int x, | |
296 unsigned int y, | |
297 unsigned int width, | |
298 unsigned int height) | |
299 { | |
300 if (!hasSize_) | |
301 { | |
302 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); | |
303 } | |
304 | |
305 if (x + width > width_ || | |
306 y + height > height_) | |
307 { | |
308 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); | |
309 } | |
310 | |
311 hasCrop_ = true; | |
312 cropX_ = x; | |
313 cropY_ = y; | |
314 cropWidth_ = width; | |
315 cropHeight_ = height; | |
316 | |
317 UpdateTransform(); | |
318 } | |
319 | |
320 void GetCrop(unsigned int& x, | |
321 unsigned int& y, | |
322 unsigned int& width, | |
323 unsigned int& height) const | |
324 { | |
325 if (hasCrop_) | |
326 { | |
327 x = cropX_; | |
328 y = cropY_; | |
329 width = cropWidth_; | |
330 height = cropHeight_; | |
331 } | |
332 else | |
333 { | |
334 x = 0; | |
335 y = 0; | |
336 width = width_; | |
337 height = height_; | |
338 } | |
339 } | |
340 | |
341 void SetAngle(double angle) | |
342 { | |
343 angle_ = angle; | |
344 UpdateTransform(); | |
345 } | |
346 | |
347 double GetAngle() const | |
348 { | |
349 return angle_; | |
350 } | |
351 | |
352 void SetSize(unsigned int width, | |
353 unsigned int height) | |
354 { | |
355 if (hasSize_ && | |
356 (width != width_ || | |
357 height != height_)) | |
358 { | |
359 throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageSize); | |
360 } | |
361 | |
362 hasSize_ = true; | |
363 width_ = width; | |
364 height_ = height; | |
365 | |
366 UpdateTransform(); | |
367 } | |
368 | |
369 | |
370 unsigned int GetWidth() const | |
371 { | |
372 return width_; | |
373 } | |
374 | |
375 | |
376 unsigned int GetHeight() const | |
377 { | |
378 return height_; | |
379 } | |
380 | |
381 | |
382 Extent2D GetExtent() const | |
383 { | |
384 Extent2D extent; | |
385 | |
386 unsigned int x, y, width, height; | |
387 GetCrop(x, y, width, height); | |
388 | |
389 double dx = static_cast<double>(x); | |
390 double dy = static_cast<double>(y); | |
391 double dwidth = static_cast<double>(width); | |
392 double dheight = static_cast<double>(height); | |
393 | |
394 AddToExtent(extent, dx, dy); | |
395 AddToExtent(extent, dx + dwidth, dy); | |
396 AddToExtent(extent, dx, dy + dheight); | |
397 AddToExtent(extent, dx + dwidth, dy + dheight); | |
398 | |
399 return extent; | |
400 } | |
401 | |
402 | |
403 bool GetPixel(unsigned int& imageX, | |
404 unsigned int& imageY, | |
405 double sceneX, | |
406 double sceneY) const | |
407 { | |
408 if (width_ == 0 || | |
409 height_ == 0) | |
410 { | |
411 return false; | |
412 } | |
413 else | |
414 { | |
415 ApplyTransform(sceneX, sceneY, transformInverse_); | |
416 | |
417 int x = static_cast<int>(std::floor(sceneX)); | |
418 int y = static_cast<int>(std::floor(sceneY)); | |
419 | |
420 if (x < 0) | |
421 { | |
422 imageX = 0; | |
423 } | |
424 else if (x >= static_cast<int>(width_)) | |
425 { | |
426 imageX = width_; | |
427 } | |
428 else | |
429 { | |
430 imageX = static_cast<unsigned int>(x); | |
431 } | |
432 | |
433 if (y < 0) | |
434 { | |
435 imageY = 0; | |
436 } | |
437 else if (y >= static_cast<int>(height_)) | |
438 { | |
439 imageY = height_; | |
440 } | |
441 else | |
442 { | |
443 imageY = static_cast<unsigned int>(y); | |
444 } | |
445 | |
446 return true; | |
447 } | |
448 } | |
449 | |
450 | |
451 void SetPan(double x, | |
452 double y) | |
453 { | |
454 panX_ = x; | |
455 panY_ = y; | |
456 UpdateTransform(); | |
457 } | |
458 | |
459 | |
460 void SetPixelSpacing(double x, | |
461 double y) | |
462 { | |
463 pixelSpacingX_ = x; | |
464 pixelSpacingY_ = y; | |
465 UpdateTransform(); | |
466 } | |
467 | |
468 double GetPixelSpacingX() const | |
469 { | |
470 return pixelSpacingX_; | |
471 } | |
472 | |
473 double GetPixelSpacingY() const | |
474 { | |
475 return pixelSpacingY_; | |
476 } | |
477 | |
478 double GetPanX() const | |
479 { | |
480 return panX_; | |
481 } | |
482 | |
483 double GetPanY() const | |
484 { | |
485 return panY_; | |
486 } | |
487 | |
488 void GetCenter(double& centerX, | |
489 double& centerY) const | |
490 { | |
491 centerX = static_cast<double>(width_) / 2.0; | |
492 centerY = static_cast<double>(height_) / 2.0; | |
493 ApplyTransform(centerX, centerY, transform_); | |
494 } | |
495 | |
496 | |
497 void GetCorner(double& x /* out */, | |
498 double& y /* out */, | |
499 Corner corner) const | |
500 { | |
501 unsigned int cropX, cropY, cropWidth, cropHeight; | |
502 GetCrop(cropX, cropY, cropWidth, cropHeight); | |
503 GetCornerInternal(x, y, corner, cropX, cropY, cropWidth, cropHeight); | |
504 } | |
505 | |
506 | |
507 bool LookupCorner(Corner& corner /* out */, | |
508 double x, | |
509 double y, | |
510 double zoom, | |
511 double viewportDistance) const | |
512 { | |
513 static const Corner CORNERS[] = { | |
514 Corner_TopLeft, | |
515 Corner_TopRight, | |
516 Corner_BottomLeft, | |
517 Corner_BottomRight | |
518 }; | |
519 | |
520 unsigned int cropX, cropY, cropWidth, cropHeight; | |
521 GetCrop(cropX, cropY, cropWidth, cropHeight); | |
522 | |
523 double threshold = Square(viewportDistance / zoom); | |
524 | |
525 for (size_t i = 0; i < 4; i++) | |
526 { | |
527 double cx, cy; | |
528 GetCornerInternal(cx, cy, CORNERS[i], cropX, cropY, cropWidth, cropHeight); | |
529 | |
530 double d = Square(cx - x) + Square(cy - y); | |
531 | |
532 if (d <= threshold) | |
533 { | |
534 corner = CORNERS[i]; | |
535 return true; | |
536 } | |
537 } | |
538 | |
539 return false; | |
540 } | |
541 | |
542 bool IsResizeable() const | |
543 { | |
544 return resizeable_; | |
545 } | |
546 | |
547 void SetResizeable(bool resizeable) | |
548 { | |
549 resizeable_ = resizeable; | |
550 } | |
551 | |
552 virtual bool GetDefaultWindowing(float& center, | |
553 float& width) const = 0; | |
554 | |
555 virtual void Render(Orthanc::ImageAccessor& buffer, | |
556 const Matrix& viewTransform, | |
557 ImageInterpolation interpolation) const = 0; | |
558 | |
559 virtual bool GetRange(float& minValue, | |
560 float& maxValue) const = 0; | |
561 }; | |
562 | |
563 | |
564 class LayerAccessor : public boost::noncopyable | |
565 { | |
566 private: | |
567 RadiologyScene& scene_; | |
568 size_t index_; | |
569 Layer* layer_; | |
570 | |
571 public: | |
572 LayerAccessor(RadiologyScene& scene, | |
573 size_t index) : | |
574 scene_(scene), | |
575 index_(index) | |
576 { | |
577 Layers::iterator layer = scene.layers_.find(index); | |
578 if (layer == scene.layers_.end()) | |
579 { | |
580 layer_ = NULL; | |
581 } | |
582 else | |
583 { | |
584 assert(layer->second != NULL); | |
585 layer_ = layer->second; | |
586 } | |
587 } | |
588 | |
589 LayerAccessor(RadiologyScene& scene, | |
590 double x, | |
591 double y) : | |
592 scene_(scene), | |
593 index_(0) // Dummy initialization | |
594 { | |
595 if (scene.LookupLayer(index_, x, y)) | |
596 { | |
597 Layers::iterator layer = scene.layers_.find(index_); | |
598 | |
599 if (layer == scene.layers_.end()) | |
600 { | |
601 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); | |
602 } | |
603 else | |
604 { | |
605 assert(layer->second != NULL); | |
606 layer_ = layer->second; | |
607 } | |
608 } | |
609 else | |
610 { | |
611 layer_ = NULL; | |
612 } | |
613 } | |
614 | |
615 void Invalidate() | |
616 { | |
617 layer_ = NULL; | |
618 } | |
619 | |
620 bool IsValid() const | |
621 { | |
622 return layer_ != NULL; | |
623 } | |
624 | |
625 RadiologyScene& GetScene() const | |
626 { | |
627 if (IsValid()) | |
628 { | |
629 return scene_; | |
630 } | |
631 else | |
632 { | |
633 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); | |
634 } | |
635 } | |
636 | |
637 size_t GetIndex() const | |
638 { | |
639 if (IsValid()) | |
640 { | |
641 return index_; | |
642 } | |
643 else | |
644 { | |
645 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); | |
646 } | |
647 } | |
648 | |
649 Layer& GetLayer() const | |
650 { | |
651 if (IsValid()) | |
652 { | |
653 return *layer_; | |
654 } | |
655 else | |
656 { | |
657 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); | |
658 } | |
659 } | |
660 }; | |
661 | |
662 | |
663 private: | |
664 class AlphaLayer : public Layer | |
665 { | |
666 private: | |
667 const RadiologyScene& scene_; | |
668 std::auto_ptr<Orthanc::ImageAccessor> alpha_; // Grayscale8 | |
669 bool useWindowing_; | |
670 float foreground_; | |
671 | |
672 public: | |
673 AlphaLayer(const RadiologyScene& scene) : | |
674 scene_(scene), | |
675 useWindowing_(true), | |
676 foreground_(0) | |
677 { | |
678 } | |
679 | |
680 | |
681 void SetForegroundValue(float foreground) | |
682 { | |
683 useWindowing_ = false; | |
684 foreground_ = foreground; | |
685 } | |
686 | |
687 | |
688 void SetAlpha(Orthanc::ImageAccessor* image) | |
689 { | |
690 std::auto_ptr<Orthanc::ImageAccessor> raii(image); | |
691 | |
692 if (image == NULL) | |
693 { | |
694 throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); | |
695 } | |
696 | |
697 if (image->GetFormat() != Orthanc::PixelFormat_Grayscale8) | |
698 { | |
699 throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat); | |
700 } | |
701 | |
702 SetSize(image->GetWidth(), image->GetHeight()); | |
703 alpha_ = raii; | |
704 } | |
705 | |
706 | |
707 void LoadText(const Orthanc::Font& font, | |
708 const std::string& utf8) | |
709 { | |
710 SetAlpha(font.RenderAlpha(utf8)); | |
711 } | |
712 | |
713 | |
714 virtual bool GetDefaultWindowing(float& center, | |
715 float& width) const | |
716 { | |
717 return false; | |
718 } | |
719 | |
720 | |
721 virtual void Render(Orthanc::ImageAccessor& buffer, | |
722 const Matrix& viewTransform, | |
723 ImageInterpolation interpolation) const | |
724 { | |
725 if (alpha_.get() == NULL) | |
726 { | |
727 return; | |
728 } | |
729 | |
730 if (buffer.GetFormat() != Orthanc::PixelFormat_Float32) | |
731 { | |
732 throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat); | |
733 } | |
734 | |
735 unsigned int cropX, cropY, cropWidth, cropHeight; | |
736 GetCrop(cropX, cropY, cropWidth, cropHeight); | |
737 | |
738 Matrix m = LinearAlgebra::Product(viewTransform, | |
739 GetTransform(), | |
740 CreateOffsetMatrix(cropX, cropY)); | |
741 | |
742 Orthanc::ImageAccessor cropped; | |
743 alpha_->GetRegion(cropped, cropX, cropY, cropWidth, cropHeight); | |
744 | |
745 Orthanc::Image tmp(Orthanc::PixelFormat_Grayscale8, buffer.GetWidth(), buffer.GetHeight(), false); | |
746 ApplyProjectiveTransform(tmp, cropped, m, interpolation, true /* clear */); | |
747 | |
748 // Blit | |
749 const unsigned int width = buffer.GetWidth(); | |
750 const unsigned int height = buffer.GetHeight(); | |
751 | |
752 float value = foreground_; | |
753 | |
754 if (useWindowing_) | |
755 { | |
756 float center, width; | |
757 if (scene_.GetWindowing(center, width)) | |
758 { | |
759 value = center + width / 2.0f; | |
760 } | |
761 } | |
762 | |
763 for (unsigned int y = 0; y < height; y++) | |
764 { | |
765 float *q = reinterpret_cast<float*>(buffer.GetRow(y)); | |
766 const uint8_t *p = reinterpret_cast<uint8_t*>(tmp.GetRow(y)); | |
767 | |
768 for (unsigned int x = 0; x < width; x++, p++, q++) | |
769 { | |
770 float a = static_cast<float>(*p) / 255.0f; | |
771 | |
772 *q = (a * value + (1.0f - a) * (*q)); | |
773 } | |
774 } | |
775 } | |
776 | |
777 | |
778 virtual bool GetRange(float& minValue, | |
779 float& maxValue) const | |
780 { | |
781 if (useWindowing_) | |
782 { | |
783 return false; | |
784 } | |
785 else | |
786 { | |
787 minValue = 0; | |
788 maxValue = 0; | |
789 | |
790 if (foreground_ < 0) | |
791 { | |
792 minValue = foreground_; | |
793 } | |
794 | |
795 if (foreground_ > 0) | |
796 { | |
797 maxValue = foreground_; | |
798 } | |
799 | |
800 return true; | |
801 } | |
802 } | |
803 }; | |
804 | |
805 | |
806 | |
807 private: | |
808 static Matrix CreateOffsetMatrix(double dx, | |
809 double dy) | |
810 { | |
811 Matrix m = LinearAlgebra::IdentityMatrix(3); | |
812 m(0, 2) = dx; | |
813 m(1, 2) = dy; | |
814 return m; | |
815 } | |
816 | |
817 | |
818 static Matrix CreateScalingMatrix(double sx, | |
819 double sy) | |
820 { | |
821 Matrix m = LinearAlgebra::IdentityMatrix(3); | |
822 m(0, 0) = sx; | |
823 m(1, 1) = sy; | |
824 return m; | |
825 } | |
826 | |
827 | |
828 static Matrix CreateRotationMatrix(double angle) | |
829 { | |
830 Matrix m; | |
831 const double v[] = { cos(angle), -sin(angle), 0, | |
832 sin(angle), cos(angle), 0, | |
833 0, 0, 1 }; | |
834 LinearAlgebra::FillMatrix(m, 3, 3, v); | |
835 return m; | |
836 } | |
837 | |
838 | |
839 class DicomLayer : public Layer | |
840 { | |
841 private: | |
842 std::auto_ptr<Orthanc::ImageAccessor> source_; // Content of PixelData | |
843 std::auto_ptr<DicomFrameConverter> converter_; | |
844 std::auto_ptr<Orthanc::ImageAccessor> converted_; // Float32 | |
845 | |
846 static OrthancPlugins::DicomTag ConvertTag(const Orthanc::DicomTag& tag) | |
847 { | |
848 return OrthancPlugins::DicomTag(tag.GetGroup(), tag.GetElement()); | |
849 } | |
850 | |
851 | |
852 void ApplyConverter() | |
853 { | |
854 if (source_.get() != NULL && | |
855 converter_.get() != NULL) | |
856 { | |
857 converted_.reset(converter_->ConvertFrame(*source_)); | |
858 } | |
859 } | |
860 | |
861 public: | |
862 void SetDicomTags(const OrthancPlugins::FullOrthancDataset& dataset) | |
863 { | |
864 converter_.reset(new DicomFrameConverter); | |
865 converter_->ReadParameters(dataset); | |
866 ApplyConverter(); | |
867 | |
868 std::string tmp; | |
869 Vector pixelSpacing; | |
870 | |
871 if (dataset.GetStringValue(tmp, ConvertTag(Orthanc::DICOM_TAG_PIXEL_SPACING)) && | |
872 LinearAlgebra::ParseVector(pixelSpacing, tmp) && | |
873 pixelSpacing.size() == 2) | |
874 { | |
875 SetPixelSpacing(pixelSpacing[0], pixelSpacing[1]); | |
876 } | |
877 | |
878 //SetPan(-0.5 * GetPixelSpacingX(), -0.5 * GetPixelSpacingY()); | |
879 | |
880 OrthancPlugins::DicomDatasetReader reader(dataset); | |
881 | |
882 unsigned int width, height; | |
883 if (!reader.GetUnsignedIntegerValue(width, ConvertTag(Orthanc::DICOM_TAG_COLUMNS)) || | |
884 !reader.GetUnsignedIntegerValue(height, ConvertTag(Orthanc::DICOM_TAG_ROWS))) | |
885 { | |
886 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); | |
887 } | |
888 else | |
889 { | |
890 SetSize(width, height); | |
891 } | |
892 } | |
893 | |
894 | |
895 void SetSourceImage(Orthanc::ImageAccessor* image) // Takes ownership | |
896 { | |
897 std::auto_ptr<Orthanc::ImageAccessor> raii(image); | |
898 | |
899 if (image == NULL) | |
900 { | |
901 throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); | |
902 } | |
903 | |
904 SetSize(image->GetWidth(), image->GetHeight()); | |
905 | |
906 source_ = raii; | |
907 ApplyConverter(); | |
908 } | |
909 | |
910 | |
911 virtual void Render(Orthanc::ImageAccessor& buffer, | |
912 const Matrix& viewTransform, | |
913 ImageInterpolation interpolation) const | |
914 { | |
915 if (converted_.get() != NULL) | |
916 { | |
917 if (converted_->GetFormat() != Orthanc::PixelFormat_Float32) | |
918 { | |
919 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); | |
920 } | |
921 | |
922 unsigned int cropX, cropY, cropWidth, cropHeight; | |
923 GetCrop(cropX, cropY, cropWidth, cropHeight); | |
924 | |
925 Matrix m = LinearAlgebra::Product(viewTransform, | |
926 GetTransform(), | |
927 CreateOffsetMatrix(cropX, cropY)); | |
928 | |
929 Orthanc::ImageAccessor cropped; | |
930 converted_->GetRegion(cropped, cropX, cropY, cropWidth, cropHeight); | |
931 | |
932 ApplyProjectiveTransform(buffer, cropped, m, interpolation, false); | |
933 } | |
934 } | |
935 | |
936 | |
937 virtual bool GetDefaultWindowing(float& center, | |
938 float& width) const | |
939 { | |
940 if (converter_.get() != NULL && | |
941 converter_->HasDefaultWindow()) | |
942 { | |
943 center = static_cast<float>(converter_->GetDefaultWindowCenter()); | |
944 width = static_cast<float>(converter_->GetDefaultWindowWidth()); | |
945 return true; | |
946 } | |
947 else | |
948 { | |
949 return false; | |
950 } | |
951 } | |
952 | |
953 | |
954 virtual bool GetRange(float& minValue, | |
955 float& maxValue) const | |
956 { | |
957 if (converted_.get() != NULL) | |
958 { | |
959 if (converted_->GetFormat() != Orthanc::PixelFormat_Float32) | |
960 { | |
961 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); | |
962 } | |
963 | |
964 Orthanc::ImageProcessing::GetMinMaxFloatValue(minValue, maxValue, *converted_); | |
965 return true; | |
966 } | |
967 else | |
968 { | |
969 return false; | |
970 } | |
971 } | |
972 }; | |
973 | |
974 | |
975 | |
976 | |
977 typedef std::map<size_t, Layer*> Layers; | |
978 | |
979 OrthancApiClient& orthanc_; | |
980 size_t countLayers_; | |
981 bool hasWindowing_; | |
982 float windowingCenter_; | |
983 float windowingWidth_; | |
984 Layers layers_; | |
985 | |
986 | |
987 Layer& RegisterLayer(Layer* layer) | |
988 { | |
989 if (layer == NULL) | |
990 { | |
991 throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); | |
992 } | |
993 | |
994 std::auto_ptr<Layer> raii(layer); | |
995 | |
996 size_t index = countLayers_++; | |
997 raii->SetIndex(index); | |
998 layers_[index] = raii.release(); | |
999 | |
1000 EmitMessage(GeometryChangedMessage(*this)); | |
1001 EmitMessage(ContentChangedMessage(*this)); | |
1002 | |
1003 return *layer; | |
1004 } | |
1005 | |
1006 | |
1007 public: | |
1008 RadiologyScene(MessageBroker& broker, | |
1009 OrthancApiClient& orthanc) : | |
1010 IObserver(broker), | |
1011 IObservable(broker), | |
1012 orthanc_(orthanc), | |
1013 countLayers_(0), | |
1014 hasWindowing_(false), | |
1015 windowingCenter_(0), // Dummy initialization | |
1016 windowingWidth_(0) // Dummy initialization | |
1017 { | |
1018 } | |
1019 | |
1020 | |
1021 virtual ~RadiologyScene() | |
1022 { | |
1023 for (Layers::iterator it = layers_.begin(); it != layers_.end(); it++) | |
1024 { | |
1025 assert(it->second != NULL); | |
1026 delete it->second; | |
1027 } | |
1028 } | |
1029 | |
1030 | |
1031 bool GetWindowing(float& center, | |
1032 float& width) const | |
1033 { | |
1034 if (hasWindowing_) | |
1035 { | |
1036 center = windowingCenter_; | |
1037 width = windowingWidth_; | |
1038 return true; | |
1039 } | |
1040 else | |
1041 { | |
1042 return false; | |
1043 } | |
1044 } | |
1045 | |
1046 | |
1047 void GetWindowingWithDefault(float& center, | |
1048 float& width) const | |
1049 { | |
1050 if (!GetWindowing(center, width)) | |
1051 { | |
1052 center = 128; | |
1053 width = 256; | |
1054 } | |
1055 } | |
1056 | |
1057 | |
1058 void SetWindowing(float center, | |
1059 float width) | |
1060 | |
1061 { | |
1062 hasWindowing_ = true; | |
1063 windowingCenter_ = center; | |
1064 windowingWidth_ = width; | |
1065 } | |
1066 | |
1067 | |
1068 Layer& LoadText(const Orthanc::Font& font, | |
1069 const std::string& utf8) | |
1070 { | |
1071 std::auto_ptr<AlphaLayer> alpha(new AlphaLayer(*this)); | |
1072 alpha->LoadText(font, utf8); | |
1073 | |
1074 return RegisterLayer(alpha.release()); | |
1075 } | |
1076 | |
1077 | |
1078 Layer& LoadTestBlock(unsigned int width, | |
1079 unsigned int height) | |
1080 { | |
1081 std::auto_ptr<Orthanc::Image> block(new Orthanc::Image(Orthanc::PixelFormat_Grayscale8, width, height, false)); | |
1082 | |
1083 for (unsigned int padding = 0; | |
1084 (width > 2 * padding) && (height > 2 * padding); | |
1085 padding++) | |
1086 { | |
1087 uint8_t color; | |
1088 if (255 > 10 * padding) | |
1089 { | |
1090 color = 255 - 10 * padding; | |
1091 } | |
1092 else | |
1093 { | |
1094 color = 0; | |
1095 } | |
1096 | |
1097 Orthanc::ImageAccessor region; | |
1098 block->GetRegion(region, padding, padding, width - 2 * padding, height - 2 * padding); | |
1099 Orthanc::ImageProcessing::Set(region, color); | |
1100 } | |
1101 | |
1102 std::auto_ptr<AlphaLayer> alpha(new AlphaLayer(*this)); | |
1103 alpha->SetAlpha(block.release()); | |
1104 | |
1105 return RegisterLayer(alpha.release()); | |
1106 } | |
1107 | |
1108 | |
1109 Layer& LoadDicomFrame(const std::string& instance, | |
1110 unsigned int frame, | |
1111 bool httpCompression) | |
1112 { | |
1113 Layer& layer = RegisterLayer(new DicomLayer); | |
1114 | |
1115 { | |
1116 IWebService::Headers headers; | |
1117 std::string uri = "/instances/" + instance + "/tags"; | |
1118 orthanc_.GetBinaryAsync(uri, headers, | |
1119 new Callable<RadiologyScene, OrthancApiClient::BinaryResponseReadyMessage> | |
1120 (*this, &RadiologyScene::OnTagsReceived), NULL, | |
1121 new Orthanc::SingleValueObject<size_t>(layer.GetIndex())); | |
1122 } | |
1123 | |
1124 { | |
1125 IWebService::Headers headers; | |
1126 headers["Accept"] = "image/x-portable-arbitrarymap"; | |
1127 | |
1128 if (httpCompression) | |
1129 { | |
1130 headers["Accept-Encoding"] = "gzip"; | |
1131 } | |
1132 | |
1133 std::string uri = "/instances/" + instance + "/frames/" + boost::lexical_cast<std::string>(frame) + "/image-uint16"; | |
1134 orthanc_.GetBinaryAsync(uri, headers, | |
1135 new Callable<RadiologyScene, OrthancApiClient::BinaryResponseReadyMessage> | |
1136 (*this, &RadiologyScene::OnFrameReceived), NULL, | |
1137 new Orthanc::SingleValueObject<size_t>(layer.GetIndex())); | |
1138 } | |
1139 | |
1140 return layer; | |
1141 } | |
1142 | |
1143 | |
1144 void OnTagsReceived(const OrthancApiClient::BinaryResponseReadyMessage& message) | |
1145 { | |
1146 size_t index = dynamic_cast<const Orthanc::SingleValueObject<size_t>&>(message.GetPayload()).GetValue(); | |
1147 | |
1148 LOG(INFO) << "JSON received: " << message.GetUri().c_str() | |
1149 << " (" << message.GetAnswerSize() << " bytes) for layer " << index; | |
1150 | |
1151 Layers::iterator layer = layers_.find(index); | |
1152 if (layer != layers_.end()) | |
1153 { | |
1154 assert(layer->second != NULL); | |
1155 | |
1156 OrthancPlugins::FullOrthancDataset dicom(message.GetAnswer(), message.GetAnswerSize()); | |
1157 dynamic_cast<DicomLayer*>(layer->second)->SetDicomTags(dicom); | |
1158 | |
1159 float c, w; | |
1160 if (!hasWindowing_ && | |
1161 layer->second->GetDefaultWindowing(c, w)) | |
1162 { | |
1163 hasWindowing_ = true; | |
1164 windowingCenter_ = c; | |
1165 windowingWidth_ = w; | |
1166 } | |
1167 | |
1168 EmitMessage(GeometryChangedMessage(*this)); | |
1169 } | |
1170 } | |
1171 | |
1172 | |
1173 void OnFrameReceived(const OrthancApiClient::BinaryResponseReadyMessage& message) | |
1174 { | |
1175 size_t index = dynamic_cast<const Orthanc::SingleValueObject<size_t>&>(message.GetPayload()).GetValue(); | |
1176 | |
1177 LOG(INFO) << "DICOM frame received: " << message.GetUri().c_str() | |
1178 << " (" << message.GetAnswerSize() << " bytes) for layer " << index; | |
1179 | |
1180 Layers::iterator layer = layers_.find(index); | |
1181 if (layer != layers_.end()) | |
1182 { | |
1183 assert(layer->second != NULL); | |
1184 | |
1185 std::string content; | |
1186 if (message.GetAnswerSize() > 0) | |
1187 { | |
1188 content.assign(reinterpret_cast<const char*>(message.GetAnswer()), message.GetAnswerSize()); | |
1189 } | |
1190 | |
1191 std::auto_ptr<Orthanc::PamReader> reader(new Orthanc::PamReader); | |
1192 reader->ReadFromMemory(content); | |
1193 dynamic_cast<DicomLayer*>(layer->second)->SetSourceImage(reader.release()); | |
1194 | |
1195 EmitMessage(ContentChangedMessage(*this)); | |
1196 } | |
1197 } | |
1198 | |
1199 | |
1200 Extent2D GetSceneExtent() const | |
1201 { | |
1202 Extent2D extent; | |
1203 | |
1204 for (Layers::const_iterator it = layers_.begin(); | |
1205 it != layers_.end(); ++it) | |
1206 { | |
1207 assert(it->second != NULL); | |
1208 extent.Union(it->second->GetExtent()); | |
1209 } | |
1210 | |
1211 return extent; | |
1212 } | |
1213 | |
1214 | |
1215 void Render(Orthanc::ImageAccessor& buffer, | |
1216 const Matrix& viewTransform, | |
1217 ImageInterpolation interpolation) const | |
1218 { | |
1219 Orthanc::ImageProcessing::Set(buffer, 0); | |
1220 | |
1221 // Render layers in the background-to-foreground order | |
1222 for (size_t index = 0; index < countLayers_; index++) | |
1223 { | |
1224 Layers::const_iterator it = layers_.find(index); | |
1225 if (it != layers_.end()) | |
1226 { | |
1227 assert(it->second != NULL); | |
1228 it->second->Render(buffer, viewTransform, interpolation); | |
1229 } | |
1230 } | |
1231 } | |
1232 | |
1233 | |
1234 bool LookupLayer(size_t& index /* out */, | |
1235 double x, | |
1236 double y) const | |
1237 { | |
1238 // Render layers in the foreground-to-background order | |
1239 for (size_t i = countLayers_; i > 0; i--) | |
1240 { | |
1241 index = i - 1; | |
1242 Layers::const_iterator it = layers_.find(index); | |
1243 if (it != layers_.end()) | |
1244 { | |
1245 assert(it->second != NULL); | |
1246 if (it->second->Contains(x, y)) | |
1247 { | |
1248 return true; | |
1249 } | |
1250 } | |
1251 } | |
1252 | |
1253 return false; | |
1254 } | |
1255 | |
1256 | |
1257 void DrawBorder(CairoContext& context, | |
1258 unsigned int layer, | |
1259 double zoom) | |
1260 { | |
1261 Layers::const_iterator found = layers_.find(layer); | |
1262 | |
1263 if (found != layers_.end()) | |
1264 { | |
1265 context.SetSourceColor(255, 0, 0); | |
1266 found->second->DrawBorders(context, zoom); | |
1267 } | |
1268 } | |
1269 | |
1270 | |
1271 void GetRange(float& minValue, | |
1272 float& maxValue) const | |
1273 { | |
1274 bool first = true; | |
1275 | |
1276 for (Layers::const_iterator it = layers_.begin(); | |
1277 it != layers_.end(); it++) | |
1278 { | |
1279 assert(it->second != NULL); | |
1280 | |
1281 float a, b; | |
1282 if (it->second->GetRange(a, b)) | |
1283 { | |
1284 if (first) | |
1285 { | |
1286 minValue = a; | |
1287 maxValue = b; | |
1288 first = false; | |
1289 } | |
1290 else | |
1291 { | |
1292 minValue = std::min(a, minValue); | |
1293 maxValue = std::max(b, maxValue); | |
1294 } | |
1295 } | |
1296 } | |
1297 | |
1298 if (first) | |
1299 { | |
1300 minValue = 0; | |
1301 maxValue = 0; | |
1302 } | |
1303 } | |
1304 | |
1305 | |
1306 // Export using PAM is faster than using PNG, but requires Orthanc | |
1307 // core >= 1.4.3 | |
1308 void Export(const Orthanc::DicomMap& dicom, | |
1309 double pixelSpacingX, | |
1310 double pixelSpacingY, | |
1311 bool invert, | |
1312 ImageInterpolation interpolation, | |
1313 bool usePam) | |
1314 { | |
1315 if (pixelSpacingX <= 0 || | |
1316 pixelSpacingY <= 0) | |
1317 { | |
1318 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); | |
1319 } | |
1320 | |
1321 LOG(INFO) << "Exporting DICOM"; | |
1322 | |
1323 Extent2D extent = GetSceneExtent(); | |
1324 | |
1325 int w = std::ceil(extent.GetWidth() / pixelSpacingX); | |
1326 int h = std::ceil(extent.GetHeight() / pixelSpacingY); | |
1327 | |
1328 if (w < 0 || h < 0) | |
1329 { | |
1330 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); | |
1331 } | |
1332 | |
1333 Orthanc::Image layers(Orthanc::PixelFormat_Float32, | |
1334 static_cast<unsigned int>(w), | |
1335 static_cast<unsigned int>(h), false); | |
1336 | |
1337 Matrix view = LinearAlgebra::Product( | |
1338 CreateScalingMatrix(1.0 / pixelSpacingX, 1.0 / pixelSpacingY), | |
1339 CreateOffsetMatrix(-extent.GetX1(), -extent.GetY1())); | |
1340 | |
1341 Render(layers, view, interpolation); | |
1342 | |
1343 Orthanc::Image rendered(Orthanc::PixelFormat_Grayscale16, | |
1344 layers.GetWidth(), layers.GetHeight(), false); | |
1345 Orthanc::ImageProcessing::Convert(rendered, layers); | |
1346 | |
1347 std::string base64; | |
1348 | |
1349 { | |
1350 std::string content; | |
1351 | |
1352 if (usePam) | |
1353 { | |
1354 Orthanc::PamWriter writer; | |
1355 writer.WriteToMemory(content, rendered); | |
1356 } | |
1357 else | |
1358 { | |
1359 Orthanc::PngWriter writer; | |
1360 writer.WriteToMemory(content, rendered); | |
1361 } | |
1362 | |
1363 Orthanc::Toolbox::EncodeBase64(base64, content); | |
1364 } | |
1365 | |
1366 std::set<Orthanc::DicomTag> tags; | |
1367 dicom.GetTags(tags); | |
1368 | |
1369 Json::Value json = Json::objectValue; | |
1370 json["Tags"] = Json::objectValue; | |
1371 | |
1372 for (std::set<Orthanc::DicomTag>::const_iterator | |
1373 tag = tags.begin(); tag != tags.end(); ++tag) | |
1374 { | |
1375 const Orthanc::DicomValue& value = dicom.GetValue(*tag); | |
1376 if (!value.IsNull() && | |
1377 !value.IsBinary()) | |
1378 { | |
1379 json["Tags"][tag->Format()] = value.GetContent(); | |
1380 } | |
1381 } | |
1382 | |
1383 json["Tags"][Orthanc::DICOM_TAG_PHOTOMETRIC_INTERPRETATION.Format()] = | |
1384 (invert ? "MONOCHROME1" : "MONOCHROME2"); | |
1385 | |
1386 // WARNING: The order of PixelSpacing is Y/X. We use "%0.8f" to | |
1387 // avoid floating-point numbers to grow over 16 characters, | |
1388 // which would be invalid according to DICOM standard | |
1389 // ("dciodvfy" would complain). | |
1390 char buf[32]; | |
1391 sprintf(buf, "%0.8f\\%0.8f", pixelSpacingY, pixelSpacingX); | |
1392 | |
1393 json["Tags"][Orthanc::DICOM_TAG_PIXEL_SPACING.Format()] = buf; | |
1394 | |
1395 float center, width; | |
1396 if (GetWindowing(center, width)) | |
1397 { | |
1398 json["Tags"][Orthanc::DICOM_TAG_WINDOW_CENTER.Format()] = | |
1399 boost::lexical_cast<std::string>(boost::math::iround(center)); | |
1400 | |
1401 json["Tags"][Orthanc::DICOM_TAG_WINDOW_WIDTH.Format()] = | |
1402 boost::lexical_cast<std::string>(boost::math::iround(width)); | |
1403 } | |
1404 | |
1405 // This is Data URI scheme: https://en.wikipedia.org/wiki/Data_URI_scheme | |
1406 json["Content"] = ("data:" + | |
1407 std::string(usePam ? Orthanc::MIME_PAM : Orthanc::MIME_PNG) + | |
1408 ";base64," + base64); | |
1409 | |
1410 orthanc_.PostJsonAsyncExpectJson( | |
1411 "/tools/create-dicom", json, | |
1412 new Callable<RadiologyScene, OrthancApiClient::JsonResponseReadyMessage> | |
1413 (*this, &RadiologyScene::OnDicomExported), | |
1414 NULL, NULL); | |
1415 } | |
1416 | |
1417 | |
1418 void OnDicomExported(const OrthancApiClient::JsonResponseReadyMessage& message) | |
1419 { | |
1420 LOG(INFO) << "DICOM export was successful:" | |
1421 << message.GetJson().toStyledString(); | |
1422 } | |
1423 }; | |
1424 | |
1425 | |
1426 class UndoRedoStack : public boost::noncopyable | |
1427 { | |
1428 public: | |
1429 class ICommand : public boost::noncopyable | |
1430 { | |
1431 public: | |
1432 virtual ~ICommand() | |
1433 { | |
1434 } | |
1435 | |
1436 virtual void Undo() const = 0; | |
1437 | |
1438 virtual void Redo() const = 0; | |
1439 }; | |
1440 | |
1441 private: | |
1442 typedef std::list<ICommand*> Stack; | |
1443 | |
1444 Stack stack_; | |
1445 Stack::iterator current_; | |
1446 | |
1447 void Clear(Stack::iterator from) | |
1448 { | |
1449 for (Stack::iterator it = from; it != stack_.end(); ++it) | |
1450 { | |
1451 assert(*it != NULL); | |
1452 delete *it; | |
1453 } | |
1454 | |
1455 stack_.erase(from, stack_.end()); | |
1456 } | |
1457 | |
1458 public: | |
1459 UndoRedoStack() : | |
1460 current_(stack_.end()) | |
1461 { | |
1462 } | |
1463 | |
1464 ~UndoRedoStack() | |
1465 { | |
1466 Clear(stack_.begin()); | |
1467 } | |
1468 | |
1469 void Add(ICommand* command) | |
1470 { | |
1471 if (command == NULL) | |
1472 { | |
1473 throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); | |
1474 } | |
1475 | |
1476 Clear(current_); | |
1477 | |
1478 stack_.push_back(command); | |
1479 current_ = stack_.end(); | |
1480 } | |
1481 | |
1482 void Undo() | |
1483 { | |
1484 if (current_ != stack_.begin()) | |
1485 { | |
1486 --current_; | |
1487 | |
1488 assert(*current_ != NULL); | |
1489 (*current_)->Undo(); | |
1490 } | |
1491 } | |
1492 | |
1493 void Redo() | |
1494 { | |
1495 if (current_ != stack_.end()) | |
1496 { | |
1497 assert(*current_ != NULL); | |
1498 (*current_)->Redo(); | |
1499 | |
1500 ++current_; | |
1501 } | |
1502 } | |
1503 }; | |
1504 | |
1505 | |
1506 class RadiologyLayerCommand : public UndoRedoStack::ICommand | |
1507 { | 55 { |
1508 private: | 56 private: |
1509 RadiologyScene& scene_; | 57 RadiographyScene& scene_; |
1510 size_t layer_; | 58 size_t layer_; |
1511 | 59 |
1512 protected: | 60 protected: |
1513 virtual void UndoInternal(RadiologyScene::Layer& layer) const = 0; | 61 virtual void UndoInternal(RadiographyScene::Layer& layer) const = 0; |
1514 | 62 |
1515 virtual void RedoInternal(RadiologyScene::Layer& layer) const = 0; | 63 virtual void RedoInternal(RadiographyScene::Layer& layer) const = 0; |
1516 | 64 |
1517 public: | 65 public: |
1518 RadiologyLayerCommand(RadiologyScene& scene, | 66 RadiographyLayerCommand(RadiographyScene& scene, |
1519 size_t layer) : | 67 size_t layer) : |
1520 scene_(scene), | 68 scene_(scene), |
1521 layer_(layer) | 69 layer_(layer) |
1522 { | 70 { |
1523 } | 71 } |
1524 | 72 |
1525 RadiologyLayerCommand(const RadiologyScene::LayerAccessor& accessor) : | 73 RadiographyLayerCommand(const RadiographyScene::LayerAccessor& accessor) : |
1526 scene_(accessor.GetScene()), | 74 scene_(accessor.GetScene()), |
1527 layer_(accessor.GetIndex()) | 75 layer_(accessor.GetIndex()) |
1528 { | 76 { |
1529 } | 77 } |
1530 | 78 |
1531 virtual void Undo() const | 79 virtual void Undo() const |
1532 { | 80 { |
1533 RadiologyScene::LayerAccessor accessor(scene_, layer_); | 81 RadiographyScene::LayerAccessor accessor(scene_, layer_); |
1534 | 82 |
1535 if (accessor.IsValid()) | 83 if (accessor.IsValid()) |
1536 { | 84 { |
1537 UndoInternal(accessor.GetLayer()); | 85 UndoInternal(accessor.GetLayer()); |
1538 } | 86 } |
1539 } | 87 } |
1540 | 88 |
1541 virtual void Redo() const | 89 virtual void Redo() const |
1542 { | 90 { |
1543 RadiologyScene::LayerAccessor accessor(scene_, layer_); | 91 RadiographyScene::LayerAccessor accessor(scene_, layer_); |
1544 | 92 |
1545 if (accessor.IsValid()) | 93 if (accessor.IsValid()) |
1546 { | 94 { |
1547 RedoInternal(accessor.GetLayer()); | 95 RedoInternal(accessor.GetLayer()); |
1548 } | 96 } |
1549 } | 97 } |
1550 }; | 98 }; |
1551 | 99 |
1552 | 100 |
1553 class RadiologyLayerRotateTracker : public IWorldSceneMouseTracker | 101 class RadiographyLayerRotateTracker : public IWorldSceneMouseTracker |
1554 { | 102 { |
1555 private: | 103 private: |
1556 UndoRedoStack& undoRedoStack_; | 104 UndoRedoStack& undoRedoStack_; |
1557 RadiologyScene::LayerAccessor accessor_; | 105 RadiographyScene::LayerAccessor accessor_; |
1558 double centerX_; | 106 double centerX_; |
1559 double centerY_; | 107 double centerY_; |
1560 double originalAngle_; | 108 double originalAngle_; |
1561 double clickAngle_; | 109 double clickAngle_; |
1562 bool roundAngles_; | 110 bool roundAngles_; |
1581 return false; | 129 return false; |
1582 } | 130 } |
1583 } | 131 } |
1584 | 132 |
1585 | 133 |
1586 class UndoRedoCommand : public RadiologyLayerCommand | 134 class UndoRedoCommand : public RadiographyLayerCommand |
1587 { | 135 { |
1588 private: | 136 private: |
1589 double sourceAngle_; | 137 double sourceAngle_; |
1590 double targetAngle_; | 138 double targetAngle_; |
1591 | 139 |
1593 { | 141 { |
1594 return boost::math::iround(angle * 180.0 / boost::math::constants::pi<double>()); | 142 return boost::math::iround(angle * 180.0 / boost::math::constants::pi<double>()); |
1595 } | 143 } |
1596 | 144 |
1597 protected: | 145 protected: |
1598 virtual void UndoInternal(RadiologyScene::Layer& layer) const | 146 virtual void UndoInternal(RadiographyScene::Layer& layer) const |
1599 { | 147 { |
1600 LOG(INFO) << "Undo - Set angle to " << ToDegrees(sourceAngle_) << " degrees"; | 148 LOG(INFO) << "Undo - Set angle to " << ToDegrees(sourceAngle_) << " degrees"; |
1601 layer.SetAngle(sourceAngle_); | 149 layer.SetAngle(sourceAngle_); |
1602 } | 150 } |
1603 | 151 |
1604 virtual void RedoInternal(RadiologyScene::Layer& layer) const | 152 virtual void RedoInternal(RadiographyScene::Layer& layer) const |
1605 { | 153 { |
1606 LOG(INFO) << "Redo - Set angle to " << ToDegrees(sourceAngle_) << " degrees"; | 154 LOG(INFO) << "Redo - Set angle to " << ToDegrees(sourceAngle_) << " degrees"; |
1607 layer.SetAngle(targetAngle_); | 155 layer.SetAngle(targetAngle_); |
1608 } | 156 } |
1609 | 157 |
1610 public: | 158 public: |
1611 UndoRedoCommand(const RadiologyLayerRotateTracker& tracker) : | 159 UndoRedoCommand(const RadiographyLayerRotateTracker& tracker) : |
1612 RadiologyLayerCommand(tracker.accessor_), | 160 RadiographyLayerCommand(tracker.accessor_), |
1613 sourceAngle_(tracker.originalAngle_), | 161 sourceAngle_(tracker.originalAngle_), |
1614 targetAngle_(tracker.accessor_.GetLayer().GetAngle()) | 162 targetAngle_(tracker.accessor_.GetLayer().GetAngle()) |
1615 { | 163 { |
1616 } | 164 } |
1617 }; | 165 }; |
1618 | 166 |
1619 | 167 |
1620 public: | 168 public: |
1621 RadiologyLayerRotateTracker(UndoRedoStack& undoRedoStack, | 169 RadiographyLayerRotateTracker(UndoRedoStack& undoRedoStack, |
1622 RadiologyScene& scene, | 170 RadiographyScene& scene, |
1623 const ViewportGeometry& view, | 171 const ViewportGeometry& view, |
1624 size_t layer, | 172 size_t layer, |
1625 double x, | 173 double x, |
1626 double y, | 174 double y, |
1627 bool roundAngles) : | 175 bool roundAngles) : |
1628 undoRedoStack_(undoRedoStack), | 176 undoRedoStack_(undoRedoStack), |
1629 accessor_(scene, layer), | 177 accessor_(scene, layer), |
1630 roundAngles_(roundAngles) | 178 roundAngles_(roundAngles) |
1631 { | 179 { |
1632 if (accessor_.IsValid()) | 180 if (accessor_.IsValid()) |
1686 } | 234 } |
1687 } | 235 } |
1688 }; | 236 }; |
1689 | 237 |
1690 | 238 |
1691 class RadiologyLayerMoveTracker : public IWorldSceneMouseTracker | 239 class RadiographyLayerMoveTracker : public IWorldSceneMouseTracker |
1692 { | 240 { |
1693 private: | 241 private: |
1694 UndoRedoStack& undoRedoStack_; | 242 UndoRedoStack& undoRedoStack_; |
1695 RadiologyScene::LayerAccessor accessor_; | 243 RadiographyScene::LayerAccessor accessor_; |
1696 double clickX_; | 244 double clickX_; |
1697 double clickY_; | 245 double clickY_; |
1698 double panX_; | 246 double panX_; |
1699 double panY_; | 247 double panY_; |
1700 bool oneAxis_; | 248 bool oneAxis_; |
1701 | 249 |
1702 class UndoRedoCommand : public RadiologyLayerCommand | 250 class UndoRedoCommand : public RadiographyLayerCommand |
1703 { | 251 { |
1704 private: | 252 private: |
1705 double sourceX_; | 253 double sourceX_; |
1706 double sourceY_; | 254 double sourceY_; |
1707 double targetX_; | 255 double targetX_; |
1708 double targetY_; | 256 double targetY_; |
1709 | 257 |
1710 protected: | 258 protected: |
1711 virtual void UndoInternal(RadiologyScene::Layer& layer) const | 259 virtual void UndoInternal(RadiographyScene::Layer& layer) const |
1712 { | 260 { |
1713 layer.SetPan(sourceX_, sourceY_); | 261 layer.SetPan(sourceX_, sourceY_); |
1714 } | 262 } |
1715 | 263 |
1716 virtual void RedoInternal(RadiologyScene::Layer& layer) const | 264 virtual void RedoInternal(RadiographyScene::Layer& layer) const |
1717 { | 265 { |
1718 layer.SetPan(targetX_, targetY_); | 266 layer.SetPan(targetX_, targetY_); |
1719 } | 267 } |
1720 | 268 |
1721 public: | 269 public: |
1722 UndoRedoCommand(const RadiologyLayerMoveTracker& tracker) : | 270 UndoRedoCommand(const RadiographyLayerMoveTracker& tracker) : |
1723 RadiologyLayerCommand(tracker.accessor_), | 271 RadiographyLayerCommand(tracker.accessor_), |
1724 sourceX_(tracker.panX_), | 272 sourceX_(tracker.panX_), |
1725 sourceY_(tracker.panY_), | 273 sourceY_(tracker.panY_), |
1726 targetX_(tracker.accessor_.GetLayer().GetPanX()), | 274 targetX_(tracker.accessor_.GetLayer().GetPanX()), |
1727 targetY_(tracker.accessor_.GetLayer().GetPanY()) | 275 targetY_(tracker.accessor_.GetLayer().GetPanY()) |
1728 { | 276 { |
1729 } | 277 } |
1730 }; | 278 }; |
1731 | 279 |
1732 | 280 |
1733 public: | 281 public: |
1734 RadiologyLayerMoveTracker(UndoRedoStack& undoRedoStack, | 282 RadiographyLayerMoveTracker(UndoRedoStack& undoRedoStack, |
1735 RadiologyScene& scene, | 283 RadiographyScene& scene, |
1736 size_t layer, | 284 size_t layer, |
1737 double x, | 285 double x, |
1738 double y, | 286 double y, |
1739 bool oneAxis) : | 287 bool oneAxis) : |
1740 undoRedoStack_(undoRedoStack), | 288 undoRedoStack_(undoRedoStack), |
1741 accessor_(scene, layer), | 289 accessor_(scene, layer), |
1742 clickX_(x), | 290 clickX_(x), |
1743 clickY_(y), | 291 clickY_(y), |
1744 oneAxis_(oneAxis) | 292 oneAxis_(oneAxis) |
1797 } | 345 } |
1798 } | 346 } |
1799 }; | 347 }; |
1800 | 348 |
1801 | 349 |
1802 class RadiologyLayerCropTracker : public IWorldSceneMouseTracker | 350 class RadiographyLayerCropTracker : public IWorldSceneMouseTracker |
1803 { | 351 { |
1804 private: | 352 private: |
1805 UndoRedoStack& undoRedoStack_; | 353 UndoRedoStack& undoRedoStack_; |
1806 RadiologyScene::LayerAccessor accessor_; | 354 RadiographyScene::LayerAccessor accessor_; |
1807 RadiologyScene::Corner corner_; | 355 RadiographyScene::Corner corner_; |
1808 unsigned int cropX_; | 356 unsigned int cropX_; |
1809 unsigned int cropY_; | 357 unsigned int cropY_; |
1810 unsigned int cropWidth_; | 358 unsigned int cropWidth_; |
1811 unsigned int cropHeight_; | 359 unsigned int cropHeight_; |
1812 | 360 |
1813 class UndoRedoCommand : public RadiologyLayerCommand | 361 class UndoRedoCommand : public RadiographyLayerCommand |
1814 { | 362 { |
1815 private: | 363 private: |
1816 unsigned int sourceCropX_; | 364 unsigned int sourceCropX_; |
1817 unsigned int sourceCropY_; | 365 unsigned int sourceCropY_; |
1818 unsigned int sourceCropWidth_; | 366 unsigned int sourceCropWidth_; |
1821 unsigned int targetCropY_; | 369 unsigned int targetCropY_; |
1822 unsigned int targetCropWidth_; | 370 unsigned int targetCropWidth_; |
1823 unsigned int targetCropHeight_; | 371 unsigned int targetCropHeight_; |
1824 | 372 |
1825 protected: | 373 protected: |
1826 virtual void UndoInternal(RadiologyScene::Layer& layer) const | 374 virtual void UndoInternal(RadiographyScene::Layer& layer) const |
1827 { | 375 { |
1828 layer.SetCrop(sourceCropX_, sourceCropY_, sourceCropWidth_, sourceCropHeight_); | 376 layer.SetCrop(sourceCropX_, sourceCropY_, sourceCropWidth_, sourceCropHeight_); |
1829 } | 377 } |
1830 | 378 |
1831 virtual void RedoInternal(RadiologyScene::Layer& layer) const | 379 virtual void RedoInternal(RadiographyScene::Layer& layer) const |
1832 { | 380 { |
1833 layer.SetCrop(targetCropX_, targetCropY_, targetCropWidth_, targetCropHeight_); | 381 layer.SetCrop(targetCropX_, targetCropY_, targetCropWidth_, targetCropHeight_); |
1834 } | 382 } |
1835 | 383 |
1836 public: | 384 public: |
1837 UndoRedoCommand(const RadiologyLayerCropTracker& tracker) : | 385 UndoRedoCommand(const RadiographyLayerCropTracker& tracker) : |
1838 RadiologyLayerCommand(tracker.accessor_), | 386 RadiographyLayerCommand(tracker.accessor_), |
1839 sourceCropX_(tracker.cropX_), | 387 sourceCropX_(tracker.cropX_), |
1840 sourceCropY_(tracker.cropY_), | 388 sourceCropY_(tracker.cropY_), |
1841 sourceCropWidth_(tracker.cropWidth_), | 389 sourceCropWidth_(tracker.cropWidth_), |
1842 sourceCropHeight_(tracker.cropHeight_) | 390 sourceCropHeight_(tracker.cropHeight_) |
1843 { | 391 { |
1846 } | 394 } |
1847 }; | 395 }; |
1848 | 396 |
1849 | 397 |
1850 public: | 398 public: |
1851 RadiologyLayerCropTracker(UndoRedoStack& undoRedoStack, | 399 RadiographyLayerCropTracker(UndoRedoStack& undoRedoStack, |
1852 RadiologyScene& scene, | 400 RadiographyScene& scene, |
1853 const ViewportGeometry& view, | 401 const ViewportGeometry& view, |
1854 size_t layer, | 402 size_t layer, |
1855 double x, | 403 double x, |
1856 double y, | 404 double y, |
1857 RadiologyScene::Corner corner) : | 405 RadiographyScene::Corner corner) : |
1858 undoRedoStack_(undoRedoStack), | 406 undoRedoStack_(undoRedoStack), |
1859 accessor_(scene, layer), | 407 accessor_(scene, layer), |
1860 corner_(corner) | 408 corner_(corner) |
1861 { | 409 { |
1862 if (accessor_.IsValid()) | 410 if (accessor_.IsValid()) |
1891 { | 439 { |
1892 if (accessor_.IsValid()) | 440 if (accessor_.IsValid()) |
1893 { | 441 { |
1894 unsigned int x, y; | 442 unsigned int x, y; |
1895 | 443 |
1896 RadiologyScene::Layer& layer = accessor_.GetLayer(); | 444 RadiographyScene::Layer& layer = accessor_.GetLayer(); |
1897 if (layer.GetPixel(x, y, sceneX, sceneY)) | 445 if (layer.GetPixel(x, y, sceneX, sceneY)) |
1898 { | 446 { |
1899 unsigned int targetX, targetWidth; | 447 unsigned int targetX, targetWidth; |
1900 | 448 |
1901 if (corner_ == RadiologyScene::Corner_TopLeft || | 449 if (corner_ == RadiographyScene::Corner_TopLeft || |
1902 corner_ == RadiologyScene::Corner_BottomLeft) | 450 corner_ == RadiographyScene::Corner_BottomLeft) |
1903 { | 451 { |
1904 targetX = std::min(x, cropX_ + cropWidth_); | 452 targetX = std::min(x, cropX_ + cropWidth_); |
1905 targetWidth = cropX_ + cropWidth_ - targetX; | 453 targetWidth = cropX_ + cropWidth_ - targetX; |
1906 } | 454 } |
1907 else | 455 else |
1910 targetWidth = std::max(x, cropX_) - cropX_; | 458 targetWidth = std::max(x, cropX_) - cropX_; |
1911 } | 459 } |
1912 | 460 |
1913 unsigned int targetY, targetHeight; | 461 unsigned int targetY, targetHeight; |
1914 | 462 |
1915 if (corner_ == RadiologyScene::Corner_TopLeft || | 463 if (corner_ == RadiographyScene::Corner_TopLeft || |
1916 corner_ == RadiologyScene::Corner_TopRight) | 464 corner_ == RadiographyScene::Corner_TopRight) |
1917 { | 465 { |
1918 targetY = std::min(y, cropY_ + cropHeight_); | 466 targetY = std::min(y, cropY_ + cropHeight_); |
1919 targetHeight = cropY_ + cropHeight_ - targetY; | 467 targetHeight = cropY_ + cropHeight_ - targetY; |
1920 } | 468 } |
1921 else | 469 else |
1929 } | 477 } |
1930 } | 478 } |
1931 }; | 479 }; |
1932 | 480 |
1933 | 481 |
1934 class RadiologyLayerResizeTracker : public IWorldSceneMouseTracker | 482 class RadiographyLayerResizeTracker : public IWorldSceneMouseTracker |
1935 { | 483 { |
1936 private: | 484 private: |
1937 UndoRedoStack& undoRedoStack_; | 485 UndoRedoStack& undoRedoStack_; |
1938 RadiologyScene::LayerAccessor accessor_; | 486 RadiographyScene::LayerAccessor accessor_; |
1939 bool roundScaling_; | 487 bool roundScaling_; |
1940 double originalSpacingX_; | 488 double originalSpacingX_; |
1941 double originalSpacingY_; | 489 double originalSpacingY_; |
1942 double originalPanX_; | 490 double originalPanX_; |
1943 double originalPanY_; | 491 double originalPanY_; |
1944 RadiologyScene::Corner oppositeCorner_; | 492 RadiographyScene::Corner oppositeCorner_; |
1945 double oppositeX_; | 493 double oppositeX_; |
1946 double oppositeY_; | 494 double oppositeY_; |
1947 double baseScaling_; | 495 double baseScaling_; |
1948 | 496 |
1949 static double ComputeDistance(double x1, | 497 static double ComputeDistance(double x1, |
1950 double y1, | 498 double y1, |
1951 double x2, | 499 double x2, |
1952 double y2) | 500 double y2) |
1954 double dx = x1 - x2; | 502 double dx = x1 - x2; |
1955 double dy = y1 - y2; | 503 double dy = y1 - y2; |
1956 return sqrt(dx * dx + dy * dy); | 504 return sqrt(dx * dx + dy * dy); |
1957 } | 505 } |
1958 | 506 |
1959 class UndoRedoCommand : public RadiologyLayerCommand | 507 class UndoRedoCommand : public RadiographyLayerCommand |
1960 { | 508 { |
1961 private: | 509 private: |
1962 double sourceSpacingX_; | 510 double sourceSpacingX_; |
1963 double sourceSpacingY_; | 511 double sourceSpacingY_; |
1964 double sourcePanX_; | 512 double sourcePanX_; |
1967 double targetSpacingY_; | 515 double targetSpacingY_; |
1968 double targetPanX_; | 516 double targetPanX_; |
1969 double targetPanY_; | 517 double targetPanY_; |
1970 | 518 |
1971 protected: | 519 protected: |
1972 virtual void UndoInternal(RadiologyScene::Layer& layer) const | 520 virtual void UndoInternal(RadiographyScene::Layer& layer) const |
1973 { | 521 { |
1974 layer.SetPixelSpacing(sourceSpacingX_, sourceSpacingY_); | 522 layer.SetPixelSpacing(sourceSpacingX_, sourceSpacingY_); |
1975 layer.SetPan(sourcePanX_, sourcePanY_); | 523 layer.SetPan(sourcePanX_, sourcePanY_); |
1976 } | 524 } |
1977 | 525 |
1978 virtual void RedoInternal(RadiologyScene::Layer& layer) const | 526 virtual void RedoInternal(RadiographyScene::Layer& layer) const |
1979 { | 527 { |
1980 layer.SetPixelSpacing(targetSpacingX_, targetSpacingY_); | 528 layer.SetPixelSpacing(targetSpacingX_, targetSpacingY_); |
1981 layer.SetPan(targetPanX_, targetPanY_); | 529 layer.SetPan(targetPanX_, targetPanY_); |
1982 } | 530 } |
1983 | 531 |
1984 public: | 532 public: |
1985 UndoRedoCommand(const RadiologyLayerResizeTracker& tracker) : | 533 UndoRedoCommand(const RadiographyLayerResizeTracker& tracker) : |
1986 RadiologyLayerCommand(tracker.accessor_), | 534 RadiographyLayerCommand(tracker.accessor_), |
1987 sourceSpacingX_(tracker.originalSpacingX_), | 535 sourceSpacingX_(tracker.originalSpacingX_), |
1988 sourceSpacingY_(tracker.originalSpacingY_), | 536 sourceSpacingY_(tracker.originalSpacingY_), |
1989 sourcePanX_(tracker.originalPanX_), | 537 sourcePanX_(tracker.originalPanX_), |
1990 sourcePanY_(tracker.originalPanY_), | 538 sourcePanY_(tracker.originalPanY_), |
1991 targetSpacingX_(tracker.accessor_.GetLayer().GetPixelSpacingX()), | 539 targetSpacingX_(tracker.accessor_.GetLayer().GetPixelSpacingX()), |
1996 } | 544 } |
1997 }; | 545 }; |
1998 | 546 |
1999 | 547 |
2000 public: | 548 public: |
2001 RadiologyLayerResizeTracker(UndoRedoStack& undoRedoStack, | 549 RadiographyLayerResizeTracker(UndoRedoStack& undoRedoStack, |
2002 RadiologyScene& scene, | 550 RadiographyScene& scene, |
2003 size_t layer, | 551 size_t layer, |
2004 double x, | 552 double x, |
2005 double y, | 553 double y, |
2006 RadiologyScene::Corner corner, | 554 RadiographyScene::Corner corner, |
2007 bool roundScaling) : | 555 bool roundScaling) : |
2008 undoRedoStack_(undoRedoStack), | 556 undoRedoStack_(undoRedoStack), |
2009 accessor_(scene, layer), | 557 accessor_(scene, layer), |
2010 roundScaling_(roundScaling) | 558 roundScaling_(roundScaling) |
2011 { | 559 { |
2012 if (accessor_.IsValid() && | 560 if (accessor_.IsValid() && |
2017 originalPanX_ = accessor_.GetLayer().GetPanX(); | 565 originalPanX_ = accessor_.GetLayer().GetPanX(); |
2018 originalPanY_ = accessor_.GetLayer().GetPanY(); | 566 originalPanY_ = accessor_.GetLayer().GetPanY(); |
2019 | 567 |
2020 switch (corner) | 568 switch (corner) |
2021 { | 569 { |
2022 case RadiologyScene::Corner_TopLeft: | 570 case RadiographyScene::Corner_TopLeft: |
2023 oppositeCorner_ = RadiologyScene::Corner_BottomRight; | 571 oppositeCorner_ = RadiographyScene::Corner_BottomRight; |
2024 break; | 572 break; |
2025 | 573 |
2026 case RadiologyScene::Corner_TopRight: | 574 case RadiographyScene::Corner_TopRight: |
2027 oppositeCorner_ = RadiologyScene::Corner_BottomLeft; | 575 oppositeCorner_ = RadiographyScene::Corner_BottomLeft; |
2028 break; | 576 break; |
2029 | 577 |
2030 case RadiologyScene::Corner_BottomLeft: | 578 case RadiographyScene::Corner_BottomLeft: |
2031 oppositeCorner_ = RadiologyScene::Corner_TopRight; | 579 oppositeCorner_ = RadiographyScene::Corner_TopRight; |
2032 break; | 580 break; |
2033 | 581 |
2034 case RadiologyScene::Corner_BottomRight: | 582 case RadiographyScene::Corner_BottomRight: |
2035 oppositeCorner_ = RadiologyScene::Corner_TopLeft; | 583 oppositeCorner_ = RadiographyScene::Corner_TopLeft; |
2036 break; | 584 break; |
2037 | 585 |
2038 default: | 586 default: |
2039 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); | 587 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); |
2040 } | 588 } |
2089 if (roundScaling_) | 637 if (roundScaling_) |
2090 { | 638 { |
2091 scaling = boost::math::round<double>((scaling / ROUND_SCALING) * ROUND_SCALING); | 639 scaling = boost::math::round<double>((scaling / ROUND_SCALING) * ROUND_SCALING); |
2092 } | 640 } |
2093 | 641 |
2094 RadiologyScene::Layer& layer = accessor_.GetLayer(); | 642 RadiographyScene::Layer& layer = accessor_.GetLayer(); |
2095 layer.SetPixelSpacing(scaling * originalSpacingX_, | 643 layer.SetPixelSpacing(scaling * originalSpacingX_, |
2096 scaling * originalSpacingY_); | 644 scaling * originalSpacingY_); |
2097 | 645 |
2098 // Keep the opposite corner at a fixed location | 646 // Keep the opposite corner at a fixed location |
2099 double ox, oy; | 647 double ox, oy; |
2103 } | 651 } |
2104 } | 652 } |
2105 }; | 653 }; |
2106 | 654 |
2107 | 655 |
2108 class RadiologyWindowingTracker : public IWorldSceneMouseTracker | 656 class RadiographyWindowingTracker : public IWorldSceneMouseTracker |
2109 { | 657 { |
2110 public: | 658 public: |
2111 enum Action | 659 enum Action |
2112 { | 660 { |
2113 Action_IncreaseWidth, | 661 Action_IncreaseWidth, |
2115 Action_IncreaseCenter, | 663 Action_IncreaseCenter, |
2116 Action_DecreaseCenter | 664 Action_DecreaseCenter |
2117 }; | 665 }; |
2118 | 666 |
2119 private: | 667 private: |
2120 UndoRedoStack& undoRedoStack_; | 668 UndoRedoStack& undoRedoStack_; |
2121 RadiologyScene& scene_; | 669 RadiographyScene& scene_; |
2122 int clickX_; | 670 int clickX_; |
2123 int clickY_; | 671 int clickY_; |
2124 Action leftAction_; | 672 Action leftAction_; |
2125 Action rightAction_; | 673 Action rightAction_; |
2126 Action upAction_; | 674 Action upAction_; |
2127 Action downAction_; | 675 Action downAction_; |
2128 float strength_; | 676 float strength_; |
2129 float sourceCenter_; | 677 float sourceCenter_; |
2130 float sourceWidth_; | 678 float sourceWidth_; |
2131 | 679 |
2132 static void ComputeAxisEffect(int& deltaCenter, | 680 static void ComputeAxisEffect(int& deltaCenter, |
2133 int& deltaWidth, | 681 int& deltaWidth, |
2134 int delta, | 682 int delta, |
2135 Action actionNegative, | 683 Action actionNegative, |
2187 | 735 |
2188 | 736 |
2189 class UndoRedoCommand : public UndoRedoStack::ICommand | 737 class UndoRedoCommand : public UndoRedoStack::ICommand |
2190 { | 738 { |
2191 private: | 739 private: |
2192 RadiologyScene& scene_; | 740 RadiographyScene& scene_; |
2193 float sourceCenter_; | 741 float sourceCenter_; |
2194 float sourceWidth_; | 742 float sourceWidth_; |
2195 float targetCenter_; | 743 float targetCenter_; |
2196 float targetWidth_; | 744 float targetWidth_; |
2197 | 745 |
2198 public: | 746 public: |
2199 UndoRedoCommand(const RadiologyWindowingTracker& tracker) : | 747 UndoRedoCommand(const RadiographyWindowingTracker& tracker) : |
2200 scene_(tracker.scene_), | 748 scene_(tracker.scene_), |
2201 sourceCenter_(tracker.sourceCenter_), | 749 sourceCenter_(tracker.sourceCenter_), |
2202 sourceWidth_(tracker.sourceWidth_) | 750 sourceWidth_(tracker.sourceWidth_) |
2203 { | 751 { |
2204 scene_.GetWindowingWithDefault(targetCenter_, targetWidth_); | 752 scene_.GetWindowingWithDefault(targetCenter_, targetWidth_); |
2215 } | 763 } |
2216 }; | 764 }; |
2217 | 765 |
2218 | 766 |
2219 public: | 767 public: |
2220 RadiologyWindowingTracker(UndoRedoStack& undoRedoStack, | 768 RadiographyWindowingTracker(UndoRedoStack& undoRedoStack, |
2221 RadiologyScene& scene, | 769 RadiographyScene& scene, |
2222 int x, | 770 int x, |
2223 int y, | 771 int y, |
2224 Action leftAction, | 772 Action leftAction, |
2225 Action rightAction, | 773 Action rightAction, |
2226 Action upAction, | 774 Action upAction, |
2227 Action downAction) : | 775 Action downAction) : |
2228 undoRedoStack_(undoRedoStack), | 776 undoRedoStack_(undoRedoStack), |
2229 scene_(scene), | 777 scene_(scene), |
2230 clickX_(x), | 778 clickX_(x), |
2231 clickY_(y), | 779 clickY_(y), |
2232 leftAction_(leftAction), | 780 leftAction_(leftAction), |
2299 scene_.SetWindowing(newCenter, newWidth); | 847 scene_.SetWindowing(newCenter, newWidth); |
2300 } | 848 } |
2301 }; | 849 }; |
2302 | 850 |
2303 | 851 |
2304 class RadiologyWidget : | 852 class RadiographyWidget : |
2305 public WorldSceneWidget, | 853 public WorldSceneWidget, |
2306 public IObserver | 854 public IObserver |
2307 { | 855 { |
2308 private: | 856 private: |
2309 RadiologyScene& scene_; | 857 RadiographyScene& scene_; |
2310 std::auto_ptr<Orthanc::Image> floatBuffer_; | 858 std::auto_ptr<Orthanc::Image> floatBuffer_; |
2311 std::auto_ptr<CairoSurface> cairoBuffer_; | 859 std::auto_ptr<CairoSurface> cairoBuffer_; |
2312 bool invert_; | 860 bool invert_; |
2313 ImageInterpolation interpolation_; | 861 ImageInterpolation interpolation_; |
2314 bool hasSelection_; | 862 bool hasSelection_; |
2426 | 974 |
2427 return true; | 975 return true; |
2428 } | 976 } |
2429 | 977 |
2430 public: | 978 public: |
2431 RadiologyWidget(MessageBroker& broker, | 979 RadiographyWidget(MessageBroker& broker, |
2432 RadiologyScene& scene, | 980 RadiographyScene& scene, |
2433 const std::string& name) : | 981 const std::string& name) : |
2434 WorldSceneWidget(name), | 982 WorldSceneWidget(name), |
2435 IObserver(broker), | 983 IObserver(broker), |
2436 scene_(scene), | 984 scene_(scene), |
2437 invert_(false), | 985 invert_(false), |
2438 interpolation_(ImageInterpolation_Nearest), | 986 interpolation_(ImageInterpolation_Nearest), |
2439 hasSelection_(false), | 987 hasSelection_(false), |
2440 selectedLayer_(0) // Dummy initialization | 988 selectedLayer_(0) // Dummy initialization |
2441 { | 989 { |
2442 scene.RegisterObserverCallback( | 990 scene.RegisterObserverCallback( |
2443 new Callable<RadiologyWidget, RadiologyScene::GeometryChangedMessage> | 991 new Callable<RadiographyWidget, RadiographyScene::GeometryChangedMessage> |
2444 (*this, &RadiologyWidget::OnGeometryChanged)); | 992 (*this, &RadiographyWidget::OnGeometryChanged)); |
2445 | 993 |
2446 scene.RegisterObserverCallback( | 994 scene.RegisterObserverCallback( |
2447 new Callable<RadiologyWidget, RadiologyScene::ContentChangedMessage> | 995 new Callable<RadiographyWidget, RadiographyScene::ContentChangedMessage> |
2448 (*this, &RadiologyWidget::OnContentChanged)); | 996 (*this, &RadiographyWidget::OnContentChanged)); |
2449 } | 997 } |
2450 | 998 |
2451 RadiologyScene& GetScene() const | 999 RadiographyScene& GetScene() const |
2452 { | 1000 { |
2453 return scene_; | 1001 return scene_; |
2454 } | 1002 } |
2455 | 1003 |
2456 void Unselect() | 1004 void Unselect() |
2475 { | 1023 { |
2476 return false; | 1024 return false; |
2477 } | 1025 } |
2478 } | 1026 } |
2479 | 1027 |
2480 void OnGeometryChanged(const RadiologyScene::GeometryChangedMessage& message) | 1028 void OnGeometryChanged(const RadiographyScene::GeometryChangedMessage& message) |
2481 { | 1029 { |
2482 LOG(INFO) << "Geometry has changed"; | 1030 LOG(INFO) << "Geometry has changed"; |
2483 FitContent(); | 1031 FitContent(); |
2484 } | 1032 } |
2485 | 1033 |
2486 void OnContentChanged(const RadiologyScene::ContentChangedMessage& message) | 1034 void OnContentChanged(const RadiographyScene::ContentChangedMessage& message) |
2487 { | 1035 { |
2488 LOG(INFO) << "Content has changed"; | 1036 LOG(INFO) << "Content has changed"; |
2489 NotifyContentChanged(); | 1037 NotifyContentChanged(); |
2490 } | 1038 } |
2491 | 1039 |
2525 }; | 1073 }; |
2526 | 1074 |
2527 | 1075 |
2528 namespace Samples | 1076 namespace Samples |
2529 { | 1077 { |
2530 class RadiologyEditorInteractor : | 1078 class RadiographyEditorInteractor : |
2531 public IWorldSceneInteractor, | 1079 public IWorldSceneInteractor, |
2532 public IObserver | 1080 public IObserver |
2533 { | 1081 { |
2534 private: | 1082 private: |
2535 enum Tool | 1083 enum Tool |
2551 return 10.0; | 1099 return 10.0; |
2552 } | 1100 } |
2553 | 1101 |
2554 | 1102 |
2555 public: | 1103 public: |
2556 RadiologyEditorInteractor(MessageBroker& broker) : | 1104 RadiographyEditorInteractor(MessageBroker& broker) : |
2557 IObserver(broker), | 1105 IObserver(broker), |
2558 tool_(Tool_Move) | 1106 tool_(Tool_Move) |
2559 { | 1107 { |
2560 } | 1108 } |
2561 | 1109 |
2567 int viewportY, | 1115 int viewportY, |
2568 double x, | 1116 double x, |
2569 double y, | 1117 double y, |
2570 IStatusBar* statusBar) | 1118 IStatusBar* statusBar) |
2571 { | 1119 { |
2572 RadiologyWidget& widget = dynamic_cast<RadiologyWidget&>(worldWidget); | 1120 RadiographyWidget& widget = dynamic_cast<RadiographyWidget&>(worldWidget); |
2573 | 1121 |
2574 if (button == MouseButton_Left) | 1122 if (button == MouseButton_Left) |
2575 { | 1123 { |
2576 size_t selected; | 1124 size_t selected; |
2577 | 1125 |
2578 if (tool_ == Tool_Windowing) | 1126 if (tool_ == Tool_Windowing) |
2579 { | 1127 { |
2580 return new RadiologyWindowingTracker(undoRedoStack_, widget.GetScene(), | 1128 return new RadiographyWindowingTracker( |
2581 viewportX, viewportY, | 1129 undoRedoStack_, widget.GetScene(), |
2582 RadiologyWindowingTracker::Action_DecreaseWidth, | 1130 viewportX, viewportY, |
2583 RadiologyWindowingTracker::Action_IncreaseWidth, | 1131 RadiographyWindowingTracker::Action_DecreaseWidth, |
2584 RadiologyWindowingTracker::Action_DecreaseCenter, | 1132 RadiographyWindowingTracker::Action_IncreaseWidth, |
2585 RadiologyWindowingTracker::Action_IncreaseCenter); | 1133 RadiographyWindowingTracker::Action_DecreaseCenter, |
1134 RadiographyWindowingTracker::Action_IncreaseCenter); | |
2586 } | 1135 } |
2587 else if (!widget.LookupSelectedLayer(selected)) | 1136 else if (!widget.LookupSelectedLayer(selected)) |
2588 { | 1137 { |
2589 // No layer is currently selected | 1138 // No layer is currently selected |
2590 size_t layer; | 1139 size_t layer; |
2596 return NULL; | 1145 return NULL; |
2597 } | 1146 } |
2598 else if (tool_ == Tool_Crop || | 1147 else if (tool_ == Tool_Crop || |
2599 tool_ == Tool_Resize) | 1148 tool_ == Tool_Resize) |
2600 { | 1149 { |
2601 RadiologyScene::LayerAccessor accessor(widget.GetScene(), selected); | 1150 RadiographyScene::LayerAccessor accessor(widget.GetScene(), selected); |
2602 RadiologyScene::Corner corner; | 1151 RadiographyScene::Corner corner; |
2603 if (accessor.GetLayer().LookupCorner(corner, x, y, view.GetZoom(), GetHandleSize())) | 1152 if (accessor.GetLayer().LookupCorner(corner, x, y, view.GetZoom(), GetHandleSize())) |
2604 { | 1153 { |
2605 switch (tool_) | 1154 switch (tool_) |
2606 { | 1155 { |
2607 case Tool_Crop: | 1156 case Tool_Crop: |
2608 return new RadiologyLayerCropTracker(undoRedoStack_, widget.GetScene(), view, selected, x, y, corner); | 1157 return new RadiographyLayerCropTracker |
1158 (undoRedoStack_, widget.GetScene(), view, selected, x, y, corner); | |
2609 | 1159 |
2610 case Tool_Resize: | 1160 case Tool_Resize: |
2611 return new RadiologyLayerResizeTracker(undoRedoStack_, widget.GetScene(), selected, x, y, corner, | 1161 return new RadiographyLayerResizeTracker |
2612 (modifiers & KeyboardModifiers_Shift)); | 1162 (undoRedoStack_, widget.GetScene(), selected, x, y, corner, |
1163 (modifiers & KeyboardModifiers_Shift)); | |
2613 | 1164 |
2614 default: | 1165 default: |
2615 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); | 1166 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); |
2616 } | 1167 } |
2617 } | 1168 } |
2640 if (layer == selected) | 1191 if (layer == selected) |
2641 { | 1192 { |
2642 switch (tool_) | 1193 switch (tool_) |
2643 { | 1194 { |
2644 case Tool_Move: | 1195 case Tool_Move: |
2645 return new RadiologyLayerMoveTracker(undoRedoStack_, widget.GetScene(), layer, x, y, | 1196 return new RadiographyLayerMoveTracker |
2646 (modifiers & KeyboardModifiers_Shift)); | 1197 (undoRedoStack_, widget.GetScene(), layer, x, y, |
1198 (modifiers & KeyboardModifiers_Shift)); | |
2647 | 1199 |
2648 case Tool_Rotate: | 1200 case Tool_Rotate: |
2649 return new RadiologyLayerRotateTracker(undoRedoStack_, widget.GetScene(), view, layer, x, y, | 1201 return new RadiographyLayerRotateTracker |
2650 (modifiers & KeyboardModifiers_Shift)); | 1202 (undoRedoStack_, widget.GetScene(), view, layer, x, y, |
1203 (modifiers & KeyboardModifiers_Shift)); | |
2651 | 1204 |
2652 default: | 1205 default: |
2653 break; | 1206 break; |
2654 } | 1207 } |
2655 | 1208 |
2679 const ViewportGeometry& view, | 1232 const ViewportGeometry& view, |
2680 double x, | 1233 double x, |
2681 double y, | 1234 double y, |
2682 IStatusBar* statusBar) | 1235 IStatusBar* statusBar) |
2683 { | 1236 { |
2684 RadiologyWidget& widget = dynamic_cast<RadiologyWidget&>(worldWidget); | 1237 RadiographyWidget& widget = dynamic_cast<RadiographyWidget&>(worldWidget); |
2685 | 1238 |
2686 #if 0 | 1239 #if 0 |
2687 if (statusBar != NULL) | 1240 if (statusBar != NULL) |
2688 { | 1241 { |
2689 char buf[64]; | 1242 char buf[64]; |
2696 | 1249 |
2697 if (widget.LookupSelectedLayer(selected) && | 1250 if (widget.LookupSelectedLayer(selected) && |
2698 (tool_ == Tool_Crop || | 1251 (tool_ == Tool_Crop || |
2699 tool_ == Tool_Resize)) | 1252 tool_ == Tool_Resize)) |
2700 { | 1253 { |
2701 RadiologyScene::LayerAccessor accessor(widget.GetScene(), selected); | 1254 RadiographyScene::LayerAccessor accessor(widget.GetScene(), selected); |
2702 | 1255 |
2703 RadiologyScene::Corner corner; | 1256 RadiographyScene::Corner corner; |
2704 if (accessor.GetLayer().LookupCorner(corner, x, y, view.GetZoom(), GetHandleSize())) | 1257 if (accessor.GetLayer().LookupCorner(corner, x, y, view.GetZoom(), GetHandleSize())) |
2705 { | 1258 { |
2706 accessor.GetLayer().GetCorner(x, y, corner); | 1259 accessor.GetLayer().GetCorner(x, y, corner); |
2707 | 1260 |
2708 double z = 1.0 / view.GetZoom(); | 1261 double z = 1.0 / view.GetZoom(); |
2731 KeyboardKeys key, | 1284 KeyboardKeys key, |
2732 char keyChar, | 1285 char keyChar, |
2733 KeyboardModifiers modifiers, | 1286 KeyboardModifiers modifiers, |
2734 IStatusBar* statusBar) | 1287 IStatusBar* statusBar) |
2735 { | 1288 { |
2736 RadiologyWidget& widget = dynamic_cast<RadiologyWidget&>(worldWidget); | 1289 RadiographyWidget& widget = dynamic_cast<RadiographyWidget&>(worldWidget); |
2737 | 1290 |
2738 switch (keyChar) | 1291 switch (keyChar) |
2739 { | 1292 { |
2740 case 'a': | 1293 case 'a': |
2741 widget.FitContent(); | 1294 widget.FitContent(); |
2765 tags.SetValue(Orthanc::DICOM_TAG_SERIES_NUMBER, "1", false); | 1318 tags.SetValue(Orthanc::DICOM_TAG_SERIES_NUMBER, "1", false); |
2766 tags.SetValue(Orthanc::DICOM_TAG_SOP_CLASS_UID, "1.2.840.10008.5.1.4.1.1.1", false); | 1319 tags.SetValue(Orthanc::DICOM_TAG_SOP_CLASS_UID, "1.2.840.10008.5.1.4.1.1.1", false); |
2767 tags.SetValue(Orthanc::DICOM_TAG_STUDY_ID, "STUDY", false); | 1320 tags.SetValue(Orthanc::DICOM_TAG_STUDY_ID, "STUDY", false); |
2768 tags.SetValue(Orthanc::DICOM_TAG_VIEW_POSITION, "", false); | 1321 tags.SetValue(Orthanc::DICOM_TAG_VIEW_POSITION, "", false); |
2769 | 1322 |
2770 widget.GetScene().Export(tags, 0.1, 0.1, widget.IsInverted(), | 1323 widget.GetScene().ExportDicom(tags, 0.1, 0.1, widget.IsInverted(), |
2771 widget.GetInterpolation(), EXPORT_USING_PAM); | 1324 widget.GetInterpolation(), EXPORT_USING_PAM); |
2772 break; | 1325 break; |
2773 } | 1326 } |
2774 | 1327 |
2775 case 'i': | 1328 case 'i': |
2776 widget.SwitchInvert(); | 1329 widget.SwitchInvert(); |
2841 public SampleSingleCanvasApplicationBase, | 1394 public SampleSingleCanvasApplicationBase, |
2842 public IObserver | 1395 public IObserver |
2843 { | 1396 { |
2844 private: | 1397 private: |
2845 std::auto_ptr<OrthancApiClient> orthancApiClient_; | 1398 std::auto_ptr<OrthancApiClient> orthancApiClient_; |
2846 std::auto_ptr<RadiologyScene> scene_; | 1399 std::auto_ptr<RadiographyScene> scene_; |
2847 RadiologyEditorInteractor interactor_; | 1400 RadiographyEditorInteractor interactor_; |
2848 | 1401 |
2849 public: | 1402 public: |
2850 SingleFrameEditorApplication(MessageBroker& broker) : | 1403 SingleFrameEditorApplication(MessageBroker& broker) : |
2851 IObserver(broker), | 1404 IObserver(broker), |
2852 interactor_(broker) | 1405 interactor_(broker) |
2905 orthancApiClient_.reset(new OrthancApiClient(GetBroker(), context_->GetWebService())); | 1458 orthancApiClient_.reset(new OrthancApiClient(GetBroker(), context_->GetWebService())); |
2906 | 1459 |
2907 Orthanc::FontRegistry fonts; | 1460 Orthanc::FontRegistry fonts; |
2908 fonts.AddFromResource(Orthanc::EmbeddedResources::FONT_UBUNTU_MONO_BOLD_16); | 1461 fonts.AddFromResource(Orthanc::EmbeddedResources::FONT_UBUNTU_MONO_BOLD_16); |
2909 | 1462 |
2910 scene_.reset(new RadiologyScene(GetBroker(), *orthancApiClient_)); | 1463 scene_.reset(new RadiographyScene(GetBroker(), *orthancApiClient_)); |
2911 scene_->LoadDicomFrame(instance, frame, false); //.SetPan(200, 0); | 1464 scene_->LoadDicomFrame(instance, frame, false); //.SetPan(200, 0); |
2912 //scene_->LoadDicomFrame("61f3143e-96f34791-ad6bbb8d-62559e75-45943e1b", 0, false); | 1465 //scene_->LoadDicomFrame("61f3143e-96f34791-ad6bbb8d-62559e75-45943e1b", 0, false); |
2913 | 1466 |
2914 { | 1467 { |
2915 RadiologyScene::Layer& layer = scene_->LoadText(fonts.GetFont(0), "Hello\nworld"); | 1468 RadiographyScene::Layer& layer = scene_->LoadText(fonts.GetFont(0), "Hello\nworld"); |
2916 //dynamic_cast<RadiologyScene::Layer&>(layer).SetForegroundValue(256); | 1469 //dynamic_cast<RadiographyScene::Layer&>(layer).SetForegroundValue(256); |
2917 dynamic_cast<RadiologyScene::Layer&>(layer).SetResizeable(true); | 1470 dynamic_cast<RadiographyScene::Layer&>(layer).SetResizeable(true); |
2918 } | 1471 } |
2919 | 1472 |
2920 { | 1473 { |
2921 RadiologyScene::Layer& layer = scene_->LoadTestBlock(100, 50); | 1474 RadiographyScene::Layer& layer = scene_->LoadTestBlock(100, 50); |
2922 //dynamic_cast<RadiologyScene::Layer&>(layer).SetForegroundValue(256); | 1475 //dynamic_cast<RadiographyScene::Layer&>(layer).SetForegroundValue(256); |
2923 dynamic_cast<RadiologyScene::Layer&>(layer).SetResizeable(true); | 1476 dynamic_cast<RadiographyScene::Layer&>(layer).SetResizeable(true); |
2924 dynamic_cast<RadiologyScene::Layer&>(layer).SetPan(0, 200); | 1477 dynamic_cast<RadiographyScene::Layer&>(layer).SetPan(0, 200); |
2925 } | 1478 } |
2926 | 1479 |
2927 | 1480 |
2928 mainWidget_ = new RadiologyWidget(GetBroker(), *scene_, "main-widget"); | 1481 mainWidget_ = new RadiographyWidget(GetBroker(), *scene_, "main-widget"); |
2929 mainWidget_->SetTransmitMouseOver(true); | 1482 mainWidget_->SetTransmitMouseOver(true); |
2930 mainWidget_->SetInteractor(interactor_); | 1483 mainWidget_->SetInteractor(interactor_); |
2931 | 1484 |
2932 //scene_->SetWindowing(128, 256); | 1485 //scene_->SetWindowing(128, 256); |
2933 } | 1486 } |