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