From e3842630f4d36c5ea8c8a0c3d4762684e1c510f4 Mon Sep 17 00:00:00 2001 From: Greg Burri Date: Tue, 26 Jan 2016 00:15:47 +0100 Subject: [PATCH] Cleaning + renaming. --- Parasitemia/ParasitemiaCore/Classifier.fs | 49 +++++++++---------- Parasitemia/ParasitemiaCore/Config.fs | 21 ++------ Parasitemia/ParasitemiaCore/ImgTools.fs | 7 +-- Parasitemia/ParasitemiaCore/MainAnalysis.fs | 40 ++++++++------- .../ParasitemiaCore/ParasitesMarker.fs | 29 ++++++----- 5 files changed, 63 insertions(+), 83 deletions(-) diff --git a/Parasitemia/ParasitemiaCore/Classifier.fs b/Parasitemia/ParasitemiaCore/Classifier.fs index 2b6f3be..615840c 100644 --- a/Parasitemia/ParasitemiaCore/Classifier.fs +++ b/Parasitemia/ParasitemiaCore/Classifier.fs @@ -24,8 +24,6 @@ let findCells (ellipses: Ellipse list) (parasites: ParasitesMarker.Result) (img: 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) @@ -150,6 +148,11 @@ let findCells (ellipses: Ellipse list) (parasites: ParasitesMarker.Result) (img: // 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 @@ -158,10 +161,9 @@ let findCells (ellipses: Ellipse list) (parasites: ParasitesMarker.Result) (img: else let minX, minY, maxX, maxY = ellipseWindow e - let infectedPixels = List() - let cytoplasmPixels = List() + let nucleusPixels = List() + let parasitePixels = List() - //let mutable stainPixels = 0 let mutable darkStainPixels = 0 let mutable nbElement = 0 @@ -171,45 +173,40 @@ let findCells (ellipses: Ellipse list) (parasites: ParasitesMarker.Result) (img: 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 @@ -217,6 +214,6 @@ let findCells (ellipses: Ellipse list) (parasites: ParasitesMarker.Result) (img: 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 }) diff --git a/Parasitemia/ParasitemiaCore/Config.fs b/Parasitemia/ParasitemiaCore/Config.fs index 3089953..4b38483 100644 --- a/Parasitemia/ParasitemiaCore/Config.fs +++ b/Parasitemia/ParasitemiaCore/Config.fs @@ -13,10 +13,6 @@ type Parameters = { rbcDiameter: float<μm> resolution: float - 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. @@ -43,21 +39,12 @@ type Parameters = { 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 // 220.e3 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 @@ -76,12 +63,12 @@ let defaultParameters = { 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) = diff --git a/Parasitemia/ParasitemiaCore/ImgTools.fs b/Parasitemia/ParasitemiaCore/ImgTools.fs index 51b0c19..cbb65b4 100644 --- a/Parasitemia/ParasitemiaCore/ImgTools.fs +++ b/Parasitemia/ParasitemiaCore/ImgTools.fs @@ -49,12 +49,7 @@ let mergeChannelsWithProjection (img: Image) (v1r: float32, v1g: f // Normalize image values between 0uy and 255uy. let normalizeAndConvert (img: Image) : Image = - 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() - (!min).[0]) / ((!max).[0] - (!min).[0]) * 255.0).Convert() + (normalize (img.Convert()) 255.).Convert() let saveImg (img: Image<'TColor, 'TDepth>) (filepath: string) = img.Save(filepath) diff --git a/Parasitemia/ParasitemiaCore/MainAnalysis.fs b/Parasitemia/ParasitemiaCore/MainAnalysis.fs index 5a8e16d..9c85c7b 100644 --- a/Parasitemia/ParasitemiaCore/MainAnalysis.fs +++ b/Parasitemia/ParasitemiaCore/MainAnalysis.fs @@ -52,22 +52,20 @@ let doAnalysis (img: Image) (name: string) (config: Config) (reportPr use img_float = img.Convert() - // 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()) range 1.0 |> float32) let! radius = logTimeWithName "Granulometry (area)" (fun() -> reportWithVal 10 (Granulometry.findRadiusByAreaClosing img_RBC_filtered range |> float32)) config.SetRBCRadius <| radius @@ -79,20 +77,20 @@ let doAnalysis (img: Image) (name: string) (config: Config) (reportPr 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)) @@ -114,7 +112,7 @@ let doAnalysis (img: Image) (name: string) (config: Config) (reportPr 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() @@ -134,16 +132,20 @@ let doAnalysis (img: Image) (name: string) (config: Config) (reportPr 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 } diff --git a/Parasitemia/ParasitemiaCore/ParasitesMarker.fs b/Parasitemia/ParasitemiaCore/ParasitesMarker.fs index e3c3ca2..6c25d04 100644 --- a/Parasitemia/ParasitemiaCore/ParasitesMarker.fs +++ b/Parasitemia/ParasitemiaCore/ParasitesMarker.fs @@ -7,25 +7,24 @@ open Emgu.CV open Emgu.CV.Structure open Utils +open ImgTools type Result = { darkStain: Image // Colored pixel, it's independent of the size of the areas. It corresponds to white cells, schizontes, gametocytes, throphozoites. nucleus: Image // Parasite nucleus. It may contain some debris. It shouldn't contain thrombocytes or larger elements. - cytoplasm: Image } // Parasite cytoplasm. + parasite: Image } // The whole parasites. let find (img: Image) (config: Config.Config) : Result * Image * Image = - 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) (closed: Image) (level: float) : Image = let diff = img.Copy() @@ -34,9 +33,9 @@ let find (img: Image) (config: Config.Config) : Result * Image() - 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 @@ -47,13 +46,13 @@ let find (img: Image) (config: Config.Config) : Result * Image