Merge branch 'master' of gburri.org:master-thesis
authorGreg Burri <greg.burri@gmail.com>
Fri, 3 Nov 2017 20:22:26 +0000 (21:22 +0100)
committerGreg Burri <greg.burri@gmail.com>
Fri, 3 Nov 2017 20:22:26 +0000 (21:22 +0100)
24 files changed:
Parasitemia/ParasitemiaCore/Analysis.fs
Parasitemia/ParasitemiaCore/AssemblyInfo.fs
Parasitemia/ParasitemiaCore/ParasitemiaCore.fsproj
Parasitemia/ParasitemiaCore/Types.fs
Parasitemia/ParasitemiaCore/packages.config
Parasitemia/ParasitemiaUI/Analysis.fs
Parasitemia/ParasitemiaUI/AssemblyInfo.fs
Parasitemia/ParasitemiaUI/CommandLineArguments.fs [new file with mode: 0644]
Parasitemia/ParasitemiaUI/Export.fs
Parasitemia/ParasitemiaUI/GUI.fs
Parasitemia/ParasitemiaUI/ParasitemiaUI.fsproj
Parasitemia/ParasitemiaUI/PiaZ.fs
Parasitemia/ParasitemiaUI/Program.fs
Parasitemia/ParasitemiaUI/SourceImage.fs [new file with mode: 0644]
Parasitemia/ParasitemiaUI/State.fs
Parasitemia/ParasitemiaUI/Types.fs
Parasitemia/ParasitemiaUI/Utils.fs
Parasitemia/ParasitemiaUI/XAML/CommandLineArgumentsWindow.xaml [new file with mode: 0644]
Parasitemia/ParasitemiaUI/XAML/CommandLineArgumentsWindow.xaml.fs [new file with mode: 0644]
Parasitemia/ParasitemiaUI/XAML/MainWindow.xaml
Parasitemia/ParasitemiaUI/XAML/RBCFrame.xaml
Parasitemia/ParasitemiaUI/packages.config
Parasitemia/Tests/ParasitemiaCore.Tests/ParasitemiaCore.Tests.fsproj
Parasitemia/Tests/ParasitemiaCore.Tests/packages.config

index 8b16c45..ed908e2 100644 (file)
@@ -17,6 +17,8 @@ open ImgTools
 open Config
 open Types
 
+let warningRatioDifferenceRBCDiameter = 1.2
+
 /// <summary>
 /// Analyze the given image and detect reb blood cell (RBC) in it.
 /// </summary>
@@ -27,7 +29,7 @@ open Types
 ///     The first call returning 'false' will cancel the analysis.
 ///     The 'int' parameter correspond to the progression from 0 to 100</param>
 /// <returns>A list of detected cells or nothing if the process has been cancelled</returns>
-let doAnalysis (img : Image<Bgr, byte>) (name : string) (config : Config) (reportProgress : (int -> bool) option) : Cell list option =
+let doAnalysis (img : Image<Bgr, byte>) (name : string) (config : Config) (reportProgress : (int -> bool) option) : AnalysisResult option =
 
     // To report the progress of this function from 0 to 100.
     // Return 'None' if the process must be aborted.
