Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion superannotate/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ def consensus(*args, **kwargs):
download_image_preannotations, get_image_annotations, get_image_bytes,
get_image_metadata, get_image_preannotations, search_images,
search_images_all_folders, set_image_annotation_status,
set_images_annotation_statuses, upload_image_annotations
set_images_annotation_statuses, upload_image_annotations, get_project_root_folder_id
)
from .db.project_api import (
create_folder, delete_folders, get_folder_metadata,
Expand Down
4 changes: 3 additions & 1 deletion superannotate/db/project_images.py
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,8 @@ def copy_images(
:type copy_annotation_status: bool
:param copy_pin: enables image pin status copy
:type copy_pin: bool
:return: list of skipped image names
:rtype: list of strs
"""
source_project, source_project_folder = get_project_and_folder_metadata(
source_project
Expand All @@ -232,7 +234,7 @@ def copy_images(
image_names,
source_project["name"] + "" if source_project_folder is None else "/" +
source_project_folder["name"], destination_project["name"] +
"" if destination_project_folder is None else "/" +
"" if destination_project_folder is None else destination_project["name"] + "/" +
destination_project_folder["name"], len(res["skipped"])
)
return res["skipped"]
Expand Down
214 changes: 121 additions & 93 deletions superannotate/db/projects.py
Original file line number Diff line number Diff line change
Expand Up @@ -205,57 +205,63 @@ def get_project_image_count(project, with_all_subfolders=False):
return len(search_images_all_folders(project))


def upload_video_to_project(
project,
video_path,
target_fps=None,
start_time=0.0,
end_time=None,
annotation_status="NotStarted",
image_quality_in_editor=None
):
"""Uploads image frames from video to platform. Uploaded images will have
names "<video_name>_<frame_no>.jpg".
def _get_video_frames_count(video_path):
"""
Get video frames count
"""
video = cv2.VideoCapture(str(video_path), cv2.CAP_FFMPEG)
total_num_of_frames = int(video.get(cv2.CAP_PROP_FRAME_COUNT))
if total_num_of_frames < 0:
total_num_of_frames = 0
flag = True
while flag:
flag, _ = video.read()
if flag:
total_num_of_frames += 1
else:
break
return total_num_of_frames

:param project: project name or folder path (e.g., "project1/folder1")
:type project: str
:param video_path: video to upload
:type video_path: Pathlike (str or Path)
:param target_fps: how many frames per second need to extract from the video (approximate).
If None, all frames will be uploaded
:type target_fps: float
:param start_time: Time (in seconds) from which to start extracting frames
:type start_time: float
:param end_time: Time (in seconds) up to which to extract frames. If None up to end
:type end_time: float
:param annotation_status: value to set the annotation statuses of the uploaded
video frames NotStarted InProgress QualityCheck Returned Completed Skipped
:type annotation_status: str
:param image_quality_in_editor: image quality be seen in SuperAnnotate web annotation editor.
Can be either "compressed" or "original". If None then the default value in project settings will be used.
:type image_quality_in_editor: str

:return: filenames of uploaded images
:rtype: list of strs
def _get_video_fps_ration(target_fps,video,ratio):
"""
project, project_folder = get_project_and_folder_metadata(project)
upload_state = common.upload_state_int_to_str(project.get("upload_state"))
if upload_state == "External":
raise SABaseException(
0,
"The function does not support projects containing images attached with URLs"
Get video fps / target fps ratio
"""
video_fps = float(video.get(cv2.CAP_PROP_FPS))
if target_fps >= video_fps:
logger.warning(
"Video frame rate %s smaller than target frame rate %s. Cannot change frame rate.",
video_fps, target_fps
)
else:
logger.info(
"Changing video frame rate from %s to target frame rate %s.",
video_fps, target_fps
)
logger.info("Uploading from video %s.", str(video_path))
ratio = video_fps / target_fps
return ratio

def _get_available_image_counts(project,folder):
if folder:
folder_id = folder["id"]
else:
folder_id = get_project_root_folder_id(project)
params = {'team_id': project['team_id'] , 'folder_id' : folder_id }
res = _get_upload_auth_token(params=params,project_id=project['id'])
return res['availableImageCount']

def _get_video_rotate_code(video_path):
rotate_code = None
try:
cv2_rotations = {
90 : cv2.ROTATE_90_CLOCKWISE,
180 : cv2.ROTATE_180,
270 :cv2.ROTATE_90_COUNTERCLOCKWISE,
}

meta_dict = ffmpeg.probe(str(video_path))
rot = int(meta_dict['streams'][0]['tags']['rotate'])
if rot == 90:
rotate_code = cv2.ROTATE_90_CLOCKWISE
elif rot == 180:
rotate_code = cv2.ROTATE_180
elif rot == 270:
rotate_code = cv2.ROTATE_90_COUNTERCLOCKWISE
rotate_code = cv2_rotations[rot]
if rot != 0:
logger.info(
"Frame rotation of %s found. Output images will be rotated accordingly.",
Expand All @@ -269,61 +275,30 @@ def upload_video_to_project(
"Couldn't read video metadata to determine rotation. %s",
warning_str
)
return rotate_code

video = cv2.VideoCapture(str(video_path), cv2.CAP_FFMPEG)
if not video.isOpened():
raise SABaseException(0, "Couldn't open video file " + str(video_path))

total_num_of_frames = int(video.get(cv2.CAP_PROP_FRAME_COUNT))
if total_num_of_frames < 0:
total_num_of_frames = 0
flag = True
while flag:
flag, frame = video.read()
if flag:
total_num_of_frames += 1
else:
break
video = cv2.VideoCapture(str(video_path), cv2.CAP_FFMPEG)
logger.info("Video frame count is %s.", total_num_of_frames)

r = 1.0
if target_fps is not None:
video_fps = float(video.get(cv2.CAP_PROP_FPS))
if target_fps >= video_fps:
logger.warning(
"Video frame rate %s smaller than target frame rate %s. Cannot change frame rate.",
video_fps, target_fps
)
else:
logger.info(
"Changing video frame rate from %s to target frame rate %s.",
video_fps, target_fps
)
r = video_fps / target_fps

zero_fill_count = len(str(total_num_of_frames))
tempdir = tempfile.TemporaryDirectory()

def _extract_frames_from_video(start_time,end_time,ratio,video,video_path,tempdir,limit,rotate_code,total_num_of_frames):
video_name = Path(video_path).stem
frame_no = 0
frame_no_with_change = 1.0
extracted_frame_no = 1
logger.info("Extracting frames from video to %s.", tempdir.name)
while True:
zero_fill_count = len(str(total_num_of_frames))
while extracted_frame_no < (limit + 1) :
success, frame = video.read()
if not success:
break
frame_no += 1
if round(frame_no_with_change) != frame_no:
continue
frame_no_with_change += r
frame_no_with_change += ratio
frame_time = video.get(cv2.CAP_PROP_POS_MSEC) / 1000.0
if end_time is not None and frame_time > end_time:
if end_time and frame_time > end_time:
break
if frame_time < start_time:
continue
if rotate_code is not None:
if rotate_code:
frame = cv2.rotate(frame, rotate_code)
cv2.imwrite(
str(
Expand All @@ -334,26 +309,79 @@ def upload_video_to_project(
), frame
)
extracted_frame_no += 1
return extracted_frame_no - 1


def upload_video_to_project(
project,
video_path,
target_fps=None,
start_time=0.0,
end_time=None,
annotation_status="NotStarted",
image_quality_in_editor=None
):
"""Uploads image frames from video to platform. Uploaded images will have
names "<video_name>_<frame_no>.jpg".

:param project: project name or folder path (e.g., "project1/folder1")
:type project: str
:param video_path: video to upload
:type video_path: Pathlike (str or Path)
:param target_fps: how many frames per second need to extract from the video (approximate).
If None, all frames will be uploaded
:type target_fps: float
:param start_time: Time (in seconds) from which to start extracting frames
:type start_time: float
:param end_time: Time (in seconds) up to which to extract frames. If None up to end
:type end_time: float
:param annotation_status: value to set the annotation statuses of the uploaded
video frames NotStarted InProgress QualityCheck Returned Completed Skipped
:type annotation_status: str
:param image_quality_in_editor: image quality be seen in SuperAnnotate web annotation editor.
Can be either "compressed" or "original". If None then the default value in project settings will be used.
:type image_quality_in_editor: str

:return: filenames of uploaded images
:rtype: list of strs
"""

project, folder = get_project_and_folder_metadata(project)
limit = _get_available_image_counts(project,folder)

upload_state = common.upload_state_int_to_str(project.get("upload_state"))
if upload_state == "External":
raise SABaseException(
0,
"The function does not support projects containing images attached with URLs"
)
logger.info("Uploading from video %s.", str(video_path))
rotate_code = _get_video_rotate_code(video_path)
video = cv2.VideoCapture(str(video_path), cv2.CAP_FFMPEG)
if not video.isOpened():
raise SABaseException(0, "Couldn't open video file " + str(video_path))

total_num_of_frames = _get_video_frames_count(video_path)
logger.info("Video frame count is %s.", total_num_of_frames)
ratio = 1.0
if target_fps:
ratio = _get_video_fps_ration(target_fps,video,ratio)
tempdir = tempfile.TemporaryDirectory()
extracted_frame_no = _extract_frames_from_video(start_time,end_time,ratio,
video,video_path,tempdir,
limit,rotate_code,total_num_of_frames)
logger.info(
"Extracted %s frames from video. Now uploading to platform.",
extracted_frame_no - 1
extracted_frame_no
)

filenames = upload_images_from_folder_to_project(
(project, project_folder),
(project, folder),
tempdir.name,
extensions=["jpg"],
annotation_status=annotation_status,
image_quality_in_editor=image_quality_in_editor
)

assert len(filenames[1]) == 0

filenames_base = []
for file in filenames[0]:
filenames_base.append(Path(file).name)

filenames_base = [Path(f).name for f in filenames[0]]
return filenames_base


Expand Down Expand Up @@ -398,7 +426,7 @@ def upload_videos_from_folder_to_project(
:return: uploaded and not-uploaded video frame images' filenames
:rtype: tuple of list of strs
"""
project, project_folder = get_project_and_folder_metadata(project)
project, folder = get_project_and_folder_metadata(project)
upload_state = common.upload_state_int_to_str(project.get("upload_state"))
if upload_state == "External":
raise SABaseException(
Expand Down Expand Up @@ -440,7 +468,7 @@ def upload_videos_from_folder_to_project(
filenames = []
for path in filtered_paths:
filenames += upload_video_to_project(
(project, project_folder),
(project, folder),
path,
target_fps=target_fps,
start_time=start_time,
Expand Down