let range =
let delta = config.Parameters.granulometryRange * config.RBCRadiusByResolution.Pixel
int <| config.RBCRadiusByResolution.Pixel - delta, int <| config.RBCRadiusByResolution.Pixel + delta
+
let! radius = logTimeWithName "Granulometry (area)" (fun() -> reportWithVal 10 (Granulometry.findRadiusByAreaClosing img_RBC_filtered range |> float32))
+ //let! radius = logTimeWithName "Granulometry (morpho)" (fun() -> reportWithVal 10 (Granulometry.findRadiusByClosing img_RBC_filtered range 1. true |> float32))
config.SetRBCRadius <| radius
logWithName (sprintf "Found erythrocyte diameter: %A" config.RBCRadius)
IO.saveImg parasites.parasite (buildFileName " - parasites - stain.png")
IO.saveImg parasites.nucleus (buildFileName " - parasites - infection.png")
- let imgAllEllipses = img.Copy()
- Drawing.drawEllipses imgAllEllipses matchingEllipses.Ellipses (Bgr(255.0, 255.0, 255.0)) 0.04
+ let imgAllEllipses = img_RBC_filtered.Copy()
+ Drawing.drawEllipses imgAllEllipses matchingEllipses.Ellipses (Gray(200.0)) 0.04
IO.saveImg imgAllEllipses (buildFileName " - ellipses - all.png")
let imgEllipses = img_RBC_filtered.Convert<Bgr, byte>()
let defaultParameters = {
rbcDiameter = 7.5<μm>
- resolution = 220.e3<ppi> // 220.e3<ppi> Correspond to 50X.
+ resolution = 230.e3<ppi> // 230.e3<ppi> Correspond to 50X.
ratioAreaPaleCenter = 2.f / 5.f // The ratio between an RBC area and the area of the its pale center.
granulometryRange = 0.5f
- minRbcRadius = -0.3f
- maxRbcRadius = 0.3f
+ minRbcRadius = -0.23f
+ maxRbcRadius = 0.23f
LPFStandardDeviationParasite = 0.15<μm>
- LPFStandardDeviationRBC = 0.2<μm>
+ LPFStandardDeviationRBC = 0.22<μm>
factorNbPick = 1.0
open MatchingEllipses
open Const
+// This is a ratio of the biggest radius.
+let minimumDistanceBetweenDrawnPoints = 0.6
+
/// <summary>
/// Try to build an ellipse from three points and two tangents passing by the first and the second point.
/// 'Ellipse.A' is always equal or greater than Ellipse.B.
let private vectorRotation (p1x: float32) (p1y: float32) (v1x: float32) (v1y: float32) (px: float32) (py: float32) : float32 =
- let mutable rotation = 1.f
if p1y > py
then
- if v1x > 0.f
- then
- rotation <- -1.f
+ if v1x > 0.f then -1.f else 1.f
elif p1y < py
then
- if v1x < 0.f
- then
- rotation <- -1.f
+ if v1x < 0.f then -1.f else 1.f
elif p1x > px
then
- if v1y < 0.f
- then
- rotation <- -1.f
- elif p1x < px
- then
- if v1y > 0.f
- then
- rotation <- -1.f
- rotation
+ if v1y < 0.f then -1.f else 1.f
+ else // p1x < px
+ if v1y > 0.f then -1.f else 1.f
let private areVectorsValid (p1x: float32) (p1y: float32) (p2x: float32) (p2y: float32) (v1x: float32) (v1y: float32) (v2x: float32) (v2y: float32) : (float32 * float32) option =
let m1 = -v1x / v1y
if diff > PI || (diff < 0.f && diff > -PI)
then
- None
+ Some (m1, m2)
else
- Some (m1, m2)
+ None
let find (edges: Matrix<byte>)
(xGradient: Matrix<float32>)
let radiusTolerance = (r2 - r1) * 0.2f
- let squaredMinimumDistance = (float r2 / 1.5) ** 2.
+ let squaredMinimumDistance = (float config.RBCRadius.Pixel * minimumDistanceBetweenDrawnPoints) ** 2.
let inline squaredDistance x1 y1 x2 y2 = (x1 - x2) ** 2. + (y1 - y2) ** 2.
let h = edges.Height
module ParasitemiaCore.Granulometry
open System
+open System.IO
open System.Drawing
open Emgu.CV
let intensityImg = scaledImg.GetSum().Intensity
// 's' must be odd.
- let octagon (s: int) : Mat =
+ let octagon (s: int) : Matrix<byte> =
if s % 2 = 0 then failwith "s must be odd"
let m = new Matrix<byte>(Array2D.create s s 1uy)
let r = (float s) / (Math.Sqrt 2. + 2.) |> roundInt
m.[s - i - 1, j] <- 0uy
m.[i, s - j - 1] <- 0uy
m.[s - i - 1, s - j - 1] <- 0uy
- m.Mat
+ m
let mutable previous_n = Double.NaN
for r in r1' .. r2' do
let se = if useOctagon
- then octagon (2 * r - 1) // It doesnd't speed up the process.
+ then (octagon (2 * r - 1)).Mat // It doesn't speed up the process.
else CvInvoke.GetStructuringElement(CvEnum.ElementShape.Ellipse, Size(2 * r, 2 * r), Point(-1, -1))
use closed = scaledImg.MorphologyEx(CvEnum.MorphOp.Close, se, Point(-1, -1), 1, CvEnum.BorderType.Replicate, MCvScalar(0.0))
open Histogram
open Otsu
+// Sensibilities of the hysteresis search.
+let sensibilityHigh = 0.1f
+let sensibilityLow = 0.0f
+
/// <summary>
/// Find edges of an image by using the Canny approach.
/// The thresholds are automatically defined with otsu on gradient magnitudes.
let h = img.Height
use sobelKernel =
- new Matrix<float32>(array2D [[ 1.0f; 0.0f; -1.0f ]
- [ 2.0f; 0.0f; -2.0f ]
- [ 1.0f; 0.0f; -1.0f ]])
+ new Matrix<float32>(array2D [[ -1.0f; 0.0f; 1.0f ]
+ [ -2.0f; 0.0f; 2.0f ]
+ [ -1.0f; 0.0f; 1.0f ]])
let xGradient = new Matrix<float32>(img.Size)
let yGradient = new Matrix<float32>(img.Size)
use magnitudes = new Matrix<float32>(xGradient.Size)
use angles = new Matrix<float32>(xGradient.Size)
- CvInvoke.CartToPolar(xGradient, yGradient, magnitudes, angles) // Compute the magnitudes and angles.
+ CvInvoke.CartToPolar(xGradient, yGradient, magnitudes, angles) // Compute the magnitudes and angles. The angles are between 0 and 2 * pi.
let thresholdHigh, thresholdLow =
- let sensibilityHigh = 0.1f
- let sensibilityLow = 0.0f
let threshold, _, _ = otsu (histogramMat magnitudes 300)
threshold + (sensibilityHigh * threshold), threshold - (sensibilityLow * threshold)
let xGradientData = xGradient.Data
let yGradientData = yGradient.Data
- for i in 0 .. h - 1 do
- nmsData.[i, 0] <- 0uy
- nmsData.[i, w - 1] <- 0uy
-
- for j in 0 .. w - 1 do
- nmsData.[0, j] <- 0uy
- nmsData.[h - 1, j] <- 0uy
-
for i in 1 .. h - 2 do
for j in 1 .. w - 2 do
let vx = xGradientData.[i, j]
// suppressMConnections nms // It's not helpful for the rest of the process (ellipse detection).
+ IO.saveMat magnitudes "magnitudes.png"
+ IO.saveMat nms "nms.png"
+
let edges = new Matrix<byte>(xGradient.Size)
let edgesData = edges.Data
<Compile Include="ImgTools\Histogram.fs" />
<Compile Include="ImgTools\Otsu.fs" />
<Compile Include="ImgTools\Drawing.fs" />
- <Compile Include="ImgTools\Edges.fs" />
- <Compile Include="ImgTools\Morpho.fs" />
<Compile Include="ImgTools\IO.fs" />
<Compile Include="ImgTools\ImgTools.fs" />
+ <Compile Include="ImgTools\Edges.fs" />
+ <Compile Include="ImgTools\Morpho.fs" />
<Compile Include="Granulometry.fs" />
<Compile Include="Config.fs" />
<Compile Include="KMedians.fs" />
imageSourceSelection.menuZoom50X.Click.AddHandler(fun obj args -> imageSourceSelection.txtResolution.Text <- "230000")
imageSourceSelection.menuZoom100X.Click.AddHandler(fun obj args -> imageSourceSelection.txtResolution.Text <- "460000")
- imageSourceSelection.butDPICalculator.Click.AddHandler(fun obj args ->
- match DPICalculator.showWindow win.Root with
+ imageSourceSelection.butPPICalculator.Click.AddHandler(fun obj args ->
+ match PPICalculator.showWindow win.Root with
| Some resolution -> imageSourceSelection.txtResolution.Text <- resolution.ToString()
| None -> ())
-module ParasitemiaUI.DPICalculator
+module ParasitemiaUI.PPICalculator
open System
open System.Windows
sprintf "%g mm × %g mm%s" this.w this.h (if this.txt = "" then "" else " (" + this.txt + ")")
let showWindow (parent: Window) : int option =
- let win = Views.DPICalculatorWindow()
+ let win = Views.PPICalculatorWindow()
win.Root.Owner <- parent
win.Root.Left <- parent.Left + parent.ActualWidth / 2. - win.Root.Width / 2.
win.Root.Top <- parent.Top + parent.ActualHeight / 2. - win.Root.Height / 2.
<Compile Include="XAML\ImageSourceSelection.xaml.fs" />
<Resource Include="XAML\RBCFrame.xaml" />
<Compile Include="XAML\RBCFrame.xaml.fs" />
- <Resource Include="XAML\DPICalculatorWindow.xaml" />
- <Compile Include="XAML\DPICalculatorWindow.xaml.fs" />
+ <Resource Include="XAML\PPICalculatorWindow.xaml" />
+ <Compile Include="XAML\PPICalculatorWindow.xaml.fs" />
<Resource Include="XAML\AnalysisWindow.xaml" />
<Compile Include="XAML\AnalysisWindow.xaml.fs" />
<Resource Include="XAML\AboutWindow.xaml" />
+++ /dev/null
-<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
- xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
- xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
- xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
- mc:Ignorable="d"
- x:Name="DPICalculatorWindow" Height="200" Width="378.5" MinHeight="200" MinWidth="280" Title="About" Icon="pack://application:,,,/Resources/icon.ico">
- <Grid Margin="3">
- <Grid.ColumnDefinitions>
- <ColumnDefinition Width="Auto"/>
- <ColumnDefinition Width="1*"/>
- </Grid.ColumnDefinitions>
- <Grid.RowDefinitions>
- <RowDefinition Height="Auto" />
- <RowDefinition Height="Auto" />
- <RowDefinition Height="Auto" />
- <RowDefinition Height="20" />
- <RowDefinition Height="Auto" />
- <RowDefinition Height="1*" />
- </Grid.RowDefinitions>
- <Label Content="Sensor size" Grid.Row="0" Grid.Column="0" />
- <ComboBox x:Name="cmbSensorSize" Grid.Row="0" Grid.Column="1" VerticalAlignment="Center" />
-
- <Label Content="Sensor resolution [Megapixel]" Grid.Row="1" Grid.Column="0" />
- <TextBox x:Name="txtSensorResolution" Grid.Row="1" Grid.Column="1" VerticalAlignment="Center" />
-
- <Label Content="Zoom" Grid.Row="2" Grid.Column="0" />
- <TextBox x:Name="txtZoom" Grid.Row="2" Grid.Column="1" VerticalAlignment="Center" />
-
- <Label Content="Image resolution [DPI]" Grid.Row="4" Grid.Column="0" />
- <TextBox x:Name="txtImageResolution" Grid.Row="4" Grid.Column="1" VerticalAlignment="Center" IsReadOnly="True" />
-
- <Grid Grid.Column="0" Grid.Row="5" Grid.ColumnSpan="2">
- <Grid.ColumnDefinitions>
- <ColumnDefinition Width="1*"/>
- <ColumnDefinition Width="Auto"/>
- </Grid.ColumnDefinitions>
- <Button x:Name="butCancel" Content="Cancel" HorizontalAlignment="Right" Margin="3" VerticalAlignment="Bottom" Width="75" Height="20" Grid.Column="0" />
- <Button x:Name="butOK" Content="OK" HorizontalAlignment="Right" Margin="3" VerticalAlignment="Bottom" Width="75" Height="20" Grid.Column="1" />
- </Grid>
- </Grid>
-</Window>
\ No newline at end of file
+++ /dev/null
-namespace ParasitemiaUI.Views
-
-open FsXaml
-
-type DPICalculatorWindow = XAML<"XAML/DPICalculatorWindow.xaml", true>
-
</Style>
</Button.Style>
</Button>
- <Button x:Name="butDPICalculator" Content="DPI calculator" Grid.Column="2" Margin="3" />
+ <Button x:Name="butPPICalculator" Content="PPI calculator" Grid.Column="2" Margin="3" />
</Grid>
</Grid>
</Grid>
--- /dev/null
+<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+ xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+ mc:Ignorable="d"
+ x:Name="PPICalculatorWindow" Height="200" Width="378.5" MinHeight="200" MinWidth="280" Title="About" Icon="pack://application:,,,/Resources/icon.ico">
+ <Grid Margin="3">
+ <Grid.ColumnDefinitions>
+ <ColumnDefinition Width="Auto"/>
+ <ColumnDefinition Width="1*"/>
+ </Grid.ColumnDefinitions>
+ <Grid.RowDefinitions>
+ <RowDefinition Height="Auto" />
+ <RowDefinition Height="Auto" />
+ <RowDefinition Height="Auto" />
+ <RowDefinition Height="20" />
+ <RowDefinition Height="Auto" />
+ <RowDefinition Height="1*" />
+ </Grid.RowDefinitions>
+ <Label Content="Sensor size" Grid.Row="0" Grid.Column="0" />
+ <ComboBox x:Name="cmbSensorSize" Grid.Row="0" Grid.Column="1" VerticalAlignment="Center" />
+
+ <Label Content="Sensor resolution [Megapixel]" Grid.Row="1" Grid.Column="0" />
+ <TextBox x:Name="txtSensorResolution" Grid.Row="1" Grid.Column="1" VerticalAlignment="Center" />
+
+ <Label Content="Zoom" Grid.Row="2" Grid.Column="0" />
+ <TextBox x:Name="txtZoom" Grid.Row="2" Grid.Column="1" VerticalAlignment="Center" />
+
+ <Label Content="Image resolution [PPI]" Grid.Row="4" Grid.Column="0" />
+ <TextBox x:Name="txtImageResolution" Grid.Row="4" Grid.Column="1" VerticalAlignment="Center" IsReadOnly="True" />
+
+ <Grid Grid.Column="0" Grid.Row="5" Grid.ColumnSpan="2">
+ <Grid.ColumnDefinitions>
+ <ColumnDefinition Width="1*"/>
+ <ColumnDefinition Width="Auto"/>
+ </Grid.ColumnDefinitions>
+ <Button x:Name="butCancel" Content="Cancel" HorizontalAlignment="Right" Margin="3" VerticalAlignment="Bottom" Width="75" Height="20" Grid.Column="0" />
+ <Button x:Name="butOK" Content="OK" HorizontalAlignment="Right" Margin="3" VerticalAlignment="Bottom" Width="75" Height="20" Grid.Column="1" />
+ </Grid>
+ </Grid>
+</Window>
\ No newline at end of file
--- /dev/null
+namespace ParasitemiaUI.Views
+
+open FsXaml
+
+type PPICalculatorWindow = XAML<"XAML/PPICalculatorWindow.xaml", true>
+