@@ -104,6 +106,12 @@ let doAnalysis (img : Image<Bgr, byte>) (name : string) (config : Config) (repor
 
         let! cells = logTimeWithName "Classifier" (fun () -> reportWithVal 100 (Classifier.findCells prunedEllipses parasites img.Width img.Height config))
 
+        do
+            if config.RBCRadiusByResolution.μm / config.RBCRadius.μm > warningRatioDifferenceRBCDiameter then
+                logWithName (sprintf "Warning: erythrocyte diameter found is too low compared to the nominal erythrocyte diameter, maybe the PPI image resolution is lesser than %.0f ppi" config.Parameters.resolution)
+            elif config.RBCRadius.μm / config.RBCRadiusByResolution.μm > warningRatioDifferenceRBCDiameter then
+                logWithName (sprintf "Warning: erythrocyte diameter found is too high compared to the nominal erythrocyte diameter, maybe the PPI image resolution is higher than %.0f" config.Parameters.resolution)
+
         logWithName "Analysis finished"
 
         do
@@ -153,7 +161,14 @@ let doAnalysis (img : Image<Bgr, byte>) (name : string) (config : Config) (repor
                 IO.saveImg img_float.[0] (buildFileName " - source - blue.png")
             | _ -> ()
 
-        return cells
+        return
+            {
+                Cells = cells
+                RBCSize_μm = config.RBCRadius.μm
+                RBCSize_px = config.RBCRadius.Pixel
+            }
+
+        //return cells
     }
 
 /// <summary>
@@ -164,7 +179,7 @@ let doAnalysis (img : Image<Bgr, byte>) (name : string) (config : Config) (repor
 ///     The first call returning 'false' will cancel the analysis.
 ///     The 'int' parameter correspond to the progression from 0 to 100</param>
 /// <returns>'None' if the process has been cancelled or the list of result as (name * cells), 'name' corresponds to the given name<returns>
-let doMultipleAnalysis (imgs : (string * Config * Image<Bgr, byte>) list) (reportProgress : (int -> bool) option) : (string * Cell list) list option =
+let doMultipleAnalysis (imgs : (string * Config * Image<Bgr, byte>) list) (reportProgress : (int -> bool) option) : (string * AnalysisResult) list option =
     let report (percent : int) : bool =
         match reportProgress with
         | Some f -> f percent
index 9e190e0..e5eace6 100644 (file)
@@ -34,8 +34,8 @@ open System.Runtime.InteropServices
 // You can specify all the values or you can default the Build and Revision Numbers
 // by using the '*' as shown below:
 // [<assembly: AssemblyVersion "1.0.*">]
-[<assembly: AssemblyVersion "1.0.0.10">]
-[<assembly: AssemblyFileVersion "1.0.0.10">]
+[<assembly: AssemblyVersion "1.0.0.12">]
+[<assembly: AssemblyFileVersion "1.0.0.12">]
 
 do
     ()
\ No newline at end of file
index c841f71..f6262e2 100644 (file)
       <Private>True</Private>
     </Reference>
     <Reference Include="FSharp.Core">
-      <HintPath>..\packages\FSharp.Core.4.2.1\lib\net45\FSharp.Core.dll</HintPath>
+      <HintPath>..\packages\FSharp.Core.4.2.3\lib\net45\FSharp.Core.dll</HintPath>
     </Reference>
     <Reference Include="MathNet.Numerics">
-      <HintPath>..\packages\MathNet.Numerics.3.19.0\lib\net40\MathNet.Numerics.dll</HintPath>
+      <HintPath>..\packages\MathNet.Numerics.3.20.0\lib\net40\MathNet.Numerics.dll</HintPath>
     </Reference>
     <Reference Include="MathNet.Numerics.FSharp">
-      <HintPath>..\packages\MathNet.Numerics.FSharp.3.19.0\lib\net40\MathNet.Numerics.FSharp.dll</HintPath>
+      <HintPath>..\packages\MathNet.Numerics.FSharp.3.20.0\lib\net40\MathNet.Numerics.FSharp.dll</HintPath>
     </Reference>
     <Reference Include="mscorlib" />
     <Reference Include="OpenTK">
     <Reference Include="System.Drawing" />
     <Reference Include="System.Numerics" />
     <Reference Include="System.ValueTuple">
-      <HintPath>..\packages\System.ValueTuple.4.3.1\lib\netstandard1.0\System.ValueTuple.dll</HintPath>
+      <HintPath>..\packages\System.ValueTuple.4.4.0\lib\netstandard1.0\System.ValueTuple.dll</HintPath>
     </Reference>
     <Reference Include="System.Windows.Forms" />
     <Reference Include="System.Xml" />
index 8f526f9..0cb2768 100644 (file)
@@ -8,6 +8,7 @@ open Emgu.CV
 open Emgu.CV.Structure
 
 open Const
+open UnitsOfMeasure
 
 type Points = HashSet<Point>
 
@@ -102,4 +103,11 @@ type ResultBuilder () =
 
     member this.ReturnFrom (x) = x
 
-let result = ResultBuilder ()
\ No newline at end of file
+let result = ResultBuilder ()
+
+type AnalysisResult = 
+    {
+        Cells : Cell list
+        RBCSize_μm : float<μm>
+        RBCSize_px : float32
+    }
\ No newline at end of file
index d0073d5..87c6b51 100644 (file)
@@ -2,11 +2,11 @@
 <packages>
   <package id="EmguCV" version="3.1.0.1" targetFramework="net452" />
   <package id="FSharp.Collections.ParallelSeq" version="1.0.2" targetFramework="net452" />
-  <package id="FSharp.Core" version="4.2.1" targetFramework="net452" />
-  <package id="MathNet.Numerics" version="3.19.0" targetFramework="net452" />
-  <package id="MathNet.Numerics.FSharp" version="3.19.0" targetFramework="net452" />
+  <package id="FSharp.Core" version="4.2.3" targetFramework="net452" />
+  <package id="MathNet.Numerics" version="3.20.0" targetFramework="net452" />
+  <package id="MathNet.Numerics.FSharp" version="3.20.0" targetFramework="net452" />
   <package id="OpenTK" version="2.0.0" targetFramework="net452" />
   <package id="OpenTK.GLControl" version="1.1.2349.61993" targetFramework="net452" />
-  <package id="System.ValueTuple" version="4.3.1" targetFramework="net452" />
+  <package id="System.ValueTuple" version="4.4.0" targetFramework="net452" />
   <package id="ZedGraph" version="5.1.7" targetFramework="net452" />
 </packages>
\ No newline at end of file
index 4a75a91..13ce48b 100644 (file)
@@ -57,13 +57,13 @@ let showWindow (parent : Window) (state : State.State) : bool =
             let imageSourceSelection = Views.ImageSourceSelection (Tag = srcImg, Margin = Thickness 3.)
             imageSourceSelection.Tag <- srcImg
 
-            imageSourceSelection.txtImageNumber.Text <- string srcImg.num
-            let height = srcImg.img.Height * width / srcImg.img.Width
-            imageSourceSelection.imagePreview.Source <- BitmapSourceConvert.ToBitmapSource (srcImg.img.Resize (width, height, Emgu.CV.CvEnum.Inter.Cubic))
-            imageSourceSelection.chkSelection.IsChecked <- Nullable<bool> (srcImg.dateLastAnalysis.Ticks = 0L)
-            imageSourceSelection.lblDateLastAnalysis.Content <- if srcImg.dateLastAnalysis.Ticks = 0L then "<Never>" else string srcImg.dateLastAnalysis
+            imageSourceSelection.txtImageNumber.Text <- string srcImg.Num
+            let height = srcImg.Img.Height * width / srcImg.Img.Width
+            imageSourceSelection.imagePreview.Source <- BitmapSourceConvert.ToBitmapSource (srcImg.Img.Resize (width, height, Emgu.CV.CvEnum.Inter.Cubic))
+            imageSourceSelection.chkSelection.IsChecked <- Nullable<bool> (srcImg.DateLastAnalysis.Ticks = 0L)
+            imageSourceSelection.lblDateLastAnalysis.Content <- if srcImg.DateLastAnalysis.Ticks = 0L then "<Never>" else string srcImg.DateLastAnalysis
 
-            imageSourceSelection.txtResolution.Text <- if srcImg.dateLastAnalysis.Ticks = 0L then "" else string srcImg.config.Parameters.resolution
+            imageSourceSelection.txtResolution.Text <- if srcImg.DateLastAnalysis.Ticks = 0L then "" else string srcImg.Config.Parameters.resolution
 
             for ppi in Utils.predefinedPPI do
                 let menu = MenuItem ()
@@ -103,9 +103,9 @@ let showWindow (parent : Window) (state : State.State) : bool =
                     let isChecked = srcImgCtrl.chkSelection.IsChecked
                     match parseAndValidatePPI srcImgCtrl.txtResolution.Text with
                         | Some resolution ->
-                            yield Some (srcImg, isChecked.HasValue && isChecked.Value, { srcImg.config.Parameters with resolution = resolution * 1.<ppi> })
+                            yield Some (srcImg, isChecked.HasValue && isChecked.Value, { srcImg.Config.Parameters with resolution = resolution * 1.<ppi> })
                         | None ->
-                            MessageBox.Show (sprintf "No resolution defined for the image number %d" srcImg.num, "No resolution defined", MessageBoxButton.OK, MessageBoxImage.Information) |> ignore
+                            MessageBox.Show (sprintf "No resolution defined for the image number %d" srcImg.Num, "No resolution defined", MessageBoxButton.OK, MessageBoxImage.Information) |> ignore
                             yield None
             } |> Seq.takeWhile (fun e -> e.IsSome) |> Seq.map (fun e -> e.Value) |> List.ofSeq
 
@@ -123,9 +123,9 @@ let showWindow (parent : Window) (state : State.State) : bool =
                 let imagesToProcess =
                     [
                         for srcImg, selected, parameters in imagesParameters do
-                            srcImg.config.Parameters <- parameters // Save parameters.
+                            srcImg.Config.Parameters <- parameters // Save parameters.
                             if selected then
-                                yield string srcImg.num, srcImg.config, srcImg.img
+                                yield string srcImg.Num, srcImg.Config, srcImg.Img
                     ]
 
                 if imagesToProcess.IsEmpty then
@@ -134,6 +134,7 @@ let showWindow (parent : Window) (state : State.State) : bool =
                     win.stackSourceImagesSelection.IsEnabled <- false
                     analysisPerformed <- false
                     win.butStart.IsEnabled <- false
+                    win.textLog.Text <- ""
                     win.butClose.Content <- "Abort"
 
                     async {
index d0754f8..3fdcd4b 100644 (file)
@@ -34,8 +34,8 @@ open System.Runtime.InteropServices
 // You can specify all the values or you can default the Build and Revision Numbers
 // by using the '*' as shown below:
 // [<assembly: AssemblyVersion "1.0.*">]
-[<assembly: AssemblyVersion "1.0.0.10">]
-[<assembly: AssemblyFileVersion "1.0.0.10">]
+[<assembly: AssemblyVersion "1.0.0.12">]
+[<assembly: AssemblyFileVersion "1.0.0.12">]
 
 do
     ()
\ No newline at end of file
diff --git a/Parasitemia/ParasitemiaUI/CommandLineArguments.fs b/Parasitemia/ParasitemiaUI/CommandLineArguments.fs
new file mode 100644 (file)
index 0000000..8025ebf
--- /dev/null
@@ -0,0 +1,20 @@
+module ParasitemiaUI.CommandLineArguments
+
+open System
+open System.Windows
+open System.Windows.Controls
+open System.Diagnostics
+
+let showWindow (parent : Window) =
+    let win = Views.CommandLineArgumentsWindow ()
+    win.Owner <- parent
+
+    win.Left <- (if parent.WindowState = WindowState.Maximized then 0. else parent.Left) + parent.ActualWidth / 2. - win.Width / 2.
+    win.Top <- (if parent.WindowState = WindowState.Maximized then 0. else parent.Top) + parent.ActualHeight / 2. - win.Height / 2.
+
+    win.txtCommandLineArguments.Text <- Utils.argsHelp
+
+    win.butClose.Click.AddHandler (fun obj args -> win.Close ())
+
+    win.ShowDialog () |> ignore
+
index 52638d3..805bcd7 100644 (file)
@@ -17,8 +17,8 @@ let exportResults (state : State) (filePath : string) =
 
     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)
+        fprintfn writer "Image name: %s" srcImg.Name
+        fprintfn writer "Parasitemia: %s" (Utils.percentText srcImg.ImageParasitemia)
+        fprintfn writer "Added infected erythrocyte: %s %s" (srcImg.ImageNbManuallyChangedRBCStr true) (srcImg.ImageManuallyChangedRBCStr true)
+        fprintfn writer "Removed infected erythrocyte: %s %s" (srcImg.ImageNbManuallyChangedRBCStr false) (srcImg.ImageManuallyChangedRBCStr false)
     ()
\ No newline at end of file
index 46171a7..d78c202 100644 (file)
@@ -54,14 +54,21 @@ let run (defaultConfig : Config) (fileToOpen : string option) =
         frame.manuallyAdded.Fill <- color
         frame.border.Stroke <- color
 
-    let RBCFrameFromExisting (srcImg : SourceImage) (rbc : RBC) (frame : Views.RBCFrame) : Views.RBCFrame =
+    let frameStrokeThickness (averageRBCSize : float) =
+        max 1. (averageRBCSize / 60.)
+
+    let frameFontSize (averageRBCSize : float) =
+        max 1. (averageRBCSize / 8.)
+
+    let RBCFrameFromExisting (srcImg : SourceImage) (rbc : RBC) (frame : Views.RBCFrame) (frameThickness : float) (fontSize : float) : Views.RBCFrame =
         frame.Visibility <- Visibility.Visible
-        frame.Height <- rbc.size.Height
         frame.Width <- rbc.size.Width
+        frame.Height <- rbc.size.Height
         frame.Tag <- rbc
         setRBCFrameStyle srcImg rbc frame
-        frame.border.StrokeThickness <- 1.
+        frame.border.StrokeThickness <- frameThickness
         frame.txtRBCNumber.Text <- string rbc.num
+        frame.txtRBCNumber.FontSize <- fontSize
         frame
 
     let updateDocumentStatus () =
@@ -70,6 +77,8 @@ let run (defaultConfig : Config) (fileToOpen : string option) =
     let statusMessageTimer = Threading.DispatcherTimer ()
     statusMessageTimer.Tick.AddHandler (fun obj args -> statusMessageTimer.Stop (); win.txtMessageStatus.Text <- "")
     statusMessageTimer.Interval <- TimeSpan (0, 0, 2)
+
+    // To show a use message while a short period of time.
     let displayStatusMessage (message : string) =
         win.txtMessageStatus.Text <- message
         statusMessageTimer.Stop ()
@@ -78,17 +87,16 @@ let run (defaultConfig : Config) (fileToOpen : string option) =
     let highlightRBCFrame (frame : Views.RBCFrame) (highlight : bool) =
         let rbc = frame.Tag :?> RBC
         if highlight then
-            frame.border.StrokeThickness <- 3.
+            frame.border.StrokeThickness <- 3. * frame.border.StrokeThickness
             if not rbc.infected && not rbc.setManually && not displayHealthy then frame.Opacity <- 1.
         else
-            frame.border.StrokeThickness <- 1.
+            frame.border.StrokeThickness <- frame.border.StrokeThickness / 3.
             if not rbc.infected && not rbc.setManually && not displayHealthy then frame.Opacity <- 0.
 
     let zoomToRBC (rbc : RBC) =
         win.scrollViewCurrentImage.ScrollToHorizontalOffset (rbc.center.X * currentScale - win.scrollViewCurrentImage.ViewportWidth / 2. + win.borderCurrentImage.BorderThickness.Left)
         win.scrollViewCurrentImage.ScrollToVerticalOffset (rbc.center.Y * currentScale - win.scrollViewCurrentImage.ViewportHeight / 2. + win.borderCurrentImage.BorderThickness.Top)
 
-
     let txtImageName_TextChanged =
         TextChangedEventHandler (fun obj args -> state.CurrentImage |> Option.iter (fun srcImg -> state.SetName srcImg win.txtImageName.Text))
 
@@ -101,24 +109,24 @@ let run (defaultConfig : Config) (fileToOpen : string option) =
         match state.CurrentImage with
         | Some srcImg ->
             win.gridImageInformation.Visibility <- Visibility.Visible
-            win.txtImageName.Text <- srcImg.name
+            win.txtImageName.Text <- srcImg.Name
             win.txtImageName.TextChanged.AddHandler txtImageName_TextChanged
 
             // The left part.
-            let parasitemiaStr = Utils.percentText (state.ImageParasitemia srcImg)
+            let parasitemiaStr = Utils.percentText srcImg.ImageParasitemia
             win.txtImageInformation1.Inlines.Add (Documents.Run ("Parasitemia: ", FontWeight = FontWeights.Bold))
             win.txtImageInformation1.Inlines.Add parasitemiaStr
             win.txtImageInformation1.Inlines.Add (Documents.LineBreak ())
 
             win.txtImageInformation1.Inlines.Add (Documents.Run ("Last analysis: ", FontWeight = FontWeights.Bold))
-            win.txtImageInformation1.Inlines.Add (Documents.Run (if srcImg.dateLastAnalysis.Ticks = 0L then "<Never>" else string (srcImg.dateLastAnalysis.ToLocalTime())))
+            win.txtImageInformation1.Inlines.Add (Documents.Run (if srcImg.DateLastAnalysis.Ticks = 0L then "<Never>" else string (srcImg.DateLastAnalysis.ToLocalTime())))
 
             // The right part part.
             win.txtImageInformation2.Inlines.Add (Documents.Run ("Added infected erythrocyte: ", FontWeight = FontWeights.Bold))
-            win.txtImageInformation2.Inlines.Add (Documents.Run ((state.ImageNbManuallyChangedRBCStr srcImg true) + " " + (state.ImageManuallyChangedRBCStr srcImg true)))
+            win.txtImageInformation2.Inlines.Add (Documents.Run ((srcImg.ImageNbManuallyChangedRBCStr true) + " " + (srcImg.ImageManuallyChangedRBCStr true)))
             win.txtImageInformation2.Inlines.Add (Documents.LineBreak ())
             win.txtImageInformation2.Inlines.Add (Documents.Run ("Removed infected erythrocyte: ", FontWeight = FontWeights.Bold))
-            win.txtImageInformation2.Inlines.Add (Documents.Run ((state.ImageNbManuallyChangedRBCStr srcImg false) + " " + (state.ImageManuallyChangedRBCStr srcImg false)))
+            win.txtImageInformation2.Inlines.Add (Documents.Run ((srcImg.ImageNbManuallyChangedRBCStr false) + " " + (srcImg.ImageManuallyChangedRBCStr false)))
 
         | _ ->
             win.gridImageInformation.Visibility <- Visibility.Hidden
@@ -175,8 +183,8 @@ let run (defaultConfig : Config) (fileToOpen : string option) =
         updateCurrentImageInformation ()
         updateGlobalParasitemia ()
 
-    and RBCFrame (srcImg : SourceImage) (rbc : RBC) : Views.RBCFrame =
-        let frame = RBCFrameFromExisting srcImg rbc (Views.RBCFrame ())
+    and RBCFrame (srcImg : SourceImage) (rbc : RBC) (frameThickness : float) (fontSize : float) : Views.RBCFrame =
+        let frame = RBCFrameFromExisting srcImg rbc (Views.RBCFrame ()) frameThickness fontSize
         frame.SetValue (Panel.ZIndexProperty, Int32.MaxValue - rbc.num) // To be sure the
         frame.menuRBCSetAsHealthy.Click.AddHandler (fun obj args -> setAsInfected srcImg (frame.Tag :?> RBC) false)
         frame.menuRBCSetAsInfected.Click.AddHandler (fun obj args -> setAsInfected srcImg (frame.Tag :?> RBC) true)
@@ -198,12 +206,12 @@ let run (defaultConfig : Config) (fileToOpen : string option) =
         match state.CurrentImage with
         | Some srcImg ->
             let mutable currentPreview = 0
-            for rbc in srcImg.rbcs |> List.filter (fun rbc -> displayHealthy || rbc.infected) do
+            for rbc in srcImg.RBCs |> List.filter (fun rbc -> displayHealthy || rbc.infected) do
                 let previewInfected =
                     if currentPreview < win.stackRBC.Children.Count then
-                        RBCFrameFromExisting srcImg rbc (win.stackRBC.Children.[currentPreview] :?> Views.RBCFrame)
+                        RBCFrameFromExisting srcImg rbc (win.stackRBC.Children.[currentPreview] :?> Views.RBCFrame) 1. 12.
                     else
-                        let f = RBCFrame srcImg rbc
+                        let f = RBCFrame srcImg rbc 1. 12.
                         f.MouseLeftButtonUp.AddHandler (fun obj args -> zoomToRBC (f.Tag :?> RBC))
                         win.stackRBC.Children.Add f |> ignore
                         f
@@ -212,7 +220,7 @@ let run (defaultConfig : Config) (fileToOpen : string option) =
 
                 previewInfected.Height <- win.stackRBC.ActualHeight
                 previewInfected.Width <- win.stackRBC.ActualHeight * rbc.size.Width / rbc.size.Height
-                previewInfected.border.Fill <- ImageBrush (BitmapSourceConvert.ToBitmapSource (extractRBCPreview srcImg.img rbc))
+                previewInfected.border.Fill <- ImageBrush (BitmapSourceConvert.ToBitmapSource (extractRBCPreview srcImg.Img rbc))
 
             win.stackRBC.Children.RemoveRange (currentPreview, win.stackRBC.Children.Count - currentPreview)
         | _ -> ()
@@ -223,12 +231,14 @@ let run (defaultConfig : Config) (fileToOpen : string option) =
         match state.CurrentImage with
         | Some srcImg ->
             let mutable currentCanvas = 0
-            for rbc in srcImg.rbcs do
+            let strokeThickness = frameStrokeThickness srcImg.AverageRBCSize
+            let fontSize = frameFontSize srcImg.AverageRBCSize
+            for rbc in srcImg.RBCs do
                 let frame =
                     if currentCanvas < win.canvasCurrentImage.Children.Count then
-                        RBCFrameFromExisting srcImg rbc (win.canvasCurrentImage.Children.[currentCanvas] :?> Views.RBCFrame)
+                        RBCFrameFromExisting srcImg rbc (win.canvasCurrentImage.Children.[currentCanvas] :?> Views.RBCFrame) strokeThickness fontSize
                     else
-                        let f = RBCFrame srcImg rbc
+                        let f = RBCFrame srcImg rbc strokeThickness fontSize
                         win.canvasCurrentImage.Children.Add f |> ignore
                         f
 
@@ -296,9 +306,9 @@ let run (defaultConfig : Config) (fileToOpen : string option) =
             |> Seq.cast<Views.ImageSourcePreview>
             |> Seq.iter (fun preview -> preview.border.BorderThickness <- Thickness (if preview.Tag = (srcImg :> Object) then 3. else 0.))
 
-            win.canvasCurrentImage.Height <- float srcImg.img.Height
-            win.canvasCurrentImage.Width <- float srcImg.img.Width
-            win.canvasCurrentImage.Background <- ImageBrush (BitmapSourceConvert.ToBitmapSource (srcImg.img))
+            win.canvasCurrentImage.Height <- float srcImg.Img.Height
+            win.canvasCurrentImage.Width <- float srcImg.Img.Width
+            win.canvasCurrentImage.Background <- ImageBrush (BitmapSourceConvert.ToBitmapSource (srcImg.Img))
 
             updateRBCFramesCurrent ()
             updateRBCFramesPreview ()
@@ -332,14 +342,14 @@ let run (defaultConfig : Config) (fileToOpen : string option) =
                 updateGlobalParasitemia ()
 
                 // Update image numbers.
-                win.stackPreviews.Children |> Seq.cast<Views.ImageSourcePreview> |> Seq.iter (fun imgPreview -> imgPreview.txtImageNumber.Text <- (imgPreview.Tag :?> SourceImage).num.ToString ())
+                win.stackPreviews.Children |> Seq.cast<Views.ImageSourcePreview> |> Seq.iter (fun imgPreview -> imgPreview.txtImageNumber.Text <- (imgPreview.Tag :?> SourceImage).Num.ToString ())
         )
 
         imgCtrl.Tag <- srcImg
