Skip to content

Commit 858304d

Browse files
committed
differences for PR #328
1 parent f58b074 commit 858304d

File tree

5 files changed

+160
-3
lines changed

5 files changed

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