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