comparison OrthancServer/Sources/OrthancRestApi/OrthancRestResources.cpp @ 4777:3b78ba359db3

Support detection of windowing and rescale in Philips multiframe images
author Sebastien Jodogne <s.jodogne@gmail.com>
date Mon, 30 Aug 2021 11:41:05 +0200
parents 9da6ca57a977
children 61da49321754 434843934307 ae643f664628
comparison
equal deleted inserted replaced
4776:79d4e155592b 4777:3b78ba359db3
664 } 664 }
665 665
666 // "dicom" is non-NULL iff. "RequiresDicomTags() == true" 666 // "dicom" is non-NULL iff. "RequiresDicomTags() == true"
667 virtual void Handle(RestApiGetCall& call, 667 virtual void Handle(RestApiGetCall& call,
668 std::unique_ptr<ImageAccessor>& decoded, 668 std::unique_ptr<ImageAccessor>& decoded,
669 const ParsedDicomFile* dicom) = 0; 669 const ParsedDicomFile* dicom,
670 unsigned int frame) = 0;
670 671
671 virtual bool RequiresDicomTags() const = 0; 672 virtual bool RequiresDicomTags() const = 0;
672 673
673 static void Apply(RestApiGetCall& call, 674 static void Apply(RestApiGetCall& call,
674 IDecodedFrameHandler& handler, 675 IDecodedFrameHandler& handler,
797 * Retrieve a summary of the DICOM tags, which is 798 * Retrieve a summary of the DICOM tags, which is
798 * necessary to deal with MONOCHROME1 photometric 799 * necessary to deal with MONOCHROME1 photometric
799 * interpretation, and with windowing parameters. 800 * interpretation, and with windowing parameters.
800 **/ 801 **/
801 ServerContext::DicomCacheLocker locker(context, publicId); 802 ServerContext::DicomCacheLocker locker(context, publicId);
802 handler.Handle(call, decoded, &locker.GetDicom()); 803 handler.Handle(call, decoded, &locker.GetDicom(), frame);
803 } 804 }
804 else 805 else
805 { 806 {
806 handler.Handle(call, decoded, NULL); 807 handler.Handle(call, decoded, NULL, frame);
807 } 808 }
808 } 809 }
809 catch (OrthancException& e) 810 catch (OrthancException& e)
810 { 811 {
811 if (e.GetErrorCode() == ErrorCode_ParameterOutOfRange || 812 if (e.GetErrorCode() == ErrorCode_ParameterOutOfRange ||
866 { 867 {
867 } 868 }
868 869
869 virtual void Handle(RestApiGetCall& call, 870 virtual void Handle(RestApiGetCall& call,
870 std::unique_ptr<ImageAccessor>& decoded, 871 std::unique_ptr<ImageAccessor>& decoded,
871 const ParsedDicomFile* dicom) ORTHANC_OVERRIDE 872 const ParsedDicomFile* dicom,
873 unsigned int frame) ORTHANC_OVERRIDE
872 { 874 {
873 bool invert = false; 875 bool invert = false;
874 876
875 if (mode_ == ImageExtractionMode_Preview) 877 if (mode_ == ImageExtractionMode_Preview)
876 { 878 {
897 899
898 900
899 class RenderedFrameHandler : public IDecodedFrameHandler 901 class RenderedFrameHandler : public IDecodedFrameHandler
900 { 902 {
901 private: 903 private:
902 static void GetDicomParameters(bool& invert, 904 static void GetUserArguments(double& windowWidth /* inout */,
903 float& rescaleSlope, 905 double& windowCenter /* inout */,
904 float& rescaleIntercept,
905 float& windowWidth,
906 float& windowCenter,
907 const ParsedDicomFile& dicom)
908 {
909 DicomMap tags;
910 OrthancConfiguration::DefaultExtractDicomSummary(tags, dicom);
911
912 DicomImageInformation info(tags);
913
914 invert = (info.GetPhotometricInterpretation() == PhotometricInterpretation_Monochrome1);
915
916 rescaleSlope = 1.0f;
917 rescaleIntercept = 0.0f;
918
919 if (dicom.HasTag(Orthanc::DICOM_TAG_RESCALE_SLOPE) &&
920 dicom.HasTag(Orthanc::DICOM_TAG_RESCALE_INTERCEPT))
921 {
922 tags.ParseFloat(rescaleSlope, Orthanc::DICOM_TAG_RESCALE_SLOPE);
923 tags.ParseFloat(rescaleIntercept, Orthanc::DICOM_TAG_RESCALE_INTERCEPT);
924 }
925
926 windowWidth = static_cast<float>(1 << info.GetBitsStored()) * rescaleSlope;
927 windowCenter = windowWidth / 2.0f + rescaleIntercept;
928
929 if (tags.HasTag(Orthanc::DICOM_TAG_WINDOW_CENTER) &&
930 tags.HasTag(Orthanc::DICOM_TAG_WINDOW_WIDTH))
931 {
932 tags.ParseFirstFloat(windowCenter, Orthanc::DICOM_TAG_WINDOW_CENTER);
933 tags.ParseFirstFloat(windowWidth, Orthanc::DICOM_TAG_WINDOW_WIDTH);
934 }
935 }
936
937 static void GetUserArguments(float& windowWidth /* inout */,
938 float& windowCenter /* inout */,
939 unsigned int& argWidth, 906 unsigned int& argWidth,
940 unsigned int& argHeight, 907 unsigned int& argHeight,
941 bool& smooth, 908 bool& smooth,
942 const RestApiGetCall& call) 909 const RestApiGetCall& call)
943 { 910 {
945 static const char* ARG_WINDOW_WIDTH = "window-width"; 912 static const char* ARG_WINDOW_WIDTH = "window-width";
946 static const char* ARG_WIDTH = "width"; 913 static const char* ARG_WIDTH = "width";
947 static const char* ARG_HEIGHT = "height"; 914 static const char* ARG_HEIGHT = "height";
948 static const char* ARG_SMOOTH = "smooth"; 915 static const char* ARG_SMOOTH = "smooth";
949 916
950 if (call.HasArgument(ARG_WINDOW_WIDTH)) 917 if (call.HasArgument(ARG_WINDOW_WIDTH) &&
951 { 918 !SerializationToolbox::ParseDouble(windowWidth, call.GetArgument(ARG_WINDOW_WIDTH, "")))
952 try 919 {
953 { 920 throw OrthancException(ErrorCode_ParameterOutOfRange,
954 windowWidth = boost::lexical_cast<float>(call.GetArgument(ARG_WINDOW_WIDTH, "")); 921 "Bad value for argument: " + std::string(ARG_WINDOW_WIDTH));
955 } 922 }
956 catch (boost::bad_lexical_cast&) 923
957 { 924 if (call.HasArgument(ARG_WINDOW_CENTER) &&
958 throw OrthancException(ErrorCode_ParameterOutOfRange, 925 !SerializationToolbox::ParseDouble(windowCenter, call.GetArgument(ARG_WINDOW_CENTER, "")))
959 "Bad value for argument: " + std::string(ARG_WINDOW_WIDTH)); 926 {
960 } 927 throw OrthancException(ErrorCode_ParameterOutOfRange,
961 } 928 "Bad value for argument: " + std::string(ARG_WINDOW_CENTER));
962
963 if (call.HasArgument(ARG_WINDOW_CENTER))
964 {
965 try
966 {
967 windowCenter = boost::lexical_cast<float>(call.GetArgument(ARG_WINDOW_CENTER, ""));
968 }
969 catch (boost::bad_lexical_cast&)
970 {
971 throw OrthancException(ErrorCode_ParameterOutOfRange,
972 "Bad value for argument: " + std::string(ARG_WINDOW_CENTER));
973 }
974 } 929 }
975 930
976 argWidth = 0; 931 argWidth = 0;
977 argHeight = 0; 932 argHeight = 0;
978 933
1030 985
1031 986
1032 public: 987 public:
1033 virtual void Handle(RestApiGetCall& call, 988 virtual void Handle(RestApiGetCall& call,
1034 std::unique_ptr<ImageAccessor>& decoded, 989 std::unique_ptr<ImageAccessor>& decoded,
1035 const ParsedDicomFile* dicom) ORTHANC_OVERRIDE 990 const ParsedDicomFile* dicom,
991 unsigned int frame) ORTHANC_OVERRIDE
1036 { 992 {
1037 if (dicom == NULL) 993 if (dicom == NULL)
1038 { 994 {
1039 throw OrthancException(ErrorCode_InternalError); 995 throw OrthancException(ErrorCode_InternalError);
1040 } 996 }
1041 997
1042 bool invert; 998 PhotometricInterpretation photometric;
1043 float rescaleSlope, rescaleIntercept, windowWidth, windowCenter; 999 const bool invert = (dicom->LookupPhotometricInterpretation(photometric) &&
1044 GetDicomParameters(invert, rescaleSlope, rescaleIntercept, windowWidth, windowCenter, *dicom); 1000 photometric == PhotometricInterpretation_Monochrome1);
1045 1001
1002 double rescaleIntercept, rescaleSlope, windowCenter, windowWidth;
1003 dicom->GetRescale(rescaleIntercept, rescaleSlope, frame);
1004 dicom->GetDefaultWindowing(windowCenter, windowWidth, frame);
1005
1046 unsigned int argWidth, argHeight; 1006 unsigned int argWidth, argHeight;
1047 bool smooth; 1007 bool smooth;
1048 GetUserArguments(windowWidth, windowCenter, argWidth, argHeight, smooth, call); 1008 GetUserArguments(windowWidth, windowCenter, argWidth, argHeight, smooth, call);
1049 1009
1050 unsigned int targetWidth = decoded->GetWidth(); 1010 unsigned int targetWidth = decoded->GetWidth();
1110 if (windowWidth <= 1.0f) 1070 if (windowWidth <= 1.0f)
1111 { 1071 {
1112 windowWidth = 1; 1072 windowWidth = 1;
1113 } 1073 }
1114 1074
1115 if (std::abs(rescaleSlope) <= 0.1f) 1075 if (std::abs(rescaleSlope) <= 0.1)
1116 { 1076 {
1117 rescaleSlope = 0.1f; 1077 rescaleSlope = 0.1;
1118 } 1078 }
1119 1079
1120 const float scaling = 255.0f * rescaleSlope / windowWidth; 1080 const double scaling = 255.0 * rescaleSlope / windowWidth;
1121 const float offset = (rescaleIntercept - windowCenter + windowWidth / 2.0f) / rescaleSlope; 1081 const double offset = (rescaleIntercept - windowCenter + windowWidth / 2.0) / rescaleSlope;
1122 1082
1123 std::unique_ptr<ImageAccessor> rescaled(new Image(PixelFormat_Grayscale8, decoded->GetWidth(), decoded->GetHeight(), false)); 1083 std::unique_ptr<ImageAccessor> rescaled(new Image(PixelFormat_Grayscale8, decoded->GetWidth(), decoded->GetHeight(), false));
1124 ImageProcessing::ShiftScale(*rescaled, converted, offset, scaling, false); 1084 ImageProcessing::ShiftScale(*rescaled, converted, static_cast<float>(offset), static_cast<float>(scaling), false);
1125 1085
1126 if (targetWidth == decoded->GetWidth() && 1086 if (targetWidth == decoded->GetWidth() &&
1127 targetHeight == decoded->GetHeight()) 1087 targetHeight == decoded->GetHeight())
1128 { 1088 {
1129 DefaultHandler(call, rescaled, ImageExtractionMode_UInt8, invert); 1089 DefaultHandler(call, rescaled, ImageExtractionMode_UInt8, invert);