let infection = parasites.infection.Copy() // To avoid to modify the parameter.
// This is the minimum window size to check if other ellipses touch 'e'.
- let searchRegion (e: Ellipse) = { KdTree.minX = e.Cx - (e.A + config.RBCMaxRadius)
- KdTree.maxX = e.Cx + (e.A + config.RBCMaxRadius)
- KdTree.minY = e.Cy - (e.A + config.RBCMaxRadius)
- KdTree.maxY = e.Cy + (e.A + config.RBCMaxRadius) }
+ let searchRegion (e: Ellipse) = { KdTree.minX = e.Cx - (e.A + config.RBCRadius.Max)
+ KdTree.maxX = e.Cx + (e.A + config.RBCRadius.Max)
+ KdTree.minY = e.Cy - (e.A + config.RBCRadius.Max)
+ KdTree.maxY = e.Cy + (e.A + config.RBCRadius.Max) }
// The minimum window to contain a given ellipse.
let ellipseWindow (e: Ellipse) =
// We reverse the list to get the lower score ellipses first.
let ellipsesWithNeigbors = ellipses |> List.map (fun e -> e, neighbors e) |> List.rev
-
// 2) Remove ellipses touching the edges.
for e in ellipses do
if e.isOutside w_f h_f then e.Removed <- true
if stdDeviation > globalStdDeviation * config.Parameters.standardDeviationMaxRatio then
e.Removed <- true
-(*
- let imgData = img.Data
- let stdDeviations = [
- for e in ellipses do
- if not e.Removed
- then
- let shrinkedE = e.Scale 0.9f
- let minX, minY, maxX, maxY = ellipseWindow shrinkedE
-
- let stdDeviation = float32 <| MathNet.Numerics.Statistics.Statistics.StandardDeviation (seq {
- 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
- if shrinkedE.Contains (float32 x) (float32 y)
- then
- yield float imgData.[y, x, 0] })
-
- e.StdDeviation <- stdDeviation
- yield stdDeviation ]
-
- // We use Otsu and eliminate some cells only if the curve may be bimodal.
- // See https://en.wikipedia.org/wiki/Multimodal_distribution#Bimodality_coefficient
- let skewness, kurtosis = MathNet.Numerics.Statistics.Statistics.PopulationSkewnessKurtosis (stdDeviations |> List.map float)
- let n = float stdDeviations.Length
- let bimodalityCoefficient = (skewness ** 2. + 1.) / (kurtosis + 3. * (n - 1.) ** 2. / ((n - 2.) * (n - 3.)))
-
- if bimodalityCoefficient > 5. / 9.
- then
- let hist = ImgTools.histogram stdDeviations 200
- let thresh, _, _ = ImgTools.otsu hist
- for e in ellipses do
- if not e.Removed && e.StdDeviation > thresh
- then e.Removed <- true
-*)
// 4) Remove ellipses with little area.
- let minArea = config.RBCMinArea
+ let minArea = config.RBCRadius.MinArea
for e, neighbors in ellipsesWithNeigbors do
if not e.Removed
then
standardDeviationMaxRatio = 0.5 // 0.5
minimumCellAreaFactor = 0.4f }
-type Config (param: Parameters) =
- let mutable parameters: Parameters = param
+type RBCRadius (radius: float32, parameters: Parameters) =
+ member this.Pixel = radius
+ member this.μm : float<μm> =
+ 1.<px> * (float radius) / parameters.resolution |> inchToμm
+
+ member this.Min = radius + parameters.minRbcRadius * radius
+ member this.Max = radius + parameters.maxRbcRadius * radius
+
+ member this.Area = PI * radius ** 2.f
+ member this.MinArea = parameters.minimumCellAreaFactor * radius
- let initialRBCRadius : float32 =
- let rbcRadiusInch: float<inch> = (μmToInch parameters.rbcDiameter) / 2.
- let rbcRadiusPx: float<px> = parameters.resolution * rbcRadiusInch
+ member this.InfectionArea = parameters.infectionArea * this.Area
+ member this.StainArea = parameters.stainArea * this.Area
+
+ override this.ToString() =
+ sprintf "%d px (%.1f μm)" (Utils.roundInt <| 2.f * radius) (2. * this.μm)
+
+
+type Config (param: Parameters) =
+ let RBCadiusInPixels (rbcDiameter: float<μm>) (resolution: float<ppi>) : float32 =
+ let rbcRadiusInch: float<inch> = (μmToInch rbcDiameter) / 2.
+ let rbcRadiusPx: float<px> = resolution * rbcRadiusInch
float32 rbcRadiusPx
+ let mutable parameters: Parameters = param
+ let mutable rbcRadiusByResolution = RBCRadius(RBCadiusInPixels parameters.rbcDiameter parameters.resolution, parameters)
+ let mutable rbcRadius = RBCRadius(0.f, parameters)
+
new () = Config(defaultParameters)
- member this.Parameters with get() = parameters and set(param) = parameters <- param
+ member this.Parameters
+ with get() = parameters
+ and set(param) =
+ parameters <- param
+ rbcRadiusByResolution <- RBCRadius(RBCadiusInPixels parameters.rbcDiameter parameters.resolution, param)
+ rbcRadius <- RBCRadius(rbcRadius.Pixel, param)
+
member val Debug = DebugOff with get, set
member this.LPFStandardDeviation =
- let stdDeviation: float<px> = (μmToInch parameters.LPFStandardDeviation) * param.resolution
+ let stdDeviation: float<px> = (μmToInch parameters.LPFStandardDeviation) * parameters.resolution
float stdDeviation
- // Mean RBC radius.
- member val RBCRadius : float32 = initialRBCRadius with get, set
-
- member this.RBCRadiusμm : float<μm> =
- 1.<px> * (float this.RBCRadius) / parameters.resolution |> inchToμm
-
- member this.RBCMinRadius = this.RBCRadius + parameters.minRbcRadius * this.RBCRadius
- member this.RBCMaxRadius = this.RBCRadius + parameters.maxRbcRadius * this.RBCRadius
-
- member this.RBCArea = PI * this.RBCRadius ** 2.f
- member this.RBCMinArea = parameters.minimumCellAreaFactor * this.RBCArea
-
- member this.InfectionArea = parameters.infectionArea * this.RBCArea
- member this.StainArea = parameters.stainArea * this.RBCArea
+ member this.RBCRadiusByResolution = rbcRadiusByResolution
+ member this.RBCRadius = rbcRadius
- member this.FormattedRadius =
- sprintf "%d px (%.1f μm)" (Utils.roundInt <| 2.f * this.RBCRadius) (2. * this.RBCRadiusμm)
+ member this.SetRBCRadius (radiusPixel: float32) =
+ rbcRadius <- RBCRadius(radiusPixel, parameters)
member this.Copy () =
this.MemberwiseClone() :?> Config
(yGradient: Image<Gray, float32>)
(config: Config) : MatchingEllipses =
- let r1, r2 = config.RBCMinRadius, config.RBCMaxRadius
+ let r1, r2 = config.RBCRadius.Min, config.RBCRadius.Max
let incrementWindowDivisor = 4.f
// We choose a window size for which the biggest ellipse can always be fitted in.
butClose.Content <- "Close"
updateSourceImages ())
- Utils.log "Analysis terminated successfully"
+ Utils.log "All analyses terminated successfully"
atLeastOneAnalysisPerformed <- true
analysisPerformed <- true)
} |> Async.Start)
if not analysisPerformed
then
analysisCancelled <- true
- atLeastOneAnalysisPerformed)
-
-
- (*let results = ImageAnalysis.doMultipleAnalysis (state.SourceImages |> Seq.map (fun srcImg -> string srcImg.num, srcImg.img) |> Seq.toList) defaultConfig
- for id, rbcRadius, cells in results do
- state.SetResult (int id) rbcRadius cells*)
\ No newline at end of file
+ atLeastOneAnalysisPerformed)
\ No newline at end of file
txtImageInformation.Inlines.Add(Documents.LineBreak())
txtImageInformation.Inlines.Add(Documents.Run("Average erytrocyte diameter: ", FontWeight = FontWeights.Bold))
- txtImageInformation.Inlines.Add(Documents.Run(srcImg.config.FormattedRadius))
+ txtImageInformation.Inlines.Add(Documents.Run(srcImg.config.RBCRadius.ToString()))
txtImageInformation.Inlines.Add(Documents.LineBreak())
txtImageInformation.Inlines.Add(Documents.Run("Last analysis: ", FontWeight = FontWeights.Bold))
// To match with previously manually altered RBC.
let manuallyAlteredPreviousRBCS = sourceImage.rbcs |> List.filter (fun rbc -> rbc.setManually)
- let tolerance = (float sourceImage.config.RBCRadius) * 0.5 // +/-.
+ let tolerance = (float sourceImage.config.RBCRadius.Pixel) * 0.5 // +/-.
let getPreviousRBC (center: Point) : RBC option =
manuallyAlteredPreviousRBCS |> List.tryFind (fun rbc -> rbc.center.X > center.X - tolerance && rbc.center.X < center.X + tolerance &&
rbc.center.Y > center.Y - tolerance && rbc.center.Y < center.Y + tolerance)
let greenFloat = green.Convert<Gray, float32>()
let filteredGreen = gaussianFilter greenFloat config.LPFStandardDeviation
- logWithName (sprintf "Nominal erytrocyte diameter: %s" config.FormattedRadius)
+ logWithName (sprintf "Nominal erytrocyte diameter: %A" config.RBCRadiusByResolution)
- let initialAreaOpening = int<| config.RBCArea * config.Parameters.ratioAreaPaleCenter * 1.2f // We do an area opening a little larger to avoid to do a second one in the case the radius found is near the initial one.
+ let initialAreaOpening = int <| config.RBCRadiusByResolution.Area * config.Parameters.ratioAreaPaleCenter * 1.2f // We do an area opening a little larger to avoid to do a second one in the case the radius found is near the initial one.
logTimeWithName "Area opening number one" (fun () -> ImgTools.areaOpenF filteredGreen initialAreaOpening)
report 8
let range =
- let delta = config.Parameters.granulometryRange * config.RBCRadius
- int <| config.RBCRadius - delta, int <| config.RBCRadius + delta
+ let delta = config.Parameters.granulometryRange * config.RBCRadiusByResolution.Pixel
+ int <| config.RBCRadiusByResolution.Pixel - delta, int <| config.RBCRadiusByResolution.Pixel + delta
//let r1 = log "Granulometry (morpho)" (fun() -> Granulometry.findRadiusByClosing (filteredGreen.Convert<Gray, byte>()) range 1.0 |> float32)
- let r2 = logTimeWithName "Granulometry (area)" (fun() -> Granulometry.findRadiusByAreaClosing filteredGreen range |> float32)
+ config.SetRBCRadius <| logTimeWithName "Granulometry (area)" (fun() -> Granulometry.findRadiusByAreaClosing filteredGreen range |> float32)
// log (sprintf "r1: %A, r2: %A" r1 r2)
- config.RBCRadius <- r2
- logWithName (sprintf "Found erytrocyte diameter: %s" config.FormattedRadius)
+ logWithName (sprintf "Found erytrocyte diameter: %A" config.RBCRadius)
report 24
- let secondAreaOpening = int <| config.RBCArea * config.Parameters.ratioAreaPaleCenter
+ let secondAreaOpening = int <| config.RBCRadius.Area * config.Parameters.ratioAreaPaleCenter
if secondAreaOpening > initialAreaOpening
then
logTimeWithName "Area opening number two" (fun () -> ImgTools.areaOpenF filteredGreen secondAreaOpening)
let edges, xGradient, yGradient = logTimeWithName "Finding edges" (fun () ->
let edges, xGradient, yGradient = ImgTools.findEdges filteredGreenWhitoutStain
- removeArea edges (config.RBCRadius ** 2.f / 50.f |> int)
+ removeArea edges (config.RBCRadius.Pixel ** 2.f / 50.f |> int)
edges, xGradient, yGradient)
let allEllipses, ellipses = logTimeWithName "Finding ellipses" (fun () ->
progressPerAnalysis.AddOrUpdate(id, progress, (fun _ _ -> progress)) |> ignore
report (progressPerAnalysis.Values.Sum() / nbImgs)
- let nbConcurrentTaskLimit = 4
+ let nbConcurrentTaskLimit = 4 // To reduce the memory taken.
let n = Environment.ProcessorCount
imgs
<PlatformTarget>x64</PlatformTarget>
<DocumentationFile>bin\Debug\Parasitemia.XML</DocumentationFile>
<Prefer32Bit>false</Prefer32Bit>
- <StartArguments>
- </StartArguments>
+ <StartArguments>--output "../../../Images/output" --debug</StartArguments>
<OutputPath>bin\DebugGUI\</OutputPath>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
// * 'Infection' corresponds to the parasite. It shouldn't contain thrombocytes.
let find (filteredGreen: Image<Gray, float32>) (config: Config.Config) : Result * Image<Gray, float32> =
use filteredGreenWithoutInfection = filteredGreen.Copy()
- ImgTools.areaCloseF filteredGreenWithoutInfection (int config.InfectionArea)
+ ImgTools.areaCloseF filteredGreenWithoutInfection (int config.RBCRadius.InfectionArea)
let filteredGreenWithoutStain = filteredGreenWithoutInfection.Copy()
- ImgTools.areaCloseF filteredGreenWithoutStain (int config.StainArea)
+ ImgTools.areaCloseF filteredGreenWithoutStain (int config.RBCRadius.StainArea)
let darkStain =
// We use the filtered image to find the dark stain.