let find (edges: Matrix<byte>)
- (xGradient: Image<Gray, float32>)
- (yGradient: Image<Gray, float32>)
+ (xGradient: Matrix<float32>)
+ (yGradient: Matrix<float32>)
(config: Config) : MatchingEllipses =
let r1, r2 = config.RBCRadius.Min, config.RBCRadius.Max
squaredDistance p1xf p1yf p3xf p3yf >= squaredMinimumDistance &&
squaredDistance p2xf p2yf p3xf p3yf >= squaredMinimumDistance
then
- match areVectorsValid (float32 p1xf) (float32 p1yf) (float32 p2xf) (float32 p2yf) -xDirData.[p1.Y, p1.X, 0] -yDirData.[p1.Y, p1.X, 0] -xDirData.[p2.Y, p2.X, 0] -yDirData.[p2.Y, p2.X, 0] with
+ match areVectorsValid (float32 p1xf) (float32 p1yf) (float32 p2xf) (float32 p2yf) -xDirData.[p1.Y, p1.X] -yDirData.[p1.Y, p1.X] -xDirData.[p2.Y, p2.X] -yDirData.[p2.Y, p2.X] with
| Some (m1, m2) ->
match ellipse2 p1xf p1yf (float m1) p2xf p2yf (float m2) p3xf p3yf with
| Some e when e.Cx > 0.f && e.Cx < w_f - 1.f && e.Cy > 0.f && e.Cy < h_f - 1.f &&
/// The thresholds are automatically defined with otsu on gradient magnitudes.
/// </summary>
/// <param name="img"></param>
-let findEdges (img: Image<Gray, float32>) : Matrix<byte> * Image<Gray, float32> * Image<Gray, float32> =
+let findEdges (img: Image<Gray, float32>) : Matrix<byte> * Matrix<float32> * Matrix<float32> =
let w = img.Width
let h = img.Height
use sobelKernel =
- new ConvolutionKernelF(array2D [[ 1.0f; 0.0f; -1.0f ]
- [ 2.0f; 0.0f; -2.0f ]
- [ 1.0f; 0.0f; -1.0f ]], Point(1, 1))
+ new Matrix<float32>(array2D [[ 1.0f; 0.0f; -1.0f ]
+ [ 2.0f; 0.0f; -2.0f ]
+ [ 1.0f; 0.0f; -1.0f ]])
- let xGradient = img.Convolution(sobelKernel)
- let yGradient = img.Convolution(sobelKernel.Transpose())
-
- let xGradientData = xGradient.Data
- let yGradientData = yGradient.Data
- for r in 0 .. h - 1 do
- xGradientData.[r, 0, 0] <- 0.f
- xGradientData.[r, w - 1, 0] <- 0.f
- yGradientData.[r, 0, 0] <- 0.f
- yGradientData.[r, w - 1, 0] <- 0.f
-
- for c in 0 .. w - 1 do
- xGradientData.[0, c, 0] <- 0.f
- xGradientData.[h - 1, c, 0] <- 0.f
- yGradientData.[0, c, 0] <- 0.f
- yGradientData.[h - 1, c, 0] <- 0.f
+ let xGradient = new Matrix<float32>(img.Size)
+ let yGradient = new Matrix<float32>(img.Size)
+ CvInvoke.Filter2D(img, xGradient, sobelKernel, Point(1, 1))
+ CvInvoke.Filter2D(img, yGradient, sobelKernel.Transpose(), Point(1, 1))
use magnitudes = new Matrix<float32>(xGradient.Size)
use angles = new Matrix<float32>(xGradient.Size)
for i in 1 .. h - 2 do
for j in 1 .. w - 2 do
- let vx = xGradientData.[i, j, 0]
- let vy = yGradientData.[i, j, 0]
+ let vx = xGradientData.[i, j]
+ let vy = yGradientData.[i, j]
if vx <> 0.f || vy <> 0.f
then
let angle = anglesData.[i, j]
let drawEllipse (img: Image<'TColor, 'TDepth>) (e: Ellipse) (color: 'TColor) (alpha: float) =
if alpha >= 1.0
then
- img.Draw(Emgu.CV.Structure.Ellipse(PointF(float32 e.Cx, float32 e.Cy), SizeF(2.f * e.B, 2.f * e.A), e.Alpha / PI * 180.f), color, 1, CvEnum.LineType.AntiAlias)
+ img.Draw(Emgu.CV.Structure.Ellipse(PointF(e.Cx, e.Cy), SizeF(2.f * e.B, 2.f * e.A), e.Alpha / PI * 180.f), color, 1, CvEnum.LineType.AntiAlias)
else
let windowPosX = e.Cx - e.A - 5.f
let gapX = windowPosX - (float32 (int windowPosX))
if roi = img.ROI // We do not display ellipses touching the edges (FIXME)
then
use i = new Image<'TColor, 'TDepth>(img.ROI.Size)
- i.Draw(Emgu.CV.Structure.Ellipse(PointF(float32 <| (e.A + 5.f + gapX) , float32 <| (e.A + 5.f + gapY)), SizeF(2.f * e.B, 2.f * e.A), e.Alpha / PI * 180.f), color, 1, CvEnum.LineType.AntiAlias)
+ i.Draw(Emgu.CV.Structure.Ellipse(PointF(e.A + 5.f + gapX, e.A + 5.f + gapY), SizeF(2.f * e.B, 2.f * e.A), e.Alpha / PI * 180.f), color, 1, CvEnum.LineType.AntiAlias)
CvInvoke.AddWeighted(img, 1.0, i, alpha, 0.0, img)
img.ROI <- Rectangle.Empty
logWithName "Starting analysis ..."
- use green = img.Item(1)
+ use green = img.[1]
let greenFloat = green.Convert<Gray, float32>()
let filteredGreen = gaussianFilter greenFloat config.LPFStandardDeviation
open Types
open Utils
+// All ellipses with a score below this are removed.
+let matchingScoreThreshold = 0.4f
+let matchingScorePower = 20.f
+let windowSizeRadiusFactor = 1.f / 2.f
+let minimumDistanceFromCenterRadiusFactor = 1.f / 3.f
+
type private EllipseScoreFlaggedKd (matchingScore: float32, e: Ellipse) =
let mutable matchingScore = matchingScore
type MatchingEllipses (radius: float32) =
let ellipses = List<EllipseScoreFlaggedKd>()
- // All ellipses with a score below this are removed.
- let matchingScoreThreshold = 0.4f
-
member this.Add (e: Ellipse) =
ellipses.Add(EllipseScoreFlaggedKd(0.f, e))
let tree = KdTree.Tree.BuildTree (List.ofSeq ellipses)
// 2) Compute the matching score of each ellipses.
- let windowSize = radius / 3.f
+ let windowSize = radius * windowSizeRadiusFactor
for e in ellipses do
e.Processed <- true
let areaE = e.Ellipse.Area
| Some (overlapArea, _, _)
// Because of approximation error, see https://github.com/chraibi/EEOver/issues/4
when overlapArea - areaE < 1.f && overlapArea - areaOther < 1.f ->
- let matchingScore = (2.f * overlapArea / (areaE + areaOther)) ** 30.f
+ let matchingScore = (2.f * overlapArea / (areaE + areaOther)) ** matchingScorePower
other.AddMatchingScore(matchingScore)
e.AddMatchingScore(matchingScore)
| _ -> ()
if not other.Removed && e.MatchingScore > other.MatchingScore
then
// Case where ellipses are too close.
- if distanceTwoPoints (PointF(e.Ellipse.Cx, e.Ellipse.Cy)) (PointF(other.Ellipse.Cx, other.Ellipse.Cy)) < 0.3f * e.Ellipse.B
+ if distanceTwoPoints (PointF(e.Ellipse.Cx, e.Ellipse.Cy)) (PointF(other.Ellipse.Cx, other.Ellipse.Cy)) < minimumDistanceFromCenterRadiusFactor * e.Ellipse.B
then
other.Removed <- true
else
--- /dev/null
+module ParasitemiaUI.Export
+
+open System
+open System.IO
+
+open State
+
+/// <exception cref="System.IOException">If the results cannot be exported</exception>
+let exportResults (state: State) (filePath: string) =
+ use writer = new StreamWriter(new FileStream(filePath, FileMode.Create, FileAccess.Write))
+ fprintfn writer "File: %s" state.FilePath
+ fprintfn writer "Export date: %A" DateTime.Now
+
+ fprintfn writer ""
+ fprintfn writer "Patient ID: %s" state.PatientID
+ fprintfn writer "Global parasitemia: %s" (Utils.percentText state.GlobalParasitemia)
+
+ for srcImg in state.SourceImages do
+ fprintfn writer ""
+ fprintfn writer "Image name: %s" srcImg.name
+ fprintfn writer "Parasitemia: %s" (Utils.percentText (state.ImageParasitemia srcImg))
+ fprintfn writer "Added infected erythrocyte: %s %s" (state.ImageNbManuallyChangedRBCStr srcImg true) (state.ImageManuallyChangedRBCStr srcImg true)
+ fprintfn writer "Removed infected erythrocyte: %s %s" (state.ImageNbManuallyChangedRBCStr srcImg false) (state.ImageManuallyChangedRBCStr srcImg false)
+ ()
\ No newline at end of file
let state = State.State()
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 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 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"
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 percentText (nbTotal: int, nb: int) : string =
- if nbTotal = 0
- then
- ""
- else
- let percent = 100. * (float nb) / (float nbTotal)
- sprintf "%.1f %% (%d / %d)" percent nb 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 = percentText (state.ImageParasitemia srcImg)
+ 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()))
- let alteredStr = percentText (state.ImageNbAltered srcImg)
- txtImageInformation2.Inlines.Add(Documents.Run("Number of erytrocytes manually altered: ", FontWeight = FontWeights.Bold))
- txtImageInformation2.Inlines.Add(Documents.Run(alteredStr))
+ // 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)))
- txtImageInformation2.Inlines.Add(Documents.Run("Average erytrocyte diameter: ", FontWeight = FontWeights.Bold))
- txtImageInformation2.Inlines.Add(Documents.Run(srcImg.config.RBCRadius.ToString()))
- | _ -> ()
+ | _ ->
+ gridImageInformation.Visibility <- Visibility.Hidden
let updateGlobalParasitemia () =
- txtGlobalParasitemia.Text <- percentText 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
state.Reset()
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())
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)
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()
<Resource Include="Resources\chuv_logo.png" />
<Resource Include="Resources\hes-so_logo.png" />
<Resource Include="Resources\icon.ico" />
- <Resource Include="XAML\NumericUpDown.xaml" />
- <Compile Include="XAML\NumericUpDown.xaml.fs" />
<Resource Include="XAML\ImageSourcePreview.xaml" />
<Compile Include="XAML\ImageSourcePreview.xaml.fs" />
<Resource Include="XAML\ImageSourceSelection.xaml" />
<Resource Include="XAML\MainWindow.xaml" />
<Compile Include="XAML\MainWindow.xaml.fs" />
<Compile Include="Types.fs" />
+ <Compile Include="Utils.fs" />
<Compile Include="PiaZ.fs" />
<Compile Include="State.fs" />
+ <Compile Include="Export.fs" />
<Compile Include="About.fs" />
<Compile Include="Analysis.fs" />
<Compile Include="GUI.fs" />
// Information associated to each images.
type JSONSourceImage = {
num: int
+ name: string
+
RBCRadius: float32 // The RBC Radius found by granulometry.
parameters: ParasitemiaCore.Config.Parameters
dateLastAnalysis: DateTime
let mainEntryName = "info.json"
let imageExtension = ".tiff"
-let currentFileVersion = 1
+let currentFileVersion = 2
/// <summary>
/// Save a document in a give file path. The file may already exist.
imgJSONFileWriter.Write(
JsonConvert.SerializeObject(
{ num = srcImg.num
+ name = srcImg.name
RBCRadius = srcImg.config.RBCRadius.Pixel
parameters = srcImg.config.Parameters
dateLastAnalysis = srcImg.dateLastAnalysis
use bitmap = new System.Drawing.Bitmap(imgEntry.Open(), false)
let img = new Image<Bgr, byte>(bitmap)
imgNum <- imgNum + 1
- let imgEntry = file.GetEntry(imgEntry.Name + ".json")
- use imgEntryFileReader = new StreamReader(imgEntry.Open())
- let imgInfo = JsonConvert.DeserializeObject<JSONSourceImage>(imgEntryFileReader.ReadToEnd())
+ let imgJSONEntry = file.GetEntry(imgEntry.Name + ".json")
+ use imgJSONFileReader = new StreamReader(imgJSONEntry.Open())
+ let imgInfo = JsonConvert.DeserializeObject<JSONSourceImage>(imgJSONFileReader.ReadToEnd())
let config = ParasitemiaCore.Config.Config(imgInfo.parameters)
config.SetRBCRadius imgInfo.RBCRadius
yield { num = imgNum
+ name = imgInfo.name
config = config
dateLastAnalysis = imgInfo.dateLastAnalysis
img = img
match ParasitemiaCore.Analysis.doMultipleAnalysis images None with
| Some results ->
for id, cells in results do
- let config = images |> List.pick (fun (id', config', _) -> if id' = id then Some config' else None)
+ let config, img = images |> List.pick (fun (id', config', img') -> if id' = id then Some (config', img') else None)
+ img.Dispose()
let total, infected = countCells cells
fprintf resultFile "File: %s %d %d %.2f (diameter: %A)\n" id total infected (100. * (float infected) / (float total)) config.RBCRadius
| None ->
List.length srcImg.rbcs,
srcImg.rbcs |> List.fold (fun nbInfected rbc -> if rbc.infected then nbInfected + 1 else nbInfected) 0
- member this.ImageNbAltered (srcImg: SourceImage) : int * int =
+ member this.ImageNbManuallyChangedRBC (srcImg: SourceImage) (setAsInfected: bool) : int * int =
List.length srcImg.rbcs,
- srcImg.rbcs |> List.fold (fun nbAltered rbc -> if rbc.setManually then nbAltered + 1 else nbAltered) 0
+ srcImg.rbcs |> List.fold (fun nb rbc -> if rbc.setManually && rbc.infected = setAsInfected then nb + 1 else nb) 0
+
+ member this.ImageNbManuallyChangedRBCStr (srcImg: SourceImage) (setAsInfected: bool) : string =
+ Utils.percentText (this.ImageNbManuallyChangedRBC srcImg setAsInfected)
+
+ member this.ImageManuallyChangedRBC (srcImg: SourceImage) (setAsInfected: bool) : int seq =
+ query {
+ for rbc in srcImg.rbcs do
+ where (rbc.setManually && rbc.infected = setAsInfected)
+ select rbc.num }
+
+ member this.ImageManuallyChangedRBCStr (srcImg: SourceImage) (setAsInfected: bool) : string =
+ let listStr = Utils.listAsStr <| this.ImageManuallyChangedRBC srcImg setAsInfected
+ if listStr = ""
+ then ""
+ else "[" + listStr + "]"
member this.GlobalParasitemia : int * int =
sourceImages
member this.AddSourceImage (filePath: string) (defaultConfig: ParasitemiaCore.Config.Config) : SourceImage =
let srcImg =
{ num = sourceImages.Count + 1
+ name = System.IO.FileInfo(filePath).Name
config = defaultConfig.Copy()
dateLastAnalysis = DateTime(0L)
rbcs = []
// Re-numbered the images.
sourceImages |> Seq.iteri (fun i srcImg -> srcImg.num <- i + 1)
+ member this.SetName (srcImg: SourceImage) (name: string) =
+ if name <> srcImg.name
+ then
+ srcImg.name <- name
+ alteredSinceLastSave <- true
+
member this.SetResult (imgNum: int) (cells: ParasitemiaCore.Types.Cell list) =
let sourceImage = sourceImages.Find(fun srcImg -> srcImg.num = imgNum)
type SourceImage = {
mutable num: int
+ mutable name: string
+
mutable config: ParasitemiaCore.Config.Config
mutable dateLastAnalysis: DateTime // UTC.
img: Image<Bgr, byte>
--- /dev/null
+module ParasitemiaUI.Utils
+
+let listAsStr (s: 'a seq) =
+ s |> Seq.fold (fun acc obj -> acc + (if acc = "" then "" else ", ") + obj.ToString()) ""
+
+let percentText (nbTotal: int, nb: int) : string =
+ if nbTotal = 0
+ then
+ ""
+ else
+ let percent = 100. * (float nb) / (float nbTotal)
+ sprintf "%.1f %% (%d / %d)" percent nb nbTotal
\ No newline at end of file
<MenuItem x:Name="menuSave" Header="_Save" InputGestureText="Ctrl+S" />
<MenuItem x:Name="menuSaveAs" Header="Save _As..." InputGestureText="Ctrl+Shift+S" />
<Separator />
+ <MenuItem x:Name="menuExportResults" Header="E_xport Results As Text..." InputGestureText="Ctrl+E" />
+ <Separator />
<MenuItem x:Name="menuExit" Header="_Exit" />
</MenuItem>
<MenuItem Header="_Images">
- <MenuItem x:Name="menuAddSourceImage" Header="_Add a source image" />
+ <MenuItem x:Name="menuAddSourceImage" Header="_Add a Source Image" />
</MenuItem>
<MenuItem x:Name="menuAnalysis" Header="_Analysis">
- <MenuItem x:Name="menuStartAnalysis" Header="_Show analysis window" />
+ <MenuItem x:Name="menuStartAnalysis" Header="_Show Analysis Window" />
</MenuItem>
<MenuItem x:Name="menuView" Header="_View">
- <MenuItem x:Name="menuHightlightRBC" Header="_Highlight all erytrocytes" IsCheckable="True" />
+ <MenuItem x:Name="menuHightlightRBC" Header="_Highlight All Erytrocytes" IsCheckable="True" />
</MenuItem>
<MenuItem x:Name="menuHelp" Header="_Help">
<MenuItem x:Name="menuAbout" Header="_About" />
<Label x:Name="lblPatient" Margin="10,0,3,0 " Content="Patient ID" Grid.ColumnSpan="2"/>
<Label x:Name="lblGlobalParasitemia" Margin="10,0,3,0" Content="Global parasitemia" Grid.Row="1" Grid.ColumnSpan="2" />
<TextBox x:Name="txtPatient" Grid.Column="2" Margin="3,4,10,4" TextWrapping="Wrap" VerticalAlignment="Center" />
- <TextBox x:Name="txtGlobalParasitemia" Grid.Column="2" Grid.Row="1" Margin="3,4,10,4" TextWrapping="Wrap" VerticalAlignment="Center" IsReadOnly="True" />
+ <Border BorderThickness="1" VerticalAlignment="Center" Margin="3,4,10,4" BorderBrush="{DynamicResource {x:Static SystemColors.ControlDarkBrushKey}}" Grid.Column="2" Grid.Row="1" >
+ <TextBlock x:Name="txtGlobalParasitemia" Margin="1" TextWrapping="Wrap" />
+ </Border>
</Grid>
<Border BorderBrush="Black" BorderThickness="0" Margin="3" Grid.Row="1" >
<ScrollViewer x:Name="scrollPreviews" VerticalScrollBarVisibility="Auto" >
<ScrollViewer x:Name="scrollRBC" VerticalScrollBarVisibility="Hidden" HorizontalScrollBarVisibility="Visible" Grid.RowSpan="1" Margin="3">
<StackPanel x:Name="stackRBC" Orientation="Horizontal" />
</ScrollViewer>
- <Grid Grid.Row="2">
+ <Grid x:Name="gridImageInformation" Grid.Row="2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="1*"/>
</Grid.ColumnDefinitions>
- <TextBlock x:Name="txtImageInformation1" TextWrapping="Wrap" Margin="3" Grid.Column="0" />
- <TextBlock x:Name="txtImageInformation2" TextWrapping="Wrap" Margin="3" Grid.Column="1" />
+ <Grid.RowDefinitions>
+ <RowDefinition Height="Auto" />
+ <RowDefinition Height="1*" />
+ </Grid.RowDefinitions>
+ <Grid Grid.ColumnSpan="2" Grid.Row="0" Margin="0,3,3,0">
+ <Grid.ColumnDefinitions>
+ <ColumnDefinition Width="Auto"/>
+ <ColumnDefinition Width="1*"/>
+ </Grid.ColumnDefinitions>
+ <Label Content="Image name" Grid.Column="0"></Label>
+ <TextBox x:Name="txtImageName" Grid.Column="1" VerticalAlignment="Center"> </TextBox>
+ </Grid>
+ <TextBlock x:Name="txtImageInformation1" TextWrapping="Wrap" Margin="3" Grid.Column="0" Grid.Row="1" />
+ <TextBlock x:Name="txtImageInformation2" TextWrapping="Wrap" Margin="3" Grid.Column="1" Grid.Row="1" />
</Grid>
</Grid>
</Grid>