Save predefined PPI and sensor sizes in JSON files.
authorGreg Burri <greg.burri@gmail.com>
Wed, 3 Feb 2016 13:18:27 +0000 (14:18 +0100)
committerGreg Burri <greg.burri@gmail.com>
Wed, 3 Feb 2016 13:18:27 +0000 (14:18 +0100)
Parasitemia/Logger/Logger.fs
Parasitemia/ParasitemiaCore/Analysis.fs
Parasitemia/ParasitemiaCore/Classifier.fs
Parasitemia/ParasitemiaUI/Analysis.fs
Parasitemia/ParasitemiaUI/DPICalculator.fs
Parasitemia/ParasitemiaUI/GUI.fs
Parasitemia/ParasitemiaUI/Types.fs
Parasitemia/ParasitemiaUI/Utils.fs
Parasitemia/ParasitemiaUI/XAML/ImageSourceSelection.xaml
Parasitemia/ParasitemiaUI/XAML/MainWindow.xaml

index 1f029d5..cc02314 100644 (file)
@@ -44,7 +44,7 @@ type Log () =
     let setLogDirectory (dir: string) =
         lock monitor (fun () ->
             logDir <- dir
     let setLogDirectory (dir: string) =
         lock monitor (fun () ->
             logDir <- dir
-            absoluteDir <- Path.Combine(System.Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), logDir)
+            absoluteDir <- Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), logDir)
 
             if stream <> null
             then
 
             if stream <> null
             then
index f7e7b1c..2a7ddc8 100644 (file)
@@ -138,10 +138,10 @@ let doAnalysis (img: Image<Bgr, byte>) (name: string) (config: Config) (reportPr
                 Drawing.drawCells imgCells' true cells
                 IO.saveImg imgCells' (buildFileName " - cells - full.png")
 
                 Drawing.drawCells imgCells' true cells
                 IO.saveImg imgCells' (buildFileName " - cells - full.png")
 
-                let filteredRBCMaxima = gaussianFilter img_RBC config.LPFStandardDeviationRBC
+                (* let filteredRBCMaxima = gaussianFilter img_RBC config.LPFStandardDeviationRBC
                 for m in findMaxima filteredRBCMaxima do
                     Drawing.drawPoints filteredRBCMaxima m 255.f
                 for m in findMaxima filteredRBCMaxima do
                     Drawing.drawPoints filteredRBCMaxima m 255.f
-                IO.saveImg filteredRBCMaxima (buildFileName " - filtered - maxima.png")
+                IO.saveImg filteredRBCMaxima (buildFileName " - filtered - maxima.png") *)
 
                 IO.saveImg imgWhitoutParasite (buildFileName " - filtered closed stain.png")
                 IO.saveImg imgWithoutNucleus (buildFileName " - filtered closed infection.png")
 
                 IO.saveImg imgWhitoutParasite (buildFileName " - filtered closed stain.png")
                 IO.saveImg imgWithoutNucleus (buildFileName " - filtered closed infection.png")
@@ -149,9 +149,9 @@ let doAnalysis (img: Image<Bgr, byte>) (name: string) (config: Config) (reportPr
                 IO.saveImg img_RBC_filtered (buildFileName " - source - RBC.png")
                 IO.saveImg img_parasites_filtered (buildFileName " - source - parasites.png")
 
                 IO.saveImg img_RBC_filtered (buildFileName " - source - RBC.png")
                 IO.saveImg img_parasites_filtered (buildFileName " - source - parasites.png")
 
-                IO.saveImg (normalize img_float.[2] 255.) (buildFileName " - source - red.png")
-                IO.saveImg (normalize img_float.[1] 255.) (buildFileName " - source - green.png")
-                IO.saveImg (normalize img_float.[0] 255.) (buildFileName " - source - blue.png")
+                IO.saveImg img_float.[2] (buildFileName " - source - red.png")
+                IO.saveImg img_float.[1] (buildFileName " - source - green.png")
+                IO.saveImg img_float.[0] (buildFileName " - source - blue.png")
             | _ -> ()
 
         return cells }
             | _ -> ()
 
         return cells }
index ea3f9dd..5a6dcbe 100644 (file)
@@ -178,6 +178,8 @@ let findCells (ellipses: Ellipse list) (parasites: ParasitesMarker.Result) (widt
                                 darkStainPixels <- darkStainPixels + 1
 
                 if float darkStainPixels > config.Parameters.maxDarkStainRatio * (float nbElement) then Some e else None)
                                 darkStainPixels <- darkStainPixels + 1
 
                 if float darkStainPixels > config.Parameters.maxDarkStainRatio * (float nbElement) then Some e else None)
