Codebase list orthanc-dicomweb / 6455066
Update upstream source from tag 'upstream/1.7+dfsg' Update to upstream version '1.7+dfsg' with Debian dir 571c00bf4394e9a740bc05a9a77a09f2d6e93814 jodogne-guest 2 years ago
5 changed file(s) with 253 addition(s) and 149 deletion(s). Raw diff Collapse all Expand all
00 repo: d5f45924411123cfd02d035fd50b8e37536eadef
1 node: 01bae1ba951d3c66e55aba6129bcba0ef825d081
2 branch: OrthancDicomWeb-1.6
1 node: f5b64f680bfbea760169e72602498b946c58c234
2 branch: OrthancDicomWeb-1.7
33 latesttag: null
4 latesttagdistance: 462
5 changessincelatesttag: 491
4 latesttagdistance: 469
5 changessincelatesttag: 498
2020
2121 project(OrthancDicomWeb)
2222
23 set(ORTHANC_DICOM_WEB_VERSION "1.6")
23 set(ORTHANC_DICOM_WEB_VERSION "1.7")
2424
2525 if (ORTHANC_DICOM_WEB_VERSION STREQUAL "mainline")
2626 set(ORTHANC_FRAMEWORK_DEFAULT_VERSION "mainline")
2727 set(ORTHANC_FRAMEWORK_DEFAULT_SOURCE "hg")
2828 else()
29 set(ORTHANC_FRAMEWORK_DEFAULT_VERSION "1.9.3")
29 set(ORTHANC_FRAMEWORK_DEFAULT_VERSION "1.9.7")
3030 set(ORTHANC_FRAMEWORK_DEFAULT_SOURCE "web")
3131 endif()
3232
5656 # Download and setup the Orthanc framework
5757 include(${CMAKE_SOURCE_DIR}/Resources/Orthanc/CMake/DownloadOrthancFramework.cmake)
5858
59 include_directories(${ORTHANC_FRAMEWORK_ROOT})
60
6159 if (ORTHANC_FRAMEWORK_SOURCE STREQUAL "system")
60 if (ORTHANC_FRAMEWORK_USE_SHARED)
61 include(FindBoost)
62 find_package(Boost COMPONENTS regex thread)
63
64 if (NOT Boost_FOUND)
65 message(FATAL_ERROR "Unable to locate Boost on this system")
66 endif()
67
68 link_libraries(${Boost_LIBRARIES} jsoncpp)
69 endif()
70
6271 link_libraries(${ORTHANC_FRAMEWORK_LIBRARIES})
63
72
6473 set(USE_SYSTEM_GOOGLE_TEST ON CACHE BOOL "Use the system version of Google Test")
6574 set(USE_GOOGLE_TEST_DEBIAN_PACKAGE OFF CACHE BOOL "Use the sources of Google Test shipped with libgtest-dev (Debian only)")
6675 mark_as_advanced(USE_GOOGLE_TEST_DEBIAN_PACKAGE)
7685 set(USE_BOOST_ICONV ON)
7786
7887 include(${ORTHANC_FRAMEWORK_ROOT}/../Resources/CMake/OrthancFrameworkConfiguration.cmake)
88 include_directories(${ORTHANC_FRAMEWORK_ROOT})
7989 endif()
8090
8191
00 Pending changes in the mainline
11 ===============================
2
3
4 Version 1.7 (2021-08-31)
5 ========================
6
7 * Detection of windowing and rescale in ".../rendered" for Philips multiframe images
28
39
410 Version 1.6 (2021-05-07)
2727 #include <Logging.h>
2828 #include <Toolbox.h>
2929
30 #if ORTHANC_FRAMEWORK_VERSION_IS_ABOVE(1, 9, 7)
31 # include <SerializationToolbox.h>
32 #else
33 # include <boost/lexical_cast.hpp>
34 #endif
35
3036 #include <boost/algorithm/string/predicate.hpp>
3137 #include <boost/math/special_functions/round.hpp>
38
39
40 static bool ParseFloat(float& target,
41 const std::string& source)
42 {
43 #if ORTHANC_FRAMEWORK_VERSION_IS_ABOVE(1, 9, 7)
44 return Orthanc::SerializationToolbox::ParseFloat(target, source);
45
46 #else
47 // Emulation for older versions of the Orthanc framework
48 std::string s = Orthanc::Toolbox::StripSpaces(source);
49
50 if (s.empty())
51 {
52 return false;
53 }
54 else
55 {
56 try
57 {
58 target = boost::lexical_cast<float>(s);
59 return true;
60 }
61 catch (boost::bad_lexical_cast&)
62 {
63 return false;
64 }
65 }
66 #endif
67 }
68
69
70 static bool ParseFirstFloat(float& target,
71 const std::string& source)
72 {
73 #if ORTHANC_FRAMEWORK_VERSION_IS_ABOVE(1, 9, 7)
74 return Orthanc::SerializationToolbox::ParseFirstFloat(target, source);
75
76 #else
77 // Emulation for older versions of the Orthanc framework
78 std::vector<std::string> tokens;
79 Orthanc::Toolbox::TokenizeString(tokens, source, '\\');
80 if (tokens.empty())
81 {
82 return false;
83 }
84 else
85 {
86 return ParseFloat(target, tokens[0]);
87 }
88 #endif
89 }
3290
3391
3492 namespace
270328 }
271329
272330
273 bool HasCustomization() const
274 {
275 return (hasViewport_ || hasQuality_ || hasWindowing_);
276 }
277
278331 unsigned int GetTargetWidth(unsigned int sourceWidth) const
279332 {
280333 if (hasVW_)
346399 return quality_;
347400 }
348401
349 bool IsWindowing() const
402 bool HasWindowing() const
350403 {
351404 return hasWindowing_;
405 }
406
407 void SetWindow(float center,
408 float width)
409 {
410 hasWindowing_ = true;
411 windowCenter_ = center;
412 windowWidth_ = width;
352413 }
353414
354415 float GetWindowCenter() const
703764 }
704765
705766
767 static bool ReadRescale(RenderingParameters& parameters,
768 const Json::Value& tags)
769 {
770 static const char* const RESCALE_INTERCEPT = "0028,1052";
771 static const char* const RESCALE_SLOPE = "0028,1053";
772
773 if (tags.type() == Json::objectValue &&
774 tags.isMember(RESCALE_SLOPE) &&
775 tags.isMember(RESCALE_INTERCEPT) &&
776 tags[RESCALE_SLOPE].type() == Json::stringValue &&
777 tags[RESCALE_INTERCEPT].type() == Json::stringValue)
778 {
779 float s, i;
780
781 if (ParseFloat(s, tags[RESCALE_SLOPE].asString()) &&
782 ParseFloat(i, tags[RESCALE_INTERCEPT].asString()))
783 {
784 parameters.SetRescaleSlope(s);
785 parameters.SetRescaleIntercept(i);
786 return true;
787 }
788 }
789
790 return false;
791 }
792
793
794 static bool ReadDefaultWindow(RenderingParameters& parameters,
795 const Json::Value& tags)
796 {
797 static const char* const WINDOW_CENTER = "0028,1050";
798 static const char* const WINDOW_WIDTH = "0028,1051";
799
800 if (tags.type() == Json::objectValue &&
801 tags.isMember(WINDOW_CENTER) &&
802 tags.isMember(WINDOW_WIDTH) &&
803 tags[WINDOW_CENTER].type() == Json::stringValue &&
804 tags[WINDOW_WIDTH].type() == Json::stringValue)
805 {
806 float wc, ww;
807
808 if (ParseFirstFloat(wc, tags[WINDOW_CENTER].asString()) &&
809 ParseFirstFloat(ww, tags[WINDOW_WIDTH].asString()))
810 {
811 parameters.SetWindow(wc, ww);
812 return true;
813 }
814 }
815
816 return false;
817 }
818
706819
707820 static void AnswerFrameRendered(OrthancPluginRestOutput* output,
708821 std::string instanceId,
709822 int frame,
710823 const OrthancPluginHttpRequest* request)
711824 {
712 static const char* const RESCALE_INTERCEPT = "0028,1052";
713 static const char* const RESCALE_SLOPE = "0028,1053";
714825 static const char* const PHOTOMETRIC_INTERPRETATION = "0028,0004";
826 static const char* const PER_FRAME_FUNCTIONAL_GROUPS_SEQUENCE = "5200,9230";
827 static const char* const PIXEL_VALUE_TRANSFORMATION_SEQUENCE = "0028,9145";
828 static const char* const FRAME_VOI_LUT_SEQUENCE = "0028,9132";
715829
716830 Orthanc::MimeType mime = Orthanc::MimeType_Jpeg; // This is the default in DICOMweb
717831
743857 RenderingParameters parameters(request);
744858
745859 OrthancPlugins::MemoryBuffer buffer;
746 bool badFrameError = false;
747
748
749 if (parameters.HasCustomization())
750 {
751 if (frame <= 0)
752 {
753 badFrameError = true;
754 }
755 else
756 {
757 buffer.GetDicomInstance(instanceId);
758
759 Json::Value tags;
760 buffer.DicomToJson(tags, OrthancPluginDicomToJsonFormat_Short, OrthancPluginDicomToJsonFlags_None, 255);
761
762 if (tags.isMember(RESCALE_SLOPE) &&
763 tags[RESCALE_SLOPE].type() == Json::stringValue)
764 {
765 try
766 {
767 parameters.SetRescaleSlope
768 (boost::lexical_cast<float>
769 (Orthanc::Toolbox::StripSpaces(tags[RESCALE_SLOPE].asString())));
770 }
771 catch (boost::bad_lexical_cast&)
772 {
773 }
774 }
775
776 if (tags.isMember(RESCALE_INTERCEPT) &&
777 tags[RESCALE_INTERCEPT].type() == Json::stringValue)
778 {
779 try
780 {
781 parameters.SetRescaleIntercept
782 (boost::lexical_cast<float>
783 (Orthanc::Toolbox::StripSpaces(tags[RESCALE_INTERCEPT].asString())));
784 }
785 catch (boost::bad_lexical_cast&)
786 {
787 }
788 }
789
790 OrthancPlugins::OrthancImage dicom;
791 dicom.DecodeDicomImage(buffer.GetData(), buffer.GetSize(), static_cast<unsigned int>(frame - 1));
792
793 Orthanc::PixelFormat targetFormat;
794 OrthancPluginPixelFormat sdkFormat;
795 if (dicom.GetPixelFormat() == OrthancPluginPixelFormat_RGB24)
796 {
797 targetFormat = Orthanc::PixelFormat_RGB24;
798 sdkFormat = OrthancPluginPixelFormat_RGB24;
799 }
800 else
801 {
802 targetFormat = Orthanc::PixelFormat_Grayscale8;
803 sdkFormat = OrthancPluginPixelFormat_Grayscale8;
804 }
805
806 Orthanc::ImageAccessor source;
807 source.AssignReadOnly(Convert(dicom.GetPixelFormat()),
808 dicom.GetWidth(), dicom.GetHeight(), dicom.GetPitch(), dicom.GetBuffer());
809
810 Orthanc::Image target(targetFormat, parameters.GetTargetWidth(source.GetWidth()),
811 parameters.GetTargetHeight(source.GetHeight()), false);
812
813 // New in 1.3: Fix for MONOCHROME1 images
814 bool invert = false;
815 if (target.GetFormat() == Orthanc::PixelFormat_Grayscale8 &&
816 tags.isMember(PHOTOMETRIC_INTERPRETATION) &&
817 tags[PHOTOMETRIC_INTERPRETATION].type() == Json::stringValue)
818 {
819 std::string s = tags[PHOTOMETRIC_INTERPRETATION].asString();
820 Orthanc::Toolbox::StripSpaces(s);
821 if (s == "MONOCHROME1")
822 {
823 invert = true;
824 }
825 }
826
827 ApplyRendering(target, source, parameters, invert);
828
829 switch (mime)
830 {
831 case Orthanc::MimeType_Png:
832 OrthancPluginCompressAndAnswerPngImage(OrthancPlugins::GetGlobalContext(), output, sdkFormat,
833 target.GetWidth(), target.GetHeight(), target.GetPitch(), target.GetBuffer());
834 break;
835
836 case Orthanc::MimeType_Jpeg:
837 OrthancPluginCompressAndAnswerJpegImage(OrthancPlugins::GetGlobalContext(), output, sdkFormat,
838 target.GetWidth(), target.GetHeight(), target.GetPitch(), target.GetBuffer(),
839 parameters.GetQuality());
840 break;
841
842 default:
843 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
844 }
845 }
846 }
847 else
848 {
849 // No customization of the rendering. Return the default
850 // preview of Orthanc.
851 std::map<std::string, std::string> headers;
852 headers["Accept"] = Orthanc::EnumerationToString(mime);
853
854 /**
855 * (1) In DICOMweb, the "frame" parameter is in the range [1..N],
856 * whereas Orthanc uses range [0..N-1], hence the "-1" below.
857 *
858 * (2) We can use "/rendered" that was introduced in the REST API
859 * of Orthanc 1.6.0, as since release 1.2 of the DICOMweb plugin,
860 * the minimal SDK version is Orthanc 1.7.0 (in order to be able
861 * to use transcoding primitives). In releases <= 1.2, "/preview"
862 * was used, which caused one issue:
863 * https://groups.google.com/d/msg/orthanc-users/mKgr2QAKTCU/R7u4I1LvBAAJ
864 **/
865 if (buffer.RestApiGet("/instances/" + instanceId + "/frames/" +
866 boost::lexical_cast<std::string>(frame - 1) + "/rendered", headers, false))
867 {
868 OrthancPluginAnswerBuffer(OrthancPlugins::GetGlobalContext(), output, buffer.GetData(),
869 buffer.GetSize(), Orthanc::EnumerationToString(mime));
870 }
871 else
872 {
873 badFrameError = true;
874 }
875 }
876
877 if (badFrameError)
860
861 if (frame <= 0)
878862 {
879863 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange,
880864 "Inexistent frame index in this image: " + boost::lexical_cast<std::string>(frame));
865 }
866 else
867 {
868 buffer.GetDicomInstance(instanceId);
869
870 Json::Value tags;
871 buffer.DicomToJson(tags, OrthancPluginDicomToJsonFormat_Short, OrthancPluginDicomToJsonFlags_None, 255);
872
873 const unsigned int f = static_cast<unsigned int>(frame - 1);
874
875 if (ReadRescale(parameters, tags))
876 {
877 }
878 else if (tags.isMember(PER_FRAME_FUNCTIONAL_GROUPS_SEQUENCE) &&
879 tags[PER_FRAME_FUNCTIONAL_GROUPS_SEQUENCE].type() == Json::arrayValue &&
880 static_cast<Json::Value::ArrayIndex>(f) < tags[PER_FRAME_FUNCTIONAL_GROUPS_SEQUENCE].size() &&
881 tags[PER_FRAME_FUNCTIONAL_GROUPS_SEQUENCE][f].type() == Json::objectValue &&
882 tags[PER_FRAME_FUNCTIONAL_GROUPS_SEQUENCE][f].isMember(PIXEL_VALUE_TRANSFORMATION_SEQUENCE) &&
883 tags[PER_FRAME_FUNCTIONAL_GROUPS_SEQUENCE][f][PIXEL_VALUE_TRANSFORMATION_SEQUENCE].type() == Json::arrayValue &&
884 tags[PER_FRAME_FUNCTIONAL_GROUPS_SEQUENCE][f][PIXEL_VALUE_TRANSFORMATION_SEQUENCE].size() == 1)
885 {
886 ReadRescale(parameters, tags[PER_FRAME_FUNCTIONAL_GROUPS_SEQUENCE][f][PIXEL_VALUE_TRANSFORMATION_SEQUENCE][0]);
887 }
888
889 if (!parameters.HasWindowing())
890 {
891 if (ReadDefaultWindow(parameters, tags))
892 {
893 }
894 else if (tags.isMember(PER_FRAME_FUNCTIONAL_GROUPS_SEQUENCE) &&
895 tags[PER_FRAME_FUNCTIONAL_GROUPS_SEQUENCE].type() == Json::arrayValue &&
896 static_cast<Json::Value::ArrayIndex>(f) < tags[PER_FRAME_FUNCTIONAL_GROUPS_SEQUENCE].size() &&
897 tags[PER_FRAME_FUNCTIONAL_GROUPS_SEQUENCE][f].type() == Json::objectValue &&
898 tags[PER_FRAME_FUNCTIONAL_GROUPS_SEQUENCE][f].isMember(FRAME_VOI_LUT_SEQUENCE) &&
899 tags[PER_FRAME_FUNCTIONAL_GROUPS_SEQUENCE][f][FRAME_VOI_LUT_SEQUENCE].type() == Json::arrayValue &&
900 tags[PER_FRAME_FUNCTIONAL_GROUPS_SEQUENCE][f][FRAME_VOI_LUT_SEQUENCE].size() == 1)
901 {
902 ReadDefaultWindow(parameters, tags[PER_FRAME_FUNCTIONAL_GROUPS_SEQUENCE][f][FRAME_VOI_LUT_SEQUENCE][0]);
903 }
904 }
905
906 OrthancPlugins::OrthancImage dicom;
907 dicom.DecodeDicomImage(buffer.GetData(), buffer.GetSize(), f);
908
909 Orthanc::PixelFormat targetFormat;
910 OrthancPluginPixelFormat sdkFormat;
911 if (dicom.GetPixelFormat() == OrthancPluginPixelFormat_RGB24)
912 {
913 targetFormat = Orthanc::PixelFormat_RGB24;
914 sdkFormat = OrthancPluginPixelFormat_RGB24;
915 }
916 else
917 {
918 targetFormat = Orthanc::PixelFormat_Grayscale8;
919 sdkFormat = OrthancPluginPixelFormat_Grayscale8;
920 }
921
922 Orthanc::ImageAccessor source;
923 source.AssignReadOnly(Convert(dicom.GetPixelFormat()),
924 dicom.GetWidth(), dicom.GetHeight(), dicom.GetPitch(), dicom.GetBuffer());
925
926 Orthanc::Image target(targetFormat, parameters.GetTargetWidth(source.GetWidth()),
927 parameters.GetTargetHeight(source.GetHeight()), false);
928
929 // New in 1.3: Fix for MONOCHROME1 images
930 bool invert = false;
931 if (target.GetFormat() == Orthanc::PixelFormat_Grayscale8 &&
932 tags.isMember(PHOTOMETRIC_INTERPRETATION) &&
933 tags[PHOTOMETRIC_INTERPRETATION].type() == Json::stringValue)
934 {
935 std::string s = tags[PHOTOMETRIC_INTERPRETATION].asString();
936 Orthanc::Toolbox::StripSpaces(s);
937 if (s == "MONOCHROME1")
938 {
939 invert = true;
940 }
941 }
942
943 ApplyRendering(target, source, parameters, invert);
944
945 switch (mime)
946 {
947 case Orthanc::MimeType_Png:
948 OrthancPluginCompressAndAnswerPngImage(OrthancPlugins::GetGlobalContext(), output, sdkFormat,
949 target.GetWidth(), target.GetHeight(), target.GetPitch(), target.GetBuffer());
950 break;
951
952 case Orthanc::MimeType_Jpeg:
953 OrthancPluginCompressAndAnswerJpegImage(OrthancPlugins::GetGlobalContext(), output, sdkFormat,
954 target.GetWidth(), target.GetHeight(), target.GetPitch(), target.GetBuffer(),
955 parameters.GetQuality());
956 break;
957
958 default:
959 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
960 }
881961 }
882962 }
883963
129129 set(ORTHANC_FRAMEWORK_MD5 "3ea66c09f64aca990016683b6375734e")
130130 elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.9.3")
131131 set(ORTHANC_FRAMEWORK_MD5 "9b86e6f00e03278293cd15643cc0233f")
132 elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.9.4")
133 set(ORTHANC_FRAMEWORK_MD5 "6d5ca4a73ac7d42445041ca79de1624d")
134 elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.9.5")
135 set(ORTHANC_FRAMEWORK_MD5 "10fc64de1254a095e5d3ed3931f0cfbb")
136 elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.9.6")
137 set(ORTHANC_FRAMEWORK_MD5 "4b5d05683d747c29b2860ad79d11e62e")
138 elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.9.7")
139 set(ORTHANC_FRAMEWORK_MD5 "c912bbb860d640d3ae3003b5c9698205")
132140
133141 # Below this point are development snapshots that were used to
134142 # release some plugin, before an official release of the Orthanc