From 9343c4deb0bf88c58d9c92d465d8e99f64656875 Mon Sep 17 00:00:00 2001 From: Greg Burri Date: Wed, 3 Feb 2016 14:18:27 +0100 Subject: [PATCH] Save predefined PPI and sensor sizes in JSON files. --- Parasitemia/Logger/Logger.fs | 2 +- Parasitemia/ParasitemiaCore/Analysis.fs | 10 ++-- Parasitemia/ParasitemiaCore/Classifier.fs | 2 + Parasitemia/ParasitemiaUI/Analysis.fs | 8 ++- Parasitemia/ParasitemiaUI/DPICalculator.fs | 23 ++------ Parasitemia/ParasitemiaUI/GUI.fs | 30 +++++++---- Parasitemia/ParasitemiaUI/Types.fs | 33 +++++++++++- Parasitemia/ParasitemiaUI/Utils.fs | 52 ++++++++++++++++++- .../XAML/ImageSourceSelection.xaml | 6 +-- .../ParasitemiaUI/XAML/MainWindow.xaml | 5 +- 10 files changed, 124 insertions(+), 47 deletions(-) diff --git a/Parasitemia/Logger/Logger.fs b/Parasitemia/Logger/Logger.fs index 1f029d5..cc02314 100644 --- a/Parasitemia/Logger/Logger.fs +++ b/Parasitemia/Logger/Logger.fs @@ -44,7 +44,7 @@ type Log () = 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 diff --git a/Parasitemia/ParasitemiaCore/Analysis.fs b/Parasitemia/ParasitemiaCore/Analysis.fs index f7e7b1c..2a7ddc8 100644 --- a/Parasitemia/ParasitemiaCore/Analysis.fs +++ b/Parasitemia/ParasitemiaCore/Analysis.fs @@ -138,10 +138,10 @@ let doAnalysis (img: Image) (name: string) (config: Config) (reportPr 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 - 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") @@ -149,9 +149,9 @@ let doAnalysis (img: Image) (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 (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 } diff --git a/Parasitemia/ParasitemiaCore/Classifier.fs b/Parasitemia/ParasitemiaCore/Classifier.fs index ea3f9dd..5a6dcbe 100644 --- a/Parasitemia/ParasitemiaCore/Classifier.fs +++ b/Parasitemia/ParasitemiaCore/Classifier.fs @@ -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) + + // 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. diff --git a/Parasitemia/ParasitemiaUI/Analysis.fs b/Parasitemia/ParasitemiaUI/Analysis.fs index 2ff0b1e..9c7ddf0 100644 --- a/Parasitemia/ParasitemiaUI/Analysis.fs +++ b/Parasitemia/ParasitemiaUI/Analysis.fs @@ -60,8 +60,12 @@ let showWindow (parent: Window) (state: State.State) : bool = imageSourceSelection.lblDateLastAnalysis.Content <- if srcImg.dateLastAnalysis.Ticks = 0L then "" 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 diff --git a/Parasitemia/ParasitemiaUI/DPICalculator.fs b/Parasitemia/ParasitemiaUI/DPICalculator.fs index 5e9c19b..c1187ec 100644 --- a/Parasitemia/ParasitemiaUI/DPICalculator.fs +++ b/Parasitemia/ParasitemiaUI/DPICalculator.fs @@ -8,15 +8,10 @@ open System.Windows.Shapes open System.Windows.Controls open System.Diagnostics -open ParasitemiaCore.UnitsOfMeasure open ParasitemiaCore.Types +open ParasitemiaCore.UnitsOfMeasure -type SensorSize = { - w: float - h: float - 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() @@ -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. - let sensorSizes = [ - { w = 5.76; h = 4.29; txt = "1/2.5″" } - { w = 7.6; h = 5.7; txt = "1/1.7″" } - { w = 8.6; h = 6.6; txt = "2/3″" } - { w = 13.2; h = 8.8; txt = "1″" } - { w = 17.3; h = 13.; txt = "" } - { w = 20.7; h = 13.8; txt = "" } - { w = 22.2; h = 14.8; txt = "" } - { w = 23.6; h = 15.7; txt = "" } - { w = 28.7; h = 19.; txt = "" } - { w = 28.7; h = 19.; txt = "" } ] - - for size in sensorSizes do + for size in Utils.sensorSizes do win.cmbSensorSize.Items.Add(size) |> ignore win.cmbSensorSize.SelectedIndex <- 0 diff --git a/Parasitemia/ParasitemiaUI/GUI.fs b/Parasitemia/ParasitemiaUI/GUI.fs index 0b0d6dd..77281d3 100644 --- a/Parasitemia/ParasitemiaUI/GUI.fs +++ b/Parasitemia/ParasitemiaUI/GUI.fs @@ -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 - 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 @@ -456,7 +447,18 @@ let run (defaultConfig: Config) (fileToOpen: string option) = 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) @@ -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 + // 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 ()) diff --git a/Parasitemia/ParasitemiaUI/Types.fs b/Parasitemia/ParasitemiaUI/Types.fs index e29465c..fa99d90 100644 --- a/Parasitemia/ParasitemiaUI/Types.fs +++ b/Parasitemia/ParasitemiaUI/Types.fs @@ -9,6 +9,8 @@ open Emgu.CV.Structure 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. @@ -45,4 +47,33 @@ type SourceImage = { 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 + label: string } with + override this.ToString() = + sprintf "%s: %d" this.label this.ppi + +type SensorSize = { + w: float + h: float + 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; label = "50×" } + { ppi = 460000; label = "100×" } ] + +let defaultSensorSizes = [ + { w = 3.2; h = 2.4; label = "1/4″" } + { w = 4.8; h = 3.6; label = "1/3″" } + { w = 5.76; h = 4.29; label = "1/2.5″" } + { w = 6.4; h = 4.8; label = "1/2″" } + { w = 7.18; h = 5.32; label = "1/1.8″" } + { w = 7.6; h = 5.7; label = "1/1.7″" } + { w = 8.8; h = 6.6; label = "2/3″" } + { w = 13.2; h = 8.8; label = "1″" } ] + + diff --git a/Parasitemia/ParasitemiaUI/Utils.fs b/Parasitemia/ParasitemiaUI/Utils.fs index 324e5f0..6579cd6 100644 --- a/Parasitemia/ParasitemiaUI/Utils.fs +++ b/Parasitemia/ParasitemiaUI/Utils.fs @@ -1,5 +1,12 @@ 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()) "" @@ -9,4 +16,47 @@ let percentText (nbTotal: int, nb: int) : string = "" 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(file.ReadToEnd()) + with + | ex -> + savePredefinedPPIToFile defaultPredefinedPPI + defaultPredefinedPPI + +let sensorSizes : SensorSize list = + try + use file = new StreamReader(sensorSizesFilepath) + JsonConvert.DeserializeObject(file.ReadToEnd()) + with + | ex -> + saveSensorSizesToFile defaultSensorSizes + defaultSensorSizes diff --git a/Parasitemia/ParasitemiaUI/XAML/ImageSourceSelection.xaml b/Parasitemia/ParasitemiaUI/XAML/ImageSourceSelection.xaml index 83fa943..39b293d 100644 --- a/Parasitemia/ParasitemiaUI/XAML/ImageSourceSelection.xaml +++ b/Parasitemia/ParasitemiaUI/XAML/ImageSourceSelection.xaml @@ -44,9 +44,9 @@