From 5b68d946369f998865d2dd330fd3b374b2b9a0ad Mon Sep 17 00:00:00 2001 From: Greg Burri Date: Mon, 14 Dec 2015 14:44:51 +0100 Subject: [PATCH] Improve the thinning process performance. --- Parasitemia/Parasitemia/Classifier.fs | 9 ++- Parasitemia/Parasitemia/ImgTools.fs | 70 ++++++++-------------- Parasitemia/Parasitemia/MainAnalysis.fs | 9 ++- Parasitemia/Parasitemia/Parasitemia.fsproj | 4 +- Parasitemia/Parasitemia/Program.fs | 11 ++-- Parasitemia/Parasitemia/Types.fs | 13 ++-- 6 files changed, 48 insertions(+), 68 deletions(-) diff --git a/Parasitemia/Parasitemia/Classifier.fs b/Parasitemia/Parasitemia/Classifier.fs index 37507c5..52e7dc6 100644 --- a/Parasitemia/Parasitemia/Classifier.fs +++ b/Parasitemia/Parasitemia/Classifier.fs @@ -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 diff --git a/Parasitemia/Parasitemia/ImgTools.fs b/Parasitemia/Parasitemia/ImgTools.fs index c34b9c7..f7e5a4a 100644 --- a/Parasitemia/Parasitemia/ImgTools.fs +++ b/Parasitemia/Parasitemia/ImgTools.fs @@ -25,62 +25,45 @@ let gaussianFilter (img : Image<'TColor, 'TDepth>) (standardDeviation : float) : // Zhang and Suen algorithm. // Modify 'mat' in place. let thin (mat: Matrix) = - 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 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) = 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); diff --git a/Parasitemia/Parasitemia/MainAnalysis.fs b/Parasitemia/Parasitemia/MainAnalysis.fs index 3a38c4e..c9e9422 100644 --- a/Parasitemia/Parasitemia/MainAnalysis.fs +++ b/Parasitemia/Parasitemia/MainAnalysis.fs @@ -59,6 +59,7 @@ let doAnalysis (img: Image) (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) (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) (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 diff --git a/Parasitemia/Parasitemia/Parasitemia.fsproj b/Parasitemia/Parasitemia/Parasitemia.fsproj index e3eff91..b9316f1 100644 --- a/Parasitemia/Parasitemia/Parasitemia.fsproj +++ b/Parasitemia/Parasitemia/Parasitemia.fsproj @@ -28,7 +28,7 @@ x64 bin\Debug\Parasitemia.XML false - --folder "../../../../Images/08.10.2015/test" --output "../../../Images/output" --debug + --folder "../../../../Images/08.10.2015/debug" --output "../../../Images/output" --debug pdbonly @@ -40,7 +40,7 @@ x64 bin\Release\Parasitemia.XML false - --folder "../../../../Images/08.10.2015/Without scale" --output "../../../Images/output" --debug + --folder "../../../../Images/08.10.2015/release" --output "../../../Images/output" --debug 11 diff --git a/Parasitemia/Parasitemia/Program.fs b/Parasitemia/Parasitemia/Program.fs index ac17c82..76fc244 100644 --- a/Parasitemia/Parasitemia/Program.fs +++ b/Parasitemia/Parasitemia/Program.fs @@ -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(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 diff --git a/Parasitemia/Parasitemia/Types.fs b/Parasitemia/Parasitemia/Types.fs index e93bbb0..d7bacbd 100644 --- a/Parasitemia/Parasitemia/Types.fs +++ b/Parasitemia/Parasitemia/Types.fs @@ -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 } - +[] type Line (a: float, b: float) = member this.A = a member this.B = b +[] type PointD (x: float, y: float) = member this.X = x member this.Y = y -- 2.43.0