From: Greg Burri Date: Sun, 10 Jan 2016 16:29:19 +0000 (+0100) Subject: GUI (work in progress..) X-Git-Tag: 1.0.11~58 X-Git-Url: http://git.euphorik.ch/?p=master-thesis.git;a=commitdiff_plain;h=999a48b8eb25c92e4403c9280fd4fe68f9bc4b7b GUI (work in progress..) --- 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/Pia.fs deleted file mode 100644 index 1821f26..0000000 --- a/Parasitemia/Parasitemia/GUI/Pia.fs +++ /dev/null @@ -1,103 +0,0 @@ -// ParasitemIA file format. -module Parasitemia.GUI.Pia - -open System.Windows -open System.IO -open System.IO.Compression - -open FSharp.Data - -open Emgu.CV -open Emgu.CV.Structure - -open Types - -let extension = ".pia" -let filter = "PIA|*.pia" - -type FileData = { - sources: SourceImage list - patientID: string } - -// The json type associated to a source image. -type JSONSourceImage = JsonProvider<""" - { - "rbcs": [ - { - "num": 1, - "infected": true, - "addedManually": false, - "removed": false, - "posX" : 42.5, - "posY" : 42.5, - "width" : 10.5, - "height" : 10.5, - "stainArea" : 10 - } - ] - } -"""> - -// The json type associated to a file. -type JSONMainInformation = JsonProvider<""" - { - "patientID": "1234abcd" - } -"""> - -let mainFilename = "info.json" -let imageExtension = ".tiff" - -let save (filePath: string) (data: FileData) = - use file = ZipFile.Open(filePath, ZipArchiveMode.Update) - - for e in List.ofSeq file.Entries do // 'ofSeq' to not iterate a collection currently modified. - e.Delete() - - // Main JSON file. - let mainJSON = JSONMainInformation.Root(data.patientID) - let mainFile = file.CreateEntry(mainFilename, CompressionLevel.Fastest) - use mainFileWriter = new StreamWriter(mainFile.Open()) - mainJSON.JsonValue.WriteTo(mainFileWriter, JsonSaveOptions.None) - - // Write each images and the associated information. - for imgSrc in data.sources do - let imgFilename = (string imgSrc.num) + imageExtension - let imgEntry = file.CreateEntry(imgFilename, CompressionLevel.NoCompression) // FIXME: It seems a compression is applied to this file despite of the 'NoCompression' flag. - imgSrc.img.ToBitmap().Save(imgEntry.Open(), System.Drawing.Imaging.ImageFormat.Tiff) - - let imgJSON = - JSONSourceImage.Root([| for rbc in imgSrc.rbcs -> - JSONSourceImage.Rbc( - rbc.num, - rbc.infected, rbc.addedManually, rbc.removed, - decimal rbc.center.X, decimal rbc.center.Y, decimal rbc.size.Width, decimal rbc.size.Height, - rbc.stainArea) |]) - - let imgJSONEntry = file.CreateEntry(imgFilename + ".json", CompressionLevel.Fastest) - use imgJSONFileWriter = new StreamWriter(imgJSONEntry.Open()) - imgJSON.JsonValue.WriteTo(imgJSONFileWriter, 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()) - - let sources = [ - let mutable imgNum = 0 - for imgEntry in file.Entries do - let filename = imgEntry.Name - if filename.EndsWith(imageExtension) - then - let img = new Image(new System.Drawing.Bitmap(imgEntry.Open(), false)) // FIXME: Should we dispose the bitmap? - imgNum <- imgNum + 1 - let imgJSONEntry = file.GetEntry(filename + ".json") - 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; - 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/PiaZ.fs b/Parasitemia/Parasitemia/GUI/PiaZ.fs new file mode 100644 index 0000000..900ec65 --- /dev/null +++ b/Parasitemia/Parasitemia/GUI/PiaZ.fs @@ -0,0 +1,102 @@ +// ParasitemIA Zipped file format. +module Parasitemia.GUI.PiaZ + +open System.Windows +open System.IO +open System.IO.Compression + +open FSharp.Data + +open Emgu.CV +open Emgu.CV.Structure + +open Types + +let extension = ".piaz" +let filter = "PIA|*.piaz" + +type FileData = { + sources: SourceImage list + patientID: string } + +// The json type associated to a source image. +type JSONSourceImage = JsonProvider<""" + { + "rbcs": [ + { + "num": 1, + "infected": true, + "setManually": false, + "posX" : 42.5, + "posY" : 42.5, + "width" : 10.5, + "height" : 10.5, + "stainArea" : 10 + } + ] + } +"""> + +// The json type associated to a file. +type JSONMainInformation = JsonProvider<""" + { + "patientID": "1234abcd" + } +"""> + +let mainFilename = "info.json" +let imageExtension = ".tiff" + +let save (filePath: string) (data: FileData) = + use file = ZipFile.Open(filePath, ZipArchiveMode.Update) + + for e in List.ofSeq file.Entries do // 'ofSeq' to not iterate a collection currently modified. + e.Delete() + + // Main JSON file. + let mainJSON = JSONMainInformation.Root(data.patientID) + let mainFile = file.CreateEntry(mainFilename, CompressionLevel.Fastest) + use mainFileWriter = new StreamWriter(mainFile.Open()) + mainJSON.JsonValue.WriteTo(mainFileWriter, JsonSaveOptions.None) + + // Write each images and the associated information. + for imgSrc in data.sources do + let imgFilename = (string imgSrc.num) + imageExtension + let imgEntry = file.CreateEntry(imgFilename, CompressionLevel.NoCompression) // FIXME: It seems a compression is applied to this file despite of the 'NoCompression' flag. + imgSrc.img.ToBitmap().Save(imgEntry.Open(), System.Drawing.Imaging.ImageFormat.Tiff) + + let imgJSON = + JSONSourceImage.Root([| for rbc in imgSrc.rbcs -> + JSONSourceImage.Rbc( + rbc.num, + rbc.infected, rbc.setManually, + decimal rbc.center.X, decimal rbc.center.Y, decimal rbc.size.Width, decimal rbc.size.Height, + rbc.stainArea) |]) + + let imgJSONEntry = file.CreateEntry(imgFilename + ".json", CompressionLevel.Fastest) + use imgJSONFileWriter = new StreamWriter(imgJSONEntry.Open()) + imgJSON.JsonValue.WriteTo(imgJSONFileWriter, 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()) + + let sources = [ + let mutable imgNum = 0 + for imgEntry in file.Entries do + let filename = imgEntry.Name + if filename.EndsWith(imageExtension) + then + let img = new Image(new System.Drawing.Bitmap(imgEntry.Open(), false)) // FIXME: Should we dispose the bitmap? + imgNum <- imgNum + 1 + let imgJSONEntry = file.GetEntry(filename + ".json") + let imgJSON = JSONSourceImage.Load(imgJSONEntry.Open()) + yield { num = imgNum; img = img; rbcs = [ for rbc in imgJSON.Rbcs -> + { num = rbc.Num; + 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