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"
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.
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 "<New document>" 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
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 "<Never>" 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 "<Never>" 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 erytrocytes should be above %d" warningBelowNumberOfRBC,
+ FontWeight = FontWeights.Bold,
+ Foreground = Brushes.Red))
let updateViewportPreview () =
for preview in stackPreviews.Children |> Seq.cast<Views.ImageSourcePreview> do
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<Views.RBCFrame>
(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
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
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
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
let updateCurrentImage () =
match state.CurrentImage with
| Some srcImg ->
+ imgLogos.Visibility <- Visibility.Collapsed
+
// Highlight the preview.
stackPreviews.Children
|> Seq.cast<Views.ImageSourcePreview>
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
if currentRemoved
then
updateCurrentImage()
+
+ updateGlobalParasitemia()
+
+ // Update image numbers.
stackPreviews.Children |> Seq.cast<Views.ImageSourcePreview> |> Seq.iter (fun imgPreview -> imgPreview.txtImageNumber.Text <- (imgPreview.Tag :?> SourceImage).num.ToString()))
imgCtrl.Tag <- srcImg
txtPatient.Text <- state.PatientID
updatePreviews ()
updateGlobalParasitemia ()
+ updateDocumentStatus ()
let loadFile (filepath: string) =
askSaveCurrent ()
| :? 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)
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 ()
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 <-
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()