+
+        // We do not change the state during the process to avoid to have peculiar neighbors which change the behavior of 'pixelOwnedByE'.
         |> List.iter (fun e -> e.State <- CellState.Peculiar)
 
         // 5) Define pixels associated to each ellipse and create the cells.
         |> List.iter (fun e -> e.State <- CellState.Peculiar)
 
         // 5) Define pixels associated to each ellipse and create the cells.
index 2ff0b1e..9c7ddf0 100644 (file)
@@ -60,8 +60,12 @@ let showWindow (parent: Window) (state: State.State) : bool =
             imageSourceSelection.lblDateLastAnalysis.Content <- if srcImg.dateLastAnalysis.Ticks = 0L then "<Never>" else srcImg.dateLastAnalysis.ToString()
 
             imageSourceSelection.txtResolution.Text <- if srcImg.dateLastAnalysis.Ticks = 0L then "" else srcImg.config.Parameters.resolution.ToString()
             imageSourceSelection.lblDateLastAnalysis.Content <- if srcImg.dateLastAnalysis.Ticks = 0L then "<Never>" else srcImg.dateLastAnalysis.ToString()
 
             imageSourceSelection.txtResolution.Text <- if srcImg.dateLastAnalysis.Ticks = 0L then "" else srcImg.config.Parameters.resolution.ToString()
