Coverage for skcvideo/timeline.py: 0%

118 statements  

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

1import cv2 

2import numpy as np 

3 

4from skcvideo.colors import RED, WHITE 

5from skcvideo.core import Button 

6from skcvideo.utils import put_text 

7 

8 

9class Timeline: 

10 def __init__(self, box=None, timeline_width=20, margin=5, pixel_per_frame=1): 

11 if box is None: 

12 box = [955, 110, 1035, 1910] 

13 self.name = "Timeline" 

14 self.box = box 

15 self.pixel_per_frame = pixel_per_frame 

16 

17 self.timeline_width = timeline_width 

18 self.margin = margin 

19 self.gap = self.timeline_width + 2 * self.margin 

20 

21 self.timeline_length = self.box[3] - self.box[1] 

22 

23 self.hitbox = (self.box[1], self.box[0], self.box[3], self.box[2]) 

24 self.buttons = [Button(self.hitbox, self.jump_event)] 

25 

26 @property 

27 def min_frame(self): 

28 return getattr(self.parent, "min_frame", 0) 

29 

30 @property 

31 def max_frame(self): 

32 return getattr(self.parent, "max_frame", 9000) 

33 

34 @property 

35 def n_timelines(self): 

36 return ((self.max_frame - self.min_frame) * self.pixel_per_frame) // self.timeline_length + 1 

37 

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

39 frame = self.get_frame(x, y) 

40 self.parent.jump(frame) 

41 

42 def timeline_color(self, frame): 

43 """ 

44 Here you define the color of the timeline with repect to the frame. 

45 """ 

46 return (0, 0, 0) 

47 

48 def build(self, image): 

49 """ 

50 Draws the timeline's background composed of several timeline lines 

51 with box and graduations. 

52 """ 

53 # Puts time labels label 

