Change the way the dark stain is marked.
[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 // Used when searching for neighbor ellipses.
16 let minimumDistanceFromCenterRadiusFactor = 1.f / 3.f
17 let minimumAreaFactor = 1.1f;
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 type MatchingEllipses (radius: float32) =
37 let ellipses = List<EllipseScoreFlaggedKd>()
38
39 member this.Add (e: Ellipse) =
40 ellipses.Add(EllipseScoreFlaggedKd(0.f, e))
41
42 member this.Ellipses : Ellipse list =
43 List.ofSeq ellipses |> List.map (fun e -> e.Ellipse)
44
45 member this.PrunedEllipses : Ellipse list =
46 if ellipses.Count = 0
47 then
48 []
49 else
50 // 1) Create a kd-tree from the ellipses list.
51 let tree = KdTree.Tree.BuildTree (List.ofSeq ellipses)
52
53 // 2) Compute the matching score of each ellipses.
54 let windowSize = radius * windowSizeRadiusFactor
55 for e in ellipses do
56 e.Processed <- true
57 let areaE = e.Ellipse.Area
58 let window = { KdTree.minX = e.Ellipse.Cx - windowSize / 2.f
59 KdTree.maxX = e.Ellipse.Cx + windowSize / 2.f
60 KdTree.minY = e.Ellipse.Cy - windowSize / 2.f
61 KdTree.maxY = e.Ellipse.Cy + windowSize / 2.f }
62 for other in tree.Search window do
63 if not other.Processed
64 then
65 let areaOther = other.Ellipse.Area
66 match EEOver.EEOverlapArea e.Ellipse other.Ellipse with
67 | Some (overlapArea, _, _)
68 // Because of approximation error, see https://github.com/chraibi/EEOver/issues/4
69 when overlapArea - areaE < 1.f && overlapArea - areaOther < 1.f ->
70 let matchingScore = (2.f * overlapArea / (areaE + areaOther)) ** matchingScorePower
71 other.AddMatchingScore(matchingScore)
72 e.AddMatchingScore(matchingScore)
73 | _ -> ()
74
75 // 3) Remove ellipses whose center is near the center of another ellipse with a better score.
76 let matchingScoreThreshold = matchingScoreThresholdPerRadiusUnit * radius
77 for e in ellipses do
78 if e.MatchingScore < matchingScoreThreshold
79 then
80 e.Removed <- true
81 else
82 let window = { KdTree.minX = e.Ellipse.Cx - e.Ellipse.A
83 KdTree.maxX = e.Ellipse.Cx + e.Ellipse.A
84 KdTree.minY = e.Ellipse.Cy - e.Ellipse.A
85 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
88 then
89 // Case where ellipses are too close.
90 if distanceTwoPoints (PointF(e.Ellipse.Cx, e.Ellipse.Cy)) (PointF(other.Ellipse.Cx, other.Ellipse.Cy)) < minimumDistanceFromCenterRadiusFactor * e.Ellipse.B
91 then
92 other.Removed <- true
93 else
94 // Case where ellipses are overlapped.
95 match EEOver.EEOverlapArea e.Ellipse other.Ellipse with
96 | Some (overlapArea, _, _) when e.Ellipse.Area < minimumAreaFactor * overlapArea || other.Ellipse.Area < minimumAreaFactor * overlapArea ->
97 other.Removed <- true
98 | _ ->
99 ()
100 ellipses
101 |> List.ofSeq
102 |> List.filter (fun e -> not e.Removed)
103 |> List.sortWith (fun e1 e2 -> e2.MatchingScore.CompareTo(e1.MatchingScore))
104 |> List.map (fun e -> e.Ellipse)
105