Project the colors to have the best contrast for RBCs and parasites analyze.
[master-thesis.git] / Parasitemia / ParasitemiaCore / Classifier.fs
index 0bea084..96532f8 100644 (file)
@@ -10,7 +10,6 @@ open Emgu.CV.Structure
 open Types
 open Utils
 
-
 type private EllipseFlaggedKd (e: Ellipse) =
     inherit Ellipse (e.Cx, e.Cy, e.A, e.B, e.Alpha)
 
@@ -20,7 +19,6 @@ type private EllipseFlaggedKd (e: Ellipse) =
         member this.X = this.Cx
         member this.Y = this.Cy
 
-
 let findCells (ellipses: Ellipse list) (parasites: ParasitesMarker.Result) (img: Image<Gray, float32>) (config: Config.Config) : Cell list =
     if ellipses.IsEmpty
     then
@@ -47,24 +45,30 @@ let findCells (ellipses: Ellipse list) (parasites: ParasitesMarker.Result) (img:
 
         // Return 'true' if the point 'p' is owned by e.
         // The lines represents all intersections with other ellipses.
-        let pixelOwnedByE (p: PointD) (e: Ellipse) (others: (Ellipse * Line) list) =
+        let pixelOwnedByE (p: PointF) (e: Ellipse) (others: (Ellipse * Line) list) =
             e.Contains p.X p.Y &&
             seq {
-                let c = PointD(e.Cx, e.Cy)
+                let c = PointF(e.Cx, e.Cy)
+
                 for e', d1 in others do
-                    let d2 = Utils.lineFromTwoPoints c p
-                    let c' = PointD(e'.Cx, e'.Cy)
+                    let d2 = lineFromTwoPoints c p
+                    let c' = PointF(e'.Cx, e'.Cy)
                     let v = pointFromTwoLines d1 (lineFromTwoPoints c c')
                     let case1 = sign (v.X - c.X) <> sign (v.X - c'.X) || Utils.squaredDistanceTwoPoints v c > Utils.squaredDistanceTwoPoints v c'
-                    if d2.Valid
+                    if not (Single.IsInfinity d2.A)
                     then
                         let p' = Utils.pointFromTwoLines d1 d2
+                        let delta, delta' =
+                            let dx1, dx2 = (c.X - p.X), (c.X - p'.X)
+                            // To avoid rounding issue.
+                            if abs dx1 < 0.01f || abs dx2 < 0.01f then c.Y - p.Y, c.Y - p'.Y else dx1, dx2
+
                         // Yield 'false' when the point is owned by another ellipse.
                         if case1
                         then
-                            yield sign (c.X - p.X) <> sign (c.X - p'.X) || Utils.squaredDistanceTwoPoints c p' > Utils.squaredDistanceTwoPoints c p
+                            yield sign delta <> sign delta' || Utils.squaredDistanceTwoPoints c p' > Utils.squaredDistanceTwoPoints c p
                         else
-                            yield sign (c.X - p.X) = sign (c.X - p'.X) && Utils.squaredDistanceTwoPoints c p' < Utils.squaredDistanceTwoPoints c p
+                            yield sign delta = sign delta' && Utils.squaredDistanceTwoPoints c p' < Utils.squaredDistanceTwoPoints c p
                     else
                         yield case1
             } |> Seq.forall id
@@ -73,7 +77,7 @@ let findCells (ellipses: Ellipse list) (parasites: ParasitesMarker.Result) (img:
 
         // 1) Associate touching ellipses with each ellipses and remove ellipse with more than two intersections.
         let tree = KdTree.Tree.BuildTree ellipses
-        let neighbors (e: EllipseFlaggedKd) : (EllipseFlaggedKd * PointD * PointD) list =
+        let neighbors (e: EllipseFlaggedKd) : (EllipseFlaggedKd * PointF * PointF) list =
             if not e.Removed
             then
                 tree.Search (searchRegion e)
@@ -86,7 +90,7 @@ let findCells (ellipses: Ellipse list) (parasites: ParasitesMarker.Result) (img:
                                 otherE.Removed <- true
                                 None
                             | Some (area, px, py) when area > 0.f && px.Length = 2 ->
-                                Some (otherE, PointD(px.[0], py.[0]), PointD(px.[1], py.[1]))
+                                Some (otherE, PointF(px.[0], py.[0]), PointF(px.[1], py.[1]))
                             | _ ->
                                 None
                         else
@@ -124,7 +128,6 @@ let findCells (ellipses: Ellipse list) (parasites: ParasitesMarker.Result) (img:
                 if stdDeviation > globalStdDeviation * config.Parameters.standardDeviationMaxRatio then
                     e.Removed <- true
 
-
         // 4) Remove ellipses with little area.
         let minArea = config.RBCRadius.MinArea
         for e, neighbors in ellipsesWithNeigbors do
@@ -135,7 +138,7 @@ let findCells (ellipses: Ellipse list) (parasites: ParasitesMarker.Result) (img:
                 let mutable area = 0
                 for y in (if minY < 0 then 0 else minY) .. (if maxY >= h then h - 1 else maxY) do
                     for x in (if minX < 0 then 0 else minX) .. (if maxX >= w then w - 1 else maxX) do
-                        let p = PointD(float32 x, float32 y)
+                        let p = PointF(float32 x, float32 y)
                         if pixelOwnedByE p e (neighbors |> List.choose (fun (otherE, p1, p2) -> if otherE.Removed then None else Some (otherE :> Ellipse, Utils.lineFromTwoPoints p1 p2)))
                         then
                             area <- area + 1
@@ -145,6 +148,8 @@ let findCells (ellipses: Ellipse list) (parasites: ParasitesMarker.Result) (img:
                     e.Removed <- true
 
         // 5) Define pixels associated to each ellipse and create the cells.
+        let perimeterParasiteSquared = (2.f * config.RBCRadius.ParasiteRadius) ** 2.f |> roundInt
+        let minimumParasiteArea = config.RBCRadius.MinimumParasiteArea |> roundInt
         ellipsesWithNeigbors
         |> List.choose (fun (e, neighbors) ->
             if e.Removed
@@ -154,47 +159,64 @@ let findCells (ellipses: Ellipse list) (parasites: ParasitesMarker.Result) (img:
                 let minX, minY, maxX, maxY = ellipseWindow e
 
                 let infectedPixels = List<Point>()
-                let mutable stainPixels = 0
+                let stainPixels = List<Point>()
+
+                //let mutable stainPixels = 0
                 let mutable darkStainPixels = 0
                 let mutable nbElement = 0
 
                 let elements = new Matrix<byte>(maxY - minY + 1, maxX - minX + 1)
                 for y in minY .. maxY do
                     for x in minX .. maxX do
-                        let p = PointD(float32 x, float32 y)
+                        let p = PointF(float32 x, float32 y)
                         if pixelOwnedByE p e (neighbors |> List.choose (fun (otherE, p1, p2) -> if otherE.Removed then None else Some (otherE :> Ellipse, Utils.lineFromTwoPoints p1 p2)))
                         then
                             elements.[y-minY, x-minX] <- 1uy
                             nbElement <- nbElement + 1
 
-                            if infection.Data.[y, x, 0] > 0uy
+                            let infected = infection.Data.[y, x, 0] > 0uy
+                            let stain = parasites.stain.Data.[y, x, 0] > 0uy
+                            let darkStain = parasites.darkStain.Data.[y, x, 0] > 0uy
+
+                            if infected
                             then
                                 infectedPixels.Add(Point(x, y))
 
-                            if parasites.stain.Data.[y, x, 0] > 0uy
+                            if stain
                             then
-                                stainPixels <- stainPixels + 1
+                                stainPixels.Add(Point(x, y))
 
-                            if parasites.darkStain.Data.[y, x, 0] > 0uy
+                            if darkStain
                             then
                                 darkStainPixels <- darkStainPixels + 1
 
+                let mutable stainArea = 0
+                if infectedPixels.Count > 0
+                then
+                    for stainPixel in stainPixels do
+                        if infectedPixels.Exists(fun p -> pown (p.X - stainPixel.X) 2 + pown (p.Y - stainPixel.Y) 2 <= perimeterParasiteSquared)
+                        then
+                            stainArea <- stainArea + 1
+
+
                 let cellClass =
-                    if float darkStainPixels > config.Parameters.maxDarkStainRatio * (float nbElement) ||
-                       float stainPixels > config.Parameters.maxStainRatio * (float nbElement)
+                    if float darkStainPixels > config.Parameters.maxDarkStainRatio * (float nbElement)
+                       //|| float stainPixels > config.Parameters.maxStainRatio * (float nbElement)
                     then
                         Peculiar
-                    elif infectedPixels.Count >= 1
+
+                    elif infectedPixels.Count > 0 && stainArea >= minimumParasiteArea
                     then
                         let infectionToRemove = ImgTools.connectedComponents parasites.stain infectedPixels
                         for p in infectionToRemove do
                             infection.Data.[p.Y, p.X, 0] <- 0uy
                         InfectedRBC
+
                     else
                         HealthyRBC
 
                 Some { cellClass = cellClass
                        center = Point(roundInt e.Cx, roundInt e.Cy)
-                       infectedArea = infectedPixels.Count
-                       stainArea = stainPixels
+                       infectedArea = if cellClass = InfectedRBC then infectedPixels.Count else 0
+                       stainArea = stainArea
                        elements = elements })