Add total analysis time #319
[master-thesis.git] / Parasitemia / ParasitemiaUI / Analysis.fs
index 2506015..1747fc2 100644 (file)
 module ParasitemiaUI.Analysis
 
 open System
-open System.IO
 open System.Linq
 open System.Windows
-open System.Windows.Media
-open System.Windows.Markup
-open System.Windows.Shapes
 open System.Windows.Controls
-open System.Diagnostics
-open Microsoft.Win32 // For the common dialogs.
-
-open Emgu.CV.WPF
 
+open ParasitemiaUIControls
 open ParasitemiaCore.UnitsOfMeasure
 open ParasitemiaCore.Config
 
 open Types
 
-let showWindow (parent: Window) (state: State.State) : bool =
-    let win = Views.AnalysisWindow()
-    win.Root.Owner <- parent
-    win.Root.Left <- (if parent.WindowState = WindowState.Maximized then 0. else parent.Left) + parent.ActualWidth / 2. - win.Root.Width / 2.
-    win.Root.Top <- (if parent.WindowState = WindowState.Maximized then 0. else parent.Top) + parent.ActualHeight / 2. - win.Root.Height / 2.
+let showWindow (parent : Window) (state : State.State) : bool =
+    let win = AnalysisWindow ()
+    win.Owner <- parent
+    win.Left <- (if parent.WindowState = WindowState.Maximized then 0. else parent.Left) + parent.ActualWidth / 2. - win.Width / 2.
+    win.Top <- (if parent.WindowState = WindowState.Maximized then 0. else parent.Top) + parent.ActualHeight / 2. - win.Height / 2.
 
     let logListener =
-        { new Logger.IListener with
-            member this.NewEntry severity mess =
-                win.Root.Dispatcher.Invoke(fun () ->
-                    win.textLog.Inlines.Add(Documents.Run(mess))
-                    win.textLog.Inlines.Add(Documents.LineBreak())
-                    win.scrollLog.ScrollToBottom()) }
-
-    Logger.Log.AddListener(logListener)
+        {
+            new Logger.IListener with
+                member this.NewEntry severity header mess =
+                    win.Dispatcher.Invoke (
+                        fun () ->
+                            win.textLog.Inlines.Add (Documents.Run mess)
+                            win.textLog.Inlines.Add (Documents.LineBreak ())
+                            win.scrollLog.ScrollToBottom ()
+                    )
+        }
+
+    Logger.Log.AddListener (logListener)
 
     let minPPI = 1.
     let maxPPI = 10e6
-    let parseAndValidatePPI (input: string) : float option =
-        match Double.TryParse(input) with
+    let parseAndValidatePPI (input : string) : float option =
+        match Double.TryParse input with
         | true, value when value >= minPPI && value <= maxPPI -> Some value
         | _ -> None
 
-    let monitor = Object()
+    let monitor = Object ()
     let mutable atLeastOneAnalysisPerformed = false
     let mutable analysisPerformed = false
     let mutable analysisCancelled = false
 
     let updateSourceImages () =
-        win.stackSourceImagesSelection.Children.Clear()
+        win.stackSourceImagesSelection.Children.Clear ()
         let width = int win.stackSourceImagesSelection.ActualWidth
         for srcImg in state.SourceImages do
-            let imageSourceSelection = Views.ImageSourceSelection(Tag = srcImg, Margin = Thickness(3.))
+            let imageSourceSelection = ImageSourceSelection (Tag = srcImg, Margin = Thickness 3.)
             imageSourceSelection.Tag <- srcImg
 
