-
-
Notifications
You must be signed in to change notification settings - Fork 125
Blur Animation source code and simplified output #328
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
13 commits
Select commit
Hold shift + click to select a range
d539366
added python script to create simple mean filter blurring animation o…
3bda9ee
added little documentation and changed default colors
2999398
updated shape reference for non-squared images
0e9ea4c
Re-organize folder structure and location of code scripts
uschille ed104ad
removed user-defined input for kernel size and added a default kernel…
7583479
Adapted text referring to blur animation with new grayscale image
578667b
removed comment about user input and added full dependencies as comme…
b97fc97
fixed typo and improved phrasing on lesson section about blurring ani…
a502e34
fixed some mistyped comments along the code
bb1bf0d
added explicitly installed package version in requirements
9dd39d4
code style corrections and few other small edits
marcodallavecchia f1c6320
Merge branch 'datacarpentry:main' into mdv/blur-anim
marcodallavecchia 7689126
rephrased references to left and right side images for clarity
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
160 changes: 160 additions & 0 deletions
160
episodes/fig/source/06-blurring/create_blur_animation.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,160 @@ | ||
### METADATA | ||
# author: Marco Dalla Vecchia @marcodallavecchia | ||
# description: Simple blurring animation of simple image | ||
# data-source: letterA.tif was created using ImageJ (https://imagej.net/ij/) | ||
### | ||
|
||
### INFO | ||
# This script creates the animated illustration of blurring in episode 6 | ||
### | ||
|
||
### USAGE | ||
# The script was written in Python 3.12 and required the following Python packages: | ||
# - numpy==2.2.3 | ||
# - scipy==1.15.2 | ||
# - matplotlib==3.10.1 | ||
# - tqdm==4.67.1 | ||
# | ||
# The script can be executed with | ||
# $ python create_blur_animation.py | ||
# The output animation will be saved directly in the fig folder where the markdown lesson file will pick it up | ||
### | ||
|
||
### POTENTIAL IMPROVEMENTS | ||
# - Change colors for rectangular patches in animation | ||
# - Ask for image input instead of hard-coding it | ||
# - Ask for FPS as input | ||
# - Ask for animation format output | ||
|
||
# Import packages | ||
import numpy as np | ||
from scipy.ndimage import convolve | ||
from matplotlib import pyplot as plt | ||
from matplotlib import patches as p | ||
from matplotlib.animation import FuncAnimation | ||
from tqdm import tqdm | ||
|
||
# Path to input and output images | ||
data_path = "../../../data/" | ||
fig_path = "../../../fig/" | ||
input_file = data_path + "letterA.tif" | ||
output_file = fig_path + "blur-demo.gif" | ||
|
||
# Change here colors to improve accessibility | ||
kernel_color = "tab:red" | ||
center_color = "tab:olive" | ||
kernel_size = 3 | ||
|
||
### ANIMATION FUNCTIONS | ||
def init(): | ||
""" | ||
Initialization function | ||
- Set image array data | ||
- Autoscale image display | ||
- Set XY coordinates of rectangular patches | ||
""" | ||
im.set_array(img_convolved) | ||
im.autoscale() | ||
k_rect.set_xy((-0.5, -0.5)) | ||
c_rect1.set_xy((kernel_size / 2 - 1, kernel_size / 2 - 1)) | ||
return [im, k_rect, c_rect1] | ||
|
||
def update(frame): | ||
""" | ||
Animation update function. For every frame do the following: | ||
- Update X and Y coordinates of rectangular patch for kernel | ||
- Update X and Y coordinates of rectangular patch for central pixel | ||
- Update blurred image frame | ||
""" | ||
pbar.update(1) | ||
row = (frame % total_frames) // (img_pad.shape[0] - kernel_size + 1) | ||
col = (frame % total_frames) % (img_pad.shape[1] - kernel_size + 1) | ||
|
||
k_rect.set_x(col - 0.5) | ||
c_rect1.set_x(col + (kernel_size/2 - 1)) | ||
k_rect.set_y(row - 0.5) | ||
c_rect1.set_y(row + (kernel_size/2 - 1)) | ||
|
||
im.set_array(all_frames[frame]) | ||
im.autoscale() | ||
|
||
return [im, k_rect, c_rect1] | ||
|
||
# MAIN PROGRAM | ||
if __name__ == "__main__": | ||
|
||
print(f"Creating blurring animation with kernel size: {kernel_size}") | ||
|
||
# Load image | ||
img = plt.imread(input_file) | ||
|
||
### HERE WE USE THE CONVOLVE FUNCTION TO GET THE FINAL BLURRED IMAGE | ||
# I chose a simple mean filter (equal kernel weights) | ||
kernel = np.ones(shape=(kernel_size, kernel_size)) / kernel_size ** 2 # create kernel | ||
# convolve the image, i.e., apply mean filter | ||
img_convolved = convolve(img, kernel, mode='constant', cval=0) # pad borders with zero like below for consistency | ||
|
||
|
||
### HERE WE CONVOLVE MANUALLY STEP-BY-STEP TO CREATE ANIMATION | ||
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 | ||
new_img = np.zeros(img.shape, dtype=np.uint16) # this will be the blurred final image | ||
|
||
# add first frame with complete blurred image for print version of GIF | ||
all_frames = [img_convolved] | ||
|
||
# precompute animation frames and append to the list | ||
total_frames = (img_pad.shape[0] - kernel_size + 1) * (img_pad.shape[1] - kernel_size + 1) # total frames if by chance image is not squared | ||
marcodallavecchia marked this conversation as resolved.
Show resolved
Hide resolved
|
||
for frame in range(total_frames): | ||
row = (frame % total_frames) // (img_pad.shape[0] - kernel_size + 1) # row index | ||
col = (frame % total_frames) % (img_pad.shape[1] - kernel_size + 1) # col index | ||
img_chunk = img_pad[row:row + kernel_size, col:col + kernel_size] # get current image chunk inside the kernel | ||
new_img[row, col] = np.mean(img_chunk).astype(np.uint16) # calculate its mean -> mean filter | ||
all_frames.append(new_img.copy()) # append to animation frames list | ||
|
||
# We now have an extra frame | ||
total_frames += 1 | ||
|
||
### FROM HERE WE START CREATING THE ANIMATION | ||
# Initialize canvas | ||
f, (ax1, ax2) = plt.subplots(ncols=2, figsize=(10, 5)) | ||
|
||
# Display the padded image -> this one won't change during the animation | ||
ax1.imshow(img_pad, cmap="gray") | ||
# Initialize the blurred image -> this is the first frame with already the final result | ||
im = ax2.imshow(img_convolved, animated=True, cmap="gray") | ||
|
||
# Define rectangular patches to identify moving kernel | ||
k_rect = p.Rectangle((-0.5, -0.5), kernel_size, kernel_size, linewidth=2, edgecolor=kernel_color, facecolor="none", alpha=0.8) # kernel rectangle | ||
c_rect1 = p.Rectangle(((kernel_size/2 - 1), (kernel_size/2 - 1)), 1, 1, linewidth=2, edgecolor=center_color, facecolor="none") # central pixel rectangle | ||
# Add them to the figure | ||
ax1.add_patch(k_rect) | ||
ax1.add_patch(c_rect1) | ||
|
||
# Fix limits of the image on the right (without padding) so that it is the same size as the image on the left (with padding) | ||
ax2.set( | ||
ylim=((img_pad.shape[0] - kernel_size / 2), -kernel_size / 2), | ||
xlim=(-kernel_size / 2, (img_pad.shape[1] - kernel_size / 2)) | ||
) | ||
|
||
# We don't need to see the ticks | ||
ax1.axis("off") | ||
ax2.axis("off") | ||
|
||
# Create progress bar to visualize animation progress | ||
pbar = tqdm(total=total_frames) | ||
|
||
### HERE WE CREATE THE ANIMATION | ||
# Use FuncAnimation to create the animation | ||
ani = FuncAnimation( | ||
f, update, | ||
frames=range(total_frames), | ||
interval=50, # we could change the animation speed | ||
init_func=init, | ||
blit=True | ||
) | ||
|
||
# Export animation | ||
plt.tight_layout() | ||
ani.save(output_file) | ||
pbar.close() | ||
print("Animation exported") |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.