* Add the possibility to set an RBC as healthy or infected
authorGreg Burri <greg.burri@gmail.com>
Mon, 11 Jan 2016 17:01:39 +0000 (18:01 +0100)
committerGreg Burri <greg.burri@gmail.com>
Mon, 11 Jan 2016 17:01:39 +0000 (18:01 +0100)
Parasitemia/Parasitemia/Classifier.fs
Parasitemia/Parasitemia/GUI/GUI.fs
Parasitemia/Parasitemia/GUI/MainWindow.xaml
Parasitemia/Parasitemia/GUI/PiaZ.fs
Parasitemia/Parasitemia/GUI/RBCFrame.xaml [new file with mode: 0644]
Parasitemia/Parasitemia/GUI/RBCFrame.xaml.fs [new file with mode: 0644]
Parasitemia/Parasitemia/GUI/State.fs
Parasitemia/Parasitemia/GUI/Types.fs
Parasitemia/Parasitemia/Parasitemia.fsproj
Parasitemia/Parasitemia/Resources/logo_256.png [new file with mode: 0644]
Parasitemia/Parasitemia/Types.fs

index 3a679e7..1a22129 100644 (file)
@@ -229,5 +229,6 @@ let findCells (ellipses: Ellipse list) (parasites: ParasitesMarker.Result) (img:
 
                 Some { cellClass = cellClass
                        center = Point(roundInt e.Cx, roundInt e.Cy)
+                       infectedArea = infectedPixels.Count
                        stainArea = stainPixels
                        elements = elements })
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()
index 281493b..9aecf1b 100644 (file)
@@ -1,5 +1,9 @@
 <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">
@@ -13,7 +17,6 @@
             <MenuItem x:Name="menuAddSourceImage" Header="_Add a source image" />
          </MenuItem>
       </Menu>
-
       <Grid x:Name="gridMain">
          <Grid.RowDefinitions>
             <RowDefinition Height="Auto"/>
@@ -32,7 +35,7 @@
                <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>
index 900ec65..a373e53 100644 (file)
@@ -71,7 +71,7 @@ let save (filePath: string) (data: FileData) =
                                             rbc.num,
                                             rbc.infected, rbc.setManually,
                                             decimal rbc.center.X, decimal rbc.center.Y, decimal rbc.size.Width, decimal rbc.size.Height,
-                                            rbc.stainArea) |])
+                                            rbc.infectedArea) |])
 
         let imgJSONEntry = file.CreateEntry(imgFilename + ".json", CompressionLevel.Fastest)
         use imgJSONFileWriter = new StreamWriter(imgJSONEntry.Open())
@@ -98,5 +98,5 @@ let load (filePath: string) : FileData =
                                                             { num = rbc.Num;
                                                               infected = rbc.Infected; setManually = rbc.SetManually;
                                                               center = Point(float rbc.PosX, float rbc.PosY); size = Size(float rbc.Width, float rbc.Height);
-                                                              stainArea = rbc.StainArea } ] } ]
+                                                              infectedArea = rbc.StainArea } ] } ]
     { sources = sources; patientID = mainJSON.PatientId }
\ No newline at end of file
diff --git a/Parasitemia/Parasitemia/GUI/RBCFrame.xaml b/Parasitemia/Parasitemia/GUI/RBCFrame.xaml
new file mode 100644 (file)
index 0000000..2a56b00
--- /dev/null
@@ -0,0 +1,21 @@
+<UserControl
+               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:fsxaml="clr-namespace:FsXaml;assembly=FsXaml.Wpf"
+      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+      mc:Ignorable="d" d:DesignWidth="100" d:DesignHeight="100"
+               >
+   <Grid x:Name="grid">
+      <Grid.ContextMenu>
+         <ContextMenu>
+            <MenuItem x:Name="menuRBCSetAsHealthy" Header="_Set as healthy"  />
+            <MenuItem x:Name="menuRBCSetAsInfected" Header="_Set as infected" />
+         </ContextMenu>
+      </Grid.ContextMenu>
+      <Rectangle x:Name="border" Fill="#00000000">
+      </Rectangle>
+      <Polygon x:Name="manuallyAdded" Points="0,0 12,0, 12,12" Fill="Black" HorizontalAlignment="Right" VerticalAlignment="Top" />
+      <Label x:Name="lblRBCNumber" HorizontalAlignment="Right" VerticalAlignment="Bottom" Background="#4C000000" Foreground="White"/>
+   </Grid>
+</UserControl>
\ No newline at end of file
diff --git a/Parasitemia/Parasitemia/GUI/RBCFrame.xaml.fs b/Parasitemia/Parasitemia/GUI/RBCFrame.xaml.fs
new file mode 100644 (file)
index 0000000..1ef4d31
--- /dev/null
@@ -0,0 +1,11 @@
+namespace Parasitemia.GUI.Views
+
+open System
+open System.Windows
+open System.Windows.Data
+open System.Windows.Input
+
+open FSharp.ViewModule
+open FsXaml
+
+type RBCFrame = XAML<"GUI/RBCFrame.xaml", true>
index a1ae052..44de2e3 100644 (file)
@@ -15,6 +15,16 @@ type State () =
     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
@@ -24,23 +34,30 @@ type State () =
         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
index e912ad3..6e00586 100644 (file)
@@ -8,12 +8,17 @@ open Emgu.CV.Structure
 type RBC = {
     num: int
 
-    infected: bool
-    setManually: bool
+    mutable infected: bool
+    mutable setManually: bool
 
     center: Point
     size: Size
-    stainArea: int }
+    infectedArea: int } with
+    member this.SetAsInfected (infected: bool) =
+        if infected <> this.infected
+        then
+            this.infected <- infected
+            this.setManually <- not this.setManually
 
 type SourceImage = {
     num: int
index d3289c6..b3a7e59 100644 (file)
@@ -94,6 +94,8 @@
     <Compile Include="GUI\NumericUpDown.xaml.fs" />
     <Resource Include="GUI\ImageSourcePreview.xaml" />
     <Compile Include="GUI\ImageSourcePreview.xaml.fs" />
+    <Resource Include="GUI\RBCFrame.xaml" />
+    <Compile Include="GUI\RBCFrame.xaml.fs" />
     <Resource Include="GUI\MainWindow.xaml" />
     <Compile Include="GUI\MainWindow.xaml.fs" />
     <Compile Include="GUI\Types.fs" />
     <Compile Include="Program.fs" />
     <None Include="App.config" />
     <Content Include="packages.config" />
+    <Resource Include="Resources\logo_256.png" />
   </ItemGroup>
   <ItemGroup>
     <Reference Include="Emgu.CV">
diff --git a/Parasitemia/Parasitemia/Resources/logo_256.png b/Parasitemia/Parasitemia/Resources/logo_256.png
new file mode 100644 (file)
index 0000000..857a793
Binary files /dev/null and b/Parasitemia/Parasitemia/Resources/logo_256.png differ
index 872844b..5275b3a 100644 (file)
@@ -48,6 +48,7 @@ type CellClass = HealthyRBC | InfectedRBC | Peculiar
 type Cell = {
     cellClass: CellClass
     center: Point
+    infectedArea: int
     stainArea: int
     elements: Matrix<byte> }