-            imageSourceSelection.txtImageNumber.Text <- srcImg.num.ToString()
-            let height = srcImg.img.Height * width / srcImg.img.Width
-            imageSourceSelection.imagePreview.Source <- BitmapSourceConvert.ToBitmapSource(srcImg.img.Resize(width, height, Emgu.CV.CvEnum.Inter.Cubic))
-            imageSourceSelection.chkSelection.IsChecked <- Nullable<bool>(srcImg.dateLastAnalysis.Ticks = 0L)
-            imageSourceSelection.lblDateLastAnalysis.Content <- if srcImg.dateLastAnalysis.Ticks = 0L then "<Never>" else srcImg.dateLastAnalysis.ToString()
+            imageSourceSelection.txtImageNumber.Text <- string srcImg.RomanNum
+            let height = srcImg.Img.Height * width / srcImg.Img.Width
+            imageSourceSelection.imagePreview.Source <- BitmapSourceConvert.ToBitmapSource (srcImg.Img.Resize (width, height, Emgu.CV.CvEnum.Inter.Cubic))
+            imageSourceSelection.chkSelection.IsChecked <- Nullable<bool> (srcImg.DateLastAnalysis.Ticks = 0L)
+            imageSourceSelection.lblDateLastAnalysis.Content <- if srcImg.DateLastAnalysis.Ticks = 0L then "<Never>" else string srcImg.DateLastAnalysis
 
-            imageSourceSelection.txtResolution.Text <- if srcImg.dateLastAnalysis.Ticks = 0L then "" else srcImg.config.Parameters.resolution.ToString()
+            imageSourceSelection.txtResolution.Text <- if srcImg.DateLastAnalysis.Ticks = 0L then "" else string srcImg.Config.Parameters.resolution
 
             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
-                | Some resolution -> imageSourceSelection.txtResolution.Text <- resolution.ToString()
-                | None -> ())
-
-            imageSourceSelection.txtResolution.PreviewTextInput.AddHandler(fun obj args ->
-                let text = imageSourceSelection.txtResolution.Text + args.Text
-                args.Handled <- match parseAndValidatePPI text with Some _ -> false | None -> true)
-
-            imageSourceSelection.imagePreview.MouseLeftButtonDown.AddHandler(fun obj args ->
-                let checkbox = imageSourceSelection.chkSelection
-                checkbox.IsChecked <- Nullable<bool>(not (checkbox.IsChecked.HasValue && checkbox.IsChecked.Value)))
-
-            win.stackSourceImagesSelection.Children.Add(imageSourceSelection) |> ignore
+                let menu = MenuItem ()
+                menu.Header <- string ppi
+                menu.Click.AddHandler (fun obj args -> imageSourceSelection.txtResolution.Text <- string ppi.ppi)
+                imageSourceSelection.predefinedValuesMenu.Items.Add menu |> ignore
+
+            imageSourceSelection.butPPICalculator.Click.AddHandler (
+                fun obj args ->
+                    match PPICalculator.showWindow win with
+                    | Some resolution -> imageSourceSelection.txtResolution.Text <- string resolution
+                    | None -> ()
+            )
+
+            imageSourceSelection.txtResolution.PreviewTextInput.AddHandler (
+                fun obj args ->
+                    let text = imageSourceSelection.txtResolution.Text + args.Text
+                    args.Handled <- match parseAndValidatePPI text with Some _ -> false | None -> true
+            )
+
+            imageSourceSelection.imagePreview.MouseLeftButtonDown.AddHandler (
+                fun obj args ->
+                    let checkbox = imageSourceSelection.chkSelection
+                    checkbox.IsChecked <- Nullable<bool> (not (checkbox.IsChecked.HasValue && checkbox.IsChecked.Value))
+            )
+
+            win.stackSourceImagesSelection.Children.Add (imageSourceSelection) |> ignore
 
     // Get the new parameters for each image. If an error occurs then 'None' is returned and a message box is displayed.
     // The boolean is 'true' if the image is selected (checked).
     let getInputImagesParameters () : (SourceImage * bool * Parameters) list option =
