module ParasitemiaUI.Analysis open System open System.Linq open System.Windows open System.Windows.Controls open ParasitemiaUIControls open ParasitemiaCore.UnitsOfMeasure open ParasitemiaCore.Config open Types 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 _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 | true, value when value >= minPPI && value <= maxPPI -> Some value | _ -> None let monitor = Object () let mutable atLeastOneAnalysisPerformed = false let mutable analysisPerformed = false let mutable analysisCancelled = false let updateSourceImages () = win.stackSourceImagesSelection.Children.Clear () let width = int win.stackSourceImagesSelection.ActualWidth for srcImg in state.SourceImages do let imageSourceSelection = ImageSourceSelection (Tag = srcImg, Margin = Thickness 3.) imageSourceSelection.Tag <- srcImg 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 (srcImg.DateLastAnalysis.Ticks = 0L) imageSourceSelection.lblDateLastAnalysis.Content <- if srcImg.DateLastAnalysis.Ticks = 0L then "" else string srcImg.DateLastAnalysis 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 <- 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 (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 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. }) | 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.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 analysisPerformed <- false win.butStart.IsEnabled <- false win.textLog.Text <- "" win.butClose.Content <- "Abort" 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" 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.butStart.IsEnabled <- true win.butClose.Content <- "Close" updateSourceImages () ) ) } |> Async.Start | _ -> () ) win.Loaded.AddHandler (fun obj args -> updateSourceImages ()) win.ShowDialog () |> ignore Logger.Log.RmListener (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 )