Use two radius in the configuration, one computed with the image resolution and one...
authorGreg Burri <greg.burri@gmail.com>
Fri, 15 Jan 2016 09:13:54 +0000 (10:13 +0100)
committerGreg Burri <greg.burri@gmail.com>
Fri, 15 Jan 2016 09:13:54 +0000 (10:13 +0100)
Parasitemia/Parasitemia/Classifier.fs
Parasitemia/Parasitemia/Config.fs
Parasitemia/Parasitemia/Ellipse.fs
Parasitemia/Parasitemia/GUI/Analysis.fs
Parasitemia/Parasitemia/GUI/GUI.fs
Parasitemia/Parasitemia/GUI/State.fs
Parasitemia/Parasitemia/MainAnalysis.fs
Parasitemia/Parasitemia/Parasitemia.fsproj
Parasitemia/Parasitemia/ParasitesMarker.fs

index 1a22129..121a7f5 100644 (file)
@@ -29,10 +29,10 @@ let findCells (ellipses: Ellipse list) (parasites: ParasitesMarker.Result) (img:
         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) =
@@ -97,7 +97,6 @@ let findCells (ellipses: Ellipse list) (parasites: ParasitesMarker.Result) (img:
         // 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
@@ -125,42 +124,9 @@ let findCells (ellipses: Ellipse list) (parasites: ParasitesMarker.Result) (img:
                 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
index e28398e..0916408 100644 (file)
@@ -68,40 +68,54 @@ let defaultParameters = {
     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
index c8d44b7..be35940 100644 (file)
@@ -209,7 +209,7 @@ let find (edges: Matrix<byte>)
          (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.
index ccca1b4..6ae1e20 100644 (file)
@@ -121,7 +121,7 @@ let showWindow (parent: Window) (state: State.State) : bool =
                                 butClose.Content <- "Close"
                                 updateSourceImages ())
 
-                            Utils.log "Analysis terminated successfully"
+                            Utils.log "All analyses terminated successfully"
                             atLeastOneAnalysisPerformed <- true
                             analysisPerformed <- true)
             } |> Async.Start)
@@ -134,9 +134,4 @@ let showWindow (parent: Window) (state: State.State) : bool =
         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
index b0af4cc..65c3cdc 100644 (file)
@@ -114,7 +114,7 @@ let run (defaultConfig: Config) =
             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))
index 3b9d11e..45272ca 100644 (file)
@@ -91,7 +91,7 @@ type State () =
 
         // 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)
index a7fdf71..8f59514 100644 (file)
@@ -31,26 +31,25 @@ let doAnalysis (img: Image<Bgr, byte>) (name: string) (config: Config) (reportPr
     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)
@@ -60,7 +59,7 @@ let doAnalysis (img: Image<Bgr, byte>) (name: string) (config: Config) (reportPr
 
     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 () ->
@@ -139,7 +138,7 @@ let doMultipleAnalysis (imgs: (string * Config * Image<Bgr, byte>) list) (report
         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
index 81198db..ed05b74 100644 (file)
@@ -40,8 +40,7 @@
     <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' ">
index a40e463..a6fce83 100644 (file)
@@ -51,10 +51,10 @@ let findMa (green: Image<Gray, float32>) (filteredGreen: Image<Gray, float32>) (
 // * '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.