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 {