X-Git-Url: http://git.euphorik.ch/?a=blobdiff_plain;f=Parasitemia%2FParasitemiaUI%2FGUI.fs;h=3746461e567527b6299573bb6f9fe5bb0da41566;hb=e588f9c954a54cb259b2c1f6f9e8d9dbd4639269;hp=98b13ea8d15171742bc186d2d6e7954cf3bbbfaa;hpb=4bfa3cbdc6145e6944f02e24829ab2ef3a851ac1;p=master-thesis.git diff --git a/Parasitemia/ParasitemiaUI/GUI.fs b/Parasitemia/ParasitemiaUI/GUI.fs index 98b13ea..3746461 100644 --- a/Parasitemia/ParasitemiaUI/GUI.fs +++ b/Parasitemia/ParasitemiaUI/GUI.fs @@ -24,17 +24,17 @@ let run (defaultConfig: Config) (fileToOpen: string option) = let mainWindow = Views.MainWindow() let ctrl (name: string): 'a = mainWindow.Root.FindName(name) :?> 'a - let colorRBCHealthy = Brushes.YellowGreen - let colorRBCInfected = Brushes.Red - - let state = State.State() + let state = State.State(defaultConfig) let mutable currentScale = 1. let mutable displayHealthy = false + let warningBelowNumberOfRBC = 1000 let menuExit: MenuItem = ctrl "menuExit" let menuSaveFile: MenuItem = ctrl "menuSave" + let menuSaveAsFile: MenuItem = ctrl "menuSaveAs" let menuLoadFile: MenuItem = ctrl "menuOpen" let menuNewFile: MenuItem = ctrl "menuNew" + let menuExportResults: MenuItem = ctrl "menuExportResults" let menuAddSourceImage: MenuItem = ctrl "menuAddSourceImage" let menuAnalysis: MenuItem = ctrl "menuAnalysis" let menuStartAnalysis: MenuItem = ctrl "menuStartAnalysis" @@ -42,20 +42,30 @@ let run (defaultConfig: Config) (fileToOpen: string option) = let menuHightlightRBC: MenuItem = ctrl "menuHightlightRBC" let menuAbout: MenuItem = ctrl "menuAbout" + let txtDocumentStatus: TextBlock = ctrl "txtDocumentStatus" + let txtMessageStatus: TextBlock = ctrl "txtMessageStatus" + let txtPatient: TextBox = ctrl "txtPatient" - let txtGlobalParasitemia: TextBox = ctrl "txtGlobalParasitemia" + let txtGlobalParasitemia: TextBlock = ctrl "txtGlobalParasitemia" let stackPreviews: StackPanel = ctrl "stackPreviews" let scrollViewCurrentImage: ScrollViewer = ctrl "scrollViewCurrentImage" let borderCurrentImage: Border = ctrl "borderCurrentImage" let canvasCurrentImage: Canvas = ctrl "canvasCurrentImage" - let txtImageInformation: TextBlock = ctrl "txtImageInformation" + + let gridImageInformation: Grid = ctrl "gridImageInformation" + let txtImageInformation1: TextBlock = ctrl "txtImageInformation1" + let txtImageInformation2: TextBlock = ctrl "txtImageInformation2" + let txtImageName: TextBox = ctrl "txtImageName" let scrollRBC: ScrollViewer = ctrl "scrollRBC" let stackRBC: StackPanel = ctrl "stackRBC" + let imgLogos: Border = ctrl "imgLogos" + // Initializations. + let canvasCurrentImageColor = canvasCurrentImage.Background menuHightlightRBC.IsChecked <- displayHealthy // Utils. @@ -70,23 +80,34 @@ let run (defaultConfig: Config) (fileToOpen: string option) = System.Drawing.Size((if x + w >= img.Width then img.Width - x else w), (if y + h >= img.Height then img.Height - y else h)))) - let setRBCFrameStyle (rbc: RBC) (frame: Views.RBCFrame) = + let setRBCFrameStyle (srcImg: SourceImage) (rbc: RBC) (frame: Views.RBCFrame) = frame.Opacity <- if displayHealthy || rbc.setManually || rbc.infected then 1. else 0. - let color = if rbc.infected then colorRBCInfected else colorRBCHealthy + let color = if rbc.infected then srcImg.InfectedRBCColor else srcImg.HealthyRBCColor frame.manuallyAdded.Visibility <- if rbc.setManually then Visibility.Visible else Visibility.Hidden frame.manuallyAdded.Fill <- color frame.border.Stroke <- color - let RBCFrameFromExisting (rbc: RBC) (frame: Views.RBCFrame) : Views.RBCFrame = + let RBCFrameFromExisting (srcImg: SourceImage) (rbc: RBC) (frame: Views.RBCFrame) : Views.RBCFrame = frame.Visibility <- Visibility.Visible frame.Height <- rbc.size.Height frame.Width <- rbc.size.Width frame.Tag <- rbc - setRBCFrameStyle rbc frame + setRBCFrameStyle srcImg rbc frame frame.border.StrokeThickness <- 1. frame.txtRBCNumber.Text <- rbc.num.ToString() frame + let updateDocumentStatus () = + txtDocumentStatus.Text <- if state.FilePath = "" then "" else state.FilePath + + let statusMessageTimer = Threading.DispatcherTimer() + statusMessageTimer.Tick.AddHandler(fun obj args -> statusMessageTimer.Stop(); txtMessageStatus.Text <- "") + statusMessageTimer.Interval <- TimeSpan(0, 0, 2) + let displayStatusMessage (message: string) = + txtMessageStatus.Text <- message + statusMessageTimer.Stop() + statusMessageTimer.Start() + let highlightRBCFrame (frame: Views.RBCFrame) (highlight: bool) = let rbc = frame.Tag :?> RBC if highlight @@ -101,33 +122,52 @@ let run (defaultConfig: Config) (fileToOpen: string option) = scrollViewCurrentImage.ScrollToHorizontalOffset(rbc.center.X * currentScale - scrollViewCurrentImage.ViewportWidth / 2. + borderCurrentImage.BorderThickness.Left) scrollViewCurrentImage.ScrollToVerticalOffset(rbc.center.Y * currentScale - scrollViewCurrentImage.ViewportHeight / 2. + borderCurrentImage.BorderThickness.Top) - let parasitemiaText (nbTotal: int, nbInfected: int) : string = - if nbTotal = 0 - then - "" - else - let percent = 100. * (float nbInfected) / (float nbTotal) - sprintf "%.1f %% (%d / %d)" percent nbInfected nbTotal + + let txtImageName_TextChanged = + TextChangedEventHandler(fun obj args -> state.CurrentImage |> Option.iter(fun srcImg -> state.SetName srcImg txtImageName.Text)) let updateCurrentImageInformation () = + txtImageName.TextChanged.RemoveHandler(txtImageName_TextChanged) + txtImageInformation1.Inlines.Clear() + txtImageInformation2.Inlines.Clear() + txtImageName.Text <- "" + match state.CurrentImage with | Some srcImg -> - let parasitemiaStr = parasitemiaText (state.ImageParasitemia srcImg) - txtImageInformation.Inlines.Clear() - txtImageInformation.Inlines.Add(Documents.Run("Parasitemia: ", FontWeight = FontWeights.Bold)) - txtImageInformation.Inlines.Add(parasitemiaStr) - txtImageInformation.Inlines.Add(Documents.LineBreak()) - - txtImageInformation.Inlines.Add(Documents.Run("Average erytrocyte diameter: ", FontWeight = FontWeights.Bold)) - txtImageInformation.Inlines.Add(Documents.Run(srcImg.config.RBCRadius.ToString())) - txtImageInformation.Inlines.Add(Documents.LineBreak()) - - txtImageInformation.Inlines.Add(Documents.Run("Last analysis: ", FontWeight = FontWeights.Bold)) - txtImageInformation.Inlines.Add(Documents.Run(if srcImg.dateLastAnalysis.Ticks = 0L then "" else srcImg.dateLastAnalysis.ToLocalTime().ToString())) - | _ -> () + gridImageInformation.Visibility <- Visibility.Visible + txtImageName.Text <- srcImg.name + txtImageName.TextChanged.AddHandler(txtImageName_TextChanged) + + // The left part. + let parasitemiaStr = Utils.percentText (state.ImageParasitemia srcImg) + txtImageInformation1.Inlines.Add(Documents.Run("Parasitemia: ", FontWeight = FontWeights.Bold)) + txtImageInformation1.Inlines.Add(parasitemiaStr) + txtImageInformation1.Inlines.Add(Documents.LineBreak()) + + txtImageInformation1.Inlines.Add(Documents.Run("Last analysis: ", FontWeight = FontWeights.Bold)) + txtImageInformation1.Inlines.Add(Documents.Run(if srcImg.dateLastAnalysis.Ticks = 0L then "" else srcImg.dateLastAnalysis.ToLocalTime().ToString())) + + // The right part part. + txtImageInformation2.Inlines.Add(Documents.Run("Added infected erythrocyte: ", FontWeight = FontWeights.Bold)) + txtImageInformation2.Inlines.Add(Documents.Run((state.ImageNbManuallyChangedRBCStr srcImg true) + " " + (state.ImageManuallyChangedRBCStr srcImg true))) + txtImageInformation2.Inlines.Add(Documents.LineBreak()) + txtImageInformation2.Inlines.Add(Documents.Run("Removed infected erythrocyte: ", FontWeight = FontWeights.Bold)) + txtImageInformation2.Inlines.Add(Documents.Run((state.ImageNbManuallyChangedRBCStr srcImg false) + " " + (state.ImageManuallyChangedRBCStr srcImg false))) + + | _ -> + gridImageInformation.Visibility <- Visibility.Hidden let updateGlobalParasitemia () = - txtGlobalParasitemia.Text <- parasitemiaText state.GlobalParasitemia + txtGlobalParasitemia.Inlines.Clear() + let total, infected = state.GlobalParasitemia + txtGlobalParasitemia.Inlines.Add(Documents.Run(Utils.percentText (total, infected), FontWeight = FontWeights.Bold)) + if total > 0 && total < warningBelowNumberOfRBC + then + txtGlobalParasitemia.Inlines.Add( + Documents.Run( + sprintf " Warning: the number of erythrocytes should be above %d" warningBelowNumberOfRBC, + FontWeight = FontWeights.Bold, + Foreground = Brushes.Red)) let updateViewportPreview () = for preview in stackPreviews.Children |> Seq.cast do @@ -154,7 +194,7 @@ let run (defaultConfig: Config) (fileToOpen: string option) = else preview.viewport.Visibility <- Visibility.Hidden - let rec setAsInfected (rbc: RBC) (infected: bool) = + let rec setAsInfected (srcImg: SourceImage) (rbc: RBC) (infected: bool) = state.SetAsInfected rbc infected canvasCurrentImage.Children |> Seq.cast @@ -162,16 +202,16 @@ let run (defaultConfig: Config) (fileToOpen: string option) = (fun frame -> if (frame.Tag :?> RBC) = rbc then - setRBCFrameStyle rbc frame) + setRBCFrameStyle srcImg rbc frame) updateRBCFramesPreview () updateCurrentImageInformation () updateGlobalParasitemia () - and RBCFrame (rbc: RBC) : Views.RBCFrame = - let frame = RBCFrameFromExisting rbc (Views.RBCFrame()) + and RBCFrame (srcImg: SourceImage) (rbc: RBC) : Views.RBCFrame = + let frame = RBCFrameFromExisting srcImg rbc (Views.RBCFrame()) frame.SetValue(Panel.ZIndexProperty, Int32.MaxValue - rbc.num) // To be sure the - frame.menuRBCSetAsHealthy.Click.AddHandler(fun obj args -> setAsInfected (frame.Tag :?> RBC) false) - frame.menuRBCSetAsInfected.Click.AddHandler(fun obj args -> setAsInfected (frame.Tag :?> RBC) true) + frame.menuRBCSetAsHealthy.Click.AddHandler(fun obj args -> setAsInfected srcImg (frame.Tag :?> RBC) false) + frame.menuRBCSetAsInfected.Click.AddHandler(fun obj args -> setAsInfected srcImg (frame.Tag :?> RBC) true) frame.ContextMenuOpening.AddHandler( fun obj args -> if (frame.Tag :?> RBC).infected @@ -195,9 +235,9 @@ let run (defaultConfig: Config) (fileToOpen: string option) = let previewInfected = if currentPreview < stackRBC.Children.Count then - RBCFrameFromExisting rbc (stackRBC.Children.[currentPreview] :?> Views.RBCFrame) + RBCFrameFromExisting srcImg rbc (stackRBC.Children.[currentPreview] :?> Views.RBCFrame) else - let f = RBCFrame rbc + let f = RBCFrame srcImg rbc f.MouseLeftButtonUp.AddHandler(fun obj args -> zoomToRBC (f.Tag :?> RBC)) stackRBC.Children.Add(f) |> ignore f @@ -221,9 +261,9 @@ let run (defaultConfig: Config) (fileToOpen: string option) = let frame = if currentCanvas < canvasCurrentImage.Children.Count then - RBCFrameFromExisting rbc (canvasCurrentImage.Children.[currentCanvas] :?> Views.RBCFrame) + RBCFrameFromExisting srcImg rbc (canvasCurrentImage.Children.[currentCanvas] :?> Views.RBCFrame) else - let f = RBCFrame rbc + let f = RBCFrame srcImg rbc f.Root.Opacity <- 0.7 canvasCurrentImage.Children.Add(f) |> ignore f @@ -237,23 +277,47 @@ let run (defaultConfig: Config) (fileToOpen: string option) = canvasCurrentImage.Children.[i].Visibility <- Visibility.Hidden | _ -> () + let askDocumentPathToSave () : string option = + let dialog = SaveFileDialog(AddExtension = true, DefaultExt = PiaZ.extension, Filter = PiaZ.filter) + + if state.FilePath <> "" + then + dialog.FileName <- FileInfo(state.FilePath).Name + elif state.PatientID <> "" + then + dialog.FileName <- state.PatientID + PiaZ.extension + + let res = dialog.ShowDialog() + if res.HasValue && res.Value then + Some dialog.FileName + else + None + let saveCurrentDocument () = try if state.FilePath = "" then - let dialog = SaveFileDialog(AddExtension = true, DefaultExt = PiaZ.extension, Filter = PiaZ.filter); - let res = dialog.ShowDialog() - if res.HasValue && res.Value - then - state.FilePath <- dialog.FileName + match askDocumentPathToSave () with + | Some filepath -> + state.FilePath <- filepath state.Save() + | _ -> () else state.Save() + updateDocumentStatus () + displayStatusMessage "Document saved" with | :? IOException as ex -> Log.Error(ex.ToString()) MessageBox.Show(sprintf "The document cannot be save in '%s'" state.FilePath, "Error saving the document", MessageBoxButton.OK, MessageBoxImage.Error) |> ignore + let saveCurrentDocumentAsNewFile () = + match askDocumentPathToSave () with + | Some filepath -> + state.FilePath <- filepath + saveCurrentDocument () + | _ -> () + // Ask the use to save the current document if neccessary. let askSaveCurrent () = if state.AlteredSinceLastSave @@ -265,6 +329,8 @@ let run (defaultConfig: Config) (fileToOpen: string option) = let updateCurrentImage () = match state.CurrentImage with | Some srcImg -> + imgLogos.Visibility <- Visibility.Collapsed + // Highlight the preview. stackPreviews.Children |> Seq.cast @@ -276,11 +342,15 @@ let run (defaultConfig: Config) (fileToOpen: string option) = updateRBCFramesCurrent () updateRBCFramesPreview () - updateCurrentImageInformation () + | None -> + imgLogos.Visibility <- Visibility.Visible + stackRBC.Children.Clear() canvasCurrentImage.Children.Clear() - canvasCurrentImage.Background <- Brushes.Black + canvasCurrentImage.Background <- canvasCurrentImageColor + + updateCurrentImageInformation () let setCurrentImage (srcImg: SourceImage) = if state.CurrentImage.IsNone || state.CurrentImage.Value <> srcImg @@ -299,6 +369,10 @@ let run (defaultConfig: Config) (fileToOpen: string option) = if currentRemoved then updateCurrentImage() + + updateGlobalParasitemia() + + // Update image numbers. stackPreviews.Children |> Seq.cast |> Seq.iter (fun imgPreview -> imgPreview.txtImageNumber.Text <- (imgPreview.Tag :?> SourceImage).num.ToString())) imgCtrl.Tag <- srcImg @@ -343,6 +417,7 @@ let run (defaultConfig: Config) (fileToOpen: string option) = txtPatient.Text <- state.PatientID updatePreviews () updateGlobalParasitemia () + updateDocumentStatus () let loadFile (filepath: string) = askSaveCurrent () @@ -355,27 +430,47 @@ let run (defaultConfig: Config) (fileToOpen: string option) = | :? IOException as ex -> Log.Error(ex.ToString()) state.FilePath <- previousFilePath - MessageBox.Show(sprintf "The document cannot be loaded from '%s'" state.FilePath, "Error saving the document", MessageBoxButton.OK, MessageBoxImage.Error) |> ignore - - txtPatient.LostFocus.AddHandler(fun obj args -> state.PatientID <- txtPatient.Text) - - menuExit.Click.AddHandler(fun obj args -> - askSaveCurrent () - mainWindow.Root.Close()) - - menuSaveFile.Click.AddHandler(fun obj args -> saveCurrentDocument ()) + MessageBox.Show(sprintf "The document cannot be loaded from '%s'" state.FilePath, "Error loading the document", MessageBoxButton.OK, MessageBoxImage.Error) |> ignore - menuLoadFile.Click.AddHandler(fun obj args -> - // TODO: if current state not saved and not empty, ask to save it. + let askLoadFile () = let dialog = OpenFileDialog(Filter = PiaZ.filter) let res = dialog.ShowDialog() if res.HasValue && res.Value - then loadFile dialog.FileName) + then loadFile dialog.FileName - menuNewFile.Click.AddHandler(fun obj args -> + let newFile () = askSaveCurrent () state.Reset() - updateGUI()) + updateGUI() + + let exportResults () = + let extension = ".txt" + let dialog = SaveFileDialog(AddExtension = true, DefaultExt = extension) + + if state.FilePath <> "" + then + dialog.FileName <- Path.GetFileNameWithoutExtension(state.FilePath) + extension + elif state.PatientID <> "" + then + dialog.FileName <- state.PatientID + extension + + let res = dialog.ShowDialog() + if res.HasValue && res.Value then + try + Export.exportResults state dialog.FileName + with + | :? IOException as ex -> + 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 + + txtPatient.TextChanged.AddHandler(fun obj args -> state.PatientID <- txtPatient.Text) + + menuExit.Click.AddHandler(fun obj args -> mainWindow.Root.Close()) + menuSaveFile.Click.AddHandler(fun obj args -> saveCurrentDocument ()) + menuSaveAsFile.Click.AddHandler(fun obj args -> saveCurrentDocumentAsNewFile ()) + menuLoadFile.Click.AddHandler(fun obj args -> askLoadFile ()) + menuNewFile.Click.AddHandler(fun obj args -> newFile ()) + menuExportResults.Click.AddHandler(fun obj args -> exportResults ()) menuAddSourceImage.Click.AddHandler(fun obj args -> let dialog = OpenFileDialog(Filter = "Image Files|*.png;*.jpg;*.tif;*.tiff", Multiselect = true) @@ -385,8 +480,13 @@ let run (defaultConfig: Config) (fileToOpen: string option) = let noSourceImage = state.SourceImages.Count() = 0 for filename in dialog.FileNames do - let srcImg = state.AddSourceImage filename defaultConfig - addPreview srcImg + try + let srcImg = state.AddSourceImage filename defaultConfig + addPreview srcImg + with + | _ as ex -> + Log.Error(ex.ToString()) + MessageBox.Show(sprintf "Unable to read the image from '%s'" filename, "Error adding an image", MessageBoxButton.OK, MessageBoxImage.Error) |> ignore updateGlobalParasitemia () @@ -409,6 +509,8 @@ let run (defaultConfig: Config) (fileToOpen: string option) = menuAbout.Click.AddHandler(fun obj args -> About.showWindow mainWindow.Root) + mainWindow.Root.Closing.AddHandler(fun obj args -> askSaveCurrent ()) + // Zoom on the current image. let adjustCurrentImageBorders (deltaX: float) (deltaY: float) = borderCurrentImage.BorderThickness <- @@ -491,13 +593,47 @@ let run (defaultConfig: Config) (fileToOpen: string option) = borderCurrentImage.ReleaseMouseCapture() args.Handled <- true) + // Shortcuts. + // Save. + mainWindow.Root.InputBindings.Add( + Input.KeyBinding( + FSharp.ViewModule.FunCommand((fun obj -> saveCurrentDocument ()), (fun obj -> true)), + Input.KeyGesture(Input.Key.S, Input.ModifierKeys.Control))) |> ignore + + // Save as. + mainWindow.Root.InputBindings.Add( + Input.KeyBinding( + FSharp.ViewModule.FunCommand((fun obj -> saveCurrentDocumentAsNewFile ()), (fun obj -> true)), + Input.KeyGesture(Input.Key.S, Input.ModifierKeys.Control ||| Input.ModifierKeys.Shift))) |> ignore + + // Open. + mainWindow.Root.InputBindings.Add( + Input.KeyBinding( + FSharp.ViewModule.FunCommand((fun obj -> askLoadFile ()), (fun obj -> true)), + Input.KeyGesture(Input.Key.O, Input.ModifierKeys.Control))) |> ignore + + // New file. + mainWindow.Root.InputBindings.Add( + Input.KeyBinding( + FSharp.ViewModule.FunCommand((fun obj -> newFile ()), (fun obj -> true)), + Input.KeyGesture(Input.Key.N, Input.ModifierKeys.Control))) |> ignore + + // Export results. + mainWindow.Root.InputBindings.Add( + Input.KeyBinding( + FSharp.ViewModule.FunCommand((fun obj -> exportResults ()), (fun obj -> true)), + Input.KeyGesture(Input.Key.E, Input.ModifierKeys.Control))) |> ignore + // Viewport preview. scrollViewCurrentImage.ScrollChanged.AddHandler(fun obj args -> updateViewportPreview ()) + updateDocumentStatus () + gridImageInformation.Visibility <- Visibility.Hidden + mainWindow.Root.Show() match fileToOpen with | Some filepath -> loadFile filepath | None -> () - app.Run() \ No newline at end of file + app.Run()