comparison OrthancStone/Sources/Scene2D/AnnotationsSceneLayer.cpp @ 1987:a595776232bb

added ellipse probe
author Sebastien Jodogne <s.jodogne@gmail.com>
date Mon, 31 Oct 2022 17:43:44 +0100
parents e29783c92419
children f03a827f8b47
comparison
equal deleted inserted replaced
1986:e29783c92419 1987:a595776232bb
24 #include "AnnotationsSceneLayer.h" 24 #include "AnnotationsSceneLayer.h"
25 25
26 #include "MacroSceneLayer.h" 26 #include "MacroSceneLayer.h"
27 #include "PolylineSceneLayer.h" 27 #include "PolylineSceneLayer.h"
28 #include "TextSceneLayer.h" 28 #include "TextSceneLayer.h"
29 #include "TextureBaseSceneLayer.h" // TODO REMOVE 29 #include "TextureBaseSceneLayer.h"
30 30
31 #include <Images/ImageTraits.h> 31 #include <Images/ImageTraits.h>
32 #include <OrthancException.h> 32 #include <OrthancException.h>
33 33
34 #include <boost/math/constants/constants.hpp> 34 #include <boost/math/constants/constants.hpp>
54 static const char* const VALUE_SEGMENT = "segment"; 54 static const char* const VALUE_SEGMENT = "segment";
55 static const char* const VALUE_MILLIMETERS = "millimeters"; 55 static const char* const VALUE_MILLIMETERS = "millimeters";
56 static const char* const VALUE_PIXELS = "pixels"; 56 static const char* const VALUE_PIXELS = "pixels";
57 static const char* const VALUE_PIXEL_PROBE = "pixel-probe"; 57 static const char* const VALUE_PIXEL_PROBE = "pixel-probe";
58 static const char* const VALUE_RECTANGLE_PROBE = "rectangle-probe"; 58 static const char* const VALUE_RECTANGLE_PROBE = "rectangle-probe";
59 static const char* const VALUE_ELLIPSE_PROBE = "ellipse-probe";
59 60
60 #if 0 61 #if 0
61 static OrthancStone::Color COLOR_PRIMITIVES(192, 192, 192); 62 static OrthancStone::Color COLOR_PRIMITIVES(192, 192, 192);
62 static OrthancStone::Color COLOR_HOVER(0, 255, 0); 63 static OrthancStone::Color COLOR_HOVER(0, 255, 0);
63 static OrthancStone::Color COLOR_TEXT(255, 0, 0); 64 static OrthancStone::Color COLOR_TEXT(255, 0, 0);
790 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); // No hit is possible 791 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); // No hit is possible
791 } 792 }
792 }; 793 };
793 794
794 795
796 class AnnotationsSceneLayer::Ellipse : public GeometricPrimitive
797 {
798 private:
799 ScenePoint2D p1_;
800 ScenePoint2D p2_;
801 ScenePoint2D delta_;
802
803 double GetCenterX() const
804 {
805 return (p1_.GetX() + p2_.GetX()) / 2.0 + delta_.GetX();
806 }
807
808 double GetCenterY() const
809 {
810 return (p1_.GetY() + p2_.GetY()) / 2.0 + delta_.GetY();
811 }
812
813 double GetRadiusX() const
814 {
815 return std::abs(p1_.GetX() - p2_.GetX()) / 2.0;
816 }
817
818 double GetRadiusY() const
819 {
820 return std::abs(p1_.GetY() - p2_.GetY()) / 2.0;
821 }
822
823 public:
824 Ellipse(Annotation& parentAnnotation,
825 const ScenePoint2D& p1,
826 const ScenePoint2D& p2) :
827 GeometricPrimitive(parentAnnotation, 2),
828 p1_(p1),
829 p2_(p2),
830 delta_(0, 0)
831 {
832 }
833
834 void SetPosition(const ScenePoint2D& p1,
835 const ScenePoint2D& p2)
836 {
837 SetModified(true);
838 p1_ = p1;
839 p2_ = p2;
840 delta_ = ScenePoint2D(0, 0);
841 }
842
843 ScenePoint2D GetPosition1() const
844 {
845 return p1_ + delta_;
846 }
847
848 ScenePoint2D GetPosition2() const
849 {
850 return p2_ + delta_;
851 }
852
853 double GetArea() const
854 {
855 return PI * GetRadiusX() * GetRadiusY();
856 }
857
858 bool IsPointInside(const ScenePoint2D& p) const
859 {
860 const double radiusX = GetRadiusX();
861 const double radiusY = GetRadiusY();
862
863 double a, b, x, y;
864
865 if (radiusX > radiusY)
866 {
867 // The ellipse is horizontal => we are in the case described
868 // on Wikipedia:
869 // https://en.wikipedia.org/wiki/Ellipse#Standard_equation
870
871 a = radiusX;
872 b = radiusY;
873 x = p.GetX() - GetCenterX();
874 y = p.GetY() - GetCenterY();
875 }
876 else
877 {
878 a = radiusY;
879 b = radiusX;
880 x = p.GetY() - GetCenterY();
881 y = p.GetX() - GetCenterX();
882 }
883
884 const double c = sqrt(a * a - b * b);
885
886 return (sqrt((x - c) * (x - c) + y * y) +
887 sqrt((x + c) * (x + c) + y * y)) <= 2.0 * a;
888 }
889
890 virtual bool IsHit(const ScenePoint2D& p,
891 const Scene2D& scene) const ORTHANC_OVERRIDE
892 {
893 const double zoom = scene.GetSceneToCanvasTransform().ComputeZoom();
894
895 const double radiusX = GetRadiusX();
896 const double radiusY = GetRadiusY();
897
898 // Warning: This is only an approximation of the
899 // point-to-ellipse distance, as explained here:
900 // https://blog.chatfield.io/simple-method-for-distance-to-ellipse/
901
902 const double x = (p.GetX() - GetCenterX()) / radiusX;
903 const double y = (p.GetY() - GetCenterY()) / radiusY;
904 const double t = atan2(y, x);
905 const double xx = cos(t) - x;
906 const double yy = sin(t) - y;
907
908 const double approximateDistance = sqrt(xx * xx + yy * yy) * (radiusX + radiusY) / 2.0;
909 return std::abs(approximateDistance) * zoom <= HANDLE_SIZE / 2.0;
910 }
911
912 virtual void RenderPolylineLayer(PolylineSceneLayer& polyline,
913 const Scene2D& scene) ORTHANC_OVERRIDE
914 {
915 static unsigned int NUM_SEGMENTS = 128;
916 polyline.AddArc(GetCenterX(), GetCenterY(), GetRadiusX(), GetRadiusY(), 0, 2.0 * PI, GetActiveColor(), NUM_SEGMENTS);
917 }
918
919 virtual void RenderOtherLayers(MacroSceneLayer& macro,
920 const Scene2D& scene) ORTHANC_OVERRIDE
921 {
922 }
923
924 virtual void MovePreview(const ScenePoint2D& delta,
925 const Scene2D& scene) ORTHANC_OVERRIDE
926 {
927 SetModified(true);
928 delta_ = delta;
929 GetParentAnnotation().SignalMove(*this, scene);
930 }
931
932 virtual void MoveDone(const ScenePoint2D& delta,
933 const Scene2D& scene) ORTHANC_OVERRIDE
934 {
935 SetModified(true);
936 p1_ = p1_ + delta;
937 p2_ = p2_ + delta;
938 delta_ = ScenePoint2D(0, 0);
939 GetParentAnnotation().SignalMove(*this, scene);
940 }
941 };
942
943
795 class AnnotationsSceneLayer::EditPrimitiveTracker : public IFlexiblePointerTracker 944 class AnnotationsSceneLayer::EditPrimitiveTracker : public IFlexiblePointerTracker
796 { 945 {
797 private: 946 private:
798 AnnotationsSceneLayer& that_; 947 AnnotationsSceneLayer& that_;
799 GeometricPrimitive& primitive_; 948 GeometricPrimitive& primitive_;
1550 1699
1551 if (GetUnits() == Units_Millimeters) 1700 if (GetUnits() == Units_Millimeters)
1552 { 1701 {
1553 const double area = std::abs(x1 - x2) * std::abs(y1 - y2); 1702 const double area = std::abs(x1 - x2) * std::abs(y1 - y2);
1554 1703
1555 sprintf(buf, "%0.2f cm%c%c", 1704 sprintf(buf, "Area: %0.2f cm%c%c",
1556 area / 100.0, 1705 area / 100.0,
1557 0xc2, 0xb2 /* two bytes corresponding to two power in UTF-8 */); 1706 0xc2, 0xb2 /* two bytes corresponding to two power in UTF-8 */);
1558 text = buf; 1707 text = buf;
1559 } 1708 }
1560 1709
1766 } 1915 }
1767 } 1916 }
1768 }; 1917 };
1769 1918
1770 1919
1920 class AnnotationsSceneLayer::EllipseProbeAnnotation : public ProbingAnnotation
1921 {
1922 private:
1923 Handle& handle1_;
1924 Handle& handle2_;
1925 Ellipse& ellipse_;
1926 Text& label_;
1927
1928 protected:
1929 virtual void UpdateProbeForLayer(const ISceneLayer& layer) ORTHANC_OVERRIDE
1930 {
1931 double x1 = handle1_.GetCenter().GetX();
1932 double y1 = handle1_.GetCenter().GetY();
1933 double x2 = handle2_.GetCenter().GetX();
1934 double y2 = handle2_.GetCenter().GetY();
1935
1936 // Put the label to the right of the right-most handle
1937 //const double y = std::min(y1, y2);
1938 const double y = (y1 + y2) / 2.0;
1939 if (x1 < x2)
1940 {
1941 label_.SetPosition(x2, y);
1942 }
1943 else
1944 {
1945 label_.SetPosition(x1, y);
1946 }
1947
1948 std::string text;
1949
1950 char buf[32];
1951
1952 if (GetUnits() == Units_Millimeters)
1953 {
1954 sprintf(buf, "Area: %0.2f cm%c%c",
1955 ellipse_.GetArea() / 100.0,
1956 0xc2, 0xb2 /* two bytes corresponding to two power in UTF-8 */);
1957 text = buf;
1958 }
1959
1960 if (layer.GetType() == ISceneLayer::Type_FloatTexture)
1961 {
1962 const TextureBaseSceneLayer& texture = dynamic_cast<const TextureBaseSceneLayer&>(layer);
1963 const AffineTransform2D& textureToScene = texture.GetTransform();
1964 const AffineTransform2D sceneToTexture = AffineTransform2D::Invert(textureToScene);
1965
1966 const Orthanc::ImageAccessor& image = texture.GetTexture();
1967 assert(image.GetFormat() == Orthanc::PixelFormat_Float32);
1968
1969 sceneToTexture.Apply(x1, y1);
1970 sceneToTexture.Apply(x2, y2);
1971 int ix1 = static_cast<int>(std::floor(x1));
1972 int iy1 = static_cast<int>(std::floor(y1));
1973 int ix2 = static_cast<int>(std::floor(x2));
1974 int iy2 = static_cast<int>(std::floor(y2));
1975
1976 if (ix1 > ix2)
1977 {
1978 std::swap(ix1, ix2);
1979 }
1980
1981 if (iy1 > iy2)
1982 {
1983 std::swap(iy1, iy2);
1984 }
1985
1986 LinearAlgebra::OnlineVarianceEstimator estimator;
1987
1988 for (int y = std::max(0, iy1); y <= std::min(static_cast<int>(image.GetHeight()) - 1, iy2); y++)
1989 {
1990 int x = std::max(0, ix1);
1991 const float* p = reinterpret_cast<const float*>(image.GetConstRow(y)) + x;
1992
1993 for (; x <= std::min(static_cast<int>(image.GetWidth()) - 1, ix2); x++, p++)
1994 {
1995 double yy = static_cast<double>(y) + 0.5;
1996 double xx = static_cast<double>(x) + 0.5;
1997 textureToScene.Apply(xx, yy);
1998 if (ellipse_.IsPointInside(ScenePoint2D(xx, yy)))
1999 {
2000 estimator.AddSample(*p);
2001 }
2002 }
2003 }
2004
2005 if (estimator.GetCount() > 0)
2006 {
2007 if (!text.empty())
2008 {
2009 text += "\n";
2010 }
2011 sprintf(buf, "Mean: %0.1f\nStdDev: %0.1f", estimator.GetMean(), estimator.GetStandardDeviation());
2012 text += buf;
2013 }
2014 }
2015
2016 label_.SetText(text);
2017 }
2018
2019 public:
2020 EllipseProbeAnnotation(AnnotationsSceneLayer& that,
2021 Units units,
2022 const ScenePoint2D& p1,
2023 const ScenePoint2D& p2) :
2024 ProbingAnnotation(that, units),
2025 handle1_(AddTypedPrimitive<Handle>(new Handle(*this, Handle::Shape_Square, p1))),
2026 handle2_(AddTypedPrimitive<Handle>(new Handle(*this, Handle::Shape_Square, p2))),
2027 ellipse_(AddTypedPrimitive<Ellipse>(new Ellipse(*this, p1, p2))),
2028 label_(AddTypedPrimitive<Text>(new Text(that, *this)))
2029 {
2030 TextSceneLayer content;
2031 content.SetAnchor(BitmapAnchor_CenterLeft);
2032 content.SetBorder(10);
2033 content.SetText("?");
2034
2035 label_.SetContent(content);
2036 label_.SetColor(COLOR_TEXT);
2037 }
2038
2039 virtual unsigned int GetHandlesCount() const ORTHANC_OVERRIDE
2040 {
2041 return 2;
2042 }
2043
2044 virtual Handle& GetHandle(unsigned int index) const ORTHANC_OVERRIDE
2045 {
2046 switch (index)
2047 {
2048 case 0:
2049 return handle1_;
2050
2051 case 1:
2052 return handle2_;
2053
2054 default:
2055 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
2056 }
2057 }
2058
2059 virtual void SignalMove(GeometricPrimitive& primitive,
2060 const Scene2D& scene) ORTHANC_OVERRIDE
2061 {
2062 if (&primitive == &handle1_ ||
2063 &primitive == &handle2_)
2064 {
2065 ellipse_.SetPosition(handle1_.GetCenter(), handle2_.GetCenter());
2066 }
2067 else if (&primitive == &ellipse_)
2068 {
2069 handle1_.SetCenter(ellipse_.GetPosition1());
2070 handle2_.SetCenter(ellipse_.GetPosition2());
2071 }
2072 else
2073 {
2074 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
2075 }
2076
2077 TagProbeAsChanged();
2078 }
2079
2080 virtual void Serialize(Json::Value& target) ORTHANC_OVERRIDE
2081 {
2082 target = Json::objectValue;
2083 target[KEY_TYPE] = VALUE_ELLIPSE_PROBE;
2084 target[KEY_X1] = handle1_.GetCenter().GetX();
2085 target[KEY_Y1] = handle1_.GetCenter().GetY();
2086 target[KEY_X2] = handle2_.GetCenter().GetX();
2087 target[KEY_Y2] = handle2_.GetCenter().GetY();
2088 }
2089
2090 static void Unserialize(AnnotationsSceneLayer& target,
2091 Units units,
2092 const Json::Value& source)
2093 {
2094 if (source.isMember(KEY_X1) &&
2095 source.isMember(KEY_Y1) &&
2096 source.isMember(KEY_X2) &&
2097 source.isMember(KEY_Y2) &&
2098 source[KEY_X1].isNumeric() &&
2099 source[KEY_Y1].isNumeric() &&
2100 source[KEY_X2].isNumeric() &&
2101 source[KEY_Y2].isNumeric())
2102 {
2103 new EllipseProbeAnnotation(target, units,
2104 ScenePoint2D(source[KEY_X1].asDouble(), source[KEY_Y1].asDouble()),
2105 ScenePoint2D(source[KEY_X2].asDouble(), source[KEY_Y2].asDouble()));
2106 }
2107 else
2108 {
2109 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat, "Cannot unserialize an ellipse probe annotation");
2110 }
2111 }
2112 };
2113
2114
1771 class AnnotationsSceneLayer::CreateTwoHandlesTracker : public IFlexiblePointerTracker 2115 class AnnotationsSceneLayer::CreateTwoHandlesTracker : public IFlexiblePointerTracker
1772 { 2116 {
1773 private: 2117 private:
1774 AnnotationsSceneLayer& layer_; 2118 AnnotationsSceneLayer& layer_;
1775 Annotation* annotation_; 2119 Annotation* annotation_;
2252 { 2596 {
2253 Annotation* annotation = new RectangleProbeAnnotation(*this, units_, s, s); 2597 Annotation* annotation = new RectangleProbeAnnotation(*this, units_, s, s);
2254 return new CreateTwoHandlesTracker(*annotation, scene.GetCanvasToSceneTransform()); 2598 return new CreateTwoHandlesTracker(*annotation, scene.GetCanvasToSceneTransform());
2255 } 2599 }
2256 2600
2601 case Tool_EllipseProbe:
2602 {
2603 Annotation* annotation = new EllipseProbeAnnotation(*this, units_, s, s);
2604 return new CreateTwoHandlesTracker(*annotation, scene.GetCanvasToSceneTransform());
2605 }
2606
2257 default: 2607 default:
2258 return NULL; 2608 return NULL;
2259 } 2609 }
2260 } 2610 }
2261 } 2611 }
2353 } 2703 }
2354 else if (type == VALUE_RECTANGLE_PROBE) 2704 else if (type == VALUE_RECTANGLE_PROBE)
2355 { 2705 {
2356 RectangleProbeAnnotation::Unserialize(*this, units_, annotations[i]); 2706 RectangleProbeAnnotation::Unserialize(*this, units_, annotations[i]);
2357 } 2707 }
2708 else if (type == VALUE_ELLIPSE_PROBE)
2709 {
2710 EllipseProbeAnnotation::Unserialize(*this, units_, annotations[i]);
2711 }
2358 else 2712 else
2359 { 2713 {
2360 LOG(ERROR) << "Cannot unserialize unknown type of annotation: " << type; 2714 LOG(ERROR) << "Cannot unserialize unknown type of annotation: " << type;
2361 } 2715 }
2362 } 2716 }