54 put_text( 

55 img=image, 

56 text="min", 

57 org=(self.box[1] - 90, self.box[0] - self.margin - self.timeline_width // 2), 

58 fontScale=0.6, 

59 align_x="left", 

60 ) 

61 

62 # Draws graduations labels 

63 for frame in range(0, self.box[3] - self.box[1], 100): 

64 if (frame + 1) % 600 == 0: 

65 put_text( 

66 img=image, 

67 text=f"{(frame + 1) // 600}min", 

68 org=(self.box[1] + frame, self.box[0] - self.margin - self.timeline_width // 2 - 2), 

69 fontScale=0.6, 

70 align_x="center", 

71 ) 

72 else: 

73 put_text( 

74 img=image, 

75 text=f"{(frame + 1) % 600 // 10}s", 

76 org=(self.box[1] + frame, self.box[0] - self.margin - self.timeline_width // 2 + 4), 

77 fontScale=0.4, 

78 align_x="center", 

79 ) 

80 

81 # Draws each timeline's line 

82 for i in range(self.n_timelines): 

83 self.draw_timeline_box(image, i) 

84 

85 # Draws graduations 

86 for frame in range(self.max_frame - self.min_frame): 

87 x = frame % self.timeline_length 

88 y = frame // self.timeline_length 

89 

90 # A small mark every 5 seconds 

91 if ((frame + 1) % 50) == 0: 

92 cv2.line( 

93 image, 

94 (self.box[1] + x, self.box[0] + y * self.gap + self.margin - 1), 

95 (self.box[1] + x, self.box[0] + (y + 1) * self.gap - self.margin + 1), 

96 color=WHITE, 

97 thickness=1, 

98 ) 

99 

100 # A big mark every minute 

101 if ((frame + 1) % 600) == 0: 

102 cv2.line( 

103 image, 

104 (self.box[1] + x, self.box[0] + y * self.gap + self.margin - 3), 

105 (self.box[1] + x, self.box[0] + (y + 1) * self.gap - self.margin + 3), 

106 color=WHITE, 

107 thickness=2, 

108 ) 

109 

110 self.draw_timeline_data(image) 

111 

112 def refresh(self, image, frame): 

113 self.draw_timer(image, frame) 

114 

115 def draw_timeline_box(self, image, i): 

116 """ 

117 Draws one line of the timeline's background, which consists in a 

118 simple white box. 

119 """ 

120 # Manage the offset 

121 y_min = self.box[0] + self.margin + i * self.gap 

122 y_max = y_min + self.timeline_width 

123 

124 # The last box may not go up to the end. 

125 if i == self.n_timelines - 1: 

126 frame_max = (self.max_frame - self.min_frame) * self.pixel_per_frame % self.timeline_length 

127 timeline_max = self.box[1] + frame_max 

128 else: 

129 timeline_max = self.box[3] 

130 

131 # Draws the box 

132 cv2.rectangle( 

133 image, 

134 (self.box[1], y_min), 

135 (timeline_max, y_max), 

136 color=WHITE, 

137 thickness=1, 

138 ) 

139 

140 # Adds a time label before the line 

141 put_text( 

142 img=image, 

143 text=f"{3 * i}-{3 * (i + 1)}", 

144 org=(self.box[1] - 90, (y_min + y_max) // 2), 

145 fontScale=0.6, 

146 align_x="left", 

147 ) 

148 

149 def draw_timeline_data(self, im): 

150 """ 

151 Draws information on the timeline. Useful to have a global view of 

152 your data or to have a reference for jumping in the video. 

153 """ 

154 for frame in range(self.min_frame, self.max_frame): 

155 self.draw_one_timeline_data(im, frame) 

156 

157 def draw_one_timeline_data(self, im, frame): 

158 """ 

159 Colors the given frame on the timeline according to the timeline_color 

160 function 

161 """ 

162 color = self.timeline_color(frame) 

163 if color != (0, 0, 0): 

164 timer = frame - self.min_frame 

165 timer_x = timer % self.timeline_length 

166 timer_y = timer // self.timeline_length 

167 

168 x1 = self.box[1] + timer_x * self.pixel_per_frame 

169 x2 = x1 + self.pixel_per_frame 

170 y1 = timer_y * self.gap + self.box[0] + self.margin + 1 

171 y2 = y1 + self.timeline_width - 2 

172 im[y1:y2, x1:x2] = color 

173 

174 def draw_timer(self, image, frame): 

175 """ 

176 Draws a timer on the timeline on the given frame. To be used in the 

177 refresh method of the parent Reader class. 

178 """ 

179 timer = (frame - self.min_frame) * self.pixel_per_frame 

180 timer_x = timer % self.timeline_length 

181 timer_y = timer // self.timeline_length 

182 cv2.line( 

183 image, 

184 (self.box[1] + timer_x, timer_y * self.gap + self.box[0]), 

185 (self.box[1] + timer_x, (timer_y + 1) * self.gap + self.box[0]), 

186 color=WHITE, 

187 thickness=3, 

188 ) 

189 cv2.line( 

190 image, 

191 (self.box[1] + timer_x, timer_y * self.gap + self.box[0]), 

192 (self.box[1] + timer_x, (timer_y + 1) * self.gap + self.box[0]), 

193 color=RED, 

194 thickness=2, 

195 ) 

196 

197 def get_frame(self, x, y): 

198 """ 

199 Returns the frame corresponding to a given pixel of the timeline. 

200 It is used to be able to click one the timeline to jump to a 

201 particular frame. 

202 """ 

203 x = x - self.box[1] 

204 y = (y - self.box[0]) // self.gap 

205 frame = (x + y * self.timeline_length) // self.pixel_per_frame + self.min_frame 

206 return frame 

207 

208 

209GRAY_BAR = np.array( 

210 [ 

211 [189, 189, 190], 

212 [193, 193, 194], 

213 [196, 197, 198], 

214 [200, 201, 202], 

215 [204, 205, 206], 

216 [208, 208, 209], 

217 [212, 212, 213], 

218 [215, 216, 217], 

219 [219, 220, 221], 

220 [235, 235, 236], 

221 ], 

222) 

223 

224BLUE_BAR = np.array( 

225 [ 

226 [224, 137, 44], 

227 [218, 134, 43], 

228 [213, 130, 42], 

229 [207, 127, 41], 

230 [204, 125, 40], 

231 [204, 125, 40], 

232 [204, 125, 40], 

233 [204, 125, 40], 

234 ], 

235) 

236 

237 

238class VlcTimeline: 

239 def __init__(self, box=None): 

240 """ 

241 Args: 

242 box: list [x1, y1, x2, y2] 

243 """ 

244 if box is None: 

245 box = [79, 966, 1771, 976] 

246 self.box = box 

247 self.buttons = [Button(self.box, self.jump_event)] 

248 self.timeline_length = float(self.box[2] - self.box[0]) 

249 

250 @property 

251 def min_frame(self): 

252 return getattr(self.parent, "min_frame", 0) 

253 

254 @property 

255 def max_frame(self): 

256 return getattr(self.parent, "max_frame", 9000) 

257 

258 @property 

259 def frames_length(self): 

260 return float(self.max_frame - self.min_frame) 

261 

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

263 frame = self.get_frame(x, y) 

264 self.parent.jump(frame) 

265 

266 def build(self, image): 

267 image[self.box[1] : self.box[1] + 18] = np.array([240, 241, 242])[np.newaxis, np.newaxis, :] 

268 image[self.box[1] + 4 : self.box[1] + 14, self.box[0] : self.box[2]] = GRAY_BAR[:, np.newaxis, :] 

269 

270 def refresh(self, image, frame): 

271 self.draw_timer(image, frame) 

272 

273 def draw_timer(self, image, frame): 

274 frame = float(frame - self.min_frame) 

275 i = int(np.round(frame / self.frames_length * self.timeline_length)) 

276 image[self.box[1] + 5 : self.box[1] + 13, self.box[0] : self.box[0] + i] = BLUE_BAR[:, np.newaxis, :] 

277 

278 def get_frame(self, x, y): 

279 x = float(x - self.box[0]) 

280 frame = int(np.round(x / self.timeline_length * self.frames_length)) + self.min_frame 

281 return frame