diff --git a/armory/datasets/adversarial/carla_obj_det_dev/__init__.py b/armory/datasets/adversarial/carla_obj_det_dev/__init__.py new file mode 100644 index 000000000..3d1c26be2 --- /dev/null +++ b/armory/datasets/adversarial/carla_obj_det_dev/__init__.py @@ -0,0 +1,3 @@ +"""carla_obj_det_dev dataset.""" + +from .carla_obj_det_dev import CarlaObjDetDev diff --git a/armory/datasets/adversarial/carla_obj_det_dev/carla_obj_det_dev.py b/armory/datasets/adversarial/carla_obj_det_dev/carla_obj_det_dev.py new file mode 100644 index 000000000..de56db940 --- /dev/null +++ b/armory/datasets/adversarial/carla_obj_det_dev/carla_obj_det_dev.py @@ -0,0 +1,225 @@ +"""carla_obj_det_dev dataset.""" + +import collections +import json +import os +from copy import deepcopy +import numpy as np + +import tensorflow as tf +import tensorflow_datasets as tfds + +_DESCRIPTION = """ +Synthetic multimodality (RGB, depth) dataset generated using CARLA (https://carla.org). +""" + +_CITATION = """ +@inproceedings{Dosovitskiy17, + title = { {CARLA}: {An} Open Urban Driving Simulator}, + author = {Alexey Dosovitskiy and German Ros and Felipe Codevilla and Antonio Lopez and Vladlen Koltun}, + booktitle = {Proceedings of the 1st Annual Conference on Robot Learning}, + pages = {1--16}, + year = {2017} +} +""" + +# fmt: off +_URLS = "https://armory-public-data.s3.us-east-2.amazonaws.com/carla/carla_od_dev_2.0.0.tar.gz" +# fmt: on + + +class CarlaObjDetDev(tfds.core.GeneratorBasedBuilder): + """DatasetBuilder for carla_obj_det_dev dataset.""" + + VERSION = tfds.core.Version("2.0.0") + RELEASE_NOTES = { + "1.0.0": "Initial release.", + "1.0.1": "Correcting error to RGB and depth image pairing", + "2.0.0": "Eval5 update with higher resolution, HD textures, accurate annotations, and objects overlapping patch", + } + + def _info(self) -> tfds.core.DatasetInfo: + """Returns the dataset metadata.""" + features = { + # sequence of [RGB, depth] images + "image": tfds.features.Sequence( + tfds.features.Image(shape=(960, 1280, 3)), + length=2, + ), + # sequence of image features for [RGB, depth] + "images": tfds.features.Sequence( + tfds.features.FeaturesDict( + { + "file_name": tfds.features.Text(), + "height": tf.int64, + "width": tf.int64, + "id": tf.int64, + }, + ), + length=2, + ), + # both modalities share the same categories + "categories": tfds.features.Sequence( + tfds.features.FeaturesDict( + { + "id": tf.int64, # {'pedstrian':1, 'vehicles':2, 'trafficlight':3} + "name": tfds.features.Text(), + "supercategory": tfds.features.Text(), + } + ) + ), + # both modalities share the same objects + "objects": tfds.features.Sequence( + { + "id": tf.int64, + "image_id": tf.int64, + "area": tf.int64, # un-normalized area + "boxes": tfds.features.BBoxFeature(), # normalized bounding box [ymin, xmin, ymax, xmax] + "labels": tfds.features.ClassLabel(num_classes=5), + "is_crowd": tf.bool, + } + ), + # these data only apply to the "green screen patch" objects, which both modalities share + "patch_metadata": tfds.features.FeaturesDict( + { + # green screen vertices in (x,y) starting from top-left moving clockwise + "gs_coords": tfds.features.Tensor(shape=[4, 2], dtype=tf.int32), + # binarized segmentation mask of patch. + # mask[x,y] == 1 indicates patch pixel; 0 otherwise + "mask": tfds.features.Image(shape=(960, 1280, 3)), + "avg_patch_depth": tfds.features.Tensor(shape=(), dtype=tf.float64), + } + ), + } + + return tfds.core.DatasetInfo( + builder=self, + description=_DESCRIPTION, + features=tfds.features.FeaturesDict(features), + citation=_CITATION, + ) + + def _split_generators(self, dl_manager: tfds.download.DownloadManager): + """Returns SplitGenerators.""" + path = dl_manager.download_and_extract(_URLS) + return {"dev": self._generate_examples(os.path.join(path, "dev"))} + + def _generate_examples(self, path): + """yield examples""" + + # For each image, gets its annotations and yield relevant data + depth_folder = "_out/sensor.camera.depth.2" + foreground_mask_folder = "_out/foreground_mask" + patch_metadata_folder = "_out/patch_metadata" + + annotation_path = os.path.join( + path, "_out", "kwcoco_annotations_without_patch_and_sans_tiny_objects.json" + ) + + cocoanno = COCOAnnotation(annotation_path) + + images_rgb = ( + cocoanno.images() + ) # list of dictionaries of RGB image id, height, width, file_name + + # sort images alphabetically + images_rgb = sorted(images_rgb, key=lambda x: x["file_name"].lower()) + + for idx, image_rgb in enumerate(images_rgb): + + # Discard irrelevant fields + image_rgb.pop("date_captured") + image_rgb.pop("license") + image_rgb.pop("coco_url") + image_rgb.pop("flickr_url") + image_rgb.pop("video_id") + image_rgb.pop("frame_index") + + # Pairing RGB and depth + fpath_rgb = image_rgb["file_name"] # rgb image path + fname = fpath_rgb.split("/")[-1] + fname_no_ext = fname.split(".")[0] + fpath_depth = os.path.join(depth_folder, fname) # depth image path + image_depth = deepcopy(image_rgb) + image_depth["file_name"] = fpath_depth + + # get object annotations for each image + annotations = cocoanno.get_annotations(image_rgb["id"]) + + # For unknown reasons, when kwcoco is saved after removing tiny objects, + # bbox format changes from [x,y,w,h] to [x1,y1,x2,y1] + def build_bbox(x1, y1, x2, y2): + return tfds.features.BBox( + ymin=y1 / image_rgb["height"], + xmin=x1 / image_rgb["width"], + ymax=y2 / image_rgb["height"], + xmax=x2 / image_rgb["width"], + ) + + example = { + "image": [ + os.path.join( + path, + modality, + ) + for modality in [fpath_rgb, fpath_depth] + ], + "images": [image_rgb, image_depth], + "categories": cocoanno.categories(), + "objects": [ + { + "id": anno["id"], + "image_id": anno["image_id"], + "area": anno["area"], + "boxes": build_bbox(*anno["bbox"]), + "labels": anno["category_id"], + "is_crowd": bool(anno["iscrowd"]), + } + for anno in annotations + ], + "patch_metadata": { + "gs_coords": np.load( + os.path.join( + path, patch_metadata_folder, fname_no_ext + "_coords.npy" + ) + ), + "avg_patch_depth": np.load( + os.path.join( + path, patch_metadata_folder, fname_no_ext + "_avg_depth.npy" + ) + ), + "mask": os.path.join(path, foreground_mask_folder, fname), + }, + } + + yield idx, example + + +class COCOAnnotation(object): + """COCO annotation helper class.""" + + def __init__(self, annotation_path): + with tf.io.gfile.GFile(annotation_path) as f: + data = json.load(f) + self._data = data + + # for each images["id"], find all annotations such that annotations["image_id"] == images["id"] + img_id2annotations = collections.defaultdict(list) + for a in self._data["annotations"]: + img_id2annotations[a["image_id"]].append(a) + self._img_id2annotations = { + k: list(sorted(v, key=lambda a: a["id"])) + for k, v in img_id2annotations.items() + } + + def categories(self): + """Return the category dicts, as sorted in the file.""" + return self._data["categories"] + + def images(self): + """Return the image dicts, as sorted in the file.""" + return self._data["images"] + + def get_annotations(self, img_id): + """Return all annotations associated with the image id string.""" + return self._img_id2annotations.get(img_id, []) diff --git a/armory/datasets/adversarial/carla_obj_det_dev/checksums.tsv b/armory/datasets/adversarial/carla_obj_det_dev/checksums.tsv new file mode 100644 index 000000000..27212e871 --- /dev/null +++ b/armory/datasets/adversarial/carla_obj_det_dev/checksums.tsv @@ -0,0 +1 @@ +https://armory-public-data.s3.us-east-2.amazonaws.com/carla/carla_od_dev_2.0.0.tar.gz 67507963 30c7593817867eb97b3c7e1358451c576805bb4423599b09ad99f15a2ebdd5c9 carla_od_dev_2.0.0.tar.gz diff --git a/armory/datasets/adversarial/carla_video_tracking_dev/__init__.py b/armory/datasets/adversarial/carla_video_tracking_dev/__init__.py new file mode 100644 index 000000000..fb635dafb --- /dev/null +++ b/armory/datasets/adversarial/carla_video_tracking_dev/__init__.py @@ -0,0 +1,3 @@ +"""carla_video_tracking_dev dataset.""" + +from .carla_video_tracking_dev import CarlaVideoTrackingDev diff --git a/armory/data/adversarial/carla_video_tracking_dev.py b/armory/datasets/adversarial/carla_video_tracking_dev/carla_video_tracking_dev.py old mode 100755 new mode 100644 similarity index 91% rename from armory/data/adversarial/carla_video_tracking_dev.py rename to armory/datasets/adversarial/carla_video_tracking_dev/carla_video_tracking_dev.py index 7795850fc..e1cc403e7 --- a/armory/data/adversarial/carla_video_tracking_dev.py +++ b/armory/datasets/adversarial/carla_video_tracking_dev/carla_video_tracking_dev.py @@ -4,7 +4,7 @@ import glob import numpy as np from PIL import Image -import tensorflow.compat.v1 as tf +import tensorflow as tf import tensorflow_datasets as tfds _DESCRIPTION = """ @@ -21,7 +21,7 @@ } """ -_URLS = "https://armory-public-data.s3.us-east-2.amazonaws.com/carla/carla_video_tracking_dev_2.0.0.tar.gz" +_URL = "https://armory-public-data.s3.us-east-2.amazonaws.com/carla/carla_video_tracking_dev_2.0.0.tar.gz" class CarlaVideoTrackingDev(tfds.core.GeneratorBasedBuilder): @@ -70,14 +70,9 @@ def _info(self) -> tfds.core.DatasetInfo: def _split_generators(self, dl_manager: tfds.download.DownloadManager): """Returns SplitGenerators.""" - path = dl_manager.download_and_extract(_URLS) - - return [ - tfds.core.SplitGenerator( - name="dev", - gen_kwargs={"path": os.path.join(path, "dev")}, - ) - ] + path = dl_manager.download_and_extract(_URL) + + return {"dev": self._generate_examples(path / "dev")} def _generate_examples(self, path): """Yields examples.""" diff --git a/armory/datasets/adversarial/carla_video_tracking_dev/checksums.tsv b/armory/datasets/adversarial/carla_video_tracking_dev/checksums.tsv new file mode 100644 index 000000000..585b6fe06 --- /dev/null +++ b/armory/datasets/adversarial/carla_video_tracking_dev/checksums.tsv @@ -0,0 +1 @@ +https://armory-public-data.s3.us-east-2.amazonaws.com/carla/carla_video_tracking_dev_2.0.0.tar.gz 1278862237 8b23ca76bd9602a8e3ff4058335b7fb8ca665660a8a958852715e9a26ffbef20 carla_video_tracking_dev_2.0.0.tar.gz diff --git a/armory/datasets/adversarial/carla_video_tracking_test/__init__.py b/armory/datasets/adversarial/carla_video_tracking_test/__init__.py new file mode 100644 index 000000000..4b0b22011 --- /dev/null +++ b/armory/datasets/adversarial/carla_video_tracking_test/__init__.py @@ -0,0 +1,3 @@ +"""carla_video_tracking_test dataset.""" + +from .carla_video_tracking_test import CarlaVideoTrackingTest diff --git a/armory/data/adversarial/carla_video_tracking_test.py b/armory/datasets/adversarial/carla_video_tracking_test/carla_video_tracking_test.py similarity index 91% rename from armory/data/adversarial/carla_video_tracking_test.py rename to armory/datasets/adversarial/carla_video_tracking_test/carla_video_tracking_test.py index ee10aed0d..b19d6d29d 100644 --- a/armory/data/adversarial/carla_video_tracking_test.py +++ b/armory/datasets/adversarial/carla_video_tracking_test/carla_video_tracking_test.py @@ -4,7 +4,7 @@ import glob import numpy as np from PIL import Image -import tensorflow.compat.v1 as tf +import tensorflow as tf import tensorflow_datasets as tfds @@ -22,7 +22,7 @@ } """ -_URLS = "https://armory-public-data.s3.us-east-2.amazonaws.com/carla/carla_video_tracking_test_2.0.0.tar.gz" +_URL = "https://armory-public-data.s3.us-east-2.amazonaws.com/carla/carla_video_tracking_test_2.0.0.tar.gz" class CarlaVideoTrackingTest(tfds.core.GeneratorBasedBuilder): @@ -71,14 +71,8 @@ def _info(self) -> tfds.core.DatasetInfo: def _split_generators(self, dl_manager: tfds.download.DownloadManager): """Returns SplitGenerators.""" - path = dl_manager.download_and_extract(_URLS) - - return [ - tfds.core.SplitGenerator( - name="test", - gen_kwargs={"path": os.path.join(path, "test")}, - ) - ] + path = dl_manager.download_and_extract(_URL) + return {"test": self._generate_examples(path / "test")} def _generate_examples(self, path): """Yields examples.""" diff --git a/armory/datasets/adversarial/carla_video_tracking_test/checksums.tsv b/armory/datasets/adversarial/carla_video_tracking_test/checksums.tsv new file mode 100644 index 000000000..de1b9276d --- /dev/null +++ b/armory/datasets/adversarial/carla_video_tracking_test/checksums.tsv @@ -0,0 +1 @@ +https://armory-public-data.s3.us-east-2.amazonaws.com/carla/carla_video_tracking_test_2.0.0.tar.gz 387465525 6bd09f5cf50c0e16f34b5054e9d77f95cb4491a373ecb842431cc58ae50b882e carla_video_tracking_test_2.0.0.tar.gz diff --git a/armory/datasets/art_wrapper.py b/armory/datasets/art_wrapper.py index 30d8562e0..7707b8ec2 100644 --- a/armory/datasets/art_wrapper.py +++ b/armory/datasets/art_wrapper.py @@ -9,6 +9,7 @@ class WrappedDataGenerator(DataGenerator): def __init__(self, gen): super().__init__(gen.size, gen.batch_size) self._iterator = gen + self.context = gen.context def __iter__(self): return iter(self._iterator) diff --git a/armory/datasets/cached_datasets.json b/armory/datasets/cached_datasets.json index a3b6a2fd8..8b6f2f3d3 100644 --- a/armory/datasets/cached_datasets.json +++ b/armory/datasets/cached_datasets.json @@ -1,4 +1,11 @@ { + "carla_obj_det_dev": { + "sha256": "a7adc2400d1fafb03f6d49d10b61ac6405382bc19df446ee25e3d4afce2775a4", + "size": 64152715, + "subdir": "carla_obj_det_dev/2.0.0", + "url": null, + "version": "2.0.0" + }, "carla_over_obj_det_dev": { "sha256": "77761f1d5c6eca40984aa40f38fab0568b9bb4a4dca696e876fccaa2dd9be56d", "size": 59760259, @@ -6,6 +13,20 @@ "url": null, "version": "1.0.0" }, + "carla_video_tracking_dev": { + "sha256": "958d470dcd394928050f4123a7af05b0e389ceeec6fa0a3261df55a65e553b69", + "size": 1281628036, + "subdir": "carla_video_tracking_dev/2.0.0", + "url": null, + "version": "2.0.0" + }, + "carla_video_tracking_test": { + "sha256": "8c52281611807243cba425ad3a588f4abca40dfb2b3ab828b9ad8a5191a7df10", + "size": 388218968, + "subdir": "carla_video_tracking_test/2.0.0", + "url": null, + "version": "2.0.0" + }, "digit": { "sha256": "805fb5e33caf2029e13f4146c9d06fdb437ac5b0f0aa9668e3201922b617c559", "size": 8349857, diff --git a/armory/datasets/config_load.py b/armory/datasets/config_load.py index e017e063d..3b00c39b1 100644 --- a/armory/datasets/config_load.py +++ b/armory/datasets/config_load.py @@ -3,6 +3,7 @@ """ from armory.datasets import load, preprocessing, generator, filtering +from armory.datasets.context import contexts def load_dataset( @@ -20,6 +21,7 @@ def load_dataset( index=None, class_ids=None, drop_remainder=False, + context=None, ): # All are keyword elements by design if name is None: @@ -64,6 +66,9 @@ def load_dataset( shuffle_elements = shuffle_files + if context is None and name in contexts: + context = contexts[name] + return generator.ArmoryDataGenerator( info, ds_dict, @@ -78,4 +83,5 @@ def load_dataset( element_map=preprocessing_fn, shuffle_elements=shuffle_elements, key_map=None, + context=context, ) diff --git a/armory/datasets/context.py b/armory/datasets/context.py new file mode 100644 index 000000000..1d449bec3 --- /dev/null +++ b/armory/datasets/context.py @@ -0,0 +1,34 @@ +import tensorflow as tf + + +class ImageContext: + def __init__(self, x_shape): + self.x_shape = x_shape + self.input_type = tf.uint8 + self.input_min = 0 + self.input_max = 255 + + self.quantization = 255 + + self.output_type = tf.float32 + self.output_min = 0.0 + self.output_max = 1.0 + + +class VideoContext(ImageContext): + def __init__(self, x_shape, frame_rate): + super().__init__(x_shape) + self.frame_rate = frame_rate + + +carla_video_tracking_dev_context = VideoContext( + x_shape=(None, 960, 1280, 3), frame_rate=10 +) +carla_video_tracking_test_context = VideoContext( + x_shape=(None, 960, 1280, 3), frame_rate=10 +) + +contexts = { + "carla_video_tracking_dev": carla_video_tracking_dev_context, + "carla_video_tracking_test": carla_video_tracking_test_context, +} diff --git a/armory/datasets/generator.py b/armory/datasets/generator.py index 1224b35b0..14ad54f36 100644 --- a/armory/datasets/generator.py +++ b/armory/datasets/generator.py @@ -39,6 +39,7 @@ class ArmoryDataGenerator: element_filter - predicate of which elements to keep; occurs prior to mapping Note: size computations will be wrong when filtering is applied element_map - function that takes a dataset element (dict) and maps to new element + context - context object """ FRAMEWORKS = ("tf", "numpy", "torch") @@ -58,6 +59,7 @@ def __init__( element_filter: callable = None, element_map: callable = None, key_map=None, + context=None, ): if split not in info.splits: raise ValueError(f"split {split} not in info.splits {list(info.splits)}") @@ -141,6 +143,7 @@ def __init__( shuffle_elements=shuffle_elements, element_filter=element_filter, element_map=element_map, + context=context, ) def _set_params(self, **kwargs): @@ -164,3 +167,13 @@ def wrap_generator(armory_data_generator): from armory.datasets import art_wrapper return art_wrapper.WrappedDataGenerator(armory_data_generator) + + +class EvalGenerator: + """ + Wraps a specified number of batches in a DataGenerator to allow for evaluating on + part of a dataset when running through a scenario + """ + + def __init__(self, armory_generator, num_eval_batches): + raise NotImplementedError("EvalGenerator not implemented") diff --git a/armory/datasets/preprocessing.py b/armory/datasets/preprocessing.py index 91e7c15b1..4d9f441c8 100644 --- a/armory/datasets/preprocessing.py +++ b/armory/datasets/preprocessing.py @@ -4,6 +4,7 @@ import tensorflow as tf +from armory.datasets.context import contexts REGISTERED_PREPROCESSORS = {} @@ -39,24 +40,49 @@ def supervised_image_classification(element): return (image_to_canon(element["image"]), element["label"]) -mnist = register(supervised_image_classification, "mnist") -cifar10 = register(supervised_image_classification, "cifar10") -resisc45 = register(supervised_image_classification, "resisc45") - - @register def digit(element): return (audio_to_canon(element["audio"]), element["label"]) +@register +def carla_obj_det_dev(element, modality="rgb"): + return carla_multimodal_obj_det(element["image"], modality=modality), ( + convert_tf_obj_det_label_to_pytorch(element["image"], element["objects"]), + element["patch_metadata"], + ) + + @register def carla_over_obj_det_dev(element, modality="rgb"): - return carla_over_obj_det_image(element["image"], modality=modality), ( + return carla_multimodal_obj_det(element["image"], modality=modality), ( convert_tf_obj_det_label_to_pytorch(element["image"], element["objects"]), element["patch_metadata"], ) +@register +def carla_video_tracking_dev(element, max_frames=None): + return carla_video_tracking( + element["video"], max_frames=max_frames, split="dev" + ), carla_video_tracking_labels( + element["video"], + (element["bboxes"], element["patch_metadata"]), + max_frames=max_frames, + ) + + +@register +def carla_video_tracking_test(element, max_frames=None): + return carla_video_tracking( + element["video"], max_frames=max_frames, split="test" + ), carla_video_tracking_labels( + element["video"], + (element["bboxes"], element["patch_metadata"]), + max_frames=max_frames, + ) + + @register def xview(element): return image_to_canon(element["image"]), convert_tf_obj_det_label_to_pytorch( @@ -64,6 +90,11 @@ def xview(element): ) +mnist = register(supervised_image_classification, "mnist") +cifar10 = register(supervised_image_classification, "cifar10") +resisc45 = register(supervised_image_classification, "resisc45") + + def image_to_canon(image, resize=None, target_dtype=tf.float32, input_type="uint8"): """ TFDS Image feature uses (height, width, channels) @@ -98,14 +129,6 @@ def audio_to_canon(audio, resample=None, target_dtype=tf.float32, input_type="in return audio -# config = { -# "preprocessor": "mnist(max_frames=1)" -# "preprocessor_kwargs": { -# "max_frames": null, -# } -# } - - def video_to_canon( video, resize=None, @@ -132,7 +155,7 @@ def video_to_canon( return video -def carla_over_obj_det_image(x, modality="rgb"): +def carla_multimodal_obj_det(x, modality="rgb"): if modality == "rgb": return image_to_canon(x[0]) elif modality == "depth": @@ -145,13 +168,152 @@ def carla_over_obj_det_image(x, modality="rgb"): ) +def preprocessing_chain(*args): + """ + Wraps and returns a sequence of preprocessing functions + """ + functions = [x for x in args if x is not None] + if not functions: + return None + + def wrapped(x): + for function in functions: + x = function(x) + return x + + return wrapped + + +def label_preprocessing_chain(*args): + """ + Wraps and returns a sequence of label preprocessing functions. + Note that this function differs from preprocessing_chain() in that + it chains across (x, y) instead of just x + """ + functions = [x for x in args if x is not None] + if not functions: + return None + + def wrapped(x, y): + for function in functions: + y = function(x, y) + return y + + return wrapped + + +def check_shapes(actual, target): + """ + Ensure that shapes match, ignoring None values + + actual and target should be tuples + """ + if type(actual) != tuple: + raise ValueError(f"actual shape {actual} is not a tuple") + if type(target) != tuple: + raise ValueError(f"target shape {target} is not a tuple") + if len(actual) != len(target): + raise ValueError(f"len(actual) {len(actual)} != len(target) {len(target)}") + for a, t in zip(actual, target): + if a != t and t is not None: + raise ValueError(f"shape {actual} does not match shape {target}") + + +def canonical_variable_image_preprocess(context, batch): + """ + Preprocessing when images are of variable size + """ + if batch.dtype != context.input_type: + if batch.dtype == object: + raise NotImplementedError( + " dtype not yet supported for variable image processing." + ) + raise ValueError( + f"input dtype {batch.dtype} not in ({context.input_type}, 'O')" + ) + check_shapes(tuple(batch.shape), context.x_shape) + assert batch.dtype == context.input_type + return batch + + +class ClipFrames: + """ + Clip Video Frames + Assumes first two dims are (batch, frames, ...) + """ + + def __init__(self, max_frames): + max_frames = int(max_frames) + if max_frames <= 0: + raise ValueError(f"max_frames {max_frames} must be > 0") + self.max_frames = max_frames + + def __call__(self, batch): + return batch[:, : self.max_frames] + + +class ClipVideoTrackingLabels: + """ + Truncate labels for CARLA video tracking, when max_frames is set + """ + + def __init__(self, max_frames): + max_frames = int(max_frames) + if max_frames <= 0: + raise ValueError(f"max_frames {max_frames} must be > 0") + self.max_frames = max_frames + + def clip_boxes(self, boxes): + return boxes[:, : self.max_frames, :] + + def clip_metadata(self, patch_metadata_dict): + return { + k: v[:, : self.max_frames, ::] for (k, v) in patch_metadata_dict.items() + } + + def __call__(self, x, labels): + boxes, patch_metadata_dict = labels + return self.clip_boxes(boxes), self.clip_metadata(patch_metadata_dict) + + +def carla_video_tracking(x, split, max_frames=None): + clip = ClipFrames(max_frames) if max_frames else None + preprocessing_fn = preprocessing_chain( + clip, lambda batch: canonical_variable_image_preprocess( + contexts[f"carla_video_tracking_{split}"], batch + ) + ) + return preprocessing_fn(x) + + +def carla_video_tracking_label_preprocessing(x, y): + box_labels, patch_metadata = y + box_array = ( + tf.squeeze(box_labels, axis=0) if box_labels.shape[0] == 1 else box_labels + ) + box_labels = {"boxes": box_array} + patch_metadata = { + k: (tf.squeeze(v, axis=0) if v.shape[0] == 1 else v) + for k, v in patch_metadata.items() + } + return box_labels, patch_metadata + + +def carla_video_tracking_labels(x, y, max_frames): + clip_labels = ClipVideoTrackingLabels(max_frames) if max_frames else None + label_preprocessing_fn = label_preprocessing_chain( + clip_labels, carla_video_tracking_label_preprocessing + ) + return label_preprocessing_fn(x, y) + + def convert_tf_boxes_to_pytorch(x, box_array): """ Converts object detection boxes from TF format of [y1/height, x1/width, y2/height, x2/width] to PyTorch format of [x1, y1, x2, y2] - :param x: TF tensor of shape (nb, H, W, C) - :param y: TF tensor of shape (num_boxes, 4) + :param x: TF tensor of shape (nb, H, W, C) or (H, W, C) + :param box_array: TF tensor of shape (num_boxes, 4) :return: TF tensor of shape (num_boxes, 4) """ x_shape = tf.shape(x) diff --git a/armory/metrics/task.py b/armory/metrics/task.py index 7d4bc78b9..14d2e395b 100644 --- a/armory/metrics/task.py +++ b/armory/metrics/task.py @@ -522,7 +522,7 @@ def _check_video_tracking_input(y, y_pred): y_pred (List[Dict, ...]): same as above """ for input in [y, y_pred]: - assert isinstance(input, list) + assert isinstance(input, list), f"Expected List[Dict, ...] got {type(input)}" for input_dict_i in input: assert isinstance(input_dict_i, dict) assert "boxes" in input_dict_i diff --git a/armory/scenarios/scenario.py b/armory/scenarios/scenario.py index 9839935db..c11def323 100644 --- a/armory/scenarios/scenario.py +++ b/armory/scenarios/scenario.py @@ -355,6 +355,11 @@ def evaluate_all(self): def next(self): self.hub.set_context(stage="next") x, y = next(self.test_dataset) + # Fix for carla_video, separate batches into list + if isinstance(y, tuple) and isinstance(y[0], dict) and 'boxes' in y[0]: + if len(y) != 2: + raise ValueError(f"Expected (y, y_metadata), got {y}") + y = ([{'boxes': batch} for batch in y[0]['boxes']], y[1]) i = self.i + 1 self.hub.set_context(batch=i) self.i, self.x, self.y = i, x, y diff --git a/armory/utils/config_loading.py b/armory/utils/config_loading.py index 9ba4524d3..c4ce1170b 100644 --- a/armory/utils/config_loading.py +++ b/armory/utils/config_loading.py @@ -26,7 +26,7 @@ from armory.art_experimental.attacks import patch from armory.art_experimental.attacks.sweep import SweepAttack -from armory.data.datasets import ArmoryDataGenerator, EvalGenerator +from armory.datasets.generator import ArmoryDataGenerator, EvalGenerator from armory.data.utils import maybe_download_weights_from_s3 from armory.utils import labels import copy diff --git a/docs/metrics.md b/docs/metrics.md index 4f5d18729..1abd0e144 100644 --- a/docs/metrics.md +++ b/docs/metrics.md @@ -73,6 +73,10 @@ The `armory.metrics` module contains functionality to measure a variety of metri We have implemented the metrics in numpy, instead of using framework-specific metrics, to prevent expanding the required set of dependencies. Please see the relevant submodules in [armory/metrics](../armory/metrics/) for more detailed descriptions. +### Custom Metrics + +TODO: describe how to add/register metrics, use them in configs, etc. + ### Perturbation Metrics Perturbation metrics compare a benign and adversarially perturbed input and return a distance. diff --git a/scenario_configs/eval5/carla_object_detection/carla_obj_det_adversarialpatch_undefended.json b/scenario_configs/eval5/carla_object_detection/carla_obj_det_adversarialpatch_undefended.json index 01169ce62..2c4244cdf 100644 --- a/scenario_configs/eval5/carla_object_detection/carla_obj_det_adversarialpatch_undefended.json +++ b/scenario_configs/eval5/carla_object_detection/carla_obj_det_adversarialpatch_undefended.json @@ -16,12 +16,14 @@ "use_label": true }, "dataset": { - "batch_size": 1, - "eval_split": "dev", - "framework": "numpy", - "modality": "rgb", - "module": "armory.data.adversarial_datasets", - "name": "carla_obj_det_dev" + "test": { + "batch_size": 1, + "name": "carla_obj_det_dev", + "preprocessor_kwargs": { + "modality": "rgb" + }, + "split": "dev" + } }, "defense": null, "metric": { diff --git a/scenario_configs/eval5/carla_object_detection/carla_obj_det_dpatch_defended.json b/scenario_configs/eval5/carla_object_detection/carla_obj_det_dpatch_defended.json index 600fada64..0f9bfd237 100755 --- a/scenario_configs/eval5/carla_object_detection/carla_obj_det_dpatch_defended.json +++ b/scenario_configs/eval5/carla_object_detection/carla_obj_det_dpatch_defended.json @@ -14,12 +14,14 @@ "use_label": false }, "dataset": { - "batch_size": 1, - "eval_split": "dev", - "framework": "numpy", - "modality": "rgb", - "module": "armory.data.adversarial_datasets", - "name": "carla_obj_det_dev" + "test": { + "batch_size": 1, + "name": "carla_obj_det_dev", + "preprocessor_kwargs": { + "modality": "rgb" + }, + "split": "dev" + } }, "defense": { "kwargs": { diff --git a/scenario_configs/eval5/carla_object_detection/carla_obj_det_dpatch_undefended.json b/scenario_configs/eval5/carla_object_detection/carla_obj_det_dpatch_undefended.json index 9346833c4..8b04c5005 100755 --- a/scenario_configs/eval5/carla_object_detection/carla_obj_det_dpatch_undefended.json +++ b/scenario_configs/eval5/carla_object_detection/carla_obj_det_dpatch_undefended.json @@ -14,12 +14,14 @@ "use_label": false }, "dataset": { - "batch_size": 1, - "eval_split": "dev", - "framework": "numpy", - "modality": "rgb", - "module": "armory.data.adversarial_datasets", - "name": "carla_obj_det_dev" + "test": { + "batch_size": 1, + "name": "carla_obj_det_dev", + "preprocessor_kwargs": { + "modality": "rgb" + }, + "split": "dev" + } }, "defense": null, "metric": { diff --git a/scenario_configs/eval5/carla_object_detection/carla_obj_det_multimodal_adversarialpatch_defended.json b/scenario_configs/eval5/carla_object_detection/carla_obj_det_multimodal_adversarialpatch_defended.json index 91b95702c..7de1d59d2 100644 --- a/scenario_configs/eval5/carla_object_detection/carla_obj_det_multimodal_adversarialpatch_defended.json +++ b/scenario_configs/eval5/carla_object_detection/carla_obj_det_multimodal_adversarialpatch_defended.json @@ -18,12 +18,14 @@ "use_label": true }, "dataset": { - "batch_size": 1, - "eval_split": "dev", - "framework": "numpy", - "modality": "both", - "module": "armory.data.adversarial_datasets", - "name": "carla_obj_det_dev" + "test": { + "batch_size": 1, + "name": "carla_obj_det_dev", + "preprocessor_kwargs": { + "modality": "both" + }, + "split": "dev" + } }, "defense": null, "metric": { diff --git a/scenario_configs/eval5/carla_object_detection/carla_obj_det_multimodal_adversarialpatch_undefended.json b/scenario_configs/eval5/carla_object_detection/carla_obj_det_multimodal_adversarialpatch_undefended.json index 18f6c1c94..4cadc0ba4 100644 --- a/scenario_configs/eval5/carla_object_detection/carla_obj_det_multimodal_adversarialpatch_undefended.json +++ b/scenario_configs/eval5/carla_object_detection/carla_obj_det_multimodal_adversarialpatch_undefended.json @@ -18,12 +18,14 @@ "use_label": true }, "dataset": { - "batch_size": 1, - "eval_split": "dev", - "framework": "numpy", - "modality": "both", - "module": "armory.data.adversarial_datasets", - "name": "carla_obj_det_dev" + "test": { + "batch_size": 1, + "name": "carla_obj_det_dev", + "preprocessor_kwargs": { + "modality": "both" + }, + "split": "dev" + } }, "defense": null, "metric": { diff --git a/scenario_configs/eval5/carla_object_detection/carla_obj_det_multimodal_dpatch_defended.json b/scenario_configs/eval5/carla_object_detection/carla_obj_det_multimodal_dpatch_defended.json index a089f9cf4..6ef18d491 100755 --- a/scenario_configs/eval5/carla_object_detection/carla_obj_det_multimodal_dpatch_defended.json +++ b/scenario_configs/eval5/carla_object_detection/carla_obj_det_multimodal_dpatch_defended.json @@ -16,12 +16,14 @@ "use_label": false }, "dataset": { - "batch_size": 1, - "eval_split": "dev", - "framework": "numpy", - "modality": "both", - "module": "armory.data.adversarial_datasets", - "name": "carla_obj_det_dev" + "test": { + "batch_size": 1, + "name": "carla_obj_det_dev", + "preprocessor_kwargs": { + "modality": "both" + }, + "split": "dev" + } }, "defense": null, "metric": { diff --git a/scenario_configs/eval5/carla_object_detection/carla_obj_det_multimodal_dpatch_undefended.json b/scenario_configs/eval5/carla_object_detection/carla_obj_det_multimodal_dpatch_undefended.json index 533e856fd..e56d71745 100755 --- a/scenario_configs/eval5/carla_object_detection/carla_obj_det_multimodal_dpatch_undefended.json +++ b/scenario_configs/eval5/carla_object_detection/carla_obj_det_multimodal_dpatch_undefended.json @@ -16,12 +16,14 @@ "use_label": false }, "dataset": { - "batch_size": 1, - "eval_split": "dev", - "framework": "numpy", - "modality": "both", - "module": "armory.data.adversarial_datasets", - "name": "carla_obj_det_dev" + "test": { + "batch_size": 1, + "name": "carla_obj_det_dev", + "preprocessor_kwargs": { + "modality": "both" + }, + "split": "dev" + } }, "defense": null, "metric": { diff --git a/scenario_configs/eval5/carla_video_tracking/carla_video_tracking_goturn_advtextures_defended.json b/scenario_configs/eval5/carla_video_tracking/carla_video_tracking_goturn_advtextures_defended.json index e9af364a3..02adb09a8 100755 --- a/scenario_configs/eval5/carla_video_tracking/carla_video_tracking_goturn_advtextures_defended.json +++ b/scenario_configs/eval5/carla_video_tracking/carla_video_tracking_goturn_advtextures_defended.json @@ -14,11 +14,11 @@ "use_label": true }, "dataset": { - "batch_size": 1, - "eval_split": "dev", - "framework": "numpy", - "module": "armory.data.adversarial_datasets", - "name": "carla_video_tracking_dev" + "test": { + "batch_size": 1, + "split": "test", + "name": "carla_video_tracking_test" + } }, "defense": { "kwargs": { diff --git a/scenario_configs/eval5/carla_video_tracking/carla_video_tracking_goturn_advtextures_undefended.json b/scenario_configs/eval5/carla_video_tracking/carla_video_tracking_goturn_advtextures_undefended.json index 1d459a492..e27fe1713 100755 --- a/scenario_configs/eval5/carla_video_tracking/carla_video_tracking_goturn_advtextures_undefended.json +++ b/scenario_configs/eval5/carla_video_tracking/carla_video_tracking_goturn_advtextures_undefended.json @@ -14,11 +14,11 @@ "use_label": true }, "dataset": { - "batch_size": 1, - "eval_split": "dev", - "framework": "numpy", - "module": "armory.data.adversarial_datasets", - "name": "carla_video_tracking_dev" + "test": { + "batch_size": 1, + "split": "dev", + "name": "carla_video_tracking_dev" + } }, "defense": null, "metric": { diff --git a/scenario_configs/eval6/carla_overhead_object_detection/carla_obj_det_adversarialpatch_undefended.json b/scenario_configs/eval6/carla_overhead_object_detection/carla_obj_det_adversarialpatch_undefended.json index 9652fe09f..961665bb2 100644 --- a/scenario_configs/eval6/carla_overhead_object_detection/carla_obj_det_adversarialpatch_undefended.json +++ b/scenario_configs/eval6/carla_overhead_object_detection/carla_obj_det_adversarialpatch_undefended.json @@ -16,12 +16,14 @@ "use_label": true }, "dataset": { - "batch_size": 1, - "eval_split": "dev", - "framework": "numpy", - "modality": "rgb", - "module": "armory.data.adversarial_datasets", - "name": "carla_over_obj_det_dev" + "test": { + "batch_size": 1, + "name": "carla_over_obj_det_dev", + "preprocessor_kwargs": { + "modality": "rgb" + }, + "split": "dev" + } }, "defense": null, "metric": { diff --git a/scenario_configs/eval6/carla_overhead_object_detection/carla_obj_det_dpatch_defended.json b/scenario_configs/eval6/carla_overhead_object_detection/carla_obj_det_dpatch_defended.json index 51810a0e6..2015d2118 100644 --- a/scenario_configs/eval6/carla_overhead_object_detection/carla_obj_det_dpatch_defended.json +++ b/scenario_configs/eval6/carla_overhead_object_detection/carla_obj_det_dpatch_defended.json @@ -14,12 +14,14 @@ "use_label": false }, "dataset": { - "batch_size": 1, - "eval_split": "dev", - "framework": "numpy", - "modality": "rgb", - "module": "armory.data.adversarial_datasets", - "name": "carla_over_obj_det_dev" + "test": { + "batch_size": 1, + "name": "carla_over_obj_det_dev", + "preprocessor_kwargs": { + "modality": "rgb" + }, + "split": "dev" + } }, "defense": { "kwargs": { diff --git a/scenario_configs/eval6/carla_overhead_object_detection/carla_obj_det_dpatch_undefended.json b/scenario_configs/eval6/carla_overhead_object_detection/carla_obj_det_dpatch_undefended.json index 0e3739246..97afa2690 100644 --- a/scenario_configs/eval6/carla_overhead_object_detection/carla_obj_det_dpatch_undefended.json +++ b/scenario_configs/eval6/carla_overhead_object_detection/carla_obj_det_dpatch_undefended.json @@ -14,12 +14,14 @@ "use_label": false }, "dataset": { - "batch_size": 1, - "eval_split": "dev", - "framework": "numpy", - "modality": "rgb", - "module": "armory.data.adversarial_datasets", - "name": "carla_over_obj_det_dev" + "test": { + "batch_size": 1, + "name": "carla_over_obj_det_dev", + "preprocessor_kwargs": { + "modality": "rgb" + }, + "split": "dev" + } }, "defense": null, "metric": { diff --git a/scenario_configs/eval6/carla_overhead_object_detection/carla_obj_det_multimodal_adversarialpatch_defended.json b/scenario_configs/eval6/carla_overhead_object_detection/carla_obj_det_multimodal_adversarialpatch_defended.json index 37cb12fb7..a673e03af 100644 --- a/scenario_configs/eval6/carla_overhead_object_detection/carla_obj_det_multimodal_adversarialpatch_defended.json +++ b/scenario_configs/eval6/carla_overhead_object_detection/carla_obj_det_multimodal_adversarialpatch_defended.json @@ -18,12 +18,14 @@ "use_label": true }, "dataset": { - "batch_size": 1, - "eval_split": "dev", - "framework": "numpy", - "modality": "both", - "module": "armory.data.adversarial_datasets", - "name": "carla_over_obj_det_dev" + "test": { + "batch_size": 1, + "name": "carla_over_obj_det_dev", + "preprocessor_kwargs": { + "modality": "both" + }, + "split": "dev" + } }, "defense": null, "metric": { diff --git a/scenario_configs/eval6/carla_overhead_object_detection/carla_obj_det_multimodal_adversarialpatch_undefended.json b/scenario_configs/eval6/carla_overhead_object_detection/carla_obj_det_multimodal_adversarialpatch_undefended.json index eec068ad9..0d75b7242 100644 --- a/scenario_configs/eval6/carla_overhead_object_detection/carla_obj_det_multimodal_adversarialpatch_undefended.json +++ b/scenario_configs/eval6/carla_overhead_object_detection/carla_obj_det_multimodal_adversarialpatch_undefended.json @@ -18,12 +18,14 @@ "use_label": true }, "dataset": { - "batch_size": 1, - "eval_split": "dev", - "framework": "numpy", - "modality": "both", - "module": "armory.data.adversarial_datasets", - "name": "carla_over_obj_det_dev" + "test": { + "batch_size": 1, + "name": "carla_over_obj_det_dev", + "preprocessor_kwargs": { + "modality": "both" + }, + "split": "dev" + } }, "defense": null, "metric": { diff --git a/scenario_configs/eval6/carla_overhead_object_detection/carla_obj_det_multimodal_dpatch_defended.json b/scenario_configs/eval6/carla_overhead_object_detection/carla_obj_det_multimodal_dpatch_defended.json index 9e4ab5226..0664827c3 100644 --- a/scenario_configs/eval6/carla_overhead_object_detection/carla_obj_det_multimodal_dpatch_defended.json +++ b/scenario_configs/eval6/carla_overhead_object_detection/carla_obj_det_multimodal_dpatch_defended.json @@ -16,12 +16,14 @@ "use_label": false }, "dataset": { - "batch_size": 1, - "eval_split": "dev", - "framework": "numpy", - "modality": "both", - "module": "armory.data.adversarial_datasets", - "name": "carla_over_obj_det_dev" + "test": { + "batch_size": 1, + "name": "carla_over_obj_det_dev", + "preprocessor_kwargs": { + "modality": "both" + }, + "split": "dev" + } }, "defense": null, "metric": { diff --git a/scenario_configs/eval6/carla_overhead_object_detection/carla_obj_det_multimodal_dpatch_undefended.json b/scenario_configs/eval6/carla_overhead_object_detection/carla_obj_det_multimodal_dpatch_undefended.json index f756e4265..72da60a2b 100644 --- a/scenario_configs/eval6/carla_overhead_object_detection/carla_obj_det_multimodal_dpatch_undefended.json +++ b/scenario_configs/eval6/carla_overhead_object_detection/carla_obj_det_multimodal_dpatch_undefended.json @@ -16,12 +16,14 @@ "use_label": false }, "dataset": { - "batch_size": 1, - "eval_split": "dev", - "framework": "numpy", - "modality": "both", - "module": "armory.data.adversarial_datasets", - "name": "carla_over_obj_det_dev" + "test": { + "batch_size": 1, + "name": "carla_over_obj_det_dev", + "preprocessor_kwargs": { + "modality": "both" + }, + "split": "dev" + } }, "defense": null, "metric": {