-        imgCtrl.txtImageNumber.Text <- string srcImg.num
+        imgCtrl.txtImageNumber.Text <- string srcImg.Num
         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))
+        let height = srcImg.Img.Height * width / srcImg.Img.Width
+        imgCtrl.imagePreview.Source <- BitmapSourceConvert.ToBitmapSource (srcImg.Img.Resize (width, height, Emgu.CV.CvEnum.Inter.Cubic))
         win.stackPreviews.Children.Add imgCtrl |> ignore
 
         // Zoom to a mouse position into the control 'imgCtrl'.
@@ -474,6 +484,8 @@ let run (defaultConfig : Config) (fileToOpen : string option) =
 
     win.menuAbout.Click.AddHandler (fun obj args -> About.showWindow win)
 
+    win.menuCommandLineArguments.Click.AddHandler (fun obj args -> CommandLineArguments.showWindow win)
+
     win.Closing.AddHandler (fun obj args -> askSaveCurrent ())
 
     // Zoom on the current image.
@@ -512,7 +524,7 @@ let run (defaultConfig : Config) (fileToOpen : string option) =
     let currentImageScaleTransform = ScaleTransform ()
     win.canvasCurrentImage.LayoutTransform <- currentImageScaleTransform
     win.borderCurrentImage.PreviewMouseWheel.AddHandler (
-        fun obj args ->
+        fun _obj args ->
             let scaleFactor = if args.Delta > 0 then 2.0 else 0.5
             if scaleFactor > 1. && currentScale < maxScale || scaleFactor < 1. && currentScale > minScale then
                 let previousScale = currentScale
@@ -538,7 +550,7 @@ let run (defaultConfig : Config) (fileToOpen : string option) =
     let mutable scrollStartOffsetX = 0.
     let mutable scrollStartOffsetY = 0.
     win.borderCurrentImage.PreviewMouseLeftButtonDown.AddHandler (
-        fun obj args ->
+        fun _obj args ->
             scrollStartPosition <- args.GetPosition win.scrollViewCurrentImage
             scrollStartOffsetX <- win.scrollViewCurrentImage.HorizontalOffset
             scrollStartOffsetY <- win.scrollViewCurrentImage.VerticalOffset
@@ -548,7 +560,7 @@ let run (defaultConfig : Config) (fileToOpen : string option) =
     )
 
     win.borderCurrentImage.PreviewMouseMove.AddHandler (
-        fun obj args ->
+        fun _obj args ->
             if win.borderCurrentImage.IsMouseCaptured then
                 let position = args.GetPosition win.scrollViewCurrentImage
                 let deltaX = scrollStartPosition.X - position.X
@@ -560,7 +572,7 @@ let run (defaultConfig : Config) (fileToOpen : string option) =
     )
 
     win.borderCurrentImage.PreviewMouseLeftButtonUp.AddHandler (
-        fun obj args ->
+        fun _obj args ->
             if win.borderCurrentImage.IsMouseCaptured then
                 win.borderCurrentImage.Cursor <- Input.Cursors.Arrow
                 win.borderCurrentImage.ReleaseMouseCapture ()
index 0324219..429ceaa 100644 (file)
     <Compile Include="XAML\PPICalculatorWindow.xaml.fs" />
     <Resource Include="XAML\AnalysisWindow.xaml" />
     <Compile Include="XAML\AnalysisWindow.xaml.fs" />
+    <Resource Include="XAML\CommandLineArgumentsWindow.xaml" />
+    <Compile Include="XAML\CommandLineArgumentsWindow.xaml.fs" />
     <Resource Include="XAML\AboutWindow.xaml" />
     <Compile Include="XAML\AboutWindow.xaml.fs" />
     <Resource Include="XAML\MainWindow.xaml" />
     <Compile Include="XAML\MainWindow.xaml.fs" />
     <Compile Include="Types.fs" />
     <Compile Include="Utils.fs" />
+    <Compile Include="SourceImage.fs" />
     <Compile Include="PiaZ.fs" />
     <Compile Include="State.fs" />
     <Compile Include="Export.fs" />
     <Compile Include="DPICalculator.fs" />
     <Compile Include="Analysis.fs" />
+    <Compile Include="CommandLineArguments.fs" />
     <Compile Include="About.fs" />
     <Compile Include="GUI.fs" />
     <None Include="App.config" />
       <HintPath>..\packages\EmguCV.3.1.0.1\lib\net30\Emgu.CV.World.dll</HintPath>
     </Reference>
     <Reference Include="FSharp.Core">
-      <HintPath>..\packages\FSharp.Core.4.2.1\lib\net45\FSharp.Core.dll</HintPath>
+      <HintPath>..\packages\FSharp.Core.4.2.3\lib\net45\FSharp.Core.dll</HintPath>
     </Reference>
     <Reference Include="FSharp.ViewModule">
       <HintPath>..\packages\FSharp.ViewModule.Core.1.0.7.0\lib\portable-net45+netcore45+wpa81+wp8+MonoAndroid1+MonoTouch1\FSharp.ViewModule.dll</HintPath>
     <Reference Include="System.IO.Compression.FileSystem" />
     <Reference Include="System.Numerics" />
     <Reference Include="System.ValueTuple">
-      <HintPath>..\packages\System.ValueTuple.4.3.1\lib\netstandard1.0\System.ValueTuple.dll</HintPath>
+      <HintPath>..\packages\System.ValueTuple.4.4.0\lib\netstandard1.0\System.ValueTuple.dll</HintPath>
     </Reference>
     <Reference Include="System.Windows.Forms" />
     <Reference Include="System.Windows.Interactivity">
index 92ebbe0..df72212 100644 (file)
@@ -68,23 +68,23 @@ let save (filePath : string) (data : DocumentData) =
 
     // Write each images and the associated information.
     for srcImg in data.images do
-        let imgFilename = (string srcImg.num) + imageExtension
+        let imgFilename = (string srcImg.Num) + imageExtension
         let imgEntry = file.CreateEntry (imgFilename, CompressionLevel.NoCompression) // FIXME: It seems a compression is applied to this file despite of the 'NoCompression' flag.
-        srcImg.img.ToBitmap().Save (imgEntry.Open (), System.Drawing.Imaging.ImageFormat.Tiff)
+        srcImg.Img.ToBitmap().Save (imgEntry.Open (), System.Drawing.Imaging.ImageFormat.Tiff)
 
         let imgJSONEntry = file.CreateEntry (imgFilename + ".json", CompressionLevel.Fastest)
         use imgJSONFileWriter = new StreamWriter (imgJSONEntry.Open ())
         imgJSONFileWriter.Write (
             JsonConvert.SerializeObject (
                 {
-                    num = srcImg.num
-                    name = srcImg.name
-                    RBCRadius = srcImg.config.RBCRadius.Pixel
-                    parameters = srcImg.config.Parameters
-                    dateLastAnalysis = srcImg.dateLastAnalysis
-                    rbcs = srcImg.rbcs
-                    healthyRBCBrightness = srcImg.healthyRBCBrightness
-                    infectedRBCBrightness = srcImg.infectedRBCBrightness
+                    num = srcImg.Num
+                    name = srcImg.Name
+                    RBCRadius = srcImg.Config.RBCRadius.Pixel
+                    parameters = srcImg.Config.Parameters
+                    dateLastAnalysis = srcImg.DateLastAnalysis
+                    rbcs = srcImg.RBCs
+                    healthyRBCBrightness = srcImg.HealthyRBCBrightness
+                    infectedRBCBrightness = srcImg.InfectedRBCBrightness
                 }
             )
         )
@@ -93,7 +93,7 @@ let updateDocumentData (fromVersion : int) (toVersion : int) (data : DocumentDat
     for v in fromVersion + 1 .. toVersion do
         match v with
         | 1 -> // Version 0 -> 1 : set initial brightness for rbc.
-            data.images |> List.iter (fun i -> i.healthyRBCBrightness <- 1.f; i.infectedRBCBrightness <- 1.f)
+            data.images |> List.iter (fun i -> i.HealthyRBCBrightness <- 1.f; i.InfectedRBCBrightness <- 1.f)
         | _ -> ()
     data
 
@@ -132,16 +132,7 @@ let load (filePath : string) (defaultConfig : ParasitemiaCore.Config.Config) : D
                                 }
 
                             config.SetRBCRadius imgInfo.RBCRadius
-                            yield
-                                {
-                                    num = imgNum
-                                    name = imgInfo.name
-                                    config = config
-                                    dateLastAnalysis = imgInfo.dateLastAnalysis
-                                    img = img
-                                    rbcs = imgInfo.rbcs
-                                    healthyRBCBrightness = imgInfo.healthyRBCBrightness
-                                    infectedRBCBrightness = imgInfo.infectedRBCBrightness
-                                }
+
+                            yield SourceImage (imgNum, imgInfo.name, config, imgInfo.dateLastAnalysis, img, imgInfo.rbcs, HealthyRBCBrightness = imgInfo.healthyRBCBrightness, InfectedRBCBrightness = imgInfo.infectedRBCBrightness)
                 ]
         }