-        let sourceImagesControls = win.stackSourceImagesSelection.Children |> Seq.cast<Views.ImageSourceSelection>
-        let parameters = seq {
-            for srcImgCtrl in sourceImagesControls do
-                let srcImg = srcImgCtrl.Tag :?> SourceImage
-                let isChecked = srcImgCtrl.chkSelection.IsChecked
-                match parseAndValidatePPI srcImgCtrl.txtResolution.Text with
-                    | Some resolution ->
-                        yield Some (srcImg, isChecked.HasValue && isChecked.Value, { srcImg.config.Parameters with resolution = resolution * 1.<ppi> })
-                    | None ->
-                        MessageBox.Show(sprintf "No resolution defined for the image number %d" srcImg.num, "No resolution defined", MessageBoxButton.OK, MessageBoxImage.Information) |> ignore
-                        yield None } |> Seq.takeWhile (fun e -> e.IsSome) |> Seq.map (fun e -> e.Value) |> List.ofSeq
-        if parameters.Count() <> sourceImagesControls.Count()
-        then None
-        else Some parameters
-
-    win.butClose.Click.AddHandler(fun obj args -> win.Root.Close())
-
-    win.butStart.Click.AddHandler(fun obj args ->
-        match getInputImagesParameters () with
-        | Some imagesParameters ->
-            let imagesToProcess = [
-                for srcImg, selected, parameters in imagesParameters do
-                    srcImg.config.Parameters <- parameters // Save parameters.
-                    if selected
-                    then yield srcImg.num.ToString(), srcImg.config, srcImg.img ]
-
-            if imagesToProcess.IsEmpty
-            then
-                MessageBox.Show("No image selected", "Cannot start analysis", MessageBoxButton.OK, MessageBoxImage.Information) |> ignore
-            else
-                win.stackSourceImagesSelection.IsEnabled <- false
-                analysisPerformed <- false
-                win.butStart.IsEnabled <- false
-                win.butClose.Content <- "Abort"
-
-                async {
-                    let maybeResults =
-                        ParasitemiaCore.Analysis.doMultipleAnalysis
-                            imagesToProcess
-                            (Some (fun progress -> win.Root.Dispatcher.Invoke(fun () -> win.progress.Value <- float progress); not analysisCancelled))
-
-                    lock monitor (
-                        fun() ->
-                            match maybeResults with
-                            | Some results ->
-                                for id, cells in results do
-                                    state.SetResult (int id) cells
-                                Logger.Log.User("All analyses terminated successfully")
-                                atLeastOneAnalysisPerformed <- true
-                                analysisPerformed <- true
-                            | None ->
-                                Logger.Log.User("Analysis aborted")
-
-                            win.Root.Dispatcher.Invoke(fun () ->
-                                win.progress.Value <- if maybeResults.IsSome then 100. else 0.
-                                win.stackSourceImagesSelection.IsEnabled <- true
-                                win.butStart.IsEnabled <- true
-                                win.butClose.Content <- "Close"
-                                updateSourceImages ()))
-                } |> Async.Start
-        | _ -> ())
-
-    win.Root.Loaded.AddHandler(fun obj args -> updateSourceImages ())
-
-    win.Root.ShowDialog() |> ignore
-
-    Logger.Log.RmListener(logListener)
-
-    lock monitor (fun () ->
-        if not analysisPerformed
-            then
+        let sourceImagesControls = win.stackSourceImagesSelection.Children |> Seq.cast<ImageSourceSelection>
+        let parameters =
+            seq {
+                for srcImgCtrl in sourceImagesControls do
+                    let srcImg = srcImgCtrl.Tag :?> SourceImage
+                    let isChecked = srcImgCtrl.chkSelection.IsChecked
+                    match parseAndValidatePPI srcImgCtrl.txtResolution.Text with
+                        | Some resolution ->
+                            Some (srcImg, isChecked.HasValue && isChecked.Value, { srcImg.Config.Parameters with resolution = resolution * 1.<ppi> })
+                        | None ->
+                            MessageBox.Show (sprintf "No resolution defined for the image number %d" srcImg.Num, "No resolution defined", MessageBoxButton.OK, MessageBoxImage.Information) |> ignore
+                            None
+            } |> Seq.takeWhile (fun e -> e.IsSome) |> Seq.map (fun e -> e.Value) |> List.ofSeq
+
+        if parameters.Count () <> sourceImagesControls.Count () then
+            None
+        else
+            Some parameters
+
+    win.butSelectAll.Click.AddHandler (
+        fun obj args ->
+            for srcImgCtrl in win.stackSourceImagesSelection.Children |> Seq.cast<ImageSourceSelection> do
+                srcImgCtrl.chkSelection.IsChecked <- true
+    )
+
+    win.butUnselectAll.Click.AddHandler (
+        fun obj args ->
+            for srcImgCtrl in win.stackSourceImagesSelection.Children |> Seq.cast<ImageSourceSelection> do
+                srcImgCtrl.chkSelection.IsChecked <- false
+    )
+
+    win.butClose.Click.AddHandler (fun obj args -> win.Close ())
+
+    win.butStart.Click.AddHandler (
+        fun obj args ->
+            match getInputImagesParameters () with
+            | Some imagesParameters ->
+                let imagesToProcess =
+                    [
+                        for srcImg, selected, parameters in imagesParameters do
+                            srcImg.Config.Parameters <- parameters // Save parameters.
+                            if selected then
+                                string srcImg.RomanNum, srcImg.Config, srcImg.Img
+                    ]
+
+                if imagesToProcess.IsEmpty then
+                    MessageBox.Show ("No image selected", "Cannot start analysis", MessageBoxButton.OK, MessageBoxImage.Information) |> ignore
+                else
+                    win.stackSourceImagesSelection.IsEnabled <- false
+                    win.butSelectAll.IsEnabled <- false
+                    win.butUnselectAll.IsEnabled <- false
+                    analysisPerformed <- false
+                    win.butStart.IsEnabled <- false
+                    win.textLog.Text <- ""
+                    win.butClose.Content <- "Abort"
+
+                    let sw = System.Diagnostics.Stopwatch ()
+                    sw.Start ()
+
+                    async {
+                        let maybeResults =
+                            ParasitemiaCore.Analysis.doMultipleAnalysis
+                                imagesToProcess
+                                (Some (fun progress -> win.Dispatcher.Invoke (fun () -> win.progress.Value <- float progress); not analysisCancelled))
+
+                        lock monitor (
+                            fun () ->
+                                match maybeResults with
+                                | Some results ->
+                                    for id, cells in results do
+                                        state.SetResult id cells
+                                    Logger.Log.Info $"All analyses terminated successfully (total time: %d{sw.ElapsedMilliseconds} ms)"
+                                    atLeastOneAnalysisPerformed <- true
+                                    analysisPerformed <- true
+                                | None ->
+                                    Logger.Log.Info "Analysis aborted"
+
+                                win.Dispatcher.Invoke (
+                                    fun () ->
+                                        win.progress.Value <- if maybeResults.IsSome then 100. else 0.
+                                        win.stackSourceImagesSelection.IsEnabled <- true
+                                        win.butSelectAll.IsEnabled <- true
+                                        win.butUnselectAll.IsEnabled <- true
+                                        win.butStart.IsEnabled <- true
+                                        win.butClose.Content <- "Close"
+                                        updateSourceImages ()
+                                )
+                        )
+                    } |> Async.Start
+            | _ -> ()
+    )
+
+    win.Loaded.AddHandler (fun obj args -> updateSourceImages ())
+
+    win.ShowDialog () |> ignore
+
+    Logger.Log.RemoveListener (logListener)
+
+    lock monitor (
+        fun () ->
+            if not analysisPerformed then
                 // To cancel the current analysis if one is running on the next call to the progress function.
                 analysisCancelled <- true
-        atLeastOneAnalysisPerformed)
+            atLeastOneAnalysisPerformed
+    )