1
module ParasitemiaCore.MatchingEllipses
4 open System.Collections.Generic
9 // All ellipses with a score below this are removed.
10 let matchingScoreThresholdPerRadiusUnit = 0.07f // For a radius of 1. // 0.25
11 let matchingScorePower = 20.f
12 let windowSizeRadiusFactor = 1.f
/ 2.f
// Used when searching for neighbor ellipses.
13 let minimumDistanceFromCenterRadiusFactor = 1.f
/ 3.f
14 let minimumAreaFactor = 1.1f
16 type private EllipseScoreFlaggedKd (matchingScore
: float32
, e
: Ellipse) =
17 let mutable matchingScore = matchingScore
19 member this
.Ellipse = e
21 member this
.MatchingScore = matchingScore
23 member this
.AddMatchingScore (score
: float32
) =
24 matchingScore <- matchingScore + score
26 member val Processed = false with get
, set
27 member val Removed = false with get
, set
29 interface KdTree.I2DCoords with
30 member this
.X = this
.Ellipse.Cx
31 member this
.Y = this
.Ellipse.Cy
33 type MatchingEllipses (radius
: float32
) =
34 let ellipses = List<EllipseScoreFlaggedKd> ()
36 member this
.Add (e
: Ellipse) =
37 ellipses.Add (EllipseScoreFlaggedKd (0.f
, e
))
39 member this
.Ellipses : Ellipse list =
40 List.ofSeq
ellipses |> List.map
(fun e
-> e
.Ellipse)
42 member this
.PrunedEllipses : Ellipse list =
43 if ellipses.Count = 0 then
46 // 1) Create a kd-tree from the ellipses list.
47 let tree = KdTree.Tree.BuildTree (List.ofSeq
ellipses)
49 // 2) Compute the matching score of each ellipses.
50 let windowSize = radius
* windowSizeRadiusFactor
53 let areaE = e
.Ellipse.Area
56 KdTree.minX
= e
.Ellipse.Cx - windowSize / 2.f
57 KdTree.maxX
= e
.Ellipse.Cx + windowSize / 2.f
58 KdTree.minY
= e
.Ellipse.Cy - windowSize / 2.f
59 KdTree.maxY
= e
.Ellipse.Cy + windowSize / 2.f
61 for other
in tree.Search window do
62 if not
other.Processed then
63 let areaOther = other.Ellipse.Area
64 match EEOver.EEOverlapArea e
.Ellipse other.Ellipse with
65 | Some (overlapArea
, _
, _
)
66 // Because of approximation error, see https://github.com/chraibi/EEOver/issues/4
67 when overlapArea
- areaE < 1.f
&& overlapArea
- areaOther < 1.f
->
68 let matchingScore = (2.f
* overlapArea
/ (areaE + areaOther)) ** matchingScorePower
69 other.AddMatchingScore matchingScore
70 e
.AddMatchingScore matchingScore
73 // 3) Remove ellipses whose center is near the center of another ellipse with a better score.
74 let matchingScoreThreshold = matchingScoreThresholdPerRadiusUnit * radius
76 if e
.MatchingScore < matchingScoreThreshold then
81 KdTree.minX
= e
.Ellipse.Cx - e
.Ellipse.A
82 KdTree.maxX
= e
.Ellipse.Cx + e
.Ellipse.A
83 KdTree.minY
= e
.Ellipse.Cy - e
.Ellipse.A
84 KdTree.maxY
= e
.Ellipse.Cy + e
.Ellipse.A
86 for other in tree.Search window do
87 if not
other.Removed && e
.MatchingScore > other.MatchingScore then
88 // Case where ellipses are too close.
89 if distanceTwoPoints
(PointF (e
.Ellipse.Cx, e
.Ellipse.Cy)) (PointF (other.Ellipse.Cx, other.Ellipse.Cy)) < minimumDistanceFromCenterRadiusFactor * e
.Ellipse.B then
92 // Case where ellipses are overlapped.
93 match EEOver.EEOverlapArea e
.Ellipse other.Ellipse with
94 | Some (overlapArea
, _, _) when e
.Ellipse.Area < minimumAreaFactor * overlapArea
|| other.Ellipse.Area < minimumAreaFactor * overlapArea
->
100 |> List.filter
(fun e
-> not
e.Removed)
101 |> List.sortWith
(fun e1 e2
-> e2
.MatchingScore.CompareTo e1
.MatchingScore)
102 |> List.map
(fun e -> e.Ellipse)