let a = int (e.A + 0.5)
cx - a, cy - a, cx + a, cy + a
- // 1) Remove ellipses touching the edges.
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 ellipses = ellipses |> List.map EllipseFlaggedKd
+
+ // 1) Associate touching ellipses with each ellipses.
+ let tree = KdTree.Tree.BuildTree ellipses
let neighbors (e: Ellipse) : (EllipseFlaggedKd * PointD * PointD) list =
tree.Search (searchRegion e)
// We only keep the ellipses touching 'e'.
| _ ->
None )
- let ellipsesWithNeigbors = ellipsesInside |> List.choose (fun e -> if e.Removed then None else Some (e, neighbors e))
+ let ellipsesWithNeigbors = ellipses |> List.choose (fun e -> if e.Removed then None else Some (e, neighbors e))
- // 3) Remove ellipses with a lower percentage of foreground.
- for e, neighbors in ellipsesWithNeigbors do
+ // 2) Remove ellipses with a lower percentage of foreground. (taken from the lower score to the highest).
+ for e, neighbors in List.rev ellipsesWithNeigbors do
let minX, minY, maxX, maxY = ellipseWindow e
let mutable totalElement = 0
for y in (if minY < 0 then 0 else minY) .. (if maxY >= fg.Height then fg.Height - 1 else maxY) do
for x in (if minX < 0 then 0 else minX) .. (if maxX >= fg.Width then fg.Width - 1 else maxX) do
let yf, xf = float y, float x
- if e.Contains xf yf && neighbors |> List.forall (fun (otherE, _, _) -> not <| otherE.Contains xf yf)
+ if e.Contains xf yf && neighbors |> List.forall (fun (otherE, _, _) -> otherE.Removed || not <| otherE.Contains xf yf)
then
totalElement <- totalElement + 1
if fg.Data.[y, x, 0] > 0uy
then
e.Removed <- true
+ // 3) Remove ellipses touching the edges.
+ for e in ellipses do
+ if e.isOutside w h then e.Removed <- true
+
// 4) Remove ellipses with little area.
for e, neighbors in ellipsesWithNeigbors do
if not e.Removed
// Ellipse.A is always equal or greater than Ellipse.B.
// Ellipse.Alpha is between 0 and Pi.
let ellipse (p1x: float) (p1y: float) (m1: float) (p2x: float) (p2y: float) (m2: float) (p3x: float) (p3y: float) : Types.Ellipse option =
- let accuracy_extremum_search_1 = 4
- let accuracy_extremum_search_2 = 3
+ let accuracy_extremum_search_1 = 7 // 3
+ let accuracy_extremum_search_2 = 7 // 4
// p3 as the referencial.
let p1x = p1x - p3x
let b1 = -m1 * p1x + p1y
let b2 = -m2 * p2x + p2y
- let px = -((b1 - b2)/(m1 - m2))
- let py = -((m2 * b1 - m1 * b2)/(m1 - m2))
+ let px = -((b1 - b2) / (m1 - m2))
+ let py = -((m2 * b1 - m1 * b2) / (m1 - m2))
let rot1 = vectorRotation p1x p1y v1x v1y px py
let rot2 = vectorRotation p2x p2y v2x v2y px py
- if rot1 = rot2 || rot1 * atan2 (p1y - py) (p1x - px) + rot2 * atan2 (p2y - py) (p2x - px) <= 0.0
+ if rot1 = rot2
then
None
else
+ let alpha1 = atan2 (p1y - py) (p1x - px)
+ let alpha2 = atan2 (p2y - py) (p2x - px)
+
+ let alpha1' = if alpha1 < 0.0 then 2.0 * Math.PI + alpha1 else alpha1
+ let alpha2' = if alpha2 < 0.0 then 2.0 * Math.PI + alpha2 else alpha2
+
+ let diff = rot1 * alpha1' + rot2 * alpha2'
+
+ if diff > Math.PI || (diff < 0.0 && diff > -Math.PI)
+ then
+ None
+ else
Some (m1, m2)
+
let find (edges: Matrix<byte>)
(xDir: Image<Gray, float>)
(yDir: Image<Gray, float>)
match ellipse p1xf p1yf m1 p2xf p2yf m2 p3xf p3yf with
| Some e when e.Cx > 0.0 && e.Cx < (float w) - 1.0 && e.Cy > 0.0 && e.Cy < (float h) - 1.0 &&
e.A >= r1 - radiusTolerance && e.A <= r2 + radiusTolerance && e.B >= r1 - radiusTolerance && e.B <= r2 + radiusTolerance ->
+
+ let prout = areVectorsValid p1xf p1yf p2xf p2yf -xDirData.[p1y, p1x, 0] -yDirData.[p1y, p1x, 0] -xDirData.[p2y, p2x, 0] -yDirData.[p2y, p2x, 0]
ellipses.Add e
| _ -> ()
| _ -> ()
--- /dev/null
+module Granulometry
+
+open System
+open System.Drawing
+
+open Emgu.CV
+open Emgu.CV.Structure
+
+open Utils
+
+// 'range': a minimum and maximum radius.
+// 'scale': <= 1.0, to speed up the process.
+let findRadius (img: Image<Gray, byte>) (range: int * int) (scale: float) : int =
+ use scaledImg = if scale = 1.0 then img else img.Resize(scale, CvEnum.Inter.Area)
+
+ let r1, r2 = range
+ let r1', r2' = roundInt (float r1 * scale), roundInt (float r2 * scale)
+
+ let patternSpectrum = Array.zeroCreate (r2' - r1')
+ let intensityImg = scaledImg.GetSum().Intensity
+
+ let mutable previous_n = Double.NaN
+ for r in r1' .. r2' do
+ let se = CvInvoke.GetStructuringElement(CvEnum.ElementShape.Ellipse, Size(2 * r, 2 * r), Point(-1, -1))
+ use closed = scaledImg.MorphologyEx(CvEnum.MorphOp.Close, se, Point(-1, -1), 1, CvEnum.BorderType.Replicate, MCvScalar(0.0))
+
+ let n = 1.0 - closed.GetSum().Intensity / intensityImg
+
+ if not (Double.IsNaN previous_n)
+ then
+ patternSpectrum.[r - r1' - 1] <- abs (n - previous_n)
+ previous_n <- n
+
+ let max = patternSpectrum |> Array.indexed |> Array.fold (fun (iMax, sMax) (i, s) -> if s > sMax then (i, s) else (iMax, sMax)) (0, Double.MinValue)
+
+ 0
+
+
open System
open System.Drawing
open System.Collections.Generic
+open System.Linq
open Emgu.CV
open Emgu.CV.Structure
open Utils
+open Heap
// Normalize image values between 0uy and 255uy.
let normalizeAndConvert (img: Image<Gray, float32>) : Image<Gray, byte> =
img.MinMax(min, max, minLocation, maxLocation)
((img - (!min).[0]) / ((!max).[0] - (!min).[0]) * 255.0).Convert<Gray, byte>()
+
let gaussianFilter (img : Image<'TColor, 'TDepth>) (standardDeviation : float) : Image<'TColor, 'TDepth> =
let size = 2 * int (ceil (4.0 * standardDeviation)) + 1
img.SmoothGaussian(size, size, standardDeviation, standardDeviation)
+
+let findMaxima (img: Image<Gray, byte>) : IEnumerable<HashSet<Point>> =
+ use suppress = new Image<Gray, byte>(img.Size)
+ let w = img.Width
+ let h = img.Height
+
+ let imgData = img.Data
+ let suppressData = suppress.Data
+
+ let result = List<List<Point>>()
+
+ let flood (start: Point) : List<List<Point>> =
+ let sameLevelToCheck = Stack<Point>()
+ let betterLevelToCheck = Stack<Point>()
+ betterLevelToCheck.Push(start)
+
+ let result' = List<List<Point>>()
+
+ while betterLevelToCheck.Count > 0 do
+ let p = betterLevelToCheck.Pop()
+ if suppressData.[p.Y, p.X, 0] = 0uy
+ then
+ suppressData.[p.Y, p.X, 0] <- 1uy
+ sameLevelToCheck.Push(p)
+ let current = List<Point>()
+
+ let mutable betterExists = false
+
+ while sameLevelToCheck.Count > 0 do
+ let p' = sameLevelToCheck.Pop()
+ let currentLevel = imgData.[p'.Y, p'.X, 0]
+ current.Add(p') |> ignore
+ for i in -1 .. 1 do
+ for j in -1 .. 1 do
+ if i <> 0 || j <> 0
+ then
+ let ni = i + p'.Y
+ let nj = j + p'.X
+ if ni >= 0 && ni < h && nj >= 0 && nj < w
+ then
+ let level = imgData.[ni, nj, 0]
+ let notSuppressed = suppressData.[ni, nj, 0] = 0uy
+
+ if level = currentLevel && notSuppressed
+ then
+ suppressData.[ni, nj, 0] <- 1uy
+ sameLevelToCheck.Push(Point(nj, ni))
+ elif level > currentLevel
+ then
+ betterExists <- true
+ if notSuppressed
+ then
+ betterLevelToCheck.Push(Point(nj, ni))
+
+ if not betterExists
+ then
+ result'.Add(current)
+ result'
+
+ for i in 0 .. h - 1 do
+ for j in 0 .. w - 1 do
+ let maxima = flood (Point(j, i))
+ if maxima.Count > 0
+ then result.AddRange(maxima)
+
+ result.Select(fun l -> HashSet<Point>(l))
+
+
+type PriorityQueue () =
+ let q = List<HashSet<Point>>() // TODO: Check performance with an HasSet
+ let mutable highest = -1 // Value of the first elements of 'q'.
+
+ member this.Next () : byte * Point =
+ if this.IsEmpty
+ then
+ invalidOp "Queue is empty"
+ else
+ let l = q.[0]
+ let next = l.First()
+ l.Remove(next) |> ignore
+ let value = byte highest
+ if l.Count = 0
+ then
+ q.RemoveAt(0)
+ highest <- highest - 1
+ while q.Count > 0 && q.[0] = null do
+ q.RemoveAt(0)
+ highest <- highest - 1
+ value, next
+
+ member this.Max =
+ highest |> byte
+
+ (*member this.UnionWith (other: PriorityQueue) =
+ while not other.IsEmpty do
+ let p, v = other.Next
+ this.Add p v*)
+
+ member this.Add (value: byte) (p: Point) =
+ let vi = int value
+
+ if this.IsEmpty
+ then
+ highest <- int value
+ q.Insert(0, null)
+ elif vi > highest
+ then
+ for i in highest .. vi - 1 do
+ q.Insert(0, null)
+ highest <- vi
+ elif highest - vi >= q.Count
+ then
+ for i in 0 .. highest - vi - q.Count do
+ q.Add(null)
+
+ let pos = highest - vi
+ if q.[pos] = null
+ then
+ q.[pos] <- HashSet<Point>([p])
+ else
+ q.[pos].Add(p) |> ignore
+
+
+ member this.IsEmpty =
+ q.Count = 0
+
+ member this.Clear () =
+ while highest >= 0 do
+ q.[highest] <- null
+ highest <- highest - 1
+
+
+
+type MaximaState = Uncertain | Validated | TooBig
+type Maxima = {
+ elements : HashSet<Point>
+ mutable intensity: byte option
+ mutable state: MaximaState }
+
+
+let areaOpen (img: Image<Gray, byte>) (area: int) =
+ let w = img.Width
+ let h = img.Height
+
+ let maxima = findMaxima img |> Seq.map (fun m -> { elements = m; intensity = None; state = Uncertain }) |> List.ofSeq
+ let toValidated = Stack<Maxima>(maxima)
+
+ while toValidated.Count > 0 do
+ let m = toValidated.Pop()
+ if m.elements.Count <= area
+ then
+ let queue =
+ let q = PriorityQueue()
+ let firstElements = HashSet<Point>()
+ for p in m.elements do
+ for i in -1 .. 1 do
+ for j in -1 .. 1 do
+ if i <> 0 || j <> 0
+ then
+ let ni = i + p.Y
+ let nj = j + p.X
+ let p' = Point(nj, ni)
+ if ni >= 0 && ni < h && nj >= 0 && nj < w && not (m.elements.Contains(p')) && not (firstElements.Contains(p'))
+ then
+ firstElements.Add(p') |> ignore
+ q.Add (img.Data.[ni, nj, 0]) p'
+ q
+
+ let mutable intensity = queue.Max
+ let nextElements = HashSet<Point>()
+
+ let mutable stop = false
+ while not stop do
+ let intensity', p = queue.Next ()
+
+ if intensity' = intensity // The intensity doesn't change.
+ then
+ if m.elements.Count + nextElements.Count + 1 > area
+ then
+ m.state <- Validated
+ m.intensity <- Some intensity
+ stop <- true
+ else
+ nextElements.Add(p) |> ignore
+ elif intensity' < intensity
+ then
+ m.elements.UnionWith(nextElements)
+ if m.elements.Count = area
+ then
+ m.state <- Validated
+ m.intensity <- Some (intensity')
+ stop <- true
+ else
+ intensity <- intensity'
+ nextElements.Clear()
+ nextElements.Add(p) |> ignore
+ else // i' > i
+ seq {
+ for m' in maxima do
+ if m' <> m && m'.elements.Contains(p) then
+ if m'.elements.Count + m.elements.Count <= area
+ then
+ m'.state <- Uncertain
+ m'.elements.UnionWith(m.elements)
+ if not <| toValidated.Contains m' // FIXME: Maybe use state instead of scanning the whole list.
+ then
+ toValidated.Push(m')
+ stop <- true
+ yield false
+ } |> Seq.forall id |> ignore
+
+ if not stop
+ then
+ m.state <- Validated
+ m.intensity <- Some (intensity)
+ stop <- true
+
+ if not stop
+ then
+ for i in -1 .. 1 do
+ for j in -1 .. 1 do
+ if i <> 0 || j <> 0
+ then
+ let ni = i + p.Y
+ let nj = j + p.X
+ let p' = Point(nj, ni)
+ if ni < 0 || ni >= h || nj < 0 || nj >= w
+ then
+ m.state <- Validated
+ m.intensity <- Some (intensity)
+ stop <- true
+ elif not (m.elements.Contains(p')) && not (nextElements.Contains(p'))
+ then
+ queue.Add (img.Data.[ni, nj, 0]) p'
+
+ if queue.IsEmpty
+ then
+ if m.elements.Count + nextElements.Count <= area
+ then
+ m.state <- Validated
+ m.intensity <- Some intensity'
+ m.elements.UnionWith(nextElements)
+ stop <- true
+
+ for m in maxima do
+ if m.state = Validated
+ then
+ match m.intensity with
+ | Some i ->
+ for p in m.elements do
+ img.Data.[p.Y, p.X, 0] <- i
+ | _ -> ()
+ ()
+
+
// Zhang and Suen algorithm.
// Modify 'mat' in place.
let thin (mat: Matrix<byte>) =
data2 <- tmp
+// FIXME: replace by a queue or stack.
let pop (l: List<'a>) : 'a =
let n = l.[l.Count - 1]
l.RemoveAt(l.Count - 1)
fg: Image<Gray, byte>
median_bg: float
median_fg: float
- d_fg: Image<Gray, float32> } // Distances to median_fg.
+ d_fg: Image<Gray, float32> } // Euclidean distances of the foreground to median_fg.
let kmedians (img: Image<Gray, float32>) (fgFactor: float) : Result =
let nbIteration = 3
for i in 1 .. nbIteration do
CvInvoke.Pow(img - median_bg, 2.0, d_bg)
CvInvoke.Pow(img - median_fg, 2.0, d_fg)
- fg <- (d_fg * fgFactor).Cmp(d_bg, CvEnum.CmpType.LessThan)
+ CvInvoke.Compare(d_fg, d_bg, fg, CvEnum.CmpType.LessThan)
- median_fg <- MathNet.Numerics.Statistics.Statistics.Median(seq {
- for i in 0 .. h - 1 do
- for j in 0 .. w - 1 do
- if fg.Data.[i, j, 0] > 0uy then yield img.Data.[i, j, 0] |> float })
+ let bg_values = List<float>()
+ let fg_values = List<float>()
- median_bg <- MathNet.Numerics.Statistics.Statistics.Median(seq {
- for i in 0 .. h - 1 do
- for j in 0 .. w - 1 do
- if fg.Data.[i, j, 0] = 0uy then yield img.Data.[i, j, 0] |> float })
+ for i in 0 .. h - 1 do
+ for j in 0 .. w - 1 do
+ if fg.Data.[i, j, 0] > 0uy
+ then fg_values.Add(float img.Data.[i, j, 0])
+ else bg_values.Add(float img.Data.[i, j, 0])
+
+ median_bg <- MathNet.Numerics.Statistics.Statistics.Median(bg_values)
+ median_fg <- MathNet.Numerics.Statistics.Statistics.Median(fg_values)
CvInvoke.Sqrt(d_fg, d_fg)
let doAnalysis (img: Image<Bgr, byte>) (name: string) (config: Config) : Cell list =
- let imgFloat = img.Convert<Bgr, float32>()
- use scaledImg = if config.scale = 1.0 then imgFloat else imgFloat.Resize(config.scale, CvEnum.Inter.Area)
+ use scaledImg = if config.scale = 1.0 then img else img.Resize(config.scale, CvEnum.Inter.Area)
+ use blue = scaledImg.Item(0)
use green = scaledImg.Item(1)
+ use red = scaledImg.Item(2)
- use filteredGreen = (gaussianFilter green config.doGSigma1) - config.doGLowFreqPercentageReduction * (gaussianFilter green config.doGSigma2)
+
+ let greenFloat = green.Convert<Gray, float32>()
+
+ let green = gaussianFilter green 1.5
+
+ // let RBCSize = Granulometry.findRadius green (10, 100) 0.5
+
+ match config.debug with
+ | DebugOn output ->
+ let dirPath = System.IO.Path.Combine(output, name)
+ System.IO.Directory.CreateDirectory dirPath |> ignore
+ let buildFileName postfix = System.IO.Path.Combine(dirPath, name + postfix)
+
+ saveImg green (buildFileName " - green.png")
+
+ let greenMaxima = green.Copy()
+ let maxima = ImgTools.findMaxima greenMaxima
+ for m in maxima do
+ for p in m do
+ greenMaxima.Data.[p.Y, p.X, 0] <- 255uy
+
+ saveImg greenMaxima (buildFileName " - maxima.png")
+
+ logTime "areaOpen" (fun () -> ImgTools.areaOpen green 800)
+ saveImg green (buildFileName " - green opened.png")
+
+ | _ -> ()
+
+ []
+ (*
+
+ use filteredGreen = (gaussianFilter greenFloat config.doGSigma1) - config.doGLowFreqPercentageReduction * (gaussianFilter greenFloat config.doGSigma2)
use sobelKernel =
new ConvolutionKernelF(array2D [[ 1.0f; 0.0f; -1.0f ]
let kmediansResults = logTime "Finding foreground (k-medians)" (fun () -> KMedians.kmedians filteredGreen 1.0)
- let parasites = ParasitesMarker.find green filteredGreen kmediansResults config
+ let parasites = ParasitesMarker.find greenFloat filteredGreen kmediansResults config
let allEllipses, ellipses = logTime "Finding ellipses" (fun () ->
let matchingEllipses = Ellipse.find edges xEdges yEdges config
// Output pictures if debug flag is set.
match config.debug with
| DebugOn output ->
- let buildFileName postfix = System.IO.Path.Combine(output, name + postfix)
+ let dirPath = System.IO.Path.Combine(output, name)
+ System.IO.Directory.CreateDirectory dirPath |> ignore
+
+ let buildFileName postfix = System.IO.Path.Combine(dirPath, name + postfix)
+
saveMat (edges * 255.0) (buildFileName " - edges.png")
saveImg parasites.darkStain (buildFileName " - parasites - dark stain.png")
let imgCells' = img.Copy()
drawCells imgCells' true cells
saveImg imgCells' (buildFileName " - cells - full.png")
+
+ saveImg (normalizeAndConvert filteredGreen) (buildFileName " - filtered.png")
+ saveImg blue (buildFileName " - blue.png")
+ saveImg green (buildFileName " - green.png")
+ saveImg red (buildFileName " - red.png")
| _ -> ()
- cells
+ cells*)
open Utils
+// Do not take in account matching score below this when two ellipses are matched.
let matchingScoreThreshold1 = 0.6
-let matchingScoreThreshold2 = 1.
+
+// All ellipsee with a score below this is removed.
+let matchingScoreThreshold2 = 1. / 50.
type private EllipseScoreFlaggedKd (matchingScore: float, e: Ellipse) =
let mutable matchingScore = matchingScore
+ let perimeter = e.Perimeter
member this.Ellipse = e
member this.MatchingScore = matchingScore
+
+ // The score is proportional to the perimeter because large ellipse will receive more votes.
member this.AddMatchingScore(score: float) =
- matchingScore <- matchingScore + score
+ matchingScore <- matchingScore + score / perimeter
member val Processed = false with get, set
member val Removed = false with get, set
member this.Ellipses : Ellipse list =
List.ofSeq ellipses |> List.map (fun e -> e.Ellipse)
+ // Process all ellipses and return ellipses ordered from the best score to the worst.
member this.PrunedEllipses : Ellipse list =
if ellipses.Count = 0
then
for other in tree.Search window do
if not other.Removed && other.MatchingScore < e.MatchingScore
then
- if e.Ellipse.Contains other.Ellipse.Cx other.Ellipse.Cy
+ if e.Ellipse.Scale(0.8).Contains other.Ellipse.Cx other.Ellipse.Cy
then
other.Removed <- true
ellipses.RemoveAll(fun e -> e.Removed) |> ignore
<PlatformTarget>x64</PlatformTarget>
<DocumentationFile>bin\Debug\Parasitemia.XML</DocumentationFile>
<Prefer32Bit>false</Prefer32Bit>
- <StartArguments>--folder "../../../../Images/08.10.2015/debug" --output "../../../Images/output" --debug</StartArguments>
+ <StartArguments>--folder "../../../Images/areaopen" --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/release" --output "../../../Images/output" --debug</StartArguments>
+ <StartArguments>--folder "../../../Images/release" --output "../../../Images/output" --debug</StartArguments>
</PropertyGroup>
<PropertyGroup>
<MinimumVisualStudioVersion Condition="'$(MinimumVisualStudioVersion)' == ''">11</MinimumVisualStudioVersion>
<Compile Include="NumericUpDown.xaml.fs" />
<Resource Include="MainWindow.xaml" />
<Compile Include="MainWindow.xaml.fs" />
+ <Compile Include="Heap.fs" />
<Compile Include="Types.fs" />
<Compile Include="Utils.fs" />
<Compile Include="ImgTools.fs" />
+ <Compile Include="Granulometry.fs" />
<Compile Include="Config.fs" />
<Compile Include="KMedians.fs" />
<Compile Include="EEOver.fs" />
// RBC size range in px at scale = 1.0.
minRBCSize = 20.
- maxRBCSize = 40.
+ maxRBCSize = 42.
doGSigma1 = 1.5
- doGSigma2 = 20.
+ doGSigma2 = 30.
doGLowFreqPercentageReduction = 0.75
- factorNbPick = 2.0
- factorWindowSize = 1.6
+ factorNbPick = 1.0
+ factorWindowSize = 2.0
darkStainLevel = 0.4 // Lower -> more sensitive.
MaxDarkStainRatio = 0.1
- minimumCellArea = 1200. * scale ** 2. |> int
+ minimumCellArea = 1000. * scale ** 2. |> int
maxOffcenter = 0.5 }
match mode with
| CmdLine (input, output) ->
- let config = { config with debug = DebugOn output }
+ let config = if debug then { config with debug = DebugOn output } else config
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)
+ Utils.log (sprintf "=== New run : %A %A ===" DateTime.Now (if debug then "[DEBUG]" else "[RELEASE]"))
let files = match input with
| File file -> [ file ]
try
use img = new Image<Bgr, byte>(file)
Utils.log (sprintf "== File: %A" file)
- let cells = Utils.logTime "Whole analyze" (fun () -> ImageAnalysis.doAnalysis img (FileInfo(file).Name) config)
+
+ let cells = Utils.logTime "Whole analyze" (fun () -> ImageAnalysis.doAnalysis img (Path.GetFileNameWithoutExtension(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
this.CutAVericalLine 0.0 || this.CutAVericalLine width ||
this.CutAnHorizontalLine 0.0 || this.CutAnHorizontalLine height
+ member this.Scale (factor: float) =
+ Ellipse(this.Cx, this.Cy, this.A * factor, this.B * factor, alpha)
+
+ // Approximation of Ramanujan.
+ member this.Perimeter =
+ Math.PI * (3.0 * (this.A + this.B) - sqrt ((3.0 * this.A + this.B) * (this.A + 3.0 * this.B)))
+
type CellClass = HealthyRBC | InfectedRBC | Peculiar