Mercurial > hg > orthanc
comparison OrthancServer/OrthancRestApi/OrthancRestResources.cpp @ 3690:a9ce35d67c3c
implementation of "/instances/.../rendered" for grayscale images
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Tue, 25 Feb 2020 13:57:43 +0100 |
parents | e6d6f8d23d02 |
children | 2a170a8f1faf |
comparison
equal
deleted
inserted
replaced
3689:e85bfba2d307 | 3690:a9ce35d67c3c |
---|---|
670 }; | 670 }; |
671 | 671 |
672 | 672 |
673 class RenderedFrameHandler : public IDecodedFrameHandler | 673 class RenderedFrameHandler : public IDecodedFrameHandler |
674 { | 674 { |
675 private: | |
676 static void GetDicomParameters(bool& invert, | |
677 float& rescaleSlope, | |
678 float& rescaleIntercept, | |
679 float& windowWidth, | |
680 float& windowCenter, | |
681 const DicomMap& dicom) | |
682 { | |
683 DicomImageInformation info(dicom); | |
684 | |
685 invert = (info.GetPhotometricInterpretation() == PhotometricInterpretation_Monochrome1); | |
686 | |
687 rescaleSlope = 1.0f; | |
688 rescaleIntercept = 0.0f; | |
689 | |
690 if (dicom.HasTag(Orthanc::DICOM_TAG_RESCALE_SLOPE) && | |
691 dicom.HasTag(Orthanc::DICOM_TAG_RESCALE_INTERCEPT)) | |
692 { | |
693 dicom.ParseFloat(rescaleSlope, Orthanc::DICOM_TAG_RESCALE_SLOPE); | |
694 dicom.ParseFloat(rescaleIntercept, Orthanc::DICOM_TAG_RESCALE_INTERCEPT); | |
695 } | |
696 | |
697 windowWidth = static_cast<float>(1 << info.GetBitsStored()); | |
698 windowCenter = windowWidth / 2.0f; | |
699 | |
700 if (dicom.HasTag(Orthanc::DICOM_TAG_WINDOW_CENTER) && | |
701 dicom.HasTag(Orthanc::DICOM_TAG_WINDOW_WIDTH)) | |
702 { | |
703 dicom.ParseFirstFloat(windowCenter, Orthanc::DICOM_TAG_WINDOW_CENTER); | |
704 dicom.ParseFirstFloat(windowWidth, Orthanc::DICOM_TAG_WINDOW_WIDTH); | |
705 } | |
706 } | |
707 | |
708 static void GetUserArguments(float& windowWidth /* inout */, | |
709 float& windowCenter /* inout */, | |
710 unsigned int& argWidth, | |
711 unsigned int& argHeight, | |
712 bool& smooth, | |
713 RestApiGetCall& call) | |
714 { | |
715 static const char* ARG_WINDOW_CENTER = "window-center"; | |
716 static const char* ARG_WINDOW_WIDTH = "window-width"; | |
717 static const char* ARG_WIDTH = "width"; | |
718 static const char* ARG_HEIGHT = "height"; | |
719 static const char* ARG_SMOOTH = "smooth"; | |
720 | |
721 if (call.HasArgument(ARG_WINDOW_WIDTH)) | |
722 { | |
723 try | |
724 { | |
725 windowWidth = boost::lexical_cast<float>(call.GetArgument(ARG_WINDOW_WIDTH, "")); | |
726 } | |
727 catch (boost::bad_lexical_cast&) | |
728 { | |
729 throw OrthancException(ErrorCode_ParameterOutOfRange, | |
730 "Bad value for argument: " + std::string(ARG_WINDOW_WIDTH)); | |
731 } | |
732 } | |
733 | |
734 if (call.HasArgument(ARG_WINDOW_CENTER)) | |
735 { | |
736 try | |
737 { | |
738 windowCenter = boost::lexical_cast<float>(call.GetArgument(ARG_WINDOW_CENTER, "")); | |
739 } | |
740 catch (boost::bad_lexical_cast&) | |
741 { | |
742 throw OrthancException(ErrorCode_ParameterOutOfRange, | |
743 "Bad value for argument: " + std::string(ARG_WINDOW_CENTER)); | |
744 } | |
745 } | |
746 | |
747 argWidth = 0; | |
748 argHeight = 0; | |
749 | |
750 if (call.HasArgument(ARG_WIDTH)) | |
751 { | |
752 try | |
753 { | |
754 int tmp = boost::lexical_cast<int>(call.GetArgument(ARG_WIDTH, "")); | |
755 if (tmp < 0) | |
756 { | |
757 throw OrthancException(ErrorCode_ParameterOutOfRange, | |
758 "Argument cannot be negative: " + std::string(ARG_WIDTH)); | |
759 } | |
760 else | |
761 { | |
762 argWidth = static_cast<unsigned int>(tmp); | |
763 } | |
764 } | |
765 catch (boost::bad_lexical_cast&) | |
766 { | |
767 throw OrthancException(ErrorCode_ParameterOutOfRange, | |
768 "Bad value for argument: " + std::string(ARG_WIDTH)); | |
769 } | |
770 } | |
771 | |
772 if (call.HasArgument(ARG_HEIGHT)) | |
773 { | |
774 try | |
775 { | |
776 int tmp = boost::lexical_cast<int>(call.GetArgument(ARG_HEIGHT, "")); | |
777 if (tmp < 0) | |
778 { | |
779 throw OrthancException(ErrorCode_ParameterOutOfRange, | |
780 "Argument cannot be negative: " + std::string(ARG_HEIGHT)); | |
781 } | |
782 else | |
783 { | |
784 argHeight = static_cast<unsigned int>(tmp); | |
785 } | |
786 } | |
787 catch (boost::bad_lexical_cast&) | |
788 { | |
789 throw OrthancException(ErrorCode_ParameterOutOfRange, | |
790 "Bad value for argument: " + std::string(ARG_HEIGHT)); | |
791 } | |
792 } | |
793 | |
794 smooth = false; | |
795 | |
796 if (call.HasArgument(ARG_SMOOTH)) | |
797 { | |
798 std::string value = call.GetArgument(ARG_SMOOTH, ""); | |
799 if (value == "0" || | |
800 value == "false") | |
801 { | |
802 smooth = false; | |
803 } | |
804 else if (value == "1" || | |
805 value == "true") | |
806 { | |
807 smooth = true; | |
808 } | |
809 else | |
810 { | |
811 throw OrthancException(ErrorCode_ParameterOutOfRange, | |
812 "Argument must be Boolean: " + std::string(ARG_SMOOTH)); | |
813 } | |
814 } | |
815 } | |
816 | |
817 | |
675 public: | 818 public: |
676 virtual void Handle(RestApiGetCall& call, | 819 virtual void Handle(RestApiGetCall& call, |
677 std::auto_ptr<ImageAccessor>& decoded, | 820 std::auto_ptr<ImageAccessor>& decoded, |
678 const DicomMap& dicom) ORTHANC_OVERRIDE | 821 const DicomMap& dicom) ORTHANC_OVERRIDE |
679 { | 822 { |
680 static const char* ARG_WINDOW_CENTER = "window-center"; | 823 bool invert; |
681 static const char* ARG_WINDOW_WIDTH = "window-width"; | 824 float rescaleSlope, rescaleIntercept, windowWidth, windowCenter; |
682 static const char* ARG_MAX_WIDTH = "max-width"; | 825 GetDicomParameters(invert, rescaleSlope, rescaleIntercept, windowWidth, windowCenter, dicom); |
683 static const char* ARG_MAX_HEIGHT = "max-height"; | 826 |
684 static const char* ARG_SMOOTH = "smooth"; | 827 unsigned int argWidth, argHeight; |
685 | 828 bool smooth; |
686 DicomImageInformation info(dicom); | 829 GetUserArguments(windowWidth, windowCenter, argWidth, argHeight, smooth, call); |
687 | 830 |
688 const bool invert = (info.GetPhotometricInterpretation() == PhotometricInterpretation_Monochrome1); | 831 unsigned int targetWidth = decoded->GetWidth(); |
689 | 832 unsigned int targetHeight = decoded->GetHeight(); |
690 float rescaleSlope = 1.0f; | |
691 float rescaleIntercept = 0.0f; | |
692 | |
693 if (dicom.HasTag(Orthanc::DICOM_TAG_RESCALE_SLOPE) && | |
694 dicom.HasTag(Orthanc::DICOM_TAG_RESCALE_INTERCEPT)) | |
695 { | |
696 dicom.ParseFloat(rescaleSlope, Orthanc::DICOM_TAG_RESCALE_SLOPE); | |
697 dicom.ParseFloat(rescaleIntercept, Orthanc::DICOM_TAG_RESCALE_INTERCEPT); | |
698 } | |
699 | |
700 float windowWidth = static_cast<float>(1 << info.GetBitsStored()); | |
701 float windowCenter = windowWidth / 2.0f; | |
702 | |
703 if (dicom.HasTag(Orthanc::DICOM_TAG_WINDOW_CENTER) && | |
704 dicom.HasTag(Orthanc::DICOM_TAG_WINDOW_WIDTH)) | |
705 { | |
706 dicom.ParseFloat(windowCenter, Orthanc::DICOM_TAG_WINDOW_CENTER); | |
707 dicom.ParseFloat(windowWidth, Orthanc::DICOM_TAG_WINDOW_WIDTH); | |
708 } | |
709 | |
710 if (call.HasArgument(ARG_WINDOW_WIDTH)) | |
711 { | |
712 try | |
713 { | |
714 windowWidth = boost::lexical_cast<float>(call.GetArgument(ARG_WINDOW_WIDTH, "")); | |
715 } | |
716 catch (boost::bad_lexical_cast&) | |
717 { | |
718 throw OrthancException(ErrorCode_ParameterOutOfRange, | |
719 "Bad value for argument: " + std::string(ARG_WINDOW_WIDTH)); | |
720 } | |
721 } | |
722 | |
723 if (call.HasArgument(ARG_WINDOW_CENTER)) | |
724 { | |
725 try | |
726 { | |
727 windowCenter = boost::lexical_cast<float>(call.GetArgument(ARG_WINDOW_CENTER, "")); | |
728 } | |
729 catch (boost::bad_lexical_cast&) | |
730 { | |
731 throw OrthancException(ErrorCode_ParameterOutOfRange, | |
732 "Bad value for argument: " + std::string(ARG_WINDOW_CENTER)); | |
733 } | |
734 } | |
735 | |
736 unsigned int maxWidth = 0; | |
737 unsigned int maxHeight = 0; | |
738 | |
739 if (call.HasArgument(ARG_MAX_WIDTH)) | |
740 { | |
741 try | |
742 { | |
743 int tmp = boost::lexical_cast<int>(call.GetArgument(ARG_MAX_WIDTH, "")); | |
744 if (tmp < 0) | |
745 { | |
746 throw OrthancException(ErrorCode_ParameterOutOfRange, | |
747 "Argument cannot be negative: " + std::string(ARG_MAX_WIDTH)); | |
748 } | |
749 else | |
750 { | |
751 maxWidth = static_cast<unsigned int>(tmp); | |
752 } | |
753 } | |
754 catch (boost::bad_lexical_cast&) | |
755 { | |
756 throw OrthancException(ErrorCode_ParameterOutOfRange, | |
757 "Bad value for argument: " + std::string(ARG_MAX_WIDTH)); | |
758 } | |
759 } | |
760 | |
761 if (call.HasArgument(ARG_MAX_HEIGHT)) | |
762 { | |
763 try | |
764 { | |
765 int tmp = boost::lexical_cast<int>(call.GetArgument(ARG_MAX_HEIGHT, "")); | |
766 if (tmp < 0) | |
767 { | |
768 throw OrthancException(ErrorCode_ParameterOutOfRange, | |
769 "Argument cannot be negative: " + std::string(ARG_MAX_HEIGHT)); | |
770 } | |
771 else | |
772 { | |
773 maxHeight = static_cast<unsigned int>(tmp); | |
774 } | |
775 } | |
776 catch (boost::bad_lexical_cast&) | |
777 { | |
778 throw OrthancException(ErrorCode_ParameterOutOfRange, | |
779 "Bad value for argument: " + std::string(ARG_MAX_HEIGHT)); | |
780 } | |
781 } | |
782 | |
783 bool smooth = true; | |
784 | |
785 if (call.HasArgument(ARG_SMOOTH)) | |
786 { | |
787 std::string value = call.GetArgument(ARG_SMOOTH, ""); | |
788 if (value == "0" || | |
789 value == "false") | |
790 { | |
791 smooth = false; | |
792 } | |
793 else if (value == "1" || | |
794 value == "true") | |
795 { | |
796 smooth = true; | |
797 } | |
798 else | |
799 { | |
800 throw OrthancException(ErrorCode_ParameterOutOfRange, | |
801 "Argument must be Boolean: " + std::string(ARG_SMOOTH)); | |
802 } | |
803 } | |
804 | |
805 | |
806 unsigned int width = decoded->GetWidth(); | |
807 unsigned int height = decoded->GetHeight(); | |
808 | 833 |
809 if (decoded->GetWidth() != 0 && | 834 if (decoded->GetWidth() != 0 && |
810 decoded->GetHeight() != 0) | 835 decoded->GetHeight() != 0) |
811 { | 836 { |
812 float ratio = 1; | 837 float ratio = 1; |
813 | 838 |
814 if (maxWidth != 0) | 839 if (argWidth != 0 && |
815 { | 840 argHeight != 0) |
816 ratio = static_cast<float>(maxWidth) / static_cast<float>(decoded->GetWidth()); | 841 { |
817 } | 842 float ratioX = static_cast<float>(argWidth) / static_cast<float>(decoded->GetWidth()); |
818 | 843 float ratioY = static_cast<float>(argHeight) / static_cast<float>(decoded->GetHeight()); |
819 if (maxHeight != 0) | 844 ratio = std::min(ratioX, ratioY); |
820 { | 845 } |
821 float ratioY = static_cast<float>(maxHeight) / static_cast<float>(decoded->GetHeight()); | 846 else if (argWidth != 0) |
822 if (ratioY < ratio) | 847 { |
823 { | 848 ratio = static_cast<float>(argWidth) / static_cast<float>(decoded->GetWidth()); |
824 ratio = ratioY; | 849 } |
825 } | 850 else if (argHeight != 0) |
826 } | 851 { |
827 | 852 ratio = static_cast<float>(argHeight) / static_cast<float>(decoded->GetHeight()); |
828 width = boost::math::iround(ratio * static_cast<float>(decoded->GetWidth())); | 853 } |
829 height = boost::math::iround(ratio * static_cast<float>(decoded->GetHeight())); | 854 |
855 targetWidth = boost::math::iround(ratio * static_cast<float>(decoded->GetWidth())); | |
856 targetHeight = boost::math::iround(ratio * static_cast<float>(decoded->GetHeight())); | |
830 } | 857 } |
831 | 858 |
832 if (decoded->GetFormat() == PixelFormat_RGB24) | 859 if (decoded->GetFormat() == PixelFormat_RGB24) |
833 { | 860 { |
834 if (width == decoded->GetWidth() && | 861 if (targetWidth == decoded->GetWidth() && |
835 height == decoded->GetHeight()) | 862 targetHeight == decoded->GetHeight()) |
836 { | 863 { |
837 DefaultHandler(call, decoded, ImageExtractionMode_Preview, false); | 864 DefaultHandler(call, decoded, ImageExtractionMode_Preview, false); |
838 } | 865 } |
839 else | 866 else |
840 { | 867 { |
841 std::auto_ptr<ImageAccessor> rescaled(new Image(decoded->GetFormat(), width, height, false)); | 868 std::auto_ptr<ImageAccessor> resized( |
869 new Image(decoded->GetFormat(), targetWidth, targetHeight, false)); | |
870 | |
842 if (smooth && | 871 if (smooth && |
843 (width < decoded->GetWidth() || | 872 (targetWidth < decoded->GetWidth() || |
844 height < decoded->GetHeight())) | 873 targetHeight < decoded->GetHeight())) |
845 { | 874 { |
846 ImageProcessing::SmoothGaussian5x5(*decoded); | 875 ImageProcessing::SmoothGaussian5x5(*decoded); |
847 } | 876 } |
848 ImageProcessing::Resize(*rescaled, *decoded); | 877 |
849 DefaultHandler(call, rescaled, ImageExtractionMode_Preview, false); | 878 ImageProcessing::Resize(*resized, *decoded); |
879 DefaultHandler(call, resized, ImageExtractionMode_Preview, false); | |
850 } | 880 } |
851 } | 881 } |
852 else | 882 else |
853 { | 883 { |
854 // TODO : (1) convert to float32, (2) apply windowing, (3) possibly rescale | 884 // Grayscale image: (1) convert to Float32, (2) apply |
855 throw OrthancException(ErrorCode_NotImplemented); | 885 // windowing to get a Grayscale8, (3) possibly resize |
886 | |
887 Image converted(PixelFormat_Float32, decoded->GetWidth(), decoded->GetHeight(), false); | |
888 ImageProcessing::Convert(converted, *decoded); | |
889 | |
890 // Avoid divisions by zero | |
891 if (windowWidth <= 1.0f) | |
892 { | |
893 windowWidth = 1; | |
894 } | |
895 | |
896 if (std::abs(rescaleSlope) <= 0.1f) | |
897 { | |
898 rescaleSlope = 0.1f; | |
899 } | |
900 | |
901 const float scaling = 255.0f * rescaleSlope / windowWidth; | |
902 const float offset = (rescaleIntercept - windowCenter + windowWidth / 2.0f) / rescaleSlope; | |
903 | |
904 std::auto_ptr<ImageAccessor> rescaled(new Image(PixelFormat_Grayscale8, decoded->GetWidth(), decoded->GetHeight(), false)); | |
905 ImageProcessing::ShiftScale(*rescaled, converted, offset, scaling, false); | |
906 | |
907 if (targetWidth == decoded->GetWidth() && | |
908 targetHeight == decoded->GetHeight()) | |
909 { | |
910 DefaultHandler(call, rescaled, ImageExtractionMode_UInt8, invert); | |
911 } | |
912 else | |
913 { | |
914 std::auto_ptr<ImageAccessor> resized( | |
915 new Image(PixelFormat_Grayscale8, targetWidth, targetHeight, false)); | |
916 | |
917 if (smooth && | |
918 (targetWidth < decoded->GetWidth() || | |
919 targetHeight < decoded->GetHeight())) | |
920 { | |
921 ImageProcessing::SmoothGaussian5x5(*rescaled); | |
922 } | |
923 | |
924 ImageProcessing::Resize(*resized, *rescaled); | |
925 DefaultHandler(call, resized, ImageExtractionMode_UInt8, invert); | |
926 } | |
856 } | 927 } |
857 } | 928 } |
858 | 929 |
859 virtual bool RequiresDicomTags() const ORTHANC_OVERRIDE | 930 virtual bool RequiresDicomTags() const ORTHANC_OVERRIDE |
860 { | 931 { |