From b63f58aa12ab2584e804785d482867e6fa637389 Mon Sep 17 00:00:00 2001 From: Greg Burri Date: Thu, 4 May 2017 21:57:20 +0200 Subject: [PATCH 01/16] Update setup builder to the new Emgu dll name. --- Parasitemia/Setup/setup.iss | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Parasitemia/Setup/setup.iss b/Parasitemia/Setup/setup.iss index e752860..00579bb 100644 --- a/Parasitemia/Setup/setup.iss +++ b/Parasitemia/Setup/setup.iss @@ -20,8 +20,7 @@ ChangesAssociations=yes [Files] Source: "{#ApplicationDir}/ParasitemiaUI/bin/Release/ParasitemiaUI.exe"; DestDir: "{app}"; Flags: comparetimestamp -Source: "{#ApplicationDir}/ParasitemiaUI/bin/Release/Emgu.CV.dll"; DestDir: "{app}"; Flags: comparetimestamp -Source: "{#ApplicationDir}/ParasitemiaUI/bin/Release/Emgu.Util.dll"; DestDir: "{app}"; Flags: comparetimestamp +Source: "{#ApplicationDir}/ParasitemiaUI/bin/Release/Emgu.CV.World.dll"; DestDir: "{app}"; Flags: comparetimestamp Source: "{#ApplicationDir}/ParasitemiaUI/bin/Release/FSharp.Collections.ParallelSeq.dll"; DestDir: "{app}"; Flags: comparetimestamp Source: "{#ApplicationDir}/ParasitemiaUI/bin/Release/FSharp.Core.dll"; DestDir: "{app}"; Flags: comparetimestamp Source: "{#ApplicationDir}/ParasitemiaUI/bin/Release/FSharp.ViewModule.dll"; DestDir: "{app}"; Flags: comparetimestamp @@ -34,7 +33,6 @@ Source: "{#ApplicationDir}/ParasitemiaUI/bin/Release/ParasitemiaCore.dll"; DestD Source: "{#ApplicationDir}/ParasitemiaUI/bin/Release/System.Windows.Interactivity.dll"; DestDir: "{app}"; Flags: comparetimestamp Source: "{#ApplicationDir}/ParasitemiaUI/bin/Release/WPF.dll"; DestDir: "{app}"; Flags: comparetimestamp Source: "{#ApplicationDir}/ParasitemiaUI/bin/Release/ParasitemiaUI.exe.config"; DestDir: "{app}"; Flags: comparetimestamp - Source: "{#ApplicationDir}/ParasitemiaUI/bin/Release/x64/*"; DestDir: "{app}/x64"; Flags: comparetimestamp recursesubdirs [Icons] -- 2.45.1 From 829c86a5f0f165438da8f8da2e072889065a4df1 Mon Sep 17 00:00:00 2001 From: Greg Burri Date: Sat, 6 May 2017 11:30:10 +0200 Subject: [PATCH 02/16] Micro optimization to improve analysis speed by ~20% --- Parasitemia/Parasitemia.sln | 7 +++++-- Parasitemia/ParasitemiaCore/AssemblyInfo.fs | 4 ++-- Parasitemia/ParasitemiaCore/EEOver.fs | 2 +- Parasitemia/ParasitemiaCore/ImgTools/Morpho.fs | 8 ++++---- Parasitemia/ParasitemiaUI/AssemblyInfo.fs | 4 ++-- 5 files changed, 14 insertions(+), 11 deletions(-) diff --git a/Parasitemia/Parasitemia.sln b/Parasitemia/Parasitemia.sln index 545e796..9db6b13 100644 --- a/Parasitemia/Parasitemia.sln +++ b/Parasitemia/Parasitemia.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 14 -VisualStudioVersion = 14.0.24720.0 +# Visual Studio 15 +VisualStudioVersion = 15.0.26403.7 MinimumVisualStudioVersion = 10.0.40219.1 Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "ParasitemiaUI", "ParasitemiaUI\ParasitemiaUI.fsproj", "{70838E65-F211-44FC-B28F-0ED1CA6E850F}" EndProject @@ -12,6 +12,9 @@ EndProject Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "ParasitemiaCore", "ParasitemiaCore\ParasitemiaCore.fsproj", "{0F8A85F4-9328-40C3-B8FF-44FB39CEB01F}" EndProject Global + GlobalSection(Performance) = preSolution + HasPerformanceSessions = true + EndGlobalSection GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU DebugGUI|Any CPU = DebugGUI|Any CPU diff --git a/Parasitemia/ParasitemiaCore/AssemblyInfo.fs b/Parasitemia/ParasitemiaCore/AssemblyInfo.fs index 184fbed..2bba4bb 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/EEOver.fs b/Parasitemia/ParasitemiaCore/EEOver.fs index a7791c9..0824e98 100644 --- a/Parasitemia/ParasitemiaCore/EEOver.fs +++ b/Parasitemia/ParasitemiaCore/EEOver.fs @@ -404,7 +404,7 @@ 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[,]) = +let inline private biquadroots (p : float[]) (r : float[,]) = if p.[0] <> 1.0 then for k = 1 to 4 do p.[k] <- p.[k] / p.[0] diff --git a/Parasitemia/ParasitemiaCore/ImgTools/Morpho.fs b/Parasitemia/ParasitemiaCore/ImgTools/Morpho.fs index 4cb1f0b..6c8d7a0 100644 --- a/Parasitemia/ParasitemiaCore/ImgTools/Morpho.fs +++ b/Parasitemia/ParasitemiaCore/ImgTools/Morpho.fs @@ -29,7 +29,7 @@ type ExtremumType = | Maxima = 1 | Minima = 2 -let findExtremum (img : Image) (extremumType : ExtremumType) : IEnumerable = +let inline findExtremum (img : Image) (extremumType : ExtremumType) : IEnumerable when 'TDepth : unmanaged = let w = img.Width let h = img.Height let se = [| -1, 0; 0, -1; 1, 0; 0, 1 |] @@ -66,7 +66,7 @@ let findExtremum (img : Image) (extremumType : ExtremumType) : IE let level = imgData.[ni, nj, 0] let notSuppressed = not suppress.[ni, nj] - if level = currentLevel && notSuppressed then + if notSuppressed && level = currentLevel then suppress.[ni, nj] <- true sameLevelToCheck.Push(Point(nj, ni)) elif (if extremumType = ExtremumType.Maxima then level > currentLevel else level < currentLevel) then @@ -86,10 +86,10 @@ let findExtremum (img : Image) (extremumType : ExtremumType) : IE result.Select(fun l -> Points(l)) -let findMaxima (img : Image) : IEnumerable = +let inline findMaxima (img : Image) : IEnumerable when 'TDepth : unmanaged = findExtremum img ExtremumType.Maxima -let findMinima (img : Image) : IEnumerable = +let inline findMinima (img : Image) : IEnumerable when 'TDepth : unmanaged = findExtremum img ExtremumType.Minima type PriorityQueue () = diff --git a/Parasitemia/ParasitemiaUI/AssemblyInfo.fs b/Parasitemia/ParasitemiaUI/AssemblyInfo.fs index 89fc224..40e4529 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 -- 2.45.1 From aeb0583fa94d4c8cef1f8b53559ecac76bc1a191 Mon Sep 17 00:00:00 2001 From: Greg Burri Date: Fri, 19 May 2017 00:08:47 +0200 Subject: [PATCH 03/16] Define Emgu as a nuget reference for the project WPF --- Documentation/logo.svg | 98 ++++ Parasitemia/Parasitemia.sln | 19 + Parasitemia/ParasitemiaCore/Classifier.fs | 2 +- Parasitemia/ParasitemiaCore/KdTree.fs | 60 --- .../ParasitemiaCore/ParasitemiaCore.fsproj | 2 +- Parasitemia/ParasitemiaCore/Types.fs | 6 +- Parasitemia/ParasitemiaCore/packages.config | 2 +- Parasitemia/ParasitemiaUI/License-LGPL.txt | 506 ++++++++++++++++++ Parasitemia/ParasitemiaUI/OpenTK.dll.config | 25 + .../ParasitemiaUI/ParasitemiaUI.fsproj | 2 +- Parasitemia/ParasitemiaUI/packages.config | 2 +- .../ParasitemiaCore.Tests/AssemblyInfo.fs | 43 ++ .../Tests/ParasitemiaCore.Tests/KdTree.fs | 71 +++ .../ParasitemiaCore.Tests.fsproj | 97 ++++ .../ParasitemiaCore.Tests/packages.config | 12 + Parasitemia/WPF/License-LGPL.txt | 506 ++++++++++++++++++ Parasitemia/WPF/OpenTK.dll.config | 25 + Parasitemia/WPF/WPF.csproj | 39 +- Parasitemia/WPF/app.config | 15 + Parasitemia/WPF/packages.config | 7 + 20 files changed, 1469 insertions(+), 70 deletions(-) create mode 100644 Documentation/logo.svg create mode 100644 Parasitemia/ParasitemiaUI/License-LGPL.txt create mode 100644 Parasitemia/ParasitemiaUI/OpenTK.dll.config create mode 100644 Parasitemia/Tests/ParasitemiaCore.Tests/AssemblyInfo.fs create mode 100644 Parasitemia/Tests/ParasitemiaCore.Tests/KdTree.fs create mode 100644 Parasitemia/Tests/ParasitemiaCore.Tests/ParasitemiaCore.Tests.fsproj create mode 100644 Parasitemia/Tests/ParasitemiaCore.Tests/packages.config create mode 100644 Parasitemia/WPF/License-LGPL.txt create mode 100644 Parasitemia/WPF/OpenTK.dll.config create mode 100644 Parasitemia/WPF/app.config create mode 100644 Parasitemia/WPF/packages.config diff --git a/Documentation/logo.svg b/Documentation/logo.svg new file mode 100644 index 0000000..b65f39b --- /dev/null +++ b/Documentation/logo.svg @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + diff --git a/Parasitemia/Parasitemia.sln b/Parasitemia/Parasitemia.sln index 9db6b13..ba7560b 100644 --- a/Parasitemia/Parasitemia.sln +++ b/Parasitemia/Parasitemia.sln @@ -11,6 +11,10 @@ Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Logger", "Logger\Logger.fsp EndProject Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "ParasitemiaCore", "ParasitemiaCore\ParasitemiaCore.fsproj", "{0F8A85F4-9328-40C3-B8FF-44FB39CEB01F}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{1EC5C716-CA52-46FD-A76C-BEF9459E5561}" +EndProject +Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "ParasitemiaCore.Tests", "Tests\ParasitemiaCore.Tests\ParasitemiaCore.Tests.fsproj", "{2AB542E3-5F90-48CA-9442-32B2780B3E4A}" +EndProject Global GlobalSection(Performance) = preSolution HasPerformanceSessions = true @@ -45,8 +49,23 @@ Global {0F8A85F4-9328-40C3-B8FF-44FB39CEB01F}.DebugGUI|Any CPU.Build.0 = Debug|Any CPU {0F8A85F4-9328-40C3-B8FF-44FB39CEB01F}.Release|Any CPU.ActiveCfg = Release|Any CPU {0F8A85F4-9328-40C3-B8FF-44FB39CEB01F}.Release|Any CPU.Build.0 = Release|Any CPU + {2AB542E3-5F90-48CA-9442-32B2780B3E4A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2AB542E3-5F90-48CA-9442-32B2780B3E4A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2AB542E3-5F90-48CA-9442-32B2780B3E4A}.DebugGUI|Any CPU.ActiveCfg = Debug|Any CPU + {2AB542E3-5F90-48CA-9442-32B2780B3E4A}.DebugGUI|Any CPU.Build.0 = Debug|Any CPU + {2AB542E3-5F90-48CA-9442-32B2780B3E4A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2AB542E3-5F90-48CA-9442-32B2780B3E4A}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {2AB542E3-5F90-48CA-9442-32B2780B3E4A} = {1EC5C716-CA52-46FD-A76C-BEF9459E5561} + EndGlobalSection + GlobalSection(Performance) = preSolution + HasPerformanceSessions = true + EndGlobalSection + GlobalSection(Performance) = preSolution + HasPerformanceSessions = true + EndGlobalSection EndGlobal diff --git a/Parasitemia/ParasitemiaCore/Classifier.fs b/Parasitemia/ParasitemiaCore/Classifier.fs index f7c1bbb..36e2851 100644 --- a/Parasitemia/ParasitemiaCore/Classifier.fs +++ b/Parasitemia/ParasitemiaCore/Classifier.fs @@ -112,7 +112,7 @@ let findCells (ellipses : Ellipse list) (parasites : ParasitesMarker.Result) (wi // 2) Remove ellipses touching the edges. let widthF, heightF = float32 width, float32 height for e in ellipses do - if e.isOutside widthF heightF then e.State <- CellState.Removed + if e.IsOutside widthF heightF then e.State <- CellState.Removed // 3) Remove ellipses with a high standard deviation (high contrast). // Obsolete. It was useful when the ellipses result quality wasn't good. diff --git a/Parasitemia/ParasitemiaCore/KdTree.fs b/Parasitemia/ParasitemiaCore/KdTree.fs index 4e20f24..f98cfa2 100644 --- a/Parasitemia/ParasitemiaCore/KdTree.fs +++ b/Parasitemia/ParasitemiaCore/KdTree.fs @@ -90,63 +90,3 @@ type Tree<'a when 'a :> I2DCoords> = (valuesInRegion downRegion part1) @ (valuesInRegion upRegion part2) searchWithRegion this { minX = Single.MinValue; maxX = Single.MaxValue; minY = Single.MinValue; maxY = Single.MaxValue } 1 - -///// Tests. TODO: to put in a unit test. - -type Point (x : float32, y : float32) = - interface I2DCoords with - member this.X = x - member this.Y = y - - override this.ToString () = - sprintf "(%.1f, %.1f)" x y - -// 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 tree = Tree.BuildTree pts - Utils.dprintfn "Tree: %A" tree - - let s1 = tree.Search { minX = 0.0f; maxX = 5.0f; minY = 0.0f; maxY = 5.0f } // All points. - Utils.dprintfn "s1: %A" s1 - - let s2 = tree.Search { minX = 2.8f; maxX = 4.5f; minY = 3.0f; maxY = 4.5f } - Utils.dprintfn "s2: %A" s2 - - let s3 = tree.Search { minX = 2.0f; maxX = 2.0f; minY = 2.0f; maxY = 2.0f } - 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 tree = Tree.BuildTree pts - Utils.dprintfn "Tree: %A" tree - - let s1 = tree.Search { minX = 1.0f; maxX = 1.0f; minY = 1.0f; maxY = 1.0f } - Utils.dprintfn "s1: %A" s1 - - let s2 = tree.Search { minX = 1.0f; maxX = 1.0f; minY = 2.0f; maxY = 2.0f } - Utils.dprintfn "s2: %A" s2 - - // This case result is wrong: FIXME - let s3 = tree.Search { minX = 1.0f; maxX = 1.0f; minY = 3.0f; maxY = 3.0f } - Utils.dprintfn "s3: %A" s3 - - let s4 = tree.Search { minX = 0.0f; maxX = 2.0f; minY = 0.0f; maxY = 4.0f } - Utils.dprintfn "s4: %A" s4 - diff --git a/Parasitemia/ParasitemiaCore/ParasitemiaCore.fsproj b/Parasitemia/ParasitemiaCore/ParasitemiaCore.fsproj index 340eb5b..5a14573 100644 --- a/Parasitemia/ParasitemiaCore/ParasitemiaCore.fsproj +++ b/Parasitemia/ParasitemiaCore/ParasitemiaCore.fsproj @@ -111,7 +111,7 @@ - ..\packages\System.ValueTuple.4.3.0\lib\netstandard1.0\System.ValueTuple.dll + ..\packages\System.ValueTuple.4.3.1\lib\netstandard1.0\System.ValueTuple.dll diff --git a/Parasitemia/ParasitemiaCore/Types.fs b/Parasitemia/ParasitemiaCore/Types.fs index f88baf8..c1a635d 100644 --- a/Parasitemia/ParasitemiaCore/Types.fs +++ b/Parasitemia/ParasitemiaCore/Types.fs @@ -29,7 +29,7 @@ type Ellipse (cx : float32, cy : float32, a : float32, b : float32, alpha : floa 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 || @@ -67,7 +67,7 @@ type MaybeBuilder () = | None -> None | Some a -> f a - member this.ReturnFrom (x) = x + member this.ReturnFrom x = x member this.TryFinally (body, compensation) = try @@ -85,7 +85,7 @@ type MaybeBuilder () = member this.Zero () = None - member this.Return (x) = + member this.Return x = Some x let maybe = MaybeBuilder() diff --git a/Parasitemia/ParasitemiaCore/packages.config b/Parasitemia/ParasitemiaCore/packages.config index 297d372..91a2fc7 100644 --- a/Parasitemia/ParasitemiaCore/packages.config +++ b/Parasitemia/ParasitemiaCore/packages.config @@ -7,6 +7,6 @@ - + \ No newline at end of file diff --git a/Parasitemia/ParasitemiaUI/License-LGPL.txt b/Parasitemia/ParasitemiaUI/License-LGPL.txt new file mode 100644 index 0000000..84d8640 --- /dev/null +++ b/Parasitemia/ParasitemiaUI/License-LGPL.txt @@ -0,0 +1,506 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! + + + + diff --git a/Parasitemia/ParasitemiaUI/OpenTK.dll.config b/Parasitemia/ParasitemiaUI/OpenTK.dll.config new file mode 100644 index 0000000..7098d39 --- /dev/null +++ b/Parasitemia/ParasitemiaUI/OpenTK.dll.config @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Parasitemia/ParasitemiaUI/ParasitemiaUI.fsproj b/Parasitemia/ParasitemiaUI/ParasitemiaUI.fsproj index f20a7f9..200460e 100644 --- a/Parasitemia/ParasitemiaUI/ParasitemiaUI.fsproj +++ b/Parasitemia/ParasitemiaUI/ParasitemiaUI.fsproj @@ -143,7 +143,7 @@ - ..\packages\System.ValueTuple.4.3.0\lib\netstandard1.0\System.ValueTuple.dll + ..\packages\System.ValueTuple.4.3.1\lib\netstandard1.0\System.ValueTuple.dll diff --git a/Parasitemia/ParasitemiaUI/packages.config b/Parasitemia/ParasitemiaUI/packages.config index 8a1eccf..89d3dcf 100644 --- a/Parasitemia/ParasitemiaUI/packages.config +++ b/Parasitemia/ParasitemiaUI/packages.config @@ -8,6 +8,6 @@ - + \ No newline at end of file diff --git a/Parasitemia/Tests/ParasitemiaCore.Tests/AssemblyInfo.fs b/Parasitemia/Tests/ParasitemiaCore.Tests/AssemblyInfo.fs new file mode 100644 index 0000000..d448a0d --- /dev/null +++ b/Parasitemia/Tests/ParasitemiaCore.Tests/AssemblyInfo.fs @@ -0,0 +1,43 @@ +namespace ParasitemiaCore.Tests.AssemblyInfo + +open System.Reflection +open System.Runtime.CompilerServices +open System.Runtime.InteropServices + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[] +[] +[] +[] +[] +[] +[] +[] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// 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/Tests/ParasitemiaCore.Tests/KdTree.fs b/Parasitemia/Tests/ParasitemiaCore.Tests/KdTree.fs new file mode 100644 index 0000000..9c9d859 --- /dev/null +++ b/Parasitemia/Tests/ParasitemiaCore.Tests/KdTree.fs @@ -0,0 +1,71 @@ +namespace ParasitemiaCore.Tests + +open Xunit +open Xunit.Abstractions + +open ParasitemiaCore.KdTree + +type Point (x : float32, y : float32) = + interface I2DCoords with + member this.X = x + member this.Y = y + + override this.ToString () = + sprintf "(%.1f, %.1f)" x y + +type KdTreeTests (output : ITestOutputHelper) = + + // TODO: test with identical X or Y coords + [] + member this.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 tree = Tree.BuildTree pts + output.WriteLine (sprintf "Tree: %A" tree) + + let s1 = tree.Search { minX = 0.0f; maxX = 5.0f; minY = 0.0f; maxY = 5.0f } // All points. + output.WriteLine (sprintf "s1: %A" s1) + + let s2 = tree.Search { minX = 2.8f; maxX = 4.5f; minY = 3.0f; maxY = 4.5f } + output.WriteLine (sprintf "s2: %A" s2) + + let s3 = tree.Search { minX = 2.0f; maxX = 2.0f; minY = 2.0f; maxY = 2.0f } + output.WriteLine (sprintf "s3: %A" s3) + + [] + member this.test2 () = + let pts = + [ + Point(1.0f, 1.0f) + Point(1.0f, 2.0f) + Point(1.0f, 3.0f) + ] + + let tree = Tree.BuildTree pts + output.WriteLine (sprintf "Tree: %A" tree) + + let s1 = tree.Search { minX = 1.0f; maxX = 1.0f; minY = 1.0f; maxY = 1.0f } + output.WriteLine (sprintf "s1: %A" s1) + + let s2 = tree.Search { minX = 1.0f; maxX = 1.0f; minY = 2.0f; maxY = 2.0f } + output.WriteLine (sprintf "s2: %A" s2) + + // This case result is wrong: FIXME + let s3 = tree.Search { minX = 1.0f; maxX = 1.0f; minY = 3.0f; maxY = 3.0f } + output.WriteLine (sprintf "s3: %A" s3) + + let s4 = tree.Search { minX = 0.0f; maxX = 2.0f; minY = 0.0f; maxY = 4.0f } + output.WriteLine (sprintf "s4: %A" s4) + + + + diff --git a/Parasitemia/Tests/ParasitemiaCore.Tests/ParasitemiaCore.Tests.fsproj b/Parasitemia/Tests/ParasitemiaCore.Tests/ParasitemiaCore.Tests.fsproj new file mode 100644 index 0000000..dc8cf62 --- /dev/null +++ b/Parasitemia/Tests/ParasitemiaCore.Tests/ParasitemiaCore.Tests.fsproj @@ -0,0 +1,97 @@ + + + + + + Debug + AnyCPU + 2.0 + {2ab542e3-5f90-48ca-9442-32b2780b3e4a} + Library + ParasitemiaCore.Tests + ParasitemiaCore.Tests + v4.5.2 + 4.4.1.0 + true + ParasitemiaCore.Tests + + + + + + true + full + false + false + bin\Debug\ + DEBUG;TRACE + 3 + bin\Debug\ParasitemiaCore.Tests.XML + AnyCPU + + + pdbonly + true + true + bin\Release\ + TRACE + 3 + + + AnyCPU + + + 11 + + + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\FSharp\Microsoft.FSharp.Targets + + + + + + + + + + ..\packages\FSharp.Core.4.1.17\lib\net45\FSharp.Core.dll + + + + + ..\..\packages\System.ValueTuple.4.3.1\lib\netstandard1.0\System.ValueTuple.dll + + + ..\..\packages\xunit.abstractions.2.0.1\lib\net35\xunit.abstractions.dll + + + ..\..\packages\xunit.assert.2.2.0\lib\netstandard1.1\xunit.assert.dll + + + ..\..\packages\xunit.extensibility.core.2.2.0\lib\netstandard1.1\xunit.core.dll + + + ..\..\packages\xunit.extensibility.execution.2.2.0\lib\net452\xunit.execution.desktop.dll + + + + + ParasitemiaCore + {0f8a85f4-9328-40c3-b8ff-44fb39ceb01f} + True + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + \ No newline at end of file diff --git a/Parasitemia/Tests/ParasitemiaCore.Tests/packages.config b/Parasitemia/Tests/ParasitemiaCore.Tests/packages.config new file mode 100644 index 0000000..63ccec1 --- /dev/null +++ b/Parasitemia/Tests/ParasitemiaCore.Tests/packages.config @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/Parasitemia/WPF/License-LGPL.txt b/Parasitemia/WPF/License-LGPL.txt new file mode 100644 index 0000000..84d8640 --- /dev/null +++ b/Parasitemia/WPF/License-LGPL.txt @@ -0,0 +1,506 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! + + + + diff --git a/Parasitemia/WPF/OpenTK.dll.config b/Parasitemia/WPF/OpenTK.dll.config new file mode 100644 index 0000000..7098d39 --- /dev/null +++ b/Parasitemia/WPF/OpenTK.dll.config @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Parasitemia/WPF/WPF.csproj b/Parasitemia/WPF/WPF.csproj index 862c74a..c6f6f53 100644 --- a/Parasitemia/WPF/WPF.csproj +++ b/Parasitemia/WPF/WPF.csproj @@ -1,5 +1,6 @@  + Debug @@ -11,6 +12,8 @@ WPF v4.0 512 + + true @@ -33,26 +36,58 @@ AnyCPU - - ..\..\..\Emgu\emgucv-windesktop 3.1.0.2282\bin\Emgu.CV.World.dll + + ..\packages\EmguCV.3.1.0.1\lib\net30\Emgu.CV.UI.dll + + + ..\packages\EmguCV.3.1.0.1\lib\net30\Emgu.CV.UI.GL.dll + + + ..\packages\EmguCV.3.1.0.1\lib\net30\Emgu.CV.World.dll + + + ..\packages\OpenTK.2.0.0\lib\net20\OpenTK.dll + + + ..\packages\OpenTK.GLControl.1.1.2349.61993\lib\NET40\OpenTK.GLControl.dll + + + ..\packages\ZedGraph.5.1.7\lib\net35-Client\ZedGraph.dll + + + + + + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + ellipse2tr(x1)= %f\n" x1 ychk.[i] a1 b1 (ellipse2tr x1 ychk.[i] aa bb cc dd ee ff) - 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) + printf "\n\tx1=%f, y1=%f, A=%f. B=%f ---> ellipse2tr (x1)= %f\n" x1 ychk.[i] a1 b1 (ellipse2tr x1 ychk.[i] aa bb cc dd ee ff) + 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 diff --git a/Parasitemia/ParasitemiaCore/Ellipse.fs b/Parasitemia/ParasitemiaCore/Ellipse.fs index 174dd68..d0dea6e 100644 --- a/Parasitemia/ParasitemiaCore/Ellipse.fs +++ b/Parasitemia/ParasitemiaCore/Ellipse.fs @@ -23,7 +23,7 @@ let minimumDistanceBetweenDrawnPoints = 0.6 /// '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 p0 = pointFromTwoLines (Types.Line(float32 m1, float32 (p1y - m1 * p1x))) (Types.Line(float32 m2, float32(p2y - m2 * p2x))) + 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 = @@ -39,8 +39,8 @@ let ellipse (p1x : float) (p1y : float) (m1 : float) (p2x : float) (p2y : float) let v2 = matrix [[ 1.; p2x; p2y ]] let v3 = matrix [[ 1.; p3x; p3y ]] - let p = (v3.Stack(v1).Stack(v2).Determinant() * v0).Stack(v0.Stack(v3).Stack(v2).Determinant() * v1).Stack(v0.Stack(v1).Stack(v3).Determinant() * v2).Transpose() - let conicMat = p * s.Inverse() * p.Transpose() + let p = (v3.Stack(v1).Stack(v2).Determinant () * v0).Stack(v0.Stack(v3).Stack(v2).Determinant () * v1).Stack(v0.Stack(v1).Stack(v3).Determinant () * v2).Transpose () + let conicMat = p * s.Inverse () * p.Transpose () let a = conicMat.[0, 0] let b = conicMat.[0, 1] let c = conicMat.[1, 1] @@ -57,7 +57,7 @@ let ellipse (p1x : float) (p1y : float) (m1 : float) (p2x : float) (p2y : float) 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 ]]) - let eigen = q.Evd() + let eigen = q.Evd () let eigenValues = eigen.EigenValues let lambda = eigenValues.[1].Real let mu = eigenValues.[0].Real @@ -77,7 +77,7 @@ let ellipse (p1x : float) (p1y : float) (m1 : float) (p2x : float) (p2y : float) let phi' = if phi < 0. then phi + Math.PI else phi let majorAxis, minorAxis = if r1 > r2 then r1, r2 else r2, r1 - Some (Types.Ellipse(float32 cx, float32 cy, float32 majorAxis, float32 minorAxis, float32 phi')) + 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 @@ -150,15 +150,15 @@ let find (edges : Matrix) let mutable last_i, last_j = Int32.MaxValue, Int32.MaxValue - let currentElements = List() + let currentElements = List () let edgesData = edges.Data let xDirData = xGradient.Data let yDirData = yGradient.Data - let rng = Random(42) + let rng = Random 42 - let ellipses = MatchingEllipses(config.RBCRadius.Pixel) + let ellipses = MatchingEllipses config.RBCRadius.Pixel for window_i in -windowSize + increment .. increment .. h - increment do for window_j in -windowSize + increment .. increment .. w - increment do @@ -169,9 +169,9 @@ let find (edges : Matrix) let window_j_end = if window_j + windowSize - 1 >= w then w - 1 else window_j + windowSize - 1 // Remove old elements. - let indexFirstElement = currentElements.FindIndex(fun p -> p.X >= window_j_begin) + let indexFirstElement = currentElements.FindIndex (fun p -> p.X >= window_j_begin) if indexFirstElement > 0 then - currentElements.RemoveRange(0, indexFirstElement) + currentElements.RemoveRange (0, indexFirstElement) // Add the new elements. let newElemsBegin_j = window_j + windowSize - increment @@ -179,15 +179,15 @@ let find (edges : Matrix) 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)) + currentElements.Add (Point (j, i)) 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)] + 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 nbOfPicks <- nbOfPicks - 1 let p1yf, p1xf = float p1.Y, float p1.X @@ -207,7 +207,7 @@ let find (edges : Matrix) | _ -> () | _ -> () - currentElements.Clear() + currentElements.Clear () ellipses diff --git a/Parasitemia/ParasitemiaCore/Granulometry.fs b/Parasitemia/ParasitemiaCore/Granulometry.fs index bbb6d66..287cd3e 100644 --- a/Parasitemia/ParasitemiaCore/Granulometry.fs +++ b/Parasitemia/ParasitemiaCore/Granulometry.fs @@ -16,7 +16,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 = - use scaledImg = if scale = 1. then img else img.Resize(scale, CvEnum.Inter.Area) + use scaledImg = if scale = 1. then img else img.Resize (scale, CvEnum.Inter.Area) let r1, r2 = range let r1', r2' = roundInt (float r1 * scale), roundInt (float r2 * scale) @@ -27,7 +27,7 @@ let findRadiusByClosing (img : Image) (range : int * int) (scale // 's' must be odd. 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 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 @@ -44,9 +44,9 @@ let findRadiusByClosing (img : Image) (range : int * int) (scale 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)) + 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)) + use closed = scaledImg.MorphologyEx (CvEnum.MorphOp.Close, se, Point (-1, -1), 1, CvEnum.BorderType.Replicate, MCvScalar (0.0)) let n = closed.GetSum().Intensity @@ -67,14 +67,17 @@ let findRadiusByAreaClosing (img : Image) (radiusRange : int * in if r1 > r2 then failwithf "'radiusRange' invalid : %O" radiusRange - use imgCopy = img.Copy() + use imgCopy = img.Copy () let mutable maxDiff = 0.f 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 - maxDiff <- diff - max_r <- r - 1) + Morpho.areaCloseFWithFun imgCopy [ for r in r1 .. r2 -> Math.PI * float r ** 2. |> roundInt, r ] ( + fun r diff -> + 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 350164b..356ea06 100644 --- a/Parasitemia/ParasitemiaCore/Heap.fs +++ b/Parasitemia/ParasitemiaCore/Heap.fs @@ -18,16 +18,16 @@ type private Node<'k, 'v> = /// The goal is to have a set of data and be able to get the value associated with the min (or max) key value. /// type Heap<'k, 'v> (kComparer : IComparer<'k>) = - let a = List>() + let a = List> () 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 + 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. @@ -42,41 +42,41 @@ type Heap<'k, 'v> (kComparer : IComparer<'k>) = 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 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 kComparer.Compare (a.[r].key, a.[i].key) > 0 then false else checkIntegrity r else true leftIntegrity && rightIntegrity interface IEnumerable<'k * 'v> with member this.GetEnumerator () : IEnumerator<'k * 'v> = - (seq { for e in a -> e.key, e.value }).GetEnumerator() + (seq { for e in a -> e.key, e.value }).GetEnumerator () interface System.Collections.IEnumerable with member this.GetEnumerator () : System.Collections.IEnumerator = - (this :> IEnumerable<'k * 'v>).GetEnumerator() :> System.Collections.IEnumerator + (this :> IEnumerable<'k * 'v>).GetEnumerator () :> System.Collections.IEnumerator member this.Next () : 'k * 'v = let node = a.[0] a.[0] <- a.[a.Count - 1] - a.RemoveAt(a.Count - 1) + a.RemoveAt (a.Count - 1) heapUp 0 node.key, node.value member this.RemoveNext () = a.[0] <- a.[a.Count - 1] - a.RemoveAt(a.Count - 1) + a.RemoveAt (a.Count - 1) heapUp 0 member this.Add (key : 'k) (value : 'v) = - a.Add(Node(key, value)) + a.Add (Node (key, value)) let mutable i = a.Count - 1 - while i > 0 && kComparer.Compare(a.[parent i].key, a.[i].key) < 0 do + while i > 0 && kComparer.Compare (a.[parent i].key, a.[i].key) < 0 do let tmp = a.[parent i] a.[parent i] <- a.[i] a.[i] <- tmp @@ -89,4 +89,4 @@ type Heap<'k, 'v> (kComparer : IComparer<'k>) = let max = a.[0] max.key, max.value - member this.Clear () = a.Clear() + member this.Clear () = a.Clear () diff --git a/Parasitemia/ParasitemiaCore/ImgTools/Drawing.fs b/Parasitemia/ParasitemiaCore/ImgTools/Drawing.fs index 9662049..2882f4b 100644 --- a/Parasitemia/ParasitemiaCore/ImgTools/Drawing.fs +++ b/Parasitemia/ParasitemiaCore/ImgTools/Drawing.fs @@ -14,14 +14,14 @@ let drawPoints (img : Image) (points : Points) (intensity : 'TDep 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) = - img.Draw(LineSegment2D(Point(x0, y0), Point(x1, y1)), color, thickness); + 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) = - img.Draw(LineSegment2DF(PointF(float32 x0, float32 y0), PointF(float32 x1, float32 y1)), color, thickness, CvEnum.LineType.AntiAlias); + 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 - 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) + img.Draw (Emgu.CV.Structure.Ellipse (PointF (e.Cx, e.Cy), SizeF (2.f * e.B, 2.f * e.A), e.Alpha / PI * 180.f), color, 1, CvEnum.LineType.AntiAlias) else let windowPosX = e.Cx - e.A - 5.f let gapX = windowPosX - (float32 (int windowPosX)) @@ -29,24 +29,24 @@ let drawEllipse (img : Image<'TColor, 'TDepth>) (e : Ellipse) (color : 'TColor) let windowPosY = e.Cy - e.A - 5.f let gapY = windowPosY - (float32 (int windowPosY)) - let roi = Rectangle(int windowPosX, int windowPosY, 2.f * (e.A + 5.f) |> int, 2.f * (e.A + 5.f) |> int) + 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 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) + 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) = List.iter (fun e -> drawEllipse img e color alpha) ellipses -let rngCell = System.Random() +let rngCell = System.Random () 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) + 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 @@ -61,9 +61,9 @@ let drawCell (img : Image) (drawCellContent : bool) (c : Cell) = let crossColor, crossColor2 = match c.cellClass with - | HealthyRBC -> Bgr(255., 0., 0.), Bgr(255., 255., 255.) - | InfectedRBC -> Bgr(0., 0., 255.), Bgr(120., 120., 255.) - | Peculiar -> Bgr(0., 0., 0.), Bgr(80., 80., 80.) + | HealthyRBC -> Bgr (255., 0., 0.), Bgr (255., 255., 255.) + | InfectedRBC -> Bgr (0., 0., 255.), Bgr (120., 120., 255.) + | Peculiar -> Bgr (0., 0., 0.), Bgr (80., 80., 80.) drawLine img crossColor2 (c.center.X - 3) c.center.Y (c.center.X + 3) c.center.Y 2 drawLine img crossColor2 c.center.X (c.center.Y - 3) c.center.X (c.center.Y + 3) 2 diff --git a/Parasitemia/ParasitemiaCore/ImgTools/Edges.fs b/Parasitemia/ParasitemiaCore/ImgTools/Edges.fs index 63ec80a..7c3e6d3 100644 --- a/Parasitemia/ParasitemiaCore/ImgTools/Edges.fs +++ b/Parasitemia/ParasitemiaCore/ImgTools/Edges.fs @@ -25,27 +25,27 @@ let find (img : Image) : Matrix * Matrix * Matrix< let h = img.Height use sobelKernel = - new Matrix( + 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) - CvInvoke.Filter2D(img, xGradient, sobelKernel, Point(1, 1)) - CvInvoke.Filter2D(img, yGradient, sobelKernel.Transpose(), Point(1, 1)) + let xGradient = new Matrix (img.Size) + let yGradient = new Matrix (img.Size) + CvInvoke.Filter2D (img, xGradient, sobelKernel, Point (1, 1)) + CvInvoke.Filter2D (img, yGradient, sobelKernel.Transpose (), Point (1, 1)) - use magnitudes = new Matrix(xGradient.Size) - use angles = new Matrix(xGradient.Size) - CvInvoke.CartToPolar(xGradient, yGradient, magnitudes, angles) // Compute the magnitudes and angles. The angles are between 0 and 2 * pi. + use magnitudes = new Matrix (xGradient.Size) + use angles = new Matrix (xGradient.Size) + CvInvoke.CartToPolar (xGradient, yGradient, magnitudes, angles) // Compute the magnitudes and angles. The angles are between 0 and 2 * pi. let thresholdHigh, thresholdLow = let threshold, _, _ = otsu (histogramMat magnitudes 300) threshold + (sensibilityHigh * threshold), threshold - (sensibilityLow * threshold) // Non-maximum suppression. - use nms = new Matrix(xGradient.Size) + use nms = new Matrix (xGradient.Size) let nmsData = nms.Data let anglesData = angles.Data @@ -88,18 +88,18 @@ let find (img : Image) : Matrix * Matrix * Matrix< // suppressMConnections nms // It's not helpful for the rest of the process (ellipse detection). - let edges = new Matrix(xGradient.Size) + let edges = new Matrix (xGradient.Size) let edgesData = edges.Data // Hysteresis thresholding. - let toVisit = Stack() + let toVisit = Stack () for i = 0 to h - 1 do for j = 0 to w - 1 do if nmsData.[i, j] = 1uy && magnitudesData.[i, j] >= thresholdHigh then nmsData.[i, j] <- 0uy - toVisit.Push(Point(j, i)) + toVisit.Push (Point (j, i)) while toVisit.Count > 0 do - let p = toVisit.Pop() + let p = toVisit.Pop () edgesData.[p.Y, p.X] <- 1uy for i' = -1 to 1 do for j' = -1 to 1 do @@ -108,6 +108,6 @@ let find (img : Image) : Matrix * Matrix * Matrix< let nj = p.X + j' if ni >= 0 && ni < h && nj >= 0 && nj < w && nmsData.[ni, nj] = 1uy then nmsData.[ni, nj] <- 0uy - toVisit.Push(Point(nj, ni)) + toVisit.Push (Point (nj, ni)) edges, xGradient, yGradient \ No newline at end of file diff --git a/Parasitemia/ParasitemiaCore/ImgTools/Histogram.fs b/Parasitemia/ParasitemiaCore/ImgTools/Histogram.fs index df1ecb6..718a9de 100644 --- a/Parasitemia/ParasitemiaCore/ImgTools/Histogram.fs +++ b/Parasitemia/ParasitemiaCore/ImgTools/Histogram.fs @@ -20,10 +20,10 @@ let histogramImg (img : Image) (nbSamples : int) : Histogram = let min, max = let min = ref [| 0.0 |] - let minLocation = ref <| [| Point() |] + let minLocation = ref <| [| Point () |] let max = ref [| 0.0 |] - let maxLocation = ref <| [| Point() |] - img.MinMax(min, max, minLocation, maxLocation) + let maxLocation = ref <| [| Point () |] + img.MinMax (min, max, minLocation, maxLocation) float32 (!min).[0], float32 (!max).[0] let inline bin (x : float32) : int = @@ -44,10 +44,10 @@ let histogramMat (mat : Matrix) (nbSamples : int) : Histogram = let min, max = let min = ref 0.0 - let minLocation = ref <| Point() + let minLocation = ref <| Point () let max = ref 0.0 - let maxLocation = ref <| Point() - mat.MinMax(min, max, minLocation, maxLocation) + let maxLocation = ref <| Point () + mat.MinMax (min, max, minLocation, maxLocation) float32 !min, float32 !max let inline bin (x : float32) : int = diff --git a/Parasitemia/ParasitemiaCore/ImgTools/IO.fs b/Parasitemia/ParasitemiaCore/ImgTools/IO.fs index 1a663c7..08adfb0 100644 --- a/Parasitemia/ParasitemiaCore/ImgTools/IO.fs +++ b/Parasitemia/ParasitemiaCore/ImgTools/IO.fs @@ -7,9 +7,9 @@ open Emgu.CV open Emgu.CV.Structure let saveImg (img : Image<'TColor, 'TDepth>) (filepath : string) = - img.Save(filepath) + img.Save filepath let saveMat (mat : Matrix<'TDepth>) (filepath : string) = - use img = new Image(mat.Size) - mat.CopyTo(img) + 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 5a404e3..fc89047 100644 --- a/Parasitemia/ParasitemiaCore/ImgTools/ImgTools.fs +++ b/Parasitemia/ParasitemiaCore/ImgTools/ImgTools.fs @@ -14,10 +14,10 @@ open Emgu.CV.Structure /// let normalize (img : Image) (upperLimit : float) : Image = let min = ref [| 0.0 |] - let minLocation = ref <| [| Point() |] + let minLocation = ref <| [| Point () |] let max = ref [| 0.0 |] - let maxLocation = ref <| [| Point() |] - img.MinMax(min, max, minLocation, maxLocation) + 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 @@ -30,17 +30,17 @@ let mergeChannels (img : Image) (rgbWeights : float * float * floa | 0., 1., 0. -> img.[1] | 0., 0., 1. -> img.[0] | redFactor, greenFactor, blueFactor -> - let result = new Image(img.Size) - CvInvoke.AddWeighted(result, 1., img.[2], redFactor, 0., result) - CvInvoke.AddWeighted(result, 1., img.[1], greenFactor, 0., result) - CvInvoke.AddWeighted(result, 1., img.[0], blueFactor, 0., result) + let result = new Image (img.Size) + CvInvoke.AddWeighted (result, 1., img.[2], redFactor, 0., result) + CvInvoke.AddWeighted (result, 1., img.[1], greenFactor, 0., result) + 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 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 result = new Image(img.Size) + let result = new Image (img.Size) // TODO: Essayer en bindant Data pour gagner du temps for i = 0 to img.Height - 1 do for j = 0 to img.Width - 1 do @@ -49,8 +49,8 @@ let mergeChannelsWithProjection (img : Image) (v1r : float32, v1g // Normalize image values between 0uy and 255uy. let normalizeAndConvert (img : Image) : Image = - (normalize (img.Convert()) 255.).Convert() + (normalize (img.Convert ()) 255.).Convert () let gaussianFilter (img : Image<'TColor, 'TDepth>) (standardDeviation : float) : Image<'TColor, 'TDepth> = let size = 2 * int (ceil (4.0 * standardDeviation)) + 1 - img.SmoothGaussian(size, size, standardDeviation, standardDeviation) + img.SmoothGaussian (size, size, standardDeviation, standardDeviation) diff --git a/Parasitemia/ParasitemiaCore/ImgTools/Morpho.fs b/Parasitemia/ParasitemiaCore/ImgTools/Morpho.fs index 6c8d7a0..d2826bf 100644 --- a/Parasitemia/ParasitemiaCore/ImgTools/Morpho.fs +++ b/Parasitemia/ParasitemiaCore/ImgTools/Morpho.fs @@ -37,28 +37,28 @@ let inline findExtremum (img : Image) (extremumType : ExtremumTyp let imgData = img.Data let suppress : bool[,] = Array2D.zeroCreate h w - let result = List>() + let result = List> () let flood (start : Point) : List> = - let sameLevelToCheck = Stack() - let betterLevelToCheck = Stack() - betterLevelToCheck.Push(start) + let sameLevelToCheck = Stack () + let betterLevelToCheck = Stack () + betterLevelToCheck.Push start - let result' = List>() + let result' = List> () while betterLevelToCheck.Count > 0 do - let p = betterLevelToCheck.Pop() + let p = betterLevelToCheck.Pop () if not suppress.[p.Y, p.X] then suppress.[p.Y, p.X] <- true - sameLevelToCheck.Push(p) - let current = List() + sameLevelToCheck.Push p + let current = List () let mutable betterExists = false while sameLevelToCheck.Count > 0 do - let p' = sameLevelToCheck.Pop() + let p' = sameLevelToCheck.Pop () let currentLevel = imgData.[p'.Y, p'.X, 0] - current.Add(p') |> ignore + current.Add p' |> ignore for i, j in se do let ni = i + p'.Y let nj = j + p'.X @@ -68,23 +68,23 @@ let inline findExtremum (img : Image) (extremumType : ExtremumTyp if notSuppressed && level = currentLevel then suppress.[ni, nj] <- true - sameLevelToCheck.Push(Point(nj, ni)) + sameLevelToCheck.Push (Point (nj, ni)) elif (if extremumType = ExtremumType.Maxima then level > currentLevel else level < currentLevel) then betterExists <- true if notSuppressed then - betterLevelToCheck.Push(Point(nj, ni)) + betterLevelToCheck.Push (Point (nj, ni)) if not betterExists then - result'.Add(current) + result'.Add current result' for i = 0 to h - 1 do for j = 0 to w - 1 do - let maxima = flood (Point(j, i)) + let maxima = flood (Point (j, i)) if maxima.Count > 0 then - result.AddRange(maxima) + result.AddRange maxima - result.Select(fun l -> Points(l)) + result.Select (fun l -> Points l) let inline findMaxima (img : Image) : IEnumerable when 'TDepth : unmanaged = findExtremum img ExtremumType.Maxima @@ -94,7 +94,7 @@ let inline findMinima (img : Image) : IEnumerable when 'T 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 @@ -103,8 +103,8 @@ type PriorityQueue () = invalidOp "Queue is empty" else let l = q.[highest] - let next = l.First() - l.Remove(next) |> ignore + let next = l.First () + l.Remove next |> ignore let value = byte highest if l.Count = 0 then @@ -122,8 +122,8 @@ type PriorityQueue () = invalidOp "Queue is empty" else let l = q.[lowest + 1] - let next = l.First() - l.Remove(next) |> ignore + let next = l.First () + l.Remove next |> ignore let value = byte (lowest + 1) if l.Count = 0 then @@ -150,11 +150,11 @@ type PriorityQueue () = if vi <= lowest then lowest <- vi - 1 - q.[vi].Add(p) |> ignore + q.[vi].Add p |> ignore member this.Remove (value : byte) (p : Point) = let vi = int value - if q.[vi].Remove(p) && q.[vi].Count = 0 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 @@ -173,7 +173,7 @@ type PriorityQueue () = member this.Clear () = while highest > lowest do - q.[highest].Clear() + q.[highest].Clear () highest <- highest - 1 highest <- -1 lowest <- size @@ -199,33 +199,33 @@ let private areaOperation (img : Image) (area : int) (op : AreaOpera let imgData = img.Data let se = [| -1, 0; 0, -1; 1, 0; 0, 1 |] - let areas = List((if op = AreaOperation.Opening then findMaxima img else findMinima img) |> Seq.map Area) + let areas = List ((if op = AreaOperation.Opening then findMaxima img else findMinima img) |> Seq.map Area) 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 queue = PriorityQueue () 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 + let p' = Point (nj, ni) + 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 - queue.Clear() + queue.Clear () addEdgeToQueue m.Elements let mutable intensity = if op = AreaOperation.Opening then queue.Max else queue.Min - let nextElements = Points() + let nextElements = Points () let mutable stop = false while not stop do @@ -238,10 +238,10 @@ let private areaOperation (img : Image) (area : int) (op : AreaOpera m.Intensity <- Some intensity stop <- true else - nextElements.Add(p) |> ignore + nextElements.Add p |> ignore elif (if op = AreaOperation.Opening then intensity' < intensity else intensity' > intensity) then - m.Elements.UnionWith(nextElements) + m.Elements.UnionWith nextElements for e in nextElements do pixels.[e.Y, e.X] <- m @@ -251,8 +251,8 @@ let private areaOperation (img : Image) (area : int) (op : AreaOpera stop <- true else intensity <- intensity' - nextElements.Clear() - nextElements.Add(p) |> ignore + nextElements.Clear () + nextElements.Add p |> ignore else match pixels.[p.Y, p.X] with @@ -264,11 +264,11 @@ let private areaOperation (img : Image) (area : int) (op : AreaOpera pixels.[e.Y, e.X] <- m queue.Remove imgData.[e.Y, e.X, 0] e addEdgeToQueue m'.Elements - m.Elements.UnionWith(m'.Elements) + m.Elements.UnionWith m'.Elements let intensityMax = if op = AreaOperation.Opening then queue.Max else queue.Min if intensityMax <> intensity then intensity <- intensityMax - nextElements.Clear() + nextElements.Clear () merged <- true if not merged then @@ -280,19 +280,19 @@ let private areaOperation (img : Image) (area : int) (op : AreaOpera for i, j in se do let ni = i + p.Y let nj = j + p.X - let p' = Point(nj, ni) + let p' = Point (nj, ni) 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 m.State <- AreaState.Validated m.Intensity <- Some intensity' - m.Elements.UnionWith(nextElements) + m.Elements.UnionWith nextElements stop <- true for m in areas do @@ -331,8 +331,8 @@ let areaOpen2 (img : Image) (area : int) = let flooded : bool[,] = Array2D.zeroCreate h w - let pointsChecked = HashSet() - let pointsToCheck = Stack() + let pointsChecked = HashSet () + let pointsToCheck = Stack () for level = 255 downto 0 do let mutable n = histogram.[level] @@ -341,22 +341,22 @@ let areaOpen2 (img : Image) (area : int) = for j = 0 to w - 1 do if not flooded.[i, j] && imgData.[i, j, 0] = byte level then let mutable maxNeighborValue = 0uy - pointsChecked.Clear() - pointsToCheck.Clear() - pointsToCheck.Push(Point(j, i)) + pointsChecked.Clear () + pointsToCheck.Clear () + pointsToCheck.Push (Point (j, i)) while pointsToCheck.Count > 0 do - let next = pointsToCheck.Pop() - pointsChecked.Add(next) |> ignore + let next = pointsToCheck.Pop () + pointsChecked.Add next |> ignore flooded.[next.Y, next.X] <- true for nx, ny in se do - let p = Point(next.X + nx, next.Y + ny) + let p = Point (next.X + nx, next.Y + ny) 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 - pointsToCheck.Push(p) + if not (pointsChecked.Contains p) then + pointsToCheck.Push p elif v > maxNeighborValue then maxNeighborValue <- v @@ -366,7 +366,7 @@ let areaOpen2 (img : Image) (area : int) = [] type Island (cmp : IComparer) = - member val Shore = Heap.Heap(cmp) with get + 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 @@ -379,29 +379,29 @@ let private areaOperationF (img : Image) (areas : (int * 'a) list let comparer = if op = AreaOperation.Opening then - { new IComparer with member this.Compare(v1, v2) = v1.CompareTo(v2) } + { new IComparer with member this.Compare (v1, v2) = v1.CompareTo v2 } else - { new IComparer with member this.Compare(v1, v2) = v2.CompareTo(v1) } + { new IComparer with member this.Compare (v1, v2) = v2.CompareTo v1 } let ownership : Island[,] = Array2D.create h w null // Initialize islands with their shore. - let islands = List() + let islands = List () let extremum = img |> if op = AreaOperation.Opening then findMaxima else findMinima for e in extremum do let island = - let p = e.First() - Island(comparer, Level = earth.[p.Y, p.X, 0], Surface = e.Count) - islands.Add(island) - let shorePoints = Points() + let p = e.First () + Island (comparer, Level = earth.[p.Y, p.X, 0], Surface = e.Count) + islands.Add island + let shorePoints = Points () for p in e do ownership.[p.Y, p.X] <- island for i, j in se do 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 - shorePoints.Add(neighbor) |> ignore + 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 + shorePoints.Add neighbor |> ignore island.Shore.Add earth.[ni, nj, 0] neighbor for area, obj in areas do @@ -422,23 +422,22 @@ let private areaOperationF (img : Image) (areas : (int * 'a) list 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 + 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 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 island.Level <- other.Level - // island.Level <- if comparer.Compare(island.Level, other.Level) > 0 then other.Level else island.Level for l, p in other.Shore do let mutable currentY = p.Y + 1 while currentY < h && ownership.[currentY, p.X] = other do ownership.[currentY, p.X] <- island currentY <- currentY + 1 island.Shore.Add l p - other.Shore.Clear() + 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 () @@ -449,7 +448,7 @@ let private areaOperationF (img : Image) (areas : (int * 'a) list island.Surface <- Int32.MaxValue stop <- true else - let neighbor = Point(nj, ni) + let neighbor = Point (nj, ni) if not <| ownedOrAdjacent neighbor then island.Shore.Add earth.[ni, nj, 0] neighbor if not stop then @@ -566,10 +565,10 @@ let removeArea (mat : Matrix) (areaSize : int) = ( 0, -1) // p8 (-1, -1) |] // p9 - use mat' = new Matrix(mat.Size) + use mat' = new Matrix (mat.Size) let w = mat'.Width let h = mat'.Height - mat.CopyTo(mat') + mat.CopyTo mat' let data = mat.Data let data' = mat'.Data @@ -577,19 +576,19 @@ 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 - let neighborhood = List() - let neighborsToCheck = Stack() - neighborsToCheck.Push(Point(j, i)) + let neighborhood = List () + let neighborsToCheck = Stack () + neighborsToCheck.Push (Point (j, i)) data'.[i, j] <- 0uy while neighborsToCheck.Count > 0 do - let n = neighborsToCheck.Pop() - neighborhood.Add(n) + let n = neighborsToCheck.Pop () + neighborhood.Add n 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 - neighborsToCheck.Push(Point(pj, pi)) + neighborsToCheck.Push (Point (pj, pi)) data'.[pi, pj] <- 0uy if neighborhood.Count <= areaSize then for n in neighborhood do @@ -599,19 +598,19 @@ let connectedComponents (img : Image) (startPoints : List) : let w = img.Width let h = img.Height - let pointChecked = Points() - let pointToCheck = Stack(startPoints); + let pointChecked = Points () + let pointToCheck = Stack startPoints; let data = img.Data while pointToCheck.Count > 0 do - let next = pointToCheck.Pop() - pointChecked.Add(next) |> ignore + let next = pointToCheck.Pop () + pointChecked.Add next |> ignore for ny = -1 to 1 do for nx = -1 to 1 do if ny <> 0 && nx <> 0 then - let p = Point(next.X + nx, next.Y + ny) + 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 - pointToCheck.Push(p) + pointToCheck.Push p pointChecked diff --git a/Parasitemia/ParasitemiaCore/KMeans.fs b/Parasitemia/ParasitemiaCore/KMeans.fs index dfd2593..c0276d9 100644 --- a/Parasitemia/ParasitemiaCore/KMeans.fs +++ b/Parasitemia/ParasitemiaCore/KMeans.fs @@ -20,10 +20,10 @@ let kmeans (img : Image) : Result = let h = img.Height let min = ref [| 0.0 |] - let minLocation = ref <| [| Point() |] + let minLocation = ref <| [| Point () |] let max = ref [| 0.0 |] - let maxLocation = ref <| [| Point() |] - img.MinMax(min, max, minLocation, maxLocation) + let maxLocation = ref <| [| Point () |] + img.MinMax (min, max, minLocation, maxLocation) let minf = float32 (!min).[0] let maxf = float32 (!max).[0] @@ -32,7 +32,7 @@ let kmeans (img : Image) : Result = let mutable mean_fg = minf + (maxf - minf) / 4.f use mutable d_bg : Image = null let mutable d_fg : Image = null - let fg = new Image(img.Size) + let fg = new Image (img.Size) let imgData = img.Data let fgData = fg.Data @@ -41,14 +41,14 @@ let kmeans (img : Image) : Result = match d_bg with | null -> () | _ -> - d_bg.Dispose() - d_fg.Dispose() + d_bg.Dispose () + d_fg.Dispose () // EmGu doesn't import the in-place version of 'AbsDiff' so we have to create two images for each iteration. - d_bg <- img.AbsDiff(Gray(float mean_bg)) - d_fg <- img.AbsDiff(Gray(float mean_fg)) + d_bg <- img.AbsDiff (Gray (float mean_bg)) + d_fg <- img.AbsDiff (Gray (float mean_fg)) - CvInvoke.Compare(d_fg, d_bg, fg, CvEnum.CmpType.LessThan) + CvInvoke.Compare (d_fg, d_bg, fg, CvEnum.CmpType.LessThan) let mutable bg_total = 0.f let mutable bg_nb = 0 diff --git a/Parasitemia/ParasitemiaCore/KMedians.fs b/Parasitemia/ParasitemiaCore/KMedians.fs index bae7a24..b2ac738 100644 --- a/Parasitemia/ParasitemiaCore/KMedians.fs +++ b/Parasitemia/ParasitemiaCore/KMedians.fs @@ -20,35 +20,35 @@ let kmedians (img : Image) : Result = let h = img.Height let min = ref [| 0.0 |] - let minLocation = ref <| [| Point() |] + let minLocation = ref <| [| Point () |] let max = ref [| 0.0 |] - let maxLocation = ref <| [| Point() |] - img.MinMax(min, max, minLocation, maxLocation) + let maxLocation = ref <| [| Point () |] + img.MinMax (min, max, minLocation, maxLocation) let mutable median_bg = (!max).[0] - ((!max).[0] - (!min).[0]) / 4.0 let mutable median_fg = (!min).[0] + ((!max).[0] - (!min).[0]) / 4.0 - use mutable d_bg = new Image(img.Size) - let mutable d_fg = new Image(img.Size) - let mutable fg = new Image(img.Size) + use mutable d_bg = new Image (img.Size) + let mutable d_fg = new Image (img.Size) + let mutable fg = new Image (img.Size) for i = 1 to nbIteration do - d_bg <- img.AbsDiff(Gray(median_bg)) - d_fg <- img.AbsDiff(Gray(median_fg)) + d_bg <- img.AbsDiff (Gray median_bg) + d_fg <- img.AbsDiff (Gray median_fg) - CvInvoke.Compare(d_fg, d_bg, fg, CvEnum.CmpType.LessThan) + CvInvoke.Compare (d_fg, d_bg, fg, CvEnum.CmpType.LessThan) - let bg_values = List() - let fg_values = List() + let bg_values = List () + let fg_values = List () 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]) + fg_values.Add (float img.Data.[i, j, 0]) else - bg_values.Add(float img.Data.[i, j, 0]) + 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) + median_bg <- MathNet.Numerics.Statistics.Statistics.Median bg_values + median_fg <- MathNet.Numerics.Statistics.Statistics.Median fg_values { fg = fg; median_bg = median_bg; median_fg = median_fg; d_fg = d_fg } diff --git a/Parasitemia/ParasitemiaCore/KdTree.fs b/Parasitemia/ParasitemiaCore/KdTree.fs index f98cfa2..2525e27 100644 --- a/Parasitemia/ParasitemiaCore/KdTree.fs +++ b/Parasitemia/ParasitemiaCore/KdTree.fs @@ -8,17 +8,17 @@ type I2DCoords = // Compare 'e1' and 'e2' by X. 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()) + match e1.X.CompareTo e2.X with + | 0 -> match e1.Y.CompareTo e2.Y with + | 0 -> e1.GetHashCode().CompareTo (e2.GetHashCode ()) | v -> v | v -> v // Compare 'e1' and 'e2' by Y. 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()) + 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 diff --git a/Parasitemia/ParasitemiaCore/MatchingEllipses.fs b/Parasitemia/ParasitemiaCore/MatchingEllipses.fs index 13fd556..03a3869 100644 --- a/Parasitemia/ParasitemiaCore/MatchingEllipses.fs +++ b/Parasitemia/ParasitemiaCore/MatchingEllipses.fs @@ -34,10 +34,10 @@ type private EllipseScoreFlaggedKd (matchingScore : float32, e : Ellipse) = member this.Y = this.Ellipse.Cy type MatchingEllipses (radius : float32) = - let ellipses = List() + let ellipses = List () member this.Add (e : Ellipse) = - ellipses.Add(EllipseScoreFlaggedKd(0.f, e)) + ellipses.Add (EllipseScoreFlaggedKd (0.f, e)) member this.Ellipses : Ellipse list = List.ofSeq ellipses |> List.map (fun e -> e.Ellipse) @@ -69,8 +69,8 @@ type MatchingEllipses (radius : float32) = // Because of approximation error, see https://github.com/chraibi/EEOver/issues/4 when overlapArea - areaE < 1.f && overlapArea - areaOther < 1.f -> let matchingScore = (2.f * overlapArea / (areaE + areaOther)) ** matchingScorePower - other.AddMatchingScore(matchingScore) - e.AddMatchingScore(matchingScore) + other.AddMatchingScore matchingScore + e.AddMatchingScore matchingScore | _ -> () // 3) Remove ellipses whose center is near the center of another ellipse with a better score. @@ -89,7 +89,7 @@ type MatchingEllipses (radius : float32) = for other in tree.Search window do 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. @@ -101,6 +101,6 @@ type MatchingEllipses (radius : float32) = ellipses |> List.ofSeq |> List.filter (fun e -> not e.Removed) - |> List.sortWith (fun e1 e2 -> e2.MatchingScore.CompareTo(e1.MatchingScore)) + |> List.sortWith (fun e1 e2 -> e2.MatchingScore.CompareTo e1.MatchingScore) |> List.map (fun e -> e.Ellipse) diff --git a/Parasitemia/ParasitemiaCore/ParasitemiaCore.fsproj b/Parasitemia/ParasitemiaCore/ParasitemiaCore.fsproj index 5a14573..c841f71 100644 --- a/Parasitemia/ParasitemiaCore/ParasitemiaCore.fsproj +++ b/Parasitemia/ParasitemiaCore/ParasitemiaCore.fsproj @@ -91,7 +91,7 @@ True - ..\packages\FSharp.Core.4.1.17\lib\net45\FSharp.Core.dll + ..\packages\FSharp.Core.4.2.1\lib\net45\FSharp.Core.dll ..\packages\MathNet.Numerics.3.19.0\lib\net40\MathNet.Numerics.dll diff --git a/Parasitemia/ParasitemiaCore/ParasitesMarker.fs b/Parasitemia/ParasitemiaCore/ParasitesMarker.fs index 263e35e..b89caa6 100644 --- a/Parasitemia/ParasitemiaCore/ParasitesMarker.fs +++ b/Parasitemia/ParasitemiaCore/ParasitesMarker.fs @@ -21,7 +21,7 @@ type Result = let find (img : Image) (config : Config.Config) : Result * Image * Image = - let imgWithoutNucleus = img.Copy() + let imgWithoutNucleus = img.Copy () areaCloseF imgWithoutNucleus (roundInt config.RBCRadius.NucleusArea) let darkStain = @@ -29,30 +29,30 @@ let find (img : Image) (config : Config.Config) : Result * Image< let _, mean_fg, mean_bg = let hist = histogramImg imgWithoutNucleus 300 otsu hist - imgWithoutNucleus.Cmp(float mean_fg - config.Parameters.darkStainLevel * float (mean_bg - mean_fg), CvEnum.CmpType.LessThan) + imgWithoutNucleus.Cmp (float mean_fg - config.Parameters.darkStainLevel * float (mean_bg - mean_fg), CvEnum.CmpType.LessThan) let marker (img : Image) (closed : Image) (level : float) : Image = - let diff = img.Copy() - diff._Mul(level) - CvInvoke.Subtract(closed, diff, diff) - diff._ThresholdBinary(Gray(0.0), Gray(255.)) - diff.Convert() + let diff = img.Copy () + diff._Mul level + CvInvoke.Subtract (closed, diff, diff) + diff._ThresholdBinary (Gray 0.0, Gray 255.) + diff.Convert () // Nucleus. let nucleusMarker = marker img imgWithoutNucleus (1. / config.Parameters.infectionSensitivity) // Cytoplasm. - let imgWithoutParasite = img.CopyBlank() + let imgWithoutParasite = img.CopyBlank () let kernelSize = let size = roundInt config.RBCRadius.CytoplasmSize if size % 2 = 0 then size + 1 else size use kernel = if kernelSize <= 3 then - CvInvoke.GetStructuringElement(CvEnum.ElementShape.Rectangle, Size(3, 3), Point(-1, -1)) + CvInvoke.GetStructuringElement (CvEnum.ElementShape.Rectangle, Size (3, 3), Point (-1, -1)) else - CvInvoke.GetStructuringElement(CvEnum.ElementShape.Ellipse, Size(kernelSize, kernelSize), Point(-1, -1)) + CvInvoke.GetStructuringElement (CvEnum.ElementShape.Ellipse, Size (kernelSize, kernelSize), Point (-1, -1)) - CvInvoke.MorphologyEx(img, imgWithoutParasite, CvEnum.MorphOp.Close, kernel, Point(-1, -1), 1, CvEnum.BorderType.Replicate, MCvScalar()) + CvInvoke.MorphologyEx (img, imgWithoutParasite, CvEnum.MorphOp.Close, kernel, Point(-1, -1), 1, CvEnum.BorderType.Replicate, MCvScalar()) let parasiteMarker = marker img imgWithoutParasite (1. / config.Parameters.cytoplasmSensitivity) { diff --git a/Parasitemia/ParasitemiaCore/Types.fs b/Parasitemia/ParasitemiaCore/Types.fs index c1a635d..8f526f9 100644 --- a/Parasitemia/ParasitemiaCore/Types.fs +++ b/Parasitemia/ParasitemiaCore/Types.fs @@ -36,14 +36,14 @@ type Ellipse (cx : float32, cy : float32, a : float32, b : float32, alpha : floa this.CutAnHorizontalLine 0.f || this.CutAnHorizontalLine height member this.Scale (factor : float32) : Ellipse = - Ellipse(this.Cx, this.Cy, this.A * factor, this.B * factor, alpha) + Ellipse (this.Cx, this.Cy, this.A * factor, this.B * factor, alpha) // Approximation of Ramanujan. member this.Perimeter = 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: %f, cy: %f, a: %f, b: %f, alpha: %f)" this.Cx this.Cy this.A this.B this.Alpha + sprintf "{Ellipse: 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 @@ -71,16 +71,16 @@ type MaybeBuilder () = member this.TryFinally (body, compensation) = try - this.ReturnFrom(body()) + this.ReturnFrom (body ()) finally - compensation() + compensation () member this.Using (disposable : 'a when 'a :> IDisposable, body) = let body' = fun () -> body disposable - this.TryFinally(body', fun () -> + this.TryFinally (body', fun () -> match disposable with | null -> () - | disp -> disp.Dispose()) + | disp -> disp.Dispose ()) member this.Zero () = None @@ -88,7 +88,7 @@ type MaybeBuilder () = member this.Return x = Some x -let maybe = MaybeBuilder() +let maybe = MaybeBuilder () type Result<'a> = | Success of 'a @@ -102,4 +102,4 @@ type ResultBuilder () = member this.ReturnFrom (x) = x -let result = ResultBuilder() \ No newline at end of file +let result = ResultBuilder () \ No newline at end of file diff --git a/Parasitemia/ParasitemiaCore/UnitsOfMeasure.fs b/Parasitemia/ParasitemiaCore/UnitsOfMeasure.fs index 8202b8e..4af1e55 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 77e5e6f..6a13337 100644 --- a/Parasitemia/ParasitemiaCore/Utils.fs +++ b/Parasitemia/ParasitemiaCore/Utils.fs @@ -13,12 +13,12 @@ let inline dprintfn fmt = 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) + Line (a, b) 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) + PointF (x, y) let inline linePassThroughSegment (l : Line) (p1 : PointF) (p2 : PointF) : bool = let p = pointFromTwoLines l (lineFromTwoPoints p1 p2) diff --git a/Parasitemia/ParasitemiaCore/packages.config b/Parasitemia/ParasitemiaCore/packages.config index 91a2fc7..d0073d5 100644 --- a/Parasitemia/ParasitemiaCore/packages.config +++ b/Parasitemia/ParasitemiaCore/packages.config @@ -2,7 +2,7 @@ - + diff --git a/Parasitemia/ParasitemiaUI/About.fs b/Parasitemia/ParasitemiaUI/About.fs index 452573a..8c643b7 100644 --- a/Parasitemia/ParasitemiaUI/About.fs +++ b/Parasitemia/ParasitemiaUI/About.fs @@ -2,14 +2,11 @@ open System open System.Windows -open System.Windows.Media -open System.Windows.Markup -open System.Windows.Shapes open System.Windows.Controls open System.Diagnostics let showWindow (parent : Window) = - let win = Views.AboutWindow() + let win = Views.AboutWindow () win.Owner <- parent win.Left <- (if parent.WindowState = WindowState.Maximized then 0. else parent.Left) + parent.ActualWidth / 2. - win.Width / 2. @@ -17,21 +14,24 @@ let showWindow (parent : Window) = let version = System.Reflection.Assembly.GetEntryAssembly().GetName().Version let txtVersion = sprintf " %d.%d.%d" version.Major version.Minor version.Revision - win.txtAbout.Inlines.FirstInline.ElementEnd.InsertTextInRun(txtVersion) + win.txtAbout.Inlines.FirstInline.ElementEnd.InsertTextInRun txtVersion - let navigateTo = Navigation.RequestNavigateEventHandler(fun obj args -> - Process.Start(ProcessStartInfo(args.Uri.AbsoluteUri)) |> ignore - args.Handled <- true) + let navigateTo = + Navigation.RequestNavigateEventHandler ( + fun obj args -> + Process.Start (ProcessStartInfo args.Uri.AbsoluteUri) |> ignore + args.Handled <- true + ) - win.linkHESSO.RequestNavigate.AddHandler(navigateTo); - win.linkCHUV.RequestNavigate.AddHandler(navigateTo); - win.linkGBurri.RequestNavigate.AddHandler(navigateTo); + win.linkHESSO.RequestNavigate.AddHandler navigateTo; + win.linkCHUV.RequestNavigate.AddHandler navigateTo; + win.linkGBurri.RequestNavigate.AddHandler navigateTo; #if DEBUG - win.txtAbout.Inlines.FirstInline.ElementEnd.InsertTextInRun(" - DEBUG") + win.txtAbout.Inlines.FirstInline.ElementEnd.InsertTextInRun " - DEBUG" #endif - win.butClose.Click.AddHandler(fun obj args -> win.Close()) + win.butClose.Click.AddHandler (fun obj args -> win.Close ()) - win.ShowDialog() |> ignore + win.ShowDialog () |> ignore diff --git a/Parasitemia/ParasitemiaUI/Analysis.fs b/Parasitemia/ParasitemiaUI/Analysis.fs index e276be6..4a75a91 100644 --- a/Parasitemia/ParasitemiaUI/Analysis.fs +++ b/Parasitemia/ParasitemiaUI/Analysis.fs @@ -19,7 +19,7 @@ open ParasitemiaCore.Config open Types let showWindow (parent : Window) (state : State.State) : bool = - let win = Views.AnalysisWindow() + let win = Views.AnalysisWindow () 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. @@ -28,62 +28,69 @@ let showWindow (parent : Window) (state : State.State) : bool = { new Logger.IListener with member this.NewEntry severity _header mess = - win.Dispatcher.Invoke(fun () -> - win.textLog.Inlines.Add(Documents.Run(mess)) - win.textLog.Inlines.Add(Documents.LineBreak()) - win.scrollLog.ScrollToBottom() + win.Dispatcher.Invoke ( + fun () -> + win.textLog.Inlines.Add (Documents.Run mess) + win.textLog.Inlines.Add (Documents.LineBreak ()) + win.scrollLog.ScrollToBottom () ) } - Logger.Log.AddListener(logListener) + Logger.Log.AddListener (logListener) let minPPI = 1. let maxPPI = 10e6 let parseAndValidatePPI (input : string) : float option = - match Double.TryParse(input) with + match Double.TryParse input with | true, value when value >= minPPI && value <= maxPPI -> Some value | _ -> None - let monitor = Object() + let monitor = Object () let mutable atLeastOneAnalysisPerformed = false let mutable analysisPerformed = false let mutable analysisCancelled = false let updateSourceImages () = - win.stackSourceImagesSelection.Children.Clear() + win.stackSourceImagesSelection.Children.Clear () let width = int win.stackSourceImagesSelection.ActualWidth for srcImg in state.SourceImages do - let imageSourceSelection = Views.ImageSourceSelection(Tag = srcImg, Margin = Thickness(3.)) + let imageSourceSelection = Views.ImageSourceSelection (Tag = srcImg, Margin = Thickness 3.) imageSourceSelection.Tag <- srcImg - imageSourceSelection.txtImageNumber.Text <- srcImg.num.ToString() + imageSourceSelection.txtImageNumber.Text <- string srcImg.num let height = srcImg.img.Height * width / srcImg.img.Width - imageSourceSelection.imagePreview.Source <- BitmapSourceConvert.ToBitmapSource(srcImg.img.Resize(width, height, Emgu.CV.CvEnum.Inter.Cubic)) - imageSourceSelection.chkSelection.IsChecked <- Nullable(srcImg.dateLastAnalysis.Ticks = 0L) - imageSourceSelection.lblDateLastAnalysis.Content <- if srcImg.dateLastAnalysis.Ticks = 0L then "" else srcImg.dateLastAnalysis.ToString() + imageSourceSelection.imagePreview.Source <- BitmapSourceConvert.ToBitmapSource (srcImg.img.Resize (width, height, Emgu.CV.CvEnum.Inter.Cubic)) + imageSourceSelection.chkSelection.IsChecked <- Nullable (srcImg.dateLastAnalysis.Ticks = 0L) + imageSourceSelection.lblDateLastAnalysis.Content <- if srcImg.dateLastAnalysis.Ticks = 0L then "" else string srcImg.dateLastAnalysis - imageSourceSelection.txtResolution.Text <- if srcImg.dateLastAnalysis.Ticks = 0L then "" else srcImg.config.Parameters.resolution.ToString() + imageSourceSelection.txtResolution.Text <- if srcImg.dateLastAnalysis.Ticks = 0L then "" else string srcImg.config.Parameters.resolution for ppi in Utils.predefinedPPI do - let menu = MenuItem() - menu.Header <- ppi.ToString() - menu.Click.AddHandler(fun obj args -> imageSourceSelection.txtResolution.Text <- ppi.ppi.ToString()) - imageSourceSelection.predefinedValuesMenu.Items.Add(menu) |> ignore - - imageSourceSelection.butPPICalculator.Click.AddHandler(fun obj args -> - match PPICalculator.showWindow win with - | Some resolution -> imageSourceSelection.txtResolution.Text <- resolution.ToString() - | None -> ()) - - imageSourceSelection.txtResolution.PreviewTextInput.AddHandler(fun obj args -> - let text = imageSourceSelection.txtResolution.Text + args.Text - args.Handled <- match parseAndValidatePPI text with Some _ -> false | None -> true) - - imageSourceSelection.imagePreview.MouseLeftButtonDown.AddHandler(fun obj args -> - let checkbox = imageSourceSelection.chkSelection - checkbox.IsChecked <- Nullable(not (checkbox.IsChecked.HasValue && checkbox.IsChecked.Value))) - - win.stackSourceImagesSelection.Children.Add(imageSourceSelection) |> ignore + let menu = MenuItem () + menu.Header <- string ppi + menu.Click.AddHandler (fun obj args -> imageSourceSelection.txtResolution.Text <- string ppi.ppi) + imageSourceSelection.predefinedValuesMenu.Items.Add menu |> ignore + + imageSourceSelection.butPPICalculator.Click.AddHandler ( + fun obj args -> + match PPICalculator.showWindow win with + | Some resolution -> imageSourceSelection.txtResolution.Text <- string resolution + | None -> () + ) + + imageSourceSelection.txtResolution.PreviewTextInput.AddHandler ( + fun obj args -> + let text = imageSourceSelection.txtResolution.Text + args.Text + args.Handled <- match parseAndValidatePPI text with Some _ -> false | None -> true + ) + + imageSourceSelection.imagePreview.MouseLeftButtonDown.AddHandler ( + fun obj args -> + let checkbox = imageSourceSelection.chkSelection + checkbox.IsChecked <- Nullable (not (checkbox.IsChecked.HasValue && checkbox.IsChecked.Value)) + ) + + win.stackSourceImagesSelection.Children.Add (imageSourceSelection) |> ignore // Get the new parameters for each image. If an error occurs then 'None' is returned and a message box is displayed. // The boolean is 'true' if the image is selected (checked). @@ -98,71 +105,73 @@ let showWindow (parent : Window) (state : State.State) : bool = | 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 + 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 + 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 - MessageBox.Show("No image selected", "Cannot start analysis", MessageBoxButton.OK, MessageBoxImage.Information) |> ignore - else - win.stackSourceImagesSelection.IsEnabled <- false - analysisPerformed <- false - win.butStart.IsEnabled <- false - win.butClose.Content <- "Abort" - - async { - let maybeResults = - ParasitemiaCore.Analysis.doMultipleAnalysis - imagesToProcess - (Some (fun progress -> win.Dispatcher.Invoke(fun () -> win.progress.Value <- float progress); not analysisCancelled)) - - lock monitor ( - fun() -> - match maybeResults with - | Some results -> - for id, cells in results do - state.SetResult (int id) cells - Logger.Log.Info "All analyses terminated successfully" - atLeastOneAnalysisPerformed <- true - analysisPerformed <- true - | None -> - Logger.Log.Info "Analysis aborted" - - 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 () - ) - ) - } |> Async.Start - | _ -> () + 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 string srcImg.num, 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 + analysisPerformed <- false + win.butStart.IsEnabled <- false + win.butClose.Content <- "Abort" + + async { + let maybeResults = + ParasitemiaCore.Analysis.doMultipleAnalysis + imagesToProcess + (Some (fun progress -> win.Dispatcher.Invoke (fun () -> win.progress.Value <- float progress); not analysisCancelled)) + + lock monitor ( + fun () -> + match maybeResults with + | Some results -> + for id, cells in results do + state.SetResult (int id) cells + Logger.Log.Info "All analyses terminated successfully" + atLeastOneAnalysisPerformed <- true + analysisPerformed <- true + | None -> + Logger.Log.Info "Analysis aborted" + + 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 () + ) + ) + } |> Async.Start + | _ -> () ) - win.Loaded.AddHandler(fun obj args -> updateSourceImages ()) + win.Loaded.AddHandler (fun obj args -> updateSourceImages ()) - win.ShowDialog() |> ignore + win.ShowDialog () |> ignore - Logger.Log.RmListener(logListener) + Logger.Log.RmListener (logListener) lock monitor ( fun () -> diff --git a/Parasitemia/ParasitemiaUI/AssemblyInfo.fs b/Parasitemia/ParasitemiaUI/AssemblyInfo.fs index 3814371..d0754f8 100644 --- a/Parasitemia/ParasitemiaUI/AssemblyInfo.fs +++ b/Parasitemia/ParasitemiaUI/AssemblyInfo.fs @@ -7,22 +7,22 @@ open System.Runtime.InteropServices // General Information about an assembly is controlled through the following // set of attributes. Change these attribute values to modify the information // associated with an assembly. -[] -[] -[] -[] -[] -[] -[] -[] +[] +[] +[] +[] +[] +[] +[] +[] // Setting ComVisible to false makes the types in this assembly not visible // to COM components. If you need to access a type in this assembly from // COM, set the ComVisible attribute to true on that type. -[] +[] // The following GUID is for the ID of the typelib if this project is exposed to COM -[] +[] // Version information for an assembly consists of the following four values: // @@ -33,9 +33,9 @@ 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 c8d25c8..2cdc184 100644 --- a/Parasitemia/ParasitemiaUI/DPICalculator.fs +++ b/Parasitemia/ParasitemiaUI/DPICalculator.fs @@ -14,13 +14,13 @@ open ParasitemiaCore.UnitsOfMeasure open Types let showWindow (parent : Window) : int option = - let win = Views.PPICalculatorWindow() + let win = Views.PPICalculatorWindow () 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.Items.Add (size) |> ignore win.cmbSensorSize.SelectedIndex <- 0 let resolution (w_p : float) (w_mm : float) (zoom : float) : float = @@ -30,7 +30,7 @@ let showWindow (parent : Window) : int option = let { w = w; h = h } = win.cmbSensorSize.SelectedValue :?> SensorSize let ratio = h / w - let parseDouble txt errorMess = match Double.TryParse(txt) with true, value -> Success value | _ -> Fail errorMess + let parseDouble txt errorMess = match Double.TryParse (txt) with true, value -> Success value | _ -> Fail errorMess match (result { @@ -39,17 +39,17 @@ let showWindow (parent : Window) : int option = 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() + | Success res -> win.txtImageResolution.Text <- int (res / 1000.) * 1000 |> string | Fail mess -> win.txtImageResolution.Text <- mess - 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.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 ()) + 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.ShowDialog() + let result = win.ShowDialog () if result.HasValue && result.Value then match Int32.TryParse win.txtImageResolution.Text with | true, res -> Some res diff --git a/Parasitemia/ParasitemiaUI/Export.fs b/Parasitemia/ParasitemiaUI/Export.fs index 29fc0a5..52638d3 100644 --- a/Parasitemia/ParasitemiaUI/Export.fs +++ b/Parasitemia/ParasitemiaUI/Export.fs @@ -7,7 +7,7 @@ open State /// If the results cannot be exported let exportResults (state : State) (filePath : string) = - use writer = new StreamWriter(new FileStream(filePath, FileMode.Create, FileAccess.Write)) + use writer = new StreamWriter (new FileStream (filePath, FileMode.Create, FileAccess.Write)) fprintfn writer "File: %s" state.FilePath fprintfn writer "Export date: %O" DateTime.Now diff --git a/Parasitemia/ParasitemiaUI/GUI.fs b/Parasitemia/ParasitemiaUI/GUI.fs index 35bc2ad..46171a7 100644 --- a/Parasitemia/ParasitemiaUI/GUI.fs +++ b/Parasitemia/ParasitemiaUI/GUI.fs @@ -20,10 +20,10 @@ open ParasitemiaCore.Utils open Types let run (defaultConfig : Config) (fileToOpen : string option) = - let app = new Application() - let win = Views.MainWindow() + let app = new Application () + let win = Views.MainWindow () - let state = State.State(defaultConfig) + let state = State.State defaultConfig let mutable currentScale = 1. let mutable displayHealthy = false let warningBelowNumberOfRBC = 1000 @@ -40,9 +40,12 @@ let run (defaultConfig : Config) (fileToOpen : string option) = let y = rbc.center.Y - rbcHeight / 2. |> roundInt let w = roundInt rbcWidth let h = roundInt rbcHeight - img.GetSubRect(System.Drawing.Rectangle(System.Drawing.Point((if x < 0 then 0 else x), (if y < 0 then 0 else y)), - System.Drawing.Size((if x + w >= img.Width then img.Width - x else w), - (if y + h >= img.Height then img.Height - y else h)))) + img.GetSubRect ( + System.Drawing.Rectangle ( + System.Drawing.Point ((if x < 0 then 0 else x), (if y < 0 then 0 else y)), + System.Drawing.Size ((if x + w >= img.Width then img.Width - x else w), (if y + h >= img.Height then img.Height - y else h)) + ) + ) let setRBCFrameStyle (srcImg : SourceImage) (rbc : RBC) (frame : Views.RBCFrame) = frame.Opacity <- if displayHealthy || rbc.setManually || rbc.infected then 1. else 0. @@ -58,19 +61,19 @@ let run (defaultConfig : Config) (fileToOpen : string option) = frame.Tag <- rbc setRBCFrameStyle srcImg rbc frame frame.border.StrokeThickness <- 1. - frame.txtRBCNumber.Text <- rbc.num.ToString() + frame.txtRBCNumber.Text <- string rbc.num frame let updateDocumentStatus () = win.txtDocumentStatus.Text <- if state.FilePath = "" then "" else state.FilePath - let statusMessageTimer = Threading.DispatcherTimer() - statusMessageTimer.Tick.AddHandler(fun obj args -> statusMessageTimer.Stop(); win.txtMessageStatus.Text <- "") - statusMessageTimer.Interval <- TimeSpan(0, 0, 2) + 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) = win.txtMessageStatus.Text <- message - statusMessageTimer.Stop() - statusMessageTimer.Start() + statusMessageTimer.Stop () + statusMessageTimer.Start () let highlightRBCFrame (frame : Views.RBCFrame) (highlight : bool) = let rbc = frame.Tag :?> RBC @@ -82,51 +85,51 @@ let run (defaultConfig : Config) (fileToOpen : string option) = if not rbc.infected && not rbc.setManually && not displayHealthy then frame.Opacity <- 0. let zoomToRBC (rbc : RBC) = - win.scrollViewCurrentImage.ScrollToHorizontalOffset(rbc.center.X * currentScale - win.scrollViewCurrentImage.ViewportWidth / 2. + win.borderCurrentImage.BorderThickness.Left) - win.scrollViewCurrentImage.ScrollToVerticalOffset(rbc.center.Y * currentScale - win.scrollViewCurrentImage.ViewportHeight / 2. + win.borderCurrentImage.BorderThickness.Top) + win.scrollViewCurrentImage.ScrollToHorizontalOffset (rbc.center.X * currentScale - win.scrollViewCurrentImage.ViewportWidth / 2. + win.borderCurrentImage.BorderThickness.Left) + win.scrollViewCurrentImage.ScrollToVerticalOffset (rbc.center.Y * currentScale - win.scrollViewCurrentImage.ViewportHeight / 2. + win.borderCurrentImage.BorderThickness.Top) let txtImageName_TextChanged = - TextChangedEventHandler(fun obj args -> state.CurrentImage |> Option.iter(fun srcImg -> state.SetName srcImg win.txtImageName.Text)) + TextChangedEventHandler (fun obj args -> state.CurrentImage |> Option.iter (fun srcImg -> state.SetName srcImg win.txtImageName.Text)) let updateCurrentImageInformation () = - win.txtImageName.TextChanged.RemoveHandler(txtImageName_TextChanged) - win.txtImageInformation1.Inlines.Clear() - win.txtImageInformation2.Inlines.Clear() + win.txtImageName.TextChanged.RemoveHandler (txtImageName_TextChanged) + win.txtImageInformation1.Inlines.Clear () + win.txtImageInformation2.Inlines.Clear () win.txtImageName.Text <- "" match state.CurrentImage with | Some srcImg -> win.gridImageInformation.Visibility <- Visibility.Visible win.txtImageName.Text <- srcImg.name - win.txtImageName.TextChanged.AddHandler(txtImageName_TextChanged) + win.txtImageName.TextChanged.AddHandler txtImageName_TextChanged // The left part. let parasitemiaStr = Utils.percentText (state.ImageParasitemia srcImg) - win.txtImageInformation1.Inlines.Add(Documents.Run("Parasitemia: ", FontWeight = FontWeights.Bold)) - win.txtImageInformation1.Inlines.Add(parasitemiaStr) - win.txtImageInformation1.Inlines.Add(Documents.LineBreak()) + win.txtImageInformation1.Inlines.Add (Documents.Run ("Parasitemia: ", FontWeight = FontWeights.Bold)) + win.txtImageInformation1.Inlines.Add parasitemiaStr + win.txtImageInformation1.Inlines.Add (Documents.LineBreak ()) - win.txtImageInformation1.Inlines.Add(Documents.Run("Last analysis: ", FontWeight = FontWeights.Bold)) - win.txtImageInformation1.Inlines.Add(Documents.Run(if srcImg.dateLastAnalysis.Ticks = 0L then "" else srcImg.dateLastAnalysis.ToLocalTime().ToString())) + win.txtImageInformation1.Inlines.Add (Documents.Run ("Last analysis: ", FontWeight = FontWeights.Bold)) + win.txtImageInformation1.Inlines.Add (Documents.Run (if srcImg.dateLastAnalysis.Ticks = 0L then "" else string (srcImg.dateLastAnalysis.ToLocalTime()))) // The right part part. - win.txtImageInformation2.Inlines.Add(Documents.Run("Added infected erythrocyte: ", FontWeight = FontWeights.Bold)) - win.txtImageInformation2.Inlines.Add(Documents.Run((state.ImageNbManuallyChangedRBCStr srcImg true) + " " + (state.ImageManuallyChangedRBCStr srcImg true))) - win.txtImageInformation2.Inlines.Add(Documents.LineBreak()) - win.txtImageInformation2.Inlines.Add(Documents.Run("Removed infected erythrocyte: ", FontWeight = FontWeights.Bold)) - win.txtImageInformation2.Inlines.Add(Documents.Run((state.ImageNbManuallyChangedRBCStr srcImg false) + " " + (state.ImageManuallyChangedRBCStr srcImg false))) + win.txtImageInformation2.Inlines.Add (Documents.Run ("Added infected erythrocyte: ", FontWeight = FontWeights.Bold)) + win.txtImageInformation2.Inlines.Add (Documents.Run ((state.ImageNbManuallyChangedRBCStr srcImg true) + " " + (state.ImageManuallyChangedRBCStr srcImg true))) + win.txtImageInformation2.Inlines.Add (Documents.LineBreak ()) + win.txtImageInformation2.Inlines.Add (Documents.Run ("Removed infected erythrocyte: ", FontWeight = FontWeights.Bold)) + win.txtImageInformation2.Inlines.Add (Documents.Run ((state.ImageNbManuallyChangedRBCStr srcImg false) + " " + (state.ImageManuallyChangedRBCStr srcImg false))) | _ -> win.gridImageInformation.Visibility <- Visibility.Hidden let updateGlobalParasitemia () = - win.txtGlobalParasitemia.Inlines.Clear() + win.txtGlobalParasitemia.Inlines.Clear () let total, infected = state.GlobalParasitemia - win.txtGlobalParasitemia.Inlines.Add(Documents.Run(Utils.percentText (total, infected), FontWeight = FontWeights.Bold)) + win.txtGlobalParasitemia.Inlines.Add (Documents.Run (Utils.percentText (total, infected), FontWeight = FontWeights.Bold)) if total > 0 && total < warningBelowNumberOfRBC then - win.txtGlobalParasitemia.Inlines.Add( - Documents.Run( + win.txtGlobalParasitemia.Inlines.Add ( + Documents.Run ( sprintf " Warning: the number of erythrocytes should be above %d" warningBelowNumberOfRBC, FontWeight = FontWeights.Bold, Foreground = Brushes.Red @@ -150,7 +153,7 @@ let run (defaultConfig : Config) (fileToOpen : string option) = let marginBottom = previewHeight * (canvasHeight - (win.scrollViewCurrentImage.VerticalOffset - win.borderCurrentImage.BorderThickness.Bottom) - win.scrollViewCurrentImage.ViewportHeight) / canvasHeight - 2. preview.viewport.Margin <- - Thickness( + Thickness ( marginLeft, marginTop, marginRight, @@ -173,11 +176,11 @@ let run (defaultConfig : Config) (fileToOpen : string option) = updateGlobalParasitemia () 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( + 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 frame.menuRBCSetAsHealthy.Visibility <- Visibility.Visible @@ -186,9 +189,9 @@ let run (defaultConfig : Config) (fileToOpen : string option) = frame.menuRBCSetAsHealthy.Visibility <- Visibility.Collapsed 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) + 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) frame and updateRBCFramesPreview () = @@ -201,17 +204,17 @@ let run (defaultConfig : Config) (fileToOpen : string option) = RBCFrameFromExisting srcImg rbc (win.stackRBC.Children.[currentPreview] :?> Views.RBCFrame) else let f = RBCFrame srcImg rbc - f.MouseLeftButtonUp.AddHandler(fun obj args -> zoomToRBC (f.Tag :?> RBC)) - win.stackRBC.Children.Add(f) |> ignore + f.MouseLeftButtonUp.AddHandler (fun obj args -> zoomToRBC (f.Tag :?> RBC)) + win.stackRBC.Children.Add f |> ignore f currentPreview <- currentPreview + 1 previewInfected.Height <- win.stackRBC.ActualHeight previewInfected.Width <- win.stackRBC.ActualHeight * rbc.size.Width / rbc.size.Height - previewInfected.border.Fill <- ImageBrush(BitmapSourceConvert.ToBitmapSource(extractRBCPreview srcImg.img rbc)) + previewInfected.border.Fill <- ImageBrush (BitmapSourceConvert.ToBitmapSource (extractRBCPreview srcImg.img rbc)) - win.stackRBC.Children.RemoveRange(currentPreview, win.stackRBC.Children.Count - currentPreview) + win.stackRBC.Children.RemoveRange (currentPreview, win.stackRBC.Children.Count - currentPreview) | _ -> () updateViewportPreview () @@ -226,27 +229,27 @@ let run (defaultConfig : Config) (fileToOpen : string option) = RBCFrameFromExisting srcImg rbc (win.canvasCurrentImage.Children.[currentCanvas] :?> Views.RBCFrame) else let f = RBCFrame srcImg rbc - win.canvasCurrentImage.Children.Add(f) |> ignore + win.canvasCurrentImage.Children.Add f |> ignore f currentCanvas <- currentCanvas + 1 - Canvas.SetLeft(frame, rbc.center.X - rbc.size.Width / 2.) - Canvas.SetTop(frame, rbc.center.Y - rbc.size.Height / 2.) + Canvas.SetLeft (frame, rbc.center.X - rbc.size.Width / 2.) + Canvas.SetTop (frame, rbc.center.Y - rbc.size.Height / 2.) for i in currentCanvas .. win.canvasCurrentImage.Children.Count - 1 do win.canvasCurrentImage.Children.[i].Visibility <- Visibility.Hidden | _ -> () let askDocumentPathToSave () : string option = - let dialog = SaveFileDialog(AddExtension = true, DefaultExt = PiaZ.extension, Filter = PiaZ.filter) + let dialog = SaveFileDialog (AddExtension = true, DefaultExt = PiaZ.extension, Filter = PiaZ.filter) if state.FilePath <> "" then dialog.FileName <- FileInfo(state.FilePath).Name elif state.PatientID <> "" then dialog.FileName <- state.PatientID + PiaZ.extension - let res = dialog.ShowDialog() + let res = dialog.ShowDialog () if res.HasValue && res.Value then Some dialog.FileName else @@ -258,16 +261,16 @@ let run (defaultConfig : Config) (fileToOpen : string option) = match askDocumentPathToSave () with | Some filepath -> state.FilePath <- filepath - state.Save() + state.Save () | _ -> () else - state.Save() + state.Save () updateDocumentStatus () displayStatusMessage "Document saved" with | :? IOException as ex -> Log.Error "%O" ex - MessageBox.Show(sprintf "The document cannot be save in \"%s\"" state.FilePath, "Error saving the document", MessageBoxButton.OK, MessageBoxImage.Error) |> ignore + MessageBox.Show (sprintf "The document cannot be save in \"%s\"" state.FilePath, "Error saving the document", MessageBoxButton.OK, MessageBoxImage.Error) |> ignore let saveCurrentDocumentAsNewFile () = match askDocumentPathToSave () with @@ -279,7 +282,7 @@ let run (defaultConfig : Config) (fileToOpen : string option) = // Ask the use to save the current document if neccessary. let askSaveCurrent () = if state.AlteredSinceLastSave then - match MessageBox.Show("Would you like to save the current document?", "Saving the current document", MessageBoxButton.YesNo, MessageBoxImage.Question) with + match MessageBox.Show ("Would you like to save the current document?", "Saving the current document", MessageBoxButton.YesNo, MessageBoxImage.Question) with | MessageBoxResult.Yes -> saveCurrentDocument () | _ -> () @@ -291,11 +294,11 @@ let run (defaultConfig : Config) (fileToOpen : string option) = // Highlight the preview. win.stackPreviews.Children |> Seq.cast - |> Seq.iter (fun preview -> preview.border.BorderThickness <- Thickness(if preview.Tag = (srcImg :> Object) then 3. else 0.)) + |> Seq.iter (fun preview -> preview.border.BorderThickness <- Thickness (if preview.Tag = (srcImg :> Object) then 3. else 0.)) win.canvasCurrentImage.Height <- float srcImg.img.Height win.canvasCurrentImage.Width <- float srcImg.img.Width - win.canvasCurrentImage.Background <- ImageBrush(BitmapSourceConvert.ToBitmapSource(srcImg.img)) + win.canvasCurrentImage.Background <- ImageBrush (BitmapSourceConvert.ToBitmapSource (srcImg.img)) updateRBCFramesCurrent () updateRBCFramesPreview () @@ -303,8 +306,8 @@ let run (defaultConfig : Config) (fileToOpen : string option) = | None -> win.imgLogos.Visibility <- Visibility.Visible - win.stackRBC.Children.Clear() - win.canvasCurrentImage.Children.Clear() + win.stackRBC.Children.Clear () + win.canvasCurrentImage.Children.Clear () win.canvasCurrentImage.Background <- canvasCurrentImageColor updateCurrentImageInformation () @@ -315,27 +318,29 @@ let run (defaultConfig : Config) (fileToOpen : string option) = updateCurrentImage () let addPreview (srcImg : SourceImage) = - let imgCtrl = Views.ImageSourcePreview(Margin = Thickness(3.)) + let imgCtrl = Views.ImageSourcePreview (Margin = Thickness 3.) - imgCtrl.menuRemoveImage.Click.AddHandler(fun obj args -> - win.stackPreviews.Children.Remove(imgCtrl) - let srcImg = imgCtrl.Tag :?> SourceImage - let currentRemoved = Some srcImg = state.CurrentImage - state.RemoveSourceImage srcImg - if currentRemoved then - updateCurrentImage() + imgCtrl.menuRemoveImage.Click.AddHandler ( + fun obj args -> + win.stackPreviews.Children.Remove (imgCtrl) + let srcImg = imgCtrl.Tag :?> SourceImage + let currentRemoved = Some srcImg = state.CurrentImage + state.RemoveSourceImage srcImg + if currentRemoved then + updateCurrentImage () - updateGlobalParasitemia() + updateGlobalParasitemia () - // Update image numbers. - win.stackPreviews.Children |> Seq.cast |> Seq.iter (fun imgPreview -> imgPreview.txtImageNumber.Text <- (imgPreview.Tag :?> SourceImage).num.ToString())) + // Update image numbers. + win.stackPreviews.Children |> Seq.cast |> Seq.iter (fun imgPreview -> imgPreview.txtImageNumber.Text <- (imgPreview.Tag :?> SourceImage).num.ToString ()) + ) imgCtrl.Tag <- srcImg - imgCtrl.txtImageNumber.Text <- srcImg.num.ToString() + imgCtrl.txtImageNumber.Text <- string srcImg.num let width = 200 let height = srcImg.img.Height * width / srcImg.img.Width - imgCtrl.imagePreview.Source <- BitmapSourceConvert.ToBitmapSource(srcImg.img.Resize(width, height, Emgu.CV.CvEnum.Inter.Cubic)) - win.stackPreviews.Children.Add(imgCtrl) |> ignore + imgCtrl.imagePreview.Source <- BitmapSourceConvert.ToBitmapSource (srcImg.img.Resize (width, height, Emgu.CV.CvEnum.Inter.Cubic)) + win.stackPreviews.Children.Add imgCtrl |> ignore // Zoom to a mouse position into the control 'imgCtrl'. let zoomTo (mousePos : Point) = @@ -343,24 +348,27 @@ let run (defaultConfig : Config) (fileToOpen : string option) = let canvasH = win.canvasCurrentImage.ActualHeight * currentScale let centerX = (mousePos.X - imgCtrl.BorderThickness.Left) / (imgCtrl.ActualWidth - imgCtrl.BorderThickness.Left) * canvasW let centerY = (mousePos.Y - imgCtrl.BorderThickness.Top) / (imgCtrl.ActualHeight - imgCtrl.BorderThickness.Top) * canvasH - win.scrollViewCurrentImage.ScrollToHorizontalOffset(centerX - win.scrollViewCurrentImage.ViewportWidth / 2. + win.borderCurrentImage.BorderThickness.Left) - win.scrollViewCurrentImage.ScrollToVerticalOffset(centerY - win.scrollViewCurrentImage.ViewportHeight / 2. + win.borderCurrentImage.BorderThickness.Top) - - imgCtrl.MouseLeftButtonDown.AddHandler(fun obj args -> - setCurrentImage (state.SourceImages |> Seq.find (fun srcImg -> (srcImg :> Object) = imgCtrl.Tag)) - imgCtrl.UpdateLayout() - zoomTo (args.GetPosition(imgCtrl)) - imgCtrl.CaptureMouse() |> ignore + win.scrollViewCurrentImage.ScrollToHorizontalOffset (centerX - win.scrollViewCurrentImage.ViewportWidth / 2. + win.borderCurrentImage.BorderThickness.Left) + win.scrollViewCurrentImage.ScrollToVerticalOffset (centerY - win.scrollViewCurrentImage.ViewportHeight / 2. + win.borderCurrentImage.BorderThickness.Top) + + imgCtrl.MouseLeftButtonDown.AddHandler ( + fun obj args -> + setCurrentImage (state.SourceImages |> Seq.find (fun srcImg -> (srcImg :> Object) = imgCtrl.Tag)) + imgCtrl.UpdateLayout () + zoomTo (args.GetPosition (imgCtrl)) + imgCtrl.CaptureMouse () |> ignore ) - imgCtrl.MouseMove.AddHandler(fun obj args -> - if imgCtrl.IsMouseCaptured then - zoomTo (args.GetPosition(imgCtrl)) + imgCtrl.MouseMove.AddHandler ( + fun obj args -> + if imgCtrl.IsMouseCaptured then + zoomTo (args.GetPosition imgCtrl) ) - imgCtrl.MouseLeftButtonUp.AddHandler(fun obj args -> - if imgCtrl.IsMouseCaptured then - imgCtrl.ReleaseMouseCapture() + imgCtrl.MouseLeftButtonUp.AddHandler ( + fun obj args -> + if imgCtrl.IsMouseCaptured then + imgCtrl.ReleaseMouseCapture () ) let updatePreviews () = @@ -380,48 +388,48 @@ let run (defaultConfig : Config) (fileToOpen : string option) = let previousFilePath = state.FilePath try state.FilePath <- filepath - state.Load() + state.Load () updateGUI () with | :? IOException as ex -> Log.Error "%O" ex state.FilePath <- previousFilePath - MessageBox.Show(sprintf "The document cannot be loaded from \"%s\"" filepath, "Error loading the document", MessageBoxButton.OK, MessageBoxImage.Error) |> ignore + MessageBox.Show (sprintf "The document cannot be loaded from \"%s\"" filepath, "Error loading the document", MessageBoxButton.OK, MessageBoxImage.Error) |> ignore let askLoadFile () = - let dialog = OpenFileDialog(Filter = PiaZ.filter) - let res = dialog.ShowDialog() + let dialog = OpenFileDialog (Filter = PiaZ.filter) + let res = dialog.ShowDialog () if res.HasValue && res.Value then loadFile dialog.FileName let newFile () = askSaveCurrent () - state.Reset() - updateGUI() + state.Reset () + updateGUI () let exportResults () = let extension = ".txt" - let dialog = SaveFileDialog(AddExtension = true, DefaultExt = extension) + let dialog = SaveFileDialog (AddExtension = true, DefaultExt = extension) if state.FilePath <> "" then - dialog.FileName <- Path.GetFileNameWithoutExtension(state.FilePath) + extension + dialog.FileName <- Path.GetFileNameWithoutExtension state.FilePath + extension elif state.PatientID <> "" then dialog.FileName <- state.PatientID + extension - let res = dialog.ShowDialog() + let res = dialog.ShowDialog () if res.HasValue && res.Value then try Export.exportResults state dialog.FileName with | :? IOException as ex -> Log.Error "%O" ex - MessageBox.Show(sprintf "The results cannot be exported in \"%s\"" state.FilePath, "Error exporting the files", MessageBoxButton.OK, MessageBoxImage.Error) |> ignore + MessageBox.Show (sprintf "The results cannot be exported in \"%s\"" state.FilePath, "Error exporting the files", MessageBoxButton.OK, MessageBoxImage.Error) |> ignore let importImage () = - let dialog = OpenFileDialog(Filter = "Image Files|*.png;*.jpg;*.tif;*.tiff", Multiselect = true) - let res = dialog.ShowDialog() + let dialog = OpenFileDialog (Filter = "Image Files|*.png;*.jpg;*.tif;*.tiff", Multiselect = true) + let res = dialog.ShowDialog () if res.HasValue && res.Value then - let noSourceImage = state.SourceImages.Count() = 0 + let noSourceImage = state.SourceImages.Count () = 0 for filename in dialog.FileNames do try @@ -430,7 +438,7 @@ let run (defaultConfig : Config) (fileToOpen : string option) = with | _ as ex -> Log.Error "%O" ex - MessageBox.Show(sprintf "Unable to read the image from \"%s\"" filename, "Error adding an image", MessageBoxButton.OK, MessageBoxImage.Error) |> ignore + MessageBox.Show (sprintf "Unable to read the image from \"%s\"" filename, "Error adding an image", MessageBoxButton.OK, MessageBoxImage.Error) |> ignore updateGlobalParasitemia () @@ -447,173 +455,179 @@ let run (defaultConfig : Config) (fileToOpen : string option) = updateRBCFramesPreview () updateRBCFramesCurrent () - win.txtPatient.TextChanged.AddHandler(fun obj args -> state.PatientID <- win.txtPatient.Text) + win.txtPatient.TextChanged.AddHandler (fun obj args -> state.PatientID <- win.txtPatient.Text) - 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 ()) - win.menuNew.Click.AddHandler(fun obj args -> newFile ()) - win.menuExportResults.Click.AddHandler(fun obj args -> exportResults ()) + 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 ()) + win.menuNew.Click.AddHandler (fun obj args -> newFile ()) + win.menuExportResults.Click.AddHandler (fun obj args -> exportResults ()) - win.menuAddSourceImage.Click.AddHandler(fun obj args -> importImage ()) + win.menuAddSourceImage.Click.AddHandler (fun obj args -> importImage ()) - win.menuAnalysis.SubmenuOpened.AddHandler(fun obj args -> win.menuStartAnalysis.IsEnabled <- state.SourceImages.Count() > 0) + win.menuAnalysis.SubmenuOpened.AddHandler (fun obj args -> win.menuStartAnalysis.IsEnabled <- state.SourceImages.Count () > 0) - win.menuStartAnalysis.Click.AddHandler(fun obj args -> showAnalysisWindow ()) + win.menuStartAnalysis.Click.AddHandler (fun obj args -> showAnalysisWindow ()) - win.menuHightlightRBC.Click.AddHandler(fun obj args -> setHighlightRBC win.menuHightlightRBC.IsChecked) + win.menuHightlightRBC.Click.AddHandler (fun obj args -> setHighlightRBC win.menuHightlightRBC.IsChecked) - win.menuAbout.Click.AddHandler(fun obj args -> About.showWindow win) + win.menuAbout.Click.AddHandler (fun obj args -> About.showWindow win) - win.Closing.AddHandler(fun obj args -> askSaveCurrent ()) + win.Closing.AddHandler (fun obj args -> askSaveCurrent ()) // Zoom on the current image. let adjustCurrentImageBorders (deltaX : float) (deltaY : float) = win.borderCurrentImage.BorderThickness <- - Thickness( + Thickness ( (win.scrollViewCurrentImage.ViewportWidth + deltaX) / 2., (win.scrollViewCurrentImage.ViewportHeight + deltaY) / 2., (win.scrollViewCurrentImage.ViewportWidth + deltaX) / 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 - 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.canvasCurrentImage.SizeChanged.AddHandler ( + fun obj args -> + let deltaX = args.NewSize.Width - args.PreviousSize.Width + let deltaY = args.NewSize.Height - args.PreviousSize.Height + if deltaX > 0.5 || deltaY > 0.5 then + adjustCurrentImageBorders 0.0 0.0 + // Center the view at the center of the image initialy. + 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.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.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.) ) let mutable maxScale = 4. let mutable minScale = 0.25 - let currentImageScaleTransform = ScaleTransform() + let currentImageScaleTransform = ScaleTransform () 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 - let previousScale = currentScale - currentScale <- - let newScale = currentScale * scaleFactor - if newScale > maxScale then maxScale elif newScale < minScale then minScale else newScale - let realScaleFactor = currentScale / previousScale + 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 + let previousScale = currentScale + currentScale <- + let newScale = currentScale * scaleFactor + if newScale > maxScale then maxScale elif newScale < minScale then minScale else newScale + let realScaleFactor = currentScale / previousScale - let centerX = win.scrollViewCurrentImage.HorizontalOffset + win.scrollViewCurrentImage.ViewportWidth / 2. - win.borderCurrentImage.BorderThickness.Left - let centerY = win.scrollViewCurrentImage.VerticalOffset + win.scrollViewCurrentImage.ViewportHeight / 2. - win.borderCurrentImage.BorderThickness.Top + let centerX = win.scrollViewCurrentImage.HorizontalOffset + win.scrollViewCurrentImage.ViewportWidth / 2. - win.borderCurrentImage.BorderThickness.Left + let centerY = win.scrollViewCurrentImage.VerticalOffset + win.scrollViewCurrentImage.ViewportHeight / 2. - win.borderCurrentImage.BorderThickness.Top - currentImageScaleTransform.ScaleX <- currentScale - currentImageScaleTransform.ScaleY <- currentScale + currentImageScaleTransform.ScaleX <- currentScale + currentImageScaleTransform.ScaleY <- currentScale - 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) + 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.) + let mutable scrollStartPosition = Point (0., 0.) let mutable scrollStartOffsetX = 0. let mutable scrollStartOffsetY = 0. - win.borderCurrentImage.PreviewMouseLeftButtonDown.AddHandler(fun obj args -> - scrollStartPosition <- args.GetPosition(win.scrollViewCurrentImage) - scrollStartOffsetX <- win.scrollViewCurrentImage.HorizontalOffset - scrollStartOffsetY <- win.scrollViewCurrentImage.VerticalOffset - win.borderCurrentImage.Cursor <- Input.Cursors.ScrollAll - win.borderCurrentImage.CaptureMouse() |> ignore - args.Handled <- true + win.borderCurrentImage.PreviewMouseLeftButtonDown.AddHandler ( + fun obj args -> + scrollStartPosition <- args.GetPosition win.scrollViewCurrentImage + scrollStartOffsetX <- win.scrollViewCurrentImage.HorizontalOffset + scrollStartOffsetY <- win.scrollViewCurrentImage.VerticalOffset + win.borderCurrentImage.Cursor <- Input.Cursors.ScrollAll + win.borderCurrentImage.CaptureMouse () |> ignore + args.Handled <- true ) - win.borderCurrentImage.PreviewMouseMove.AddHandler(fun obj args -> - 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) + win.borderCurrentImage.PreviewMouseMove.AddHandler ( + fun obj args -> + 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 - win.borderCurrentImage.Cursor <- Input.Cursors.Arrow - win.borderCurrentImage.ReleaseMouseCapture() - args.Handled <- true + win.borderCurrentImage.PreviewMouseLeftButtonUp.AddHandler ( + fun obj args -> + if win.borderCurrentImage.IsMouseCaptured then + win.borderCurrentImage.Cursor <- Input.Cursors.Arrow + win.borderCurrentImage.ReleaseMouseCapture () + args.Handled <- true ) // Shortcuts. // Save. - win.InputBindings.Add( - Input.KeyBinding( - ViewModule.FunCommand((fun obj -> saveCurrentDocument ()), (fun obj -> true)), - Input.KeyGesture(Input.Key.S, Input.ModifierKeys.Control) + win.InputBindings.Add ( + Input.KeyBinding ( + ViewModule.FunCommand ((fun obj -> saveCurrentDocument ()), (fun obj -> true)), + Input.KeyGesture (Input.Key.S, Input.ModifierKeys.Control) ) ) |> ignore // Save as. - win.InputBindings.Add( - Input.KeyBinding( - ViewModule.FunCommand((fun obj -> saveCurrentDocumentAsNewFile ()), (fun obj -> true)), - Input.KeyGesture(Input.Key.S, Input.ModifierKeys.Control ||| Input.ModifierKeys.Shift) + win.InputBindings.Add ( + Input.KeyBinding ( + ViewModule.FunCommand ((fun obj -> saveCurrentDocumentAsNewFile ()), (fun obj -> true)), + Input.KeyGesture (Input.Key.S, Input.ModifierKeys.Control ||| Input.ModifierKeys.Shift) ) ) |> ignore // Open. - win.InputBindings.Add( - Input.KeyBinding( - ViewModule.FunCommand((fun obj -> askLoadFile ()), (fun obj -> true)), - Input.KeyGesture(Input.Key.O, Input.ModifierKeys.Control) + win.InputBindings.Add ( + Input.KeyBinding ( + ViewModule.FunCommand ((fun obj -> askLoadFile ()), (fun obj -> true)), + Input.KeyGesture (Input.Key.O, Input.ModifierKeys.Control) ) ) |> ignore // New file. - win.InputBindings.Add( - Input.KeyBinding( - ViewModule.FunCommand((fun obj -> newFile ()), (fun obj -> true)), - Input.KeyGesture(Input.Key.N, Input.ModifierKeys.Control) + win.InputBindings.Add ( + Input.KeyBinding ( + ViewModule.FunCommand ((fun obj -> newFile ()), (fun obj -> true)), + Input.KeyGesture (Input.Key.N, Input.ModifierKeys.Control) ) ) |> ignore // Export results. - win.InputBindings.Add( - Input.KeyBinding( - ViewModule.FunCommand((fun obj -> exportResults ()), (fun obj -> true)), - Input.KeyGesture(Input.Key.E, Input.ModifierKeys.Control) + win.InputBindings.Add ( + Input.KeyBinding ( + ViewModule.FunCommand ((fun obj -> exportResults ()), (fun obj -> true)), + Input.KeyGesture (Input.Key.E, Input.ModifierKeys.Control) ) ) |> ignore // Import an image. - win.InputBindings.Add( - Input.KeyBinding( - ViewModule.FunCommand((fun obj -> importImage ()), (fun obj -> true)), - Input.KeyGesture(Input.Key.A, Input.ModifierKeys.Control) + win.InputBindings.Add ( + Input.KeyBinding ( + ViewModule.FunCommand ((fun obj -> importImage ()), (fun obj -> true)), + Input.KeyGesture (Input.Key.A, Input.ModifierKeys.Control) ) ) |> ignore // Show analysis dialog. - win.InputBindings.Add( - Input.KeyBinding( - ViewModule.FunCommand((fun obj -> showAnalysisWindow ()), (fun obj -> state.SourceImages.Count() > 0)), - Input.KeyGesture(Input.Key.Y, Input.ModifierKeys.Control) + win.InputBindings.Add ( + Input.KeyBinding ( + ViewModule.FunCommand ((fun obj -> showAnalysisWindow ()), (fun obj -> state.SourceImages.Count () > 0)), + Input.KeyGesture (Input.Key.Y, Input.ModifierKeys.Control) ) ) |> ignore // Toggle RBC highlight. - win.InputBindings.Add( - Input.KeyBinding( - ViewModule.FunCommand( + win.InputBindings.Add ( + Input.KeyBinding ( + ViewModule.FunCommand ( ( fun obj -> win.menuHightlightRBC.IsChecked <- not win.menuHightlightRBC.IsChecked @@ -621,20 +635,20 @@ let run (defaultConfig : Config) (fileToOpen : string option) = ), (fun obj -> true) ), - Input.KeyGesture(Input.Key.H, Input.ModifierKeys.Control) + Input.KeyGesture (Input.Key.H, Input.ModifierKeys.Control) ) ) |> ignore // Viewport preview. - win.scrollViewCurrentImage.ScrollChanged.AddHandler(fun obj args -> updateViewportPreview ()) + win.scrollViewCurrentImage.ScrollChanged.AddHandler (fun obj args -> updateViewportPreview ()) updateDocumentStatus () win.gridImageInformation.Visibility <- Visibility.Hidden - win.Show() + win.Show () match fileToOpen with | Some filepath -> loadFile filepath | None -> () - app.Run() + app.Run () diff --git a/Parasitemia/ParasitemiaUI/ParasitemiaUI.fsproj b/Parasitemia/ParasitemiaUI/ParasitemiaUI.fsproj index 200460e..0324219 100644 --- a/Parasitemia/ParasitemiaUI/ParasitemiaUI.fsproj +++ b/Parasitemia/ParasitemiaUI/ParasitemiaUI.fsproj @@ -110,7 +110,7 @@ ..\packages\EmguCV.3.1.0.1\lib\net30\Emgu.CV.World.dll - ..\packages\FSharp.Core.4.1.17\lib\net45\FSharp.Core.dll + ..\packages\FSharp.Core.4.2.1\lib\net45\FSharp.Core.dll ..\packages\FSharp.ViewModule.Core.1.0.7.0\lib\portable-net45+netcore45+wpa81+wp8+MonoAndroid1+MonoTouch1\FSharp.ViewModule.dll @@ -123,7 +123,7 @@ - ..\packages\Newtonsoft.Json.10.0.2\lib\net45\Newtonsoft.Json.dll + ..\packages\Newtonsoft.Json.10.0.3\lib\net45\Newtonsoft.Json.dll ..\packages\OpenTK.2.0.0\lib\net20\OpenTK.dll diff --git a/Parasitemia/ParasitemiaUI/PiaZ.fs b/Parasitemia/ParasitemiaUI/PiaZ.fs index 0215254..92ebbe0 100644 --- a/Parasitemia/ParasitemiaUI/PiaZ.fs +++ b/Parasitemia/ParasitemiaUI/PiaZ.fs @@ -56,26 +56,26 @@ let currentFileVersion = 2 /// /// If the file cannot be written let save (filePath : string) (data : DocumentData) = - use file = ZipFile.Open(filePath, ZipArchiveMode.Update) + use file = ZipFile.Open (filePath, ZipArchiveMode.Update) for e in List.ofSeq file.Entries do // 'ofSeq' to not iterate a collection currently modified. - e.Delete() + e.Delete () // Main JSON file. - let mainEntry = file.CreateEntry(mainEntryName, CompressionLevel.Fastest) - use mainEntryWriter = new StreamWriter(mainEntry.Open()) - mainEntryWriter.Write(JsonConvert.SerializeObject({ patientID = data.patientID; fileVersion = currentFileVersion })) + let mainEntry = file.CreateEntry (mainEntryName, CompressionLevel.Fastest) + use mainEntryWriter = new StreamWriter (mainEntry.Open ()) + mainEntryWriter.Write (JsonConvert.SerializeObject ({ patientID = data.patientID; fileVersion = currentFileVersion })) // Write each images and the associated information. for srcImg in data.images do let imgFilename = (string srcImg.num) + imageExtension - let imgEntry = file.CreateEntry(imgFilename, CompressionLevel.NoCompression) // FIXME: It seems a compression is applied to this file despite of the 'NoCompression' flag. - srcImg.img.ToBitmap().Save(imgEntry.Open(), System.Drawing.Imaging.ImageFormat.Tiff) + let imgEntry = file.CreateEntry (imgFilename, CompressionLevel.NoCompression) // FIXME: It seems a compression is applied to this file despite of the 'NoCompression' flag. + srcImg.img.ToBitmap().Save (imgEntry.Open (), System.Drawing.Imaging.ImageFormat.Tiff) - let imgJSONEntry = file.CreateEntry(imgFilename + ".json", CompressionLevel.Fastest) - use imgJSONFileWriter = new StreamWriter(imgJSONEntry.Open()) - imgJSONFileWriter.Write( - JsonConvert.SerializeObject( + let imgJSONEntry = file.CreateEntry (imgFilename + ".json", CompressionLevel.Fastest) + use imgJSONFileWriter = new StreamWriter (imgJSONEntry.Open ()) + imgJSONFileWriter.Write ( + JsonConvert.SerializeObject ( { num = srcImg.num name = srcImg.name @@ -103,11 +103,11 @@ let updateDocumentData (fromVersion : int) (toVersion : int) (data : DocumentDat /// /// If the file cannot be read let load (filePath : string) (defaultConfig : ParasitemiaCore.Config.Config) : DocumentData = - use file = ZipFile.Open(filePath, ZipArchiveMode.Read) + use file = ZipFile.Open (filePath, ZipArchiveMode.Read) - let mainEntry = file.GetEntry(mainEntryName) - use mainEntryReader = new StreamReader(mainEntry.Open()) - let info = JsonConvert.DeserializeObject(mainEntryReader.ReadToEnd()) + let mainEntry = file.GetEntry (mainEntryName) + use mainEntryReader = new StreamReader (mainEntry.Open ()) + let info = JsonConvert.DeserializeObject (mainEntryReader.ReadToEnd ()) updateDocumentData info.fileVersion currentFileVersion { @@ -116,15 +116,15 @@ let load (filePath : string) (defaultConfig : ParasitemiaCore.Config.Config) : D [ let mutable imgNum = 0 for imgEntry in file.Entries do - if imgEntry.Name.EndsWith(imageExtension) then - use bitmap = new System.Drawing.Bitmap(imgEntry.Open(), false) - let img = new Image(bitmap) + if imgEntry.Name.EndsWith (imageExtension) then + use bitmap = new System.Drawing.Bitmap (imgEntry.Open (), false) + let img = new Image (bitmap) imgNum <- imgNum + 1 - let imgJSONEntry = file.GetEntry(imgEntry.Name + ".json") - use imgJSONFileReader = new StreamReader(imgJSONEntry.Open()) - let imgInfo = JsonConvert.DeserializeObject(imgJSONFileReader.ReadToEnd()) + let imgJSONEntry = file.GetEntry (imgEntry.Name + ".json") + use imgJSONFileReader = new StreamReader (imgJSONEntry.Open ()) + let imgInfo = JsonConvert.DeserializeObject (imgJSONFileReader.ReadToEnd ()) - let config = defaultConfig.Copy() + let config = defaultConfig.Copy () config.Parameters <- { ParasitemiaCore.Config.defaultParameters with diff --git a/Parasitemia/ParasitemiaUI/Program.fs b/Parasitemia/ParasitemiaUI/Program.fs index 0f6df12..b7b4202 100644 --- a/Parasitemia/ParasitemiaUI/Program.fs +++ b/Parasitemia/ParasitemiaUI/Program.fs @@ -35,7 +35,7 @@ let parseArgs (args : string[]) : Arguments = | Some i, Some i_output when i < args.Length - 2 && i_output < args.Length - 2 -> CmdLine ((File args.[i + 1]), args.[i_output + 1]) |_ -> - Window (if args.Length > 0 && not (args.[0].StartsWith("--")) then Some args.[0] else None) + Window (if args.Length > 0 && not (args.[0].StartsWith ("--")) then Some args.[0] else None) runningMode, Array.exists ((=) "--debug") args @@ -53,14 +53,14 @@ let showArgsHelp () = printfn " : a PIAZ file to automatically open at startup" printfn " --debug : output information like intermediate images if set in the current directory" -[] -extern bool AttachConsole(int dwProcessId) +[] +extern bool AttachConsole (int dwProcessId) [] -[] +[] let main args = // To redirect stdout to the attached console. - AttachConsole(-1) |> ignore // -1 to attach to the parent process. + AttachConsole -1 |> ignore // -1 to attach to the parent process. if Array.exists (fun e -> e = "--help" || e = "-h") args then showArgsHelp () @@ -72,7 +72,7 @@ let main args = let result = match parseArgs args with | mode, debug -> - let config = Config(defaultParameters) + let config = Config defaultParameters match mode with | CmdLine (input, output) -> @@ -81,9 +81,9 @@ let main args = Directory.CreateDirectory output |> ignore - use logFile = new StreamWriter(new FileStream(Path.Combine(output, "log.txt"), FileMode.Append, FileAccess.Write)) - let listener = { new IListener with member this.NewEntry severity header mess = logFile.WriteLine(header + " : " + mess) } - Log.AddListener(listener) + use logFile = new StreamWriter (new FileStream (Path.Combine(output, "log.txt"), FileMode.Append, FileAccess.Write)) + let listener = { new IListener with member this.NewEntry severity header mess = logFile.WriteLine (header + " : " + mess) } + Log.AddListener listener Log.Info "=== New run : %O %s ===" DateTime.Now (if debug then "[DEBUG]" else "[RELEASE]") @@ -91,23 +91,25 @@ let main args = | File file -> [ file ] | Dir dir -> Directory.EnumerateFiles dir |> List.ofSeq - use resultFile = new StreamWriter(new FileStream(Path.Combine(output, "results.txt"), FileMode.Append, FileAccess.Write)) - - let images = [ for file in files -> Path.GetFileNameWithoutExtension(FileInfo(file).Name), config.Copy(), new Image(file) ] - - Log.LogWithTime Severity.INFO (fun () -> - match ParasitemiaCore.Analysis.doMultipleAnalysis images None with - | Some results -> - for id, cells in results do - let config, img = images |> List.pick (fun (id', config', img') -> if id' = id then Some (config', img') else None) - img.Dispose() - let total, infected = countCells cells - fprintf resultFile "File: %s %d %d %.2f (diameter: %O)\n" id total infected (100. * (float infected) / (float total)) config.RBCRadius - | None -> - fprintf resultFile "Analysis aborted" - Some ()) "Whole analyze" |> ignore - - Log.RmListener(listener) + use resultFile = new StreamWriter (new FileStream (Path.Combine (output, "results.txt"), FileMode.Append, FileAccess.Write)) + + let images = [ for file in files -> Path.GetFileNameWithoutExtension (FileInfo(file).Name), config.Copy(), new Image (file) ] + + Log.LogWithTime Severity.INFO ( + fun () -> + match ParasitemiaCore.Analysis.doMultipleAnalysis images None with + | Some results -> + for id, cells in results do + let config, img = images |> List.pick (fun (id', config', img') -> if id' = id then Some (config', img') else None) + img.Dispose () + let total, infected = countCells cells + fprintf resultFile "File: %s %d %d %.2f (diameter: %O)\n" id total infected (100. * (float infected) / (float total)) config.RBCRadius + | None -> + fprintf resultFile "Analysis aborted" + Some () + ) "Whole analyze" |> ignore + + Log.RmListener (listener) 0 | Window fileToOpen -> diff --git a/Parasitemia/ParasitemiaUI/State.fs b/Parasitemia/ParasitemiaUI/State.fs index d09fe23..3355a42 100644 --- a/Parasitemia/ParasitemiaUI/State.fs +++ b/Parasitemia/ParasitemiaUI/State.fs @@ -10,7 +10,7 @@ open Emgu.CV.Structure open Types type State (defaultConfig : ParasitemiaCore.Config.Config) = - let sourceImages = List() + let sourceImages = List () let mutable alteredSinceLastSave = false let mutable patientID = "" @@ -80,8 +80,8 @@ type State (defaultConfig : ParasitemiaCore.Config.Config) = member this.Load () = let data = PiaZ.load this.FilePath defaultConfig this.PatientID <- data.patientID - sourceImages.Clear() - sourceImages.InsertRange(0, data.images) + sourceImages.Clear () + sourceImages.InsertRange (0, data.images) this.CurrentImage <- if sourceImages.Count > 0 then Some sourceImages.[0] else None alteredSinceLastSave <- false @@ -93,15 +93,15 @@ type State (defaultConfig : ParasitemiaCore.Config.Config) = { num = sourceImages.Count + 1 name = System.IO.FileInfo(filePath).Name - config = defaultConfig.Copy() - dateLastAnalysis = DateTime(0L) + config = defaultConfig.Copy () + dateLastAnalysis = DateTime (0L) rbcs = [] - img = new Image(filePath) + img = new Image (filePath) healthyRBCBrightness = 1.f infectedRBCBrightness = 1.f } - sourceImages.Add(srcImg) + sourceImages.Add srcImg if sourceImages.Count = 1 then this.CurrentImage <- Some sourceImages.[0] alteredSinceLastSave <- true @@ -113,7 +113,7 @@ type State (defaultConfig : ParasitemiaCore.Config.Config) = | Some srcImg' -> srcImg = srcImg' | _ -> false - if sourceImages.Remove(srcImg) then + if sourceImages.Remove srcImg then alteredSinceLastSave <- true if isCurrent then this.CurrentImage <- if sourceImages.Count > 0 then Some sourceImages.[0] else None @@ -126,7 +126,7 @@ type State (defaultConfig : ParasitemiaCore.Config.Config) = alteredSinceLastSave <- true member this.SetResult (imgNum : int) (cells : ParasitemiaCore.Types.Cell list) = - let sourceImage = sourceImages.Find(fun srcImg -> srcImg.num = imgNum) + let sourceImage = sourceImages.Find (fun srcImg -> srcImg.num = imgNum) let w = sourceImage.img.Width let h = sourceImage.img.Height @@ -137,8 +137,14 @@ type State (defaultConfig : ParasitemiaCore.Config.Config) = let manuallyAlteredPreviousRBCS = sourceImage.rbcs |> List.filter (fun rbc -> rbc.setManually) let tolerance = (float sourceImage.config.RBCRadius.Pixel) * 0.5 // +/-. let getPreviousManuallyAlteredRBC (center : Point) : RBC option = - manuallyAlteredPreviousRBCS |> List.tryFind (fun rbc -> rbc.center.X > center.X - tolerance && rbc.center.X < center.X + tolerance && - rbc.center.Y > center.Y - tolerance && rbc.center.Y < center.Y + tolerance) + manuallyAlteredPreviousRBCS + |> List.tryFind ( + fun rbc -> + rbc.center.X > center.X - tolerance && + rbc.center.X < center.X + tolerance && + rbc.center.Y > center.Y - tolerance && + rbc.center.Y < center.Y + tolerance + ) sourceImage.rbcs <- cells @@ -146,7 +152,7 @@ type State (defaultConfig : ParasitemiaCore.Config.Config) = |> List.sortByDescending (fun cell -> cell.nucleusArea, (w - cell.center.X) + (h - cell.center.Y)) |> List.mapi ( fun i cell -> - let center = Point(float cell.center.X, float cell.center.Y) + let center = Point (float cell.center.X, float cell.center.Y) let infected, setManually = let infected = cell.cellClass = ParasitemiaCore.Types.InfectedRBC match getPreviousManuallyAlteredRBC center with @@ -157,7 +163,7 @@ type State (defaultConfig : ParasitemiaCore.Config.Config) = infected = infected setManually = setManually center = center - size = Size(float cell.elements.Width, float cell.elements.Height) + size = Size (float cell.elements.Width, float cell.elements.Height) infectedArea = cell.nucleusArea } ) @@ -171,5 +177,5 @@ type State (defaultConfig : ParasitemiaCore.Config.Config) = this.PatientID <- "" this.FilePath <- "" this.CurrentImage <- None - sourceImages.Clear() + sourceImages.Clear () alteredSinceLastSave <- false \ No newline at end of file diff --git a/Parasitemia/ParasitemiaUI/Types.fs b/Parasitemia/ParasitemiaUI/Types.fs index eb778d8..ada2a1f 100644 --- a/Parasitemia/ParasitemiaUI/Types.fs +++ b/Parasitemia/ParasitemiaUI/Types.fs @@ -11,8 +11,8 @@ open Newtonsoft.Json open ParasitemiaCore.UnitsOfMeasure -let healthyRBColor = Color.FromRgb(255uy, 255uy, 0uy) // Yellow-green. -let infectedRBColor = Color.FromRgb(255uy, 0uy, 40uy) // Red with a bit of blue. +let healthyRBColor = Color.FromRgb (255uy, 255uy, 0uy) // Yellow-green. +let infectedRBColor = Color.FromRgb (255uy, 0uy, 40uy) // Red with a bit of blue. type RBC = { @@ -46,12 +46,12 @@ type SourceImage = member this.HealthyRBCColor : SolidColorBrush = let mutable color = healthyRBColor * this.healthyRBCBrightness color.A <- 255uy - SolidColorBrush(color) + SolidColorBrush (color) member this.InfectedRBCColor : SolidColorBrush = let mutable color = infectedRBColor * this.infectedRBCBrightness color.A <- 255uy - SolidColorBrush(color) + SolidColorBrush (color) type PredefinedPPI = { @@ -59,7 +59,7 @@ type PredefinedPPI = label : string } with - override this.ToString() = + override this.ToString () = sprintf "%s: %d" this.label this.ppi type SensorSize = diff --git a/Parasitemia/ParasitemiaUI/Utils.fs b/Parasitemia/ParasitemiaUI/Utils.fs index fa4af25..384a0d0 100644 --- a/Parasitemia/ParasitemiaUI/Utils.fs +++ b/Parasitemia/ParasitemiaUI/Utils.fs @@ -7,8 +7,8 @@ open Newtonsoft.Json.Converters open Types -let listAsStr (s : 'a seq) = - s |> Seq.fold (fun acc obj -> acc + (if acc = "" then "" else ", ") + obj.ToString()) "" +let inline listAsStr (s : 'a seq) = + s |> Seq.fold (fun acc obj -> acc + (if acc = "" then "" else ", ") + string obj) "" let percentText (nbTotal : int, nb : int) : string = if nbTotal = 0 then @@ -18,34 +18,34 @@ let percentText (nbTotal : int, nb : int) : string = sprintf "%.1f %% (%d / %d)" percent nb nbTotal let roamingDir = - Path.Combine(System.Environment.GetFolderPath(System.Environment.SpecialFolder.ApplicationData), "Parasitemia") + Path.Combine (System.Environment.GetFolderPath (System.Environment.SpecialFolder.ApplicationData), "Parasitemia") let predefinedPPIFilename = "predefined-ppi.json" -let predefinedPPIFilepath = Path.Combine(roamingDir, predefinedPPIFilename) +let predefinedPPIFilepath = Path.Combine (roamingDir, predefinedPPIFilename) let sensorSizesFilename = "sensor-sizes.json" -let sensorSizesFilepath = Path.Combine(roamingDir, sensorSizesFilename) +let sensorSizesFilepath = Path.Combine (roamingDir, sensorSizesFilename) let private savePredefinedPPIToFile (predefinedPPI : PredefinedPPI list) = try - use file = new StreamWriter(predefinedPPIFilepath) - file.Write(JsonConvert.SerializeObject(predefinedPPI, JsonSerializerSettings(Formatting = Formatting.Indented))) + use file = new StreamWriter (predefinedPPIFilepath) + file.Write (JsonConvert.SerializeObject (predefinedPPI, JsonSerializerSettings (Formatting = Formatting.Indented))) with ex -> Logger.Log.Error "Unable to save predefined PPI to file \"%s\": %O" predefinedPPIFilepath ex let private saveSensorSizesToFile (sensorSizes : SensorSize list) = try - use file = new StreamWriter(sensorSizesFilepath) - file.Write(JsonConvert.SerializeObject(sensorSizes, JsonSerializerSettings(Formatting = Formatting.Indented))) + use file = new StreamWriter (sensorSizesFilepath) + file.Write (JsonConvert.SerializeObject (sensorSizes, JsonSerializerSettings (Formatting = Formatting.Indented))) with ex -> Logger.Log.Error "Unable to save sensor sizes to file \"%s\": %O" sensorSizesFilepath ex let predefinedPPI : PredefinedPPI list = try - use file = new StreamReader(predefinedPPIFilepath) - JsonConvert.DeserializeObject(file.ReadToEnd()) + use file = new StreamReader (predefinedPPIFilepath) + JsonConvert.DeserializeObject (file.ReadToEnd ()) with | ex -> savePredefinedPPIToFile defaultPredefinedPPI @@ -53,8 +53,8 @@ let predefinedPPI : PredefinedPPI list = let sensorSizes : SensorSize list = try - use file = new StreamReader(sensorSizesFilepath) - JsonConvert.DeserializeObject(file.ReadToEnd()) + use file = new StreamReader (sensorSizesFilepath) + JsonConvert.DeserializeObject (file.ReadToEnd ()) with | ex -> saveSensorSizesToFile defaultSensorSizes diff --git a/Parasitemia/ParasitemiaUI/packages.config b/Parasitemia/ParasitemiaUI/packages.config index 89d3dcf..30067c6 100644 --- a/Parasitemia/ParasitemiaUI/packages.config +++ b/Parasitemia/ParasitemiaUI/packages.config @@ -2,10 +2,10 @@ - + - + diff --git a/Parasitemia/Tests/ParasitemiaCore.Tests/AssemblyInfo.fs b/Parasitemia/Tests/ParasitemiaCore.Tests/AssemblyInfo.fs index d448a0d..931f17d 100644 --- a/Parasitemia/Tests/ParasitemiaCore.Tests/AssemblyInfo.fs +++ b/Parasitemia/Tests/ParasitemiaCore.Tests/AssemblyInfo.fs @@ -7,22 +7,22 @@ open System.Runtime.InteropServices // General Information about an assembly is controlled through the following // set of attributes. Change these attribute values to modify the information // associated with an assembly. -[] -[] -[] -[] -[] -[] -[] -[] +[] +[] +[] +[] +[] +[] +[] +[] // Setting ComVisible to false makes the types in this assembly not visible // to COM components. If you need to access a type in this assembly from // COM, set the ComVisible attribute to true on that type. -[] +[] // The following GUID is for the ID of the typelib if this project is exposed to COM -[] +[] // Version information for an assembly consists of the following four values: // @@ -33,11 +33,11 @@ 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/Tests/ParasitemiaCore.Tests/KdTree.fs b/Parasitemia/Tests/ParasitemiaCore.Tests/KdTree.fs index 9c9d859..9181c26 100644 --- a/Parasitemia/Tests/ParasitemiaCore.Tests/KdTree.fs +++ b/Parasitemia/Tests/ParasitemiaCore.Tests/KdTree.fs @@ -20,13 +20,13 @@ type KdTreeTests (output : ITestOutputHelper) = member this.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) + 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 @@ -45,9 +45,9 @@ type KdTreeTests (output : ITestOutputHelper) = member this.test2 () = let pts = [ - Point(1.0f, 1.0f) - Point(1.0f, 2.0f) - Point(1.0f, 3.0f) + Point (1.0f, 1.0f) + Point (1.0f, 2.0f) + Point (1.0f, 3.0f) ] let tree = Tree.BuildTree pts diff --git a/Parasitemia/Tests/ParasitemiaCore.Tests/ParasitemiaCore.Tests.fsproj b/Parasitemia/Tests/ParasitemiaCore.Tests/ParasitemiaCore.Tests.fsproj index dc8cf62..d0cdfa5 100644 --- a/Parasitemia/Tests/ParasitemiaCore.Tests/ParasitemiaCore.Tests.fsproj +++ b/Parasitemia/Tests/ParasitemiaCore.Tests/ParasitemiaCore.Tests.fsproj @@ -54,7 +54,7 @@ - ..\packages\FSharp.Core.4.1.17\lib\net45\FSharp.Core.dll + ..\..\packages\FSharp.Core.4.2.1\lib\net45\FSharp.Core.dll diff --git a/Parasitemia/Tests/ParasitemiaCore.Tests/packages.config b/Parasitemia/Tests/ParasitemiaCore.Tests/packages.config index 63ccec1..6d282b2 100644 --- a/Parasitemia/Tests/ParasitemiaCore.Tests/packages.config +++ b/Parasitemia/Tests/ParasitemiaCore.Tests/packages.config @@ -1,6 +1,6 @@  - + -- 2.45.1 From a6aee26e20f3b24e6b07120cf95a7221dc072778 Mon Sep 17 00:00:00 2001 From: Greg Burri Date: Sun, 8 Oct 2017 11:26:22 +0200 Subject: [PATCH 06/16] Fix an error where the json files can't be created. --- Parasitemia/ParasitemiaCore/AssemblyInfo.fs | 4 ++-- Parasitemia/ParasitemiaUI/AssemblyInfo.fs | 4 ++-- Parasitemia/ParasitemiaUI/Utils.fs | 2 ++ 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/Parasitemia/ParasitemiaCore/AssemblyInfo.fs b/Parasitemia/ParasitemiaCore/AssemblyInfo.fs index 9e190e0..b7c1930 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/ParasitemiaUI/AssemblyInfo.fs b/Parasitemia/ParasitemiaUI/AssemblyInfo.fs index d0754f8..799a4c6 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/Utils.fs b/Parasitemia/ParasitemiaUI/Utils.fs index 384a0d0..97c7425 100644 --- a/Parasitemia/ParasitemiaUI/Utils.fs +++ b/Parasitemia/ParasitemiaUI/Utils.fs @@ -28,6 +28,7 @@ let sensorSizesFilepath = Path.Combine (roamingDir, sensorSizesFilename) let private savePredefinedPPIToFile (predefinedPPI : PredefinedPPI list) = try + Directory.CreateDirectory roamingDir |> ignore use file = new StreamWriter (predefinedPPIFilepath) file.Write (JsonConvert.SerializeObject (predefinedPPI, JsonSerializerSettings (Formatting = Formatting.Indented))) with @@ -36,6 +37,7 @@ let private savePredefinedPPIToFile (predefinedPPI : PredefinedPPI list) = let private saveSensorSizesToFile (sensorSizes : SensorSize list) = try + Directory.CreateDirectory roamingDir |> ignore use file = new StreamWriter (sensorSizesFilepath) file.Write (JsonConvert.SerializeObject (sensorSizes, JsonSerializerSettings (Formatting = Formatting.Indented))) with -- 2.45.1 From d3f9cd7b16d25f49bd8d06394b0f1d4040809fbd Mon Sep 17 00:00:00 2001 From: Greg Burri Date: Wed, 18 Oct 2017 17:00:54 +0200 Subject: [PATCH 07/16] Frame width depends now from the RBC sizes #275 --- Parasitemia/ParasitemiaCore/Analysis.fs | 13 ++- Parasitemia/ParasitemiaCore/AssemblyInfo.fs | 4 +- Parasitemia/ParasitemiaCore/Types.fs | 10 ++- Parasitemia/ParasitemiaUI/Analysis.fs | 20 ++--- Parasitemia/ParasitemiaUI/AssemblyInfo.fs | 4 +- Parasitemia/ParasitemiaUI/Export.fs | 8 +- Parasitemia/ParasitemiaUI/GUI.fs | 72 ++++++++------- .../ParasitemiaUI/ParasitemiaUI.fsproj | 1 + Parasitemia/ParasitemiaUI/PiaZ.fs | 35 +++----- Parasitemia/ParasitemiaUI/Program.fs | 4 +- Parasitemia/ParasitemiaUI/SourceImage.fs | 88 +++++++++++++++++++ Parasitemia/ParasitemiaUI/State.fs | 64 +++----------- Parasitemia/ParasitemiaUI/Types.fs | 27 ------ Parasitemia/ParasitemiaUI/Utils.fs | 4 +- Parasitemia/ParasitemiaUI/XAML/RBCFrame.xaml | 2 +- 15 files changed, 199 insertions(+), 157 deletions(-) create mode 100644 Parasitemia/ParasitemiaUI/SourceImage.fs diff --git a/Parasitemia/ParasitemiaCore/Analysis.fs b/Parasitemia/ParasitemiaCore/Analysis.fs index 8b16c45..19a92fe 100644 --- a/Parasitemia/ParasitemiaCore/Analysis.fs +++ b/Parasitemia/ParasitemiaCore/Analysis.fs @@ -27,7 +27,7 @@ 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) : AnalysisResult option = // To report the progress of this function from 0 to 100. // Return 'None' if the process must be aborted. @@ -153,7 +153,14 @@ let doAnalysis (img : Image) (name : string) (config : Config) (repor IO.saveImg img_float.[0] (buildFileName " - source - blue.png") | _ -> () - return cells + return + { + Cells = cells + RBCSize_μm = config.RBCRadius.μm + RBCSize_px = config.RBCRadius.Pixel + } + + //return cells } /// @@ -164,7 +171,7 @@ let doAnalysis (img : Image) (name : string) (config : Config) (repor /// The first call returning 'false' will cancel the analysis. /// The 'int' parameter correspond to the progression from 0 to 100 /// '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 doMultipleAnalysis (imgs : (string * Config * Image) list) (reportProgress : (int -> bool) option) : (string * AnalysisResult) list option = let report (percent : int) : bool = match reportProgress with | Some f -> f percent diff --git a/Parasitemia/ParasitemiaCore/AssemblyInfo.fs b/Parasitemia/ParasitemiaCore/AssemblyInfo.fs index b7c1930..e5eace6 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/Types.fs b/Parasitemia/ParasitemiaCore/Types.fs index 8f526f9..0cb2768 100644 --- a/Parasitemia/ParasitemiaCore/Types.fs +++ b/Parasitemia/ParasitemiaCore/Types.fs @@ -8,6 +8,7 @@ open Emgu.CV open Emgu.CV.Structure open Const +open UnitsOfMeasure type Points = HashSet @@ -102,4 +103,11 @@ type ResultBuilder () = member this.ReturnFrom (x) = x -let result = ResultBuilder () \ No newline at end of file +let result = ResultBuilder () + +type AnalysisResult = + { + Cells : Cell list + RBCSize_μm : float<μm> + RBCSize_px : float32 + } \ No newline at end of file diff --git a/Parasitemia/ParasitemiaUI/Analysis.fs b/Parasitemia/ParasitemiaUI/Analysis.fs index 4a75a91..4fa5290 100644 --- a/Parasitemia/ParasitemiaUI/Analysis.fs +++ b/Parasitemia/ParasitemiaUI/Analysis.fs @@ -57,13 +57,13 @@ let showWindow (parent : Window) (state : State.State) : bool = let imageSourceSelection = Views.ImageSourceSelection (Tag = srcImg, Margin = Thickness 3.) imageSourceSelection.Tag <- srcImg - imageSourceSelection.txtImageNumber.Text <- string srcImg.num - let height = srcImg.img.Height * width / srcImg.img.Width - imageSourceSelection.imagePreview.Source <- BitmapSourceConvert.ToBitmapSource (srcImg.img.Resize (width, height, Emgu.CV.CvEnum.Inter.Cubic)) - imageSourceSelection.chkSelection.IsChecked <- Nullable (srcImg.dateLastAnalysis.Ticks = 0L) - imageSourceSelection.lblDateLastAnalysis.Content <- if srcImg.dateLastAnalysis.Ticks = 0L then "" else string srcImg.dateLastAnalysis + imageSourceSelection.txtImageNumber.Text <- string srcImg.Num + let height = srcImg.Img.Height * width / srcImg.Img.Width + imageSourceSelection.imagePreview.Source <- BitmapSourceConvert.ToBitmapSource (srcImg.Img.Resize (width, height, Emgu.CV.CvEnum.Inter.Cubic)) + imageSourceSelection.chkSelection.IsChecked <- Nullable (srcImg.DateLastAnalysis.Ticks = 0L) + imageSourceSelection.lblDateLastAnalysis.Content <- if srcImg.DateLastAnalysis.Ticks = 0L then "" else string srcImg.DateLastAnalysis - imageSourceSelection.txtResolution.Text <- if srcImg.dateLastAnalysis.Ticks = 0L then "" else string srcImg.config.Parameters.resolution + imageSourceSelection.txtResolution.Text <- if srcImg.DateLastAnalysis.Ticks = 0L then "" else string srcImg.Config.Parameters.resolution for ppi in Utils.predefinedPPI do let menu = MenuItem () @@ -103,9 +103,9 @@ let showWindow (parent : Window) (state : State.State) : bool = let isChecked = srcImgCtrl.chkSelection.IsChecked match parseAndValidatePPI srcImgCtrl.txtResolution.Text with | Some resolution -> - yield Some (srcImg, isChecked.HasValue && isChecked.Value, { srcImg.config.Parameters with resolution = resolution * 1. }) + 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 + MessageBox.Show (sprintf "No resolution defined for the image number %d" srcImg.Num, "No resolution defined", MessageBoxButton.OK, MessageBoxImage.Information) |> ignore yield None } |> Seq.takeWhile (fun e -> e.IsSome) |> Seq.map (fun e -> e.Value) |> List.ofSeq @@ -123,9 +123,9 @@ let showWindow (parent : Window) (state : State.State) : bool = let imagesToProcess = [ for srcImg, selected, parameters in imagesParameters do - srcImg.config.Parameters <- parameters // Save parameters. + srcImg.Config.Parameters <- parameters // Save parameters. if selected then - yield string srcImg.num, srcImg.config, srcImg.img + yield string srcImg.Num, srcImg.Config, srcImg.Img ] if imagesToProcess.IsEmpty then diff --git a/Parasitemia/ParasitemiaUI/AssemblyInfo.fs b/Parasitemia/ParasitemiaUI/AssemblyInfo.fs index 799a4c6..3fdcd4b 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/Export.fs b/Parasitemia/ParasitemiaUI/Export.fs index 52638d3..805bcd7 100644 --- a/Parasitemia/ParasitemiaUI/Export.fs +++ b/Parasitemia/ParasitemiaUI/Export.fs @@ -17,8 +17,8 @@ let exportResults (state : State) (filePath : string) = for srcImg in state.SourceImages do fprintfn writer "" - fprintfn writer "Image name: %s" srcImg.name - fprintfn writer "Parasitemia: %s" (Utils.percentText (state.ImageParasitemia srcImg)) - fprintfn writer "Added infected erythrocyte: %s %s" (state.ImageNbManuallyChangedRBCStr srcImg true) (state.ImageManuallyChangedRBCStr srcImg true) - fprintfn writer "Removed infected erythrocyte: %s %s" (state.ImageNbManuallyChangedRBCStr srcImg false) (state.ImageManuallyChangedRBCStr srcImg false) + fprintfn writer "Image name: %s" srcImg.Name + fprintfn writer "Parasitemia: %s" (Utils.percentText srcImg.ImageParasitemia) + fprintfn writer "Added infected erythrocyte: %s %s" (srcImg.ImageNbManuallyChangedRBCStr true) (srcImg.ImageManuallyChangedRBCStr true) + fprintfn writer "Removed infected erythrocyte: %s %s" (srcImg.ImageNbManuallyChangedRBCStr false) (srcImg.ImageManuallyChangedRBCStr false) () \ No newline at end of file diff --git a/Parasitemia/ParasitemiaUI/GUI.fs b/Parasitemia/ParasitemiaUI/GUI.fs index 46171a7..4eee1e5 100644 --- a/Parasitemia/ParasitemiaUI/GUI.fs +++ b/Parasitemia/ParasitemiaUI/GUI.fs @@ -54,14 +54,21 @@ let run (defaultConfig : Config) (fileToOpen : string option) = frame.manuallyAdded.Fill <- color frame.border.Stroke <- color - let RBCFrameFromExisting (srcImg : SourceImage) (rbc : RBC) (frame : Views.RBCFrame) : Views.RBCFrame = + let frameStrokeThickness (averageRBCSize : float) = + max 1. (averageRBCSize / 60.) + + let frameFontSize (averageRBCSize : float) = + max 1. (averageRBCSize / 8.) + + let RBCFrameFromExisting (srcImg : SourceImage) (rbc : RBC) (frame : Views.RBCFrame) (frameThickness : float) (fontSize : float) : Views.RBCFrame = frame.Visibility <- Visibility.Visible - frame.Height <- rbc.size.Height frame.Width <- rbc.size.Width + frame.Height <- rbc.size.Height frame.Tag <- rbc setRBCFrameStyle srcImg rbc frame - frame.border.StrokeThickness <- 1. + frame.border.StrokeThickness <- frameThickness frame.txtRBCNumber.Text <- string rbc.num + frame.txtRBCNumber.FontSize <- fontSize frame let updateDocumentStatus () = @@ -70,6 +77,8 @@ let run (defaultConfig : Config) (fileToOpen : string option) = let statusMessageTimer = Threading.DispatcherTimer () statusMessageTimer.Tick.AddHandler (fun obj args -> statusMessageTimer.Stop (); win.txtMessageStatus.Text <- "") statusMessageTimer.Interval <- TimeSpan (0, 0, 2) + + // To show a use message while a short period of time. let displayStatusMessage (message : string) = win.txtMessageStatus.Text <- message statusMessageTimer.Stop () @@ -78,17 +87,16 @@ let run (defaultConfig : Config) (fileToOpen : string option) = let highlightRBCFrame (frame : Views.RBCFrame) (highlight : bool) = let rbc = frame.Tag :?> RBC if highlight then - frame.border.StrokeThickness <- 3. + frame.border.StrokeThickness <- 3. * frame.border.StrokeThickness if not rbc.infected && not rbc.setManually && not displayHealthy then frame.Opacity <- 1. else - frame.border.StrokeThickness <- 1. + frame.border.StrokeThickness <- frame.border.StrokeThickness / 3. if not rbc.infected && not rbc.setManually && not displayHealthy then frame.Opacity <- 0. let zoomToRBC (rbc : RBC) = win.scrollViewCurrentImage.ScrollToHorizontalOffset (rbc.center.X * currentScale - win.scrollViewCurrentImage.ViewportWidth / 2. + win.borderCurrentImage.BorderThickness.Left) win.scrollViewCurrentImage.ScrollToVerticalOffset (rbc.center.Y * currentScale - win.scrollViewCurrentImage.ViewportHeight / 2. + win.borderCurrentImage.BorderThickness.Top) - let txtImageName_TextChanged = TextChangedEventHandler (fun obj args -> state.CurrentImage |> Option.iter (fun srcImg -> state.SetName srcImg win.txtImageName.Text)) @@ -101,24 +109,24 @@ let run (defaultConfig : Config) (fileToOpen : string option) = match state.CurrentImage with | Some srcImg -> win.gridImageInformation.Visibility <- Visibility.Visible - win.txtImageName.Text <- srcImg.name + win.txtImageName.Text <- srcImg.Name win.txtImageName.TextChanged.AddHandler txtImageName_TextChanged // The left part. - let parasitemiaStr = Utils.percentText (state.ImageParasitemia srcImg) + let parasitemiaStr = Utils.percentText srcImg.ImageParasitemia win.txtImageInformation1.Inlines.Add (Documents.Run ("Parasitemia: ", FontWeight = FontWeights.Bold)) win.txtImageInformation1.Inlines.Add parasitemiaStr win.txtImageInformation1.Inlines.Add (Documents.LineBreak ()) win.txtImageInformation1.Inlines.Add (Documents.Run ("Last analysis: ", FontWeight = FontWeights.Bold)) - win.txtImageInformation1.Inlines.Add (Documents.Run (if srcImg.dateLastAnalysis.Ticks = 0L then "" else string (srcImg.dateLastAnalysis.ToLocalTime()))) + win.txtImageInformation1.Inlines.Add (Documents.Run (if srcImg.DateLastAnalysis.Ticks = 0L then "" else string (srcImg.DateLastAnalysis.ToLocalTime()))) // The right part part. win.txtImageInformation2.Inlines.Add (Documents.Run ("Added infected erythrocyte: ", FontWeight = FontWeights.Bold)) - win.txtImageInformation2.Inlines.Add (Documents.Run ((state.ImageNbManuallyChangedRBCStr srcImg true) + " " + (state.ImageManuallyChangedRBCStr srcImg true))) + win.txtImageInformation2.Inlines.Add (Documents.Run ((srcImg.ImageNbManuallyChangedRBCStr true) + " " + (srcImg.ImageManuallyChangedRBCStr true))) win.txtImageInformation2.Inlines.Add (Documents.LineBreak ()) win.txtImageInformation2.Inlines.Add (Documents.Run ("Removed infected erythrocyte: ", FontWeight = FontWeights.Bold)) - win.txtImageInformation2.Inlines.Add (Documents.Run ((state.ImageNbManuallyChangedRBCStr srcImg false) + " " + (state.ImageManuallyChangedRBCStr srcImg false))) + win.txtImageInformation2.Inlines.Add (Documents.Run ((srcImg.ImageNbManuallyChangedRBCStr false) + " " + (srcImg.ImageManuallyChangedRBCStr false))) | _ -> win.gridImageInformation.Visibility <- Visibility.Hidden @@ -175,8 +183,8 @@ let run (defaultConfig : Config) (fileToOpen : string option) = updateCurrentImageInformation () updateGlobalParasitemia () - and RBCFrame (srcImg : SourceImage) (rbc : RBC) : Views.RBCFrame = - let frame = RBCFrameFromExisting srcImg rbc (Views.RBCFrame ()) + and RBCFrame (srcImg : SourceImage) (rbc : RBC) (frameThickness : float) (fontSize : float) : Views.RBCFrame = + let frame = RBCFrameFromExisting srcImg rbc (Views.RBCFrame ()) frameThickness fontSize frame.SetValue (Panel.ZIndexProperty, Int32.MaxValue - rbc.num) // To be sure the frame.menuRBCSetAsHealthy.Click.AddHandler (fun obj args -> setAsInfected srcImg (frame.Tag :?> RBC) false) frame.menuRBCSetAsInfected.Click.AddHandler (fun obj args -> setAsInfected srcImg (frame.Tag :?> RBC) true) @@ -198,12 +206,12 @@ let run (defaultConfig : Config) (fileToOpen : string option) = match state.CurrentImage with | Some srcImg -> let mutable currentPreview = 0 - for rbc in srcImg.rbcs |> List.filter (fun rbc -> displayHealthy || rbc.infected) do + for rbc in srcImg.RBCs |> List.filter (fun rbc -> displayHealthy || rbc.infected) do let previewInfected = if currentPreview < win.stackRBC.Children.Count then - RBCFrameFromExisting srcImg rbc (win.stackRBC.Children.[currentPreview] :?> Views.RBCFrame) + RBCFrameFromExisting srcImg rbc (win.stackRBC.Children.[currentPreview] :?> Views.RBCFrame) 1. 12. else - let f = RBCFrame srcImg rbc + let f = RBCFrame srcImg rbc 1. 12. f.MouseLeftButtonUp.AddHandler (fun obj args -> zoomToRBC (f.Tag :?> RBC)) win.stackRBC.Children.Add f |> ignore f @@ -212,7 +220,7 @@ let run (defaultConfig : Config) (fileToOpen : string option) = previewInfected.Height <- win.stackRBC.ActualHeight previewInfected.Width <- win.stackRBC.ActualHeight * rbc.size.Width / rbc.size.Height - previewInfected.border.Fill <- ImageBrush (BitmapSourceConvert.ToBitmapSource (extractRBCPreview srcImg.img rbc)) + previewInfected.border.Fill <- ImageBrush (BitmapSourceConvert.ToBitmapSource (extractRBCPreview srcImg.Img rbc)) win.stackRBC.Children.RemoveRange (currentPreview, win.stackRBC.Children.Count - currentPreview) | _ -> () @@ -223,12 +231,14 @@ let run (defaultConfig : Config) (fileToOpen : string option) = match state.CurrentImage with | Some srcImg -> let mutable currentCanvas = 0 - for rbc in srcImg.rbcs do + let strokeThickness = frameStrokeThickness srcImg.AverageRBCSize + let fontSize = frameFontSize srcImg.AverageRBCSize + for rbc in srcImg.RBCs do let frame = if currentCanvas < win.canvasCurrentImage.Children.Count then - RBCFrameFromExisting srcImg rbc (win.canvasCurrentImage.Children.[currentCanvas] :?> Views.RBCFrame) + RBCFrameFromExisting srcImg rbc (win.canvasCurrentImage.Children.[currentCanvas] :?> Views.RBCFrame) strokeThickness fontSize else - let f = RBCFrame srcImg rbc + let f = RBCFrame srcImg rbc strokeThickness fontSize win.canvasCurrentImage.Children.Add f |> ignore f @@ -296,9 +306,9 @@ let run (defaultConfig : Config) (fileToOpen : string option) = |> Seq.cast |> Seq.iter (fun preview -> preview.border.BorderThickness <- Thickness (if preview.Tag = (srcImg :> Object) then 3. else 0.)) - win.canvasCurrentImage.Height <- float srcImg.img.Height - win.canvasCurrentImage.Width <- float srcImg.img.Width - win.canvasCurrentImage.Background <- ImageBrush (BitmapSourceConvert.ToBitmapSource (srcImg.img)) + win.canvasCurrentImage.Height <- float srcImg.Img.Height + win.canvasCurrentImage.Width <- float srcImg.Img.Width + win.canvasCurrentImage.Background <- ImageBrush (BitmapSourceConvert.ToBitmapSource (srcImg.Img)) updateRBCFramesCurrent () updateRBCFramesPreview () @@ -332,14 +342,14 @@ let run (defaultConfig : Config) (fileToOpen : string option) = updateGlobalParasitemia () // Update image numbers. - win.stackPreviews.Children |> Seq.cast |> Seq.iter (fun imgPreview -> imgPreview.txtImageNumber.Text <- (imgPreview.Tag :?> SourceImage).num.ToString ()) + win.stackPreviews.Children |> Seq.cast |> Seq.iter (fun imgPreview -> imgPreview.txtImageNumber.Text <- (imgPreview.Tag :?> SourceImage).Num.ToString ()) ) imgCtrl.Tag <- srcImg - imgCtrl.txtImageNumber.Text <- string srcImg.num + imgCtrl.txtImageNumber.Text <- string srcImg.Num let width = 200 - let height = srcImg.img.Height * width / srcImg.img.Width - imgCtrl.imagePreview.Source <- BitmapSourceConvert.ToBitmapSource (srcImg.img.Resize (width, height, Emgu.CV.CvEnum.Inter.Cubic)) + let height = srcImg.Img.Height * width / srcImg.Img.Width + imgCtrl.imagePreview.Source <- BitmapSourceConvert.ToBitmapSource (srcImg.Img.Resize (width, height, Emgu.CV.CvEnum.Inter.Cubic)) win.stackPreviews.Children.Add imgCtrl |> ignore // Zoom to a mouse position into the control 'imgCtrl'. @@ -512,7 +522,7 @@ let run (defaultConfig : Config) (fileToOpen : string option) = let currentImageScaleTransform = ScaleTransform () win.canvasCurrentImage.LayoutTransform <- currentImageScaleTransform win.borderCurrentImage.PreviewMouseWheel.AddHandler ( - fun obj args -> + fun _obj args -> let scaleFactor = if args.Delta > 0 then 2.0 else 0.5 if scaleFactor > 1. && currentScale < maxScale || scaleFactor < 1. && currentScale > minScale then let previousScale = currentScale @@ -538,7 +548,7 @@ let run (defaultConfig : Config) (fileToOpen : string option) = let mutable scrollStartOffsetX = 0. let mutable scrollStartOffsetY = 0. win.borderCurrentImage.PreviewMouseLeftButtonDown.AddHandler ( - fun obj args -> + fun _obj args -> scrollStartPosition <- args.GetPosition win.scrollViewCurrentImage scrollStartOffsetX <- win.scrollViewCurrentImage.HorizontalOffset scrollStartOffsetY <- win.scrollViewCurrentImage.VerticalOffset @@ -548,7 +558,7 @@ let run (defaultConfig : Config) (fileToOpen : string option) = ) win.borderCurrentImage.PreviewMouseMove.AddHandler ( - fun obj args -> + fun _obj args -> if win.borderCurrentImage.IsMouseCaptured then let position = args.GetPosition win.scrollViewCurrentImage let deltaX = scrollStartPosition.X - position.X @@ -560,7 +570,7 @@ let run (defaultConfig : Config) (fileToOpen : string option) = ) win.borderCurrentImage.PreviewMouseLeftButtonUp.AddHandler ( - fun obj args -> + fun _obj args -> if win.borderCurrentImage.IsMouseCaptured then win.borderCurrentImage.Cursor <- Input.Cursors.Arrow win.borderCurrentImage.ReleaseMouseCapture () diff --git a/Parasitemia/ParasitemiaUI/ParasitemiaUI.fsproj b/Parasitemia/ParasitemiaUI/ParasitemiaUI.fsproj index 0324219..b9ad387 100644 --- a/Parasitemia/ParasitemiaUI/ParasitemiaUI.fsproj +++ b/Parasitemia/ParasitemiaUI/ParasitemiaUI.fsproj @@ -86,6 +86,7 @@ + diff --git a/Parasitemia/ParasitemiaUI/PiaZ.fs b/Parasitemia/ParasitemiaUI/PiaZ.fs index 92ebbe0..df72212 100644 --- a/Parasitemia/ParasitemiaUI/PiaZ.fs +++ b/Parasitemia/ParasitemiaUI/PiaZ.fs @@ -68,23 +68,23 @@ let save (filePath : string) (data : DocumentData) = // Write each images and the associated information. for srcImg in data.images do - let imgFilename = (string srcImg.num) + imageExtension + let imgFilename = (string srcImg.Num) + imageExtension let imgEntry = file.CreateEntry (imgFilename, CompressionLevel.NoCompression) // FIXME: It seems a compression is applied to this file despite of the 'NoCompression' flag. - srcImg.img.ToBitmap().Save (imgEntry.Open (), System.Drawing.Imaging.ImageFormat.Tiff) + srcImg.Img.ToBitmap().Save (imgEntry.Open (), System.Drawing.Imaging.ImageFormat.Tiff) let imgJSONEntry = file.CreateEntry (imgFilename + ".json", CompressionLevel.Fastest) use imgJSONFileWriter = new StreamWriter (imgJSONEntry.Open ()) imgJSONFileWriter.Write ( JsonConvert.SerializeObject ( { - num = srcImg.num - name = srcImg.name - RBCRadius = srcImg.config.RBCRadius.Pixel - parameters = srcImg.config.Parameters - dateLastAnalysis = srcImg.dateLastAnalysis - rbcs = srcImg.rbcs - healthyRBCBrightness = srcImg.healthyRBCBrightness - infectedRBCBrightness = srcImg.infectedRBCBrightness + num = srcImg.Num + name = srcImg.Name + RBCRadius = srcImg.Config.RBCRadius.Pixel + parameters = srcImg.Config.Parameters + dateLastAnalysis = srcImg.DateLastAnalysis + rbcs = srcImg.RBCs + healthyRBCBrightness = srcImg.HealthyRBCBrightness + infectedRBCBrightness = srcImg.InfectedRBCBrightness } ) ) @@ -93,7 +93,7 @@ let updateDocumentData (fromVersion : int) (toVersion : int) (data : DocumentDat for v in fromVersion + 1 .. toVersion do match v with | 1 -> // Version 0 -> 1 : set initial brightness for rbc. - data.images |> List.iter (fun i -> i.healthyRBCBrightness <- 1.f; i.infectedRBCBrightness <- 1.f) + data.images |> List.iter (fun i -> i.HealthyRBCBrightness <- 1.f; i.InfectedRBCBrightness <- 1.f) | _ -> () data @@ -132,16 +132,7 @@ let load (filePath : string) (defaultConfig : ParasitemiaCore.Config.Config) : D } config.SetRBCRadius imgInfo.RBCRadius - yield - { - num = imgNum - name = imgInfo.name - config = config - dateLastAnalysis = imgInfo.dateLastAnalysis - img = img - rbcs = imgInfo.rbcs - healthyRBCBrightness = imgInfo.healthyRBCBrightness - infectedRBCBrightness = imgInfo.infectedRBCBrightness - } + + yield SourceImage (imgNum, imgInfo.name, config, imgInfo.dateLastAnalysis, img, imgInfo.rbcs, HealthyRBCBrightness = imgInfo.healthyRBCBrightness, InfectedRBCBrightness = imgInfo.infectedRBCBrightness) ] } \ No newline at end of file diff --git a/Parasitemia/ParasitemiaUI/Program.fs b/Parasitemia/ParasitemiaUI/Program.fs index b7b4202..87e1c26 100644 --- a/Parasitemia/ParasitemiaUI/Program.fs +++ b/Parasitemia/ParasitemiaUI/Program.fs @@ -99,10 +99,10 @@ let main args = fun () -> match ParasitemiaCore.Analysis.doMultipleAnalysis images None with | Some results -> - for id, cells in results do + for id, result in results do let config, img = images |> List.pick (fun (id', config', img') -> if id' = id then Some (config', img') else None) img.Dispose () - let total, infected = countCells cells + let total, infected = countCells result.Cells fprintf resultFile "File: %s %d %d %.2f (diameter: %O)\n" id total infected (100. * (float infected) / (float total)) config.RBCRadius | None -> fprintf resultFile "Analysis aborted" diff --git a/Parasitemia/ParasitemiaUI/SourceImage.fs b/Parasitemia/ParasitemiaUI/SourceImage.fs new file mode 100644 index 0000000..2330227 --- /dev/null +++ b/Parasitemia/ParasitemiaUI/SourceImage.fs @@ -0,0 +1,88 @@ +namespace ParasitemiaUI + +open System +open System.Windows +open System.Windows.Media + +open Emgu.CV +open Emgu.CV.Structure + +open Types + +type SourceImage (num : int, name : string, config : ParasitemiaCore.Config.Config, dateLastAnalysis : DateTime, img : Image, rbcs : RBC list) = + let mutable num = num + let mutable name = name + let mutable config = config + let mutable dateLastAnalysis = dateLastAnalysis // UTC. + let img = img + let mutable rbcs = rbcs + let mutable healthyRBCBrightness = 1.f + let mutable infectedRBCBrightness = 1.f + + let mutable averageRBCSize = 1. + + let healthyRBColor = Color.FromRgb (255uy, 255uy, 0uy) // Yellow-green. + let infectedRBColor = Color.FromRgb (255uy, 0uy, 40uy) // Red with a bit of blue. + + let updateAverageRBCSize () = + averageRBCSize <- + rbcs + |> List.collect (fun rbc -> [ rbc.size.Width; rbc.size.Height ]) + |> List.average + + do + updateAverageRBCSize () + + member this.Num with get () = num and set value = num <- value + + member this.Name with get () = name and set value = name <- value + + member this.Config = config + + member this.DateLastAnalysis with get () = dateLastAnalysis and set value = dateLastAnalysis <- value + + member this.Img = img + + member this.RBCs + with get () = rbcs + and set value = rbcs <- value + + member this.ImageParasitemia : int * int = + List.length rbcs, + rbcs |> List.fold (fun nbInfected rbc -> if rbc.infected then nbInfected + 1 else nbInfected) 0 + + member this.ImageNbManuallyChangedRBC (setAsInfected : bool) : int * int = + List.length rbcs, + rbcs |> List.fold (fun nb rbc -> if rbc.setManually && rbc.infected = setAsInfected then nb + 1 else nb) 0 + + member this.ImageNbManuallyChangedRBCStr (setAsInfected : bool) : string = + Utils.percentText (this.ImageNbManuallyChangedRBC setAsInfected) + + member this.ImageManuallyChangedRBC (setAsInfected : bool) : int seq = + query { + for rbc in rbcs do + where (rbc.setManually && rbc.infected = setAsInfected) + select rbc.num + } + + member this.ImageManuallyChangedRBCStr (setAsInfected : bool) : string = + let listStr = Utils.listAsStr <| this.ImageManuallyChangedRBC setAsInfected + if listStr = "" then + "" + else + "[" + listStr + "]" + + member this.HealthyRBCBrightness with get () = healthyRBCBrightness and set value = healthyRBCBrightness <- value + member this.InfectedRBCBrightness with get () = infectedRBCBrightness and set value = infectedRBCBrightness <- value + + member this.HealthyRBCColor : SolidColorBrush = + let mutable color = healthyRBColor * healthyRBCBrightness + color.A <- 255uy + SolidColorBrush (color) + + member this.InfectedRBCColor : SolidColorBrush = + let mutable color = infectedRBColor * infectedRBCBrightness + color.A <- 255uy + SolidColorBrush (color) + + member this.AverageRBCSize = averageRBCSize \ No newline at end of file diff --git a/Parasitemia/ParasitemiaUI/State.fs b/Parasitemia/ParasitemiaUI/State.fs index 3355a42..129e6a6 100644 --- a/Parasitemia/ParasitemiaUI/State.fs +++ b/Parasitemia/ParasitemiaUI/State.fs @@ -25,36 +25,11 @@ type State (defaultConfig : ParasitemiaCore.Config.Config) = alteredSinceLastSave <- true patientID <- id - member this.ImageParasitemia (srcImg : SourceImage) : int * int = - List.length srcImg.rbcs, - srcImg.rbcs |> List.fold (fun nbInfected rbc -> if rbc.infected then nbInfected + 1 else nbInfected) 0 - - member this.ImageNbManuallyChangedRBC (srcImg : SourceImage) (setAsInfected : bool) : int * int = - List.length srcImg.rbcs, - srcImg.rbcs |> List.fold (fun nb rbc -> if rbc.setManually && rbc.infected = setAsInfected then nb + 1 else nb) 0 - - member this.ImageNbManuallyChangedRBCStr (srcImg : SourceImage) (setAsInfected : bool) : string = - Utils.percentText (this.ImageNbManuallyChangedRBC srcImg setAsInfected) - - member this.ImageManuallyChangedRBC (srcImg : SourceImage) (setAsInfected : bool) : int seq = - query { - for rbc in srcImg.rbcs do - where (rbc.setManually && rbc.infected = setAsInfected) - select rbc.num - } - - member this.ImageManuallyChangedRBCStr (srcImg : SourceImage) (setAsInfected : bool) : string = - let listStr = Utils.listAsStr <| this.ImageManuallyChangedRBC srcImg setAsInfected - if listStr = "" then - "" - else - "[" + listStr + "]" - member this.GlobalParasitemia : int * int = sourceImages |> Seq.fold ( fun (nbTotal, nbTotalInfected) srcImg -> - let nb, nbInfected = this.ImageParasitemia srcImg + let nb, nbInfected = srcImg.ImageParasitemia nbTotal + nb, nbTotalInfected + nbInfected ) (0, 0) @@ -89,18 +64,7 @@ type State (defaultConfig : ParasitemiaCore.Config.Config) = /// /// If the image cannot be read member this.AddSourceImage (filePath : string) (defaultConfig : ParasitemiaCore.Config.Config) : SourceImage = - let srcImg = - { - num = sourceImages.Count + 1 - name = System.IO.FileInfo(filePath).Name - config = defaultConfig.Copy () - dateLastAnalysis = DateTime (0L) - rbcs = [] - img = new Image (filePath) - healthyRBCBrightness = 1.f - infectedRBCBrightness = 1.f - } - + let srcImg = SourceImage (sourceImages.Count + 1, System.IO.FileInfo(filePath).Name, defaultConfig.Copy (), DateTime (0L), new Image (filePath), []) sourceImages.Add srcImg if sourceImages.Count = 1 then this.CurrentImage <- Some sourceImages.[0] @@ -118,24 +82,24 @@ type State (defaultConfig : ParasitemiaCore.Config.Config) = if isCurrent then this.CurrentImage <- if sourceImages.Count > 0 then Some sourceImages.[0] else None // Re-numbered the images. - sourceImages |> Seq.iteri (fun i srcImg -> srcImg.num <- i + 1) + sourceImages |> Seq.iteri (fun i srcImg -> srcImg.Num <- i + 1) member this.SetName (srcImg : SourceImage) (name : string) = - if name <> srcImg.name then - srcImg.name <- name + if name <> srcImg.Name then + srcImg.Name <- name alteredSinceLastSave <- true - member this.SetResult (imgNum : int) (cells : ParasitemiaCore.Types.Cell list) = - let sourceImage = sourceImages.Find (fun srcImg -> srcImg.num = imgNum) + member this.SetResult (imgNum : int) (result : ParasitemiaCore.Types.AnalysisResult) = + let sourceImage = sourceImages.Find (fun srcImg -> srcImg.Num = imgNum) - let w = sourceImage.img.Width - let h = sourceImage.img.Height + let w = sourceImage.Img.Width + let h = sourceImage.Img.Height - sourceImage.dateLastAnalysis <- DateTime.UtcNow + sourceImage.DateLastAnalysis <- DateTime.UtcNow // To match with previously manually altered RBC. - let manuallyAlteredPreviousRBCS = sourceImage.rbcs |> List.filter (fun rbc -> rbc.setManually) - let tolerance = (float sourceImage.config.RBCRadius.Pixel) * 0.5 // +/-. + let manuallyAlteredPreviousRBCS = sourceImage.RBCs |> List.filter (fun rbc -> rbc.setManually) + let tolerance = (float sourceImage.Config.RBCRadius.Pixel) * 0.5 // +/-. let getPreviousManuallyAlteredRBC (center : Point) : RBC option = manuallyAlteredPreviousRBCS |> List.tryFind ( @@ -146,8 +110,8 @@ type State (defaultConfig : ParasitemiaCore.Config.Config) = rbc.center.Y < center.Y + tolerance ) - sourceImage.rbcs <- - cells + sourceImage.RBCs <- + result.Cells |> List.filter (fun cell -> match cell.cellClass with ParasitemiaCore.Types.HealthyRBC | ParasitemiaCore.Types.InfectedRBC -> true | _ -> false ) |> List.sortByDescending (fun cell -> cell.nucleusArea, (w - cell.center.X) + (h - cell.center.Y)) |> List.mapi ( diff --git a/Parasitemia/ParasitemiaUI/Types.fs b/Parasitemia/ParasitemiaUI/Types.fs index ada2a1f..3a3cf56 100644 --- a/Parasitemia/ParasitemiaUI/Types.fs +++ b/Parasitemia/ParasitemiaUI/Types.fs @@ -11,9 +11,6 @@ open Newtonsoft.Json open ParasitemiaCore.UnitsOfMeasure -let healthyRBColor = Color.FromRgb (255uy, 255uy, 0uy) // Yellow-green. -let infectedRBColor = Color.FromRgb (255uy, 0uy, 40uy) // Red with a bit of blue. - type RBC = { num : int @@ -29,30 +26,6 @@ type RBC = infectedArea : int } -type SourceImage = - { - mutable num : int - mutable name : string - - mutable config : ParasitemiaCore.Config.Config - mutable dateLastAnalysis : DateTime // UTC. - img : Image - mutable rbcs : RBC list - - mutable healthyRBCBrightness : float32 - mutable infectedRBCBrightness : float32 - } - with - member this.HealthyRBCColor : SolidColorBrush = - let mutable color = healthyRBColor * this.healthyRBCBrightness - color.A <- 255uy - SolidColorBrush (color) - - member this.InfectedRBCColor : SolidColorBrush = - let mutable color = infectedRBColor * this.infectedRBCBrightness - color.A <- 255uy - SolidColorBrush (color) - type PredefinedPPI = { ppi : int diff --git a/Parasitemia/ParasitemiaUI/Utils.fs b/Parasitemia/ParasitemiaUI/Utils.fs index 97c7425..8635bcd 100644 --- a/Parasitemia/ParasitemiaUI/Utils.fs +++ b/Parasitemia/ParasitemiaUI/Utils.fs @@ -49,7 +49,7 @@ let predefinedPPI : PredefinedPPI list = use file = new StreamReader (predefinedPPIFilepath) JsonConvert.DeserializeObject (file.ReadToEnd ()) with - | ex -> + | _ex -> savePredefinedPPIToFile defaultPredefinedPPI defaultPredefinedPPI @@ -58,6 +58,6 @@ let sensorSizes : SensorSize list = use file = new StreamReader (sensorSizesFilepath) JsonConvert.DeserializeObject (file.ReadToEnd ()) with - | ex -> + | _ex -> saveSensorSizesToFile defaultSensorSizes defaultSensorSizes diff --git a/Parasitemia/ParasitemiaUI/XAML/RBCFrame.xaml b/Parasitemia/ParasitemiaUI/XAML/RBCFrame.xaml index 124dc6f..41c1fc6 100644 --- a/Parasitemia/ParasitemiaUI/XAML/RBCFrame.xaml +++ b/Parasitemia/ParasitemiaUI/XAML/RBCFrame.xaml @@ -16,7 +16,7 @@ - + \ No newline at end of file -- 2.45.1 From f4e3404aaa28beedc9040c0b924c3a8162472b43 Mon Sep 17 00:00:00 2001 From: Greg Burri Date: Fri, 20 Oct 2017 13:58:12 +0200 Subject: [PATCH 08/16] FIX #279 A warning is displayed when the RBC diameter found do not match the image resolution. --- Parasitemia/ParasitemiaCore/Analysis.fs | 10 +++++++++- Parasitemia/ParasitemiaUI/Analysis.fs | 1 + 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/Parasitemia/ParasitemiaCore/Analysis.fs b/Parasitemia/ParasitemiaCore/Analysis.fs index 19a92fe..ed908e2 100644 --- a/Parasitemia/ParasitemiaCore/Analysis.fs +++ b/Parasitemia/ParasitemiaCore/Analysis.fs @@ -17,6 +17,8 @@ open ImgTools open Config open Types +let warningRatioDifferenceRBCDiameter = 1.2 + /// /// Analyze the given image and detect reb blood cell (RBC) in it. /// @@ -104,6 +106,12 @@ let doAnalysis (img : Image) (name : string) (config : Config) (repor let! cells = logTimeWithName "Classifier" (fun () -> reportWithVal 100 (Classifier.findCells prunedEllipses parasites img.Width img.Height config)) + do + if config.RBCRadiusByResolution.μm / config.RBCRadius.μm > warningRatioDifferenceRBCDiameter then + logWithName (sprintf "Warning: erythrocyte diameter found is too low compared to the nominal erythrocyte diameter, maybe the PPI image resolution is lesser than %.0f ppi" config.Parameters.resolution) + elif config.RBCRadius.μm / config.RBCRadiusByResolution.μm > warningRatioDifferenceRBCDiameter then + logWithName (sprintf "Warning: erythrocyte diameter found is too high compared to the nominal erythrocyte diameter, maybe the PPI image resolution is higher than %.0f" config.Parameters.resolution) + logWithName "Analysis finished" do @@ -153,7 +161,7 @@ let doAnalysis (img : Image) (name : string) (config : Config) (repor IO.saveImg img_float.[0] (buildFileName " - source - blue.png") | _ -> () - return + return { Cells = cells RBCSize_μm = config.RBCRadius.μm diff --git a/Parasitemia/ParasitemiaUI/Analysis.fs b/Parasitemia/ParasitemiaUI/Analysis.fs index 4fa5290..13ce48b 100644 --- a/Parasitemia/ParasitemiaUI/Analysis.fs +++ b/Parasitemia/ParasitemiaUI/Analysis.fs @@ -134,6 +134,7 @@ let showWindow (parent : Window) (state : State.State) : bool = win.stackSourceImagesSelection.IsEnabled <- false analysisPerformed <- false win.butStart.IsEnabled <- false + win.textLog.Text <- "" win.butClose.Content <- "Abort" async { -- 2.45.1 From 77cb62e17c3e8e7de65b8dab6769be84ca794b92 Mon Sep 17 00:00:00 2001 From: Greg Burri Date: Sat, 21 Oct 2017 14:00:39 +0200 Subject: [PATCH 09/16] FIX #277 Add some information in the GUI to see the possible command arguments. --- .../ParasitemiaCore/ParasitemiaCore.fsproj | 8 ++++---- Parasitemia/ParasitemiaCore/packages.config | 8 ++++---- .../ParasitemiaUI/CommandLineArguments.fs | 20 +++++++++++++++++++ Parasitemia/ParasitemiaUI/GUI.fs | 2 ++ .../ParasitemiaUI/ParasitemiaUI.fsproj | 7 +++++-- Parasitemia/ParasitemiaUI/Program.fs | 19 ++++-------------- Parasitemia/ParasitemiaUI/Utils.fs | 15 ++++++++++++++ .../XAML/CommandLineArgumentsWindow.xaml | 17 ++++++++++++++++ .../XAML/CommandLineArgumentsWindow.xaml.fs | 6 ++++++ .../ParasitemiaUI/XAML/MainWindow.xaml | 1 + Parasitemia/ParasitemiaUI/packages.config | 4 ++-- .../ParasitemiaCore.Tests.fsproj | 18 ++++++++++------- .../ParasitemiaCore.Tests/packages.config | 17 ++++++++-------- 13 files changed, 100 insertions(+), 42 deletions(-) create mode 100644 Parasitemia/ParasitemiaUI/CommandLineArguments.fs create mode 100644 Parasitemia/ParasitemiaUI/XAML/CommandLineArgumentsWindow.xaml create mode 100644 Parasitemia/ParasitemiaUI/XAML/CommandLineArgumentsWindow.xaml.fs diff --git a/Parasitemia/ParasitemiaCore/ParasitemiaCore.fsproj b/Parasitemia/ParasitemiaCore/ParasitemiaCore.fsproj index c841f71..f6262e2 100644 --- a/Parasitemia/ParasitemiaCore/ParasitemiaCore.fsproj +++ b/Parasitemia/ParasitemiaCore/ParasitemiaCore.fsproj @@ -91,13 +91,13 @@ True - ..\packages\FSharp.Core.4.2.1\lib\net45\FSharp.Core.dll + ..\packages\FSharp.Core.4.2.3\lib\net45\FSharp.Core.dll - ..\packages\MathNet.Numerics.3.19.0\lib\net40\MathNet.Numerics.dll + ..\packages\MathNet.Numerics.3.20.0\lib\net40\MathNet.Numerics.dll - ..\packages\MathNet.Numerics.FSharp.3.19.0\lib\net40\MathNet.Numerics.FSharp.dll + ..\packages\MathNet.Numerics.FSharp.3.20.0\lib\net40\MathNet.Numerics.FSharp.dll @@ -111,7 +111,7 @@ - ..\packages\System.ValueTuple.4.3.1\lib\netstandard1.0\System.ValueTuple.dll + ..\packages\System.ValueTuple.4.4.0\lib\netstandard1.0\System.ValueTuple.dll diff --git a/Parasitemia/ParasitemiaCore/packages.config b/Parasitemia/ParasitemiaCore/packages.config index d0073d5..87c6b51 100644 --- a/Parasitemia/ParasitemiaCore/packages.config +++ b/Parasitemia/ParasitemiaCore/packages.config @@ -2,11 +2,11 @@ - - - + + + - + \ No newline at end of file diff --git a/Parasitemia/ParasitemiaUI/CommandLineArguments.fs b/Parasitemia/ParasitemiaUI/CommandLineArguments.fs new file mode 100644 index 0000000..8025ebf --- /dev/null +++ b/Parasitemia/ParasitemiaUI/CommandLineArguments.fs @@ -0,0 +1,20 @@ +module ParasitemiaUI.CommandLineArguments + +open System +open System.Windows +open System.Windows.Controls +open System.Diagnostics + +let showWindow (parent : Window) = + let win = Views.CommandLineArgumentsWindow () + win.Owner <- parent + + win.Left <- (if parent.WindowState = WindowState.Maximized then 0. else parent.Left) + parent.ActualWidth / 2. - win.Width / 2. + win.Top <- (if parent.WindowState = WindowState.Maximized then 0. else parent.Top) + parent.ActualHeight / 2. - win.Height / 2. + + win.txtCommandLineArguments.Text <- Utils.argsHelp + + win.butClose.Click.AddHandler (fun obj args -> win.Close ()) + + win.ShowDialog () |> ignore + diff --git a/Parasitemia/ParasitemiaUI/GUI.fs b/Parasitemia/ParasitemiaUI/GUI.fs index 4eee1e5..d78c202 100644 --- a/Parasitemia/ParasitemiaUI/GUI.fs +++ b/Parasitemia/ParasitemiaUI/GUI.fs @@ -484,6 +484,8 @@ let run (defaultConfig : Config) (fileToOpen : string option) = win.menuAbout.Click.AddHandler (fun obj args -> About.showWindow win) + win.menuCommandLineArguments.Click.AddHandler (fun obj args -> CommandLineArguments.showWindow win) + win.Closing.AddHandler (fun obj args -> askSaveCurrent ()) // Zoom on the current image. diff --git a/Parasitemia/ParasitemiaUI/ParasitemiaUI.fsproj b/Parasitemia/ParasitemiaUI/ParasitemiaUI.fsproj index b9ad387..429ceaa 100644 --- a/Parasitemia/ParasitemiaUI/ParasitemiaUI.fsproj +++ b/Parasitemia/ParasitemiaUI/ParasitemiaUI.fsproj @@ -80,6 +80,8 @@ + + @@ -92,6 +94,7 @@ + @@ -111,7 +114,7 @@ ..\packages\EmguCV.3.1.0.1\lib\net30\Emgu.CV.World.dll - ..\packages\FSharp.Core.4.2.1\lib\net45\FSharp.Core.dll + ..\packages\FSharp.Core.4.2.3\lib\net45\FSharp.Core.dll ..\packages\FSharp.ViewModule.Core.1.0.7.0\lib\portable-net45+netcore45+wpa81+wp8+MonoAndroid1+MonoTouch1\FSharp.ViewModule.dll @@ -144,7 +147,7 @@ - ..\packages\System.ValueTuple.4.3.1\lib\netstandard1.0\System.ValueTuple.dll + ..\packages\System.ValueTuple.4.4.0\lib\netstandard1.0\System.ValueTuple.dll diff --git a/Parasitemia/ParasitemiaUI/Program.fs b/Parasitemia/ParasitemiaUI/Program.fs index 87e1c26..63bd963 100644 --- a/Parasitemia/ParasitemiaUI/Program.fs +++ b/Parasitemia/ParasitemiaUI/Program.fs @@ -20,7 +20,7 @@ type RunningMode = | CmdLine of Input * string // A file or a directory to process and the output directory. | Window of string option // An optional path to a file to open can be given in window mode. -type Arguments = RunningMode * bool +type Arguments = RunningMode * bool // bool : true if in debug mode. let parseArgs (args : string[]) : Arguments = @@ -40,18 +40,7 @@ let parseArgs (args : string[]) : Arguments = runningMode, Array.exists ((=) "--debug") args let showArgsHelp () = - printfn "Usage of Parasitemia :" - printfn "Non-interactive mode:" - printfn " %s (--folder |--file ) --output [--debug]" System.AppDomain.CurrentDomain.FriendlyName - printfn " --folder : an input folder containing images to analyze" - printfn " --file : an image file to be analyzed" - printfn " --output : a folder to put the results" - printfn " --debug : output more information like intermediate images if set" - - printfn "Interactive mode:" - printfn " %s [] [--debug]" System.AppDomain.CurrentDomain.FriendlyName - printfn " : a PIAZ file to automatically open at startup" - printfn " --debug : output information like intermediate images if set in the current directory" + Console.WriteLine Utils.argsHelp [] extern bool AttachConsole (int dwProcessId) @@ -93,7 +82,7 @@ let main args = use resultFile = new StreamWriter (new FileStream (Path.Combine (output, "results.txt"), FileMode.Append, FileAccess.Write)) - let images = [ for file in files -> Path.GetFileNameWithoutExtension (FileInfo(file).Name), config.Copy(), new Image (file) ] + let images = [ for file in files -> Path.GetFileNameWithoutExtension (FileInfo(file).Name), config.Copy (), new Image (file) ] Log.LogWithTime Severity.INFO ( fun () -> @@ -109,7 +98,7 @@ let main args = Some () ) "Whole analyze" |> ignore - Log.RmListener (listener) + Log.RmListener listener 0 | Window fileToOpen -> diff --git a/Parasitemia/ParasitemiaUI/Utils.fs b/Parasitemia/ParasitemiaUI/Utils.fs index 8635bcd..810494e 100644 --- a/Parasitemia/ParasitemiaUI/Utils.fs +++ b/Parasitemia/ParasitemiaUI/Utils.fs @@ -61,3 +61,18 @@ let sensorSizes : SensorSize list = | _ex -> saveSensorSizesToFile defaultSensorSizes defaultSensorSizes + +let argsHelp = + let programName = System.AppDomain.CurrentDomain.FriendlyName + "Usage of Parasitemia:\n" + + "Non-interactive mode:\n" + + (sprintf " %s (--folder |--file ) --output [--debug]\n" programName) + + " --folder : an input folder containing images to analyze\n" + + " --file : an image file to be analyzed\n" + + " --output : a folder to put the results\n" + + " --debug : output more information like intermediate images (it takes more CPU and memory)\n" + + + "Interactive mode:\n" + + (sprintf " %s [] [--debug]\n" programName) + + " : a PIAZ file to automatically open at startup\n" + + " --debug : output information like intermediate images in the current directory (it takes more CPU and memory)" \ No newline at end of file diff --git a/Parasitemia/ParasitemiaUI/XAML/CommandLineArgumentsWindow.xaml b/Parasitemia/ParasitemiaUI/XAML/CommandLineArgumentsWindow.xaml new file mode 100644 index 0000000..8952ea3 --- /dev/null +++ b/Parasitemia/ParasitemiaUI/XAML/CommandLineArgumentsWindow.xaml @@ -0,0 +1,17 @@ + + + + + + + + Parasitemia can be launched via a command line, here are the possible arguments + +