* Treat some special cases when ellipses intersecting.
[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) then
40 img.[i, j] <- 0uy
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
44 img.[i, j] <- 0uy
45
46 let findEdges (img: Image<Gray, float32>) : Matrix<byte> * Image<Gray, float> * Image<Gray, float> =
47 let w = img.Width
48 let h = img.Height
49
50 use sobelKernel =
51 new ConvolutionKernelF(array2D [[ 1.0f; 0.0f; -1.0f ]
52 [ 2.0f; 0.0f; -2.0f ]
53 [ 1.0f; 0.0f; -1.0f ]], Point(1, 1))
54
55 let xGradient = img.Convolution(sobelKernel).Convert<Gray, float>()
56 let yGradient = img.Convolution(sobelKernel.Transpose()).Convert<Gray, float>()
57
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
65
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
71
72 use magnitudes = new Matrix<float>(xGradient.Size)
73 CvInvoke.CartToPolar(xGradient, yGradient, magnitudes, new Mat()) // Compute the magnitudes (without angles).
74
75 let thresholdHigh, thresholdLow =
76 let sensibility = 0.1
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)
80
81 // Non-maximum suppression.
82 use nms = new Matrix<byte>(xGradient.Size)
83 nms.SetValue(1.0)
84
85 for i in 0 .. h - 1 do
86 nms.Data.[i, 0] <- 0uy
87 nms.Data.[i, w - 1] <- 0uy
88
89 for j in 0 .. w - 1 do
90 nms.Data.[0, j] <- 0uy
91 nms.Data.[h - 1, j] <- 0uy
92
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]
97 let angle =
98 let a = atan2 vy vx
99 if a < 0.0 then 2. * Math.PI + a else a
100
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]
110
111 let m = magnitudes.Data.[i, j]
112 if m < mNeigbors 1 || m < mNeigbors -1 || m < thresholdLow then
113 nms.Data.[i, j] <- 0uy
114
115 // suppressMConnections nms // It's not usefull for the rest of the process (ellipse detection).
116
117 let edges = new Matrix<byte>(xGradient.Size)
118
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
129 for i' in -1 .. 1 do
130 for j' in -1 .. 1 do
131 if i' <> 0 || j' <> 0 then
132 let ni = p.Y + i'
133 let nj = p.X + j'
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))
137
138
139 edges, xGradient, yGradient
140
141
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)
145
146
147 type Points = HashSet<Point>
148
149 let drawPoints (img: Image<Gray, byte>) (points: Points) (intensity: byte) =
150 for p in points do
151 img.Data.[p.Y, p.X, 0] <- intensity
152
153 type ExtremumType =
154 | Maxima = 1
155 | Minima = 2
156
157 let findExtremum (img: Image<Gray, byte>) (extremumType: ExtremumType) : IEnumerable<Points> =
158 let w = img.Width
159 let h = img.Height
160 let se = [| -1, 0; 0, -1; 1, 0; 0, 1 |]
161
162 let imgData = img.Data
163 let suppress: bool[,] = Array2D.zeroCreate h w
164
165 let result = List<List<Point>>()
166
167 let flood (start: Point) : List<List<Point>> =
168 let sameLevelToCheck = Stack<Point>()
169 let betterLevelToCheck = Stack<Point>()
170 betterLevelToCheck.Push(start)
171
172 let result' = List<List<Point>>()
173
174 while betterLevelToCheck.Count > 0 do
175 let p = betterLevelToCheck.Pop()
176 if not suppress.[p.Y, p.X]
177 then
178 suppress.[p.Y, p.X] <- true
179 sameLevelToCheck.Push(p)
180 let current = List<Point>()
181
182 let mutable betterExists = false
183
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
188 for i, j in se do
189 let ni = i + p'.Y
190 let nj = j + p'.X
191 if ni >= 0 && ni < h && nj >= 0 && nj < w
192 then
193 let level = imgData.[ni, nj, 0]
194 let notSuppressed = not suppress.[ni, nj]
195
196 if level = currentLevel && notSuppressed
197 then
198 suppress.[ni, nj] <- true
199 sameLevelToCheck.Push(Point(nj, ni))
200 elif if extremumType = ExtremumType.Maxima then level > currentLevel else level < currentLevel
201 then
202 betterExists <- true
203 if notSuppressed
204 then
205 betterLevelToCheck.Push(Point(nj, ni))
206
207 if not betterExists
208 then
209 result'.Add(current)
210 result'
211
212 for i in 0 .. h - 1 do
213 for j in 0 .. w - 1 do
214 let maxima = flood (Point(j, i))
215 if maxima.Count > 0
216 then
217 result.AddRange(maxima)
218
219 result.Select(fun l -> Points(l))
220
221
222 let findMaxima (img: Image<Gray, byte>) : IEnumerable<Points> =
223 findExtremum img ExtremumType.Maxima
224
225 let findMinima (img: Image<Gray, byte>) : IEnumerable<Points> =
226 findExtremum img ExtremumType.Minima
227
228
229 type PriorityQueue () =
230 let size = 256
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
234
235 member this.NextMax () : byte * Point =
236 if this.IsEmpty
237 then
238 invalidOp "Queue is empty"
239 else
240 let l = q.[highest]
241 let next = l.First()
242 l.Remove(next) |> ignore
243 let value = byte highest
244
245 if l.Count = 0
246 then
247 highest <- highest - 1
248 while highest > lowest && q.[highest].Count = 0 do
249 highest <- highest - 1
250 if highest = lowest
251 then
252 highest <- -1
253 lowest <- size
254
255 value, next
256
257 member this.NextMin () : byte * Point =
258 if this.IsEmpty
259 then
260 invalidOp "Queue is empty"
261 else
262 let l = q.[lowest + 1]
263 let next = l.First()
264 l.Remove(next) |> ignore
265 let value = byte (lowest + 1)
266
267 if l.Count = 0
268 then
269 lowest <- lowest + 1
270 while lowest < highest && q.[lowest + 1].Count = 0 do
271 lowest <- lowest + 1
272 if highest = lowest
273 then
274 highest <- -1
275 lowest <- size
276
277 value, next
278
279 member this.Max =
280 highest |> byte
281
282 member this.Min =
283 lowest + 1 |> byte
284
285 member this.Add (value: byte) (p: Point) =
286 let vi = int value
287
288 if vi > highest
289 then
290 highest <- vi
291 if vi <= lowest
292 then
293 lowest <- vi - 1
294
295 q.[vi].Add(p) |> ignore
296
297 member this.Remove (value: byte) (p: Point) =
298 let vi = int value
299 if q.[vi].Remove(p) && q.[vi].Count = 0
300 then
301 if vi = highest
302 then
303 highest <- highest - 1
304 while highest > lowest && q.[highest].Count = 0 do
305 highest <- highest - 1
306 elif vi - 1 = lowest
307 then
308 lowest <- lowest + 1
309 while lowest < highest && q.[lowest + 1].Count = 0 do
310 lowest <- lowest + 1
311
312 if highest = lowest // The queue is now empty.
313 then
314 highest <- -1
315 lowest <- size
316
317 member this.IsEmpty =
318 highest = -1
319
320 member this.Clear () =
321 while highest > lowest do
322 q.[highest].Clear()
323 highest <- highest - 1
324 highest <- -1
325 lowest <- size
326
327
328 type private AreaState =
329 | Removed = 1
330 | Unprocessed = 2
331 | Validated = 3
332
333 type private AreaOperation =
334 | Opening = 1
335 | Closing = 2
336
337 [<AllowNullLiteral>]
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
342
343 let private areaOperation (img: Image<Gray, byte>) (area: int) (op: AreaOperation) =
344 let w = img.Width
345 let h = img.Height
346 let imgData = img.Data
347 let se = [| -1, 0; 0, -1; 1, 0; 0, 1 |]
348
349 let areas = List<Area>((if op = AreaOperation.Opening then findMaxima img else findMinima img) |> Seq.map Area)
350
351 let pixels: Area[,] = Array2D.create h w null
352 for m in areas do
353 for e in m.Elements do
354 pixels.[e.Y, e.X] <- m
355
356 let queue = PriorityQueue()
357
358 let addEdgeToQueue (elements: Points) =
359 for p in elements do
360 for i, j in se do
361 let ni = i + p.Y
362 let nj = j + p.X
363 let p' = Point(nj, ni)
364 if ni >= 0 && ni < h && nj >= 0 && nj < w && not (elements.Contains(p'))
365 then
366 queue.Add (imgData.[ni, nj, 0]) p'
367
368 // Reverse order is quicker.
369 for i in areas.Count - 1 .. -1 .. 0 do
370 let m = areas.[i]
371 if m.Elements.Count <= area && m.State <> AreaState.Removed
372 then
373 queue.Clear()
374 addEdgeToQueue m.Elements
375
376 let mutable intensity = if op = AreaOperation.Opening then queue.Max else queue.Min
377 let nextElements = Points()
378
379 let mutable stop = false
380 while not stop do
381 let intensity', p = if op = AreaOperation.Opening then queue.NextMax () else queue.NextMin ()
382 let mutable merged = false
383
384 if intensity' = intensity // The intensity doesn't change.
385 then
386 if m.Elements.Count + nextElements.Count + 1 > area
387 then
388 m.State <- AreaState.Validated
389 m.Intensity <- Some intensity
390 stop <- true
391 else
392 nextElements.Add(p) |> ignore
393
394 elif if op = AreaOperation.Opening then intensity' < intensity else intensity' > intensity
395 then
396 m.Elements.UnionWith(nextElements)
397 for e in nextElements do
398 pixels.[e.Y, e.X] <- m
399
400 if m.Elements.Count = area
401 then
402 m.State <- AreaState.Validated
403 m.Intensity <- Some (intensity')
404 stop <- true
405 else
406 intensity <- intensity'
407 nextElements.Clear()
408 nextElements.Add(p) |> ignore
409
410 else
411 let m' = pixels.[p.Y, p.X]
412 if m' <> null
413 then
414 if m'.Elements.Count + m.Elements.Count <= area
415 then
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
424 then
425 intensity <- intensityMax
426 nextElements.Clear()
427 merged <- true
428
429 if not merged
430 then
431 m.State <- AreaState.Validated
432 m.Intensity <- Some (intensity)
433 stop <- true
434
435 if not stop && not merged
436 then
437 for i, j in se do
438 let ni = i + p.Y
439 let nj = j + p.X
440 let p' = Point(nj, ni)
441 if ni < 0 || ni >= h || nj < 0 || nj >= w
442 then
443 m.State <- AreaState.Validated
444 m.Intensity <- Some (intensity)
445 stop <- true
446 elif not (m.Elements.Contains(p')) && not (nextElements.Contains(p'))
447 then
448 queue.Add (imgData.[ni, nj, 0]) p'
449
450 if queue.IsEmpty
451 then
452 if m.Elements.Count + nextElements.Count <= area
453 then
454 m.State <- AreaState.Validated
455 m.Intensity <- Some intensity'
456 m.Elements.UnionWith(nextElements)
457 stop <- true
458
459 for m in areas do
460 if m.State = AreaState.Validated
461 then
462 match m.Intensity with
463 | Some i ->
464 for p in m.Elements do
465 imgData.[p.Y, p.X, 0] <- i
466 | _ -> ()
467 ()
468
469
470 let areaOpen (img: Image<Gray, byte>) (area: int) =
471 areaOperation img area AreaOperation.Opening
472
473 let areaClose (img: Image<Gray, byte>) (area: int) =
474 areaOperation img area AreaOperation.Closing
475
476 // A simpler algorithm than 'areaOpen' but slower.
477 let areaOpen2 (img: Image<Gray, byte>) (area: int) =
478 let w = img.Width
479 let h = img.Height
480 let imgData = img.Data
481 let se = [| -1, 0; 0, -1; 1, 0; 0, 1 |]
482
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
488
489 let flooded : bool[,] = Array2D.zeroCreate h w
490
491 let pointsChecked = HashSet<Point>()
492 let pointsToCheck = Stack<Point>()
493
494 for level in 255 .. -1 .. 0 do
495 let mutable n = histogram.[level]
496 if n > 0
497 then
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
501 then
502 let mutable maxNeighborValue = 0uy
503 pointsChecked.Clear()
504 pointsToCheck.Clear()
505 pointsToCheck.Push(Point(j, i))
506
507 while pointsToCheck.Count > 0 do
508 let next = pointsToCheck.Pop()
509 pointsChecked.Add(next) |> ignore
510 flooded.[next.Y, next.X] <- true
511
512 for nx, ny in se do
513 let p = Point(next.X + nx, next.Y + ny)
514 if p.X >= 0 && p.X < w && p.Y >= 0 && p.Y < h
515 then
516 let v = imgData.[p.Y, p.X, 0]
517 if v = byte level
518 then
519 if not (pointsChecked.Contains(p))
520 then
521 pointsToCheck.Push(p)
522 elif v > maxNeighborValue
523 then
524 maxNeighborValue <- v
525
526 if int maxNeighborValue < level && pointsChecked.Count <= area
527 then
528 for p in pointsChecked do
529 imgData.[p.Y, p.X, 0] <- maxNeighborValue
530
531
532 // Zhang and Suen algorithm.
533 // Modify 'mat' in place.
534 let thin (mat: Matrix<byte>) =
535 let w = mat.Width
536 let h = mat.Height
537 let mutable data1 = mat.Data
538 let mutable data2 = Array2D.copy data1
539
540 let mutable pixelChanged = true
541 let mutable oddIteration = true
542
543 while pixelChanged do
544 pixelChanged <- false
545 for i in 0..h-1 do
546 for j in 0..w-1 do
547 if data1.[i, j] = 1uy
548 then
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]
557
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 &&
568 if oddIteration
569 then p2 * p4 * p6 = 0uy && p4 * p6 * p8 = 0uy
570 else p2 * p4 * p8 = 0uy && p2 * p6 * p8 = 0uy
571 then
572 data2.[i, j] <- 0uy
573 pixelChanged <- true
574 else
575 data2.[i, j] <- 0uy
576
577 oddIteration <- not oddIteration
578 let tmp = data1
579 data1 <- data2
580 data2 <- tmp
581
582
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)
587 n
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<(int*int)>()
615 let neighborsToCheck = List<(int*int)>()
616 neighborsToCheck.Add((i, j))
617 data'.[i, j] <- 0uy
618
619 while neighborsToCheck.Count > 0 do
620 let (ci, cj) = pop neighborsToCheck
621 neighborhood.Add((ci, cj))
622 for (ni, nj) in neighbors do
623 let pi = ci + ni
624 let pj = cj + nj
625 if pi >= 0 && pi < h && pj >= 0 && pj < w && data'.[pi, pj] = 1uy
626 then
627 neighborsToCheck.Add((pi, pj))
628 data'.[pi, pj] <- 0uy
629 if neighborhood.Count <= areaSize
630 then
631 for (ni, nj) in neighborhood do
632 data.[ni, nj] <- 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 = List<Point>(startPoints);
640
641 let data = img.Data
642
643 while pointToCheck.Count > 0 do
644 let next = pop pointToCheck
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.Add(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