--- /dev/null
+module ParasitemiaCore.Analysis
+
+open System
+open System.Linq
+open System.Drawing
+
+open FSharp.Collections.ParallelSeq
+
+open Emgu.CV
+open Emgu.CV.Structure
+
+open Logger
+
+open Utils
+open Morpho
+open ImgTools
+open Config
+open Types
+
+/// <summary>
+/// Analyze the given image and detect reb blood cell (RBC) in it.
+/// </summary>
+/// <param name="img">The image</param>
+/// <param name="name">The name, used during logging</param>
+/// <param name="config">The configuration, must not be shared with another analysis</param>
+/// <param name="reportProgress">An optional function to report progress and/or cancel the process.
+/// 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 =
+
+ // To report the progress of this function from 0 to 100.
+ // Return 'None' if the process must be aborted.
+ let reportWithVal (percent: int) (value: 'a) : 'a option =
+ match reportProgress with
+ | Some f ->
+ if f percent
+ then Some value
+ else None
+ | _ -> Some value
+
+ let report (percent: int) : unit option =
+ reportWithVal percent ()
+
+ let inline buildLogWithName (text: string) = sprintf "(%s) %s" name text
+ let logWithName mess = Log.User(buildLogWithName mess)
+ let inline logTimeWithName (text: string) (f: unit -> 'a option) : 'a option = Log.LogWithTime((buildLogWithName text), Severity.USER, f)
+
+ maybe {
+ do! report 0
+
+ logWithName "Starting analysis ..."
+
+ use img_float = img.Convert<Bgr, float32>()
+
+ use img_RBC = img_float.[1] // mergeChannelsWithProjection img_float config.Parameters.averageColor_RBC config.Parameters.averageColor_BG 255.
+ use img_RBC_filtered = gaussianFilter img_RBC config.LPFStandardDeviationRBC
+
+ use img_parasites = img_float.[2] // mergeChannelsWithProjection img_float config.Parameters.averageColor_Parasite config.Parameters.averageColor_RBC 255.
+ use img_parasites_filtered = gaussianFilter img_parasites config.LPFStandardDeviationParasite
+
+ logWithName (sprintf "Nominal erythrocyte diameter: %A" config.RBCRadiusByResolution)
+
+ let initialAreaOpening = int <| config.RBCRadiusByResolution.Area * config.Parameters.ratioAreaPaleCenter * 1.1f // We do an area opening a little larger to avoid to do a second one in the case the radius found is near the initial one.
+ do! logTimeWithName "First area opening" (fun () -> areaOpenF img_RBC_filtered initialAreaOpening; report 10)
+
+ let range =
+ let delta = config.Parameters.granulometryRange * config.RBCRadiusByResolution.Pixel
+ int <| config.RBCRadiusByResolution.Pixel - delta, int <| config.RBCRadiusByResolution.Pixel + delta
+ let! radius = logTimeWithName "Granulometry (area)" (fun() -> reportWithVal 10 (Granulometry.findRadiusByAreaClosing img_RBC_filtered range |> float32))
+ config.SetRBCRadius <| radius
+
+ logWithName (sprintf "Found erythrocyte diameter: %A" config.RBCRadius)
+
+ do! report 20
+
+ do!
+ let secondAreaOpening = int <| config.RBCRadius.Area * config.Parameters.ratioAreaPaleCenter
+ if secondAreaOpening > initialAreaOpening
+ then
+ logTimeWithName "Second area opening" (fun () -> areaOpenF img_RBC_filtered secondAreaOpening; report 30)
+ else
+ report 30
+
+ // Remove pale centers from the parasites image.
+ areaOpenF img_parasites_filtered (int <| config.RBCRadiusByResolution.Area * config.Parameters.ratioAreaPaleCenter)
+
+ // Removing parasites.
+ areaCloseF img_RBC_filtered (roundInt <| Const.PI * config.RBCRadius.ParasiteRadius ** 2.f)
+
+ let! parasites, imgWhitoutParasite, imgWithoutNucleus =
+ logTimeWithName "Parasites segmentation" (fun () -> reportWithVal 40 (ParasitesMarker.find img_parasites_filtered config))
+
+ let! edges, xGradient, yGradient = logTimeWithName "Finding edges" (fun () ->
+ let edges, xGradient, yGradient = Edges.find img_RBC_filtered
+ removeArea edges (config.RBCRadius.Pixel ** 2.f / 50.f |> int)
+ reportWithVal 50 (edges, xGradient, yGradient))
+
+ let! matchingEllipses = logTimeWithName "Finding ellipses" (fun () -> reportWithVal 60 (Ellipse.find edges xGradient yGradient config))
+
+ let! prunedEllipses = logTimeWithName "Ellipses pruning" (fun () -> reportWithVal 80 (matchingEllipses.PrunedEllipses))
+
+ let! cells = logTimeWithName "Classifier" (fun () -> reportWithVal 100 (Classifier.findCells prunedEllipses parasites img_RBC_filtered config))
+
+ logWithName "Analysis finished"
+
+ do
+ // Output pictures if debug flag is set.
+ match config.Debug with
+ | DebugOn output ->
+ let dirPath = System.IO.Path.Combine(output, name)
+ System.IO.Directory.CreateDirectory dirPath |> ignore
+
+ let buildFileName postfix = System.IO.Path.Combine(dirPath, name + postfix)
+
+ IO.saveMat (edges * 255.0) (buildFileName " - edges.png")
+
+ IO.saveImg parasites.darkStain (buildFileName " - parasites - dark stain.png")
+ IO.saveImg parasites.parasite (buildFileName " - parasites - stain.png")
+ IO.saveImg parasites.nucleus (buildFileName " - parasites - infection.png")
+
+ let imgAllEllipses = img.Copy()
+ Drawing.drawEllipses imgAllEllipses matchingEllipses.Ellipses (Bgr(255.0, 255.0, 255.0)) 0.04
+ IO.saveImg imgAllEllipses (buildFileName " - ellipses - all.png")
+
+ let imgEllipses = img_RBC_filtered.Convert<Bgr, byte>()
+ Drawing.drawEllipses imgEllipses prunedEllipses (Bgr(0.0, 240.0, 240.0)) 1.0
+ IO.saveImg imgEllipses (buildFileName " - ellipses.png")
+
+ let imgCells = img.Copy()
+ Drawing.drawCells imgCells false cells
+ IO.saveImg imgCells (buildFileName " - cells.png")
+
+ let imgCells' = img.Copy()
+ Drawing.drawCells imgCells' true cells
+ IO.saveImg imgCells' (buildFileName " - cells - full.png")
+
+ let filteredGreenMaxima = gaussianFilter img_RBC config.LPFStandardDeviationRBC
+ for m in findMaxima filteredGreenMaxima do
+ Drawing.drawPoints filteredGreenMaxima m 255.f
+ IO.saveImg filteredGreenMaxima (buildFileName " - filtered - maxima.png")
+
+ IO.saveImg imgWhitoutParasite (buildFileName " - filtered closed stain.png")
+ IO.saveImg imgWithoutNucleus (buildFileName " - filtered closed infection.png")
+
+ IO.saveImg img_RBC_filtered (buildFileName " - source - RBC.png")
+ IO.saveImg img_parasites_filtered (buildFileName " - source - parasites.png")
+
+ IO.saveImg (normalize img_float.[2] 255.) (buildFileName " - source - red.png")
+ IO.saveImg (normalize img_float.[1] 255.) (buildFileName " - source - green.png")
+ IO.saveImg (normalize img_float.[0] 255.) (buildFileName " - source - blue.png")
+ | _ -> ()
+
+ return cells }
+
+/// <summary>
+/// Do multiple analyses on the same time. The number of concurrent process depends if the number of the core.
+/// </summary>
+/// <param name="imgs">The images: (name * configuration * image)</param>
+/// <param name="reportProgress">An optional function to report progress and/or cancel the process.
+/// 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 report (percent: int) : bool =
+ match reportProgress with
+ | Some f -> f percent
+ | _ -> true
+
+ let progressPerAnalysis = System.Collections.Concurrent.ConcurrentDictionary<string, int>()
+ let nbImgs = List.length imgs
+
+ let reportProgressImg (id: string) (progress: int) =
+ progressPerAnalysis.AddOrUpdate(id, progress, (fun _ _ -> progress)) |> ignore
+ report (progressPerAnalysis.Values.Sum() / nbImgs)
+
+ let n = Environment.ProcessorCount
+
+ let results =
+ imgs
+ |> PSeq.choose (
+ fun (id, config, img) ->
+ match doAnalysis img id config (Some (fun p -> reportProgressImg id p)) with
+ | Some result -> Some (id, result)
+ | None -> None)
+ |> PSeq.withDegreeOfParallelism n
+ |> PSeq.toList
+
+ // If one of the analyses has been aborted we return 'None'.
+ if List.length results <> List.length imgs
+ then None
+ else Some results
+
// 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.4")>]
-[<assembly: AssemblyFileVersion("1.0.0.4")>]
+[<assembly: AssemblyVersion("1.0.0.5")>]
+[<assembly: AssemblyFileVersion("1.0.0.5")>]
do
()
\ No newline at end of file
minimumCellAreaFactor: float32 } // Factor of the mean RBC area. A cell with an area below this will be rejected.
let defaultParameters = {
- rbcDiameter = 8.<μm>
+ rbcDiameter = 7.5<μm>
resolution = 220.e3<ppi> // 220.e3<ppi> Correspond to 50X.
ratioAreaPaleCenter = 2.f / 5.f // The ratio between an RBC area and the area of the its pale center.
+++ /dev/null
-module ParasitemiaCore.Analysis
-
-open System
-open System.Linq
-open System.Drawing
-
-open FSharp.Collections.ParallelSeq
-
-open Emgu.CV
-open Emgu.CV.Structure
-
-open Logger
-
-open Utils
-open Morpho
-open ImgTools
-open Config
-open Types
-
-/// <summary>
-/// Analyze the given image and detect reb blood cell (RBC) in it.
-/// </summary>
-/// <param name="img">The image</param>
-/// <param name="name">The name, used during logging</param>
-/// <param name="config">The configuration, must not be shared with another analysis</param>
-/// <param name="reportProgress">An optional function to report progress and/or cancel the process.
-/// 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 =
-
- // To report the progress of this function from 0 to 100.
- // Return 'None' if the process must be aborted.
- let reportWithVal (percent: int) (value: 'a) : 'a option =
- match reportProgress with
- | Some f ->
- if f percent
- then Some value
- else None
- | _ -> Some value
-
- let report (percent: int) : unit option =
- reportWithVal percent ()
-
- let inline buildLogWithName (text: string) = sprintf "(%s) %s" name text
- let logWithName mess = Log.User(buildLogWithName mess)
- let inline logTimeWithName (text: string) (f: unit -> 'a option) : 'a option = Log.LogWithTime((buildLogWithName text), Severity.USER, f)
-
- maybe {
- do! report 0
-
- logWithName "Starting analysis ..."
-
- use img_float = img.Convert<Bgr, float32>()
-
- use img_RBC = img_float.[1] // mergeChannelsWithProjection img_float config.Parameters.averageColor_RBC config.Parameters.averageColor_BG 255.
- use img_RBC_filtered = gaussianFilter img_RBC config.LPFStandardDeviationRBC
-
- use img_parasites = img_float.[2] // mergeChannelsWithProjection img_float config.Parameters.averageColor_Parasite config.Parameters.averageColor_RBC 255.
- use img_parasites_filtered = gaussianFilter img_parasites config.LPFStandardDeviationParasite
-
- logWithName (sprintf "Nominal erythrocyte diameter: %A" config.RBCRadiusByResolution)
-
- let initialAreaOpening = int <| config.RBCRadiusByResolution.Area * config.Parameters.ratioAreaPaleCenter * 1.1f // We do an area opening a little larger to avoid to do a second one in the case the radius found is near the initial one.
- do! logTimeWithName "First area opening" (fun () -> areaOpenF img_RBC_filtered initialAreaOpening; report 10)
-
- let range =
- let delta = config.Parameters.granulometryRange * config.RBCRadiusByResolution.Pixel
- int <| config.RBCRadiusByResolution.Pixel - delta, int <| config.RBCRadiusByResolution.Pixel + delta
- let! radius = logTimeWithName "Granulometry (area)" (fun() -> reportWithVal 10 (Granulometry.findRadiusByAreaClosing img_RBC_filtered range |> float32))
- config.SetRBCRadius <| radius
-
- logWithName (sprintf "Found erythrocyte diameter: %A" config.RBCRadius)
-
- do! report 20
-
- do!
- let secondAreaOpening = int <| config.RBCRadius.Area * config.Parameters.ratioAreaPaleCenter
- if secondAreaOpening > initialAreaOpening
- then
- logTimeWithName "Second area opening" (fun () -> areaOpenF img_RBC_filtered secondAreaOpening; report 30)
- else
- report 30
-
- // Removing parasites.
- areaCloseF img_RBC_filtered (roundInt <| Const.PI * config.RBCRadius.ParasiteRadius ** 2.f)
-
- let! parasites, imgWhitoutParasite, imgWithoutNucleus =
- logTimeWithName "Parasites segmentation" (fun () -> reportWithVal 40 (ParasitesMarker.find img_parasites_filtered config))
-
- let! edges, xGradient, yGradient = logTimeWithName "Finding edges" (fun () ->
- let edges, xGradient, yGradient = Edges.find img_RBC_filtered
- removeArea edges (config.RBCRadius.Pixel ** 2.f / 50.f |> int)
- reportWithVal 50 (edges, xGradient, yGradient))
-
- let! matchingEllipses = logTimeWithName "Finding ellipses" (fun () -> reportWithVal 60 (Ellipse.find edges xGradient yGradient config))
-
- let! prunedEllipses = logTimeWithName "Ellipses pruning" (fun () -> reportWithVal 80 (matchingEllipses.PrunedEllipses))
-
- let! cells = logTimeWithName "Classifier" (fun () -> reportWithVal 100 (Classifier.findCells prunedEllipses parasites img_RBC_filtered config))
-
- logWithName "Analysis finished"
-
- do
- // Output pictures if debug flag is set.
- match config.Debug with
- | DebugOn output ->
- let dirPath = System.IO.Path.Combine(output, name)
- System.IO.Directory.CreateDirectory dirPath |> ignore
-
- let buildFileName postfix = System.IO.Path.Combine(dirPath, name + postfix)
-
- IO.saveMat (edges * 255.0) (buildFileName " - edges.png")
-
- IO.saveImg parasites.darkStain (buildFileName " - parasites - dark stain.png")
- IO.saveImg parasites.parasite (buildFileName " - parasites - stain.png")
- IO.saveImg parasites.nucleus (buildFileName " - parasites - infection.png")
-
- let imgAllEllipses = img.Copy()
- Drawing.drawEllipses imgAllEllipses matchingEllipses.Ellipses (Bgr(255.0, 255.0, 255.0)) 0.04
- IO.saveImg imgAllEllipses (buildFileName " - ellipses - all.png")
-
- let imgEllipses = img_RBC_filtered.Convert<Bgr, byte>()
- Drawing.drawEllipses imgEllipses prunedEllipses (Bgr(0.0, 240.0, 240.0)) 1.0
- IO.saveImg imgEllipses (buildFileName " - ellipses.png")
-
- let imgCells = img.Copy()
- Drawing.drawCells imgCells false cells
- IO.saveImg imgCells (buildFileName " - cells.png")
-
- let imgCells' = img.Copy()
- Drawing.drawCells imgCells' true cells
- IO.saveImg imgCells' (buildFileName " - cells - full.png")
-
- let filteredGreenMaxima = gaussianFilter img_RBC config.LPFStandardDeviationRBC
- for m in findMaxima filteredGreenMaxima do
- Drawing.drawPoints filteredGreenMaxima m 255.f
- IO.saveImg filteredGreenMaxima (buildFileName " - filtered - maxima.png")
-
- IO.saveImg img_RBC_filtered (buildFileName " - filtered.png")
- IO.saveImg imgWhitoutParasite (buildFileName " - filtered closed stain.png")
- IO.saveImg imgWithoutNucleus (buildFileName " - filtered closed infection.png")
-
- IO.saveImg img_RBC (buildFileName " - source - RBC.png")
- IO.saveImg img_parasites (buildFileName " - source - parasites.png")
-
- IO.saveImg (normalize img_float.[2] 255.) (buildFileName " - source - red.png")
- IO.saveImg (normalize img_float.[1] 255.) (buildFileName " - source - green.png")
- IO.saveImg (normalize img_float.[0] 255.) (buildFileName " - source - blue.png")
- | _ -> ()
-
- return cells }
-
-/// <summary>
-/// Do multiple analyses on the same time. The number of concurrent process depends if the number of the core.
-/// </summary>
-/// <param name="imgs">The images: (name * configuration * image)</param>
-/// <param name="reportProgress">An optional function to report progress and/or cancel the process.
-/// 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 report (percent: int) : bool =
- match reportProgress with
- | Some f -> f percent
- | _ -> true
-
- let progressPerAnalysis = System.Collections.Concurrent.ConcurrentDictionary<string, int>()
- let nbImgs = List.length imgs
-
- let reportProgressImg (id: string) (progress: int) =
- progressPerAnalysis.AddOrUpdate(id, progress, (fun _ _ -> progress)) |> ignore
- report (progressPerAnalysis.Values.Sum() / nbImgs)
-
- let n = Environment.ProcessorCount
-
- let results =
- imgs
- |> PSeq.choose (
- fun (id, config, img) ->
- match doAnalysis img id config (Some (fun p -> reportProgressImg id p)) with
- | Some result -> Some (id, result)
- | None -> None)
- |> PSeq.withDegreeOfParallelism n
- |> PSeq.toList
-
- // If one of the analyses has been aborted we return 'None'.
- if List.length results <> List.length imgs
- then None
- else Some results
-
<Compile Include="MatchingEllipses.fs" />
<Compile Include="Ellipse.fs" />
<Compile Include="Classifier.fs" />
- <Compile Include="MainAnalysis.fs" />
+ <Compile Include="Analysis.fs" />
<Content Include="packages.config" />
</ItemGroup>
<ItemGroup>
member this.Return (x) =
Some x
-let maybe = MaybeBuilder()
\ No newline at end of file
+let maybe = MaybeBuilder()
+
+type Result<'a> =
+ | Success of 'a
+ | Fail of string // Error message.
+
+type ResultBuilder () =
+ member this.Bind (res, f) =
+ match res with
+ | Success value -> f value
+ | fail -> fail
+
+ member this.ReturnFrom (x) = x
+
+let result = ResultBuilder()
\ No newline at end of file
module ParasitemiaCore.UnitsOfMeasure
[<Measure>] type px // Pixel.
+[<Measure>] type mm
[<Measure>] type μm
[<Measure>] type inch
[<Measure>] type ppi = px / inch // Pixel per inch.
let μmInchRatio = 25.4e3<μm/inch>
+let mmInchRatio = 25.4<mm/inch>
let μmToInch(x: float<μm>) : float<inch> = x / μmInchRatio
let inchToμm(x: float<inch>) : float<μm> = x * μmInchRatio
+let mmToInch(x: float<mm>) : float<inch> = x / mmInchRatio
+let inchTomm(x: float<inch>) : float<mm> = x * mmInchRatio
+
open System.Diagnostics
let showWindow (parent: Window) =
- let window = Views.AboutWindow()
- 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 txtAbout: TextBlock = ctrl "txtAbout"
-
- let linkHESSO: Documents.Hyperlink = ctrl "linkHESSO"
- let linkCHUV: Documents.Hyperlink = ctrl "linkCHUV"
- let linkGBurri: Documents.Hyperlink = ctrl "linkGBurri"
+ let win = Views.AboutWindow()
+ win.Root.Owner <- parent
+ win.Root.Left <- parent.Left + parent.ActualWidth / 2. - win.Root.Width / 2.
+ win.Root.Top <- parent.Top + parent.ActualHeight / 2. - win.Root.Height / 2.
let version = System.Reflection.Assembly.GetEntryAssembly().GetName().Version
let txtVersion = sprintf " %d.%d.%d" version.Major version.Minor version.Revision
- txtAbout.Inlines.FirstInline.ElementEnd.InsertTextInRun(txtVersion)
+ win.txtAbout.Inlines.FirstInline.ElementEnd.InsertTextInRun(txtVersion)
let navigateTo = Navigation.RequestNavigateEventHandler(fun obj args ->
Process.Start(ProcessStartInfo(args.Uri.AbsoluteUri)) |> ignore
args.Handled <- true)
- linkHESSO.RequestNavigate.AddHandler(navigateTo);
- linkCHUV.RequestNavigate.AddHandler(navigateTo);
- linkGBurri.RequestNavigate.AddHandler(navigateTo);
+ win.linkHESSO.RequestNavigate.AddHandler(navigateTo);
+ win.linkCHUV.RequestNavigate.AddHandler(navigateTo);
+ win.linkGBurri.RequestNavigate.AddHandler(navigateTo);
#if DEBUG
- txtAbout.Inlines.FirstInline.ElementEnd.InsertTextInRun(" - DEBUG")
+ win.txtAbout.Inlines.FirstInline.ElementEnd.InsertTextInRun(" - DEBUG")
#endif
- butClose.Click.AddHandler(fun obj args -> window.Root.Close())
+ win.butClose.Click.AddHandler(fun obj args -> win.Root.Close())
- window.Root.ShowDialog() |> ignore
+ win.Root.ShowDialog() |> ignore
open Types
let showWindow (parent: Window) (state: State.State) : 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 stackSourceImagesSelection: StackPanel = ctrl "stackSourceImagesSelection"
- let progressBar: ProgressBar = ctrl "progress"
- let textLog: TextBlock = ctrl "textLog"
- let scrollLog: ScrollViewer = ctrl "scrollLog"
+ let win = Views.AnalysisWindow()
+ win.Root.Owner <- parent
+ win.Root.Left <- parent.Left + parent.ActualWidth / 2. - win.Root.Width / 2.
+ win.Root.Top <- parent.Top + parent.ActualHeight / 2. - win.Root.Height / 2.
let logListener =
{ new Logger.IListener with
member this.NewEntry severity mess =
- window.Root.Dispatcher.Invoke(fun () ->
- textLog.Inlines.Add(Documents.Run(mess))
- textLog.Inlines.Add(Documents.LineBreak())
- scrollLog.ScrollToBottom()) }
+ win.Root.Dispatcher.Invoke(fun () ->
+ win.textLog.Inlines.Add(Documents.Run(mess))
+ win.textLog.Inlines.Add(Documents.LineBreak())
+ win.scrollLog.ScrollToBottom()) }
Logger.Log.AddListener(logListener)
let minPPI = 1.
let maxPPI = 10e6
let parseAndValidatePPI (input: string) : float option =
- let res = ref 0.
- if Double.TryParse(input, res) && !res >= minPPI && !res <= maxPPI
- then Some !res
- else None
+ match Double.TryParse(input) with
+ | true, value when value >= minPPI && value <= maxPPI -> Some value
+ | _ -> None
let monitor = Object()
let mutable atLeastOneAnalysisPerformed = false
let mutable analysisCancelled = false
let updateSourceImages () =
- stackSourceImagesSelection.Children.Clear()
- let width = int stackSourceImagesSelection.ActualWidth
+ win.stackSourceImagesSelection.Children.Clear()
+ let width = int win.stackSourceImagesSelection.ActualWidth
for srcImg in state.SourceImages do
let imageSourceSelection = Views.ImageSourceSelection(Tag = srcImg, Margin = Thickness(3.))
imageSourceSelection.Tag <- srcImg
imageSourceSelection.menuZoom50X.Click.AddHandler(fun obj args -> imageSourceSelection.txtResolution.Text <- "230000")
imageSourceSelection.menuZoom100X.Click.AddHandler(fun obj args -> imageSourceSelection.txtResolution.Text <- "460000")
+ imageSourceSelection.butDPICalculator.Click.AddHandler(fun obj args ->
+ match DPICalculator.showWindow win.Root with
+ | Some resolution -> imageSourceSelection.txtResolution.Text <- resolution.ToString()
+ | None -> ())
+
imageSourceSelection.txtResolution.PreviewTextInput.AddHandler(fun obj args ->
let text = imageSourceSelection.txtResolution.Text + args.Text
args.Handled <- match parseAndValidatePPI text with Some _ -> false | None -> true)
let checkbox = imageSourceSelection.chkSelection
checkbox.IsChecked <- Nullable<bool>(not (checkbox.IsChecked.HasValue && checkbox.IsChecked.Value)))
- stackSourceImagesSelection.Children.Add(imageSourceSelection) |> ignore
+ win.stackSourceImagesSelection.Children.Add(imageSourceSelection) |> ignore
// Get the new parameters for each image. If an error occurs then 'None' is returned and a message box is displayed.
// The boolean is 'true' if the image is selected (checked).
let getInputImagesParameters () : (SourceImage * bool * Parameters) list option =
- let sourceImagesControls = stackSourceImagesSelection.Children |> Seq.cast<Views.ImageSourceSelection>
+ let sourceImagesControls = win.stackSourceImagesSelection.Children |> Seq.cast<Views.ImageSourceSelection>
let parameters = seq {
for srcImgCtrl in sourceImagesControls do
let srcImg = srcImgCtrl.Tag :?> SourceImage
then None
else Some parameters
- butClose.Click.AddHandler(fun obj args -> window.Root.Close())
+ win.butClose.Click.AddHandler(fun obj args -> win.Root.Close())
- butStart.Click.AddHandler(fun obj args ->
+ win.butStart.Click.AddHandler(fun obj args ->
match getInputImagesParameters () with
| Some imagesParameters ->
let imagesToProcess = [
then
MessageBox.Show("No image selected", "Cannot start analysis", MessageBoxButton.OK, MessageBoxImage.Information) |> ignore
else
- stackSourceImagesSelection.IsEnabled <- false
+ win.stackSourceImagesSelection.IsEnabled <- false
analysisPerformed <- false
- butStart.IsEnabled <- false
- butClose.Content <- "Abort"
+ win.butStart.IsEnabled <- false
+ win.butClose.Content <- "Abort"
async {
let maybeResults =
ParasitemiaCore.Analysis.doMultipleAnalysis
imagesToProcess
- (Some (fun progress -> window.Root.Dispatcher.Invoke(fun () -> progressBar.Value <- float progress); not analysisCancelled))
+ (Some (fun progress -> win.Root.Dispatcher.Invoke(fun () -> win.progress.Value <- float progress); not analysisCancelled))
lock monitor (
fun() ->
for id, cells in results do
state.SetResult (int id) cells
- window.Root.Dispatcher.Invoke(fun () ->
- stackSourceImagesSelection.IsEnabled <- true
- butStart.IsEnabled <- true
- butClose.Content <- "Close"
+ win.Root.Dispatcher.Invoke(fun () ->
+ win.stackSourceImagesSelection.IsEnabled <- true
+ win.butStart.IsEnabled <- true
+ win.butClose.Content <- "Close"
updateSourceImages ())
Logger.Log.User("All analyses terminated successfully")
} |> Async.Start
| _ -> ())
- window.Root.Loaded.AddHandler(fun obj args -> updateSourceImages ())
+ win.Root.Loaded.AddHandler(fun obj args -> updateSourceImages ())
- window.Root.ShowDialog() |> ignore
+ win.Root.ShowDialog() |> ignore
Logger.Log.RmListener(logListener)
// 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.4")>]
-[<assembly: AssemblyFileVersion("1.0.0.4")>]
+[<assembly: AssemblyVersion("1.0.0.5")>]
+[<assembly: AssemblyFileVersion("1.0.0.5")>]
do
()
\ No newline at end of file
--- /dev/null
+module ParasitemiaUI.DPICalculator
+
+open System
+open System.Windows
+open System.Windows.Media
+open System.Windows.Markup
+open System.Windows.Shapes
+open System.Windows.Controls
+open System.Diagnostics
+
+open ParasitemiaCore.UnitsOfMeasure
+open ParasitemiaCore.Types
+
+type SensorSize = {
+ w: float<mm>
+ h: float<mm>
+ txt: string } with
+ override this.ToString () =
+ sprintf "%g mm × %g mm%s" this.w this.h (if this.txt = "" then "" else " (" + this.txt + ")")
+
+let showWindow (parent: Window) : int option =
+ let win = Views.DPICalculatorWindow()
+ win.Root.Owner <- parent
+ win.Root.Left <- parent.Left + parent.ActualWidth / 2. - win.Root.Width / 2.
+ win.Root.Top <- parent.Top + parent.ActualHeight / 2. - win.Root.Height / 2.
+
+ let sensorSizes = [
+ { w = 5.76<mm>; h = 4.29<mm>; txt = "1/2.5″" }
+ { w = 7.6<mm>; h = 5.7<mm>; txt = "1/1.7″" }
+ { w = 8.6<mm>; h = 6.6<mm>; txt = "2/3″" }
+ { w = 13.2<mm>; h = 8.8<mm>; txt = "1″" }
+ { w = 17.3<mm>; h = 13.<mm>; txt = "" }
+ { w = 20.7<mm>; h = 13.8<mm>; txt = "" }
+ { w = 22.2<mm>; h = 14.8<mm>; txt = "" }
+ { w = 23.6<mm>; h = 15.7<mm>; txt = "" }
+ { w = 28.7<mm>; h = 19.<mm>; txt = "" }
+ { w = 28.7<mm>; h = 19.<mm>; txt = "" } ]
+
+ for size in sensorSizes do
+ win.cmbSensorSize.Items.Add(size) |> ignore
+ win.cmbSensorSize.SelectedIndex <- 0
+
+ let resolution (w_p: float<px>) (w_mm: float<mm>) (zoom: float) : float<ppi> =
+ w_p * zoom / mmToInch w_mm
+
+ let updateCurrentResolution () =
+ let { w = w; h = h } = win.cmbSensorSize.SelectedValue :?> SensorSize
+ let ratio = h / w
+
+ let parseDouble txt errorMess = match Double.TryParse(txt) with true, value -> Success value | _ -> Fail errorMess
+
+ match result
+ { let! sensorResolution = parseDouble win.txtSensorResolution.Text "The sensor resolution is not valid"
+ let! zoom = parseDouble win.txtZoom.Text "The zoom is not valid"
+ let wPixel = 1.<px> * sqrt (sensorResolution * 1e6 / ratio)
+ return! Success (float <| resolution wPixel w zoom) } with
+ | Success res -> win.txtImageResolution.Text <- (int (res / 1000.) * 1000).ToString()
+ | Fail mess -> win.txtImageResolution.Text <- mess
+
+ win.butCancel.Click.AddHandler(fun obj args -> win.Root.DialogResult <- Nullable<bool>(false); win.Root.Close())
+ win.butOK.Click.AddHandler(fun obj args -> win.Root.DialogResult <- Nullable<bool>(true); win.Root.Close())
+
+ win.cmbSensorSize.SelectionChanged.AddHandler(fun obj arg -> updateCurrentResolution ())
+ win.txtSensorResolution.TextChanged.AddHandler(fun obj arg -> updateCurrentResolution ())
+ win.txtZoom.TextChanged.AddHandler(fun obj arg -> updateCurrentResolution ())
+
+ let result = win.Root.ShowDialog()
+ if result.HasValue && result.Value
+ then
+ match Int32.TryParse win.txtImageResolution.Text with
+ | true, res -> Some res
+ | _ -> None
+ else
+ None
+
let run (defaultConfig: Config) (fileToOpen: string option) =
let app = new Application()
- let mainWindow = Views.MainWindow()
- let ctrl (name: string): 'a = mainWindow.Root.FindName(name) :?> 'a
+ let win = Views.MainWindow()
let state = State.State(defaultConfig)
let mutable currentScale = 1.
let mutable displayHealthy = false
let warningBelowNumberOfRBC = 1000
- let menuExit: MenuItem = ctrl "menuExit"
- let menuSaveFile: MenuItem = ctrl "menuSave"
- let menuSaveAsFile: MenuItem = ctrl "menuSaveAs"
- let menuLoadFile: MenuItem = ctrl "menuOpen"
- let menuNewFile: MenuItem = ctrl "menuNew"
- let menuExportResults: MenuItem = ctrl "menuExportResults"
- let menuAddSourceImage: MenuItem = ctrl "menuAddSourceImage"
- let menuAnalysis: MenuItem = ctrl "menuAnalysis"
- let menuStartAnalysis: MenuItem = ctrl "menuStartAnalysis"
- let menuView: MenuItem = ctrl "menuView"
- let menuHightlightRBC: MenuItem = ctrl "menuHightlightRBC"
- let menuAbout: MenuItem = ctrl "menuAbout"
-
- let txtDocumentStatus: TextBlock = ctrl "txtDocumentStatus"
- let txtMessageStatus: TextBlock = ctrl "txtMessageStatus"
-
- let txtPatient: TextBox = ctrl "txtPatient"
- let txtGlobalParasitemia: TextBlock = ctrl "txtGlobalParasitemia"
-
- let stackPreviews: StackPanel = ctrl "stackPreviews"
-
- let scrollViewCurrentImage: ScrollViewer = ctrl "scrollViewCurrentImage"
- let borderCurrentImage: Border = ctrl "borderCurrentImage"
- let canvasCurrentImage: Canvas = ctrl "canvasCurrentImage"
-
- let gridImageInformation: Grid = ctrl "gridImageInformation"
- let txtImageInformation1: TextBlock = ctrl "txtImageInformation1"
- let txtImageInformation2: TextBlock = ctrl "txtImageInformation2"
- let txtImageName: TextBox = ctrl "txtImageName"
-
- let scrollRBC: ScrollViewer = ctrl "scrollRBC"
- let stackRBC: StackPanel = ctrl "stackRBC"
-
- let imgLogos: Border = ctrl "imgLogos"
-
// Initializations.
- let canvasCurrentImageColor = canvasCurrentImage.Background
- menuHightlightRBC.IsChecked <- displayHealthy
+ let canvasCurrentImageColor = win.canvasCurrentImage.Background
+ win.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> =
frame
let updateDocumentStatus () =
- txtDocumentStatus.Text <- if state.FilePath = "" then "<New document>" else state.FilePath
+ win.txtDocumentStatus.Text <- if state.FilePath = "" then "<New document>" else state.FilePath
let statusMessageTimer = Threading.DispatcherTimer()
- statusMessageTimer.Tick.AddHandler(fun obj args -> statusMessageTimer.Stop(); txtMessageStatus.Text <- "")
+ statusMessageTimer.Tick.AddHandler(fun obj args -> statusMessageTimer.Stop(); win.txtMessageStatus.Text <- "")
statusMessageTimer.Interval <- TimeSpan(0, 0, 2)
let displayStatusMessage (message: string) =
- txtMessageStatus.Text <- message
+ win.txtMessageStatus.Text <- message
statusMessageTimer.Stop()
statusMessageTimer.Start()
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)
- scrollViewCurrentImage.ScrollToVerticalOffset(rbc.center.Y * currentScale - scrollViewCurrentImage.ViewportHeight / 2. + borderCurrentImage.BorderThickness.Top)
+ 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 txtImageName.Text))
+ TextChangedEventHandler(fun obj args -> state.CurrentImage |> Option.iter(fun srcImg -> state.SetName srcImg win.txtImageName.Text))
let updateCurrentImageInformation () =
- txtImageName.TextChanged.RemoveHandler(txtImageName_TextChanged)
- txtImageInformation1.Inlines.Clear()
- txtImageInformation2.Inlines.Clear()
- txtImageName.Text <- ""
+ win.txtImageName.TextChanged.RemoveHandler(txtImageName_TextChanged)
+ win.txtImageInformation1.Inlines.Clear()
+ win.txtImageInformation2.Inlines.Clear()
+ win.txtImageName.Text <- ""
match state.CurrentImage with
| Some srcImg ->
- gridImageInformation.Visibility <- Visibility.Visible
- txtImageName.Text <- srcImg.name
- txtImageName.TextChanged.AddHandler(txtImageName_TextChanged)
+ win.gridImageInformation.Visibility <- Visibility.Visible
+ win.txtImageName.Text <- srcImg.name
+ win.txtImageName.TextChanged.AddHandler(txtImageName_TextChanged)
// The left part.
let parasitemiaStr = Utils.percentText (state.ImageParasitemia srcImg)
- txtImageInformation1.Inlines.Add(Documents.Run("Parasitemia: ", FontWeight = FontWeights.Bold))
- txtImageInformation1.Inlines.Add(parasitemiaStr)
- txtImageInformation1.Inlines.Add(Documents.LineBreak())
+ win.txtImageInformation1.Inlines.Add(Documents.Run("Parasitemia: ", FontWeight = FontWeights.Bold))
+ win.txtImageInformation1.Inlines.Add(parasitemiaStr)
+ win.txtImageInformation1.Inlines.Add(Documents.LineBreak())
- txtImageInformation1.Inlines.Add(Documents.Run("Last analysis: ", FontWeight = FontWeights.Bold))
- txtImageInformation1.Inlines.Add(Documents.Run(if srcImg.dateLastAnalysis.Ticks = 0L then "<Never>" else srcImg.dateLastAnalysis.ToLocalTime().ToString()))
+ 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 srcImg.dateLastAnalysis.ToLocalTime().ToString()))
// The right part part.
- txtImageInformation2.Inlines.Add(Documents.Run("Added infected erythrocyte: ", FontWeight = FontWeights.Bold))
- txtImageInformation2.Inlines.Add(Documents.Run((state.ImageNbManuallyChangedRBCStr srcImg true) + " " + (state.ImageManuallyChangedRBCStr srcImg true)))
- txtImageInformation2.Inlines.Add(Documents.LineBreak())
- txtImageInformation2.Inlines.Add(Documents.Run("Removed infected erythrocyte: ", FontWeight = FontWeights.Bold))
- txtImageInformation2.Inlines.Add(Documents.Run((state.ImageNbManuallyChangedRBCStr srcImg false) + " " + (state.ImageManuallyChangedRBCStr srcImg false)))
+ 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.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)))
| _ ->
- gridImageInformation.Visibility <- Visibility.Hidden
+ win.gridImageInformation.Visibility <- Visibility.Hidden
let updateGlobalParasitemia () =
- txtGlobalParasitemia.Inlines.Clear()
+ win.txtGlobalParasitemia.Inlines.Clear()
let total, infected = state.GlobalParasitemia
- txtGlobalParasitemia.Inlines.Add(Documents.Run(Utils.percentText (total, infected), FontWeight = FontWeights.Bold))
+ win.txtGlobalParasitemia.Inlines.Add(Documents.Run(Utils.percentText (total, infected), FontWeight = FontWeights.Bold))
if total > 0 && total < warningBelowNumberOfRBC
then
- txtGlobalParasitemia.Inlines.Add(
+ win.txtGlobalParasitemia.Inlines.Add(
Documents.Run(
sprintf " Warning: the number of erythrocytes should be above %d" warningBelowNumberOfRBC,
FontWeight = FontWeights.Bold,
Foreground = Brushes.Red))
let updateViewportPreview () =
- for preview in stackPreviews.Children |> Seq.cast<Views.ImageSourcePreview> do
+ for preview in win.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 canvasWidth = win.canvasCurrentImage.ActualWidth * currentScale
+ let canvasHeight = win.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.
+ let marginLeft = previewWidth * (win.scrollViewCurrentImage.HorizontalOffset - win.borderCurrentImage.BorderThickness.Left) / canvasWidth - 2.
+ let marginRight = previewWidth * (canvasWidth - (win.scrollViewCurrentImage.HorizontalOffset - win.borderCurrentImage.BorderThickness.Right) - win.scrollViewCurrentImage.ViewportWidth) / canvasWidth - 2.
+ let marginTop = previewHeight * (win.scrollViewCurrentImage.VerticalOffset - win.borderCurrentImage.BorderThickness.Top) / canvasHeight - 2.
+ let marginBottom = previewHeight * (canvasHeight - (win.scrollViewCurrentImage.VerticalOffset - win.borderCurrentImage.BorderThickness.Bottom) - win.scrollViewCurrentImage.ViewportHeight) / canvasHeight - 2.
preview.viewport.Margin <-
Thickness(
let rec setAsInfected (srcImg: SourceImage) (rbc: RBC) (infected: bool) =
state.SetAsInfected rbc infected
- canvasCurrentImage.Children
+ win.canvasCurrentImage.Children
|> Seq.cast<Views.RBCFrame>
|> Seq.iter
(fun frame ->
let mutable currentPreview = 0
for rbc in srcImg.rbcs |> List.filter (fun rbc -> displayHealthy || rbc.infected) do
let previewInfected =
- if currentPreview < stackRBC.Children.Count
+ if currentPreview < win.stackRBC.Children.Count
then
- RBCFrameFromExisting srcImg rbc (stackRBC.Children.[currentPreview] :?> Views.RBCFrame)
+ RBCFrameFromExisting srcImg rbc (win.stackRBC.Children.[currentPreview] :?> Views.RBCFrame)
else
let f = RBCFrame srcImg rbc
f.MouseLeftButtonUp.AddHandler(fun obj args -> zoomToRBC (f.Tag :?> RBC))
- stackRBC.Children.Add(f) |> ignore
+ win.stackRBC.Children.Add(f) |> ignore
f
currentPreview <- currentPreview + 1
- previewInfected.Height <- stackRBC.ActualHeight
- previewInfected.Width <- stackRBC.ActualHeight * rbc.size.Width / rbc.size.Height
+ 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))
- stackRBC.Children.RemoveRange(currentPreview, stackRBC.Children.Count - currentPreview)
+ win.stackRBC.Children.RemoveRange(currentPreview, win.stackRBC.Children.Count - currentPreview)
| _ -> ()
updateViewportPreview ()
let mutable currentCanvas = 0
for rbc in srcImg.rbcs do
let frame =
- if currentCanvas < canvasCurrentImage.Children.Count
+ if currentCanvas < win.canvasCurrentImage.Children.Count
then
- RBCFrameFromExisting srcImg rbc (canvasCurrentImage.Children.[currentCanvas] :?> Views.RBCFrame)
+ RBCFrameFromExisting srcImg rbc (win.canvasCurrentImage.Children.[currentCanvas] :?> Views.RBCFrame)
else
let f = RBCFrame srcImg rbc
f.Root.Opacity <- 0.7
- canvasCurrentImage.Children.Add(f) |> ignore
+ win.canvasCurrentImage.Children.Add(f) |> ignore
f
currentCanvas <- currentCanvas + 1
Canvas.SetLeft(frame, rbc.center.X - rbc.size.Width / 2.)
Canvas.SetTop(frame, rbc.center.Y - rbc.size.Height / 2.)
- for i in currentCanvas .. canvasCurrentImage.Children.Count - 1 do
- canvasCurrentImage.Children.[i].Visibility <- Visibility.Hidden
+ for i in currentCanvas .. win.canvasCurrentImage.Children.Count - 1 do
+ win.canvasCurrentImage.Children.[i].Visibility <- Visibility.Hidden
| _ -> ()
let askDocumentPathToSave () : string option =
let updateCurrentImage () =
match state.CurrentImage with
| Some srcImg ->
- imgLogos.Visibility <- Visibility.Collapsed
+ win.imgLogos.Visibility <- Visibility.Collapsed
// Highlight the preview.
- stackPreviews.Children
+ win.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))
+ win.canvasCurrentImage.Height <- float srcImg.img.Height
+ win.canvasCurrentImage.Width <- float srcImg.img.Width
+ win.canvasCurrentImage.Background <- ImageBrush(BitmapSourceConvert.ToBitmapSource(srcImg.img))
updateRBCFramesCurrent ()
updateRBCFramesPreview ()
| None ->
- imgLogos.Visibility <- Visibility.Visible
+ win.imgLogos.Visibility <- Visibility.Visible
- stackRBC.Children.Clear()
- canvasCurrentImage.Children.Clear()
- canvasCurrentImage.Background <- canvasCurrentImageColor
+ win.stackRBC.Children.Clear()
+ win.canvasCurrentImage.Children.Clear()
+ win.canvasCurrentImage.Background <- canvasCurrentImageColor
updateCurrentImageInformation ()
let imgCtrl = Views.ImageSourcePreview(Margin = Thickness(3.))
imgCtrl.menuRemoveImage.Click.AddHandler(fun obj args ->
- stackPreviews.Children.Remove(imgCtrl)
+ win.stackPreviews.Children.Remove(imgCtrl)
let srcImg = imgCtrl.Tag :?> SourceImage
let currentRemoved = Some srcImg = state.CurrentImage
state.RemoveSourceImage srcImg
updateGlobalParasitemia()
// Update image numbers.
- 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 <- 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
+ win.stackPreviews.Children.Add(imgCtrl) |> ignore
// Zoom to a mouse position into the control 'imgCtrl'.
let zoomTo (mousePos: Point) =
- let canvasW = canvasCurrentImage.ActualWidth * currentScale
- let canvasH = canvasCurrentImage.ActualHeight * currentScale
+ let canvasW = win.canvasCurrentImage.ActualWidth * currentScale
+ let canvasH = win.canvasCurrentImage.ActualHeight * currentScale
let centerX = (mousePos.X - imgCtrl.BorderThickness.Left) / (imgCtrl.ActualWidth - imgCtrl.BorderThickness.Left) * canvasW
let centerY = (mousePos.Y - imgCtrl.BorderThickness.Top) / (imgCtrl.ActualHeight - imgCtrl.BorderThickness.Top) * canvasH
- scrollViewCurrentImage.ScrollToHorizontalOffset(centerX - scrollViewCurrentImage.ViewportWidth / 2. + borderCurrentImage.BorderThickness.Left)
- scrollViewCurrentImage.ScrollToVerticalOffset(centerY - scrollViewCurrentImage.ViewportHeight / 2. + borderCurrentImage.BorderThickness.Top)
+ win.scrollViewCurrentImage.ScrollToHorizontalOffset(centerX - win.scrollViewCurrentImage.ViewportWidth / 2. + win.borderCurrentImage.BorderThickness.Left)
+ win.scrollViewCurrentImage.ScrollToVerticalOffset(centerY - win.scrollViewCurrentImage.ViewportHeight / 2. + win.borderCurrentImage.BorderThickness.Top)
imgCtrl.MouseLeftButtonDown.AddHandler(fun obj args ->
setCurrentImage (state.SourceImages |> Seq.find (fun srcImg -> (srcImg :> Object) = imgCtrl.Tag))
imgCtrl.ReleaseMouseCapture())
let updatePreviews () =
- stackPreviews.Children.Clear ()
+ win.stackPreviews.Children.Clear ()
for srcImg in state.SourceImages do
addPreview srcImg
updateCurrentImage ()
let updateGUI () =
- txtPatient.Text <- state.PatientID
+ win.txtPatient.Text <- state.PatientID
updatePreviews ()
updateGlobalParasitemia ()
updateDocumentStatus ()
Log.Error(ex.ToString())
MessageBox.Show(sprintf "The results cannot be exported in '%s'" state.FilePath, "Error exporting the files", MessageBoxButton.OK, MessageBoxImage.Error) |> ignore
- txtPatient.TextChanged.AddHandler(fun obj args -> state.PatientID <- txtPatient.Text)
+ win.txtPatient.TextChanged.AddHandler(fun obj args -> state.PatientID <- win.txtPatient.Text)
- menuExit.Click.AddHandler(fun obj args -> mainWindow.Root.Close())
- menuSaveFile.Click.AddHandler(fun obj args -> saveCurrentDocument ())
- menuSaveAsFile.Click.AddHandler(fun obj args -> saveCurrentDocumentAsNewFile ())
- menuLoadFile.Click.AddHandler(fun obj args -> askLoadFile ())
- menuNewFile.Click.AddHandler(fun obj args -> newFile ())
- menuExportResults.Click.AddHandler(fun obj args -> exportResults ())
+ win.menuExit.Click.AddHandler(fun obj args -> win.Root.Close())
+ win.menuSave.Click.AddHandler(fun obj args -> saveCurrentDocument ())
+ win.menuSaveAs.Click.AddHandler(fun obj args -> saveCurrentDocumentAsNewFile ())
+ win.menuOpen.Click.AddHandler(fun obj args -> askLoadFile ())
+ win.menuNew.Click.AddHandler(fun obj args -> newFile ())
+ win.menuExportResults.Click.AddHandler(fun obj args -> exportResults ())
- menuAddSourceImage.Click.AddHandler(fun obj args ->
+ win.menuAddSourceImage.Click.AddHandler(fun obj args ->
let dialog = OpenFileDialog(Filter = "Image Files|*.png;*.jpg;*.tif;*.tiff", Multiselect = true)
let res = dialog.ShowDialog()
if res.HasValue && res.Value
then
updateCurrentImage ())
- menuAnalysis.SubmenuOpened.AddHandler(fun obj args -> menuStartAnalysis.IsEnabled <- state.SourceImages.Count() > 0)
+ win.menuAnalysis.SubmenuOpened.AddHandler(fun obj args -> win.menuStartAnalysis.IsEnabled <- state.SourceImages.Count() > 0)
- menuStartAnalysis.Click.AddHandler(fun obj args ->
- if Analysis.showWindow mainWindow.Root state
+ win.menuStartAnalysis.Click.AddHandler(fun obj args ->
+ if Analysis.showWindow win.Root state
then
updateGlobalParasitemia ()
updateCurrentImage ())
- menuHightlightRBC.Click.AddHandler(fun obj args ->
- displayHealthy <- menuHightlightRBC.IsChecked
+ win.menuHightlightRBC.Click.AddHandler(fun obj args ->
+ displayHealthy <- win.menuHightlightRBC.IsChecked
updateRBCFramesPreview ()
updateRBCFramesCurrent ())
- menuAbout.Click.AddHandler(fun obj args -> About.showWindow mainWindow.Root)
+ win.menuAbout.Click.AddHandler(fun obj args -> About.showWindow win.Root)
- mainWindow.Root.Closing.AddHandler(fun obj args -> askSaveCurrent ())
+ win.Root.Closing.AddHandler(fun obj args -> askSaveCurrent ())
// Zoom on the current image.
let adjustCurrentImageBorders (deltaX: float) (deltaY: float) =
- borderCurrentImage.BorderThickness <-
+ win.borderCurrentImage.BorderThickness <-
Thickness(
- (scrollViewCurrentImage.ViewportWidth + deltaX) / 2.,
- (scrollViewCurrentImage.ViewportHeight + deltaY) / 2.,
- (scrollViewCurrentImage.ViewportWidth + deltaX) / 2.,
- (scrollViewCurrentImage.ViewportHeight + deltaY) / 2.)
+ (win.scrollViewCurrentImage.ViewportWidth + deltaX) / 2.,
+ (win.scrollViewCurrentImage.ViewportHeight + deltaY) / 2.,
+ (win.scrollViewCurrentImage.ViewportWidth + deltaX) / 2.,
+ (win.scrollViewCurrentImage.ViewportHeight + deltaY) / 2.)
- canvasCurrentImage.SizeChanged.AddHandler(fun obj args ->
+ win.canvasCurrentImage.SizeChanged.AddHandler(fun obj args ->
let deltaX = args.NewSize.Width - args.PreviousSize.Width
let deltaY = args.NewSize.Height - args.PreviousSize.Height
if deltaX > 0.5 || deltaY > 0.5
then
adjustCurrentImageBorders 0.0 0.0
// Center the view at the center of the image initialy.
- scrollViewCurrentImage.UpdateLayout()
- scrollViewCurrentImage.ScrollToHorizontalOffset(borderCurrentImage.ActualWidth / 2. - scrollViewCurrentImage.ViewportWidth / 2.)
- scrollViewCurrentImage.ScrollToVerticalOffset(borderCurrentImage.ActualHeight / 2. - scrollViewCurrentImage.ViewportHeight / 2.))
+ win.scrollViewCurrentImage.UpdateLayout()
+ win.scrollViewCurrentImage.ScrollToHorizontalOffset(win.borderCurrentImage.ActualWidth / 2. - win.scrollViewCurrentImage.ViewportWidth / 2.)
+ win.scrollViewCurrentImage.ScrollToVerticalOffset(win.borderCurrentImage.ActualHeight / 2. - win.scrollViewCurrentImage.ViewportHeight / 2.))
- scrollViewCurrentImage.SizeChanged.AddHandler(fun obj args ->
+ win.scrollViewCurrentImage.SizeChanged.AddHandler(fun obj args ->
let deltaX = args.NewSize.Width - args.PreviousSize.Width
let deltaY = args.NewSize.Height - args.PreviousSize.Height
adjustCurrentImageBorders deltaX deltaY
- scrollViewCurrentImage.ScrollToHorizontalOffset(scrollViewCurrentImage.HorizontalOffset + deltaX / 8.)
- scrollViewCurrentImage.ScrollToVerticalOffset(scrollViewCurrentImage.VerticalOffset + deltaY / 8.))
+ win.scrollViewCurrentImage.ScrollToHorizontalOffset(win.scrollViewCurrentImage.HorizontalOffset + deltaX / 8.)
+ win.scrollViewCurrentImage.ScrollToVerticalOffset(win.scrollViewCurrentImage.VerticalOffset + deltaY / 8.))
let mutable maxScale = 4.
let mutable minScale = 0.25
let currentImageScaleTransform = ScaleTransform()
- canvasCurrentImage.LayoutTransform <- currentImageScaleTransform
- borderCurrentImage.PreviewMouseWheel.AddHandler(fun obj args ->
+ win.canvasCurrentImage.LayoutTransform <- currentImageScaleTransform
+ win.borderCurrentImage.PreviewMouseWheel.AddHandler(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
if newScale > maxScale then maxScale elif newScale < minScale then minScale else newScale
let realScaleFactor = currentScale / previousScale
- let centerX = scrollViewCurrentImage.HorizontalOffset + scrollViewCurrentImage.ViewportWidth / 2. - borderCurrentImage.BorderThickness.Left
- let centerY = scrollViewCurrentImage.VerticalOffset + scrollViewCurrentImage.ViewportHeight / 2. - borderCurrentImage.BorderThickness.Top
+ let centerX = win.scrollViewCurrentImage.HorizontalOffset + win.scrollViewCurrentImage.ViewportWidth / 2. - win.borderCurrentImage.BorderThickness.Left
+ let centerY = win.scrollViewCurrentImage.VerticalOffset + win.scrollViewCurrentImage.ViewportHeight / 2. - win.borderCurrentImage.BorderThickness.Top
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)
+ win.scrollViewCurrentImage.ScrollToHorizontalOffset(centerX * realScaleFactor - win.scrollViewCurrentImage.ViewportWidth / 2. + win.borderCurrentImage.BorderThickness.Left)
+ win.scrollViewCurrentImage.ScrollToVerticalOffset(centerY * realScaleFactor - win.scrollViewCurrentImage.ViewportHeight / 2. + win.borderCurrentImage.BorderThickness.Top)
args.Handled <- true)
let mutable scrollStartPosition = Point(0., 0.)
let mutable scrollStartOffsetX = 0.
let mutable scrollStartOffsetY = 0.
- borderCurrentImage.PreviewMouseLeftButtonDown.AddHandler(fun obj args ->
- scrollStartPosition <- args.GetPosition(scrollViewCurrentImage)
- scrollStartOffsetX <- scrollViewCurrentImage.HorizontalOffset
- scrollStartOffsetY <- scrollViewCurrentImage.VerticalOffset
- borderCurrentImage.Cursor <- Input.Cursors.ScrollAll
- borderCurrentImage.CaptureMouse() |> ignore
+ win.borderCurrentImage.PreviewMouseLeftButtonDown.AddHandler(fun obj args ->
+ scrollStartPosition <- args.GetPosition(win.scrollViewCurrentImage)
+ scrollStartOffsetX <- win.scrollViewCurrentImage.HorizontalOffset
+ scrollStartOffsetY <- win.scrollViewCurrentImage.VerticalOffset
+ win.borderCurrentImage.Cursor <- Input.Cursors.ScrollAll
+ win.borderCurrentImage.CaptureMouse() |> ignore
args.Handled <- true)
- borderCurrentImage.PreviewMouseMove.AddHandler(fun obj args ->
- if borderCurrentImage.IsMouseCaptured
+ win.borderCurrentImage.PreviewMouseMove.AddHandler(fun obj args ->
+ if win.borderCurrentImage.IsMouseCaptured
then
- let position = args.GetPosition(scrollViewCurrentImage)
+ let position = args.GetPosition(win.scrollViewCurrentImage)
let deltaX = scrollStartPosition.X - position.X
let deltaY = scrollStartPosition.Y - position.Y
- scrollViewCurrentImage.ScrollToHorizontalOffset(deltaX + scrollStartOffsetX)
- scrollViewCurrentImage.ScrollToVerticalOffset(deltaY + scrollStartOffsetY)
+ win.scrollViewCurrentImage.ScrollToHorizontalOffset(deltaX + scrollStartOffsetX)
+ win.scrollViewCurrentImage.ScrollToVerticalOffset(deltaY + scrollStartOffsetY)
args.Handled <- true)
- borderCurrentImage.PreviewMouseLeftButtonUp.AddHandler(fun obj args ->
- if borderCurrentImage.IsMouseCaptured
+ win.borderCurrentImage.PreviewMouseLeftButtonUp.AddHandler(fun obj args ->
+ if win.borderCurrentImage.IsMouseCaptured
then
- borderCurrentImage.Cursor <- Input.Cursors.Arrow
- borderCurrentImage.ReleaseMouseCapture()
+ win.borderCurrentImage.Cursor <- Input.Cursors.Arrow
+ win.borderCurrentImage.ReleaseMouseCapture()
args.Handled <- true)
// Shortcuts.
// Save.
- mainWindow.Root.InputBindings.Add(
+ win.Root.InputBindings.Add(
Input.KeyBinding(
FSharp.ViewModule.FunCommand((fun obj -> saveCurrentDocument ()), (fun obj -> true)),
Input.KeyGesture(Input.Key.S, Input.ModifierKeys.Control))) |> ignore
// Save as.
- mainWindow.Root.InputBindings.Add(
+ win.Root.InputBindings.Add(
Input.KeyBinding(
FSharp.ViewModule.FunCommand((fun obj -> saveCurrentDocumentAsNewFile ()), (fun obj -> true)),
Input.KeyGesture(Input.Key.S, Input.ModifierKeys.Control ||| Input.ModifierKeys.Shift))) |> ignore
// Open.
- mainWindow.Root.InputBindings.Add(
+ win.Root.InputBindings.Add(
Input.KeyBinding(
FSharp.ViewModule.FunCommand((fun obj -> askLoadFile ()), (fun obj -> true)),
Input.KeyGesture(Input.Key.O, Input.ModifierKeys.Control))) |> ignore
// New file.
- mainWindow.Root.InputBindings.Add(
+ win.Root.InputBindings.Add(
Input.KeyBinding(
FSharp.ViewModule.FunCommand((fun obj -> newFile ()), (fun obj -> true)),
Input.KeyGesture(Input.Key.N, Input.ModifierKeys.Control))) |> ignore
// Export results.
- mainWindow.Root.InputBindings.Add(
+ win.Root.InputBindings.Add(
Input.KeyBinding(
FSharp.ViewModule.FunCommand((fun obj -> exportResults ()), (fun obj -> true)),
Input.KeyGesture(Input.Key.E, Input.ModifierKeys.Control))) |> ignore
// Viewport preview.
- scrollViewCurrentImage.ScrollChanged.AddHandler(fun obj args -> updateViewportPreview ())
+ win.scrollViewCurrentImage.ScrollChanged.AddHandler(fun obj args -> updateViewportPreview ())
updateDocumentStatus ()
- gridImageInformation.Visibility <- Visibility.Hidden
+ win.gridImageInformation.Visibility <- Visibility.Hidden
- mainWindow.Root.Show()
+ win.Root.Show()
match fileToOpen with
| Some filepath -> loadFile filepath
<Compile Include="XAML\ImageSourceSelection.xaml.fs" />
<Resource Include="XAML\RBCFrame.xaml" />
<Compile Include="XAML\RBCFrame.xaml.fs" />
+ <Resource Include="XAML\DPICalculatorWindow.xaml" />
+ <Compile Include="XAML\DPICalculatorWindow.xaml.fs" />
<Resource Include="XAML\AnalysisWindow.xaml" />
<Compile Include="XAML\AnalysisWindow.xaml.fs" />
<Resource Include="XAML\AboutWindow.xaml" />
<Compile Include="PiaZ.fs" />
<Compile Include="State.fs" />
<Compile Include="Export.fs" />
- <Compile Include="About.fs" />
+ <Compile Include="DPICalculator.fs" />
<Compile Include="Analysis.fs" />
+ <Compile Include="About.fs" />
<Compile Include="GUI.fs" />
<None Include="App.config" />
<Content Include="packages.config" />
open FsXaml
-type AboutWindow = XAML<"XAML/AboutWindow.xaml">
+type AboutWindow = XAML<"XAML/AboutWindow.xaml", true>
open FsXaml
-type AnalysisWindow = XAML<"XAML/AnalysisWindow.xaml">
+type AnalysisWindow = XAML<"XAML/AnalysisWindow.xaml", true>
--- /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="DPICalculatorWindow" Height="200" Width="378.5" MinHeight="200" MinWidth="280" Title="About" Icon="pack://application:,,,/Resources/icon.ico">
+ <Grid Margin="3">
+ <Grid.ColumnDefinitions>
+ <ColumnDefinition Width="Auto"/>
+ <ColumnDefinition Width="1*"/>
+ </Grid.ColumnDefinitions>
+ <Grid.RowDefinitions>
+ <RowDefinition Height="Auto" />
+ <RowDefinition Height="Auto" />
+ <RowDefinition Height="Auto" />
+ <RowDefinition Height="20" />
+ <RowDefinition Height="Auto" />
+ <RowDefinition Height="1*" />
+ </Grid.RowDefinitions>
+ <Label Content="Sensor size" Grid.Row="0" Grid.Column="0" />
+ <ComboBox x:Name="cmbSensorSize" Grid.Row="0" Grid.Column="1" VerticalAlignment="Center" />
+
+ <Label Content="Sensor resolution [Megapixel]" Grid.Row="1" Grid.Column="0" />
+ <TextBox x:Name="txtSensorResolution" Grid.Row="1" Grid.Column="1" VerticalAlignment="Center" />
+
+ <Label Content="Zoom" Grid.Row="2" Grid.Column="0" />
+ <TextBox x:Name="txtZoom" Grid.Row="2" Grid.Column="1" VerticalAlignment="Center" />
+
+ <Label Content="Image resolution [DPI]" Grid.Row="4" Grid.Column="0" />
+ <TextBox x:Name="txtImageResolution" Grid.Row="4" Grid.Column="1" VerticalAlignment="Center" IsReadOnly="True" />
+
+ <Grid Grid.Column="0" Grid.Row="5" Grid.ColumnSpan="2">
+ <Grid.ColumnDefinitions>
+ <ColumnDefinition Width="1*"/>
+ <ColumnDefinition Width="Auto"/>
+ </Grid.ColumnDefinitions>
+ <Button x:Name="butCancel" Content="Cancel" HorizontalAlignment="Right" Margin="3" VerticalAlignment="Bottom" Width="75" Height="20" Grid.Column="0" />
+ <Button x:Name="butOK" Content="OK" HorizontalAlignment="Right" Margin="3" VerticalAlignment="Bottom" Width="75" Height="20" Grid.Column="1" />
+ </Grid>
+ </Grid>
+</Window>
\ No newline at end of file
--- /dev/null
+namespace ParasitemiaUI.Views
+
+open FsXaml
+
+type DPICalculatorWindow = XAML<"XAML/DPICalculatorWindow.xaml", true>
+
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="Auto"/>
+ <ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBox x:Name="txtResolution" Margin="3" Text="" Grid.Column="0" />
<Button x:Name="butDefaultResolutions" Content="Predefined values" Grid.Column="1" Margin="3">
</Style>
</Button.Style>
</Button>
+ <Button x:Name="butDPICalculator" Content="DPI calculator" Grid.Column="2" Margin="3" />
</Grid>
</Grid>
</Grid>
open FsXaml
-type MainWindow = XAML<"XAML/MainWindow.xaml">
+type MainWindow = XAML<"XAML/MainWindow.xaml", true>