-
Notifications
You must be signed in to change notification settings - Fork 0
/
Animator.py
145 lines (100 loc) · 3.98 KB
/
Animator.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
#!/usr/bin/env python
from math import ceil
import numpy as np
from contextlib import contextmanager
import cv2
from ImageSource import ImageSource
from ScriptParser import ScriptParser
class Animator:
def __init__(self, args):
self.args = args
self.script_parser = ScriptParser(args)
self.image_source = ImageSource(args)
self.codec = cv2.VideoWriter_fourcc(*args.codec)
def parse_script(self):
temporal_dict = self.script_parser.parse_script()
if len(temporal_dict) == 0:
raise ValueError("Script contains no state")
self.temporal_dict = temporal_dict
if self.args.print_states:
temporal_dict.print()
return self
def animate(self):
if self.args.store_new:
if self.args.verbose:
print("Finding/Generating frames")
self.image_source.create_frames(self.temporal_dict.states)
if self.args.create_texture:
self.create_texture()
else:
self.create_video()
return self
def create_texture(self):
if self.args.verbose:
print("Collecting frames")
image_list = list(self.frames)
if self.args.verbose:
print(f"Creating texture from {len(image_list)} frames")
texture = self.stitch_images(image_list)
self.save_texture(texture)
def save_texture(self, texture):
#Defaults to saving texture in full resolution
if self.args.texture_dimensions is None:
if self.args.verbose:
print("Saving full resolution texture")
cv2.imwrite(str(self.args.output_path), texture)
return
#Otherwise we pull out path information to give each produced texture a proper filename
original_path = self.args.output_path
stem = str(original_path.stem)
suffix = str(original_path.suffix)
for width, height in self.args.texture_dimensions:
if self.args.verbose:
print(f"Saving {width}x{height} texture")
new_path = str(original_path.with_name(f"{stem}_{width}x{height}{suffix}"))
resized = cv2.resize(texture, (width, height), interpolation = cv2.INTER_AREA)#TODO: make interpolation a CL argument
cv2.imwrite(new_path, resized)
def create_video(self):
if self.args.verbose:
print("Writing frames to video")
with self.video_writer as video:
for frame in self.frames:
video.write(frame)
@property
@contextmanager
def video_writer(self):
frame = next(self.frames)
height, width, layers = frame.shape
writer = cv2.VideoWriter(str(self.args.output_path), self.codec, self.args.fps, (width, height))
try:
yield writer
finally:
cv2.destroyAllWindows()
writer.release()
@property
def frames(self):
for state in self.temporal_dict.states:
yield self.image_source.get_image(state)
def stitch_images(self, images):
image_matrix = self.get_image_matrix(images)
return cv2.vconcat([cv2.hconcat(rows) for rows in image_matrix])
def get_image_matrix(self, images):
n = len(images)
layout = self.args.texture_layout
if layout == "square":
width = ceil(n ** 0.5)
height = ceil(n / width)
elif layout == "horizontal":
width = n
height = 1
elif layout == "vertical":
width = 1
height = n
if n != width * height:
images = self.pad_with_blank(images, width, height)
return [images[i:i+width] for i in range(0, n, width)]
def pad_with_blank(self, images, width, height):
image_height, image_width, _ = images[0].shape
blank_image = np.zeros((image_height,image_width,3), np.uint8)
padding = [blank_image] * ((width * height) - len(images))
return images + padding