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) then
41 for i
in 1 .. h - 2 do
42 for j
in 1 .. w - 2 do
43 if img.[i
, j
] > 0uy && img.Data.[i
- 1, j
] > 0uy && (img.Data.[i
, j
- 1] > 0uy && img.Data.[i
+ 1, j
+ 1] = 0uy || img.Data.[i
, j
+ 1] > 0uy && img.Data.[i
+ 1, j
- 1] = 0uy) then
46 let findEdges (img: Image<Gray, float32
>) : Matrix<byte
> * Image<Gray, float> * Image<Gray, float> =
51 new ConvolutionKernelF(array2D
[[ 1.0f; 0.0f; -1.0f ]
53 [ 1.0f; 0.0f; -1.0f ]], Point(1, 1))
55 let xGradient = img.Convolution(sobelKernel).Convert<Gray, float>()
56 let yGradient = img.Convolution(sobelKernel.Transpose()).Convert<Gray, float>()
58 let xGradientData = xGradient.Data
59 let yGradientData = yGradient.Data
60 for r
in 0 .. h - 1 do
61 xGradientData.[r
, 0, 0] <- 0.0
62 xGradientData.[r
, w - 1, 0] <- 0.0
63 yGradientData.[r
, 0, 0] <- 0.0
64 yGradientData.[r
, w - 1, 0] <- 0.0
66 for c
in 0 .. w - 1 do
67 xGradientData.[0, c
, 0] <- 0.0
68 xGradientData.[h - 1, c
, 0] <- 0.0
69 yGradientData.[0, c
, 0] <- 0.0
70 yGradientData.[h - 1, c
, 0] <- 0.0
72 use magnitudes = new Matrix<float>(xGradient.Size)
73 CvInvoke.CartToPolar(xGradient, yGradient, magnitudes, new Mat()) // Compute the magnitudes (without angles).
75 let thresholdHigh, thresholdLow
=
77 use magnitudesByte = magnitudes.Convert<byte
>()
78 let threshold = CvInvoke.Threshold(magnitudesByte, magnitudesByte, 0.0, 1.0, CvEnum.ThresholdType.Otsu ||| CvEnum.ThresholdType.Binary)
79 threshold + (sensibility * threshold), threshold - (sensibility * threshold)
81 // Non-maximum suppression.
82 use nms = new Matrix<byte
>(xGradient.Size)
85 for i
in 0 .. h - 1 do
86 nms.Data.[i
, 0] <- 0uy
87 nms.Data.[i
, w - 1] <- 0uy
89 for j
in 0 .. w - 1 do
90 nms.Data.[0, j
] <- 0uy
91 nms.Data.[h - 1, j
] <- 0uy
93 for i
in 1 .. h - 2 do
94 for j
in 1 .. w - 2 do
95 let vx = xGradient.Data.[i
, j
, 0]
96 let vy = yGradient.Data.[i
, j
, 0]
99 if a < 0.0 then 2. * Math.PI + a else a
101 let mNeigbors (sign
: int) : float =
102 if angle < Math.PI / 8. || angle >= 15.0 * Math.PI / 8. then magnitudes.Data.[i
, j
+ sign
]
103 elif
angle < 3.0 * Math.PI / 8. then magnitudes.Data.[i
+ sign
, j
+ sign
]
104 elif
angle < 5.0 * Math.PI / 8. then magnitudes.Data.[i
+ sign
, j
]
105 elif
angle < 7.0 * Math.PI / 8. then magnitudes.Data.[i
+ sign
, j
- sign
]
106 elif
angle < 9.0 * Math.PI / 8. then magnitudes.Data.[i
, j
- sign
]
107 elif
angle < 11.0 * Math.PI / 8. then magnitudes.Data.[i
- sign
, j
- sign
]
108 elif
angle < 13.0 * Math.PI / 8. then magnitudes.Data.[i
- sign
, j
]
109 else magnitudes.Data.[i
- sign
, j
+ sign
]
111 let m = magnitudes.Data.[i
, j
]
112 if m < mNeigbors 1 || m < mNeigbors -1 || m < thresholdLow
then
113 nms.Data.[i
, j
] <- 0uy
115 // suppressMConnections nms // It's not usefull for the rest of the process (ellipse detection).
117 let edges = new Matrix<byte
>(xGradient.Size)
119 // Histeresis thresholding.
120 let toVisit = Stack<Point>()
121 for i
in 0 .. h - 1 do
122 for j
in 0 .. w - 1 do
123 if nms.Data.[i
, j
] = 1uy && magnitudes.Data.[i
, j
] >= thresholdHigh then
124 nms.Data.[i
, j
] <- 0uy
125 toVisit.Push(Point(j
, i
))
126 while toVisit.Count > 0 do
127 let p = toVisit.Pop()
128 edges.Data.[p.Y, p.X] <- 1uy
131 if i
' <> 0 || j' <> 0 then
134 if ni >= 0 && ni < h && nj >= 0 && nj < w && nms.Data.[ni, nj] = 1uy then
135 nms.Data.[ni, nj] <- 0uy
136 toVisit.Push(Point(nj, ni))
139 edges, xGradient, yGradient
142 let gaussianFilter (img : Image<'TColor, 'TDepth>) (standardDeviation
: float) : Image<'TColor, 'TDepth> =
143 let size = 2 * int (ceil
(4.0 * standardDeviation
)) + 1
144 img.SmoothGaussian(size, size, standardDeviation
, standardDeviation
)
147 type Points = HashSet<Point>
149 let drawPoints (img: Image<Gray, byte
>) (points
: Points) (intensity
: byte
) =
151 img.Data.[p.Y, p.X, 0] <- intensity
157 let findExtremum (img: Image<Gray, byte
>) (extremumType
: ExtremumType) : IEnumerable<Points> =
160 let se = [| -1, 0; 0, -1; 1, 0; 0, 1 |]
162 let imgData = img.Data
163 let suppress: bool[,] = Array2D.zeroCreate
h w
165 let result = List<List<Point>>()
167 let flood (start
: Point) : List<List<Point>> =
168 let sameLevelToCheck = Stack<Point>()
169 let betterLevelToCheck = Stack<Point>()
170 betterLevelToCheck.Push(start
)
172 let result' = List<List<Point>>()
174 while betterLevelToCheck.Count > 0 do
175 let p = betterLevelToCheck.Pop()
176 if not suppress.[p.Y, p.X]
178 suppress.[p.Y, p.X] <- true
179 sameLevelToCheck.Push(p)
180 let current = List<Point>()
182 let mutable betterExists = false
184 while sameLevelToCheck.Count > 0 do
185 let p' = sameLevelToCheck.Pop()
186 let currentLevel = imgData.[p'.Y, p'.X, 0]
187 current.Add(p') |> ignore
191 if ni >= 0 && ni < h && nj >= 0 && nj < w
193 let level = imgData.[ni, nj, 0]
194 let notSuppressed = not suppress.[ni, nj]
196 if level = currentLevel && notSuppressed
198 suppress.[ni, nj] <- true
199 sameLevelToCheck.Push(Point(nj, ni))
200 elif if extremumType = ExtremumType.Maxima then level > currentLevel else level < currentLevel
205 betterLevelToCheck.Push(Point(nj, ni))
212 for i in 0 .. h - 1 do
213 for j in 0 .. w - 1 do
214 let maxima = flood (Point(j, i))
217 result.AddRange(maxima)
219 result.Select(fun l -> Points(l))
222 let findMaxima (img: Image<Gray, byte>) : IEnumerable<Points> =
223 findExtremum img ExtremumType.Maxima
225 let findMinima (img: Image<Gray, byte>) : IEnumerable<Points> =
226 findExtremum img ExtremumType.Minima
229 type PriorityQueue () =
231 let q: Points[] = Array.init size (fun i -> Points())
232 let mutable highest = -1 // Value of the first elements of 'q'.
233 let mutable lowest = size
235 member this.NextMax () : byte * Point =
238 invalidOp "Queue is empty"
242 l.Remove(next) |> ignore
243 let value = byte highest
247 highest <- highest - 1
248 while highest > lowest && q.[highest].Count = 0 do
249 highest <- highest - 1
257 member this.NextMin () : byte * Point =
260 invalidOp "Queue is empty"
262 let l = q.[lowest + 1]
264 l.Remove(next) |> ignore
265 let value = byte (lowest + 1)
270 while lowest < highest && q.[lowest + 1].Count = 0 do
285 member this.Add (value: byte) (p: Point) =
295 q.[vi].Add(p) |> ignore
297 member this.Remove (value: byte) (p: Point) =
299 if q.[vi].Remove(p) && q.[vi].Count = 0
303 highest <- highest - 1
304 while highest > lowest && q.[highest].Count = 0 do
305 highest <- highest - 1
309 while lowest < highest && q.[lowest + 1].Count = 0 do
312 if highest = lowest // The queue is now empty.
317 member this.IsEmpty =
320 member this.Clear () =
321 while highest > lowest do
323 highest <- highest - 1
328 type private AreaState =
333 type private AreaOperation =
338 type private Area (elements: Points) =
339 member this.Elements = elements
340 member val Intensity = None with get, set
341 member val State = AreaState.Unprocessed with get, set
343 let private areaOperation (img: Image<Gray, byte>) (area: int) (op: AreaOperation) =
346 let imgData = img.Data
347 let se = [| -1, 0; 0, -1; 1, 0; 0, 1 |]
349 let areas = List<Area>((if op = AreaOperation.Opening then findMaxima img else findMinima img) |> Seq.map Area)
351 let pixels: Area[,] = Array2D.create h w null
353 for e in m.Elements do
354 pixels.[e.Y, e.X] <- m
356 let queue = PriorityQueue()
358 let addEdgeToQueue (elements: Points) =
363 let p' = Point(nj, ni)
364 if ni >= 0 && ni < h && nj >= 0 && nj < w && not (elements
.Contains(p'))
366 queue.Add (imgData.[ni, nj, 0]) p'
368 // Reverse order is quicker.
369 for i
in areas.Count - 1 .. -1 .. 0 do
371 if m.Elements.Count <= area
&& m.State <> AreaState.Removed
374 addEdgeToQueue m.Elements
376 let mutable intensity = if op
= AreaOperation.Opening then queue.Max else queue.Min
377 let nextElements = Points()
379 let mutable stop = false
381 let intensity', p = if op = AreaOperation.Opening then queue.NextMax () else queue.NextMin ()
382 let mutable merged = false
384 if intensity' = intensity // The intensity doesn't change.
386 if m.Elements.Count + nextElements.Count + 1 > area
388 m.State <- AreaState.Validated
389 m.Intensity <- Some intensity
392 nextElements.Add(p) |> ignore
394 elif
if op
= AreaOperation.Opening then intensity' < intensity else intensity' > intensity
396 m.Elements.UnionWith(nextElements)
397 for e
in nextElements do
398 pixels.[e
.Y, e
.X] <- m
400 if m.Elements.Count = area
402 m.State <- AreaState.Validated
403 m.Intensity <- Some (intensity')
406 intensity <- intensity'
408 nextElements.Add(p) |> ignore
411 let m' = pixels.[p.Y, p.X]
414 if m'.Elements.Count + m.Elements.Count <= area
416 m'.State <- AreaState.Removed
417 for e
in m'.Elements do
418 pixels.[e.Y, e.X] <- m
419 queue.Remove imgData.[e.Y, e.X, 0] e
420 addEdgeToQueue m'.Elements
421 m.Elements.UnionWith(m'.Elements)
422 let intensityMax = if op = AreaOperation.Opening then queue.Max else queue.Min
423 if intensityMax <> intensity
425 intensity <- intensityMax
431 m.State <- AreaState.Validated
432 m.Intensity <- Some (intensity)
435 if not stop && not merged
440 let p' = Point(nj, ni)
441 if ni < 0 || ni >= h || nj < 0 || nj >= w
443 m.State <- AreaState.Validated
444 m.Intensity <- Some (intensity)
446 elif
not (m.Elements.Contains(p')) && not (nextElements.Contains(p'))
448 queue.Add (imgData.[ni, nj, 0]) p'
452 if m.Elements.Count + nextElements.Count <= area
454 m.State <- AreaState.Validated
455 m.Intensity <- Some intensity'
456 m.Elements.UnionWith(nextElements)
460 if m.State = AreaState.Validated
462 match m.Intensity with
464 for p in m.Elements do
465 imgData.[p.Y, p.X, 0] <- i
470 let areaOpen (img: Image<Gray, byte
>) (area
: int) =
471 areaOperation
img area
AreaOperation.Opening
473 let areaClose (img: Image<Gray, byte
>) (area
: int) =
474 areaOperation
img area
AreaOperation.Closing
476 // A simpler algorithm than 'areaOpen' but slower.
477 let areaOpen2 (img: Image<Gray, byte
>) (area
: int) =
480 let imgData = img.Data
481 let se = [| -1, 0; 0, -1; 1, 0; 0, 1 |]
483 let histogram = Array.zeroCreate
256
484 for i
in 0 .. h - 1 do
485 for j in 0 .. w - 1 do
486 let v = imgData.[i
, j, 0] |> int
487 histogram.[v] <- histogram.[v] + 1
489 let flooded : bool[,] = Array2D.zeroCreate
h w
491 let pointsChecked = HashSet<Point>()
492 let pointsToCheck = Stack<Point>()
494 for level in 255 .. -1 .. 0 do
495 let mutable n = histogram.[level]
498 for i
in 0 .. h - 1 do
499 for j in 0 .. w - 1 do
500 if not flooded.[i
, j] && imgData.[i
, j, 0] = byte
level
502 let mutable maxNeighborValue = 0uy
503 pointsChecked.Clear()
504 pointsToCheck.Clear()
505 pointsToCheck.Push(Point(j, i
))
507 while pointsToCheck.Count > 0 do
508 let next = pointsToCheck.Pop()
509 pointsChecked.Add(next) |> ignore
510 flooded.[next.Y, next.X] <- true
513 let p = Point(next.X + nx
, next.Y + ny
)
514 if p.X >= 0 && p.X < w && p.Y >= 0 && p.Y < h
516 let v = imgData.[p.Y, p.X, 0]
519 if not (pointsChecked.Contains(p))
521 pointsToCheck.Push(p)
522 elif
v > maxNeighborValue
524 maxNeighborValue <- v
526 if int maxNeighborValue < level && pointsChecked.Count <= area
528 for p in pointsChecked do
529 imgData.[p.Y, p.X, 0] <- maxNeighborValue
532 // Zhang and Suen algorithm.
533 // Modify 'mat' in place.
534 let thin (mat
: Matrix<byte
>) =
537 let mutable data1 = mat
.Data
538 let mutable data2 = Array2D.copy
data1
540 let mutable pixelChanged = true
541 let mutable oddIteration = true
543 while pixelChanged do
544 pixelChanged <- false
547 if data1.[i
, j] = 1uy
549 let p2 = if i
= 0 then 0uy else data1.[i
-1, j]
550 let p3 = if i
= 0 || j = w-1 then 0uy else data1.[i
-1, j+1]
551 let p4 = if j = w-1 then 0uy else data1.[i
, j+1]
552 let p5 = if i
= h-1 || j = w-1 then 0uy else data1.[i
+1, j+1]
553 let p6 = if i
= h-1 then 0uy else data1.[i
+1, j]
554 let p7 = if i
= h-1 || j = 0 then 0uy else data1.[i
+1, j-1]
555 let p8 = if j = 0 then 0uy else data1.[i
, j-1]
556 let p9 = if i
= 0 || j = 0 then 0uy else data1.[i
-1, j-1]
558 let sumNeighbors = p2 + p3 + p4 + p5 + p6 + p7 + p8 + p9
559 if sumNeighbors >= 2uy && sumNeighbors <= 6uy &&
560 (if p2 = 0uy && p3 = 1uy then 1 else 0) +
561 (if p3 = 0uy && p4 = 1uy then 1 else 0) +
562 (if p4 = 0uy && p5 = 1uy then 1 else 0) +
563 (if p5 = 0uy && p6 = 1uy then 1 else 0) +
564 (if p6 = 0uy && p7 = 1uy then 1 else 0) +
565 (if p7 = 0uy && p8 = 1uy then 1 else 0) +
566 (if p8 = 0uy && p9 = 1uy then 1 else 0) +
567 (if p9 = 0uy && p2 = 1uy then 1 else 0) = 1 &&
569 then p2 * p4 * p6 = 0uy && p4 * p6 * p8 = 0uy
570 else p2 * p4 * p8 = 0uy && p2 * p6 * p8 = 0uy
577 oddIteration <- not oddIteration
583 // FIXME: replace by a queue or stack.
584 let pop (l: List<'a>) : 'a =
585 let n = l.[l.Count - 1]
586 l.RemoveAt(l.Count - 1)
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<(int*int)>()
615 let neighborsToCheck = List<(int*int)>()
616 neighborsToCheck.Add((i, j))
619 while neighborsToCheck.Count > 0 do
620 let (ci
, cj
) = pop neighborsToCheck
621 neighborhood.Add((ci
, cj
))
622 for (ni, nj) in neighbors do
625 if pi >= 0 && pi < h && pj >= 0 && pj < w && data'.[pi, pj] = 1uy
627 neighborsToCheck.Add((pi, pj))
628 data'.[pi, pj] <- 0uy
629 if neighborhood.Count <= areaSize
631 for (ni, nj) in neighborhood do
634 let connectedComponents (img: Image<Gray, byte
>) (startPoints
: List<Point>) : List<Point> =
638 let pointChecked = Points()
639 let pointToCheck = List<Point>(startPoints
);
643 while pointToCheck.Count > 0 do
644 let next = pop pointToCheck
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