forked from datacarpentry/image-processing
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathedge-detection.html
827 lines (781 loc) · 55.3 KB
/
edge-detection.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
<!DOCTYPE html>
<!-- START: inst/pkgdown/templates/layout.html --><!-- Generated by pkgdown: do not edit by hand --><html lang="en"><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><meta charset="utf-8"><title>Image Processing with Python: Extra Episode: Edge Detection</title><meta name="viewport" content="width=device-width, initial-scale=1"><link rel="stylesheet" type="text/css" href="assets/styles.css"><script src="assets/scripts.js" type="text/javascript"></script><!-- mathjax --><script type="text/x-mathjax-config">
MathJax.Hub.Config({
config: ["MMLorHTML.js"],
jax: ["input/TeX","input/MathML","output/HTML-CSS","output/NativeMML", "output/PreviewHTML"],
extensions: ["tex2jax.js","mml2jax.js","MathMenu.js","MathZoom.js", "fast-preview.js", "AssistiveMML.js", "a11y/accessibility-menu.js"],
TeX: {
extensions: ["AMSmath.js","AMSsymbols.js","noErrors.js","noUndefined.js"]
},
tex2jax: {
inlineMath: [ ['$','$'], ['\\(', '\\)']],
displayMath: [ ['$$','$$'], ['\\[', '\\]'] ],
processEscapes: true
}
});
</script><script src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/MathJax.js" integrity="sha256-nvJJv9wWKEm88qvoQl9ekL2J+k/RWIsaSScxxlsrv8k=" crossorigin="anonymous"></script><!-- Responsive Favicon for The Carpentries --><link rel="apple-touch-icon" sizes="180x180" href="apple-touch-icon.png"><link rel="icon" type="image/png" sizes="32x32" href="favicon-32x32.png"><link rel="icon" type="image/png" sizes="16x16" href="favicon-16x16.png"><link rel="manifest" href="site.webmanifest"><link rel="mask-icon" href="safari-pinned-tab.svg" color="#5bbad5"><meta name="msapplication-TileColor" content="#da532c"><meta name="theme-color" content="#ffffff"></head><body>
<header id="top" class="navbar navbar-expand-md navbar-light bg-white top-nav data"><a class="visually-hidden-focusable skip-link" href="#main-content">Skip to main content</a>
<div class="container-fluid top-nav-container">
<div class="col-md-6">
<div class="large-logo">
<img alt="Data Carpentry" src="assets/images/data-logo.svg"></div>
</div>
<div class="selector-container ">
<div class="dropdown">
<button class="btn btn-secondary dropdown-toggle bordered-button" type="button" id="dropdownMenu1" data-bs-toggle="dropdown" aria-expanded="false">
<i aria-hidden="true" class="icon" data-feather="eye"></i> Learner View <i data-feather="chevron-down"></i>
</button>
<ul class="dropdown-menu" aria-labelledby="dropdownMenu1"><li><button class="dropdown-item" type="button" onclick="window.location.href='instructor/edge-detection.html';">Instructor View</button></li>
</ul></div>
</div>
</div>
<hr></header><nav class="navbar navbar-expand-xl navbar-light bg-white bottom-nav data" aria-label="Main Navigation"><div class="container-fluid nav-container">
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
<span class="menu-title">Menu</span>
</button>
<div class="nav-logo">
<img class="small-logo" alt="Data Carpentry" src="assets/images/data-logo-sm.svg"></div>
<div class="lesson-title-md">
Image Processing with Python
</div>
<div class="search-icon-sm">
<!-- TODO: do not show until we have search
<i role="img" aria-label="search button" data-feather="search"></i>
-->
</div>
<div class="desktop-nav">
<ul class="navbar-nav me-auto mb-2 mb-lg-0"><li class="nav-item">
<span class="lesson-title">
Image Processing with Python
</span>
</li>
<li class="nav-item">
<a class="nav-link" href="key-points.html">Key Points</a>
</li>
<li class="nav-item">
<a class="nav-link" href="reference.html#glossary">Glossary</a>
</li>
<li class="nav-item">
<a class="nav-link" href="profiles.html">Learner Profiles</a>
</li>
<li class="nav-item dropdown">
<button class="nav-link dropdown-toggle" id="navbarDropdown" data-bs-toggle="dropdown" aria-expanded="false">
More <i data-feather="chevron-down"></i>
</button>
<ul class="dropdown-menu" aria-labelledby="navbarDropdown"><li><a class="dropdown-item" href="discuss.html">Discussion</a></li><li><a class="dropdown-item" href="edge-detection.html">Extra Episode: Edge Detection</a></li><li><a class="dropdown-item" href="prereqs.html">Prerequisites</a></li><li><a class="dropdown-item" href="reference.html">Reference</a></li>
</ul></li>
</ul></div>
<form class="d-flex col-md-2 search-form">
<fieldset disabled><input class="form-control me-2 searchbox" type="search" placeholder="Search" aria-label="Search"><button class="btn btn-outline-success tablet-search-button" type="submit">
<i class="search-icon" data-feather="search" role="img" aria-label="search button"></i>
</button>
</fieldset></form>
</div><!--/div.container-fluid -->
</nav><div class="col-md-12 mobile-title">
Image Processing with Python
</div>
<aside class="col-md-12 lesson-progress"><div style="width: NA%" class="percentage">
NA%
</div>
<div class="progress data">
<div class="progress-bar data" role="progressbar" style="width: NA%" aria-valuenow="NA" aria-label="Lesson Progress" aria-valuemin="0" aria-valuemax="100">
</div>
</div>
</aside><div class="container">
<div class="row">
<!-- START: inst/pkgdown/templates/navbar.html -->
<div id="sidebar-col" class="col-lg-4">
<div id="sidebar" class="sidebar">
<nav aria-labelledby="flush-headingEleven"><button role="button" aria-label="close menu" alt="close menu" aria-expanded="true" aria-controls="sidebar" class="collapse-toggle"><i class="search-icon" data-feather="x" role="img"></i></button>
<div class="sidebar-inner">
<div class="row mobile-row">
<div class="col">
<div class="sidenav-view-selector">
<div class="accordion accordion-flush" id="accordionFlush9">
<div class="accordion-item">
<h2 class="accordion-header" id="flush-headingNine">
<button class="accordion-button collapsed" id="instructor" type="button" data-bs-toggle="collapse" data-bs-target="#flush-collapseNine" aria-expanded="false" aria-controls="flush-collapseNine">
<i id="eye" aria-hidden="true" class="icon" data-feather="eye"></i> Learner View
</button>
</h2>
<div id="flush-collapseNine" class="accordion-collapse collapse" aria-labelledby="flush-headingNine" data-bs-parent="#accordionFlush2">
<div class="accordion-body">
<a href="instructor/edge-detection.html">Instructor View</a>
</div>
</div>
</div><!--/div.accordion-item-->
</div><!--/div.accordion-flush-->
</div><!--div.sidenav-view-selector -->
</div><!--/div.col -->
<hr></div><!--/div.mobile-row -->
<div class="accordion accordion-flush" id="accordionFlush11">
<div class="accordion-item">
<button id="chapters" class="accordion-button show" type="button" data-bs-toggle="collapse" data-bs-target="#flush-collapseEleven" aria-expanded="false" aria-controls="flush-collapseEleven">
<h2 class="accordion-header chapters" id="flush-headingEleven">
EPISODES
</h2>
</button>
<div id="flush-collapseEleven" class="accordion-collapse show collapse" aria-labelledby="flush-headingEleven" data-bs-parent="#accordionFlush11">
<div class="accordion-body">
<div class="accordion accordion-flush" id="accordionFlush1">
<div class="accordion-item">
<div class="accordion-header" id="flush-heading1">
<a href="index.html">Summary and Setup</a>
</div><!--/div.accordion-header-->
</div><!--/div.accordion-item-->
</div><!--/div.accordion-flush-->
<div class="accordion accordion-flush" id="accordionFlush2">
<div class="accordion-item">
<div class="accordion-header" id="flush-heading2">
<a href="01-introduction.html">1. Introduction</a>
</div><!--/div.accordion-header-->
</div><!--/div.accordion-item-->
</div><!--/div.accordion-flush-->
<div class="accordion accordion-flush" id="accordionFlush3">
<div class="accordion-item">
<div class="accordion-header" id="flush-heading3">
<a href="02-image-basics.html">2. Image Basics</a>
</div><!--/div.accordion-header-->
</div><!--/div.accordion-item-->
</div><!--/div.accordion-flush-->
<div class="accordion accordion-flush" id="accordionFlush4">
<div class="accordion-item">
<div class="accordion-header" id="flush-heading4">
<a href="03-skimage-images.html">3. Working with scikit-image</a>
</div><!--/div.accordion-header-->
</div><!--/div.accordion-item-->
</div><!--/div.accordion-flush-->
<div class="accordion accordion-flush" id="accordionFlush5">
<div class="accordion-item">
<div class="accordion-header" id="flush-heading5">
<a href="04-drawing.html">4. Drawing and Bitwise Operations</a>
</div><!--/div.accordion-header-->
</div><!--/div.accordion-item-->
</div><!--/div.accordion-flush-->
<div class="accordion accordion-flush" id="accordionFlush6">
<div class="accordion-item">
<div class="accordion-header" id="flush-heading6">
<a href="05-creating-histograms.html">5. Creating Histograms</a>
</div><!--/div.accordion-header-->
</div><!--/div.accordion-item-->
</div><!--/div.accordion-flush-->
<div class="accordion accordion-flush" id="accordionFlush7">
<div class="accordion-item">
<div class="accordion-header" id="flush-heading7">
<a href="06-blurring.html">6. Blurring Images</a>
</div><!--/div.accordion-header-->
</div><!--/div.accordion-item-->
</div><!--/div.accordion-flush-->
<div class="accordion accordion-flush" id="accordionFlush8">
<div class="accordion-item">
<div class="accordion-header" id="flush-heading8">
<a href="07-thresholding.html">7. Thresholding</a>
</div><!--/div.accordion-header-->
</div><!--/div.accordion-item-->
</div><!--/div.accordion-flush-->
<div class="accordion accordion-flush" id="accordionFlush9">
<div class="accordion-item">
<div class="accordion-header" id="flush-heading9">
<a href="08-connected-components.html">8. Connected Component Analysis</a>
</div><!--/div.accordion-header-->
</div><!--/div.accordion-item-->
</div><!--/div.accordion-flush-->
<div class="accordion accordion-flush" id="accordionFlush10">
<div class="accordion-item">
<div class="accordion-header" id="flush-heading10">
<a href="09-challenges.html">9. Capstone Challenge</a>
</div><!--/div.accordion-header-->
</div><!--/div.accordion-item-->
</div><!--/div.accordion-flush-->
</div>
</div>
</div>
<hr class="half-width"><div class="accordion accordion-flush resources" id="accordionFlush12">
<div class="accordion-item">
<h2 class="accordion-header" id="flush-headingTwelve">
<button class="accordion-button collapsed" id="resources" type="button" data-bs-toggle="collapse" data-bs-target="#flush-collapseTwelve" aria-expanded="false" aria-controls="flush-collapseTwelve">
RESOURCES
</button>
</h2>
<div id="flush-collapseTwelve" class="accordion-collapse collapse" aria-labelledby="flush-headingTwelve" data-bs-parent="#accordionFlush12">
<div class="accordion-body">
<ul><li>
<a href="key-points.html">Key Points</a>
</li>
<li>
<a href="reference.html#glossary">Glossary</a>
</li>
<li>
<a href="profiles.html">Learner Profiles</a>
</li>
<li><a href="discuss.html">Discussion</a></li><li><a href="edge-detection.html">Extra Episode: Edge Detection</a></li><li><a href="prereqs.html">Prerequisites</a></li><li><a href="reference.html">Reference</a></li>
</ul></div>
</div>
</div>
</div>
<hr class="half-width resources"><a href="aio.html">See all in one page</a>
<hr class="d-none d-sm-block d-md-none"><div class="d-grid gap-1">
</div>
</div><!-- /div.accordion -->
</div><!-- /div.sidebar-inner -->
</nav></div><!-- /div.sidebar -->
</div><!-- /div.sidebar-col -->
<!-- END: inst/pkgdown/templates/navbar.html-->
<!-- START: inst/pkgdown/templates/content-instructor.html -->
<div class="col-xl-8 col-lg-12 primary-content">
<nav class="lesson-content mx-md-4" aria-label="Previous and Next Chapter"><!-- content for small screens --><div class="d-block d-sm-block d-md-none">
<a class="chapter-link" href="index.html"><i aria-hidden="true" class="small-arrow" data-feather="arrow-left"></i>Previous</a>
<a class="chapter-link float-end" href="index.html">Next<i aria-hidden="true" class="small-arrow" data-feather="arrow-right"></i></a>
</div>
<!-- content for large screens -->
<div class="d-none d-sm-none d-md-block">
<a class="chapter-link" href="index.html" rel="prev"><i aria-hidden="true" class="small-arrow" data-feather="arrow-left"></i>Previous</a>
<a class="chapter-link float-end" href="index.html" rel="next">Next... <i aria-hidden="true" class="small-arrow" data-feather="arrow-right"></i></a>
</div>
<hr></nav><main id="main-content" class="main-content"><div class="container lesson-content">
<h1>Extra Episode: Edge Detection</h1>
<p> Last updated on 2023-07-26 |
<a href="https://github.com/datacarpentry/image-processing/edit/main/learners/edge-detection.md" class="external-link">Edit this page <i aria-hidden="true" data-feather="edit"></i></a></p>
<div class="text-end">
<button role="button" aria-pressed="false" tabindex="0" id="expand-code" class="pull-right"> Expand All Solutions <i aria-hidden="true" data-feather="plus"></i></button>
</div>
<div class="overview card">
<h2 class="card-header">Overview</h2>
<div class="row g-0">
<div class="col-md-4">
<div class="card-body">
<div class="inner">
<h3 class="card-title">Questions</h3>
<ul><li>How can we automatically detect the edges of the objects in an
image?</li>
</ul></div>
</div>
</div>
<div class="col-md-8">
<div class="card-body">
<div class="inner bordered">
<h3 class="card-title">Objectives</h3>
<ul><li>Apply Canny edge detection to an image.</li>
<li>Explain how we can use sliders to expedite finding appropriate
parameter values for our scikit-image function calls.</li>
<li>Create scikit-image windows with sliders and associated callback
functions.</li>
</ul></div>
</div>
</div>
</div>
</div>
<p>In this episode, we will learn how to use scikit-image functions to
apply <em>edge detection</em> to an image. In edge detection, we find
the boundaries or edges of objects in an image, by determining where the
brightness of the image changes dramatically. Edge detection can be used
to extract the structure of objects in an image. If we are interested in
the number, size, shape, or relative location of objects in an image,
edge detection allows us to focus on the parts of the image most
helpful, while ignoring parts of the image that will not help us.</p>
<p>For example, once we have found the edges of the objects in the image
(or once we have converted the image to binary using thresholding), we
can use that information to find the image <em>contours</em>, which we
will learn about in <a href="08-connected-components.html">the
<em>Connected Component Analysis</em> episode</a>. With the contours, we
can do things like counting the number of objects in the image, measure
the size of the objects, classify the shapes of the objects, and so
on.</p>
<p>As was the case for blurring and thresholding, there are several
different methods in scikit-image that can be used for edge detection,
so we will examine only one in detail.</p>
<section id="introduction-to-edge-detection"><h2 class="section-heading">Introduction to edge detection<a class="anchor" aria-label="anchor" href="#introduction-to-edge-detection"></a>
</h2>
<hr class="half-width"><p>To begin our introduction to edge detection, let us look at an image
with a very simple edge - this grayscale image of two overlapped pieces
of paper, one black and and one white:</p>
<figure><img src="fig/black-and-white.jpg" alt="Black and white image" class="figure mx-auto d-block"></figure><p>The obvious edge in the image is the vertical line between the black
paper and the white paper. To our eyes, there is a quite sudden change
between the black pixels and the white pixels. But, at a pixel-by-pixel
level, is the transition really that sudden?</p>
<p>If we zoom in on the edge more closely, as in this image, we can see
that the edge between the black and white areas of the image is not a
clear-cut line.</p>
<figure><img src="fig/black-and-white-edge-pixels.jpg" alt="Black and white edge pixels" class="figure mx-auto d-block"></figure><p>We can learn more about the edge by examining the colour values of
some of the pixels. Imagine a short line segment, halfway down the image
and straddling the edge between the black and white paper. This plot
shows the pixel values (between 0 and 255, since this is a grayscale
image) for forty pixels spanning the transition from black to white.</p>
<figure><img src="fig/black-and-white-gradient.png" alt="Gradient near transition" class="figure mx-auto d-block"></figure><p>It is obvious that the “edge” here is not so sudden! So, any
scikit-image method to detect edges in an image must be able to decide
where the edge is, and place appropriately-coloured pixels in that
location.</p>
</section><section id="canny-edge-detection"><h2 class="section-heading">Canny edge detection<a class="anchor" aria-label="anchor" href="#canny-edge-detection"></a>
</h2>
<hr class="half-width"><p>Our edge detection method in this workshop is <em>Canny edge
detection</em>, created by John Canny in 1986. This method uses a series
of steps, some incorporating other types of edge detection. The
<code>skimage.feature.canny()</code> function performs the following
steps:</p>
<ol style="list-style-type: decimal"><li>A Gaussian blur (that is characterised by the <code>sigma</code>
parameter, see <a href="06-blurring.html"><em>Blurring Images</em></a>
is applied to remove noise from the image. (So if we are doing edge
detection via this function, we should not perform our own blurring
step.)</li>
<li>Sobel edge detection is performed on both the cx and ry dimensions,
to find the intensity gradients of the edges in the image. Sobel edge
detection computes the derivative of a curve fitting the gradient
between light and dark areas in an image, and then finds the peak of the
derivative, which is interpreted as the location of an edge pixel.</li>
<li>Pixels that would be highlighted, but seem too far from any edge,
are removed. This is called <em>non-maximum suppression</em>, and the
result is edge lines that are thinner than those produced by other
methods.</li>
<li>A double threshold is applied to determine potential edges. Here
extraneous pixels caused by noise or milder colour variation than
desired are eliminated. If a pixel’s gradient value - based on the Sobel
differential - is above the high threshold value, it is considered a
strong candidate for an edge. If the gradient is below the low threshold
value, it is turned off. If the gradient is in between, the pixel is
considered a weak candidate for an edge pixel.</li>
<li>Final detection of edges is performed using <em>hysteresis</em>.
Here, weak candidate pixels are examined, and if they are connected to
strong candidate pixels, they are considered to be edge pixels; the
remaining, non-connected weak candidates are turned off.</li>
</ol><p>For a user of the <code>skimage.feature.canny()</code> edge detection
function, there are three important parameters to pass in:
<code>sigma</code> for the Gaussian filter in step one and the low and
high threshold values used in step four of the process. These values
generally are determined empirically, based on the contents of the
image(s) to be processed.</p>
<p>The following program illustrates how the
<code>skimage.feature.canny()</code> method can be used to detect the
edges in an image. We will execute the program on the
<code>data/shapes-01.jpg</code> image, which we used before in <a href="07-thresholding.html">the <em>Thresholding</em> episode</a>:</p>
<figure><img src="data/shapes-01.jpg" alt="coloured shapes" class="figure mx-auto d-block"></figure><p>We are interested in finding the edges of the shapes in the image,
and so the colours are not important. Our strategy will be to read the
image as grayscale, and then apply Canny edge detection. Note that when
reading the image with <code>iio.imread(..., mode="L")</code> the image
is converted to a grayscale image of same dtype.</p>
<p>This program takes three command-line arguments: the filename of the
image to process, and then two arguments related to the double
thresholding in step four of the Canny edge detection process. These are
the low and high threshold values for that step. After the required
libraries are imported, the program reads the command-line arguments and
saves them in their respective variables.</p>
<div class="codewrapper sourceCode" id="cb1">
<h3 class="code-label">PYTHON<i aria-hidden="true" data-feather="chevron-left"></i><i aria-hidden="true" data-feather="chevron-right"></i>
</h3>
<pre class="sourceCode python" tabindex="0"><code class="sourceCode python"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="co">"""</span></span>
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a><span class="co"> * Python script to demonstrate Canny edge detection.</span></span>
<span id="cb1-3"><a href="#cb1-3" aria-hidden="true" tabindex="-1"></a><span class="co"> *</span></span>
<span id="cb1-4"><a href="#cb1-4" aria-hidden="true" tabindex="-1"></a><span class="co"> * usage: python CannyEdge.py <filename> <sigma> <low_threshold> <high_threshold></span></span>
<span id="cb1-5"><a href="#cb1-5" aria-hidden="true" tabindex="-1"></a><span class="co">"""</span></span>
<span id="cb1-6"><a href="#cb1-6" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> imageio.v3 <span class="im">as</span> iio</span>
<span id="cb1-7"><a href="#cb1-7" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> matplotlib.pyplot <span class="im">as</span> plt</span>
<span id="cb1-8"><a href="#cb1-8" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> skimage.feature</span>
<span id="cb1-9"><a href="#cb1-9" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> sys</span>
<span id="cb1-10"><a href="#cb1-10" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-11"><a href="#cb1-11" aria-hidden="true" tabindex="-1"></a><span class="co"># read command-line arguments</span></span>
<span id="cb1-12"><a href="#cb1-12" aria-hidden="true" tabindex="-1"></a>filename <span class="op">=</span> sys.argv[<span class="dv">1</span>]</span>
<span id="cb1-13"><a href="#cb1-13" aria-hidden="true" tabindex="-1"></a>sigma <span class="op">=</span> <span class="bu">float</span>(sys.argv[<span class="dv">2</span>])</span>
<span id="cb1-14"><a href="#cb1-14" aria-hidden="true" tabindex="-1"></a>low_threshold <span class="op">=</span> <span class="bu">float</span>(sys.argv[<span class="dv">3</span>])</span>
<span id="cb1-15"><a href="#cb1-15" aria-hidden="true" tabindex="-1"></a>high_threshold <span class="op">=</span> <span class="bu">float</span>(sys.argv[<span class="dv">4</span>])</span></code></pre>
</div>
<p>Next, the original images is read, in grayscale, and displayed.</p>
<div class="codewrapper sourceCode" id="cb2">
<h3 class="code-label">PYTHON<i aria-hidden="true" data-feather="chevron-left"></i><i aria-hidden="true" data-feather="chevron-right"></i>
</h3>
<pre class="sourceCode python" tabindex="0"><code class="sourceCode python"><span id="cb2-1"><a href="#cb2-1" aria-hidden="true" tabindex="-1"></a><span class="co"># load and display original image as grayscale</span></span>
<span id="cb2-2"><a href="#cb2-2" aria-hidden="true" tabindex="-1"></a>image <span class="op">=</span> iio.imread(uri<span class="op">=</span>filename, mode<span class="op">=</span><span class="st">"L"</span>)</span>
<span id="cb2-3"><a href="#cb2-3" aria-hidden="true" tabindex="-1"></a>plt.imshow(image)</span></code></pre>
</div>
<p>Then, we apply Canny edge detection with this function call:</p>
<div class="codewrapper sourceCode" id="cb3">
<h3 class="code-label">PYTHON<i aria-hidden="true" data-feather="chevron-left"></i><i aria-hidden="true" data-feather="chevron-right"></i>
</h3>
<pre class="sourceCode python" tabindex="0"><code class="sourceCode python"><span id="cb3-1"><a href="#cb3-1" aria-hidden="true" tabindex="-1"></a>edges <span class="op">=</span> skimage.feature.canny(</span>
<span id="cb3-2"><a href="#cb3-2" aria-hidden="true" tabindex="-1"></a> image<span class="op">=</span>image,</span>
<span id="cb3-3"><a href="#cb3-3" aria-hidden="true" tabindex="-1"></a> sigma<span class="op">=</span>sigma,</span>
<span id="cb3-4"><a href="#cb3-4" aria-hidden="true" tabindex="-1"></a> low_threshold<span class="op">=</span>low_threshold,</span>
<span id="cb3-5"><a href="#cb3-5" aria-hidden="true" tabindex="-1"></a> high_threshold<span class="op">=</span>high_threshold,</span>
<span id="cb3-6"><a href="#cb3-6" aria-hidden="true" tabindex="-1"></a>)</span></code></pre>
</div>
<p>As we are using it here, the <code>skimage.feature.canny()</code>
function takes four parameters. The first parameter is the input image.
The <code>sigma</code> parameter determines the amount of Gaussian
smoothing that is applied to the image. The next two parameters are the
low and high threshold values for the fourth step of the process.</p>
<p>The result of this call is a binary image. In the image, the edges
detected by the process are white, while everything else is black.</p>
<p>Finally, the program displays the <code>edges</code> image, showing
the edges that were found in the original.</p>
<div class="codewrapper sourceCode" id="cb4">
<h3 class="code-label">PYTHON<i aria-hidden="true" data-feather="chevron-left"></i><i aria-hidden="true" data-feather="chevron-right"></i>
</h3>
<pre class="sourceCode python" tabindex="0"><code class="sourceCode python"><span id="cb4-1"><a href="#cb4-1" aria-hidden="true" tabindex="-1"></a><span class="co"># display edges</span></span>
<span id="cb4-2"><a href="#cb4-2" aria-hidden="true" tabindex="-1"></a>skimage.io.imshow(edges)</span></code></pre>
</div>
<p>Here is the result, for the coloured shape image above, with sigma
value 2.0, low threshold value 0.1 and high threshold value 0.3:</p>
<figure><img src="fig/shapes-01-canny-edges.png" alt="Output file of Canny edge detection" class="figure mx-auto d-block"></figure><p>Note that the edge output shown in a scikit-image window may look
significantly worse than the image would look if it were saved to a file
due to resampling artefacts in the interactive image viewer. The image
above is the edges of the junk image, saved in a PNG file. Here is how
the same image looks when displayed in a scikit-image output window:</p>
<figure><img src="fig/shapes-01-canny-edge-output.png" alt="Output window of Canny edge detection" class="figure mx-auto d-block"></figure></section><section id="interacting-with-the-image-viewer-using-viewer-plugins"><h2 class="section-heading">Interacting with the image viewer using viewer plugins<a class="anchor" aria-label="anchor" href="#interacting-with-the-image-viewer-using-viewer-plugins"></a>
</h2>
<hr class="half-width"><p>As we have seen, for a user of the
<code>skimage.feature.canny()</code> edge detection function, three
important parameters to pass in are sigma, and the low and high
threshold values used in step four of the process. These values
generally are determined empirically, based on the contents of the
image(s) to be processed.</p>
<p>Here is an image of some glass beads that we can use as input into a
Canny edge detection program:</p>
<figure><img src="data/beads.jpg" alt="Beads image" class="figure mx-auto d-block"></figure><p>We could use the <code>code/edge-detection/CannyEdge.py</code>
program above to find edges in this image. To find acceptable values for
the thresholds, we would have to run the program over and over again,
trying different threshold values and examining the resulting image,
until we find a combination of parameters that works best for the
image.</p>
<p><em>Or</em>, we can write a Python program and create a viewer plugin
that uses scikit-image <em>sliders</em>, that allow us to vary the
function parameters while the program is running. In other words, we can
write a program that presents us with a window like this:</p>
<figure><img src="fig/beads-canny-ui.png" alt="Canny UI" class="figure mx-auto d-block"></figure><p>Then, when we run the program, we can use the sliders to vary the
values of the sigma and threshold parameters until we are satisfied with
the results. After we have determined suitable values, we can use the
simpler program to utilise the parameters without bothering with the
user interface and sliders.</p>
<p>Here is a Python program that shows how to apply Canny edge
detection, and how to add sliders to the user interface. There are four
parts to this program, making it a bit (but only a <em>bit</em>) more
complicated than the programs we have looked at so far. The added
complexity comes from setting up the sliders for the parameters that
were previously read from the command line: In particular, we have
added</p>
<ul><li>The <code>canny()</code> filter function that returns an edge
image,</li>
<li>The <code>cannyPlugin</code> plugin object, to which we add</li>
<li>The sliders for <em>sigma</em>, and <em>low</em> and <em>high
threshold</em> values, and</li>
<li>The main program, i.e., the code that is executed when the program
runs.</li>
</ul><p>We will look at the main program part first, and then return to
writing the plugin. The first several lines of the main program are
easily recognizable at this point: saving the command-line argument,
reading the image in grayscale, and creating a window.</p>
<div class="codewrapper sourceCode" id="cb5">
<h3 class="code-label">PYTHON<i aria-hidden="true" data-feather="chevron-left"></i><i aria-hidden="true" data-feather="chevron-right"></i>
</h3>
<pre class="sourceCode python" tabindex="0"><code class="sourceCode python"><span id="cb5-1"><a href="#cb5-1" aria-hidden="true" tabindex="-1"></a><span class="co">"""</span></span>
<span id="cb5-2"><a href="#cb5-2" aria-hidden="true" tabindex="-1"></a><span class="co"> * Python script to demonstrate Canny edge detection</span></span>
<span id="cb5-3"><a href="#cb5-3" aria-hidden="true" tabindex="-1"></a><span class="co"> * with sliders to adjust the thresholds.</span></span>
<span id="cb5-4"><a href="#cb5-4" aria-hidden="true" tabindex="-1"></a><span class="co"> *</span></span>
<span id="cb5-5"><a href="#cb5-5" aria-hidden="true" tabindex="-1"></a><span class="co"> * usage: python CannyTrack.py <filename></span></span>
<span id="cb5-6"><a href="#cb5-6" aria-hidden="true" tabindex="-1"></a><span class="co">"""</span></span>
<span id="cb5-7"><a href="#cb5-7" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> imageio.v3 <span class="im">as</span> iio</span>
<span id="cb5-8"><a href="#cb5-8" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> matplotlib.pyplot <span class="im">as</span> plt</span>
<span id="cb5-9"><a href="#cb5-9" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> skimage.feature</span>
<span id="cb5-10"><a href="#cb5-10" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> skimage.viewer</span>
<span id="cb5-11"><a href="#cb5-11" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> sys</span>
<span id="cb5-12"><a href="#cb5-12" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-13"><a href="#cb5-13" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-14"><a href="#cb5-14" aria-hidden="true" tabindex="-1"></a>filename <span class="op">=</span> sys.argv[<span class="dv">1</span>]</span>
<span id="cb5-15"><a href="#cb5-15" aria-hidden="true" tabindex="-1"></a>image <span class="op">=</span> iio.imread(uri<span class="op">=</span>filename, mode<span class="op">=</span><span class="st">"L"</span>)</span>
<span id="cb5-16"><a href="#cb5-16" aria-hidden="true" tabindex="-1"></a>viewer <span class="op">=</span> plt.imshow(image)</span></code></pre>
</div>
<p>The <code>skimage.viewer.plugins.Plugin</code> class is designed to
manipulate images. It takes an <code>image_filter</code> argument in the
constructor that should be a function. This function should produce a
new image as an output, given an image as the first argument, which then
will be automatically displayed in the image viewer.</p>
<div class="codewrapper sourceCode" id="cb6">
<h3 class="code-label">PYTHON<i aria-hidden="true" data-feather="chevron-left"></i><i aria-hidden="true" data-feather="chevron-right"></i>
</h3>
<pre class="sourceCode python" tabindex="0"><code class="sourceCode python"><span id="cb6-1"><a href="#cb6-1" aria-hidden="true" tabindex="-1"></a><span class="co"># Create the plugin and give it a name</span></span>
<span id="cb6-2"><a href="#cb6-2" aria-hidden="true" tabindex="-1"></a>canny_plugin <span class="op">=</span> skimage.viewer.plugins.Plugin(image_filter<span class="op">=</span>skimage.feature.canny)</span>
<span id="cb6-3"><a href="#cb6-3" aria-hidden="true" tabindex="-1"></a>canny_plugin.name <span class="op">=</span> <span class="st">"Canny Filter Plugin"</span></span></code></pre>
</div>
<p>We want to interactively modify the parameters of the filter function
interactively. scikit-image allows us to further enrich the plugin by
adding widgets, like <code>skimage.viewer.widgets.Slider</code>,
<code>skimage.viewer.widgets.CheckBox</code>,
<code>skimage.viewer.widgets.ComboBox</code>. Whenever a widget
belonging to the plugin is updated, the filter function is called with
the updated parameters. This function is also called a callback
function. The following code adds sliders for <code>sigma</code>,
<code>low_threshold</code> and <code>high_thresholds</code>.</p>
<div class="codewrapper sourceCode" id="cb7">
<h3 class="code-label">PYTHON<i aria-hidden="true" data-feather="chevron-left"></i><i aria-hidden="true" data-feather="chevron-right"></i>
</h3>
<pre class="sourceCode python" tabindex="0"><code class="sourceCode python"><span id="cb7-1"><a href="#cb7-1" aria-hidden="true" tabindex="-1"></a><span class="co"># Add sliders for the parameters</span></span>
<span id="cb7-2"><a href="#cb7-2" aria-hidden="true" tabindex="-1"></a>canny_plugin <span class="op">+=</span> skimage.viewer.widgets.Slider(</span>
<span id="cb7-3"><a href="#cb7-3" aria-hidden="true" tabindex="-1"></a> name<span class="op">=</span><span class="st">"sigma"</span>, low<span class="op">=</span><span class="fl">0.0</span>, high<span class="op">=</span><span class="fl">7.0</span>, value<span class="op">=</span><span class="fl">2.0</span></span>
<span id="cb7-4"><a href="#cb7-4" aria-hidden="true" tabindex="-1"></a>)</span>
<span id="cb7-5"><a href="#cb7-5" aria-hidden="true" tabindex="-1"></a>canny_plugin <span class="op">+=</span> skimage.viewer.widgets.Slider(</span>
<span id="cb7-6"><a href="#cb7-6" aria-hidden="true" tabindex="-1"></a> name<span class="op">=</span><span class="st">"low_threshold"</span>, low<span class="op">=</span><span class="fl">0.0</span>, high<span class="op">=</span><span class="fl">1.0</span>, value<span class="op">=</span><span class="fl">0.1</span></span>
<span id="cb7-7"><a href="#cb7-7" aria-hidden="true" tabindex="-1"></a>)</span>
<span id="cb7-8"><a href="#cb7-8" aria-hidden="true" tabindex="-1"></a>canny_plugin <span class="op">+=</span> skimage.viewer.widgets.Slider(</span>
<span id="cb7-9"><a href="#cb7-9" aria-hidden="true" tabindex="-1"></a> name<span class="op">=</span><span class="st">"high_threshold"</span>, low<span class="op">=</span><span class="fl">0.0</span>, high<span class="op">=</span><span class="fl">1.0</span>, value<span class="op">=</span><span class="fl">0.2</span></span>
<span id="cb7-10"><a href="#cb7-10" aria-hidden="true" tabindex="-1"></a>)</span></code></pre>
</div>
<p>A slider is a widget that lets you choose a number by dragging a
handle along a line. On the left side of the line, we have the lowest
value, on the right side the highest value that can be chosen. The range
of values in between is distributed equally along this line. All three
sliders are constructed in the same way: The first argument is the name
of the parameter that is tweaked by the slider. With the arguments
<code>low</code>, and <code>high</code>, we supply the limits for the
range of numbers that is represented by the slider. The
<code>value</code> argument specifies the initial value of that
parameter, so where the handle is located when the plugin is started.
Adding the slider to the plugin makes the values available as parameters
to the <code>filter_function</code>.</p>
<div id="how-does-the-plugin-know-how-to-call-the-filter-function-with-the-parameters" class="callout callout">
<div class="callout-square">
<i class="callout-icon" data-feather="bell"></i>
</div>
<div id="how-does-the-plugin-know-how-to-call-the-filter-function-with-the-parameters" class="callout-inner">
<h3 class="callout-title">How does the plugin know how to call the
filter function with the parameters?<a class="anchor" aria-label="anchor" href="#how-does-the-plugin-know-how-to-call-the-filter-function-with-the-parameters"></a>
</h3>
<div class="callout-content">
<p>The filter function will be called with the slider parameters
according to their <em>names</em> as <em>keyword</em> arguments. So it
is very important to name the sliders appropriately.</p>
</div>
</div>
</div>
<p>Finally, we add the plugin the viewer and display the resulting user
interface:</p>
<div class="codewrapper sourceCode" id="cb8">
<h3 class="code-label">PYTHON<i aria-hidden="true" data-feather="chevron-left"></i><i aria-hidden="true" data-feather="chevron-right"></i>
</h3>
<pre class="sourceCode python" tabindex="0"><code class="sourceCode python"><span id="cb8-1"><a href="#cb8-1" aria-hidden="true" tabindex="-1"></a><span class="co"># add the plugin to the viewer and show the window</span></span>
<span id="cb8-2"><a href="#cb8-2" aria-hidden="true" tabindex="-1"></a>viewer <span class="op">+=</span> canny_plugin</span>
<span id="cb8-3"><a href="#cb8-3" aria-hidden="true" tabindex="-1"></a>viewer.show()</span></code></pre>
</div>
<p>Here is the result of running the preceding program on the beads
image, with a sigma value 1.0, low threshold value 0.1 and high
threshold value 0.3. The image shows the edges in an output file.</p>
<figure><img src="fig/beads-out.png" alt="Beads edges (file)" class="figure mx-auto d-block"></figure><div id="applying-canny-edge-detection-to-another-image-5-min" class="callout challenge">
<div class="callout-square">
<i class="callout-icon" data-feather="zap"></i>
</div>
<div id="applying-canny-edge-detection-to-another-image-5-min" class="callout-inner">
<h3 class="callout-title">Applying Canny edge detection to another image
(5 min)<a class="anchor" aria-label="anchor" href="#applying-canny-edge-detection-to-another-image-5-min"></a>
</h3>
<div class="callout-content">
<p>Now, run the program above on the image of coloured shapes,
<code>data/shapes-01.jpg</code>. Use a sigma of 1.0 and adjust low and
high threshold sliders to produce an edge image that looks like
this:</p>
<figure><img src="fig/shapes-01-canny-track-edges.png" alt="coloured shape edges" class="figure mx-auto d-block"></figure><p>What values for the low and high threshold values did you use to
produce an image similar to the one above?</p>
</div>
</div>
</div>
<div id="accordionSolution1" class="accordion challenge-accordion accordion-flush">
<div class="accordion-item">
<button class="accordion-button solution-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseSolution1" aria-expanded="false" aria-controls="collapseSolution1">
<h4 class="accordion-header" id="headingSolution1">
Show me the solution
</h4>
</button>
<div id="collapseSolution1" class="accordion-collapse collapse" data-bs-parent="#accordionSolution1" aria-labelledby="headingSolution1">
<div class="accordion-body">
<p>The coloured shape edge image above was produced with a low threshold
value of 0.05 and a high threshold value of 0.07. You may be able to
achieve similar results with other threshold values.</p>
</div>
</div>
</div>
</div>
<div id="using-sliders-for-thresholding-30-min" class="callout challenge">
<div class="callout-square">
<i class="callout-icon" data-feather="zap"></i>
</div>
<div id="using-sliders-for-thresholding-30-min" class="callout-inner">
<h3 class="callout-title">Using sliders for thresholding (30 min)<a class="anchor" aria-label="anchor" href="#using-sliders-for-thresholding-30-min"></a>
</h3>
<div class="callout-content">
<p>Now, let us apply what we know about creating sliders to another,
similar situation. Consider this image of a collection of maize
seedlings, and suppose we wish to use simple fixed-level thresholding to
mask out everything that is not part of one of the plants.</p>
<figure><img src="data/maize-roots-grayscale.jpg" alt="Maize roots image" class="figure mx-auto d-block"></figure><p>To perform the thresholding, we could first create a histogram, then
examine it, and select an appropriate threshold value. Here, however,
let us create an application with a slider to set the threshold value.
Create a program that reads in the image, displays it in a window with a
slider, and allows the slider value to vary the threshold value used.
You will find the image at
<code>data/maize-roots-grayscale.jpg</code>.</p>
</div>
</div>
</div>
<div id="accordionSolution2" class="accordion challenge-accordion accordion-flush">
<div class="accordion-item">
<button class="accordion-button solution-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseSolution2" aria-expanded="false" aria-controls="collapseSolution2">
<h4 class="accordion-header" id="headingSolution2">
Show me the solution
</h4>
</button>
<div id="collapseSolution2" class="accordion-collapse collapse" data-bs-parent="#accordionSolution2" aria-labelledby="headingSolution2">
<div class="accordion-body">
<p>Here is a program that uses a slider to vary the threshold value used
in a simple, fixed-level thresholding process.</p>
<div class="codewrapper sourceCode" id="cb9">
<h3 class="code-label">PYTHON<i aria-hidden="true" data-feather="chevron-left"></i><i aria-hidden="true" data-feather="chevron-right"></i>
</h3>
<pre class="sourceCode python" tabindex="0"><code class="sourceCode python"><span id="cb9-1"><a href="#cb9-1" aria-hidden="true" tabindex="-1"></a><span class="co">"""</span></span>
<span id="cb9-2"><a href="#cb9-2" aria-hidden="true" tabindex="-1"></a><span class="co"> * Python program to use a slider to control fixed-level</span></span>
<span id="cb9-3"><a href="#cb9-3" aria-hidden="true" tabindex="-1"></a><span class="co"> * thresholding value.</span></span>
<span id="cb9-4"><a href="#cb9-4" aria-hidden="true" tabindex="-1"></a><span class="co"> *</span></span>
<span id="cb9-5"><a href="#cb9-5" aria-hidden="true" tabindex="-1"></a><span class="co"> * usage: python interactive_thresholding.py <filename></span></span>
<span id="cb9-6"><a href="#cb9-6" aria-hidden="true" tabindex="-1"></a><span class="co">"""</span></span>
<span id="cb9-7"><a href="#cb9-7" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-8"><a href="#cb9-8" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> imageio.v3 <span class="im">as</span> iio</span>
<span id="cb9-9"><a href="#cb9-9" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> skimage</span>
<span id="cb9-10"><a href="#cb9-10" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> skimage.viewer</span>
<span id="cb9-11"><a href="#cb9-11" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> sys</span>
<span id="cb9-12"><a href="#cb9-12" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-13"><a href="#cb9-13" aria-hidden="true" tabindex="-1"></a>filename <span class="op">=</span> sys.argv[<span class="dv">1</span>]</span>
<span id="cb9-14"><a href="#cb9-14" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-15"><a href="#cb9-15" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-16"><a href="#cb9-16" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> filter_function(image, sigma, threshold):</span>
<span id="cb9-17"><a href="#cb9-17" aria-hidden="true" tabindex="-1"></a> masked <span class="op">=</span> image.copy()</span>
<span id="cb9-18"><a href="#cb9-18" aria-hidden="true" tabindex="-1"></a> masked[skimage.filters.gaussian(image, sigma<span class="op">=</span>sigma) <span class="op"><=</span> threshold] <span class="op">=</span> <span class="dv">0</span></span>
<span id="cb9-19"><a href="#cb9-19" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> masked</span>
<span id="cb9-20"><a href="#cb9-20" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-21"><a href="#cb9-21" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-22"><a href="#cb9-22" aria-hidden="true" tabindex="-1"></a>smooth_threshold_plugin <span class="op">=</span> skimage.viewer.plugins.Plugin(</span>
<span id="cb9-23"><a href="#cb9-23" aria-hidden="true" tabindex="-1"></a> image_filter<span class="op">=</span>filter_function</span>
<span id="cb9-24"><a href="#cb9-24" aria-hidden="true" tabindex="-1"></a>)</span>
<span id="cb9-25"><a href="#cb9-25" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-26"><a href="#cb9-26" aria-hidden="true" tabindex="-1"></a>smooth_threshold_plugin.name <span class="op">=</span> <span class="st">"Smooth and Threshold Plugin"</span></span>
<span id="cb9-27"><a href="#cb9-27" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-28"><a href="#cb9-28" aria-hidden="true" tabindex="-1"></a>smooth_threshold_plugin <span class="op">+=</span> skimage.viewer.widgets.Slider(</span>
<span id="cb9-29"><a href="#cb9-29" aria-hidden="true" tabindex="-1"></a> <span class="st">"sigma"</span>, low<span class="op">=</span><span class="fl">0.0</span>, high<span class="op">=</span><span class="fl">7.0</span>, value<span class="op">=</span><span class="fl">1.0</span></span>
<span id="cb9-30"><a href="#cb9-30" aria-hidden="true" tabindex="-1"></a>)</span>
<span id="cb9-31"><a href="#cb9-31" aria-hidden="true" tabindex="-1"></a>smooth_threshold_plugin <span class="op">+=</span> skimage.viewer.widgets.Slider(</span>
<span id="cb9-32"><a href="#cb9-32" aria-hidden="true" tabindex="-1"></a> <span class="st">"threshold"</span>, low<span class="op">=</span><span class="fl">0.0</span>, high<span class="op">=</span><span class="fl">1.0</span>, value<span class="op">=</span><span class="fl">0.5</span></span>
<span id="cb9-33"><a href="#cb9-33" aria-hidden="true" tabindex="-1"></a>)</span>
<span id="cb9-34"><a href="#cb9-34" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-35"><a href="#cb9-35" aria-hidden="true" tabindex="-1"></a>image <span class="op">=</span> iio.imread(uri<span class="op">=</span>filename, mode<span class="op">=</span><span class="st">"L"</span>)</span>
<span id="cb9-36"><a href="#cb9-36" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-37"><a href="#cb9-37" aria-hidden="true" tabindex="-1"></a>viewer <span class="op">=</span> skimage.viewer.ImageViewer(image<span class="op">=</span>image)</span>
<span id="cb9-38"><a href="#cb9-38" aria-hidden="true" tabindex="-1"></a>viewer <span class="op">+=</span> smooth_threshold_plugin</span>
<span id="cb9-39"><a href="#cb9-39" aria-hidden="true" tabindex="-1"></a>viewer.show()</span></code></pre>
</div>
<p>Here is the output of the program, blurring with a sigma of 1.5 and a
threshold value of 0.45:</p>
<figure><img src="fig/maize-roots-threshold.png" alt="Thresholded maize roots" class="figure mx-auto d-block"></figure></div>
</div>
</div>
</div>
<p>Keep this plugin technique in your image processing “toolbox.” You
can use sliders (or other interactive elements, see <a href="https://scikit-image.org/docs/dev/api/skimage.viewer.widgets.html" class="external-link">the
scikit-image documentation</a>) to vary other kinds of parameters, such
as sigma for blurring, binary thresholding values, and so on. A few
minutes developing a program to tweak parameters like this can save you
the hassle of repeatedly running a program from the command line with
different parameter values. Furthermore, scikit-image already comes with
a few viewer plugins that you can check out in <a href="https://scikit-image.org/docs/dev/api/skimage.viewer.plugins.html" class="external-link">the
documentation</a>.</p>
</section><section id="other-edge-detection-functions"><h2 class="section-heading">Other edge detection functions<a class="anchor" aria-label="anchor" href="#other-edge-detection-functions"></a>
</h2>
<hr class="half-width"><p>As with blurring, there are other options for finding edges in
skimage. These include <code>skimage.filters.sobel()</code>, which you
will recognise as part of the Canny method. Another choice is
<code>skimage.filters.laplace()</code>.</p>
<div id="keypoints1" class="callout keypoints">
<div class="callout-square">
<i class="callout-icon" data-feather="key"></i>
</div>
<div class="callout-inner">
<h3 class="callout-title">Keypoints<a class="anchor" aria-label="anchor" href="#keypoints1"></a>
</h3>
<div class="callout-content">
<ul><li>The <code>skimage.viewer.ImageViewer</code> is extended using a
<code>skimage.viewer.plugins.Plugin</code>.</li>
<li>We supply a filter function callback when creating a Plugin.</li>
<li>Parameters of the callback function are manipulated interactively by
creating sliders with the <code>skimage.viewer.widgets.slider()</code>
function and adding them to the plugin.</li>
</ul></div>
</div>
</div>
</section></div> <!-- / div.lesson-content -->
</main><!-- / main#main-content.main-content --><nav class="bottom-pagination mx-md-4" aria-label="Previous and Next Chapter"><div class="d-block d-sm-block d-md-none">
<a class="chapter-link" href="index.html"><i aria-hidden="true" class="small-arrow" data-feather="arrow-left"></i>Previous</a>
<a class="chapter-link float-end" href="index.html">Next<i aria-hidden="true" class="small-arrow" data-feather="arrow-right"></i></a>
</div>
<!-- content for large screens -->
<div class="d-none d-sm-none d-md-block">
<a class="chapter-link" href="index.html" rel="prev"><i aria-hidden="true" class="small-arrow" data-feather="arrow-left"></i>Previous</a>
<a class="chapter-link float-end" href="index.html" rel="next">Next... <i aria-hidden="true" class="small-arrow" data-feather="arrow-right"></i></a>
</div>
</nav></div> <!-- / div.primary-content.col-xs-12 -->
<!-- END: inst/pkgdown/templates/content-instructor.html-->
</div><!--/div.row-->
<footer class="row footer mx-md-3"><hr><div class="col-md-6">
<p>This lesson is subject to the <a href="CODE_OF_CONDUCT.html">Code of Conduct</a></p>
<p>
<a href="https://github.com/datacarpentry/image-processing/edit/main/learners/edge-detection.md" class="external-link">Edit on GitHub</a>
| <a href="https://github.com/datacarpentry/image-processing/blob/main/CONTRIBUTING.md" class="external-link">Contributing</a>
| <a href="https://github.com/datacarpentry/image-processing/" class="external-link">Source</a></p>
<p><a href="https://github.com/datacarpentry/image-processing/blob/main/CITATION" class="external-link">Cite</a> | <a href="mailto:team@carpentries.org">Contact</a> | <a href="https://carpentries.org/about/" class="external-link">About</a></p>
</div>
<div class="col-md-6">
<p>Materials licensed under <a href="LICENSE.html">CC-BY 4.0</a> by the authors</p>
<p><a href="https://creativecommons.org/licenses/by-sa/4.0/" class="external-link">Template licensed under CC-BY 4.0</a> by <a href="https://carpentries.org" class="external-link">The Carpentries</a></p>
<p>Built with <a href="https://github.com/carpentries/sandpaper" class="external-link">sandpaper (0.12.5)</a>,
<a href="https://github.com/carpentries/pegboard" class="external-link">pegboard (0.5.3)</a>,
and <a href="https://github.com/carpentries/varnish/tree/0.2.17" class="external-link">varnish (0.2.17)</a>.</p>
</div>
</footer></div> <!-- / div.container -->
<div id="to-top">
<a href="#top">
<i class="search-icon" data-feather="arrow-up" role="img" aria-label="Back to top"></i><br><span class="d-none d-sm-none d-md-none d-lg-none d-xl-block">Back</span> To Top
</a>
</div>
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "TrainingMaterial",
"@id": "https://datacarpentry.github.io/image-processing/edge-detection.html",
"dct:conformsTo": "https://bioschemas.org/profiles/TrainingMaterial/1.0-RELEASE",
"description": "A Carpentries Lesson teaching foundational data and coding skills to researchers worldwide",
"keywords": "software, data, lesson, The Carpentries",
"name": "Extra Episode: Edge Detection",
"creativeWorkStatus": "active",
"url": "https://datacarpentry.github.io/image-processing/edge-detection.html",
"identifier": "https://datacarpentry.github.io/image-processing/edge-detection.html",
"dateCreated": "2017-03-14",
"dateModified": "2023-07-26",
"datePublished": "2023-08-08"
}
</script><script>
feather.replace();
</script><!-- Matomo
2022-11-07: we have gotten a notification that we have an overage for our
tracking and I'm pretty sure this has to do with Workbench usage.
Considering that I am not _currently_ using this tracking because I do not
yet know how to access the data, I am turning this off for now.
<script>
var _paq = window._paq = window._paq || [];
/* tracker methods like "setCustomDimension" should be called before "trackPageView" */
_paq.push(["setDocumentTitle", document.domain + "/" + document.title]);
_paq.push(["setDomains", ["*.preview.carpentries.org","*.datacarpentry.github.io","*.datacarpentry.org","*.librarycarpentry.github.io","*.librarycarpentry.org","*.swcarpentry.github.io", "*.carpentries.github.io"]]);
_paq.push(["setDoNotTrack", true]);
_paq.push(["disableCookies"]);
_paq.push(['trackPageView']);
_paq.push(['enableLinkTracking']);
(function() {
var u="https://carpentries.matomo.cloud/";
_paq.push(['setTrackerUrl', u+'matomo.php']);
_paq.push(['setSiteId', '1']);
var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0];
g.async=true; g.src='https://cdn.matomo.cloud/carpentries.matomo.cloud/matomo.js'; s.parentNode.insertBefore(g,s);
})();
</script>
End Matomo Code --></body></html><!-- END: inst/pkgdown/templates/layout.html-->