Skip to content

Commit 3985f68

Browse files
committed
differences for PR #328
1 parent 015f2f2 commit 3985f68

File tree

5 files changed

+164
-3
lines changed

5 files changed

+164
-3
lines changed

06-blurring.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -247,8 +247,7 @@ in [the scikit-image documentation](https://scikit-image.org/docs/dev/user_guide
247247

248248
::::::::::::::::::::::::::::::::::::::::::::::::::
249249

250-
This animation shows how the blur kernel moves along in the original image in
251-
order to calculate the colour channel values for the blurred image.
250+
Let's consider a very simple image to see blurring in action. The animation below shows how the blur kernel (large red square) moves along the image on the left in order to calculate the corresponding values for the blurred image (yellow central square) on the right. In this simple case, the original image is single-channel, but blurring would work likewise on a multi-channel image.
252251

253252
![](fig/blur-demo.gif){alt='Blur demo animation'}
254253

data/letterA.tif

1.11 KB
Binary file not shown.

fig/blur-demo.gif

-30.6 MB
Loading
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
### METADATA
2+
# author: Marco Dalla Vecchia @marcodallavecchia
3+
# description: Simple blurring animation of simple image
4+
# data-source: letterA.tif was created using ImageJ (https://imagej.net/ij/)
5+
###
6+
7+
### INFO
8+
# This script creates the animated illustration of blurring in episode 6
9+
###
10+
11+
### USAGE
12+
# The script requires the following Python packages:
13+
# - numpy
14+
# - scipy
15+
# - matplotlib
16+
# - tqdm
17+
# Install them with
18+
# $ conda install numpy scipy matplotlib tqdm
19+
#
20+
# The script can be executed with
21+
# $ python create_blur_animation.py
22+
# The output animation will be saved directly in the fig folder where the markdown lesson file will pick it up
23+
###
24+
25+
### POTENTIAL IMPROVEMENTS
26+
# - Change colors for rectangular patches in animation
27+
# - Ask for image input instead of hard-coding it
28+
# - Ask for FPS as input
29+
# - Ask for animation format output
30+
31+
# Import packages
32+
import numpy as np
33+
from scipy.ndimage import convolve
34+
from matplotlib import pyplot as plt
35+
from matplotlib import patches as p
36+
from matplotlib.animation import FuncAnimation
37+
from tqdm import tqdm
38+
39+
# Path to input and output images
40+
data_path = "../../../data/"
41+
fig_path = "../../../fig/"
42+
input_file = data_path + "letterA.tif"
43+
output_file = fig_path + "blur-demo.gif"
44+
45+
# Change here colors to improve accessibility
46+
kernel_color = "tab:red"
47+
center_color = "tab:olive"
48+
kernel_size = 3
49+
50+
### ANIMATION FUNCTIONS
51+
def init():
52+
"""
53+
Initialization function
54+
- Set image array data
55+
- Autoscale image display
56+
- Set XY coordinates of rectangular patches
57+
"""
58+
im.set_array(img_convolved)
59+
im.autoscale()
60+
k_rect.set_xy((-0.5, -0.5))
61+
c_rect1.set_xy((kernel_size / 2 - 1, kernel_size / 2 - 1))
62+
return [im, k_rect, c_rect1]
63+
64+
def update(frame):
65+
"""
66+
Animation update function. For every frame do the following:
67+
- Update X and Y coordinates of rectangular patch for kernel
68+
- Update X and Y coordinates of rectangular patch for central pixel
69+
- Update blurred image frame
70+
"""
71+
pbar.update(1)
72+
row = (frame % total_frames) // (img_pad.shape[0] - kernel_size + 1)
73+
col = (frame % total_frames) % (img_pad.shape[1] - kernel_size + 1)
74+
75+
k_rect.set_x(col - 0.5)
76+
c_rect1.set_x(col + (kernel_size/2 - 1))
77+
k_rect.set_y(row - 0.5)
78+
c_rect1.set_y(row + (kernel_size/2 - 1))
79+
80+
im.set_array(all_frames[frame])
81+
im.autoscale()
82+
83+
return [im, k_rect, c_rect1]
84+
85+
# MAIN PROGRAM
86+
if __name__ == "__main__":
87+
88+
print("Creating blurred animation with kernel size:", kernel_size)
89+
90+
# Load image
91+
img = plt.imread(input_file)
92+
93+
### HERE WE USE THE CONVOLVE FUNCTION TO GET THE FINAL BLURRED IMAGE
94+
# I chose a simple mean filter (equal kernel weights)
95+
kernel = np.ones(shape=(kernel_size, kernel_size)) / kernel_size ** 2 # create kernel
96+
# convolve the image i.e. apply mean filter
97+
img_convolved = convolve(img, kernel, mode='constant', cval=0) # pad borders with zero like below for consistency
98+
99+
100+
### HERE WE CONVOLVE MANUALLY STEP-BY-STEP TO CREATE ANIMATION
101+
img_pad = np.pad(img, (int(np.ceil(kernel_size/2) - 1), int(np.ceil(kernel_size/2) - 1))) # Pad image to deal with borders
102+
new_img = np.zeros(img.shape, dtype=np.uint16) # this will be the blurred final image
103+
104+
# add first frame with complete blurred image for print version of GIF
105+
all_frames = [img_convolved]
106+
107+
# precompute animation frames and append to the list
108+
total_frames = (img_pad.shape[0] - kernel_size + 1) * (img_pad.shape[1] - kernel_size + 1) # total frames if by change image is not squared
109+
for frame in range(total_frames):
110+
row = (frame % total_frames) // (img_pad.shape[0] - kernel_size + 1) # row index
111+
col = (frame % total_frames) % (img_pad.shape[1] - kernel_size + 1) # col index
112+
img_chunk = img_pad[row : row + kernel_size, col : col + kernel_size] # get current image chunk inside the kernel
113+
new_img[row, col] = np.mean(img_chunk).astype(np.uint16) # calculate its mean -> mean filter
114+
all_frames.append(new_img.copy()) # append to animation frames list
115+
116+
# We now have an extra frame
117+
total_frames += 1
118+
119+
### FROM HERE WE START CREATING THE ANIMATION
120+
# Initialize canvas
121+
f, (ax1, ax2) = plt.subplots(1,2, figsize=(10,5))
122+
123+
# Display the padded image -> this one won't change during the animation
124+
ax1.imshow(img_pad, cmap='gray')
125+
# Initialize the blurred image -> this is the first frame with already the final result
126+
im = ax2.imshow(img_convolved, animated=True, cmap='gray')
127+
128+
# Define rectangular patches to identify moving kernel
129+
k_rect = p.Rectangle((-0.5,-0.5), kernel_size, kernel_size, linewidth=2, edgecolor=kernel_color, facecolor='none', alpha=0.8) # kernel rectangle
130+
c_rect1 = p.Rectangle(((kernel_size/2 - 1), (kernel_size/2 - 1)), 1, 1, linewidth=2, edgecolor=center_color, facecolor='none') # central pixel rectangle
131+
# Add them to the figure
132+
ax1.add_patch(k_rect)
133+
ax1.add_patch(c_rect1)
134+
135+
# Fix limits to the right image (without padding) is the same size as the left image (with padding)
136+
ax2.set(
137+
ylim=((img_pad.shape[0] - kernel_size / 2), -kernel_size / 2),
138+
xlim=(-kernel_size / 2, (img_pad.shape[1] - kernel_size / 2))
139+
)
140+
141+
# We don't need to see the ticks
142+
ax1.axis("off")
143+
ax2.axis("off")
144+
145+
# Create progress bar to visualize animation progress
146+
pbar = tqdm(total=total_frames)
147+
148+
### HERE WE CREATE THE ANIMATION
149+
# Use FuncAnimation to create the animation
150+
ani = FuncAnimation(
151+
f, update,
152+
frames=range(total_frames),
153+
interval=50, # we could change the animation speed
154+
init_func=init,
155+
blit=True
156+
)
157+
158+
# Export animation
159+
plt.tight_layout()
160+
ani.save(output_file)
161+
pbar.close()
162+
print("Animation exported")

md5sum.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
"episodes/03-skimage-images.md" "d7890de460222e8cdf461c76cba37692" "site/built/03-skimage-images.md" "2024-06-07"
99
"episodes/04-drawing.md" "48b42ee384b5b907d9f9b93ad0e98ce8" "site/built/04-drawing.md" "2024-06-07"
1010
"episodes/05-creating-histograms.md" "4abbd123ba97d63c795398be6f6b7621" "site/built/05-creating-histograms.md" "2024-03-14"
11-
"episodes/06-blurring.md" "894ff33379584a8ee777f779564d5b01" "site/built/06-blurring.md" "2024-06-18"
11+
"episodes/06-blurring.md" "5e65bcd38a39e786b6a6b37775cd5ed9" "site/built/06-blurring.md" "2025-03-15"
1212
"episodes/07-thresholding.md" "512a1806b8061dded3a68871e7bcdfe9" "site/built/07-thresholding.md" "2025-02-09"
1313
"episodes/08-connected-components.md" "f599af69b770c7234a4e7d4a06a7f6dd" "site/built/08-connected-components.md" "2024-11-13"
1414
"episodes/09-challenges.md" "3e95485b1bae2c37538830225ea26598" "site/built/09-challenges.md" "2024-03-12"

0 commit comments

Comments
 (0)