* Add the possibility to set an RBC as healthy or infected
[master-thesis.git] / Parasitemia / Parasitemia / GUI / GUI.fs
index 2aadefe..8aef251 100644 (file)
@@ -27,6 +27,7 @@ let run (defaultConfig: Config) =
     let colorRBCInfected = Brushes.Red
 
     let state = State.State()
+    let mutable currentScale = 1.
 
     let exit: MenuItem = ctrl "menuExit"
     let saveFile: MenuItem = ctrl "menuSave"
@@ -35,15 +36,157 @@ let run (defaultConfig: Config) =
 
     let addSourceImage: MenuItem = ctrl "menuAddSourceImage"
     let txtPatient: TextBox = ctrl "txtPatient"
-    let stackPreviews: StackPanel = ctrl "stackPreviews"
+    let txtGlobalParasitemia: TextBox = ctrl "txtGlobalParasitemia"
+
     let butStartAnalysis: Button = ctrl "butStartAnalysis"
 
+    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 scrollRBC: ScrollViewer = ctrl "scrollRBC"
+    let stackRBC: StackPanel = ctrl "stackRBC"
 
     // Initializations.
 
+    // Utils.
+    let extractRBCPreview (img: Emgu.CV.Image<Emgu.CV.Structure.Bgr, byte>) (rbc: RBC) : Emgu.CV.Image<Emgu.CV.Structure.Bgr, byte> =
+        let rbcWidth = rbc.size.Width
+        let rbcHeight = rbc.size.Height
+        img.GetSubRect(System.Drawing.Rectangle(System.Drawing.Point(rbc.center.X - rbcWidth / 2. |> Utils.roundInt, rbc.center.Y - rbcHeight / 2. |> Utils.roundInt),
+                                                System.Drawing.Size(Utils.roundInt rbcWidth, Utils.roundInt rbcHeight)))
+
+    let setRBCFrameStyle (rbc: RBC) (frame: Views.RBCFrame) =
+        frame.Opacity <- if rbc.setManually || rbc.infected then 1. else 0.
+        let color = if rbc.infected then colorRBCInfected else colorRBCHealthy
+        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 =
+        frame.Visibility <- Visibility.Visible
+        frame.Height <- rbc.size.Height
+        frame.Width <- rbc.size.Width
+        frame.Tag <- rbc
+        setRBCFrameStyle rbc frame
+        frame.border.StrokeThickness <- 1.
+        frame.lblRBCNumber.Content <- rbc.num
+        frame
+
+    let highlightRBCFrame (frame: Views.RBCFrame) (highlight: bool) =
+        let rbc = frame.Tag :?> RBC
+        if highlight
+        then
+            frame.border.StrokeThickness <- 3.
+            if not rbc.infected && not rbc.setManually then frame.Opacity <- 1.
+        else
+            frame.border.StrokeThickness <- 1.
+            if not rbc.infected && not rbc.setManually then frame.Opacity <- 0.
+
+    let zoomToRBC (rbc: RBC) =
+        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 "%.2f %% (%d / %d)" percent nbInfected nbTotal
+
+    let updateCurrentImageInformation () =
+        match state.CurrentImage with
+        | Some srcImg ->
+            let parasitemiaStr = parasitemiaText (state.ImageParasitemia srcImg)
+            txtImageInformation.Text <- sprintf "Parasitemia: %s" parasitemiaStr
+        | _ -> ()
+
+    let updateGlobalParasitemia () =
+        txtGlobalParasitemia.Text <- parasitemiaText state.GlobalParasitemia
+
+    let rec setAsInfected (rbc: RBC) (infected: bool) =
+        rbc.SetAsInfected infected
+        canvasCurrentImage.Children
+        |> Seq.cast<Views.RBCFrame>
+        |> Seq.iter
+            (fun frame ->
+                if (frame.Tag :?> RBC) = rbc
+                then
+                    setRBCFrameStyle rbc frame)
+        updateRBCFramesPreview ()
+        updateCurrentImageInformation ()
+        updateGlobalParasitemia ()
+
+    and RBCFrame (rbc: RBC) : Views.RBCFrame =
+        let f = RBCFrameFromExisting rbc (Views.RBCFrame())
+        f.menuRBCSetAsHealthy.Click.AddHandler(fun obj args -> setAsInfected (f.Tag :?> RBC) false)
+        f.menuRBCSetAsInfected.Click.AddHandler(fun obj args -> setAsInfected (f.Tag :?> RBC) true)
+        f.ContextMenuOpening.AddHandler(
+            fun obj args ->
+                if (f.Tag :?> RBC).infected
+                then
+                    f.menuRBCSetAsHealthy.Visibility <- Visibility.Visible
+                    f.menuRBCSetAsInfected.Visibility <- Visibility.Collapsed
+                else
+                    f.menuRBCSetAsHealthy.Visibility <- Visibility.Collapsed
+                    f.menuRBCSetAsInfected.Visibility <- Visibility.Visible)
+
+        f.ContextMenuClosing.AddHandler(fun obj args -> if not f.IsMouseOver then highlightRBCFrame f false )
+        f.MouseEnter.AddHandler(fun obj args -> highlightRBCFrame f true)
+        f.MouseLeave.AddHandler(fun obj args -> if not f.grid.ContextMenu.IsOpen then highlightRBCFrame f false)
+        f
+
+    and updateRBCFramesPreview () =
+        match state.CurrentImage with
+        | Some srcImg ->
+            let mutable currentPreview = 0
+            for rbc in srcImg.rbcs |> List.filter (fun rbc -> rbc.infected) do
+                let previewInfected =
+                    if currentPreview < stackRBC.Children.Count
+                    then
+                        RBCFrameFromExisting rbc (stackRBC.Children.[currentPreview] :?> Views.RBCFrame)
+                    else
+                        let f = RBCFrame rbc
+                        f.MouseLeftButtonUp.AddHandler(fun obj args -> zoomToRBC ((obj :?> Views.RBCFrame).Tag :?> RBC))
+                        stackRBC.Children.Add(f) |> ignore
+                        f
+
+                currentPreview <- currentPreview + 1
+
+                previewInfected.Height <- stackRBC.ActualHeight
+                previewInfected.Width <- stackRBC.ActualHeight * rbc.size.Width / rbc.size.Height
+                previewInfected.border.Fill <- ImageBrush(BitmapSourceConvert.ToBitmapSource(extractRBCPreview srcImg.img rbc))
+
+            stackRBC.Children.RemoveRange(currentPreview, stackRBC.Children.Count - currentPreview)
+        | _ -> ()
+
+    let updateRBCFramesCurrent () =
+        match state.CurrentImage with
+        | Some srcImg ->
+            let mutable currentCanvas = 0
+            for rbc in srcImg.rbcs do
+                let frame =
+                    if currentCanvas < canvasCurrentImage.Children.Count
+                    then
+                        RBCFrameFromExisting rbc (canvasCurrentImage.Children.[currentCanvas] :?> Views.RBCFrame)
+                    else
+                        let f = RBCFrame rbc
+                        canvasCurrentImage.Children.Add(f) |> ignore
+                        f
+
+                currentCanvas <- currentCanvas + 1
+
+                Canvas.SetLeft(frame, rbc.center.X - rbc.size.Width / 2.)
+                Canvas.SetTop(frame, rbc.center.Y - rbc.size.Height / 2.)
+
+            for i in currentCanvas .. canvasCurrentImage.Children.Count - 1 do
+                canvasCurrentImage.Children.[i].Visibility <- Visibility.Hidden
+        | _ -> ()
+
     // Operations.
     let synchronizeState () =
         state.PatientID <- txtPatient.Text
