Coverage for skcvideo/reader.py: 0%
105 statements
« prev ^ index » next coverage.py v7.6.1, created at 2024-10-02 14:10 +0200
« prev ^ index » next coverage.py v7.6.1, created at 2024-10-02 14:10 +0200
1import os
2import sys
4import cv2
5import imageio
6import numpy as np
8from skcvideo.minimap import Minimap
9from skcvideo.timeline import VlcTimeline
10from skcvideo.utils import put_text
11from skcvideo.video_capture import StoredImagesVideoCapture
14class Button:
15 """
16 Used to define a clickable button on the image executing a given callback
17 when cliked. Some data specifying the button can be passed at the object
18 creation.
20 Args:
21 - hitbox: tuple (x1, y1, x2, y2) the bounding box of the clickable area.
22 - callback: a function taking x, y (the coordinates of the click) and
23 optionnaly data as arguments.
24 - data (optionnal): data of any shape that will be used by the callback.
25 """
27 def __init__(self, hitbox, callback, data=None):
28 self.hitbox = hitbox
29 self.data = data
30 self.given_callback = callback
32 def callback(self, *kwargs):
33 if self.data is None:
34 return self.given_callback(*kwargs)
35 else:
36 return self.given_callback(self.data, *kwargs)
39class Reader(StoredImagesVideoCapture):
40 """
41 A video displayer that allows interaction with the image by using buttons
42 or keyboard.
44 The main advantage of this displayer is that it allows to read the video
45 backward while keeping relatively fast.
47 The best way to use this displayer is to make your own class inheriting
48 from this one and overridding its methods.
49 """
51 def __init__(self, video_path, timeline=None, **kwargs):
52 super().__init__(video_path, colormap="bgr", **kwargs)
54 self.to_exit = False
56 # The key/function mapping
57 self.keydict = {
58 "k": self.next,
59 "j": self.previous,
60 "q": self.exit,
61 }
63 # The clickable buttons
64 self.buttons = []
66 if not hasattr(self, "video_incrustation"):
67 self.video_incrustation = None
69 if not hasattr(self, "minimap"):
70 self.minimap = Minimap(
71 box=[200, 0, 590, 575],
72 pitch_length=105.0,
73 pitch_width=68.0,
74 )
76 if not hasattr(self, "timeline"):
77 self.timeline = VlcTimeline(
78 box=[79, 1771],
79 parent=self,
80 )
82 self.buttons.append(Button(self.timeline.hitbox, self.jump_event))
84 self.background = self.create_background()
86 self.init(video_path, **kwargs)
88 self._refresh()
90 cv2.namedWindow("image", cv2.WINDOW_NORMAL)
91 cv2.resizeWindow("image", 1280, 720)
92 cv2.setMouseCallback("image", self.click_event)
94 def init(self, video_path, **kwargs):
95 pass
97 @property
98 def image_to_disp(self):
99 """
100 This property specifies the image to be displayed. You would override
101 it at your convenience e.g. to only display a subpart of the global
102 image.
103 """
104 return self.big_image
106 def exit(self):
107 self.to_exit = True
109 def create_background(self):
110 """
111 Here you define the elements of the image that don't change throughout
112 the video or manipulations.
113 """
114 im = np.zeros((980, 1855, 3), dtype=np.uint8)
115 if self.video_incrustation is not None:
116 self.video_incrustation.build(im)
117 self.minimap.build(im)
118 self.timeline.build(im)
119 im = self.draw_background(im)
120 return im
122 def draw_background(self, im):
123 return im
125 def timeline_color(self, frame):
126 """
127 Here you define the color of the timeline with repect to the frame.
128 """
129 return (0, 0, 0)
131 def jump_event(self, x, y, *kwargs):
132 frame = self.timeline.get_frame(x, y)
133 self.jump(frame)
135 def click_event(self, event, x, y, flags, param):
136 """
137 Part of the core engine that manages the buttons.
139 /!\\ Should not be overridden without knowing what you do.
140 """
141 if event == cv2.EVENT_LBUTTONUP:
142 if hasattr(self, "buttons"):
143 for button in self.buttons:
144 x1, y1, x2, y2 = button.hitbox
145 if x1 < x < x2 and y1 < y < y2:
146 button.callback(x, y)
147 self._refresh()
149 def _refresh(self):
150 """
151 Here you define the appearance of the image to be displayed with
152 respect to structural elements such as the frame index.
154 It is called each time the user is interacting with the image
155 (clicks, keys, previous, next, ...) to allow updating it with new
156 information.
157 """
158 self.big_image = self.background.copy()
159 self.refresh()
161 def refresh(self):
162 put_text(
163 img=self.big_image,
164 text=f"Frame {self.frame}",
165 org=(20, 20),
166 align_x="left",
167 align_y="top",
168 )
169 self.timeline.refresh(self.big_image, self.frame)
170 if self.video_incrustation is not None:
171 self.video_incrustation.refresh(self.big_image, self.image.copy())
172 self.minimap.refresh(self.big_image, [])
174 def start(self):
175 """
176 Part of the core engine that manages the display of the image and the
177 keys.
179 /!\\ Should not be overridden without knowing what you do.
180 """
181 while not self.to_exit:
182 cv2.imshow("image", self.image_to_disp)
183 key = cv2.waitKey(1) & 0xFF
184 for k, fun in self.keydict.items():
185 if key == ord(k):
186 fun()
188 def create_video(self, video_path="video.mp4"):
189 if os.path.exists(video_path):
190 print("video_path already exists, overwite (y/n)?")
191 answer = input()
192 if answer.lower() != "y":
193 return
194 video = imageio.get_writer(video_path, "ffmpeg", fps=10, quality=5.5)
195 print("Creating video...")
196 for frame in range(self.min_frame, self.max_frame):
197 sys.stdout.write(f"\r{frame - self.min_frame}/{self.max_frame - self.min_frame - 1}")
198 sys.stdout.flush()
199 self.seek(frame)
200 self._refresh()
201 video.append_data(cv2.cvtColor(self.big_image, cv2.COLOR_BGR2RGB))
202 sys.stdout.write("\n")
203 sys.stdout.flush()
204 print("Done")
205 video.close()
208if __name__ == "__main__":
209 video_path = sys.argv[1]
210 reader = Reader(video_path, fps=10)
211 reader.start()