From 999a48b8eb25c92e4403c9280fd4fe68f9bc4b7b Mon Sep 17 00:00:00 2001 From: Greg Burri Date: Sun, 10 Jan 2016 17:29:19 +0100 Subject: [PATCH] GUI (work in progress..) --- Parasitemia/Parasitemia/Config.fs | 2 +- Parasitemia/Parasitemia/GUI/GUI.fs | 59 ++++++++++++------- .../Parasitemia/GUI/ImageSourcePreview.xaml | 10 ++-- .../Parasitemia/GUI/{Pia.fs => PiaZ.fs} | 15 +++-- Parasitemia/Parasitemia/GUI/State.fs | 26 +++++--- Parasitemia/Parasitemia/GUI/Types.fs | 5 +- Parasitemia/Parasitemia/MainAnalysis.fs | 12 ++++ Parasitemia/Parasitemia/Parasitemia.fsproj | 2 +- Parasitemia/Parasitemia/Program.fs | 13 +--- 9 files changed, 88 insertions(+), 56 deletions(-) rename Parasitemia/Parasitemia/GUI/{Pia.fs => PiaZ.fs} (89%) diff --git a/Parasitemia/Parasitemia/Config.fs b/Parasitemia/Parasitemia/Config.fs index 701a3fd..5bb9936 100644 --- a/Parasitemia/Parasitemia/Config.fs +++ b/Parasitemia/Parasitemia/Config.fs @@ -20,7 +20,7 @@ type Parameters = { factorNbPick: float // Parasites detection. - darkStainLevel: float + darkStainLevel: float // Lower -> more sensitive. Careful about illumination on the borders. maxDarkStainRatio: float stainArea: float32 // Factor of a RBC area. 0.5 means the half of RBC area. diff --git a/Parasitemia/Parasitemia/GUI/GUI.fs b/Parasitemia/Parasitemia/GUI/GUI.fs index c6e6390..2aadefe 100644 --- a/Parasitemia/Parasitemia/GUI/GUI.fs +++ b/Parasitemia/Parasitemia/GUI/GUI.fs @@ -10,8 +10,6 @@ open System.Windows.Controls open System.Diagnostics open Microsoft.Win32 // For the common dialogs. -//open Emgu.CV -//open Emgu.CV.Structure open Emgu.CV.WPF open Config @@ -25,8 +23,8 @@ let run (defaultConfig: Config) = // Utils.log <- (fun m -> log mainWindow m) - let colorRBCHealthy = Brushes.Green - let colorRBCInfected = Brushes.Yellow + let colorRBCHealthy = Brushes.YellowGreen + let colorRBCInfected = Brushes.Red let state = State.State() @@ -51,22 +49,44 @@ let run (defaultConfig: Config) = state.PatientID <- txtPatient.Text let setCurrentImage (srcImg: SourceImage) = + state.CurrentImage <- Some srcImg + + // Highlight the preview. + stackPreviews.Children + |> Seq.cast + |> Seq.iter (fun preview -> preview.border.BorderThickness <- Thickness(if preview.lblImageNumber.Content = box srcImg.num then 3. else 0.)) + canvasCurrentImage.Height <- float srcImg.img.Height canvasCurrentImage.Width <- float srcImg.img.Width canvasCurrentImage.Background <- ImageBrush(BitmapSourceConvert.ToBitmapSource(srcImg.img)) // Remove all image canvas children and add the RBC. canvasCurrentImage.Children.Clear() - for rbc in (*srcImg.rbcs*) [{ num = 1; infected = true; addedManually = false; removed = false; center = Point(100., 100.); size = Size(40., 40.); stainArea = 10 }] do + for rbc in srcImg.rbcs do let rectangle = Rectangle( Height = rbc.size.Height, Width = rbc.size.Width, Stroke = (if rbc.infected then colorRBCInfected else colorRBCHealthy), - StrokeThickness = 1.) - Canvas.SetLeft(rectangle, rbc.center.X + rbc.size.Width / 2.) - Canvas.SetTop(rectangle, rbc.center.Y + rbc.size.Height / 2.) + StrokeThickness = 1., + Fill = SolidColorBrush(Color.FromArgb(0uy, 0uy, 0uy, 0uy)), + Tag = rbc, + Opacity = if rbc.infected then 1. else 0.) + Canvas.SetLeft(rectangle, rbc.center.X - rbc.size.Width / 2.) + Canvas.SetTop(rectangle, rbc.center.Y - rbc.size.Height / 2.) canvasCurrentImage.Children.Add(rectangle) |> ignore + rectangle.MouseEnter.AddHandler( + fun obj args -> match obj with + | :? Rectangle as r -> + r.StrokeThickness <- 3. + if not (r.Tag :?> RBC).infected then r.Opacity <- 1. + | _ -> ()) + rectangle.MouseLeave.AddHandler( + fun obj args -> match obj with + | :? Rectangle as r -> + r.StrokeThickness <- 1. + if not (r.Tag :?> RBC).infected then r.Opacity <- 0. + | _ -> ()) let addPreview (srcImg: SourceImage) = let imgCtrl = Views.ImageSourcePreview(Margin = Thickness(3.)) @@ -75,6 +95,7 @@ let run (defaultConfig: Config) = let height = srcImg.img.Height * width / srcImg.img.Width imgCtrl.imagePreview.Source <- BitmapSourceConvert.ToBitmapSource(srcImg.img.Resize(width, height, Emgu.CV.CvEnum.Inter.Cubic)) stackPreviews.Children.Add(imgCtrl) |> ignore + imgCtrl.MouseLeftButtonUp.AddHandler(fun obj args -> setCurrentImage (state.SourceImages |> Seq.find (fun i -> box i.num = (obj :?> Views.ImageSourcePreview).lblImageNumber.Content))) let updatePreviews () = stackPreviews.Children.Clear () @@ -93,7 +114,7 @@ let run (defaultConfig: Config) = synchronizeState () if state.FilePath = "" then - let dialog = SaveFileDialog(AddExtension = true, DefaultExt = Pia.extension, Filter = Pia.filter); + let dialog = SaveFileDialog(AddExtension = true, DefaultExt = PiaZ.extension, Filter = PiaZ.filter); let res = dialog.ShowDialog() if res.HasValue && res.Value then @@ -104,7 +125,7 @@ let run (defaultConfig: Config) = loadFile.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 dialog = OpenFileDialog(Filter = PiaZ.filter) let res = dialog.ShowDialog() if res.HasValue && res.Value then @@ -128,7 +149,11 @@ let run (defaultConfig: Config) = then setCurrentImage srcImg) - butStartAnalysis.Click.AddHandler(fun obj args -> ()) + butStartAnalysis.Click.AddHandler(fun obj args -> + let results = ImageAnalysis.doMultipleAnalysis (state.SourceImages |> Seq.map (fun srcImg -> string srcImg.num, srcImg.img) |> Seq.toList) defaultConfig + for id, cells in results do + state.SetResult (int id) cells + ) // Zoom on the current image. let adjustCurrentImageBorders (deltaX: float) (deltaY: float) = @@ -158,8 +183,8 @@ let run (defaultConfig: Config) = scrollViewCurrentImage.ScrollToVerticalOffset(scrollViewCurrentImage.VerticalOffset + deltaY / 8.)) let mutable currentScale = 1. - let mutable maxScale = 5. - let mutable minScale = 0.1 + let mutable maxScale = 4. + let mutable minScale = 0.25 let currentImageScaleTransform = ScaleTransform() canvasCurrentImage.LayoutTransform <- currentImageScaleTransform borderCurrentImage.PreviewMouseWheel.AddHandler(fun obj args -> @@ -212,14 +237,6 @@ let run (defaultConfig: Config) = borderCurrentImage.ReleaseMouseCapture() args.Handled <- true) - (* 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/ImageSourcePreview.xaml b/Parasitemia/Parasitemia/GUI/ImageSourcePreview.xaml index 3593e0e..6fd7ba2 100644 --- a/Parasitemia/Parasitemia/GUI/ImageSourcePreview.xaml +++ b/Parasitemia/Parasitemia/GUI/ImageSourcePreview.xaml @@ -6,8 +6,10 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" d:DesignWidth="119.223" d:DesignHeight="84.911" > - - - + + + + + \ No newline at end of file diff --git a/Parasitemia/Parasitemia/GUI/Pia.fs b/Parasitemia/Parasitemia/GUI/PiaZ.fs similarity index 89% rename from Parasitemia/Parasitemia/GUI/Pia.fs rename to Parasitemia/Parasitemia/GUI/PiaZ.fs index 1821f26..900ec65 100644 --- a/Parasitemia/Parasitemia/GUI/Pia.fs +++ b/Parasitemia/Parasitemia/GUI/PiaZ.fs @@ -1,5 +1,5 @@ -// ParasitemIA file format. -module Parasitemia.GUI.Pia +// ParasitemIA Zipped file format. +module Parasitemia.GUI.PiaZ open System.Windows open System.IO @@ -12,8 +12,8 @@ open Emgu.CV.Structure open Types -let extension = ".pia" -let filter = "PIA|*.pia" +let extension = ".piaz" +let filter = "PIA|*.piaz" type FileData = { sources: SourceImage list @@ -26,8 +26,7 @@ type JSONSourceImage = JsonProvider<""" { "num": 1, "infected": true, - "addedManually": false, - "removed": false, + "setManually": false, "posX" : 42.5, "posY" : 42.5, "width" : 10.5, @@ -70,7 +69,7 @@ let save (filePath: string) (data: FileData) = JSONSourceImage.Root([| for rbc in imgSrc.rbcs -> JSONSourceImage.Rbc( rbc.num, - rbc.infected, rbc.addedManually, rbc.removed, + rbc.infected, rbc.setManually, decimal rbc.center.X, decimal rbc.center.Y, decimal rbc.size.Width, decimal rbc.size.Height, rbc.stainArea) |]) @@ -97,7 +96,7 @@ let load (filePath: string) : FileData = let imgJSON = JSONSourceImage.Load(imgJSONEntry.Open()) yield { num = imgNum; img = img; rbcs = [ for rbc in imgJSON.Rbcs -> { num = rbc.Num; - infected = rbc.Infected; addedManually = rbc.AddedManually; removed = rbc.Removed; + infected = rbc.Infected; setManually = rbc.SetManually; center = Point(float rbc.PosX, float rbc.PosY); size = Size(float rbc.Width, float rbc.Height); stainArea = rbc.StainArea } ] } ] { sources = sources; patientID = mainJSON.PatientId } \ No newline at end of file diff --git a/Parasitemia/Parasitemia/GUI/State.fs b/Parasitemia/Parasitemia/GUI/State.fs index 4502536..a1ae052 100644 --- a/Parasitemia/Parasitemia/GUI/State.fs +++ b/Parasitemia/Parasitemia/GUI/State.fs @@ -1,6 +1,7 @@ module Parasitemia.GUI.State open System.Collections.Generic +open System.Windows open Emgu.CV open Emgu.CV.Structure @@ -9,17 +10,17 @@ open Types type State () = let sourceImages = List() - let mutable currentImage = -1 + member val CurrentImage: SourceImage option = None with get, set member val FilePath: string = "" with get, set member val PatientID: string = "" with get, set member this.Save () = - let data = { Pia.sources = List.ofSeq sourceImages; Pia.patientID = this.PatientID } - Pia.save this.FilePath data + let data = { PiaZ.sources = List.ofSeq sourceImages; PiaZ.patientID = this.PatientID } + PiaZ.save this.FilePath data member this.Load () = - let data = Pia.load this.FilePath + let data = PiaZ.load this.FilePath this.PatientID <- data.patientID sourceImages.Clear() sourceImages.InsertRange(0, data.sources) @@ -27,12 +28,21 @@ type State () = member this.AddSourceImage (filePath: string) : SourceImage = let srcImg = { num = sourceImages.Count + 1; rbcs = []; img = new Image(filePath) } sourceImages.Add(srcImg) - if sourceImages.Count = 1 - then - currentImage <- 1 srcImg - member x.SourceImages : SourceImage seq = + member this.SetResult (num: int) (cells: Cell list) = + let sourceImage = sourceImages.Find(fun srcImg -> srcImg.num = num) + sourceImage.rbcs <- cells + |> List.filter (fun cell -> match cell.cellClass with HealthyRBC | InfectedRBC -> true | _ -> false ) + |> List.mapi (fun i cell -> + { num = i + infected = cell.cellClass = InfectedRBC + setManually = false + center = Point(float cell.center.X, float cell.center.Y) + size = Size(float cell.elements.Width, float cell.elements.Height) + stainArea = cell.stainArea }) + + member this.SourceImages : SourceImage seq = sourceImages :> SourceImage seq member this.Reset () = diff --git a/Parasitemia/Parasitemia/GUI/Types.fs b/Parasitemia/Parasitemia/GUI/Types.fs index d6e9cf9..e912ad3 100644 --- a/Parasitemia/Parasitemia/GUI/Types.fs +++ b/Parasitemia/Parasitemia/GUI/Types.fs @@ -9,8 +9,7 @@ type RBC = { num: int infected: bool - addedManually: bool - removed: bool + setManually: bool center: Point size: Size @@ -19,4 +18,4 @@ type RBC = { type SourceImage = { num: int img: Image - rbcs: RBC list } \ No newline at end of file + mutable rbcs: RBC list } \ No newline at end of file diff --git a/Parasitemia/Parasitemia/MainAnalysis.fs b/Parasitemia/Parasitemia/MainAnalysis.fs index 043eb73..5ee6886 100644 --- a/Parasitemia/Parasitemia/MainAnalysis.fs +++ b/Parasitemia/Parasitemia/MainAnalysis.fs @@ -3,6 +3,8 @@ open System open System.Drawing +open FSharp.Collections.ParallelSeq + open Emgu.CV open Emgu.CV.Structure @@ -91,3 +93,13 @@ let doAnalysis (img: Image) (name: string) (config: Config) : Cell li | _ -> () cells + + +let doMultipleAnalysis (imgs: (string * Image) list) (config : Config) : (string * Cell list) list = + let nbConcurrentTaskLimit = 4 + let n = Environment.ProcessorCount + + imgs + |> PSeq.map (fun (id, img) -> id, doAnalysis img id (config.Copy())) + |> PSeq.withDegreeOfParallelism (if n > nbConcurrentTaskLimit then nbConcurrentTaskLimit else n) + |> PSeq.toList \ No newline at end of file diff --git a/Parasitemia/Parasitemia/Parasitemia.fsproj b/Parasitemia/Parasitemia/Parasitemia.fsproj index 223be2c..d3289c6 100644 --- a/Parasitemia/Parasitemia/Parasitemia.fsproj +++ b/Parasitemia/Parasitemia/Parasitemia.fsproj @@ -97,7 +97,7 @@ - + diff --git a/Parasitemia/Parasitemia/Program.fs b/Parasitemia/Parasitemia/Program.fs index bb61f4f..711ec6f 100644 --- a/Parasitemia/Parasitemia/Program.fs +++ b/Parasitemia/Parasitemia/Program.fs @@ -4,8 +4,6 @@ open System open System.IO open System.Threading -open FSharp.Collections.ParallelSeq - open Emgu.CV open Emgu.CV.Structure @@ -56,7 +54,7 @@ let main args = factorNbPick = 1.0 - darkStainLevel = 0.25 // Lower -> more sensitive. 0.3. Careful about illumination on the borders. + darkStainLevel = 0.25 // 0.3 maxDarkStainRatio = 0.1 // 10 % infectionArea = 0.012f // 1.2 % @@ -88,16 +86,11 @@ let main args = use resultFile = new StreamWriter(new FileStream(Path.Combine(output, "results.txt"), FileMode.Append, FileAccess.Write)) //try - let images = seq { for file in files -> Path.GetFileNameWithoutExtension(FileInfo(file).Name), new Image(file) } + let images = [ for file in files -> Path.GetFileNameWithoutExtension(FileInfo(file).Name), new Image(file) ] - let nbConcurrentTaskLimit = 4 - let n = Environment.ProcessorCount Utils.logTime "Whole analyze" (fun () -> - let results = - images - |> PSeq.map (fun (id, img) -> id, ImageAnalysis.doAnalysis img id (config.Copy())) - |> PSeq.withDegreeOfParallelism (if n > nbConcurrentTaskLimit then nbConcurrentTaskLimit else n) + let results = ImageAnalysis.doMultipleAnalysis images config for id, cells in results do let total, infected = Utils.countCells cells -- 2.43.0