-            imageSourceSelection.menuZoom50X.Click.AddHandler(fun obj args -> imageSourceSelection.txtResolution.Text <- "230000")
-            imageSourceSelection.menuZoom100X.Click.AddHandler(fun obj args -> imageSourceSelection.txtResolution.Text <- "460000")
+
+            for ppi in Utils.predefinedPPI do
+                let menu = MenuItem()
+                menu.Header <- ppi.ToString()
+                menu.Click.AddHandler(fun obj args -> imageSourceSelection.txtResolution.Text <- ppi.ppi.ToString())
+                imageSourceSelection.predefinedValuesMenu.Items.Add(menu) |> ignore
 
             imageSourceSelection.butPPICalculator.Click.AddHandler(fun obj args ->
                 match PPICalculator.showWindow win.Root with
 
             imageSourceSelection.butPPICalculator.Click.AddHandler(fun obj args ->
                 match PPICalculator.showWindow win.Root with
index 5e9c19b..c1187ec 100644 (file)
@@ -8,15 +8,10 @@ open System.Windows.Shapes
 open System.Windows.Controls
 open System.Diagnostics
 
 open System.Windows.Controls
 open System.Diagnostics
 
-open ParasitemiaCore.UnitsOfMeasure
 open ParasitemiaCore.Types
 open ParasitemiaCore.Types
+open ParasitemiaCore.UnitsOfMeasure
 
 
-type SensorSize = {
-    w: float<mm>
-    h: float<mm>
-    txt: string } with
-    override this.ToString () =
-        sprintf "%g mm × %g mm%s" this.w this.h (if this.txt = "" then "" else " (" + this.txt + ")")
+open Types
 
 let showWindow (parent: Window) : int option =
     let win = Views.PPICalculatorWindow()
 
 let showWindow (parent: Window) : int option =
     let win = Views.PPICalculatorWindow()
@@ -24,19 +19,7 @@ let showWindow (parent: Window) : int option =
     win.Root.Left <- parent.Left + parent.ActualWidth / 2. - win.Root.Width / 2.
     win.Root.Top <- parent.Top + parent.ActualHeight / 2. - win.Root.Height / 2.
 
     win.Root.Left <- parent.Left + parent.ActualWidth / 2. - win.Root.Width / 2.
     win.Root.Top <- parent.Top + parent.ActualHeight / 2. - win.Root.Height / 2.
 
-    let sensorSizes = [
-        { w = 5.76<mm>; h = 4.29<mm>; txt = "1/2.5″" }
-        { w = 7.6<mm>;  h = 5.7<mm>;  txt = "1/1.7″" }
-        { w = 8.6<mm>;  h = 6.6<mm>;  txt = "2/3″" }
-        { w = 13.2<mm>; h = 8.8<mm>;  txt = "1″" }
-        { w = 17.3<mm>; h = 13.<mm>;  txt = "" }
-        { w = 20.7<mm>; h = 13.8<mm>; txt = "" }
-        { w = 22.2<mm>; h = 14.8<mm>; txt = "" }
-        { w = 23.6<mm>; h = 15.7<mm>; txt = "" }
-        { w = 28.7<mm>; h = 19.<mm>;  txt = "" }
-        { w = 28.7<mm>; h = 19.<mm>;  txt = "" } ]
-
-    for size in sensorSizes do
+    for size in Utils.sensorSizes do
         win.cmbSensorSize.Items.Add(size) |> ignore
     win.cmbSensorSize.SelectedIndex <- 0
 
         win.cmbSensorSize.Items.Add(size) |> ignore
     win.cmbSensorSize.SelectedIndex <- 0
 
index 0b0d6dd..77281d3 100644 (file)
@@ -427,16 +427,7 @@ let run (defaultConfig: Config) (fileToOpen: string option) =
                 Log.Error(ex.ToString())
                 MessageBox.Show(sprintf "The results cannot be exported in \"%s\"" state.FilePath, "Error exporting the files", MessageBoxButton.OK, MessageBoxImage.Error) |> ignore
 
                 Log.Error(ex.ToString())
                 MessageBox.Show(sprintf "The results cannot be exported in \"%s\"" state.FilePath, "Error exporting the files", MessageBoxButton.OK, MessageBoxImage.Error) |> ignore
 
-    win.txtPatient.TextChanged.AddHandler(fun obj args -> state.PatientID <- win.txtPatient.Text)
-
-    win.menuExit.Click.AddHandler(fun obj args -> win.Root.Close())
-    win.menuSave.Click.AddHandler(fun obj args -> saveCurrentDocument ())
-    win.menuSaveAs.Click.AddHandler(fun obj args -> saveCurrentDocumentAsNewFile ())
-    win.menuOpen.Click.AddHandler(fun obj args -> askLoadFile ())
-    win.menuNew.Click.AddHandler(fun obj args -> newFile ())
-    win.menuExportResults.Click.AddHandler(fun obj args -> exportResults ())
-
-    win.menuAddSourceImage.Click.AddHandler(fun obj args ->
+    let importImage () =
         let dialog = OpenFileDialog(Filter = "Image Files|*.png;*.jpg;*.tif;*.tiff", Multiselect = true)
         let res = dialog.ShowDialog()
         if res.HasValue && res.Value
         let dialog = OpenFileDialog(Filter = "Image Files|*.png;*.jpg;*.tif;*.tiff", Multiselect = true)
         let res = dialog.ShowDialog()
         if res.HasValue && res.Value
@@ -456,7 +447,18 @@ let run (defaultConfig: Config) (fileToOpen: string option) =
 
             if noSourceImage
             then
 
             if noSourceImage
             then
-                updateCurrentImage ())
+                updateCurrentImage ()
+
+    win.txtPatient.TextChanged.AddHandler(fun obj args -> state.PatientID <- win.txtPatient.Text)
+
+    win.menuExit.Click.AddHandler(fun obj args -> win.Root.Close())
+    win.menuSave.Click.AddHandler(fun obj args -> saveCurrentDocument ())
+    win.menuSaveAs.Click.AddHandler(fun obj args -> saveCurrentDocumentAsNewFile ())
+    win.menuOpen.Click.AddHandler(fun obj args -> askLoadFile ())
+    win.menuNew.Click.AddHandler(fun obj args -> newFile ())
+    win.menuExportResults.Click.AddHandler(fun obj args -> exportResults ())
+
+    win.menuAddSourceImage.Click.AddHandler(fun obj args -> importImage ())
 
     win.menuAnalysis.SubmenuOpened.AddHandler(fun obj args -> win.menuStartAnalysis.IsEnabled <- state.SourceImages.Count() > 0)
 
 
     win.menuAnalysis.SubmenuOpened.AddHandler(fun obj args -> win.menuStartAnalysis.IsEnabled <- state.SourceImages.Count() > 0)
 
@@ -588,6 +590,12 @@ let run (defaultConfig: Config) (fileToOpen: string option) =
             FSharp.ViewModule.FunCommand((fun obj -> exportResults ()), (fun obj -> true)),
             Input.KeyGesture(Input.Key.E, Input.ModifierKeys.Control))) |> ignore
 
             FSharp.ViewModule.FunCommand((fun obj -> exportResults ()), (fun obj -> true)),
             Input.KeyGesture(Input.Key.E, Input.ModifierKeys.Control))) |> ignore
 
