Doc + cleaning.
[master-thesis.git] / Parasitemia / ParasitemiaCore / MatchingEllipses.fs
1 module ParasitemiaCore.MatchingEllipses
2
3 open System
4 open System.Drawing
5 open System.Linq
6 open System.Collections
7 open System.Collections.Generic
8
9 open Types
10 open Utils
11
12 // All ellipses with a score below this are removed.
13 let matchingScoreThresholdPerRadiusUnit = 0.07f // For a radius of 1. // 0.25
14 let matchingScorePower = 20.f
15 let windowSizeRadiusFactor = 1.f / 2.f
16 let minimumDistanceFromCenterRadiusFactor = 1.f / 3.f
17
18 type private EllipseScoreFlaggedKd (matchingScore: float32, e: Ellipse) =
19 let mutable matchingScore = matchingScore
20
21 member this.Ellipse = e
22
23 member this.MatchingScore = matchingScore
24
25 member this.AddMatchingScore (score: float32) =
26 matchingScore <- matchingScore + score
27
28 member val Processed = false with get, set
29 member val Removed = false with get, set
30
31 interface KdTree.I2DCoords with
32 member this.X = this.Ellipse.Cx
33 member this.Y = this.Ellipse.Cy
34
35 type MatchingEllipses (radius: float32) =
36 let ellipses = List<EllipseScoreFlaggedKd>()
37
38 member this.Add (e: Ellipse) =
39 ellipses.Add(EllipseScoreFlaggedKd(0.f, e))
40
41 member this.Ellipses : Ellipse list =
42 List.ofSeq ellipses |> List.map (fun e -> e.Ellipse)
43
44 member this.PrunedEllipses : Ellipse list =
45 if ellipses.Count = 0
46 then
47 []
48 else
49 // 1) Create a kd-tree from the ellipses list.
50 let tree = KdTree.Tree.BuildTree (List.ofSeq ellipses)
51
52 // 2) Compute the matching score of each ellipses.
53 let windowSize = radius * windowSizeRadiusFactor
54 for e in ellipses do
55 e.Processed <- true
56 let areaE = e.Ellipse.Area
57 let window = { KdTree.minX = e.Ellipse.Cx - windowSize / 2.f
58 KdTree.maxX = e.Ellipse.Cx + windowSize / 2.f
59 KdTree.minY = e.Ellipse.Cy - windowSize / 2.f
60 KdTree.maxY = e.Ellipse.Cy + windowSize / 2.f }
61 for other in tree.Search window do
62 if not other.Processed
63 then
64 let areaOther = other.Ellipse.Area
65 match EEOver.EEOverlapArea e.Ellipse other.Ellipse with
66 | Some (overlapArea, _, _)
67 // Because of approximation error, see https://github.com/chraibi/EEOver/issues/4
68 when overlapArea - areaE < 1.f && overlapArea - areaOther < 1.f ->
69 let matchingScore = (2.f * overlapArea / (areaE + areaOther)) ** matchingScorePower
70 other.AddMatchingScore(matchingScore)
71 e.AddMatchingScore(matchingScore)
72 | _ -> ()
73
74 // 3) Remove ellipses whose center is near the center of another ellipse with a better score.
75 let matchingScoreThreshold = matchingScoreThresholdPerRadiusUnit * radius
76 for e in ellipses do
77 if e.MatchingScore < matchingScoreThreshold
78 then
79 e.Removed <- true
80 else
81 let window = { 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 }
85 for other in tree.Search window do
86 if not other.Removed && e.MatchingScore > other.MatchingScore
87 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
90 then
91 other.Removed <- true
92 else
93 // Case where ellipses are overlapped.
94 match EEOver.EEOverlapArea e.Ellipse other.Ellipse with
95 | Some (overlapArea, _, _) when e.Ellipse.Area < 1.1f * overlapArea || other.Ellipse.Area < 1.1f * overlapArea ->
96 other.Removed <- true
97 | _ ->
98 ()
99 ellipses
100 |> List.ofSeq
101 |> List.filter (fun e -> not e.Removed)
102 |> List.sortWith (fun e1 e2 -> e2.MatchingScore.CompareTo(e1.MatchingScore))
103 |> List.map (fun e -> e.Ellipse)
104