Coverage for skcvideo/reader.py: 0%

105 statements  

« prev     ^ index     » next       coverage.py v7.6.1, created at 2024-10-02 14:10 +0200

1import os 

2import sys 

3 

4import cv2 

5import imageio 

6import numpy as np 

7 

8from skcvideo.minimap import Minimap 

9from skcvideo.timeline import VlcTimeline 

10from skcvideo.utils import put_text 

11from skcvideo.video_capture import StoredImagesVideoCapture 

12 

13 

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. 

19 

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 """ 

26 

27 def __init__(self, hitbox, callback, data=None): 

28 self.hitbox = hitbox 

29 self.data = data 

30 self.given_callback = callback 

31 

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) 

37 

38 

39class Reader(StoredImagesVideoCapture): 

40 """ 

41 A video displayer that allows interaction with the image by using buttons 

42 or keyboard. 

43 

44 The main advantage of this displayer is that it allows to read the video 

45 backward while keeping relatively fast. 

46 

47 The best way to use this displayer is to make your own class inheriting 

48 from this one and overridding its methods. 

49 """ 

50 

51 def __init__(self, video_path, timeline=None, **kwargs): 

52 super().__init__(video_path, colormap="bgr", **kwargs) 

53 

54 self.to_exit = False 

55 

56 # The key/function mapping 

57 self.keydict = { 

58 "k": self.next, 

59 "j": self.previous, 

60 "q": self.exit, 

61 } 

62 

63 # The clickable buttons 

64 self.buttons = [] 

65 

66 if not hasattr(self, "video_incrustation"): 

67 self.video_incrustation = None 

68 

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 ) 

75 

76 if not hasattr(self, "timeline"): 

77 self.timeline = VlcTimeline( 

78 box=[79, 1771], 

79 parent=self, 

80 ) 

81 

82 self.buttons.append(Button(self.timeline.hitbox, self.jump_event)) 

83 

84 self.background = self.create_background() 

85 

86 self.init(video_path, **kwargs) 

87 

88 self._refresh() 

89 

90 cv2.namedWindow("image", cv2.WINDOW_NORMAL) 

91 cv2.resizeWindow("image", 1280, 720) 

92 cv2.setMouseCallback("image", self.click_event) 

93 

94 def init(self, video_path, **kwargs): 

95 pass 

96 

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 

105 

106 def exit(self): 

107 self.to_exit = True 

108 

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 

121 

122 def draw_background(self, im): 

123 return im 

124 

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) 

130 

131 def jump_event(self, x, y, *kwargs): 

132 frame = self.timeline.get_frame(x, y) 

133 self.jump(frame) 

134 

135 def click_event(self, event, x, y, flags, param): 

136 """ 

137 Part of the core engine that manages the buttons. 

138 

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() 

148 

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. 

153 

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() 

160 

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, []) 

173 

174 def start(self): 

175 """ 

176 Part of the core engine that manages the display of the image and the 

177 keys. 

178 

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() 

187 

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() 

206 

207 

208if __name__ == "__main__": 

209 video_path = sys.argv[1] 

210 reader = Reader(video_path, fps=10) 

211 reader.start()