+    // Import an image.
+    win.Root.InputBindings.Add(
+        Input.KeyBinding(
+            FSharp.ViewModule.FunCommand((fun obj -> importImage ()), (fun obj -> true)),
+            Input.KeyGesture(Input.Key.A, Input.ModifierKeys.Control))) |> ignore
+
     // Viewport preview.
     win.scrollViewCurrentImage.ScrollChanged.AddHandler(fun obj args -> updateViewportPreview ())
 
     // Viewport preview.
     win.scrollViewCurrentImage.ScrollChanged.AddHandler(fun obj args -> updateViewportPreview ())
 
index e29465c..fa99d90 100644 (file)
@@ -9,6 +9,8 @@ open Emgu.CV.Structure
 
 open Newtonsoft.Json
 
 
 open Newtonsoft.Json
 
+open ParasitemiaCore.UnitsOfMeasure
+
 let healthyRBColor = Color.FromRgb(255uy, 255uy, 0uy) // Yellow-green.
 let infectedRBColor = Color.FromRgb(255uy, 0uy, 40uy) // Red with a bit of blue.
 
 let healthyRBColor = Color.FromRgb(255uy, 255uy, 0uy) // Yellow-green.
 let infectedRBColor = Color.FromRgb(255uy, 0uy, 40uy) // Red with a bit of blue.
 
