Skip to content

Commit 9c69ab0

Browse files
authored
Merge pull request #32 from jni/filter-improvements
Improvements to filters lesson
2 parents 3a2de18 + 7dd880e commit 9c69ab0

File tree

3 files changed

+129
-75
lines changed

3 files changed

+129
-75
lines changed

lectures/1_image_filters.ipynb

Lines changed: 83 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -372,11 +372,11 @@
372372
},
373373
"outputs": [],
374374
"source": [
375-
"from scipy.ndimage import convolve\n",
375+
"import scipy.ndimage as ndi\n",
376376
"\n",
377377
"%precision 2\n",
378378
"print(bright_square)\n",
379-
"print(convolve(bright_square, mean_kernel))"
379+
"print(ndi.convolve(bright_square, mean_kernel))"
380380
]
381381
},
382382
{
@@ -409,7 +409,7 @@
409409
"source": [
410410
"Let's see a convolution in action.\n",
411411
"\n",
412-
"(Execute the following cell, but don't try to read it; it's purpose is to generate an example.)"
412+
"(Execute the following cell, but don't try to read it; its purpose is to generate an example.)"
413413
]
414414
},
415415
{
@@ -502,6 +502,13 @@
502502
"mean_filter_interactive_demo(bright_square)"
503503
]
504504
},
505+
{
506+
"cell_type": "markdown",
507+
"metadata": {},
508+
"source": [
509+
"Incidentally, the above filtering is the exact same principle behind the *convolutional neural networks*, or CNNs, that you might have heard much about over the past few years. The only difference is that while above, the simple mean kernel is used, in CNNs, the values inside the kernel are *learned* to find a specific feature, or accomplish a specific task."
510+
]
511+
},
505512
{
506513
"cell_type": "markdown",
507514
"metadata": {
@@ -655,8 +662,8 @@
655662
},
656663
"outputs": [],
657664
"source": [
658-
"filtered = convolve(pixelated, mean_kernel)\n",
659-
"imshow_all(pixelated, filtered)"
665+
"filtered = ndi.convolve(pixelated, mean_kernel)\n",
666+
"imshow_all(pixelated, filtered, titles=['pixelated', 'mean filtered'])"
660667
]
661668
},
662669
{
@@ -689,7 +696,7 @@
689696
}
690697
},
691698
"source": [
692-
"If you read through the last section, you're already familiar with the essential concepts of image filtering. But, of course, you don't have to create custom filter kernels for all of your filtering needs.\n"
699+
"If you read through the last section, you're already familiar with the essential concepts of image filtering. But, of course, you don't have to create custom filter kernels for all of your filtering needs. There are many standard filter kernels pre-defined from half a century of image and signal processing."
693700
]
694701
},
695702
{
@@ -727,7 +734,7 @@
727734
"# Rename module so we don't shadow the builtin function\n",
728735
"from skimage import filters\n",
729736
"\n",
730-
"smooth_mean = convolve(bright_square, mean_kernel)\n",
737+
"smooth_mean = ndi.convolve(bright_square, mean_kernel)\n",
731738
"sigma = 1\n",
732739
"smooth = filters.gaussian(bright_square, sigma)\n",
733740
"imshow_all(bright_square, smooth_mean, smooth,\n",
@@ -761,7 +768,7 @@
761768
"# The Gaussian filter returns a float image, regardless of input.\n",
762769
"# Cast to float so the images have comparable intensity ranges.\n",
763770
"pixelated_float = img_as_float(pixelated)\n",
764-
"smooth = filters.gaussian(pixelated_float, 1)\n",
771+
"smooth = filters.gaussian(pixelated_float, sigma=1)\n",
765772
"imshow_all(pixelated_float, smooth)"
766773
]
767774
},
@@ -802,7 +809,52 @@
802809
}
803810
},
804811
"source": [
805-
"The size of the structuring element used for the mean filter and the size (standard deviation) of the Gaussian filter are tweaked to produce an approximately equal amount of smoothing in the two results."
812+
"(Above, we've tweaked the size of the structuring element used for the mean filter and the standard deviation of the Gaussian filter to produce an approximately equal amount of smoothing in the two results.)"
813+
]
814+
},
815+
{
816+
"cell_type": "markdown",
817+
"metadata": {},
818+
"source": [
819+
"Incidentally, for reference, let's have a look at what the Gaussian filter actually looks like. Technically, the value of the kernel at a pixel that is $r$ rows and $c$ cols from the center is:\n",
820+
"\n",
821+
"$$\n",
822+
"k_{r, c} = \\frac{1}{2\\pi \\sigma} \\exp{\\left(-\\frac{r^2 + c^2}{2\\sigma^2}\\right)}\n",
823+
"$$\n",
824+
"\n",
825+
"Practically speaking, this value is pretty close to zero for values more than $4\\sigma$ away from the center, so practical Gaussian filters are truncated at about $4\\sigma$:"
826+
]
827+
},
828+
{
829+
"cell_type": "code",
830+
"execution_count": null,
831+
"metadata": {},
832+
"outputs": [],
833+
"source": [
834+
"sidelen = 45\n",
835+
"sigma = (sidelen - 1) // 2 // 4\n",
836+
"spot = np.zeros((sidelen, sidelen), dtype=float)\n",
837+
"spot[sidelen // 2, sidelen // 2] = 1\n",
838+
"kernel = filters.gaussian(spot, sigma=sigma)\n",
839+
"\n",
840+
"imshow_all(spot, kernel / np.max(kernel))"
841+
]
842+
},
843+
{
844+
"cell_type": "markdown",
845+
"metadata": {},
846+
"source": [
847+
"Let's look at a side view of that Gaussian \"bump\":"
848+
]
849+
},
850+
{
851+
"cell_type": "code",
852+
"execution_count": null,
853+
"metadata": {},
854+
"outputs": [],
855+
"source": [
856+
"fig, ax = plt.subplots()\n",
857+
"ax.plot(kernel[sidelen//2])"
806858
]
807859
},
808860
{
@@ -899,7 +951,9 @@
899951
"outputs": [],
900952
"source": [
901953
"fig, ax = plt.subplots()\n",
902-
"ax.plot(step_signal, 'b:', change, 'r')\n",
954+
"ax.plot(step_signal, label='signal')\n",
955+
"ax.plot(change, linestyle='dashed', label='change')\n",
956+
"ax.legend(loc='upper left')\n",
903957
"ax.margins(0.1)"
904958
]
905959
},
@@ -936,7 +990,9 @@
936990
"source": [
937991
"noisy_change = ndi.convolve(noisy_signal, np.array([1, 0, -1]))\n",
938992
"fig, ax = plt.subplots()\n",
939-
"ax.plot(noisy_signal, 'b:', noisy_change, 'r')\n",
993+
"ax.plot(noisy_signal, label='signal')\n",
994+
"ax.plot(noisy_change, linestyle='dashed', label='change')\n",
995+
"ax.legend(loc='upper left')\n",
940996
"ax.margins(0.1)"
941997
]
942998
},
@@ -963,7 +1019,7 @@
9631019
"cell_type": "markdown",
9641020
"metadata": {},
9651021
"source": [
966-
"*Note:* I use `np.convolve` here, because it has the option to output a *wider* result than either of the two inputs."
1022+
"*Note:* we use `np.convolve` here, because it has the option to output a *wider* result than either of the two inputs."
9671023
]
9681024
},
9691025
{
@@ -981,9 +1037,10 @@
9811037
"source": [
9821038
"smooth_change = ndi.convolve(noisy_signal, mean_diff)\n",
9831039
"fig, ax = plt.subplots()\n",
984-
"ax.plot(noisy_signal, 'b:', smooth_change, 'r')\n",
1040+
"ax.plot(noisy_signal, label='signal')\n",
1041+
"ax.plot(smooth_change, linestyle='dashed', label='change')\n",
9851042
"ax.margins(0.1)\n",
986-
"ax.hlines([-0.5, 0.5], 0, 100, linewidth=0.5);"
1043+
"ax.hlines([-0.5, 0.5], 0, 100, linewidth=0.5, color='gray');"
9871044
]
9881045
},
9891046
{
@@ -1024,8 +1081,10 @@
10241081
" [-1, -1, -1]\n",
10251082
"])\n",
10261083
"\n",
1027-
"gradient_vertical = ndi.convolve(pixelated.astype(float), vertical_kernel)\n",
1028-
"plt.imshow(gradient_vertical);"
1084+
"gradient_vertical = ndi.convolve(pixelated.astype(float),\n",
1085+
" vertical_kernel)\n",
1086+
"fig, ax = plt.subplots()\n",
1087+
"ax.imshow(gradient_vertical);"
10291088
]
10301089
},
10311090
{
@@ -1185,7 +1244,7 @@
11851244
}
11861245
},
11871246
"source": [
1188-
"This is a bit arbitrary, but here, we distinguish smoothing filters from denoising filters. We'll label denoising filters as those that are edge preserving.\n",
1247+
"At this point, we make a distinction. The earlier filters were implemented as a *linear dot-product* of values in the filter kernel and values in the image. The following kernels implement an *arbitrary* function of the local image neighborhood. Denoising filters in particular are filters that preserve the sharpness of edges in the image.\n",
11891248
"\n",
11901249
"As you can see from our earlier examples, mean and Gaussian filters smooth an image rather uniformly, including the edges of objects in an image. When denoising, however, you typically want to preserve features and just remove noise. The distinction between noise and features can, of course, be highly situation-dependent and subjective."
11911250
]
@@ -1223,8 +1282,8 @@
12231282
"outputs": [],
12241283
"source": [
12251284
"from skimage.morphology import disk\n",
1226-
"selem = disk(1) # \"selem\" is often the name used for \"structuring element\"\n",
1227-
"median = filters.rank.median(pixelated, selem)\n",
1285+
"neighborhood = disk(radius=1) # \"selem\" is often the name used for \"structuring element\"\n",
1286+
"median = filters.rank.median(pixelated, neighborhood)\n",
12281287
"titles = ['image', 'gaussian', 'median']\n",
12291288
"imshow_all(pixelated, smooth, median, titles=titles)"
12301289
]
@@ -1250,10 +1309,10 @@
12501309
},
12511310
"outputs": [],
12521311
"source": [
1253-
"selem = disk(10)\n",
1312+
"neighborhood = disk(10)\n",
12541313
"coins = data.coins()\n",
1255-
"mean_coin = filters.rank.mean(coins, selem)\n",
1256-
"median_coin = filters.rank.median(coins, selem)\n",
1314+
"mean_coin = filters.rank.mean(coins, neighborhood)\n",
1315+
"median_coin = filters.rank.median(coins, neighborhood)\n",
12571316
"titles = ['image', 'mean', 'median']\n",
12581317
"imshow_all(coins, mean_coin, median_coin, titles=titles)"
12591318
]
@@ -1321,7 +1380,7 @@
13211380
"* [Rank filters example](http://scikit-image.org/docs/dev/auto_examples/applications/plot_rank_filters.html)\n",
13221381
"* [Restoration API](http://scikit-image.org/docs/stable/api/skimage.restoration.html)\n",
13231382
"\n",
1324-
"Take a look at this [neat feature](https://github.com/scikit-image/scikit-image/pull/2647) that just got merged:\n",
1383+
"Take a look at this [neat feature](https://github.com/scikit-image/scikit-image/pull/2647) merged last year:\n",
13251384
"\n",
13261385
"![cycle spinning](../images/cycle_spin.png)"
13271386
]
@@ -1343,7 +1402,7 @@
13431402
"name": "python",
13441403
"nbconvert_exporter": "python",
13451404
"pygments_lexer": "ipython3",
1346-
"version": "3.6.0"
1405+
"version": "3.6.3"
13471406
}
13481407
},
13491408
"nbformat": 4,

lectures/3_morphological_operations.ipynb

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,7 @@
3131
"source": [
3232
"import numpy as np\n",
3333
"from matplotlib import pyplot as plt, cm\n",
34-
"import skdemo\n",
35-
"plt.rcParams['image.cmap'] = 'cubehelix'\n",
36-
"plt.rcParams['image.interpolation'] = 'none'"
34+
"import skdemo"
3735
]
3836
},
3937
{
@@ -265,9 +263,9 @@
265263
"name": "python",
266264
"nbconvert_exporter": "python",
267265
"pygments_lexer": "ipython3",
268-
"version": "3.4.3"
266+
"version": "3.6.3"
269267
}
270268
},
271269
"nbformat": 4,
272-
"nbformat_minor": 0
270+
"nbformat_minor": 1
273271
}

0 commit comments

Comments
 (0)