* Add area granulometry (not used for the moment)
authorGreg Burri <greg.burri@gmail.com>
Thu, 7 Jan 2016 17:34:45 +0000 (18:34 +0100)
committerGreg Burri <greg.burri@gmail.com>
Thu, 7 Jan 2016 17:34:45 +0000 (18:34 +0100)
* beginning of a little GUI.

22 files changed:
Parasitemia/Parasitemia.sln
Parasitemia/Parasitemia/Classifier.fs
Parasitemia/Parasitemia/Const.fs [new file with mode: 0644]
Parasitemia/Parasitemia/GUI/GUI.fs [new file with mode: 0644]
Parasitemia/Parasitemia/GUI/MainWindow.xaml [new file with mode: 0644]
Parasitemia/Parasitemia/GUI/MainWindow.xaml.fs [new file with mode: 0644]
Parasitemia/Parasitemia/GUI/NumericUpDown.xaml [new file with mode: 0644]
Parasitemia/Parasitemia/GUI/NumericUpDown.xaml.fs [new file with mode: 0644]
Parasitemia/Parasitemia/GUI/Pia.fs [new file with mode: 0644]
Parasitemia/Parasitemia/GUI/State.fs [new file with mode: 0644]
Parasitemia/Parasitemia/Granulometry.fs
Parasitemia/Parasitemia/ImgTools.fs
Parasitemia/Parasitemia/KMeans.fs
Parasitemia/Parasitemia/MainAnalysis.fs
Parasitemia/Parasitemia/MainWindow.xaml [deleted file]
Parasitemia/Parasitemia/MainWindow.xaml.fs [deleted file]
Parasitemia/Parasitemia/NumericUpDown.xaml [deleted file]
Parasitemia/Parasitemia/NumericUpDown.xaml.fs [deleted file]
Parasitemia/Parasitemia/Parasitemia.fsproj
Parasitemia/Parasitemia/Program.fs
Parasitemia/Parasitemia/Types.fs
Parasitemia/Parasitemia/packages.config

index aeabd42..ea32a4b 100644 (file)
@@ -1,7 +1,7 @@
 
 Microsoft Visual Studio Solution File, Format Version 12.00
 # Visual Studio 14
-VisualStudioVersion = 14.0.22823.1
+VisualStudioVersion = 14.0.24720.0
 MinimumVisualStudioVersion = 10.0.40219.1
 Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Parasitemia", "Parasitemia\Parasitemia.fsproj", "{70838E65-F211-44FC-B28F-0ED1CA6E850F}"
 EndProject
@@ -10,15 +10,20 @@ EndProject
 Global
        GlobalSection(SolutionConfigurationPlatforms) = preSolution
                Debug|Any CPU = Debug|Any CPU
