let colorRBCInfected = Brushes.Red
let state = State.State()
+ let mutable currentScale = 1.
let exit: MenuItem = ctrl "menuExit"
let saveFile: MenuItem = ctrl "menuSave"
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
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.))
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 ->
then
let srcImg = state.AddSourceImage(dialog.FileName)
addPreview srcImg
+ updateGlobalParasitemia ()
if state.SourceImages.Count() = 1
then
setCurrentImage srcImg)
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) =
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()
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
- xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" x:Name="MainWindow" Height="681.888" Width="787.61" MinHeight="200" MinWidth="300">
+ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+ xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+ mc:Ignorable="d"
+ x:Name="MainWindow" Height="681.888" Width="787.61" MinHeight="200" MinWidth="300" Title="Parasitemia" Icon="pack://application:,,,/Resources/logo_256.png">
<DockPanel x:Name="dockPanelMain" LastChildFill="True">
<Menu DockPanel.Dock="Top">
<MenuItem Header="_File">
<MenuItem x:Name="menuAddSourceImage" Header="_Add a source image" />
</MenuItem>
</Menu>
-
<Grid x:Name="gridMain">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
- <Label x:Name="lblPatient" Margin="10, 3, 3, 3" Content="Patient"/>
+ <Label x:Name="lblPatient" Margin="10, 3, 3, 3" Content="Patient ID"/>
<Label x:Name="lblGlobalParasitemia" Margin="10,3,3,3" Content="Global parasitemia" Grid.Row="1" />
<TextBox x:Name="txtPatient" Grid.Column="1" Margin="3, 3, 10, 3" TextWrapping="Wrap" VerticalAlignment="Center" />
<TextBox x:Name="txtGlobalParasitemia" Grid.Column="1" Grid.Row="2" Margin="3, 3, 10, 3" TextWrapping="Wrap" VerticalAlignment="Center" IsReadOnly="True" />
<Canvas x:Name="canvasCurrentImage" Height="100" Width="100" />
</Border>
</ScrollViewer>
+ <Border Grid.RowSpan="1" Margin="3" >
+ <ScrollViewer x:Name="scrollRBC" VerticalScrollBarVisibility="Hidden" HorizontalScrollBarVisibility="Visible">
+ <StackPanel x:Name="stackRBC" Orientation="Horizontal" />
+ </ScrollViewer>
+ </Border>
+ <TextBlock x:Name="txtImageInformation" Grid.Row="2" TextWrapping="Wrap" />
</Grid>
</Grid>
</DockPanel>
member val FilePath: string = "" with get, set
member val PatientID: string = "" with get, set
+ member this.ImageParasitemia (srcImg: SourceImage) : int * int =
+ List.length srcImg.rbcs,
+ srcImg.rbcs |> List.fold (fun nbInfected rbc -> if rbc.infected then nbInfected + 1 else nbInfected) 0
+
+ member this.GlobalParasitemia : int * int =
+ sourceImages
+ |> Seq.fold (fun (nbTotal, nbTotalInfected) srcImg ->
+ let nb, nbInfected = this.ImageParasitemia srcImg
+ nbTotal + nb, nbTotalInfected + nbInfected) (0, 0)
+
member this.Save () =
let data = { PiaZ.sources = List.ofSeq sourceImages; PiaZ.patientID = this.PatientID }
PiaZ.save this.FilePath data
this.PatientID <- data.patientID
sourceImages.Clear()
sourceImages.InsertRange(0, data.sources)
+ if sourceImages.Count > 0
+ then this.CurrentImage <- Some sourceImages.[0]
member this.AddSourceImage (filePath: string) : SourceImage =
let srcImg = { num = sourceImages.Count + 1; rbcs = []; img = new Image<Bgr, byte>(filePath) }
sourceImages.Add(srcImg)
+ if sourceImages.Count = 1
+ then this.CurrentImage <- Some sourceImages.[0]
srcImg
- member this.SetResult (num: int) (cells: Cell list) =
- let sourceImage = sourceImages.Find(fun srcImg -> srcImg.num = num)
+ member this.SetResult (imgNum: int) (cells: Cell list) =
+ let sourceImage = sourceImages.Find(fun srcImg -> srcImg.num = imgNum)
+ let w = sourceImage.img.Width
+ let h = sourceImage.img.Height
sourceImage.rbcs <- cells
|> List.filter (fun cell -> match cell.cellClass with HealthyRBC | InfectedRBC -> true | _ -> false )
+ |> List.sortByDescending (fun cell -> cell.infectedArea, (w - cell.center.X) + (h - cell.center.Y))
|> List.mapi (fun i cell ->
- { num = i
+ { num = i + 1
infected = cell.cellClass = InfectedRBC
setManually = false
center = Point(float cell.center.X, float cell.center.Y)
size = Size(float cell.elements.Width, float cell.elements.Height)
- stainArea = cell.stainArea })
+ infectedArea = cell.infectedArea })
member this.SourceImages : SourceImage seq =
sourceImages :> SourceImage seq