* Other improvements.
| DebugOn of string // Output directory.
type Parameters = {
- initialAreaOpen: int
+ initialAreaOpen: int // Area of the first initial opening to remove the central illumination of RBC.
+ ratioSecondAreaOpen: float32 // The area of the second opening is 'ratioSecondAreaOpen' * mean RBC area. It's applied only if greater than 'initialAreaOpen'.
- minRbcRadius: float32
- maxRbcRadius: float32
+ minRbcRadius: float32 // Factor of the mean RBC radius.
+ maxRbcRadius: float32 // Factor of the mean RBC radius.
- preFilterSigma: float
+ preFilterSigma: float // To remove the high frequency noise.
// Ellipse.
- factorNbPick: float
+ factorNbPick: float // The number of computed ellipse per edge pixel.
// Parasites detection.
darkStainLevel: float // Lower -> more sensitive. Careful about illumination on the borders.
- maxDarkStainRatio: float
+ maxDarkStainRatio: float // When a cell must own less than this ratio to be a RBC.
stainArea: float32 // Factor of a RBC area. 0.5 means the half of RBC area.
stainLevel: float // > 1
- maxStainRatio: float // [0, 1]
+ maxStainRatio: float // When a cell must own less than this ratio to be a RBC.
infectionArea: float32 // Factor of a RBC area. 0.5 means the half of RBC area.
infectionLevel: float // > 1
standardDeviationMaxRatio: float // The standard deviation of the pixel values of a cell can't be greater than standardDeviationMaxRatio * global standard deviation
- minimumCellArea: float32 // Factor of the nominal RBC area.
+ minimumCellAreaFactor: float32 // Factor of the mean RBC area.
}
type Config (param: Parameters) =
member this.Parameters = param
member val Debug = DebugOff with get, set
+
+ // Mean RBC radius.
member val RBCRadius = 30.f with get, set
member this.RBCMinRadius = this.RBCRadius + param.minRbcRadius * this.RBCRadius
member this.RBCMaxRadius = this.RBCRadius + param.maxRbcRadius * this.RBCRadius
member this.RBCArea = PI * this.RBCRadius ** 2.f
- member this.RBCMinArea = param.minimumCellArea * this.RBCArea
+ member this.RBCMinArea = param.minimumCellAreaFactor * this.RBCArea
member this.InfectionArea = param.infectionArea * this.RBCArea
member this.StainArea = param.stainArea * this.RBCArea
--- /dev/null
+module Parasitemia.GUI.Analysis
+
+open System
+open System.Windows
+open System.Windows.Media
+open System.Windows.Markup
+open System.Windows.Shapes
+open System.Windows.Controls
+
+open Config
+
+let showWindow (parent: Window) (state: State.State) (defaultConfig: Config) : bool =
+ let window = Views.AnalysisWindow()
+ window.Root.Owner <- parent
+ window.Root.Left <- parent.Left + parent.ActualWidth / 2. - window.Root.Width / 2.
+ window.Root.Top <- parent.Top + parent.ActualHeight / 2. - window.Root.Height / 2.
+
+ let ctrl (name: string): 'a =
+ window.Root.FindName(name) :?> 'a
+
+ let butClose: Button = ctrl "butClose"
+ let butStart: Button = ctrl "butStart"
+
+ let progressBar: ProgressBar = ctrl "progress"
+ let textLog: TextBlock = ctrl "textLog"
+ let scrollLog: ScrollViewer = ctrl "scrollLog"
+
+ Utils.log <- (fun mess -> window.Root.Dispatcher.Invoke(fun () ->
+ textLog.Inlines.Add(Documents.Run(mess))
+ textLog.Inlines.Add(Documents.LineBreak())
+ scrollLog.ScrollToBottom()))
+
+ let monitor = Object()
+ let mutable analysisPerformed = false
+ let mutable analysisCancelled = false
+
+ butClose.Click.AddHandler(fun obj args -> window.Root.Close())
+
+ butStart.Click.AddHandler(fun obj args ->
+ butStart.IsEnabled <- false
+ butClose.Content <- "Abort"
+ async {
+ let results =
+ ImageAnalysis.doMultipleAnalysis
+ (state.SourceImages |> Seq.map (fun srcImg -> string srcImg.num, srcImg.img) |> Seq.toList)
+ defaultConfig
+ (Some (fun progress -> window.Root.Dispatcher.Invoke(fun () -> progressBar.Value <- float progress)))
+
+ lock monitor (
+ fun() ->
+ if not analysisCancelled
+ then
+ for id, rbcRadius, cells in results do
+ state.SetResult (int id) rbcRadius cells
+
+ window.Root.Dispatcher.Invoke(fun () ->
+ butStart.IsEnabled <- false
+ butClose.Content <- "Close")
+
+ Utils.log "Analysis terminated successfully"
+ analysisPerformed <- true)
+ } |> Async.Start)
+
+ window.Root.ShowDialog() |> ignore
+
+ lock monitor (fun () ->
+ if not analysisPerformed
+ then
+ analysisCancelled <- true
+ analysisPerformed)
+
+
+ (*let results = ImageAnalysis.doMultipleAnalysis (state.SourceImages |> Seq.map (fun srcImg -> string srcImg.num, srcImg.img) |> Seq.toList) defaultConfig
+ for id, rbcRadius, cells in results do
+ state.SetResult (int id) rbcRadius cells*)
\ No newline at end of file
--- /dev/null
+<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="AnalysisWindow" Height="350" Width="500" MinHeight="100" MinWidth="100" Title="Analysis">
+ <Grid>
+ <Grid.RowDefinitions>
+ <RowDefinition Height="100"/>
+ <RowDefinition Height="30"/>
+ <RowDefinition/>
+ <RowDefinition Height="Auto"/>
+ </Grid.RowDefinitions>
+ <ScrollViewer x:Name="scrollImagesSourceSelection" VerticalScrollBarVisibility="Hidden" HorizontalScrollBarVisibility="Visible" Grid.Row="0" Margin="3" >
+ <StackPanel x:Name="stackImagesSourceSelection" Orientation="Horizontal" />
+ </ScrollViewer>
+ <ProgressBar x:Name="progress" Grid.Row="1" Margin="3" Minimum="0" Maximum="100" />
+ <ScrollViewer x:Name="scrollLog" Grid.Row="2" Margin="3" HorizontalScrollBarVisibility="Auto">
+ <TextBlock x:Name="textLog" />
+ </ScrollViewer>
+ <Grid Grid.Row="3">
+ <Grid.ColumnDefinitions>
+ <ColumnDefinition/>
+ <ColumnDefinition/>
+ </Grid.ColumnDefinitions>
+ <Button x:Name="butStart" Content="Start analysis" Margin="3" Grid.Column="0"/>
+ <Button x:Name="butClose" Content="Close" Margin="3" Grid.Column="1"/>
+ </Grid>
+ </Grid>
+</Window>
\ No newline at end of file
--- /dev/null
+namespace Parasitemia.GUI.Views
+
+open FsXaml
+
+type AnalysisWindow = XAML<"GUI/AnalysisWindow.xaml">
+
module Parasitemia.GUI.Main
+open System
open System.IO
open System.Linq
open System.Windows
let ctrl (name: string): 'a =
mainWindow.Root.FindName(name) :?> 'a
- // Utils.log <- (fun m -> log mainWindow m)
-
let colorRBCHealthy = Brushes.YellowGreen
let colorRBCInfected = Brushes.Red
let state = State.State()
let mutable currentScale = 1.
+ let mutable displayHealthy = false
- let exit: MenuItem = ctrl "menuExit"
- let saveFile: MenuItem = ctrl "menuSave"
- let loadFile: MenuItem = ctrl "menuOpen"
- let newFile: MenuItem = ctrl "menuNew"
+ let menuExit: MenuItem = ctrl "menuExit"
+ let menuSaveFile: MenuItem = ctrl "menuSave"
+ let menuLoadFile: MenuItem = ctrl "menuOpen"
+ let menuNewFile: MenuItem = ctrl "menuNew"
+ let menuAddSourceImage: MenuItem = ctrl "menuAddSourceImage"
+ let menuStartAnalysis: MenuItem = ctrl "menuStartAnalysis"
+ let menuHightlightRBC: MenuItem = ctrl "menuHightlightRBC"
- let addSourceImage: MenuItem = ctrl "menuAddSourceImage"
let txtPatient: TextBox = ctrl "txtPatient"
let txtGlobalParasitemia: TextBox = ctrl "txtGlobalParasitemia"
- let butStartAnalysis: Button = ctrl "butStartAnalysis"
-
let stackPreviews: StackPanel = ctrl "stackPreviews"
let scrollViewCurrentImage: ScrollViewer = ctrl "scrollViewCurrentImage"
let stackRBC: StackPanel = ctrl "stackRBC"
// Initializations.
+ menuHightlightRBC.IsChecked <- displayHealthy
// 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 x = rbc.center.X - rbcWidth / 2. |> Utils.roundInt
+ let y = rbc.center.Y - rbcHeight / 2. |> Utils.roundInt
+ let w = Utils.roundInt rbcWidth
+ let h = Utils.roundInt rbcHeight
+ //Utils.dprintfn "w: %A, h: %A, cx: %A, cy: %A, img.w: %A, img.h: %A" w h x y img.Width img.Height
+ img.GetSubRect(System.Drawing.Rectangle(System.Drawing.Point((if x < 0 then 0 else x), (if y < 0 then 0 else y)),
+ 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) =
- frame.Opacity <- if rbc.setManually || rbc.infected then 1. else 0.
+ frame.Opacity <- if displayHealthy || 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.Tag <- rbc
setRBCFrameStyle rbc frame
frame.border.StrokeThickness <- 1.
- frame.lblRBCNumber.Content <- rbc.num
+ frame.txtRBCNumber.Text <- rbc.num.ToString()
frame
let highlightRBCFrame (frame: Views.RBCFrame) (highlight: bool) =
if highlight
then
frame.border.StrokeThickness <- 3.
- if not rbc.infected && not rbc.setManually then frame.Opacity <- 1.
+ if not rbc.infected && not rbc.setManually && not displayHealthy then frame.Opacity <- 1.
else
frame.border.StrokeThickness <- 1.
- if not rbc.infected && not rbc.setManually then frame.Opacity <- 0.
+ if not rbc.infected && not rbc.setManually && not displayHealthy then frame.Opacity <- 0.
let zoomToRBC (rbc: RBC) =
scrollViewCurrentImage.ScrollToHorizontalOffset(rbc.center.X * currentScale - scrollViewCurrentImage.ViewportWidth / 2. + borderCurrentImage.BorderThickness.Left)
""
else
let percent = 100. * (float nbInfected) / (float nbTotal)
- sprintf "%.2f %% (%d / %d)" percent nbInfected nbTotal
+ sprintf "%.1f %% (%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
+ 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(string (Utils.roundInt <| 2. * srcImg.rbcRadius)))
+ txtImageInformation.Inlines.Add(Documents.Run(" px"))
+ 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()))
| _ -> ()
let updateGlobalParasitemia () =
txtGlobalParasitemia.Text <- parasitemiaText state.GlobalParasitemia
let rec setAsInfected (rbc: RBC) (infected: bool) =
- rbc.SetAsInfected infected
+ state.SetAsInfected rbc infected
canvasCurrentImage.Children
|> Seq.cast<Views.RBCFrame>
|> Seq.iter
match state.CurrentImage with
| Some srcImg ->
let mutable currentPreview = 0
- for rbc in srcImg.rbcs |> List.filter (fun rbc -> rbc.infected) do
+ for rbc in srcImg.rbcs |> List.filter (fun rbc -> displayHealthy || 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))
+ f.MouseLeftButtonUp.AddHandler(fun obj args -> zoomToRBC (f.Tag :?> RBC))
stackRBC.Children.Add(f) |> ignore
f
canvasCurrentImage.Children.[i].Visibility <- Visibility.Hidden
| _ -> ()
- // Operations.
- let synchronizeState () =
- state.PatientID <- txtPatient.Text
-
- let setCurrentImage (srcImg: SourceImage) =
- state.CurrentImage <- Some srcImg
-
- // Highlight the preview.
- stackPreviews.Children
- |> Seq.cast<Views.ImageSourcePreview>
- |> Seq.iter (fun preview -> preview.border.BorderThickness <- Thickness(if preview.lblImageNumber.Content = box srcImg.num then 3. else 0.))
+ let saveCurrentDocument () =
+ 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
+ state.Save()
+ else
+ state.Save()
- canvasCurrentImage.Height <- float srcImg.img.Height
- canvasCurrentImage.Width <- float srcImg.img.Width
- canvasCurrentImage.Background <- ImageBrush(BitmapSourceConvert.ToBitmapSource(srcImg.img))
+ // Ask the use to save the current document if neccessary.
+ let askSaveCurrent () =
+ if state.AlteredSinceLastSave
+ then
+ match MessageBox.Show("Would you like to save the current document?", "Saving the current document", MessageBoxButton.YesNo, MessageBoxImage.Question) with
+ | MessageBoxResult.Yes -> saveCurrentDocument ()
+ | _ -> ()
- updateRBCFramesCurrent ()
- updateRBCFramesPreview ()
- updateCurrentImageInformation ()
+ let updateCurrentImage () =
+ match state.CurrentImage with
+ | Some srcImg ->
+ // Highlight the preview.
+ stackPreviews.Children
+ |> Seq.cast<Views.ImageSourcePreview>
+ |> Seq.iter (fun preview -> preview.border.BorderThickness <- Thickness(if preview.Tag = (srcImg :> Object) then 3. else 0.))
+
+ canvasCurrentImage.Height <- float srcImg.img.Height
+ canvasCurrentImage.Width <- float srcImg.img.Width
+ canvasCurrentImage.Background <- ImageBrush(BitmapSourceConvert.ToBitmapSource(srcImg.img))
+
+ updateRBCFramesCurrent ()
+ updateRBCFramesPreview ()
+ updateCurrentImageInformation ()
+ | None ->
+ stackRBC.Children.Clear()
+ canvasCurrentImage.Children.Clear()
+ canvasCurrentImage.Background <- Brushes.Black
+ let setCurrentImage (srcImg: SourceImage) =
+ if state.CurrentImage.IsNone || state.CurrentImage.Value <> srcImg
+ then
+ state.CurrentImage <- Some srcImg
+ updateCurrentImage ()
+
+ let updateViewportPreview () =
+ for preview in stackPreviews.Children |> Seq.cast<Views.ImageSourcePreview> do
+ let srcImg = preview.Tag :?> SourceImage
+ if Some srcImg = state.CurrentImage then
+ preview.viewport.Visibility <- Visibility.Visible
+
+ let canvasWidth = canvasCurrentImage.ActualWidth * currentScale
+ let canvasHeight = canvasCurrentImage.ActualHeight * currentScale
+ let previewWidth = (preview.ActualWidth - preview.BorderThickness.Left - preview.BorderThickness.Right)
+ let previewHeight = (preview.ActualHeight - preview.BorderThickness.Top - preview.BorderThickness.Bottom)
+
+ let marginLeft = previewWidth * (scrollViewCurrentImage.HorizontalOffset - borderCurrentImage.BorderThickness.Left) / canvasWidth - 2.
+ let marginRight = previewWidth * (canvasWidth - (scrollViewCurrentImage.HorizontalOffset - borderCurrentImage.BorderThickness.Right) - scrollViewCurrentImage.ViewportWidth) / canvasWidth - 2.
+ let marginTop = previewHeight * (scrollViewCurrentImage.VerticalOffset - borderCurrentImage.BorderThickness.Top) / canvasHeight - 2.
+ let marginBottom = previewHeight * (canvasHeight - (scrollViewCurrentImage.VerticalOffset - borderCurrentImage.BorderThickness.Bottom) - scrollViewCurrentImage.ViewportHeight) / canvasHeight - 2.
+
+ preview.viewport.Margin <-
+ Thickness(
+ marginLeft,
+ marginTop,
+ marginRight,
+ marginBottom)
+ else
+ preview.viewport.Visibility <- Visibility.Hidden
let addPreview (srcImg: SourceImage) =
let imgCtrl = Views.ImageSourcePreview(Margin = Thickness(3.))
- imgCtrl.lblImageNumber.Content <- srcImg.num
+
+ imgCtrl.menuRemoveImage.Click.AddHandler(fun obj args ->
+ stackPreviews.Children.Remove(imgCtrl)
+ let srcImg = imgCtrl.Tag :?> SourceImage
+ let currentRemoved = Some srcImg = state.CurrentImage
+ state.RemoveSourceImage srcImg
+ if currentRemoved
+ then
+ updateCurrentImage()
+ stackPreviews.Children |> Seq.cast<Views.ImageSourcePreview> |> Seq.iter (fun imgPreview -> imgPreview.txtImageNumber.Text <- (imgPreview.Tag :?> SourceImage).num.ToString()))
+
+ imgCtrl.Tag <- srcImg
+ imgCtrl.txtImageNumber.Text <- srcImg.num.ToString()
let width = 200
let height = srcImg.img.Height * width / srcImg.img.Width
imgCtrl.imagePreview.Source <- BitmapSourceConvert.ToBitmapSource(srcImg.img.Resize(width, height, Emgu.CV.CvEnum.Inter.Cubic))
stackPreviews.Children.Add(imgCtrl) |> ignore
- imgCtrl.MouseLeftButtonUp.AddHandler(fun obj args -> setCurrentImage (state.SourceImages |> Seq.find (fun i -> box i.num = (obj :?> Views.ImageSourcePreview).lblImageNumber.Content)))
+ imgCtrl.MouseLeftButtonUp.AddHandler(fun obj args -> setCurrentImage (state.SourceImages |> Seq.find (fun srcImg -> (srcImg :> Object) = imgCtrl.Tag)))
let updatePreviews () =
stackPreviews.Children.Clear ()
- if state.SourceImages.Count() > 0
- then
- for srcImg in state.SourceImages do
- addPreview srcImg
- match state.CurrentImage with
- | Some srcImg -> setCurrentImage srcImg
- | _ -> ()
+ for srcImg in state.SourceImages do
+ addPreview srcImg
+ updateCurrentImage ()
let updateGUI () =
txtPatient.Text <- state.PatientID
updatePreviews ()
updateGlobalParasitemia ()
- exit.Click.AddHandler(fun obj args -> mainWindow.Root.Close())
- saveFile.Click.AddHandler(fun obj args ->
- synchronizeState ()
- 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
- state.Save()
- else
- state.Save())
+ txtPatient.TextChanged.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 ())
- loadFile.Click.AddHandler(fun obj args ->
+ menuLoadFile.Click.AddHandler(fun obj args ->
// TODO: if current state not saved and not empty, ask to save it.
let dialog = OpenFileDialog(Filter = PiaZ.filter)
let res = dialog.ShowDialog()
if res.HasValue && res.Value
then
+ askSaveCurrent ()
state.FilePath <- dialog.FileName
state.Load()
updateGUI ())
- newFile.Click.AddHandler(fun obj args ->
- // TODO: if current state not saved and not empty, ask to save it.
+ menuNewFile.Click.AddHandler(fun obj args ->
+ askSaveCurrent ()
state.Reset()
updateGUI())
- addSourceImage.Click.AddHandler(fun obj args ->
+ menuAddSourceImage.Click.AddHandler(fun obj args ->
let dialog = OpenFileDialog(Filter = "Image Files|*.png;*.jpg;*.tif;*.tiff")
let res = dialog.ShowDialog()
if res.HasValue && res.Value
updateGlobalParasitemia ()
if state.SourceImages.Count() = 1
then
- setCurrentImage srcImg)
+ updateCurrentImage ())
- butStartAnalysis.Click.AddHandler(fun obj args ->
- 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 ()
+ menuStartAnalysis.Click.AddHandler(fun obj args ->
+ if Analysis.showWindow mainWindow.Root state defaultConfig
+ then
+ updateGlobalParasitemia ()
+ updateCurrentImage ())
- // Refresh current image.
- match state.CurrentImage with
- | Some srcImg -> setCurrentImage srcImg
- | _ -> ())
+ menuHightlightRBC.Click.AddHandler(fun obj args ->
+ displayHealthy <- menuHightlightRBC.IsChecked
+ updateRBCFramesPreview ()
+ updateRBCFramesCurrent ())
// Zoom on the current image.
let adjustCurrentImageBorders (deltaX: float) (deltaY: float) =
let centerX = scrollViewCurrentImage.HorizontalOffset + scrollViewCurrentImage.ViewportWidth / 2. - borderCurrentImage.BorderThickness.Left
let centerY = scrollViewCurrentImage.VerticalOffset + scrollViewCurrentImage.ViewportHeight / 2. - borderCurrentImage.BorderThickness.Top
- //canvasCurrentImage.Margin <- Thickness(canvasCurrentImage.Margin.Top * realScaleFactor)
currentImageScaleTransform.ScaleX <- currentScale
currentImageScaleTransform.ScaleY <- currentScale
scrollViewCurrentImage.ScrollToHorizontalOffset(centerX * realScaleFactor - scrollViewCurrentImage.ViewportWidth / 2. + borderCurrentImage.BorderThickness.Left)
scrollViewCurrentImage.ScrollToVerticalOffset(centerY * realScaleFactor - scrollViewCurrentImage.ViewportHeight / 2. + borderCurrentImage.BorderThickness.Top)
+
args.Handled <- true)
// Pan on the current image.
let deltaY = scrollStartPosition.Y - position.Y
scrollViewCurrentImage.ScrollToHorizontalOffset(deltaX + scrollStartOffsetX)
scrollViewCurrentImage.ScrollToVerticalOffset(deltaY + scrollStartOffsetY)
+
args.Handled <- true)
borderCurrentImage.PreviewMouseLeftButtonUp.AddHandler(fun obj args ->
borderCurrentImage.ReleaseMouseCapture()
args.Handled <- true)
- // display mainWindow img
+ // Viewport preview.
+ scrollViewCurrentImage.ScrollChanged.AddHandler(fun obj args -> updateViewportPreview ())
+
mainWindow.Root.Show()
app.Run()
\ No newline at end of file
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="119.223" d:DesignHeight="84.911"
>
- <Border x:Name="border" BorderBrush="{DynamicResource {x:Static SystemColors.HighlightBrushKey}}">
- <Grid>
+ <Border x:Name="border" ClipToBounds="True" BorderBrush="{DynamicResource {x:Static SystemColors.HighlightBrushKey}}">
+ <Grid x:Name="grid">
+ <Grid.ContextMenu>
+ <ContextMenu>
+ <MenuItem x:Name="menuRemoveImage" Header="_Remove image" />
+ </ContextMenu>
+ </Grid.ContextMenu>
<Image x:Name="imagePreview" />
- <Label x:Name="lblImageNumber" HorizontalAlignment="Right" VerticalAlignment="Bottom" Background="#4C000000" Foreground="White"/>
+ <TextBlock x:Name="txtImageNumber" Padding="2" Text="42" HorizontalAlignment="Right" VerticalAlignment="Bottom" Background="#4C000000" Foreground="White" Margin="0,0,3,3"/>
+ <Rectangle x:Name="viewport" Margin="24,30,71,26" Stroke="#BFFFFF00" RenderTransformOrigin="0.5,0.5"/>
</Grid>
</Border>
</UserControl>
\ No newline at end of file
<MenuItem Header="_Images">
<MenuItem x:Name="menuAddSourceImage" Header="_Add a source image" />
</MenuItem>
+ <MenuItem Header="_Analysis">
+ <MenuItem x:Name="menuStartAnalysis" Header="_Show analyses window" />
+ </MenuItem>
+ <MenuItem Header="_View">
+ <MenuItem x:Name="menuHightlightRBC" Header="_Highlight healthy erytrocytes" IsCheckable="True" />
+ </MenuItem>
</Menu>
<Grid x:Name="gridMain">
<Grid.RowDefinitions>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
- <ColumnDefinition Width="130"/>
+ <ColumnDefinition Width="180"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid x:Name="gridGlobalInfo" Grid.ColumnSpan="2" Margin="3,3,3,3" >
<Grid.ColumnDefinitions>
- <ColumnDefinition Width="Auto"/>
+ <ColumnDefinition Width="101"/>
+ <ColumnDefinition Width="21"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
- <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" />
- </Grid>
- <Grid Grid.Row="1">
- <Grid.RowDefinitions>
- <RowDefinition/>
- <RowDefinition Height="Auto" />
- </Grid.RowDefinitions>
- <Border BorderBrush="Black" BorderThickness="1" Margin="3" Grid.Row="0" >
- <ScrollViewer x:Name="scrollPreviews" VerticalScrollBarVisibility="Auto" >
- <StackPanel x:Name="stackPreviews" />
- </ScrollViewer>
- </Border>
- <Button x:Name="butStartAnalysis" Content="Start analysis" Grid.Row="1" Margin="3"/>
+ <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" />
</Grid>
+ <Border BorderBrush="Black" BorderThickness="1" Margin="3" Grid.Row="1" >
+ <ScrollViewer x:Name="scrollPreviews" VerticalScrollBarVisibility="Auto" >
+ <StackPanel x:Name="stackPreviews" />
+ </ScrollViewer>
+ </Border>
<Grid Grid.Column="2" Grid.Row="2">
<Grid.RowDefinitions>
<RowDefinition Height="100"/>
<RowDefinition/>
- <RowDefinition Height="60"/>
+ <RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<ScrollViewer x:Name="scrollViewCurrentImage" Grid.Row="1" VerticalScrollBarVisibility="Visible" HorizontalScrollBarVisibility="Visible" Background="Black" MinHeight="100" MinWidth="100">
<Border x:Name="borderCurrentImage" BorderBrush="Transparent">
<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" />
+ <ScrollViewer x:Name="scrollRBC" VerticalScrollBarVisibility="Hidden" HorizontalScrollBarVisibility="Visible" Grid.RowSpan="1" Margin="3">
+ <StackPanel x:Name="stackRBC" Orientation="Horizontal" />
+ </ScrollViewer>
+ <TextBlock x:Name="txtImageInformation" Grid.Row="2" TextWrapping="Wrap" Margin="3" />
</Grid>
</Grid>
</DockPanel>
// ParasitemIA Zipped file format.
module Parasitemia.GUI.PiaZ
+open System
open System.Windows
open System.IO
open System.IO.Compression
// The json type associated to a source image.
type JSONSourceImage = JsonProvider<"""
{
+ "RBCRadius" : 32.5,
+ "dateLastAnalysis" : 1.5,
"rbcs": [
{
"num": 1,
imgSrc.img.ToBitmap().Save(imgEntry.Open(), System.Drawing.Imaging.ImageFormat.Tiff)
let imgJSON =
- JSONSourceImage.Root([| for rbc in imgSrc.rbcs ->
- JSONSourceImage.Rbc(
+ JSONSourceImage.Root(decimal imgSrc.rbcRadius,
+ decimal <| imgSrc.dateLastAnalysis.ToFileTimeUtc(),
+ [| for rbc in imgSrc.rbcs ->
+ JSONSourceImage.Rbc(
rbc.num,
rbc.infected, rbc.setManually,
decimal rbc.center.X, decimal rbc.center.Y, decimal rbc.size.Width, decimal rbc.size.Height,
imgNum <- imgNum + 1
let imgJSONEntry = file.GetEntry(filename + ".json")
let imgJSON = JSONSourceImage.Load(imgJSONEntry.Open())
- yield { num = imgNum; img = img; rbcs = [ for rbc in imgJSON.Rbcs ->
- { 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);
- infectedArea = rbc.StainArea } ] } ]
+ yield { num = imgNum
+ rbcRadius = float imgJSON.RbcRadius
+ dateLastAnalysis = DateTime.FromFileTimeUtc(int64 imgJSON.DateLastAnalysis)
+ img = img
+ rbcs = [ for rbc in imgJSON.Rbcs ->
+ { 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);
+ infectedArea = rbc.StainArea } ] } ]
{ sources = sources; patientID = mainJSON.PatientId }
\ No newline at end of file
<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"/>
+ <TextBlock x:Name="txtRBCNumber" Padding="2" Text="42" HorizontalAlignment="Right" VerticalAlignment="Bottom" Background="#4C000000" Foreground="White" Margin="0,0,3,3"/>
</Grid>
</UserControl>
\ No newline at end of file
module Parasitemia.GUI.State
+open System
open System.Collections.Generic
open System.Windows
type State () =
let sourceImages = List<SourceImage>()
+ let mutable alteredSinceLastSave = false
+ let mutable patientID = ""
+ member this.AlteredSinceLastSave = alteredSinceLastSave
member val CurrentImage: SourceImage option = None with get, set
member val FilePath: string = "" with get, set
- member val PatientID: string = "" with get, set
+
+ member this.PatientID
+ with get () : string = patientID
+ and set id =
+ if id <> patientID
+ then
+ alteredSinceLastSave <- true
+ patientID <- id
member this.ImageParasitemia (srcImg: SourceImage) : int * int =
List.length srcImg.rbcs,
let nb, nbInfected = this.ImageParasitemia srcImg
nbTotal + nb, nbTotalInfected + nbInfected) (0, 0)
+
+ member this.SetAsInfected (rbc: RBC) (infected: bool) =
+ if infected <> rbc.infected
+ then
+ alteredSinceLastSave <- true
+ rbc.infected <- infected
+ rbc.setManually <- not rbc.setManually
+
member this.Save () =
let data = { PiaZ.sources = List.ofSeq sourceImages; PiaZ.patientID = this.PatientID }
PiaZ.save this.FilePath data
+ alteredSinceLastSave <- false
member this.Load () =
let data = PiaZ.load this.FilePath
sourceImages.InsertRange(0, data.sources)
if sourceImages.Count > 0
then this.CurrentImage <- Some sourceImages.[0]
+ alteredSinceLastSave <- false
member this.AddSourceImage (filePath: string) : SourceImage =
- let srcImg = { num = sourceImages.Count + 1; rbcs = []; img = new Image<Bgr, byte>(filePath) }
+ let srcImg = { num = sourceImages.Count + 1; rbcRadius = 0.; dateLastAnalysis = DateTime(0L); rbcs = []; img = new Image<Bgr, byte>(filePath) }
sourceImages.Add(srcImg)
if sourceImages.Count = 1
then this.CurrentImage <- Some sourceImages.[0]
+ alteredSinceLastSave <- true
srcImg
- member this.SetResult (imgNum: int) (cells: Cell list) =
+ member this.RemoveSourceImage (srcImg: SourceImage) =
+ let isCurrent =
+ match this.CurrentImage with
+ | Some srcImg' -> srcImg = srcImg'
+ | _ -> false
+
+ if sourceImages.Remove(srcImg)
+ then
+ alteredSinceLastSave <- true
+ if isCurrent
+ then
+ this.CurrentImage <- if sourceImages.Count > 0 then Some sourceImages.[0] else None
+ // Re-numbered the images.
+ sourceImages |> Seq.iteri (fun i srcImg -> srcImg.num <- i + 1)
+
+ member this.SetResult (imgNum: int) (rbcRadius: float) (cells: Cell list) =
let sourceImage = sourceImages.Find(fun srcImg -> srcImg.num = imgNum)
+
let w = sourceImage.img.Width
let h = sourceImage.img.Height
+
+ sourceImage.rbcRadius <- rbcRadius
+ sourceImage.dateLastAnalysis <- DateTime.UtcNow
+
+ // To match with previously manually altered RBC.
+ let manuallyAlteredPreviousRBCS = sourceImage.rbcs |> List.filter (fun rbc -> rbc.setManually)
+ let tolerance = rbcRadius * 0.5 // +/-.
+ let getPreviousRBC (center: Point) : RBC option =
+ manuallyAlteredPreviousRBCS |> List.tryFind (fun rbc -> rbc.center.X > center.X - tolerance && rbc.center.X < center.X + tolerance &&
+ rbc.center.Y > center.Y - tolerance && rbc.center.Y < center.Y + tolerance)
+
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 ->
+ let center = Point(float cell.center.X, float cell.center.Y)
+ let infected, setManually =
+ match getPreviousRBC center with
+ | Some rbc -> rbc.infected, true
+ | _ -> cell.cellClass = InfectedRBC, false
+
{ num = i + 1
- infected = cell.cellClass = InfectedRBC
- setManually = false
- center = Point(float cell.center.X, float cell.center.Y)
+ infected = infected
+ setManually = setManually
+ center = center
size = Size(float cell.elements.Width, float cell.elements.Height)
infectedArea = cell.infectedArea })
+ alteredSinceLastSave <- true
+
member this.SourceImages : SourceImage seq =
sourceImages :> SourceImage seq
member this.Reset () =
this.PatientID <- ""
- sourceImages.Clear()
\ No newline at end of file
+ this.FilePath <- ""
+ this.CurrentImage <- None
+ sourceImages.Clear()
+ alteredSinceLastSave <- false
\ No newline at end of file
module Parasitemia.GUI.Types
+open System
open System.Windows
open Emgu.CV
center: Point
size: Size
- infectedArea: int } with
- member this.SetAsInfected (infected: bool) =
- if infected <> this.infected
- then
- this.infected <- infected
- this.setManually <- not this.setManually
+ infectedArea: int }
type SourceImage = {
- num: int
+ mutable num: int
+ mutable rbcRadius: float
+ mutable dateLastAnalysis: DateTime // UTC.
img: Image<Bgr, byte>
mutable rbcs: RBC list }
\ No newline at end of file
open Types
-let doAnalysis (img: Image<Bgr, byte>) (name: string) (config: Config) : Cell list =
+let doAnalysis (img: Image<Bgr, byte>) (name: string) (config: Config) (reportProgress: (int -> unit) option) : Cell list =
+ let inline report (percent: int) =
+ match reportProgress with
+ | Some f -> f percent
+ | _ -> ()
+
use green = img.Item(1)
let greenFloat = green.Convert<Gray, float32>()
let filteredGreen = gaussianFilter greenFloat (float config.Parameters.preFilterSigma)
logTime "areaOpen 1" (fun () -> ImgTools.areaOpenF filteredGreen config.Parameters.initialAreaOpen)
+ report 8
let r1 = logTime "Granulometry (morpho)" (fun() -> Granulometry.findRadiusByClosing (filteredGreen.Convert<Gray, byte>()) (10, 80) 0.5 |> float32)
// let r2 = logTime "Granulometry (area)" (fun() -> Granulometry.findRadiusByAreaClosing filteredGreen (10, 80) |> float32)
// log (sprintf "r1: %A, r2: %A" r1 r2)
config.RBCRadius <- r1
+ report 24
- let secondAreaOpen = int <| config.RBCArea / 3.f
+ let secondAreaOpen = int <| config.RBCArea * config.Parameters.ratioSecondAreaOpen
if secondAreaOpen > config.Parameters.initialAreaOpen
then
let allEllipses, ellipses = logTime "Finding ellipses" (fun () ->
let matchingEllipses = Ellipse.find edges xGradient yGradient config
matchingEllipses.Ellipses, matchingEllipses.PrunedEllipses)
+ report 80
let cells = logTime "Classifier" (fun () -> Classifier.findCells ellipses parasites filteredGreenWhitoutStain config)
+ report 100
// Output pictures if debug flag is set.
match config.Debug with
cells
-let doMultipleAnalysis (imgs: (string * Image<Bgr, byte>) list) (config : Config) : (string * Cell list) list =
+// ID * cell radius * cell list.
+let doMultipleAnalysis (imgs: (string * Image<Bgr, byte>) list) (config : Config) (reportProgress: (int -> unit) option) : (string * float * Cell list) list =
+ let inline report (percent: int) =
+ match reportProgress with
+ | Some f -> f percent
+ | _ -> ()
+
+ let monitor = Object()
+ let mutable total = 0
+ let nbImgs = List.length imgs
+ let reportProgressImg =
+ (fun progress ->
+ lock monitor (fun () -> total <- total + progress)
+ report (total / nbImgs))
+
let nbConcurrentTaskLimit = 4
let n = Environment.ProcessorCount
imgs
- |> PSeq.map (fun (id, img) -> id, doAnalysis img id (config.Copy()))
+ |> PSeq.map (fun (id, img) ->
+ let localConfig = config.Copy()
+ let cells = doAnalysis img id localConfig (Some reportProgressImg)
+ id, float localConfig.RBCRadius, cells)
|> PSeq.withDegreeOfParallelism (if n > nbConcurrentTaskLimit then nbConcurrentTaskLimit else n)
- |> PSeq.toList
\ No newline at end of file
+ |> PSeq.toList
<Compile Include="GUI\ImageSourcePreview.xaml.fs" />
<Resource Include="GUI\RBCFrame.xaml" />
<Compile Include="GUI\RBCFrame.xaml.fs" />
+ <Resource Include="GUI\AnalysisWindow.xaml" />
+ <Compile Include="GUI\AnalysisWindow.xaml.fs" />
<Resource Include="GUI\MainWindow.xaml" />
<Compile Include="GUI\MainWindow.xaml.fs" />
<Compile Include="GUI\Types.fs" />
<Compile Include="GUI\PiaZ.fs" />
<Compile Include="GUI\State.fs" />
+ <Compile Include="GUI\Analysis.fs" />
<Compile Include="GUI\GUI.fs" />
<Compile Include="Program.fs" />
<None Include="App.config" />
<Resource Include="Resources\logo_256.png" />
</ItemGroup>
<ItemGroup>
+ <Reference Include="Castle.Core">
+ <HintPath>..\packages\Castle.Core.3.3.3\lib\net45\Castle.Core.dll</HintPath>
+ <Private>True</Private>
+ </Reference>
<Reference Include="Emgu.CV">
<HintPath>..\..\..\Emgu\emgucv-windows-universal 3.0.0.2157\bin\Emgu.CV.dll</HintPath>
</Reference>
<HintPath>..\packages\FSharp.Collections.ParallelSeq.1.0.2\lib\net40\FSharp.Collections.ParallelSeq.dll</HintPath>
<Private>True</Private>
</Reference>
- <Reference Include="FSharp.Core">
+ <Reference Include="FSharp.Core, Version=$(TargetFSharpCoreVersion), Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<HintPath>..\packages\FSharp.Core.4.0.0.1\lib\net40\FSharp.Core.dll</HintPath>
<Private>True</Private>
</Reference>
<HintPath>..\packages\FSharp.Data.2.2.5\lib\net40\FSharp.Data.dll</HintPath>
<Private>True</Private>
</Reference>
+ <Reference Include="FSharp.Data.TypeProviders" />
<Reference Include="FSharp.ViewModule">
<HintPath>..\packages\FSharp.ViewModule.Core.0.9.9.2\lib\portable-net45+netcore45+wpa81+wp8+MonoAndroid1+MonoTouch1\FSharp.ViewModule.dll</HintPath>
<Private>True</Private>
<Private>True</Private>
</Reference>
<Reference Include="log4net">
- <HintPath>..\packages\log4net.1.2.10\lib\2.0\log4net.dll</HintPath>
+ <HintPath>..\packages\log4net.2.0.5\lib\net45-full\log4net.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="MathNet.Numerics">
<Reference Include="System.Core" />
<Reference Include="System.Data" />
<Reference Include="System.Data.DataSetExtensions" />
+ <Reference Include="System.Data.Linq" />
<Reference Include="System.Drawing" />
<Reference Include="System.IO.Compression" />
<Reference Include="System.IO.Compression.FileSystem" />
</ProjectReference>
</ItemGroup>
<PropertyGroup>
- <PostBuildEvent>xcopy "D:\Emgu\emgucv-windows-universal 3.0.0.2157\bin\x64\*" "$(TargetDir)" /Y /D</PostBuildEvent>
+ <PostBuildEvent>xcopy "D:\Emgu\emgucv-windows-universal 3.0.0.2157\bin\x64" "$(TargetDir)x64" /Y /D /I</PostBuildEvent>
</PropertyGroup>
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
let filteredGreenWithoutStain = filteredGreenWithoutInfection.Copy()
ImgTools.areaCloseF filteredGreenWithoutStain (int config.StainArea)
- // We use the filtered image to find the dark stain.
- let _, mean_fg, mean_bg =
- let hist = ImgTools.histogramImg filteredGreenWithoutInfection 300
- ImgTools.otsu hist
-
- let darkStain = filteredGreenWithoutInfection.Cmp(-(float mean_bg) * config.Parameters.darkStainLevel + (float mean_fg), CvEnum.CmpType.LessThan)
+ let darkStain =
+ // We use the filtered image to find the dark stain.
+ let _, mean_fg, mean_bg =
+ let hist = ImgTools.histogramImg filteredGreenWithoutInfection 300
+ ImgTools.otsu hist
+ filteredGreenWithoutInfection.Cmp(-(float mean_bg) * config.Parameters.darkStainLevel + (float mean_fg), CvEnum.CmpType.LessThan)
let marker (img: Image<Gray, float32>) (closed: Image<Gray, float32>) (level: float) : Image<Gray, byte> =
let diff = img.Copy()
let infectionMarker = marker filteredGreen filteredGreenWithoutInfection config.Parameters.infectionLevel
let stainMarker = marker filteredGreenWithoutInfection filteredGreenWithoutStain config.Parameters.stainLevel
+ // TODO: comprendre pourquoi des valeurs sont negatives!?!?
+ let blackTopHat = filteredGreen.CopyBlank()
+ CvInvoke.Subtract(filteredGreenWithoutInfection, filteredGreen, blackTopHat)
+ ImgTools.saveImg (ImgTools.normalizeAndConvert blackTopHat) "BottomHat.png"
+
{ darkStain = darkStain
infection = infectionMarker
stain = stainMarker },
Config(
{
initialAreaOpen = 2000
+ ratioSecondAreaOpen = 1.f / 3.f
minRbcRadius = -0.3f
maxRbcRadius = 0.3f
maxStainRatio = 0.12 // 12 %
standardDeviationMaxRatio = 0.5 // 0.55
- minimumCellArea = 0.5f })
+ minimumCellAreaFactor = 0.5f })
match mode with
| CmdLine (input, output) ->
Utils.logTime "Whole analyze" (fun () ->
- let results = ImageAnalysis.doMultipleAnalysis images config
+ let results = ImageAnalysis.doMultipleAnalysis images config None
- for id, cells in results do
+ for id, _, cells in results do
let total, infected = Utils.countCells cells
fprintf resultFile "File: %s %d %d %.2f\n" id total infected (100. * (float infected) / (float total)))
sw.Start()
let res = f ()
sw.Stop()
- log <| sprintf "%A (time: %A ms)" m sw.ElapsedMilliseconds
+ log <| sprintf "%s (time: %d ms)" m sw.ElapsedMilliseconds
res
let inline lineFromTwoPoints (p1: PointD) (p2: PointD) : Line =
<?xml version="1.0" encoding="utf-8"?>
<packages>
- <package id="Castle.Core" version="1.1.0" targetFramework="net46" />
+ <package id="Castle.Core" version="3.3.3" targetFramework="net461" />
<package id="Expression.Blend.Sdk" version="1.0.2" targetFramework="net46" />
<package id="FSharp.Collections.ParallelSeq" version="1.0.2" targetFramework="net461" />
<package id="FSharp.Core" version="4.0.0.1" targetFramework="net461" />
<package id="FSharp.Data" version="2.2.5" targetFramework="net461" />
<package id="FSharp.ViewModule.Core" version="0.9.9.2" targetFramework="net461" />
<package id="FsXaml.Wpf" version="0.9.9" targetFramework="net46" />
- <package id="log4net" version="1.2.10" targetFramework="net46" />
+ <package id="log4net" version="2.0.5" targetFramework="net461" />
<package id="MathNet.Numerics" version="3.10.0" targetFramework="net461" />
<package id="MathNet.Numerics.FSharp" version="3.10.0" targetFramework="net461" />
</packages>
\ No newline at end of file
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
- <UseVSHostingProcess>true</UseVSHostingProcess>
+ <UseVSHostingProcess>false</UseVSHostingProcess>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>