@@ -60,33 +203,10 @@ let run (defaultConfig: Config) =
         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 do
-            let rectangle =
-                Rectangle(
-                    Height = rbc.size.Height,
-                    Width = rbc.size.Width,
-                    Stroke = (if rbc.infected then colorRBCInfected else colorRBCHealthy),
-                    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.
-                                | _ -> ())
+        updateRBCFramesCurrent ()
+        updateRBCFramesPreview ()
+        updateCurrentImageInformation ()
+
 
     let addPreview (srcImg: SourceImage) =
         let imgCtrl = Views.ImageSourcePreview(Margin = Thickness(3.))
@@ -103,11 +223,14 @@ let run (defaultConfig: Config) =
         then
             for srcImg in state.SourceImages do
                 addPreview srcImg
-            setCurrentImage (state.SourceImages.First())
+            match state.CurrentImage with
+            | Some srcImg -> setCurrentImage srcImg
+            | _ -> ()
 
     let updateGUI () =
         txtPatient.Text <- state.PatientID
         updatePreviews ()
+        updateGlobalParasitemia ()
 
     exit.Click.AddHandler(fun obj args -> mainWindow.Root.Close())
     saveFile.Click.AddHandler(fun obj args ->
@@ -145,6 +268,7 @@ let run (defaultConfig: Config) =
         then
             let srcImg = state.AddSourceImage(dialog.FileName)
             addPreview srcImg
+            updateGlobalParasitemia ()
             if state.SourceImages.Count() = 1
             then
                 setCurrentImage srcImg)
@@ -153,7 +277,12 @@ let run (defaultConfig: Config) =
         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
-        )
+        updateGlobalParasitemia ()
+
+        // Refresh current image.
+        match state.CurrentImage with
+        | Some srcImg -> setCurrentImage srcImg
+        | _ -> ())
 
     // Zoom on the current image.
     let adjustCurrentImageBorders (deltaX: float) (deltaY: float) =
@@ -182,7 +311,6 @@ let run (defaultConfig: Config) =
         scrollViewCurrentImage.ScrollToHorizontalOffset(scrollViewCurrentImage.HorizontalOffset + deltaX / 8.)
         scrollViewCurrentImage.ScrollToVerticalOffset(scrollViewCurrentImage.VerticalOffset + deltaY / 8.))
 
-    let mutable currentScale = 1.
     let mutable maxScale = 4.
     let mutable minScale = 0.25
     let currentImageScaleTransform = ScaleTransform()