From 973d9cc7b87b1b001c42c5a0726c19000d03f8bb Mon Sep 17 00:00:00 2001 From: John Wilkie <124276291+JBWilkie@users.noreply.github.com> Date: Thu, 18 Apr 2024 09:11:09 +0100 Subject: [PATCH] [PLA-585][external] Allow imports where some annotation frames are missing a main type (#788) * WIP * WIP * WIP * WIP * WIP * Ensure COCO categories are always in the same, ascending order to avoid E2E flakiness * Ensure COCO categories are always in the same, ascending order to avoid E2E test flakiness * WIP * Removed deprecated functions & removed erroneous deprecation labels * Fixed teststhat broke when removing deprecated functions * WIP * Updated data.zip with v2 data structures * Undo accidental changes * 1 failing test remaining (Video annotation parsing) (WIP) * Moved ObjectStore tests to datatypes_test * Fixed video annotation import test * Added support for simple table annotations incase exports are compressed to avoid OOM * Small fix for importing the raster layer * Removed 2 unused functions * Removed deprecated api_url parameter * Replace lambda with itemgetter() for improved performance * Turned 4 functions private * Removed deprecated api_url parameter from function call * Fixed private function calls * Removed debugging print statements * Linting * Update darwin/datatypes.py Co-authored-by: saurbhc * docstring updates --------- Co-authored-by: saurbhc --- darwin/dataset/download_manager.py | 172 ----- darwin/dataset/local_dataset.py | 5 +- darwin/dataset/remote_dataset.py | 1 - darwin/dataset/utils.py | 2 +- darwin/datatypes.py | 66 +- darwin/exporter/formats/coco.py | 354 +--------- darwin/exporter/formats/cvat.py | 188 ------ darwin/exporter/formats/darwin.py | 44 +- darwin/exporter/formats/dataloop.py | 83 --- .../formats/helpers/yolo_class_builder.py | 2 +- darwin/exporter/formats/instance_mask.py | 2 - darwin/exporter/formats/mask.py | 13 +- darwin/exporter/formats/nifti.py | 6 - darwin/exporter/formats/pascalvoc.py | 153 +---- darwin/exporter/formats/yolo.py | 2 +- darwin/importer/formats/coco.py | 10 +- darwin/importer/formats/dataloop.py | 3 +- darwin/importer/formats/nifti.py | 6 +- darwin/importer/importer.py | 68 +- darwin/item.py | 38 -- darwin/torch/dataset.py | 2 - darwin/utils/utils.py | 470 ++++++------- tests/darwin/dataset/dataset_utils_test.py | 186 ++++- tests/darwin/dataset/remote_dataset_test.py | 2 +- tests/darwin/datatypes_test.py | 25 +- .../exporter/formats/export_coco_test.py | 2 +- .../exporter/formats/export_mask_test.py | 42 +- .../exporter/formats/export_pascalvoc_test.py | 4 +- .../importer/formats/import_darwin_test.py | 579 ++++++++++++---- .../importer/formats/import_labelbox_test.py | 14 +- .../formats/import_superannotate_test.py | 16 +- tests/darwin/importer/importer_mcpu_test.py | 16 +- tests/darwin/importer/importer_test.py | 52 +- tests/darwin/torch/dataset_test.py | 4 +- tests/darwin/utils/find_files_test.py | 19 +- tests/darwin/utils_test.py | 637 ++++++++++++------ tests/data.zip | Bin 239047 -> 240444 bytes 37 files changed, 1428 insertions(+), 1860 deletions(-) diff --git a/darwin/dataset/download_manager.py b/darwin/dataset/download_manager.py index c54e9b126..e6cc50cbb 100644 --- a/darwin/dataset/download_manager.py +++ b/darwin/dataset/download_manager.py @@ -9,7 +9,6 @@ from tempfile import TemporaryDirectory from typing import Any, Callable, Dict, Iterable, List, Optional, Tuple -import deprecation import numpy as np import orjson as json import requests @@ -28,18 +27,10 @@ is_image_extension_allowed, parse_darwin_json, ) -from darwin.version import __version__ -@deprecation.deprecated( - deprecated_in="0.7.5", - removed_in="0.8.0", - current_version=__version__, - details="The api_url parameter will be removed.", -) def download_all_images_from_annotations( api_key: str, - api_url: str, annotations_path: Path, images_path: Path, force_replace: bool = False, @@ -57,8 +48,6 @@ def download_all_images_from_annotations( ---------- api_key : str API Key of the current team - api_url : str - Url of the darwin API (e.g. 'https://darwin.v7labs.com/api/') annotations_path : Path Path where the annotations are located images_path : Path @@ -152,70 +141,6 @@ def download_all_images_from_annotations( return lambda: download_functions, len(download_functions) -@deprecation.deprecated( - deprecated_in="0.7.5", - removed_in="0.8.0", - current_version=__version__, - details="The api_url parameter will be removed.", -) -def download_image_from_annotation( - api_key: str, - api_url: str, - annotation_path: Path, - images_path: Path, - annotation_format: str, - use_folders: bool, - video_frames: bool, - force_slots: bool, - ignore_slots: bool = False, -) -> None: - """ - Dispatches functions to download an image given an annotation. - - Parameters - ---------- - api_key : str - API Key of the current team - api_url : str - Url of the darwin API (e.g. 'https://darwin.v7labs.com/api/') - annotation_path : Path - Path where the annotation is located - images_path : Path - Path where to download the image - annotation_format : str - Format of the annotations. Currently only JSON is supported - use_folders : bool - Recreate folder structure - video_frames : bool - Pulls video frames images instead of video files - force_slots: bool - Pulls all slots of items into deeper file structure ({prefix}/{item_name}/{slot_name}/{file_name}) - - Raises - ------ - NotImplementedError - If the format of the annotation is not supported. - """ - - console = Console() - - if annotation_format == "json": - downloadables = _download_image_from_json_annotation( - api_key, - annotation_path, - images_path, - use_folders, - video_frames, - force_slots, - ignore_slots, - ) - for downloadable in downloadables: - downloadable() - else: - console.print("[bold red]Unsupported file format. Please use 'json'.") - raise NotImplementedError - - def lazy_download_image_from_annotation( api_key: str, annotation_path: Path, @@ -454,103 +379,6 @@ def _update_local_path(annotation: AnnotationFile, url, local_path): file.write(op) -@deprecation.deprecated( - deprecated_in="0.7.5", - removed_in="0.8.0", - current_version=__version__, - details="Use the ``download_image_from_annotation`` instead.", -) -def download_image_from_json_annotation( - api_key: str, - api_url: str, - annotation_path: Path, - image_path: Path, - use_folders: bool, - video_frames: bool, -) -> None: - """ - Downloads an image given a ``.json`` annotation path and renames the json after the image's - filename. - - Parameters - ---------- - api_key : str - API Key of the current team - api_url : str - Url of the darwin API (e.g. 'https://darwin.v7labs.com/api/') - annotation_path : Path - Path where the annotation is located - image_path : Path - Path where to download the image - use_folders : bool - Recreate folders - video_frames : bool - Pulls video frames images instead of video files - """ - annotation = attempt_decode(annotation_path) - - # If we are using folders, extract the path for the image and create the folder if needed - sub_path = annotation["image"].get("path", "/") if use_folders else "/" - parent_path = Path(image_path) / Path(sub_path).relative_to(Path(sub_path).anchor) - parent_path.mkdir(exist_ok=True, parents=True) - - if video_frames and "frame_urls" in annotation["image"]: - video_path: Path = parent_path / annotation_path.stem - video_path.mkdir(exist_ok=True, parents=True) - for i, frame_url in enumerate(annotation["image"]["frame_urls"]): - path = video_path / f"{i:07d}.png" - _download_image(frame_url, path, api_key) - else: - image_url = annotation["image"]["url"] - image_path = parent_path / sanitize_filename(annotation["image"]["filename"]) - _download_image(image_url, image_path, api_key) - - -@deprecation.deprecated( - deprecated_in="0.7.5", - removed_in="0.8.0", - current_version=__version__, - details="Use the ``download_image_from_annotation`` instead.", -) -def download_image(url: str, path: Path, api_key: str) -> None: - """ - Helper function: downloads one image from url. - - Parameters - ---------- - url : str - Url of the image to download - path : Path - Path where to download the image, with filename - api_key : str - API Key of the current team - """ - if path.exists(): - return - TIMEOUT: int = 60 - start: float = time.time() - while True: - if "token" in url: - response: requests.Response = requests.get(url, stream=True) - else: - response = requests.get( - url, headers={"Authorization": f"ApiKey {api_key}"}, stream=True - ) - # Correct status: download image - if response.ok: - with open(str(path), "wb") as file: - for chunk in response: - file.write(chunk) - return - # Fatal-error status: fail - if 400 <= response.status_code <= 499: - raise Exception(response.status_code, response.json()) - # Timeout - if time.time() - start > TIMEOUT: - raise Exception(f"Timeout url request ({url}) after {TIMEOUT} seconds.") - time.sleep(1) - - def _download_image( url: str, path: Path, api_key: str, slot: Optional[dt.Slot] = None ) -> None: diff --git a/darwin/dataset/local_dataset.py b/darwin/dataset/local_dataset.py index 5f22320e1..cff6fedc8 100644 --- a/darwin/dataset/local_dataset.py +++ b/darwin/dataset/local_dataset.py @@ -335,12 +335,11 @@ def annotation_type_supported(self, annotation) -> bool: elif self.annotation_type == "bounding_box": is_bounding_box = annotation_type == "bounding_box" is_supported_polygon = ( - annotation_type in ["polygon", "complex_polygon"] - and "bounding_box" in annotation.data + annotation_type == "polygon" and "bounding_box" in annotation.data ) return is_bounding_box or is_supported_polygon elif self.annotation_type == "polygon": - return annotation_type in ["polygon", "complex_polygon"] + return annotation_type == "polygon" else: raise ValueError( "annotation_type should be either 'tag', 'bounding_box', or 'polygon'" diff --git a/darwin/dataset/remote_dataset.py b/darwin/dataset/remote_dataset.py index 0be301c22..db9db9689 100644 --- a/darwin/dataset/remote_dataset.py +++ b/darwin/dataset/remote_dataset.py @@ -345,7 +345,6 @@ def pull( # Create the generator with the download instructions progress, count = download_all_images_from_annotations( api_key=api_key, - api_url=self.client.url, annotations_path=annotations_dir, images_path=self.local_images_path, force_replace=force_replace, diff --git a/darwin/dataset/utils.py b/darwin/dataset/utils.py index 3070c6912..4d31de604 100644 --- a/darwin/dataset/utils.py +++ b/darwin/dataset/utils.py @@ -705,7 +705,7 @@ def convert_to_rgb(pic: PILImage.Image) -> PILImage.Image: def compute_max_density(annotations_dir: Path) -> int: """ Calculates the maximum density of all of the annotations in the given folder. - Density is calculated as the number of polygons / complex_polygons present in an annotation + Density is calculated as the number of polygons present in an annotation file. Parameters diff --git a/darwin/datatypes.py b/darwin/datatypes.py index e922b17c6..30a02bb00 100644 --- a/darwin/datatypes.py +++ b/darwin/datatypes.py @@ -89,7 +89,6 @@ def from_dict(cls, json: JSONFreeForm) -> "JSONType": AnnotationType = Literal[ # NB: Some of these are not supported yet "bounding_box", "polygon", - "complex_polygon", "ellipse", "cuboid", "segmentation", @@ -645,7 +644,7 @@ def make_tag( def make_polygon( class_name: str, - point_path: List[Point], + point_paths: List[List[Point]] | List[Point], bounding_box: Optional[Dict] = None, subs: Optional[List[SubAnnotation]] = None, slot_names: Optional[List[str]] = None, @@ -655,55 +654,22 @@ def make_polygon( Parameters ---------- - class_name : str + class_name: str The name of the class for this ``Annotation``. - point_path : List[Point] - A list of points that comprises the polygon. The list should have a format similar to: + point_paths: List[List[Point]] | List[Point] + Either a list of points that comprises a polygon or a list of lists of points that comprises a complex polygon. + A complex polygon is a polygon that is defined by >1 path. - .. code-block:: python + A polygon should be defined by a List[Point] and have a format similar to: + + ... code-block:: python [ {"x": 1, "y": 0}, {"x": 2, "y": 1} ] - bounding_box : Optional[Dict], default: None - The bounding box that encompasses the polyong. - subs : Optional[List[SubAnnotation]], default: None - List of ``SubAnnotation``s for this ``Annotation``. - - Returns - ------- - Annotation - A polygon ``Annotation``. - """ - return Annotation( - AnnotationClass(class_name, "polygon"), - _maybe_add_bounding_box_data({"path": point_path}, bounding_box), - subs or [], - slot_names=slot_names or [], - ) - - -def make_complex_polygon( - class_name: str, - point_paths: List[List[Point]], - bounding_box: Optional[Dict] = None, - subs: Optional[List[SubAnnotation]] = None, - slot_names: Optional[List[str]] = None, -) -> Annotation: - """ - Creates and returns a complex polygon annotation. Complex polygons are those who have holes - and/or disform shapes. - - Parameters - ---------- - class_name: str - The name of the class for this ``Annotation``. - point_paths: List[List[Point]] - A list of lists points that comprises the complex polygon. This is needed as a complex - polygon can be effectively seen as a sum of multiple simple polygons. The list should have - a format similar to: + A complex polygon should be defined by a List[List[Point]] and have a format similar to: .. code-block:: python @@ -727,10 +693,20 @@ def make_complex_polygon( Returns ------- Annotation - A complex polygon ``Annotation``. + A polygon ``Annotation``. """ + + # Check if point_paths is List[Point] and convert to List[List[Point]] + if ( + len(point_paths) > 1 + and isinstance(point_paths[0], dict) + and "x" in point_paths[0] + and "y" in point_paths[0] + ): + point_paths = [point_paths] + return Annotation( - AnnotationClass(class_name, "complex_polygon", "polygon"), + AnnotationClass(class_name, "polygon", "polygon"), _maybe_add_bounding_box_data({"paths": point_paths}, bounding_box), subs or [], slot_names=slot_names or [], diff --git a/darwin/exporter/formats/coco.py b/darwin/exporter/formats/coco.py index fbba4d504..a66c1b53a 100644 --- a/darwin/exporter/formats/coco.py +++ b/darwin/exporter/formats/coco.py @@ -1,16 +1,14 @@ from datetime import date +from operator import itemgetter from pathlib import Path from typing import Any, Dict, Iterator, List, Optional from zlib import crc32 -import deprecation import numpy as np import orjson as json -from upolygon import draw_polygon, rle_encode import darwin.datatypes as dt from darwin.utils import convert_polygons_to_sequences -from darwin.version import __version__ DEPRECATION_MESSAGE = """ @@ -41,321 +39,6 @@ def export(annotation_files: Iterator[dt.AnnotationFile], output_dir: Path) -> N f.write(op) -@deprecation.deprecated( - deprecated_in="0.7.7", - removed_in="0.8.0", - current_version=__version__, - details=DEPRECATION_MESSAGE, -) -def build_json(annotation_files: List[dt.AnnotationFile]) -> Dict[str, Any]: - categories: Dict[str, int] = calculate_categories(annotation_files) - tag_categories: Dict[str, int] = calculate_tag_categories(annotation_files) - return { - "info": build_info(), - "licenses": build_licenses(), - "images": build_images(annotation_files, tag_categories), - "annotations": list(build_annotations(annotation_files, categories)), - "categories": list(build_categories(categories)), - "tag_categories": list(build_tag_categories(tag_categories)), - } - - -@deprecation.deprecated( - deprecated_in="0.7.7", - removed_in="0.8.0", - current_version=__version__, - details=DEPRECATION_MESSAGE, -) -def calculate_categories(annotation_files: List[dt.AnnotationFile]) -> Dict[str, int]: - categories: Dict[str, int] = {} - for annotation_file in annotation_files: - for annotation_class in annotation_file.annotation_classes: - if ( - annotation_class.name not in categories - and annotation_class.annotation_type - in [ - "polygon", - "complex_polygon", - "bounding_box", - ] - ): - categories[annotation_class.name] = _calculate_category_id( - annotation_class - ) - return categories - - -@deprecation.deprecated( - deprecated_in="0.7.7", - removed_in="0.8.0", - current_version=__version__, - details=DEPRECATION_MESSAGE, -) -def calculate_tag_categories( - annotation_files: List[dt.AnnotationFile], -) -> Dict[str, int]: - categories: Dict[str, int] = {} - for annotation_file in annotation_files: - for annotation_class in annotation_file.annotation_classes: - if ( - annotation_class.name not in categories - and annotation_class.annotation_type == "tag" - ): - categories[annotation_class.name] = _calculate_category_id( - annotation_class - ) - return categories - - -@deprecation.deprecated( - deprecated_in="0.7.7", - removed_in="0.8.0", - current_version=__version__, - details=DEPRECATION_MESSAGE, -) -def build_info() -> Dict[str, Any]: - # TODO fill out these fields in a meaningful way - today = date.today() - return { - "description": "Exported from Darwin", - "url": "n/a", - "version": "n/a", - "year": today.year, - "contributor": "n/a", - "date_created": today.strftime("%Y/%m/%d"), - } - - -@deprecation.deprecated( - deprecated_in="0.7.7", - removed_in="0.8.0", - current_version=__version__, - details=DEPRECATION_MESSAGE, -) -def build_licenses() -> List[Dict[str, Any]]: - return [{"url": "n/a", "id": 0, "name": "placeholder license"}] - - -@deprecation.deprecated( - deprecated_in="0.7.7", - removed_in="0.8.0", - current_version=__version__, - details=DEPRECATION_MESSAGE, -) -def build_images( - annotation_files: List[dt.AnnotationFile], tag_categories: Dict[str, int] -) -> List[Dict[str, Any]]: - return [ - build_image(annotation_file, tag_categories) - for annotation_file in sorted(annotation_files, key=lambda x: x.seq) - ] - - -@deprecation.deprecated( - deprecated_in="0.7.7", - removed_in="0.8.0", - current_version=__version__, - details=DEPRECATION_MESSAGE, -) -def build_image( - annotation_file: dt.AnnotationFile, tag_categories: Dict[str, int] -) -> Dict[str, Any]: - tags = [ - annotation - for annotation in annotation_file.annotations - if annotation.annotation_class.annotation_type == "tag" - ] - return { - "license": 0, - "file_name": annotation_file.filename, - "coco_url": "n/a", - "height": annotation_file.image_height, - "width": annotation_file.image_width, - "date_captured": "", - "flickr_url": "n/a", - "darwin_url": annotation_file.image_url, - "darwin_workview_url": annotation_file.workview_url, - "id": _build_image_id(annotation_file), - "tag_ids": [tag_categories[tag.annotation_class.name] for tag in tags], - } - - -@deprecation.deprecated( - deprecated_in="0.7.7", - removed_in="0.8.0", - current_version=__version__, - details=DEPRECATION_MESSAGE, -) -def build_annotations( - annotation_files: List[dt.AnnotationFile], categories: Dict[str, int] -) -> Iterator[Optional[Dict[str, Any]]]: - annotation_id = 0 - for annotation_file in annotation_files: - for annotation in annotation_file.annotations: - annotation_id += 1 - annotation_data = build_annotation( - annotation_file, annotation_id, annotation, categories - ) - if annotation_data: - yield annotation_data - - -@deprecation.deprecated( - deprecated_in="0.7.7", - removed_in="0.8.0", - current_version=__version__, - details=DEPRECATION_MESSAGE, -) -def build_annotation( - annotation_file: dt.AnnotationFile, - annotation_id: int, - annotation: dt.Annotation, - categories: Dict[str, int], -) -> Optional[Dict[str, Any]]: - annotation_type = annotation.annotation_class.annotation_type - if annotation_type == "polygon": - sequences = convert_polygons_to_sequences( - annotation.data["path"], rounding=False - ) - x_coords = [s[0::2] for s in sequences] - y_coords = [s[1::2] for s in sequences] - min_x = np.min([np.min(x_coord) for x_coord in x_coords]) - min_y = np.min([np.min(y_coord) for y_coord in y_coords]) - max_x = np.max([np.max(x_coord) for x_coord in x_coords]) - max_y = np.max([np.max(y_coord) for y_coord in y_coords]) - w = max_x - min_x - h = max_y - min_y - # Compute the area of the polygon - poly_area = np.sum( - [ - polygon_area(x_coord, y_coord) - for x_coord, y_coord in zip(x_coords, y_coords) - ] - ) - - return { - "id": annotation_id, - "image_id": _build_image_id(annotation_file), - "category_id": categories[annotation.annotation_class.name], - "segmentation": sequences, - "area": poly_area, - "bbox": [min_x, min_y, w, h], - "iscrowd": 0, - "extra": build_extra(annotation), - } - elif annotation_type == "complex_polygon": - mask = np.zeros((annotation_file.image_height, annotation_file.image_width)) - sequences = convert_polygons_to_sequences(annotation.data["paths"]) - draw_polygon(mask, sequences, 1) - counts = rle_encode(mask) - - x_coords = [s[0::2] for s in sequences] - y_coords = [s[1::2] for s in sequences] - min_x = np.min([np.min(x_coord) for x_coord in x_coords]) - min_y = np.min([np.min(y_coord) for y_coord in y_coords]) - max_x = np.max([np.max(x_coord) for x_coord in x_coords]) - max_y = np.max([np.max(y_coord) for y_coord in y_coords]) - w = max_x - min_x + 1 - h = max_y - min_y + 1 - - return { - "id": annotation_id, - "image_id": _build_image_id(annotation_file), - "category_id": categories[annotation.annotation_class.name], - "segmentation": { - "counts": counts, - "size": [annotation_file.image_height, annotation_file.image_width], - }, - "area": np.sum(mask), - "bbox": [min_x, min_y, w, h], - "iscrowd": 1, - "extra": build_extra(annotation), - } - elif annotation_type == "tag": - pass - elif annotation_type == "bounding_box": - x = annotation.data["x"] - y = annotation.data["y"] - w = annotation.data["w"] - h = annotation.data["h"] - - return build_annotation( - annotation_file, - annotation_id, - dt.make_polygon( - annotation.annotation_class.name, - [ - {"x": x, "y": y}, - {"x": x + w, "y": y}, - {"x": x + w, "y": y + h}, - {"x": x, "y": y + h}, - ], - None, - annotation.subs, - ), - categories, - ) - else: - print(f"skipping unsupported annotation_type '{annotation_type}'") - - -@deprecation.deprecated( - deprecated_in="0.7.7", - removed_in="0.8.0", - current_version=__version__, - details=DEPRECATION_MESSAGE, -) -def build_extra(annotation: dt.Annotation) -> Dict[str, Any]: - data = {} - instance_id_sub = annotation.get_sub("instance_id") - attributes_sub = annotation.get_sub("attributes") - text_sub = annotation.get_sub("text") - - if instance_id_sub: - data["instance_id"] = instance_id_sub.data - if attributes_sub: - data["attributes"] = attributes_sub.data - if text_sub: - data["text"] = text_sub.data - return data - - -@deprecation.deprecated( - deprecated_in="0.7.7", - removed_in="0.8.0", - current_version=__version__, - details=DEPRECATION_MESSAGE, -) -def build_categories(categories: Dict[str, int]) -> Iterator[Dict[str, Any]]: - for name, id in categories.items(): - yield {"id": id, "name": name, "supercategory": "root"} - - -@deprecation.deprecated( - deprecated_in="0.7.7", - removed_in="0.8.0", - current_version=__version__, - details=DEPRECATION_MESSAGE, -) -def build_tag_categories(categories: Dict[str, int]) -> Iterator[Dict[str, Any]]: - for name, id in categories.items(): - yield {"id": id, "name": name} - - -@deprecation.deprecated( - deprecated_in="0.7.7", - removed_in="0.8.0", - current_version=__version__, - details=DEPRECATION_MESSAGE, -) -def polygon_area(x: np.ndarray, y: np.ndarray) -> float: - """ - Returns the area of the input polygon, represented with two numpy arrays - for x and y coordinates. - """ - return 0.5 * np.abs(np.dot(x, np.roll(y, 1)) - np.dot(y, np.roll(x, 1))) - - def _build_json(annotation_files: List[dt.AnnotationFile]) -> Dict[str, Any]: categories: Dict[str, int] = _calculate_categories(annotation_files) tag_categories: Dict[str, int] = _calculate_tag_categories(annotation_files) @@ -378,14 +61,13 @@ def _calculate_categories(annotation_files: List[dt.AnnotationFile]) -> Dict[str and annotation_class.annotation_type in [ "polygon", - "complex_polygon", "bounding_box", ] ): categories[annotation_class.name] = _calculate_category_id( annotation_class ) - return categories + return dict(sorted(categories.items(), key=itemgetter(1))) def _calculate_tag_categories( @@ -401,7 +83,7 @@ def _calculate_tag_categories( categories[annotation_class.name] = _calculate_category_id( annotation_class ) - return categories + return dict(sorted(categories.items(), key=itemgetter(1))) def _calculate_category_id(annotation_class: dt.AnnotationClass) -> int: @@ -494,7 +176,7 @@ def _build_annotation( annotation_type = annotation.annotation_class.annotation_type if annotation_type == "polygon": sequences = convert_polygons_to_sequences( - annotation.data["path"], rounding=False + annotation.data["paths"], rounding=False ) x_coords = [s[0::2] for s in sequences] y_coords = [s[1::2] for s in sequences] @@ -522,34 +204,6 @@ def _build_annotation( "iscrowd": 0, "extra": _build_extra(annotation), } - elif annotation_type == "complex_polygon": - mask = np.zeros((annotation_file.image_height, annotation_file.image_width)) - sequences = convert_polygons_to_sequences(annotation.data["paths"]) - draw_polygon(mask, sequences, 1) - counts = rle_encode(mask) - - x_coords = [s[0::2] for s in sequences] - y_coords = [s[1::2] for s in sequences] - min_x = np.min([np.min(x_coord) for x_coord in x_coords]) - min_y = np.min([np.min(y_coord) for y_coord in y_coords]) - max_x = np.max([np.max(x_coord) for x_coord in x_coords]) - max_y = np.max([np.max(y_coord) for y_coord in y_coords]) - w = max_x - min_x + 1 - h = max_y - min_y + 1 - - return { - "id": annotation_id, - "image_id": _build_image_id(annotation_file), - "category_id": categories[annotation.annotation_class.name], - "segmentation": { - "counts": counts, - "size": [annotation_file.image_height, annotation_file.image_width], - }, - "area": np.sum(mask), - "bbox": [min_x, min_y, w, h], - "iscrowd": 1, - "extra": _build_extra(annotation), - } elif annotation_type == "tag": pass elif annotation_type == "bounding_box": diff --git a/darwin/exporter/formats/cvat.py b/darwin/exporter/formats/cvat.py index 20d6dcfa5..88e685dcd 100644 --- a/darwin/exporter/formats/cvat.py +++ b/darwin/exporter/formats/cvat.py @@ -3,10 +3,8 @@ from typing import Any, Dict, Iterator, List, Optional from xml.etree.ElementTree import Element, SubElement, tostring -import deprecation import darwin.datatypes as dt -from darwin.version import __version__ DEPRECATION_MESSAGE = """ @@ -35,192 +33,6 @@ def export(annotation_files: Iterator[dt.AnnotationFile], output_dir: Path) -> N f.write(tostring(output)) -@deprecation.deprecated( - deprecated_in="0.7.7", - removed_in="0.8.0", - current_version=__version__, - details=DEPRECATION_MESSAGE, -) -def add_subelement_text(parent: Element, name: str, value: Any) -> Element: - sub = SubElement(parent, name) - sub.text = str(value) - return sub - - -@deprecation.deprecated( - deprecated_in="0.7.7", - removed_in="0.8.0", - current_version=__version__, - details=DEPRECATION_MESSAGE, -) -def build_xml(annotation_files: List[dt.AnnotationFile]) -> Element: - label_lookup: Dict[str, int] = build_label_lookup(annotation_files) - root: Element = Element("annotations") - add_subelement_text(root, "version", "1.1") - build_meta(root, annotation_files, label_lookup) - build_images(root, annotation_files, label_lookup) - return root - - -@deprecation.deprecated( - deprecated_in="0.7.7", - removed_in="0.8.0", - current_version=__version__, - details=DEPRECATION_MESSAGE, -) -def build_images( - root: Element, - annotation_files: List[dt.AnnotationFile], - label_lookup: Dict[str, int], -) -> None: - for id, annotation_file in enumerate(annotation_files, 1): - image = SubElement(root, "image") - image.attrib["id"] = str(id) - image.attrib["name"] = annotation_file.filename - image.attrib["width"] = str(annotation_file.image_width) - image.attrib["height"] = str(annotation_file.image_height) - - for annotation in annotation_file.annotations: - build_annotation(image, annotation) - - -@deprecation.deprecated( - deprecated_in="0.7.7", - removed_in="0.8.0", - current_version=__version__, - details=DEPRECATION_MESSAGE, -) -def build_annotation(image: Element, annotation: dt.Annotation) -> None: - if annotation.annotation_class.annotation_type == "bounding_box": - box = SubElement(image, "box") - box.attrib["label"] = annotation.annotation_class.name - box.attrib["xtl"] = str(annotation.data["x"]) - box.attrib["ytl"] = str(annotation.data["y"]) - box.attrib["xbr"] = str(annotation.data["x"] + annotation.data["w"]) - box.attrib["ybr"] = str(annotation.data["y"] + annotation.data["h"]) - box.attrib["occluded"] = "0" - build_attributes(box, annotation) - else: - print(f"[warning] skipping {annotation.annotation_class.annotation_type}") - - -@deprecation.deprecated( - deprecated_in="0.7.7", - removed_in="0.8.0", - current_version=__version__, - details=DEPRECATION_MESSAGE, -) -def build_attributes(box: Element, annotation: dt.Annotation) -> None: - annotation_text: Optional[dt.SubAnnotation] = annotation.get_sub("text") - if annotation_text: - attribute = add_subelement_text(box, "attribute", annotation_text.data) - attribute.attrib["name"] = "__text" - - annotation_instance_id: Optional[dt.SubAnnotation] = annotation.get_sub( - "instance_id" - ) - if annotation_instance_id: - attribute = add_subelement_text( - box, "attribute", str(annotation_instance_id.data) - ) - attribute.attrib["name"] = "__instance_id" - - annotation_attributes: Optional[dt.SubAnnotation] = annotation.get_sub("attributes") - if annotation_attributes: - for attrib in annotation_attributes.data: - attribute = add_subelement_text(box, "attribute", "") - attribute.attrib["name"] = attrib - - -@deprecation.deprecated( - deprecated_in="0.7.7", - removed_in="0.8.0", - current_version=__version__, - details=DEPRECATION_MESSAGE, -) -def build_meta( - root: Element, - annotation_files: List[dt.AnnotationFile], - label_lookup: Dict[str, int], -) -> None: - meta: Element = SubElement(root, "meta") - add_subelement_text( - meta, "dumped", str(datetime.datetime.now(tz=datetime.timezone.utc)) - ) - - task: Element = SubElement(meta, "task") - add_subelement_text(task, "id", 1) - add_subelement_text(task, "name", "exported_task_from_darwin") - add_subelement_text(task, "size", len(annotation_files)) - add_subelement_text(task, "mode", "annotation") - add_subelement_text(task, "overlapp", 0) - add_subelement_text(task, "bugtracker", None) - add_subelement_text(task, "flipped", False) - add_subelement_text( - task, "created", str(datetime.datetime.now(tz=datetime.timezone.utc)) - ) - add_subelement_text( - task, "updated", str(datetime.datetime.now(tz=datetime.timezone.utc)) - ) - - labels: Element = SubElement(task, "labels") - build_labels(labels, label_lookup) - - segments: Element = SubElement(task, "segments") - build_segments(segments, annotation_files) - - owner: Element = SubElement(task, "owner") - add_subelement_text(owner, "username", "example_username") - add_subelement_text(owner, "email", "user@example.com") - - -@deprecation.deprecated( - deprecated_in="0.7.7", - removed_in="0.8.0", - current_version=__version__, - details=DEPRECATION_MESSAGE, -) -def build_segments( - segments: Element, annotation_files: List[dt.AnnotationFile] -) -> None: - segment: Element = SubElement(segments, "segment") - add_subelement_text(segment, "id", 1) - add_subelement_text(segment, "start", 1) - add_subelement_text(segment, "end", len(annotation_files)) - add_subelement_text(segment, "url", "not applicable") - - -@deprecation.deprecated( - deprecated_in="0.7.7", - removed_in="0.8.0", - current_version=__version__, - details=DEPRECATION_MESSAGE, -) -def build_labels(labels: Element, label_lookup: Dict[str, int]) -> None: - for key in label_lookup.keys(): - label: Element = SubElement(labels, "label") - add_subelement_text(label, "name", key) - SubElement(label, "attributes") - - -@deprecation.deprecated( - deprecated_in="0.7.7", - removed_in="0.8.0", - current_version=__version__, - details=DEPRECATION_MESSAGE, -) -def build_label_lookup(annotation_files: List[dt.AnnotationFile]) -> Dict[str, int]: - labels: Dict[str, int] = {} - for annotation_file in annotation_files: - for annotation_class in annotation_file.annotation_classes: - if ( - annotation_class.name not in labels - and annotation_class.annotation_type == "bounding_box" - ): - labels[annotation_class.name] = len(labels) - return labels - - def _add_subelement_text(parent: Element, name: str, value: Any) -> Element: sub = SubElement(parent, name) sub.text = str(value) diff --git a/darwin/exporter/formats/darwin.py b/darwin/exporter/formats/darwin.py index 430f9ca67..becf4a179 100644 --- a/darwin/exporter/formats/darwin.py +++ b/darwin/exporter/formats/darwin.py @@ -1,11 +1,9 @@ from typing import Any, Dict, List -import deprecation import darwin.datatypes as dt # from darwin.datatypes import PolygonPath, PolygonPaths -from darwin.version import __version__ DEPRECATION_MESSAGE = """ @@ -58,10 +56,7 @@ def _build_v2_annotation_data(annotation: dt.Annotation) -> Dict[str, Any]: annotation_data["bounding_box"] = _build_bounding_box_data(annotation.data) elif annotation.annotation_class.annotation_type == "tag": annotation_data["tag"] = {} - elif ( - annotation.annotation_class.annotation_type == "polygon" - or annotation.annotation_class.annotation_type == "complex_polygon" - ): + elif annotation.annotation_class.annotation_type == "polygon": polygon_data = _build_polygon_data(annotation.data) annotation_data["polygon"] = polygon_data annotation_data["bounding_box"] = _build_bounding_box_data(annotation.data) @@ -94,12 +89,7 @@ def _build_polygon_data(data: Dict[str, Any]) -> Dict[str, Any]: Dict[str, List[List[Dict[str, float]]]] The polygon data in the format required for Darwin v2 annotations. """ - - # Complex polygon - if "paths" in data: - return {"paths": data["paths"]} - else: - return {"paths": [data["path"]]} + return {"paths": data["paths"]} def _build_item_data( @@ -167,33 +157,3 @@ def _build_slots_data(slots: List[dt.Slot]) -> List[Dict[str, Any]]: slots_data.append(slot_data) return slots_data - - -@deprecation.deprecated( - deprecated_in="0.7.8", - removed_in="0.8.0", - current_version=__version__, - details=DEPRECATION_MESSAGE, -) -def build_annotation_data(annotation: dt.Annotation) -> Dict[str, Any]: - if annotation.annotation_class.annotation_type == "complex_polygon": - return {"path": annotation.data["paths"]} - - if annotation.annotation_class.annotation_type == "polygon": - return dict( - filter(lambda item: item[0] != "bounding_box", annotation.data.items()) - ) - - return dict(annotation.data) - - -def _build_annotation_data(annotation: dt.Annotation) -> Dict[str, Any]: - if annotation.annotation_class.annotation_type == "complex_polygon": - return {"path": annotation.data["paths"]} - - if annotation.annotation_class.annotation_type == "polygon": - return dict( - filter(lambda item: item[0] != "bounding_box", annotation.data.items()) - ) - - return dict(annotation.data) diff --git a/darwin/exporter/formats/dataloop.py b/darwin/exporter/formats/dataloop.py index 96fc47bdd..5f5bee622 100644 --- a/darwin/exporter/formats/dataloop.py +++ b/darwin/exporter/formats/dataloop.py @@ -1,11 +1,9 @@ from pathlib import Path from typing import Any, Dict, Iterable -import deprecation import orjson as json import darwin.datatypes as dt -from darwin.version import __version__ DEPRECATION_MESSAGE = """ @@ -31,87 +29,6 @@ def export(annotation_files: Iterable[dt.AnnotationFile], output_dir: Path) -> N _export_file(annotation_file, id, output_dir) -@deprecation.deprecated( - deprecated_in="0.7.8", - removed_in="0.8.0", - current_version=__version__, - details=DEPRECATION_MESSAGE, -) -def export_file(annotation_file: dt.AnnotationFile, id: int, output_dir: Path) -> None: - output: Dict[str, Any] = _build_json(annotation_file, id) - output_file_path: Path = (output_dir / annotation_file.filename).with_suffix( - ".json" - ) - with open(output_file_path, "w") as f: - op = json.dumps( - output, option=json.OPT_INDENT_2 | json.OPT_SERIALIZE_NUMPY - ).decode("utf-8") - f.write(op) - - -@deprecation.deprecated( - deprecated_in="0.7.8", - removed_in="0.8.0", - current_version=__version__, - details=DEPRECATION_MESSAGE, -) -def build_json(annotation_file: dt.AnnotationFile, id: int) -> Dict[str, Any]: - return { - "_id": id, - "filename": annotation_file.filename, - "itemMetadata": [], - "annotations": _build_annotations(annotation_file, id), - } - - -@deprecation.deprecated( - deprecated_in="0.7.8", - removed_in="0.8.0", - current_version=__version__, - details=DEPRECATION_MESSAGE, -) -def build_annotations( - annotation_file: dt.AnnotationFile, id: int -) -> Iterable[Dict[str, Any]]: - output = [] - for annotation_id, annotation in enumerate(annotation_file.annotations): - print(annotation) - if annotation.annotation_class.annotation_type == "bounding_box": - entry = { - "id": annotation_id, - "datasetId": "darwin", - "type": "box", - "label": annotation.annotation_class.name, - "attributes": [], - "coordinates": [ - {"x": annotation.data["x"], "y": annotation.data["y"], "z": 0}, - { - "x": annotation.data["x"] + annotation.data["w"], - "y": annotation.data["y"] + annotation.data["h"], - "z": 0, - }, - ], - "metadata": {}, - } - output.append(entry) - elif annotation.annotation_class.annotation_type == "polygon": - entry = { - "id": annotation_id, - "datasetId": "darwin", - "type": "segment", - "label": annotation.annotation_class.name, - "attributes": [], - "coordinates": [ - {"x": point["x"], "y": point["y"], "z": 0} - for point in annotation.data["path"] - ], - "metadata": {}, - } - output.append(entry) - - return output - - def _export_file(annotation_file: dt.AnnotationFile, id: int, output_dir: Path) -> None: output: Dict[str, Any] = _build_json(annotation_file, id) output_file_path: Path = (output_dir / annotation_file.filename).with_suffix( diff --git a/darwin/exporter/formats/helpers/yolo_class_builder.py b/darwin/exporter/formats/helpers/yolo_class_builder.py index ac20a495b..fd5c3e41f 100644 --- a/darwin/exporter/formats/helpers/yolo_class_builder.py +++ b/darwin/exporter/formats/helpers/yolo_class_builder.py @@ -8,7 +8,7 @@ def build_class_index( annotation_files: Iterable[AnnotationFile], - include_types: List[str] = ["bounding_box", "polygon", "complex_polygon"], + include_types: List[str] = ["bounding_box", "polygon"], ) -> ClassIndex: classes = set() for annotation_file in annotation_files: diff --git a/darwin/exporter/formats/instance_mask.py b/darwin/exporter/formats/instance_mask.py index 2991faaa9..e7a31b0d4 100644 --- a/darwin/exporter/formats/instance_mask.py +++ b/darwin/exporter/formats/instance_mask.py @@ -40,8 +40,6 @@ def export(annotation_files: Iterable[dt.AnnotationFile], output_dir: Path) -> N for i, annotation in enumerate(annotations): cat = annotation.annotation_class.name if annotation.annotation_class.annotation_type == "polygon": - polygon = annotation.data["path"] - elif annotation.annotation_class.annotation_type == "complex_polygon": polygon = annotation.data["paths"] else: continue diff --git a/darwin/exporter/formats/mask.py b/darwin/exporter/formats/mask.py index 22d92d2ca..534755844 100644 --- a/darwin/exporter/formats/mask.py +++ b/darwin/exporter/formats/mask.py @@ -138,7 +138,7 @@ def get_render_mode(annotations: List[dt.AnnotationLike]) -> dt.MaskTypes.TypeOf types: Set[str] = set(list_of_types) is_raster_mask = ("mask" in types) and ("raster_layer" in types) - is_polygon = ("polygon" in types) or ("complex_polygon" in types) + is_polygon = "polygon" in types raster_layer_count = len([a for a in types if a == "raster_layer"]) @@ -260,8 +260,6 @@ def render_polygons( categories.append(cat) if a.annotation_class.annotation_type == "polygon": - polygon = a.data["path"] - elif a.annotation_class.annotation_type == "complex_polygon": polygon = a.data["paths"] else: raise ValueError( @@ -418,7 +416,7 @@ def export( masks_dir: Path = output_dir / "masks" masks_dir.mkdir(exist_ok=True, parents=True) annotation_files = list(annotation_files) - accepted_types = ["polygon", "complex_polygon", "raster_layer", "mask"] + accepted_types = ["polygon", "raster_layer", "mask"] all_classes_sets: List[Set[dt.AnnotationClass]] = [ a.annotation_classes for a in annotation_files ] @@ -570,13 +568,10 @@ def offset_polygon(polygon: List, offset_x: int, offset_y: int) -> List: Returns: List: polygon with offset applied """ - if isinstance(polygon[0], list): - return offset_complex_polygon(polygon, offset_x, offset_y) - else: - return offset_simple_polygon(polygon, offset_x, offset_y) + return offset_polygon_paths(polygon, offset_x, offset_y) -def offset_complex_polygon(polygons: List, offset_x: int, offset_y: int) -> List: +def offset_polygon_paths(polygons: List, offset_x: int, offset_y: int) -> List: new_polygons = [] for polygon in polygons: new_polygons.append(offset_simple_polygon(polygon, offset_x, offset_y)) diff --git a/darwin/exporter/formats/nifti.py b/darwin/exporter/formats/nifti.py index da4d1b8bd..c0baec33a 100644 --- a/darwin/exporter/formats/nifti.py +++ b/darwin/exporter/formats/nifti.py @@ -340,12 +340,6 @@ def populate_output_volumes_from_polygons( shift_polygon_coords(polygon_path, pixdims) for polygon_path in frame_data["paths"] ] - elif "path" in frame_data: - # Dealing with a simple polygon - polygons = shift_polygon_coords( - frame_data["path"], - pixdims, - ) else: continue im_mask = convert_polygons_to_mask(polygons, height=height, width=width) diff --git a/darwin/exporter/formats/pascalvoc.py b/darwin/exporter/formats/pascalvoc.py index aa7acd08b..8c698ed93 100644 --- a/darwin/exporter/formats/pascalvoc.py +++ b/darwin/exporter/formats/pascalvoc.py @@ -1,12 +1,9 @@ from pathlib import Path -from typing import Any, Dict, Iterable +from typing import Any, Iterable from xml.etree.ElementTree import Element, SubElement, tostring -import deprecation import darwin.datatypes as dt -from darwin.utils import attempt_decode -from darwin.version import __version__ DEPRECATION_MESSAGE = """ @@ -40,150 +37,6 @@ def export(annotation_files: Iterable[dt.AnnotationFile], output_dir: Path) -> N _export_file(annotation_file, output_dir) -@deprecation.deprecated( - deprecated_in="0.7.10", - removed_in="0.8.0", - current_version=__version__, - details=DEPRECATION_MESSAGE, -) -def export_file(annotation_file: dt.AnnotationFile, output_dir: Path) -> None: - xml = build_xml(annotation_file) - output_file_path = (output_dir / annotation_file.filename).with_suffix(".xml") - output_file_path.parent.mkdir(parents=True, exist_ok=True) - with open(output_file_path, "wb") as f: - f.write(tostring(xml)) - - -@deprecation.deprecated( - deprecated_in="0.7.10", - removed_in="0.8.0", - current_version=__version__, - details=DEPRECATION_MESSAGE, -) -def build_xml(annotation_file: dt.AnnotationFile) -> Element: - root: Element = Element("annotation") - add_subelement_text(root, "folder", "images") - add_subelement_text(root, "filename", annotation_file.filename) - add_subelement_text(root, "path", f"images/{annotation_file.filename}") - - source = SubElement(root, "source") - add_subelement_text(source, "database", "darwin") - - size = SubElement(root, "size") - add_subelement_text(size, "width", str(annotation_file.image_width)) - add_subelement_text(size, "height", str(annotation_file.image_height)) - add_subelement_text(size, "depth", "3") - - add_subelement_text(root, "segmented", "0") - - for annotation in annotation_file.annotations: - annotation_type = annotation.annotation_class.annotation_type - if annotation_type not in ["bounding_box", "polygon", "complex_polygon"]: - continue - - data = annotation.data - sub_annotation = SubElement(root, "object") - add_subelement_text(sub_annotation, "name", annotation.annotation_class.name) - add_subelement_text(sub_annotation, "pose", "Unspecified") - add_subelement_text(sub_annotation, "truncated", "0") - add_subelement_text(sub_annotation, "difficult", "0") - bndbox = SubElement(sub_annotation, "bndbox") - - if annotation_type == "polygon" or annotation_type == "complex_polygon": - data = data.get("bounding_box") - - xmin = data.get("x") - ymin = data.get("y") - xmax = xmin + data.get("w") - ymax = ymin + data.get("h") - add_subelement_text(bndbox, "xmin", str(round(xmin))) - add_subelement_text(bndbox, "ymin", str(round(ymin))) - add_subelement_text(bndbox, "xmax", str(round(xmax))) - add_subelement_text(bndbox, "ymax", str(round(ymax))) - - return root - - -@deprecation.deprecated( - deprecated_in="0.7.10", - removed_in="0.8.0", - current_version=__version__, - details=DEPRECATION_MESSAGE, -) -def add_subelement_text(parent: Element, name: str, value: Any) -> Element: - sub: Element = SubElement(parent, name) - sub.text = value - return sub - - -@deprecation.deprecated( - deprecated_in="0.7.10", - removed_in="0.8.0", - current_version=__version__, - details=REMOVAL_MESSAGE, -) -def convert_file(path: Path) -> Element: - data = attempt_decode(path) - return build_voc(data["image"], data["annotations"]) - - -@deprecation.deprecated( - deprecated_in="0.7.10", - removed_in="0.8.0", - current_version=__version__, - details=REMOVAL_MESSAGE, -) -def save_xml(xml: Element, path: Path) -> None: - with open(path, "wb") as f: - f.write(tostring(xml)) - - -@deprecation.deprecated( - deprecated_in="0.7.10", - removed_in="0.8.0", - current_version=__version__, - details=REMOVAL_MESSAGE, -) -def build_voc( - metadata: Dict[str, Any], annotations: Iterable[Dict[str, Any]] -) -> Element: - print(metadata) - root: Element = Element("annotation") - add_subelement_text(root, "folder", "images") - add_subelement_text(root, "filename", metadata["original_filename"]) - add_subelement_text(root, "path", f"images/{metadata['original_filename']}") - - source: Element = SubElement(root, "source") - add_subelement_text(source, "database", "darwin") - - size: Element = SubElement(root, "size") - add_subelement_text(size, "width", str(metadata["width"])) - add_subelement_text(size, "height", str(metadata["height"])) - add_subelement_text(size, "depth", "3") - - add_subelement_text(root, "segmented", "0") - - for annotation in annotations: - if "bounding_box" not in annotation: - continue - data = annotation["bounding_box"] - sub_annotation = SubElement(root, "object") - add_subelement_text(sub_annotation, "name", annotation["name"]) - add_subelement_text(sub_annotation, "pose", "Unspecified") - add_subelement_text(sub_annotation, "truncated", "0") - add_subelement_text(sub_annotation, "difficult", "0") - bndbox = SubElement(sub_annotation, "bndbox") - add_subelement_text(bndbox, "xmin", str(round(data["x"]))) - add_subelement_text(bndbox, "ymin", str(round(data["y"]))) - add_subelement_text(bndbox, "xmax", str(round(data["x"] + data["w"]))) - add_subelement_text(bndbox, "ymax", str(round(data["y"] + data["h"]))) - - return root - - -###################################### - - def _export_file(annotation_file: dt.AnnotationFile, output_dir: Path) -> None: xml = _build_xml(annotation_file) output_file_path = (output_dir / annotation_file.filename).with_suffix(".xml") @@ -210,7 +63,7 @@ def _build_xml(annotation_file: dt.AnnotationFile) -> Element: for annotation in annotation_file.annotations: annotation_type = annotation.annotation_class.annotation_type - if annotation_type not in ["bounding_box", "polygon", "complex_polygon"]: + if annotation_type not in ["bounding_box", "polygon"]: continue data = annotation.data @@ -221,7 +74,7 @@ def _build_xml(annotation_file: dt.AnnotationFile) -> Element: _add_subelement_text(sub_annotation, "difficult", "0") bndbox = SubElement(sub_annotation, "bndbox") - if annotation_type == "polygon" or annotation_type == "complex_polygon": + if annotation_type == "polygon": data = data.get("bounding_box") xmin = data.get("x") diff --git a/darwin/exporter/formats/yolo.py b/darwin/exporter/formats/yolo.py index 73ac5ddb3..3aeb993d6 100644 --- a/darwin/exporter/formats/yolo.py +++ b/darwin/exporter/formats/yolo.py @@ -45,7 +45,7 @@ def _build_txt(annotation_file: dt.AnnotationFile, class_index: ClassIndex) -> s if annotation_type == "bounding_box": data = annotation.data - elif annotation_type in ["polygon", "complex_polygon"]: + elif annotation_type == "polygon": data = annotation.data data = data.get("bounding_box") else: diff --git a/darwin/importer/formats/coco.py b/darwin/importer/formats/coco.py index 28d4f4f8f..616a4eeb5 100644 --- a/darwin/importer/formats/coco.py +++ b/darwin/importer/formats/coco.py @@ -2,14 +2,12 @@ from pathlib import Path from typing import Dict, Iterator, List, Optional -import deprecation import orjson as json from upolygon import find_contours, rle_decode import darwin.datatypes as dt from darwin.path_utils import deconstruct_full_path from darwin.utils import attempt_decode -from darwin.version import __version__ DEPRECATION_MESSAGE = """ @@ -169,7 +167,7 @@ def parse_annotation( except StopIteration: break paths.append(path) - return dt.make_complex_polygon(category["name"], paths) + return dt.make_polygon(category["name"], paths) elif isinstance(segmentation, list): path = [] points = iter( @@ -196,12 +194,6 @@ def _decode_file(current_encoding: str, path: Path): return list(parse_json(path, data)) -@deprecation.deprecated( - deprecated_in="0.7.12", - removed_in="0.8.0", - current_version=__version__, - details=DEPRECATION_MESSAGE, -) def decode_binary_rle(data: str) -> List[int]: """ Decodes binary rle to integer list rle. diff --git a/darwin/importer/formats/dataloop.py b/darwin/importer/formats/dataloop.py index 4f546ab09..a075b4a2b 100644 --- a/darwin/importer/formats/dataloop.py +++ b/darwin/importer/formats/dataloop.py @@ -1,7 +1,6 @@ from pathlib import Path from typing import Any, Dict, List, Optional, Set - import darwin.datatypes as dt from darwin.exceptions import ( DataloopComplexPolygonsNotYetSupported, @@ -76,6 +75,6 @@ def _parse_annotation(annotation: Dict[str, Any]) -> Optional[dt.Annotation]: raise DataloopComplexPolygonsNotYetSupported() points: List[dt.Point] = [{"x": c["x"], "y": c["y"]} for c in coords[0]] - return dt.make_polygon(annotation_label, point_path=points) + return dt.make_polygon(annotation_label, point_paths=points) return None diff --git a/darwin/importer/formats/nifti.py b/darwin/importer/formats/nifti.py index 018690188..64e2ee097 100644 --- a/darwin/importer/formats/nifti.py +++ b/darwin/importer/formats/nifti.py @@ -350,11 +350,11 @@ def adjust_for_pixdims(x, y, pixdims): ] paths.append(path) if len(paths) > 1: - polygon = dt.make_complex_polygon(class_name, paths) + polygon = dt.make_polygon(class_name, paths) elif len(paths) == 1: polygon = dt.make_polygon( class_name, - point_path=paths[0], + point_paths=paths[0], ) else: return None @@ -364,7 +364,7 @@ def adjust_for_pixdims(x, y, pixdims): return None polygon = dt.make_polygon( class_name, - point_path=[ + point_paths=[ adjust_for_pixdims(x, y, pixdims) for x, y in zip(external_path[0::2], external_path[1::2]) ], diff --git a/darwin/importer/importer.py b/darwin/importer/importer.py index 5d398334f..785182f74 100644 --- a/darwin/importer/importer.py +++ b/darwin/importer/importer.py @@ -36,7 +36,6 @@ from darwin.client import Client from darwin.dataset.remote_dataset import RemoteDataset -import deprecation from rich.console import Console from rich.progress import track from rich.theme import Theme @@ -46,7 +45,6 @@ from darwin.exceptions import IncompatibleOptions, RequestEntitySizeExceeded from darwin.utils import secure_continue_request from darwin.utils.flatten_list import flatten_list -from darwin.version import __version__ logger = getLogger(__name__) @@ -72,13 +70,7 @@ """ -@deprecation.deprecated( # type:ignore - deprecated_in="0.7.12", - removed_in="0.8.0", - current_version=__version__, - details=DEPRECATION_MESSAGE, -) -def build_main_annotations_lookup_table( +def _build_main_annotations_lookup_table( annotation_classes: List[Dict[str, Unknown]] ) -> Dict[str, Unknown]: MAIN_ANNOTATION_TYPES = [ @@ -109,13 +101,7 @@ def build_main_annotations_lookup_table( return lookup -@deprecation.deprecated( # type:ignore - deprecated_in="0.7.12", - removed_in="0.8.0", - current_version=__version__, - details=DEPRECATION_MESSAGE, -) -def find_and_parse( # noqa: C901 +def _find_and_parse( # noqa: C901 importer: Callable[[Path], Union[List[dt.AnnotationFile], dt.AnnotationFile, None]], file_paths: List[PathLike], console: Optional[Console] = None, @@ -183,13 +169,7 @@ def _get_files_for_parsing(file_paths: List[PathLike]) -> List[Path]: return [file for files in packed_files for file in files] -@deprecation.deprecated( # type:ignore - deprecated_in="0.7.12", - removed_in="0.8.0", - current_version=__version__, - details=DEPRECATION_MESSAGE, -) -def build_attribute_lookup(dataset: "RemoteDataset") -> Dict[str, Unknown]: +def _build_attribute_lookup(dataset: "RemoteDataset") -> Dict[str, Unknown]: attributes: List[Dict[str, Unknown]] = dataset.fetch_remote_attributes() lookup: Dict[str, Unknown] = {} for attribute in attributes: @@ -200,13 +180,7 @@ def build_attribute_lookup(dataset: "RemoteDataset") -> Dict[str, Unknown]: return lookup -@deprecation.deprecated( # type:ignore - deprecated_in="0.7.12", - removed_in="0.8.0", - current_version=__version__, - details=DEPRECATION_MESSAGE, -) -def get_remote_files( +def _get_remote_files( dataset: "RemoteDataset", filenames: List[str], chunk_size: int = 100 ) -> Dict[str, Tuple[int, str]]: """ @@ -779,28 +753,28 @@ def import_annotations( # noqa: C901 if not team_classes: raise ValueError("Unable to fetch remote class list.") - classes_in_dataset: dt.DictFreeForm = build_main_annotations_lookup_table( + classes_in_dataset: dt.DictFreeForm = _build_main_annotations_lookup_table( [ cls for cls in team_classes if cls["available"] or cls["name"] in GLOBAL_CLASSES ] ) - classes_in_team: dt.DictFreeForm = build_main_annotations_lookup_table( + classes_in_team: dt.DictFreeForm = _build_main_annotations_lookup_table( [ cls for cls in team_classes if not cls["available"] and cls["name"] not in GLOBAL_CLASSES ] ) - attributes = build_attribute_lookup(dataset) + attributes = _build_attribute_lookup(dataset) console.print("Retrieving local annotations ...", style="info") local_files = [] local_files_missing_remotely = [] # ! Other place we can use multiprocessing - hard to pass in the importer though - maybe_parsed_files: Optional[Iterable[dt.AnnotationFile]] = find_and_parse( + maybe_parsed_files: Optional[Iterable[dt.AnnotationFile]] = _find_and_parse( importer, file_paths, console, use_multi_cpu, cpu_limit ) @@ -823,7 +797,7 @@ def import_annotations( # noqa: C901 chunk_size = 100 while chunk_size > 0: try: - remote_files = get_remote_files(dataset, filenames, chunk_size) + remote_files = _get_remote_files(dataset, filenames, chunk_size) break except RequestEntitySizeExceeded: chunk_size -= 8 @@ -912,9 +886,9 @@ def import_annotations( # noqa: C901 if not maybe_remote_classes: raise ValueError("Unable to fetch remote classes.") - remote_classes = build_main_annotations_lookup_table(maybe_remote_classes) + remote_classes = _build_main_annotations_lookup_table(maybe_remote_classes) else: - remote_classes = build_main_annotations_lookup_table(team_classes) + remote_classes = _build_main_annotations_lookup_table(team_classes) if delete_for_empty: console.print( @@ -1069,15 +1043,17 @@ def _handle_subs( return data -def _handle_complex_polygon( +def _format_polygon_for_import( annotation: dt.Annotation, data: dt.DictFreeForm ) -> dt.DictFreeForm: - if "complex_polygon" in data: - del data["complex_polygon"] - data["polygon"] = { - "path": annotation.data["paths"][0], - "additional_paths": annotation.data["paths"][1:], - } + if "polygon" in data: + if len(annotation.data["paths"]) > 1: + data["polygon"] = { + "path": annotation.data["paths"][0], + "additional_paths": annotation.data["paths"][1:], + } + elif len(annotation.data["paths"]) == 1: + data["polygon"] = {"path": annotation.data["paths"][0]} return data @@ -1145,14 +1121,14 @@ def _get_annotation_data( only_keyframes=True, post_processing=lambda annotation, data: _handle_subs( annotation, - _handle_complex_polygon(annotation, data), + _format_polygon_for_import(annotation, data), annotation_class_id, attributes, ), ) else: data = {annotation_class.annotation_type: annotation.data} - data = _handle_complex_polygon(annotation, data) + data = _format_polygon_for_import(annotation, data) data = _handle_subs(annotation, data, annotation_class_id, attributes) return data diff --git a/darwin/item.py b/darwin/item.py index 2b554e725..6b11fbffc 100644 --- a/darwin/item.py +++ b/darwin/item.py @@ -1,11 +1,9 @@ from dataclasses import dataclass from typing import Any, Dict, List, Optional -import deprecation from pydantic import BaseModel from darwin.path_utils import construct_full_path -from darwin.version import __version__ @dataclass(frozen=True, eq=True) @@ -114,39 +112,3 @@ def parse(cls, raw: Dict[str, Any], dataset_slug: str = "n/a") -> "DatasetItem": "slots": [], } return DatasetItem(**data) - - -@deprecation.deprecated( - deprecated_in="0.7.5", - removed_in="0.8.0", - current_version=__version__, - details="Use the ``DatasetItem.parse`` instead.", -) -def parse_dataset_item(raw: Dict[str, Any]) -> DatasetItem: - """ - Parses the given dictionary into a ``DatasetItem``. Performs no validations. - - Parameters - ---------- - raw : Dict[str, Any] - The dictionary to parse. - - Returns - ------- - DatasetItem - A dataset item with the parsed information. - """ - return DatasetItem( - id=raw["id"], - filename=raw["filename"], - status=raw["status"], - archived=raw["archived"], - filesize=raw["file_size"], - dataset_id=raw["dataset_id"], - dataset_slug="n/a", - seq=raw["seq"], - current_workflow_id=raw.get("current_workflow_id"), - path=raw["path"], - slots=[], - current_workflow=raw.get("current_workflow"), - ) diff --git a/darwin/torch/dataset.py b/darwin/torch/dataset.py index b98592e0c..256a519db 100644 --- a/darwin/torch/dataset.py +++ b/darwin/torch/dataset.py @@ -326,8 +326,6 @@ def get_target(self, index: int) -> Dict[str, Any]: annotations = [] for annotation in target["annotations"]: - annotation_type: str = annotation.annotation_class.annotation_type - path_key = "paths" if annotation_type == "complex_polygon" else "path" # Darwin V2 only has paths (TODO it might be more robust fixes) if "paths" in annotation.data: diff --git a/darwin/utils/utils.py b/darwin/utils/utils.py index 49bf6079f..05a36aa2c 100644 --- a/darwin/utils/utils.py +++ b/darwin/utils/utils.py @@ -19,7 +19,6 @@ cast, ) -import deprecation import json_stream import numpy as np import orjson as json @@ -39,7 +38,6 @@ UnsupportedFileType, ) from darwin.future.data_objects.properties import SelectedProperty -from darwin.version import __version__ if TYPE_CHECKING: from darwin.client import Client @@ -91,26 +89,6 @@ def is_extension_allowed_by_filename(filename: str) -> bool: return any(filename.lower().endswith(ext) for ext in SUPPORTED_EXTENSIONS) -@deprecation.deprecated(deprecated_in="0.8.4", current_version=__version__) -def is_extension_allowed(extension: str) -> bool: - """ - Returns whether or not the given extension is allowed. - @Deprecated. Use is_extension_allowed_by_filename instead, and pass full filename. - This is due to the fact that some extensions now include multiple dots, e.g. .nii.gz - - Parameters - ---------- - extension : str - The extension. - - Returns - ------- - bool - Whether or not the given extension is allowed. - """ - return extension.lower() in SUPPORTED_EXTENSIONS - - def is_image_extension_allowed_by_filename(filename: str) -> bool: """ Returns whether or not the given image extension is allowed. @@ -128,7 +106,6 @@ def is_image_extension_allowed_by_filename(filename: str) -> bool: return any(filename.lower().endswith(ext) for ext in SUPPORTED_IMAGE_EXTENSIONS) -@deprecation.deprecated(deprecated_in="0.8.4", current_version=__version__) def is_image_extension_allowed(extension: str) -> bool: """ Returns whether or not the given image extension is allowed. @@ -146,41 +123,6 @@ def is_image_extension_allowed(extension: str) -> bool: return extension.lower() in SUPPORTED_IMAGE_EXTENSIONS -def is_video_extension_allowed_by_filename(extension: str) -> bool: - """ - Returns whether or not the given image extension is allowed. - - Parameters - ---------- - extension : str - The image extension. - - Returns - ------- - bool - Whether or not the given extension is allowed. - """ - return any(extension.lower().endswith(ext) for ext in SUPPORTED_VIDEO_EXTENSIONS) - - -@deprecation.deprecated(deprecated_in="0.8.4", current_version=__version__) -def is_video_extension_allowed(extension: str) -> bool: - """ - Returns whether or not the given video extension is allowed. - - Parameters - ---------- - extension : str - The video extension. - - Returns - ------- - bool - Whether or not the given extension is allowed. - """ - return extension.lower() in SUPPORTED_VIDEO_EXTENSIONS - - def urljoin(*parts: str) -> str: """ Take as input an unpacked list of strings and joins them to form an URL. @@ -464,13 +406,7 @@ def parse_darwin_json( if "annotations" not in data: return None - if version.major == 2: - return _parse_darwin_v2(path, data) - else: - if "fps" in data["image"] or "frame_count" in data["image"]: - return _parse_darwin_video(path, data, count) - else: - return _parse_darwin_image(path, data, count) + return _parse_darwin_v2(path, data) def stream_darwin_json(path: Path) -> PersistentStreamingJSONObject: @@ -726,61 +662,31 @@ def _parse_darwin_video( return annotation_file -def _parse_darwin_annotation(annotation: Dict[str, Any]) -> Optional[dt.Annotation]: +def _parse_darwin_annotation( + annotation: Dict[str, Any], + only_keyframes: bool = False, + annotation_type: Optional[str] = None, + annotation_data: Optional[Dict] = None, +) -> Optional[dt.Annotation]: slot_names = parse_slot_names(annotation) name: str = annotation["name"] main_annotation: Optional[dt.Annotation] = None - # Darwin JSON 2.0 representation of complex polygons - if ( - "polygon" in annotation - and "paths" in annotation["polygon"] - and len(annotation["polygon"]["paths"]) > 1 - ): + # Darwin JSON 2.0 representation of polygons + if "polygon" in annotation and "paths" in annotation["polygon"]: bounding_box = annotation.get("bounding_box") paths = annotation["polygon"]["paths"] - main_annotation = dt.make_complex_polygon( + main_annotation = dt.make_polygon( name, paths, bounding_box, slot_names=slot_names ) - # Darwin JSON 2.0 representation of simple polygons - elif ( - "polygon" in annotation - and "paths" in annotation["polygon"] - and len(annotation["polygon"]["paths"]) == 1 - ): + + elif "polygon" in annotation and "path" in annotation["polygon"]: bounding_box = annotation.get("bounding_box") - paths = annotation["polygon"]["paths"] + path = annotation["polygon"]["path"] main_annotation = dt.make_polygon( - name, paths[0], bounding_box, slot_names=slot_names + name, path, bounding_box, slot_names=slot_names ) - # Darwin JSON 1.0 representation of complex and simple polygons - elif "polygon" in annotation: - bounding_box = annotation.get("bounding_box") - if "additional_paths" in annotation["polygon"]: - paths = [annotation["polygon"]["path"]] + annotation["polygon"][ - "additional_paths" - ] - main_annotation = dt.make_complex_polygon( - name, paths, bounding_box, slot_names=slot_names - ) - else: - main_annotation = dt.make_polygon( - name, annotation["polygon"]["path"], bounding_box, slot_names=slot_names - ) - # Darwin JSON 1.0 representation of complex polygons - elif "complex_polygon" in annotation: - bounding_box = annotation.get("bounding_box") - if isinstance(annotation["complex_polygon"]["path"][0], list): - paths = annotation["complex_polygon"]["path"] - else: - paths = [annotation["complex_polygon"]["path"]] - - if "additional_paths" in annotation["complex_polygon"]: - paths.extend(annotation["complex_polygon"]["additional_paths"]) - main_annotation = dt.make_complex_polygon( - name, paths, bounding_box, slot_names=slot_names - ) elif "bounding_box" in annotation: bounding_box = annotation["bounding_box"] main_annotation = dt.make_bounding_box( @@ -853,6 +759,10 @@ def _parse_darwin_annotation(annotation: Dict[str, Any]) -> Optional[dt.Annotati raster_layer["dense_rle"], slot_names=slot_names, ) + elif only_keyframes: + main_annotation = make_keyframe_annotation( + annotation_type, annotation_data, name, slot_names + ) if not main_annotation: print(f"[WARNING] Unsupported annotation type: '{annotation.keys()}'") @@ -897,15 +807,155 @@ def _parse_darwin_annotation(annotation: Dict[str, Any]) -> Optional[dt.Annotati return main_annotation +def make_keyframe_annotation( + annotation_type: Optional[str], + annotation_data: Optional[Dict], + name: str, + slot_names: List[str], +) -> dt.Annotation: + if annotation_type == "polygon": + return dt.make_polygon( + name, annotation_data["paths"], annotation_data["bounding_box"] + ) + elif annotation_type == "bounding_box": + return dt.make_bounding_box( + name, + annotation_data["x"], + annotation_data["y"], + annotation_data["w"], + annotation_data["h"], + ) + elif annotation_type == "tag": + return dt.make_tag(name) + elif annotation_type == "line": + return dt.make_line(name, annotation_data["path"]) + elif annotation_type == "keypoint": + return dt.make_keypoint(name, annotation_data["x"], annotation_data["y"]) + elif annotation_type == "ellipse": + return dt.make_ellipse(name, annotation_data) + elif annotation_type == "cuboid": + return dt.make_cuboid(name, annotation_data) + elif annotation_type == "skeleton": + return dt.make_skeleton(name, annotation_data["nodes"]) + elif annotation_type == "table": + return dt.make_table( + name, annotation_data["bounding_box"], annotation_data["cells"] + ) + elif annotation_type == "simple_table": + return dt.make_simple_table( + name, + annotation_data["bounding_box"], + annotation_data["col_offsets"], + annotation_data["row_offsets"], + ) + elif annotation_type == "string": + return dt.make_string(name, annotation_data["sources"]) + elif annotation_type == "graph": + return dt.make_graph(name, annotation_data["nodes"], annotation_data["edges"]) + elif annotation_type == "mask": + return dt.make_mask(name) + elif annotation_type == "raster_layer": + return dt.make_raster_layer( + name, + annotation_data["mask_annotation_ids_mapping"], + annotation_data["total_pixels"], + annotation_data["dense_rle"], + ) + else: + raise ValueError(f"Unsupported annotation type: '{annotation_type}'") + + +def update_annotation_data( + main_annotation_data: Dict[str, Any], + annotation_type: Optional[str], + annotation_data: Optional[Dict], +) -> Tuple[Optional[str], Optional[Dict]]: + if annotation_type == "polygon": + bounding_box = main_annotation_data.get("bounding_box") + paths = main_annotation_data["paths"] + annotation_data = {"paths": paths, "bounding_box": bounding_box} + elif annotation_type == "bounding_box": + annotation_data = { + "x": main_annotation_data["x"], + "y": main_annotation_data["y"], + "w": main_annotation_data["w"], + "h": main_annotation_data["h"], + } + elif annotation_type == "tag": + annotation_data = {} + elif annotation_type == "line": + annotation_data = {"path": main_annotation_data["path"]} + elif annotation_type == "keypoint": + annotation_data = { + "x": main_annotation_data["x"], + "y": main_annotation_data["y"], + } + elif annotation_type == "ellipse": + annotation_data = { + "angle": main_annotation_data["angle"], + "center": main_annotation_data["center"], + "radius": main_annotation_data["radius"], + } + elif annotation_type == "cuboid": + annotation_data = { + "back": main_annotation_data["back"], + "front": main_annotation_data["front"], + } + elif annotation_type == "skeleton": + annotation_data = {"nodes": main_annotation_data["nodes"]} + elif annotation_type == "table": + annotation_type = "table" + annotation_data = { + "bounding_box": main_annotation_data["table"]["bounding_box"], + "cells": main_annotation_data["table"]["cells"], + } + elif annotation_type == "string": + annotation_data = {"sources": main_annotation_data["string"]["sources"]} + elif annotation_type == "graph": + annotation_data = { + "nodes": main_annotation_data["graph"]["nodes"], + "edges": main_annotation_data["graph"]["edges"], + } + elif annotation_type == "mask": + annotation_data = {} + elif annotation_type == "raster_layer": + annotation_data = { + "dense_rle": main_annotation_data["dense_rle"], + "mask_annotation_ids_mapping": main_annotation_data[ + "mask_annotation_ids_mapping" + ], + "total_pixels": main_annotation_data["total_pixels"], + } + + return annotation_data + + def _parse_darwin_video_annotation(annotation: dict) -> Optional[dt.VideoAnnotation]: name = annotation["name"] frame_annotations = {} keyframes: Dict[int, bool] = {} frames = {**annotation.get("frames", {}), **annotation.get("sections", {})} + only_keyframes = annotation.get("only_keyframes", False) + annotation_type, annotation_data = None, None + if only_keyframes: + for f, frame in frames.items(): + annotation_type, annotation_data = get_annotation_type_and_data( + frame, annotation_type, annotation_data + ) + if annotation_type: + break for f, frame in frames.items(): frame_annotations[int(f)] = _parse_darwin_annotation( - {**frame, **{"name": name, "id": annotation.get("id", None)}} + {**frame, **{"name": name, "id": annotation.get("id", None)}}, + only_keyframes, + annotation_type, + annotation_data, ) + # If we hit a keyframe, we need to update annotation_data for frames later on that may be missing a main type + if only_keyframes: + annotation_data = update_annotation_data( + frame_annotations[int(f)].data, annotation_type, annotation_data + ) keyframes[int(f)] = frame.get("keyframe", False) if not frame_annotations or None in frame_annotations.values(): @@ -932,6 +982,84 @@ def _parse_darwin_video_annotation(annotation: dict) -> Optional[dt.VideoAnnotat return main_annotation +def get_annotation_type_and_data( + frame: Dict, annotation_type: str, annotation_data: Dict +) -> Tuple[Optional[str], Optional[Dict]]: + """ + Returns the type of a given video annotation and its data. + """ + + if "polygon" in frame: + if frame["polygon"]["paths"]: + bounding_box = frame.get("bounding_box") + paths = frame["polygon"]["paths"] + annotation_type = "polygon" + annotation_data = {"paths": paths, "bounding_box": bounding_box} + else: + bounding_box = frame.get("bounding_box") + path = frame["polygon"]["paths"] + annotation_type = "polygon" + annotation_data = {"paths": path, "bounding_box": bounding_box} + elif "bounding_box" in frame: + bounding_box = frame["bounding_box"] + annotation_type = "bounding_box" + annotation_data = { + "x": bounding_box["x"], + "y": bounding_box["y"], + "w": bounding_box["w"], + "h": bounding_box["h"], + } + elif "tag" in frame: + annotation_type = "tag" + annotation_data = {} + elif "line" in frame: + annotation_type = "line" + annotation_data = {"path": frame["line"]["path"]} + elif "keypoint" in frame: + annotation_type = "keypoint" + annotation_data = { + "x": frame["keypoint"]["x"], + "y": frame["keypoint"]["y"], + } + elif "ellipse" in frame: + annotation_type = "ellipse" + annotation_data = frame["ellipse"] + elif "cuboid" in frame: + annotation_type = "cuboid" + annotation_data = frame["cuboid"] + elif "skeleton" in frame: + annotation_type = "skeleton" + annotation_data = {"nodes": frame["skeleton"]["nodes"]} + elif "table" in frame: + annotation_type = "table" + annotation_data = { + "bounding_box": frame["table"]["bounding_box"], + "cells": frame["table"]["cells"], + } + elif "string" in frame: + annotation_type = "string" + annotation_data = {"sources": frame["string"]["sources"]} + elif "graph" in frame: + annotation_type = "graph" + annotation_type = { + "nodes": frame["graph"]["nodes"], + "edges": frame["graph"]["edges"], + } + elif "mask" in frame: + annotation_type = "mask" + annotation_data = {} + elif "raster_layer" in frame: + raster_layer = frame["raster_layer"] + annotation_type = "raster_layer" + annotation_data = { + "dense_rle": raster_layer["dense_rle"], + "mask_annotation_ids_mapping": raster_layer["mask_annotation_ids_mapping"], + "total_pixels": raster_layer["total_pixels"], + } + + return annotation_type, annotation_data + + def _parse_darwin_raster_annotation(annotation: dict) -> Optional[dt.Annotation]: if not annotation.get("raster_layer"): raise ValueError("Raster annotation must have a 'raster_layer' field") @@ -1053,6 +1181,7 @@ def split_video_annotation(annotation: dt.AnnotationFile) -> List[dt.AnnotationF urls = annotation.frame_urls or [None] * (annotation.frame_count or 1) frame_annotations = [] for i, frame_url in enumerate(urls): + print(i) annotations = [ a.frames[i] for a in annotation.annotations @@ -1101,7 +1230,7 @@ def ispolygon(annotation: dt.AnnotationClass) -> bool: ------- ``True`` is the given ``AnnotationClass`` is a polygon, ``False`` otherwise. """ - return annotation.annotation_type in ["polygon", "complex_polygon"] + return annotation.annotation_type == "polygon" def convert_polygons_to_sequences( @@ -1169,127 +1298,6 @@ def convert_polygons_to_sequences( return sequences -@deprecation.deprecated( - deprecated_in="0.7.5", - removed_in="0.8.0", - current_version=__version__, - details="Do not use.", -) -def convert_sequences_to_polygons( - sequences: List[Union[List[int], List[float]]], - height: Optional[int] = None, - width: Optional[int] = None, -) -> Dict[str, List[dt.Polygon]]: - """ - Converts a list of polygons, encoded as a list of dictionaries of into a list of nd.arrays - of coordinates. - - Parameters - ---------- - sequences : List[Union[List[int], List[float]]] - List of arrays of coordinates in the format ``[x1, y1, x2, y2, ..., xn, yn]`` or as a list - of them as ``[[x1, y1, x2, y2, ..., xn, yn], ..., [x1, y1, x2, y2, ..., xn, yn]]``. - height : Optional[int], default: None - Maximum height for a polygon coordinate. - width : Optional[int], default: None - Maximum width for a polygon coordinate. - - Returns - ------- - Dict[str, List[dt.Polygon]] - Dictionary with the key ``path`` containing a list of coordinates in the format of - ``[[{x: x1, y:y1}, ..., {x: xn, y:yn}], ..., [{x: x1, y:y1}, ..., {x: xn, y:yn}]]``. - - Raises - ------ - ValueError - If sequences is a falsy value (such as ``[]``) or if it is in an incorrect format. - """ - if not sequences: - raise ValueError("No sequences provided") - # If there is a single sequences composing the instance then this is - # transformed to polygons = [[x1, y1, ..., xn, yn]] - if not isinstance(sequences[0], list): - sequences = [sequences] - - if not isinstance(sequences[0][0], (int, float)): - raise ValueError("Unknown input format") - - def grouped(iterable, n): - return zip(*[iter(iterable)] * n) - - polygons = [] - for sequence in sequences: - path = [] - for x, y in grouped(sequence, 2): - # Clip coordinates to the image size - x = max(min(x, width - 1) if width else x, 0) - y = max(min(y, height - 1) if height else y, 0) - path.append({"x": x, "y": y}) - polygons.append(path) - return {"path": polygons} - - -@deprecation.deprecated( - deprecated_in="0.7.5", - removed_in="0.8.0", - current_version=__version__, - details="Do not use.", -) -def convert_xyxy_to_bounding_box(box: List[Union[int, float]]) -> dt.BoundingBox: - """ - Converts a list of xy coordinates representing a bounding box into a dictionary. - - Parameters - ---------- - box : List[Union[int, float]] - List of arrays of coordinates in the format [x1, y1, x2, y2] - - Returns - ------- - BoundingBox - Bounding box in the format ``{x: x1, y: y1, h: height, w: width}``. - - Raises - ------ - ValueError - If ``box`` has an incorrect format. - """ - if not isinstance(box[0], float) and not isinstance(box[0], int): - raise ValueError("Unknown input format") - - x1, y1, x2, y2 = box - width = x2 - x1 - height = y2 - y1 - return {"x": x1, "y": y1, "w": width, "h": height} - - -@deprecation.deprecated( - deprecated_in="0.7.5", - removed_in="0.8.0", - current_version=__version__, - details="Do not use.", -) -def convert_bounding_box_to_xyxy(box: dt.BoundingBox) -> List[float]: - """ - Converts dictionary representing a bounding box into a list of xy coordinates. - - Parameters - ---------- - box : BoundingBox - Bounding box in the format ``{x: x1, y: y1, h: height, w: width}``. - - Returns - ------- - List[float] - List of arrays of coordinates in the format ``[x1, y1, x2, y2]``. - """ - - x2 = box["x"] + box["width"] - y2 = box["y"] + box["height"] - return [box["x"], box["y"], x2, y2] - - def convert_polygons_to_mask( polygons: List, height: int, width: int, value: Optional[int] = 1 ) -> np.ndarray: diff --git a/tests/darwin/dataset/dataset_utils_test.py b/tests/darwin/dataset/dataset_utils_test.py index 92f4fc18c..cee354aec 100644 --- a/tests/darwin/dataset/dataset_utils_test.py +++ b/tests/darwin/dataset/dataset_utils_test.py @@ -27,20 +27,58 @@ def open_resource_file(): def parsed_annotation_file(): return { + "version": "2.0", + "schema_ref": "https://darwin-public.s3.eu-west-1.amazonaws.com/darwin_json/2.0/schema.json", + "item": { + "name": "test.jpg", + "path": "/", + "slots": [ + { + "type": "image", + "slot_name": "0", + "width": 1920, + "height": 1080, + "source_files": [ + { + "file_name": "test.jpg", + "url": "https://darwin.v7labs.com/test.jpg", + } + ], + } + ], + }, "annotations": [ - {"name": "class_1", "polygon": {"path": []}}, - {"name": "class_1", "polygon": {"path": []}}, - {"name": "class_2", "polygon": {"path": []}}, - {"name": "class_2", "polygon": {"path": []}}, - {"name": "class_2", "polygon": {"path": []}}, - {"name": "class_3", "polygon": {"path": []}}, + { + "name": "class_1", + "polygon": {"paths": [[{"x": 0, "y": 0}]]}, + "slot_names": ["0"], + }, + { + "name": "class_1", + "polygon": {"paths": [[{"x": 0, "y": 0}]]}, + "slot_names": ["0"], + }, + { + "name": "class_2", + "polygon": {"paths": [[{"x": 0, "y": 0}]]}, + "slot_names": ["0"], + }, + { + "name": "class_2", + "polygon": {"paths": [[{"x": 0, "y": 0}]]}, + "slot_names": ["0"], + }, + { + "name": "class_2", + "polygon": {"paths": [[{"x": 0, "y": 0}]]}, + "slot_names": ["0"], + }, + { + "name": "class_3", + "polygon": {"paths": [[{"x": 0, "y": 0}]]}, + "slot_names": ["0"], + }, ], - "image": { - "filename": "test.jpg", - "height": 1080, - "url": "https://darwin.v7labs.com/test.jpg", - "width": 1920, - }, } @@ -73,32 +111,60 @@ def annotations_path(self, tmp_path: Path): def test_builds_correct_mapping_dictionaries(self, annotations_path: Path): payload = { + "version": "2.0", + "schema_ref": "https://darwin-public.s3.eu-west-1.amazonaws.com/darwin_json/2.0/schema.json", + "item": { + "name": "0.jpg", + "path": "/", + "slots": [ + { + "type": "image", + "slot_name": "0", + "source_files": [ + {"file_name": "0.jpg", "url": "https://example.com/0.jpg"} + ], + } + ], + }, "annotations": [ - {"name": "class_1", "polygon": {"path": []}}, + {"name": "class_1", "polygon": {"paths": [[]]}}, { "name": "class_2", "bounding_box": {"x": 0, "y": 0, "w": 100, "h": 100}, }, - {"name": "class_3", "polygon": {"path": []}}, + {"name": "class_3", "polygon": {"paths": [[]]}}, {"name": "class_4", "tag": {}}, - {"name": "class_1", "polygon": {"path": []}}, + {"name": "class_1", "polygon": {"paths": [[]]}}, ], - "image": {"filename": "0.jpg"}, } _create_annotation_file(annotations_path, "0.json", payload) payload = { + "version": "2.0", + "schema_ref": "https://darwin-public.s3.eu-west-1.amazonaws.com/darwin_json/2.0/schema.json", + "item": { + "name": "1.jpg", + "path": "/", + "slots": [ + { + "type": "image", + "slot_name": "0", + "source_files": [ + {"file_name": "1.jpg", "url": "https://example.com/1.jpg"} + ], + } + ], + }, "annotations": [ - {"name": "class_5", "polygon": {"path": []}}, + {"name": "class_5", "polygon": {"paths": [[]]}}, { "name": "class_6", "bounding_box": {"x": 0, "y": 0, "w": 100, "h": 100}, }, - {"name": "class_1", "polygon": {"path": []}}, + {"name": "class_1", "polygon": {"paths": [[]]}}, {"name": "class_4", "tag": {}}, - {"name": "class_1", "polygon": {"path": []}}, + {"name": "class_1", "polygon": {"paths": [[]]}}, ], - "image": {"filename": "1.jpg"}, } _create_annotation_file(annotations_path, "1.json", payload) class_dict, index_dict = extract_classes(annotations_path, "polygon") @@ -123,34 +189,94 @@ def test_extract_multiple_annotation_types(self, annotations_path: Path): annotations_path, "0.json", { + "version": "2.0", + "schema_ref": "https://darwin-public.s3.eu-west-1.amazonaws.com/darwin_json/2.0/schema.json", + "item": { + "name": "0.jpg", + "path": "/", + "slots": [ + { + "type": "image", + "slot_name": "0", + "source_files": [ + { + "file_name": "0.jpg", + "url": "https://example.com/0.jpg", + } + ], + } + ], + }, "annotations": [ - {"name": "class_1", "polygon": {"path": []}}, + { + "name": "class_1", + "polygon": {"paths": [[]]}, + "slot_names": ["0"], + }, { "name": "class_2", "bounding_box": {"x": 0, "y": 0, "w": 100, "h": 100}, + "slot_names": ["0"], + }, + { + "name": "class_3", + "polygon": {"paths": [[]]}, + "slot_names": ["0"], + }, + {"name": "class_4", "slot_names": ["0"]}, + { + "name": "class_1", + "polygon": {"paths": [[]]}, + "slot_names": ["0"], }, - {"name": "class_3", "polygon": {"path": []}}, - {"name": "class_4", "tag": {}}, - {"name": "class_1", "polygon": {"path": []}}, ], - "image": {"filename": "0.jpg"}, }, ) _create_annotation_file( annotations_path, "1.json", { + "version": "2.0", + "schema_ref": "https://darwin-public.s3.eu-west-1.amazonaws.com/darwin_json/2.0/schema.json", + "item": { + "name": "1.jpg", + "path": "/", + "slots": [ + { + "type": "image", + "slot_name": "0", + "source_files": [ + { + "file_name": "1.jpg", + "url": "https://example.com/1.jpg", + } + ], + } + ], + }, "annotations": [ - {"name": "class_5", "polygon": {"path": []}}, + { + "name": "class_5", + "polygon": {"paths": [[]]}, + "slot_names": ["0"], + }, { "name": "class_6", "bounding_box": {"x": 0, "y": 0, "w": 100, "h": 100}, + "slot_names": ["0"], + }, + { + "name": "class_1", + "polygon": {"paths": [[]]}, + "slot_names": ["0"], + }, + {"name": "class_4", "slot_names": ["0"]}, + { + "name": "class_1", + "polygon": {"paths": [[]]}, + "slot_names": ["0"], }, - {"name": "class_1", "polygon": {"path": []}}, - {"name": "class_4", "tag": {}}, - {"name": "class_1", "polygon": {"path": []}}, ], - "image": {"filename": "1.jpg"}, }, ) diff --git a/tests/darwin/dataset/remote_dataset_test.py b/tests/darwin/dataset/remote_dataset_test.py index 3df57d8a5..98adcb6a5 100644 --- a/tests/darwin/dataset/remote_dataset_test.py +++ b/tests/darwin/dataset/remote_dataset_test.py @@ -791,7 +791,7 @@ def fake_download_zip(self, path): with patch.object( RemoteDataset, "get_release", return_value=stub_release_response - ) as get_release_stub: + ): with patch.object(Release, "download_zip", new=fake_download_zip): remote_dataset.pull(only_annotations=True) metadata_path = ( diff --git a/tests/darwin/datatypes_test.py b/tests/darwin/datatypes_test.py index 13852c493..e2fb6d662 100644 --- a/tests/darwin/datatypes_test.py +++ b/tests/darwin/datatypes_test.py @@ -12,7 +12,6 @@ from darwin.datatypes import ( ObjectStore, Point, - make_complex_polygon, make_polygon, parse_property_classes, split_paths_by_metadata, @@ -22,24 +21,24 @@ class TestMakePolygon: def test_it_returns_annotation_with_default_params(self): class_name: str = "class_name" - points: List[Point] = [{"x": 1, "y": 2}, {"x": 3, "y": 4}, {"x": 1, "y": 2}] + points: List[Point] = [[{"x": 1, "y": 2}, {"x": 3, "y": 4}, {"x": 1, "y": 2}]] annotation = make_polygon(class_name, points) - assert_annotation_class(annotation, class_name, "polygon") + assert_annotation_class(annotation, class_name, "polygon", "polygon") - path = annotation.data.get("path") - assert path == points + paths = annotation.data.get("paths") + assert paths == points def test_it_returns_annotation_with_bounding_box(self): class_name: str = "class_name" - points: List[Point] = [{"x": 1, "y": 2}, {"x": 3, "y": 4}, {"x": 1, "y": 2}] + points: List[Point] = [[{"x": 1, "y": 2}, {"x": 3, "y": 4}, {"x": 1, "y": 2}]] bbox: Dict[str, float] = {"x": 1, "y": 2, "w": 2, "h": 2} annotation = make_polygon(class_name, points, bbox) - assert_annotation_class(annotation, class_name, "polygon") + assert_annotation_class(annotation, class_name, "polygon", "polygon") - path = annotation.data.get("path") - assert path == points + paths = annotation.data.get("paths") + assert paths == points class_bbox = annotation.data.get("bounding_box") assert class_bbox == bbox @@ -52,9 +51,9 @@ def test_it_returns_annotation_with_default_params(self): [{"x": 1, "y": 2}, {"x": 3, "y": 4}, {"x": 1, "y": 2}], [{"x": 4, "y": 5}, {"x": 6, "y": 7}, {"x": 4, "y": 5}], ] - annotation = make_complex_polygon(class_name, points) + annotation = make_polygon(class_name, points) - assert_annotation_class(annotation, class_name, "complex_polygon", "polygon") + assert_annotation_class(annotation, class_name, "polygon", "polygon") paths = annotation.data.get("paths") assert paths == points @@ -66,9 +65,9 @@ def test_it_returns_annotation_with_bounding_box(self): [{"x": 4, "y": 5}, {"x": 6, "y": 7}, {"x": 4, "y": 5}], ] bbox: Dict[str, float] = {"x": 1, "y": 2, "w": 2, "h": 2} - annotation = make_complex_polygon(class_name, points, bbox) + annotation = make_polygon(class_name, points, bbox) - assert_annotation_class(annotation, class_name, "complex_polygon", "polygon") + assert_annotation_class(annotation, class_name, "polygon", "polygon") paths = annotation.data.get("paths") assert paths == points diff --git a/tests/darwin/exporter/formats/export_coco_test.py b/tests/darwin/exporter/formats/export_coco_test.py index ecdac9aed..b3471ddf3 100644 --- a/tests/darwin/exporter/formats/export_coco_test.py +++ b/tests/darwin/exporter/formats/export_coco_test.py @@ -19,7 +19,7 @@ def annotation_file(self) -> dt.AnnotationFile: def test_polygon_include_extras(self, annotation_file: dt.AnnotationFile): polygon = dt.Annotation( dt.AnnotationClass("polygon_class", "polygon"), - {"path": [{"x": 1, "y": 1}, {"x": 2, "y": 2}, {"x": 1, "y": 2}]}, + {"paths": [{"x": 1, "y": 1}, {"x": 2, "y": 2}, {"x": 1, "y": 2}]}, [dt.make_instance_id(1)], ) diff --git a/tests/darwin/exporter/formats/export_mask_test.py b/tests/darwin/exporter/formats/export_mask_test.py index b0e01e3be..24bb61593 100644 --- a/tests/darwin/exporter/formats/export_mask_test.py +++ b/tests/darwin/exporter/formats/export_mask_test.py @@ -173,9 +173,7 @@ def annotations() -> List[dt.Annotation]: ), dt.Annotation(dt.AnnotationClass("class_2", "mask"), data={"sparse_rle": []}), dt.Annotation(dt.AnnotationClass("class_3", "polygon"), data={"path": "data"}), - dt.Annotation( - dt.AnnotationClass("class_4", "complex_polygon"), data={"paths": "data"} - ), + dt.Annotation(dt.AnnotationClass("class_4", "polygon"), data={"paths": "data"}), ] @@ -232,12 +230,14 @@ def test_beyond_polygon_beyond_window() -> None: dt.Annotation( dt.AnnotationClass("cat1", "polygon"), { - "path": [ - {"x": -1, "y": -1}, - {"x": -1, "y": 1}, - {"x": 1, "y": 1}, - {"x": 1, "y": -1}, - {"x": -1, "y": -1}, + "paths": [ + [ + {"x": -1, "y": -1}, + {"x": -1, "y": 1}, + {"x": 1, "y": 1}, + {"x": 1, "y": -1}, + {"x": -1, "y": -1}, + ] ], "bounding_box": {"x": -1, "y": -1, "w": 2, "h": 2}, }, @@ -268,13 +268,13 @@ def test_beyond_polygon_beyond_window() -> None: assert not errors -def test_beyond_complex_polygon() -> None: +def test_beyond_multi_path_polygons() -> None: mask = np.zeros((5, 5), dtype=np.uint8) colours: dt.MaskTypes.ColoursDict = {} categories: dt.MaskTypes.CategoryList = ["__background__"] annotations: List[dt.AnnotationLike] = [ dt.Annotation( - dt.AnnotationClass("cat3", "complex_polygon"), + dt.AnnotationClass("cat3", "polygon"), { "paths": [ [ @@ -333,7 +333,7 @@ def test_render_polygons() -> None: dt.Annotation( dt.AnnotationClass("cat1", "polygon"), { - "path": [ + "paths": [ {"x": 10, "y": 10}, {"x": 20, "y": 10}, {"x": 20, "y": 20}, @@ -345,7 +345,7 @@ def test_render_polygons() -> None: dt.Annotation( dt.AnnotationClass("cat2", "polygon"), { - "path": [ + "paths": [ {"x": 30, "y": 30}, {"x": 40, "y": 30}, {"x": 40, "y": 40}, @@ -357,7 +357,7 @@ def test_render_polygons() -> None: dt.Annotation( dt.AnnotationClass("cat1", "polygon"), { - "path": [ + "paths": [ {"x": 50, "y": 50}, {"x": 60, "y": 50}, {"x": 60, "y": 60}, @@ -369,12 +369,12 @@ def test_render_polygons() -> None: dt.Annotation( dt.AnnotationClass("cat1", "polygon"), { - "path": [{"x": 10, "y": 80}, {"x": 20, "y": 80}, {"x": 20, "y": 60}], + "paths": [{"x": 10, "y": 80}, {"x": 20, "y": 80}, {"x": 20, "y": 60}], "bounding_box": base_bb, }, ), dt.Annotation( - dt.AnnotationClass("cat3", "complex_polygon"), + dt.AnnotationClass("cat3", "polygon"), { "paths": [ [ @@ -736,7 +736,7 @@ def test_class_mappings_preserved_on_large_export(tmpdir) -> None: dt.Annotation( dt.AnnotationClass("cat1", "polygon"), { - "path": [ + "paths": [ {"x": 0, "y": 0}, {"x": 1, "y": 0}, {"x": 1, "y": 1}, @@ -748,7 +748,7 @@ def test_class_mappings_preserved_on_large_export(tmpdir) -> None: dt.Annotation( dt.AnnotationClass("cat2", "polygon"), { - "path": [ + "paths": [ {"x": 2, "y": 2}, {"x": 4, "y": 2}, {"x": 4, "y": 4}, @@ -760,7 +760,7 @@ def test_class_mappings_preserved_on_large_export(tmpdir) -> None: dt.Annotation( dt.AnnotationClass("cat3", "polygon"), { - "path": [ + "paths": [ {"x": 5, "y": 5}, {"x": 8, "y": 5}, {"x": 8, "y": 8}, @@ -772,7 +772,7 @@ def test_class_mappings_preserved_on_large_export(tmpdir) -> None: dt.Annotation( dt.AnnotationClass("cat1", "polygon"), { - "path": [ + "paths": [ {"x": 4, "y": 0}, {"x": 5, "y": 0}, {"x": 5, "y": 1}, @@ -782,7 +782,7 @@ def test_class_mappings_preserved_on_large_export(tmpdir) -> None: }, ), dt.Annotation( - dt.AnnotationClass("cat4", "complex_polygon"), + dt.AnnotationClass("cat4", "polygon"), { "paths": [ [ diff --git a/tests/darwin/exporter/formats/export_pascalvoc_test.py b/tests/darwin/exporter/formats/export_pascalvoc_test.py index d31251267..1bff42daf 100644 --- a/tests/darwin/exporter/formats/export_pascalvoc_test.py +++ b/tests/darwin/exporter/formats/export_pascalvoc_test.py @@ -80,10 +80,10 @@ def test_xml_has_bounding_boxes_of_polygons(self): assert_xml_element_text(bndbox, "xmax", "1803") assert_xml_element_text(bndbox, "ymax", "983") - def test_xml_has_bounding_boxes_of_complex_polygons(self): + def test_xml_has_bounding_boxes_of_multi_path_polygons(self): annotation_class = AnnotationClass( name="rubber", - annotation_type="complex_polygon", + annotation_type="polygon", annotation_internal_type="polygon", ) annotation = Annotation( diff --git a/tests/darwin/importer/formats/import_darwin_test.py b/tests/darwin/importer/formats/import_darwin_test.py index 6dd6c2335..d6db42aec 100644 --- a/tests/darwin/importer/formats/import_darwin_test.py +++ b/tests/darwin/importer/formats/import_darwin_test.py @@ -22,53 +22,148 @@ def test_it_parses_slot_names_properly_if_present_for_sequences( self, file_path: Path ): json: str = """ - { - "dataset": "test", - "image": { - "width": 2479, - "height": 3508, - "fps": 30.0, - "original_filename": "Invoice.pdf", - "filename": "Invoice.pdf", - "url": "https://staging.v7labs.com/api/v2/teams/rafals-team/files/1a46356d-005b-4095-98fc-fc4ea6d7294a/original", - "path": "/", - "workview_url": "https://staging.v7labs.com/teams/rafals-team/items/0182e9d2-d217-3260-52db-d7828422f86b/workview", - "frame_count": 2, - "frame_urls": [ - "https://staging.v7labs.com/api/v2/teams/rafals-team/files/1a46356d-005b-4095-98fc-fc4ea6d7294a/sections/0", - "https://staging.v7labs.com/api/v2/teams/rafals-team/files/1a46356d-005b-4095-98fc-fc4ea6d7294a/sections/1" - ] + { + "version": "2.0", + "schema_ref": "https://darwin-public.s3.eu-west-1.amazonaws.com/darwin_json/2.0/schema.json", + "item": { + "name": "Invoice.pdf", + "path": "/", + "source_info": { + "item_id": "018e3385-822c-fbab-e766-acd624a8a273", + "dataset": { + "name": "folder_test", + "slug": "folder_test", + "dataset_management_url": "https://darwin.v7labs.com/datasets/722603/dataset-management" + }, + "team": { + "name": "V7 John", + "slug": "v7-john" + }, + "workview_url": "https://darwin.v7labs.com/workview?dataset=722603&item=018e3385-822c-fbab-e766-acd624a8a273" }, - "annotations": [ + "slots": [ { - "frames": { - "0": { - "bounding_box": { - "h": 338.29, - "w": 444.87, - "x": 845.6, - "y": 1056.57 - }, - "keyframe": true, - "text": { - "text": "some weird text" - } - } - }, - "id": "d89a5895-c721-420b-9c7d-d71880e3679b", - "interpolate_algorithm": "linear-1.1", - "interpolated": true, - "name": "address", - "segments": [ - [0, 2] - ], - "slot_names": [ - "my_slot" - ] + "type": "video", + "slot_name": "0", + "width": 1920, + "height": 1080, + "fps": 1, + "thumbnail_url": "https://darwin.v7labs.com/api/v2/teams/v7-john/files/926ee041-03c0-4354-aea2-8b9db422341d/thumbnail", + "source_files": [ + { + "file_name": "mini_uct.mp4", + "url": "https://darwin.v7labs.com/api/v2/teams/v7-john/uploads/db035ac4-4327-4b11-85b7-432c0e09c896" + } + ], + "frame_count": 8, + "frame_urls": [ + "https://darwin.v7labs.com/api/v2/teams/v7-john/files/926ee041-03c0-4354-aea2-8b9db422341d/sections/0", + "https://darwin.v7labs.com/api/v2/teams/v7-john/files/926ee041-03c0-4354-aea2-8b9db422341d/sections/1", + "https://darwin.v7labs.com/api/v2/teams/v7-john/files/926ee041-03c0-4354-aea2-8b9db422341d/sections/2", + "https://darwin.v7labs.com/api/v2/teams/v7-john/files/926ee041-03c0-4354-aea2-8b9db422341d/sections/3", + "https://darwin.v7labs.com/api/v2/teams/v7-john/files/926ee041-03c0-4354-aea2-8b9db422341d/sections/4", + "https://darwin.v7labs.com/api/v2/teams/v7-john/files/926ee041-03c0-4354-aea2-8b9db422341d/sections/5", + "https://darwin.v7labs.com/api/v2/teams/v7-john/files/926ee041-03c0-4354-aea2-8b9db422341d/sections/6", + "https://darwin.v7labs.com/api/v2/teams/v7-john/files/926ee041-03c0-4354-aea2-8b9db422341d/sections/7" + ] } ] + }, + "annotations": [ + { + "frames": { + "0": { + "bounding_box": { + "h": 152.502, + "w": 309.579, + "x": 466.6561, + "y": 338.5544 + }, + "keyframe": true + }, + "1": { + "bounding_box": { + "h": 152.502, + "w": 309.579, + "x": 466.6561, + "y": 338.5544 + }, + "keyframe": false + }, + "2": { + "bounding_box": { + "h": 152.502, + "w": 309.579, + "x": 466.6561, + "y": 338.5544 + }, + "keyframe": false + }, + "3": { + "bounding_box": { + "h": 152.502, + "w": 309.579, + "x": 466.6561, + "y": 338.5544 + }, + "keyframe": false + }, + "4": { + "bounding_box": { + "h": 152.502, + "w": 309.579, + "x": 466.6561, + "y": 338.5544 + }, + "keyframe": false + }, + "5": { + "bounding_box": { + "h": 152.502, + "w": 309.579, + "x": 466.6561, + "y": 338.5544 + }, + "keyframe": false + }, + "6": { + "bounding_box": { + "h": 152.502, + "w": 309.579, + "x": 466.6561, + "y": 338.5544 + }, + "keyframe": false + }, + "7": { + "bounding_box": { + "h": 152.502, + "w": 309.579, + "x": 466.6561, + "y": 338.5544 + }, + "keyframe": true + } + }, + "hidden_areas": [], + "id": "06865ac8-d2f8-4b8f-a653-9cd08df5b3f5", + "interpolate_algorithm": "linear-1.1", + "interpolated": true, + "name": "curia", + "properties": [], + "ranges": [ + [ + 0, + 8 + ] + ], + "slot_names": [ + "0" + ] } - """ + ] + } + """ file_path.write_text(json) @@ -83,37 +178,151 @@ def test_it_parses_slot_names_properly_if_present_for_sequences( assert annotation_file.annotations for annotation in annotation_file.annotations: - assert annotation.slot_names == ["my_slot"] + assert annotation.slot_names == ["0"] def test_it_parses_slot_names_properly_if_present_for_images(self, file_path: Path): json: str = """ { - "dataset": "test", - "image": { - "width": 500, - "height": 375, - "original_filename": "my_image.jpg", - "filename": "my_image.jpg", - "url": "https://staging.v7labs.com/api/v2/teams/rafals-team/files/d119a57f-bbbb-4b9b-a7a2-6dcb16a59e98/original", - "thumbnail_url": "https://staging.v7labs.com/api/v2/teams/rafals-team/files/d119a57f-bbbb-4b9b-a7a2-6dcb16a59e98/thumbnail", - "path": "/", - "workview_url": "https://staging.v7labs.com/teams/rafals-team/items/0182e9d2-d217-681d-2448-197904d2e05c/workview" + "version": "2.0", + "schema_ref": "https://darwin-public.s3.eu-west-1.amazonaws.com/darwin_json/2.0/schema.json", + "item": { + "name": "ferrari-laferrari.jpg", + "path": "/", + "source_info": { + "item_id": "018c4450-d91d-ff3e-b226-60d48b66f86e", + "dataset": { + "name": "bbox", + "slug": "bbox", + "dataset_management_url": "https://darwin.v7labs.com/datasets/623079/dataset-management" + }, + "team": { + "name": "V7 John", + "slug": "v7-john" + }, + "workview_url": "https://darwin.v7labs.com/workview?dataset=623079&item=018c4450-d91d-ff3e-b226-60d48b66f86e" }, - "annotations": [ + "slots": [ { - "bounding_box": { - "h": 151.76, - "w": 140.89, - "x": 252.09, - "y": 173.49 - }, - "id": "ab8035d0-61b8-4294-b348-085461555df8", - "name": "dog", - "slot_names": [ - "my_slot" - ] + "type": "image", + "slot_name": "0", + "width": 640, + "height": 425, + "thumbnail_url": "https://darwin.v7labs.com/api/v2/teams/v7-john/files/ddc5cbc2-8438-4e36-8ab6-43e2f3746bf1/thumbnail", + "source_files": [ + { + "file_name": "000000007751.jpg", + "url": "https://darwin.v7labs.com/api/v2/teams/v7-john/uploads/3395d29a-7539-4a51-a3ca-c7a95f460345" + } + ] } ] + }, + "annotations": [ + { + "bounding_box": { + "h": 53.963699999999996, + "w": 83.7195, + "x": 32.7817, + "y": 53.9638 + }, + "id": "8940a690-d8a9-4c83-9f59-38f0ef780246", + "name": "new-class-2", + "polygon": { + "paths": [ + [ + { + "x": 65.0591, + "y": 53.9638 + }, + { + "x": 32.7817, + "y": 107.9275 + }, + { + "x": 116.5012, + "y": 104.9015 + } + ] + ] + }, + "properties": [], + "slot_names": [ + "0" + ] + }, + { + "id": "782618fb-4c69-436e-80cb-71765d255dbf", + "name": "skeleton-test", + "properties": [], + "skeleton": { + "nodes": [ + { + "name": "node", + "occluded": false, + "x": 264.7754, + "y": 121.5445 + }, + { + "name": "2", + "occluded": false, + "x": 245.1335, + "y": 107.3425 + }, + { + "name": "3", + "occluded": false, + "x": 240.4646, + "y": 125.4178 + }, + { + "name": "4", + "occluded": false, + "x": 280.3923, + "y": 137.468 + } + ] + }, + "slot_names": [ + "0" + ] + }, + { + "id": "b6bea00c-c8a4-4d34-b72f-88567d9e8cd5", + "name": "skeleton-test", + "properties": [], + "skeleton": { + "nodes": [ + { + "name": "node", + "occluded": false, + "x": 136.1702, + "y": 306.1308 + }, + { + "name": "2", + "occluded": false, + "x": 145.1629, + "y": 291.263 + }, + { + "name": "3", + "occluded": false, + "x": 147.3005, + "y": 310.1857 + }, + { + "name": "4", + "occluded": false, + "x": 129.0203, + "y": 322.8007 + } + ] + }, + "slot_names": [ + "0" + ] + } + ] } """ @@ -123,64 +332,159 @@ def test_it_parses_slot_names_properly_if_present_for_images(self, file_path: Pa assert annotation_file is not None assert annotation_file.path == file_path - assert annotation_file.filename == "my_image.jpg" + assert annotation_file.filename == "ferrari-laferrari.jpg" assert annotation_file.annotation_classes assert annotation_file.remote_path == "/" assert annotation_file.annotations for annotation in annotation_file.annotations: - assert annotation.slot_names == ["my_slot"] + assert annotation.slot_names == ["0"] def test_it_skips_slot_names_when_no_slot_names_for_sequences( self, file_path: Path ): json: str = """ - { - "dataset": "test", - "image": { - "width": 2479, - "height": 3508, - "fps": 30.0, - "original_filename": "Invoice.pdf", - "filename": "Invoice.pdf", - "url": "https://staging.v7labs.com/api/v2/teams/rafals-team/files/1a46356d-005b-4095-98fc-fc4ea6d7294a/original", - "path": "/", - "workview_url": "https://staging.v7labs.com/teams/rafals-team/items/0182e9d2-d217-3260-52db-d7828422f86b/workview", - "frame_count": 2, - "frame_urls": [ - "https://staging.v7labs.com/api/v2/teams/rafals-team/files/1a46356d-005b-4095-98fc-fc4ea6d7294a/sections/0", - "https://staging.v7labs.com/api/v2/teams/rafals-team/files/1a46356d-005b-4095-98fc-fc4ea6d7294a/sections/1" - ] + { + "version": "2.0", + "schema_ref": "https://darwin-public.s3.eu-west-1.amazonaws.com/darwin_json/2.0/schema.json", + "item": { + "name": "Invoice.pdf", + "path": "/", + "source_info": { + "item_id": "018e3385-822c-fbab-e766-acd624a8a273", + "dataset": { + "name": "folder_test", + "slug": "folder_test", + "dataset_management_url": "https://darwin.v7labs.com/datasets/722603/dataset-management" + }, + "team": { + "name": "V7 John", + "slug": "v7-john" + }, + "workview_url": "https://darwin.v7labs.com/workview?dataset=722603&item=018e3385-822c-fbab-e766-acd624a8a273" }, - "annotations": [ + "slots": [ { - "frames": { - "0": { - "bounding_box": { - "h": 338.29, - "w": 444.87, - "x": 845.6, - "y": 1056.57 - }, - "keyframe": true, - "text": { - "text": "some weird text" - } - } - }, - "id": "d89a5895-c721-420b-9c7d-d71880e3679b", - "interpolate_algorithm": "linear-1.1", - "interpolated": true, - "name": "address", - "segments": [ - [0, 2] - ] + "type": "video", + "slot_name": "", + "width": 1920, + "height": 1080, + "fps": 1, + "thumbnail_url": "https://darwin.v7labs.com/api/v2/teams/v7-john/files/926ee041-03c0-4354-aea2-8b9db422341d/thumbnail", + "source_files": [ + { + "file_name": "mini_uct.mp4", + "url": "https://darwin.v7labs.com/api/v2/teams/v7-john/uploads/db035ac4-4327-4b11-85b7-432c0e09c896" + } + ], + "frame_count": 8, + "frame_urls": [ + "https://darwin.v7labs.com/api/v2/teams/v7-john/files/926ee041-03c0-4354-aea2-8b9db422341d/sections/0", + "https://darwin.v7labs.com/api/v2/teams/v7-john/files/926ee041-03c0-4354-aea2-8b9db422341d/sections/1", + "https://darwin.v7labs.com/api/v2/teams/v7-john/files/926ee041-03c0-4354-aea2-8b9db422341d/sections/2", + "https://darwin.v7labs.com/api/v2/teams/v7-john/files/926ee041-03c0-4354-aea2-8b9db422341d/sections/3", + "https://darwin.v7labs.com/api/v2/teams/v7-john/files/926ee041-03c0-4354-aea2-8b9db422341d/sections/4", + "https://darwin.v7labs.com/api/v2/teams/v7-john/files/926ee041-03c0-4354-aea2-8b9db422341d/sections/5", + "https://darwin.v7labs.com/api/v2/teams/v7-john/files/926ee041-03c0-4354-aea2-8b9db422341d/sections/6", + "https://darwin.v7labs.com/api/v2/teams/v7-john/files/926ee041-03c0-4354-aea2-8b9db422341d/sections/7" + ] } ] + }, + "annotations": [ + { + "frames": { + "0": { + "bounding_box": { + "h": 152.502, + "w": 309.579, + "x": 466.6561, + "y": 338.5544 + }, + "keyframe": true + }, + "1": { + "bounding_box": { + "h": 152.502, + "w": 309.579, + "x": 466.6561, + "y": 338.5544 + }, + "keyframe": false + }, + "2": { + "bounding_box": { + "h": 152.502, + "w": 309.579, + "x": 466.6561, + "y": 338.5544 + }, + "keyframe": false + }, + "3": { + "bounding_box": { + "h": 152.502, + "w": 309.579, + "x": 466.6561, + "y": 338.5544 + }, + "keyframe": false + }, + "4": { + "bounding_box": { + "h": 152.502, + "w": 309.579, + "x": 466.6561, + "y": 338.5544 + }, + "keyframe": false + }, + "5": { + "bounding_box": { + "h": 152.502, + "w": 309.579, + "x": 466.6561, + "y": 338.5544 + }, + "keyframe": false + }, + "6": { + "bounding_box": { + "h": 152.502, + "w": 309.579, + "x": 466.6561, + "y": 338.5544 + }, + "keyframe": false + }, + "7": { + "bounding_box": { + "h": 152.502, + "w": 309.579, + "x": 466.6561, + "y": 338.5544 + }, + "keyframe": true + } + }, + "hidden_areas": [], + "id": "06865ac8-d2f8-4b8f-a653-9cd08df5b3f5", + "interpolate_algorithm": "linear-1.1", + "interpolated": true, + "name": "curia", + "properties": [], + "ranges": [ + [ + 0, + 8 + ] + ], + "slot_names": [] } - """ - + ] + } + """ file_path.write_text(json) annotation_file: Optional[AnnotationFile] = parse_path(file_path) @@ -199,29 +503,54 @@ def test_it_skips_slot_names_when_no_slot_names_for_sequences( def test_it_skips_slot_names_when_no_slot_names_for_images(self, file_path: Path): json: str = """ { - "dataset": "test", - "image": { - "width": 500, - "height": 375, - "original_filename": "my_image.jpg", - "filename": "my_image.jpg", - "url": "https://staging.v7labs.com/api/v2/teams/rafals-team/files/d119a57f-bbbb-4b9b-a7a2-6dcb16a59e98/original", - "thumbnail_url": "https://staging.v7labs.com/api/v2/teams/rafals-team/files/d119a57f-bbbb-4b9b-a7a2-6dcb16a59e98/thumbnail", - "path": "/", + "version": "2.0", + "schema_ref": "https://darwin-public.s3.eu-west-1.amazonaws.com/darwin_json/2.0/schema.json", + "item": { + "name": "my_image.jpg", + "path": "/", + "source_info": { + "item_id": "0182e9d2-d217-681d-2448-197904d2e05c", + "dataset": { + "name": "test", + "slug": "test", + "dataset_management_url": "https://staging.v7labs.com/teams/rafals-team/items/0182e9d2-d217-681d-2448-197904d2e05c/workview" + }, + "team": { + "name": "rafals-team", + "slug": "rafals-team" + }, "workview_url": "https://staging.v7labs.com/teams/rafals-team/items/0182e9d2-d217-681d-2448-197904d2e05c/workview" }, - "annotations": [ + "slots": [ { - "bounding_box": { - "h": 151.76, - "w": 140.89, - "x": 252.09, - "y": 173.49 - }, - "id": "ab8035d0-61b8-4294-b348-085461555df8", - "name": "dog" + "type": "image", + "slot_name": "", + "width": 500, + "height": 375, + "thumbnail_url": "https://staging.v7labs.com/api/v2/teams/rafals-team/files/d119a57f-bbbb-4b9b-a7a2-6dcb16a59e98/thumbnail", + "source_files": [ + { + "file_name": "my_image.jpg", + "url": "https://staging.v7labs.com/api/v2/teams/rafals-team/files/d119a57f-bbbb-4b9b-a7a2-6dcb16a59e98/original" + } + ] } ] + }, + "annotations": [ + { + "bounding_box": { + "h": 151.76, + "w": 140.89, + "x": 252.09, + "y": 173.49 + }, + "id": "ab8035d0-61b8-4294-b348-085461555df8", + "name": "dog", + "properties": [], + "slot_names": [] + } + ] } """ diff --git a/tests/darwin/importer/formats/import_labelbox_test.py b/tests/darwin/importer/formats/import_labelbox_test.py index f01f7b320..755c0e93a 100644 --- a/tests/darwin/importer/formats/import_labelbox_test.py +++ b/tests/darwin/importer/formats/import_labelbox_test.py @@ -384,14 +384,16 @@ def test_it_imports_polygon_images(self, file_path: Path): assert_polygon( polygon_annotation, [ - {"x": 3665.814, "y": 351.628}, - {"x": 3762.93, "y": 810.419}, - {"x": 3042.93, "y": 914.233}, + [ + {"x": 3665.814, "y": 351.628}, + {"x": 3762.93, "y": 810.419}, + {"x": 3042.93, "y": 914.233}, + ], ], ) annotation_class = polygon_annotation.annotation_class - assert_annotation_class(annotation_class, "Fish", "polygon") + assert_annotation_class(annotation_class, "Fish", "polygon", "polygon") def test_it_imports_point_images(self, file_path: Path): json: str = """ @@ -728,8 +730,8 @@ def assert_bbox(annotation: Annotation, x: float, y: float, h: float, w: float) assert data.get("h") == h -def assert_polygon(annotation: Annotation, points: List[Point]) -> None: - actual_points = annotation.data.get("path") +def assert_polygon(annotation: Annotation, points: List[List[Point]]) -> None: + actual_points = annotation.data.get("paths") assert actual_points assert actual_points == points diff --git a/tests/darwin/importer/formats/import_superannotate_test.py b/tests/darwin/importer/formats/import_superannotate_test.py index 2b28ada40..5af2c5edd 100644 --- a/tests/darwin/importer/formats/import_superannotate_test.py +++ b/tests/darwin/importer/formats/import_superannotate_test.py @@ -471,14 +471,18 @@ def test_imports_polygon_vectors( assert_polygon( polygon_annotation, [ - {"x": 1053, "y": 587.2}, - {"x": 1053.1, "y": 586}, - {"x": 1053.8, "y": 585.4}, + [ + {"x": 1053, "y": 587.2}, + {"x": 1053.1, "y": 586}, + {"x": 1053.8, "y": 585.4}, + ], ], ) annotation_class = polygon_annotation.annotation_class - assert_annotation_class(annotation_class, "Person-polygon", "polygon") + assert_annotation_class( + annotation_class, "Person-polygon", "polygon", "polygon" + ) def test_raises_if_polyline_has_missing_points( self, annotations_file_path: Path, classes_file_path: Path @@ -890,8 +894,8 @@ def assert_bbox(annotation: Annotation, x: float, y: float, h: float, w: float) assert data.get("h") == h -def assert_polygon(annotation: Annotation, points: List[Point]) -> None: - actual_points = annotation.data.get("path") +def assert_polygon(annotation: Annotation, points: List[List[Point]]) -> None: + actual_points = annotation.data.get("paths") assert actual_points assert actual_points == points diff --git a/tests/darwin/importer/importer_mcpu_test.py b/tests/darwin/importer/importer_mcpu_test.py index ce7528325..9168fc5ea 100644 --- a/tests/darwin/importer/importer_mcpu_test.py +++ b/tests/darwin/importer/importer_mcpu_test.py @@ -99,7 +99,7 @@ def tearDown(self) -> None: def test_uses_mpire_if_use_multi_cpu_true( self, mock_wp: MagicMock, mock_gffp: MagicMock, mock_gmcus: MagicMock ) -> None: - from darwin.importer.importer import find_and_parse + from darwin.importer.importer import _find_and_parse mock_gmcus.return_value = (2, True) mock_gffp.return_value = [ @@ -123,7 +123,7 @@ def __exit__(self, *args) -> None: # type: ignore mock_wp.return_value = MockWorkerPool() mock_map.return_value = ["1", "2"] - result = find_and_parse( + result = _find_and_parse( mock_importer, [Path("example_dir")], self.mock_console, True, 2 ) @@ -138,7 +138,7 @@ def __exit__(self, *args) -> None: # type: ignore def test_runs_single_threaded_if_use_multi_cpu_false( self, mock_wp: MagicMock, mock_gffp: MagicMock ) -> None: - from darwin.importer.importer import find_and_parse + from darwin.importer.importer import _find_and_parse mock_gffp.return_value = [ Path("example_dir/file1.txt"), @@ -148,7 +148,7 @@ def test_runs_single_threaded_if_use_multi_cpu_false( mock_importer = MagicMock() mock_importer.side_effect = ["1", "2"] - result = find_and_parse( + result = _find_and_parse( mock_importer, [Path("example_dir")], self.mock_console, False ) @@ -163,7 +163,7 @@ def test_runs_single_threaded_if_use_multi_cpu_false( def test_returns_list_if_solo_value( self, mock_wp: MagicMock, mock_gffp: MagicMock, mock_gmcus: MagicMock ) -> None: - from darwin.importer.importer import find_and_parse + from darwin.importer.importer import _find_and_parse mock_gmcus.return_value = (2, True) mock_gffp.return_value = [ @@ -187,7 +187,7 @@ def __exit__(self, *args) -> None: # type: ignore mock_wp.return_value = MockWorkerPool() mock_map.return_value = "1" - result = find_and_parse( + result = _find_and_parse( mock_importer, [Path("example_dir")], self.mock_console, True, 2 ) @@ -203,7 +203,7 @@ def __exit__(self, *args) -> None: # type: ignore def test_returns_none_if_pool_raises_error( self, mock_wp: MagicMock, mock_gffp: MagicMock, mock_gmcus: MagicMock ) -> None: - from darwin.importer.importer import find_and_parse + from darwin.importer.importer import _find_and_parse mock_gmcus.return_value = (2, True) mock_gffp.return_value = [ @@ -227,7 +227,7 @@ def __exit__(self, *args) -> None: # type: ignore mock_wp.return_value = MockWorkerPool() mock_map.side_effect = Exception("Test") - result = find_and_parse( + result = _find_and_parse( mock_importer, [Path("example_dir")], self.mock_console, True, 2 ) diff --git a/tests/darwin/importer/importer_test.py b/tests/darwin/importer/importer_test.py index 15c87696f..b7822e19c 100644 --- a/tests/darwin/importer/importer_test.py +++ b/tests/darwin/importer/importer_test.py @@ -106,29 +106,35 @@ def test_handle_subs() -> None: assert result == expected_result -def test__handle_complex_polygon() -> None: - from darwin.importer.importer import _handle_complex_polygon +def test__format_polygon_for_import() -> None: + from darwin.importer.importer import _format_polygon_for_import - assert _handle_complex_polygon( - {}, - { - "example": "data", - "example2": "data2", - "example3": "data3", - }, - ) == { # type: ignore - "example": "data", - "example2": "data2", - "example3": "data3", - } - assert _handle_complex_polygon( + # Test case when "polygon" key is not in data + assert _format_polygon_for_import( dt.Annotation( - dt.AnnotationClass("Class", "bbox"), {"paths": [1, 2, 3, 4, 5]}, [], [] + dt.AnnotationClass("Class", "polygon"), {"paths": [1, 2, 3, 4, 5]}, [], [] ), - {"complex_polygon": "test_data"}, - ) == { - "polygon": {"path": 1, "additional_paths": [2, 3, 4, 5]}, - } + {"example": "data"}, + ) == {"example": "data"} + + # Test case when "polygon" key is in data and there is more than one path + assert _format_polygon_for_import( + dt.Annotation( + dt.AnnotationClass("Class", "polygon"), + {"paths": [[1, 2, 3, 4, 5], [6, 7, 8, 9, 10]]}, + [], + [], + ), + {"polygon": {"paths": [[1, 2, 3, 4, 5], [6, 7, 8, 9, 10]]}}, + ) == {"polygon": {"path": [1, 2, 3, 4, 5], "additional_paths": [[6, 7, 8, 9, 10]]}} + + # Test case when "polygon" key is in data and there is only one path + assert _format_polygon_for_import( + dt.Annotation( + dt.AnnotationClass("Class", "polygon"), {"paths": [[1, 2, 3, 4, 5]]}, [], [] + ), + {"polygon": {"paths": [[1, 2, 3, 4, 5]]}}, + ) == {"polygon": {"path": [1, 2, 3, 4, 5]}} def test__annotators_or_reviewers_to_payload() -> None: @@ -189,7 +195,7 @@ def test__get_annotation_data() -> None: annotation.data = "TEST DATA" - with patch_factory("_handle_complex_polygon") as mock_hcp, patch_factory( + with patch_factory("_format_polygon_for_import") as mock_hcp, patch_factory( "_handle_subs" ) as mock_hs, patch.object( dt.VideoAnnotation, "get_data", return_value="TEST VIDEO DATA" @@ -208,7 +214,7 @@ def test__get_annotation_data() -> None: assert mock_hcp.call_count == 1 assert mock_hs.call_count == 1 - with patch_factory("_handle_complex_polygon") as mock_hcp, patch_factory( + with patch_factory("_format_polygon_for_import") as mock_hcp, patch_factory( "_handle_subs" ) as mock_hs: from darwin.importer.importer import _get_annotation_data @@ -482,7 +488,7 @@ def test__parse_empty_masks_video(raster_layer_video_annotations) -> None: def test__import_annotations() -> None: - with patch_factory("_handle_complex_polygon") as mock_hcp, patch_factory( + with patch_factory("_format_polygon_for_import") as mock_hcp, patch_factory( "_handle_reviewers" ) as mock_hr, patch_factory("_handle_annotators") as mock_ha, patch_factory( "_handle_subs" diff --git a/tests/darwin/torch/dataset_test.py b/tests/darwin/torch/dataset_test.py index 21a60de4a..fac24bf6c 100644 --- a/tests/darwin/torch/dataset_test.py +++ b/tests/darwin/torch/dataset_test.py @@ -181,7 +181,7 @@ def test_loads_object_detection_dataset_from_polygon_annotations( "iscrowd": [0], } - def test_loads_object_detection_dataset_from_complex_polygon_annotations( + def test_loads_object_detection_dataset_from_multi_path_polygon_annotations( self, team_slug_darwin_json_v2: str, local_config_file: Config, @@ -260,7 +260,7 @@ def test_loads_instance_segmentation_dataset_from_polygon_annotations( assert label["image_path"] == str(dataset.dataset_path / "images" / "0.png") assert label["width"] == 50 - def test_loads_instance_segmentation_dataset_from_complex_polygon_annotations( + def test_loads_instance_segmentation_dataset_from_multi_path_polygon_annotations( self, team_slug_darwin_json_v2: str, local_config_file: Config, diff --git a/tests/darwin/utils/find_files_test.py b/tests/darwin/utils/find_files_test.py index 64c4dadce..d56fd352b 100644 --- a/tests/darwin/utils/find_files_test.py +++ b/tests/darwin/utils/find_files_test.py @@ -8,7 +8,6 @@ from darwin.utils import ( SUPPORTED_EXTENSIONS, SUPPORTED_IMAGE_EXTENSIONS, - SUPPORTED_VIDEO_EXTENSIONS, find_files, ) @@ -132,9 +131,8 @@ def dependency_factory(self) -> Dependencies: """ from darwin.utils import is_extension_allowed_by_filename as ieabf from darwin.utils import is_image_extension_allowed_by_filename as iieabf - from darwin.utils import is_video_extension_allowed_by_filename as iveabf - return self.Dependencies(ieabf=ieabf, iveabf=iveabf, iieabf=iieabf) + return self.Dependencies(ieabf=ieabf, iieabf=iieabf) def test_ieabf_returns_true_for_a_valid_extension(self): valid_extensions = [ @@ -152,21 +150,6 @@ def test_ieabf_returns_false_for_an_invalid_extension(self): self.assertFalse(all(results)) - def test_iveabf_returns_true_for_a_valid_extension(self): - results = [ - self.dependency_factory().iveabf(file) - for file in SUPPORTED_VIDEO_EXTENSIONS - ] - - self.assertTrue(all(results)) - - def test_iveabf_returns_false_for_an_invalid_extension(self): - results = [ - self.dependency_factory().iveabf(file) for file in self.fake_invalid_files - ] - - self.assertFalse(all(results)) - def test_iieabf_returns_true_for_a_valid_extension(self): results = [ self.dependency_factory().iieabf(file) diff --git a/tests/darwin/utils_test.py b/tests/darwin/utils_test.py index 58a8ccac5..6f7e16459 100644 --- a/tests/darwin/utils_test.py +++ b/tests/darwin/utils_test.py @@ -8,11 +8,9 @@ from darwin.utils import ( get_response_content, has_json_content_type, - is_extension_allowed, is_image_extension_allowed, is_project_dir, is_unix_like_os, - is_video_extension_allowed, parse_darwin_json, urljoin, validate_data_against_schema, @@ -46,24 +44,12 @@ def test_validates_correct_data(self): class TestExtensions: - def test_returns_true_for_allowed_extensions(self): - assert is_extension_allowed(".png") - - def test_returns_false_for_unknown_extensions(self): - assert not is_extension_allowed(".mkv") - def test_returns_true_for_allowed_image_extensions(self): assert is_image_extension_allowed(".png") def test_returns_false_for_unknown_image_extensions(self): assert not is_image_extension_allowed(".not_an_image") - def test_returns_true_for_allowed_video_extensions(self): - assert is_video_extension_allowed(".mp4") - - def test_returns_false_for_unknown_video_extensions(self): - assert not is_video_extension_allowed(".not_video") - class TestUrlJoin: def test_returns_an_url(self): @@ -111,31 +97,51 @@ class TestParseDarwinJson: def test_parses_darwin_images_correctly(self, tmp_path): content = """ { - "image": { - "width": 497, - "height": 778, - "original_filename": "P49-RediPad-ProPlayLEFTY_442.jpg", - "filename": "P49-RediPad-ProPlayLEFTY_442.jpg", - "url": "", - "path": "/tmp_files" + "version": "2.0", + "schema_ref": "https://darwin-public.s3.eu-west-1.amazonaws.com/darwin_json/2.0/schema.json", + "item": { + "name": "P49-RediPad-ProPlayLEFTY_442.jpg", + "path": "/tmp_files", + "slots": [ + { + "type": "image", + "slot_name": "0", + "width": 497, + "height": 778, + "source_files": [ + { + "file_name": "P49-RediPad-ProPlayLEFTY_442.jpg", + "url": "" + } + ] + } + ] }, "annotations": [ { - "keypoint": { - "x": 207.97048950195312, - "y": 449.39691162109375 - }, - "name": "left_knee" + "id": "unique_id_1", + "name": "left_knee", + "keypoint": { + "x": 207.97048950195312, + "y": 449.39691162109375 + }, + "slot_names": [ + "0" + ] }, { - "keypoint": { - "x": 302.9606018066406, - "y": 426.13946533203125 - }, - "name": "left_ankle" + "id": "unique_id_2", + "name": "left_ankle", + "keypoint": { + "x": 302.9606018066406, + "y": 426.13946533203125 + }, + "slot_names": [ + "0" + ] } ] - } + } """ directory = tmp_path / "imports" @@ -149,7 +155,7 @@ def test_parses_darwin_images_correctly(self, tmp_path): assert annotation_file.filename == "P49-RediPad-ProPlayLEFTY_442.jpg" assert annotation_file.dataset_name is None assert annotation_file.version == dt.AnnotationFileVersion( - major=1, minor=0, suffix="" + major=2, minor=0, suffix="" ) assert len(annotation_file.annotations) == 2 @@ -166,68 +172,65 @@ def test_parses_darwin_images_correctly(self, tmp_path): def test_parses_darwin_videos_correctly(self, tmp_path): content = """ { - "dataset": "my-dataset", - "image": { - "width": 3840, - "height": 2160, - "fps": 0.0, - "original_filename": "above tractor.mp4", - "filename": "above tractor.mp4", - "url": "https://my-website.com/api/videos/209/original", + "version": "2.0", + "schema_ref": "https://darwin-public.s3.eu-west-1.amazonaws.com/darwin_json_2_0.schema.json", + "item": { + "name": "above tractor.mp4", "path": "/", - "workview_url": "https://my-website.com/workview?dataset=102&image=530", - "frame_count": 343, - "frame_urls": [ - "https://my-website.com/api/videos/209/frames/0" + "source_info": { + "item_id": "018a4ad2-41cb-5b6a-8141-fe1afeb65746", + "team": {"name": "Test Team", "slug": "test-team"}, + "dataset": { + "name": "My dataset", + "slug": "my-dataset", + "dataset_management_url": "https://my-website.com/datasets/018a4ad2-41cb-5b6a-8141-fe1afeb65746/dataset-management" + }, + "workview_url": "https://my-website.com/workview?dataset=102&image=530" + }, + "slots": [ + { + "type": "video", + "slot_name": "0", + "width": 3840, + "height": 2160, + "fps": 0.0, + "thumbnail_url": "https://my-website.com/api/videos/209/thumbnail", + "source_files": [ + { + "file_name": "above tractor.mp4", + "url": "https://my-website.com/api/videos/209/original" + } + ], + "frame_count": 343, + "frame_urls": ["https://my-website.com/api/videos/209/frames/0"] + } ] }, "annotations": [ { "frames": { "3": { - "bounding_box": { - "h": 547.0, - "w": 400.0, - "x": 363.0, - "y": 701.0 - }, - "instance_id": { - "value": 119 - }, + "bounding_box": {"h": 547.0, "w": 400.0, "x": 363.0, "y": 701.0}, + "instance_id": {"value": 119}, "keyframe": true, "polygon": { - "path": [ - { - "x": 748.0, - "y": 732.0 - }, - { - "x": 751.0, - "y": 735.0 - }, - { - "x": 748.0, - "y": 733.0 - } + "paths": [ + [ + {"x": 748.0, "y": 732.0}, + {"x": 751.0, "y": 735.0}, + {"x": 748.0, "y": 733.0} + ] ] } } }, + "id": "f8f5f235-bd47-47be-b4fe-07d49e0177a7", "interpolate_algorithm": "linear-1.1", "interpolated": true, "name": "Hand", - "segments": [ - [ - 3, - 46 - ] - ], - "hidden_areas": [ - [ - 5, - 8 - ] - ] + "ranges": [[3, 46]], + "hidden_areas": [[5, 8]], + "slot_names": ["0"] } ] } @@ -238,13 +241,13 @@ def test_parses_darwin_videos_correctly(self, tmp_path): import_file = directory / "darwin-file.json" import_file.write_text(content) - annotation_file: dt.AnnotationFile = parse_darwin_json(import_file, None) + annotation_file: dt.AnnotationFile = parse_darwin_json(import_file) assert annotation_file.path == import_file assert annotation_file.filename == "above tractor.mp4" - assert annotation_file.dataset_name is None + assert annotation_file.dataset_name == "My dataset" assert annotation_file.version == dt.AnnotationFileVersion( - major=1, minor=0, suffix="" + major=2, minor=0, suffix="" ) assert len(annotation_file.annotations) == 1 @@ -271,20 +274,22 @@ def test_parses_darwin_videos_correctly(self, tmp_path): annotation_class=dt.AnnotationClass( name="Hand", annotation_type="polygon", - annotation_internal_type=None, + annotation_internal_type="polygon", ), frames={ 3: dt.Annotation( annotation_class=dt.AnnotationClass( name="Hand", annotation_type="polygon", - annotation_internal_type=None, + annotation_internal_type="polygon", ), data={ - "path": [ - {"x": 748.0, "y": 732.0}, - {"x": 751.0, "y": 735.0}, - {"x": 748.0, "y": 733.0}, + "paths": [ + [ + {"x": 748.0, "y": 732.0}, + {"x": 751.0, "y": 735.0}, + {"x": 748.0, "y": 733.0}, + ] ], "bounding_box": { "x": 363.0, @@ -296,12 +301,22 @@ def test_parses_darwin_videos_correctly(self, tmp_path): subs=[ dt.SubAnnotation(annotation_type="instance_id", data=119) ], + slot_names=[], + annotators=None, + reviewers=None, + id="f8f5f235-bd47-47be-b4fe-07d49e0177a7", + properties=None, ) }, keyframes={3: True}, segments=[[3, 46]], hidden_areas=[[5, 8]], interpolated=True, + slot_names=["0"], + annotators=None, + reviewers=None, + id="f8f5f235-bd47-47be-b4fe-07d49e0177a7", + properties=None, ) ] @@ -554,21 +569,56 @@ def test_returns_None_if_no_annotations_exist(self, tmp_path): def test_uses_a_default_path_if_one_is_missing(self, tmp_path): content = """ + { + "version": "2.0", + "schema_ref": "https://darwin-public.s3.eu-west-1.amazonaws.com/darwin_json/2.0/schema.json", + "item": { + "name": "P49-RediPad-ProPlayLEFTY_442.jpg", + "path": "/", + "source_info": { + "item_id": "unknown", + "dataset": { + "name": "unknown", + "slug": "unknown", + "dataset_management_url": "unknown" + }, + "team": { + "name": "unknown", + "slug": "unknown" + }, + "workview_url": "unknown" + }, + "slots": [ { - "image": { - "original_filename": "P49-RediPad-ProPlayLEFTY_442.jpg", - "filename": "P49-RediPad-ProPlayLEFTY_442.jpg" - }, - "annotations": [ - { - "keypoint": { - "x": 207.97048950195312, - "y": 449.39691162109375 - }, - "name": "left_knee" - } + "type": "image", + "slot_name": "0", + "width": 640, + "height": 425, + "thumbnail_url": "unknown", + "source_files": [ + { + "file_name": "P49-RediPad-ProPlayLEFTY_442.jpg", + "url": "unknown" + } ] } + ] + }, + "annotations": [ + { + "id": "unknown", + "name": "left_knee", + "properties": [], + "keypoint": { + "x": 207.97048950195312, + "y": 449.39691162109375 + }, + "slot_names": [ + "0" + ] + } + ] + } """ directory = tmp_path / "imports" @@ -582,58 +632,148 @@ def test_uses_a_default_path_if_one_is_missing(self, tmp_path): def test_imports_a_skeleton(self, tmp_path): content = """ + { + "version": "2.0", + "schema_ref": "https://darwin-public.s3.eu-west-1.amazonaws.com/darwin_json/2.0/schema.json", + "item": { + "name": "ferrari-laferrari.jpg", + "path": "/", + "source_info": { + "item_id": "018c4450-d91d-ff3e-b226-60d48b66f86e", + "dataset": { + "name": "bbox", + "slug": "bbox", + "dataset_management_url": "https://darwin.v7labs.com/datasets/623079/dataset-management" + }, + "team": { + "name": "V7 John", + "slug": "v7-john" + }, + "workview_url": "https://darwin.v7labs.com/workview?dataset=623079&item=018c4450-d91d-ff3e-b226-60d48b66f86e" + }, + "slots": [ { - "dataset": "cars", - "image": { - "filename": "ferrari-laferrari.jpg" - }, - "annotations": [ + "type": "image", + "slot_name": "0", + "width": 640, + "height": 425, + "thumbnail_url": "https://darwin.v7labs.com/api/v2/teams/v7-john/files/ddc5cbc2-8438-4e36-8ab6-43e2f3746bf1/thumbnail", + "source_files": [ + { + "file_name": "000000007751.jpg", + "url": "https://darwin.v7labs.com/api/v2/teams/v7-john/uploads/3395d29a-7539-4a51-a3ca-c7a95f460345" + } + ] + } + ] + }, + "annotations": [ + { + "bounding_box": { + "h": 53.963699999999996, + "w": 83.7195, + "x": 32.7817, + "y": 53.9638 + }, + "id": "8940a690-d8a9-4c83-9f59-38f0ef780246", + "name": "new-class-2", + "polygon": { + "paths": [ + [ { - "bounding_box": { - "h": 547.0, - "w": 1709.0, - "x": 96.0, - "y": 437.0 - }, - "name": "car", - "polygon": { - "path": [ - { - "x": 1805.0, - "y": 586.0 - }, - { - "x": 1802.0, - "y": 586.0 - }, - { - "x": 1805.0, - "y": 588.0 - } - ] - } + "x": 65.0591, + "y": 53.9638 }, { - "name": "wheels", - "skeleton": { - "nodes": [ - { - "name": "1", - "occluded": false, - "x": 829.56, - "y": 824.5 - }, - { - "name": "2", - "occluded": false, - "x": 1670.5, - "y": 741.76 - } - ] - } + "x": 32.7817, + "y": 107.9275 + }, + { + "x": 116.5012, + "y": 104.9015 } ] + ] + }, + "properties": [], + "slot_names": [ + "0" + ] + }, + { + "id": "782618fb-4c69-436e-80cb-71765d255dbf", + "name": "skeleton-test", + "properties": [], + "skeleton": { + "nodes": [ + { + "name": "node", + "occluded": false, + "x": 264.7754, + "y": 121.5445 + }, + { + "name": "2", + "occluded": false, + "x": 245.1335, + "y": 107.3425 + }, + { + "name": "3", + "occluded": false, + "x": 240.4646, + "y": 125.4178 + }, + { + "name": "4", + "occluded": false, + "x": 280.3923, + "y": 137.468 + } + ] + }, + "slot_names": [ + "0" + ] + }, + { + "id": "b6bea00c-c8a4-4d34-b72f-88567d9e8cd5", + "name": "skeleton-test", + "properties": [], + "skeleton": { + "nodes": [ + { + "name": "node", + "occluded": false, + "x": 136.1702, + "y": 306.1308 + }, + { + "name": "2", + "occluded": false, + "x": 145.1629, + "y": 291.263 + }, + { + "name": "3", + "occluded": false, + "x": 147.3005, + "y": 310.1857 + }, + { + "name": "4", + "occluded": false, + "x": 129.0203, + "y": 322.8007 + } + ] + }, + "slot_names": [ + "0" + ] } + ] + } """ directory = tmp_path / "imports" @@ -653,89 +793,148 @@ def test_imports_a_skeleton(self, tmp_path): def test_imports_multiple_skeletetons(self, tmp_path): content = """ + { + "version": "2.0", + "schema_ref": "https://darwin-public.s3.eu-west-1.amazonaws.com/darwin_json/2.0/schema.json", + "item": { + "name": "ferrari-laferrari.jpg", + "path": "/", + "source_info": { + "item_id": "018c4450-d91d-ff3e-b226-60d48b66f86e", + "dataset": { + "name": "bbox", + "slug": "bbox", + "dataset_management_url": "https://darwin.v7labs.com/datasets/623079/dataset-management" + }, + "team": { + "name": "V7 John", + "slug": "v7-john" + }, + "workview_url": "https://darwin.v7labs.com/workview?dataset=623079&item=018c4450-d91d-ff3e-b226-60d48b66f86e" + }, + "slots": [ { - "dataset":"cars", - "image":{ - "filename":"ferrari-laferrari.jpg" - }, - "annotations":[ + "type": "image", + "slot_name": "0", + "width": 640, + "height": 425, + "thumbnail_url": "https://darwin.v7labs.com/api/v2/teams/v7-john/files/ddc5cbc2-8438-4e36-8ab6-43e2f3746bf1/thumbnail", + "source_files": [ + { + "file_name": "000000007751.jpg", + "url": "https://darwin.v7labs.com/api/v2/teams/v7-john/uploads/3395d29a-7539-4a51-a3ca-c7a95f460345" + } + ] + } + ] + }, + "annotations": [ + { + "bounding_box": { + "h": 53.963699999999996, + "w": 83.7195, + "x": 32.7817, + "y": 53.9638 + }, + "id": "8940a690-d8a9-4c83-9f59-38f0ef780246", + "name": "new-class-2", + "polygon": { + "paths": [ + [ { - "bounding_box":{ - "h":547.0, - "w":1709.0, - "x":96.0, - "y":437.0 - }, - "name":"car", - "polygon":{ - "path":[ - { - "x":1805.0, - "y":586.0 - }, - { - "x":1802.0, - "y":586.0 - }, - { - "x":1805.0, - "y":588.0 - } - ] - } + "x": 65.0591, + "y": 53.9638 }, { - "name":"wheels", - "skeleton":{ - "nodes":[ - { - "name":"1", - "occluded":false, - "x":829.56, - "y":824.5 - }, - { - "name":"2", - "occluded":false, - "x":1670.5, - "y":741.76 - } - ] - } + "x": 32.7817, + "y": 107.9275 }, { - "name":"door", - "skeleton":{ - "nodes":[ - { - "name":"1", - "occluded":false, - "x":867.86, - "y":637.16 - }, - { - "name":"2", - "occluded":false, - "x":1100.21, - "y":810.09 - }, - { - "name":"3", - "occluded":false, - "x":1298.45, - "y":856.56 - }, - { - "name":"4", - "occluded":false, - "x":1234.63, - "y":492.12 - } - ] - } + "x": 116.5012, + "y": 104.9015 } ] + ] + }, + "properties": [], + "slot_names": [ + "0" + ] + }, + { + "id": "782618fb-4c69-436e-80cb-71765d255dbf", + "name": "skeleton-test", + "properties": [], + "skeleton": { + "nodes": [ + { + "name": "node", + "occluded": false, + "x": 264.7754, + "y": 121.5445 + }, + { + "name": "2", + "occluded": false, + "x": 245.1335, + "y": 107.3425 + }, + { + "name": "3", + "occluded": false, + "x": 240.4646, + "y": 125.4178 + }, + { + "name": "4", + "occluded": false, + "x": 280.3923, + "y": 137.468 + } + ] + }, + "slot_names": [ + "0" + ] + }, + { + "id": "b6bea00c-c8a4-4d34-b72f-88567d9e8cd5", + "name": "skeleton-test", + "properties": [], + "skeleton": { + "nodes": [ + { + "name": "node", + "occluded": false, + "x": 136.1702, + "y": 306.1308 + }, + { + "name": "2", + "occluded": false, + "x": 145.1629, + "y": 291.263 + }, + { + "name": "3", + "occluded": false, + "x": 147.3005, + "y": 310.1857 + }, + { + "name": "4", + "occluded": false, + "x": 129.0203, + "y": 322.8007 + } + ] + }, + "slot_names": [ + "0" + ] } + ] + } """ directory = tmp_path / "imports" diff --git a/tests/data.zip b/tests/data.zip index ce18d5d62af3f14c0cc48df2c00df6fd2221f8cb..19825eeaae785a10e99cc3b8bbcef3564b8528aa 100644 GIT binary patch delta 74705 zcma&O2|SeF+dobS2}uz$Qz^<`NEjp`OVJ{-%p_~}N=(eSWv#>{Wh;B3EE9^d%vh>P zma%K>lPqH}GmK?sEdSf*^Zh*E@9*_I|L661z25hI-Pc*}bFS;0>s;r$&mgpl)b)!9 z+gS4O?&I42YeD-Y2_F}-P@uX03#p7f+|oSTd}pQqhkx=E$R7gvA5IDdfcgK0^113A ze`l8yzjaH^_L?vk|Nm57GK)v`|Ecev_as~Ms{D@$NzQWOe!l-pjjOym|6{>a5ixGL z)NCp4-FyC9bGCbtT94q~lNut%Es;FHxBs8kRxa@!{HrI)(n4~nHl{qgk_`p;{+Acz zy#N1wftAufFQ5wi`vv@hd;WRBssC25Feq~SzjU&FN&Rq*TPk(WZ*I~5xeFpU!~UJy zb1`{T2qyIP$bTs@`PlA5fISzITYcct2Oc>NdPD zebTPLSIegyZ0GLbp7n`n2rLa%10uwL0K|wsK8!KDLdr%M083L-?PMUF6BmL-uTEzh zZVWJL5yPV+bvX3qB59-=F;ceyh#=}iS@Y9ae^ESVW@}N12GpT9Cu>u;m~2-3P_s35 zgE|6iFhU4*TPJ2}fqp!&;9W4CGp@6gp@}us{`_7!0P=uOsM|#8J=WP;owqix{MXf+L@;jcvP54o7E+X@ zNtfEAU>}cmV(Wc0rwb-X&5K$Y_m<5Q$P^*PN1keC*skYi5pBjW^spa2FMYKV1U`EF@hDhSmenv~GuZkSQ z!WZ~hs@<%0masD*xw+oBHaB@%&mQp0RWQP&e4xJj*fmWamm~ln22+ zvh{k1!=1lrR%2nNK88?Hm5mOjoYBX-3`S?uxRsirTXk$A!CzPwGb}w2I;oV`(;C}? z4l5mSlGCjZ6z?L5?_3l|>z+D94O)c=1TM+~!1_!vG<Y1&NWu>OAeh9N9BXf@^a0G?^i$Z0lkRXsi`^mJQdtWg6@ZRi zh(_h~aFBZz*)~0UoHD7mTdmgMyWNyjmVt6U-%D(p1`KWQVTv!?Ci-vI=Cey0=}eb& za^DPeT)&@RB6gZ{s9i>#g@Q&;9$qsLXf>v@>>-h~;t#IQ*_cdf!P47l;a{zxKP4BI zsLR*djnfwAl|&E20w$ZFTfnd7>7{ZQ?-s}Xfq((DRYm}@<@YSE0rkL8^dW;q#J8oc zPLE9qx2>(b)E>vuq_Pch>QeG3z%L3W_&qfm)@ zIOhgUL0M7#k-|7<&5O4p3lU)@@6GTl_TK_l3JZ&QFD8TUxOgjCGE2U3xUIlknNd-z zQdnrNUaKB@6&a-|m=gC44Ivkr=j@Ni5W0^B-GA}cd?ks8JY847F&Huq4ie?=ipQY-9{U%c?cgFj4dJ4xD5~Y>PjIy;3z=JQl zPe_|0Nq2-ppWW>_Dv!LF9LQ?&%NF(xPwC$?svpK<5bQ7Av3h)D=Zc@|_h}69lnC8Q zOIR36q*TFv>xtqGPmjA+q1M)GjRw!wBy_BG`vn1;z=i6`^aQx$W}2uzG?O!7=|>YB z!}$itVyjkpF{Dva+(SxLNU&wX&lxl1%P#()mt6xrwx+2wiur0FwwTio-I%S-aQC3# z^SMgZyRB0vrLsnMN~_#M!59r2dd173AeS3Z`zEya^`-0Cp%3{J#@W4= zFu+5huMqy*oocxJ&J1aF^YLiL5SOTm8Coe>xb>x}0l*|k?Ea;y|f&L@(Le{5G@rw{dqGjd12|T9>oidZ=9~iicmJ! zTVK!WQZ#V76gix*Zkq;f?uUMTBkXIB=%)i&;YGIjKqTR5xGdImi+3BCi{7CZ^<`4> zm-e9Yv!DJnaPHZ2v}PuBnXbt@^5SezjYs6-&LA@dKvy`|d@qk~g+&*Q6h{2=E3sot z@@4{M*?E6#A^!to7>CS%x&R|mqyxASJy)|k?=H%U?#sD%-z%a6J3iZ`&_0KPp7}6~ zoGED`7{7v3SO&U~*436oXgBq*fZ<4Uh4XStkzfwpJ^0 zlT!MZ0RRg&pAtIR73UsXXs2A^yaTij$cutf<8KS2-BQWa6NmR;4K$e*}0bsTe#II!^? za;s@=u24?8;`h?LqMJJpF1Dm?O}gUNqL!QccS#(+>)A^=gQ|^?ELodUvxc)UG&MaY zdWJt`xAmBmCzAbevh2zk=EGyKn5edz_9XjmEwgiZK8asLYo;zJ&t%AzX8>h7dwIa8 zGePZ12guGdrG1sIsyA(nT~$+%tB`A;i57h!cL(j2_*H%t9&B4E_b5* zprCV#S=e0zILM?b6qxyxrwN6%b2BRL9c99^)Ti)u;C^KGuP7ZDIb&)R!mZG)?0_2#fo_uHs+dMS%OcZ0-SugacP_)%*d;qH- z3mWs8+l|(qH<2XD?i%VZx`3FqD1f;s9mp(T??ftx8XKy+PDv^ zt6z*JQ(h|g+9#^c|2ni8@m6Ezh*Go9dLu8E!7pC-9co3F2GkoshQKysd#OZ{!k<9Y zxEK|VR{ncb8!lNjPu2;q{Yt<6`88;Qk|%mt-OZvX9nIP-cTX~~TQdBNfHXpNofE}i z4L~O>@tf=P0%qWz?~>tL{tbjCOO$~G{5JwnuKPM|Or2f}+_SZ0Da!AH66Q~5xYKZJ zlta7KV6x}O_IV;n=?$Sm@B{F?fF~NTjpHxL?$EcV>;4E}$;WtfkM5-I|5D^rd&#U| zw_zRcx9*vs<0(KvqZ*YWy37B!lR@FE)e~57Le1RJ-S#RMJ`vgloj=> zK7D3dAi%m&C-k9?RY6zw)%28oi34sKf?2qGKY%97jM9p)uH1Xs{qoW&Xqe`QHIm{? z$1=lS_faCD+lbk-MMuTYbrvvDLa_s=c)=|Ai*Iyhp0Wf!SJE9cZf`soGNg^q>nI@! zd>5DwUs91rl35a@i}D*_kyJl>&9PgN)kY{@J&9cdt>7ZaZ%k&-?nEg=j%E^p0ht@- z53$-6kx5MC&MnSGjOR@hnXx=MXpg%x#0;SE7&_Ka0!m&gUwcGfUfP)r zomirYN&w?n(F2GHl@yeE_TezvH|y%NdUhA9&osFT@{k;Vw|kyxGNyJ{&J}Sg=km`& zF>f7bnimthRZchf)O`!p-3J8Z_GpdX(svH7i0!3Btp*!H1{+cE$E|v|IvdSKltZ&XW+B-VuJP94}acdh{)b7 zT;tCJ_hHw+6-|^Qh0t0Zzg2=@*LP%O;<}Ka3{@|_Ee!au{-^*5P-b*KKbvZDI18sV z9QaHS;$TA&+1l@5!$Nu)z2(Dzi*LM`$rVk?XJ))v(M;ItFNrwGSb#kdv?$;{S)9k}s6n*aX+8j?d#vQtbQsx}ecWjt; zz28kC@wZFZFKRi0?ym1c!O^k`zKZIS4yKAyib_tvjgxhAQHeh}ptrILdMcyPPTNt0 zwcP_mUU3~h2Si5M;$}qIBF=a^Aj1d-jGT0TrMPlr*C2zW(yKNnBRR+jWpX$BFdO&M zMkklI2fOb>vp5f-KL~yH?43VYUD ze0keN2YvTlz2bF&4;`Q>&3xXmb`L`%r&r6|`DMuAH#U}5*M5;qKSSU1D_jM}rd>CK zbP+Ys07LQ1xIn{A!`qTT#j&xSo8ShBhM1ypP;FzYtTa3g0kn--uTE^xw1>vxc7!f( z9D^)PRA#rK(b-a?eXFU+HhRaX`tmZG`D=>G*{#MoSiFNBS+;{60V1?!*UVmLJ|-WC zzM%b`{2*j?fdPeK3|Lnc6i6B|`L#Nce$Lr2JQ676FjoD~WTOZ?f-@M={ha`x9R>n> zj=r{=FGv=%-W;Bddq3dbcA!qkV$2)ZcLaR~q|h0ajj65A!71Q@q#}Lo`w995HgOY_PqKjT zl^!3x16gpfA5%{CN1l=0wX5i1HgYgTuBnm)BG&^MYH|qdyV4GHOvd&ayEL(UBT8^) z+Xt$k{y;asl0Dc<&l-=>o*6!CxwZGF7 zMV^RKejOR)(uA`?nuAojMfX0?X5jfg7(`BYnhFB$=%vUR0c|#z@O`+CV*y z%Nj&&yxvy&pvIY2k=uqnag(wwoYT&e8{D$bSMxcHYZ%bHU=Z7|w$l&SAIV~_&4?1m zdZR*7pX;;zx>+j*OM851YK&iZp4Cf7AVWVl)Kkd{V~n#an?1(!8)(2u-|;u4QeH+{ zTJ^(M*PiEzg#Lpy@=y9}4IssLdYbv{AHPZ@C=NF@^D)hgt^ ziCOo0Dn--e-y_1vBz`OLzSYvo+MMP&veO}U-R&r~RPYI@>~t1t|La4^ zM0p^07$6=iG{g7O7wGR)3ON7|n(Wva3Ty5WQcSPMN?tn!tIv5(;*j`W;vhsiGty$^ z&^w}Z+vvbw?qw{E%Gi$K{H2pX?rpc9G@_>cjCkYo+0U=59}c%)f}?PA<8IQb<4fg% z4}%O4qn_j)xH_W6sisN)`d=!D1rOBbYU?bj8JNGlk()|sVHetc35&G*GSpl=tl!u= z8ecMuj<(}Q>O1p|F9+?9I99W(K?nquqR53U97^UYS@msQNzQeVSy1q7J%h_`yH>z% zv4l;%>$8Uc9;tN+1wHqpTnLCF7j0sev=?kHsWXSu;yXOB;pXvcR#8&J_x?+v{Xpp2 z4)ym-bkM+c^&*iOqgGxm#_xr&ZN}VR9z6Fg}oUM~8CcU~w>3K)bOA+$-6M z`A4RXeDmeO#6Ji%0H8}ulywY9A%f2zS!SYeL86I_w_$;li@x)pxR;sh@{1J6VQB<2 z1M;KL&iAqbzC~k6y_q%^T0&!#Q`KPnE<6Sw4f=-mUA4Y3R<$eOT)WlK>0J2l+=SH7 zpJqsM)+r4?>Ira6-!2Yd=iV4teE}{TJ~{eYctOx{2jV6jMyV9<2iU^mNtLd?MR+F& zYrEDk{gFgvnuhcD^Bf8Gt{{Y8*8Rgz{fgbROG=`{qX|we7BiN0VFQmq-~6F6t=L$F zhG^`&+8XxQE+I5x;Ck&5c-2qog%ecS=iU151woV$)vGl@6Hnij=t8dExDP9+n8j@q zz&3@PxnWr?oBnfl;-uX*!wwfDxvU7z)j((N;PTMBuJC%?_P^k?O)r9A%v&lj@9P)b zdrJ+)C0@(y{8N1C%ST?m0KIt*u4!DRZn6AxH)tM;~&a1$oZd&U~`7dH??_f zb)9&peoqU@hv|0;CfuvHqV)ZiEBE&ITqhXcE8Ci^Scul$wwtii%4X0Qzlc+(^({;+Y1z4YbSs;(B(fkJKRJ^FdSa1&0VI{_uKQAMSgnfGc z>pMPt*)7}hEUdl}A|u`O_MNY|1HqWMiz2+j6!-hJWeubWhSb=;7bI}oY< z8*B6J6`~67Y{d;W{cdik;{vHw*So_|pPtL3d>_z?C1=RT52_6pGfIzs3}P78dmD<^ zIe)?m(L@U;PTsZ{V*swgznHRy;I`MZi2D^pFMT9JxXT2ZXeWQL(^h_6$JxD!dnD2A zT)n{FE{ZMc!u{U(wX|6;L%nmbkta8Y6hjMTVhdYC!I*7xI_?CYt=R@>joGD*D{Vp9 zkI57R@#dam?o|^-n6TZ}&{fc4FN|=^7g|DK&-hT`MSOJtrr6UGhSq^!zSdlPT(>do zV150^hXm=-SI<~Zx{#$WOvfmYfL9Uu!o+6_b=s!88EGEHwb53UeT-ivxfAXLp|e8v z1lP*g@ZJ^=F!C+j8p`5rZcW^=>Id4tp+Kwt9=wx0HAd1npn*_z5=DxfcU?nS^)I<= z$Wz|}ORQ4|hhnS(9!|DiAk}^j^LaQq2DO@6q=|l?Zq1wHEW39xC4A>NenM4J2&8@U zPE?yOeW0+!+h#zpk~*66R4TC$_(InIm3HYD#McrA8jSBwziq#RM&mn^USTcHOuQ;U z3^DQ9S5|0z{q?1|lF#f!p&E7~l`^YWRtOZ+K0`zm?~rl%3meg#M3RhB=dYfCgut<+ zc}{Q0>v0NB?&|MZy-RfF9olHZCvZP-D3|`*CN~gjM~p?{Xg>y@c*GrWnn~7ejt{?7 zs-oM}Bh+>n&RiJSiL@%|TE(Z?=TErDYjuY7GSa@}IbLnsCTK<3r$NV_&E@*q`@)QqvH=cUfwYRVF$lxGMyG+Kc$j=}i#03AsPqu?$2(Is6Bvvaqn-E%8j1FbtTwXe_C0d{y*Kk>7F*KnX z2^_Z2hs+7Bk=>6Q%D>aPsG`dum3K!xDjZme)f1j;)EmD2W8qSMfJI})>fGxMU12k1 z$z1)VW?pdaEz~P+fZp{5X(Z}bv1Wq^m027|rT~|WS1v0!CKhU|KMuEAj{k889?T=I zA0YGFJ*2R!_-eY??}_r7yBi(u_a}AX!OkrAU}v4VOZk|vw!*H=ol`wCo*zJpQK5hC zSEi}8W?Pn4TEZV8C95xwM51B9n?mVRBMPkzXoxwu|~vMdz_Q;T2eUaS9H!N3DBUC_PWYM|0z)VZRP;$rQ zL=4rbEfq9WYHufEss&vpvJz8E%TFCPho+=I`!jL!u(_Rg+Os!c*p&q&g+FQE-YSA- z%S9rzpU(Jo&p$Z2W;AE#tP@W4*(fxcqDiiKf)hG_Um77?^PRt4mpxQP@ z2q0YE-;!7Sgn19Bd4GWi#9n9HH_Jgaoo6L-D0wg7Nl;ab^1iUo$M=tZIZ^BNC9hWu za__dH#l>yOyffJU7K~1p)x6%GWEZSOwUSouEc-q##OkEsi-{Q}=Zf=scZg zU0HetSlROsS+&|fnO`&wAWDP%jEYxIXH&t}0;&n&{a=0e$YMx`$Tczg)8|IcsSUeS z$JUR!-ZwFTR2o$*;g=WGI(IT&^?ZmE#%I7dx1&x&EdsKx4uPv6i0Ppnq$K0s6~O@i z>oT{$S3kViyaq-yU%up^O3Gki4>J7a+~XH1r=M@y@{5rE=XSZ5iemV~Q;|;i#dK_9~ zmNv4^7aFY#M=nYBZ`%imeU4cXRJ`(z8hc+D56r`57IOG+kBq9d%h$dm@k2l3rnTxw zQycin{Tt}FRmN_<`EHSbhIw##TJET6+9f<5)ZCv1^cA^Km&(h(Y5PhcI275Xlg(Y) zzCFU1J#H4rk|cMQkS9(isMlU`r2P@lI)_h^l1W7iCknUqF+EUz(GS`PiK6SK zz*B63I)=6KR);OEm0`$iZt;%3it~;ZubYe1E)ttPGF+y4ZbVaPK;VX4+Y=zrvTjcI z&yzrDXGfi~!eW8yELFB!Mf={=pgtz}!A|$Bcc@T}{_W7N(u3&7&q@<3g&cd1og2~q zG5RqASrgE(axG{qg6zKfW>MCxO75-o-=`ZczxnHQL;vRx%lU1if27Xf+L2*GU*%-| zfI%8)H!1E|ok9L30YFq=Wi3&z81DmZ1J_7_N?9}K0`riAm9-W|gv;PUt;i>92F~2# zeb>NUX2H!_q*Y^Yym(sVyNQ#0SFxElD@}uL$I;Tun+`xREt;Y8f{rRw>4Se=uA;$s z7kftfp!n^e?V>TAVA*YREcpGrw})qCz5A*lJi{`HmO{41u4a7k};LPNk ze%VrRzie(0a{pp)Cy4|9`S8Ec8n{7&|AaikFZ&n>F?jG7$L-tSb#LF7gFb;4bP%+l zg9gPEuw7ug&w#vbdC7xF0RaPF#RCElFnbJqIZg+?IlU_npx7!RFSZI3C%JLde#kQx zwC)jhL(>9(v$vD}&v**v{SRPTz`C{(o$9=B9lZTrwP!Q{68g@5(8py}#7Gd{V550J zh&cZ(FhQ_G$KZZYQLsKP5NaDAMTZP9BA?LSoH`6xIMQy20D9hgOXfA2LESVI&)?gw z9Ay8W0Q{5jucZtu^shVNPw{*u2#uyz)sXGm-*s=_E3?X~Fz3OnMj*7nc5;L5j|1Dj z`(N_eyDdU6ZCiG!;e(3-i3U%bbEYTfGhyL@w?}hUF$ zhZH0M_V0F9G9HBvW=wlpXQMdm7D>d`SRZM~FncU~5uZJbX7w3vuKambYiA7w7S~r+ z`bL&&C9(cv>zir;@R7|mY$$qZvbgq@r{Q{Hc4OTa*3hCF?!mG$8;ihyW=(l+<&3RU zx43OA`30&6NgJ1rb8&et?&SLW5UK!|02g?uH2HysHt-*(O5N`xg}AtRm$|vP{yM2T zpLWrr`Ru-ku{T|p#10F^M84wIR^7E?Z8H4TLDD~ACePwx#+9fZtJ@B>$74#C_d=S5 zkbI&OrpayMw-R!cOs1pAKFO=kS>~Rn__(?c)b8iv;W~c7hwn%9EmlOzu0t(az5+mw zP@|Y+=A6%oaH+e2aa~1>qBq}(J4lBo(=Ukuc;b}sT8Uy zPuCRW{>y|3WsI(c?YZfL2t`Do+4+RHy!>C?5h zy7#z_2|K(K|9Do&FaJzte~99->%|Lg$z__yf#o(|O`2^<;*98X=C{&MC22UX)8(#t zi6R*7kA#i6Gpm+&qKcXNG!5!~S7Eg3I^?LKMa;R}A3F;9YnYXNHplnjpCQ9rghgt^R;$wF$WFAaDO0W<$Tt@_`TojLMcmXPjDn?51W z{+Vd=2+hWC9K_u_zUP48b9J5_nh{-(veH7dFPLk-?>v6nHVghKX|(QnXI#QJm@R8* z{$=_W%X({_k{zEKFoxr-ElyBF0ubvf?X99vIQsJ%bZZ?Cgm03-Jkx4hJ{y#Q07G2p z*2?57Nhq*R?kfbQf5(Lcgs(T(R!id9v-rgj1J=sqs^QieVib?sSoxiry)i)CqH5zE zmNxTq47WTt@ME`f+dVC%^@#Oky)&_@Xh%~*@K-ddydU@*XH<<{~lFj7g* zRK?!M6~9|k&1Z=q*UJP)qY^`s6C_7NXGlwojuDN3@ZZ{q4N8w8Yq46N0a}F+jroQL ztOFzW4%hWe+MdZa41A8G3K(>Q2Ll0?xTt0jTO2bqS1w2%p(0m#Y1av1hmhK&(&>p2 z_laC{?3Q<%CS{qSn$PGkP|&~C5~xRe&b{ok`TXIyY`NP_6y+*B8)G%N$XY+flfh(L z?(D5yGgip3Wh0B0*Kf;6la|eC`!ggd`a;b;{8dVt6nzTPt0gC-wPYCuz?gHaDCyah z4IQJkThNPwDA=_>yVYK4#>9dPu~w)lNFNcg)kj>ee|KC`e6nT%BskWeUs%^FN-|}vci%V zEA^*&EWQ@)q)y}tz68IMJ_;8;?I?KD_B>S0Q4HTEF-L?)4X=OIc=?Dnts>?bU}=gp zd-15J`eI?~&6b9Aq9o3ZZuyl&R%;dt5!&2n_$@@%u-c z#YQ#mP@42h3R8PE0}KMNU$r_|S3km_fSl7=emVY#be%2}PBt;$(+Mg``P3nMx^%MF?tl8*MB zAoWDGTf=~>pO@5qJ}(_`K4=;g{*i~#oh+EGwlQqnx|vtY-93R`5{=n>lb-TN?84JnK^ZYQ)+gd%|D)`y~+iv z=r$ZD$>o2qHyAU{o#sZmotvB}@9Z(u6{PGUyZQr2w>j$~8#hp~R{hy3a+1e5u(Jrw zTaSZRAnQDI8^Yf3`;myVt)p`-9p4riou`H|PcQpUdsdJX2AxeM1EBdzW1gaYrwtia zNz)Spazt>VZ^mh3H`kAx?md6(XxO>$GjmdW`K_b;C@#o*k=O|qWDiA~wsBaD%vzE? z1<1#)hHi0imzT(GcMvnxXr9Pb{Y%>Vhph43ZDGhltHIEuU|6RL%RF}>XaJ~9e<+vp6&v-K&? z<(aw{bM-^~%d-CVaLcAk}>g(@dXRl+$=NcXD1#b}COs zy-R_dt?xIw&KumC73&Cn&iEFWb*ghg4ey_Wo{00$(MjHnHW^!w$pW%f*_$$eX<_l9 zN0*?NFXLtW>{t_MCd2j70+;ads>FgnNLxYiTN4WQ{viYIYS>zx(1^MS!Z#Sv6xM+0$<=-*hEqEy!rsOFNX&E;0pc zx>3qqM@N^>%vAW+%v?JwdCdkGqM&!>$CqVNqUK4-Pr(sMH0NI}ynRnzQ@op_e7i;3ISA1*DXB4JcFS6STC^N0?9&y`!@Y1;cS=15pC9l-Sar)WeED0>>h zkq(h7OCCLx{fC(CrgjnQ%KTlF`&~2j6`s~Y+XL)IN8iUAmzbBso~r8JUi6tQIbJf* z+vC$uL6yD>L-F_aCY{4#ZMu$pS35X*o|b1glH<~DXI^2&&Lj877_XZEjRcvR^j8y3Ufa^_y~9cqIgzUR<}tb z<5FtKflRoqVTgOsk~r~NHO_y8{cJU{Q`RQT9Z97TByE4J)`sym9GsPX~J*)7@BlUvFasRx2V2P1e-D)hS&DoU$PB$0* zF#hW`C0Usg(D81wY?p5Pb6`0J%3939cKmUR8Tj*c(&uhPlh55#k&;=jbFTVS6#7zH z9%CdS7js-)z*P*g0ybt^L<2O9E`2`u&spfcL(M&7!;aS={Zwpq(dUH$#N{GjW+L~( z=`f8U?n&}ozK#u5HbN3@C-=%mHX`oHHIgIu#;jKJ(txy^MJMN8D)Q>S0h*%|1_*X= zLXgYvEjp}2Czs->T?3^}gLs5fz&yZ<{j?=kw=E0@4zy8dEae&cD6IGp+xB&Oq^vBa zZ*$|O0>|DzcA8(0#Q$m-$a5AC#e@CC!pA%YHUlb0d|n)Y+Bli51g>0f;PKufP3dNA zH%3f4!moGj|qQMoSLAQ{b ziLx*BSbD*oS;>Jg!%yIn?H%`o8m^pzzSZp)yzte*8WCIB-YDzM zgv{$mvP>}GVet)Bmwx3W9w!BPog=Jg0I`r?S2SPIKeC%hyx$Zh{_*n3gGAb@4Z{I3 zJ?b9t3k=Lz%&XQ`byudGs=6j9@(UczsqqRI7=>Oyqls z*tjTIqB}Rxe;M6_EzRHSa3%}EHXC@f_7;eqPTdrP2eD3nVglY2!%fm{3J^2Ujc*y~ z-jc??1=XLjt$sGN!G{6}1Qc}R5p`Mbm}AWa*OB_Z-n`hBuL6)7{$|5e9zH<908+8J zp)dY}y}D|3sC1Oj+Hv1LaBt!?;gXj|3RExmFI8Pr=4>wO1$9M3^$x=fgMZ>oqRA#^ z{O`(pY+Vc2JPXTvcF}?^%AZfzminVi8*~rxJ}B9BC(nXwZJ6z6OlHOt;c>0~GEF{; zry~ch`SN1l{Str!SIfEUAbncV1D*c7YN%@QpZl7%U0M8@S|4Q^g}{=8=LsQa6=Uzp z8tyl4e2g*kXyuO%79fhwcaB^N_ll$EMWyz3F4W{YuUF<<4?wyi%Br22y0PfW73AX? z=Ar=GW4T*0zh{gz!x2+F@pRnwiaJa2L`T)>a{6;-8?kJz3N4EJDx=Mt_E;;1O zeWvv7^xO}ZvJ$yHZiw&vAezr!1q7lu2bA+jI=#&O7AQp z|DBxwPcr1^EK2aEd`w>TU>V&*R}Z)A+b%W7+?$1NGd*3>5M!iQFWq|7EI$?_>5t4w z-nH&}{8>ca#7I6cRJUw24;h(~O_a=h|CW8Sx(ED#SXEp|>e_$Mw2<(vsP4@xt}e;% zr*t<;&%V^j6#1qTJSo&AJC0PZ(}gJEbRn+D`!G}bEE1F#>4nENzy44pyvmSfi)u*X?+}H1_6xWw#XJI#lWM5Jf`m)Ga2RerhQHJQC+_#X_}zmi|ZxiST$j3Ll%u zY>8aaA9d)ILw?FJqk9`+#`_{6jjuqi*2K&!{B%VQ8lzqF(M~9IH&%iv09Tn2t4mk* zLTHrn=|_TG-fb=bk=8Ad-Q7p zY_e(2rqCTNd#3|=5Y@hWoIjrUt+(JM^~N#gkt$nVckBoT4ru6(=o<%xV($7|KLFhX zx1=#dn|G2YKBS2FWW{|0&fR5_9ZIFa&r01|2`j;p1|cwNCGmx31lGmL2xcaBeo?+V7FA|q5NQ{W0=odFpO;{9X9Ne` z2~dq6Ud{2W_<_Dy3-aMAi(+*%nxzt~Js^3w&fyOzsUK!^j~YLm1Z&rJQOI5(0SzYb zfC>7r4HEd{#@h*9^>=00xodRS_Ks-N#=*^Oy48GK{EdtAbE_hK{z0y%Ybw6tNWT0C zXu`AXJ)j;TI(a>WpI%GRUG7+Ca9w~u#eY19#F=N!=dEalBokMEv?%6sc)zc$4@CKi z(@j>>s|45bI95Zvs4f6q;I{)8H|DoP4%t=fU3>B7ZqWp3TpY$!7!j0x`&EQmO~|VT z0eH3G)A9TN5QoVt2?*L%iSVH5YI4VuyH5|4rn@^!ke*Xl`Ah z)6A=f{dV}&`HOdMCmT9f3o6falmbfI9duhH#Q*%m_*p*c=?>+{I$!k+BKhY8B3YP*DzkNIstgX zJUB+5`XL^h=Eu?w62bBvceZ)bLvnvrpEuI*<*DD+A0}iD900i}-T?Vr*q~ZlaNfoQ zK0l-^YeaB#{p-?!Axx^IRux9Qsw>$~bo!}ksOVF~N7Lq{>&qJ3=ns?!}<4uL3pq|+%0At=|0RQ;j<^(uPuP_B5-9sqq0WeFyUh=+qN5acI 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 delta 74082 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