\ No newline at end of file
index b7b4202..63bd963 100644 (file)
@@ -20,7 +20,7 @@ type RunningMode =
     | CmdLine of Input * string // A file or a directory to process and the output directory.
     | Window of string option // An optional path to a file to open can be given in window mode.
 
-type Arguments = RunningMode * bool
+type Arguments = RunningMode * bool // bool : true if in debug mode.
 
 let parseArgs (args : string[]) : Arguments =
 
@@ -40,18 +40,7 @@ let parseArgs (args : string[]) : Arguments =
     runningMode, Array.exists ((=) "--debug") args
 
 let showArgsHelp () =
-    printfn "Usage of Parasitemia :"
-    printfn "Non-interactive mode:"
-    printfn "  %s (--folder <folder>|--file <file>) --output <folder> [--debug]" System.AppDomain.CurrentDomain.FriendlyName
-    printfn "    --folder <folder> : an input folder containing images to analyze"
-    printfn "    --file <file> : an image file to be analyzed"
-    printfn "    --output <folder> : a folder to put the results"
-    printfn "    --debug : output more information like intermediate images if set"
-
-    printfn "Interactive mode:"
-    printfn "  %s [<document-file>] [--debug]" System.AppDomain.CurrentDomain.FriendlyName
-    printfn "    <document-file> : a PIAZ file to automatically open at startup"
-    printfn "    --debug : output information like intermediate images if set in the current directory"
+    Console.WriteLine Utils.argsHelp
 
 [<System.Runtime.InteropServices.DllImport "kernel32.dll">]
 extern bool AttachConsole (int dwProcessId)
