util.py 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692
  1. import collections
  2. import copy
  3. import datetime
  4. import hashlib
  5. import json
  6. import logging
  7. import math
  8. import signal
  9. import subprocess as sp
  10. import threading
  11. import time
  12. import traceback
  13. from abc import ABC, abstractmethod
  14. from multiprocessing import shared_memory
  15. from typing import AnyStr
  16. import cv2
  17. import matplotlib.pyplot as plt
  18. import numpy as np
  19. import os
  20. import psutil
  21. logger = logging.getLogger(__name__)
  22. def deep_merge(dct1: dict, dct2: dict, override=False, merge_lists=False) -> dict:
  23. """
  24. :param dct1: First dict to merge
  25. :param dct2: Second dict to merge
  26. :param override: if same key exists in both dictionaries, should override? otherwise ignore. (default=True)
  27. :return: The merge dictionary
  28. """
  29. merged = copy.deepcopy(dct1)
  30. for k, v2 in dct2.items():
  31. if k in merged:
  32. v1 = merged[k]
  33. if isinstance(v1, dict) and isinstance(v2, collections.Mapping):
  34. merged[k] = deep_merge(v1, v2, override)
  35. elif isinstance(v1, list) and isinstance(v2, list):
  36. if merge_lists:
  37. merged[k] = v1 + v2
  38. else:
  39. if override:
  40. merged[k] = copy.deepcopy(v2)
  41. else:
  42. merged[k] = copy.deepcopy(v2)
  43. return merged
  44. def draw_timestamp(
  45. frame,
  46. timestamp,
  47. timestamp_format,
  48. font_effect=None,
  49. font_thickness=2,
  50. font_color=(255, 255, 255),
  51. position="tl",
  52. ):
  53. time_to_show = datetime.datetime.fromtimestamp(timestamp).strftime(timestamp_format)
  54. # calculate a dynamic font size
  55. size = cv2.getTextSize(
  56. time_to_show,
  57. cv2.FONT_HERSHEY_SIMPLEX,
  58. fontScale=1.0,
  59. thickness=font_thickness,
  60. )
  61. text_width = size[0][0]
  62. desired_size = max(150, 0.33 * frame.shape[1])
  63. font_scale = desired_size / text_width
  64. # calculate the actual size with the dynamic scale
  65. size = cv2.getTextSize(
  66. time_to_show,
  67. cv2.FONT_HERSHEY_SIMPLEX,
  68. fontScale=font_scale,
  69. thickness=font_thickness,
  70. )
  71. image_width = frame.shape[1]
  72. image_height = frame.shape[0]
  73. text_width = size[0][0]
  74. text_height = size[0][1]
  75. line_height = text_height + size[1]
  76. if position == "tl":
  77. text_offset_x = 0
  78. text_offset_y = 0 if 0 < line_height else 0 - (line_height + 8)
  79. elif position == "tr":
  80. text_offset_x = image_width - text_width
  81. text_offset_y = 0 if 0 < line_height else 0 - (line_height + 8)
  82. elif position == "bl":
  83. text_offset_x = 0
  84. text_offset_y = image_height - (line_height + 8)
  85. elif position == "br":
  86. text_offset_x = image_width - text_width
  87. text_offset_y = image_height - (line_height + 8)
  88. if font_effect == "solid":
  89. # make the coords of the box with a small padding of two pixels
  90. timestamp_box_coords = np.array(
  91. [
  92. [text_offset_x, text_offset_y],
  93. [text_offset_x + text_width, text_offset_y],
  94. [text_offset_x + text_width, text_offset_y + line_height + 8],
  95. [text_offset_x, text_offset_y + line_height + 8],
  96. ]
  97. )
  98. cv2.fillPoly(
  99. frame,
  100. [timestamp_box_coords],
  101. # inverse color of text for background for max. contrast
  102. (255 - font_color[0], 255 - font_color[1], 255 - font_color[2]),
  103. )
  104. elif font_effect == "shadow":
  105. cv2.putText(
  106. frame,
  107. time_to_show,
  108. (text_offset_x + 3, text_offset_y + line_height),
  109. cv2.FONT_HERSHEY_SIMPLEX,
  110. fontScale=font_scale,
  111. color=(255 - font_color[0], 255 - font_color[1], 255 - font_color[2]),
  112. thickness=font_thickness,
  113. )
  114. cv2.putText(
  115. frame,
  116. time_to_show,
  117. (text_offset_x, text_offset_y + line_height - 3),
  118. cv2.FONT_HERSHEY_SIMPLEX,
  119. fontScale=font_scale,
  120. color=font_color,
  121. thickness=font_thickness,
  122. )
  123. def draw_box_with_label(
  124. frame,
  125. x_min,
  126. y_min,
  127. x_max,
  128. y_max,
  129. label,
  130. info,
  131. thickness=2,
  132. color=None,
  133. position="ul",
  134. ):
  135. if color is None:
  136. color = (0, 0, 255)
  137. display_text = "{}: {}".format(label, info)
  138. cv2.rectangle(frame, (x_min, y_min), (x_max, y_max), color, thickness)
  139. font_scale = 0.5
  140. font = cv2.FONT_HERSHEY_SIMPLEX
  141. # get the width and height of the text box
  142. size = cv2.getTextSize(display_text, font, fontScale=font_scale, thickness=2)
  143. text_width = size[0][0]
  144. text_height = size[0][1]
  145. line_height = text_height + size[1]
  146. # set the text start position
  147. if position == "ul":
  148. text_offset_x = x_min
  149. text_offset_y = 0 if y_min < line_height else y_min - (line_height + 8)
  150. elif position == "ur":
  151. text_offset_x = x_max - (text_width + 8)
  152. text_offset_y = 0 if y_min < line_height else y_min - (line_height + 8)
  153. elif position == "bl":
  154. text_offset_x = x_min
  155. text_offset_y = y_max
  156. elif position == "br":
  157. text_offset_x = x_max - (text_width + 8)
  158. text_offset_y = y_max
  159. # make the coords of the box with a small padding of two pixels
  160. textbox_coords = (
  161. (text_offset_x, text_offset_y),
  162. (text_offset_x + text_width + 2, text_offset_y + line_height),
  163. )
  164. cv2.rectangle(frame, textbox_coords[0], textbox_coords[1], color, cv2.FILLED)
  165. cv2.putText(
  166. frame,
  167. display_text,
  168. (text_offset_x, text_offset_y + line_height - 3),
  169. font,
  170. fontScale=font_scale,
  171. color=(0, 0, 0),
  172. thickness=2,
  173. )
  174. def calculate_region(frame_shape, xmin, ymin, xmax, ymax, model_size, multiplier=2):
  175. # size is the longest edge and divisible by 4
  176. size = int((max(xmax - xmin, ymax - ymin) * multiplier) // 4 * 4)
  177. # dont go any smaller than the model_size
  178. if size < model_size:
  179. size = model_size
  180. # x_offset is midpoint of bounding box minus half the size
  181. x_offset = int((xmax - xmin) / 2.0 + xmin - size / 2.0)
  182. # if outside the image
  183. if x_offset < 0:
  184. x_offset = 0
  185. elif x_offset > (frame_shape[1] - size):
  186. x_offset = max(0, (frame_shape[1] - size))
  187. # y_offset is midpoint of bounding box minus half the size
  188. y_offset = int((ymax - ymin) / 2.0 + ymin - size / 2.0)
  189. # # if outside the image
  190. if y_offset < 0:
  191. y_offset = 0
  192. elif y_offset > (frame_shape[0] - size):
  193. y_offset = max(0, (frame_shape[0] - size))
  194. return (x_offset, y_offset, x_offset + size, y_offset + size)
  195. def get_yuv_crop(frame_shape, crop):
  196. # crop should be (x1,y1,x2,y2)
  197. frame_height = frame_shape[0] // 3 * 2
  198. frame_width = frame_shape[1]
  199. # compute the width/height of the uv channels
  200. uv_width = frame_width // 2 # width of the uv channels
  201. uv_height = frame_height // 4 # height of the uv channels
  202. # compute the offset for upper left corner of the uv channels
  203. uv_x_offset = crop[0] // 2 # x offset of the uv channels
  204. uv_y_offset = crop[1] // 4 # y offset of the uv channels
  205. # compute the width/height of the uv crops
  206. uv_crop_width = (crop[2] - crop[0]) // 2 # width of the cropped uv channels
  207. uv_crop_height = (crop[3] - crop[1]) // 4 # height of the cropped uv channels
  208. # ensure crop dimensions are multiples of 2 and 4
  209. y = (crop[0], crop[1], crop[0] + uv_crop_width * 2, crop[1] + uv_crop_height * 4)
  210. u1 = (
  211. 0 + uv_x_offset,
  212. frame_height + uv_y_offset,
  213. 0 + uv_x_offset + uv_crop_width,
  214. frame_height + uv_y_offset + uv_crop_height,
  215. )
  216. u2 = (
  217. uv_width + uv_x_offset,
  218. frame_height + uv_y_offset,
  219. uv_width + uv_x_offset + uv_crop_width,
  220. frame_height + uv_y_offset + uv_crop_height,
  221. )
  222. v1 = (
  223. 0 + uv_x_offset,
  224. frame_height + uv_height + uv_y_offset,
  225. 0 + uv_x_offset + uv_crop_width,
  226. frame_height + uv_height + uv_y_offset + uv_crop_height,
  227. )
  228. v2 = (
  229. uv_width + uv_x_offset,
  230. frame_height + uv_height + uv_y_offset,
  231. uv_width + uv_x_offset + uv_crop_width,
  232. frame_height + uv_height + uv_y_offset + uv_crop_height,
  233. )
  234. return y, u1, u2, v1, v2
  235. def yuv_crop_and_resize(frame, region, height=None):
  236. # Crops and resizes a YUV frame while maintaining aspect ratio
  237. # https://stackoverflow.com/a/57022634
  238. height = frame.shape[0] // 3 * 2
  239. width = frame.shape[1]
  240. # get the crop box if the region extends beyond the frame
  241. crop_x1 = max(0, region[0])
  242. crop_y1 = max(0, region[1])
  243. # ensure these are a multiple of 4
  244. crop_x2 = min(width, region[2])
  245. crop_y2 = min(height, region[3])
  246. crop_box = (crop_x1, crop_y1, crop_x2, crop_y2)
  247. y, u1, u2, v1, v2 = get_yuv_crop(frame.shape, crop_box)
  248. # if the region starts outside the frame, indent the start point in the cropped frame
  249. y_channel_x_offset = abs(min(0, region[0]))
  250. y_channel_y_offset = abs(min(0, region[1]))
  251. uv_channel_x_offset = y_channel_x_offset // 2
  252. uv_channel_y_offset = y_channel_y_offset // 4
  253. # create the yuv region frame
  254. # make sure the size is a multiple of 4
  255. # TODO: this should be based on the size after resize now
  256. size = (region[3] - region[1]) // 4 * 4
  257. yuv_cropped_frame = np.zeros((size + size // 2, size), np.uint8)
  258. # fill in black
  259. yuv_cropped_frame[:] = 128
  260. yuv_cropped_frame[0:size, 0:size] = 16
  261. # copy the y channel
  262. yuv_cropped_frame[
  263. y_channel_y_offset : y_channel_y_offset + y[3] - y[1],
  264. y_channel_x_offset : y_channel_x_offset + y[2] - y[0],
  265. ] = frame[y[1] : y[3], y[0] : y[2]]
  266. uv_crop_width = u1[2] - u1[0]
  267. uv_crop_height = u1[3] - u1[1]
  268. # copy u1
  269. yuv_cropped_frame[
  270. size + uv_channel_y_offset : size + uv_channel_y_offset + uv_crop_height,
  271. 0 + uv_channel_x_offset : 0 + uv_channel_x_offset + uv_crop_width,
  272. ] = frame[u1[1] : u1[3], u1[0] : u1[2]]
  273. # copy u2
  274. yuv_cropped_frame[
  275. size + uv_channel_y_offset : size + uv_channel_y_offset + uv_crop_height,
  276. size // 2
  277. + uv_channel_x_offset : size // 2
  278. + uv_channel_x_offset
  279. + uv_crop_width,
  280. ] = frame[u2[1] : u2[3], u2[0] : u2[2]]
  281. # copy v1
  282. yuv_cropped_frame[
  283. size
  284. + size // 4
  285. + uv_channel_y_offset : size
  286. + size // 4
  287. + uv_channel_y_offset
  288. + uv_crop_height,
  289. 0 + uv_channel_x_offset : 0 + uv_channel_x_offset + uv_crop_width,
  290. ] = frame[v1[1] : v1[3], v1[0] : v1[2]]
  291. # copy v2
  292. yuv_cropped_frame[
  293. size
  294. + size // 4
  295. + uv_channel_y_offset : size
  296. + size // 4
  297. + uv_channel_y_offset
  298. + uv_crop_height,
  299. size // 2
  300. + uv_channel_x_offset : size // 2
  301. + uv_channel_x_offset
  302. + uv_crop_width,
  303. ] = frame[v2[1] : v2[3], v2[0] : v2[2]]
  304. return yuv_cropped_frame
  305. def copy_yuv_to_position(
  306. destination_frame,
  307. destination_offset,
  308. destination_shape,
  309. source_frame=None,
  310. source_channel_dim=None,
  311. ):
  312. # get the coordinates of the channels for this position in the layout
  313. y, u1, u2, v1, v2 = get_yuv_crop(
  314. destination_frame.shape,
  315. (
  316. destination_offset[1],
  317. destination_offset[0],
  318. destination_offset[1] + destination_shape[1],
  319. destination_offset[0] + destination_shape[0],
  320. ),
  321. )
  322. # clear y
  323. destination_frame[
  324. y[1] : y[3],
  325. y[0] : y[2],
  326. ] = 16
  327. # clear u1
  328. destination_frame[u1[1] : u1[3], u1[0] : u1[2]] = 128
  329. # clear u2
  330. destination_frame[u2[1] : u2[3], u2[0] : u2[2]] = 128
  331. # clear v1
  332. destination_frame[v1[1] : v1[3], v1[0] : v1[2]] = 128
  333. # clear v2
  334. destination_frame[v2[1] : v2[3], v2[0] : v2[2]] = 128
  335. if not source_frame is None:
  336. # calculate the resized frame, maintaining the aspect ratio
  337. source_aspect_ratio = source_frame.shape[1] / (source_frame.shape[0] // 3 * 2)
  338. dest_aspect_ratio = destination_shape[1] / destination_shape[0]
  339. if source_aspect_ratio <= dest_aspect_ratio:
  340. y_resize_height = int(destination_shape[0] // 4 * 4)
  341. y_resize_width = int((y_resize_height * source_aspect_ratio) // 4 * 4)
  342. else:
  343. y_resize_width = int(destination_shape[1] // 4 * 4)
  344. y_resize_height = int((y_resize_width / source_aspect_ratio) // 4 * 4)
  345. uv_resize_width = int(y_resize_width // 2)
  346. uv_resize_height = int(y_resize_height // 4)
  347. y_y_offset = int((destination_shape[0] - y_resize_height) / 4 // 4 * 4)
  348. y_x_offset = int((destination_shape[1] - y_resize_width) / 2 // 4 * 4)
  349. uv_y_offset = y_y_offset // 4
  350. uv_x_offset = y_x_offset // 2
  351. interpolation = cv2.INTER_LINEAR
  352. # resize/copy y channel
  353. destination_frame[
  354. y[1] + y_y_offset : y[1] + y_y_offset + y_resize_height,
  355. y[0] + y_x_offset : y[0] + y_x_offset + y_resize_width,
  356. ] = cv2.resize(
  357. source_frame[
  358. source_channel_dim["y"][1] : source_channel_dim["y"][3],
  359. source_channel_dim["y"][0] : source_channel_dim["y"][2],
  360. ],
  361. dsize=(y_resize_width, y_resize_height),
  362. interpolation=interpolation,
  363. )
  364. # resize/copy u1
  365. destination_frame[
  366. u1[1] + uv_y_offset : u1[1] + uv_y_offset + uv_resize_height,
  367. u1[0] + uv_x_offset : u1[0] + uv_x_offset + uv_resize_width,
  368. ] = cv2.resize(
  369. source_frame[
  370. source_channel_dim["u1"][1] : source_channel_dim["u1"][3],
  371. source_channel_dim["u1"][0] : source_channel_dim["u1"][2],
  372. ],
  373. dsize=(uv_resize_width, uv_resize_height),
  374. interpolation=interpolation,
  375. )
  376. # resize/copy u2
  377. destination_frame[
  378. u2[1] + uv_y_offset : u2[1] + uv_y_offset + uv_resize_height,
  379. u2[0] + uv_x_offset : u2[0] + uv_x_offset + uv_resize_width,
  380. ] = cv2.resize(
  381. source_frame[
  382. source_channel_dim["u2"][1] : source_channel_dim["u2"][3],
  383. source_channel_dim["u2"][0] : source_channel_dim["u2"][2],
  384. ],
  385. dsize=(uv_resize_width, uv_resize_height),
  386. interpolation=interpolation,
  387. )
  388. # resize/copy v1
  389. destination_frame[
  390. v1[1] + uv_y_offset : v1[1] + uv_y_offset + uv_resize_height,
  391. v1[0] + uv_x_offset : v1[0] + uv_x_offset + uv_resize_width,
  392. ] = cv2.resize(
  393. source_frame[
  394. source_channel_dim["v1"][1] : source_channel_dim["v1"][3],
  395. source_channel_dim["v1"][0] : source_channel_dim["v1"][2],
  396. ],
  397. dsize=(uv_resize_width, uv_resize_height),
  398. interpolation=interpolation,
  399. )
  400. # resize/copy v2
  401. destination_frame[
  402. v2[1] + uv_y_offset : v2[1] + uv_y_offset + uv_resize_height,
  403. v2[0] + uv_x_offset : v2[0] + uv_x_offset + uv_resize_width,
  404. ] = cv2.resize(
  405. source_frame[
  406. source_channel_dim["v2"][1] : source_channel_dim["v2"][3],
  407. source_channel_dim["v2"][0] : source_channel_dim["v2"][2],
  408. ],
  409. dsize=(uv_resize_width, uv_resize_height),
  410. interpolation=interpolation,
  411. )
  412. def yuv_region_2_rgb(frame, region):
  413. try:
  414. # TODO: does this copy the numpy array?
  415. yuv_cropped_frame = yuv_crop_and_resize(frame, region)
  416. return cv2.cvtColor(yuv_cropped_frame, cv2.COLOR_YUV2RGB_I420)
  417. except:
  418. print(f"frame.shape: {frame.shape}")
  419. print(f"region: {region}")
  420. raise
  421. def intersection(box_a, box_b):
  422. return (
  423. max(box_a[0], box_b[0]),
  424. max(box_a[1], box_b[1]),
  425. min(box_a[2], box_b[2]),
  426. min(box_a[3], box_b[3]),
  427. )
  428. def area(box):
  429. return (box[2] - box[0] + 1) * (box[3] - box[1] + 1)
  430. def intersection_over_union(box_a, box_b):
  431. # determine the (x, y)-coordinates of the intersection rectangle
  432. intersect = intersection(box_a, box_b)
  433. # compute the area of intersection rectangle
  434. inter_area = max(0, intersect[2] - intersect[0] + 1) * max(
  435. 0, intersect[3] - intersect[1] + 1
  436. )
  437. if inter_area == 0:
  438. return 0.0
  439. # compute the area of both the prediction and ground-truth
  440. # rectangles
  441. box_a_area = (box_a[2] - box_a[0] + 1) * (box_a[3] - box_a[1] + 1)
  442. box_b_area = (box_b[2] - box_b[0] + 1) * (box_b[3] - box_b[1] + 1)
  443. # compute the intersection over union by taking the intersection
  444. # area and dividing it by the sum of prediction + ground-truth
  445. # areas - the interesection area
  446. iou = inter_area / float(box_a_area + box_b_area - inter_area)
  447. # return the intersection over union value
  448. return iou
  449. def clipped(obj, frame_shape):
  450. # if the object is within 5 pixels of the region border, and the region is not on the edge
  451. # consider the object to be clipped
  452. box = obj[2]
  453. region = obj[4]
  454. if (
  455. (region[0] > 5 and box[0] - region[0] <= 5)
  456. or (region[1] > 5 and box[1] - region[1] <= 5)
  457. or (frame_shape[1] - region[2] > 5 and region[2] - box[2] <= 5)
  458. or (frame_shape[0] - region[3] > 5 and region[3] - box[3] <= 5)
  459. ):
  460. return True
  461. else:
  462. return False
  463. def restart_frigate():
  464. proc = psutil.Process(1)
  465. # if this is running via s6, sigterm pid 1
  466. if proc.name() == "s6-svscan":
  467. proc.terminate()
  468. # otherwise, just try and exit frigate
  469. else:
  470. os.kill(os.getpid(), signal.SIGTERM)
  471. class EventsPerSecond:
  472. def __init__(self, max_events=1000):
  473. self._start = None
  474. self._max_events = max_events
  475. self._timestamps = []
  476. def start(self):
  477. self._start = datetime.datetime.now().timestamp()
  478. def update(self):
  479. if self._start is None:
  480. self.start()
  481. self._timestamps.append(datetime.datetime.now().timestamp())
  482. # truncate the list when it goes 100 over the max_size
  483. if len(self._timestamps) > self._max_events + 100:
  484. self._timestamps = self._timestamps[(1 - self._max_events) :]
  485. def eps(self, last_n_seconds=10):
  486. if self._start is None:
  487. self.start()
  488. # compute the (approximate) events in the last n seconds
  489. now = datetime.datetime.now().timestamp()
  490. seconds = min(now - self._start, last_n_seconds)
  491. # avoid divide by zero
  492. if seconds == 0:
  493. seconds = 1
  494. return (
  495. len([t for t in self._timestamps if t > (now - last_n_seconds)]) / seconds
  496. )
  497. def print_stack(sig, frame):
  498. traceback.print_stack(frame)
  499. def listen():
  500. signal.signal(signal.SIGUSR1, print_stack)
  501. def create_mask(frame_shape, mask):
  502. mask_img = np.zeros(frame_shape, np.uint8)
  503. mask_img[:] = 255
  504. if isinstance(mask, list):
  505. for m in mask:
  506. add_mask(m, mask_img)
  507. elif isinstance(mask, str):
  508. add_mask(mask, mask_img)
  509. return mask_img
  510. def add_mask(mask, mask_img):
  511. points = mask.split(",")
  512. contour = np.array(
  513. [[int(points[i]), int(points[i + 1])] for i in range(0, len(points), 2)]
  514. )
  515. cv2.fillPoly(mask_img, pts=[contour], color=(0))
  516. def load_labels(path, encoding="utf-8"):
  517. """Loads labels from file (with or without index numbers).
  518. Args:
  519. path: path to label file.
  520. encoding: label file encoding.
  521. Returns:
  522. Dictionary mapping indices to labels.
  523. """
  524. with open(path, "r", encoding=encoding) as f:
  525. lines = f.readlines()
  526. if not lines:
  527. return {}
  528. if lines[0].split(" ", maxsplit=1)[0].isdigit():
  529. pairs = [line.split(" ", maxsplit=1) for line in lines]
  530. return {int(index): label.strip() for index, label in pairs}
  531. else:
  532. return {index: line.strip() for index, line in enumerate(lines)}
  533. class FrameManager(ABC):
  534. @abstractmethod
  535. def create(self, name, size) -> AnyStr:
  536. pass
  537. @abstractmethod
  538. def get(self, name, timeout_ms=0):
  539. pass
  540. @abstractmethod
  541. def close(self, name):
  542. pass
  543. @abstractmethod
  544. def delete(self, name):
  545. pass
  546. class DictFrameManager(FrameManager):
  547. def __init__(self):
  548. self.frames = {}
  549. def create(self, name, size) -> AnyStr:
  550. mem = bytearray(size)
  551. self.frames[name] = mem
  552. return mem
  553. def get(self, name, shape):
  554. mem = self.frames[name]
  555. return np.ndarray(shape, dtype=np.uint8, buffer=mem)
  556. def close(self, name):
  557. pass
  558. def delete(self, name):
  559. del self.frames[name]
  560. class SharedMemoryFrameManager(FrameManager):
  561. def __init__(self):
  562. self.shm_store = {}
  563. def create(self, name, size) -> AnyStr:
  564. shm = shared_memory.SharedMemory(name=name, create=True, size=size)
  565. self.shm_store[name] = shm
  566. return shm.buf
  567. def get(self, name, shape):
  568. if name in self.shm_store:
  569. shm = self.shm_store[name]
  570. else:
  571. shm = shared_memory.SharedMemory(name=name)
  572. self.shm_store[name] = shm
  573. return np.ndarray(shape, dtype=np.uint8, buffer=shm.buf)
  574. def close(self, name):
  575. if name in self.shm_store:
  576. self.shm_store[name].close()
  577. del self.shm_store[name]
  578. def delete(self, name):
  579. if name in self.shm_store:
  580. self.shm_store[name].close()
  581. self.shm_store[name].unlink()
  582. del self.shm_store[name]