From 2dea8702bdfa5d5801a2b0daa9d21ab120383285 Mon Sep 17 00:00:00 2001 From: tk-woven <61102060+tk-woven@users.noreply.github.com> Date: Fri, 12 Aug 2022 12:16:16 +0900 Subject: [PATCH] feat: fix latent pylint errors in many source files (#115) --- dgp/agents/agent_2d.py | 3 +- dgp/agents/agent_3d.py | 22 +- dgp/agents/base_agent.py | 7 +- dgp/annotations/base_annotation.py | 2 +- dgp/annotations/bounding_box_3d_annotation.py | 38 ++- dgp/annotations/depth_annotation.py | 2 +- dgp/annotations/ontology.py | 7 + .../panoptic_segmentation_2d_annotation.py | 18 +- dgp/annotations/transform_utils.py | 10 +- dgp/annotations/transforms.py | 29 +- .../visibility_filter_transform.py | 10 + dgp/cli.py | 4 +- dgp/datasets/agent_dataset.py | 10 +- dgp/datasets/base_dataset.py | 70 +++- dgp/datasets/frame_dataset.py | 5 + dgp/datasets/pd_dataset.py | 8 +- dgp/datasets/synchronized_dataset.py | 17 +- dgp/features/feature_ontology.py | 7 + dgp/utils/accumulate.py | 3 +- dgp/utils/annotation.py | 9 +- dgp/utils/cache.py | 19 +- dgp/utils/camera.py | 33 +- dgp/utils/cli_utils.py | 5 + dgp/utils/cloud/s3.py | 57 ++-- dgp/utils/colors.py | 18 +- dgp/utils/dataset_conversion.py | 66 +++- dgp/utils/protobuf.py | 20 +- dgp/utils/statistics.py | 9 +- dgp/utils/structures/bounding_box_2d.py | 20 +- dgp/utils/structures/bounding_box_3d.py | 24 +- dgp/utils/structures/instance_mask.py | 13 +- dgp/utils/testing.py | 8 +- dgp/utils/torch_extension/camera.py | 64 ++-- dgp/utils/torch_extension/pose.py | 167 ++++++--- dgp/utils/visualization_engine.py | 221 +++++++----- dgp/utils/visualization_utils.py | 319 ++++++++++-------- tests/test_datasets_timeout.py | 4 +- tests/utilities.py | 9 + tests/utils/test_utils_colors.py | 2 +- 39 files changed, 903 insertions(+), 456 deletions(-) diff --git a/dgp/agents/agent_2d.py b/dgp/agents/agent_2d.py index e0c0715c..cccb0c71 100644 --- a/dgp/agents/agent_2d.py +++ b/dgp/agents/agent_2d.py @@ -46,12 +46,13 @@ def load(cls, agent_snapshots_pb2, ontology, feature_ontology_table): ontology: Ontology Ontology for given agent. - feature_ontology_table: dict, default: None + feature_ontology_table: dict, optional A dictionary mapping feature type key(s) to Ontology(s), i.e.: { "agent_2d": AgentFeatureOntology[], "agent_3d": AgentFeatureOntology[] } + Default: None. Returns ------- diff --git a/dgp/agents/agent_3d.py b/dgp/agents/agent_3d.py index bc1aefea..907ac8ba 100644 --- a/dgp/agents/agent_3d.py +++ b/dgp/agents/agent_3d.py @@ -47,7 +47,7 @@ def load(cls, agent_snapshots_pb2, ontology, feature_ontology_table): ontology: Ontology Ontology for given agent. - feature_ontology_table: dict, default: None + feature_ontology_table: dict A dictionary mapping feature type key(s) to Ontology(s), i.e.: { "agent_2d": AgentFeatureOntology[], @@ -94,17 +94,25 @@ def render(self, image, camera, line_thickness=2, font_scale=0.5): Parameters ---------- - image: np.uint8 array - Image (H, W, C) to render the bounding box onto. We assume the input image is in *RGB* format + image: np.ndarray + Image (H, W, C) to render the bounding box onto. We assume the input image is in *RGB* format. + Data type is uint8. camera: dgp.utils.camera.Camera Camera used to render the bounding box. - line_thickness: int, default: 2 - Thickness of bounding box lines. + line_thickness: int, optional + Thickness of bounding box lines. Default: 2. - font_scale: float, default: 0.5 - Font scale used in text labels. + font_scale: float, optional + Font scale used in text labels. Default: 0.5. + + Raises + ------ + ValueError + Raised if `image` is not a 3-channel uint8 numpy array. + TypeError + Raised if `camera` is not an instance of Camera. """ if ( not isinstance(image, np.ndarray) or image.dtype != np.uint8 or len(image.shape) != 3 or image.shape[2] != 3 diff --git a/dgp/agents/base_agent.py b/dgp/agents/base_agent.py index fb15f8eb..b264c273 100644 --- a/dgp/agents/base_agent.py +++ b/dgp/agents/base_agent.py @@ -32,18 +32,19 @@ def load(cls, agent_snapshots_pb2, ontology, feature_ontology_table): Parameters ---------- - agent_snapshots_pb2: agent proto object - A proto message holding agent information. + agent_snapshots_pb2: object + An agent proto message holding agent information. ontology: Ontology Ontology for given agent. - feature_ontology_table: dict, default: None + feature_ontology_table: dict, optional A dictionary mapping feature type key(s) to Ontology(s), i.e.: { "agent_2d": AgentFeatureOntology[], "agent_3d": AgentFeatureOntology[] } + Default: None. """ @abstractmethod diff --git a/dgp/annotations/base_annotation.py b/dgp/annotations/base_annotation.py index cedffb03..db7e8e1d 100644 --- a/dgp/annotations/base_annotation.py +++ b/dgp/annotations/base_annotation.py @@ -43,7 +43,7 @@ def save(self, save_dir): """Serialize annotation object if possible, and saved to specified directory. Annotations are saved in format /. - Paramaters + Parameters ---------- save_dir: str Path to directory to saved annotation diff --git a/dgp/annotations/bounding_box_3d_annotation.py b/dgp/annotations/bounding_box_3d_annotation.py index c1b00ca5..b54b20ca 100644 --- a/dgp/annotations/bounding_box_3d_annotation.py +++ b/dgp/annotations/bounding_box_3d_annotation.py @@ -88,6 +88,11 @@ def to_proto(self): def save(self, save_dir): """Serialize Annotation object and saved to specified directory. Annotations are saved in format /. + Parameters + ---------- + save_dir: str + A pathname to a directory to save the annotation object into. + Returns ------- output_annotation_file: str @@ -107,17 +112,25 @@ def render(self, image, camera, line_thickness=2, font_scale=0.5): Parameters ---------- - image: np.uint8 array - Image (H, W, C) to render the bounding box onto. We assume the input image is in *RGB* format + image: np.uint8 + Image (H, W, C) to render the bounding box onto. We assume the input image is in *RGB* format. + Element type must be uint8. camera: dgp.utils.camera.Camera Camera used to render the bounding box. - line_thickness: int, default: 2 - Thickness of bounding box lines. + line_thickness: int, optional + Thickness of bounding box lines. Default: 2. - font_scale: float, default: 0.5 - Font scale used in text labels. + font_scale: float, optional + Font scale used in text labels. Default: 0.5. + + Raises + ------ + ValueError + Raised if image is not a 3-channel uint8 numpy array. + TypeError + Raised if camera is not an instance of Camera. """ if ( not isinstance(image, np.ndarray) or image.dtype != np.uint8 or len(image.shape) != 3 or image.shape[2] != 3 @@ -165,5 +178,16 @@ def hexdigest(self): return generate_uid_from_pbobject(self.to_proto()) def project(self, camera): - """Project bounding boxes into a camera and get back 2D bounding boxes in the frustum.""" + """Project bounding boxes into a camera and get back 2D bounding boxes in the frustum. + + Parameters + ---------- + camera: Camera + The Camera instance to project into. + + Raises + ------ + NotImplementedError + Unconditionally. + """ raise NotImplementedError diff --git a/dgp/annotations/depth_annotation.py b/dgp/annotations/depth_annotation.py index 5702358c..dd6dff89 100644 --- a/dgp/annotations/depth_annotation.py +++ b/dgp/annotations/depth_annotation.py @@ -49,7 +49,7 @@ def save(self, save_dir): """Serialize annotation object if possible, and saved to specified directory. Annotations are saved in format /. - Paramaters + Parameters ---------- save_dir: str Path to directory to saved annotation diff --git a/dgp/annotations/ontology.py b/dgp/annotations/ontology.py index c9c192a6..ba82bfa6 100644 --- a/dgp/annotations/ontology.py +++ b/dgp/annotations/ontology.py @@ -70,6 +70,13 @@ def load(cls, ontology_file): ---------- ontology_file: str Path to ontology JSON + + Raises + ------ + FileNotFoundError + Raised if ontology_file does not exist. + Exception + Raised if we could not open the ontology file for some reason. """ if os.path.exists(ontology_file): ontology_pb2 = open_ontology_pbobject(ontology_file) diff --git a/dgp/annotations/panoptic_segmentation_2d_annotation.py b/dgp/annotations/panoptic_segmentation_2d_annotation.py index df7b19b4..2df06fee 100644 --- a/dgp/annotations/panoptic_segmentation_2d_annotation.py +++ b/dgp/annotations/panoptic_segmentation_2d_annotation.py @@ -1,5 +1,6 @@ # Copyright 2021 Toyota Research Institute. All rights reserved. import json +import locale import os import cv2 @@ -98,6 +99,11 @@ def parse_panoptic_image(self): ------- instance_masks: list[InstanceMask2D] Instance mask for each instance in panoptic annotation. + + Raises + ------ + ValueError + Raised if an instance ID, parsed from a label, is negative. """ instance_masks = [] for class_name, labels in self.index_to_label.items(): @@ -181,8 +187,8 @@ def load(cls, annotation_file, ontology, panoptic_image_dtype=DEFAULT_PANOPTIC_I ontology: Ontology Ontology for given annotation - panoptic_image_dtype: type, default: np.uint16 - Numpy data type (e.g. np.uint16, np.uint32, etc) of panoptic image. + panoptic_image_dtype: type, optional + Numpy data type (e.g. np.uint16, np.uint32, etc) of panoptic image. Default: np.uint16. """ panoptic_image = cv2.imread(annotation_file, cv2.IMREAD_UNCHANGED) if len(panoptic_image.shape) == 3: @@ -191,7 +197,7 @@ def load(cls, annotation_file, ontology, panoptic_image_dtype=DEFAULT_PANOPTIC_I _L = panoptic_image label_map = _L[:, :, 2] + 256 * _L[:, :, 1] + 256 * 256 * _L[:, :, 0] panoptic_image = label_map.astype(panoptic_image_dtype) - with open('{}.json'.format(os.path.splitext(annotation_file)[0])) as _f: + with open('{}.json'.format(os.path.splitext(annotation_file)[0]), encoding=locale.getpreferredencoding()) as _f: index_to_label = json.load(_f) return cls(ontology, panoptic_image, index_to_label, panoptic_image_dtype) @@ -219,8 +225,8 @@ def from_masklist(cls, masklist, ontology, mask_shape=None, panoptic_image_dtype mask_shape: list[int] Height and width of the mask. Only used to create an empty panoptic image when masklist is empty. - panoptic_image_dtype: type, default: np.uint16 - Numpy data type (e.g. np.uint16, np.uint32, etc) of panoptic image. + panoptic_image_dtype: type, optional + Numpy data type (e.g. np.uint16, np.uint32, etc) of panoptic image. Default: np.uint16. """ if not masklist: panoptic_image = np.ones(mask_shape, panoptic_image_dtype) * ontology.VOID_ID @@ -283,7 +289,7 @@ def save(self, save_dir, datum=None): cv2.imwrite(panoptic_image_path, self.panoptic_image) index_to_label_path = '{}.json'.format(os.path.splitext(panoptic_image_path)[0]) - with open(index_to_label_path, 'w') as _f: + with open(index_to_label_path, 'w', encoding=locale.getpreferredencoding()) as _f: json.dump(self.index_to_label, _f) return panoptic_image_path diff --git a/dgp/annotations/transform_utils.py b/dgp/annotations/transform_utils.py index 6c8779b2..b485161d 100644 --- a/dgp/annotations/transform_utils.py +++ b/dgp/annotations/transform_utils.py @@ -18,7 +18,7 @@ def remap_bounding_box_annotations(bounding_box_annotations, lookup_table, origi bounding_box_annotations: BoundingBox2DAnnotationList or BoundingBox3DAnnotationList Annotations to remap - lookup: dict + lookup_table: dict Lookup from old class names to new class names e.g.: { @@ -60,10 +60,10 @@ def remap_semantic_segmentation_2d_annotation( """ Parameters ---------- - semantic_segmentation_2d_annotation: SemanticSegmentation2DAnnotation + semantic_segmentation_annotation: SemanticSegmentation2DAnnotation Annotation to remap - lookup: dict + lookup_table: dict Lookup from old class names to new class names e.g.: { @@ -103,10 +103,10 @@ def remap_instance_segmentation_2d_annotation( """ Parameters ---------- - instance_segmentation_2d_annotation: PanopticSegmentation2DAnnotation + instance_segmentation_annotation: PanopticSegmentation2DAnnotation Annotation to remap - lookup: dict + lookup_table: dict Lookup from old class names to new class names e.g.: { diff --git a/dgp/annotations/transforms.py b/dgp/annotations/transforms.py index 5fdf8d76..6b22a6cf 100644 --- a/dgp/annotations/transforms.py +++ b/dgp/annotations/transforms.py @@ -153,11 +153,11 @@ def __init__(self, original_ontology_table, lookup_table, remapped_ontology_tabl class_name in self.remapped_ontology_table[annotation_key].class_names for class_name in lookup.values() ]), 'All values in `lookup` need to be valid class names in specified `remapped_ontology`' - def transform_datum(self, data): + def transform_datum(self, datum): """ Parameters ---------- - data: OrderedDict + datum: OrderedDict Dictionary containing raw data and annotations, with keys such as: 'rgb', 'intrinsics', 'bounding_box_2d'. All annotation_keys in `self.lookup_table` (and `self.remapped_ontology_table`) @@ -165,29 +165,34 @@ def transform_datum(self, data): Returns ------- - data: OrderedDict + datum: OrderedDict Same dictionary but with annotations in `self.lookup_table` remapped to desired ontologies + + Raises + ------ + ValueError + Raised if the datum to remap does not contain all expected annotations. """ - if not all([annotation_key in data for annotation_key in self.remapped_ontology_table]): + if not all([annotation_key in datum for annotation_key in self.remapped_ontology_table]): raise ValueError('The data you are trying to remap does not have all annotations it expects') for annotation_key, remapped_ontology in self.remapped_ontology_table.items(): lookup_table = self.lookup_table[annotation_key] - original_ontology = data[annotation_key].ontology + original_ontology = datum[annotation_key].ontology # Need to have specific handlers for each annotation type if annotation_key == 'bounding_box_2d' or annotation_key == 'bounding_box_3d': - data[annotation_key] = remap_bounding_box_annotations( - data[annotation_key], lookup_table, original_ontology, remapped_ontology + datum[annotation_key] = remap_bounding_box_annotations( + datum[annotation_key], lookup_table, original_ontology, remapped_ontology ) elif annotation_key == 'semantic_segmentation_2d': - data[annotation_key] = remap_semantic_segmentation_2d_annotation( - data[annotation_key], lookup_table, original_ontology, remapped_ontology + datum[annotation_key] = remap_semantic_segmentation_2d_annotation( + datum[annotation_key], lookup_table, original_ontology, remapped_ontology ) elif annotation_key == 'instance_segmentation_2d': - data[annotation_key] = remap_instance_segmentation_2d_annotation( - data[annotation_key], lookup_table, original_ontology, remapped_ontology + datum[annotation_key] = remap_instance_segmentation_2d_annotation( + datum[annotation_key], lookup_table, original_ontology, remapped_ontology ) - return data + return datum diff --git a/dgp/annotations/visibility_filter_transform.py b/dgp/annotations/visibility_filter_transform.py index eea9dc04..1cef2574 100644 --- a/dgp/annotations/visibility_filter_transform.py +++ b/dgp/annotations/visibility_filter_transform.py @@ -45,6 +45,11 @@ def transform_sample(self, sample): ------- new_sample: list[OrderedDict] Multimodal sample with all detection annotations are filtered. + + Raises + ------ + ValueError + Raised if a 2D or 3D bounding box instance lacks any required instance IDs. """ cam_datums = [datum for datum in sample if datum['datum_name'] in self._camera_datum_names] @@ -189,6 +194,11 @@ def transform_sample(self, sample): ------- new_sample: list[OrderedDict] Multimodal sample with updated 3D bounding box annotations. + + Raises + ------ + ValueError + Raised if there are multiple instances of the same kind of datum in a sample. """ # Mapping index to datum. The order of datums is preserved in output. datums, src_datum_inds, dst_datum_ind = OrderedDict(), [], [] diff --git a/dgp/cli.py b/dgp/cli.py index 0e84a8fc..400639ba 100755 --- a/dgp/cli.py +++ b/dgp/cli.py @@ -94,7 +94,7 @@ def cli(): @cli.command(name='visualize-scene') @add_options(options=VISUALIZE_OPTIONS) @click.option("--scene-json", required=True, help="Path to Scene JSON") -def visualize_scene( +def visualize_scene( # pylint: disable=missing-any-param-doc scene_json, annotations, camera_datum_names, dataset_class, show_instance_id, max_num_items, video_fps, dst_dir, verbose, lidar_datum_names, render_pointcloud, radar_datum_names, render_radar_pointcloud, render_raw ): @@ -216,7 +216,7 @@ def visualize_scene( help="Dataset split to be fetched." ) @add_options(options=VISUALIZE_OPTIONS) -def visualize_scenes( +def visualize_scenes( # pylint: disable=missing-any-param-doc scene_dataset_json, split, annotations, camera_datum_names, dataset_class, show_instance_id, max_num_items, video_fps, dst_dir, verbose, lidar_datum_names, render_pointcloud, radar_datum_names, render_radar_pointcloud, render_raw diff --git a/dgp/datasets/agent_dataset.py b/dgp/datasets/agent_dataset.py index 5acf25b9..3daafe21 100644 --- a/dgp/datasets/agent_dataset.py +++ b/dgp/datasets/agent_dataset.py @@ -327,7 +327,7 @@ def feature_ontology_files(self): return feature_ontology_files @property - @lru_cache(maxsize=None) + @lru_cache(maxsize=None) # pylint: disable=method-cache-max-size-none def metadata_index(self): """Helper for building metadata index. @@ -342,11 +342,11 @@ def metadata_index(self): 'agent_group_description': agent_group.description } - def agent_slice(self, sample_id): + def agent_slice(self, sample_id): # pylint: disable=missing-param-doc,missing-type-doc """Return AgentSnapshotList in a frame.""" return self.sample_id_to_agent_snapshots[sample_id] - def agent_track(self, instance_id): + def agent_track(self, instance_id): # pylint: disable=missing-param-doc,missing-type-doc """Return AgentSnapshotList in a track.""" return self.instance_id_to_agent_snapshots[instance_id] @@ -1027,6 +1027,10 @@ def from_agent_containers(cls, agent_containers, requested_agent_types=None, req requested_feature_types: List(str) List of feature types, such as ['parked_car', 'ego_intention'] + Raises + ------ + Exception + Raised if an ontology from an agent container is not in our ontology registry. """ assert len(agent_containers), 'SceneContainers is empty.' requested_agent_types = [] if requested_agent_types is None else requested_agent_types diff --git a/dgp/datasets/base_dataset.py b/dgp/datasets/base_dataset.py index cc57d122..c1321430 100644 --- a/dgp/datasets/base_dataset.py +++ b/dgp/datasets/base_dataset.py @@ -127,13 +127,20 @@ def select_datums(self, datum_names, requested_annotations=None, requested_autol datum_names: list List of datum names to be used for instance of dataset - requested_annotations: tuple, default: None + requested_annotations: tuple, optional Tuple of annotation types, i.e. ('bounding_box_2d', 'bounding_box_3d'). Should be equivalent to directory containing annotation from dataset root. + Default: None. - requested_autolabels: tuple[str], default: None + requested_autolabels: tuple[str], optional Tuple of annotation types similar to `requested_annotations`, but associated with a particular autolabeling model. Expected format is "/" + Default: None. + + Raises + ------ + ValueError + Raised if datum_names is not a list or tuple or if it is a sequence with no elements. """ if not isinstance(datum_names, (list, tuple)) or len(datum_names) == 0: raise ValueError('Provide a set of datum names as a list.') @@ -188,14 +195,20 @@ def get_sample(self, sample_idx_in_scene): @lru_cache(maxsize=1024) def get_datum_type(self, datum_name): - """Get datum type based on the datum name""" + """Get datum type based on the datum name + + Parameters + ---------- + datum_name: str + The name of the datum to find a type for. + """ for datum in self.data: if datum.id.name.lower() == datum_name.lower(): return datum.datum.WhichOneof('datum_oneof') return None @property - @lru_cache(maxsize=None) + @lru_cache(maxsize=None) # pylint: disable=method-cache-max-size-none def datum_names(self): """"Gets the list of datums names available within a scene.""" logging.debug(f'Listing all available datum names in scene={self}.') @@ -316,7 +329,7 @@ def calibration_files(self): return calibration_files @property - @lru_cache(maxsize=None) + @lru_cache(maxsize=None) # pylint: disable=method-cache-max-size-none def annotation_index(self): """Build 2D boolean DataArray for annotations. Rows correspond to the `datum_idx_in_scene` and columns correspond to requested annotation types. @@ -376,7 +389,7 @@ def annotation_index(self): return scene_annotation_index @property - @lru_cache(maxsize=None) + @lru_cache(maxsize=None) # pylint: disable=method-cache-max-size-none def datum_index(self): """Build a multidimensional DataArray to represent a scene. Rows correspond to samples, and columns correspond to datums. The value at each location @@ -441,7 +454,7 @@ def datum_index(self): return scene_datum_index @property - @lru_cache(maxsize=None) + @lru_cache(maxsize=None) # pylint: disable=method-cache-max-size-none def metadata_index(self): """Helper for building metadata index. @@ -491,6 +504,11 @@ def check_datum_file(self, datum_idx_in_scene): Returns ------- bool: True if datum file exists + + Raises + ------ + TypeError + Raised if the referenced datum has an unuspported type. """ datum = self.data[datum_idx_in_scene] if datum.datum.HasField('image'): @@ -516,9 +534,6 @@ def get_autolabels(self, sample_idx_in_scene, datum_name): Parameters ---------- - scene_idx: int - Index of the scene. - sample_idx_in_scene: int Index of the sample within the scene at scene_idx. @@ -581,7 +596,7 @@ def __init__(self, scenes, directory, ontology_table=None): self.ontology_table = ontology_table @property - @lru_cache(maxsize=None) + @lru_cache(maxsize=None) # pylint: disable=method-cache-max-size-none def metadata(self): st = time.time() logging.info('Computing SceneDataset statistics on-the-fly.') @@ -631,8 +646,13 @@ def from_scene_containers( requested_autolabels: List(str) List of autolabels, such as['model_a/bounding_box_3d', 'model_a/bounding_box_2d'] - autolabel_root: str, default: None - Optional path to autolabel root directory + autolabel_root: str, optional + Optional path to autolabel root directory. Default: None. + + Raises + ------ + Exception + Raised if an ontology in a scene has no corresponding implementation yet. """ assert len(scene_containers), 'SceneContainers is empty.' requested_annotations = [] if requested_annotations is None else requested_annotations @@ -1183,7 +1203,7 @@ def get_scene_metadata(self, scene_idx): return metadata @property - @lru_cache(maxsize=None) + @lru_cache(maxsize=None) # pylint: disable=method-cache-max-size-none def metadata_index(self): """Builds an index of metadata items that refer to the scene index, sample index index. @@ -1344,6 +1364,11 @@ def load_datum(self, scene_idx, sample_idx_in_scene, datum_name): For different datums, we return different types. For image types, we return a PIL.Image For point cloud types, we return a numpy float64 array + + Raises + ------ + TypeError + Raised if the datum type is unsupported. """ # Get which scene datum comes from, otherwise use dataset directory scene_dir = self.scenes[scene_idx].directory @@ -1390,6 +1415,11 @@ def load_annotations(self, scene_idx, sample_idx_in_scene, datum_name): ------- annotations: dict Dictionary mapping annotation key to Annotation object for given annotation type. + + Raises + ------ + Exception + Raised if we cannot load an annotation type due to not finding an ontology for a requested annotation. """ datum = self.get_datum(scene_idx, sample_idx_in_scene, datum_name) annotations = self.get_annotations(datum) @@ -1547,6 +1577,11 @@ def get_datum_pose(self, datum): ------- datum_pose: Pose Pose object of datum's ego pose + + Raises + ------ + TypeError + Raised if datum type is unsupported. """ if datum.datum.HasField('image'): @@ -1790,7 +1825,7 @@ def get_radar_point_cloud_from_datum(self, scene_idx, sample_idx_in_scene, datum # to index into the numpy array with. channels = list(datum.datum.radar_point_cloud.point_format) - def fetch_channel_index_if_available(channel_ids, channel_format): + def fetch_channel_index_if_available(channel_ids, channel_format): # pylint: disable=missing-any-param-doc """ Helper function, returns index into channel_format that map to the requested chanels_ids. If not all of the channel ids are available in the channel_format, then return None. """ @@ -1909,6 +1944,11 @@ def _parse_autolabeled_scenes( ------- autolabeled_scenes: dict Mapping from requested_autolabel key "/" to SceneContainer + + Raises + ------ + ValueError + Raised if we encounter an invalid autolabel format in requested_autolabels. """ autolabeled_scenes = {} for autolabel in requested_autolabels: diff --git a/dgp/datasets/frame_dataset.py b/dgp/datasets/frame_dataset.py index a3637c32..908e7e3f 100644 --- a/dgp/datasets/frame_dataset.py +++ b/dgp/datasets/frame_dataset.py @@ -147,6 +147,11 @@ def __getitem__(self, index): ------- data: OrderedDict See `get_point_cloud_from_datum` and `get_image_from_datum` for details. + + Raises + ------ + ValueError + Raised if the datum type of item at index is unsupported. """ assert self.dataset_item_index is not None, ('Index is not built, select datums before getting elements.') diff --git a/dgp/datasets/pd_dataset.py b/dgp/datasets/pd_dataset.py index 60f89176..e51ce808 100644 --- a/dgp/datasets/pd_dataset.py +++ b/dgp/datasets/pd_dataset.py @@ -204,7 +204,13 @@ def coalesce_pc_data(self, items): return coalesced_pc def coalesce_sample(self, sample): - """Coalesce a point cloud for a single sample""" + """Coalesce a point cloud for a single sample. + + Parameters + ---------- + sample: list + List of OrderedDict, containing parsed point cloud or image data. + """ # First coalesce the point cloud item and assign at the right index. items_dict = OrderedDict() items_dict[self._datum_name_to_index[COALESCED_LIDAR_DATUM_NAME]] = self.coalesce_pc_data(sample) diff --git a/dgp/datasets/synchronized_dataset.py b/dgp/datasets/synchronized_dataset.py index 84e92b71..7f787b41 100644 --- a/dgp/datasets/synchronized_dataset.py +++ b/dgp/datasets/synchronized_dataset.py @@ -164,14 +164,14 @@ def set_context(self, backward=1, forward=1, accumulation_context=None): Parameters ---------- - backward: int, default: 1 - Backward context in frames [T-backward, ..., T-1] + backward: int, optional + Backward context in frames [T-backward, ..., T-1]. Default: 1. - forward: int, default: 1 - Forward context in frames [T+1, ..., T+forward] + forward: int, optional + Forward context in frames [T+1, ..., T+forward]. Default: 1. - accumulation_context: dict, default None - dictionary of accumulation context + accumulation_context: dict, optional + Dictionary of accumulation context. Default: None """ assert backward >= 0 and forward >= 0, 'Provide valid context' @@ -226,6 +226,11 @@ def get_datum_data(self, scene_idx, sample_idx_in_scene, datum_name): datum_name: str Datum within the sample. + + Raises + ------ + ValueError + Raised if the type of the requested datum is unsupported. """ # Get corresponding datum and load it datum = self.get_datum(scene_idx, sample_idx_in_scene, datum_name) diff --git a/dgp/features/feature_ontology.py b/dgp/features/feature_ontology.py index abd8b52c..0393f638 100644 --- a/dgp/features/feature_ontology.py +++ b/dgp/features/feature_ontology.py @@ -54,6 +54,13 @@ def load(cls, ontology_file): ---------- ontology_file: str Path to ontology JSON + + Raises + ------ + FileNotFoundError + Raised if ontology_file does not exist. + TypeError + Raised if we could not read an ontology out of the ontology file. """ if os.path.exists(ontology_file): feature_ontology_pb2 = open_feature_ontology_pbobject(ontology_file) diff --git a/dgp/utils/accumulate.py b/dgp/utils/accumulate.py index 9349f6ca..23422358 100644 --- a/dgp/utils/accumulate.py +++ b/dgp/utils/accumulate.py @@ -78,9 +78,10 @@ def accumulate_points(point_datums, target_datum, transform_boxes=False): target_datum: dict A single datum to use as the reference frame and reference time. - transform_boxes: bool, default: False + transform_boxes: bool, optional Flag to denote if cuboid annotations and instance ids should be used to warp points to the target frame. Only valid for Lidar. + Default: False. Returns ------- diff --git a/dgp/utils/annotation.py b/dgp/utils/annotation.py index 3401e776..2717a452 100644 --- a/dgp/utils/annotation.py +++ b/dgp/utils/annotation.py @@ -1,4 +1,6 @@ # Copyright 2021 Toyota Research Institute. All rights reserved. +import locale + from dgp.utils.cache import diskcache from dgp.utils.camera import Camera, generate_depth_map from dgp.utils.protobuf import open_pbobject @@ -9,15 +11,18 @@ def is_empty_annotation(annotation_file, annotation_type): Parameters ---------- - annotations: str + annotation_file: str Path to JSON file containing annotations for 2D/3D bounding boxes + annotation_type: object + Protobuf pb2 object we want to load into. + Returns ------- bool: True if empty annotation, otherwise False """ - with open(annotation_file) as _f: + with open(annotation_file, encoding=locale.getpreferredencoding()) as _f: annotations = open_pbobject(annotation_file, annotation_type) return len(list(annotations.annotations)) == 0 diff --git a/dgp/utils/cache.py b/dgp/utils/cache.py index bb471225..7f32e999 100644 --- a/dgp/utils/cache.py +++ b/dgp/utils/cache.py @@ -16,7 +16,13 @@ # in the case of varying arg/kwargs in the function / class-method to be # decorated. def clear_cache(directory=DGP_CACHE_DIR): - """Clear DGP cache to avoid growing disk-usage.""" + """Clear DGP cache to avoid growing disk-usage. + + Parameters + ---------- + directory: str, optional + A pathname to the directory used by DGP for caching. Default: DGP_CACHE_DIR. + """ if os.path.isdir(directory): logging.info('Clearing dgp disk-cache.') try: @@ -31,14 +37,11 @@ def diskcache(protocol='npz', cache_dir=None): Parameters ---------- - func: function - Function to be called and wrapped with disk-caching support. - - protocol: str, (default: npz) - Use numpy for serialization protocol, otherwise use pkl for pickle. + protocol: str, optional + Serialization protocol. Choices: {npz, pkl}. Default: "npz" (numpy). - cache_dir: str, (default: None) - Directory to cache instead of the default DGP cache. + cache_dir: str, optional + Directory to cache instead of the default DGP cache. Default: None. """ assert protocol in ('npz', 'pkl'), 'Unknown protocol {}'.format(protocol) logging.info('Using dgp disk-cache.') diff --git a/dgp/utils/camera.py b/dgp/utils/camera.py index bf7f297c..fcb30184 100644 --- a/dgp/utils/camera.py +++ b/dgp/utils/camera.py @@ -18,11 +18,11 @@ def generate_depth_map(camera, Xw, shape): camera: Camera Camera object with appropriately set extrinsics wrt world. - Xw: np.ndarray (N x 3) - 3D point cloud (x, y, z) in the world coordinate. + Xw: np.ndarray + 3D point cloud (x, y, z) in the world coordinate. Shape is (N x 3). - shape: np.ndarray (H, W) - Output depth image shape. + shape: np.ndarray + Output depth image shape as (H, W). Returns ------- @@ -171,11 +171,11 @@ def from_params(cls, fx, fy, cx, cy, p_cw=None, distortion=None): p_cw: Pose Pose from world to camera frame. - distortion_params: dict[str, float], default: None - Optional dictionary of distortion parameters k1,k2,.. etc. + distortion: dict[str, float], optional + Optional dictionary of distortion parameters k1,k2,.. etc. Default: None. Returns - ---------- + ------- Camera Camera object with relevant intrinsics. """ @@ -241,11 +241,11 @@ def project(self, Xw): Parameters ---------- - Xw: np.ndarray (N x 3) - 3D spatial coordinates for each pixel in the world reference frame. + Xw: np.ndarray + 3D spatial coordinates for each pixel in the world reference frame. Shape is (N x 3). Returns - ---------- + ------- x: np.ndarray (N x 2) 2D image coordinates for each pixel in the specified reference frame. @@ -277,14 +277,15 @@ def scale_intrinsics(K, x_scale, y_scale): Parameters ---------- - K: np.ndarray (3x3) - Camera calibration matrix. + K: np.ndarray + Camera calibration matrix. Shape is (3 x 3). x_scale: float x-axis scale factor. y_scale: float y-axis scale factor. + Returns ------- np.array of shape (3x3) @@ -318,14 +319,14 @@ def in_frustum(self, X, height, width): Parameters ---------- - X: np.ndarray (N x 3) - 3D spatial coordinates for each pixel IN THE CAMERA FRAME + X: np.ndarray + 3D spatial coordinates for each pixel IN THE CAMERA FRAME. Shape is (N x 3). height: int - Height of image + Height of image. width: int - Width of image + Width of image. Returns ------- diff --git a/dgp/utils/cli_utils.py b/dgp/utils/cli_utils.py index 93e06fd0..1f231fcb 100644 --- a/dgp/utils/cli_utils.py +++ b/dgp/utils/cli_utils.py @@ -5,6 +5,11 @@ def add_options(**kwargs): """Decorator function to add a list of Click options to a Click subcommand. + + Parameters + ---------- + **kwargs: dict + The `options` keyword argument shall be a list of string options to extend a Click CLI. """ def _add_options(func): return functools.reduce(lambda x, opt: opt(x), kwargs["options"], func) diff --git a/dgp/utils/cloud/s3.py b/dgp/utils/cloud/s3.py index 1d708cc6..41cfb56e 100644 --- a/dgp/utils/cloud/s3.py +++ b/dgp/utils/cloud/s3.py @@ -25,9 +25,9 @@ def init_s3_client(use_ssl=False): Parameters ---------- - use_ssl: boolean, default: False + use_ssl: bool, optional Use secure sockets layer. Provieds better security to s3, but - can fail intermittently in a multithreaded environment. + can fail intermittently in a multithreaded environment. Default: False. Returns ------- @@ -86,7 +86,13 @@ def s3_recursive_list(s3_prefix): def return_last_value(retry_state): - """Return the result of the last call attempt""" + """Return the result of the last call attempt. + + Parameters + ---------- + retry_state: tenacity.RetryCallState + Retry-state metadata for a flaky call. + """ return retry_state.outcome.result() @@ -110,8 +116,8 @@ def s3_copy(source_path, target_path, verbose=True): target_path: str Path to copy file to - verbose: bool, default: True - If True print some helpful messages + verbose: bool, optional + If True print some helpful messages. Default: True. Returns ------- @@ -184,11 +190,11 @@ def sync_dir(source, target, file_ext=None, verbose=True): target: str Directory to which all files will be synced - file_ext: str, default: None - Only sync files ending with this extension. Eg: 'csv' or 'json'. If None, sync all files. + file_ext: str, optional + Only sync files ending with this extension. Eg: 'csv' or 'json'. If None, sync all files. Default: None. - verbose: bool, default: True - If True, log some helpful messages + verbose: bool, optional + If True, log some helpful messages. Default: True. Returns ------- @@ -265,8 +271,8 @@ def sync_dir_safe(source, target, verbose=True): target: str Directory to which all files will be synced - verbose: bool, default: True - If True, log some helpful messages + verbose: bool, optional + If True, log some helpful messages. Default: True. Returns ------- @@ -307,7 +313,7 @@ def s3_bucket(bucket_name): Parameters ---------- - bucket : str + bucket_name : str Bucket name to instantiate. Returns @@ -360,7 +366,7 @@ def convert_uri_to_bucket_path(uri, strip_trailing_slash=True): uri: str A full s3 path (e.g. 's3:///') - strip_trailing_slash: bool, default: True + strip_trailing_slash: bool, optional If True, we strip any trailing slash in `s3_path` before returning it (i.e. s3_path='' is returned for uri='s3:////'). Otherwise, we do not strip it @@ -368,6 +374,7 @@ def convert_uri_to_bucket_path(uri, strip_trailing_slash=True): If there is no trailing slash in `uri` then there will be no trailing slash in `s3_path` regardless of the value of this parameter. + Default: True. Returns ------- @@ -398,10 +405,9 @@ def parallel_download_s3_objects(s3_files, destination_filepaths, bucket_name, p bucket_name: str S3 bucket to pull from - process_pool_size: int, default: None + process_pool_size: int, optional Number of threads to use to fetch these files. If not specified, will default to - number of cores on the machine. - + number of cores on the machine. Default: None. """ if process_pool_size is None: process_pool_size = cpu_count() @@ -424,17 +430,17 @@ def parallel_upload_s3_objects(source_filepaths, s3_destinations, bucket_name, p Parameters ---------- source_filepaths: list - List of all local files to upload + List of all local files to upload. s3_destinations: list - Paths relative to bucket in S3 where files are uploaded + Paths relative to bucket in S3 where files are uploaded. bucket_name: str - S3 bucket to upload to + S3 bucket to upload to. - process_pool_size: int, default: None + process_pool_size: int, optional Number of threads to use to fetch these files. If not specified, will default to - number of cores on the machine. + number of cores on the machine. Default: None. """ if process_pool_size is None: process_pool_size = cpu_count() @@ -493,8 +499,8 @@ def delete_s3_object(bucket_name, object_key, verbose=True): object_key: str Key name of the object to delete. - verbose: bool, default: True - If True print messages + verbose: bool, optional + If True print messages. Default: True. """ s3_client = init_s3_client() @@ -543,6 +549,11 @@ def get_s3_object(bucket_name, url): Returns ------- S3 object + + Raises + ------ + ValueError + Raised if `url` cannot be found in S3. """ # Retrieve a collection of S3 objects with that prefix and check first item in it diff --git a/dgp/utils/colors.py b/dgp/utils/colors.py index fce95d55..ddfa3578 100644 --- a/dgp/utils/colors.py +++ b/dgp/utils/colors.py @@ -24,12 +24,12 @@ def get_unique_colors(num_colors, in_bgr=False, cmap='tab20'): num_colors: int Number of colors. Must be less than 20. - in_bgr: bool, default: False - Whether or not to return the color in BGR order. + in_bgr: bool, optional + Whether or not to return the color in BGR order. Default: False. - cmap: str, default='tab20' + cmap: str, optional matplotlib colormap name (https://matplotlib.org/tutorials/colors/colormaps.html) - Must be a qualitative color map. + Must be a qualitative color map. Default: "tab20". Returns ------- @@ -52,11 +52,11 @@ def color_borders(img, color, thickness=10): img: np.ndarray Input image with shape of (H, W, 3) and unit8 type. - color: Tuple(int) + color: Tuple[int] Color to draw the frame. - thickness: int, default: 10 - Thickness of the frame. + thickness: int, optional + Thickness of the frame. Default: 10. Returns ------- @@ -82,8 +82,8 @@ def adjust_lightness(color, factor=1.0): color: Tuple[int] RGB color - factor: float, default: 1.0 - Factor of lightness adjustment. + factor: float, optional + Factor of lightness adjustment. Default: 1.0. Returns ------- diff --git a/dgp/utils/dataset_conversion.py b/dgp/utils/dataset_conversion.py index 173eea9e..2ed4c821 100644 --- a/dgp/utils/dataset_conversion.py +++ b/dgp/utils/dataset_conversion.py @@ -185,12 +185,19 @@ def generate_uid_from_semantic_segmentation_2d_annotation(annotation): Parameters ---------- - image: np.array + annotation: np.array semantic_segmentation_2d annotation to be hashed Returns ------- Hexdigest of annotation content + + Raises + ------ + TypeError + Raised if annotation is not of type np.uint8. + ValueError + Raised if annotation is not 2-dimensional. """ if annotation.dtype != np.uint8: raise TypeError('`annotation` should be of type np.uint8') @@ -204,12 +211,19 @@ def generate_uid_from_instance_segmentation_2d_annotation(annotation): Parameters ---------- - image: np.array + annotation: np.array instance_segmentation_2d annotation to be hashed Returns ------- Hexdigest of annotation content + + Raises + ------ + TypeError + Raised if annotation is not of type np.uint16 or np.uint23. + ValueError + Raised if annotation is not 2-dimensional. """ if annotation.dtype not in (np.uint16, np.uint32): raise TypeError('`annotation` should be of type np.uint16 or np.uint32') @@ -322,43 +336,74 @@ def __init__(self, version, raw_path, local_output_path): def convert(self): """Parse raw data into DGP format and return the dataset json. + Returns ------- dataset_json_path: str Dataset json path + + Raises + ------ + NotImplementedError + Unconditionally. """ raise NotImplementedError def populate_metadata(self): """Populate boilerplate fields for dataset conversion. Statistics, size, etc... - are recomputed during conversion""" + are recomputed during conversion + + Raises + ------ + NotImplementedError + Unconditionally. + """ raise NotImplementedError def populate_ontologies(self): """Populate ontologies' fields for dataset conversion. + + Raises + ------ + NotImplementedError + Unconditionally. """ raise NotImplementedError def populate_statistics(self): - """Compute dataset (image/point_cloud) statistics.""" + """Compute dataset (image/point_cloud) statistics. + + Raises + ------ + NotImplementedError + Unconditionally. + """ raise NotImplementedError @staticmethod def open_image(filename): """Returns an image given a filename. + + Parameters + ---------- + filename: str + A pathname of an image to open. + Returns ------- - image: PIL.Image - Image to be opened. + PIL.Image + Image that was opened. """ return Image.open(filename) def write_dataset(self, upload=False): """Write the final scene dataset JSON. + Parameters ---------- - upload: bool, default: False - If true, upload the dataset to the scene pool in s3. + upload: bool, optional + If True, upload the dataset to the scene pool in s3. Default: False. + Returns ------- scene_dataset_json_path: str @@ -513,10 +558,11 @@ def merge_scene_split_files(self): def write_dataset(self, upload=False): """Write the final scene dataset JSON. + Parameters ---------- - upload: bool, default: False - If true, upload the dataset to the scene pool in s3. + upload: bool, optional + If true, upload the dataset to the scene pool in s3. Default: False. Returns ------- diff --git a/dgp/utils/protobuf.py b/dgp/utils/protobuf.py index 535bf836..e0d7b9ef 100644 --- a/dgp/utils/protobuf.py +++ b/dgp/utils/protobuf.py @@ -25,8 +25,8 @@ def open_pbobject(path, pb_class): path: str Local JSON file path or remote scene dataset JSON URI to load. - pb_class: pb2 object class - Protobuf object we want to load into. + pb_class: object + Protobuf pb2 object we want to load into. Returns ---------- @@ -50,14 +50,18 @@ def open_remote_pb_object(s3_object_uri, pb_class): s3_object_uri: str Remote scene dataset JSON URI. - pb_class: pb2 object class - Protobuf object we want to load into. + pb_class: object + Protobuf pb2 object we want to load into. Returns ---------- pb_object: pb2 object Desired pb2 object to be opened. + Raises + ------ + ValueError + Raised if s3_object_uri is not a valid S3 path. """ if s3_object_uri.startswith('s3://'): bucket_name, s3_base_path = convert_uri_to_bucket_path(s3_object_uri) @@ -78,8 +82,8 @@ def save_pbobject_as_json(pb_object, save_path): Parameters ---------- - pb_object: pb2 object - Protobuf object we want to save to file + pb_object: object + Protobuf pb2 object we want to save to file save_path: str If save path is a JSON, serialized object is saved to that path. If save path is directory, @@ -163,8 +167,8 @@ def generate_uid_from_pbobject(pb_object): Parameters ---------- - pb_object: pb2 object - pb_object to be hashed. + pb_object: object + A protobuf pb2 object to be hashed. Returns ------- diff --git a/dgp/utils/statistics.py b/dgp/utils/statistics.py index 151061f6..8f6ed542 100644 --- a/dgp/utils/statistics.py +++ b/dgp/utils/statistics.py @@ -16,8 +16,8 @@ def get_scene_statistics(scene, verbose=True): scene: dgp.proto.scene_pb2.Scene Scene Object. - verbose: bool, default: True - Print the stats if True. + verbose: bool, optional + Print the stats if True. Default: True. Returns -------- @@ -78,6 +78,11 @@ def _get_bounding_box_annotation_info(annotation_enum): ------- str, dgp.proto.annotations_pb2.[AnnotationClass] The datum type, and annotation class corresponding to the annotation enum + + Raises + ------ + Exception + Raised if annotation_enum value does not map to a supported box type. """ if annotation_enum == annotations_pb2.BOUNDING_BOX_3D: return 'point_cloud', annotations_pb2.BoundingBox3DAnnotations diff --git a/dgp/utils/structures/bounding_box_2d.py b/dgp/utils/structures/bounding_box_2d.py index 49e0d760..32546586 100644 --- a/dgp/utils/structures/bounding_box_2d.py +++ b/dgp/utils/structures/bounding_box_2d.py @@ -33,6 +33,11 @@ class BoundingBox2D: mode: str, default: ltwh One of "ltwh" or "ltrb". Corresponds to "[left, top, width, height]" representation or "[left, top, right, bottom]" + + Raises + ------ + Exception + Raised if the value of mode is unsupported. """ def __init__( self, box, class_id=GENERIC_OBJECT_CLASS, instance_id=None, color=(0, 0, 0), attributes=None, mode="ltwh" @@ -52,7 +57,7 @@ def __init__( self.w = box[2] - box[0] self.h = box[3] - box[1] else: - raise "Bounding box must be initialized as 'ltrb' or 'ltwh', cannot recognize {}".format(mode) + raise Exception(f"Bounding box must be initialized as 'ltrb' or 'ltwh', cannot recognize {mode}") self._class_id = class_id self._instance_id = instance_id @@ -60,7 +65,18 @@ def __init__( self._attributes = dict(attributes) if attributes is not None else {} def intersection_over_union(self, other): - """Compute intersection over union of this box against other(s).""" + """Compute intersection over union of this box against other(s). + + Parameters + ---------- + other: BoundingBox2D + A separate BoundingBox2D instance to compute IoU against. + + Raises + ------ + NotImplementedError + Unconditionally + """ raise NotImplementedError @property diff --git a/dgp/utils/structures/bounding_box_3d.py b/dgp/utils/structures/bounding_box_3d.py index 4505ba8d..a45cf193 100644 --- a/dgp/utils/structures/bounding_box_3d.py +++ b/dgp/utils/structures/bounding_box_3d.py @@ -234,25 +234,33 @@ def render(self, image, camera, line_thickness=2, class_name=None, font_scale=0. Parameters ---------- - image: np.uint8 array - Image (H, W, C) to render the bounding box onto. We assume the input image is in *RGB* format + image: np.ndarray + Image (H, W, C) to render the bounding box onto. We assume the input image is in *RGB* format. + The type must be uint8. camera: dgp.utils.camera.Camera Camera used to render the bounding box. - line_thickness: int, default: 2 - Thickness of bounding box lines. + line_thickness: int, optional + Thickness of bounding box lines. Default: 2. - class_name: str, default: None - Class name of the bounding box. + class_name: str, optional + Class name of the bounding box. Default: None. - font_scale: float, default: 0.5 - Font scale used in text labels. + font_scale: float, optional + Font scale used in text labels. Default: 0.5. Returns ---------- image: np.uint8 array Rendered image (H, W, 3). + + Raises + ------ + ValueError + Raised if image is not a 3-channel uint8 numpy array. + TypeError + Raised if camera is not an instance of Camera. """ if ( not isinstance(image, np.ndarray) or image.dtype != np.uint8 or len(image.shape) != 3 or image.shape[2] != 3 diff --git a/dgp/utils/structures/instance_mask.py b/dgp/utils/structures/instance_mask.py index 5bff7d0c..ca5400f2 100644 --- a/dgp/utils/structures/instance_mask.py +++ b/dgp/utils/structures/instance_mask.py @@ -37,7 +37,18 @@ def __init__(self, mask, class_id=GENERIC_OBJECT_CLASS, instance_id=None, color= self._attributes = dict(attributes) if attributes is not None else {} def intersection_over_union(self, other): - """Compute intersection over union of this box against other(s).""" + """Compute intersection over union of this box against other(s). + + Parameters + ---------- + other: InstanceMask2D + Another instance of InstanceMask2D to compute IoU against. + + Raises + ------ + NotImplementedError + Unconditionally. + """ raise NotImplementedError @property diff --git a/dgp/utils/testing.py b/dgp/utils/testing.py index 9a4b3332..d766754c 100644 --- a/dgp/utils/testing.py +++ b/dgp/utils/testing.py @@ -56,11 +56,11 @@ def assert_between(value, low, high, low_inclusive=True, high_inclusive=True): high : comparable Range upper bound. - low_inclusive : bool, default: True - Allow case when value == low. + low_inclusive : bool, optional + Allow case when value == low. Default: True. - high_inclusive : bool, default: True - Allow case when value == high + high_inclusive : bool, optional + Allow case when value == high. Default: True. """ if low_inclusive: assert_greater_equal(value, low) diff --git a/dgp/utils/torch_extension/camera.py b/dgp/utils/torch_extension/camera.py index 76763286..11ff3d6f 100644 --- a/dgp/utils/torch_extension/camera.py +++ b/dgp/utils/torch_extension/camera.py @@ -35,11 +35,13 @@ def construct_K(fx, fy, cx, cy, dtype=torch.float, device=None): def scale_intrinsics(K, x_scale, y_scale): - """Scale intrinsic matrix (B33, or 33) given x and y-axes scales. + """Scale intrinsics matrix given x and y-axes scales. Note: This function works for both torch and numpy. Parameters ---------- + K: torch.FloatTensor, np.ndarray + An intrinsics matrix to scale. Shape is B33 or 33. x_scale: float x-axis scale factor. y_scale: float @@ -47,8 +49,8 @@ def scale_intrinsics(K, x_scale, y_scale): Returns ------- - torch.FloatTensor (33 or B33) - Scaled camera intrinsic matrix + torch.FloatTensor, np.ndarray + Scaled camera intrinsic matrix. Shape is B33 or 33. """ K[..., 0, 0] *= x_scale K[..., 1, 1] *= y_scale @@ -87,7 +89,15 @@ def __init__(self, K, D=None, p_cw=None): assert issubclass(type(self.p_cw), (Pose, QuaternionPose)), 'p_cw needs to be a Pose type' def to(self, *args, **kwargs): - """Move camera object to specified device""" + """Move camera object to specified device. + + Parameters + ---------- + *args: tuple + Positional arguments to a to() call to move a tensor-like object to a particular device. + **kwargs: dict + Keyword arguments to a to() call to move a tensor-like object to a particular device. + """ self.K = self.K.to(*args, **kwargs) self.p_cw = self.p_cw.to(*args, **kwargs) return self @@ -194,8 +204,8 @@ def transform(self, X, frame): Parameters ---------- - X: torch.FloatTensor (B3*) - Points reference in the `frame` reference frame. + X: torch.FloatTensor + Points reference in the `frame` reference frame. Shape must be B3*. frame: str Reference frame in which the output 3-D points are specified. @@ -203,9 +213,14 @@ def transform(self, X, frame): reference frames. Returns - ---------- + ------- Xc: torch.FloatTensor (B3*) Transformed 3D points into the camera reference frame. + + Raises + ------ + ValueError + Raised if frame is an unsupported reference frame. """ if frame == 'c': Xc = X @@ -220,17 +235,17 @@ def reconstruct(self, depth, frame='c'): Parameters ---------- - target_depth: torch.FloatTensor (B1HW) - Depth image. + depth: torch.FloatTensor + Depth image. Shape must be B1HW. - frame: str - Reference frame in which the output 3-D points are specified. + frame: str, optional + Reference frame in which the output 3-D points are specified. Default: 'c'. Returns - ---------- - X: torch.FloatTensor (B3HW) + ------- + X: torch.FloatTensor Batch of 3D spatial coordinates for each pixel in the specified - reference frame. + reference frame. Shape will be B3HW. """ B, C, H, W = depth.shape assert C == 1 @@ -254,21 +269,26 @@ def project(self, X, frame='w', shape=None): Parameters ---------- - X: torch.FloatTensor (B3HW or B3N) + X: torch.FloatTensor Batch of 3D spatial coordinates for each pixel in the specified - reference frame. + reference frame. Shape must be B3HW or B3N. - frame: str - Reference frame in which the input points (X) are specified. + frame: str, optional + Reference frame in which the input points (X) are specified. Default: 'w'. - shape: tuple, default: None - Optional image shape. + shape: tuple, optional + Optional image shape. Default: None. Returns - ---------- + ------- x: torch.FloatTensor (B2HW or B2N) Batch of normalized 2D image coordinates for each pixel in the specified - reference frame. Normalized points range from (-1,1). + reference frame. Normalized points range from (-1,1). + + Raises + ------ + ValueError + Raised if the shape of X is unsupported or if the frame type is unknown. """ # If X.dim == 3, i.e. X shape is B3N, then we expect the image shape to be provided. # normalize and assume that X can be either B3HW or B3N diff --git a/dgp/utils/torch_extension/pose.py b/dgp/utils/torch_extension/pose.py index 367b26b4..6bdba6f5 100644 --- a/dgp/utils/torch_extension/pose.py +++ b/dgp/utils/torch_extension/pose.py @@ -17,11 +17,11 @@ def qmul(q, r): Parameters ---------- - q: torch.FloatTensor (B4) - Input quaternion to use for rotation. + q: torch.FloatTensor + Input quaternion to use for rotation. Shape is B4. - r: torch.FloatTensor (B4) - Second quaternion to use for rotation composition. + r: torch.FloatTensor + Second quaternion to use for rotation composition. Shape is B4. Returns ---------- @@ -52,14 +52,14 @@ def qrot(q, v): Parameters ---------- - q: torch.FloatTensor (B4) - Input quaternion to use for rotation. + q: torch.FloatTensor + Input quaternion to use for rotation. Shape is B4. - v: torch.FloatTensor (B3) - Input vector to rotate with. + v: torch.FloatTensor + Input vector to rotate with. Shape is B3. Returns - ---------- + ------- vector: torch.FloatTensor (B3) Rotated vector. """ @@ -82,11 +82,11 @@ def qinv(q): Parameters ---------- - quaternion: torch.FloatTensor (B4) - Input quaternion to invert. + q: torch.FloatTensor + Input quaternion to invert. Shape is B4. Returns - ---------- + ------- quaternion: torch.FloatTensor (B4) Inverted quaternion. """ @@ -100,13 +100,20 @@ def quaternion_to_rotation_matrix(quaternion): Parameters ---------- - quaternion: torch.FloatTensor (B4) - Input quaternion to convert. + quaternion: torch.FloatTensor + Input quaternion to convert. Shape is B4. Returns ---------- rotation_matrix: torch.FloatTensor (B33) Batched rotation matrix. + + Raises + ------ + TypeError + Raised if quaternion is not a torch.Tensor. + ValueError + Raised if the shape of quaternion is not supported. """ if not isinstance(quaternion, torch.Tensor): raise TypeError("Input type is not a torch.Tensor. Got {}".format(type(quaternion))) @@ -151,16 +158,23 @@ def rotation_matrix_to_quaternion(rotation_matrix, eps=1e-8): Parameters ---------- - rotation_matrix: torch.FloatTensor (B33) - Input rotation matrix to convert. + rotation_matrix: torch.FloatTensor + Input rotation matrix to convert. Shape is B33. - eps: float, default: 1e-8 - Epsilon value to avoid zero division. + eps: float, optional + Epsilon value to avoid zero division. Default: 1e-8. Returns ---------- quaternion: torch.FloatTensor (B4) Batched rotation in quaternion. + + Raises + ------ + TypeError + Raised if rotation_matrix is not a torch.Tensor. + ValueError + Raised if the shape of rotation_matrix is unsupported. """ if not isinstance(rotation_matrix, torch.Tensor): raise TypeError("Input type is not a torch.Tensor. Got {}".format(type(rotation_matrix))) @@ -223,16 +237,23 @@ def normalize_quaternion(quaternion, eps=1e-12): Parameters ---------- - quaternion: torch.FloatTensor (B4) - Input quaternion to normalize. + quaternion: torch.FloatTensor + Input quaternion to normalize. Shape is B4. - eps: float, default: 1e-12 - Epsilon value to avoid zero division. + eps: float, optional + Epsilon value to avoid zero division. Default: 1e-12. Returns ---------- normalized_quaternion: torch.FloatTensor (B4) Normalized quaternion. + + Raises + ------ + TypeError + Raised if quaternion is not a torch.Tensor. + ValueError + Raised if the shape of quaternion is not supported. """ if not isinstance(quaternion, torch.Tensor): raise TypeError("Input type is not a torch.Tensor. Got {}".format(type(quaternion))) @@ -248,8 +269,8 @@ def invert_pose(T01): Parameters ---------- - T01: torch.FloatTensor (B44) - Input batch of transformation tensors. + T01: torch.FloatTensor + Input batch of transformation tensors. Shape is B44. Returns ---------- @@ -300,8 +321,14 @@ def identity(cls, B=1, device=None, dtype=torch.float): Parameters ---------- - N: int - Batch size. + B: int, optional + Batch size. Default: 1. + + device: str, optional + A device for a tensor-like object; ex: "cpu". Default: None. + + dtype: optional + A data type for a tensor-like object. Default: torch.float. Returns ---------- @@ -344,12 +371,28 @@ def translation(self): return self.value[..., :3, -1] def repeat(self, *args, **kwargs): - """Repeat the Pose tensor""" + """Repeat the Pose tensor + + Parameters + ---------- + *args: tuple + Positional arguments to a repeat() call to repeat a tensor-like object along particular dimensions. + **kwargs: dict + Keyword arguments to a repeat() call to repeat a tensor-like object along particular dimension. + """ self.value = self.value.repeat(*args, **kwargs) return self def to(self, *args, **kwargs): - """Move object to specified device""" + """Move object to specified device + + Parameters + ---------- + *args: tuple + Positional arguments to a to() call to move a tensor-like object to a particular device. + **kwargs: dict + Keyword arguments to a to() call to move a tensor-like object to a particular device. + """ self.value = self.value.to(*args, **kwargs) return self @@ -363,10 +406,17 @@ def __mul__(self, other): Either Pose, or 3-D points torch.FloatTensor (B3N or B3HW). Returns - ---------- + ------- Pose Transformed pose, or 3-D points via rigid-transform on the manifold, - with same type as other. + with same type as other. + + Raises + ------ + ValueError + Raised if the shape of other is unsupported. + NotImplementedError + Raised if other is neither a Pose nor a torch.Tensor. """ if isinstance(other, Pose): return self.transform_pose(other) @@ -382,6 +432,12 @@ def __mul__(self, other): # jscpd:ignore-end def __rmul__(self, other): + """ + Raises + ------ + NotImplementedError + Unconditionally. + """ raise NotImplementedError('Right multiply not implemented yet!') def transform_pose(self, other): @@ -405,7 +461,7 @@ def transform_points(self, X0): Parameters ---------- - X0: torch.FloatTensor (B3N or B3HW) + X0: torch.FloatTensor 3-D points in torch.FloatTensor (shaped either B3N or B3HW). Returns @@ -466,8 +522,14 @@ def identity(cls, B=1, device=None, dtype=torch.float): Parameters ---------- - N: int - Batch size. + B: int, optional + Batch size. Default: 1. + + device: str + A device to send a tensor-like object to. Ex: "cpu". + + dtype: optional + A data type for a tensor-like object. Default: torch.float. Returns ---------- @@ -485,13 +547,13 @@ def from_matrix(cls, value): Parameters ---------- - value: torch.FloatTensor (B44) - Batched homogeneous matrix. + value: torch.FloatTensor + Batched homogeneous matrix. Shape is B44. Returns ---------- - pose: QuaternionPose with batch B - QuaternionPose batch. + pose: QuaternionPosec + QuaternionPose batch. Batch dimension is shape B. """ if value.dim() == 2: value = value.unsqueeze(0) @@ -537,7 +599,13 @@ def translation(self): return self.tvec def repeat(self, B): - """Repeat the QuaternionPose tensor""" + """Repeat the QuaternionPose tensor + + Parameters + ---------- + B: int + The size of the batch dimension. + """ self.quat = self.quat.repeat([B, 1]) self.tvec = self.tvec.repeat([B, 1]) assert self.quat.dim() == self.tvec.dim() == 2, ( @@ -548,7 +616,15 @@ def repeat(self, B): return self def to(self, *args, **kwargs): - """Move object to specified device""" + """Move object to specified device + + Parameters + ---------- + *args: tuple + Positional arguments to a to() call to move a tensor-like object to a particular device. + **kwargs: dict + Keyword arguments to a to() call to move a tensor-like object to a particular device. + """ self.quat = self.quat.to(*args, **kwargs) self.tvec = self.tvec.to(*args, **kwargs) return self @@ -559,14 +635,21 @@ def __mul__(self, other): Parameters ---------- - other: Pose or torch.FloatTensor + other: QuaternionPose or torch.FloatTensor Either Pose, or 3-D points torch.FloatTensor (B3N or B3HW). Returns ---------- Pose Transformed pose, or 3-D points via rigid-transform on the manifold, - with same type as other. + with same type as other. + + Raises + ------ + ValueError + Raised if other.shape is not supported. + NotImplementedError + Raised if other is neither a QuaternionPose or a torch.Tensor. """ if isinstance(other, QuaternionPose): return self.transform_pose(other) @@ -608,7 +691,7 @@ def transform_points(self, X0): Parameters ---------- - X0: torch.FloatTensor (B3N or B3HW) + X0: torch.FloatTensor 3-D points in torch.FloatTensor (shaped either B3N or B3HW). Returns diff --git a/dgp/utils/visualization_engine.py b/dgp/utils/visualization_engine.py index dc138435..5c9ecf36 100644 --- a/dgp/utils/visualization_engine.py +++ b/dgp/utils/visualization_engine.py @@ -61,46 +61,71 @@ def visualize_dataset_3d( ---------- dataset: _SynchronizedDataset A multimodel dataset of which `__getitem__` returns a list of `OrderedDict`, one item for each datum. - lidar_datum_names: None or List[str], default: None + lidar_datum_names: None or List[str], optional Names of lidar datums. If None, then use all datums whose type is `point_cloud` and are available in all scenes. - camera_datum_names: None or List[str], default: None + Default: None. + camera_datum_names: None or List[str], optional Names of camera_datums. If None, then use all datums whose type is `image` and are available in all scenes. In the output video, the image visualizations are tiled in row-major order according to the order of this list. - render_pointcloud_on_images: bool, default: True - Whether or not to render projected pointclouds on images. - show_instance_id_on_bev: bool, default: True + Default: None. + render_pointcloud_on_images: bool, optional + Whether or not to render projected pointclouds on images. Default: True. + show_instance_id_on_bev: bool, optional If True, then show `instance_id` on a corner of 3D bounding boxes in BEV view. If False, then show `class_name` instead. - max_num_items: None or int, default: None + Default: True. + max_num_items: None or int, optional If not None, then show only up to this number of items. This is useful for debugging a large dataset. - caption_fn: Callable or None + Default: None. + caption_fn: Callable, optional A function that take as input a `_SynchronizedDataset` and index, and return a text (str). - The text is put as caption at the top left corner of video. - output_video_file: None or str, default: None - If not None, then write the visualization on a video of this name. It must ends with `.avi`. - output_video_fps: int, default: 30 - Frame rate of the video. - overwrite_output_file: bool, default: False - If True, then FFMPEG overwrites existing output file without prompting confirmation. - This is useful when the `output_video_file` is created by `tempfile` with context manager. - class_colormap: dict or None, default: None - Dict of class name to RGB color tuple. If None, then use class color defined in `dataset`. - adjust_lightness_factor: float, default: 1.0 - Enhance the brightness of colormap by this factor. - rgb_resize_factor: float, default: 0.5 - Resize images by this factor before tiling them into a single panel. - rgb_border_thickness: int, default: 10 + The text is put as caption at the top left corner of video. Default: None. + output_video_file: None or str, optional + If not None, then write the visualization on a video of this name. It must ends with `.avi`. Default: None. + output_video_fps: int, optional + Frame rate of the video. Default: 30. + class_colormap: dict, optional + Dict of class name to RGB color tuple. If None, then use class color defined in `dataset`. Default: None. + adjust_lightness_factor: float, optional + Enhance the brightness of colormap by this factor. Default: 1.0. + rgb_resize_factor: float, optional + Resize images by this factor before tiling them into a single panel. Default: 0.5. + rgb_border_thickness: int, optional Put a colored boundary on camera visualization of this thickness before tiling them into single panel. - bev-*: - See `BEVImage` for these keyword arguments. - bbox3d-*: - See `geometry.BoundingBox3D` for these keyword arguments. - pc_rgb-*: - See `render_pointcloud_on_image()` for these keyword arguments. - radar_datum_names: None or List[str], default: None - Names of the radar datums - render_radar_pointcloud_on_images: bool, default: True - Whether or not to render projected radar pointclouds on images. + Default: 4. + bev_metric_width: float, optional + See `BEVImage` for this keyword argument. Default: 100.0. + bev_metric_height: float, optional + See `BEVImage` for this keyword argument. Default: 100.0. + bev_pixels_per_meter: float, optional + See `BEVImage` for this keyword argument. Default: 10.0. + bev_polar_step_size_meters: int, optional + See `BEVImage` for this keyword argument. Default: 10. + bev_forward: tuple of int, optional + See `BEVImage` for this keyword argument. Default: (1, 0, 0). + bev_left: tuple of int, optional + See `BEVImage` for this keyword argument. Default: (0, 1, 0). + bev_background_clr: tuple of int + See `BEVImage` for this keyword argument. Default: (0, 0, 0). + bev_font_scale: float + See `BEVImage` for this keyword argument. Default: 0.5. + bev_line_thickness: int + See `BEVImage` for this keyword argument. Default: 4. + bbox3d_font_scale: float + See `geometry.BoundingBox3D` for this keyword argument. Default: 1.0. + bbox3d_line_thickness: int + See `geometry.BoundingBox3D` for this keyword argument. Default: 4. + pc_rgb_cmap: optional + See `render_pointcloud_on_image()` for this keyword argument. The value type comes from + matplotlib.cm.get_cmap. Default: MPL_JET_CMAP. + pc_rgb_norm_depth: int, optional + See `render_pointcloud_on_image()` for this keyword argument. Default: 10. + pc_rgb_dilation: int, optional + See `render_pointcloud_on_image()` for this keyword argument. Default: 3. + radar_datum_names: List[str], optional + Names of the radar datums. Default: None + render_radar_pointcloud_on_images: bool, optional + Whether or not to render projected radar pointclouds on images. Default: True. """ if output_video_file: assert output_video_file.endswith('.avi'), "'output_video' must ends with `.avi`." @@ -235,37 +260,42 @@ def visualize_dataset_2d( dataset: _SynchronizedDataset A multimodel dataset of which `__getitem__` returns a list of `OrderedDict`, one item for each datum. - camera_datum_names: None or List[str], default: None + camera_datum_names: None or List[str], optional Names of camera_datums. If None, then use all datums whose type is `image` and are available in all scenes. In the output video, the image visualizations are tiled in row-major order according to the order of this list. + Default: None. - max_num_items: None or int, default: None + max_num_items: None or int, optional If not None, then show only up to this number of items. This is useful for debugging a large dataset. + Default: None. - caption_fn: Callable or None + caption_fn: Callable, optional A function that take as input a `_SynchronizedDataset` and index, and return a text (str). The text is put as caption at the top left corner of video. + Default: None. - show_instance_id: bool, default: False + show_instance_id: bool, optional Option to show instance id instead of instance class name on annotated images. + Default: False. - output_video_file: None or str, default: None + output_video_file: None or str, optional If not None, then write the visualization on a video of this name. It must ends with `.avi`. + Default: None. - output_video_fps: int, default: 30 - Frame rate of the video. + output_video_fps: int, optional + Frame rate of the video. Default: 30 - class_colormap: dict or None, default: None - Dict of class name to RGB color tuple. If None, then use class color defined in `dataset`. + class_colormap: dict or None, optional + Dict of class name to RGB color tuple. If None, then use class color defined in `dataset`. Default: None. - adjust_lightness_factor: float, default: 1.0 - Enhance the brightness of colormap by this factor. + adjust_lightness_factor: float, optional + Enhance the brightness of colormap by this factor. Default: 1.0. - font_scale: float, default: 1 - Font scale used for all text. + font_scale: float, optional + Font scale used for all text. Default: 1. - rgb_resize_factor: float, default: 0.5 - Resize images by this factor before tiling them into a single panel. + rgb_resize_factor: float, optional + Resize images by this factor before tiling them into a single panel. Default: 0.5. """ if output_video_file: assert output_video_file.endswith('.avi'), "'output_video' must ends with `.avi`." @@ -424,31 +454,56 @@ def visualize_dataset_sample_3d( scene index into dataset sample_idx: int index of sample in scene - lidar_datum_names: None or List[str], default: None - Names of lidar datum - camera_datum_names: None or List[str], default: None - Names of camera_datums - render_pointcloud_on_images: bool, default: True - Whether or not to render projected pointclouds on images. - show_instance_id_on_bev: bool, default: True + lidar_datum_names: None or List[str], optional + Names of lidar datum. Default: None. + camera_datum_names: None or List[str], optional + Names of camera_datums. Default: None. + render_pointcloud_on_images: bool, optional + Whether or not to render projected pointclouds on images. Default: True. + show_instance_id_on_bev: bool, optional If True, then show `instance_id` on a corner of 3D bounding boxes in BEV view. If False, then show `class_name` instead. - class_colormap: dict or None, default: None - Dict of class name to RGB color tuple. If None, then use class color defined in `dataset`. - adjust_lightness_factor: float, default: 1.0 - Enhance the brightness of colormap by this factor. - rgb_resize_factor: float, default: 0.5 - Resize images by this factor before tiling them into a single panel. - rgb_border_thickness: int, default: 10 + Default: True. + class_colormap: dict or None, optional + Dict of class name to RGB color tuple. If None, then use class color defined in `dataset`. Default: None. + adjust_lightness_factor: float, optional + Enhance the brightness of colormap by this factor. Default: 1.0. + rgb_resize_factor: float, optional + Resize images by this factor before tiling them into a single panel. Default: 0.5. + rgb_border_thickness: int, optional Put a colored boundary on camera visualization of this thickness before tiling them into single panel. - bev-*: - See `BEVImage` for these keyword arguments. - bbox3d-*: - See `geometry.BoundingBox3D` for these keyword arguments. - pc_rgb-*: - See `render_pointcloud_on_image()` for these keyword arguments. - radar_datum_names: None or List[str], default: None - Names of the radar datums + Default: 4. + bev_metric_width: float, optional + See `BEVImage` for this keyword argument. Default: 100.0. + bev_metric_height: float, optional + See `BEVImage` for this keyword argument. Default: 100.0. + bev_pixels_per_meter: float, optional + See `BEVImage` for this keyword argument. Default: 10.0. + bev_polar_step_size_meters: int, optional + See `BEVImage` for this keyword argument. Default: 10. + bev_forward: tuple, optional + See `BEVImage` for this keyword argument. Default: (1, 0, 0). + bev_left: tuple, optional + See `BEVImage` for this keyword argument. Default: (0, 1, 0). + bev_background_clr: tuple, optional + See `BEVImage` for this keyword argument. Default: (0, 0, 0). + bev_font_scale: float, optional + See `BEVImage` for this keyword argument. Default: 0.5. + bev_line_thickness: int, optional + See `BEVImage` for this keyword argument. Default: 4. + bbox3d_font_scale: float, optional + See `geometry.BoundingBox3D` for these keyword arguments. Default: 1.0. + bbox3d_line_thickness: int, optional + See `geometry.BoundingBox3D` for these keyword arguments. Default: 4. + pc_rgb_cmap: optional + See `render_pointcloud_on_image()` for these keyword arguments. The value type comes from + matplotlib.cm.get_cmap. Default: MPL_JET_CMAP. + pc_rgb_norm_depth: int, optional + See `render_pointcloud_on_image()` for these keyword arguments. Default: 10. + pc_rgb_dilation: int, optional + See `render_pointcloud_on_image()` for these keyword arguments. Default: 3. + radar_datum_names: None or List[str], optional + Names of the radar datums. Default: None. Returns ------- @@ -560,24 +615,28 @@ def visualize_dataset_sample_2d( dataset: _SynchronizedDataset A multimodel dataset of which `__getitem__` returns a list of `OrderedDict`, one item for each datum. - camera_datum_names: None or List[str], default: None + scene_idx: int + The scene index into the dataset. + + sample_idx: int + The sample index into the scene. + + camera_datum_names: None or List[str], optional Names of camera_datums. If None, then use all datums whose type is `image` and are available in all scenes. In the output video, the image visualizations are tiled in row-major order according to the order of this list. + Default: None. - max_num_items: None or int, default: None - If not None, then show only up to this number of items. This is useful for debugging a large dataset. - - show_instance_id: bool, default: False - Option to show instance id instead of instance class name on annotated images. + show_instance_id: bool, optional + Option to show instance id instead of instance class name on annotated images. Default: False. - class_colormap: dict or None, default: None - Dict of class name to RGB color tuple. If None, then use class color defined in `dataset`. + class_colormap: dict or None, optional + Dict of class name to RGB color tuple. If None, then use class color defined in `dataset`. Default: None. - adjust_lightness_factor: float, default: 1.0 - Enhance the brightness of colormap by this factor. + adjust_lightness_factor: float, optional + Enhance the brightness of colormap by this factor. Default: 1.0. - rgb_resize_factor: float, default: 0.5 - Resize images by this factor before tiling them into a single panel. + rgb_resize_factor: float, optional + Resize images by this factor before tiling them into a single panel. Default: 0.5. Returns ------- diff --git a/dgp/utils/visualization_utils.py b/dgp/utils/visualization_utils.py index 61dc6e35..0b466a8c 100644 --- a/dgp/utils/visualization_utils.py +++ b/dgp/utils/visualization_utils.py @@ -21,14 +21,14 @@ def make_caption(dataset, idx, prefix=''): """Make caption that tells scene directory and sample index. - Paramters + Parameters --------- dataset: BaseDataset - BaseDataset. e.g. ParallelDomainScene(Dataset), SynchronizedScene(Dataset) + BaseDataset. e.g. ParallelDomainScene(Dataset), SynchronizedScene(Dataset). idx: int - Image index - prefix: str - Caption prefix + Image index. + prefix: str, optional + Caption prefix. Default: "". Returns ------- @@ -45,8 +45,8 @@ def print_status(image, text): Parameters ---------- - image: np.array of shape (H, W, 3) - Image to print status on. + image: np.ndarray + Image to print status on. Shape is (H, W, 3). text: str Text to be printed. @@ -70,17 +70,17 @@ def mosaic(items, scale=1.0, pad=3, grid_width=None): Parameters ---------- - items: list of np.ndarray + items: List[np.ndarray] List of images to mosaic. - scale: float, default=1.0 - Scale factor applied to images. scale > 1.0 enlarges images. + scale: float, optional + Scale factor applied to images. scale > 1.0 enlarges images. Default: 1.0. - pad: int, default=3 - Padding size of the images before mosaic + pad: int, optional + Padding size of the images before mosaic. Default: 3. - grid_width: int, default=None - Mosaic width or grid width of the mosaic + grid_width: int, optional + Mosaic width or grid width of the mosaic. Default: None. Returns ------- @@ -121,20 +121,20 @@ def render_bbox2d_on_image(img, bboxes2d, instance_masks=None, colors=None, text img: np.ndarray Image to render bounding boxes onto. - bboxes2d: np.ndarray (N x 4) - Array of 2d bounding box (x, y, w, h). + bboxes2d: np.ndarray + Array of 2d bounding box (x, y, w, h). Shape is (N x 4). - instance_masks: list - List of binary instance masks cropped to (w,h). + instance_masks: list, optional + List of binary instance masks cropped to (w,h). Default: None. - colors: list - List of color tuples. + colors: list, optional + List of color tuples. Default: None. - texts: list, default: None - List of str classes. + texts: list, optional + List of str classes. Default: None. - line_thickness: int - Line thickness value for bounding box edges. + line_thickness: int, optional + Line thickness value for bounding box edges. Default: 4. Returns ------- @@ -178,18 +178,17 @@ def visualize_bounding_box_2d(image, bounding_box_2d, ontology, debug=False): Parameters ---------- - image: np.uint8 array - Image to visualize boxes on in BGR format - shape: (H, W, 3) + image: np.ndarray + Image to visualize boxes on in BGR format. Data type is uint8. Shape is (H, W, 3). bounding_box_2d: dgp.proto.annotations_pb2.BoundingBox2DAnnotations - Bounding box annotations + Bounding box annotations. ontology: dgp.proto.ontology_pb2.Ontology - Ontology with which to visualize (for colors and class names) + Ontology with which to visualize (for colors and class names). - debug: bool, default: False - If True render image until key-press + debug: bool, optional + If True render image until key-press. Default: False. Returns ------- @@ -221,17 +220,17 @@ def render_pointcloud_on_image(img, camera, Xw, cmap=MPL_JET_CMAP, norm_depth=10 camera: Camera Camera object with appropriately set extrinsics wrt world. - Xw: np.ndarray (N x 3) - 3D point cloud (x, y, z) in the world coordinate. + Xw: np.ndarray + 3D point cloud (x, y, z) in the world coordinate. Shape is (N x 3). - cmap: matplotlib.colors.Colormap - Colormap used for visualizing the inverse depth. + cmap: matplotlib.colors.Colormap, optional + Colormap used for visualizing the inverse depth. Default: MPL_JET_CMAP. - norm_depth: float, default: 10 - Depth value to normalize (inverse) pointcloud depths in color mapping. + norm_depth: float, optional + Depth value to normalize (inverse) pointcloud depths in color mapping. Default: 10.0. - dilation: int, default: 3 - Dilation factor applied on each point. + dilation: int, optional + Dilation factor applied on each point. Default: 3. Returns ------- @@ -274,23 +273,23 @@ def render_radar_pointcloud_on_image( camera: Camera Camera object with appropriately set extrinsics wrt world. - Xw: np.ndarray (N x 8) - point cloud in spherical coordinates of radar sensor frame + point_cloud: np.ndarray + point cloud in spherical coordinates of radar sensor frame. Shape is (N x 8). cmap: matplotlib.colors.Colormap Colormap used for visualizing the inverse depth. - norm_depth: float, default: 10 - Depth value to normalize (inverse) pointcloud depths in color mapping. + norm_depth: float, optional + Depth value to normalize (inverse) pointcloud depths in color mapping. Default: 10. - velocity: numpy array with shape (N,3), default None - velocity vector of points + velocity: np.ndarray + Velocity vector of points. Shape is (N, 3). Default: None. - velocity_scale: float - factor to scale velocity vector by + velocity_scale: float, optional + Factor to scale velocity vector by. Default: 1.0. - velocity_max_pix: float - Maximum length of velocity vector rendering in percent of image width + velocity_max_pix: float, optional + Maximum length of velocity vector rendering in percent of image width. Default: 0.05. Returns ------- @@ -450,14 +449,15 @@ def render_point_cloud(self, point_cloud, extrinsics=Pose(), color=GRAY): Parameters ---------- - point_cloud: numpy array with shape (N, 3) - 3D cloud points in the sensor coordinate frame. + point_cloud: np.ndarray + 3D cloud points in the sensor coordinate frame. Shape is (N, 3). - extrinsics: Pose, default: Identity pose + extrinsics: Pose, optional The pose of the pointcloud sensor wrt the body frame (Sensor frame -> (Vehicle) Body frame). + Default: Identity pose. - color: Tuple[int] - Color in RGB to render the points. + color: Tuple[int], optional + Color in RGB to render the points. Default: GRAY. """ combined_transform = self._bev_rotation * extrinsics @@ -485,23 +485,24 @@ def render_radar_point_cloud( Parameters ---------- - radar_point_cloud: numpy array with shape (N, 3) - point cloud in rectangular coordinates of sensor frame + point_cloud: np.ndarray + Point cloud in rectangular coordinates of sensor frame. Shape is (N, 3). - extrinsics: Pose, default: Identity pose + extrinsics: Pose, optional The pose of the pointcloud sensor wrt the body frame (Sensor frame -> (Vehicle) Body frame). + Default: Identity pose. - color: Tuple[int] - Color in RGB to render the points. + color: Tuple[int], optional + Color in RGB to render the points. Default: RED. - velocity: numpy array with shape (N,3), default None - velocity vector of points + velocity: np.ndarray, optional + Velocity vector of points. Shape: (N, 3). Default: None. - velocity_scale: float - factor to scale velocity vector by + velocity_scale: float, optional + Factor to scale velocity vector by. Default: 1.0. - velocity_max_pix: float - Maximum length of velocity vector rendering in percent of image width + velocity_max_pix: float, optional + Maximum length of velocity vector rendering in percent of image width. Default: 0.05. """ combined_transform = self._bev_rotation * extrinsics @@ -562,17 +563,18 @@ def render_paths(self, paths, extrinsics=Pose(), colors=(GREEN, ), line_thicknes paths: list[list[Pose]] List of object poses in the coordinate frame of the current timestep. - extrinsics: Pose, default: Identity pose + extrinsics: Pose, optional The pose of the pointcloud sensor wrt the body frame (Sensor frame -> (Vehicle) Body frame). + Default: Identity pose. - colors: List of RGB tuple, default: [GREEN,] - Draw path using this color. + colors: List[Tuple[int, int, int]], optional + Draw path using this color. Default: (GREEN, ). - line_thickness: int, default: 1 - Thickness of lines. + line_thickness: int, optional + Thickness of lines. Default: 1. - tint: float, default: 1.0 - Mulitiplicative factor applied to color used to darken lines. + tint: float, optional + Mulitiplicative factor applied to color used to darken lines. Default: 1.0. """ if len(colors) == 1: @@ -613,42 +615,44 @@ def render_bounding_box_3d( Parameters ---------- - bboxes3d: List of BoundingBox3D + bboxes3d: List[BoundingBox3D] 3D annotations in the sensor coordinate frame. - extrinsics: Pose, default: Identity pose + extrinsics: Pose, optional The pose of the pointcloud sensor wrt the body frame (Sensor frame -> (Vehicle) Body frame). + Default: Identity pose. - colors: List of RGB tuple, default: [GREEN,] - Draw boxes using this color. + colors: Sequence[Tuple[int, int, int]], optional + Draw boxes using this color where each color is an RGB tuple. Default: (GREEN, ). - side_color_fraction: float, default: 0.6 - A fraction in brightness of side edge colors of bounding box wrt the front face. + side_color_fraction: float, optional + A fraction in brightness of side edge colors of bounding box wrt the front face. Default: 0.6. - rear_color_fraction: float, default: 0.3 - A fraction in brightness of rear face colors of bounding box wrt the front face. + rear_color_fraction: float, optional + A fraction in brightness of rear face colors of bounding box wrt the front face. Default: 0.3. - texts: list of str, default: None - 3D annotation category name. + texts: list of str, optional + 3D annotation category name. Default: None. - line_thickness: int, default: 2 - Thickness of lines. + line_thickness: int, optional + Thickness of lines. Default: 2. - font_scale: float, default: 0.5 - Font scale used for text labels. + font_scale: float, optional + Font scale used for text labels. Default: 0.5. - font_colors: List of RGB tuple, default: [WHITE,] - Color used for text labels. + font_colors: Sequence[Tuple[int, int, int]], optional + Color used for text labels. Default: (WHITE, ). - markers: List[int], default: None + markers: List[int], optional List of opencv markers to draw in bottom right corner of cuboid. Should be one of: cv2.MARKER_CROSS, cv2.MARKER_DIAMOND, cv2.MARKER_SQUARE, cv2.MARKER_STAR, cv2.MARKER_TILTED_CROSS, cv2.MARKER_TRIANGLE_DOWN, cv2.MARKER_TRIANGLE_UP, or None. + Default: None. - marker_scale: float, default: .5 - Scale factor for markers, + marker_scale: float, optional + Scale factor for markers. Default: 0.5. - marker_colors: List of RGB Tuple, default: [RED,] - Draw markers using this color. + marker_colors: Sequence[Tuple[int, int, int]], optional + Draw markers using this color. Default: (RED, ). """ if len(colors) == 1: @@ -733,11 +737,11 @@ def render_camera_frustrum(self, intrinsics, extrinsics, width, color=YELLOW, li width: int Width of image. - color: Tuple[int], default: Yellow - Color in RGB of line. + color: Tuple[int], optional + Color in RGB of line. Default: YELLOW. - line_thickness: int, default: 1 - Thickness of line. + line_thickness: int, optional + Thickness of line. Default: 1. """ K_inv = np.linalg.inv(intrinsics) @@ -769,8 +773,8 @@ def ontology_to_viz_colormap(ontology, void_class_id=255): ontology: dgp.proto.ontology_pb2.Ontology DGP ontology object for which we want to create a viz-friendly colormap look-up - void_class_id: int, default: 255 - Class ID used to denote VOID or IGNORE in ontology + void_class_id: int, optional + Class ID used to denote VOID or IGNORE in ontology. Default: 255. Returns ------- @@ -806,17 +810,18 @@ def visualize_semantic_segmentation_2d( ontology: dgp.proto.ontology_pb2.Ontology Ontology under which we want to visualize the semseg frame - void_class_id: int, default: 255 - ID in `semantic_segmentation_2d` that denotes VOID or IGNORE + void_class_id: int, optional + ID in `semantic_segmentation_2d` that denotes VOID or IGNORE. Default: 255. - image: np.uint8 array, default: None - If specified then will blend image into visualization with weight `alpha` + image: np.ndarray, optional + If specified then will blend image into visualization with weight `alpha`. Element type is uint8. Default: None. - alpha: float, default: 0.3 - If `image` is specified, then will visualize an image/semseg blend with `alpha` weight given to image + alpha: float, optional + If `image` is specified, then will visualize an image/semseg blend with `alpha` weight given to image. + Default: 0.3. - debug: bool, default: True - If True then visualize frame to display + debug: bool, optional + If True then visualize frame to display. Default: True. Returns ------- @@ -877,36 +882,60 @@ def visualize_bev( List of lidar datums as a dictionary. class_colormap: Dict Mapping from class IDs to RGB colors. - show_instance_id_on_bev: Bool, default: False + show_instance_id_on_bev: Bool, optional If True, then show `instance_id` on a corner of 3D bounding boxes in BEV view. If False, then show `class_name` instead. - id_to_name: OrderedDict, default: None - Mapping from class IDs to class names. - camera_datums: List[OrderedDict], default: None - List of camera datums as a dictionary. - camera_colors: List[Tuple[int]], default: None - List of RGB colors associated with each camera. The colors are used to draw frustrum. - bev_*: - See `BEVImage` for these keyword arguments. - radar_datums: List[OrderedDict], default: None - List of radar datums to visualize - instance_colormap: Dict - Mapping from instance id to RGB colors. - cuboid_caption_fn: Callable, BoundingBox3d -> Tuple[String,Tuple[3]] + Default: False. + id_to_name: OrderedDict, optional + Mapping from class IDs to class names. Default: None. + camera_datums: List[OrderedDict], optional + List of camera datums as a dictionary. Default: None. + camera_colors: List[Tuple[int]], optional + List of RGB colors associated with each camera. The colors are used to draw frustrum. Default: None. + bev_metric_width: int, optional + See `BEVImage` for this keyword argument. Default: 100. + bev_metric_height: int, optional + See `BEVImage` for this keyword argument. Default: 100. + bev_pixels_per_meter: int, optional + See `BEVImage` for this keyword argument. Default: 10. + bev_polar_step_size_meters: int, optional + See `BEVImage` for this keyword argument. Default: 10. + bev_forward: tuple of int, optional + See `BEVImage` for this keyword argument. Default: (1, 0, 0) + bev_left: tuple of int, optional + See `BEVImage` for this keyword argument. Default: (0, 1, 0) + bev_background_clr: tuple of int, optional + See `BEVImage` for this keyword argument. Default: (0, 0, 0) + bev_line_thickness: int, optional + See `BEVImage` for this keyword argument. Default: 4. + bev_font_scale: float, optional + See `BEVImage` for this keyword argument. Default: 0.5. + radar_datums: List[OrderedDict], optional + List of radar datums to visualize. Default: None. + instance_colormap: Dict, optional + Mapping from instance id to RGB colors. Default: None. + cuboid_caption_fn: Callable, optional Function taking a BoundingBox3d object and returning a tuple with the caption string, and the rgb - value for that caption. e.g., ( 'car', (255,0,0) ) - marker_fn: Callable, BoundingBox3d -> Tuple[int,Tuple[3]] + value for that caption. e.g., ( 'car', (255,0,0) ). + Signature: f(BoundingBox3d) -> Tuple[String, Tuple[3]] + Default: None. + marker_fn: Callable, optional Function taking a BoundingBox3d object and returning a tuple with the caption a marker id, and the rgb value for that marker. e.g., ( cv2.MARKER_DIAMOND, (255,0,0) ). Marker should be one of cv2.MARKER_CROSS, cv2.MARKER_DIAMOND, cv2.MARKER_SQUARE, cv2.MARKER_STAR, cv2.MARKER_TILTED_CROSS, cv2.MARKER_TRIANGLE_DOWN, cv2.MARKER_TRIANGLE_UP, or None. - show_paths_on_bev: Bool, default: False + Signature: f(BoundingBox3d) -> Tuple[int,Tuple[3]] + Default: None. + marker_scale: float, optional + Default: 0.5. + show_paths_on_bev: bool, optional If true draw a path for each cuboid. Paths are stored in cuboid attributes under the 'path' key, i.e., path = cuboid.attributes['path'], paths are themselves a list of pose objects transformed to the correct frame. This method does not handle creating or transforming the paths. - bev_enter_offset_w: int, default: 0 - Offset in pixels to move ego center in BEV. - bev_center_offset_h: int, default: 0 - Offset in pixels to move ego center in BEV. + Default: False. + bev_center_offset_w: int, optional + Offset in pixels to move ego center in BEV. Default: 0. + bev_center_offset_h: int, optional + Offset in pixels to move ego center in BEV. Default: 0. Returns ------- @@ -1023,24 +1052,26 @@ def visualize_cameras( """Create camera visualization that shows 3D bounding boxes, and optionally projected pointcloud. Parameters ---------- - camera_datums: List[OrderedDict], default: None + camera_datums: List[OrderedDict] List of camera datums as a dictionary. - id_to_name: OrderedDict, default: None + id_to_name: OrderedDict Mapping from class IDs to class names. - lidar_datums: List[OrderedDict] or None, default: None - List of lidar datums as a dictionary. If given, then draw pointcloud contained in all datums. - rgb_resize_factor: float, default: 1.0 - Resize images by this factor before tiling them into a single panel. - bbox3d_font_scale: float, default: 1.0 - Font scale used for text labels. - bbox3d_line_thickness: int, default: 4 - Thickness of lines used for drawing 3D bounding boxes. - pc_rgb_norm_depth: int, default: 10 - Depth value to normalize (inverse) pointcloud depths in color mapping. - pc_rgb_dilation: int, default: 3 - Dilation factor applied on each point in pointcloud. - radar_datums: List[OrderedDict], default: None - List of radar datums to visualize + lidar_datums: List[OrderedDict] or None, optional + List of lidar datums as a dictionary. If given, then draw pointcloud contained in all datums. Default: None. + rgb_resize_factor: float, optional + Resize images by this factor before tiling them into a single panel. Default: 1.0. + bbox3d_font_scale: float, optional + Font scale used for text labels. Default: 1.0. + bbox3d_line_thickness: int, optional + Thickness of lines used for drawing 3D bounding boxes. Default: 4. + pc_rgb_cmap: color_map + A matplotlib color map. Default: MPL_JET_CMAP. + pc_rgb_norm_depth: int, optional + Depth value to normalize (inverse) pointcloud depths in color mapping. Default: 10. + pc_rgb_dilation: int, optional + Dilation factor applied on each point in pointcloud. Default: 3. + radar_datums: List[OrderedDict], optional + List of radar datums to visualize. Default: None. Returns ------- rgb_viz: List[np.ndarray] diff --git a/tests/test_datasets_timeout.py b/tests/test_datasets_timeout.py index 50a2b69a..43cc3e29 100644 --- a/tests/test_datasets_timeout.py +++ b/tests/test_datasets_timeout.py @@ -18,7 +18,7 @@ ] ) @pytest.mark.timeout(6.0) # Repeat 10 times, takes ~0.6 second to instantiate FrameSceneDataset. -def test_create_frame_scene_dataset( +def test_create_frame_scene_dataset( # pylint: disable=missing-any-param-doc split, datum_names, requested_autolabels, only_annotated_datums, use_diskcache, requested_annotations, skip_missing_data, expected_len ): @@ -47,7 +47,7 @@ def test_create_frame_scene_dataset( ] ) @pytest.mark.timeout(6.0) # Repeat 10 times, takes ~0.6 second to instantiate SynchronizedSceneDataset. -def test_create_sync_scene_dataset( +def test_create_sync_scene_dataset( # pylint: disable=missing-any-param-doc split, datum_names, forward_context, backward_context, generate_depth_from_datum, requested_annotations, expected_len ): diff --git a/tests/utilities.py b/tests/utilities.py index 628683f1..3af3b409 100644 --- a/tests/utilities.py +++ b/tests/utilities.py @@ -5,6 +5,15 @@ def requires_env(*envs): """ annotation that only runs tests in specified workflow or environment + + Parameters + ---------- + *envs: Sequence of str + One or more string names identifying the runtime environment we're in. Ex: `test`. + + Returns + ------- + None """ env = os.environ.get('ENVIRONMENT', 'test') diff --git a/tests/utils/test_utils_colors.py b/tests/utils/test_utils_colors.py index 2dc854c8..38348786 100644 --- a/tests/utils/test_utils_colors.py +++ b/tests/utils/test_utils_colors.py @@ -16,7 +16,7 @@ (3, False, 'Paired', [(166, 206, 227), (31, 120, 180), (178, 223, 138)]), ] ) -def test_get_unique_colors(num_colors, in_bgr, cmap, expected_map): +def test_get_unique_colors(num_colors, in_bgr, cmap, expected_map): # pylint: disable=missing-any-param-doc ''' Uses parametrized testing to run multiple cases for colorbar retreival '''