@@ -93,23 +82,23 @@ let main args =
 
                         use resultFile = new StreamWriter (new FileStream (Path.Combine (output, "results.txt"), FileMode.Append, FileAccess.Write))
 
-                        let images = [ for file in files -> Path.GetFileNameWithoutExtension (FileInfo(file).Name), config.Copy(), new Image<Bgr, byte> (file) ]
+                        let images = [ for file in files -> Path.GetFileNameWithoutExtension (FileInfo(file).Name), config.Copy (), new Image<Bgr, byte> (file) ]
 
                         Log.LogWithTime Severity.INFO (
                             fun () ->
                                 match ParasitemiaCore.Analysis.doMultipleAnalysis images None with
                                 | Some results ->
-                                    for id, cells in results do
+                                    for id, result in results do
                                         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
+                                        let total, infected = countCells result.Cells
                                         fprintf resultFile "File: %s %d %d %.2f (diameter: %O)\n" id total infected (100. * (float infected) / (float total)) config.RBCRadius
                                 | None ->
                                     fprintf resultFile "Analysis aborted"
                                 Some ()
                         ) "Whole analyze" |> ignore
 
-                        Log.RmListener (listener)
+                        Log.RmListener listener
                         0
 
                     | Window fileToOpen ->
diff --git a/Parasitemia/ParasitemiaUI/SourceImage.fs b/Parasitemia/ParasitemiaUI/SourceImage.fs
new file mode 100644 (file)
index 0000000..2330227
--- /dev/null
@@ -0,0 +1,88 @@
+namespace ParasitemiaUI
+
+open System
+open System.Windows
+open System.Windows.Media
+
+open Emgu.CV
+open Emgu.CV.Structure
+
+open Types
+
+type SourceImage (num : int, name : string, config : ParasitemiaCore.Config.Config, dateLastAnalysis : DateTime, img : Image<Bgr, byte>, rbcs : RBC list) =
+    let mutable num = num
+    let mutable name = name
+    let mutable config = config
+    let mutable dateLastAnalysis = dateLastAnalysis // UTC.
+    let img = img
+    let mutable rbcs = rbcs
+    let mutable healthyRBCBrightness = 1.f
+    let mutable infectedRBCBrightness = 1.f
+
+    let mutable averageRBCSize = 1.
+
+    let healthyRBColor = Color.FromRgb (255uy, 255uy, 0uy) // Yellow-green.
+    let infectedRBColor = Color.FromRgb (255uy, 0uy, 40uy) // Red with a bit of blue.
+
+    let updateAverageRBCSize () =
+        averageRBCSize <-
+            rbcs
+            |> List.collect (fun rbc -> [ rbc.size.Width; rbc.size.Height ])
+            |> List.average
+
+    do
+        updateAverageRBCSize ()
+
+    member this.Num with get () = num and set value = num <- value
+
+    member this.Name with get () = name and set value = name <- value
+
+    member this.Config = config
+
+    member this.DateLastAnalysis with get () = dateLastAnalysis and set value = dateLastAnalysis <- value
+
+    member this.Img = img
+
+    member this.RBCs
+        with get () = rbcs
+        and set value = rbcs <- value
+
+    member this.ImageParasitemia : int * int =
+        List.length rbcs,
+        rbcs |> List.fold (fun nbInfected rbc -> if rbc.infected then nbInfected + 1 else nbInfected) 0
+
+    member this.ImageNbManuallyChangedRBC (setAsInfected : bool) : int * int =
+        List.length rbcs,
+        rbcs |> List.fold (fun nb rbc -> if rbc.setManually && rbc.infected = setAsInfected then nb + 1 else nb) 0
+
+    member this.ImageNbManuallyChangedRBCStr (setAsInfected : bool) : string =
+        Utils.percentText (this.ImageNbManuallyChangedRBC setAsInfected)
+
+    member this.ImageManuallyChangedRBC (setAsInfected : bool) : int seq =
+        query {
+            for rbc in rbcs do
+            where (rbc.setManually && rbc.infected = setAsInfected)
+            select rbc.num
+        }
+
+    member this.ImageManuallyChangedRBCStr (setAsInfected : bool) : string =
+        let listStr = Utils.listAsStr <| this.ImageManuallyChangedRBC setAsInfected
+        if listStr = "" then
+            ""
+        else
+            "[" + listStr + "]"
+
+    member this.HealthyRBCBrightness with get () = healthyRBCBrightness and set value = healthyRBCBrightness <- value
+    member this.InfectedRBCBrightness with get () = infectedRBCBrightness and set value = infectedRBCBrightness <- value
+
+    member this.HealthyRBCColor : SolidColorBrush =
+        let mutable color = healthyRBColor * healthyRBCBrightness
+        color.A <- 255uy
+        SolidColorBrush (color)
+
+    member this.InfectedRBCColor : SolidColorBrush =
+        let mutable color = infectedRBColor * infectedRBCBrightness
+        color.A <- 255uy
+        SolidColorBrush (color)
+
+    member this.AverageRBCSize = averageRBCSize
\ No newline at end of file
index 3355a42..129e6a6 100644 (file)
@@ -25,36 +25,11 @@ type State (defaultConfig : ParasitemiaCore.Config.Config) =
                 alteredSinceLastSave <- true
                 patientID <- id
 
