then
let p' = Utils.pointFromTwoLines d1 d2
let delta, delta' =
- let d = c.X - p.X
- // To avoid rounding.
- if abs d < 0.001f then c.Y - p.Y, c.Y - p'.Y else d, c.X - p'.X
+ let dx1, dx2 = (c.X - p.X), (c.X - p'.X)
+ // To avoid rounding issue.
+ if abs dx1 < 0.01f || abs dx2 < 0.01f then c.Y - p.Y, c.Y - p'.Y else dx1, dx2
// Yield 'false' when the point is owned by another ellipse.
if case1
e.Removed <- true
// 5) Define pixels associated to each ellipse and create the cells.
+ let radiusParasiteRatio = 0.4f
+ let radiusParasite = config.RBCRadius.Pixel * 0.5f
+ let perimeterParasiteSquared = (2.f * radiusParasite) ** 2.f |> roundInt
+ let parasiteOccupation = 0.08f // 8 %
+ let minimumParasiteArea = Const.PI * radiusParasite ** 2.f * parasiteOccupation |> roundInt
+ //let minimumStainArea = roundInt <| config.RBCRadius.Area * 0.02f // 1.5 %
ellipsesWithNeigbors
|> List.choose (fun (e, neighbors) ->
if e.Removed
let minX, minY, maxX, maxY = ellipseWindow e
let infectedPixels = List<Point>()
- let mutable stainPixels = 0
+ let stainPixels = List<Point>()
+
+ //let mutable stainPixels = 0
let mutable darkStainPixels = 0
let mutable nbElement = 0
elements.[y-minY, x-minX] <- 1uy
nbElement <- nbElement + 1
- if infection.Data.[y, x, 0] > 0uy
+ let infected = infection.Data.[y, x, 0] > 0uy
+ let stain = parasites.stain.Data.[y, x, 0] > 0uy
+ let darkStain = parasites.darkStain.Data.[y, x, 0] > 0uy
+
+ if infected
then
infectedPixels.Add(Point(x, y))
- if parasites.stain.Data.[y, x, 0] > 0uy
+ if stain
then
- stainPixels <- stainPixels + 1
+ stainPixels.Add(Point(x, y))
- if parasites.darkStain.Data.[y, x, 0] > 0uy
+ if darkStain
then
darkStainPixels <- darkStainPixels + 1
+ let mutable stainArea = 0
+ if infectedPixels.Count > 0
+ then
+ for stainPixel in stainPixels do
+ if infectedPixels.Exists(fun p -> pown (p.X - stainPixel.X) 2 + pown (p.Y - stainPixel.Y) 2 <= perimeterParasiteSquared)
+ then
+ stainArea <- stainArea + 1
+
+
let cellClass =
- if float darkStainPixels > config.Parameters.maxDarkStainRatio * (float nbElement) ||
- float stainPixels > config.Parameters.maxStainRatio * (float nbElement)
+ if float darkStainPixels > config.Parameters.maxDarkStainRatio * (float nbElement)
+ //|| float stainPixels > config.Parameters.maxStainRatio * (float nbElement)
then
Peculiar
- elif infectedPixels.Count >= 1
+
+ elif infectedPixels.Count > 0 && stainArea >= minimumParasiteArea
then
let infectionToRemove = ImgTools.connectedComponents parasites.stain infectedPixels
for p in infectionToRemove do
infection.Data.[p.Y, p.X, 0] <- 0uy
InfectedRBC
+
else
HealthyRBC
Some { cellClass = cellClass
center = Point(roundInt e.Cx, roundInt e.Cy)
- infectedArea = infectedPixels.Count
- stainArea = stainPixels
+ infectedArea = if cellClass = InfectedRBC then infectedPixels.Count else 0
+ stainArea = stainArea
elements = elements })
rbcDiameter: float<μm>
resolution: float<ppi>
+ colorContribution_BG_RBC: float * float * float // (R, G, B).
+ colorContribution_RBC_parasite: float * float * float // (R, G, B).
+
ratioAreaPaleCenter: float32 // The area of the second opening is 'ratioSecondAreaOpen' * mean RBC area. It's applied only if greater than 'initialAreaOpen'.
granulometryRange: float32 // The radius will be seeked from radius - granulometryRange * radius to radius + granulometryRange * radius.
minRbcRadius: float32 // Factor of the mean RBC radius.
maxRbcRadius: float32 // Factor of the mean RBC radius.
- LPFStandardDeviation: float<μm> // Sigma parameter of the gaussian to remove the high frequency noise.
+ LPFStandardDeviationParasite: float<μm> // Sigma parameter of the gaussian to remove the high frequency noise.
+ LPFStandardDeviationStain: float<μm>
+ LPFStandardDeviationRBC: float<μm>
// Ellipse.
factorNbPick: float // The number of computed ellipse per edge pixel.
rbcDiameter = 8.<μm>
resolution = 220.e3<ppi> // 220.e3<ppi> Correspond to 50X.
- ratioAreaPaleCenter = 1.f / 3.f // The ratio between an RBC area and the area of the its pale center.
+ colorContribution_BG_RBC = 0.16, 0.44, 0.4
+ colorContribution_RBC_parasite = 0.54, 0.41, 0.05
+
+ ratioAreaPaleCenter = 2.f / 5.f // The ratio between an RBC area and the area of the its pale center.
granulometryRange = 0.5f
minRbcRadius = -0.3f
maxRbcRadius = 0.3f
- LPFStandardDeviation = 0.2<μm> // 8.5e-6<inch>.
+ LPFStandardDeviationParasite = 0.15<μm>
+ LPFStandardDeviationStain = 0.15<μm> // 0.12
+ LPFStandardDeviationRBC = 0.2<μm> // 8.5e-6<inch>. // 0.2<μm>
factorNbPick = 1.0
darkStainLevel = 0.25 // 0.3
maxDarkStainRatio = 0.1 // 10 %
- infectionArea = 0.012f // 1.2 %
- infectionSensitivity = 0.9
+ infectionArea = 0.01f // 0.8 % // 0.012f
+ infectionSensitivity = 0.9 // 1) 0.93, 2) 0.94
- stainArea = 0.08f // 8 %
- stainSensitivity = 0.9
+ stainArea = 0.08f // 6 % // 0.08f
+ stainSensitivity = 0.96 // 1) 0.91, 2) 0.92
maxStainRatio = 0.12 // 12 %
standardDeviationMaxRatio = 0.5 // 0.5
member val Debug = DebugOff with get, set
- member this.LPFStandardDeviation =
- let stdDeviation: float<px> = (μmToInch parameters.LPFStandardDeviation) * parameters.resolution
+ member this.LPFStandardDeviationParasite =
+ let stdDeviation: float<px> = (μmToInch parameters.LPFStandardDeviationParasite) * parameters.resolution
+ float stdDeviation
+
+ member this.LPFStandardDeviationStain =
+ let stdDeviation: float<px> = (μmToInch parameters.LPFStandardDeviationStain) * parameters.resolution
+ float stdDeviation
+
+ member this.LPFStandardDeviationRBC =
+ let stdDeviation: float<px> = (μmToInch parameters.LPFStandardDeviationRBC) * parameters.resolution
float stdDeviation
member this.RBCRadiusByResolution = rbcRadiusByResolution
else
if not <| Object.ReferenceEquals(other, null)
then // We touching another island.
- if island.IsInfinite || other.IsInfinite || island.Surface + other.Surface >= area
+ if island.IsInfinite || other.IsInfinite || island.Surface + other.Surface >= area || comparer.Compare(island.Level, other.Level) < 0
then
stop <- true
else // We can merge 'other' into 'surface'.
island.Surface <- island.Surface + other.Surface
- island.Level <- if comparer.Compare(island.Level, other.Level) > 0 then island.Level else other.Level
+ island.Level <- other.Level
+ // island.Level <- if comparer.Compare(island.Level, other.Level) > 0 then other.Level else island.Level
for l, p in other.Shore do
let mutable currentY = p.Y + 1
while currentY < h && ownership.[currentY, p.X] = other do
logWithName "Starting analysis ..."
- use green = img.[1]
- let greenFloat = green.Convert<Gray, float32>()
- let filteredGreen = gaussianFilter greenFloat config.LPFStandardDeviation
+ use img_RBC =
+ use imgFloat = img.Convert<Bgr, float32>()
+ let redFactor, greenFactor, blueFactor = config.Parameters.colorContribution_BG_RBC
+ blueFactor * imgFloat.[0] + greenFactor * imgFloat.[1] + redFactor * imgFloat.[2]
+
+ let img_RBC_filtered = gaussianFilter img_RBC config.LPFStandardDeviationRBC
+
+ use img_parasites =
+ use imgFloat = img.Convert<Bgr, float32>()
+ let redFactor, greenFactor, blueFactor = config.Parameters.colorContribution_RBC_parasite
+ blueFactor * imgFloat.[0] + greenFactor * imgFloat.[1] + redFactor * imgFloat.[2]
logWithName (sprintf "Nominal erytrocyte diameter: %A" config.RBCRadiusByResolution)
- 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.
- do! logTimeWithName "First area opening" (fun () -> ImgTools.areaOpenF filteredGreen initialAreaOpening; report 10)
+ let initialAreaOpening = int <| config.RBCRadiusByResolution.Area * config.Parameters.ratioAreaPaleCenter * 1.1f // 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.
+ do! logTimeWithName "First area opening" (fun () -> ImgTools.areaOpenF img_RBC_filtered initialAreaOpening; report 10)
let range =
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! radius = logTimeWithName "Granulometry (area)" (fun() -> reportWithVal 10 (Granulometry.findRadiusByAreaClosing filteredGreen range |> float32))
+ let! radius = logTimeWithName "Granulometry (area)" (fun() -> reportWithVal 10 (Granulometry.findRadiusByAreaClosing img_RBC_filtered range |> float32))
config.SetRBCRadius <| radius
logWithName (sprintf "Found erytrocyte diameter: %A" config.RBCRadius)
let secondAreaOpening = int <| config.RBCRadius.Area * config.Parameters.ratioAreaPaleCenter
if secondAreaOpening > initialAreaOpening
then
- logTimeWithName "Second area opening" (fun () -> ImgTools.areaOpenF filteredGreen secondAreaOpening; report 30)
+ logTimeWithName "Second area opening" (fun () -> ImgTools.areaOpenF img_RBC_filtered secondAreaOpening; report 30)
else
report 30
- let parasites, filteredGreenWhitoutStain = ParasitesMarker.find filteredGreen config
+ ImgTools.areaCloseF img_RBC_filtered (config.RBCRadius.Area * 0.1f |> roundInt)
+
+ let parasites, filteredGreenWhitoutStain, filteredGreenWithoutInfection = ParasitesMarker.find img_parasites config
//let parasites, filteredGreenWhitoutInfection, filteredGreenWhitoutStain = ParasitesMarker.findMa greenFloat filteredGreenFloat config
let! edges, xGradient, yGradient = logTimeWithName "Finding edges" (fun () ->
- let edges, xGradient, yGradient = ImgTools.findEdges filteredGreenWhitoutStain
+ let edges, xGradient, yGradient = ImgTools.findEdges img_RBC_filtered
removeArea edges (config.RBCRadius.Pixel ** 2.f / 50.f |> int)
reportWithVal 40 (edges, xGradient, yGradient))
let! prunedEllipses = logTimeWithName "Ellipses pruning" (fun () -> reportWithVal 80 (matchingEllipses.PrunedEllipses))
- let! cells = logTimeWithName "Classifier" (fun () -> reportWithVal 100 (Classifier.findCells prunedEllipses parasites filteredGreenWhitoutStain config))
+ let! cells = logTimeWithName "Classifier" (fun () -> reportWithVal 100 (Classifier.findCells prunedEllipses parasites img_RBC_filtered config))
logWithName "Analysis finished"
drawCells imgCells' true cells
saveImg imgCells' (buildFileName " - cells - full.png")
- let filteredGreenMaxima = gaussianFilter greenFloat config.LPFStandardDeviation
+ let filteredGreenMaxima = gaussianFilter img_RBC config.LPFStandardDeviationRBC
for m in ImgTools.findMaxima filteredGreenMaxima do
ImgTools.drawPoints filteredGreenMaxima m 255.f
saveImg filteredGreenMaxima (buildFileName " - filtered - maxima.png")
- saveImg filteredGreen (buildFileName " - filtered.png")
+ saveImg img_RBC_filtered (buildFileName " - filtered.png")
saveImg filteredGreenWhitoutStain (buildFileName " - filtered closed stain.png")
- //saveImg filteredGreenWhitoutInfection (buildFileName " - filtered closed infection.png")
-
- saveImg green (buildFileName " - green.png")
-
- use blue = img.Item(0)
- saveImg blue (buildFileName " - blue.png")
+ saveImg filteredGreenWithoutInfection (buildFileName " - filtered closed infection.png")
- use red = img.Item(2)
- saveImg red (buildFileName " - red.png")
+ saveImg img_RBC (buildFileName " - source - RBC.png")
+ saveImg img_parasites (buildFileName " - source - parasites.png")
| _ -> ()
return cells }
// * 'Dark stain' corresponds to the colored pixel, it's independent of the size of the areas.
// * 'Stain' corresponds to the stain around the parasites.
// * '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()
+let find (img: Image<Gray, float32>) (config: Config.Config) : Result * Image<Gray, float32> * Image<Gray, float32> =
+
+ let imgFilteredInfection = ImgTools.gaussianFilter img config.LPFStandardDeviationParasite
+ let filteredGreenWithoutInfection = imgFilteredInfection.Copy()
ImgTools.areaCloseF filteredGreenWithoutInfection (int config.RBCRadius.InfectionArea)
+ (*
let filteredGreenWithoutStain = filteredGreenWithoutInfection.Copy()
- ImgTools.areaCloseF filteredGreenWithoutStain (int config.RBCRadius.StainArea)
+ ImgTools.areaCloseF filteredGreenWithoutStain (int config.RBCRadius.StainArea) *)
let darkStain =
// We use the filtered image to find the dark stain.
diff._ThresholdBinary(Gray(0.0), Gray(255.))
diff.Convert<Gray, byte>()
- let infectionMarker = marker filteredGreen filteredGreenWithoutInfection (1. / config.Parameters.infectionSensitivity)
- let stainMarker = marker filteredGreenWithoutInfection filteredGreenWithoutStain (1. / config.Parameters.stainSensitivity)
-
- // TODO: comprendre pourquoi des valeurs sont negatives!?!?
- (*
- let blackTopHat = filteredGreen.CopyBlank()
- CvInvoke.Subtract(filteredGreenWithoutInfection, filteredGreen, blackTopHat)
- ImgTools.saveImg (ImgTools.normalizeAndConvert blackTopHat) "BottomHat.png"
- *)
+ let infectionMarker = marker imgFilteredInfection filteredGreenWithoutInfection (1. / config.Parameters.infectionSensitivity)
+
+ let imgFilteredStain = ImgTools.gaussianFilter img config.LPFStandardDeviationStain
+ let areaOpening = int <| config.RBCRadius.Area * config.Parameters.ratioAreaPaleCenter
+ //ImgTools.areaOpenF imgFilteredStain areaOpening
+
+ let filteredGreenWithoutStain = imgFilteredStain.CopyBlank()
+ let kernelSize =
+ let size = roundInt (config.RBCRadius.Pixel / 5.f)
+ if size % 2 = 0 then size + 1 else size
+ use kernel =
+ if kernelSize <= 3
+ then
+ CvInvoke.GetStructuringElement(CvEnum.ElementShape.Rectangle, Size(3, 3), Point(-1, -1))
+ else
+ CvInvoke.GetStructuringElement(CvEnum.ElementShape.Ellipse, Size(kernelSize, kernelSize), Point(-1, -1))
+ CvInvoke.MorphologyEx(imgFilteredStain, filteredGreenWithoutStain, CvEnum.MorphOp.Close, kernel, Point(-1, -1), 1, CvEnum.BorderType.Replicate, MCvScalar())
+ let stainMarker = marker (*filteredGreenWithoutInfection*) imgFilteredStain filteredGreenWithoutStain (1. / config.Parameters.stainSensitivity)
+
+ //
+ (*let blackTopHat = filteredGreenWithoutStain.CopyBlank()
+ CvInvoke.Subtract(filteredGreenWithoutStain, imgFilteredStain, blackTopHat)
+ ImgTools.saveImg blackTopHat "blackTopHat.png"*)
{ darkStain = darkStain
infection = infectionMarker
stain = stainMarker },
- filteredGreenWithoutStain
+ filteredGreenWithoutStain,
+ filteredGreenWithoutInfection
let mainWindow = Views.MainWindow()
let ctrl (name: string): 'a = mainWindow.Root.FindName(name) :?> 'a
- let state = State.State()
+ let state = State.State(defaultConfig)
let mutable currentScale = 1.
let mutable displayHealthy = false
let warningBelowNumberOfRBC = 1000
/// </summary>
/// <param name="filePath"></param>
/// <exception cref="System.IOException">If the file cannot be read</exception>
-let load (filePath: string) : DocumentData =
+let load (filePath: string) (defaultConfig: ParasitemiaCore.Config.Config) : DocumentData =
use file = ZipFile.Open(filePath, ZipArchiveMode.Read)
let mainEntry = file.GetEntry(mainEntryName)
let imgJSONEntry = file.GetEntry(imgEntry.Name + ".json")
use imgJSONFileReader = new StreamReader(imgJSONEntry.Open())
let imgInfo = JsonConvert.DeserializeObject<JSONSourceImage>(imgJSONFileReader.ReadToEnd())
- let config = ParasitemiaCore.Config.Config(imgInfo.parameters)
+
+ let config = defaultConfig.Copy()
+ config.Parameters <-
+ { ParasitemiaCore.Config.defaultParameters with
+ resolution = imgInfo.parameters.resolution }
+
config.SetRBCRadius imgInfo.RBCRadius
yield { num = imgNum
name = imgInfo.name
open Types
-type State () =
+type State (defaultConfig: ParasitemiaCore.Config.Config) =
let sourceImages = List<SourceImage>()
let mutable alteredSinceLastSave = false
let mutable patientID = ""
/// </summary>
/// <exception cref="System.IOException">If the file cannot be loaded</exception>
member this.Load () =
- let data = PiaZ.load this.FilePath
+ let data = PiaZ.load this.FilePath defaultConfig
this.PatientID <- data.patientID
sourceImages.Clear()
sourceImages.InsertRange(0, data.images)