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