readers.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556
  1. # Copyright (c) 2024 PaddlePaddle Authors. All Rights Reserved.
  2. #
  3. # Licensed under the Apache License, Version 2.0 (the "License");
  4. # you may not use this file except in compliance with the License.
  5. # You may obtain a copy of the License at
  6. #
  7. # http://www.apache.org/licenses/LICENSE-2.0
  8. #
  9. # Unless required by applicable law or agreed to in writing, software
  10. # distributed under the License is distributed on an "AS IS" BASIS,
  11. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. # See the License for the specific language governing permissions and
  13. # limitations under the License.
  14. import enum
  15. import itertools
  16. import random
  17. import numpy as np
  18. import pandas as pd
  19. import yaml
  20. from PIL import Image, ImageOps
  21. from ....utils.deps import class_requires_deps, is_dep_available
  22. if is_dep_available("opencv-contrib-python"):
  23. import cv2
  24. if is_dep_available("pypdfium2"):
  25. import pypdfium2 as pdfium
  26. if is_dep_available("soundfile"):
  27. import soundfile
  28. __all__ = [
  29. "ReaderType",
  30. "ImageReader",
  31. "VideoReader",
  32. "CSVReader",
  33. "PDFReader",
  34. "YAMLReader",
  35. "AudioReader",
  36. ]
  37. class ReaderType(enum.Enum):
  38. """ReaderType"""
  39. IMAGE = 1
  40. GENERATIVE = 2
  41. POINT_CLOUD = 3
  42. JSON = 4
  43. TS = 5
  44. PDF = 6
  45. YAML = 8
  46. MARKDOWN = 9
  47. TXT = 10
  48. class _BaseReader(object):
  49. """_BaseReader"""
  50. def __init__(self, backend, **bk_args):
  51. super().__init__()
  52. if len(bk_args) == 0:
  53. bk_args = self.get_default_backend_args()
  54. self.bk_type = backend
  55. self.bk_args = bk_args
  56. self._backend = self.get_backend()
  57. def read(self, in_path):
  58. """read file from path"""
  59. raise NotImplementedError
  60. def get_backend(self, bk_args=None):
  61. """get the backend"""
  62. if bk_args is None:
  63. bk_args = self.bk_args
  64. return self._init_backend(self.bk_type, bk_args)
  65. def set_backend(self, backend, **bk_args):
  66. self.bk_type = backend
  67. self.bk_args = bk_args
  68. self._backend = self.get_backend()
  69. def _init_backend(self, bk_type, bk_args):
  70. """init backend"""
  71. raise NotImplementedError
  72. def get_type(self):
  73. """get type"""
  74. raise NotImplementedError
  75. def get_default_backend_args(self):
  76. """get default backend arguments"""
  77. return {}
  78. class PDFReader(_BaseReader):
  79. """PDFReader"""
  80. def __init__(self, backend="pypdfium2", **bk_args):
  81. super().__init__(backend, **bk_args)
  82. def read(self, in_path):
  83. yield from self._backend.read_file(str(in_path))
  84. def _init_backend(self, bk_type, bk_args):
  85. return PDFReaderBackend(**bk_args)
  86. def get_type(self):
  87. return ReaderType.PDF
  88. class ImageReader(_BaseReader):
  89. """ImageReader"""
  90. def __init__(self, backend="opencv", **bk_args):
  91. super().__init__(backend=backend, **bk_args)
  92. def read(self, in_path):
  93. """read the image file from path"""
  94. arr = self._backend.read_file(str(in_path))
  95. return arr
  96. def _init_backend(self, bk_type, bk_args):
  97. """init backend"""
  98. if bk_type == "opencv":
  99. return OpenCVImageReaderBackend(**bk_args)
  100. elif bk_type == "pil" or bk_type == "pillow":
  101. return PILImageReaderBackend(**bk_args)
  102. else:
  103. raise ValueError("Unsupported backend type")
  104. def get_type(self):
  105. """get type"""
  106. return ReaderType.IMAGE
  107. class _GenerativeReader(_BaseReader):
  108. """_GenerativeReader"""
  109. def get_type(self):
  110. """get type"""
  111. return ReaderType.GENERATIVE
  112. def is_generative_reader(reader):
  113. """is_generative_reader"""
  114. return isinstance(reader, _GenerativeReader)
  115. class VideoReader(_GenerativeReader):
  116. """VideoReader"""
  117. def __init__(
  118. self,
  119. backend="opencv",
  120. st_frame_id=0,
  121. max_num_frames=None,
  122. auto_close=True,
  123. **bk_args,
  124. ):
  125. super().__init__(backend=backend, **bk_args)
  126. self.st_frame_id = st_frame_id
  127. self.max_num_frames = max_num_frames
  128. self.auto_close = auto_close
  129. self._fps = 0
  130. def read(self, in_path):
  131. """read vide file from path"""
  132. self._backend.set_pos(self.st_frame_id)
  133. gen = self._backend.read_file(str(in_path))
  134. if self.max_num_frames is not None:
  135. gen = itertools.islice(gen, self.num_frames)
  136. yield from gen
  137. if self.auto_close:
  138. self._backend.close()
  139. def get_fps(self):
  140. """get fps"""
  141. return self._backend.get_fps()
  142. def _init_backend(self, bk_type, bk_args):
  143. """init backend"""
  144. if bk_type == "opencv":
  145. return OpenCVVideoReaderBackend(**bk_args)
  146. elif bk_type == "decord":
  147. return DecordVideoReaderBackend(**bk_args)
  148. else:
  149. raise ValueError("Unsupported backend type")
  150. class YAMLReader(_BaseReader):
  151. def __init__(self, backend="PyYAML", **bk_args):
  152. super().__init__(backend, **bk_args)
  153. def read(self, in_path):
  154. return self._backend.read_file(str(in_path))
  155. def _init_backend(self, bk_type, bk_args):
  156. if bk_type == "PyYAML":
  157. return YAMLReaderBackend(**bk_args)
  158. else:
  159. raise ValueError("Unsupported backend type")
  160. def get_type(self):
  161. return ReaderType.YAML
  162. class MarkDownReader(_BaseReader):
  163. def __init__(self, backend="Markdown", **bk_args):
  164. super().__init__(backend, **bk_args)
  165. def read(self, in_path):
  166. return self._backend.read_file(str(in_path))
  167. def _init_backend(self, bk_type, bk_args):
  168. if bk_type == "Markdown":
  169. return TXTReaderBackend(**bk_args)
  170. else:
  171. raise ValueError("Unsupported backend type")
  172. def get_type(self):
  173. return ReaderType.MARKDOWN
  174. class TXTReader(_BaseReader):
  175. """TXTReader"""
  176. def __init__(self, backend="txt", **bk_args):
  177. super().__init__(backend, **bk_args)
  178. def read(self, in_path):
  179. return self._backend.read_file(str(in_path))
  180. def _init_backend(self, bk_type, bk_args):
  181. if bk_type == "txt":
  182. return TXTReaderBackend(**bk_args)
  183. def get_type(self):
  184. return ReaderType.TXT
  185. class _BaseReaderBackend(object):
  186. """_BaseReaderBackend"""
  187. def read_file(self, in_path):
  188. """read file from path"""
  189. raise NotImplementedError
  190. class _ImageReaderBackend(_BaseReaderBackend):
  191. """_ImageReaderBackend"""
  192. @class_requires_deps("opencv-contrib-python")
  193. class OpenCVImageReaderBackend(_ImageReaderBackend):
  194. """OpenCVImageReaderBackend"""
  195. def __init__(self, flags=None):
  196. super().__init__()
  197. if flags is None:
  198. flags = cv2.IMREAD_COLOR
  199. self.flags = flags
  200. def read_file(self, in_path):
  201. """read image file from path by OpenCV"""
  202. with open(in_path, "rb") as f:
  203. img_array = np.frombuffer(f.read(), np.uint8)
  204. return cv2.imdecode(img_array, flags=self.flags)
  205. class PILImageReaderBackend(_ImageReaderBackend):
  206. """PILImageReaderBackend"""
  207. def __init__(self):
  208. super().__init__()
  209. def read_file(self, in_path):
  210. """read image file from path by PIL"""
  211. return ImageOps.exif_transpose(Image.open(in_path))
  212. @class_requires_deps("pypdfium2", "opencv-contrib-python")
  213. class PDFReaderBackend(_BaseReaderBackend):
  214. def __init__(self, rotate=0, zoom=2.0):
  215. super().__init__()
  216. self._rotation = rotate
  217. self._scale = zoom
  218. def read_file(self, in_path):
  219. doc = pdfium.PdfDocument(in_path)
  220. try:
  221. for page in doc:
  222. image = page.render(scale=self._scale, rotation=self._rotation).to_pil()
  223. image = image.convert("RGB")
  224. img_cv = np.array(image)
  225. img_cv = cv2.cvtColor(img_cv, cv2.COLOR_RGB2BGR)
  226. yield img_cv
  227. finally:
  228. doc.close()
  229. class TXTReaderBackend(_BaseReaderBackend):
  230. """TXTReaderBackend"""
  231. def read_file(self, in_path):
  232. with open(in_path, "r") as f:
  233. data = f.read()
  234. return data
  235. class _VideoReaderBackend(_BaseReaderBackend):
  236. """_VideoReaderBackend"""
  237. def set_pos(self, pos):
  238. """set pos"""
  239. raise NotImplementedError
  240. def close(self):
  241. """close io"""
  242. raise NotImplementedError
  243. @class_requires_deps("opencv-contrib-python")
  244. class OpenCVVideoReaderBackend(_VideoReaderBackend):
  245. """OpenCVVideoReaderBackend"""
  246. def __init__(self, **bk_args):
  247. super().__init__()
  248. self.cap_init_args = bk_args
  249. self.num_seg = bk_args.get("num_seg", None)
  250. self._cap = None
  251. self._pos = 0
  252. self._max_num_frames = None
  253. def get_fps(self):
  254. return self._cap.get(cv2.CAP_PROP_FPS)
  255. def read_file(self, in_path):
  256. """read video file from path"""
  257. if self._cap is not None:
  258. self._cap_release()
  259. self._cap = self._cap_open(in_path)
  260. if self._pos is not None:
  261. self._cap_set_pos()
  262. return self._read_frames(self._cap)
  263. def _read_frames(self, cap):
  264. """read frames"""
  265. if self.num_seg:
  266. queue = []
  267. while True:
  268. ret, frame = cap.read()
  269. if not ret:
  270. break
  271. queue = []
  272. if (
  273. len(queue) <= 0
  274. ): # At initialization, populate queue with initial frame
  275. for i in range(self.num_seg):
  276. queue.append(frame)
  277. queue.append(frame)
  278. queue.pop(0)
  279. yield queue.copy()
  280. else:
  281. while True:
  282. ret, frame = cap.read()
  283. if not ret:
  284. break
  285. yield frame
  286. self._cap_release()
  287. def _cap_open(self, video_path):
  288. self.cap_init_args.pop("num_seg", None)
  289. self.cap_init_args.pop("seg_len", None)
  290. self.cap_init_args.pop("sample_type", None)
  291. self._cap = cv2.VideoCapture(video_path, **self.cap_init_args)
  292. if not self._cap.isOpened():
  293. raise RuntimeError(f"Failed to open {video_path}")
  294. return self._cap
  295. def _cap_release(self):
  296. self._cap.release()
  297. def _cap_set_pos(self):
  298. self._cap.set(cv2.CAP_PROP_POS_FRAMES, self._pos)
  299. def set_pos(self, pos):
  300. self._pos = pos
  301. def close(self):
  302. if self._cap is not None:
  303. self._cap_release()
  304. self._cap = None
  305. class DecordVideoReaderBackend(_VideoReaderBackend):
  306. """DecordVideoReaderBackend"""
  307. def __init__(self, **bk_args):
  308. super().__init__()
  309. self.cap_init_args = bk_args
  310. self._cap = None
  311. self._pos = 0
  312. self._max_num_frames = None
  313. self.num_seg = bk_args.get("num_seg", 8)
  314. self.seg_len = bk_args.get("seg_len", 1)
  315. self.sample_type = bk_args.get("sample_type", 1)
  316. self.valid_mode = True
  317. self._fps = 0
  318. # XXX(gaotingquan): There is a conflict with `paddle` when import `decord` globally.
  319. try:
  320. import decord
  321. self.decord_module = decord
  322. except ModuleNotFoundError():
  323. raise Exception(
  324. "Please install `decord` manually, otherwise, the related model cannot work. It can be automatically installed only on `x86_64`. Refers: `https://github.com/dmlc/decord`."
  325. )
  326. def set_pos(self, pos):
  327. self._pos = pos
  328. def sample(self, frames_len, video_object):
  329. frames_idx = []
  330. average_dur = int(frames_len / self.num_seg)
  331. for i in range(self.num_seg):
  332. idx = 0
  333. if not self.valid_mode:
  334. if average_dur >= self.seg_len:
  335. idx = random.randint(0, average_dur - self.seg_len)
  336. idx += i * average_dur
  337. elif average_dur >= 1:
  338. idx += i * average_dur
  339. else:
  340. idx = i
  341. else:
  342. if average_dur >= self.seg_len:
  343. idx = (average_dur - 1) // 2
  344. idx += i * average_dur
  345. elif average_dur >= 1:
  346. idx += i * average_dur
  347. else:
  348. idx = i
  349. for jj in range(idx, idx + self.seg_len):
  350. frames_idx.append(int(jj % frames_len))
  351. frames_select = video_object.get_batch(frames_idx)
  352. # dearray_to_img
  353. np_frames = frames_select.asnumpy()
  354. imgs = []
  355. for i in range(np_frames.shape[0]):
  356. imgbuf = np_frames[i]
  357. imgs.append(imgbuf)
  358. return imgs
  359. def get_fps(self):
  360. return self._cap.get_avg_fps()
  361. def read_file(self, in_path):
  362. """read video file from path"""
  363. self._cap = self.decord_module.VideoReader(in_path)
  364. frame_len = len(self._cap)
  365. if self.sample_type == "uniform":
  366. sample_video = self.sample(frame_len, self._cap)
  367. return sample_video
  368. else:
  369. return self._cap
  370. def close(self):
  371. pass
  372. class CSVReader(_BaseReader):
  373. """CSVReader"""
  374. def __init__(self, backend="pandas", **bk_args):
  375. super().__init__(backend=backend, **bk_args)
  376. def read(self, in_path):
  377. """read the image file from path"""
  378. arr = self._backend.read_file(str(in_path))
  379. return arr
  380. def _init_backend(self, bk_type, bk_args):
  381. """init backend"""
  382. if bk_type == "pandas":
  383. return PandasCSVReaderBackend(**bk_args)
  384. else:
  385. raise ValueError("Unsupported backend type")
  386. def get_type(self):
  387. """get type"""
  388. return ReaderType.TS
  389. class _CSVReaderBackend(_BaseReaderBackend):
  390. """_CSVReaderBackend"""
  391. class PandasCSVReaderBackend(_CSVReaderBackend):
  392. """PandasCSVReaderBackend"""
  393. def __init__(self):
  394. super().__init__()
  395. def read_file(self, in_path):
  396. """read image file from path by OpenCV"""
  397. return pd.read_csv(in_path)
  398. class YAMLReaderBackend(_BaseReaderBackend):
  399. def read_file(self, in_path, **kwargs):
  400. with open(in_path, "r", encoding="utf-8", **kwargs) as yaml_file:
  401. data = yaml.load(yaml_file, Loader=yaml.FullLoader)
  402. return data
  403. class AudioReader(_BaseReader):
  404. def __init__(self, backend="wav", **bk_args):
  405. super().__init__(backend="wav", **bk_args)
  406. def _init_backend(self, bk_type, bk_args):
  407. """init backend"""
  408. if bk_type == "wav":
  409. return WAVReaderBackend(**bk_args)
  410. else:
  411. raise ValueError("Unsupported backend type")
  412. def read(self, in_path):
  413. audio, audio_sample_rate = self._backend.read_file(str(in_path))
  414. return audio, audio_sample_rate
  415. class _AudioReaderBackend(_BaseReaderBackend):
  416. """_AudioReaderBackend"""
  417. @class_requires_deps("soundfile")
  418. class WAVReaderBackend(_AudioReaderBackend):
  419. """PandasCSVReaderBackend"""
  420. def __init__(self):
  421. super().__init__()
  422. def read_file(self, in_path):
  423. """read wav file from path"""
  424. audio, audio_sample_rate = soundfile.read(
  425. in_path, dtype="float32", always_2d=True
  426. )
  427. return audio, audio_sample_rate