5 open System.Collections.Generic
14 // Normalize image values between 0uy and 255uy.
15 let normalizeAndConvert (img
: Image<Gray, float32
>) : Image<Gray, byte
> =
16 let min = ref [| 0.0
|]
17 let minLocation = ref <| [| Point() |]
18 let max = ref [| 0.0
|]
19 let maxLocation = ref <| [| Point() |]
20 img
.MinMax(min, max, minLocation, maxLocation)
21 ((img
- (!min).[0]) / ((!max).[0] - (!min).[0]) * 255.0).Convert<Gray, byte
>()
24 let saveImg (img
: Image<'TColor, 'TDepth>) (filepath
: string) =
28 let saveMat (mat
: Matrix<'TDepth>) (filepath: string) =
29 use img = new Image<Gray, 'TDeph>(mat
.Size)
34 let suppressMConnections (img: Matrix<byte
>) =
37 for i
in 1 .. h - 2 do
38 for j
in 1 .. w - 2 do
39 if img.[i
, j
] > 0uy && img.Data.[i
+ 1, j
] > 0uy && (img.Data.[i
, j
- 1] > 0uy && img.Data.[i
- 1, j
+ 1] = 0uy || img.Data.[i
, j
+ 1] > 0uy && img.Data.[i
- 1, j
- 1] = 0uy)
42 for i
in 1 .. h - 2 do
43 for j
in 1 .. w - 2 do
44 if img.[i
, j
] > 0uy && img.Data.[i
- 1, j
] > 0uy && (img.Data.[i
, j
- 1] > 0uy && img.Data.[i
+ 1, j
+ 1] = 0uy || img.Data.[i
, j
+ 1] > 0uy && img.Data.[i
+ 1, j
- 1] = 0uy)
48 let findEdges (img: Image<Gray, float32
>) : Matrix<byte
> * Image<Gray, float> * Image<Gray, float> =
53 new ConvolutionKernelF(array2D
[[ 1.0f; 0.0f; -1.0f ]
55 [ 1.0f; 0.0f; -1.0f ]], Point(1, 1))
57 let xGradient = img.Convolution(sobelKernel).Convert<Gray, float>()
58 let yGradient = img.Convolution(sobelKernel.Transpose()).Convert<Gray, float>()
60 let xGradientData = xGradient.Data
61 let yGradientData = yGradient.Data
62 for r
in 0 .. h - 1 do
63 xGradientData.[r
, 0, 0] <- 0.0
64 xGradientData.[r
, w - 1, 0] <- 0.0
65 yGradientData.[r
, 0, 0] <- 0.0
66 yGradientData.[r
, w - 1, 0] <- 0.0
68 for c
in 0 .. w - 1 do
69 xGradientData.[0, c
, 0] <- 0.0
70 xGradientData.[h - 1, c
, 0] <- 0.0
71 yGradientData.[0, c
, 0] <- 0.0
72 yGradientData.[h - 1, c
, 0] <- 0.0
74 use magnitudes = new Matrix<float>(xGradient.Size)
75 CvInvoke.CartToPolar(xGradient, yGradient, magnitudes, new Mat()) // Compute the magnitudes (without angles).
77 let thresholdHigh, thresholdLow
=
79 use magnitudesByte = magnitudes.Convert<byte
>()
80 let threshold = CvInvoke.Threshold(magnitudesByte, magnitudesByte, 0.0, 1.0, CvEnum.ThresholdType.Otsu ||| CvEnum.ThresholdType.Binary)
81 threshold + (sensibility * threshold), threshold - (sensibility * threshold)
83 // Non-maximum suppression.
84 use nms = new Matrix<byte
>(xGradient.Size)
87 for i
in 0 .. h - 1 do
88 nms.Data.[i
, 0] <- 0uy
89 nms.Data.[i
, w - 1] <- 0uy
91 for j
in 0 .. w - 1 do
92 nms.Data.[0, j
] <- 0uy
93 nms.Data.[h - 1, j
] <- 0uy
95 for i
in 1 .. h - 2 do
96 for j
in 1 .. w - 2 do
97 let vx = xGradient.Data.[i
, j
, 0]
98 let vy = yGradient.Data.[i
, j
, 0]
101 if a < 0.0 then 2. * Math.PI + a else a
103 let mNeigbors (sign
: int) : float =
104 if angle < Math.PI / 8. || angle >= 15.0 * Math.PI / 8. then magnitudes.Data.[i
, j
+ sign
]
105 elif
angle < 3.0 * Math.PI / 8. then magnitudes.Data.[i
+ sign
, j
+ sign
]
106 elif
angle < 5.0 * Math.PI / 8. then magnitudes.Data.[i
+ sign
, j
]
107 elif
angle < 7.0 * Math.PI / 8. then magnitudes.Data.[i
+ sign
, j
- sign
]
108 elif
angle < 9.0 * Math.PI / 8. then magnitudes.Data.[i
, j
- sign
]
109 elif
angle < 11.0 * Math.PI / 8. then magnitudes.Data.[i
- sign
, j
- sign
]
110 elif
angle < 13.0 * Math.PI / 8. then magnitudes.Data.[i
- sign
, j
]
111 else magnitudes.Data.[i
- sign
, j
+ sign
]
113 let m = magnitudes.Data.[i
, j
]
114 if m < mNeigbors 1 || m < mNeigbors -1 || m < thresholdLow
116 nms.Data.[i
, j
] <- 0uy
118 // suppressMConnections nms // It's not usefull for the rest of the process (ellipse detection).
120 let edges = new Matrix<byte
>(xGradient.Size)
122 // Histeresis thresholding.
123 let toVisit = Stack<Point>()
124 for i
in 0 .. h - 1 do
125 for j
in 0 .. w - 1 do
126 if nms.Data.[i
, j
] = 1uy && magnitudes.Data.[i
, j
] >= thresholdHigh
128 nms.Data.[i
, j
] <- 0uy
129 toVisit.Push(Point(j
, i
))
130 while toVisit.Count > 0 do
131 let p = toVisit.Pop()
132 edges.Data.[p.Y, p.X] <- 1uy
135 if i
' <> 0 || j' <> 0
139 if ni >= 0 && ni < h && nj >= 0 && nj < w && nms.Data.[ni, nj] = 1uy
141 nms.Data.[ni, nj] <- 0uy
142 toVisit.Push(Point(nj, ni))
145 edges, xGradient, yGradient
148 let gaussianFilter (img : Image<'TColor, 'TDepth>) (standardDeviation
: float) : Image<'TColor, 'TDepth> =
149 let size = 2 * int (ceil
(4.0 * standardDeviation
)) + 1
150 img.SmoothGaussian(size, size, standardDeviation
, standardDeviation
)
153 type Points = HashSet<Point>
155 let drawPoints (img: Image<Gray, byte
>) (points
: Points) (intensity
: byte
) =
157 img.Data.[p.Y, p.X, 0] <- intensity
163 let findExtremum (img: Image<Gray, byte
>) (extremumType
: ExtremumType) : IEnumerable<Points> =
166 let se = [| -1, 0; 0, -1; 1, 0; 0, 1 |]
168 let imgData = img.Data
169 let suppress: bool[,] = Array2D.zeroCreate
h w
171 let result = List<List<Point>>()
173 let flood (start
: Point) : List<List<Point>> =
174 let sameLevelToCheck = Stack<Point>()
175 let betterLevelToCheck = Stack<Point>()
176 betterLevelToCheck.Push(start
)
178 let result' = List<List<Point>>()
180 while betterLevelToCheck.Count > 0 do
181 let p = betterLevelToCheck.Pop()
182 if not suppress.[p.Y, p.X]
184 suppress.[p.Y, p.X] <- true
185 sameLevelToCheck.Push(p)
186 let current = List<Point>()
188 let mutable betterExists = false
190 while sameLevelToCheck.Count > 0 do
191 let p' = sameLevelToCheck.Pop()
192 let currentLevel = imgData.[p'.Y, p'.X, 0]
193 current.Add(p') |> ignore
197 if ni >= 0 && ni < h && nj >= 0 && nj < w
199 let level = imgData.[ni, nj, 0]
200 let notSuppressed = not suppress.[ni, nj]
202 if level = currentLevel && notSuppressed
204 suppress.[ni, nj] <- true
205 sameLevelToCheck.Push(Point(nj, ni))
206 elif if extremumType = ExtremumType.Maxima then level > currentLevel else level < currentLevel
211 betterLevelToCheck.Push(Point(nj, ni))
218 for i in 0 .. h - 1 do
219 for j in 0 .. w - 1 do
220 let maxima = flood (Point(j, i))
223 result.AddRange(maxima)
225 result.Select(fun l -> Points(l))
228 let findMaxima (img: Image<Gray, byte>) : IEnumerable<Points> =
229 findExtremum img ExtremumType.Maxima
231 let findMinima (img: Image<Gray, byte>) : IEnumerable<Points> =
232 findExtremum img ExtremumType.Minima
235 type PriorityQueue () =
237 let q: Points[] = Array.init size (fun i -> Points())
238 let mutable highest = -1 // Value of the first elements of 'q'.
239 let mutable lowest = size
241 member this.NextMax () : byte * Point =
244 invalidOp "Queue is empty"
248 l.Remove(next) |> ignore
249 let value = byte highest
253 highest <- highest - 1
254 while highest > lowest && q.[highest].Count = 0 do
255 highest <- highest - 1
263 member this.NextMin () : byte * Point =
266 invalidOp "Queue is empty"
268 let l = q.[lowest + 1]
270 l.Remove(next) |> ignore
271 let value = byte (lowest + 1)
276 while lowest < highest && q.[lowest + 1].Count = 0 do
291 member this.Add (value: byte) (p: Point) =
301 q.[vi].Add(p) |> ignore
303 member this.Remove (value: byte) (p: Point) =
305 if q.[vi].Remove(p) && q.[vi].Count = 0
309 highest <- highest - 1
310 while highest > lowest && q.[highest].Count = 0 do
311 highest <- highest - 1
315 while lowest < highest && q.[lowest + 1].Count = 0 do
318 if highest = lowest // The queue is now empty.
323 member this.IsEmpty =
326 member this.Clear () =
327 while highest > lowest do
329 highest <- highest - 1
334 type private AreaState =
339 type private AreaOperation =
344 type private Area (elements: Points) =
345 member this.Elements = elements
346 member val Intensity = None with get, set
347 member val State = AreaState.Unprocessed with get, set
349 let private areaOperation (img: Image<Gray, byte>) (area: int) (op: AreaOperation) =
352 let imgData = img.Data
353 let se = [| -1, 0; 0, -1; 1, 0; 0, 1 |]
355 let areas = List<Area>((if op = AreaOperation.Opening then findMaxima img else findMinima img) |> Seq.map Area)
357 let pixels: Area[,] = Array2D.create h w null
359 for e in m.Elements do
360 pixels.[e.Y, e.X] <- m
362 let queue = PriorityQueue()
364 let addEdgeToQueue (elements: Points) =
369 let p' = Point(nj, ni)
370 if ni >= 0 && ni < h && nj >= 0 && nj < w && not (elements
.Contains(p'))
372 queue.Add (imgData.[ni, nj, 0]) p'
374 // Reverse order is quicker.
375 for i
in areas.Count - 1 .. -1 .. 0 do
377 if m.Elements.Count <= area
&& m.State <> AreaState.Removed
380 addEdgeToQueue m.Elements
382 let mutable intensity = if op
= AreaOperation.Opening then queue.Max else queue.Min
383 let nextElements = Points()
385 let mutable stop = false
387 let intensity', p = if op = AreaOperation.Opening then queue.NextMax () else queue.NextMin ()
388 let mutable merged = false
390 if intensity' = intensity // The intensity doesn't change.
392 if m.Elements.Count + nextElements.Count + 1 > area
394 m.State <- AreaState.Validated
395 m.Intensity <- Some intensity
398 nextElements.Add(p) |> ignore
400 elif
if op
= AreaOperation.Opening then intensity' < intensity else intensity' > intensity
402 m.Elements.UnionWith(nextElements)
403 for e
in nextElements do
404 pixels.[e
.Y, e
.X] <- m
406 if m.Elements.Count = area
408 m.State <- AreaState.Validated
409 m.Intensity <- Some (intensity')
412 intensity <- intensity'
414 nextElements.Add(p) |> ignore
417 let m' = pixels.[p.Y, p.X]
420 if m'.Elements.Count + m.Elements.Count <= area
422 m'.State <- AreaState.Removed
423 for e
in m'.Elements do
424 pixels.[e.Y, e.X] <- m
425 queue.Remove imgData.[e.Y, e.X, 0] e
426 addEdgeToQueue m'.Elements
427 m.Elements.UnionWith(m'.Elements)
428 let intensityMax = if op = AreaOperation.Opening then queue.Max else queue.Min
429 if intensityMax <> intensity
431 intensity <- intensityMax
437 m.State <- AreaState.Validated
438 m.Intensity <- Some (intensity)
441 if not stop && not merged
446 let p' = Point(nj, ni)
447 if ni < 0 || ni >= h || nj < 0 || nj >= w
449 m.State <- AreaState.Validated
450 m.Intensity <- Some (intensity)
452 elif
not (m.Elements.Contains(p')) && not (nextElements.Contains(p'))
454 queue.Add (imgData.[ni, nj, 0]) p'
458 if m.Elements.Count + nextElements.Count <= area
460 m.State <- AreaState.Validated
461 m.Intensity <- Some intensity'
462 m.Elements.UnionWith(nextElements)
466 if m.State = AreaState.Validated
468 match m.Intensity with
470 for p in m.Elements do
471 imgData.[p.Y, p.X, 0] <- i
476 let areaOpen (img: Image<Gray, byte
>) (area
: int) =
477 areaOperation
img area
AreaOperation.Opening
479 let areaClose (img: Image<Gray, byte
>) (area
: int) =
480 areaOperation
img area
AreaOperation.Closing
482 // A simpler algorithm than 'areaOpen' but slower.
483 let areaOpen2 (img: Image<Gray, byte
>) (area
: int) =
486 let imgData = img.Data
487 let se = [| -1, 0; 0, -1; 1, 0; 0, 1 |]
489 let histogram = Array.zeroCreate
256
490 for i
in 0 .. h - 1 do
491 for j in 0 .. w - 1 do
492 let v = imgData.[i
, j, 0] |> int
493 histogram.[v] <- histogram.[v] + 1
495 let flooded : bool[,] = Array2D.zeroCreate
h w
497 let pointsChecked = HashSet<Point>()
498 let pointsToCheck = Stack<Point>()
500 for level in 255 .. -1 .. 0 do
501 let mutable n = histogram.[level]
504 for i
in 0 .. h - 1 do
505 for j in 0 .. w - 1 do
506 if not flooded.[i
, j] && imgData.[i
, j, 0] = byte
level
508 let mutable maxNeighborValue = 0uy
509 pointsChecked.Clear()
510 pointsToCheck.Clear()
511 pointsToCheck.Push(Point(j, i
))
513 while pointsToCheck.Count > 0 do
514 let next = pointsToCheck.Pop()
515 pointsChecked.Add(next) |> ignore
516 flooded.[next.Y, next.X] <- true
519 let p = Point(next.X + nx
, next.Y + ny
)
520 if p.X >= 0 && p.X < w && p.Y >= 0 && p.Y < h
522 let v = imgData.[p.Y, p.X, 0]
525 if not (pointsChecked.Contains(p))
527 pointsToCheck.Push(p)
528 elif
v > maxNeighborValue
530 maxNeighborValue <- v
532 if int maxNeighborValue < level && pointsChecked.Count <= area
534 for p in pointsChecked do
535 imgData.[p.Y, p.X, 0] <- maxNeighborValue
538 // Zhang and Suen algorithm.
539 // Modify 'mat' in place.
540 let thin (mat
: Matrix<byte
>) =
543 let mutable data1 = mat
.Data
544 let mutable data2 = Array2D.copy
data1
546 let mutable pixelChanged = true
547 let mutable oddIteration = true
549 while pixelChanged do
550 pixelChanged <- false
553 if data1.[i
, j] = 1uy
555 let p2 = if i
= 0 then 0uy else data1.[i
-1, j]
556 let p3 = if i
= 0 || j = w-1 then 0uy else data1.[i
-1, j+1]
557 let p4 = if j = w-1 then 0uy else data1.[i
, j+1]
558 let p5 = if i
= h-1 || j = w-1 then 0uy else data1.[i
+1, j+1]
559 let p6 = if i
= h-1 then 0uy else data1.[i
+1, j]
560 let p7 = if i
= h-1 || j = 0 then 0uy else data1.[i
+1, j-1]
561 let p8 = if j = 0 then 0uy else data1.[i
, j-1]
562 let p9 = if i
= 0 || j = 0 then 0uy else data1.[i
-1, j-1]
564 let sumNeighbors = p2 + p3 + p4 + p5 + p6 + p7 + p8 + p9
565 if sumNeighbors >= 2uy && sumNeighbors <= 6uy &&
566 (if p2 = 0uy && p3 = 1uy then 1 else 0) +
567 (if p3 = 0uy && p4 = 1uy then 1 else 0) +
568 (if p4 = 0uy && p5 = 1uy then 1 else 0) +
569 (if p5 = 0uy && p6 = 1uy then 1 else 0) +
570 (if p6 = 0uy && p7 = 1uy then 1 else 0) +
571 (if p7 = 0uy && p8 = 1uy then 1 else 0) +
572 (if p8 = 0uy && p9 = 1uy then 1 else 0) +
573 (if p9 = 0uy && p2 = 1uy then 1 else 0) = 1 &&
575 then p2 * p4 * p6 = 0uy && p4 * p6 * p8 = 0uy
576 else p2 * p4 * p8 = 0uy && p2 * p6 * p8 = 0uy
583 oddIteration <- not oddIteration
589 // Remove all 8-connected pixels with an area equal or greater than 'areaSize'.
590 // Modify 'mat' in place.
591 let removeArea (mat
: Matrix<byte
>) (areaSize
: int) =
602 let mat' = new Matrix<byte>(mat.Size)
608 let data' = mat'.Data
612 if data'.[i, j] = 1uy
614 let neighborhood = List<Point>()
615 let neighborsToCheck = Stack<Point>()
616 neighborsToCheck.Push(Point(j, i))
619 while neighborsToCheck.Count > 0 do
620 let n = neighborsToCheck.Pop()
622 for (ni, nj) in neighbors do
625 if pi >= 0 && pi < h && pj >= 0 && pj < w && data'.[pi, pj] = 1uy
627 neighborsToCheck.Push(Point(pj, pi))
628 data'.[pi, pj] <- 0uy
629 if neighborhood.Count <= areaSize
631 for n in neighborhood do
632 data.[n.Y, n.X] <- 0uy
634 let connectedComponents (img: Image<Gray, byte
>) (startPoints
: List<Point>) : List<Point> =
638 let pointChecked = Points()
639 let pointToCheck = Stack<Point>(startPoints
);
643 while pointToCheck.Count > 0 do
644 let next = pointToCheck.Pop()
645 pointChecked.Add(next) |> ignore
648 if ny
<> 0 && nx
<> 0
650 let p = Point(next.X + nx
, next.Y + ny
)
651 if p.X >= 0 && p.X < w && p.Y >= 0 && p.Y < h && data.[p.Y, p.X, 0] > 0uy && not (pointChecked.Contains p)
655 List<Point>(pointChecked)
658 let drawLine (img: Image<'TColor, 'TDepth>) (color
: 'TColor) (x0: int) (y0: int) (x1: int) (y1: int) (thickness: int) =
659 img.Draw(LineSegment2D(Point(x0, y0), Point(x1, y1)), color, thickness);
662 let drawLineF (img: Image<'TColor, 'TDepth>) (color: 'TColor) (x0
: float) (y0
: float) (x1
: float) (y1
: float) (thickness
: int) =
663 img.Draw(LineSegment2DF(PointF(float32 x0
, float32 y0
), PointF(float32 x1
, float32 y1
)), color
, thickness
, CvEnum.LineType.AntiAlias);
666 let drawEllipse (img: Image<'TColor, 'TDepth>) (e
: Types.Ellipse) (color
: 'TColor) (alpha: float) =
670 img.Draw(Ellipse(PointF(float32 e.Cx, float32 e.Cy), SizeF(2. * e.B |> float32, 2. * e.A |> float32), float32 <| e.Alpha / Math.PI * 180.), color, 1, CvEnum.LineType.AntiAlias)
672 let windowPosX = e.Cx - e.A - 5.0
673 let gapX = windowPosX - (float (int windowPosX))
675 let windowPosY = e.Cy - e.A - 5.0
676 let gapY = windowPosY - (float (int windowPosY))
678 let roi = Rectangle(int windowPosX, int windowPosY, 2. * (e.A + 5.0) |> int, 2.* (e.A + 5.0) |> int)
681 if roi = img.ROI // We do not display ellipses touching the edges (FIXME)
683 use i = new Image<'TColor, 'TDepth>(img.ROI.Size)
684 i.Draw(Ellipse(PointF(float32 <| (e.A + 5. + gapX) , float32 <| (e.A + 5. + gapY)), SizeF(2. * e.B |> float32, 2. * e.A |> float32), float32 <| e.Alpha / Math.PI * 180.), color, 1, CvEnum.LineType.AntiAlias)
685 CvInvoke.AddWeighted(img, 1.0, i, alpha, 0.0, img)
686 img.ROI <- Rectangle.Empty
689 let drawEllipses (img: Image<'TColor, 'TDepth>) (ellipses: Types.Ellipse list) (color: 'TColor) (alpha
: float) =
690 List.iter
(fun e -> drawEllipse img e color alpha
) ellipses
693 let rngCell = System.Random()
694 let drawCell (img: Image<Bgr, byte
>) (drawCellContent
: bool) (c
: Types.Cell) =
697 let colorB = rngCell.Next(20, 70)
698 let colorG = rngCell.Next(20, 70)
699 let colorR = rngCell.Next(20, 70)
701 for y
in 0 .. c
.elements
.Height - 1 do
702 for x
in 0 .. c
.elements
.Width - 1 do
703 if c
.elements
.[y
, x
] > 0uy
705 let dx, dy
= c
.center
.X - c
.elements
.Width / 2, c
.center
.Y - c
.elements
.Height / 2
706 let b = img.Data.[y
+ dy
, x
+ dx, 0] |> int
707 let g = img.Data.[y
+ dy
, x
+ dx, 1] |> int
708 let r = img.Data.[y
+ dy
, x
+ dx, 2] |> int
709 img.Data.[y
+ dy
, x
+ dx, 0] <- if b + colorB > 255 then 255uy else byte (b + colorB)
710 img.Data.[y
+ dy
, x
+ dx, 1] <- if g + colorG > 255 then 255uy else byte (g + colorG)
711 img.Data.[y
+ dy
, x
+ dx, 2] <- if r + colorR > 255 then 255uy else byte (r + colorR)
713 let crossColor, crossColor2
=
714 match c
.cellClass
with
715 | Types.HealthyRBC -> Bgr(255., 0., 0.), Bgr(255., 255., 255.)
716 | Types.InfectedRBC -> Bgr(0., 0., 255.), Bgr(120., 120., 255.)
717 | Types.Peculiar -> Bgr(0., 0., 0.), Bgr(80., 80., 80.)
719 drawLine img crossColor2
(c
.center
.X - 3) c
.center
.Y (c
.center
.X + 3) c
.center
.Y 2
720 drawLine img crossColor2 c
.center
.X (c
.center
.Y - 3) c
.center
.X (c
.center
.Y + 3) 2
722 drawLine img crossColor (c
.center
.X - 3) c
.center
.Y (c
.center
.X + 3) c
.center
.Y 1
723 drawLine img crossColor c
.center
.X (c
.center
.Y - 3) c
.center
.X (c
.center
.Y + 3) 1
726 let drawCells (img: Image<Bgr, byte>) (drawCellContent
: bool) (cells
: Types.Cell list) =
727 List.iter
(fun c
-> drawCell img drawCellContent
c) cells