then
[]
else
- let infection = parasites.nucleus.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.RBCRadius.Max)
KdTree.maxX = e.Cx + (e.A + config.RBCRadius.Max)
// 5) Define pixels associated to each ellipse and create the cells.
let perimeterParasiteSquared = (2.f * config.RBCRadius.ParasiteRadius) ** 2.f |> roundInt
let minimumParasiteArea = config.RBCRadius.MinimumParasiteArea |> roundInt
+
+ let nucleusData = parasites.nucleus.Copy().Data // Will be modified thus the copy.
+ let parasiteData = parasites.parasite.Data
+ let darkStainData = parasites.darkStain.Data
+
ellipsesWithNeigbors
|> List.choose (fun (e, neighbors) ->
if e.Removed
else
let minX, minY, maxX, maxY = ellipseWindow e
- let infectedPixels = List<Point>()
- let cytoplasmPixels = List<Point>()
+ let nucleusPixels = List<Point>()
+ let parasitePixels = List<Point>()
- //let mutable stainPixels = 0
let mutable darkStainPixels = 0
let mutable nbElement = 0
let p = PointF(float32 x, float32 y)
if pixelOwnedByE p e (neighbors |> List.choose (fun (otherE, p1, p2) -> if otherE.Removed then None else Some (otherE :> Ellipse, Utils.lineFromTwoPoints p1 p2)))
then
- elements.[y-minY, x-minX] <- 1uy
+ elements.[y - minY, x - minX] <- 1uy
nbElement <- nbElement + 1
- let infected = infection.Data.[y, x, 0] > 0uy
- let stain = parasites.cytoplasm.Data.[y, x, 0] > 0uy
- let darkStain = parasites.darkStain.Data.[y, x, 0] > 0uy
-
- if infected
+ if nucleusData.[y, x, 0] > 0uy
then
- infectedPixels.Add(Point(x, y))
+ nucleusPixels.Add(Point(x, y))
- if stain
+ if parasiteData.[y, x, 0] > 0uy
then
- cytoplasmPixels.Add(Point(x, y))
+ parasitePixels.Add(Point(x, y))
- if darkStain
+ if darkStainData.[y, x, 0] > 0uy
then
darkStainPixels <- darkStainPixels + 1
- let mutable cytoplasmArea = 0
- if infectedPixels.Count > 0
+ let mutable parasiteArea = 0
+ if nucleusPixels.Count > 0
then
- for cytoplasmPixel in cytoplasmPixels do
- if infectedPixels.Exists(fun p -> pown (p.X - cytoplasmPixel.X) 2 + pown (p.Y - cytoplasmPixel.Y) 2 <= perimeterParasiteSquared)
+ for parasitePixel in parasitePixels do
+ if nucleusPixels.Exists(fun p -> pown (p.X - parasitePixel.X) 2 + pown (p.Y - parasitePixel.Y) 2 <= perimeterParasiteSquared)
then
- cytoplasmArea <- cytoplasmArea + 1
+ parasiteArea <- parasiteArea + 1
let cellClass =
if float darkStainPixels > config.Parameters.maxDarkStainRatio * (float nbElement)
- //|| float stainPixels > config.Parameters.maxStainRatio * (float nbElement)
then
Peculiar
- elif infectedPixels.Count > 0 && cytoplasmArea >= minimumParasiteArea
+ elif nucleusPixels.Count > 0 && parasiteArea >= minimumParasiteArea
then
- let infectionToRemove = ImgTools.connectedComponents parasites.cytoplasm infectedPixels
+ let infectionToRemove = ImgTools.connectedComponents parasites.parasite nucleusPixels
for p in infectionToRemove do
- infection.Data.[p.Y, p.X, 0] <- 0uy
+ nucleusData.[p.Y, p.X, 0] <- 0uy
InfectedRBC
else
Some { cellClass = cellClass
center = Point(roundInt e.Cx, roundInt e.Cy)
- nucleusArea = if cellClass = InfectedRBC then infectedPixels.Count else 0
- parasiteArea = cytoplasmArea
+ nucleusArea = if cellClass = InfectedRBC then nucleusPixels.Count else 0
+ parasiteArea = parasiteArea
elements = elements })
rbcDiameter: float<μm>
resolution: float<ppi>
- averageColor_BG: float32 * float32 * float32 // R * G * B.
- averageColor_RBC: float32 * float32 * float32 // R * G * B.
- averageColor_Parasite: float32 * float32 * float32 // 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.
infectionSensitivity: float // between 0 (the least sensitive) and 1 (the most sensitive).
standardDeviationMaxRatio: float // The standard deviation of the pixel values of a cell can't be greater than standardDeviationMaxRatio * global standard deviation
- minimumCellAreaFactor: float32 // Factor of the mean RBC area. A cell with an area below this will be rejected.
-}
+ minimumCellAreaFactor: float32 } // Factor of the mean RBC area. A cell with an area below this will be rejected.
let defaultParameters = {
rbcDiameter = 8.<μm>
resolution = 220.e3<ppi> // 220.e3<ppi> Correspond to 50X.
- averageColor_BG = 113.3f, 135.3f, 150.3f
- averageColor_RBC = 94.7f, 80.7f, 99.3f
- averageColor_Parasite = 76.f, 58.f, 94.f
-
- (*averageColor_BG = 179.f, 148.f, 121.f
- averageColor_RBC = 141.f, 96.f, 83.f
- averageColor_Parasite = 123.f, 89.f, 83.f*)
-
ratioAreaPaleCenter = 2.f / 5.f // The ratio between an RBC area and the area of the its pale center.
granulometryRange = 0.5f
parasiteRadiusRatio = 0.5f // 40 %
minimumParasiteAreaRatio = 0.02f // 2 %
- cytoplasmSensitivity = 0.96 // 1) 0.91, 2) 0.92
+ cytoplasmSensitivity = 0.96
nucleusAreaRatio = 0.01f // 1.0 %
- infectionSensitivity = 0.9 // 1) 0.93, 2) 0.94
+ infectionSensitivity = 0.9
- standardDeviationMaxRatio = 0.5 // 0.5
+ standardDeviationMaxRatio = 0.5
minimumCellAreaFactor = 0.4f }
type RBCRadius (radius: float32, parameters: Parameters) =
// Normalize image values between 0uy and 255uy.
let normalizeAndConvert (img: Image<Gray, 'TDepth>) : Image<Gray, byte> =
- let min = ref [| 0.0 |]
- let minLocation = ref <| [| Point() |]
- let max = ref [| 0.0 |]
- let maxLocation = ref <| [| Point() |]
- img.MinMax(min, max, minLocation, maxLocation)
- ((img.Convert<Gray, float32>() - (!min).[0]) / ((!max).[0] - (!min).[0]) * 255.0).Convert<Gray, byte>()
+ (normalize (img.Convert<Gray, float32>()) 255.).Convert<Gray, byte>()
let saveImg (img: Image<'TColor, 'TDepth>) (filepath: string) =
img.Save(filepath)
use img_float = img.Convert<Bgr, float32>()
- // use img_RBC = mergeChannels img_float config.Parameters.colorContribution_BG_RBC
- use img_RBC = mergeChannelsWithProjection img_float config.Parameters.averageColor_RBC config.Parameters.averageColor_BG 255.
- let img_RBC_filtered = gaussianFilter img_RBC config.LPFStandardDeviationRBC
+ use img_RBC = img_float.[1] // mergeChannelsWithProjection img_float config.Parameters.averageColor_RBC config.Parameters.averageColor_BG 255.
+ use img_RBC_filtered = gaussianFilter img_RBC config.LPFStandardDeviationRBC
- //use img_parasites = mergeChannels img_float config.Parameters.colorContribution_RBC_parasite
- use img_parasites = mergeChannelsWithProjection img_float config.Parameters.averageColor_Parasite config.Parameters.averageColor_RBC 255.
+ use img_parasites = img_float.[2] // mergeChannelsWithProjection img_float config.Parameters.averageColor_Parasite config.Parameters.averageColor_RBC 255.
+ use img_parasites_filtered = gaussianFilter img_parasites config.LPFStandardDeviationParasite
logWithName (sprintf "Nominal erytrocyte diameter: %A" config.RBCRadiusByResolution)
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)
+ do! logTimeWithName "First area opening" (fun () -> 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 img_RBC_filtered range |> float32))
config.SetRBCRadius <| radius
let secondAreaOpening = int <| config.RBCRadius.Area * config.Parameters.ratioAreaPaleCenter
if secondAreaOpening > initialAreaOpening
then
- logTimeWithName "Second area opening" (fun () -> ImgTools.areaOpenF img_RBC_filtered secondAreaOpening; report 30)
+ logTimeWithName "Second area opening" (fun () -> areaOpenF img_RBC_filtered secondAreaOpening; report 30)
else
report 30
- // Removing of parasites.
- ImgTools.areaCloseF img_RBC_filtered (roundInt <| Const.PI * config.RBCRadius.ParasiteRadius ** 2.f)
+ // Removing parasites.
+ areaCloseF img_RBC_filtered (roundInt <| Const.PI * config.RBCRadius.ParasiteRadius ** 2.f)
- let parasites, filteredGreenWhitoutStain, filteredGreenWithoutInfection = ParasitesMarker.find img_parasites config
- //let parasites, filteredGreenWhitoutInfection, filteredGreenWhitoutStain = ParasitesMarker.findMa greenFloat filteredGreenFloat config
+ let! parasites, imgWhitoutParasite, imgWithoutNucleus =
+ logTimeWithName "Parasites segmentation" (fun () -> reportWithVal 40 (ParasitesMarker.find img_parasites_filtered config))
let! edges, xGradient, yGradient = logTimeWithName "Finding edges" (fun () ->
- let edges, xGradient, yGradient = ImgTools.findEdges img_RBC_filtered
+ let edges, xGradient, yGradient = findEdges img_RBC_filtered
removeArea edges (config.RBCRadius.Pixel ** 2.f / 50.f |> int)
- reportWithVal 40 (edges, xGradient, yGradient))
+ reportWithVal 50 (edges, xGradient, yGradient))
let! matchingEllipses = logTimeWithName "Finding ellipses" (fun () -> reportWithVal 60 (Ellipse.find edges xGradient yGradient config))
saveMat (edges * 255.0) (buildFileName " - edges.png")
saveImg parasites.darkStain (buildFileName " - parasites - dark stain.png")
- saveImg parasites.cytoplasm (buildFileName " - parasites - stain.png")
+ saveImg parasites.parasite (buildFileName " - parasites - stain.png")
saveImg parasites.nucleus (buildFileName " - parasites - infection.png")
let imgAllEllipses = img.Copy()
saveImg imgCells' (buildFileName " - cells - full.png")
let filteredGreenMaxima = gaussianFilter img_RBC config.LPFStandardDeviationRBC
- for m in ImgTools.findMaxima filteredGreenMaxima do
- ImgTools.drawPoints filteredGreenMaxima m 255.f
+ for m in findMaxima filteredGreenMaxima do
+ drawPoints filteredGreenMaxima m 255.f
saveImg filteredGreenMaxima (buildFileName " - filtered - maxima.png")
saveImg img_RBC_filtered (buildFileName " - filtered.png")
- saveImg filteredGreenWhitoutStain (buildFileName " - filtered closed stain.png")
- saveImg filteredGreenWithoutInfection (buildFileName " - filtered closed infection.png")
+ saveImg imgWhitoutParasite (buildFileName " - filtered closed stain.png")
+ saveImg imgWithoutNucleus (buildFileName " - filtered closed infection.png")
saveImg img_RBC (buildFileName " - source - RBC.png")
saveImg img_parasites (buildFileName " - source - parasites.png")
+
+ saveImg (normalize img_float.[2] 255.) (buildFileName " - source - red.png")
+ saveImg (normalize img_float.[1] 255.) (buildFileName " - source - green.png")
+ saveImg (normalize img_float.[0] 255.) (buildFileName " - source - blue.png")
| _ -> ()
return cells }
open Emgu.CV.Structure
open Utils
+open ImgTools
type Result = {
darkStain: Image<Gray, byte> // Colored pixel, it's independent of the size of the areas. It corresponds to white cells, schizontes, gametocytes, throphozoites.
nucleus: Image<Gray, byte> // Parasite nucleus. It may contain some debris. It shouldn't contain thrombocytes or larger elements.
- cytoplasm: Image<Gray, byte> } // Parasite cytoplasm.
+ parasite: Image<Gray, byte> } // The whole parasites.
let find (img: Image<Gray, float32>) (config: Config.Config) : Result * Image<Gray, float32> * Image<Gray, float32> =
- let imgFilteredParasite = ImgTools.gaussianFilter img config.LPFStandardDeviationParasite
-
- let filteredGreenWithoutNucleus = imgFilteredParasite.Copy()
- ImgTools.areaCloseF filteredGreenWithoutNucleus (roundInt config.RBCRadius.NucleusArea)
+ let imgWithoutNucleus = img.Copy()
+ areaCloseF imgWithoutNucleus (roundInt config.RBCRadius.NucleusArea)
let darkStain =
// We use the filtered image to find the dark stain.
let _, mean_fg, mean_bg =
- let hist = ImgTools.histogramImg filteredGreenWithoutNucleus 300
- ImgTools.otsu hist
- filteredGreenWithoutNucleus.Cmp(-(float mean_bg) * config.Parameters.darkStainLevel + (float mean_fg), CvEnum.CmpType.LessThan)
+ let hist = histogramImg imgWithoutNucleus 300
+ otsu hist
+ imgWithoutNucleus.Cmp(-(float mean_bg) * config.Parameters.darkStainLevel + (float mean_fg), CvEnum.CmpType.LessThan)
let marker (img: Image<Gray, float32>) (closed: Image<Gray, float32>) (level: float) : Image<Gray, byte> =
let diff = img.Copy()
diff._ThresholdBinary(Gray(0.0), Gray(255.))
diff.Convert<Gray, byte>()
- let nucleusMarker = marker imgFilteredParasite filteredGreenWithoutNucleus (1. / config.Parameters.infectionSensitivity)
+ let nucleusMarker = marker img imgWithoutNucleus (1. / config.Parameters.infectionSensitivity)
- let filteredGreenWithoutCytoplasm = imgFilteredParasite.CopyBlank()
+ let imgWithoutParasite = img.CopyBlank()
let kernelSize =
let size = roundInt (config.RBCRadius.Pixel / 5.f)
if size % 2 = 0 then size + 1 else size
else
CvInvoke.GetStructuringElement(CvEnum.ElementShape.Ellipse, Size(kernelSize, kernelSize), Point(-1, -1))
- CvInvoke.MorphologyEx(imgFilteredParasite, filteredGreenWithoutCytoplasm, CvEnum.MorphOp.Close, kernel, Point(-1, -1), 1, CvEnum.BorderType.Replicate, MCvScalar())
- let cytoplasmMarker = marker imgFilteredParasite filteredGreenWithoutCytoplasm (1. / config.Parameters.cytoplasmSensitivity)
+ CvInvoke.MorphologyEx(img, imgWithoutParasite, CvEnum.MorphOp.Close, kernel, Point(-1, -1), 1, CvEnum.BorderType.Replicate, MCvScalar())
+ let parasiteMarker = marker img imgWithoutParasite (1. / config.Parameters.cytoplasmSensitivity)
{ darkStain = darkStain
nucleus = nucleusMarker
- cytoplasm = cytoplasmMarker },
- filteredGreenWithoutCytoplasm,
- filteredGreenWithoutNucleus
+ parasite = parasiteMarker },
+ imgWithoutParasite,
+ imgWithoutNucleus