From 5d7864d153196bbba2bdfda3bbe0aa70e7dfb81e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kartal=20Kaan=20Bozdo=C4=9Fan?= Date: Tue, 14 Jan 2020 00:08:05 +0300 Subject: [PATCH 1/9] Added the option to change the propeller speed --- rolling-shutter-tkinter.py | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/rolling-shutter-tkinter.py b/rolling-shutter-tkinter.py index 0b8a2f4..65d466c 100644 --- a/rolling-shutter-tkinter.py +++ b/rolling-shutter-tkinter.py @@ -11,26 +11,28 @@ x, y = np.ogrid[-height/2: height/2, -height/2: height/2] plane = x + 1j * y bentprop = np.zeros_like(plane, dtype=np.bool) +propeller_angular_speed=0.3 +animation_speed=3 for frame in range(height): - backgnd = np.zeros_like(plane, dtype=np.uint8) propellor = np.zeros_like(plane, dtype=np.bool) - angle = 2 * np.pi * ( frame / height + 1/12) + angle = 2 * np.pi * ( propeller_angular_speed * frame / height + 1/12) for blade in range(6): phase = np.exp( 1j * (angle + blade * np.pi/3)) ellipse = abs(plane - 0.49 * height * phase) + abs(plane) propellor |= ellipse < 0.5 * height bentprop[frame] = propellor[frame] - greenbar = [f for f in range(frame, min(frame + 3, height -3))] - colors = ("white","lightblue","red","green") - rgbcolors = np.array(list(map(root.winfo_rgb, colors))) / 256 - composite = np.maximum.reduce((backgnd, propellor*1, bentprop*2)) - composite[greenbar] = 3 - rgb = rgbcolors.astype(np.uint8)[composite] - image = ImageTk.PhotoImage(Image.fromarray(rgb, mode="RGB")) - label.config(image=image) - root.update() + if frame % animation_speed == 0: + greenbar = list(range(frame, min(frame + 3, height -3))) + colors = ("white","lightblue","red","green") + rgbcolors = np.array(list(map(root.winfo_rgb, colors))) / 256 + composite = np.maximum.reduce((propellor*1, bentprop*2)) + composite[greenbar] = 3 + rgb = rgbcolors.astype(np.uint8)[composite] + image = ImageTk.PhotoImage(Image.fromarray(rgb, mode="RGB")) + label.config(image=image) + root.update() root.mainloop() From 0316615bbf3a93b9ae63b3ac1c553ed7e158ee0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kartal=20Kaan=20Bozdo=C4=9Fan?= Date: Tue, 14 Jan 2020 00:20:26 +0300 Subject: [PATCH 2/9] Optimizations to the tkinter player --- rolling-shutter-tkinter.py | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/rolling-shutter-tkinter.py b/rolling-shutter-tkinter.py index 65d466c..dee4734 100644 --- a/rolling-shutter-tkinter.py +++ b/rolling-shutter-tkinter.py @@ -10,24 +10,32 @@ height = 600 x, y = np.ogrid[-height/2: height/2, -height/2: height/2] plane = x + 1j * y +plane_abs = abs(plane) bentprop = np.zeros_like(plane, dtype=np.bool) -propeller_angular_speed=0.3 +propeller_angular_speed=1 animation_speed=3 +propeller_cache = {} + for frame in range(height): - propellor = np.zeros_like(plane, dtype=np.bool) + propellors = np.zeros_like(plane, dtype=np.bool) angle = 2 * np.pi * ( propeller_angular_speed * frame / height + 1/12) for blade in range(6): phase = np.exp( 1j * (angle + blade * np.pi/3)) - ellipse = abs(plane - 0.49 * height * phase) + abs(plane) - propellor |= ellipse < 0.5 * height + if phase in propeller_cache: + this_propellor = propeller_cache[phase] + else: + ellipse = abs(plane - 0.49 * height * phase) + plane_abs + this_propellor = ellipse < 0.5 * height + propeller_cache[phase] = this_propellor + propellors |= this_propellor - bentprop[frame] = propellor[frame] + bentprop[frame] = propellors[frame] if frame % animation_speed == 0: greenbar = list(range(frame, min(frame + 3, height -3))) colors = ("white","lightblue","red","green") rgbcolors = np.array(list(map(root.winfo_rgb, colors))) / 256 - composite = np.maximum.reduce((propellor*1, bentprop*2)) + composite = np.maximum.reduce((propellors*1, bentprop*2)) composite[greenbar] = 3 rgb = rgbcolors.astype(np.uint8)[composite] image = ImageTk.PhotoImage(Image.fromarray(rgb, mode="RGB")) From 1b74b9c4c18ec8009b1d53cb52b10de80c9051d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kartal=20Kaan=20Bozdo=C4=9Fan?= Date: Tue, 14 Jan 2020 00:32:34 +0300 Subject: [PATCH 3/9] Further optimizations --- rolling-shutter-tkinter.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/rolling-shutter-tkinter.py b/rolling-shutter-tkinter.py index dee4734..c695fcf 100644 --- a/rolling-shutter-tkinter.py +++ b/rolling-shutter-tkinter.py @@ -19,9 +19,11 @@ for frame in range(height): propellors = np.zeros_like(plane, dtype=np.bool) - angle = 2 * np.pi * ( propeller_angular_speed * frame / height + 1/12) + base_angle = 2 * np.pi * ( propeller_angular_speed * frame / height + 1/12) for blade in range(6): - phase = np.exp( 1j * (angle + blade * np.pi/3)) + this_angle = base_angle + blade * np.pi/3 + this_angle = round(this_angle, 3) + phase = np.exp( 1j * this_angle) if phase in propeller_cache: this_propellor = propeller_cache[phase] else: From 309d409af0d1e9423150409e0c4f5304ed0fd659 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kartal=20Kaan=20Bozdo=C4=9Fan?= Date: Tue, 14 Jan 2020 10:44:10 +0300 Subject: [PATCH 4/9] Added a script to visualize the end result as a function of the speed of the propellers --- rolling-shutter-vary_propeller_speed.py | 49 +++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 rolling-shutter-vary_propeller_speed.py diff --git a/rolling-shutter-vary_propeller_speed.py b/rolling-shutter-vary_propeller_speed.py new file mode 100644 index 0000000..8150725 --- /dev/null +++ b/rolling-shutter-vary_propeller_speed.py @@ -0,0 +1,49 @@ +#!/usr/bin/env python3 + +import tkinter as tk +import numpy as np +from PIL import Image, ImageTk # pillow fork of PIL + +root = tk.Tk() +label = tk.Label(root) +label.pack() +height = 600 +x, y = np.ogrid[-height/2: height/2, -height/2: height/2] +plane = x + 1j * y +plane_abs = abs(plane) + +propeller_cache = {} + +for propeller_angular_speed in np.arange(0.1, 6.001, 0.1): + bentprop = np.zeros_like(plane, dtype=np.bool) + + for frame in range(height): + propellors = np.zeros_like(plane, dtype=np.bool) + base_angle = 2 * np.pi * ( propeller_angular_speed * frame / height + 1/12) + for blade in range(6): + this_angle = base_angle + blade * np.pi/3 + this_angle = round(this_angle, 3) + phase = np.exp( 1j * this_angle) + if phase in propeller_cache: + this_propellor = propeller_cache[phase] + else: + ellipse = abs(plane - 0.49 * height * phase) + plane_abs + this_propellor = ellipse < 0.5 * height + propeller_cache[phase] = this_propellor + propellors |= this_propellor + + bentprop[frame] = propellors[frame] + #greenbar = list(range(frame, min(frame + 3, height -3))) + colors = ("white","lightblue","red","green") + rgbcolors = np.array(list(map(root.winfo_rgb, colors))) / 256 + composite = np.maximum.reduce((propellors*1, bentprop*2)) + #composite[greenbar] = 3 + rgb = rgbcolors.astype(np.uint8)[composite] + image = Image.fromarray(rgb, mode="RGB") + image.save('vary_speed/{:.1f}.png'.format(propeller_angular_speed)) + tk_image = ImageTk.PhotoImage(image) + label.config(image=tk_image) + root.update() + +root.mainloop() + From 5b1fc74f443e6199f0da0c90d3423276677d2c3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kartal=20Kaan=20Bozdo=C4=9Fan?= Date: Tue, 14 Jan 2020 10:50:53 +0300 Subject: [PATCH 5/9] Further optimization. Consider only the module pi/3 of the base angle to increase cache hits --- rolling-shutter-tkinter.py | 1 + rolling-shutter-vary_propeller_speed.py | 1 + 2 files changed, 2 insertions(+) diff --git a/rolling-shutter-tkinter.py b/rolling-shutter-tkinter.py index c695fcf..d9e849e 100644 --- a/rolling-shutter-tkinter.py +++ b/rolling-shutter-tkinter.py @@ -20,6 +20,7 @@ for frame in range(height): propellors = np.zeros_like(plane, dtype=np.bool) base_angle = 2 * np.pi * ( propeller_angular_speed * frame / height + 1/12) + base_angle %= np.pi/3 for blade in range(6): this_angle = base_angle + blade * np.pi/3 this_angle = round(this_angle, 3) diff --git a/rolling-shutter-vary_propeller_speed.py b/rolling-shutter-vary_propeller_speed.py index 8150725..8bb8771 100644 --- a/rolling-shutter-vary_propeller_speed.py +++ b/rolling-shutter-vary_propeller_speed.py @@ -20,6 +20,7 @@ for frame in range(height): propellors = np.zeros_like(plane, dtype=np.bool) base_angle = 2 * np.pi * ( propeller_angular_speed * frame / height + 1/12) + base_angle %= np.pi/3 for blade in range(6): this_angle = base_angle + blade * np.pi/3 this_angle = round(this_angle, 3) From 849e10cc2dbf175c6fd5eccdc96d0808f4ada537 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kartal=20Kaan=20Bozdo=C4=9Fan?= Date: Tue, 14 Jan 2020 11:04:32 +0300 Subject: [PATCH 6/9] Further optimizations We now cache the pixel values of all propellers, keyed by the modulo'd base angle --- rolling-shutter-tkinter.py | 20 ++++++++++---------- rolling-shutter-vary_propeller_speed.py | 24 ++++++++++++------------ 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/rolling-shutter-tkinter.py b/rolling-shutter-tkinter.py index d9e849e..a90c8a0 100644 --- a/rolling-shutter-tkinter.py +++ b/rolling-shutter-tkinter.py @@ -18,20 +18,20 @@ propeller_cache = {} for frame in range(height): - propellors = np.zeros_like(plane, dtype=np.bool) base_angle = 2 * np.pi * ( propeller_angular_speed * frame / height + 1/12) base_angle %= np.pi/3 - for blade in range(6): - this_angle = base_angle + blade * np.pi/3 - this_angle = round(this_angle, 3) - phase = np.exp( 1j * this_angle) - if phase in propeller_cache: - this_propellor = propeller_cache[phase] - else: + base_angle = round(base_angle, 3) + if base_angle in propeller_cache: + propellors = propeller_cache[base_angle] + else: + propellors = np.zeros_like(plane, dtype=np.bool) + for blade in range(6): + this_angle = base_angle + blade * np.pi/3 + phase = np.exp( 1j * this_angle) ellipse = abs(plane - 0.49 * height * phase) + plane_abs this_propellor = ellipse < 0.5 * height - propeller_cache[phase] = this_propellor - propellors |= this_propellor + propellors |= this_propellor + propeller_cache[base_angle] = propellors bentprop[frame] = propellors[frame] if frame % animation_speed == 0: diff --git a/rolling-shutter-vary_propeller_speed.py b/rolling-shutter-vary_propeller_speed.py index 8bb8771..9d3bb5e 100644 --- a/rolling-shutter-vary_propeller_speed.py +++ b/rolling-shutter-vary_propeller_speed.py @@ -14,24 +14,24 @@ propeller_cache = {} -for propeller_angular_speed in np.arange(0.1, 6.001, 0.1): +for propeller_angular_speed in np.arange(0.03, 2.001, 0.03): bentprop = np.zeros_like(plane, dtype=np.bool) for frame in range(height): - propellors = np.zeros_like(plane, dtype=np.bool) base_angle = 2 * np.pi * ( propeller_angular_speed * frame / height + 1/12) base_angle %= np.pi/3 - for blade in range(6): - this_angle = base_angle + blade * np.pi/3 - this_angle = round(this_angle, 3) - phase = np.exp( 1j * this_angle) - if phase in propeller_cache: - this_propellor = propeller_cache[phase] - else: + base_angle = round(base_angle, 3) + if base_angle in propeller_cache: + propellors = propeller_cache[base_angle] + else: + propellors = np.zeros_like(plane, dtype=np.bool) + for blade in range(6): + this_angle = base_angle + blade * np.pi/3 + phase = np.exp( 1j * this_angle) ellipse = abs(plane - 0.49 * height * phase) + plane_abs this_propellor = ellipse < 0.5 * height - propeller_cache[phase] = this_propellor - propellors |= this_propellor + propellors |= this_propellor + propeller_cache[base_angle] = propellors bentprop[frame] = propellors[frame] #greenbar = list(range(frame, min(frame + 3, height -3))) @@ -41,7 +41,7 @@ #composite[greenbar] = 3 rgb = rgbcolors.astype(np.uint8)[composite] image = Image.fromarray(rgb, mode="RGB") - image.save('vary_speed/{:.1f}.png'.format(propeller_angular_speed)) + image.save('vary_speed/{:.2f}.png'.format(propeller_angular_speed)) tk_image = ImageTk.PhotoImage(image) label.config(image=tk_image) root.update() From 104a161e1607664df6b7a77fc5b165ae86afad1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kartal=20Kaan=20Bozdo=C4=9Fan?= Date: Tue, 14 Jan 2020 11:18:11 +0300 Subject: [PATCH 7/9] vary_propeller_speed now produces a video as well --- rolling-shutter-vary_propeller_speed.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/rolling-shutter-vary_propeller_speed.py b/rolling-shutter-vary_propeller_speed.py index 9d3bb5e..94d552d 100644 --- a/rolling-shutter-vary_propeller_speed.py +++ b/rolling-shutter-vary_propeller_speed.py @@ -2,6 +2,7 @@ import tkinter as tk import numpy as np +import os from PIL import Image, ImageTk # pillow fork of PIL root = tk.Tk() @@ -11,8 +12,8 @@ x, y = np.ogrid[-height/2: height/2, -height/2: height/2] plane = x + 1j * y plane_abs = abs(plane) - propeller_cache = {} +file_name_counter=0 for propeller_angular_speed in np.arange(0.03, 2.001, 0.03): bentprop = np.zeros_like(plane, dtype=np.bool) @@ -41,10 +42,14 @@ #composite[greenbar] = 3 rgb = rgbcolors.astype(np.uint8)[composite] image = Image.fromarray(rgb, mode="RGB") - image.save('vary_speed/{:.2f}.png'.format(propeller_angular_speed)) + image.save('vary_speed/{}.png'.format(file_name_counter)) + file_name_counter += 1 tk_image = ImageTk.PhotoImage(image) label.config(image=tk_image) root.update() +os.system("ffmpeg -y -loglevel warning -r 7 -i vary_speed/%d.png " + + "-framerate 25 -pix_fmt yuv420p vary_speed.mp4") + root.mainloop() From f7417cdf2b94f082350a3f7a25bd023b5e266415 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kartal=20Kaan=20Bozdo=C4=9Fan?= Date: Tue, 14 Jan 2020 19:03:59 +0300 Subject: [PATCH 8/9] Added a script to simulate a video of a propeller shot with a rolling shutter --- rolling-shutter-continuous.py | 51 +++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 rolling-shutter-continuous.py diff --git a/rolling-shutter-continuous.py b/rolling-shutter-continuous.py new file mode 100644 index 0000000..51a5ca8 --- /dev/null +++ b/rolling-shutter-continuous.py @@ -0,0 +1,51 @@ +#!/usr/bin/env python3 + +import tkinter as tk +import numpy as np +from PIL import Image, ImageTk # pillow fork of PIL + +root = tk.Tk() +label = tk.Label(root) +label.pack() +height = 600 +x, y = np.ogrid[-height/2: height/2, -height/2: height/2] +plane = x + 1j * y +plane_abs = abs(plane) +propeller_angular_speed=0.3 +animation_speed=3 + +propeller_cache = {} +frame = 0 + +while True: + bentprop = np.zeros_like(plane, dtype=np.bool) + for line in range(height): + line_base_angle = 2 * np.pi * ( propeller_angular_speed * (frame+line) / height + 1/12) + line_base_angle %= np.pi/3 + line_base_angle = round(line_base_angle, 3) + if line_base_angle in propeller_cache: + propellors = propeller_cache[line_base_angle] + else: + propellors = np.zeros_like(plane, dtype=np.bool) + for blade in range(6): + this_angle = line_base_angle + blade * np.pi/3 + phase = np.exp( 1j * this_angle) + ellipse = abs(plane - 0.49 * height * phase) + plane_abs + this_propellor = ellipse < 0.5 * height + propellors |= this_propellor + propeller_cache[line_base_angle] = propellors + + bentprop[line] = propellors[line] + #greenbar = list(range(frame, min(frame + 3, height -3))) + colors = ("white","lightblue","red","green") + rgbcolors = np.array(list(map(root.winfo_rgb, colors))) / 256 + composite = bentprop*2 + #composite[greenbar] = 3 + rgb = rgbcolors.astype(np.uint8)[composite] + image = ImageTk.PhotoImage(Image.fromarray(rgb, mode="RGB")) + label.config(image=image) + root.update() + frame += 1 + +root.mainloop() + From 0ac698e8c55a0a1eecd594e140e5b84b6b2a8bfd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kartal=20Kaan=20Bozdo=C4=9Fan?= Date: Tue, 14 Jan 2020 19:24:23 +0300 Subject: [PATCH 9/9] Optimized rolling-shutter-continuous.py --- rolling-shutter-continuous.py | 63 ++++++++++++++++++++--------------- 1 file changed, 37 insertions(+), 26 deletions(-) diff --git a/rolling-shutter-continuous.py b/rolling-shutter-continuous.py index 51a5ca8..ec2ce38 100644 --- a/rolling-shutter-continuous.py +++ b/rolling-shutter-continuous.py @@ -2,6 +2,7 @@ import tkinter as tk import numpy as np +import time from PIL import Image, ImageTk # pillow fork of PIL root = tk.Tk() @@ -11,38 +12,48 @@ x, y = np.ogrid[-height/2: height/2, -height/2: height/2] plane = x + 1j * y plane_abs = abs(plane) -propeller_angular_speed=0.3 -animation_speed=3 +propeller_angular_speed=1 +frame_cache = {} propeller_cache = {} frame = 0 while True: - bentprop = np.zeros_like(plane, dtype=np.bool) - for line in range(height): - line_base_angle = 2 * np.pi * ( propeller_angular_speed * (frame+line) / height + 1/12) - line_base_angle %= np.pi/3 - line_base_angle = round(line_base_angle, 3) - if line_base_angle in propeller_cache: - propellors = propeller_cache[line_base_angle] - else: - propellors = np.zeros_like(plane, dtype=np.bool) - for blade in range(6): - this_angle = line_base_angle + blade * np.pi/3 - phase = np.exp( 1j * this_angle) - ellipse = abs(plane - 0.49 * height * phase) + plane_abs - this_propellor = ellipse < 0.5 * height - propellors |= this_propellor - propeller_cache[line_base_angle] = propellors + frame_base_angle = 2 * np.pi * ( propeller_angular_speed * frame / height + 1/12) + frame_base_angle %= np.pi/3 + frame_base_angle = round(frame_base_angle, 3) + if frame_base_angle in frame_cache: + print('frame hit') + image = frame_cache[frame_base_angle] + time.sleep(1/500) + else: + print('frame miss') + bentprop = np.zeros_like(plane, dtype=np.bool) + for line in range(height): + line_base_angle = 2 * np.pi * ( propeller_angular_speed * (frame+line) / height + 1/12) + line_base_angle %= np.pi/3 + line_base_angle = round(line_base_angle, 3) + if line_base_angle in propeller_cache: + propellors = propeller_cache[line_base_angle] + else: + propellors = np.zeros_like(plane, dtype=np.bool) + for blade in range(6): + this_angle = line_base_angle + blade * np.pi/3 + phase = np.exp( 1j * this_angle) + ellipse = abs(plane - 0.49 * height * phase) + plane_abs + this_propellor = ellipse < 0.5 * height + propellors |= this_propellor + propeller_cache[line_base_angle] = propellors - bentprop[line] = propellors[line] - #greenbar = list(range(frame, min(frame + 3, height -3))) - colors = ("white","lightblue","red","green") - rgbcolors = np.array(list(map(root.winfo_rgb, colors))) / 256 - composite = bentprop*2 - #composite[greenbar] = 3 - rgb = rgbcolors.astype(np.uint8)[composite] - image = ImageTk.PhotoImage(Image.fromarray(rgb, mode="RGB")) + bentprop[line] = propellors[line] + #greenbar = list(range(frame, min(frame + 3, height -3))) + colors = ("white","lightblue","red","green") + rgbcolors = np.array(list(map(root.winfo_rgb, colors))) / 256 + composite = bentprop*2 + #composite[greenbar] = 3 + rgb = rgbcolors.astype(np.uint8)[composite] + image = ImageTk.PhotoImage(Image.fromarray(rgb, mode="RGB")) + frame_cache[frame_base_angle] = image label.config(image=image) root.update() frame += 1