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
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
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
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
// 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
data2 <- tmp
-
let pop (l: List<'a>) : 'a =
let n = l.[l.Count - 1]
l.RemoveAt(l.Count - 1)
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);
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
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")
let imgCells' = img.Copy()
drawCells imgCells' true cells
- saveImg imgCells' (buildFileName " - cells - full.png")
+ saveImg imgCells' (buildFileName " - cells - full.png")*)
| _ -> ()
cells
<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>
<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>
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
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
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
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