From b87b35b922551f122228df1fd9c530bbb807935a Mon Sep 17 00:00:00 2001 From: Greg Burri Date: Tue, 28 Mar 2017 22:53:42 +0200 Subject: [PATCH] Cleaning syntax. --- Parasitemia/Logger/Logger.fs | 60 ++-- Parasitemia/Logger/Logger.fsproj | 4 +- Parasitemia/ParasitemiaCore/Analysis.fs | 41 ++- Parasitemia/ParasitemiaCore/AssemblyInfo.fs | 4 +- Parasitemia/ParasitemiaCore/Classifier.fs | 194 ++++++------ Parasitemia/ParasitemiaCore/Config.fs | 114 +++---- Parasitemia/ParasitemiaCore/EEOver.fs | 279 ++++++++---------- Parasitemia/ParasitemiaCore/Ellipse.fs | 59 ++-- Parasitemia/ParasitemiaCore/Granulometry.fs | 27 +- Parasitemia/ParasitemiaCore/Heap.fs | 38 +-- .../ParasitemiaCore/ImgTools/Drawing.fs | 26 +- Parasitemia/ParasitemiaCore/ImgTools/Edges.fs | 58 ++-- .../ParasitemiaCore/ImgTools/Histogram.fs | 28 +- Parasitemia/ParasitemiaCore/ImgTools/IO.fs | 4 +- .../ParasitemiaCore/ImgTools/ImgTools.fs | 17 +- .../ParasitemiaCore/ImgTools/Morpho.fs | 241 ++++++--------- Parasitemia/ParasitemiaCore/ImgTools/Otsu.fs | 11 +- Parasitemia/ParasitemiaCore/KMeans.fs | 19 +- Parasitemia/ParasitemiaCore/KMedians.fs | 23 +- Parasitemia/ParasitemiaCore/KdTree.fs | 67 +++-- .../ParasitemiaCore/MatchingEllipses.fs | 47 +-- .../ParasitemiaCore/ParasitemiaCore.fsproj | 22 +- .../ParasitemiaCore/ParasitesMarker.fs | 25 +- Parasitemia/ParasitemiaCore/Types.fs | 30 +- Parasitemia/ParasitemiaCore/UnitsOfMeasure.fs | 8 +- Parasitemia/ParasitemiaCore/Utils.fs | 25 +- Parasitemia/ParasitemiaCore/packages.config | 7 +- Parasitemia/ParasitemiaUI/About.fs | 12 +- Parasitemia/ParasitemiaUI/Analysis.fs | 102 ++++--- Parasitemia/ParasitemiaUI/App.config | 2 +- Parasitemia/ParasitemiaUI/AssemblyInfo.fs | 4 +- Parasitemia/ParasitemiaUI/DPICalculator.fs | 31 +- Parasitemia/ParasitemiaUI/Export.fs | 4 +- Parasitemia/ParasitemiaUI/GUI.fs | 213 ++++++------- .../ParasitemiaUI/ParasitemiaUI.fsproj | 34 +-- Parasitemia/ParasitemiaUI/PiaZ.fs | 97 +++--- Parasitemia/ParasitemiaUI/Program.fs | 14 +- Parasitemia/ParasitemiaUI/State.fs | 120 ++++---- Parasitemia/ParasitemiaUI/Types.fs | 136 +++++---- Parasitemia/ParasitemiaUI/Utils.fs | 11 +- .../ParasitemiaUI/XAML/AboutWindow.xaml.fs | 2 +- .../ParasitemiaUI/XAML/AnalysisWindow.xaml.fs | 2 +- .../XAML/ImageSourcePreview.xaml.fs | 10 +- .../XAML/ImageSourceSelection.xaml.fs | 10 +- .../ParasitemiaUI/XAML/MainWindow.xaml.fs | 2 +- .../XAML/PPICalculatorWindow.xaml.fs | 2 +- .../ParasitemiaUI/XAML/RBCFrame.xaml.fs | 4 +- Parasitemia/ParasitemiaUI/packages.config | 9 +- Parasitemia/WPF/WPF.csproj | 7 +- 49 files changed, 1108 insertions(+), 1198 deletions(-) diff --git a/Parasitemia/Logger/Logger.fs b/Parasitemia/Logger/Logger.fs index cc02314..1b4c332 100644 --- a/Parasitemia/Logger/Logger.fs +++ b/Parasitemia/Logger/Logger.fs @@ -21,10 +21,10 @@ type Log () = let moduleName = System.Diagnostics.StackFrame(1).GetMethod().Module.Name - let mutable stream: StreamWriter = null + let mutable stream : StreamWriter = null - let mutable logDir: string = null - let mutable absoluteDir: string = null + let mutable logDir : string = null + let mutable absoluteDir : string = null let mutable nbEntries = 0L @@ -41,42 +41,36 @@ type Log () = static let instance = new Log() - let setLogDirectory (dir: string) = + let setLogDirectory (dir : string) = lock monitor (fun () -> logDir <- dir absoluteDir <- Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), logDir) - if stream <> null - then + if stream <> null then stream.Close() stream <- null try - if not <| Directory.Exists(absoluteDir) - then + if not <| Directory.Exists(absoluteDir) then Directory.CreateDirectory(absoluteDir) |> ignore with | _ as ex -> Console.Error.WriteLine("Unable to create the log directory: {0}", absoluteDir)) let openLogFile () = try - if stream = null || (nbEntries % (int64 nbEntriesCheckSize) = 0L) && stream.BaseStream.Length > maxSizeFile - then - if stream <> null - then + if stream = null || (nbEntries % (int64 nbEntriesCheckSize) = 0L) && stream.BaseStream.Length > maxSizeFile then + if stream <> null then stream.Close() let mutable n = 1 for existingFile in Directory.GetFiles(absoluteDir) do let current_n = ref 0 - if Int32.TryParse(existingFile.Remove(existingFile.LastIndexOf('.')), current_n) && !current_n > n - then + if Int32.TryParse(existingFile.Remove(existingFile.LastIndexOf('.')), current_n) && !current_n > n then n <- !current_n let mutable filename = Path.Combine(absoluteDir, String.Format(filenameFormat, n)) try - if (FileInfo(filename).Length > maxSizeFile) - then + if (FileInfo(filename).Length > maxSizeFile) then filename <- Path.Combine(absoluteDir, String.Format(filenameFormat, n + 1)) with | :? FileNotFoundException -> () // The file may not exist. @@ -90,17 +84,15 @@ type Log () = interface IDisposable with member this.Dispose () = - if stream <> null - then + if stream <> null then stream.Dispose() - member private this.Write (message: string, severity: Severity) = + member private this.Write (message : string, severity : Severity) = lock monitor (fun () -> nbEntries <- nbEntries + 1L openLogFile () - if stream <> null - then + if stream <> null then let mutable moduleNameCaller = moduleName match StackTrace().GetFrames() |> Array.tryPick (fun frame -> let name = frame.GetMethod().Module.Name if name <> moduleName then Some name else None) with @@ -126,45 +118,43 @@ type Log () = | :? IOException as ex -> Console.Error.WriteLine("Unable to write to the log file: {0}", ex)) - member private this.AddListener (listener: IListener) = + member private this.AddListener (listener : IListener) = lock monitor (fun () -> - if not <| listeners.Contains(listener) - then + if not <| listeners.Contains(listener) then listeners.Add(listener)) - member private this.RmListener (listener: IListener) = + member private this.RmListener (listener : IListener) = lock monitor (fun () -> listeners.Remove(listener) |> ignore) - static member AddListener (listener: IListener) = instance.AddListener(listener) - static member RmListener (listener: IListener) = instance.RmListener(listener) + static member AddListener (listener : IListener) = instance.AddListener(listener) + static member RmListener (listener : IListener) = instance.RmListener(listener) - static member LogWithTime (message: string, severity: Severity, f: unit -> 'a option, [] args: Object[]) : 'a option = + static member LogWithTime (message : string, severity : Severity, f : unit -> 'a option, [] args: Object[]) : 'a option = let sw = Stopwatch() sw.Start() let res = f () sw.Stop() - if res.IsSome - then + if res.IsSome then instance.Write(String.Format(message, args) + sprintf " (time: %d ms)" sw.ElapsedMilliseconds, severity) res - static member Debug (message: string, [] args: Object[]) = + static member Debug (message : string, [] args : Object[]) = #if DEBUG instance.Write(String.Format(message, args), Severity.DEBUG) #else () #endif - static member User (message: string, [] args: Object[]) = + static member User (message : string, [] args : Object[]) = instance.Write(String.Format(message, args), Severity.USER) - static member Warning (message: string, [] args: Object[]) = + static member Warning (message : string, [] args : Object[]) = instance.Write(String.Format(message, args), Severity.WARNING) - static member Error (message: string, [] args: Object[]) = + static member Error (message : string, [] args : Object[]) = instance.Write(String.Format(message, args), Severity.ERROR) - static member Fatal (message: string, [] args: Object[]) = + static member Fatal (message : string, [] args : Object[]) = instance.Write(String.Format(message, args), Severity.FATAL) diff --git a/Parasitemia/Logger/Logger.fsproj b/Parasitemia/Logger/Logger.fsproj index c67f1ef..3f7eaa6 100644 --- a/Parasitemia/Logger/Logger.fsproj +++ b/Parasitemia/Logger/Logger.fsproj @@ -9,8 +9,8 @@ Library Logger Logger - v4.5.2 - 4.4.0.0 + v4.6.2 + 4.4.1.0 true Logger diff --git a/Parasitemia/ParasitemiaCore/Analysis.fs b/Parasitemia/ParasitemiaCore/Analysis.fs index 2a7ddc8..afcf4df 100644 --- a/Parasitemia/ParasitemiaCore/Analysis.fs +++ b/Parasitemia/ParasitemiaCore/Analysis.fs @@ -27,24 +27,21 @@ open Types /// The first call returning 'false' will cancel the analysis. /// The 'int' parameter correspond to the progression from 0 to 100 /// A list of detected cells or nothing if the process has been cancelled -let doAnalysis (img: Image) (name: string) (config: Config) (reportProgress: (int -> bool) option) : Cell list option = +let doAnalysis (img : Image) (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 = + let reportWithVal (percent : int) (value : 'a) : 'a option = match reportProgress with - | Some f -> - if f percent - then Some value - else None + | Some f -> if f percent then Some value else None | _ -> Some value - let report (percent: int) : unit option = + let report (percent : int) : unit option = reportWithVal percent () - let inline buildLogWithName (text: string) = sprintf "№ %s: %s" name text + 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) + let inline logTimeWithName (text : string) (f : unit -> 'a option) : 'a option = Log.LogWithTime((buildLogWithName text), Severity.USER, f) // Monadic construction to be able to abort the progress when running. maybe { @@ -60,7 +57,7 @@ let doAnalysis (img: Image) (name: string) (config: Config) (reportPr use img_parasites = img_float.[2] // Red. use img_parasites_filtered = gaussianFilter img_parasites config.LPFStandardDeviationParasite - logWithName (sprintf "Nominal erythrocyte diameter: %A" config.RBCRadiusByResolution) + logWithName (sprintf "Nominal erythrocyte diameter: %O" 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) @@ -73,14 +70,13 @@ let doAnalysis (img: Image) (name: string) (config: Config) (reportPr //let! radius = logTimeWithName "Granulometry (morpho)" (fun() -> reportWithVal 10 (Granulometry.findRadiusByClosing img_RBC_filtered range 1. true |> float32)) config.SetRBCRadius <| radius - logWithName (sprintf "Found erythrocyte diameter: %A" config.RBCRadius) + logWithName (sprintf "Found erythrocyte diameter: %O" config.RBCRadius) do! report 20 do! let secondAreaOpening = int <| config.RBCRadius.Area * config.Parameters.ratioAreaPaleCenter - if secondAreaOpening > initialAreaOpening - then + if secondAreaOpening > initialAreaOpening then logTimeWithName "Second area opening" (fun () -> areaOpenF img_RBC_filtered secondAreaOpening; report 30) else report 30 @@ -154,7 +150,8 @@ let doAnalysis (img: Image) (name: string) (config: Config) (reportPr IO.saveImg img_float.[0] (buildFileName " - source - blue.png") | _ -> () - return cells } + return cells + } /// /// Do multiple analyses on the same time. The number of concurrent process depends if the number of the core. @@ -164,8 +161,8 @@ let doAnalysis (img: Image) (name: string) (config: Config) (reportPr /// The first call returning 'false' will cancel the analysis. /// The 'int' parameter correspond to the progression from 0 to 100 /// 'None' if the process has been cancelled or the list of result as (name * cells), 'name' corresponds to the given name -let doMultipleAnalysis (imgs: (string * Config * Image) list) (reportProgress: (int -> bool) option) : (string * Cell list) list option = - let report (percent: int) : bool = +let doMultipleAnalysis (imgs : (string * Config * Image) list) (reportProgress : (int -> bool) option) : (string * Cell list) list option = + let report (percent : int) : bool = match reportProgress with | Some f -> f percent | _ -> true @@ -173,7 +170,7 @@ let doMultipleAnalysis (imgs: (string * Config * Image) list) (report let progressPerAnalysis = System.Collections.Concurrent.ConcurrentDictionary() let nbImgs = List.length imgs - let reportProgressImg (id: string) (progress: int) = + let reportProgressImg (id : string) (progress : int) = progressPerAnalysis.AddOrUpdate(id, progress, (fun _ _ -> progress)) |> ignore report (progressPerAnalysis.Values.Sum() / nbImgs) @@ -190,12 +187,14 @@ let doMultipleAnalysis (imgs: (string * Config * Image) list) (report with | ex -> Log.Error("Analysis {0} failed: {1}", id, ex) - 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 + if List.length results <> List.length imgs then + None + else + Some results diff --git a/Parasitemia/ParasitemiaCore/AssemblyInfo.fs b/Parasitemia/ParasitemiaCore/AssemblyInfo.fs index cfda389..184fbed 100644 --- a/Parasitemia/ParasitemiaCore/AssemblyInfo.fs +++ b/Parasitemia/ParasitemiaCore/AssemblyInfo.fs @@ -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: // [] -[] -[] +[] +[] do () \ No newline at end of file diff --git a/Parasitemia/ParasitemiaCore/Classifier.fs b/Parasitemia/ParasitemiaCore/Classifier.fs index 1db8b03..f7c1bbb 100644 --- a/Parasitemia/ParasitemiaCore/Classifier.fs +++ b/Parasitemia/ParasitemiaCore/Classifier.fs @@ -12,7 +12,7 @@ open Utils type CellState = RBC = 1 | Removed = 2 | Peculiar = 3 -type private EllipseFlaggedKd (e: Ellipse) = +type private EllipseFlaggedKd (e : Ellipse) = inherit Ellipse (e.Cx, e.Cy, e.A, e.B, e.Alpha) member val State = CellState.RBC with get, set @@ -21,43 +21,47 @@ type private EllipseFlaggedKd (e: Ellipse) = member this.X = this.Cx member this.Y = this.Cy -let findCells (ellipses: Ellipse list) (parasites: ParasitesMarker.Result) (width: int) (height: int) (config: Config.Config) : Cell list = - if ellipses.IsEmpty - then +let findCells (ellipses : Ellipse list) (parasites : ParasitesMarker.Result) (width : int) (height : int) (config : Config.Config) : Cell list = + if ellipses.IsEmpty then [] else // This is the minimum window size to check if other ellipses touch 'e'. - let searchRegion (e: Ellipse) = { KdTree.minX = e.Cx - (e.A + config.RBCRadius.Max) - KdTree.maxX = e.Cx + (e.A + config.RBCRadius.Max) - KdTree.minY = e.Cy - (e.A + config.RBCRadius.Max) - KdTree.maxY = e.Cy + (e.A + config.RBCRadius.Max) } + let searchRegion (e : Ellipse) = + { + KdTree.minX = e.Cx - (e.A + config.RBCRadius.Max) + KdTree.maxX = e.Cx + (e.A + config.RBCRadius.Max) + KdTree.minY = e.Cy - (e.A + config.RBCRadius.Max) + KdTree.maxY = e.Cy + (e.A + config.RBCRadius.Max) + } // The minimum window to contain a given ellipse. - let ellipseWindow (e: Ellipse) = + let ellipseWindow (e : Ellipse) = let cx, cy = roundInt e.Cx, roundInt e.Cy let a = int (e.A + 0.5f) cx - a, cy - a, cx + a, cy + a // Return 'true' if the point 'p' is owned by e. // The lines represents all intersections with other ellipses. - let pixelOwnedByE (p: PointF) (e: EllipseFlaggedKd) (neighbors: (EllipseFlaggedKd * PointF * PointF) list) = + let pixelOwnedByE (p : PointF) (e : EllipseFlaggedKd) (neighbors : (EllipseFlaggedKd * PointF * PointF) list) = e.Contains p.X p.Y && seq { let c = PointF(e.Cx, e.Cy) - for e', d1 in neighbors - |> List.choose (fun (otherE, p1, p2) -> - if otherE.State = CellState.Removed - then None - else Some (otherE, Utils.lineFromTwoPoints p1 p2)) do - if e'.State = e.State // Peculiar vs peculiar or RBC vs RBC. - then + for e', d1 in + (neighbors + |> List.choose ( + fun (otherE, p1, p2) -> + if otherE.State = CellState.Removed then + None + else + Some (otherE, Utils.lineFromTwoPoints p1 p2) + )) do + if e'.State = e.State then // Peculiar vs peculiar or RBC vs RBC. let d2 = lineFromTwoPoints c p let c' = PointF(e'.Cx, e'.Cy) let v = pointFromTwoLines d1 (lineFromTwoPoints c c') let case1 = sign (v.X - c.X) <> sign (v.X - c'.X) || Utils.squaredDistanceTwoPoints v c > Utils.squaredDistanceTwoPoints v c' - if not (Single.IsInfinity d2.A) - then + if not (Single.IsInfinity d2.A) then let p' = Utils.pointFromTwoLines d1 d2 let delta, delta' = let dx1, dx2 = (c.X - p.X), (c.X - p'.X) @@ -65,16 +69,14 @@ let findCells (ellipses: Ellipse list) (parasites: ParasitesMarker.Result) (widt if abs dx1 < 0.01f || abs dx2 < 0.01f then c.Y - p.Y, c.Y - p'.Y else dx1, dx2 // Yield 'false' when the point is owned by another ellipse. - if case1 - then + if case1 then yield sign delta <> sign delta' || Utils.squaredDistanceTwoPoints c p' > Utils.squaredDistanceTwoPoints c p else yield sign delta = sign delta' && Utils.squaredDistanceTwoPoints c p' < Utils.squaredDistanceTwoPoints c p else yield case1 - elif e.State = CellState.Peculiar // A peculiar always win against a RBC. - then + elif e.State = CellState.Peculiar then // A peculiar always win against a RBC. yield true else yield not <| e'.Contains p.X p.Y @@ -85,14 +87,12 @@ let findCells (ellipses: Ellipse list) (parasites: ParasitesMarker.Result) (widt // 1) Associate touching ellipses with each ellipses and remove ellipse with more than two intersections. let tree = KdTree.Tree.BuildTree ellipses - let neighbors (e: EllipseFlaggedKd) : (EllipseFlaggedKd * PointF * PointF) list = - if e.State <> CellState.Removed - then + let neighbors (e : EllipseFlaggedKd) : (EllipseFlaggedKd * PointF * PointF) list = + if e.State <> CellState.Removed then tree.Search (searchRegion e) // We only keep the ellipses touching 'e'. |> List.choose (fun otherE -> - if e <> otherE - then + if e <> otherE then match EEOver.EEOverlapArea e otherE with | Some (_, px, _) when px.Length > 2 -> otherE.State <- CellState.Removed @@ -102,7 +102,8 @@ let findCells (ellipses: Ellipse list) (parasites: ParasitesMarker.Result) (widt | _ -> None else - None) + None + ) else [] @@ -122,16 +123,14 @@ let findCells (ellipses: Ellipse list) (parasites: ParasitesMarker.Result) (widt yield float imgData.[y, x, 0] }) for e in ellipses do - if not e.Removed - then + if not e.Removed then let shrinkedE = e.Scale 0.9f let minX, minY, maxX, maxY = ellipseWindow shrinkedE let stdDeviation = MathNet.Numerics.Statistics.Statistics.StandardDeviation (seq { for y in (if minY < 0 then 0 else minY) .. (if maxY >= h then h - 1 else maxY) do for x in (if minX < 0 then 0 else minX) .. (if maxX >= w then w - 1 else maxX) do - if shrinkedE.Contains (float32 x) (float32 y) - then + if shrinkedE.Contains (float32 x) (float32 y) then yield float imgData.[y, x, 0] }) if stdDeviation > globalStdDeviation * config.Parameters.standardDeviationMaxRatio then @@ -140,28 +139,24 @@ let findCells (ellipses: Ellipse list) (parasites: ParasitesMarker.Result) (widt // 4) Remove ellipses with little area. let minArea = config.RBCRadius.MinArea for e, neighbors in ellipsesWithNeigbors do - if e.State <> CellState.Removed - then + if e.State <> CellState.Removed then let minX, minY, maxX, maxY = ellipseWindow e let mutable area = 0 for y = (if minY < 0 then 0 else minY) to (if maxY >= height then height - 1 else maxY) do for x = (if minX < 0 then 0 else minX) to (if maxX >= width then width - 1 else maxX) do let p = PointF(float32 x, float32 y) - if pixelOwnedByE p e neighbors - then + if pixelOwnedByE p e neighbors then area <- area + 1 - if area < int minArea - then + if area < int minArea then e.State <- CellState.Removed // 5) Define non-rbc (peculiar) cells. let darkStainData = parasites.darkStain.Data ellipsesWithNeigbors |> List.choose (fun (e, neighbors) -> - if e.State = CellState.Removed - then + if e.State = CellState.Removed then None else let mutable darkStainPixels = 0 @@ -170,11 +165,9 @@ let findCells (ellipses: Ellipse list) (parasites: ParasitesMarker.Result) (widt for y = minY to maxY do for x = minX to maxX do let p = PointF(float32 x, float32 y) - if pixelOwnedByE p e neighbors - then + if pixelOwnedByE p e neighbors then nbElement <- nbElement + 1 - if darkStainData.[y, x, 0] > 0uy - then + if darkStainData.[y, x, 0] > 0uy then darkStainPixels <- darkStainPixels + 1 if float darkStainPixels > config.Parameters.maxDarkStainRatio * (float nbElement) then Some e else None) @@ -191,62 +184,61 @@ let findCells (ellipses: Ellipse list) (parasites: ParasitesMarker.Result) (widt let darkStainData = parasites.darkStain.Data ellipsesWithNeigbors - |> List.choose (fun (e, neighbors) -> - if e.State = CellState.Removed - then - None - else - let minX, minY, maxX, maxY = ellipseWindow e - - let nucleusPixels = List() - let parasitePixels = List() - - let mutable nbElement = 0 - - let elements = new Matrix(maxY - minY + 1, maxX - minX + 1) - for y = minY to maxY do - for x = minX to maxX do - let p = PointF(float32 x, float32 y) - if pixelOwnedByE p e neighbors - then - elements.[y - minY, x - minX] <- 1uy - nbElement <- nbElement + 1 - - if nucleusData.[y, x, 0] > 0uy - then - nucleusPixels.Add(Point(x, y)) - - if parasiteData.[y, x, 0] > 0uy - then - parasitePixels.Add(Point(x, y)) - - let parasiteArea = - if nucleusPixels.Count > 0 - then - seq { - for parasitePixel in parasitePixels do - if nucleusPixels.Exists(fun p -> pown (p.X - parasitePixel.X) 2 + pown (p.Y - parasitePixel.Y) 2 <= diameterParasiteSquared) - then yield 1 } |> Seq.sum - else - 0 + |> List.choose ( + fun (e, neighbors) -> + if e.State = CellState.Removed then + None + else + let minX, minY, maxX, maxY = ellipseWindow e + + let nucleusPixels = List() + let parasitePixels = List() + + let mutable nbElement = 0 + + let elements = new Matrix(maxY - minY + 1, maxX - minX + 1) + for y = minY to maxY do + for x = minX to maxX do + let p = PointF(float32 x, float32 y) + if pixelOwnedByE p e neighbors then + elements.[y - minY, x - minX] <- 1uy + nbElement <- nbElement + 1 + + if nucleusData.[y, x, 0] > 0uy then + nucleusPixels.Add(Point(x, y)) + + if parasiteData.[y, x, 0] > 0uy then + parasitePixels.Add(Point(x, y)) + + let parasiteArea = + if nucleusPixels.Count > 0 then + seq { + for parasitePixel in parasitePixels do + if nucleusPixels.Exists(fun p -> pown (p.X - parasitePixel.X) 2 + pown (p.Y - parasitePixel.Y) 2 <= diameterParasiteSquared) then + yield 1 + } |> Seq.sum + else + 0 - let cellClass = - if e.State = CellState.Peculiar - then - Peculiar + let cellClass = + if e.State = CellState.Peculiar then + Peculiar - elif nucleusPixels.Count > 0 && parasiteArea >= minimumParasiteArea - then - let infectionToRemove = Morpho.connectedComponents parasites.parasite nucleusPixels - for p in infectionToRemove do - nucleusData.[p.Y, p.X, 0] <- 0uy - InfectedRBC + elif nucleusPixels.Count > 0 && parasiteArea >= minimumParasiteArea then + let infectionToRemove = Morpho.connectedComponents parasites.parasite nucleusPixels + for p in infectionToRemove do + nucleusData.[p.Y, p.X, 0] <- 0uy + InfectedRBC - else - HealthyRBC - - Some { cellClass = cellClass - center = Point(roundInt e.Cx, roundInt e.Cy) - nucleusArea = if cellClass = InfectedRBC then nucleusPixels.Count else 0 - parasiteArea = parasiteArea - elements = elements }) + else + HealthyRBC + + Some + { + cellClass = cellClass + center = Point(roundInt e.Cx, roundInt e.Cy) + nucleusArea = if cellClass = InfectedRBC then nucleusPixels.Count else 0 + parasiteArea = parasiteArea + elements = elements + } + ) diff --git a/Parasitemia/ParasitemiaCore/Config.fs b/Parasitemia/ParasitemiaCore/Config.fs index 8159173..fdeed8f 100644 --- a/Parasitemia/ParasitemiaCore/Config.fs +++ b/Parasitemia/ParasitemiaCore/Config.fs @@ -9,75 +9,79 @@ type Debug = | DebugOff | DebugOn of string // Output directory. -type Parameters = { - rbcDiameter: float<μm> - resolution: float +type Parameters = + { + rbcDiameter : float<μm> + resolution : float - ratioAreaPaleCenter: float32 // The area of the second opening is 'ratioSecondAreaOpen' * mean RBC area. It's applied only if greater than 'initialAreaOpen'. + ratioAreaPaleCenter : float32 // The area of the second opening is 'ratioSecondAreaOpen' * mean RBC area. It's applied only if greater than 'initialAreaOpen'. - granulometryRange: float32 // The radius will be seeked from radius - granulometryRange * radius to radius + granulometryRange * radius. + granulometryRange : float32 // The radius will be seeked from radius - granulometryRange * radius to radius + granulometryRange * radius. - minRbcRadius: float32 // Factor of the mean RBC radius. - maxRbcRadius: float32 // Factor of the mean RBC radius. + minRbcRadius : float32 // Factor of the mean RBC radius. + maxRbcRadius : float32 // Factor of the mean RBC radius. - LPFStandardDeviationParasite: float<μm> // Sigma parameter of the gaussian to remove the high frequency noise. - LPFStandardDeviationRBC: float<μm> + LPFStandardDeviationParasite : float<μm> // Sigma parameter of the gaussian to remove the high frequency noise. + LPFStandardDeviationRBC : float<μm> - // Ellipse. - nbPickElementsMin: int - factorNbValidPick: float // The number of computed ellipse per edge pixel. - factorNbMaxPick: float + // Ellipse. + nbPickElementsMin : int + factorNbValidPick : float // The number of computed ellipse per edge pixel. + factorNbMaxPick : float - // Parasites detection. - darkStainLevel: float // Lower -> more sensitive. Careful about illumination on the borders. - maxDarkStainRatio: float // When a cell must own less than this ratio to be a RBC. + // Parasites detection. + darkStainLevel : float // Lower -> more sensitive. Careful about illumination on the borders. + maxDarkStainRatio : float // When a cell must own less than this ratio to be a RBC. - parasiteRadiusRatio: float32 // The ratio of the parasite radius of the RBC radius. - minimumParasiteAreaRatio: float32 // Factor of a RBC area. 0.5 means the half of RBC area. + parasiteRadiusRatio : float32 // The ratio of the parasite radius of the RBC radius. + minimumParasiteAreaRatio : float32 // Factor of a RBC area. 0.5 means the half of RBC area. - cytoplasmSizeRatio: float32 - cytoplasmSensitivity: float // between 0 (the least sensitive) and 1 (the most sensitive). + cytoplasmSizeRatio : float32 + cytoplasmSensitivity : float // between 0 (the least sensitive) and 1 (the most sensitive). - nucleusAreaRatio: float32 // Factor of a RBC area. 0.5 means the half of RBC area. - infectionSensitivity: float // between 0 (the least sensitive) and 1 (the most sensitive). + nucleusAreaRatio : float32 // Factor of a RBC area. 0.5 means the half of RBC area. + infectionSensitivity : float // between 0 (the least sensitive) and 1 (the most sensitive). - // [] standardDeviationMaxRatio: float // The standard deviation of the pixel values of a cell can't be greater than standardDeviationMaxRatio * global standard deviation - minimumCellAreaFactor: float32 } // Factor of the mean RBC area. A cell with an area below this will be rejected. + // [] standardDeviationMaxRatio: float // The standard deviation of the pixel values of a cell can't be greater than standardDeviationMaxRatio * global standard deviation + minimumCellAreaFactor : float32 // Factor of the mean RBC area. A cell with an area below this will be rejected. + } -let defaultParameters = { - rbcDiameter = 7.5<μm> - resolution = 230.e3 // 230.e3 Correspond to 50X. +let defaultParameters = + { + rbcDiameter = 7.5<μm> + resolution = 230.e3 // 230.e3 Correspond to 50X. - ratioAreaPaleCenter = 2.f / 5.f // The ratio between an RBC area and the area of the its pale center. + ratioAreaPaleCenter = 2.f / 5.f // The ratio between an RBC area and the area of the its pale center. - granulometryRange = 0.5f + granulometryRange = 0.5f - minRbcRadius = -0.23f - maxRbcRadius = 0.23f + minRbcRadius = -0.23f + maxRbcRadius = 0.23f - LPFStandardDeviationParasite = 0.15<μm> - LPFStandardDeviationRBC = 0.22<μm> + LPFStandardDeviationParasite = 0.15<μm> + LPFStandardDeviationRBC = 0.22<μm> - nbPickElementsMin = 10 - factorNbValidPick = 0.06 //1.0 - factorNbMaxPick = 4. + nbPickElementsMin = 10 + factorNbValidPick = 0.06 //1.0 + factorNbMaxPick = 4. - darkStainLevel = 1. - maxDarkStainRatio = 0.1 // 10 % + darkStainLevel = 1. + maxDarkStainRatio = 0.1 // 10 % - parasiteRadiusRatio = 0.5f // 50 % - minimumParasiteAreaRatio = 0.02f // 2 % + parasiteRadiusRatio = 0.5f // 50 % + minimumParasiteAreaRatio = 0.02f // 2 % - cytoplasmSizeRatio = 1.f / 5.f - cytoplasmSensitivity = 0.96 + cytoplasmSizeRatio = 1.f / 5.f + cytoplasmSensitivity = 0.96 - nucleusAreaRatio = 0.01f // 1.0 % - infectionSensitivity = 0.92 + nucleusAreaRatio = 0.01f // 1.0 % + infectionSensitivity = 0.92 - // standardDeviationMaxRatio = 0.6 // Obsolete. - minimumCellAreaFactor = 0.4f } + // standardDeviationMaxRatio = 0.6 // Obsolete. + minimumCellAreaFactor = 0.4f + } -type RBCRadius (radius: float32, parameters: Parameters) = +type RBCRadius (radius : float32, parameters : Parameters) = member this.Pixel = radius member this.μm : float<μm> = 1. * (float radius) / parameters.resolution |> inchToμm @@ -97,13 +101,13 @@ type RBCRadius (radius: float32, parameters: Parameters) = override this.ToString() = sprintf "%d px (%.1f μm)" (Utils.roundInt <| 2.f * radius) (2. * this.μm) -type Config (param: Parameters) = - let RBCadiusInPixels (rbcDiameter: float<μm>) (resolution: float) : float32 = - let rbcRadiusInch: float = (μmToInch rbcDiameter) / 2. - let rbcRadiusPx: float = resolution * rbcRadiusInch +type Config (param : Parameters) = + let RBCadiusInPixels (rbcDiameter : float<μm>) (resolution : float) : float32 = + let rbcRadiusInch : float = (μmToInch rbcDiameter) / 2. + let rbcRadiusPx : float = resolution * rbcRadiusInch float32 rbcRadiusPx - let mutable parameters: Parameters = param + let mutable parameters : Parameters = param let mutable rbcRadiusByResolution = RBCRadius(RBCadiusInPixels parameters.rbcDiameter parameters.resolution, parameters) let mutable rbcRadius = RBCRadius(0.f, parameters) @@ -119,17 +123,17 @@ type Config (param: Parameters) = member val Debug = DebugOff with get, set member this.LPFStandardDeviationParasite = - let stdDeviation: float = (μmToInch parameters.LPFStandardDeviationParasite) * parameters.resolution + let stdDeviation : float = (μmToInch parameters.LPFStandardDeviationParasite) * parameters.resolution float stdDeviation member this.LPFStandardDeviationRBC = - let stdDeviation: float = (μmToInch parameters.LPFStandardDeviationRBC) * parameters.resolution + let stdDeviation : float = (μmToInch parameters.LPFStandardDeviationRBC) * parameters.resolution float stdDeviation member this.RBCRadiusByResolution = rbcRadiusByResolution member this.RBCRadius = rbcRadius - member this.SetRBCRadius (radiusPixel: float32) = + member this.SetRBCRadius (radiusPixel : float32) = rbcRadius <- RBCRadius(radiusPixel, parameters) member this.Copy () = diff --git a/Parasitemia/ParasitemiaCore/EEOver.fs b/Parasitemia/ParasitemiaCore/EEOver.fs index 9a77f48..a7791c9 100644 --- a/Parasitemia/ParasitemiaCore/EEOver.fs +++ b/Parasitemia/ParasitemiaCore/EEOver.fs @@ -5,46 +5,48 @@ open System let private EPS = 1.0e-7 -let inline private ellipse2tr (x: float) (y: float) (aa: float) (bb: float) (cc: float) (dd: float) (ee: float) (ff: float) : float = +let inline private ellipse2tr (x : float) (y : float) (aa : float) (bb : float) (cc : float) (dd : float) (ee : float) (ff : float) : float = aa * x * x + bb * x * y + cc * y * y + dd * x + ee * y + ff -let private nointpts (a1: float) (b1: float) (a2: float) (b2: float) (h1: float) (k1: float) (h2: float) (k2: float) (phi_1: float) (phi_2: float) (h2_tr: float) (k2_tr: float) (aa: float) (bb: float) (cc: float) (dd: float) (ee: float) (ff: float) = +let private nointpts (a1 : float) (b1 : float) (a2 : float) (b2 : float) (h1 : float) (k1 : float) (h2 : float) (k2 : float) (phi_1 : float) (phi_2 : float) (h2_tr : float) (k2_tr : float) (aa : float) (bb : float) (cc : float) (dd : float) (ee : float) (ff : float) = let a1b1 = a1 * b1 let a2b2 = a2 * b2 let area_1 = Math.PI * a1b1 let area_2 = Math.PI * a2b2 let relsize = a1b1 - a2b2 - if relsize > 0.0 - then - if (h2_tr * h2_tr) / (a1 * a1) + (k2_tr * k2_tr) / (b1 * b1) < 1.0 - then area_2 - else 0.0 + if relsize > 0.0 then + if (h2_tr * h2_tr) / (a1 * a1) + (k2_tr * k2_tr) / (b1 * b1) < 1.0 then + area_2 + else + 0.0 - elif relsize < 0.0 - then - if ff < 0.0 - then area_1 - else 0.0 + elif relsize < 0.0 then + if ff < 0.0 then + area_1 + else + 0.0 else - if abs (h1 - h2) < EPS && abs (k1 - k2) < EPS && abs (area_1 - area_2) < EPS - then area_1 - else 0.0 + if abs (h1 - h2) < EPS && abs (k1 - k2) < EPS && abs (area_1 - area_2) < EPS then + area_1 + else + 0.0 type private PointType = TANGENT_POINT | INTERSECTION_POINT -let private istanpt (x: float) (y: float) (a1: float) (b1: float) (aa: float) (bb: float) (cc: float) (dd: float) (ee: float) (ff: float) : PointType = +let private istanpt (x : float) (y : float) (a1 : float) (b1 : float) (aa : float) (bb : float) (cc : float) (dd : float) (ee : float) (ff : float) : PointType = let x = - if abs x > a1 - then + if abs x > a1 then if x < 0.0 then -a1 else a1 - else x + else + x let theta = - if y < 0.0 - then 2.0 * Math.PI - acos (x / a1) - else acos (x / a1) + if y < 0.0 then + 2.0 * Math.PI - acos (x / a1) + else + acos (x / a1) let eps_radian = 0.1 @@ -66,29 +68,31 @@ let private istanpt (x: float) (y: float) (a1: float) (b1: float) (aa: float) (b printf "test2=%f\n" test2 #endif - if test1 * test2 > 0.0 - then TANGENT_POINT - else INTERSECTION_POINT + if test1 * test2 > 0.0 then + TANGENT_POINT + else + INTERSECTION_POINT -let private twointpts (x: float[]) (y: float[]) (a1: float) (b1: float) (phi_1: float) (a2: float) (b2: float) (h2_tr: float) (k2_tr: float) (phi_2: float) (aa: float) (bb: float) (cc: float) (dd: float) (ee: float) (ff: float) = - if abs x.[0] > a1 - then x.[0] <- if x.[0] < 0.0 then -a1 else a1 +let private twointpts (x : float[]) (y : float[]) (a1 : float) (b1 : float) (phi_1 : float) (a2 : float) (b2 : float) (h2_tr : float) (k2_tr : float) (phi_2 : float) (aa : float) (bb : float) (cc : float) (dd : float) (ee : float) (ff : float) = + if abs x.[0] > a1 then + x.[0] <- if x.[0] < 0.0 then -a1 else a1 let mutable theta1 = - if y.[0] < 0.0 - then 2.0 * Math.PI - acos (x.[0] / a1) - else acos (x.[0] / a1) + if y.[0] < 0.0 then + 2.0 * Math.PI - acos (x.[0] / a1) + else + acos (x.[0] / a1) - if abs x.[1] > a1 - then x.[1] <- if x.[1] < 0.0 then -a1 else a1 + if abs x.[1] > a1 then + x.[1] <- if x.[1] < 0.0 then -a1 else a1 let mutable theta2 = - if y.[1] < 0.0 - then 2.0 * Math.PI - acos (x.[1] / a1) - else acos (x.[1] / a1) + if y.[1] < 0.0 then + 2.0 * Math.PI - acos (x.[1] / a1) + else + acos (x.[1] / a1) - if theta1 > theta2 - then + if theta1 > theta2 then let tmp = theta1 theta1 <- theta2 theta2 <- tmp @@ -96,22 +100,19 @@ let private twointpts (x: float[]) (y: float[]) (a1: float) (b1: float) (phi_1: let xmid = a1 * cos ((theta1 + theta2) / 2.0) let ymid = b1 * sin ((theta1 + theta2) / 2.0) - if ellipse2tr xmid ymid aa bb cc dd ee ff > 0.0 - then + if ellipse2tr xmid ymid aa bb cc dd ee ff > 0.0 then let tmp = theta1 theta1 <- theta2 theta2 <- tmp - if theta1 > theta2 - then + if theta1 > theta2 then theta1 <- theta1 - 2.0 * Math.PI let trsign = if (theta2 - theta1) > Math.PI then 1.0 else -1.0 let mutable area1 = 0.5 * (a1 * b1 * (theta2 - theta1) + trsign * abs (x.[0] * y.[1] - x.[1] * y.[0])) - if area1 < 0.0 - then + if area1 < 0.0 then #if DEBUG_LOG printf "TWO area1=%f\n" area1 #endif @@ -125,28 +126,23 @@ let private twointpts (x: float[]) (y: float[]) (a1: float) (b1: float) (phi_1: let mutable x2_tr = (x.[1] - h2_tr) * cosphi + (y.[1] - k2_tr) * -sinphi let mutable y2_tr = (x.[1] - h2_tr) * sinphi + (y.[1] - k2_tr) * cosphi - if abs x1_tr > a2 - then + if abs x1_tr > a2 then x1_tr <- if x1_tr < 0.0 then -a2 else a2 - if y1_tr < 0.0 - then + if y1_tr < 0.0 then theta1 <- 2.0 * Math.PI - acos (x1_tr / a2) else theta1 <- acos (x1_tr / a2) - if abs x2_tr > a2 - then + if abs x2_tr > a2 then x2_tr <- if x2_tr < 0.0 then -a2 else a2 - if y2_tr < 0.0 - then + if y2_tr < 0.0 then theta2 <- 2.0 * Math.PI - acos (x2_tr / a2) else theta2 <- acos (x2_tr / a2) - if theta1 > theta2 - then + if theta1 > theta2 then let tmp = theta1 theta1 <- theta2 theta2 <- tmp @@ -159,21 +155,18 @@ let private twointpts (x: float[]) (y: float[]) (a1: float) (b1: float) (phi_1: let xmid_rt = xmid * cosphi + ymid * -sinphi + h2_tr let ymid_rt = xmid * sinphi + ymid * cosphi + k2_tr - if (xmid_rt * xmid_rt) / (a1 * a1) + (ymid_rt * ymid_rt) / (b1 * b1) > 1.0 - then + if (xmid_rt * xmid_rt) / (a1 * a1) + (ymid_rt * ymid_rt) / (b1 * b1) > 1.0 then let tmp = theta1 theta1 <- theta2 theta2 <- tmp - if theta1 > theta2 - then + if theta1 > theta2 then theta1 <- theta1 - 2.0 * Math.PI let trsign = if theta2 - theta1 > Math.PI then 1.0 else -1.0 let mutable area2 = 0.5 * (a2 * b2 * (theta2 - theta1) + trsign * abs (x1_tr * y2_tr - x2_tr * y1_tr)) - if area2 < 0.0 - then + if area2 < 0.0 then #if DEBUG_LOG printf "TWO area2=%f\n" area2 #endif @@ -181,20 +174,18 @@ let private twointpts (x: float[]) (y: float[]) (a1: float) (b1: float) (phi_1: area1 + area2 -let private threeintpts (xint: float[]) (yint: float[]) (a1: float) (b1: float) (phi_1: float) (a2: float) (b2: float) (h2_tr: float) (k2_tr: float) (phi_2: float) (aa: float) (bb: float) (cc: float) (dd: float) (ee: float) (ff: float) : float = +let private threeintpts (xint : float[]) (yint : float[]) (a1 : float) (b1 : float) (phi_1 : float) (a2 : float) (b2 : float) (h2_tr : float) (k2_tr : float) (phi_2 : float) (aa : float) (bb : float) (cc : float) (dd : float) (ee : float) (ff : float) : float = let mutable tanpts = 0 let mutable tanindex = 0 for i = 0 to 2 do - if istanpt xint.[i] yint.[i] a1 b2 aa bb cc dd ee ff = TANGENT_POINT - then + if istanpt xint.[i] yint.[i] a1 b2 aa bb cc dd ee ff = TANGENT_POINT then tanpts <- tanpts + 1 tanindex <- i #if DEBUG_LOG printf "tanindex=%d\n" tanindex #endif - if tanpts <> 1 - then + if tanpts <> 1 then -1.0 else match tanindex with @@ -208,7 +199,7 @@ let private threeintpts (xint: float[]) (yint: float[]) (a1: float) (b1: float) () twointpts xint yint a1 b1 phi_1 a2 b2 h2_tr k2_tr phi_2 aa bb cc dd ee ff -let private fourintpts (xint: float[]) (yint: float[]) (a1: float) (b1: float) (phi_1: float) (a2: float) (b2: float) (h2_tr: float) (k2_tr: float) (phi_2: float) (aa: float) (bb: float) (cc: float) (dd: float) (ee: float) (ff: float) : float = +let private fourintpts (xint : float[]) (yint : float[]) (a1 : float) (b1 : float) (phi_1 : float) (a2 : float) (b2 : float) (h2_tr : float) (k2_tr : float) (phi_2 : float) (aa : float) (bb : float) (cc : float) (dd : float) (ee : float) (ff : float) : float = let a1b1 = a1 * b1 let a2b2 = a2 * b2 let area_1 = Math.PI * a1b1 @@ -217,8 +208,7 @@ let private fourintpts (xint: float[]) (yint: float[]) (a1: float) (b1: float) ( let theta = Array.zeroCreate 4 for i = 0 to 3 do - if abs xint.[i] > a1 - then + if abs xint.[i] > a1 then xint.[i] <- if xint.[i] < 0.0 then -a1 else a1 theta.[i] <- if yint.[i] < 0.0 then 2.0 * Math.PI - acos (xint.[i] / a1) else acos (xint.[i] / a1) @@ -235,8 +225,7 @@ let private fourintpts (xint: float[]) (yint: float[]) (a1: float) (b1: float) ( let mutable k = j - 1 let mutable k2 = 0 while k >= 0 do - if theta.[k] <= tmp0 - then + if theta.[k] <= tmp0 then k2 <- k + 1 k <- -1 else @@ -270,8 +259,7 @@ let private fourintpts (xint: float[]) (yint: float[]) (a1: float) (b1: float) ( xint_tr.[i] <- (xint.[i] - h2_tr) * cosphi + (yint.[i] - k2_tr) * -sinphi yint_tr.[i] <- (xint.[i] - h2_tr) * sinphi + (yint.[i] - k2_tr) * cosphi - if abs xint_tr.[i] > a2 - then + if abs xint_tr.[i] > a2 then xint_tr.[i] <- if xint_tr.[i] < 0.0 then -a2 else a2 theta_tr.[i] <- if yint_tr.[i] < 0.0 then 2.0 * Math.PI - acos (xint_tr.[i] / a2) else acos (xint_tr.[i] / a2) @@ -281,14 +269,12 @@ let private fourintpts (xint: float[]) (yint: float[]) (a1: float) (b1: float) ( let mutable area2, area3, area4, area5 = 0.0, 0.0, 0.0, 0.0 - if ellipse2tr xmid ymid aa bb cc dd ee ff < 0.0 - then + if ellipse2tr xmid ymid aa bb cc dd ee ff < 0.0 then area2 <- 0.5 * (a1b1 * (theta.[1] - theta.[0]) - abs (xint.[0] * yint.[1] - xint.[1] * yint.[0])) area3 <- 0.5 * (a1b1 * (theta.[3] - theta.[2]) - abs (xint.[2] * yint.[3] - xint.[3] * yint.[2])) area4 <- 0.5 * (a2b2 * (theta_tr.[2] - theta_tr.[1]) - abs (xint_tr.[1] * yint_tr.[2] - xint_tr.[2] * yint_tr.[1])) - if theta_tr.[3] > theta_tr.[0] - then + if theta_tr.[3] > theta_tr.[0] then area5 <- 0.5 * (a2b2 * (theta_tr.[0] - (theta_tr.[3] - 2.0 * Math.PI)) - abs (xint_tr.[3] * yint_tr.[0] - xint_tr.[0] * yint_tr.[3])) else area5 <- 0.5 * (a2b2 * (theta_tr.[0] - theta_tr.[3]) - abs (xint_tr.[3] * yint_tr.[0] - xint_tr.[0] * yint_tr.[3])) @@ -298,29 +284,25 @@ let private fourintpts (xint: float[]) (yint: float[]) (a1: float) (b1: float) ( area4 <- 0.5 * (a2b2 * (theta_tr.[1] - theta_tr.[0]) - abs (xint_tr.[0] * yint_tr.[1] - xint_tr.[1] * yint_tr.[0])) area5 <- 0.5 * (a2b2 * (theta_tr.[3] - theta_tr.[2]) - abs (xint_tr.[2] * yint_tr.[3] - xint_tr.[3] * yint_tr.[2])) - if area5 < 0.0 - then + if area5 < 0.0 then #if DEBUG_LOG printf "\n\t\t-------------> area5 is negative (%f). Add: pi*A2*B2=%f <------------\n" area5 area_2 #endif area5 <- area5 + area_2 - if area4 < 0.0 - then + if area4 < 0.0 then #if DEBUG_LOG printf "\n\t\t-------------> area4 is negative (%f). Add: pi*A2*B2=%f <------------\n" area4 area_2 #endif area4 <- area4 + area_2 - if area3 < 0.0 - then + if area3 < 0.0 then #if DEBUG_LOG printf "\n\t\t-------------> area3 is negative (%f). Add: pi*A2*B2=%f <------------\n" area3 area_1 #endif area3 <- area3 + area_1 - if area2 < 0.0 - then + if area2 < 0.0 then #if DEBUG_LOG printf "\n\t\t-------------> area2 is negative (%f). Add: pi*A2*B2=%f <------------\n" area2 area_1 #endif @@ -332,15 +314,13 @@ let private fourintpts (xint: float[]) (yint: float[]) (a1: float) (b1: float) ( area1 + area2 + area3 + area4 + area5 -let private quadroots (p: float[]) (r: float[,]) = +let private quadroots (p : float[]) (r : float[,]) = let mutable b = -p.[1] / (2.0 * p.[0]) let c = p.[2] / p.[0] let mutable d = b * b - c - if d >= 0.0 - then - if b > 0.0 - then + if d >= 0.0 then + if b > 0.0 then b <- sqrt d + b r.[1, 2] <- b else @@ -356,7 +336,7 @@ let private quadroots (p: float[]) (r: float[,]) = r.[1, 1] <- b r.[1, 2] <- b -let private cubicroots (p: float[]) (r: float[,]) = +let private cubicroots (p : float[]) (r : float[,]) = if p.[0] <> 1.0 then for k = 1 to 3 do p.[k] <- p.[k] / p.[0] @@ -368,22 +348,20 @@ let private cubicroots (p: float[]) (r: float[,]) = let mutable c = t * t * t let mutable d = b * b - c - if d >= 0.0 - then - d <- ((sqrt d) + (abs b)) ** (1.0 / 3.0) - if d <> 0.0 - then - if b > 0.0 - then b <- -d - else b <- d + if d >= 0.0 then + d <- (sqrt d + abs b) ** (1.0 / 3.0) + if d <> 0.0 then + if b > 0.0 then + b <- -d + else + b <- d c <- t / b d <- sqrt(0.75) * (b - c) r.[2, 2] <- d b <- b + c c <- -0.5 * b - s r.[1, 2] <- c - if b > 0.0 && s <= 0.0 || b < 0.0 && s > 0.0 - then + if b > 0.0 && s <= 0.0 || b < 0.0 && s > 0.0 then r.[1, 1] <- c r.[2, 1] <- -d r.[1, 3] <- b - s @@ -394,13 +372,15 @@ let private cubicroots (p: float[]) (r: float[,]) = r.[1, 3] <- c r.[2, 3] <- -d else - if b = 0.0 - then d <- (atan 1.0) / 1.5 - else d <- atan ((sqrt -d) / (abs b)) / 3.0 + if b = 0.0 then + d <- (atan 1.0) / 1.5 + else + d <- atan ((sqrt -d) / (abs b)) / 3.0 - if b < 0.0 - then b <- 2.0 * (sqrt t) - else b <- -2.0 * (sqrt t) + if b < 0.0 then + b <- 2.0 * (sqrt t) + else + b <- -2.0 * (sqrt t) c <- (cos d) * b t <- -(sqrt 0.75) * (sin d) * b - 0.5 * c @@ -408,15 +388,13 @@ let private cubicroots (p: float[]) (r: float[,]) = c <- c - s t <- t - s - if abs c > abs t - then + if abs c > abs t then r.[1, 3] <- c else r.[1, 3] <- t t <- c - if abs d > abs t - then + if abs d > abs t then r.[1, 2] <- d else r.[1, 2] <- t @@ -426,9 +404,8 @@ let private cubicroots (p: float[]) (r: float[,]) = for k = 1 to 3 do r.[2, k] <- 0.0 -let private biquadroots (p: float[]) (r: float[,]) = - if p.[0] <> 1.0 - then +let private biquadroots (p : float[]) (r : float[,]) = + if p.[0] <> 1.0 then for k = 1 to 4 do p.[k] <- p.[k] / p.[0] p.[0] <- 1.0 @@ -443,8 +420,7 @@ let private biquadroots (p: float[]) (r: float[,]) = let mutable quadExecuted = false let inline quad () = - if not quadExecuted - then + if not quadExecuted then p.[2] <- c / b quadroots p r for k = 1 to 2 do @@ -460,20 +436,16 @@ let private biquadroots (p: float[]) (r: float[,]) = p.[1] <- 0.5 * a p.[2] <- (p.[1] * p.[1] - c) * 0.25 p.[3] <- b * b / -64.0 - if p.[3] < 0.0 - then + if p.[3] < 0.0 then cubicroots p r let mutable k = 1 while k < 4 do - if r.[2, k] = 0.0 && r.[1, k] > 0.0 - then + if r.[2, k] = 0.0 && r.[1, k] > 0.0 then d <- r.[1, k] * 4.0 a <- a + d - if a >= 0.0 && b >= 0.0 - then + if a >= 0.0 && b >= 0.0 then p.[1] <- sqrt d - elif a <= 0.0 && b <= 0.0 - then + elif a <= 0.0 && b <= 0.0 then p.[1] <- sqrt d else p.[1] <- -(sqrt d) @@ -482,24 +454,19 @@ let private biquadroots (p: float[]) (r: float[,]) = k <- 4 k <- k + 1 - if not quadExecuted && p.[2] < 0.0 - then + if not quadExecuted && p.[2] < 0.0 then b <- sqrt c d <- b + b - a p.[1] <- 0.0 - if d > 0.0 - then + if d > 0.0 then p.[1] <- sqrt d - elif not quadExecuted - then - if p.[1] > 0.0 - then + elif not quadExecuted then + if p.[1] > 0.0 then b <- (sqrt p.[2]) * 2.0 + p.[1] else b <- -(sqrt p.[2]) * 2.0 + p.[1] - if b <> 0.0 - then + if b <> 0.0 then p.[1] <- 0.0 else for k = 1 to 4 do @@ -512,12 +479,11 @@ let private biquadroots (p: float[]) (r: float[,]) = /// /// Return a tuple (area, x intersections, y intersections). /// -let EEOverlapArea (e1: Types.Ellipse) (e2: Types.Ellipse) : (float32 * float32[] * float32[]) option = +let EEOverlapArea (e1 : Types.Ellipse) (e2 : Types.Ellipse) : (float32 * float32[] * float32[]) option = let h1, k1, a1, b1, phi_1 = float e1.Cx, float e1.Cy, float e1.A, float e1.B, float e1.Alpha let h2, k2, a2, b2, phi_2 = float e2.Cx, float e2.Cy, float e2.A, float e2.B, float e2.Alpha - if a1 <= EPS || b1 <= EPS || a2 <= EPS || b2 <= EPS - then + if a1 <= EPS || b1 <= EPS || a2 <= EPS || b2 <= EPS then None else let phi_1 = phi_1 % Math.PI @@ -567,8 +533,7 @@ let EEOverlapArea (e1: Types.Ellipse) (e2: Types.Ellipse) : (float32 * float32[] let r = Array2D.zeroCreate 3 5 let nroots = - if abs cy.[4] > EPS - then + if abs cy.[4] > EPS then for i = 0 to 3 do py.[4-i] <- cy.[i] / cy.[4] py.[0] <- 1.0 @@ -579,24 +544,21 @@ let EEOverlapArea (e1: Types.Ellipse) (e2: Types.Ellipse) : (float32 * float32[] biquadroots py r 4 - elif abs cy.[3] > EPS - then + elif abs cy.[3] > EPS then for i = 0 to 2 do py.[3 - i] <- cy.[i] / cy.[3] py.[0] <- 1.0 cubicroots py r 3 - elif abs cy.[2] > EPS - then + elif abs cy.[2] > EPS then for i = 0 to 1 do py.[2-i] <- cy.[i] / cy.[2] py.[0] <- 1.0 quadroots py r 2 - elif abs cy.[1] > EPS - then + elif abs cy.[1] > EPS then r.[1, 1] <- -cy.[0] / cy.[1] r.[2, 1] <- 0.0 1 @@ -611,8 +573,7 @@ let EEOverlapArea (e1: Types.Ellipse) (e2: Types.Ellipse) : (float32 * float32[] let ychk = Array.init nroots (fun _ -> Double.MaxValue) let mutable nychk = 0 for i = 1 to nroots do - if abs r.[2, i] < EPS - then + if abs r.[2, i] < EPS then ychk.[nychk] <- r.[1, i] * b1 nychk <- nychk + 1 #if DEBUG_LOG @@ -639,8 +600,7 @@ let EEOverlapArea (e1: Types.Ellipse) (e2: Types.Ellipse) : (float32 * float32[] printf "------------->i=%d (nychk=%d)\n" i nychk #endif - if not (i < nychk - 1 && abs (ychk.[i] - ychk.[i+1]) < EPS / 2.0) - then + if not (i < nychk - 1 && abs (ychk.[i] - ychk.[i+1]) < EPS / 2.0) then #if DEBUG_LOG printf "check intersecting points. nintps is %d" nintpts #endif @@ -653,14 +613,12 @@ let EEOverlapArea (e1: Types.Ellipse) (e2: Types.Ellipse) : (float32 * float32[] printf "\tx2=%f, y1=%f, A=%f. B=%f ---> ellipse2tr(x2)= %f\n" x2 ychk.[i] a1 b1 (ellipse2tr x2 ychk.[i] aa bb cc dd ee ff) #endif - if abs (ellipse2tr x1 ychk.[i] aa bb cc dd ee ff) < EPS - then + if abs (ellipse2tr x1 ychk.[i] aa bb cc dd ee ff) < EPS then nintpts <- nintpts + 1 #if DEBUG_LOG printf "first if x1. acc nintps=%d\n" nintpts #endif - if nintpts > 4 - then + if nintpts > 4 then returnValue <- -1.0 else xint.[nintpts-1] <- x1 @@ -669,14 +627,12 @@ let EEOverlapArea (e1: Types.Ellipse) (e2: Types.Ellipse) : (float32 * float32[] printf "nintpts=%d, xint=%f, x2=%f, i=%d, yint=%f\n" nintpts x1 x2 i ychk.[i] #endif - if returnValue <> -1.0 && abs (ellipse2tr x2 ychk.[i] aa bb cc dd ee ff) < EPS && abs (x2 - x1) > EPS - then + if returnValue <> -1.0 && abs (ellipse2tr x2 ychk.[i] aa bb cc dd ee ff) < EPS && abs (x2 - x1) > EPS then nintpts <- nintpts + 1 #if DEBUG_LOG printf "first if x2. nintps=%d, Dx=%f (eps2=%f) \n" nintpts (abs (x2 - x1)) EPS #endif - if nintpts > 4 - then + if nintpts > 4 then returnValue <- -1.0 else xint.[nintpts-1] <- x2 @@ -692,8 +648,7 @@ let EEOverlapArea (e1: Types.Ellipse) (e2: Types.Ellipse) : (float32 * float32[] #endif i <- i + 1 - if returnValue = -1.0 - then + if returnValue = -1.0 then None else let area = @@ -715,16 +670,14 @@ let EEOverlapArea (e1: Types.Ellipse) (e2: Types.Ellipse) : (float32 * float32[] | 4 -> fourintpts xint yint a1 b1 phi_1 a2 b2 h2_tr k2_tr phi_2 aa bb cc dd ee ff | _ -> -1.0 - if area = -1.0 - then + if area = -1.0 then None - elif nintpts = 0 - then + elif nintpts = 0 then Some (float32 area, [||], [||]) else let xTransform : float32[] = Array.zeroCreate nintpts let yTransform : float32[] = Array.zeroCreate nintpts - for i in 0 .. (nintpts - 1) do + for i = 0 to (nintpts - 1) do xTransform.[i] <- float32 <| cos phi_1 * xint.[i] - sin phi_1 * yint.[i] + h1 yTransform.[i] <- float32 <| sin phi_1 * xint.[i] + cos phi_1 * yint.[i] + k1 Some (float32 area, xTransform, yTransform) \ No newline at end of file diff --git a/Parasitemia/ParasitemiaCore/Ellipse.fs b/Parasitemia/ParasitemiaCore/Ellipse.fs index b39ab4d..174dd68 100644 --- a/Parasitemia/ParasitemiaCore/Ellipse.fs +++ b/Parasitemia/ParasitemiaCore/Ellipse.fs @@ -22,13 +22,17 @@ let minimumDistanceBetweenDrawnPoints = 0.6 /// 'Ellipse.A' is always equal or greater than Ellipse.B. /// 'Ellipse.Alpha' is between 0 and Pi. /// -let ellipse (p1x: float) (p1y: float) (m1: float) (p2x: float) (p2y: float) (m2: float) (p3x: float) (p3y: float) : Types.Ellipse option = +let ellipse (p1x : float) (p1y : float) (m1 : float) (p2x : float) (p2y : float) (m2 : float) (p3x : float) (p3y : float) : Types.Ellipse option = let p0 = pointFromTwoLines (Types.Line(float32 m1, float32 (p1y - m1 * p1x))) (Types.Line(float32 m2, float32(p2y - m2 * p2x))) let p0x, p0y = float p0.X, float p0.Y - let s = matrix [[ 1.; 0.; 0. ] - [ 0.; 0.; -0.5 ] - [ 0.; -0.5; 0. ]] + let s = + matrix + [ + [ 1.; 0.; 0. ] + [ 0.; 0.; -0.5 ] + [ 0.; -0.5; 0. ] + ] let v0 = matrix [[ 1.; p0x; p0y ]] let v1 = matrix [[ 1.; p1x; p1y ]] @@ -49,8 +53,7 @@ let ellipse (p1x: float) (p1y: float) (m1: float) (p2x: float) (p2y: float) (m2: let cy = d / a let at = c * f - e ** 2. + (e * d - b * f) * cx + (b * e - c * d) * cy - if at = 0. - then + if at = 0. then None else let q = (-1. / at) * (matrix [[ a * f - d ** 2.0; b * d - a * e ]; [ b * d - a * e; a * c - b ** 2.0 ]]) @@ -59,8 +62,7 @@ let ellipse (p1x: float) (p1y: float) (m1: float) (p2x: float) (p2y: float) (m2: let lambda = eigenValues.[1].Real let mu = eigenValues.[0].Real - if lambda <= 0. || mu <= 0. - then + if lambda <= 0. || mu <= 0. then None else let r1, r2 = 1. / (sqrt lambda), 1. / (sqrt mu) @@ -77,20 +79,17 @@ let ellipse (p1x: float) (p1y: float) (m1: float) (p2x: float) (p2y: float) (m2: Some (Types.Ellipse(float32 cx, float32 cy, float32 majorAxis, float32 minorAxis, float32 phi')) -let inline private vectorRotation (px: float32) (py: float32) (vx: float32) (vy: float32) (p0x: float32) (p0y: float32) : float32 = - if py > p0y - then +let inline private vectorRotation (px : float32) (py : float32) (vx : float32) (vy : float32) (p0x : float32) (p0y : float32) : float32 = + if py > p0y then if vx > 0.f then -1.f else 1.f - elif py < p0y - then + elif py < p0y then if vx < 0.f then -1.f else 1.f - elif px > p0x - then + elif px > p0x then if vy < 0.f then -1.f else 1.f else // p1x < px if vy > 0.f then -1.f else 1.f -let private areVectorsValid (p1x: float32) (p1y: float32) (p2x: float32) (p2y: float32) (v1x: float32) (v1y: float32) (v2x: float32) (v2y: float32) : (float32 * float32) option = +let private areVectorsValid (p1x : float32) (p1y : float32) (p2x : float32) (p2y : float32) (v1x : float32) (v1y : float32) (v2x : float32) (v2y : float32) : (float32 * float32) option = let m1 = -v1x / v1y let m2 = -v2x / v2y @@ -102,8 +101,7 @@ let private areVectorsValid (p1x: float32) (p1y: float32) (p2x: float32) (p2y: f let rot1 = vectorRotation p1x p1y v1x v1y p0x p0y let rot2 = vectorRotation p2x p2y v2x v2y p0x p0y - if rot1 = rot2 - then + if rot1 = rot2 then None else let alpha1 = @@ -116,8 +114,7 @@ let private areVectorsValid (p1x: float32) (p1y: float32) (p2x: float32) (p2y: f let diff = rot1 * alpha1 + rot2 * alpha2 - if diff > PI || (diff < 0.f && diff > -PI) - then + if diff > PI || (diff < 0.f && diff > -PI) then Some (m1, m2) else None @@ -125,10 +122,10 @@ let private areVectorsValid (p1x: float32) (p1y: float32) (p2x: float32) (p2y: f /// /// Build a set of ellipses as a 'MatchingEllipses' object by finding ellipses with the given edges and gradient. /// -let find (edges: Matrix) - (xGradient: Matrix) - (yGradient: Matrix) - (config: Config) : MatchingEllipses = +let find (edges : Matrix) + (xGradient : Matrix) + (yGradient : Matrix) + (config : Config) : MatchingEllipses = let r1, r2 = config.RBCRadius.Min, config.RBCRadius.Max let incrementWindowDivisor = 4.f @@ -173,27 +170,25 @@ let find (edges: Matrix) // Remove old elements. let indexFirstElement = currentElements.FindIndex(fun p -> p.X >= window_j_begin) - if indexFirstElement > 0 - then currentElements.RemoveRange(0, indexFirstElement) + if indexFirstElement > 0 then + currentElements.RemoveRange(0, indexFirstElement) // Add the new elements. let newElemsBegin_j = window_j + windowSize - increment let newElemsEnd_j = window_j + windowSize - 1 for j = (if newElemsBegin_j < 0 then 0 else newElemsBegin_j) to (if newElemsEnd_j >= w then w - 1 else newElemsEnd_j) do for i = window_i_begin to window_i_end do - if edgesData.[i, j] = 1uy - then currentElements.Add(Point(j, i)) + if edgesData.[i, j] = 1uy then + currentElements.Add(Point(j, i)) - if currentElements.Count >= nbPickElementsMin - then + if currentElements.Count >= nbPickElementsMin then let mutable nbOfPicks = (float currentElements.Count) * factorNbMaxPick |> int let mutable nbOfValidPicks = (float currentElements.Count) * factorNbValidPick |> int while nbOfPicks > 0 && nbOfValidPicks > 0 do let p1 = currentElements.[rng.Next(currentElements.Count)] let p2 = currentElements.[rng.Next(currentElements.Count)] let p3 = currentElements.[rng.Next(currentElements.Count)] - if p1 <> p2 && p1 <> p3 && p2 <> p3 - then + if p1 <> p2 && p1 <> p3 && p2 <> p3 then nbOfPicks <- nbOfPicks - 1 let p1yf, p1xf = float p1.Y, float p1.X let p2yf, p2xf = float p2.Y, float p2.X diff --git a/Parasitemia/ParasitemiaCore/Granulometry.fs b/Parasitemia/ParasitemiaCore/Granulometry.fs index 004a25e..bbb6d66 100644 --- a/Parasitemia/ParasitemiaCore/Granulometry.fs +++ b/Parasitemia/ParasitemiaCore/Granulometry.fs @@ -15,7 +15,7 @@ open Utils /// /// Minimum radius * maximum radius /// le 1.0, to speed up the process. -let findRadiusByClosing (img: Image) (range: int * int) (scale: float) (useOctagon: bool) : int = +let findRadiusByClosing (img : Image) (range : int * int) (scale : float) (useOctagon : bool) : int = use scaledImg = if scale = 1. then img else img.Resize(scale, CvEnum.Inter.Area) let r1, r2 = range @@ -25,14 +25,13 @@ let findRadiusByClosing (img: Image) (range: int * int) (scale: f let intensityImg = scaledImg.GetSum().Intensity // 's' must be odd. - let octagon (s: int) : Matrix = + let octagon (s : int) : Matrix = if s % 2 = 0 then failwith "s must be odd" let m = new Matrix(Array2D.create s s 1uy) let r = (float s) / (Math.Sqrt 2. + 2.) |> roundInt for i = 0 to r - 1 do for j = 0 to r - 1 do - if i + j < r - then + if i + j < r then m.[i, j] <- 0uy m.[s - i - 1, j] <- 0uy m.[i, s - j - 1] <- 0uy @@ -41,16 +40,17 @@ let findRadiusByClosing (img: Image) (range: int * int) (scale: f let mutable previous_n = Double.NaN for r = r1' to r2' do - let se = if useOctagon - then (octagon (2 * r - 1)).Mat // It doesn't speed up the process. - else CvInvoke.GetStructuringElement(CvEnum.ElementShape.Ellipse, Size(2 * r, 2 * r), Point(-1, -1)) + let se = + if useOctagon then + (octagon (2 * r - 1)).Mat // It doesn't speed up the process. + else + CvInvoke.GetStructuringElement(CvEnum.ElementShape.Ellipse, Size(2 * r, 2 * r), Point(-1, -1)) use closed = scaledImg.MorphologyEx(CvEnum.MorphOp.Close, se, Point(-1, -1), 1, CvEnum.BorderType.Replicate, MCvScalar(0.0)) let n = closed.GetSum().Intensity - if not (Double.IsNaN previous_n) - then + if not (Double.IsNaN previous_n) then patternSpectrum.[r - r1' - 1] <- abs (n - previous_n) previous_n <- n @@ -61,11 +61,11 @@ let findRadiusByClosing (img: Image) (range: int * int) (scale: f /// /// Granulometry by area closing on the image 'img' by testing the circle area into the given radius range. /// -let findRadiusByAreaClosing (img: Image) (radiusRange: int * int) : int = +let findRadiusByAreaClosing (img : Image) (radiusRange : int * int) : int = let r1, r2 = radiusRange - if r1 > r2 - then failwithf "'radiusRange' invalid : %A" radiusRange + if r1 > r2 then + failwithf "'radiusRange' invalid : %O" radiusRange use imgCopy = img.Copy() @@ -73,8 +73,7 @@ let findRadiusByAreaClosing (img: Image) (radiusRange: int * int) let mutable max_r = r1 Morpho.areaCloseFWithFun imgCopy [ for r in r1 .. r2 -> Math.PI * float r ** 2. |> roundInt, r ] (fun r diff -> - if r <> r1 && diff > maxDiff - then + if r <> r1 && diff > maxDiff then maxDiff <- diff max_r <- r - 1) max_r diff --git a/Parasitemia/ParasitemiaCore/Heap.fs b/Parasitemia/ParasitemiaCore/Heap.fs index 32887a2..350164b 100644 --- a/Parasitemia/ParasitemiaCore/Heap.fs +++ b/Parasitemia/ParasitemiaCore/Heap.fs @@ -2,16 +2,16 @@ open System.Collections.Generic -let inline private parent (i: int) : int = (i - 1) / 2 -let inline private left (i: int) : int = 2 * (i + 1) - 1 -let inline private right (i: int) : int = 2 * (i + 1) +let inline private parent (i : int) : int = (i - 1) / 2 +let inline private left (i : int) : int = 2 * (i + 1) - 1 +let inline private right (i : int) : int = 2 * (i + 1) [] type private Node<'k, 'v> = - val mutable key: 'k - val value: 'v + val mutable key : 'k + val value : 'v new (k, v) = { key = k; value = v } - override this.ToString () = sprintf "%A -> %A" this.key this.value + override this.ToString () = sprintf "%O -> %O" this.key this.value /// /// An heap min or max depending of the given key comparer. @@ -20,42 +20,34 @@ type private Node<'k, 'v> = type Heap<'k, 'v> (kComparer : IComparer<'k>) = let a = List>() - let rec heapUp (i: int) = + let rec heapUp (i : int) = let l, r = left i, right i // Is the left child greater than the parent? let mutable max = if l < a.Count && kComparer.Compare(a.[l].key, a.[i].key) > 0 then l else i // Is the right child greater than the parent and the left child? - if r < a.Count && kComparer.Compare(a.[r].key, a.[max].key) > 0 - then + if r < a.Count && kComparer.Compare(a.[r].key, a.[max].key) > 0 then max <- r // If a child is greater than the parent. - if max <> i - then + if max <> i then let tmp = a.[i] a.[i] <- a.[max] a.[max] <- tmp heapUp max // Check the integrity of the heap, use for debug only. - let rec checkIntegrity (i: int) : bool = + let rec checkIntegrity (i : int) : bool = let l, r = left i, right i let leftIntegrity = - if l < a.Count - then - if kComparer.Compare(a.[l].key, a.[i].key) > 0 - then false - else checkIntegrity l + if l < a.Count then + if kComparer.Compare(a.[l].key, a.[i].key) > 0 then false else checkIntegrity l else true let rightIntegrity = - if r < a.Count - then - if kComparer.Compare(a.[r].key, a.[i].key) > 0 - then false - else checkIntegrity r + if r < a.Count then + if kComparer.Compare(a.[r].key, a.[i].key) > 0 then false else checkIntegrity r else true leftIntegrity && rightIntegrity @@ -80,7 +72,7 @@ type Heap<'k, 'v> (kComparer : IComparer<'k>) = a.RemoveAt(a.Count - 1) heapUp 0 - member this.Add (key: 'k) (value: 'v) = + member this.Add (key : 'k) (value : 'v) = a.Add(Node(key, value)) let mutable i = a.Count - 1 diff --git a/Parasitemia/ParasitemiaCore/ImgTools/Drawing.fs b/Parasitemia/ParasitemiaCore/ImgTools/Drawing.fs index edf4cd2..9662049 100644 --- a/Parasitemia/ParasitemiaCore/ImgTools/Drawing.fs +++ b/Parasitemia/ParasitemiaCore/ImgTools/Drawing.fs @@ -9,19 +9,18 @@ open Emgu.CV.Structure open Const open Types -let drawPoints (img: Image) (points: Points) (intensity: 'TDepth) = +let drawPoints (img : Image) (points : Points) (intensity : 'TDepth) = for p in points do img.Data.[p.Y, p.X, 0] <- intensity -let drawLine (img: Image<'TColor, 'TDepth>) (color: 'TColor) (x0: int) (y0: int) (x1: int) (y1: int) (thickness: int) = +let drawLine (img : Image<'TColor, 'TDepth>) (color : 'TColor) (x0 : int) (y0 : int) (x1 : int) (y1 : int) (thickness : int) = img.Draw(LineSegment2D(Point(x0, y0), Point(x1, y1)), color, thickness); -let drawLineF (img: Image<'TColor, 'TDepth>) (color: 'TColor) (x0: float) (y0: float) (x1: float) (y1: float) (thickness: int) = +let drawLineF (img : Image<'TColor, 'TDepth>) (color : 'TColor) (x0 : float) (y0 : float) (x1 : float) (y1 : float) (thickness : int) = img.Draw(LineSegment2DF(PointF(float32 x0, float32 y0), PointF(float32 x1, float32 y1)), color, thickness, CvEnum.LineType.AntiAlias); -let drawEllipse (img: Image<'TColor, 'TDepth>) (e: Ellipse) (color: 'TColor) (alpha: float) = - if alpha >= 1.0 - then +let drawEllipse (img : Image<'TColor, 'TDepth>) (e : Ellipse) (color : 'TColor) (alpha : float) = + if alpha >= 1.0 then img.Draw(Emgu.CV.Structure.Ellipse(PointF(e.Cx, e.Cy), SizeF(2.f * e.B, 2.f * e.A), e.Alpha / PI * 180.f), color, 1, CvEnum.LineType.AntiAlias) else let windowPosX = e.Cx - e.A - 5.f @@ -33,28 +32,25 @@ let drawEllipse (img: Image<'TColor, 'TDepth>) (e: Ellipse) (color: 'TColor) (al let roi = Rectangle(int windowPosX, int windowPosY, 2.f * (e.A + 5.f) |> int, 2.f * (e.A + 5.f) |> int) img.ROI <- roi - if roi = img.ROI // We do not display ellipses touching the edges (FIXME) - then + if roi = img.ROI then // We do not display ellipses touching the edges (FIXME) use i = new Image<'TColor, 'TDepth>(img.ROI.Size) i.Draw(Emgu.CV.Structure.Ellipse(PointF(e.A + 5.f + gapX, e.A + 5.f + gapY), SizeF(2.f * e.B, 2.f * e.A), e.Alpha / PI * 180.f), color, 1, CvEnum.LineType.AntiAlias) CvInvoke.AddWeighted(img, 1.0, i, alpha, 0.0, img) img.ROI <- Rectangle.Empty -let drawEllipses (img: Image<'TColor, 'TDepth>) (ellipses: Ellipse list) (color: 'TColor) (alpha: float) = +let drawEllipses (img : Image<'TColor, 'TDepth>) (ellipses : Ellipse list) (color : 'TColor) (alpha : float) = List.iter (fun e -> drawEllipse img e color alpha) ellipses let rngCell = System.Random() -let drawCell (img: Image) (drawCellContent: bool) (c: Cell) = - if drawCellContent - then +let drawCell (img : Image) (drawCellContent : bool) (c : Cell) = + if drawCellContent then let colorB = rngCell.Next(20, 70) let colorG = rngCell.Next(20, 70) let colorR = rngCell.Next(20, 70) for y = 0 to c.elements.Height - 1 do for x = 0 to c.elements.Width - 1 do - if c.elements.[y, x] > 0uy - then + if c.elements.[y, x] > 0uy then let dx, dy = c.center.X - c.elements.Width / 2, c.center.Y - c.elements.Height / 2 let b = img.Data.[y + dy, x + dx, 0] |> int let g = img.Data.[y + dy, x + dx, 1] |> int @@ -75,5 +71,5 @@ let drawCell (img: Image) (drawCellContent: bool) (c: Cell) = drawLine img crossColor (c.center.X - 3) c.center.Y (c.center.X + 3) c.center.Y 1 drawLine img crossColor c.center.X (c.center.Y - 3) c.center.X (c.center.Y + 3) 1 -let drawCells (img: Image) (drawCellContent: bool) (cells: Cell list) = +let drawCells (img : Image) (drawCellContent : bool) (cells : Cell list) = List.iter (fun c -> drawCell img drawCellContent c) cells \ No newline at end of file diff --git a/Parasitemia/ParasitemiaCore/ImgTools/Edges.fs b/Parasitemia/ParasitemiaCore/ImgTools/Edges.fs index df5858d..63ec80a 100644 --- a/Parasitemia/ParasitemiaCore/ImgTools/Edges.fs +++ b/Parasitemia/ParasitemiaCore/ImgTools/Edges.fs @@ -20,14 +20,16 @@ let sensibilityLow = 0.0f /// The thresholds are automatically defined with otsu on gradient magnitudes. /// /// -let find (img: Image) : Matrix * Matrix * Matrix = +let find (img : Image) : Matrix * Matrix * Matrix = let w = img.Width let h = img.Height use sobelKernel = - new Matrix(array2D [[ -1.0f; 0.0f; 1.0f ] - [ -2.0f; 0.0f; 2.0f ] - [ -1.0f; 0.0f; 1.0f ]]) + new Matrix( + array2D [[ -1.0f; 0.0f; 1.0f ] + [ -2.0f; 0.0f; 2.0f ] + [ -1.0f; 0.0f; 1.0f ]] + ) let xGradient = new Matrix(img.Size) let yGradient = new Matrix(img.Size) @@ -55,34 +57,33 @@ let find (img: Image) : Matrix * Matrix * Matrix 0.f || vy <> 0.f - then + if vx <> 0.f || vy <> 0.f then let angle = anglesData.[i, j] let vx', vy' = abs vx, abs vy let ratio2 = if vx' > vy' then vy' / vx' else vx' / vy' let ratio1 = 1.f - ratio2 - let mNeigbors (sign: int) : float32 = - if angle < PI / 4.f - then ratio1 * magnitudesData.[i, j + sign] + ratio2 * magnitudesData.[i + sign, j + sign] - elif angle < PI / 2.f - then ratio2 * magnitudesData.[i + sign, j + sign] + ratio1 * magnitudesData.[i + sign, j] - elif angle < 3.f * PI / 4.f - then ratio1 * magnitudesData.[i + sign, j] + ratio2 * magnitudesData.[i + sign, j - sign] - elif angle < PI - then ratio2 * magnitudesData.[i + sign, j - sign] + ratio1 * magnitudesData.[i, j - sign] - elif angle < 5.f * PI / 4.f - then ratio1 * magnitudesData.[i, j - sign] + ratio2 * magnitudesData.[i - sign, j - sign] - elif angle < 3.f * PI / 2.f - then ratio2 * magnitudesData.[i - sign, j - sign] + ratio1 * magnitudesData.[i - sign, j] - elif angle < 7.f * PI / 4.f - then ratio1 * magnitudesData.[i - sign, j] + ratio2 * magnitudesData.[i - sign, j + sign] - else ratio2 * magnitudesData.[i - sign, j + sign] + ratio1 * magnitudesData.[i, j + sign] + let mNeigbors (sign : int) : float32 = + if angle < PI / 4.f then + ratio1 * magnitudesData.[i, j + sign] + ratio2 * magnitudesData.[i + sign, j + sign] + elif angle < PI / 2.f then + ratio2 * magnitudesData.[i + sign, j + sign] + ratio1 * magnitudesData.[i + sign, j] + elif angle < 3.f * PI / 4.f then + ratio1 * magnitudesData.[i + sign, j] + ratio2 * magnitudesData.[i + sign, j - sign] + elif angle < PI then + ratio2 * magnitudesData.[i + sign, j - sign] + ratio1 * magnitudesData.[i, j - sign] + elif angle < 5.f * PI / 4.f then + ratio1 * magnitudesData.[i, j - sign] + ratio2 * magnitudesData.[i - sign, j - sign] + elif angle < 3.f * PI / 2.f then + ratio2 * magnitudesData.[i - sign, j - sign] + ratio1 * magnitudesData.[i - sign, j] + elif angle < 7.f * PI / 4.f then + ratio1 * magnitudesData.[i - sign, j] + ratio2 * magnitudesData.[i - sign, j + sign] + else + ratio2 * magnitudesData.[i - sign, j + sign] + ratio1 * magnitudesData.[i, j + sign] let m = magnitudesData.[i, j] - if m >= thresholdLow && m > mNeigbors 1 && m > mNeigbors -1 - then + if m >= thresholdLow && m > mNeigbors 1 && m > mNeigbors -1 then nmsData.[i, j] <- 1uy // suppressMConnections nms // It's not helpful for the rest of the process (ellipse detection). @@ -94,8 +95,7 @@ let find (img: Image) : Matrix * Matrix * Matrix() for i = 0 to h - 1 do for j = 0 to w - 1 do - if nmsData.[i, j] = 1uy && magnitudesData.[i, j] >= thresholdHigh - then + if nmsData.[i, j] = 1uy && magnitudesData.[i, j] >= thresholdHigh then nmsData.[i, j] <- 0uy toVisit.Push(Point(j, i)) while toVisit.Count > 0 do @@ -103,12 +103,10 @@ let find (img: Image) : Matrix * Matrix * Matrix 0 || j' <> 0 - then + if i' <> 0 || j' <> 0 then let ni = p.Y + i' let nj = p.X + j' - if ni >= 0 && ni < h && nj >= 0 && nj < w && nmsData.[ni, nj] = 1uy - then + if ni >= 0 && ni < h && nj >= 0 && nj < w && nmsData.[ni, nj] = 1uy then nmsData.[ni, nj] <- 0uy toVisit.Push(Point(nj, ni)) diff --git a/Parasitemia/ParasitemiaCore/ImgTools/Histogram.fs b/Parasitemia/ParasitemiaCore/ImgTools/Histogram.fs index e3a9950..df1ecb6 100644 --- a/Parasitemia/ParasitemiaCore/ImgTools/Histogram.fs +++ b/Parasitemia/ParasitemiaCore/ImgTools/Histogram.fs @@ -6,14 +6,16 @@ open System.Drawing open Emgu.CV open Emgu.CV.Structure -type Histogram = { - data: int[] - total: int // Number of elements. - sum: int // Sum of all intensity. - min: float32 - max: float32 } - -let histogramImg (img: Image) (nbSamples: int) : Histogram = +type Histogram = + { + data : int[] + total : int // Number of elements. + sum : int // Sum of all intensity. + min : float32 + max : float32 + } + +let histogramImg (img : Image) (nbSamples : int) : Histogram = let imgData = img.Data let min, max = @@ -24,7 +26,7 @@ let histogramImg (img: Image) (nbSamples: int) : Histogram = img.MinMax(min, max, minLocation, maxLocation) float32 (!min).[0], float32 (!max).[0] - let inline bin (x: float32) : int = + let inline bin (x : float32) : int = let p = int ((x - min) / (max - min) * float32 nbSamples) if p >= nbSamples then nbSamples - 1 else p @@ -37,7 +39,7 @@ let histogramImg (img: Image) (nbSamples: int) : Histogram = { data = data; total = img.Height * img.Width; sum = Array.sum data; min = min; max = max } -let histogramMat (mat: Matrix) (nbSamples: int) : Histogram = +let histogramMat (mat : Matrix) (nbSamples : int) : Histogram = let matData = mat.Data let min, max = @@ -48,7 +50,7 @@ let histogramMat (mat: Matrix) (nbSamples: int) : Histogram = mat.MinMax(min, max, minLocation, maxLocation) float32 !min, float32 !max - let inline bin (x: float32) : int = + let inline bin (x : float32) : int = let p = int ((x - min) / (max - min) * float32 nbSamples) if p >= nbSamples then nbSamples - 1 else p @@ -61,7 +63,7 @@ let histogramMat (mat: Matrix) (nbSamples: int) : Histogram = { data = data; total = mat.Height * mat.Width; sum = Array.sum data; min = min; max = max } -let histogram (values: float32 seq) (nbSamples: int) : Histogram = +let histogram (values : float32 seq) (nbSamples : int) : Histogram = let mutable min = Single.MaxValue let mutable max = Single.MinValue let mutable n = 0 @@ -71,7 +73,7 @@ let histogram (values: float32 seq) (nbSamples: int) : Histogram = if v < min then min <- v if v > max then max <- v - let inline bin (x: float32) : int = + let inline bin (x : float32) : int = let p = int ((x - min) / (max - min) * float32 nbSamples) if p >= nbSamples then nbSamples - 1 else p diff --git a/Parasitemia/ParasitemiaCore/ImgTools/IO.fs b/Parasitemia/ParasitemiaCore/ImgTools/IO.fs index 219eab6..1a663c7 100644 --- a/Parasitemia/ParasitemiaCore/ImgTools/IO.fs +++ b/Parasitemia/ParasitemiaCore/ImgTools/IO.fs @@ -6,10 +6,10 @@ open System.Drawing open Emgu.CV open Emgu.CV.Structure -let saveImg (img: Image<'TColor, 'TDepth>) (filepath: string) = +let saveImg (img : Image<'TColor, 'TDepth>) (filepath : string) = img.Save(filepath) -let saveMat (mat: Matrix<'TDepth>) (filepath: string) = +let saveMat (mat : Matrix<'TDepth>) (filepath : string) = use img = new Image(mat.Size) mat.CopyTo(img) saveImg img filepath diff --git a/Parasitemia/ParasitemiaCore/ImgTools/ImgTools.fs b/Parasitemia/ParasitemiaCore/ImgTools/ImgTools.fs index ae542ce..5a404e3 100644 --- a/Parasitemia/ParasitemiaCore/ImgTools/ImgTools.fs +++ b/Parasitemia/ParasitemiaCore/ImgTools/ImgTools.fs @@ -12,18 +12,19 @@ open Emgu.CV.Structure /// /// /// -let normalize (img: Image) (upperLimit: float) : Image = +let normalize (img : Image) (upperLimit : float) : Image = let min = ref [| 0.0 |] let minLocation = ref <| [| Point() |] let max = ref [| 0.0 |] let maxLocation = ref <| [| Point() |] img.MinMax(min, max, minLocation, maxLocation) let normalized = (img - (!min).[0]) / ((!max).[0] - (!min).[0]) - if upperLimit = 1.0 - then normalized - else upperLimit * normalized + if upperLimit = 1.0 then + normalized + else + upperLimit * normalized -let mergeChannels (img: Image) (rgbWeights: float * float * float) : Image = +let mergeChannels (img : Image) (rgbWeights : float * float * float) : Image = match rgbWeights with | 1., 0., 0. -> img.[2] | 0., 1., 0. -> img.[1] @@ -35,10 +36,10 @@ let mergeChannels (img: Image) (rgbWeights: float * float * float) CvInvoke.AddWeighted(result, 1., img.[0], blueFactor, 0., result) result -let mergeChannelsWithProjection (img: Image) (v1r: float32, v1g: float32, v1b: float32) (v2r: float32, v2g: float32, v2b: float32) (upperLimit: float) : Image = +let mergeChannelsWithProjection (img : Image) (v1r : float32, v1g : float32, v1b : float32) (v2r : float32, v2g : float32, v2b : float32) (upperLimit : float) : Image = let vr, vg, vb = v2r - v1r, v2g - v1g, v2b - v1b let vMagnitude = sqrt (vr ** 2.f + vg ** 2.f + vb ** 2.f) - let project (r: float32) (g: float32) (b: float32) = ((r - v1r) * vr + (g - v1g) * vg + (b - v1b) * vb) / vMagnitude + let project (r : float32) (g : float32) (b : float32) = ((r - v1r) * vr + (g - v1g) * vg + (b - v1b) * vb) / vMagnitude let result = new Image(img.Size) // TODO: Essayer en bindant Data pour gagner du temps for i = 0 to img.Height - 1 do @@ -47,7 +48,7 @@ let mergeChannelsWithProjection (img: Image) (v1r: float32, v1g: f normalize result upperLimit // Normalize image values between 0uy and 255uy. -let normalizeAndConvert (img: Image) : Image = +let normalizeAndConvert (img : Image) : Image = (normalize (img.Convert()) 255.).Convert() let gaussianFilter (img : Image<'TColor, 'TDepth>) (standardDeviation : float) : Image<'TColor, 'TDepth> = diff --git a/Parasitemia/ParasitemiaCore/ImgTools/Morpho.fs b/Parasitemia/ParasitemiaCore/ImgTools/Morpho.fs index 588622e..4cb1f0b 100644 --- a/Parasitemia/ParasitemiaCore/ImgTools/Morpho.fs +++ b/Parasitemia/ParasitemiaCore/ImgTools/Morpho.fs @@ -13,35 +13,33 @@ open Types /// /// Remove M-adjacent pixels. It may be used after thinning. /// -let suppressMAdjacency (img: Matrix) = +let suppressMAdjacency (img : Matrix) = let w = img.Width let h = img.Height for i = 1 to h - 2 do for j = 1 to w - 2 do - if img.[i, j] > 0uy && img.Data.[i + 1, j] > 0uy && (img.Data.[i, j - 1] > 0uy && img.Data.[i - 1, j + 1] = 0uy || img.Data.[i, j + 1] > 0uy && img.Data.[i - 1, j - 1] = 0uy) - then + if img.[i, j] > 0uy && img.Data.[i + 1, j] > 0uy && (img.Data.[i, j - 1] > 0uy && img.Data.[i - 1, j + 1] = 0uy || img.Data.[i, j + 1] > 0uy && img.Data.[i - 1, j - 1] = 0uy) then img.[i, j] <- 0uy for i = 1 to h - 2 do for j = 1 to w - 2 do - if img.[i, j] > 0uy && img.Data.[i - 1, j] > 0uy && (img.Data.[i, j - 1] > 0uy && img.Data.[i + 1, j + 1] = 0uy || img.Data.[i, j + 1] > 0uy && img.Data.[i + 1, j - 1] = 0uy) - then + if img.[i, j] > 0uy && img.Data.[i - 1, j] > 0uy && (img.Data.[i, j - 1] > 0uy && img.Data.[i + 1, j + 1] = 0uy || img.Data.[i, j + 1] > 0uy && img.Data.[i + 1, j - 1] = 0uy) then img.[i, j] <- 0uy type ExtremumType = | Maxima = 1 | Minima = 2 -let findExtremum (img: Image) (extremumType: ExtremumType) : IEnumerable = +let findExtremum (img : Image) (extremumType : ExtremumType) : IEnumerable = let w = img.Width let h = img.Height let se = [| -1, 0; 0, -1; 1, 0; 0, 1 |] let imgData = img.Data - let suppress: bool[,] = Array2D.zeroCreate h w + let suppress : bool[,] = Array2D.zeroCreate h w let result = List>() - let flood (start: Point) : List> = + let flood (start : Point) : List> = let sameLevelToCheck = Stack() let betterLevelToCheck = Stack() betterLevelToCheck.Push(start) @@ -50,8 +48,7 @@ let findExtremum (img: Image) (extremumType: ExtremumType) : IEnu while betterLevelToCheck.Count > 0 do let p = betterLevelToCheck.Pop() - if not suppress.[p.Y, p.X] - then + if not suppress.[p.Y, p.X] then suppress.[p.Y, p.X] <- true sameLevelToCheck.Push(p) let current = List() @@ -65,51 +62,44 @@ let findExtremum (img: Image) (extremumType: ExtremumType) : IEnu for i, j in se do let ni = i + p'.Y let nj = j + p'.X - if ni >= 0 && ni < h && nj >= 0 && nj < w - then + if ni >= 0 && ni < h && nj >= 0 && nj < w then let level = imgData.[ni, nj, 0] let notSuppressed = not suppress.[ni, nj] - if level = currentLevel && notSuppressed - then + if level = currentLevel && notSuppressed then suppress.[ni, nj] <- true sameLevelToCheck.Push(Point(nj, ni)) - elif if extremumType = ExtremumType.Maxima then level > currentLevel else level < currentLevel - then + elif (if extremumType = ExtremumType.Maxima then level > currentLevel else level < currentLevel) then betterExists <- true - if notSuppressed - then + if notSuppressed then betterLevelToCheck.Push(Point(nj, ni)) - if not betterExists - then + if not betterExists then result'.Add(current) result' for i = 0 to h - 1 do for j = 0 to w - 1 do let maxima = flood (Point(j, i)) - if maxima.Count > 0 - then + if maxima.Count > 0 then result.AddRange(maxima) result.Select(fun l -> Points(l)) -let findMaxima (img: Image) : IEnumerable = +let findMaxima (img : Image) : IEnumerable = findExtremum img ExtremumType.Maxima -let findMinima (img: Image) : IEnumerable = +let findMinima (img : Image) : IEnumerable = findExtremum img ExtremumType.Minima type PriorityQueue () = let size = 256 - let q: Points[] = Array.init size (fun i -> Points()) + let q : Points[] = Array.init size (fun i -> Points()) let mutable highest = -1 // Value of the first elements of 'q'. let mutable lowest = size member this.NextMax () : byte * Point = - if this.IsEmpty - then + if this.IsEmpty then invalidOp "Queue is empty" else let l = q.[highest] @@ -117,21 +107,18 @@ type PriorityQueue () = l.Remove(next) |> ignore let value = byte highest - if l.Count = 0 - then + if l.Count = 0 then highest <- highest - 1 while highest > lowest && q.[highest].Count = 0 do highest <- highest - 1 - if highest = lowest - then + if highest = lowest then highest <- -1 lowest <- size value, next member this.NextMin () : byte * Point = - if this.IsEmpty - then + if this.IsEmpty then invalidOp "Queue is empty" else let l = q.[lowest + 1] @@ -139,13 +126,11 @@ type PriorityQueue () = l.Remove(next) |> ignore let value = byte (lowest + 1) - if l.Count = 0 - then + if l.Count = 0 then lowest <- lowest + 1 while lowest < highest && q.[lowest + 1].Count = 0 do lowest <- lowest + 1 - if highest = lowest - then + if highest = lowest then highest <- -1 lowest <- size @@ -157,35 +142,29 @@ type PriorityQueue () = member this.Min = lowest + 1 |> byte - member this.Add (value: byte) (p: Point) = + member this.Add (value : byte) (p : Point) = let vi = int value - if vi > highest - then + if vi > highest then highest <- vi - if vi <= lowest - then + if vi <= lowest then lowest <- vi - 1 q.[vi].Add(p) |> ignore - member this.Remove (value: byte) (p: Point) = + member this.Remove (value : byte) (p : Point) = let vi = int value - if q.[vi].Remove(p) && q.[vi].Count = 0 - then - if vi = highest - then + if q.[vi].Remove(p) && q.[vi].Count = 0 then + if vi = highest then highest <- highest - 1 while highest > lowest && q.[highest].Count = 0 do highest <- highest - 1 - elif vi - 1 = lowest - then + elif vi - 1 = lowest then lowest <- lowest + 1 while lowest < highest && q.[lowest + 1].Count = 0 do lowest <- lowest + 1 - if highest = lowest // The queue is now empty. - then + if highest = lowest then // The queue is now empty. highest <- -1 lowest <- size @@ -209,12 +188,12 @@ type private AreaOperation = | Closing = 2 [] -type private Area (elements: Points) = +type private Area (elements : Points) = member this.Elements = elements member val Intensity = None with get, set member val State = AreaState.Unprocessed with get, set -let private areaOperation (img: Image) (area: int) (op: AreaOperation) = +let private areaOperation (img : Image) (area : int) (op : AreaOperation) = let w = img.Width let h = img.Height let imgData = img.Data @@ -222,28 +201,26 @@ let private areaOperation (img: Image) (area: int) (op: AreaOperatio let areas = List((if op = AreaOperation.Opening then findMaxima img else findMinima img) |> Seq.map Area) - let pixels: Area[,] = Array2D.create h w null + let pixels : Area[,] = Array2D.create h w null for m in areas do for e in m.Elements do pixels.[e.Y, e.X] <- m let queue = PriorityQueue() - let addEdgeToQueue (elements: Points) = + let addEdgeToQueue (elements : Points) = for p in elements do for i, j in se do let ni = i + p.Y let nj = j + p.X let p' = Point(nj, ni) - if ni >= 0 && ni < h && nj >= 0 && nj < w && not (elements.Contains(p')) - then + if ni >= 0 && ni < h && nj >= 0 && nj < w && not (elements.Contains(p')) then queue.Add (imgData.[ni, nj, 0]) p' // Reverse order is quicker. for i = areas.Count - 1 downto 0 do let m = areas.[i] - if m.Elements.Count <= area && m.State <> AreaState.Removed - then + if m.Elements.Count <= area && m.State <> AreaState.Removed then queue.Clear() addEdgeToQueue m.Elements @@ -255,24 +232,20 @@ let private areaOperation (img: Image) (area: int) (op: AreaOperatio let intensity', p = if op = AreaOperation.Opening then queue.NextMax () else queue.NextMin () let mutable merged = false - if intensity' = intensity // The intensity doesn't change. - then - if m.Elements.Count + nextElements.Count + 1 > area - then + if intensity' = intensity then // The intensity doesn't change. + if m.Elements.Count + nextElements.Count + 1 > area then m.State <- AreaState.Validated m.Intensity <- Some intensity stop <- true else nextElements.Add(p) |> ignore - elif if op = AreaOperation.Opening then intensity' < intensity else intensity' > intensity - then + elif (if op = AreaOperation.Opening then intensity' < intensity else intensity' > intensity) then m.Elements.UnionWith(nextElements) for e in nextElements do pixels.[e.Y, e.X] <- m - if m.Elements.Count = area - then + if m.Elements.Count = area then m.State <- AreaState.Validated m.Intensity <- Some (intensity') stop <- true @@ -285,8 +258,7 @@ let private areaOperation (img: Image) (area: int) (op: AreaOperatio match pixels.[p.Y, p.X] with | null -> () | m' -> - if m'.Elements.Count + m.Elements.Count <= area - then + if m'.Elements.Count + m.Elements.Count <= area then m'.State <- AreaState.Removed for e in m'.Elements do pixels.[e.Y, e.X] <- m @@ -294,45 +266,37 @@ let private areaOperation (img: Image) (area: int) (op: AreaOperatio addEdgeToQueue m'.Elements m.Elements.UnionWith(m'.Elements) let intensityMax = if op = AreaOperation.Opening then queue.Max else queue.Min - if intensityMax <> intensity - then + if intensityMax <> intensity then intensity <- intensityMax nextElements.Clear() merged <- true - if not merged - then + if not merged then m.State <- AreaState.Validated m.Intensity <- Some (intensity) stop <- true - if not stop && not merged - then + if not stop && not merged then for i, j in se do let ni = i + p.Y let nj = j + p.X let p' = Point(nj, ni) - if ni < 0 || ni >= h || nj < 0 || nj >= w - then + if ni < 0 || ni >= h || nj < 0 || nj >= w then m.State <- AreaState.Validated m.Intensity <- Some (intensity) stop <- true - elif not (m.Elements.Contains(p')) && not (nextElements.Contains(p')) - then + elif not (m.Elements.Contains(p')) && not (nextElements.Contains(p')) then queue.Add (imgData.[ni, nj, 0]) p' - if queue.IsEmpty - then - if m.Elements.Count + nextElements.Count <= area - then + if queue.IsEmpty then + if m.Elements.Count + nextElements.Count <= area then m.State <- AreaState.Validated m.Intensity <- Some intensity' m.Elements.UnionWith(nextElements) stop <- true for m in areas do - if m.State = AreaState.Validated - then + if m.State = AreaState.Validated then match m.Intensity with | Some i -> for p in m.Elements do @@ -343,17 +307,17 @@ let private areaOperation (img: Image) (area: int) (op: AreaOperatio /// /// Area opening on byte image. /// -let areaOpen (img: Image) (area: int) = +let areaOpen (img : Image) (area : int) = areaOperation img area AreaOperation.Opening /// /// Area closing on byte image. /// -let areaClose (img: Image) (area: int) = +let areaClose (img : Image) (area : int) = areaOperation img area AreaOperation.Closing // A simpler algorithm than 'areaOpen' on byte image but slower. -let areaOpen2 (img: Image) (area: int) = +let areaOpen2 (img : Image) (area : int) = let w = img.Width let h = img.Height let imgData = img.Data @@ -372,12 +336,10 @@ let areaOpen2 (img: Image) (area: int) = for level = 255 downto 0 do let mutable n = histogram.[level] - if n > 0 - then + if n > 0 then for i = 0 to h - 1 do for j = 0 to w - 1 do - if not flooded.[i, j] && imgData.[i, j, 0] = byte level - then + if not flooded.[i, j] && imgData.[i, j, 0] = byte level then let mutable maxNeighborValue = 0uy pointsChecked.Clear() pointsToCheck.Clear() @@ -390,41 +352,38 @@ let areaOpen2 (img: Image) (area: int) = for nx, ny in se do let p = Point(next.X + nx, next.Y + ny) - if p.X >= 0 && p.X < w && p.Y >= 0 && p.Y < h - then + if p.X >= 0 && p.X < w && p.Y >= 0 && p.Y < h then let v = imgData.[p.Y, p.X, 0] - if v = byte level - then - if not (pointsChecked.Contains(p)) - then + if v = byte level then + if not (pointsChecked.Contains(p)) then pointsToCheck.Push(p) - elif v > maxNeighborValue - then + elif v > maxNeighborValue then maxNeighborValue <- v - if int maxNeighborValue < level && pointsChecked.Count <= area - then + if int maxNeighborValue < level && pointsChecked.Count <= area then for p in pointsChecked do imgData.[p.Y, p.X, 0] <- maxNeighborValue [] -type Island (cmp: IComparer) = +type Island (cmp : IComparer) = member val Shore = Heap.Heap(cmp) with get member val Level = 0.f with get, set member val Surface = 0 with get, set member this.IsInfinite = this.Surface = Int32.MaxValue -let private areaOperationF (img: Image) (areas: (int * 'a) list) (f: ('a -> float32 -> unit) option) (op: AreaOperation) = +let private areaOperationF (img : Image) (areas : (int * 'a) list) (f : ('a -> float32 -> unit) option) (op : AreaOperation) = let w = img.Width let h = img.Height let earth = img.Data let se = [| -1, 0; 0, -1; 1, 0; 0, 1 |] - let comparer = if op = AreaOperation.Opening - then { new IComparer with member this.Compare(v1, v2) = v1.CompareTo(v2) } - else { new IComparer with member this.Compare(v1, v2) = v2.CompareTo(v1) } + let comparer = + if op = AreaOperation.Opening then + { new IComparer with member this.Compare(v1, v2) = v1.CompareTo(v2) } + else + { new IComparer with member this.Compare(v1, v2) = v2.CompareTo(v1) } - let ownership: Island[,] = Array2D.create h w null + let ownership : Island[,] = Array2D.create h w null // Initialize islands with their shore. let islands = List() @@ -441,8 +400,7 @@ let private areaOperationF (img: Image) (areas: (int * 'a) list) let ni = i + p.Y let nj = j + p.X let neighbor = Point(nj, ni) - if ni >= 0 && ni < h && nj >= 0 && nj < w && Object.ReferenceEquals(ownership.[ni, nj], null) && not (shorePoints.Contains(neighbor)) - then + if ni >= 0 && ni < h && nj >= 0 && nj < w && Object.ReferenceEquals(ownership.[ni, nj], null) && not (shorePoints.Contains(neighbor)) then shorePoints.Add(neighbor) |> ignore island.Shore.Add earth.[ni, nj, 0] neighbor @@ -451,7 +409,7 @@ let private areaOperationF (img: Image) (areas: (int * 'a) list) let mutable stop = island.Shore.IsEmpty // 'true' if 'p' is owned or adjacent to 'island'. - let inline ownedOrAdjacent (p: Point) : bool = + let inline ownedOrAdjacent (p : Point) : bool = ownership.[p.Y, p.X] = island || (p.Y > 0 && ownership.[p.Y - 1, p.X] = island) || (p.Y < h - 1 && ownership.[p.Y + 1, p.X] = island) || @@ -461,14 +419,12 @@ let private areaOperationF (img: Image) (areas: (int * 'a) list) while not stop && island.Surface < area do let level, next = island.Shore.Max let other = ownership.[next.Y, next.X] - if other = island // During merging, some points on the shore may be owned by the island itself -> ignored. - then + if other = island then // During merging, some points on the shore may be owned by the island itself -> ignored. island.Shore.RemoveNext () else - if not <| Object.ReferenceEquals(other, null) - then // We touching another island. - if island.IsInfinite || other.IsInfinite || island.Surface + other.Surface >= area || comparer.Compare(island.Level, other.Level) < 0 - then + if not <| Object.ReferenceEquals(other, null) then + // We touching another island. + if island.IsInfinite || other.IsInfinite || island.Surface + other.Surface >= area || comparer.Compare(island.Level, other.Level) < 0 then stop <- true else // We can merge 'other' into 'surface'. island.Surface <- island.Surface + other.Surface @@ -482,25 +438,21 @@ let private areaOperationF (img: Image) (areas: (int * 'a) list) island.Shore.Add l p other.Shore.Clear() - elif comparer.Compare(level, island.Level) > 0 - then + elif comparer.Compare(level, island.Level) > 0 then stop <- true else island.Shore.RemoveNext () for i, j in se do let ni = i + next.Y let nj = j + next.X - if ni < 0 || ni >= h || nj < 0 || nj >= w - then + if ni < 0 || ni >= h || nj < 0 || nj >= w then island.Surface <- Int32.MaxValue stop <- true else let neighbor = Point(nj, ni) - if not <| ownedOrAdjacent neighbor - then + if not <| ownedOrAdjacent neighbor then island.Shore.Add earth.[ni, nj, 0] neighbor - if not stop - then + if not stop then ownership.[next.Y, next.X] <- island island.Level <- level island.Surface <- island.Surface + 1 @@ -524,13 +476,13 @@ let private areaOperationF (img: Image) (areas: (int * 'a) list) /// /// Area opening on float image. /// -let areaOpenF (img: Image) (area: int) = +let areaOpenF (img : Image) (area : int) = areaOperationF img [ area, () ] None AreaOperation.Opening /// /// Area closing on float image. /// -let areaCloseF (img: Image) (area: int) = +let areaCloseF (img : Image) (area : int) = areaOperationF img [ area, () ] None AreaOperation.Closing /// @@ -538,20 +490,20 @@ let areaCloseF (img: Image) (area: int) = /// For each area the function 'f' is called with the associated area value of type 'a and the volume difference /// Between the previous and the current closing. /// -let areaOpenFWithFun (img: Image) (areas: (int * 'a) list) (f: 'a -> float32 -> unit) = +let areaOpenFWithFun (img : Image) (areas : (int * 'a) list) (f : 'a -> float32 -> unit) = areaOperationF img areas (Some f) AreaOperation.Opening /// /// Same as 'areaOpenFWithFun' for closing operation. /// -let areaCloseFWithFun (img: Image) (areas: (int * 'a) list) (f: 'a -> float32 -> unit) = +let areaCloseFWithFun (img : Image) (areas : (int * 'a) list) (f : 'a -> float32 -> unit) = areaOperationF img areas (Some f) AreaOperation.Closing /// /// Zhang and Suen thinning algorithm. /// Modify 'mat' in place. /// -let thin (mat: Matrix) = +let thin (mat : Matrix) = let w = mat.Width let h = mat.Height let mutable data1 = mat.Data @@ -564,8 +516,7 @@ let thin (mat: Matrix) = pixelChanged <- false for i = 0 to h - 1 do for j = 0 to w - 1 do - if data1.[i, j] = 1uy - then + if data1.[i, j] = 1uy then let p2 = if i = 0 then 0uy else data1.[i-1, j] let p3 = if i = 0 || j = w-1 then 0uy else data1.[i-1, j+1] let p4 = if j = w-1 then 0uy else data1.[i, j+1] @@ -585,9 +536,10 @@ let thin (mat: Matrix) = (if p7 = 0uy && p8 = 1uy then 1 else 0) + (if p8 = 0uy && p9 = 1uy then 1 else 0) + (if p9 = 0uy && p2 = 1uy then 1 else 0) = 1 && - if oddIteration - then p2 * p4 * p6 = 0uy && p4 * p6 * p8 = 0uy - else p2 * p4 * p8 = 0uy && p2 * p6 * p8 = 0uy + if oddIteration then + p2 * p4 * p6 = 0uy && p4 * p6 * p8 = 0uy + else + p2 * p4 * p8 = 0uy && p2 * p6 * p8 = 0uy then data2.[i, j] <- 0uy pixelChanged <- true @@ -603,7 +555,7 @@ let thin (mat: Matrix) = /// Remove all 8-connected pixels with an area equal or greater than 'areaSize'. /// Modify 'mat' in place. /// -let removeArea (mat: Matrix) (areaSize: int) = +let removeArea (mat : Matrix) (areaSize : int) = let neighbors = [| (-1, 0) // p2 (-1, 1) // p3 @@ -624,8 +576,7 @@ let removeArea (mat: Matrix) (areaSize: int) = for i = 0 to h - 1 do for j = 0 to w - 1 do - if data'.[i, j] = 1uy - then + if data'.[i, j] = 1uy then let neighborhood = List() let neighborsToCheck = Stack() neighborsToCheck.Push(Point(j, i)) @@ -637,16 +588,14 @@ let removeArea (mat: Matrix) (areaSize: int) = for (ni, nj) in neighbors do let pi = n.Y + ni let pj = n.X + nj - if pi >= 0 && pi < h && pj >= 0 && pj < w && data'.[pi, pj] = 1uy - then + if pi >= 0 && pi < h && pj >= 0 && pj < w && data'.[pi, pj] = 1uy then neighborsToCheck.Push(Point(pj, pi)) data'.[pi, pj] <- 0uy - if neighborhood.Count <= areaSize - then + if neighborhood.Count <= areaSize then for n in neighborhood do data.[n.Y, n.X] <- 0uy -let connectedComponents (img: Image) (startPoints: List) : Points = +let connectedComponents (img : Image) (startPoints : List) : Points = let w = img.Width let h = img.Height @@ -660,11 +609,9 @@ let connectedComponents (img: Image) (startPoints: List) : Po pointChecked.Add(next) |> ignore for ny = -1 to 1 do for nx = -1 to 1 do - if ny <> 0 && nx <> 0 - then + if ny <> 0 && nx <> 0 then let p = Point(next.X + nx, next.Y + ny) - if p.X >= 0 && p.X < w && p.Y >= 0 && p.Y < h && data.[p.Y, p.X, 0] > 0uy && not (pointChecked.Contains p) - then + if p.X >= 0 && p.X < w && p.Y >= 0 && p.Y < h && data.[p.Y, p.X, 0] > 0uy && not (pointChecked.Contains p) then pointToCheck.Push(p) pointChecked diff --git a/Parasitemia/ParasitemiaCore/ImgTools/Otsu.fs b/Parasitemia/ParasitemiaCore/ImgTools/Otsu.fs index 82dfde4..8d976ff 100644 --- a/Parasitemia/ParasitemiaCore/ImgTools/Otsu.fs +++ b/Parasitemia/ParasitemiaCore/ImgTools/Otsu.fs @@ -2,7 +2,7 @@ open Histogram -let otsu (hist: Histogram) : float32 * float32 * float32 = +let otsu (hist : Histogram) : float32 * float32 * float32 = let mutable sumB = 0 let mutable wB = 0 let mutable maximum = 0.0 @@ -11,17 +11,14 @@ let otsu (hist: Histogram) : float32 * float32 * float32 = for i = 0 to hist.data.Length - 1 do wB <- wB + hist.data.[i] - if wB <> 0 - then + if wB <> 0 then let wF = hist.total - wB - if wF <> 0 - then + if wF <> 0 then sumB <- sumB + i * hist.data.[i] let mB = (float sumB) / (float wB) let mF = (sum - float sumB) / (float wF) let between = (float wB) * (float wF) * (mB - mF) ** 2.; - if between >= maximum - then + if between >= maximum then level <- i maximum <- between diff --git a/Parasitemia/ParasitemiaCore/KMeans.fs b/Parasitemia/ParasitemiaCore/KMeans.fs index 6e52947..dfd2593 100644 --- a/Parasitemia/ParasitemiaCore/KMeans.fs +++ b/Parasitemia/ParasitemiaCore/KMeans.fs @@ -6,13 +6,15 @@ open System.Drawing open Emgu.CV open Emgu.CV.Structure -type Result = { - fg: Image - mean_bg: float32 - mean_fg: float32 - d_fg: Image } // Euclidean distances of the foreground to mean_fg. - -let kmeans (img: Image) : Result = +type Result = + { + fg : Image + mean_bg : float32 + mean_fg : float32 + d_fg : Image // Euclidean distances of the foreground to mean_fg. + } + +let kmeans (img : Image) : Result = let nbIteration = 4 let w = img.Width let h = img.Height @@ -56,8 +58,7 @@ let kmeans (img: Image) : Result = for i = 0 to h - 1 do for j = 0 to w - 1 do - if fgData.[i, j, 0] > 0uy - then + if fgData.[i, j, 0] > 0uy then fg_total <- fg_total + imgData.[i, j, 0] fg_nb <- fg_nb + 1 else diff --git a/Parasitemia/ParasitemiaCore/KMedians.fs b/Parasitemia/ParasitemiaCore/KMedians.fs index 504172a..bae7a24 100644 --- a/Parasitemia/ParasitemiaCore/KMedians.fs +++ b/Parasitemia/ParasitemiaCore/KMedians.fs @@ -6,13 +6,15 @@ open System.Drawing open Emgu.CV open Emgu.CV.Structure -type Result = { - fg: Image - median_bg: float - median_fg: float - d_fg: Image } // Euclidean distances of the foreground to median_fg. - -let kmedians (img: Image) : Result = +type Result = + { + fg : Image + median_bg : float + median_fg : float + d_fg : Image // Euclidean distances of the foreground to median_fg. + } + +let kmedians (img : Image) : Result = let nbIteration = 4 let w = img.Width let h = img.Height @@ -40,9 +42,10 @@ let kmedians (img: Image) : Result = for i = 0 to h - 1 do for j = 0 to w - 1 do - if fg.Data.[i, j, 0] > 0uy - then fg_values.Add(float img.Data.[i, j, 0]) - else bg_values.Add(float img.Data.[i, j, 0]) + if fg.Data.[i, j, 0] > 0uy then + fg_values.Add(float img.Data.[i, j, 0]) + else + bg_values.Add(float img.Data.[i, j, 0]) median_bg <- MathNet.Numerics.Statistics.Statistics.Median(bg_values) median_fg <- MathNet.Numerics.Statistics.Statistics.Median(fg_values) diff --git a/Parasitemia/ParasitemiaCore/KdTree.fs b/Parasitemia/ParasitemiaCore/KdTree.fs index 3a60977..4e20f24 100644 --- a/Parasitemia/ParasitemiaCore/KdTree.fs +++ b/Parasitemia/ParasitemiaCore/KdTree.fs @@ -7,7 +7,7 @@ type I2DCoords = abstract Y : float32 // Compare 'e1' and 'e2' by X. -let cmpX (e1: I2DCoords) (e2: I2DCoords) : int = +let cmpX (e1 : I2DCoords) (e2 : I2DCoords) : int = match e1.X.CompareTo(e2.X) with | 0 -> match e1.Y.CompareTo(e2.Y) with | 0 -> e1.GetHashCode().CompareTo(e2.GetHashCode()) @@ -15,14 +15,14 @@ let cmpX (e1: I2DCoords) (e2: I2DCoords) : int = | v -> v // Compare 'e1' and 'e2' by Y. -let cmpY (e1: I2DCoords) (e2: I2DCoords) : int = +let cmpY (e1 : I2DCoords) (e2 : I2DCoords) : int = match e1.Y.CompareTo(e2.Y) with | 0 -> match e1.X.CompareTo(e2.X) with | 0 -> e1.GetHashCode().CompareTo(e2.GetHashCode()) | v -> v | v -> v -type Region = { minX: float32; maxX: float32; minY: float32; maxY: float32 } with +type Region = { minX : float32; maxX : float32; minY : float32; maxY : float32 } with member this.Contains px py : bool = px >= this.minX && px <= this.maxX && py >= this.minY && py <= this.maxY @@ -39,53 +39,48 @@ type Tree<'a when 'a :> I2DCoords> = | Node of float32 * Tree<'a> * Tree<'a> | Leaf of 'a - static member BuildTree (l: 'a list) : Tree<'a> = + static member BuildTree (l : 'a list) : Tree<'a> = let xSorted = List.toArray l let ySorted = List.toArray l Array.sortInPlaceWith cmpX xSorted Array.sortInPlaceWith cmpY ySorted - let rec buildTreeFromSortedArray (pXSorted: 'a[]) (pYSorted: 'a[]) (depth: int) : Tree<'a> = - if pXSorted.Length = 1 - then + let rec buildTreeFromSortedArray (pXSorted : 'a[]) (pYSorted : 'a[]) (depth : int) : Tree<'a> = + if pXSorted.Length = 1 then Leaf pXSorted.[0] else - if depth % 2 = 1 // 'depth' is odd -> vertical splitting else horizontal splitting. - then + if depth % 2 = 1 then // 'depth' is odd -> vertical splitting else horizontal splitting. let leftX, rightX = Array.splitAt ((pXSorted.Length + 1) / 2) pXSorted let splitElement = Array.last leftX - let leftY, rightY = Array.partition (fun (e: 'a) -> cmpX e splitElement <= 0) pYSorted // FIXME: Maybe this operation can be optimized. + let leftY, rightY = Array.partition (fun (e : 'a) -> cmpX e splitElement <= 0) pYSorted // FIXME: Maybe this operation can be optimized. Node (splitElement.X, buildTreeFromSortedArray leftX leftY (depth + 1), buildTreeFromSortedArray rightX rightY (depth + 1)) else let downY, upY = Array.splitAt ((pYSorted.Length + 1) / 2) pYSorted let splitElement = Array.last downY - let downX, upX = Array.partition (fun (e: 'a) -> cmpY e splitElement <= 0) pXSorted // FIXME: Maybe this operation can be optimized. + let downX, upX = Array.partition (fun (e : 'a) -> cmpY e splitElement <= 0) pXSorted // FIXME: Maybe this operation can be optimized. Node (splitElement.Y, buildTreeFromSortedArray downX downY (depth + 1), buildTreeFromSortedArray upX upY (depth + 1)) buildTreeFromSortedArray xSorted ySorted 1 - member this.Search (searchRegion: Region) : 'a list = - let rec valuesFrom (tree: Tree<'a>) (acc: 'a list) : 'a list = + member this.Search (searchRegion : Region) : 'a list = + let rec valuesFrom (tree : Tree<'a>) (acc : 'a list) : 'a list = match tree with | Node (_, left, right) -> (valuesFrom right (valuesFrom left acc)) | Leaf v -> v :: acc - let rec searchWithRegion (tree: Tree<'a>) (currentRegion: Region) (depth: int) : 'a list = + let rec searchWithRegion (tree : Tree<'a>) (currentRegion : Region) (depth : int) : 'a list = match tree with | Leaf v -> if searchRegion.Contains v.X v.Y then [v] else [] | Node (splitValue, part1, part2) -> - let valuesInRegion (region: Region) (treeRegion: Tree<'a>) = - if region.IsSub searchRegion - then + let valuesInRegion (region : Region) (treeRegion : Tree<'a>) = + if region.IsSub searchRegion then valuesFrom treeRegion [] - elif region.Intersects searchRegion - then + elif region.Intersects searchRegion then searchWithRegion treeRegion region (depth + 1) else [] - if depth % 2 = 1 // Vertical splitting. - then + if depth % 2 = 1 then // Vertical splitting. let leftRegion = { currentRegion with maxX = splitValue } let rightRegion = { currentRegion with minX = splitValue } (valuesInRegion leftRegion part1) @ (valuesInRegion rightRegion part2) @@ -98,7 +93,7 @@ type Tree<'a when 'a :> I2DCoords> = ///// Tests. TODO: to put in a unit test. -type Point (x: float32, y: float32) = +type Point (x : float32, y : float32) = interface I2DCoords with member this.X = x member this.Y = y @@ -108,14 +103,16 @@ type Point (x: float32, y: float32) = // TODO: test with identical X or Y coords let test () = - let pts = [ - Point(1.0f, 1.0f) - Point(2.0f, 2.0f) - Point(1.5f, 3.6f) - Point(3.0f, 3.2f) - Point(4.0f, 4.0f) - Point(3.5f, 1.5f) - Point(2.5f, 0.5f) ] + let pts = + [ + Point(1.0f, 1.0f) + Point(2.0f, 2.0f) + Point(1.5f, 3.6f) + Point(3.0f, 3.2f) + Point(4.0f, 4.0f) + Point(3.5f, 1.5f) + Point(2.5f, 0.5f) + ] let tree = Tree.BuildTree pts Utils.dprintfn "Tree: %A" tree @@ -130,10 +127,12 @@ let test () = Utils.dprintfn "s3: %A" s3 let test2 () = - let pts = [ - Point(1.0f, 1.0f) - Point(1.0f, 2.0f) - Point(1.0f, 3.0f) ] + let pts = + [ + Point(1.0f, 1.0f) + Point(1.0f, 2.0f) + Point(1.0f, 3.0f) + ] let tree = Tree.BuildTree pts Utils.dprintfn "Tree: %A" tree diff --git a/Parasitemia/ParasitemiaCore/MatchingEllipses.fs b/Parasitemia/ParasitemiaCore/MatchingEllipses.fs index d4a6a41..13fd556 100644 --- a/Parasitemia/ParasitemiaCore/MatchingEllipses.fs +++ b/Parasitemia/ParasitemiaCore/MatchingEllipses.fs @@ -14,16 +14,16 @@ let matchingScoreThresholdPerRadiusUnit = 0.07f // For a radius of 1. // 0.25 let matchingScorePower = 20.f let windowSizeRadiusFactor = 1.f / 2.f // Used when searching for neighbor ellipses. let minimumDistanceFromCenterRadiusFactor = 1.f / 3.f -let minimumAreaFactor = 1.1f; +let minimumAreaFactor = 1.1f -type private EllipseScoreFlaggedKd (matchingScore: float32, e: Ellipse) = +type private EllipseScoreFlaggedKd (matchingScore : float32, e : Ellipse) = let mutable matchingScore = matchingScore member this.Ellipse = e member this.MatchingScore = matchingScore - member this.AddMatchingScore (score: float32) = + member this.AddMatchingScore (score : float32) = matchingScore <- matchingScore + score member val Processed = false with get, set @@ -33,18 +33,17 @@ type private EllipseScoreFlaggedKd (matchingScore: float32, e: Ellipse) = member this.X = this.Ellipse.Cx member this.Y = this.Ellipse.Cy -type MatchingEllipses (radius: float32) = +type MatchingEllipses (radius : float32) = let ellipses = List() - member this.Add (e: Ellipse) = + member this.Add (e : Ellipse) = ellipses.Add(EllipseScoreFlaggedKd(0.f, e)) member this.Ellipses : Ellipse list = List.ofSeq ellipses |> List.map (fun e -> e.Ellipse) member this.PrunedEllipses : Ellipse list = - if ellipses.Count = 0 - then + if ellipses.Count = 0 then [] else // 1) Create a kd-tree from the ellipses list. @@ -55,13 +54,15 @@ type MatchingEllipses (radius: float32) = for e in ellipses do e.Processed <- true let areaE = e.Ellipse.Area - let window = { KdTree.minX = e.Ellipse.Cx - windowSize / 2.f - KdTree.maxX = e.Ellipse.Cx + windowSize / 2.f - KdTree.minY = e.Ellipse.Cy - windowSize / 2.f - KdTree.maxY = e.Ellipse.Cy + windowSize / 2.f } + let window = + { + KdTree.minX = e.Ellipse.Cx - windowSize / 2.f + KdTree.maxX = e.Ellipse.Cx + windowSize / 2.f + KdTree.minY = e.Ellipse.Cy - windowSize / 2.f + KdTree.maxY = e.Ellipse.Cy + windowSize / 2.f + } for other in tree.Search window do - if not other.Processed - then + if not other.Processed then let areaOther = other.Ellipse.Area match EEOver.EEOverlapArea e.Ellipse other.Ellipse with | Some (overlapArea, _, _) @@ -75,20 +76,20 @@ type MatchingEllipses (radius: float32) = // 3) Remove ellipses whose center is near the center of another ellipse with a better score. let matchingScoreThreshold = matchingScoreThresholdPerRadiusUnit * radius for e in ellipses do - if e.MatchingScore < matchingScoreThreshold - then + if e.MatchingScore < matchingScoreThreshold then e.Removed <- true else - let window = { KdTree.minX = e.Ellipse.Cx - e.Ellipse.A - KdTree.maxX = e.Ellipse.Cx + e.Ellipse.A - KdTree.minY = e.Ellipse.Cy - e.Ellipse.A - KdTree.maxY = e.Ellipse.Cy + e.Ellipse.A } + let window = + { + KdTree.minX = e.Ellipse.Cx - e.Ellipse.A + KdTree.maxX = e.Ellipse.Cx + e.Ellipse.A + KdTree.minY = e.Ellipse.Cy - e.Ellipse.A + KdTree.maxY = e.Ellipse.Cy + e.Ellipse.A + } for other in tree.Search window do - if not other.Removed && e.MatchingScore > other.MatchingScore - then + if not other.Removed && e.MatchingScore > other.MatchingScore then // Case where ellipses are too close. - if distanceTwoPoints (PointF(e.Ellipse.Cx, e.Ellipse.Cy)) (PointF(other.Ellipse.Cx, other.Ellipse.Cy)) < minimumDistanceFromCenterRadiusFactor * e.Ellipse.B - then + if distanceTwoPoints (PointF(e.Ellipse.Cx, e.Ellipse.Cy)) (PointF(other.Ellipse.Cx, other.Ellipse.Cy)) < minimumDistanceFromCenterRadiusFactor * e.Ellipse.B then other.Removed <- true else // Case where ellipses are overlapped. diff --git a/Parasitemia/ParasitemiaCore/ParasitemiaCore.fsproj b/Parasitemia/ParasitemiaCore/ParasitemiaCore.fsproj index 1acdac0..f3210a9 100644 --- a/Parasitemia/ParasitemiaCore/ParasitemiaCore.fsproj +++ b/Parasitemia/ParasitemiaCore/ParasitemiaCore.fsproj @@ -9,8 +9,8 @@ Library ParasitemiaCore ParasitemiaCore - v4.5.2 - 4.4.0.0 + v4.6.2 + 4.4.1.0 true ParasitemiaCore @@ -71,33 +71,31 @@ - - ..\..\..\Emgu\emgucv-windows-universal 3.0.0.2157\bin\Emgu.CV.dll - - - ..\..\..\Emgu\emgucv-windows-universal 3.0.0.2157\bin\Emgu.Util.dll + + ..\..\..\Emgu\emgucv-windesktop 3.1.0.2282\bin\Emgu.CV.World.dll ..\packages\FSharp.Collections.ParallelSeq.1.0.2\lib\net40\FSharp.Collections.ParallelSeq.dll True - + ..\packages\FSharp.Core.4.0.0.1\lib\net40\FSharp.Core.dll True - ..\packages\MathNet.Numerics.3.10.0\lib\net40\MathNet.Numerics.dll - True + ..\packages\MathNet.Numerics.3.17.0\lib\net40\MathNet.Numerics.dll - ..\packages\MathNet.Numerics.FSharp.3.10.0\lib\net40\MathNet.Numerics.FSharp.dll - True + ..\packages\MathNet.Numerics.FSharp.3.17.0\lib\net40\MathNet.Numerics.FSharp.dll + + ..\packages\System.ValueTuple.4.3.0\lib\netstandard1.0\System.ValueTuple.dll + diff --git a/Parasitemia/ParasitemiaCore/ParasitesMarker.fs b/Parasitemia/ParasitemiaCore/ParasitesMarker.fs index 2b70682..263e35e 100644 --- a/Parasitemia/ParasitemiaCore/ParasitesMarker.fs +++ b/Parasitemia/ParasitemiaCore/ParasitesMarker.fs @@ -12,12 +12,14 @@ open Otsu open Morpho open ImgTools -type Result = { - darkStain: Image // Colored pixel, it's independent of the size of the areas. It corresponds to white cells, schizontes, gametocytes, throphozoites. - nucleus: Image // Parasite nucleus. It may contain some debris. It shouldn't contain thrombocytes or larger elements. - parasite: Image } // The whole parasites. +type Result = + { + darkStain : Image // Colored pixel, it's independent of the size of the areas. It corresponds to white cells, schizontes, gametocytes, throphozoites. + nucleus : Image // Parasite nucleus. It may contain some debris. It shouldn't contain thrombocytes or larger elements. + parasite : Image // The whole parasites. + } -let find (img: Image) (config: Config.Config) : Result * Image * Image = +let find (img : Image) (config : Config.Config) : Result * Image * Image = let imgWithoutNucleus = img.Copy() areaCloseF imgWithoutNucleus (roundInt config.RBCRadius.NucleusArea) @@ -29,7 +31,7 @@ let find (img: Image) (config: Config.Config) : Result * Image) (closed: Image) (level: float) : Image = + let marker (img : Image) (closed : Image) (level : float) : Image = let diff = img.Copy() diff._Mul(level) CvInvoke.Subtract(closed, diff, diff) @@ -45,8 +47,7 @@ let find (img: Image) (config: Config.Config) : Result * Image) (config: Config.Config) : Result * Image -type Ellipse (cx: float32, cy: float32, a: float32, b: float32, alpha: float32) = +type Ellipse (cx : float32, cy : float32, a : float32, b : float32, alpha : float32) = member this.Cx = cx member this.Cy = cy member this.A = a @@ -23,19 +23,19 @@ type Ellipse (cx: float32, cy: float32, a: float32, b: float32, alpha: float32) member this.Contains x y = ((x - cx) * cos alpha + (y - cy) * sin alpha) ** 2.f / a ** 2.f + ((x - cx) * sin alpha - (y - cy) * cos alpha) ** 2.f / b ** 2.f <= 1.f - member this.CutAVericalLine (y: float32) : bool = + member this.CutAVericalLine (y : float32) : bool = a ** 2.f + b ** 2.f - 2.f * y ** 2.f + 4.f * y * cx - 2.f * cx ** 2.f + a ** 2.f * cos (2.f * alpha) - b ** 2.f * cos (2.f * alpha) > 0.f - member this.CutAnHorizontalLine (x: float32) : bool = + member this.CutAnHorizontalLine (x : float32) : bool = a ** 2.f + b ** 2.f - 2.f * x ** 2.f + 4.f * x * cy - 2.f * cy ** 2.f - a ** 2.f * cos (2.f * alpha) + b ** 2.f * cos (2.f * alpha) > 0.f - member this.isOutside (width: float32) (height: float32) = + member this.isOutside (width : float32) (height : float32) = this.Cx < 0.f || this.Cx >= width || this.Cy < 0.f || this.Cy >= height || this.CutAVericalLine 0.f || this.CutAVericalLine width || this.CutAnHorizontalLine 0.f || this.CutAnHorizontalLine height - member this.Scale (factor: float32) : Ellipse = + member this.Scale (factor : float32) : Ellipse = Ellipse(this.Cx, this.Cy, this.A * factor, this.B * factor, alpha) // Approximation of Ramanujan. @@ -43,19 +43,21 @@ type Ellipse (cx: float32, cy: float32, a: float32, b: float32, alpha: float32) PI * (3.f * (this.A + this.B) - sqrt ((3.f * this.A + this.B) * (this.A + 3.f * this.B))) override this.ToString () = - sprintf "(cx: %A, cy: %A, a: %A, b: %A, alpha: %A)" this.Cx this.Cy this.A this.B this.Alpha + sprintf "(cx: %f, cy: %f, a: %f, b: %f, alpha: %f)" this.Cx this.Cy this.A this.B this.Alpha type CellClass = HealthyRBC | InfectedRBC | Peculiar -type Cell = { - cellClass: CellClass - center: Point - nucleusArea: int - parasiteArea: int - elements: Matrix } +type Cell = + { + cellClass : CellClass + center : Point + nucleusArea : int + parasiteArea : int + elements : Matrix + } [] -type Line (a: float32, b: float32) = +type Line (a : float32, b : float32) = member this.A = a member this.B = b @@ -73,7 +75,7 @@ type MaybeBuilder () = finally compensation() - member this.Using (disposable: 'a when 'a :> IDisposable, body) = + member this.Using (disposable : 'a when 'a :> IDisposable, body) = let body' = fun () -> body disposable this.TryFinally(body', fun () -> match disposable with diff --git a/Parasitemia/ParasitemiaCore/UnitsOfMeasure.fs b/Parasitemia/ParasitemiaCore/UnitsOfMeasure.fs index ec343ea..8202b8e 100644 --- a/Parasitemia/ParasitemiaCore/UnitsOfMeasure.fs +++ b/Parasitemia/ParasitemiaCore/UnitsOfMeasure.fs @@ -9,9 +9,9 @@ let μmPerInch = 25.4e3<μm/inch> let mmPerInch = 25.4 -let μmToInch(x: float<μm>) : float = x / μmPerInch -let inchToμm(x: float) : float<μm> = x * μmPerInch +let μmToInch(x : float<μm>) : float = x / μmPerInch +let inchToμm(x : float) : float<μm> = x * μmPerInch -let mmToInch(x: float) : float = x / mmPerInch -let inchTomm(x: float) : float = x * mmPerInch +let mmToInch(x : float) : float = x / mmPerInch +let inchTomm(x : float) : float = x * mmPerInch diff --git a/Parasitemia/ParasitemiaCore/Utils.fs b/Parasitemia/ParasitemiaCore/Utils.fs index d83bf8d..77e5e6f 100644 --- a/Parasitemia/ParasitemiaCore/Utils.fs +++ b/Parasitemia/ParasitemiaCore/Utils.fs @@ -10,29 +10,32 @@ let inline roundInt v = v |> round |> int let inline dprintfn fmt = Printf.ksprintf System.Diagnostics.Debug.WriteLine fmt -let inline lineFromTwoPoints (p1: PointF) (p2: PointF) : Line = +let inline lineFromTwoPoints (p1 : PointF) (p2 : PointF) : Line = let a = (p1.Y - p2.Y) / (p1.X - p2.X) let b = -(p2.X * p1.Y - p1.X * p2.Y) / (p1.X - p2.X) Line(a, b) -let inline pointFromTwoLines (l1: Line) (l2: Line) : PointF = +let inline pointFromTwoLines (l1 : Line) (l2 : Line) : PointF = let x = -(l1.B - l2.B) / (l1.A - l2.A) let y = -(l2.A * l1.B - l1.A * l2.B) / (l1.A - l2.A) PointF(x, y) -let inline linePassThroughSegment (l: Line) (p1: PointF) (p2: PointF) : bool = +let inline linePassThroughSegment (l : Line) (p1 : PointF) (p2 : PointF) : bool = let p = pointFromTwoLines l (lineFromTwoPoints p1 p2) sign (p.X - p1.X) <> sign (p.X - p2.X) -let inline squaredDistanceTwoPoints (p1: PointF) (p2: PointF) = +let inline squaredDistanceTwoPoints (p1 : PointF) (p2 : PointF) = (p1.X - p2.X) ** 2.f + (p1.Y - p2.Y) ** 2.f -let inline distanceTwoPoints (p1: PointF) (p2: PointF) = +let inline distanceTwoPoints (p1 : PointF) (p2 : PointF) = squaredDistanceTwoPoints p1 p2 |> sqrt -let countCells (cells: Cell list) : int * int = - cells |> List.fold (fun (total, infected) { cellClass = cellClass } -> - match cellClass with - | HealthyRBC -> (total + 1, infected) - | InfectedRBC -> (total + 1, infected + 1) - | Peculiar -> (total, infected)) (0, 0) \ No newline at end of file +let countCells (cells : Cell list) : int * int = + cells + |> List.fold ( + fun (total, infected) { cellClass = cellClass } -> + match cellClass with + | HealthyRBC -> (total + 1, infected) + | InfectedRBC -> (total + 1, infected + 1) + | Peculiar -> (total, infected) + ) (0, 0) \ No newline at end of file diff --git a/Parasitemia/ParasitemiaCore/packages.config b/Parasitemia/ParasitemiaCore/packages.config index 4d1f091..0bdf2f7 100644 --- a/Parasitemia/ParasitemiaCore/packages.config +++ b/Parasitemia/ParasitemiaCore/packages.config @@ -1,7 +1,8 @@  - - - + + + + \ No newline at end of file diff --git a/Parasitemia/ParasitemiaUI/About.fs b/Parasitemia/ParasitemiaUI/About.fs index 908ccd5..452573a 100644 --- a/Parasitemia/ParasitemiaUI/About.fs +++ b/Parasitemia/ParasitemiaUI/About.fs @@ -8,12 +8,12 @@ open System.Windows.Shapes open System.Windows.Controls open System.Diagnostics -let showWindow (parent: Window) = +let showWindow (parent : Window) = let win = Views.AboutWindow() - win.Root.Owner <- parent + win.Owner <- parent - win.Root.Left <- (if parent.WindowState = WindowState.Maximized then 0. else parent.Left) + parent.ActualWidth / 2. - win.Root.Width / 2. - win.Root.Top <- (if parent.WindowState = WindowState.Maximized then 0. else parent.Top) + parent.ActualHeight / 2. - win.Root.Height / 2. + 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. let version = System.Reflection.Assembly.GetEntryAssembly().GetName().Version let txtVersion = sprintf " %d.%d.%d" version.Major version.Minor version.Revision @@ -31,7 +31,7 @@ let showWindow (parent: Window) = win.txtAbout.Inlines.FirstInline.ElementEnd.InsertTextInRun(" - DEBUG") #endif - win.butClose.Click.AddHandler(fun obj args -> win.Root.Close()) + win.butClose.Click.AddHandler(fun obj args -> win.Close()) - win.Root.ShowDialog() |> ignore + win.ShowDialog() |> ignore diff --git a/Parasitemia/ParasitemiaUI/Analysis.fs b/Parasitemia/ParasitemiaUI/Analysis.fs index 2506015..ceb61be 100644 --- a/Parasitemia/ParasitemiaUI/Analysis.fs +++ b/Parasitemia/ParasitemiaUI/Analysis.fs @@ -18,25 +18,28 @@ open ParasitemiaCore.Config open Types -let showWindow (parent: Window) (state: State.State) : bool = +let showWindow (parent : Window) (state : State.State) : bool = let win = Views.AnalysisWindow() - win.Root.Owner <- parent - win.Root.Left <- (if parent.WindowState = WindowState.Maximized then 0. else parent.Left) + parent.ActualWidth / 2. - win.Root.Width / 2. - win.Root.Top <- (if parent.WindowState = WindowState.Maximized then 0. else parent.Top) + parent.ActualHeight / 2. - win.Root.Height / 2. + 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. let logListener = - { new Logger.IListener with - member this.NewEntry severity mess = - win.Root.Dispatcher.Invoke(fun () -> - win.textLog.Inlines.Add(Documents.Run(mess)) - win.textLog.Inlines.Add(Documents.LineBreak()) - win.scrollLog.ScrollToBottom()) } + { + new Logger.IListener with + member this.NewEntry severity mess = + win.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 parseAndValidatePPI (input : string) : float option = match Double.TryParse(input) with | true, value when value >= minPPI && value <= maxPPI -> Some value | _ -> None @@ -68,7 +71,7 @@ let showWindow (parent: Window) (state: State.State) : bool = imageSourceSelection.predefinedValuesMenu.Items.Add(menu) |> ignore imageSourceSelection.butPPICalculator.Click.AddHandler(fun obj args -> - match PPICalculator.showWindow win.Root with + match PPICalculator.showWindow win with | Some resolution -> imageSourceSelection.txtResolution.Text <- resolution.ToString() | None -> ()) @@ -86,33 +89,38 @@ let showWindow (parent: Window) (state: State.State) : bool = // The boolean is 'true' if the image is selected (checked). let getInputImagesParameters () : (SourceImage * bool * Parameters) list option = let sourceImagesControls = win.stackSourceImagesSelection.Children |> Seq.cast - let parameters = seq { - for srcImgCtrl in sourceImagesControls do - let srcImg = srcImgCtrl.Tag :?> SourceImage - 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. }) - | None -> - 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 - if parameters.Count() <> sourceImagesControls.Count() - then None - else Some parameters - - win.butClose.Click.AddHandler(fun obj args -> win.Root.Close()) + let parameters = + seq { + for srcImgCtrl in sourceImagesControls do + let srcImg = srcImgCtrl.Tag :?> SourceImage + 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. }) + | None -> + 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 + + if parameters.Count() <> sourceImagesControls.Count() then + None + else + Some parameters + + win.butClose.Click.AddHandler(fun obj args -> win.Close()) win.butStart.Click.AddHandler(fun obj args -> match getInputImagesParameters () with | Some imagesParameters -> - let imagesToProcess = [ - for srcImg, selected, parameters in imagesParameters do - srcImg.config.Parameters <- parameters // Save parameters. - if selected - then yield srcImg.num.ToString(), srcImg.config, srcImg.img ] - - if imagesToProcess.IsEmpty - then + let imagesToProcess = + [ + for srcImg, selected, parameters in imagesParameters do + srcImg.config.Parameters <- parameters // Save parameters. + if selected then + yield srcImg.num.ToString(), srcImg.config, srcImg.img + ] + + if imagesToProcess.IsEmpty then MessageBox.Show("No image selected", "Cannot start analysis", MessageBoxButton.OK, MessageBoxImage.Information) |> ignore else win.stackSourceImagesSelection.IsEnabled <- false @@ -124,7 +132,7 @@ let showWindow (parent: Window) (state: State.State) : bool = let maybeResults = ParasitemiaCore.Analysis.doMultipleAnalysis imagesToProcess - (Some (fun progress -> win.Root.Dispatcher.Invoke(fun () -> win.progress.Value <- float progress); not analysisCancelled)) + (Some (fun progress -> win.Dispatcher.Invoke(fun () -> win.progress.Value <- float progress); not analysisCancelled)) lock monitor ( fun() -> @@ -138,24 +146,28 @@ let showWindow (parent: Window) (state: State.State) : bool = | None -> Logger.Log.User("Analysis aborted") - win.Root.Dispatcher.Invoke(fun () -> + win.Dispatcher.Invoke(fun () -> win.progress.Value <- if maybeResults.IsSome then 100. else 0. win.stackSourceImagesSelection.IsEnabled <- true win.butStart.IsEnabled <- true win.butClose.Content <- "Close" - updateSourceImages ())) + updateSourceImages () + ) + ) } |> Async.Start - | _ -> ()) + | _ -> () + ) - win.Root.Loaded.AddHandler(fun obj args -> updateSourceImages ()) + win.Loaded.AddHandler(fun obj args -> updateSourceImages ()) - win.Root.ShowDialog() |> ignore + win.ShowDialog() |> ignore Logger.Log.RmListener(logListener) - lock monitor (fun () -> - if not analysisPerformed - then + lock monitor ( + fun () -> + if not analysisPerformed then // To cancel the current analysis if one is running on the next call to the progress function. analysisCancelled <- true - atLeastOneAnalysisPerformed) + atLeastOneAnalysisPerformed + ) diff --git a/Parasitemia/ParasitemiaUI/App.config b/Parasitemia/ParasitemiaUI/App.config index 4be2caf..f0c7a74 100644 --- a/Parasitemia/ParasitemiaUI/App.config +++ b/Parasitemia/ParasitemiaUI/App.config @@ -1,7 +1,7 @@  - + diff --git a/Parasitemia/ParasitemiaUI/AssemblyInfo.fs b/Parasitemia/ParasitemiaUI/AssemblyInfo.fs index a4b44e1..89fc224 100644 --- a/Parasitemia/ParasitemiaUI/AssemblyInfo.fs +++ b/Parasitemia/ParasitemiaUI/AssemblyInfo.fs @@ -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: // [] -[] -[] +[] +[] do () \ No newline at end of file diff --git a/Parasitemia/ParasitemiaUI/DPICalculator.fs b/Parasitemia/ParasitemiaUI/DPICalculator.fs index c1187ec..c8d25c8 100644 --- a/Parasitemia/ParasitemiaUI/DPICalculator.fs +++ b/Parasitemia/ParasitemiaUI/DPICalculator.fs @@ -13,17 +13,17 @@ open ParasitemiaCore.UnitsOfMeasure open Types -let showWindow (parent: Window) : int option = +let showWindow (parent : Window) : int option = let win = Views.PPICalculatorWindow() - 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. + win.Owner <- parent + win.Left <- parent.Left + parent.ActualWidth / 2. - win.Width / 2. + win.Top <- parent.Top + parent.ActualHeight / 2. - win.Height / 2. for size in Utils.sensorSizes do win.cmbSensorSize.Items.Add(size) |> ignore win.cmbSensorSize.SelectedIndex <- 0 - let resolution (w_p: float) (w_mm: float) (zoom: float) : float = + let resolution (w_p : float) (w_mm : float) (zoom : float) : float = w_p * zoom / mmToInch w_mm let updateCurrentResolution () = @@ -32,24 +32,25 @@ let showWindow (parent: Window) : int option = 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. * sqrt (sensorResolution * 1e6 / ratio) - return! Success (float <| resolution wPixel w zoom) } with + 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. * 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(false); win.Root.Close()) - win.butOK.Click.AddHandler(fun obj args -> win.Root.DialogResult <- Nullable(true); win.Root.Close()) + win.butCancel.Click.AddHandler(fun obj args -> win.DialogResult <- Nullable(false); win.Close()) + win.butOK.Click.AddHandler(fun obj args -> win.DialogResult <- Nullable(true); win.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 + let result = win.ShowDialog() + if result.HasValue && result.Value then match Int32.TryParse win.txtImageResolution.Text with | true, res -> Some res | _ -> None diff --git a/Parasitemia/ParasitemiaUI/Export.fs b/Parasitemia/ParasitemiaUI/Export.fs index adecd6c..29fc0a5 100644 --- a/Parasitemia/ParasitemiaUI/Export.fs +++ b/Parasitemia/ParasitemiaUI/Export.fs @@ -6,10 +6,10 @@ open System.IO open State /// If the results cannot be exported -let exportResults (state: State) (filePath: string) = +let exportResults (state : State) (filePath : string) = use writer = new StreamWriter(new FileStream(filePath, FileMode.Create, FileAccess.Write)) fprintfn writer "File: %s" state.FilePath - fprintfn writer "Export date: %A" DateTime.Now + fprintfn writer "Export date: %O" DateTime.Now fprintfn writer "" fprintfn writer "Patient ID: %s" state.PatientID diff --git a/Parasitemia/ParasitemiaUI/GUI.fs b/Parasitemia/ParasitemiaUI/GUI.fs index 77281d3..ade197e 100644 --- a/Parasitemia/ParasitemiaUI/GUI.fs +++ b/Parasitemia/ParasitemiaUI/GUI.fs @@ -19,7 +19,7 @@ open ParasitemiaCore.Config open ParasitemiaCore.Utils open Types -let run (defaultConfig: Config) (fileToOpen: string option) = +let run (defaultConfig : Config) (fileToOpen : string option) = let app = new Application() let win = Views.MainWindow() @@ -33,7 +33,7 @@ let run (defaultConfig: Config) (fileToOpen: string option) = win.menuHightlightRBC.IsChecked <- displayHealthy // Utils. - let extractRBCPreview (img: Emgu.CV.Image) (rbc: RBC) : Emgu.CV.Image = + let extractRBCPreview (img : Emgu.CV.Image) (rbc : RBC) : Emgu.CV.Image = let rbcWidth = rbc.size.Width let rbcHeight = rbc.size.Height let x = rbc.center.X - rbcWidth / 2. |> roundInt @@ -44,14 +44,14 @@ let run (defaultConfig: Config) (fileToOpen: string option) = System.Drawing.Size((if x + w >= img.Width then img.Width - x else w), (if y + h >= img.Height then img.Height - y else h)))) - let setRBCFrameStyle (srcImg: SourceImage) (rbc: RBC) (frame: Views.RBCFrame) = + let setRBCFrameStyle (srcImg : SourceImage) (rbc : RBC) (frame : Views.RBCFrame) = frame.Opacity <- if displayHealthy || rbc.setManually || rbc.infected then 1. else 0. let color = if rbc.infected then srcImg.InfectedRBCColor else srcImg.HealthyRBCColor frame.manuallyAdded.Visibility <- if rbc.setManually then Visibility.Visible else Visibility.Hidden frame.manuallyAdded.Fill <- color frame.border.Stroke <- color - let RBCFrameFromExisting (srcImg: SourceImage) (rbc: RBC) (frame: Views.RBCFrame) : Views.RBCFrame = + let RBCFrameFromExisting (srcImg : SourceImage) (rbc : RBC) (frame : Views.RBCFrame) : Views.RBCFrame = frame.Visibility <- Visibility.Visible frame.Height <- rbc.size.Height frame.Width <- rbc.size.Width @@ -67,22 +67,21 @@ 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) - let displayStatusMessage (message: string) = + let displayStatusMessage (message : string) = win.txtMessageStatus.Text <- message statusMessageTimer.Stop() statusMessageTimer.Start() - let highlightRBCFrame (frame: Views.RBCFrame) (highlight: bool) = + let highlightRBCFrame (frame : Views.RBCFrame) (highlight : bool) = let rbc = frame.Tag :?> RBC - if highlight - then + if highlight then frame.border.StrokeThickness <- 3. if not rbc.infected && not rbc.setManually && not displayHealthy then frame.Opacity <- 1. else frame.border.StrokeThickness <- 1. if not rbc.infected && not rbc.setManually && not displayHealthy then frame.Opacity <- 0. - let zoomToRBC (rbc: RBC) = + 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) @@ -125,13 +124,14 @@ let run (defaultConfig: Config) (fileToOpen: string option) = win.txtGlobalParasitemia.Inlines.Clear() let total, infected = state.GlobalParasitemia win.txtGlobalParasitemia.Inlines.Add(Documents.Run(Utils.percentText (total, infected), FontWeight = FontWeights.Bold)) - if total > 0 && total < warningBelowNumberOfRBC - then + if total > 0 && total < warningBelowNumberOfRBC then win.txtGlobalParasitemia.Inlines.Add( Documents.Run( sprintf " Warning: the number of erythrocytes should be above %d" warningBelowNumberOfRBC, FontWeight = FontWeights.Bold, - Foreground = Brushes.Red)) + Foreground = Brushes.Red + ) + ) let updateViewportPreview () = for preview in win.stackPreviews.Children |> Seq.cast do @@ -154,38 +154,38 @@ let run (defaultConfig: Config) (fileToOpen: string option) = marginLeft, marginTop, marginRight, - marginBottom) + marginBottom + ) else preview.viewport.Visibility <- Visibility.Hidden - let rec setAsInfected (srcImg: SourceImage) (rbc: RBC) (infected: bool) = + let rec setAsInfected (srcImg : SourceImage) (rbc : RBC) (infected : bool) = state.SetAsInfected rbc infected win.canvasCurrentImage.Children |> Seq.cast - |> Seq.iter - (fun frame -> - if (frame.Tag :?> RBC) = rbc - then - setRBCFrameStyle srcImg rbc frame) + |> Seq.iter ( + fun frame -> + if (frame.Tag :?> RBC) = rbc then + setRBCFrameStyle srcImg rbc frame + ) updateRBCFramesPreview () updateCurrentImageInformation () updateGlobalParasitemia () - and RBCFrame (srcImg: SourceImage) (rbc: RBC) : Views.RBCFrame = + and RBCFrame (srcImg : SourceImage) (rbc : RBC) : Views.RBCFrame = let frame = RBCFrameFromExisting srcImg rbc (Views.RBCFrame()) 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) frame.ContextMenuOpening.AddHandler( fun obj args -> - if (frame.Tag :?> RBC).infected - then + if (frame.Tag :?> RBC).infected then frame.menuRBCSetAsHealthy.Visibility <- Visibility.Visible frame.menuRBCSetAsInfected.Visibility <- Visibility.Collapsed else frame.menuRBCSetAsHealthy.Visibility <- Visibility.Collapsed - frame.menuRBCSetAsInfected.Visibility <- Visibility.Visible) - + frame.menuRBCSetAsInfected.Visibility <- Visibility.Visible + ) frame.ContextMenuClosing.AddHandler(fun obj args -> if not frame.IsMouseOver then highlightRBCFrame frame false ) frame.MouseEnter.AddHandler(fun obj args -> highlightRBCFrame frame true) frame.MouseLeave.AddHandler(fun obj args -> if not frame.grid.ContextMenu.IsOpen then highlightRBCFrame frame false) @@ -197,8 +197,7 @@ let run (defaultConfig: Config) (fileToOpen: string option) = let mutable currentPreview = 0 for rbc in srcImg.rbcs |> List.filter (fun rbc -> displayHealthy || rbc.infected) do let previewInfected = - if currentPreview < win.stackRBC.Children.Count - then + if currentPreview < win.stackRBC.Children.Count then RBCFrameFromExisting srcImg rbc (win.stackRBC.Children.[currentPreview] :?> Views.RBCFrame) else let f = RBCFrame srcImg rbc @@ -223,12 +222,11 @@ let run (defaultConfig: Config) (fileToOpen: string option) = let mutable currentCanvas = 0 for rbc in srcImg.rbcs do let frame = - if currentCanvas < win.canvasCurrentImage.Children.Count - then + if currentCanvas < win.canvasCurrentImage.Children.Count then RBCFrameFromExisting srcImg rbc (win.canvasCurrentImage.Children.[currentCanvas] :?> Views.RBCFrame) else let f = RBCFrame srcImg rbc - f.Root.Opacity <- 0.7 + f.Opacity <- 0.7 win.canvasCurrentImage.Children.Add(f) |> ignore f @@ -244,11 +242,9 @@ let run (defaultConfig: Config) (fileToOpen: string option) = let askDocumentPathToSave () : string option = let dialog = SaveFileDialog(AddExtension = true, DefaultExt = PiaZ.extension, Filter = PiaZ.filter) - if state.FilePath <> "" - then + if state.FilePath <> "" then dialog.FileName <- FileInfo(state.FilePath).Name - elif state.PatientID <> "" - then + elif state.PatientID <> "" then dialog.FileName <- state.PatientID + PiaZ.extension let res = dialog.ShowDialog() @@ -259,8 +255,7 @@ let run (defaultConfig: Config) (fileToOpen: string option) = let saveCurrentDocument () = try - if state.FilePath = "" - then + if state.FilePath = "" then match askDocumentPathToSave () with | Some filepath -> state.FilePath <- filepath @@ -284,8 +279,7 @@ let run (defaultConfig: Config) (fileToOpen: string option) = // Ask the use to save the current document if neccessary. let askSaveCurrent () = - if state.AlteredSinceLastSave - then + if state.AlteredSinceLastSave then match MessageBox.Show("Would you like to save the current document?", "Saving the current document", MessageBoxButton.YesNo, MessageBoxImage.Question) with | MessageBoxResult.Yes -> saveCurrentDocument () | _ -> () @@ -316,13 +310,12 @@ let run (defaultConfig: Config) (fileToOpen: string option) = updateCurrentImageInformation () - let setCurrentImage (srcImg: SourceImage) = - if state.CurrentImage.IsNone || state.CurrentImage.Value <> srcImg - then + let setCurrentImage (srcImg : SourceImage) = + if state.CurrentImage.IsNone || state.CurrentImage.Value <> srcImg then state.CurrentImage <- Some srcImg updateCurrentImage () - let addPreview (srcImg: SourceImage) = + let addPreview (srcImg : SourceImage) = let imgCtrl = Views.ImageSourcePreview(Margin = Thickness(3.)) imgCtrl.menuRemoveImage.Click.AddHandler(fun obj args -> @@ -330,8 +323,7 @@ let run (defaultConfig: Config) (fileToOpen: string option) = let srcImg = imgCtrl.Tag :?> SourceImage let currentRemoved = Some srcImg = state.CurrentImage state.RemoveSourceImage srcImg - if currentRemoved - then + if currentRemoved then updateCurrentImage() updateGlobalParasitemia() @@ -347,7 +339,7 @@ let run (defaultConfig: Config) (fileToOpen: string option) = win.stackPreviews.Children.Add(imgCtrl) |> ignore // Zoom to a mouse position into the control 'imgCtrl'. - let zoomTo (mousePos: Point) = + let zoomTo (mousePos : Point) = 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 @@ -359,17 +351,18 @@ let run (defaultConfig: Config) (fileToOpen: string option) = setCurrentImage (state.SourceImages |> Seq.find (fun srcImg -> (srcImg :> Object) = imgCtrl.Tag)) imgCtrl.UpdateLayout() zoomTo (args.GetPosition(imgCtrl)) - imgCtrl.CaptureMouse() |> ignore) + imgCtrl.CaptureMouse() |> ignore + ) imgCtrl.MouseMove.AddHandler(fun obj args -> - if imgCtrl.IsMouseCaptured - then - zoomTo (args.GetPosition(imgCtrl))) + if imgCtrl.IsMouseCaptured then + zoomTo (args.GetPosition(imgCtrl)) + ) imgCtrl.MouseLeftButtonUp.AddHandler(fun obj args -> - if imgCtrl.IsMouseCaptured - then - imgCtrl.ReleaseMouseCapture()) + if imgCtrl.IsMouseCaptured then + imgCtrl.ReleaseMouseCapture() + ) let updatePreviews () = win.stackPreviews.Children.Clear () @@ -383,7 +376,7 @@ let run (defaultConfig: Config) (fileToOpen: string option) = updateGlobalParasitemia () updateDocumentStatus () - let loadFile (filepath: string) = + let loadFile (filepath : string) = askSaveCurrent () let previousFilePath = state.FilePath try @@ -399,8 +392,8 @@ let run (defaultConfig: Config) (fileToOpen: string option) = let askLoadFile () = let dialog = OpenFileDialog(Filter = PiaZ.filter) let res = dialog.ShowDialog() - if res.HasValue && res.Value - then loadFile dialog.FileName + if res.HasValue && res.Value then + loadFile dialog.FileName let newFile () = askSaveCurrent () @@ -411,11 +404,9 @@ let run (defaultConfig: Config) (fileToOpen: string option) = let extension = ".txt" let dialog = SaveFileDialog(AddExtension = true, DefaultExt = extension) - if state.FilePath <> "" - then + if state.FilePath <> "" then dialog.FileName <- Path.GetFileNameWithoutExtension(state.FilePath) + extension - elif state.PatientID <> "" - then + elif state.PatientID <> "" then dialog.FileName <- state.PatientID + extension let res = dialog.ShowDialog() @@ -430,8 +421,7 @@ let run (defaultConfig: Config) (fileToOpen: string option) = let importImage () = let dialog = OpenFileDialog(Filter = "Image Files|*.png;*.jpg;*.tif;*.tiff", Multiselect = true) let res = dialog.ShowDialog() - if res.HasValue && res.Value - then + if res.HasValue && res.Value then let noSourceImage = state.SourceImages.Count() = 0 for filename in dialog.FileNames do @@ -445,13 +435,12 @@ let run (defaultConfig: Config) (fileToOpen: string option) = updateGlobalParasitemia () - if noSourceImage - then + if noSourceImage then updateCurrentImage () win.txtPatient.TextChanged.AddHandler(fun obj args -> state.PatientID <- win.txtPatient.Text) - win.menuExit.Click.AddHandler(fun obj args -> win.Root.Close()) + win.menuExit.Click.AddHandler(fun obj args -> win.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 ()) @@ -463,46 +452,49 @@ let run (defaultConfig: Config) (fileToOpen: string option) = win.menuAnalysis.SubmenuOpened.AddHandler(fun obj args -> win.menuStartAnalysis.IsEnabled <- state.SourceImages.Count() > 0) win.menuStartAnalysis.Click.AddHandler(fun obj args -> - if Analysis.showWindow win.Root state - then + if Analysis.showWindow win state then updateGlobalParasitemia () - updateCurrentImage ()) + updateCurrentImage () + ) win.menuHightlightRBC.Click.AddHandler(fun obj args -> displayHealthy <- win.menuHightlightRBC.IsChecked updateRBCFramesPreview () - updateRBCFramesCurrent ()) + updateRBCFramesCurrent () + ) - win.menuAbout.Click.AddHandler(fun obj args -> About.showWindow win.Root) + win.menuAbout.Click.AddHandler(fun obj args -> About.showWindow win) - win.Root.Closing.AddHandler(fun obj args -> askSaveCurrent ()) + win.Closing.AddHandler(fun obj args -> askSaveCurrent ()) // Zoom on the current image. - let adjustCurrentImageBorders (deltaX: float) (deltaY: float) = + let adjustCurrentImageBorders (deltaX : float) (deltaY : float) = win.borderCurrentImage.BorderThickness <- Thickness( (win.scrollViewCurrentImage.ViewportWidth + deltaX) / 2., (win.scrollViewCurrentImage.ViewportHeight + deltaY) / 2., (win.scrollViewCurrentImage.ViewportWidth + deltaX) / 2., - (win.scrollViewCurrentImage.ViewportHeight + deltaY) / 2.) + (win.scrollViewCurrentImage.ViewportHeight + deltaY) / 2. + ) 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 + if deltaX > 0.5 || deltaY > 0.5 then adjustCurrentImageBorders 0.0 0.0 // Center the view at the center of the image initialy. 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.)) + win.scrollViewCurrentImage.ScrollToVerticalOffset(win.borderCurrentImage.ActualHeight / 2. - win.scrollViewCurrentImage.ViewportHeight / 2.) + ) 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 win.scrollViewCurrentImage.ScrollToHorizontalOffset(win.scrollViewCurrentImage.HorizontalOffset + deltaX / 8.) - win.scrollViewCurrentImage.ScrollToVerticalOffset(win.scrollViewCurrentImage.VerticalOffset + deltaY / 8.)) + win.scrollViewCurrentImage.ScrollToVerticalOffset(win.scrollViewCurrentImage.VerticalOffset + deltaY / 8.) + ) let mutable maxScale = 4. let mutable minScale = 0.25 @@ -510,8 +502,7 @@ let run (defaultConfig: Config) (fileToOpen: string option) = 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 scaleFactor > 1. && currentScale < maxScale || scaleFactor < 1. && currentScale > minScale then let previousScale = currentScale currentScale <- let newScale = currentScale * scaleFactor @@ -527,7 +518,8 @@ let run (defaultConfig: Config) (fileToOpen: string option) = 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) + args.Handled <- true + ) // Pan on the current image. let mutable scrollStartPosition = Point(0., 0.) @@ -539,62 +531,75 @@ let run (defaultConfig: Config) (fileToOpen: string option) = scrollStartOffsetY <- win.scrollViewCurrentImage.VerticalOffset win.borderCurrentImage.Cursor <- Input.Cursors.ScrollAll win.borderCurrentImage.CaptureMouse() |> ignore - args.Handled <- true) + args.Handled <- true + ) win.borderCurrentImage.PreviewMouseMove.AddHandler(fun obj args -> - if win.borderCurrentImage.IsMouseCaptured - then + if win.borderCurrentImage.IsMouseCaptured then let position = args.GetPosition(win.scrollViewCurrentImage) let deltaX = scrollStartPosition.X - position.X let deltaY = scrollStartPosition.Y - position.Y win.scrollViewCurrentImage.ScrollToHorizontalOffset(deltaX + scrollStartOffsetX) win.scrollViewCurrentImage.ScrollToVerticalOffset(deltaY + scrollStartOffsetY) - args.Handled <- true) + args.Handled <- true + ) win.borderCurrentImage.PreviewMouseLeftButtonUp.AddHandler(fun obj args -> - if win.borderCurrentImage.IsMouseCaptured - then + if win.borderCurrentImage.IsMouseCaptured then win.borderCurrentImage.Cursor <- Input.Cursors.Arrow win.borderCurrentImage.ReleaseMouseCapture() - args.Handled <- true) + args.Handled <- true + ) // Shortcuts. // Save. - win.Root.InputBindings.Add( + win.InputBindings.Add( Input.KeyBinding( - FSharp.ViewModule.FunCommand((fun obj -> saveCurrentDocument ()), (fun obj -> true)), - Input.KeyGesture(Input.Key.S, Input.ModifierKeys.Control))) |> ignore + ViewModule.FunCommand((fun obj -> saveCurrentDocument ()), (fun obj -> true)), + Input.KeyGesture(Input.Key.S, Input.ModifierKeys.Control) + ) + ) |> ignore // Save as. - win.Root.InputBindings.Add( + win.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 + ViewModule.FunCommand((fun obj -> saveCurrentDocumentAsNewFile ()), (fun obj -> true)), + Input.KeyGesture(Input.Key.S, Input.ModifierKeys.Control ||| Input.ModifierKeys.Shift) + ) + ) |> ignore // Open. - win.Root.InputBindings.Add( + win.InputBindings.Add( Input.KeyBinding( - FSharp.ViewModule.FunCommand((fun obj -> askLoadFile ()), (fun obj -> true)), - Input.KeyGesture(Input.Key.O, Input.ModifierKeys.Control))) |> ignore + ViewModule.FunCommand((fun obj -> askLoadFile ()), (fun obj -> true)), + Input.KeyGesture(Input.Key.O, Input.ModifierKeys.Control) + ) + ) |> ignore // New file. - win.Root.InputBindings.Add( + win.InputBindings.Add( Input.KeyBinding( - FSharp.ViewModule.FunCommand((fun obj -> newFile ()), (fun obj -> true)), - Input.KeyGesture(Input.Key.N, Input.ModifierKeys.Control))) |> ignore + ViewModule.FunCommand((fun obj -> newFile ()), (fun obj -> true)), + Input.KeyGesture(Input.Key.N, Input.ModifierKeys.Control) + ) + ) |> ignore // Export results. - win.Root.InputBindings.Add( + win.InputBindings.Add( Input.KeyBinding( - FSharp.ViewModule.FunCommand((fun obj -> exportResults ()), (fun obj -> true)), - Input.KeyGesture(Input.Key.E, Input.ModifierKeys.Control))) |> ignore + ViewModule.FunCommand((fun obj -> exportResults ()), (fun obj -> true)), + Input.KeyGesture(Input.Key.E, Input.ModifierKeys.Control) + ) + ) |> ignore // Import an image. - win.Root.InputBindings.Add( + win.InputBindings.Add( Input.KeyBinding( - FSharp.ViewModule.FunCommand((fun obj -> importImage ()), (fun obj -> true)), - Input.KeyGesture(Input.Key.A, Input.ModifierKeys.Control))) |> ignore + ViewModule.FunCommand((fun obj -> importImage ()), (fun obj -> true)), + Input.KeyGesture(Input.Key.A, Input.ModifierKeys.Control) + ) + ) |> ignore // Viewport preview. win.scrollViewCurrentImage.ScrollChanged.AddHandler(fun obj args -> updateViewportPreview ()) @@ -602,7 +607,7 @@ let run (defaultConfig: Config) (fileToOpen: string option) = updateDocumentStatus () win.gridImageInformation.Visibility <- Visibility.Hidden - win.Root.Show() + win.Show() match fileToOpen with | Some filepath -> loadFile filepath diff --git a/Parasitemia/ParasitemiaUI/ParasitemiaUI.fsproj b/Parasitemia/ParasitemiaUI/ParasitemiaUI.fsproj index a423093..96b0732 100644 --- a/Parasitemia/ParasitemiaUI/ParasitemiaUI.fsproj +++ b/Parasitemia/ParasitemiaUI/ParasitemiaUI.fsproj @@ -9,9 +9,9 @@ WinExe ParasitemiaUI ParasitemiaUI - v4.5.2 + v4.6.2 true - 4.4.0.0 + 4.4.1.0 ParasitemiaUI @@ -50,7 +50,7 @@ true bin\Release\ TRACE - 3 + 1 AnyCPU bin\Release\ParasitemiaUI.XML false @@ -96,32 +96,25 @@ - - ..\..\..\Emgu\emgucv-windows-universal 3.0.0.2157\bin\Emgu.CV.dll - - - ..\..\..\Emgu\emgucv-windows-universal 3.0.0.2157\bin\Emgu.Util.dll + + ..\..\..\Emgu\emgucv-windesktop 3.1.0.2282\bin\Emgu.CV.World.dll - + ..\packages\FSharp.Core.4.0.0.1\lib\net40\FSharp.Core.dll True - ..\packages\FSharp.ViewModule.Core.0.9.9.2\lib\portable-net45+netcore45+wpa81+wp8+MonoAndroid1+MonoTouch1\FSharp.ViewModule.dll - True + ..\packages\FSharp.ViewModule.Core.1.0.7.0\lib\portable-net45+netcore45+wpa81+wp8+MonoAndroid1+MonoTouch1\FSharp.ViewModule.dll - ..\packages\FsXaml.Wpf.0.9.9\lib\net45\FsXaml.Wpf.dll - True + ..\packages\FsXaml.Wpf.3.1.6\lib\net45\FsXaml.Wpf.dll - ..\packages\FsXaml.Wpf.0.9.9\lib\net45\FsXaml.Wpf.TypeProvider.dll - True + ..\packages\FsXaml.Wpf.3.1.6\lib\net45\FsXaml.Wpf.TypeProvider.dll - ..\packages\Newtonsoft.Json.8.0.2\lib\net45\Newtonsoft.Json.dll - True + ..\packages\Newtonsoft.Json.10.0.1\lib\net45\Newtonsoft.Json.dll @@ -134,6 +127,9 @@ + + ..\packages\System.ValueTuple.4.3.0\lib\netstandard1.0\System.ValueTuple.dll + ..\packages\Expression.Blend.Sdk.1.0.2\lib\net45\System.Windows.Interactivity.dll True @@ -161,8 +157,8 @@ - xcopy "D:\Emgu\emgucv-windows-universal 3.0.0.2157\bin\x64" "$(TargetDir)x64" /Y /D /I -xcopy "D:\Emgu\emgucv-windows-universal 3.0.0.2157\bin\x86" "$(TargetDir)x86" /Y /D /I + xcopy "D:\Emgu\emgucv-windesktop 3.1.0.2282\bin\x64" "$(TargetDir)x64" /Y /D /I +xcopy "D:\Emgu\emgucv-windesktop 3.1.0.2282\bin\x86" "$(TargetDir)x86" /Y /D /I