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