Skip to content

Commit 9966763

Browse files
authored
Merge pull request #244 from superannotateai/sdk-354
Sdk 354
2 parents 3765700 + bac713a commit 9966763

File tree

8 files changed

+299
-168
lines changed

8 files changed

+299
-168
lines changed

src/superannotate/lib/app/interface/cli_interface.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -268,7 +268,7 @@ def upload_videos(
268268
self,
269269
project,
270270
folder,
271-
target_fps=1,
271+
target_fps=None,
272272
recursive=False,
273273
extensions=constances.DEFAULT_VIDEO_EXTENSIONS,
274274
set_annotation_status=constances.AnnotationStatus.NOT_STARTED.name,

src/superannotate/lib/app/interface/sdk_interface.py

Lines changed: 117 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
from lib.core import LIMITED_FUNCTIONS
4242
from lib.core.enums import ImageQuality
4343
from lib.core.exceptions import AppException
44+
from lib.core.plugin import VideoPlugin
4445
from lib.core.types import AttributeGroup
4546
from lib.core.types import ClassesJson
4647
from lib.core.types import MLModel
@@ -1632,13 +1633,13 @@ def upload_videos_from_folder_to_project(
16321633
extensions: Optional[
16331634
Union[Tuple[NotEmptyStr], List[NotEmptyStr]]
16341635
] = constances.DEFAULT_VIDEO_EXTENSIONS,
1635-
exclude_file_patterns: Optional[Iterable[NotEmptyStr]] = (),
1636+
exclude_file_patterns: Optional[List[NotEmptyStr]] = [],
16361637
recursive_subfolders: Optional[StrictBool] = False,
16371638
target_fps: Optional[int] = None,
16381639
start_time: Optional[float] = 0.0,
16391640
end_time: Optional[float] = None,
16401641
annotation_status: Optional[AnnotationStatuses] = "NotStarted",
1641-
image_quality_in_editor: Optional[str] = None,
1642+
image_quality_in_editor: Optional[ImageQualityChoices] = None,
16421643
):
16431644
"""Uploads image frames from all videos with given extensions from folder_path to the project.
16441645
Sets status of all the uploaded images to set_status if it is not None.
@@ -1694,19 +1695,33 @@ def upload_videos_from_folder_to_project(
16941695
filtered_paths = []
16951696
video_paths = [str(path) for path in video_paths]
16961697
for path in video_paths:
1698+
16971699
not_in_exclude_list = [x not in Path(path).name for x in exclude_file_patterns]
16981700
if all(not_in_exclude_list):
16991701
filtered_paths.append(path)
17001702

17011703
project_folder_name = project_name + (f"/{folder_name}" if folder_name else "")
1702-
17031704
logger.info(
1704-
f"Uploading all videos with extensions {extensions} from {str(folder_path)} to project {project_name}. Excluded file patterns are: {[*exclude_file_patterns]}.",
1705+
f"Uploading all videos with extensions {extensions} from {str(folder_path)} to project {project_name}. Excluded file patterns are: {exclude_file_patterns}."
17051706
)
17061707
uploaded_paths = []
1707-
for path in video_paths:
1708+
for path in filtered_paths:
1709+
progress_bar = None
17081710
with tempfile.TemporaryDirectory() as temp_path:
1709-
res = controller.extract_video_frames(
1711+
frame_names = VideoPlugin.get_extractable_frames(
1712+
path, start_time, end_time, target_fps
1713+
)
1714+
duplicate_images = (
1715+
controller.get_duplicate_images(
1716+
project_name=project_name,
1717+
folder_name=folder_name,
1718+
images=frame_names,
1719+
)
1720+
.execute()
1721+
.data
1722+
)
1723+
duplicate_images = [image.name for image in duplicate_images]
1724+
frames_generator = controller.extract_video_frames(
17101725
project_name=project_name,
17111726
folder_name=folder_name,
17121727
video_path=path,
@@ -1717,48 +1732,51 @@ def upload_videos_from_folder_to_project(
17171732
annotation_status=annotation_status,
17181733
image_quality_in_editor=image_quality_in_editor,
17191734
)
1720-
if res.errors:
1721-
raise AppException(res.errors)
1722-
use_case = controller.upload_images_from_folder_to_project(
1723-
project_name=project_name,
1724-
folder_name=folder_name,
1725-
folder_path=temp_path,
1726-
annotation_status=annotation_status,
1727-
image_quality_in_editor=image_quality_in_editor,
1728-
)
1729-
images_to_upload, duplicates = use_case.images_to_upload
1735+
total_frames_count = len(frame_names)
1736+
logger.info(f"Video frame count is {total_frames_count}.")
17301737
logger.info(
1731-
f"Extracted {len(res.data)} frames from video. Now uploading to platform.",
1738+
f"Extracted {total_frames_count} frames from video. Now uploading to platform.",
17321739
)
17331740
logger.info(
1734-
f"Uploading {len(images_to_upload)} images to project {str(project_folder_name)}."
1741+
f"Uploading {total_frames_count} images to project {str(project_folder_name)}."
17351742
)
1736-
1737-
if len(duplicates):
1743+
if len(duplicate_images):
17381744
logger.warning(
1739-
f"{len(duplicates)} already existing images found that won't be uploaded."
1740-
)
1741-
if not images_to_upload:
1742-
logger.warning(
1743-
f"{len(duplicates)} already existing images found that won't be uploaded."
1745+
f"{len(duplicate_images)} already existing images found that won't be uploaded."
17441746
)
1747+
if set(duplicate_images) == set(frame_names):
17451748
continue
1746-
if use_case.is_valid():
1747-
with tqdm(
1748-
total=len(images_to_upload), desc="Uploading images"
1749-
) as progress_bar:
1749+
1750+
for _ in frames_generator:
1751+
use_case = controller.upload_images_from_folder_to_project(
1752+
project_name=project_name,
1753+
folder_name=folder_name,
1754+
folder_path=temp_path,
1755+
annotation_status=annotation_status,
1756+
image_quality_in_editor=image_quality_in_editor,
1757+
)
1758+
1759+
images_to_upload, duplicates = use_case.images_to_upload
1760+
if not len(images_to_upload):
1761+
continue
1762+
if not progress_bar:
1763+
progress_bar = tqdm(
1764+
total=total_frames_count, desc="Uploading images"
1765+
)
1766+
if use_case.is_valid():
17501767
for _ in use_case.execute():
17511768
progress_bar.update()
1752-
uploaded, failed_images, duplicated = use_case.response.data
1753-
uploaded_paths.extend(uploaded)
1754-
if failed_images:
1755-
logger.warning(f"Failed {len(uploaded)}.")
1756-
if duplicated:
1757-
logger.warning(
1758-
f"{len(duplicated)} already existing images found that won't be uploaded."
1759-
)
1760-
else:
1761-
raise AppException(use_case.response.errors)
1769+
uploaded, failed_images, _ = use_case.response.data
1770+
uploaded_paths.extend(uploaded)
1771+
if failed_images:
1772+
logger.warning(f"Failed {len(failed_images)}.")
1773+
files = os.listdir(temp_path)
1774+
image_paths = [f"{temp_path}/{f}" for f in files]
1775+
for path in image_paths:
1776+
os.remove(path)
1777+
else:
1778+
raise AppException(use_case.response.errors)
1779+
17621780
return uploaded_paths
17631781

17641782

@@ -1771,7 +1789,7 @@ def upload_video_to_project(
17711789
start_time: Optional[float] = 0.0,
17721790
end_time: Optional[float] = None,
17731791
annotation_status: Optional[AnnotationStatuses] = "NotStarted",
1774-
image_quality_in_editor: Optional[NotEmptyStr] = None,
1792+
image_quality_in_editor: Optional[ImageQualityChoices] = None,
17751793
):
17761794
"""Uploads image frames from video to platform. Uploaded images will have
17771795
names "<video_name>_<frame_no>.jpg".
@@ -1799,36 +1817,77 @@ def upload_video_to_project(
17991817
"""
18001818

18011819
project_name, folder_name = extract_project_folder(project)
1820+
project_folder_name = project_name + (f"/{folder_name}" if folder_name else "")
1821+
1822+
uploaded_paths = []
1823+
path = video_path
1824+
progress_bar = None
18021825
with tempfile.TemporaryDirectory() as temp_path:
1803-
res = controller.extract_video_frames(
1826+
frame_names = VideoPlugin.get_extractable_frames(
1827+
video_path, start_time, end_time, target_fps
1828+
)
1829+
duplicate_images = (
1830+
controller.get_duplicate_images(
1831+
project_name=project_name, folder_name=folder_name, images=frame_names,
1832+
)
1833+
.execute()
1834+
.data
1835+
)
1836+
duplicate_images = [image.name for image in duplicate_images]
1837+
frames_generator = controller.extract_video_frames(
18041838
project_name=project_name,
18051839
folder_name=folder_name,
1806-
video_path=video_path,
1840+
video_path=path,
18071841
extract_path=temp_path,
18081842
target_fps=target_fps,
18091843
start_time=start_time,
18101844
end_time=end_time,
18111845
annotation_status=annotation_status,
18121846
image_quality_in_editor=image_quality_in_editor,
18131847
)
1814-
if res.errors:
1815-
raise AppException(res.errors)
1816-
use_case = controller.upload_images_from_folder_to_project(
1817-
project_name=project_name,
1818-
folder_name=folder_name,
1819-
folder_path=temp_path,
1820-
annotation_status=annotation_status,
1821-
image_quality_in_editor=image_quality_in_editor,
1848+
total_frames_count = len(frame_names)
1849+
logger.info(
1850+
f"Extracted {total_frames_count} frames from video. Now uploading to platform.",
18221851
)
1823-
images_to_upload, _ = use_case.images_to_upload
1824-
if use_case.is_valid():
1825-
with tqdm(
1826-
total=len(images_to_upload), desc="Uploading frames."
1827-
) as progress_bar:
1852+
logger.info(
1853+
f"Uploading {total_frames_count} images to project {str(project_folder_name)}."
1854+
)
1855+
if len(duplicate_images):
1856+
logger.warning(
1857+
f"{len(duplicate_images)} already existing images found that won't be uploaded."
1858+
)
1859+
if set(duplicate_images) == set(frame_names):
1860+
return []
1861+
1862+
for _ in frames_generator:
1863+
use_case = controller.upload_images_from_folder_to_project(
1864+
project_name=project_name,
1865+
folder_name=folder_name,
1866+
folder_path=temp_path,
1867+
annotation_status=annotation_status,
1868+
image_quality_in_editor=image_quality_in_editor,
1869+
)
1870+
1871+
images_to_upload, duplicates = use_case.images_to_upload
1872+
if not len(images_to_upload):
1873+
continue
1874+
if not progress_bar:
1875+
progress_bar = tqdm(total=total_frames_count, desc="Uploading images")
1876+
if use_case.is_valid():
18281877
for _ in use_case.execute():
1829-
progress_bar.update(1)
1830-
return use_case.data[0]
1831-
raise AppException(use_case.response.errors)
1878+
progress_bar.update()
1879+
uploaded, failed_images, _ = use_case.response.data
1880+
uploaded_paths.extend(uploaded)
1881+
if failed_images:
1882+
logger.warning(f"Failed {len(failed_images)}.")
1883+
files = os.listdir(temp_path)
1884+
image_paths = [f"{temp_path}/{f}" for f in files]
1885+
for path in image_paths:
1886+
os.remove(path)
1887+
else:
1888+
raise AppException(use_case.response.errors)
1889+
1890+
return uploaded_paths
18321891

18331892

18341893
@Trackable

0 commit comments

Comments
 (0)