-    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.ImageNbManuallyChangedRBC (srcImg : SourceImage) (setAsInfected : bool) : int * int =
-        List.length srcImg.rbcs,
-        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
         |> Seq.fold (
             fun (nbTotal, nbTotalInfected) srcImg ->
-                let nb, nbInfected = this.ImageParasitemia srcImg
+                let nb, nbInfected = srcImg.ImageParasitemia
                 nbTotal + nb, nbTotalInfected + nbInfected
         ) (0, 0)
 
@@ -89,18 +64,7 @@ type State (defaultConfig : ParasitemiaCore.Config.Config) =
     /// </summary>
     /// <exception cref="System.IOException">If the image cannot be read</exception>
     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 = []
-                img = new Image<Bgr, byte> (filePath)
-                healthyRBCBrightness = 1.f
-                infectedRBCBrightness = 1.f
-            }
-
+        let srcImg = SourceImage (sourceImages.Count + 1, System.IO.FileInfo(filePath).Name, defaultConfig.Copy (), DateTime (0L), new Image<Bgr, byte> (filePath), [])
         sourceImages.Add srcImg
         if sourceImages.Count = 1 then
             this.CurrentImage <- Some sourceImages.[0]
@@ -118,24 +82,24 @@ type State (defaultConfig : ParasitemiaCore.Config.Config) =
             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)
+        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
+        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)
+    member this.SetResult (imgNum : int) (result : ParasitemiaCore.Types.AnalysisResult) =
+        let sourceImage = sourceImages.Find (fun srcImg -> srcImg.Num = imgNum)
 
-        let w = sourceImage.img.Width
-        let h = sourceImage.img.Height
+        let w = sourceImage.Img.Width
+        let h = sourceImage.Img.Height
 
-        sourceImage.dateLastAnalysis <- DateTime.UtcNow
+        sourceImage.DateLastAnalysis <- DateTime.UtcNow
 
         // To match with previously manually altered RBC.