@@ -45,4 +47,33 @@ type SourceImage = {
     member this.InfectedRBCColor: SolidColorBrush =
         let mutable color = infectedRBColor * this.infectedRBCBrightness
         color.A <- 255uy;
     member this.InfectedRBCColor: SolidColorBrush =
         let mutable color = infectedRBColor * this.infectedRBCBrightness
         color.A <- 255uy;
-        SolidColorBrush(color)
\ No newline at end of file
+        SolidColorBrush(color)
+
+type PredefinedPPI = {
+    ppi: int<ppi>
+    label: string } with
+    override this.ToString() =
+        sprintf "%s: %d" this.label this.ppi
+
+type SensorSize = {
+    w: float<mm>
+    h: float<mm>
+    label: string } with
+    override this.ToString () =
+        sprintf "%g mm × %g mm%s" this.w this.h (if this.label = "" then "" else " (" + this.label + ")")
+
+let defaultPredefinedPPI = [
+    { ppi = 230000<ppi>; label = "50×" }
+    { ppi = 460000<ppi>; label = "100×" } ]
+
+let defaultSensorSizes = [
+    { w = 3.2<mm>;  h = 2.4<mm>;  label = "1/4″" }
+    { w = 4.8<mm>;  h = 3.6<mm>;  label = "1/3″" }
+    { w = 5.76<mm>; h = 4.29<mm>; label = "1/2.5″" }
+    { w = 6.4<mm>;  h = 4.8<mm>;  label = "1/2″" }
+    { w = 7.18<mm>; h = 5.32<mm>; label = "1/1.8″" }
+    { w = 7.6<mm>;  h = 5.7<mm>;  label = "1/1.7″" }
+    { w = 8.8<mm>;  h = 6.6<mm>;  label = "2/3″" }
+    { w = 13.2<mm>; h = 8.8<mm>;  label = "1″" } ]
+
+
index 324e5f0..6579cd6 100644 (file)
@@ -1,5 +1,12 @@
 module ParasitemiaUI.Utils
 
 module ParasitemiaUI.Utils
 
+open System.IO
+
+open Newtonsoft.Json
+open Newtonsoft.Json.Converters
+
+open Types
+
 let listAsStr (s: 'a seq) =
     s |> Seq.fold (fun acc obj -> acc + (if acc = "" then "" else ", ") + obj.ToString()) ""
 
 let listAsStr (s: 'a seq) =
     s |> Seq.fold (fun acc obj -> acc + (if acc = "" then "" else ", ") + obj.ToString()) ""
 
@@ -9,4 +16,47 @@ let percentText (nbTotal: int, nb: int) : string =
         ""
     else
         let percent = 100. * (float nb) / (float nbTotal)
         ""
     else
         let percent = 100. * (float nb) / (float nbTotal)
-        sprintf "%.1f %% (%d / %d)" percent nb nbTotal
\ No newline at end of file
+        sprintf "%.1f %% (%d / %d)" percent nb nbTotal
+
+let roamingDir =
+    Path.Combine(System.Environment.GetFolderPath(System.Environment.SpecialFolder.ApplicationData), "Parasitemia")
+
+let predefinedPPIFilename = "predefined-ppi.json"
+let predefinedPPIFilepath = Path.Combine(roamingDir, predefinedPPIFilename)
+
+let sensorSizesFilename = "sensor-sizes.json"
+let sensorSizesFilepath = Path.Combine(roamingDir, sensorSizesFilename)
+
+let private savePredefinedPPIToFile (predefinedPPI: PredefinedPPI list) =
+    try
+        use file = new StreamWriter(predefinedPPIFilepath)
+        file.Write(JsonConvert.SerializeObject(predefinedPPI, JsonSerializerSettings(Formatting = Formatting.Indented)))
+    with
+        ex ->
+            Logger.Log.Error("Unable to save predefined PPI to file \"{0}\": {1}", predefinedPPIFilepath, ex)
+
+let private saveSensorSizesToFile (sensorSizes: SensorSize list) =
+    try
+        use file = new StreamWriter(sensorSizesFilepath)
+        file.Write(JsonConvert.SerializeObject(sensorSizes, JsonSerializerSettings(Formatting = Formatting.Indented)))
+    with
+        ex ->
+            Logger.Log.Error("Unable to save sensor sizes to file \"{0}\": {1}", sensorSizesFilepath, ex)
+
+let predefinedPPI : PredefinedPPI list =
+    try
+        use file = new StreamReader(predefinedPPIFilepath)
+        JsonConvert.DeserializeObject<PredefinedPPI list>(file.ReadToEnd())
+    with
+    | ex ->
+        savePredefinedPPIToFile defaultPredefinedPPI
+        defaultPredefinedPPI
+
+let sensorSizes : SensorSize list =
+    try
+        use file = new StreamReader(sensorSizesFilepath)
+        JsonConvert.DeserializeObject<SensorSize list>(file.ReadToEnd())
+    with
+    | ex ->
+        saveSensorSizesToFile defaultSensorSizes
+        defaultSensorSizes
index 83fa943..39b293d 100644 (file)
@@ -44,9 +44,9 @@
             <TextBox x:Name="txtResolution" Margin="3" Text="" Grid.Column="0" />
             <Button x:Name="butDefaultResolutions" Content="Predefined values" Grid.Column="1" Margin="3">
                <Button.ContextMenu>
             <TextBox x:Name="txtResolution" Margin="3" Text="" Grid.Column="0" />
             <Button x:Name="butDefaultResolutions" Content="Predefined values" Grid.Column="1" Margin="3">
                <Button.ContextMenu>
-                  <ContextMenu>
-                     <MenuItem x:Name="menuZoom50X" Header="50X: 230'000 PPI" />
-                     <MenuItem x:Name="menuZoom100X" Header="100X: 460'000 PPI" />
+                  <ContextMenu x:Name="predefinedValuesMenu">
+                     <!-- MenuItem x:Name="menuZoom50X" Header="50X: 230'000 PPI" />
+                     <MenuItem x:Name="menuZoom100X" Header="100X: 460'000 PPI" / -->
                   </ContextMenu>
                </Button.ContextMenu>
                <Button.Style>
                   </ContextMenu>
                </Button.ContextMenu>
                <Button.Style>
index a330fa3..e84bd54 100644 (file)
             <MenuItem x:Name="menuSave" Header="_Save" InputGestureText="Ctrl+S" />
             <MenuItem x:Name="menuSaveAs" Header="Save _As..." InputGestureText="Ctrl+Shift+S" />
             <Separator />
             <MenuItem x:Name="menuSave" Header="_Save" InputGestureText="Ctrl+S" />
             <MenuItem x:Name="menuSaveAs" Header="Save _As..." InputGestureText="Ctrl+Shift+S" />
             <Separator />
+            <MenuItem x:Name="menuAddSourceImage" Header="_Import Images..." InputGestureText="Ctrl+A" />
+            <Separator />
             <MenuItem x:Name="menuExportResults" Header="E_xport Results As Text..." InputGestureText="Ctrl+E" />
             <Separator />
             <MenuItem x:Name="menuExit" Header="_Exit" />
          </MenuItem>
             <MenuItem x:Name="menuExportResults" Header="E_xport Results As Text..." InputGestureText="Ctrl+E" />
             <Separator />
             <MenuItem x:Name="menuExit" Header="_Exit" />
          </MenuItem>
-         <MenuItem Header="_Images">
-            <MenuItem x:Name="menuAddSourceImage" Header="_Add a Source Image" />
-         </MenuItem>
          <MenuItem x:Name="menuAnalysis" Header="_Analysis">
             <MenuItem x:Name="menuStartAnalysis" Header="_Show Analysis Window" />
          </MenuItem>
          <MenuItem x:Name="menuAnalysis" Header="_Analysis">
             <MenuItem x:Name="menuStartAnalysis" Header="_Show Analysis Window" />
          </MenuItem>