Use k-means instead of k-medians.
[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 CvInvoke.CartToPolar(xGradient, yGradient, magnitudes, new Mat()) // Compute the magnitudes (without angles).
76
77 let thresholdHigh, thresholdLow =
78 let sensibility = 0.1
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)
82
83 // Non-maximum suppression.
84 use nms = new Matrix<byte>(xGradient.Size)
85 nms.SetValue(1.0)
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 let angle =
100 let a = atan2 vy vx
101 if a < 0.0 then 2. * Math.PI + a else a
102
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]
112
113 let m = magnitudes.Data.[i, j]
114 if m < mNeigbors 1 || m < mNeigbors -1 || m < thresholdLow
115 then
116 nms.Data.[i, j] <- 0uy
117
118 // suppressMConnections nms // It's not usefull for the rest of the process (ellipse detection).
119
120 let edges = new Matrix<byte>(xGradient.Size)
121
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
127 then
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
133 for i' in -1 .. 1 do
134 for j' in -1 .. 1 do
135 if i' <> 0 || j' <> 0
136 then
137 let ni = p.Y + i'
138 let nj = p.X + j'
139 if ni >= 0 && ni < h && nj >= 0 && nj < w && nms.Data.[ni, nj] = 1uy
140 then
141 nms.Data.[ni, nj] <- 0uy
142 toVisit.Push(Point(nj, ni))
143
144
145 edges, xGradient, yGradient
146
147
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)
151
152
153 type Points = HashSet<Point>
154
155 let drawPoints (img: Image<Gray, byte>) (points: Points) (intensity: byte) =
156 for p in points do
157 img.Data.[p.Y, p.X, 0] <- intensity
158
159 type ExtremumType =
160 | Maxima = 1
161 | Minima = 2
162
163 let findExtremum (img: Image<Gray, byte>) (extremumType: ExtremumType) : IEnumerable<Points> =
164 let w = img.Width
165 let h = img.Height
166 let se = [| -1, 0; 0, -1; 1, 0; 0, 1 |]
167
168 let imgData = img.Data
169 let suppress: bool[,] = Array2D.zeroCreate h w
170
171 let result = List<List<Point>>()
172
173 let flood (start: Point) : List<List<Point>> =
174 let sameLevelToCheck = Stack<Point>()
175 let betterLevelToCheck = Stack<Point>()
176 betterLevelToCheck.Push(start)
177
178 let result' = List<List<Point>>()
179
180 while betterLevelToCheck.Count > 0 do
181 let p = betterLevelToCheck.Pop()
182 if not suppress.[p.Y, p.X]
183 then
184 suppress.[p.Y, p.X] <- true
185 sameLevelToCheck.Push(p)
186 let current = List<Point>()
187
188 let mutable betterExists = false
189
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
194 for i, j in se do
195 let ni = i + p'.Y
196 let nj = j + p'.X
197 if ni >= 0 && ni < h && nj >= 0 && nj < w
198 then
199 let level = imgData.[ni, nj, 0]
200 let notSuppressed = not suppress.[ni, nj]
201
202 if level = currentLevel && notSuppressed
203 then
204 suppress.[ni, nj] <- true
205 sameLevelToCheck.Push(Point(nj, ni))
206 elif if extremumType = ExtremumType.Maxima then level > currentLevel else level < currentLevel
207 then
208 betterExists <- true
209 if notSuppressed
210 then
211 betterLevelToCheck.Push(Point(nj, ni))
212
213 if not betterExists
214 then
215 result'.Add(current)
216 result'
217
218 for i in 0 .. h - 1 do
219 for j in 0 .. w - 1 do
220 let maxima = flood (Point(j, i))
221 if maxima.Count > 0
222 then
223 result.AddRange(maxima)
224
225 result.Select(fun l -> Points(l))
226
227
228 let findMaxima (img: Image<Gray, byte>) : IEnumerable<Points> =
229 findExtremum img ExtremumType.Maxima
230
231 let findMinima (img: Image<Gray, byte>) : IEnumerable<Points> =
232 findExtremum img ExtremumType.Minima
233
234
235 type PriorityQueue () =
236 let size = 256
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
240
241 member this.NextMax () : byte * Point =
242 if this.IsEmpty
243 then
244 invalidOp "Queue is empty"
245 else
246 let l = q.[highest]
247 let next = l.First()
248 l.Remove(next) |> ignore
249 let value = byte highest
250
251 if l.Count = 0
252 then
253 highest <- highest - 1
254 while highest > lowest && q.[highest].Count = 0 do
255 highest <- highest - 1
256 if highest = lowest
257 then
258 highest <- -1
259 lowest <- size
260
261 value, next
262
263 member this.NextMin () : byte * Point =
264 if this.IsEmpty
265 then
266 invalidOp "Queue is empty"
267 else
268 let l = q.[lowest + 1]
269 let next = l.First()
270 l.Remove(next) |> ignore
271 let value = byte (lowest + 1)
272
273 if l.Count = 0
274 then
275 lowest <- lowest + 1
276 while lowest < highest && q.[lowest + 1].Count = 0 do
277 lowest <- lowest + 1
278 if highest = lowest
279 then
280 highest <- -1
281 lowest <- size
282
283 value, next
284
285 member this.Max =
286 highest |> byte
287
288 member this.Min =
289 lowest + 1 |> byte
290
291 member this.Add (value: byte) (p: Point) =
292 let vi = int value
293
294 if vi > highest
295 then
296 highest <- vi
297 if vi <= lowest
298 then
299 lowest <- vi - 1
300
301 q.[vi].Add(p) |> ignore
302
303 member this.Remove (value: byte) (p: Point) =
304 let vi = int value
305 if q.[vi].Remove(p) && q.[vi].Count = 0
306 then
307 if vi = highest
308 then
309 highest <- highest - 1
310 while highest > lowest && q.[highest].Count = 0 do
311 highest <- highest - 1
312 elif vi - 1 = lowest
313 then
314 lowest <- lowest + 1
315 while lowest < highest && q.[lowest + 1].Count = 0 do
316 lowest <- lowest + 1
317
318 if highest = lowest // The queue is now empty.
319 then
320 highest <- -1
321 lowest <- size
322
323 member this.IsEmpty =
324 highest = -1
325
326 member this.Clear () =
327 while highest > lowest do
328 q.[highest].Clear()
329 highest <- highest - 1
330 highest <- -1
331 lowest <- size
332
333
334 type private AreaState =
335 | Removed = 1
336 | Unprocessed = 2
337 | Validated = 3
338
339 type private AreaOperation =
340 | Opening = 1
341 | Closing = 2
342
343 [<AllowNullLiteral>]
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
348
349 let private areaOperation (img: Image<Gray, byte>) (area: int) (op: AreaOperation) =
350 let w = img.Width
351 let h = img.Height
352 let imgData = img.Data
353 let se = [| -1, 0; 0, -1; 1, 0; 0, 1 |]
354
355 let areas = List<Area>((if op = AreaOperation.Opening then findMaxima img else findMinima img) |> Seq.map Area)
356
357 let pixels: Area[,] = Array2D.create h w null
358 for m in areas do
359 for e in m.Elements do
360 pixels.[e.Y, e.X] <- m
361
362 let queue = PriorityQueue()
363
364 let addEdgeToQueue (elements: Points) =
365 for p in elements do
366 for i, j in se do
367 let ni = i + p.Y
368 let nj = j + p.X
369 let p' = Point(nj, ni)
370 if ni >= 0 && ni < h && nj >= 0 && nj < w && not (elements.Contains(p'))
371 then
372 queue.Add (imgData.[ni, nj, 0]) p'
373
374 // Reverse order is quicker.
375 for i in areas.Count - 1 .. -1 .. 0 do
376 let m = areas.[i]
377 if m.Elements.Count <= area && m.State <> AreaState.Removed
378 then
379 queue.Clear()
380 addEdgeToQueue m.Elements
381
382 let mutable intensity = if op = AreaOperation.Opening then queue.Max else queue.Min
383 let nextElements = Points()
384
385 let mutable stop = false
386 while not stop do
387 let intensity', p = if op = AreaOperation.Opening then queue.NextMax () else queue.NextMin ()
388 let mutable merged = false
389
390 if intensity' = intensity // The intensity doesn't change.
391 then
392 if m.Elements.Count + nextElements.Count + 1 > area
393 then
394 m.State <- AreaState.Validated
395 m.Intensity <- Some intensity
396 stop <- true
397 else
398 nextElements.Add(p) |> ignore
399
400 elif if op = AreaOperation.Opening then intensity' < intensity else intensity' > intensity
401 then
402 m.Elements.UnionWith(nextElements)
403 for e in nextElements do
404 pixels.[e.Y, e.X] <- m
405
406 if m.Elements.Count = area
407 then
408 m.State <- AreaState.Validated
409 m.Intensity <- Some (intensity')
410 stop <- true
411 else
412 intensity <- intensity'
413 nextElements.Clear()
414 nextElements.Add(p) |> ignore
415
416 else
417 let m' = pixels.[p.Y, p.X]
418 if m' <> null
419 then
420 if m'.Elements.Count + m.Elements.Count <= area
421 then
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
430 then
431 intensity <- intensityMax
432 nextElements.Clear()
433 merged <- true
434
435 if not merged
436 then
437 m.State <- AreaState.Validated
438 m.Intensity <- Some (intensity)
439 stop <- true
440
441 if not stop && not merged
442 then
443 for i, j in se do
444 let ni = i + p.Y
445 let nj = j + p.X
446 let p' = Point(nj, ni)
447 if ni < 0 || ni >= h || nj < 0 || nj >= w
448 then
449 m.State <- AreaState.Validated
450 m.Intensity <- Some (intensity)
451 stop <- true
452 elif not (m.Elements.Contains(p')) && not (nextElements.Contains(p'))
453 then
454 queue.Add (imgData.[ni, nj, 0]) p'
455
456 if queue.IsEmpty
457 then
458 if m.Elements.Count + nextElements.Count <= area
459 then
460 m.State <- AreaState.Validated
461 m.Intensity <- Some intensity'
462 m.Elements.UnionWith(nextElements)
463 stop <- true
464
465 for m in areas do
466 if m.State = AreaState.Validated
467 then
468 match m.Intensity with
469 | Some i ->
470 for p in m.Elements do
471 imgData.[p.Y, p.X, 0] <- i
472 | _ -> ()
473 ()
474
475
476 let areaOpen (img: Image<Gray, byte>) (area: int) =
477 areaOperation img area AreaOperation.Opening
478
479 let areaClose (img: Image<Gray, byte>) (area: int) =
480 areaOperation img area AreaOperation.Closing
481
482 // A simpler algorithm than 'areaOpen' but slower.
483 let areaOpen2 (img: Image<Gray, byte>) (area: int) =
484 let w = img.Width
485 let h = img.Height
486 let imgData = img.Data
487 let se = [| -1, 0; 0, -1; 1, 0; 0, 1 |]
488
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
494
495 let flooded : bool[,] = Array2D.zeroCreate h w
496
497 let pointsChecked = HashSet<Point>()
498 let pointsToCheck = Stack<Point>()
499
500 for level in 255 .. -1 .. 0 do
501 let mutable n = histogram.[level]
502 if n > 0
503 then
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
507 then
508 let mutable maxNeighborValue = 0uy
509 pointsChecked.Clear()
510 pointsToCheck.Clear()
511 pointsToCheck.Push(Point(j, i))
512
513 while pointsToCheck.Count > 0 do
514 let next = pointsToCheck.Pop()
515 pointsChecked.Add(next) |> ignore
516 flooded.[next.Y, next.X] <- true
517
518 for nx, ny in se do
519 let p = Point(next.X + nx, next.Y + ny)
520 if p.X >= 0 && p.X < w && p.Y >= 0 && p.Y < h
521 then
522 let v = imgData.[p.Y, p.X, 0]
523 if v = byte level
524 then
525 if not (pointsChecked.Contains(p))
526 then
527 pointsToCheck.Push(p)
528 elif v > maxNeighborValue
529 then
530 maxNeighborValue <- v
531
532 if int maxNeighborValue < level && pointsChecked.Count <= area
533 then
534 for p in pointsChecked do
535 imgData.[p.Y, p.X, 0] <- maxNeighborValue
536
537
538 // Zhang and Suen algorithm.
539 // Modify 'mat' in place.
540 let thin (mat: Matrix<byte>) =
541 let w = mat.Width
542 let h = mat.Height
543 let mutable data1 = mat.Data
544 let mutable data2 = Array2D.copy data1
545
546 let mutable pixelChanged = true
547 let mutable oddIteration = true
548
549 while pixelChanged do
550 pixelChanged <- false
551 for i in 0..h-1 do
552 for j in 0..w-1 do
553 if data1.[i, j] = 1uy
554 then
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]
563
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 &&
574 if oddIteration
575 then p2 * p4 * p6 = 0uy && p4 * p6 * p8 = 0uy
576 else p2 * p4 * p8 = 0uy && p2 * p6 * p8 = 0uy
577 then
578 data2.[i, j] <- 0uy
579 pixelChanged <- true
580 else
581 data2.[i, j] <- 0uy
582
583 oddIteration <- not oddIteration
584 let tmp = data1
585 data1 <- data2
586 data2 <- tmp
587
588
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) =
592 let neighbors = [|
593 (-1, 0) // p2
594 (-1, 1) // p3
595 ( 0, 1) // p4
596 ( 1, 1) // p5
597 ( 1, 0) // p6
598 ( 1, -1) // p7
599 ( 0, -1) // p8
600 (-1, -1) |] // p9
601
602 let mat' = new Matrix<byte>(mat.Size)
603 let w = mat'.Width
604 let h = mat'.Height
605 mat.CopyTo(mat')
606
607 let data = mat.Data
608 let data' = mat'.Data
609
610 for i in 0..h-1 do
611 for j in 0..w-1 do
612 if data'.[i, j] = 1uy
613 then
614 let neighborhood = List<Point>()
615 let neighborsToCheck = Stack<Point>()
616 neighborsToCheck.Push(Point(j, i))
617 data'.[i, j] <- 0uy
618
619 while neighborsToCheck.Count > 0 do
620 let n = neighborsToCheck.Pop()
621 neighborhood.Add(n)
622 for (ni, nj) in neighbors do
623 let pi = n.Y + ni
624 let pj = n.X + nj
625 if pi >= 0 && pi < h && pj >= 0 && pj < w && data'.[pi, pj] = 1uy
626 then
627 neighborsToCheck.Push(Point(pj, pi))
628 data'.[pi, pj] <- 0uy
629 if neighborhood.Count <= areaSize
630 then
631 for n in neighborhood do
632 data.[n.Y, n.X] <- 0uy
633
634 let connectedComponents (img: Image<Gray, byte>) (startPoints: List<Point>) : List<Point> =
635 let w = img.Width
636 let h = img.Height
637
638 let pointChecked = Points()
639 let pointToCheck = Stack<Point>(startPoints);
640
641 let data = img.Data
642
643 while pointToCheck.Count > 0 do
644 let next = pointToCheck.Pop()
645 pointChecked.Add(next) |> ignore
646 for ny in -1 .. 1 do
647 for nx in -1 .. 1 do
648 if ny <> 0 && nx <> 0
649 then
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)
652 then
653 pointToCheck.Push(p)
654
655 List<Point>(pointChecked)
656
657
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);
660
661
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);
664
665
666 let drawEllipse (img: Image<'TColor, 'TDepth>) (e: Types.Ellipse) (color: 'TColor) (alpha: float) =
667
668 if alpha >= 1.0
669 then
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)
671 else
672 let windowPosX = e.Cx - e.A - 5.0
673 let gapX = windowPosX - (float (int windowPosX))
674
675 let windowPosY = e.Cy - e.A - 5.0
676 let gapY = windowPosY - (float (int windowPosY))
677
678 let roi = Rectangle(int windowPosX, int windowPosY, 2. * (e.A + 5.0) |> int, 2.* (e.A + 5.0) |> int)
679
680 img.ROI <- roi
681 if roi = img.ROI // We do not display ellipses touching the edges (FIXME)
682 then
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
687
688
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
691
692
693 let rngCell = System.Random()
694 let drawCell (img: Image<Bgr, byte>) (drawCellContent: bool) (c: Types.Cell) =
695 if drawCellContent
696 then
697 let colorB = rngCell.Next(20, 70)
698 let colorG = rngCell.Next(20, 70)
699 let colorR = rngCell.Next(20, 70)
700
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
704 then
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)
712
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.)
718
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
721
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
724
725
726 let drawCells (img: Image<Bgr, byte>) (drawCellContent: bool) (cells: Types.Cell list) =
727 List.iter (fun c -> drawCell img drawCellContent c) cells