Improve the thinning process performance.
authorGreg Burri <greg.burri@gmail.com>
Mon, 14 Dec 2015 13:44:51 +0000 (14:44 +0100)
committerGreg Burri <greg.burri@gmail.com>
Mon, 14 Dec 2015 13:44:51 +0000 (14:44 +0100)
Parasitemia/Parasitemia/Classifier.fs
Parasitemia/Parasitemia/ImgTools.fs
Parasitemia/Parasitemia/MainAnalysis.fs
Parasitemia/Parasitemia/Parasitemia.fsproj
Parasitemia/Parasitemia/Program.fs
Parasitemia/Parasitemia/Types.fs

index 37507c5..52e7dc6 100644 (file)
@@ -41,8 +41,9 @@ let findCells (ellipses: Ellipse list) (parasites: ParasitesMarker.Result) (fg:
             cx - a, cy - a, cx + a, cy + a
 
         // 1) Remove ellipses touching the edges.
-        let ellipsesInside = ellipses |> List.map (fun e ->
-            EllipseFlaggedKd (e, Removed = e.isOutside (float fg.Width) (float fg.Height)))
+        let w = float fg.Width
+        let h = float fg.Height
+        let ellipsesInside = ellipses |> List.map (fun e -> EllipseFlaggedKd (e, Removed = e.isOutside w h))
 
         // 2) Associate touching ellipses with each ellipses.
         let tree = KdTree.Tree.BuildTree ellipsesInside
@@ -59,7 +60,6 @@ let findCells (ellipses: Ellipse list) (parasites: ParasitesMarker.Result) (fg:
         let ellipsesWithNeigbors = ellipsesInside |> List.choose (fun e -> if e.Removed then None else Some (e, neighbors e))
 
         // 3) Remove ellipse with a lower percentage of foreground.
-        let fgData = fg.Data
         for e, neighbors in ellipsesWithNeigbors do
             let minX, minY, maxX, maxY = ellipseWindow e
 
@@ -72,7 +72,7 @@ let findCells (ellipses: Ellipse list) (parasites: ParasitesMarker.Result) (fg:
                     if e.Contains xf yf && neighbors |> List.forall (fun (otherE, _, _) -> not <| otherE.Contains xf yf)
                     then
                         totalElement <- totalElement + 1
-                        if fgData.[y, x, 0] > 0uy
+                        if fg.Data.[y, x, 0] > 0uy
                         then
                             fgElement <- fgElement + 1
 
@@ -94,7 +94,6 @@ let findCells (ellipses: Ellipse list) (parasites: ParasitesMarker.Result) (fg:
                     yield sign (c.X - p.X) <> sign (c.X - p'.X) || Utils.squaredDistanceTwoPoints c p' > Utils.squaredDistanceTwoPoints c p // 'false' -> the point is owned by another ellipse.
             } |> Seq.forall id
 
-
         ellipsesWithNeigbors
         |> List.choose (fun (e, neighbors) ->
             if e.Removed
index c34b9c7..f7e5a4a 100644 (file)
@@ -25,62 +25,45 @@ let gaussianFilter (img : Image<'TColor, 'TDepth>) (standardDeviation : float) :
 // Zhang and Suen algorithm.
 // Modify 'mat' in place.
 let thin (mat: Matrix<byte>) =
-    let neighbors = [|
-        (-1,  0) // p2
-        (-1,  1) // p3
-        ( 0,  1) // p4
-        ( 1,  1) // p5
-        ( 1,  0) // p6
-        ( 1, -1) // p7
-        ( 0, -1) // p8
-        (-1, -1) |] // p9
-
     let w = mat.Width
     let h = mat.Height
     let mutable data1 = mat.Data
-    let mutable data2 = Array2D.zeroCreate<byte> h w
-
-    // Return the list of neighbor values.
-    let neighborsValues (p1i, p1j) =
-        Array.map (fun (ni, nj) ->
-            let pi = p1i + ni
-            let pj = p1j + nj
-            if pi < 0 || pi >= h || pj < 0 || pj >= w then 0uy else data1.[pi, pj]
-            ) neighbors
-
-    // Return the number of 01 pattern in 'values' in a circular way.
-    let pattern01 (values: byte[]) =
-        let mutable nb = 0
-        let mutable lastValue = 255uy
-        for v in values do
-            if lastValue = 0uy && v = 1uy
-            then
-                nb <- nb + 1
-            lastValue <- v
-        if lastValue = 0uy && values.[0] = 1uy
-        then
-            nb <- nb + 1
-        nb
+    let mutable data2 = Array2D.copy data1
 
     let mutable pixelChanged = true
     let mutable oddIteration = true
+
     while pixelChanged do
         pixelChanged <- false
         for i in 0..h-1 do
             for j in 0..w-1 do
                 if data1.[i, j] = 1uy
                 then
-                    let values = neighborsValues (i, j)
-                    let s = Array.reduce (+) values
-                    if s >= 2uy && s <= 6uy &&
-                        pattern01 values = 1 &&
-                        (not oddIteration || (values.[0] * values.[2] * values.[4] = 0uy && values.[2] * values.[4] * values.[6] = 0uy)) && // Odd iteration.
-                        (oddIteration || (values.[0] * values.[2] * values.[6] = 0uy && values.[0] * values.[4] * values.[6] = 0uy)) // Even iterations.
+                    let p2 = if i = 0 then 0uy else data1.[i-1, j]
+                    let p3 = if i = 0 || j = w-1 then 0uy else data1.[i-1, j+1]
+                    let p4 = if j = w-1 then 0uy else data1.[i, j+1]
+                    let p5 = if i = h-1 || j = w-1 then 0uy else data1.[i+1, j+1]
+                    let p6 = if i = h-1 then 0uy else data1.[i+1, j]
+                    let p7 = if i = h-1 || j = 0 then 0uy else data1.[i+1, j-1]
+                    let p8 = if j = 0 then 0uy else data1.[i, j-1]
+                    let p9 = if i = 0 || j = 0 then 0uy else data1.[i-1, j-1]
+
+                    let sumNeighbors = p2 + p3 + p4 + p5 + p6 + p7 + p8 + p9
+                    if sumNeighbors >= 2uy && sumNeighbors <= 6uy &&
+                        (if p2 = 0uy && p3 = 1uy then 1 else 0) +
+                        (if p3 = 0uy && p4 = 1uy then 1 else 0) +
+                        (if p4 = 0uy && p5 = 1uy then 1 else 0) +
+                        (if p5 = 0uy && p6 = 1uy then 1 else 0) +
+                        (if p6 = 0uy && p7 = 1uy then 1 else 0) +
+                        (if p7 = 0uy && p8 = 1uy then 1 else 0) +
+                        (if p8 = 0uy && p9 = 1uy then 1 else 0) +
+                        (if p9 = 0uy && p2 = 1uy then 1 else 0) = 1 &&
+                        if oddIteration
+                        then p2 * p4 * p6 = 0uy && p4 * p6 * p8 = 0uy
+                        else p2 * p4 * p8 = 0uy && p2 * p6 * p8 = 0uy
                     then
                         data2.[i, j] <- 0uy
                         pixelChanged <- true
-                    else
-                        data2.[i, j] <- 1uy
                 else
                     data2.[i, j] <- 0uy
 
@@ -90,7 +73,6 @@ let thin (mat: Matrix<byte>) =
         data2 <- tmp
 
 
-
 let pop (l: List<'a>) : 'a =
     let n = l.[l.Count - 1]
     l.RemoveAt(l.Count - 1)
@@ -174,10 +156,6 @@ let saveMat (mat: Matrix<'TDepth>) (filepath: string) =
     mat.CopyTo(img)
     saveImg img filepath
 
-(*let drawEllipse (img: Image<'TColor, 'TDepth>) (e: Types.Ellipse) (color: 'TColor) =
-    let e' = Ellipse(PointF(float32 e.cx, float32 e.cy), SizeF(2.0f * float32 e.a, 2.0f * float32 e.b), float32 e.alpha)
-    img.Draw(e', color)*)
-
 let drawLine (img: Image<'TColor, 'TDepth>) (color: 'TColor) (x0: int) (y0: int) (x1: int) (y1: int) =
     img.Draw(LineSegment2D(Point(x0, y0), Point(x1, y1)), color, 1);
 
index 3a38c4e..c9e9422 100644 (file)
@@ -59,6 +59,7 @@ let doAnalysis (img: Image<Bgr, byte>) (name: string) (config: Config) : Cell li
     logTime "Finding edges" (fun() -> thin edges)
     logTime "Removing small connected components from thinning" (fun () -> removeArea edges 12)
 
+    (*
     let kmediansResults = KMedians.kmedians filteredGreen 1.0
 
     let parasites = ParasitesMarker.find green filteredGreen kmediansResults config
@@ -70,12 +71,16 @@ let doAnalysis (img: Image<Bgr, byte>) (name: string) (config: Config) : Cell li
         Ellipse.find edges xEdges yEdges radiusRange windowSize factorNbPick)
 
     let cells = logTime "Classifier" (fun () -> Classifier.findCells ellipses parasites kmediansResults.fg config)
+    *)
+    let cells = []
 
+    // Output pictures if debug flag is set.
     match config.debug with
     | DebugOn output ->
         let buildFileName postfix = System.IO.Path.Combine(output, name + postfix)
         saveMat (edges * 255.0) (buildFileName " - edges.png")
-        saveImg parasites.darkStain (buildFileName " - parasites - dark stain.png")
+
+        (*saveImg parasites.darkStain (buildFileName " - parasites - dark stain.png")
         saveImg parasites.stain (buildFileName " - parasites - stain.png")
         saveImg parasites.infection (buildFileName " - parasites - infection.png")
 
@@ -91,7 +96,7 @@ let doAnalysis (img: Image<Bgr, byte>) (name: string) (config: Config) : Cell li
 
         let imgCells' = img.Copy()
         drawCells imgCells' true cells
-        saveImg imgCells' (buildFileName " - cells - full.png")
+        saveImg imgCells' (buildFileName " - cells - full.png")*)
     | _ -> ()
 
     cells
index e3eff91..b9316f1 100644 (file)
@@ -28,7 +28,7 @@
     <PlatformTarget>x64</PlatformTarget>
     <DocumentationFile>bin\Debug\Parasitemia.XML</DocumentationFile>
     <Prefer32Bit>false</Prefer32Bit>
-    <StartArguments>--folder "../../../../Images/08.10.2015/test" --output "../../../Images/output" --debug</StartArguments>
+    <StartArguments>--folder "../../../../Images/08.10.2015/debug" --output "../../../Images/output" --debug</StartArguments>
   </PropertyGroup>
   <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
     <DebugType>pdbonly</DebugType>
@@ -40,7 +40,7 @@
     <PlatformTarget>x64</PlatformTarget>
     <DocumentationFile>bin\Release\Parasitemia.XML</DocumentationFile>
     <Prefer32Bit>false</Prefer32Bit>
-    <StartArguments>--folder "../../../../Images/08.10.2015/Without scale" --output "../../../Images/output" --debug</StartArguments>
+    <StartArguments>--folder "../../../../Images/08.10.2015/release" --output "../../../Images/output" --debug</StartArguments>
   </PropertyGroup>
   <PropertyGroup>
     <MinimumVisualStudioVersion Condition="'$(MinimumVisualStudioVersion)' == ''">11</MinimumVisualStudioVersion>
index ac17c82..76fc244 100644 (file)
@@ -91,21 +91,24 @@ let main args =
         match mode with
         | CmdLine (input, output) ->
             let config = { config with debug = DebugOn output }
-            Utils.log <- (fun m -> Console.WriteLine m)
 
             Directory.CreateDirectory output |> ignore
 
+            use logFile = new StreamWriter(new FileStream(Path.Combine(output, "log.txt"), FileMode.Append, FileAccess.Write))
+            Utils.log <- (fun m -> logFile.WriteLine(m))
+            Utils.log (sprintf "=== New run : %A ===" DateTime.Now)
+
             let files = match input with
                         | File file -> [ file ]
                         | Dir dir -> Directory.EnumerateFiles dir |> List.ofSeq
 
-            use resultFile = new StreamWriter(new FileStream(Path.Combine(output, "results.txt"), FileMode.Create, FileAccess.Write))
+            use resultFile = new StreamWriter(new FileStream(Path.Combine(output, "results.txt"), FileMode.Append, FileAccess.Write))
 
             for file in files do
                 try
-                    let fileInfo = FileInfo(file)
                     use img = new Image<Bgr, byte>(file)
-                    let cells = Utils.logTime "Whole analyze" (fun () -> ImageAnalysis.doAnalysis img fileInfo.Name config)
+                    Utils.log (sprintf "== File: %A" file)
+                    let cells = Utils.logTime "Whole analyze" (fun () -> ImageAnalysis.doAnalysis img (FileInfo(file).Name) config)
                     let total, infected = Utils.countCells cells
                     fprintf resultFile "File: %s %d %d %.2f\n" file total infected (100. * (float infected) / (float total))
                 with
index e93bbb0..d7bacbd 100644 (file)
@@ -18,12 +18,6 @@ type Ellipse (cx: float, cy: float, a: float, b: float, alpha: float) =
     member this.Contains x y =
         ((x - cx) * cos alpha + (y - cy) * sin alpha) ** 2.0 / a ** 2.0 + ((x - cx) * sin alpha - (y - cy) * cos alpha) ** 2.0 / b ** 2.0 <= 1.0
 
-    // A line is defined as : y = mx + l
-    member this.CutALine (m: float) (l: float) : bool =
-        -2.0 * l ** 2.0 + a ** 2.0 + m ** 2.0 * a ** 2.0 + b ** 2.0 + m ** 2.0 * b ** 2.0 -
-        4.0 * m * l * cx - 2.0 * m ** 2.0 * cx ** 2.0 + 4.0 * l * cy + 4.0 * m * cx * cy -
-        2.0 * cy ** 2.0 + (-1.0 + m ** 2.0) * (a ** 2.0 - b ** 2.0) * cos (2.0 * alpha) - 2.0 * m * (a ** 2.0 - b ** 2.0) * sin (2.0 * alpha) > 0.0
-
     member this.CutAVericalLine (y: float) : bool =
         a ** 2.0 + b ** 2.0 - 2.0 * y ** 2.0 + 4.0 * y * cx - 2.0 * cx ** 2.0 + a ** 2.0 * cos (2.0 * alpha) - b ** 2.0 * cos (2.0 * alpha) > 0.0
 
@@ -31,8 +25,8 @@ type Ellipse (cx: float, cy: float, a: float, b: float, alpha: float) =
         a ** 2.0 + b ** 2.0 - 2.0 * x ** 2.0 + 4.0 * x * cy - 2.0 * cy ** 2.0 - a ** 2.0 * cos (2.0 * alpha) + b ** 2.0 * cos (2.0 * alpha) > 0.0
 
     member this.isOutside (width: float) (height: float) =
-        this.Cx <= 0.0 || this.Cx >= width ||
-        this.Cy <= 0.0 || this.Cy >= height ||
+        this.Cx < 0.0 || this.Cx >= width ||
+        this.Cy < 0.0 || this.Cy >= height ||
         this.CutAVericalLine 0.0 || this.CutAVericalLine width ||
         this.CutAnHorizontalLine 0.0 || this.CutAnHorizontalLine height
 
@@ -44,11 +38,12 @@ type Cell = {
     center: Point
     elements: Matrix<byte> }
 
-
+[<Struct>]
 type Line (a: float, b: float) =
     member this.A = a
     member this.B = b
 
+[<Struct>]
 type PointD (x: float, y: float) =
     member this.X = x
     member this.Y = y