From 5032c05048a27e030839f013fa7c593fd8ee03d4 Mon Sep 17 00:00:00 2001 From: Pedro Date: Thu, 21 Apr 2022 18:13:24 +0200 Subject: [PATCH 1/5] Completed docs --- darwin/torch/dataset.py | 153 ++++++++++++++++++++++++++++++++-------- 1 file changed, 124 insertions(+), 29 deletions(-) diff --git a/darwin/torch/dataset.py b/darwin/torch/dataset.py index b89cc116b..21f76fe1b 100644 --- a/darwin/torch/dataset.py +++ b/darwin/torch/dataset.py @@ -89,7 +89,7 @@ class ClassificationDataset(LocalDataset): Parameters ---------- - transform: Optional[Union[Callable, List]], default: None + transform: Optional[Union[Callable, List[Callable]]], default: None torchvision function or list to set the ``transform`` attribute. If it is a list, it will be composed via torchvision. """ @@ -193,7 +193,7 @@ def get_class_idx(self, index: int) -> int: target: torch.Tensor = self.get_target(index) return target["category_id"] - def measure_weights(self, **kwargs) -> np.ndarray: + def measure_weights(self) -> np.ndarray: """ Computes the class balancing weights (not the frequencies!!) given the train loader. Gets the weights proportional to the inverse of their class frequencies. @@ -209,7 +209,7 @@ def measure_weights(self, **kwargs) -> np.ndarray: for i, _filename in enumerate(self.images_path): target = self.get_target(i) if self.is_multi_label: - # get the indixes of the class present + # get the indices of the class present target = torch.where(target == 1)[0] labels.extend(target.tolist()) else: @@ -233,7 +233,7 @@ class InstanceSegmentationDataset(LocalDataset): Parameters ---------- - transform: Optional[Union[Callable, List]], default: None + transform: Optional[Union[Callable, List[Callable]]], default: None torchvision function or list to set the ``transform`` attribute. If it is a list, it will be composed via torchvision. """ @@ -279,7 +279,30 @@ def __getitem__(self, index: int) -> Tuple[torch.Tensor, Dict[str, Any]]: def get_target(self, index: int) -> Dict[str, Any]: """ - Returns the instance segmentation target + Builds and returns the target dictionary for the item at the given index. + The target dictionary will have the following format: + + .. code-block:: python + { + "annotations": [ + { + "category_id": int, + "segmentation": List[List[int | float]], + "bbox": List[float], + "area": float + } + ] + } + + Parameters + ---------- + index : int + The actual index of the item in the ``Dataset``. + + Returns + ------- + Dict[str, Any] + The target. """ target = self.parse_json(index) @@ -297,15 +320,15 @@ def get_target(self, index: int) -> Dict[str, Any]: # Compute the bbox of the polygon 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 + min_x: float = np.min([np.min(x_coord) for x_coord in x_coords]) + min_y: float = np.min([np.min(y_coord) for y_coord in y_coords]) + max_x: float = np.max([np.max(x_coord) for x_coord in x_coords]) + max_y: float = np.max([np.max(y_coord) for y_coord in y_coords]) + w: float = max_x - min_x + 1 + h: float = max_y - min_y + 1 # Compute the area of the polygon # TODO fix with addictive/subtractive paths in complex polygons - poly_area = np.sum([polygon_area(x_coord, y_coord) for x_coord, y_coord in zip(x_coords, y_coords)]) + poly_area: float = np.sum([polygon_area(x_coord, y_coord) for x_coord, y_coord in zip(x_coords, y_coords)]) # Create and append the new entry for this annotation annotations.append( @@ -320,16 +343,16 @@ def get_target(self, index: int) -> Dict[str, Any]: return target - def measure_weights(self, **kwargs) -> np.ndarray: + def measure_weights(self) -> np.ndarray: """ Computes the class balancing weights (not the frequencies!!) given the train loader Get the weights proportional to the inverse of their class frequencies. - The vector sums up to 1 + The vector sums up to 1. Returns ------- class_weights : ndarray[double] - Weight for each class in the train set (one for each class) as a 1D array normalized + Weight for each class in the train set (one for each class) as a 1D array normalized. """ # Collect all the labels by iterating over the whole dataset labels: List[int] = [] @@ -340,10 +363,25 @@ def measure_weights(self, **kwargs) -> np.ndarray: class SemanticSegmentationDataset(LocalDataset): - def __init__(self, transform: Optional[Union[List, Callable]] = None, **kwargs): - """ - See `LocalDataset` class for documentation - """ + """ + Represents an instance of a LocalDataset used for training on semantic segmentation tasks. + + Attributes + ---------- + transform : Optional[Callable], default: None + torchvision transform function(s) to run on the dataset. + convert_polygons : ConvertPolygonsToSemanticMask + Object used to convert polygons to semantic masks. + + Parameters + ---------- + transform : Optional[Union[List[Callable], Callable]], default: None + torchvision function or list to set the ``transform`` attribute. If it is a list, it will + be composed via torchvision. + """ + + def __init__(self, transform: Optional[Union[List[Callable], Callable]] = None, **kwargs): + super().__init__(annotation_type="polygon", **kwargs) if transform is not None and isinstance(transform, list): @@ -379,11 +417,32 @@ def __getitem__(self, index: int) -> Tuple[torch.Tensor, Dict[str, Any]]: def get_target(self, index: int) -> Dict[str, Any]: """ - Returns the semantic segmentation target + Builds and returns the target dictionary for the item at the given index. + The returned dictionary has the following structure: + + .. code-block:: python + { + "annotations": [ + { + "category_id": int, + "segmentation": List[List[float | int]] + } + ] + } + + Parameters + ---------- + index : int + The actual index of the item in the ``Dataset``. + + Returns + ------- + Dict[str, Any] + The target. """ target = self.parse_json(index) - annotations = [] + annotations: List[Dict[str, Union[int, List[List[Union[int, float]]]]]] = [] for obj in target["annotations"]: sequences = convert_polygons_to_sequences( obj["polygon"]["path"], @@ -399,16 +458,16 @@ def get_target(self, index: int) -> Dict[str, Any]: return target - def measure_weights(self, **kwargs) -> np.ndarray: + def measure_weights(self) -> np.ndarray: """ Computes the class balancing weights (not the frequencies!!) given the train loader Get the weights proportional to the inverse of their class frequencies. - The vector sums up to 1 + The vector sums up to 1. Returns ------- class_weights : ndarray[double] - Weight for each class in the train set (one for each class) as a 1D array normalized + Weight for each class in the train set (one for each class) as a 1D array normalized. """ # Collect all the labels by iterating over the whole dataset labels = [] @@ -419,8 +478,22 @@ def measure_weights(self, **kwargs) -> np.ndarray: class ObjectDetectionDataset(LocalDataset): + """ + Represents an instance of a LocalDataset used for training on object detection tasks. + + Attributes + ---------- + transform : Optional[Callable], default: None + torchvision transform function(s) to run on the dataset. + + Parameters + ---------- + transform : Optional[Union[List[Callable], Callable]], default: None + torchvision function or list to set the ``transform`` attribute. If it is a list, it will + be composed via torchvision. + """ + def __init__(self, transform: Optional[List] = None, **kwargs): - """See `LocalDataset` class for documentation""" super().__init__(annotation_type="bounding_box", **kwargs) if transform is not None and isinstance(transform, list): @@ -455,7 +528,29 @@ def __getitem__(self, index: int): return img_tensor, target def get_target(self, index: int) -> Dict[str, Tensor]: - """Returns the object detection target""" + """ + Builds and returns the target dictionary for the item at the given index. + The returned dictionary has the following structure: + + .. code-block:: python + { + "boxes": Tensor, + "area": Tensor, + "labels": Tensor, + "image_id": Tensor, + "iscrowd": Tensor + } + + Parameters + ---------- + index : int + The actual index of the item in the ``Dataset``. + + Returns + ------- + Dict[str, Any] + The target. + """ target = self.parse_json(index) annotations = target.pop("annotations") @@ -487,16 +582,16 @@ def get_target(self, index: int) -> Dict[str, Tensor]: return stacked_targets - def measure_weights(self, **kwargs) -> np.ndarray: + def measure_weights(self) -> np.ndarray: """ Computes the class balancing weights (not the frequencies!!) given the train loader Get the weights proportional to the inverse of their class frequencies. - The vector sums up to 1 + The vector sums up to 1. Returns ------- class_weights : ndarray[double] - Weight for each class in the train set (one for each class) as a 1D array normalized + Weight for each class in the train set (one for each class) as a 1D array normalized. """ # Collect all the labels by iterating over the whole dataset labels = [] From abf73cae2278a92b012d2e1862bbf46408670721 Mon Sep 17 00:00:00 2001 From: Pedro Date: Fri, 22 Apr 2022 09:06:09 +0200 Subject: [PATCH 2/5] Added numpy typing --- darwin/torch/dataset.py | 35 ++++++++++++++++++----------------- darwin/torch/utils.py | 3 ++- 2 files changed, 20 insertions(+), 18 deletions(-) diff --git a/darwin/torch/dataset.py b/darwin/torch/dataset.py index 21f76fe1b..6d3390138 100644 --- a/darwin/torch/dataset.py +++ b/darwin/torch/dataset.py @@ -1,6 +1,7 @@ from typing import Any, Callable, Dict, List, Optional, Tuple, Union import numpy as np +import numpy.typing as npt from darwin.cli_functions import _error, _load_client from darwin.dataset import LocalDataset from darwin.dataset.identifier import DatasetIdentifier @@ -105,7 +106,7 @@ def __init__(self, transform: Optional[Union[Callable, List]] = None, **kwargs): self.is_multi_label = False self.check_if_multi_label() - def __getitem__(self, index: int) -> Tuple[torch.Tensor, torch.Tensor]: + def __getitem__(self, index: int) -> Tuple[Tensor, Tensor]: """ See superclass for documentation. @@ -116,7 +117,7 @@ def __getitem__(self, index: int) -> Tuple[torch.Tensor, torch.Tensor]: Returns ------- - Tuple[torch.Tensor, torch.Tensor] + Tuple[Tensor, Tensor] A tuple of tensors, where the first value is the image tensor and the second is the target's tensor. """ @@ -130,7 +131,7 @@ def __getitem__(self, index: int) -> Tuple[torch.Tensor, torch.Tensor]: return img_tensor, target - def get_target(self, index: int) -> torch.Tensor: + def get_target(self, index: int) -> Tensor: """ Returns the classification target. @@ -141,7 +142,7 @@ def get_target(self, index: int) -> torch.Tensor: Returns ------- - torch.Tensor + Tensor The target's tensor. """ @@ -151,7 +152,7 @@ def get_target(self, index: int) -> torch.Tensor: assert len(tags) >= 1, f"No tags were found for index={index}" - target: torch.Tensor = torch.tensor(self.classes.index(tags[0])) + target: Tensor = torch.tensor(self.classes.index(tags[0])) if self.is_multi_label: target = torch.zeros(len(self.classes)) @@ -190,10 +191,10 @@ def get_class_idx(self, index: int) -> int: int ``category_id`` of the image. """ - target: torch.Tensor = self.get_target(index) + target: Tensor = self.get_target(index) return target["category_id"] - def measure_weights(self) -> np.ndarray: + def measure_weights(self) -> npt.NDArray[np.float64]: """ Computes the class balancing weights (not the frequencies!!) given the train loader. Gets the weights proportional to the inverse of their class frequencies. @@ -201,13 +202,13 @@ def measure_weights(self) -> np.ndarray: Returns ------- - ndarray[double] + npt.NDArray[np.float64] Weight for each class in the train set (one for each class) as a 1D array normalized. """ # Collect all the labels by iterating over the whole dataset labels = [] for i, _filename in enumerate(self.images_path): - target = self.get_target(i) + target: Tensor = self.get_target(i) if self.is_multi_label: # get the indices of the class present target = torch.where(target == 1)[0] @@ -248,7 +249,7 @@ def __init__(self, transform: Optional[Union[Callable, List]] = None, **kwargs): self.convert_polygons = ConvertPolygonsToInstanceMasks() - def __getitem__(self, index: int) -> Tuple[torch.Tensor, Dict[str, Any]]: + def __getitem__(self, index: int) -> Tuple[Tensor, Dict[str, Any]]: """ Notes ----- @@ -343,7 +344,7 @@ def get_target(self, index: int) -> Dict[str, Any]: return target - def measure_weights(self) -> np.ndarray: + def measure_weights(self) -> npt.NDArray[np.float64]: """ Computes the class balancing weights (not the frequencies!!) given the train loader Get the weights proportional to the inverse of their class frequencies. @@ -351,7 +352,7 @@ def measure_weights(self) -> np.ndarray: Returns ------- - class_weights : ndarray[double] + class_weights : npt.NDArray[np.float64] Weight for each class in the train set (one for each class) as a 1D array normalized. """ # Collect all the labels by iterating over the whole dataset @@ -390,7 +391,7 @@ def __init__(self, transform: Optional[Union[List[Callable], Callable]] = None, self.transform: Optional[Callable] = transform self.convert_polygons = ConvertPolygonsToSemanticMask() - def __getitem__(self, index: int) -> Tuple[torch.Tensor, Dict[str, Any]]: + def __getitem__(self, index: int) -> Tuple[Tensor, Dict[str, Any]]: """ See superclass for documentation @@ -458,7 +459,7 @@ def get_target(self, index: int) -> Dict[str, Any]: return target - def measure_weights(self) -> np.ndarray: + def measure_weights(self) -> npt.NDArray[np.float64]: """ Computes the class balancing weights (not the frequencies!!) given the train loader Get the weights proportional to the inverse of their class frequencies. @@ -466,7 +467,7 @@ def measure_weights(self) -> np.ndarray: Returns ------- - class_weights : ndarray[double] + class_weights : npt.NDArray[np.float64] Weight for each class in the train set (one for each class) as a 1D array normalized. """ # Collect all the labels by iterating over the whole dataset @@ -582,7 +583,7 @@ def get_target(self, index: int) -> Dict[str, Tensor]: return stacked_targets - def measure_weights(self) -> np.ndarray: + def measure_weights(self) -> npt.NDArray[np.float64]: """ Computes the class balancing weights (not the frequencies!!) given the train loader Get the weights proportional to the inverse of their class frequencies. @@ -590,7 +591,7 @@ def measure_weights(self) -> np.ndarray: Returns ------- - class_weights : ndarray[double] + class_weights : npt.NDArray[np.float64] Weight for each class in the train set (one for each class) as a 1D array normalized. """ # Collect all the labels by iterating over the whole dataset diff --git a/darwin/torch/utils.py b/darwin/torch/utils.py index 5dbad657e..2c1280152 100644 --- a/darwin/torch/utils.py +++ b/darwin/torch/utils.py @@ -4,6 +4,7 @@ from typing import List, Optional, Tuple import numpy as np +import numpy.typing as npt from darwin.cli_functions import _error, _load_client from darwin.dataset.identifier import DatasetIdentifier from darwin.datatypes import Segment @@ -34,7 +35,7 @@ def convert_segmentation_to_mask(segmentations: List[Segment], height: int, widt return torch.stack(masks) -def polygon_area(x: np.ndarray, y: np.ndarray) -> float: +def polygon_area(x: npt.NDArray[np.float64], y: npt.NDArray[np.float64]) -> float: """ Returns the area of the input polygon, represented with two numpy arrays for x and y coordinates. From b30f7e04e673949d57d2871a0dd106821b1727f4 Mon Sep 17 00:00:00 2001 From: Pedro Date: Fri, 22 Apr 2022 16:24:24 +0200 Subject: [PATCH 3/5] updating deps --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 67dcfb759..490a2f340 100644 --- a/setup.py +++ b/setup.py @@ -25,7 +25,7 @@ "argcomplete", "dataclasses", "humanize", - "numpy", + "numpy>=1.20", "pillow", "pyyaml>=5.1", "requests", From baa9ea7ddc13d68bd5e6d0229fc043b7b5eca03c Mon Sep 17 00:00:00 2001 From: Pedro Date: Fri, 22 Apr 2022 16:40:31 +0200 Subject: [PATCH 4/5] reverting back to np.ndarray --- darwin/torch/dataset.py | 17 ++++++++--------- darwin/torch/utils.py | 3 +-- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/darwin/torch/dataset.py b/darwin/torch/dataset.py index 6d3390138..450c2d565 100644 --- a/darwin/torch/dataset.py +++ b/darwin/torch/dataset.py @@ -1,7 +1,6 @@ from typing import Any, Callable, Dict, List, Optional, Tuple, Union import numpy as np -import numpy.typing as npt from darwin.cli_functions import _error, _load_client from darwin.dataset import LocalDataset from darwin.dataset.identifier import DatasetIdentifier @@ -194,7 +193,7 @@ def get_class_idx(self, index: int) -> int: target: Tensor = self.get_target(index) return target["category_id"] - def measure_weights(self) -> npt.NDArray[np.float64]: + def measure_weights(self) -> np.ndarray: """ Computes the class balancing weights (not the frequencies!!) given the train loader. Gets the weights proportional to the inverse of their class frequencies. @@ -202,7 +201,7 @@ def measure_weights(self) -> npt.NDArray[np.float64]: Returns ------- - npt.NDArray[np.float64] + np.ndarray[float] Weight for each class in the train set (one for each class) as a 1D array normalized. """ # Collect all the labels by iterating over the whole dataset @@ -344,7 +343,7 @@ def get_target(self, index: int) -> Dict[str, Any]: return target - def measure_weights(self) -> npt.NDArray[np.float64]: + def measure_weights(self) -> np.ndarray: """ Computes the class balancing weights (not the frequencies!!) given the train loader Get the weights proportional to the inverse of their class frequencies. @@ -352,7 +351,7 @@ def measure_weights(self) -> npt.NDArray[np.float64]: Returns ------- - class_weights : npt.NDArray[np.float64] + class_weights : np.ndarray[float] Weight for each class in the train set (one for each class) as a 1D array normalized. """ # Collect all the labels by iterating over the whole dataset @@ -459,7 +458,7 @@ def get_target(self, index: int) -> Dict[str, Any]: return target - def measure_weights(self) -> npt.NDArray[np.float64]: + def measure_weights(self) -> np.ndarray: """ Computes the class balancing weights (not the frequencies!!) given the train loader Get the weights proportional to the inverse of their class frequencies. @@ -467,7 +466,7 @@ def measure_weights(self) -> npt.NDArray[np.float64]: Returns ------- - class_weights : npt.NDArray[np.float64] + class_weights : np.ndarray[float] Weight for each class in the train set (one for each class) as a 1D array normalized. """ # Collect all the labels by iterating over the whole dataset @@ -583,7 +582,7 @@ def get_target(self, index: int) -> Dict[str, Tensor]: return stacked_targets - def measure_weights(self) -> npt.NDArray[np.float64]: + def measure_weights(self) -> np.ndarray: """ Computes the class balancing weights (not the frequencies!!) given the train loader Get the weights proportional to the inverse of their class frequencies. @@ -591,7 +590,7 @@ def measure_weights(self) -> npt.NDArray[np.float64]: Returns ------- - class_weights : npt.NDArray[np.float64] + class_weights : np.ndarray[float] Weight for each class in the train set (one for each class) as a 1D array normalized. """ # Collect all the labels by iterating over the whole dataset diff --git a/darwin/torch/utils.py b/darwin/torch/utils.py index 2c1280152..5dbad657e 100644 --- a/darwin/torch/utils.py +++ b/darwin/torch/utils.py @@ -4,7 +4,6 @@ from typing import List, Optional, Tuple import numpy as np -import numpy.typing as npt from darwin.cli_functions import _error, _load_client from darwin.dataset.identifier import DatasetIdentifier from darwin.datatypes import Segment @@ -35,7 +34,7 @@ def convert_segmentation_to_mask(segmentations: List[Segment], height: int, widt return torch.stack(masks) -def polygon_area(x: npt.NDArray[np.float64], y: npt.NDArray[np.float64]) -> float: +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. From e3b6a456b6efa3816f9ad87cb28c45dffc176727 Mon Sep 17 00:00:00 2001 From: Pedro Date: Fri, 22 Apr 2022 16:43:25 +0200 Subject: [PATCH 5/5] reverted changed to stup --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 490a2f340..67dcfb759 100644 --- a/setup.py +++ b/setup.py @@ -25,7 +25,7 @@ "argcomplete", "dataclasses", "humanize", - "numpy>=1.20", + "numpy", "pillow", "pyyaml>=5.1", "requests",