FileOperations.py 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233
  1. import os
  2. from typing import Tuple
  3. import cv2
  4. from PIL import Image
  5. from pycs.database.File import File
  6. DEFAULT_JPEG_QUALITY = 80
  7. def file_info(data_folder: str, file_name: str, file_ext: str):
  8. """
  9. Receive file type, frame count and frames per second.
  10. The last two are always None for images.
  11. :param data_folder: path to data folder
  12. :param file_name: file name
  13. :param file_ext: file extension
  14. :return: file type, frame count, frames per second
  15. """
  16. # determine file type
  17. if file_ext.lower() in ['.jpg', '.png']:
  18. file_type = 'image'
  19. elif file_ext.lower() in ['.mp4']:
  20. file_type = 'video'
  21. else:
  22. raise ValueError(f"Unsupported file extension: {file_ext}!")
  23. # determine frames and fps for video files
  24. if file_type == 'image':
  25. frames = None
  26. fps = None
  27. else:
  28. file_path = os.path.join(data_folder, file_name + file_ext)
  29. video = cv2.VideoCapture(file_path)
  30. frames = int(video.get(cv2.CAP_PROP_FRAME_COUNT))
  31. fps = video.get(cv2.CAP_PROP_FPS)
  32. video.release()
  33. # return values
  34. return file_type, frames, fps
  35. def resize_file(file: File, project_root: str, max_width: int, max_height: int) -> Tuple[str, str]:
  36. """
  37. If file type equals video this function extracts a thumbnail first. It calls resize_image
  38. to resize and returns the resized files directory and name.
  39. :param file: file object
  40. :param project_root: project root folder path
  41. :param max_width: maximum image or thumbnail width
  42. :param max_height: maximum image or thumbnail height
  43. :return: resized file directory, resized file name
  44. """
  45. abs_file_path = file.absolute_path
  46. # extract video thumbnail
  47. if file.type == 'video':
  48. abs_target_path = os.path.join(os.getcwd(), project_root, 'temp', f'{file.uuid}.jpg')
  49. create_thumbnail(abs_file_path, abs_target_path)
  50. abs_file_path = abs_target_path
  51. # resize image file
  52. abs_target_path = os.path.join(os.getcwd(),
  53. project_root,
  54. 'temp',
  55. f'{file.uuid}_{max_width}_{max_height}.jpg')
  56. result = resize_image(abs_file_path, abs_target_path, max_width, max_height)
  57. # return path
  58. if result is not None:
  59. return os.path.split(abs_target_path)
  60. return os.path.split(abs_file_path)
  61. def crop_file(file: File, project_root: str,
  62. x: float, y: float, w: float, h: float,
  63. max_width: int, max_height: int) -> Tuple[str, str]:
  64. """
  65. gets a file for the given file_id, crops the according image to the
  66. bounding box and saves the crops in the temp folder of the project.
  67. :param file: file object
  68. :param project_root: project root folder path
  69. :param max_width: maximum image or thumbnail width
  70. :param max_height: maximum image or thumbnail height
  71. :param x: relative x-coordinate of the top left corner
  72. :param y: relative y-coordinate of the top left corner
  73. :param w: relative width of the bounding box
  74. :param h: relative height of the bounding box
  75. :return: directory and file name of the cropped patch
  76. """
  77. abs_file_path = file.absolute_path
  78. # extract video thumbnail
  79. if file.type == 'video':
  80. abs_target_path = os.path.join(os.getcwd(), project_root, 'temp', f'{file.uuid}.jpg')
  81. create_thumbnail(abs_file_path, abs_target_path)
  82. abs_file_path = abs_target_path
  83. # crop image file
  84. abs_target_path = os.path.join(os.getcwd(),
  85. project_root,
  86. 'temp',
  87. f'{file.uuid}_{x}_{y}_{w}_{h}.jpg')
  88. result = crop_image(abs_file_path, abs_target_path, x, y, w, h)
  89. if result:
  90. abs_file_path = abs_target_path
  91. # resize image
  92. abs_target_path = os.path.join(os.getcwd(),
  93. project_root,
  94. 'temp',
  95. f'{file.uuid}_{max_width}_{max_height}_{x}_{y}_{w}_{h}.jpg')
  96. result = resize_image(abs_file_path, abs_target_path, max_width, max_height)
  97. if result:
  98. abs_file_path = abs_target_path
  99. # return image
  100. return os.path.split(abs_file_path)
  101. def create_thumbnail(file_path: str, target_path: str) -> None:
  102. """
  103. extract a thumbnail from a video
  104. :param file_path: path to source file
  105. :param target_path: path to target file
  106. :return:
  107. """
  108. # return if file exists
  109. if os.path.exists(target_path):
  110. return
  111. # load video
  112. video = cv2.VideoCapture(file_path)
  113. # create thumbnail
  114. _, image = video.read()
  115. cv2.imwrite(target_path, image)
  116. # close video file
  117. video.release()
  118. def resize_image(file_path: str, target_path: str, max_width: int, max_height: int) -> bool:
  119. """
  120. Resize an image so width < `max_width` and height < `max_height` applies.
  121. If the image is already smaller than the given dimensions no new file is stored.
  122. :param file_path: path to source file
  123. :param target_path: path to target file
  124. :param max_width: maximum image width
  125. :param max_height: maximum image height
  126. :return: `True` if a resize operation was performed or the target file already exists
  127. """
  128. # return if file exists
  129. if os.path.exists(target_path):
  130. return True
  131. # load full size image
  132. image = Image.open(file_path)
  133. img_width, img_height = image.size
  134. # abort if file is smaller than desired
  135. if img_width < max_width and img_height < max_height:
  136. return False
  137. # calculate target size
  138. target_width = int(max_width)
  139. target_height = int(max_width * img_height / img_width)
  140. if target_height > max_height:
  141. target_height = int(max_height)
  142. target_width = int(max_height * img_width / img_height)
  143. # resize image
  144. resized_image = image.resize((target_width, target_height))
  145. # save to file
  146. resized_image.save(target_path, quality=DEFAULT_JPEG_QUALITY)
  147. return True
  148. def crop_image(file_path: str, target_path: str, x: float, y: float, w: float, h: float) -> bool:
  149. """
  150. Crop an image with the given coordinates, width and height.
  151. If however no crop is applied no new file is stored.
  152. :param file_path: path to source file
  153. :param target_path: path to target file
  154. :param x: crop x position (normalized)
  155. :param y: crop y position (normalized)
  156. :param w: crop width (normalized)
  157. :param h: crop height (normalized)
  158. :return: `True` if a crop operation was performed or the target file already exists
  159. """
  160. # return if file exists
  161. if os.path.exists(target_path):
  162. return True
  163. # abort if no crop would be applied
  164. if x <= 0 and y <= 0 and w >= 1 and h >= 1:
  165. return False
  166. # load full size image
  167. image = Image.open(file_path)
  168. img_width, img_height = image.size
  169. # calculate absolute crop position
  170. crop_x1 = int(img_width * x)
  171. crop_y1 = int(img_height * y)
  172. crop_x2 = min(int(img_width * w) + crop_x1, img_width)
  173. crop_y2 = min(int(img_height * h) + crop_y1, img_height)
  174. # crop image
  175. print(crop_x1, crop_y1, crop_x2, crop_y2)
  176. cropped_image = image.crop((crop_x1, crop_y1, crop_x2, crop_y2))
  177. # save to file
  178. cropped_image.save(target_path, quality=DEFAULT_JPEG_QUALITY)
  179. return True