+               DebugGUI|Any CPU = DebugGUI|Any CPU
                Release|Any CPU = Release|Any CPU
        EndGlobalSection
        GlobalSection(ProjectConfigurationPlatforms) = postSolution
                {70838E65-F211-44FC-B28F-0ED1CA6E850F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
                {70838E65-F211-44FC-B28F-0ED1CA6E850F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+               {70838E65-F211-44FC-B28F-0ED1CA6E850F}.DebugGUI|Any CPU.ActiveCfg = DebugGUI|Any CPU
+               {70838E65-F211-44FC-B28F-0ED1CA6E850F}.DebugGUI|Any CPU.Build.0 = DebugGUI|Any CPU
                {70838E65-F211-44FC-B28F-0ED1CA6E850F}.Release|Any CPU.ActiveCfg = Release|Any CPU
                {70838E65-F211-44FC-B28F-0ED1CA6E850F}.Release|Any CPU.Build.0 = Release|Any CPU
                {314FD78E-870E-4794-BB16-EA4586F2ABDB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
                {314FD78E-870E-4794-BB16-EA4586F2ABDB}.Debug|Any CPU.Build.0 = Debug|Any CPU
+               {314FD78E-870E-4794-BB16-EA4586F2ABDB}.DebugGUI|Any CPU.ActiveCfg = Debug|Any CPU
+               {314FD78E-870E-4794-BB16-EA4586F2ABDB}.DebugGUI|Any CPU.Build.0 = Debug|Any CPU
                {314FD78E-870E-4794-BB16-EA4586F2ABDB}.Release|Any CPU.ActiveCfg = Release|Any CPU
                {314FD78E-870E-4794-BB16-EA4586F2ABDB}.Release|Any CPU.Build.0 = Release|Any CPU
        EndGlobalSection
index 341f7c9..3a679e7 100644 (file)
@@ -229,4 +229,5 @@ let findCells (ellipses: Ellipse list) (parasites: ParasitesMarker.Result) (img:
 
                 Some { cellClass = cellClass
                        center = Point(roundInt e.Cx, roundInt e.Cy)
+                       stainArea = stainPixels
                        elements = elements })
diff --git a/Parasitemia/Parasitemia/Const.fs b/Parasitemia/Parasitemia/Const.fs
new file mode 100644 (file)
index 0000000..0d2e52e
--- /dev/null
@@ -0,0 +1,4 @@
+module Const
+
+// TODO: try with a literal and check performance.
+let PI = float32 System.Math.PI
\ No newline at end of file
diff --git a/Parasitemia/Parasitemia/GUI/GUI.fs b/Parasitemia/Parasitemia/GUI/GUI.fs
new file mode 100644 (file)
index 0000000..cf7ed67
--- /dev/null
@@ -0,0 +1,76 @@
+module Parasitemia.GUI
+
+open System.IO
+open System.Windows
+open System.Windows.Media
+open System.Windows.Markup
+open System.Windows.Shapes
+open System.Windows.Controls
+open System.Drawing
+open System.Diagnostics
+open Microsoft.Win32 // For the common dialogs.
+
+open Emgu.CV
+open Emgu.CV.Structure
+open Emgu.CV.WPF
+
+open Config
+
+let run (defaultConfig: Config) =
+    let app = new Application()
+    let mainWindow = Views.MainWindow()
+    let ctrl (name: string): 'a =
+        mainWindow.Root.FindName(name) :?> 'a
+
+    // Utils.log <- (fun m -> log mainWindow m)
+
+    let state = State.State()
+
+    let exit: Controls.MenuItem = ctrl "menuExit"
+    let save: Controls.MenuItem = ctrl "menuSave"
+    let load: Controls.MenuItem = ctrl "menuOpen"
+
+    let txtPatient: Controls.TextBox = ctrl "txtPatient"
+
+
+    let synchronizeState () =
+        state.PatientID <- txtPatient.Text
+
+    let synchronizeView () =
+        txtPatient.Text <- state.PatientID
+
+    exit.Click.AddHandler(fun obj args -> mainWindow.Root.Close())
+    save.Click.AddHandler(fun obj args ->
+        synchronizeState ()
+        if state.FilePath = ""
+        then
+            let dialog = SaveFileDialog(AddExtension = true, DefaultExt = Pia.extension, Filter = Pia.filter);
+            let res = dialog.ShowDialog()
+            if res.HasValue && res.Value
+            then
+                state.FilePath <- dialog.FileName
+                state.Save()
+        else
+            state.Save())
+
+    load.Click.AddHandler(fun obj args ->
+        // TODO: if current state not saved and not empty, ask to save it.
+        let dialog = OpenFileDialog(Filter = Pia.filter)
+        let res = dialog.ShowDialog()
+        if res.HasValue && res.Value
+        then
+            state.FilePath <- dialog.FileName
+            state.Load()
+            synchronizeView ())
+
+    (*let txtPatient: Controls.TextBox = ctrl "txtPatient"
+    txtPatient.TextChanged.AddHandler(fun obj args ->
+        state.PatientID <- txtPatient.Text)*)
+
+     (*saveFileDialog1.Filter = "txt files (*.txt)|*.txt|All files (*.*)|*.*"  ;
+     saveFileDialog1.FilterIndex = 2 ;
+     saveFileDialog1.RestoreDirectory = true ;*)
+
+    // display mainWindow img
+    mainWindow.Root.Show()
+    app.Run()
\ No newline at end of file
diff --git a/Parasitemia/Parasitemia/GUI/MainWindow.xaml b/Parasitemia/Parasitemia/GUI/MainWindow.xaml
new file mode 100644 (file)
index 0000000..5b7a3c5
--- /dev/null
@@ -0,0 +1,39 @@
+<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" x:Name="MainWindow" Height="681.888" Width="787.61">
+   <DockPanel x:Name="dockPanelMain" LastChildFill="True">
+      <Menu DockPanel.Dock="Top">
+         <MenuItem Header="_File">
+            <MenuItem x:Name="menuNew" Header="_New"  />
+            <MenuItem x:Name="menuOpen" Header="_Open" />
+            <MenuItem x:Name="menuSave" Header="_Save" />
+            <Separator />
+            <MenuItem x:Name="menuExit" Header="_Exit" />
+         </MenuItem>
+      </Menu>
+
+      <Grid x:Name="gridMain">
+         <Grid.RowDefinitions>
+            <RowDefinition Height="Auto"/>
+            <RowDefinition/>
+         </Grid.RowDefinitions>
+         <Grid.ColumnDefinitions>
+            <ColumnDefinition/>
+            <ColumnDefinition Width="5*"/>
+         </Grid.ColumnDefinitions>
+         <Grid x:Name="gridGlobalInfo" Grid.ColumnSpan="2" Margin="3,3,3,3" >
+            <Grid.ColumnDefinitions>
+               <ColumnDefinition Width="Auto"/>
+               <ColumnDefinition/>
+            </Grid.ColumnDefinitions>
+            <Grid.RowDefinitions>
+               <RowDefinition Height="Auto"/>
+               <RowDefinition Height="Auto"/>
+            </Grid.RowDefinitions>
+            <Label x:Name="lblPatient" Margin="10, 3, 3, 3" Content="Patient"/>
+            <Label x:Name="lblGlobalParasitemia" Margin="10,3,3,3" Content="Global parasitemia" Grid.Row="1" />
+            <TextBox x:Name="txtPatient" Grid.Column="1" Margin="3, 3, 10, 3" TextWrapping="Wrap" VerticalAlignment="Center" />
+            <TextBox x:Name="txtGlobalParasitemia" Grid.Column="1" Grid.Row="2" Margin="3, 3, 10, 3" TextWrapping="Wrap" VerticalAlignment="Center" IsReadOnly="True" />
+         </Grid>
+      </Grid>
+   </DockPanel>
+</Window>
\ No newline at end of file
diff --git a/Parasitemia/Parasitemia/GUI/MainWindow.xaml.fs b/Parasitemia/Parasitemia/GUI/MainWindow.xaml.fs
new file mode 100644 (file)
index 0000000..62c35a4
--- /dev/null
@@ -0,0 +1,6 @@
+namespace Parasitemia.Views
+
+open FsXaml
+
+type MainWindow = XAML<"GUI/MainWindow.xaml">
+
diff --git a/Parasitemia/Parasitemia/GUI/NumericUpDown.xaml b/Parasitemia/Parasitemia/GUI/NumericUpDown.xaml
new file mode 100644 (file)
index 0000000..ae20a5b
--- /dev/null
@@ -0,0 +1,21 @@
+<UserControl
+               xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+               xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+               xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+      mc:Ignorable="d" d:DesignWidth="259.5" d:DesignHeight="45.5"
+               >
+   <Grid>
+      <Grid.RowDefinitions>
+         <RowDefinition />
+         <RowDefinition />
+      </Grid.RowDefinitions>
+      <Grid.ColumnDefinitions>
+         <ColumnDefinition Width="*" />
+         <ColumnDefinition Width="Auto" />
+      </Grid.ColumnDefinitions>
+      <Button x:Name="upButton" Grid.Column="1" Content="^"/>
+      <Button x:Name="downButton" Grid.Column="1" Grid.Row="1" Content="v"/>
+      <TextBox x:Name="input" Grid.RowSpan="2" />
+   </Grid>
+</UserControl>
\ No newline at end of file
diff --git a/Parasitemia/Parasitemia/GUI/NumericUpDown.xaml.fs b/Parasitemia/Parasitemia/GUI/NumericUpDown.xaml.fs
new file mode 100644 (file)
index 0000000..6f91924
--- /dev/null
@@ -0,0 +1,17 @@
+namespace Parasitemia.Views
+
+open System
+open System.Windows
+open System.Windows.Data
+open System.Windows.Input
+
+open FsXaml
+
+type NumericUpDown = XAML<"GUI/NumericUpDown.xaml", true>
+
+type NumericUpDownEvents = Up | Down
+
+type NumericUpDownController() =
+    inherit UserControlViewController<NumericUpDown>()
+
+
diff --git a/Parasitemia/Parasitemia/GUI/Pia.fs b/Parasitemia/Parasitemia/GUI/Pia.fs
new file mode 100644 (file)
index 0000000..9ddd063
--- /dev/null
@@ -0,0 +1,71 @@
+// ParasitemIA file format.
+module Pia
+
+open System.Drawing
+open System.IO
+open System.IO.Compression
+
+open FSharp.Data
+
+open Emgu.CV
+open Emgu.CV.Structure
+
+let extension = ".pia"
+let filter = "PIA|*.pia"
+
+type RBC = {
+    infected: bool
+    addedManually: bool
+    removed: bool
+
+    center: Point
+    size: Size
+    stainArea: int }
+
+type ImageSource = {
+    img: Image<Bgr, byte>
+    rbcs: RBC list }
+
+type FileData = {
+    sources: ImageSource list
+    patientID: string }
+
+// The json type associated to a source image.
+type JSONSourceImage = JsonProvider<"""
+    {
+        "rbcs": [
+            { "infected": true, "addedManually": false, "removed": false, "posX" : 42, "posY" : 42, "width" : 10, "height" : 10, "stainArea" : 10 }
+        ]
+    }
+""">
+
+// The json type associated to a file.
+type JSONMainInformation = JsonProvider<"""
+    {
+        "patientID": "1234abcd"
+    }
+""">
+
+let mainFilename = "info.json"
+
+let save (filePath: string) (data: FileData) =
+    use file = ZipFile.Open(filePath, ZipArchiveMode.Update)
+
+    let mainJSON = JSONMainInformation.Root(data.patientID)
+
+    let mainFile =
+        match file.GetEntry(mainFilename) with
+        | null -> file.CreateEntry(mainFilename)
+        | entry -> entry
+
+    use mainFileWriter = new StreamWriter(mainFile.Open())
+    mainJSON.JsonValue.WriteTo(mainFileWriter, JsonSaveOptions.None)
+
+
+let load (filePath: string) : FileData =
+    use file = ZipFile.Open(filePath, ZipArchiveMode.Read)
+
+    let mainFile = file.GetEntry(mainFilename)
+    let mainJSON = JSONMainInformation.Load(mainFile.Open())
+
+    { sources = []; patientID = mainJSON.PatientId }
\ No newline at end of file
diff --git a/Parasitemia/Parasitemia/GUI/State.fs b/Parasitemia/Parasitemia/GUI/State.fs
new file mode 100644 (file)
index 0000000..a4a1773
--- /dev/null
@@ -0,0 +1,17 @@
+module State
+
+open System.Collections.Generic
+
+type State () =
+    let cells = List<Types.Cell>()
+
+    member val FilePath: string = "" with get, set
+    member val PatientID: string = "" with get, set
+
+    member this.Save () =
+        let data = { Pia.sources = []; Pia.patientID = this.PatientID }
+        Pia.save this.FilePath data
+
+    member this.Load () =
+        let data = Pia.load this.FilePath
+        this.PatientID <- data.patientID
\ No newline at end of file
index 0ef862b..7886043 100644 (file)
@@ -10,7 +10,7 @@ open Utils
 
 // 'range': a minimum and maximum radius.
 // 'scale': <= 1.0, to speed up the process.
-let findRadius (img: Image<Gray, 'TDepth>) (range: int * int) (scale: float) : int =
+let findRadiusByClosing (img: Image<Gray, 'TDepth>) (range: int * int) (scale: float) : int =
     use scaledImg = if scale = 1. then img else img.Resize(scale, CvEnum.Inter.Area)
 
     let r1, r2 = range
@@ -19,9 +19,26 @@ let findRadius (img: Image<Gray, 'TDepth>) (range: int * int) (scale: float) : i
     let patternSpectrum = Array.zeroCreate (r2' - r1')
     let intensityImg = scaledImg.GetSum().Intensity
 
+    // 's' must be odd.
+    let octagon (s: int) : Matrix<byte> =
+        if s % 2 = 0 then failwith "s must be odd"
+        let m = new Matrix<byte>(Array2D.create s s 1uy)
+        let r = (float s) / (Math.Sqrt 2. + 2.) |> roundInt
+        for i in 0 .. r - 1 do
+            for j in 0 .. r - 1 do
+                if i + j < r
+                then
+                    m.[i, j] <- 0uy
+                    m.[s - i - 1, j] <- 0uy
+                    m.[i, s - j - 1] <- 0uy
+                    m.[s - i - 1, s - j - 1] <- 0uy
+        m
+
     let mutable previous_n = Double.NaN
     for r in r1' .. r2' do
         let se = CvInvoke.GetStructuringElement(CvEnum.ElementShape.Ellipse, Size(2 * r, 2 * r), Point(-1, -1))
+        //let se = octagon (2 * r - 1)
+
         use closed = scaledImg.MorphologyEx(CvEnum.MorphOp.Close, se, Point(-1, -1), 1, CvEnum.BorderType.Replicate, MCvScalar(0.0))
 
         let n = closed.GetSum().Intensity
@@ -36,3 +53,18 @@ let findRadius (img: Image<Gray, 'TDepth>) (range: int * int) (scale: float) : i
     float (max + r1') / scale |> roundInt
 
 
+let findRadiusByAreaClosing (img: Image<Gray, float32>) (range: int * int) : int =
+    let r1, r2 = range
+
+    use imgCopy = img.Copy()
+
+    let mutable maxDiff = 0.f
+    let mutable max_r = r1
+
+    ImgTools.areaCloseFWithFun imgCopy [ for r in r1 .. r2 -> Math.PI * float r ** 2. |> roundInt, r ] (fun r diff ->
+        if r <> r1 && diff > maxDiff
+        then
+            maxDiff <- diff
+            max_r <- r - 1 )
+    max_r
+
index 3cfdc89..23c9aee 100644 (file)
@@ -198,9 +198,7 @@ let findEdges (img: Image<Gray, float32>) : Matrix<byte> * Image<Gray, float32>
         let sensibilityHigh = 0.1f
         let sensibilityLow = 0.0f
         use magnitudesByte = magnitudes.Convert<byte>()
-        let threshold = float32 <| CvInvoke.Threshold(magnitudesByte, magnitudesByte, 0.0, 1.0, CvEnum.ThresholdType.Otsu ||| CvEnum.ThresholdType.Binary)
         let threshold, _, _ = otsu (histogramMat magnitudes 300)
-
         threshold + (sensibilityHigh * threshold), threshold - (sensibilityLow * threshold)
 
     // Non-maximum suppression.
@@ -556,9 +554,9 @@ let private areaOperation (img: Image<Gray, byte>) (area: int) (op: AreaOperatio
                         nextElements.Add(p) |> ignore
 
                 else
-                    let m' = pixels.[p.Y, p.X]
-                    if m' <> null
-                    then
+                    match pixels.[p.Y, p.X] with
+                    | null -> ()
+                    | m' ->
                         if m'.Elements.Count + m.Elements.Count <= area
                         then
                             m'.State <- AreaState.Removed
@@ -628,7 +626,7 @@ type Island (cmp: IComparer<float32>) =
     member val Surface = 0 with get, set
 
 
-let private areaOperationF (img: Image<Gray, float32>) (area: int) (op: AreaOperation) =
+let private areaOperationF (img: Image<Gray, float32>) (areas: (int * 'a) list) (f: ('a -> float32 -> unit) option) (op: AreaOperation) =
     let w = img.Width
     let h = img.Height
     let earth = img.Data
@@ -655,82 +653,97 @@ let private areaOperationF (img: Image<Gray, float32>) (area: int) (op: AreaOper
                 let ni = i + p.Y
                 let nj = j + p.X
                 let neighbor = Point(nj, ni)
-                if ni >= 0 && ni < h && nj >= 0 && nj < w && ownership.[ni, nj] = null && not (shorePoints.Contains(neighbor))
+                if ni >= 0 && ni < h && nj >= 0 && nj < w && Object.ReferenceEquals(ownership.[ni, nj], null) && not (shorePoints.Contains(neighbor))
                 then
                     shorePoints.Add(neighbor) |> ignore
                     island.Shore.Add earth.[ni, nj, 0] neighbor
 
-    for island in islands do
-        let mutable stop = island.Shore.IsEmpty
-
-        // 'true' if 'p' is owned or adjacent to 'island'.
-        let ownedOrAdjacent (p: Point) : bool =
-            ownership.[p.Y, p.X] = island ||
-            (p.Y > 0 && ownership.[p.Y - 1, p.X] = island) ||
-            (p.Y < h - 1 && ownership.[p.Y + 1, p.X] = island) ||
-            (p.X > 0 && ownership.[p.Y, p.X - 1] = island) ||
-            (p.X < w - 1 && ownership.[p.Y, p.X + 1] = island)
-
-        while not stop && island.Surface < area do
-            let level, next = island.Shore.Max
-            let other = ownership.[next.Y, next.X]
-            if other = island // During merging, some points on the shore may be owned by the island itself -> ignored.
-            then
-                island.Shore.RemoveNext ()
-            else
-                if other <> null
-                then // We touching another island.
-                    if island.Surface + other.Surface >= area
-                    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
-                        for l, p in other.Shore do
-                            let mutable currentY = p.Y + 1
-                            while currentY < h && ownership.[currentY, p.X] = other do
-                                ownership.[currentY, p.X] <- island
-                                currentY <- currentY + 1
-                            island.Shore.Add l p
-                        other.Shore.Clear()
-
-                elif comparer.Compare(level, island.Level) > 0
+    for area, obj in areas do
+        for island in islands do
+            let mutable stop = island.Shore.IsEmpty
+
+            // 'true' if 'p' is owned or adjacent to 'island'.
+            let inline ownedOrAdjacent (p: Point) : bool =
+                ownership.[p.Y, p.X] = island ||
+                (p.Y > 0 && ownership.[p.Y - 1, p.X] = island) ||
+                (p.Y < h - 1 && ownership.[p.Y + 1, p.X] = island) ||
+                (p.X > 0 && ownership.[p.Y, p.X - 1] = island) ||
+                (p.X < w - 1 && ownership.[p.Y, p.X + 1] = island)
+
+            while not stop && island.Surface < area do
+                let level, next = island.Shore.Max
+                let other = ownership.[next.Y, next.X]
+                if other = island // During merging, some points on the shore may be owned by the island itself -> ignored.
                 then
-                    stop <- true
-                else
                     island.Shore.RemoveNext ()
-                    for i, j in se do
-                        let ni = i + next.Y
-                        let nj = j + next.X
-                        if ni < 0 || ni >= h || nj < 0 || nj >= w
+                else
+                    if not <| Object.ReferenceEquals(other, null)
+                    then // We touching another island.
+                        if island.Surface + other.Surface >= area
                         then
-                            island.Surface <- Int32.MaxValue
                             stop <- true
-                        else
-                            let neighbor = Point(nj, ni)
-                            if not <| ownedOrAdjacent neighbor
-                            then
-                                island.Shore.Add earth.[ni, nj, 0] neighbor
-                    if not stop
+                        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
+                            for l, p in other.Shore do
+                                let mutable currentY = p.Y + 1
+                                while currentY < h && ownership.[currentY, p.X] = other do
+                                    ownership.[currentY, p.X] <- island
+                                    currentY <- currentY + 1
+                                island.Shore.Add l p
+                            other.Shore.Clear()
+
+                    elif comparer.Compare(level, island.Level) > 0
                     then
-                        ownership.[next.Y, next.X] <- island
-                        island.Level <- level
-                        island.Surface <- island.Surface + 1
-
-    for i in 0 .. h - 1 do
-        for j in 0 .. w - 1 do
-            let island = ownership.[i, j]
-            if island <> null
-            then
-                earth.[i, j, 0] <- island.Level
+                        stop <- true
+                    else
+                        island.Shore.RemoveNext ()
+                        for i, j in se do
+                            let ni = i + next.Y
+                            let nj = j + next.X
+                            if ni < 0 || ni >= h || nj < 0 || nj >= w
+                            then
+                                island.Surface <- Int32.MaxValue
+                                stop <- true
+                            else
+                                let neighbor = Point(nj, ni)
+                                if not <| ownedOrAdjacent neighbor
+                                then
+                                    island.Shore.Add earth.[ni, nj, 0] neighbor
+                        if not stop
+                        then
+                            ownership.[next.Y, next.X] <- island
+                            island.Level <- level
+                            island.Surface <- island.Surface + 1
+
+        let mutable diff = 0.f
+
+        for i in 0 .. h - 1 do
+            for j in 0 .. w - 1 do
+                match ownership.[i, j] with
+                | null -> ()
+                | island ->
+                    let l = island.Level
+                    diff <- diff + l - earth.[i, j, 0]
+                    earth.[i, j, 0] <- l
+
+        match f with
+        | Some f' -> f' obj diff
+        | _ -> ()
     ()
 
 
 let areaOpenF (img: Image<Gray, float32>) (area: int) =
-    areaOperationF img area AreaOperation.Opening
+    areaOperationF img [ area, () ] None AreaOperation.Opening
 
 let areaCloseF (img: Image<Gray, float32>) (area: int) =
-    areaOperationF img area AreaOperation.Closing
+    areaOperationF img [ area, () ] None AreaOperation.Closing
+
+let areaOpenFWithFun (img: Image<Gray, float32>) (areas: (int * 'a) list) (f: 'a -> float32 -> unit) =
+    areaOperationF img areas (Some f) AreaOperation.Opening
+
+let areaCloseFWithFun (img: Image<Gray, float32>) (areas: (int * 'a) list) (f: 'a -> float32 -> unit) =
+    areaOperationF img areas (Some f) AreaOperation.Closing
 
 // A simpler algorithm than 'areaOpen' but slower.
 let areaOpen2 (img: Image<Gray, byte>) (area: int) =
index 55b899d..afb6daf 100644 (file)
@@ -37,8 +37,9 @@ let kmeans (img: Image<Gray, float32>) : Result =
     let fgData = fg.Data
 
     for i in 1 .. nbIteration do
-        if d_bg <> null
-        then
+        match d_bg with
+        | null -> ()
+        | _ ->
             d_bg.Dispose()
             d_fg.Dispose()
 
index 9f91a6d..043eb73 100644 (file)
@@ -19,9 +19,13 @@ let doAnalysis (img: Image<Bgr, byte>) (name: string) (config: Config) : Cell li
 
     logTime "areaOpen 1" (fun () -> ImgTools.areaOpenF filteredGreen config.Parameters.initialAreaOpen)
 
-    config.RBCRadius <- logTime "Granulometry" (fun() -> Granulometry.findRadius (filteredGreen.Convert<Gray, byte>()) (10, 100) 0.3 |> float32)
+    let r1 = logTime "Granulometry (morpho)" (fun() -> Granulometry.findRadiusByClosing (filteredGreen.Convert<Gray, byte>()) (10, 80) 0.5 |> float32)
+    // let r2 = logTime "Granulometry (area)" (fun() -> Granulometry.findRadiusByAreaClosing filteredGreen (10, 80) |> float32)
+    // log (sprintf "r1: %A, r2: %A" r1 r2)
+    config.RBCRadius <- r1
 
     let secondAreaOpen = int <| config.RBCArea / 3.f
+
     if secondAreaOpen > config.Parameters.initialAreaOpen
     then
         logTime "areaOpen 2" (fun () -> ImgTools.areaOpenF filteredGreen secondAreaOpen)
diff --git a/Parasitemia/Parasitemia/MainWindow.xaml b/Parasitemia/Parasitemia/MainWindow.xaml
deleted file mode 100644 (file)
index 4d1a8b0..0000000
+++ /dev/null
@@ -1,30 +0,0 @@
-<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
-        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" x:Name="MainWindow" Height="931.638" Width="1360.61">
-   <Grid x:Name="mainGrid" Margin="10" RenderTransformOrigin="0.491,0.524">
-      <Grid.RowDefinitions>
-         <RowDefinition/>
-         <RowDefinition Height="100"/>
-      </Grid.RowDefinitions>
-      <Grid.ColumnDefinitions>
-         <ColumnDefinition/>
-         <ColumnDefinition Width="300"/>
-      </Grid.ColumnDefinitions>
-      <Image x:Name="img" HorizontalAlignment="Stretch" Margin="10" VerticalAlignment="Stretch"/>
-      <TextBlock x:Name="txtLog" Margin="10" TextWrapping="Wrap" FontFamily="Monaco" Grid.Row="2" Background="#FF95E633" Grid.ColumnSpan="2"/>
-      <Grid Grid.Column="1" Margin="10" >
-         <Grid.RowDefinitions>
-            <RowDefinition Height="40"/>
-            <RowDefinition Height="40"/>
-            <RowDefinition Height="40"/>
-            <RowDefinition />
-         </Grid.RowDefinitions>
-         <Grid.ColumnDefinitions>
-            <ColumnDefinition/>
-            <ColumnDefinition/>
-         </Grid.ColumnDefinitions>
-         <Label Content="Ecart type 1" Margin="0,7" VerticalAlignment="Center" HorizontalAlignment="Left" HorizontalContentAlignment="Stretch" />
-         <Label Content="Facteur deuxième gaussienne" Margin="0,7" VerticalAlignment="Center" HorizontalAlignment="Left" HorizontalContentAlignment="Stretch" Grid.Row="1" />
-         <Label Content="Facteur deuxième gaussienne" Margin="0,7" VerticalAlignment="Center" HorizontalAlignment="Left" HorizontalContentAlignment="Stretch" Grid.Row="2" />
-      </Grid>
-   </Grid>
-</Window>
\ No newline at end of file
diff --git a/Parasitemia/Parasitemia/MainWindow.xaml.fs b/Parasitemia/Parasitemia/MainWindow.xaml.fs
deleted file mode 100644 (file)
index 26d30c9..0000000
+++ /dev/null
@@ -1,6 +0,0 @@
-namespace Parasitemia.Views
-
-open FsXaml
-
-type MainWindow = XAML<"MainWindow.xaml">
-
diff --git a/Parasitemia/Parasitemia/NumericUpDown.xaml b/Parasitemia/Parasitemia/NumericUpDown.xaml
deleted file mode 100644 (file)
index ae20a5b..0000000
+++ /dev/null
@@ -1,21 +0,0 @@
-<UserControl
-               xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
-               xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
-               xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
-      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
-      mc:Ignorable="d" d:DesignWidth="259.5" d:DesignHeight="45.5"
-               >
-   <Grid>
-      <Grid.RowDefinitions>
-         <RowDefinition />
-         <RowDefinition />
-      </Grid.RowDefinitions>
-      <Grid.ColumnDefinitions>
-         <ColumnDefinition Width="*" />
-         <ColumnDefinition Width="Auto" />
-      </Grid.ColumnDefinitions>
-      <Button x:Name="upButton" Grid.Column="1" Content="^"/>
-      <Button x:Name="downButton" Grid.Column="1" Grid.Row="1" Content="v"/>
-      <TextBox x:Name="input" Grid.RowSpan="2" />
-   </Grid>
-</UserControl>
\ No newline at end of file
diff --git a/Parasitemia/Parasitemia/NumericUpDown.xaml.fs b/Parasitemia/Parasitemia/NumericUpDown.xaml.fs
deleted file mode 100644 (file)
index b30e59a..0000000
+++ /dev/null
@@ -1,17 +0,0 @@
-namespace Parasitemia.Views
-
-open System
-open System.Windows
-open System.Windows.Data
-open System.Windows.Input
-
-open FsXaml
-
-type NumericUpDown = XAML<"NumericUpDown.xaml", true>
-    
-type NumericUpDownEvents = Up | Down
-
-type NumericUpDownController() = 
-    inherit UserControlViewController<NumericUpDown>()
-
-
index 14e1c11..abdb475 100644 (file)
     <Prefer32Bit>false</Prefer32Bit>
     <StartArguments>--folder "../../../Images/debug" --output "../../../Images/output" --debug</StartArguments>
   </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'DebugGUI|AnyCPU' ">
+    <DebugSymbols>true</DebugSymbols>
+    <DebugType>full</DebugType>
+    <Optimize>false</Optimize>
+    <Tailcalls>false</Tailcalls>
+    <DefineConstants>DEBUG;TRACE</DefineConstants>
+    <WarningLevel>3</WarningLevel>
+    <PlatformTarget>x64</PlatformTarget>
+    <DocumentationFile>bin\Debug\Parasitemia.XML</DocumentationFile>
+    <Prefer32Bit>false</Prefer32Bit>
+    <StartArguments>
+    </StartArguments>
+    <OutputPath>bin\DebugGUI\</OutputPath>
+  </PropertyGroup>
   <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
     <DebugType>pdbonly</DebugType>
     <Optimize>true</Optimize>
   <Import Project="$(FSharpTargetsPath)" />
   <ItemGroup>
     <Compile Include="AssemblyInfo.fs" />
-    <None Include="NumericUpDown.xaml" />
-    <Compile Include="NumericUpDown.xaml.fs" />
-    <Resource Include="MainWindow.xaml" />
-    <Compile Include="MainWindow.xaml.fs" />
     <Compile Include="Heap.fs" />
     <Compile Include="Const.fs" />
     <Compile Include="Types.fs" />
     <Compile Include="ParasitesMarker.fs" />
     <Compile Include="KdTree.fs" />
     <Compile Include="MatchingEllipses.fs" />
-    <Compile Include="Classifier.fs" />
     <Compile Include="Ellipse.fs" />
+    <Compile Include="Classifier.fs" />
     <Compile Include="MainAnalysis.fs" />
+    <None Include="GUI\NumericUpDown.xaml" />
+    <Compile Include="GUI\NumericUpDown.xaml.fs" />
+    <Resource Include="GUI\MainWindow.xaml" />
+    <Compile Include="GUI\MainWindow.xaml.fs" />
+    <Compile Include="GUI\Pia.fs" />
+    <Compile Include="GUI\State.fs" />
+    <Compile Include="GUI\GUI.fs" />
     <Compile Include="Program.fs" />
     <None Include="App.config" />
     <Content Include="packages.config" />
       <HintPath>..\packages\FSharp.Core.4.0.0.1\lib\net40\FSharp.Core.dll</HintPath>
       <Private>True</Private>
     </Reference>
+    <Reference Include="FSharp.Data">
+      <HintPath>..\packages\FSharp.Data.2.2.5\lib\net40\FSharp.Data.dll</HintPath>
+      <Private>True</Private>
+    </Reference>
     <Reference Include="FSharp.ViewModule">
       <HintPath>..\packages\FSharp.ViewModule.Core.0.9.9.2\lib\portable-net45+netcore45+wpa81+wp8+MonoAndroid1+MonoTouch1\FSharp.ViewModule.dll</HintPath>
       <Private>True</Private>
     <Reference Include="System.Data" />
     <Reference Include="System.Data.DataSetExtensions" />
     <Reference Include="System.Drawing" />
+    <Reference Include="System.IO.Compression" />
+    <Reference Include="System.IO.Compression.FileSystem" />
     <Reference Include="System.Numerics" />
     <Reference Include="System.Windows.Interactivity">
       <HintPath>..\packages\Expression.Blend.Sdk.1.0.2\lib\net45\System.Windows.Interactivity.dll</HintPath>
     </Reference>
     <Reference Include="System.Xaml" />
     <Reference Include="System.Xml" />
+    <Reference Include="System.Xml.Linq" />
     <Reference Include="WindowsBase" />
   </ItemGroup>
   <ItemGroup>
index f477cfe..f0e2748 100644 (file)
@@ -2,32 +2,15 @@
 
 open System
 open System.IO
-open System.Windows
-open System.Windows.Media
-open System.Windows.Markup
-open System.Windows.Shapes
-open System.Windows.Controls
-open System.Drawing
-open System.Diagnostics
 open System.Threading
 
 open FSharp.Collections.ParallelSeq
 
 open Emgu.CV
 open Emgu.CV.Structure
-open Emgu.CV.WPF
 
 open Config
 
-let display (window : Views.MainWindow) (img : IImage) =
-   let imgControl = window.Root.FindName("img") :?> Controls.Image
-   imgControl.Source <- BitmapSourceConvert.ToBitmapSource(img)
-
-let log (window : Views.MainWindow) (mess : string) =
-   let txtLog = window.Root.FindName("txtLog") :?> Controls.TextBlock
-   txtLog.Text <- txtLog.Text + mess + "\n"
-
-
 type Input =
     | File of string
     | Dir of string
@@ -57,6 +40,7 @@ let parseArgs (args: string[]) : Arguments =
 
 
 [<EntryPoint>]
+[<STAThread()>]
 let main args =
     match parseArgs args with
     | mode, debug ->
@@ -65,8 +49,8 @@ let main args =
               {
                 initialAreaOpen = 2000
 
-                minRbcRadius = -0.32f
-                maxRbcRadius = 0.32f
+                minRbcRadius = -0.3f
+                maxRbcRadius = 0.3f
 
                 preFilterSigma = 1.7 // 1.5
 
@@ -125,15 +109,13 @@ let main args =
             0
 
         | Window ->
-            let app = new Application()
-            let mainWindow = Views.MainWindow()
-
-            if debug
-            then
-                config.Debug <- DebugOn "."
+            (*let display (window : Views.MainWindow) (img : IImage) =
+               let imgControl = window.Root.FindName("img") :?> Controls.Image
+               imgControl.Source <- BitmapSourceConvert.ToBitmapSource(img)
 
-            Utils.log <- (fun m -> log mainWindow m)
+            let log (window : Views.MainWindow) (mess : string) =
+               let txtLog = window.Root.FindName("txtLog") :?> Controls.TextBlock
+               txtLog.Text <- txtLog.Text + mess + "\n"*)
 
-            //display mainWindow img
-            mainWindow.Root.Show()
-            app.Run()
+            if debug then config.Debug <- DebugOn "."
+            GUI.run config
index 6240519..872844b 100644 (file)
@@ -48,6 +48,7 @@ type CellClass = HealthyRBC | InfectedRBC | Peculiar
 type Cell = {
     cellClass: CellClass
     center: Point
+    stainArea: int
     elements: Matrix<byte> }
 
 [<Struct>]
index d6eabef..da70217 100644 (file)
@@ -4,6 +4,7 @@
   <package id="Expression.Blend.Sdk" version="1.0.2" targetFramework="net46" />
   <package id="FSharp.Collections.ParallelSeq" version="1.0.2" targetFramework="net461" />
   <package id="FSharp.Core" version="4.0.0.1" targetFramework="net461" />
+  <package id="FSharp.Data" version="2.2.5" targetFramework="net461" />
   <package id="FSharp.ViewModule.Core" version="0.9.9.2" targetFramework="net461" />
   <package id="FsXaml.Wpf" version="0.9.9" targetFramework="net46" />
   <package id="log4net" version="1.2.10" targetFramework="net46" />