-        let manuallyAlteredPreviousRBCS = sourceImage.rbcs |> List.filter (fun rbc -> rbc.setManually)
-        let tolerance = (float sourceImage.config.RBCRadius.Pixel) * 0.5 // +/-.
+        let manuallyAlteredPreviousRBCS = sourceImage.RBCs |> List.filter (fun rbc -> rbc.setManually)
+        let tolerance = (float sourceImage.Config.RBCRadius.Pixel) * 0.5 // +/-.
         let getPreviousManuallyAlteredRBC (center : Point) : RBC option =
             manuallyAlteredPreviousRBCS
             |> List.tryFind (
@@ -146,8 +110,8 @@ type State (defaultConfig : ParasitemiaCore.Config.Config) =
                     rbc.center.Y < center.Y + tolerance
             )
 
-        sourceImage.rbcs <-
-            cells
+        sourceImage.RBCs <-
+            result.Cells
             |> List.filter (fun cell -> match cell.cellClass with ParasitemiaCore.Types.HealthyRBC | ParasitemiaCore.Types.InfectedRBC -> true | _ -> false )
             |> List.sortByDescending (fun cell -> cell.nucleusArea, (w - cell.center.X) + (h - cell.center.Y))
             |> List.mapi (
index ada2a1f..3a3cf56 100644 (file)
@@ -11,9 +11,6 @@ open Newtonsoft.Json
 
 open ParasitemiaCore.UnitsOfMeasure
 
-let healthyRBColor = Color.FromRgb (255uy, 255uy, 0uy) // Yellow-green.
-let infectedRBColor = Color.FromRgb (255uy, 0uy, 40uy) // Red with a bit of blue.
-
 type RBC =
     {
         num : int
@@ -29,30 +26,6 @@ type RBC =
         infectedArea : int
     }
 
-type SourceImage =
-    {
-        mutable num : int
-        mutable name : string
-
-        mutable config : ParasitemiaCore.Config.Config
-        mutable dateLastAnalysis : DateTime // UTC.
-        img : Image<Bgr, byte>
-        mutable rbcs : RBC list
-
-        mutable healthyRBCBrightness : float32
-        mutable infectedRBCBrightness : float32
-    }
-    with
-        member this.HealthyRBCColor : SolidColorBrush =
-            let mutable color = healthyRBColor * this.healthyRBCBrightness
-            color.A <- 255uy
-            SolidColorBrush (color)
-
-        member this.InfectedRBCColor : SolidColorBrush =
-            let mutable color = infectedRBColor * this.infectedRBCBrightness
-            color.A <- 255uy
-            SolidColorBrush (color)
-
 type PredefinedPPI =
     {
         ppi : int<ppi>
index 7a30174..e7c9784 100644 (file)
@@ -27,6 +27,7 @@ let sensorSizesFilepath = Path.Combine (roamingDir, sensorSizesFilename)
 
 let private savePredefinedPPIToFile (predefinedPPI : PredefinedPPI list) =
     try
+        Directory.CreateDirectory roamingDir |> ignore
         use file = new StreamWriter (predefinedPPIFilepath)
         file.Write (JsonConvert.SerializeObject (predefinedPPI, JsonSerializerSettings (Formatting = Formatting.Indented)))
     with
@@ -35,6 +36,7 @@ let private savePredefinedPPIToFile (predefinedPPI : PredefinedPPI list) =
 
 let private saveSensorSizesToFile (sensorSizes : SensorSize list) =
     try
+        Directory.CreateDirectory roamingDir |> ignore
         use file = new StreamWriter (sensorSizesFilepath)
         file.Write (JsonConvert.SerializeObject (sensorSizes, JsonSerializerSettings (Formatting = Formatting.Indented)))
     with
@@ -46,7 +48,7 @@ let predefinedPPI : PredefinedPPI list =
         use file = new StreamReader (predefinedPPIFilepath)
         JsonConvert.DeserializeObject<PredefinedPPI list> (file.ReadToEnd ())
     with
-    | ex ->
+    | _ex ->
         savePredefinedPPIToFile defaultPredefinedPPI
         defaultPredefinedPPI
 
@@ -55,6 +57,21 @@ let sensorSizes : SensorSize list =
         use file = new StreamReader (sensorSizesFilepath)
         JsonConvert.DeserializeObject<SensorSize list> (file.ReadToEnd ())
     with
-    | ex ->
+    | _ex ->
         saveSensorSizesToFile defaultSensorSizes
         defaultSensorSizes
+
+let argsHelp =
+    let programName = System.AppDomain.CurrentDomain.FriendlyName
+    "Usage of Parasitemia:\n" +
+    "Non-interactive mode:\n" +
+    (sprintf "  %s (--folder <folder>|--file <file>) --output <folder> [--debug]\n" programName) +
+    "    --folder <folder> : an input folder containing images to analyze\n" +
+    "    --file <file> : an image file to be analyzed\n" +
+    "    --output <folder> : a folder to put the results\n" +
+    "    --debug : output more information like intermediate images (it takes more CPU and memory)\n" +
+
+    "Interactive mode:\n" +
+    (sprintf "  %s [<document-file>] [--debug]\n" programName) +
+    "    <document-file> : a PIAZ file to automatically open at startup\n" +
+    "    --debug : output information like intermediate images in the current directory (it takes more CPU and memory)"
\ No newline at end of file
diff --git a/Parasitemia/ParasitemiaUI/XAML/CommandLineArgumentsWindow.xaml b/Parasitemia/ParasitemiaUI/XAML/CommandLineArgumentsWindow.xaml
new file mode 100644 (file)
index 0000000..8952ea3
--- /dev/null
@@ -0,0 +1,17 @@
+<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="CommandLineArgumentsWindow" Height="420" Width="620" Title="Command Line Arguments" Icon="pack://application:,,,/Resources/icon.ico" ResizeMode="CanResizeWithGrip">
+   <Grid Margin="3">
+        <Grid.RowDefinitions>
+            <RowDefinition Height="Auto"/>
+            <RowDefinition Height="*"/>
+            <RowDefinition Height="Auto"/>
+      </Grid.RowDefinitions>
+        <TextBlock Margin="3">Parasitemia can be launched via a command line, here are the possible arguments</TextBlock>
+        <TextBox x:Name="txtCommandLineArguments" Margin="6" Grid.Row="1" TextWrapping="Wrap" FontFamily="Courier New" FontSize="11" IsReadOnly="True" />
+        <Button x:Name="butClose" Content="Close" HorizontalAlignment="Right" Margin="3" VerticalAlignment="Bottom" Width="75" Grid.Row="2" Height="20"/>
+   </Grid>
+</Window>
\ No newline at end of file
diff --git a/Parasitemia/ParasitemiaUI/XAML/CommandLineArgumentsWindow.xaml.fs b/Parasitemia/ParasitemiaUI/XAML/CommandLineArgumentsWindow.xaml.fs
new file mode 100644 (file)
index 0000000..716cb63
--- /dev/null
@@ -0,0 +1,6 @@
+namespace ParasitemiaUI.Views
+
+open FsXaml
+
+type CommandLineArgumentsWindow = XAML<"XAML/CommandLineArgumentsWindow.xaml">
+
index 9ce3f20..ac125de 100644 (file)
@@ -25,6 +25,7 @@
             <MenuItem x:Name="menuHightlightRBC" Header="_Highlight All Erythrocytes" IsCheckable="True" InputGestureText="Ctrl+H" />
          </MenuItem>
          <MenuItem x:Name="menuHelp" Header="_Help">
+            <MenuItem x:Name="menuCommandLineArguments" Header="_Command Line Arguments" />
             <MenuItem x:Name="menuAbout" Header="_About" />
          </MenuItem>
       </Menu>
index 124dc6f..41c1fc6 100644 (file)
@@ -16,7 +16,7 @@
       <Rectangle x:Name="border" Fill="#00000000" />
       <Polygon x:Name="manuallyAdded" Points="0,0 12,0, 12,12" Fill="Black" HorizontalAlignment="Right" VerticalAlignment="Top" />
       <Border HorizontalAlignment="Right" VerticalAlignment="Bottom" Margin="0,0,3,3" Background="#66000000" CornerRadius="5">
-         <TextBlock x:Name="txtRBCNumber" Padding="2" Text="42" Foreground="White" />
+         <TextBlock x:Name="txtRBCNumber" Padding="2" Text="42" Foreground="White" FontSize="12" />
       </Border>
    </Grid>
 </UserControl>
\ No newline at end of file
index 30067c6..7e4b4ac 100644 (file)
@@ -2,12 +2,12 @@
 <packages>
   <package id="EmguCV" version="3.1.0.1" targetFramework="net452" />
   <package id="Expression.Blend.Sdk" version="1.0.2" targetFramework="net46" />
-  <package id="FSharp.Core" version="4.2.1" targetFramework="net452" />
+  <package id="FSharp.Core" version="4.2.3" targetFramework="net452" />
   <package id="FSharp.ViewModule.Core" version="1.0.7.0" targetFramework="net462" />
   <package id="FsXaml.Wpf" version="3.1.6" targetFramework="net462" />
   <package id="Newtonsoft.Json" version="10.0.3" targetFramework="net452" />
   <package id="OpenTK" version="2.0.0" targetFramework="net452" />
   <package id="OpenTK.GLControl" version="1.1.2349.61993" targetFramework="net452" />
-  <package id="System.ValueTuple" version="4.3.1" targetFramework="net452" />
+  <package id="System.ValueTuple" version="4.4.0" targetFramework="net452" />
   <package id="ZedGraph" version="5.1.7" targetFramework="net452" />
 </packages>
\ No newline at end of file
index d0cdfa5..75c810e 100644 (file)
@@ -1,6 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
 <Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
-  <Import Project="..\..\packages\xunit.runner.visualstudio.2.2.0\build\net20\xunit.runner.visualstudio.props" Condition="Exists('..\..\packages\xunit.runner.visualstudio.2.2.0\build\net20\xunit.runner.visualstudio.props')" />
+  <Import Project="..\..\packages\xunit.runner.visualstudio.2.3.0\build\net20\xunit.runner.visualstudio.props" Condition="Exists('..\..\packages\xunit.runner.visualstudio.2.3.0\build\net20\xunit.runner.visualstudio.props')" />
+  <Import Project="..\..\packages\xunit.core.2.3.0\build\xunit.core.props" Condition="Exists('..\..\packages\xunit.core.2.3.0\build\xunit.core.props')" />
   <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
   <PropertyGroup>
     <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
   </ItemGroup>
   <ItemGroup>
     <Reference Include="FSharp.Core">
-      <HintPath>..\..\packages\FSharp.Core.4.2.1\lib\net45\FSharp.Core.dll</HintPath>
+      <HintPath>..\..\packages\FSharp.Core.4.2.3\lib\net45\FSharp.Core.dll</HintPath>
     </Reference>
     <Reference Include="System" />
     <Reference Include="System.Core" />
     <Reference Include="System.ValueTuple">
-      <HintPath>..\..\packages\System.ValueTuple.4.3.1\lib\netstandard1.0\System.ValueTuple.dll</HintPath>
+      <HintPath>..\..\packages\System.ValueTuple.4.4.0\lib\netstandard1.0\System.ValueTuple.dll</HintPath>
     </Reference>
     <Reference Include="xunit.abstractions">
       <HintPath>..\..\packages\xunit.abstractions.2.0.1\lib\net35\xunit.abstractions.dll</HintPath>
     </Reference>
     <Reference Include="xunit.assert">
-      <HintPath>..\..\packages\xunit.assert.2.2.0\lib\netstandard1.1\xunit.assert.dll</HintPath>
+      <HintPath>..\..\packages\xunit.assert.2.3.0\lib\netstandard1.1\xunit.assert.dll</HintPath>
     </Reference>
     <Reference Include="xunit.core">
-      <HintPath>..\..\packages\xunit.extensibility.core.2.2.0\lib\netstandard1.1\xunit.core.dll</HintPath>
+      <HintPath>..\..\packages\xunit.extensibility.core.2.3.0\lib\netstandard1.1\xunit.core.dll</HintPath>
     </Reference>
     <Reference Include="xunit.execution.desktop">
-      <HintPath>..\..\packages\xunit.extensibility.execution.2.2.0\lib\net452\xunit.execution.desktop.dll</HintPath>
+      <HintPath>..\..\packages\xunit.extensibility.execution.2.3.0\lib\net452\xunit.execution.desktop.dll</HintPath>
     </Reference>
   </ItemGroup>
   <ItemGroup>
     <PropertyGroup>
       <ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them.  For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
     </PropertyGroup>
-    <Error Condition="!Exists('..\..\packages\xunit.runner.visualstudio.2.2.0\build\net20\xunit.runner.visualstudio.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\xunit.runner.visualstudio.2.2.0\build\net20\xunit.runner.visualstudio.props'))" />
+    <Error Condition="!Exists('..\..\packages\xunit.core.2.3.0\build\xunit.core.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\xunit.core.2.3.0\build\xunit.core.props'))" />
+    <Error Condition="!Exists('..\..\packages\xunit.core.2.3.0\build\xunit.core.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\xunit.core.2.3.0\build\xunit.core.targets'))" />
+    <Error Condition="!Exists('..\..\packages\xunit.runner.visualstudio.2.3.0\build\net20\xunit.runner.visualstudio.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\xunit.runner.visualstudio.2.3.0\build\net20\xunit.runner.visualstudio.props'))" />
   </Target>
+  <Import Project="..\..\packages\xunit.core.2.3.0\build\xunit.core.targets" Condition="Exists('..\..\packages\xunit.core.2.3.0\build\xunit.core.targets')" />
   <!-- 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.
   <Target Name="BeforeBuild">
index 6d282b2..2921e92 100644 (file)
@@ -1,12 +1,13 @@
 <?xml version="1.0" encoding="utf-8"?>
 <packages>
-  <package id="FSharp.Core" version="4.2.1" targetFramework="net452" />
-  <package id="System.ValueTuple" version="4.3.1" targetFramework="net452" />
-  <package id="xunit" version="2.2.0" targetFramework="net452" />
+  <package id="FSharp.Core" version="4.2.3" targetFramework="net452" />
+  <package id="System.ValueTuple" version="4.4.0" targetFramework="net452" />
+  <package id="xunit" version="2.3.0" targetFramework="net452" />
   <package id="xunit.abstractions" version="2.0.1" targetFramework="net452" />
-  <package id="xunit.assert" version="2.2.0" targetFramework="net452" />
-  <package id="xunit.core" version="2.2.0" targetFramework="net452" />
-  <package id="xunit.extensibility.core" version="2.2.0" targetFramework="net452" />
-  <package id="xunit.extensibility.execution" version="2.2.0" targetFramework="net452" />
-  <package id="xunit.runner.visualstudio" version="2.2.0" targetFramework="net452" developmentDependency="true" />
+  <package id="xunit.analyzers" version="0.7.0" targetFramework="net452" />
+  <package id="xunit.assert" version="2.3.0" targetFramework="net452" />
+  <package id="xunit.core" version="2.3.0" targetFramework="net452" />
+  <package id="xunit.extensibility.core" version="2.3.0" targetFramework="net452" />
+  <package id="xunit.extensibility.execution" version="2.3.0" targetFramework="net452" />
+  <package id="xunit.runner.visualstudio" version="2.3.0" targetFramework="net452" developmentDependency="true" />
 </packages>
\ No newline at end of file