Cleaning, micro-optimizations.
[master-thesis.git] / Parasitemia / Parasitemia / MatchingEllipses.fs
1 module MatchingEllipses
2
3 open System
4 open System.Linq
5 open System.Collections
6 open System.Collections.Generic
7
8 open Types
9 open Utils
10
11
12 // Do not take in account matching score below this when two ellipses are matched.
13 let matchingScoreThreshold1 = 0.6
14
15 let scaleOverlapTest = 0.8
16
17 type private EllipseScoreFlaggedKd (matchingScore: float, e: Ellipse) =
18 let mutable matchingScore = matchingScore
19
20 member this.Ellipse = e
21
22 member this.MatchingScore = matchingScore
23
24 member this.AddMatchingScore(score: float) =
25 matchingScore <- matchingScore + score
26
27 member val Processed = false with get, set
28 member val Removed = false with get, set
29
30 interface KdTree.I2DCoords with
31 member this.X = this.Ellipse.Cx
32 member this.Y = this.Ellipse.Cy
33
34
35 type MatchingEllipses (radiusMin: float) =
36 let ellipses = List<EllipseScoreFlaggedKd>()
37
38 // All ellipses with a score below this are removed.
39 let matchingScoreThreshold2 = 20. * radiusMin
40
41 member this.Add (e: Ellipse) =
42 ellipses.Add(EllipseScoreFlaggedKd(0.0, e))
43
44 member this.Ellipses : Ellipse list =
45 List.ofSeq ellipses |> List.map (fun e -> e.Ellipse)
46
47 // Process all ellipses and return ellipses ordered from the best score to the worst.
48 member this.PrunedEllipses : Ellipse list =
49 if ellipses.Count = 0
50 then
51 []
52 else
53 // 1) Create a kd-tree from the ellipses list.
54 let tree = KdTree.Tree.BuildTree (List.ofSeq ellipses)
55
56 // 2) Compute the matching score of each ellipses.
57 let windowSize = radiusMin
58 for e in ellipses do
59 e.Processed <- true
60 let areaE = e.Ellipse.Area
61 let window = { KdTree.minX = e.Ellipse.Cx - windowSize / 2.0
62 KdTree.maxX = e.Ellipse.Cx + windowSize / 2.0
63 KdTree.minY = e.Ellipse.Cy - windowSize / 2.0
64 KdTree.maxY = e.Ellipse.Cy + windowSize / 2.0 }
65 for other in tree.Search window do
66 if not other.Processed
67 then
68 let areaOther = other.Ellipse.Area
69 match EEOver.EEOverlapArea e.Ellipse other.Ellipse with
70 | Some (commonArea, _, _) ->
71 let matchingScore = 2.0 * commonArea / (areaE + areaOther)
72 if matchingScore >= matchingScoreThreshold1
73 then
74 other.AddMatchingScore(matchingScore * e.Ellipse.Perimeter)
75 e.AddMatchingScore(matchingScore * other.Ellipse.Perimeter)
76 | _ -> ()
77
78 // 3) Sort ellipses by their score.
79 ellipses.Sort(fun e1 e2 -> e2.MatchingScore.CompareTo(e1.MatchingScore))
80
81 // 4) Remove ellipses with a low score.
82 let i = ellipses.BinarySearch(EllipseScoreFlaggedKd(matchingScoreThreshold2, Ellipse(0.0, 0.0, 0.0, 0.0, 0.0)),
83 { new IComparer<EllipseScoreFlaggedKd> with
84 member this.Compare(e1, e2) = e2.MatchingScore.CompareTo(e1.MatchingScore) }) |> abs
85 let nbToRemove = ellipses.Count - i
86 if nbToRemove > 0
87 then
88 for j in i .. nbToRemove - 1 do
89 ellipses.[j].Removed <- true
90 ellipses.RemoveRange(i, nbToRemove)
91
92 // 5) Remove ellipses whose center is into an ellipse with a better score
93 for e in ellipses do
94 if not e.Removed
95 then
96 let window = { KdTree.minX = e.Ellipse.Cx - e.Ellipse.A
97 KdTree.maxX = e.Ellipse.Cx + e.Ellipse.A
98 KdTree.minY = e.Ellipse.Cy - e.Ellipse.A
99 KdTree.maxY = e.Ellipse.Cy + e.Ellipse.A }
100 for other in tree.Search window do
101 if not other.Removed && other.MatchingScore < e.MatchingScore
102 then
103 if e.Ellipse.Scale(scaleOverlapTest).Contains other.Ellipse.Cx other.Ellipse.Cy
104 then
105 other.Removed <- true
106 ellipses.RemoveAll(fun e -> e.Removed) |> ignore
107
108 List.ofSeq ellipses |> List.map (fun e -> e.Ellipse)
109
110
111