comparison Applications/Samples/SingleFrameEditorApplication.h @ 362:12cec26d08ce am-2

export of a valid DICOM CR
author Sebastien Jodogne <s.jodogne@gmail.com>
date Tue, 30 Oct 2018 18:30:48 +0100
parents f559ac66ef55
children 54ae0577f5bb
comparison
equal deleted inserted replaced
361:f559ac66ef55 362:12cec26d08ce
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>
34 #include <Core/Images/PamWriter.h> 34 #include <Core/Images/PamWriter.h>
35 #include <Core/Images/PngWriter.h>
35 #include <Core/Logging.h> 36 #include <Core/Logging.h>
36 #include <Core/Toolbox.h> 37 #include <Core/Toolbox.h>
37 #include <Core/SystemToolbox.h> 38 #include <Core/SystemToolbox.h>
38 #include <Plugins/Samples/Common/DicomDatasetReader.h> 39 #include <Plugins/Samples/Common/DicomDatasetReader.h>
39 #include <Plugins/Samples/Common/FullOrthancDataset.h> 40 #include <Plugins/Samples/Common/FullOrthancDataset.h>
40 41
42 #define EXPORT_USING_PAM 1
43
41 44
42 #include <boost/math/constants/constants.hpp> 45 #include <boost/math/constants/constants.hpp>
43 46
44 namespace OrthancStone 47 namespace OrthancStone
45 { 48 {
49 static Matrix CreateOffsetMatrix(double dx,
50 double dy)
51 {
52 Matrix m = LinearAlgebra::IdentityMatrix(3);
53 m(0, 2) = dx;
54 m(1, 2) = dy;
55 return m;
56 }
57
58
59 static Matrix CreateScalingMatrix(double sx,
60 double sy)
61 {
62 Matrix m = LinearAlgebra::IdentityMatrix(3);
63 m(0, 0) = sx;
64 m(1, 1) = sy;
65 return m;
66 }
67
68
69 static Matrix CreateRotationMatrix(double angle)
70 {
71 Matrix m;
72 const double v[] = { cos(angle), -sin(angle), 0,
73 sin(angle), cos(angle), 0,
74 0, 0, 1 };
75 LinearAlgebra::FillMatrix(m, 3, 3, v);
76 return m;
77 }
78
79
46 class BitmapStack : 80 class BitmapStack :
47 public IObserver, 81 public IObserver,
48 public IObservable 82 public IObservable
49 { 83 {
50 public: 84 public:
83 double angle_; 117 double angle_;
84 bool resizeable_; 118 bool resizeable_;
85 119
86 120
87 protected: 121 protected:
88 static Matrix CreateOffsetMatrix(double dx,
89 double dy)
90 {
91 Matrix m = LinearAlgebra::IdentityMatrix(3);
92 m(0, 2) = dx;
93 m(1, 2) = dy;
94 return m;
95 }
96
97
98 static Matrix CreateScalingMatrix(double sx,
99 double sy)
100 {
101 Matrix m = LinearAlgebra::IdentityMatrix(3);
102 m(0, 0) = sx;
103 m(1, 1) = sy;
104 return m;
105 }
106
107
108 static Matrix CreateRotationMatrix(double angle)
109 {
110 Matrix m;
111 const double v[] = { cos(angle), -sin(angle), 0,
112 sin(angle), cos(angle), 0,
113 0, 0, 1 };
114 LinearAlgebra::FillMatrix(m, 3, 3, v);
115 return m;
116 }
117
118
119 const Matrix& GetTransform() const 122 const Matrix& GetTransform() const
120 { 123 {
121 return transform_; 124 return transform_;
122 } 125 }
123 126
581 { 584 {
582 return false; 585 return false;
583 } 586 }
584 587
585 virtual void Render(Orthanc::ImageAccessor& buffer, 588 virtual void Render(Orthanc::ImageAccessor& buffer,
586 const ViewportGeometry& view, 589 const Matrix& viewTransform,
587 ImageInterpolation interpolation) const = 0; 590 ImageInterpolation interpolation) const = 0;
588 591
589 virtual bool GetRange(float& minValue, 592 virtual bool GetRange(float& minValue,
590 float& maxValue) const = 0; 593 float& maxValue) const = 0;
591 }; 594 };
741 SetAlpha(font.RenderAlpha(utf8)); 744 SetAlpha(font.RenderAlpha(utf8));
742 } 745 }
743 746
744 747
745 virtual void Render(Orthanc::ImageAccessor& buffer, 748 virtual void Render(Orthanc::ImageAccessor& buffer,
746 const ViewportGeometry& view, 749 const Matrix& viewTransform,
747 ImageInterpolation interpolation) const 750 ImageInterpolation interpolation) const
748 { 751 {
749 if (buffer.GetFormat() != Orthanc::PixelFormat_Float32) 752 if (buffer.GetFormat() != Orthanc::PixelFormat_Float32)
750 { 753 {
751 throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat); 754 throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat);
752 } 755 }
753 756
754 unsigned int cropX, cropY, cropWidth, cropHeight; 757 unsigned int cropX, cropY, cropWidth, cropHeight;
755 GetCrop(cropX, cropY, cropWidth, cropHeight); 758 GetCrop(cropX, cropY, cropWidth, cropHeight);
756 759
757 Matrix m = LinearAlgebra::Product(view.GetMatrix(), 760 Matrix m = LinearAlgebra::Product(viewTransform,
758 GetTransform(), 761 GetTransform(),
759 CreateOffsetMatrix(cropX, cropY)); 762 CreateOffsetMatrix(cropX, cropY));
760 763
761 Orthanc::ImageAccessor cropped; 764 Orthanc::ImageAccessor cropped;
762 alpha_->GetRegion(cropped, cropX, cropY, cropWidth, cropHeight); 765 alpha_->GetRegion(cropped, cropX, cropY, cropWidth, cropHeight);
899 ApplyConverter(); 902 ApplyConverter();
900 } 903 }
901 904
902 905
903 virtual void Render(Orthanc::ImageAccessor& buffer, 906 virtual void Render(Orthanc::ImageAccessor& buffer,
904 const ViewportGeometry& view, 907 const Matrix& viewTransform,
905 ImageInterpolation interpolation) const 908 ImageInterpolation interpolation) const
906 { 909 {
907 if (converted_.get() != NULL) 910 if (converted_.get() != NULL)
908 { 911 {
909 if (converted_->GetFormat() != Orthanc::PixelFormat_Float32) 912 if (converted_->GetFormat() != Orthanc::PixelFormat_Float32)
912 } 915 }
913 916
914 unsigned int cropX, cropY, cropWidth, cropHeight; 917 unsigned int cropX, cropY, cropWidth, cropHeight;
915 GetCrop(cropX, cropY, cropWidth, cropHeight); 918 GetCrop(cropX, cropY, cropWidth, cropHeight);
916 919
917 Matrix m = LinearAlgebra::Product(view.GetMatrix(), 920 Matrix m = LinearAlgebra::Product(viewTransform,
918 GetTransform(), 921 GetTransform(),
919 CreateOffsetMatrix(cropX, cropY)); 922 CreateOffsetMatrix(cropX, cropY));
920 923
921 Orthanc::ImageAccessor cropped; 924 Orthanc::ImageAccessor cropped;
922 converted_->GetRegion(cropped, cropX, cropY, cropWidth, cropHeight); 925 converted_->GetRegion(cropped, cropX, cropY, cropWidth, cropHeight);
1228 return extent; 1231 return extent;
1229 } 1232 }
1230 1233
1231 1234
1232 void Render(Orthanc::ImageAccessor& buffer, 1235 void Render(Orthanc::ImageAccessor& buffer,
1233 const ViewportGeometry& view, 1236 const Matrix& viewTransform,
1234 ImageInterpolation interpolation) const 1237 ImageInterpolation interpolation) const
1235 { 1238 {
1236 Orthanc::ImageProcessing::Set(buffer, 0); 1239 Orthanc::ImageProcessing::Set(buffer, 0);
1237 1240
1238 // Render layers in the background-to-foreground order 1241 // Render layers in the background-to-foreground order
1240 { 1243 {
1241 Bitmaps::const_iterator it = bitmaps_.find(index); 1244 Bitmaps::const_iterator it = bitmaps_.find(index);
1242 if (it != bitmaps_.end()) 1245 if (it != bitmaps_.end())
1243 { 1246 {
1244 assert(it->second != NULL); 1247 assert(it->second != NULL);
1245 it->second->Render(buffer, view, interpolation); 1248 it->second->Render(buffer, viewTransform, interpolation);
1246 } 1249 }
1247 } 1250 }
1248 } 1251 }
1249 1252
1250 1253
2239 cairoBuffer_->GetHeight() != height) 2242 cairoBuffer_->GetHeight() != height)
2240 { 2243 {
2241 cairoBuffer_.reset(new CairoSurface(width, height)); 2244 cairoBuffer_.reset(new CairoSurface(width, height));
2242 } 2245 }
2243 2246
2244 stack_.Render(*floatBuffer_, GetView(), interpolation); 2247 stack_.Render(*floatBuffer_, GetView().GetMatrix(), interpolation);
2245 2248
2246 // Conversion from Float32 to BGRA32 (cairo). Very similar to 2249 // Conversion from Float32 to BGRA32 (cairo). Very similar to
2247 // GrayscaleFrameRenderer => TODO MERGE? 2250 // GrayscaleFrameRenderer => TODO MERGE?
2248 2251
2249 Orthanc::ImageAccessor target; 2252 Orthanc::ImageAccessor target;
2388 return interpolation_; 2391 return interpolation_;
2389 } 2392 }
2390 }; 2393 };
2391 2394
2392 2395
2393 class BitmapStackInteractor : public IWorldSceneInteractor 2396 class BitmapStackInteractor :
2397 public IWorldSceneInteractor,
2398 public IObserver
2394 { 2399 {
2395 private: 2400 private:
2396 enum Tool 2401 enum Tool
2397 { 2402 {
2398 Tool_Move, 2403 Tool_Move,
2425 return GetWidget(widget).GetStack(); 2430 return GetWidget(widget).GetStack();
2426 } 2431 }
2427 2432
2428 2433
2429 public: 2434 public:
2430 BitmapStackInteractor() : 2435 BitmapStackInteractor(MessageBroker& broker) :
2436 IObserver(broker),
2431 tool_(Tool_Move), 2437 tool_(Tool_Move),
2432 orthanc_(NULL) 2438 orthanc_(NULL)
2433 { 2439 {
2434 } 2440 }
2435 2441
2604 break; 2610 break;
2605 2611
2606 case 'e': 2612 case 'e':
2607 { 2613 {
2608 Orthanc::DicomMap tags; 2614 Orthanc::DicomMap tags;
2615
2616 // Minimal set of tags to generate a valid CR image
2617 tags.SetValue(Orthanc::DICOM_TAG_ACCESSION_NUMBER, "NOPE", false);
2618 tags.SetValue(Orthanc::DICOM_TAG_BODY_PART_EXAMINED, "PELVIS", false);
2619 tags.SetValue(Orthanc::DICOM_TAG_INSTANCE_NUMBER, "1", false);
2620 //tags.SetValue(Orthanc::DICOM_TAG_LATERALITY, "", false);
2621 tags.SetValue(Orthanc::DICOM_TAG_MANUFACTURER, "OSIMIS", false);
2622 tags.SetValue(Orthanc::DICOM_TAG_MODALITY, "CR", false);
2623 tags.SetValue(Orthanc::DICOM_TAG_PATIENT_BIRTH_DATE, "20000101", false);
2609 tags.SetValue(Orthanc::DICOM_TAG_PATIENT_ID, "hello", false); 2624 tags.SetValue(Orthanc::DICOM_TAG_PATIENT_ID, "hello", false);
2610 tags.SetValue(Orthanc::DICOM_TAG_PATIENT_NAME, "HELLO^WORLD", false); 2625 tags.SetValue(Orthanc::DICOM_TAG_PATIENT_NAME, "HELLO^WORLD", false);
2611 Export(GetStack(widget), 0.1, 0.1, GetWidget(widget).GetInterpolation(), tags); 2626 tags.SetValue(Orthanc::DICOM_TAG_PATIENT_ORIENTATION, "", false);
2627 tags.SetValue(Orthanc::DICOM_TAG_PATIENT_SEX, "M", false);
2628 tags.SetValue(Orthanc::DICOM_TAG_REFERRING_PHYSICIAN_NAME, "HOUSE^MD", false);
2629 tags.SetValue(Orthanc::DICOM_TAG_SERIES_NUMBER, "1", false);
2630 tags.SetValue(Orthanc::DICOM_TAG_SOP_CLASS_UID, "1.2.840.10008.5.1.4.1.1.1", false);
2631 tags.SetValue(Orthanc::DICOM_TAG_STUDY_ID, "STUDY", false);
2632 tags.SetValue(Orthanc::DICOM_TAG_VIEW_POSITION, "", false);
2633
2634 Export(GetWidget(widget), 0.1, 0.1, tags);
2612 break; 2635 break;
2613 } 2636 }
2614 2637
2615 case 'i': 2638 case 'i':
2616 GetWidget(widget).SwitchInvert(); 2639 GetWidget(widget).SwitchInvert();
2679 { 2702 {
2680 orthanc_ = &orthanc; 2703 orthanc_ = &orthanc;
2681 } 2704 }
2682 2705
2683 2706
2684 void Export(const BitmapStack& stack, 2707 void Export(const BitmapStackWidget& widget,
2685 double pixelSpacingX, 2708 double pixelSpacingX,
2686 double pixelSpacingY, 2709 double pixelSpacingY,
2687 ImageInterpolation interpolation,
2688 const Orthanc::DicomMap& dicom) 2710 const Orthanc::DicomMap& dicom)
2689 { 2711 {
2690 if (pixelSpacingX <= 0 || 2712 if (pixelSpacingX <= 0 ||
2691 pixelSpacingY <= 0) 2713 pixelSpacingY <= 0)
2692 { 2714 {
2698 return; 2720 return;
2699 } 2721 }
2700 2722
2701 LOG(WARNING) << "Exporting DICOM"; 2723 LOG(WARNING) << "Exporting DICOM";
2702 2724
2703 Extent2D extent = stack.GetSceneExtent(); 2725 Extent2D extent = widget.GetStack().GetSceneExtent();
2704 2726
2705 int w = std::ceil(extent.GetWidth() / pixelSpacingX); 2727 int w = std::ceil(extent.GetWidth() / pixelSpacingX);
2706 int h = std::ceil(extent.GetHeight() / pixelSpacingY); 2728 int h = std::ceil(extent.GetHeight() / pixelSpacingY);
2707 2729
2708 if (w < 0 || h < 0) 2730 if (w < 0 || h < 0)
2712 2734
2713 Orthanc::Image layers(Orthanc::PixelFormat_Float32, 2735 Orthanc::Image layers(Orthanc::PixelFormat_Float32,
2714 static_cast<unsigned int>(w), 2736 static_cast<unsigned int>(w),
2715 static_cast<unsigned int>(h), false); 2737 static_cast<unsigned int>(h), false);
2716 2738
2717 ViewportGeometry view; 2739 Matrix view = LinearAlgebra::Product(
2718 view.SetDisplaySize(layers.GetWidth(), layers.GetHeight()); 2740 CreateScalingMatrix(1.0 / pixelSpacingX, 1.0 / pixelSpacingY),
2719 view.SetSceneExtent(extent); 2741 CreateOffsetMatrix(-extent.GetX1(), -extent.GetY1()));
2720 view.FitContent(); 2742
2721 2743 widget.GetStack().Render(layers, view, widget.GetInterpolation());
2722 stack.Render(layers, view, interpolation);
2723 2744
2724 Orthanc::Image rendered(Orthanc::PixelFormat_Grayscale16, 2745 Orthanc::Image rendered(Orthanc::PixelFormat_Grayscale16,
2725 layers.GetWidth(), layers.GetHeight(), false); 2746 layers.GetWidth(), layers.GetHeight(), false);
2726 Orthanc::ImageProcessing::Convert(rendered, layers); 2747 Orthanc::ImageProcessing::Convert(rendered, layers);
2727 2748
2728 std::string pam; 2749 std::string base64;
2729 { 2750
2730 Orthanc::PamWriter writer; 2751 {
2731 writer.WriteToMemory(pam, rendered); 2752 std::string content;
2732 } 2753
2733 2754 #if EXPORT_USING_PAM == 1
2734 std::string content; 2755 {
2735 Orthanc::Toolbox::EncodeBase64(content, pam); 2756 Orthanc::PamWriter writer;
2757 writer.WriteToMemory(content, rendered);
2758 }
2759 #else
2760 {
2761 Orthanc::PngWriter writer;
2762 writer.WriteToMemory(content, rendered);
2763 }
2764 #endif
2765
2766 Orthanc::Toolbox::EncodeBase64(base64, content);
2767 }
2736 2768
2737 std::set<Orthanc::DicomTag> tags; 2769 std::set<Orthanc::DicomTag> tags;
2738 dicom.GetTags(tags); 2770 dicom.GetTags(tags);
2739 2771
2740 Json::Value json = Json::objectValue; 2772 Json::Value json = Json::objectValue;
2748 !value.IsBinary()) 2780 !value.IsBinary())
2749 { 2781 {
2750 json["Tags"][tag->Format()] = value.GetContent(); 2782 json["Tags"][tag->Format()] = value.GetContent();
2751 } 2783 }
2752 } 2784 }
2753 2785
2754 json["Content"] = "data:" + std::string(Orthanc::MIME_PAM) + ";base64," + content; 2786 json["Tags"][Orthanc::DICOM_TAG_PHOTOMETRIC_INTERPRETATION.Format()] =
2755 2787 (widget.IsInvert() ? "MONOCHROME1" : "MONOCHROME2");
2756 orthanc_->PostJsonAsyncExpectJson("/tools/create-dicom", json, NULL, NULL, NULL); 2788
2789
2790 char buf[32];
2791 sprintf(buf, "%0.08f\\%0.08f", pixelSpacingX, pixelSpacingY);
2792
2793 json["Tags"][Orthanc::DICOM_TAG_PIXEL_SPACING.Format()] = buf;
2794
2795 float center, width;
2796 if (widget.GetStack().GetWindowing(center, width))
2797 {
2798 json["Tags"][Orthanc::DICOM_TAG_WINDOW_CENTER.Format()] =
2799 boost::lexical_cast<std::string>(lroundf(center));
2800
2801 json["Tags"][Orthanc::DICOM_TAG_WINDOW_WIDTH.Format()] =
2802 boost::lexical_cast<std::string>(lroundf(width));
2803 }
2804
2805 #if EXPORT_USING_PAM == 1
2806 json["Content"] = "data:" + std::string(Orthanc::MIME_PAM) + ";base64," + base64;
2807 #else
2808 json["Content"] = "data:" + std::string(Orthanc::MIME_PNG) + ";base64," + base64;
2809 #endif
2810
2811 orthanc_->PostJsonAsyncExpectJson(
2812 "/tools/create-dicom", json,
2813 new Callable<BitmapStackInteractor, OrthancApiClient::JsonResponseReadyMessage>
2814 (*this, &BitmapStackInteractor::OnDicomExported),
2815 NULL, NULL);
2816 }
2817
2818
2819 void OnDicomExported(const OrthancApiClient::JsonResponseReadyMessage& message)
2820 {
2821 LOG(WARNING) << "DICOM export was successful:"
2822 << message.Response.toStyledString();
2757 } 2823 }
2758 }; 2824 };
2759 2825
2760 2826
2761 2827
2770 std::auto_ptr<BitmapStack> stack_; 2836 std::auto_ptr<BitmapStack> stack_;
2771 BitmapStackInteractor interactor_; 2837 BitmapStackInteractor interactor_;
2772 2838
2773 public: 2839 public:
2774 SingleFrameEditorApplication(MessageBroker& broker) : 2840 SingleFrameEditorApplication(MessageBroker& broker) :
2775 IObserver(broker) 2841 IObserver(broker),
2842 interactor_(broker)
2776 { 2843 {
2777 } 2844 }
2778 2845
2779 virtual void DeclareStartupOptions(boost::program_options::options_description& options) 2846 virtual void DeclareStartupOptions(boost::program_options::options_description& options)
2780 { 2847 {