Add a way to detect the membrane of a parasite in the ring stage.
[master-thesis.git] / Parasitemia / ParasitemiaCore / ImgTools.fs
1 module ParasitemiaCore.ImgTools
2
3 open System
4 open System.Drawing
5 open System.Collections.Generic
6 open System.Linq
7
8 open Emgu.CV
9 open Emgu.CV.Structure
10
11 open Heap
12 open Const
13 open Types
14 open Utils
15
16 // Normalize image values between 0uy and 255uy.
17 let normalizeAndConvert (img: Image<Gray, 'TDepth>) : Image<Gray, byte> =
18 let min = ref [| 0.0 |]
19 let minLocation = ref <| [| Point() |]
20 let max = ref [| 0.0 |]
21 let maxLocation = ref <| [| Point() |]
22 img.MinMax(min, max, minLocation, maxLocation)
23 ((img.Convert<Gray, float32>() - (!min).[0]) / ((!max).[0] - (!min).[0]) * 255.0).Convert<Gray, byte>()
24
25 let saveImg (img: Image<'TColor, 'TDepth>) (filepath: string) =
26 img.Save(filepath)
27
28 let saveMat (mat: Matrix<'TDepth>) (filepath: string) =
29 use img = new Image<Gray, 'TDeph>(mat.Size)
30 mat.CopyTo(img)
31 saveImg img filepath
32
33 type Histogram = { data: int[]; total: int; sum: int; min: float32; max: float32 }
34
35 let histogramImg (img: Image<Gray, float32>) (nbSamples: int) : Histogram =
36 let imgData = img.Data
37
38 let min, max =
39 let min = ref [| 0.0 |]
40 let minLocation = ref <| [| Point() |]
41 let max = ref [| 0.0 |]
42 let maxLocation = ref <| [| Point() |]
43 img.MinMax(min, max, minLocation, maxLocation)
44 float32 (!min).[0], float32 (!max).[0]
45
46 let inline bin (x: float32) : int =
47 let p = int ((x - min) / (max - min) * float32 nbSamples)
48 if p >= nbSamples then nbSamples - 1 else p
49
50 let data = Array.zeroCreate nbSamples
51
52 for i in 0 .. img.Height - 1 do
53 for j in 0 .. img.Width - 1 do
54 let p = bin imgData.[i, j, 0]
55 data.[p] <- data.[p] + 1
56
57 { data = data; total = img.Height * img.Width; sum = Array.sum data; min = min; max = max }
58
59 let histogramMat (mat: Matrix<float32>) (nbSamples: int) : Histogram =
60 let matData = mat.Data
61
62 let min, max =
63 let min = ref 0.0
64 let minLocation = ref <| Point()
65 let max = ref 0.0
66 let maxLocation = ref <| Point()
67 mat.MinMax(min, max, minLocation, maxLocation)
68 float32 !min, float32 !max
69
70 let inline bin (x: float32) : int =
71 let p = int ((x - min) / (max - min) * float32 nbSamples)
72 if p >= nbSamples then nbSamples - 1 else p
73
74 let data = Array.zeroCreate nbSamples
75
76 for i in 0 .. mat.Height - 1 do
77 for j in 0 .. mat.Width - 1 do
78 let p = bin matData.[i, j]
79 data.[p] <- data.[p] + 1
80
81 { data = data; total = mat.Height * mat.Width; sum = Array.sum data; min = min; max = max }
82
83 let histogram (values: float32 seq) (nbSamples: int) : Histogram =
84 let mutable min = Single.MaxValue
85 let mutable max = Single.MinValue
86 let mutable n = 0
87
88 for v in values do
89 n <- n + 1
90 if v < min then min <- v
91 if v > max then max <- v
92
93 let inline bin (x: float32) : int =
94 let p = int ((x - min) / (max - min) * float32 nbSamples)
95 if p >= nbSamples then nbSamples - 1 else p
96
97 let data = Array.zeroCreate nbSamples
98
99 for v in values do
100 let p = bin v
101 data.[p] <- data.[p] + 1
102
103 { data = data; total = n; sum = Array.sum data; min = min; max = max }
104
105 let otsu (hist: Histogram) : float32 * float32 * float32 =
106 let mutable sumB = 0
107 let mutable wB = 0
108 let mutable maximum = 0.0
109 let mutable level = 0
110 let sum = hist.data |> Array.mapi (fun i v -> i * v) |> Array.sum |> float
111
112 for i in 0 .. hist.data.Length - 1 do
113 wB <- wB + hist.data.[i]
114 if wB <> 0
115 then
116 let wF = hist.total - wB
117 if wF <> 0
118 then
119 sumB <- sumB + i * hist.data.[i]
120 let mB = (float sumB) / (float wB)
121 let mF = (sum - float sumB) / (float wF)
122 let between = (float wB) * (float wF) * (mB - mF) ** 2.;
123 if between >= maximum
124 then
125 level <- i
126 maximum <- between
127
128 let mean1 =
129 let mutable sum = 0
130 let mutable nb = 0
131 for i in 0 .. level - 1 do
132 sum <- sum + i * hist.data.[i]
133 nb <- nb + hist.data.[i]
134 (sum + level * hist.data.[level] / 2) / (nb + hist.data.[level] / 2)
135
136 let mean2 =
137 let mutable sum = 0
138 let mutable nb = 0
139 for i in level + 1 .. hist.data.Length - 1 do
140 sum <- sum + i * hist.data.[i]
141 nb <- nb + hist.data.[i]
142 (sum + level * hist.data.[level] / 2) / (nb + hist.data.[level] / 2)
143
144 let toFloat l =
145 float32 l / float32 hist.data.Length * (hist.max - hist.min) + hist.min
146
147 toFloat level, toFloat mean1, toFloat mean2
148
149 /// <summary>
150 /// Remove M-adjacent pixels. It may be used after thinning.
151 /// </summary>
152 let suppressMAdjacency (img: Matrix<byte>) =
153 let w = img.Width
154 let h = img.Height
155 for i in 1 .. h - 2 do
156 for j in 1 .. w - 2 do
157 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)
158 then
159 img.[i, j] <- 0uy
160 for i in 1 .. h - 2 do
161 for j in 1 .. w - 2 do
162 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)
163 then
164 img.[i, j] <- 0uy
165
166 /// <summary>
167 /// Find edges of an image by using the Canny approach.
168 /// The thresholds are automatically defined with otsu on gradient magnitudes.
169 /// </summary>
170 /// <param name="img"></param>
171 let findEdges (img: Image<Gray, float32>) : Matrix<byte> * Matrix<float32> * Matrix<float32> =
172 let w = img.Width
173 let h = img.Height
174
175 use sobelKernel =
176 new Matrix<float32>(array2D [[ 1.0f; 0.0f; -1.0f ]
177 [ 2.0f; 0.0f; -2.0f ]
178 [ 1.0f; 0.0f; -1.0f ]])
179
180 let xGradient = new Matrix<float32>(img.Size)
181 let yGradient = new Matrix<float32>(img.Size)
182 CvInvoke.Filter2D(img, xGradient, sobelKernel, Point(1, 1))
183 CvInvoke.Filter2D(img, yGradient, sobelKernel.Transpose(), Point(1, 1))
184
185 use magnitudes = new Matrix<float32>(xGradient.Size)
186 use angles = new Matrix<float32>(xGradient.Size)
187 CvInvoke.CartToPolar(xGradient, yGradient, magnitudes, angles) // Compute the magnitudes and angles.
188
189 let thresholdHigh, thresholdLow =
190 let sensibilityHigh = 0.1f
191 let sensibilityLow = 0.0f
192 let threshold, _, _ = otsu (histogramMat magnitudes 300)
193 threshold + (sensibilityHigh * threshold), threshold - (sensibilityLow * threshold)
194
195 // Non-maximum suppression.
196 use nms = new Matrix<byte>(xGradient.Size)
197
198 let nmsData = nms.Data
199 let anglesData = angles.Data
200 let magnitudesData = magnitudes.Data
201 let xGradientData = xGradient.Data
202 let yGradientData = yGradient.Data
203
204 for i in 0 .. h - 1 do
205 nmsData.[i, 0] <- 0uy
206 nmsData.[i, w - 1] <- 0uy
207
208 for j in 0 .. w - 1 do
209 nmsData.[0, j] <- 0uy
210 nmsData.[h - 1, j] <- 0uy
211
212 for i in 1 .. h - 2 do
213 for j in 1 .. w - 2 do
214 let vx = xGradientData.[i, j]
215 let vy = yGradientData.[i, j]
216 if vx <> 0.f || vy <> 0.f
217 then
218 let angle = anglesData.[i, j]
219
220 let vx', vy' = abs vx, abs vy
221 let ratio2 = if vx' > vy' then vy' / vx' else vx' / vy'
222 let ratio1 = 1.f - ratio2
223
224 let mNeigbors (sign: int) : float32 =
225 if angle < PI / 4.f
226 then ratio1 * magnitudesData.[i, j + sign] + ratio2 * magnitudesData.[i + sign, j + sign]
227 elif angle < PI / 2.f
228 then ratio2 * magnitudesData.[i + sign, j + sign] + ratio1 * magnitudesData.[i + sign, j]
229 elif angle < 3.f * PI / 4.f
230 then ratio1 * magnitudesData.[i + sign, j] + ratio2 * magnitudesData.[i + sign, j - sign]
231 elif angle < PI
232 then ratio2 * magnitudesData.[i + sign, j - sign] + ratio1 * magnitudesData.[i, j - sign]
233 elif angle < 5.f * PI / 4.f
234 then ratio1 * magnitudesData.[i, j - sign] + ratio2 * magnitudesData.[i - sign, j - sign]
235 elif angle < 3.f * PI / 2.f
236 then ratio2 * magnitudesData.[i - sign, j - sign] + ratio1 * magnitudesData.[i - sign, j]
237 elif angle < 7.f * PI / 4.f
238 then ratio1 * magnitudesData.[i - sign, j] + ratio2 * magnitudesData.[i - sign, j + sign]
239 else ratio2 * magnitudesData.[i - sign, j + sign] + ratio1 * magnitudesData.[i, j + sign]
240
241 let m = magnitudesData.[i, j]
242 if m >= thresholdLow && m > mNeigbors 1 && m > mNeigbors -1
243 then
244 nmsData.[i, j] <- 1uy
245
246 // suppressMConnections nms // It's not helpful for the rest of the process (ellipse detection).
247
248 let edges = new Matrix<byte>(xGradient.Size)
249 let edgesData = edges.Data
250
251 // Hysteresis thresholding.
252 let toVisit = Stack<Point>()
253 for i in 0 .. h - 1 do
254 for j in 0 .. w - 1 do
255 if nmsData.[i, j] = 1uy && magnitudesData.[i, j] >= thresholdHigh
256 then
257 nmsData.[i, j] <- 0uy
258 toVisit.Push(Point(j, i))
259 while toVisit.Count > 0 do
260 let p = toVisit.Pop()
261 edgesData.[p.Y, p.X] <- 1uy
262 for i' in -1 .. 1 do
263 for j' in -1 .. 1 do
264 if i' <> 0 || j' <> 0
265 then
266 let ni = p.Y + i'
267 let nj = p.X + j'
268 if ni >= 0 && ni < h && nj >= 0 && nj < w && nmsData.[ni, nj] = 1uy
269 then
270 nmsData.[ni, nj] <- 0uy
271 toVisit.Push(Point(nj, ni))
272
273 edges, xGradient, yGradient
274
275 let gaussianFilter (img : Image<'TColor, 'TDepth>) (standardDeviation : float) : Image<'TColor, 'TDepth> =
276 let size = 2 * int (ceil (4.0 * standardDeviation)) + 1
277 img.SmoothGaussian(size, size, standardDeviation, standardDeviation)
278
279 let drawPoints (img: Image<Gray, 'TDepth>) (points: Points) (intensity: 'TDepth) =
280 for p in points do
281 img.Data.[p.Y, p.X, 0] <- intensity
282
283 type ExtremumType =
284 | Maxima = 1
285 | Minima = 2
286
287 let findExtremum (img: Image<Gray, 'TDepth>) (extremumType: ExtremumType) : IEnumerable<Points> =
288 let w = img.Width
289 let h = img.Height
290 let se = [| -1, 0; 0, -1; 1, 0; 0, 1 |]
291
292 let imgData = img.Data
293 let suppress: bool[,] = Array2D.zeroCreate h w
294
295 let result = List<List<Point>>()
296
297 let flood (start: Point) : List<List<Point>> =
298 let sameLevelToCheck = Stack<Point>()
299 let betterLevelToCheck = Stack<Point>()
300 betterLevelToCheck.Push(start)
301
302 let result' = List<List<Point>>()
303
304 while betterLevelToCheck.Count > 0 do
305 let p = betterLevelToCheck.Pop()
306 if not suppress.[p.Y, p.X]
307 then
308 suppress.[p.Y, p.X] <- true
309 sameLevelToCheck.Push(p)
310 let current = List<Point>()
311
312 let mutable betterExists = false
313
314 while sameLevelToCheck.Count > 0 do
315 let p' = sameLevelToCheck.Pop()
316 let currentLevel = imgData.[p'.Y, p'.X, 0]
317 current.Add(p') |> ignore
318 for i, j in se do
319 let ni = i + p'.Y
320 let nj = j + p'.X
321 if ni >= 0 && ni < h && nj >= 0 && nj < w
322 then
323 let level = imgData.[ni, nj, 0]
324 let notSuppressed = not suppress.[ni, nj]
325
326 if level = currentLevel && notSuppressed
327 then
328 suppress.[ni, nj] <- true
329 sameLevelToCheck.Push(Point(nj, ni))
330 elif if extremumType = ExtremumType.Maxima then level > currentLevel else level < currentLevel
331 then
332 betterExists <- true
333 if notSuppressed
334 then
335 betterLevelToCheck.Push(Point(nj, ni))
336
337 if not betterExists
338 then
339 result'.Add(current)
340 result'
341
342 for i in 0 .. h - 1 do
343 for j in 0 .. w - 1 do
344 let maxima = flood (Point(j, i))
345 if maxima.Count > 0
346 then
347 result.AddRange(maxima)
348
349 result.Select(fun l -> Points(l))
350
351 let findMaxima (img: Image<Gray, 'TDepth>) : IEnumerable<Points> =
352 findExtremum img ExtremumType.Maxima
353
354 let findMinima (img: Image<Gray, 'TDepth>) : IEnumerable<Points> =
355 findExtremum img ExtremumType.Minima
356
357 type PriorityQueue () =
358 let size = 256
359 let q: Points[] = Array.init size (fun i -> Points())
360 let mutable highest = -1 // Value of the first elements of 'q'.
361 let mutable lowest = size
362
363 member this.NextMax () : byte * Point =
364 if this.IsEmpty
365 then
366 invalidOp "Queue is empty"
367 else
368 let l = q.[highest]
369 let next = l.First()
370 l.Remove(next) |> ignore
371 let value = byte highest
372
373 if l.Count = 0
374 then
375 highest <- highest - 1
376 while highest > lowest && q.[highest].Count = 0 do
377 highest <- highest - 1
378 if highest = lowest
379 then
380 highest <- -1
381 lowest <- size
382
383 value, next
384
385 member this.NextMin () : byte * Point =
386 if this.IsEmpty
387 then
388 invalidOp "Queue is empty"
389 else
390 let l = q.[lowest + 1]
391 let next = l.First()
392 l.Remove(next) |> ignore
393 let value = byte (lowest + 1)
394
395 if l.Count = 0
396 then
397 lowest <- lowest + 1
398 while lowest < highest && q.[lowest + 1].Count = 0 do
399 lowest <- lowest + 1
400 if highest = lowest
401 then
402 highest <- -1
403 lowest <- size
404
405 value, next
406
407 member this.Max =
408 highest |> byte
409
410 member this.Min =
411 lowest + 1 |> byte
412
413 member this.Add (value: byte) (p: Point) =
414 let vi = int value
415
416 if vi > highest
417 then
418 highest <- vi
419 if vi <= lowest
420 then
421 lowest <- vi - 1
422
423 q.[vi].Add(p) |> ignore
424
425 member this.Remove (value: byte) (p: Point) =
426 let vi = int value
427 if q.[vi].Remove(p) && q.[vi].Count = 0
428 then
429 if vi = highest
430 then
431 highest <- highest - 1
432 while highest > lowest && q.[highest].Count = 0 do
433 highest <- highest - 1
434 elif vi - 1 = lowest
435 then
436 lowest <- lowest + 1
437 while lowest < highest && q.[lowest + 1].Count = 0 do
438 lowest <- lowest + 1
439
440 if highest = lowest // The queue is now empty.
441 then
442 highest <- -1
443 lowest <- size
444
445 member this.IsEmpty =
446 highest = -1
447
448 member this.Clear () =
449 while highest > lowest do
450 q.[highest].Clear()
451 highest <- highest - 1
452 highest <- -1
453 lowest <- size
454
455 type private AreaState =
456 | Removed = 1
457 | Unprocessed = 2
458 | Validated = 3
459
460 type private AreaOperation =
461 | Opening = 1
462 | Closing = 2
463
464 [<AllowNullLiteral>]
465 type private Area (elements: Points) =
466 member this.Elements = elements
467 member val Intensity = None with get, set
468 member val State = AreaState.Unprocessed with get, set
469
470 let private areaOperation (img: Image<Gray, byte>) (area: int) (op: AreaOperation) =
471 let w = img.Width
472 let h = img.Height
473 let imgData = img.Data
474 let se = [| -1, 0; 0, -1; 1, 0; 0, 1 |]
475
476 let areas = List<Area>((if op = AreaOperation.Opening then findMaxima img else findMinima img) |> Seq.map Area)
477
478 let pixels: Area[,] = Array2D.create h w null
479 for m in areas do
480 for e in m.Elements do
481 pixels.[e.Y, e.X] <- m
482
483 let queue = PriorityQueue()
484
485 let addEdgeToQueue (elements: Points) =
486 for p in elements do
487 for i, j in se do
488 let ni = i + p.Y
489 let nj = j + p.X
490 let p' = Point(nj, ni)
491 if ni >= 0 && ni < h && nj >= 0 && nj < w && not (elements.Contains(p'))
492 then
493 queue.Add (imgData.[ni, nj, 0]) p'
494
495 // Reverse order is quicker.
496 for i in areas.Count - 1 .. -1 .. 0 do
497 let m = areas.[i]
498 if m.Elements.Count <= area && m.State <> AreaState.Removed
499 then
500 queue.Clear()
501 addEdgeToQueue m.Elements
502
503 let mutable intensity = if op = AreaOperation.Opening then queue.Max else queue.Min
504 let nextElements = Points()
505
506 let mutable stop = false
507 while not stop do
508 let intensity', p = if op = AreaOperation.Opening then queue.NextMax () else queue.NextMin ()
509 let mutable merged = false
510
511 if intensity' = intensity // The intensity doesn't change.
512 then
513 if m.Elements.Count + nextElements.Count + 1 > area
514 then
515 m.State <- AreaState.Validated
516 m.Intensity <- Some intensity
517 stop <- true
518 else
519 nextElements.Add(p) |> ignore
520
521 elif if op = AreaOperation.Opening then intensity' < intensity else intensity' > intensity
522 then
523 m.Elements.UnionWith(nextElements)
524 for e in nextElements do
525 pixels.[e.Y, e.X] <- m
526
527 if m.Elements.Count = area
528 then
529 m.State <- AreaState.Validated
530 m.Intensity <- Some (intensity')
531 stop <- true
532 else
533 intensity <- intensity'
534 nextElements.Clear()
535 nextElements.Add(p) |> ignore
536
537 else
538 match pixels.[p.Y, p.X] with
539 | null -> ()
540 | m' ->
541 if m'.Elements.Count + m.Elements.Count <= area
542 then
543 m'.State <- AreaState.Removed
544 for e in m'.Elements do
545 pixels.[e.Y, e.X] <- m
546 queue.Remove imgData.[e.Y, e.X, 0] e
547 addEdgeToQueue m'.Elements
548 m.Elements.UnionWith(m'.Elements)
549 let intensityMax = if op = AreaOperation.Opening then queue.Max else queue.Min
550 if intensityMax <> intensity
551 then
552 intensity <- intensityMax
553 nextElements.Clear()
554 merged <- true
555
556 if not merged
557 then
558 m.State <- AreaState.Validated
559 m.Intensity <- Some (intensity)
560 stop <- true
561
562 if not stop && not merged
563 then
564 for i, j in se do
565 let ni = i + p.Y
566 let nj = j + p.X
567 let p' = Point(nj, ni)
568 if ni < 0 || ni >= h || nj < 0 || nj >= w
569 then
570 m.State <- AreaState.Validated
571 m.Intensity <- Some (intensity)
572 stop <- true
573 elif not (m.Elements.Contains(p')) && not (nextElements.Contains(p'))
574 then
575 queue.Add (imgData.[ni, nj, 0]) p'
576
577 if queue.IsEmpty
578 then
579 if m.Elements.Count + nextElements.Count <= area
580 then
581 m.State <- AreaState.Validated
582 m.Intensity <- Some intensity'
583 m.Elements.UnionWith(nextElements)
584 stop <- true
585
586 for m in areas do
587 if m.State = AreaState.Validated
588 then
589 match m.Intensity with
590 | Some i ->
591 for p in m.Elements do
592 imgData.[p.Y, p.X, 0] <- i
593 | _ -> ()
594 ()
595
596 /// <summary>
597 /// Area opening on byte image.
598 /// </summary>
599 let areaOpen (img: Image<Gray, byte>) (area: int) =
600 areaOperation img area AreaOperation.Opening
601
602 /// <summary>
603 /// Area closing on byte image.
604 /// </summary>
605 let areaClose (img: Image<Gray, byte>) (area: int) =
606 areaOperation img area AreaOperation.Closing
607
608 // A simpler algorithm than 'areaOpen' on byte image but slower.
609 let areaOpen2 (img: Image<Gray, byte>) (area: int) =
610 let w = img.Width
611 let h = img.Height
612 let imgData = img.Data
613 let se = [| -1, 0; 0, -1; 1, 0; 0, 1 |]
614
615 let histogram = Array.zeroCreate 256
616 for i in 0 .. h - 1 do
617 for j in 0 .. w - 1 do
618 let v = imgData.[i, j, 0] |> int
619 histogram.[v] <- histogram.[v] + 1
620
621 let flooded : bool[,] = Array2D.zeroCreate h w
622
623 let pointsChecked = HashSet<Point>()
624 let pointsToCheck = Stack<Point>()
625
626 for level in 255 .. -1 .. 0 do
627 let mutable n = histogram.[level]
628 if n > 0
629 then
630 for i in 0 .. h - 1 do
631 for j in 0 .. w - 1 do
632 if not flooded.[i, j] && imgData.[i, j, 0] = byte level
633 then
634 let mutable maxNeighborValue = 0uy
635 pointsChecked.Clear()
636 pointsToCheck.Clear()
637 pointsToCheck.Push(Point(j, i))
638
639 while pointsToCheck.Count > 0 do
640 let next = pointsToCheck.Pop()
641 pointsChecked.Add(next) |> ignore
642 flooded.[next.Y, next.X] <- true
643
644 for nx, ny in se do
645 let p = Point(next.X + nx, next.Y + ny)
646 if p.X >= 0 && p.X < w && p.Y >= 0 && p.Y < h
647 then
648 let v = imgData.[p.Y, p.X, 0]
649 if v = byte level
650 then
651 if not (pointsChecked.Contains(p))
652 then
653 pointsToCheck.Push(p)
654 elif v > maxNeighborValue
655 then
656 maxNeighborValue <- v
657
658 if int maxNeighborValue < level && pointsChecked.Count <= area
659 then
660 for p in pointsChecked do
661 imgData.[p.Y, p.X, 0] <- maxNeighborValue
662
663 [<AllowNullLiteral>]
664 type Island (cmp: IComparer<float32>) =
665 member val Shore = Heap.Heap<float32, Point>(cmp) with get
666 member val Level = 0.f with get, set
667 member val Surface = 0 with get, set
668 member this.IsInfinite = this.Surface = Int32.MaxValue
669
670 let private areaOperationF (img: Image<Gray, float32>) (areas: (int * 'a) list) (f: ('a -> float32 -> unit) option) (op: AreaOperation) =
671 let w = img.Width
672 let h = img.Height
673 let earth = img.Data
674 let se = [| -1, 0; 0, -1; 1, 0; 0, 1 |]
675
676 let comparer = if op = AreaOperation.Opening
677 then { new IComparer<float32> with member this.Compare(v1, v2) = v1.CompareTo(v2) }
678 else { new IComparer<float32> with member this.Compare(v1, v2) = v2.CompareTo(v1) }
679
680 let ownership: Island[,] = Array2D.create h w null
681
682 // Initialize islands with their shore.
683 let islands = List<Island>()
684 let extremum = img |> if op = AreaOperation.Opening then findMaxima else findMinima
685 for e in extremum do
686 let island =
687 let p = e.First()
688 Island(comparer, Level = earth.[p.Y, p.X, 0], Surface = e.Count)
689 islands.Add(island)
690 let shorePoints = Points()
691 for p in e do
692 ownership.[p.Y, p.X] <- island
693 for i, j in se do
694 let ni = i + p.Y
695 let nj = j + p.X
696 let neighbor = Point(nj, ni)
697 if ni >= 0 && ni < h && nj >= 0 && nj < w && Object.ReferenceEquals(ownership.[ni, nj], null) && not (shorePoints.Contains(neighbor))
698 then
699 shorePoints.Add(neighbor) |> ignore
700 island.Shore.Add earth.[ni, nj, 0] neighbor
701
702 for area, obj in areas do
703 for island in islands do
704 let mutable stop = island.Shore.IsEmpty
705
706 // 'true' if 'p' is owned or adjacent to 'island'.
707 let inline ownedOrAdjacent (p: Point) : bool =
708 ownership.[p.Y, p.X] = island ||
709 (p.Y > 0 && ownership.[p.Y - 1, p.X] = island) ||
710 (p.Y < h - 1 && ownership.[p.Y + 1, p.X] = island) ||
711 (p.X > 0 && ownership.[p.Y, p.X - 1] = island) ||
712 (p.X < w - 1 && ownership.[p.Y, p.X + 1] = island)
713
714 while not stop && island.Surface < area do
715 let level, next = island.Shore.Max
716 let other = ownership.[next.Y, next.X]
717 if other = island // During merging, some points on the shore may be owned by the island itself -> ignored.
718 then
719 island.Shore.RemoveNext ()
720 else
721 if not <| Object.ReferenceEquals(other, null)
722 then // We touching another island.
723 if island.IsInfinite || other.IsInfinite || island.Surface + other.Surface >= area || comparer.Compare(island.Level, other.Level) < 0
724 then
725 stop <- true
726 else // We can merge 'other' into 'surface'.
727 island.Surface <- island.Surface + other.Surface
728 island.Level <- other.Level
729 // island.Level <- if comparer.Compare(island.Level, other.Level) > 0 then other.Level else island.Level
730 for l, p in other.Shore do
731 let mutable currentY = p.Y + 1
732 while currentY < h && ownership.[currentY, p.X] = other do
733 ownership.[currentY, p.X] <- island
734 currentY <- currentY + 1
735 island.Shore.Add l p
736 other.Shore.Clear()
737
738 elif comparer.Compare(level, island.Level) > 0
739 then
740 stop <- true
741 else
742 island.Shore.RemoveNext ()
743 for i, j in se do
744 let ni = i + next.Y
745 let nj = j + next.X
746 if ni < 0 || ni >= h || nj < 0 || nj >= w
747 then
748 island.Surface <- Int32.MaxValue
749 stop <- true
750 else
751 let neighbor = Point(nj, ni)
752 if not <| ownedOrAdjacent neighbor
753 then
754 island.Shore.Add earth.[ni, nj, 0] neighbor
755 if not stop
756 then
757 ownership.[next.Y, next.X] <- island
758 island.Level <- level
759 island.Surface <- island.Surface + 1
760
761 let mutable diff = 0.f
762
763 for i in 0 .. h - 1 do
764 for j in 0 .. w - 1 do
765 match ownership.[i, j] with
766 | null -> ()
767 | island ->
768 let l = island.Level
769 diff <- diff + l - earth.[i, j, 0]
770 earth.[i, j, 0] <- l
771
772 match f with
773 | Some f' -> f' obj diff
774 | _ -> ()
775 ()
776
777 /// <summary>
778 /// Area opening on float image.
779 /// </summary>
780 let areaOpenF (img: Image<Gray, float32>) (area: int) =
781 areaOperationF img [ area, () ] None AreaOperation.Opening
782
783 /// <summary>
784 /// Area closing on float image.
785 /// </summary>
786 let areaCloseF (img: Image<Gray, float32>) (area: int) =
787 areaOperationF img [ area, () ] None AreaOperation.Closing
788
789 /// <summary>
790 /// Area closing on float image with different areas. Given areas must be sorted increasingly.
791 /// For each area the function 'f' is called with the associated area value of type 'a and the volume difference
792 /// Between the previous and the current closing.
793 /// </summary>
794 let areaOpenFWithFun (img: Image<Gray, float32>) (areas: (int * 'a) list) (f: 'a -> float32 -> unit) =
795 areaOperationF img areas (Some f) AreaOperation.Opening
796
797 /// <summary>
798 /// Same as 'areaOpenFWithFun' for closing operation.
799 /// </summary>
800 let areaCloseFWithFun (img: Image<Gray, float32>) (areas: (int * 'a) list) (f: 'a -> float32 -> unit) =
801 areaOperationF img areas (Some f) AreaOperation.Closing
802
803 /// <summary>
804 /// Zhang and Suen thinning algorithm.
805 /// Modify 'mat' in place.
806 /// </summary>
807 let thin (mat: Matrix<byte>) =
808 let w = mat.Width
809 let h = mat.Height
810 let mutable data1 = mat.Data
811 let mutable data2 = Array2D.copy data1
812
813 let mutable pixelChanged = true
814 let mutable oddIteration = true
815
816 while pixelChanged do
817 pixelChanged <- false
818 for i in 0..h-1 do
819 for j in 0..w-1 do
820 if data1.[i, j] = 1uy
821 then
822 let p2 = if i = 0 then 0uy else data1.[i-1, j]
823 let p3 = if i = 0 || j = w-1 then 0uy else data1.[i-1, j+1]
824 let p4 = if j = w-1 then 0uy else data1.[i, j+1]
825 let p5 = if i = h-1 || j = w-1 then 0uy else data1.[i+1, j+1]
826 let p6 = if i = h-1 then 0uy else data1.[i+1, j]
827 let p7 = if i = h-1 || j = 0 then 0uy else data1.[i+1, j-1]
828 let p8 = if j = 0 then 0uy else data1.[i, j-1]
829 let p9 = if i = 0 || j = 0 then 0uy else data1.[i-1, j-1]
830
831 let sumNeighbors = p2 + p3 + p4 + p5 + p6 + p7 + p8 + p9
832 if sumNeighbors >= 2uy && sumNeighbors <= 6uy &&
833 (if p2 = 0uy && p3 = 1uy then 1 else 0) +
834 (if p3 = 0uy && p4 = 1uy then 1 else 0) +
835 (if p4 = 0uy && p5 = 1uy then 1 else 0) +
836 (if p5 = 0uy && p6 = 1uy then 1 else 0) +
837 (if p6 = 0uy && p7 = 1uy then 1 else 0) +
838 (if p7 = 0uy && p8 = 1uy then 1 else 0) +
839 (if p8 = 0uy && p9 = 1uy then 1 else 0) +
840 (if p9 = 0uy && p2 = 1uy then 1 else 0) = 1 &&
841 if oddIteration
842 then p2 * p4 * p6 = 0uy && p4 * p6 * p8 = 0uy
843 else p2 * p4 * p8 = 0uy && p2 * p6 * p8 = 0uy
844 then
845 data2.[i, j] <- 0uy
846 pixelChanged <- true
847 else
848 data2.[i, j] <- 0uy
849
850 oddIteration <- not oddIteration
851 let tmp = data1
852 data1 <- data2
853 data2 <- tmp
854
855 /// <summary>
856 /// Remove all 8-connected pixels with an area equal or greater than 'areaSize'.
857 /// Modify 'mat' in place.
858 /// </summary>
859 let removeArea (mat: Matrix<byte>) (areaSize: int) =
860 let neighbors = [|
861 (-1, 0) // p2
862 (-1, 1) // p3
863 ( 0, 1) // p4
864 ( 1, 1) // p5
865 ( 1, 0) // p6
866 ( 1, -1) // p7
867 ( 0, -1) // p8
868 (-1, -1) |] // p9
869
870 use mat' = new Matrix<byte>(mat.Size)
871 let w = mat'.Width
872 let h = mat'.Height
873 mat.CopyTo(mat')
874
875 let data = mat.Data
876 let data' = mat'.Data
877
878 for i in 0..h-1 do
879 for j in 0..w-1 do
880 if data'.[i, j] = 1uy
881 then
882 let neighborhood = List<Point>()
883 let neighborsToCheck = Stack<Point>()
884 neighborsToCheck.Push(Point(j, i))
885 data'.[i, j] <- 0uy
886
887 while neighborsToCheck.Count > 0 do
888 let n = neighborsToCheck.Pop()
889 neighborhood.Add(n)
890 for (ni, nj) in neighbors do
891 let pi = n.Y + ni
892 let pj = n.X + nj
893 if pi >= 0 && pi < h && pj >= 0 && pj < w && data'.[pi, pj] = 1uy
894 then
895 neighborsToCheck.Push(Point(pj, pi))
896 data'.[pi, pj] <- 0uy
897 if neighborhood.Count <= areaSize
898 then
899 for n in neighborhood do
900 data.[n.Y, n.X] <- 0uy
901
902 let connectedComponents (img: Image<Gray, byte>) (startPoints: List<Point>) : Points =
903 let w = img.Width
904 let h = img.Height
905
906 let pointChecked = Points()
907 let pointToCheck = Stack<Point>(startPoints);
908
909 let data = img.Data
910
911 while pointToCheck.Count > 0 do
912 let next = pointToCheck.Pop()
913 pointChecked.Add(next) |> ignore
914 for ny in -1 .. 1 do
915 for nx in -1 .. 1 do
916 if ny <> 0 && nx <> 0
917 then
918 let p = Point(next.X + nx, next.Y + ny)
919 if p.X >= 0 && p.X < w && p.Y >= 0 && p.Y < h && data.[p.Y, p.X, 0] > 0uy && not (pointChecked.Contains p)
920 then
921 pointToCheck.Push(p)
922
923 pointChecked
924
925 let drawLine (img: Image<'TColor, 'TDepth>) (color: 'TColor) (x0: int) (y0: int) (x1: int) (y1: int) (thickness: int) =
926 img.Draw(LineSegment2D(Point(x0, y0), Point(x1, y1)), color, thickness);
927
928 let drawLineF (img: Image<'TColor, 'TDepth>) (color: 'TColor) (x0: float) (y0: float) (x1: float) (y1: float) (thickness: int) =
929 img.Draw(LineSegment2DF(PointF(float32 x0, float32 y0), PointF(float32 x1, float32 y1)), color, thickness, CvEnum.LineType.AntiAlias);
930
931 let drawEllipse (img: Image<'TColor, 'TDepth>) (e: Ellipse) (color: 'TColor) (alpha: float) =
932 if alpha >= 1.0
933 then
934 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)
935 else
936 let windowPosX = e.Cx - e.A - 5.f
937 let gapX = windowPosX - (float32 (int windowPosX))
938
939 let windowPosY = e.Cy - e.A - 5.f
940 let gapY = windowPosY - (float32 (int windowPosY))
941
942 let roi = Rectangle(int windowPosX, int windowPosY, 2.f * (e.A + 5.f) |> int, 2.f * (e.A + 5.f) |> int)
943
944 img.ROI <- roi
945 if roi = img.ROI // We do not display ellipses touching the edges (FIXME)
946 then
947 use i = new Image<'TColor, 'TDepth>(img.ROI.Size)
948 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)
949 CvInvoke.AddWeighted(img, 1.0, i, alpha, 0.0, img)
950 img.ROI <- Rectangle.Empty
951
952 let drawEllipses (img: Image<'TColor, 'TDepth>) (ellipses: Ellipse list) (color: 'TColor) (alpha: float) =
953 List.iter (fun e -> drawEllipse img e color alpha) ellipses
954
955 let rngCell = System.Random()
956 let drawCell (img: Image<Bgr, byte>) (drawCellContent: bool) (c: Cell) =
957 if drawCellContent
958 then
959 let colorB = rngCell.Next(20, 70)
960 let colorG = rngCell.Next(20, 70)
961 let colorR = rngCell.Next(20, 70)
962
963 for y in 0 .. c.elements.Height - 1 do
964 for x in 0 .. c.elements.Width - 1 do
965 if c.elements.[y, x] > 0uy
966 then
967 let dx, dy = c.center.X - c.elements.Width / 2, c.center.Y - c.elements.Height / 2
968 let b = img.Data.[y + dy, x + dx, 0] |> int
969 let g = img.Data.[y + dy, x + dx, 1] |> int
970 let r = img.Data.[y + dy, x + dx, 2] |> int
971 img.Data.[y + dy, x + dx, 0] <- if b + colorB > 255 then 255uy else byte (b + colorB)
972 img.Data.[y + dy, x + dx, 1] <- if g + colorG > 255 then 255uy else byte (g + colorG)
973 img.Data.[y + dy, x + dx, 2] <- if r + colorR > 255 then 255uy else byte (r + colorR)
974
975 let crossColor, crossColor2 =
976 match c.cellClass with
977 | HealthyRBC -> Bgr(255., 0., 0.), Bgr(255., 255., 255.)
978 | InfectedRBC -> Bgr(0., 0., 255.), Bgr(120., 120., 255.)
979 | Peculiar -> Bgr(0., 0., 0.), Bgr(80., 80., 80.)
980
981 drawLine img crossColor2 (c.center.X - 3) c.center.Y (c.center.X + 3) c.center.Y 2
982 drawLine img crossColor2 c.center.X (c.center.Y - 3) c.center.X (c.center.Y + 3) 2
983
984 drawLine img crossColor (c.center.X - 3) c.center.Y (c.center.X + 3) c.center.Y 1
985 drawLine img crossColor c.center.X (c.center.Y - 3) c.center.X (c.center.Y + 3) 1
986
987
988 let drawCells (img: Image<Bgr, byte>) (drawCellContent: bool) (cells: Cell list) =
989 List.iter (fun c -> drawCell img drawCellContent c) cells