From ed7d997226f19274a6af56e2745980f2dd281215 Mon Sep 17 00:00:00 2001 From: "Murillo Rojas, Luis" Date: Thu, 27 Jul 2023 03:05:07 +0000 Subject: [PATCH 01/12] Add fiftyone datamodule --- .pre-commit-config.yaml | 2 +- mart/configs/datamodule/fiftyone.yaml | 76 ++++++++++++++++++ mart/datamodules/__init__.py | 1 + mart/datamodules/fiftyone.py | 110 ++++++++++++++++++++++++++ pyproject.toml | 4 + 5 files changed, 192 insertions(+), 1 deletion(-) create mode 100644 mart/configs/datamodule/fiftyone.yaml create mode 100644 mart/datamodules/fiftyone.py diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 31a624f2..ac184d67 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -99,4 +99,4 @@ repos: - id: codespell args: - --skip=logs/**,data/** - - --ignore-words-list=abc,def,gard + - --ignore-words-list=abc,def,gard,fo diff --git a/mart/configs/datamodule/fiftyone.yaml b/mart/configs/datamodule/fiftyone.yaml new file mode 100644 index 00000000..7f44ca44 --- /dev/null +++ b/mart/configs/datamodule/fiftyone.yaml @@ -0,0 +1,76 @@ +defaults: + - default.yaml + +train_dataset: + _target_: mart.datamodules.fiftyone.FiftyOneDataset + dataset_name: ??? + gt_field: "ground_truth_detections" + sample_tags: [] + label_tags: [] + transforms: + _target_: mart.transforms.Compose + transforms: + - _target_: torchvision.transforms.ToTensor + - _target_: mart.transforms.RandomHorizontalFlip + p: 0.5 + - _target_: mart.transforms.Denormalize + center: 0 + scale: 255 + - _target_: torch.fake_quantize_per_tensor_affine + _partial_: true + # (x/1+0).round().clamp(0, 255) * 1 + scale: 1 + zero_point: 0 + quant_min: 0 + quant_max: 255 + +val_dataset: + _target_: mart.datamodules.fiftyone.FiftyOneDataset + dataset_name: ??? + gt_field: ${..train_dataset.gt_field} + sample_tags: [] + label_tags: [] + transforms: + _target_: mart.transforms.Compose + transforms: + - _target_: torchvision.transforms.ToTensor + - _target_: mart.transforms.RandomHorizontalFlip + p: 0.5 + - _target_: mart.transforms.Denormalize + center: 0 + scale: 255 + - _target_: torch.fake_quantize_per_tensor_affine + _partial_: true + # (x/1+0).round().clamp(0, 255) * 1 + scale: 1 + zero_point: 0 + quant_min: 0 + quant_max: 255 + +test_dataset: + _target_: mart.datamodules.fiftyone.FiftyOneDataset + dataset_name: ??? + gt_field: ${..train_dataset.gt_field} + sample_tags: [] + label_tags: [] + transforms: + _target_: mart.transforms.Compose + transforms: + - _target_: torchvision.transforms.ToTensor + - _target_: mart.transforms.RandomHorizontalFlip + p: 0.5 + - _target_: mart.transforms.Denormalize + center: 0 + scale: 255 + - _target_: torch.fake_quantize_per_tensor_affine + _partial_: true + # (x/1+0).round().clamp(0, 255) * 1 + scale: 1 + zero_point: 0 + quant_min: 0 + quant_max: 255 + +num_workers: 2 +collate_fn: + _target_: hydra.utils.get_method + path: mart.datamodules.coco.collate_fn diff --git a/mart/datamodules/__init__.py b/mart/datamodules/__init__.py index 91284fe2..f36d3b9d 100644 --- a/mart/datamodules/__init__.py +++ b/mart/datamodules/__init__.py @@ -5,4 +5,5 @@ # from .coco import * +from .fiftyone import * from .modular import * diff --git a/mart/datamodules/fiftyone.py b/mart/datamodules/fiftyone.py new file mode 100644 index 00000000..2c79774f --- /dev/null +++ b/mart/datamodules/fiftyone.py @@ -0,0 +1,110 @@ +# +# Copyright (C) 2022 Intel Corporation +# +# SPDX-License-Identifier: BSD-3-Clause +# + +import logging +import os +from typing import Any, Callable, List, Optional, Tuple + +import numpy as np +import torch +from PIL import Image +from torchvision.datasets import VisionDataset as VisionDataset_ + +logger = logging.getLogger(__name__) +try: + import fiftyone as fo + import fiftyone.utils.coco as fouc +except ImportError: + logger.debug("fiftyone module is not installed!") + +__all__ = ["FiftyOneDataset"] + + +class FiftyOneDataset(VisionDataset_): + # Adapted from FiftyOne example: https://github.com/voxel51/fiftyone-examples/blob/master/examples/pytorch_detection_training.ipynb + + def __init__( + self, + dataset_name: str, + gt_field: str, + sample_tags: List[str] = [], + label_tags: List[str] = [], + transform: Optional[Callable] = None, + target_transform: Optional[Callable] = None, + transforms: Optional[Callable] = None, + ) -> None: + super().__init__("", transforms, transform, target_transform) + + self.gt_field = gt_field + + # Verify the FiftyOne + fo_datasets = fo.list_datasets() + assert dataset_name in fo_datasets, f"Dataset {dataset_name} does not exist!" + + # Load FiftyOne dataset + self.dataset = fo.load_dataset(dataset_name) + self.dataset = self.dataset.exists(self.gt_field) + + # filter with tags + self.filtered_dataset = ( + self.dataset.match_tags(sample_tags) if len(sample_tags) > 0 else self.dataset + ) + self.filtered_dataset = ( + self.filtered_dataset.select_labels(tags=label_tags) + if len(label_tags) > 0 + else self.filtered_dataset + ) + + # extract samples' IDs + self.ids = self.filtered_dataset.values("id") + + # set classes + self.classes = self.filtered_dataset.default_classes + self.labels_map_rev = {c: i for i, c in enumerate(self.classes)} + + self.filtered_dataset.shuffle() + print( + f"FiftyOne dataset {dataset_name}, with {len(self.ids)} samples, successfully loaded." + ) + + def __getitem__(self, index: int) -> Any: + sample_id = self.ids[index] + sample = self.filtered_dataset[sample_id] + metadata = sample.metadata + img = Image.open(sample.filepath).convert("RGB") + + boxes = [] + labels = [] + area = [] + iscrowd = [] + detections = sample[self.gt_field].detections + for det in detections: + category_id = self.labels_map_rev[det.label] + coco_obj = fouc.COCOObject.from_label( + det, + metadata, + category_id=category_id, + ) + x, y, w, h = coco_obj.bbox + boxes.append([x, y, x + w, y + h]) + labels.append(coco_obj.category_id) + area.append(coco_obj.area) + iscrowd.append(coco_obj.iscrowd) + + target = {} + target["image_id"] = torch.as_tensor([index]) + target["boxes"] = torch.as_tensor(boxes, dtype=torch.float32) + target["labels"] = torch.as_tensor(labels, dtype=torch.int64) + target["area"] = torch.as_tensor(area, dtype=torch.float32) + target["iscrowd"] = torch.as_tensor(iscrowd, dtype=torch.int64) + + if self.transforms is not None: + img, target = self.transforms(img, target) + + return img, target + + def __len__(self) -> int: + return len(self.filtered_dataset) diff --git a/pyproject.toml b/pyproject.toml index a13938c5..2839733d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -54,6 +54,10 @@ developer = [ "protobuf==3.20.0" ] +fiftyone = [ + "fiftyone ~= 0.21.4", +] + extras = [ ] From 23d54414d012b8bafe739fdbda769ab552d0b305 Mon Sep 17 00:00:00 2001 From: "Murillo Rojas, Luis" Date: Sat, 29 Jul 2023 03:33:09 +0000 Subject: [PATCH 02/12] Add fiftyone datamodule pertubable mask version --- mart/configs/datamodule/fiftyone.yaml | 5 +- .../datamodule/fiftyone_perturbable_mask.yaml | 76 +++++++++++++++++++ mart/datamodules/fiftyone.py | 28 +++---- 3 files changed, 92 insertions(+), 17 deletions(-) create mode 100644 mart/configs/datamodule/fiftyone_perturbable_mask.yaml diff --git a/mart/configs/datamodule/fiftyone.yaml b/mart/configs/datamodule/fiftyone.yaml index 7f44ca44..08bf969f 100644 --- a/mart/configs/datamodule/fiftyone.yaml +++ b/mart/configs/datamodule/fiftyone.yaml @@ -4,13 +4,14 @@ defaults: train_dataset: _target_: mart.datamodules.fiftyone.FiftyOneDataset dataset_name: ??? - gt_field: "ground_truth_detections" + gt_field: "ground_truth_segmentations" sample_tags: [] label_tags: [] transforms: _target_: mart.transforms.Compose transforms: - _target_: torchvision.transforms.ToTensor + - _target_: mart.transforms.ConvertCocoPolysToMask - _target_: mart.transforms.RandomHorizontalFlip p: 0.5 - _target_: mart.transforms.Denormalize @@ -34,6 +35,7 @@ val_dataset: _target_: mart.transforms.Compose transforms: - _target_: torchvision.transforms.ToTensor + - _target_: mart.transforms.ConvertCocoPolysToMask - _target_: mart.transforms.RandomHorizontalFlip p: 0.5 - _target_: mart.transforms.Denormalize @@ -57,6 +59,7 @@ test_dataset: _target_: mart.transforms.Compose transforms: - _target_: torchvision.transforms.ToTensor + - _target_: mart.transforms.ConvertCocoPolysToMask - _target_: mart.transforms.RandomHorizontalFlip p: 0.5 - _target_: mart.transforms.Denormalize diff --git a/mart/configs/datamodule/fiftyone_perturbable_mask.yaml b/mart/configs/datamodule/fiftyone_perturbable_mask.yaml new file mode 100644 index 00000000..69a7f622 --- /dev/null +++ b/mart/configs/datamodule/fiftyone_perturbable_mask.yaml @@ -0,0 +1,76 @@ +defaults: + - fiftyone + +train_dataset: + _target_: mart.datamodules.fiftyone.FiftyOneDataset + dataset_name: ??? + gt_field: "ground_truth_segmentations" + sample_tags: [] + label_tags: [] + transforms: + _target_: mart.transforms.Compose + transforms: + - _target_: torchvision.transforms.ToTensor + # ConvertCocoPolysToMask must be prior to ConvertInstanceSegmentationToPerturbable. + - _target_: mart.transforms.ConvertCocoPolysToMask + - _target_: mart.transforms.RandomHorizontalFlip + p: 0.5 + - _target_: mart.transforms.ConvertInstanceSegmentationToPerturbable + - _target_: mart.transforms.Denormalize + center: 0 + scale: 255 + - _target_: torch.fake_quantize_per_tensor_affine + _partial_: true + # (x/1+0).round().clamp(0, 255) * 1 + scale: 1 + zero_point: 0 + quant_min: 0 + quant_max: 255 + +val_dataset: + _target_: mart.datamodules.fiftyone.FiftyOneDataset + dataset_name: ??? + gt_field: ${..train_dataset.gt_field} + sample_tags: [] + label_tags: [] + transforms: + _target_: mart.transforms.Compose + transforms: + - _target_: torchvision.transforms.ToTensor + # ConvertCocoPolysToMask must be prior to ConvertInstanceSegmentationToPerturbable. + - _target_: mart.transforms.ConvertCocoPolysToMask + - _target_: mart.transforms.ConvertInstanceSegmentationToPerturbable + - _target_: mart.transforms.Denormalize + center: 0 + scale: 255 + - _target_: torch.fake_quantize_per_tensor_affine + _partial_: true + # (x/1+0).round().clamp(0, 255) * 1 + scale: 1 + zero_point: 0 + quant_min: 0 + quant_max: 255 + +test_dataset: + _target_: mart.datamodules.fiftyone.FiftyOneDataset + dataset_name: ??? + gt_field: ${..train_dataset.gt_field} + sample_tags: [] + label_tags: [] + transforms: + _target_: mart.transforms.Compose + transforms: + - _target_: torchvision.transforms.ToTensor + # ConvertCocoPolysToMask must be prior to ConvertInstanceSegmentationToPerturbable. + - _target_: mart.transforms.ConvertCocoPolysToMask + - _target_: mart.transforms.ConvertInstanceSegmentationToPerturbable + - _target_: mart.transforms.Denormalize + center: 0 + scale: 255 + - _target_: torch.fake_quantize_per_tensor_affine + _partial_: true + # (x/1+0).round().clamp(0, 255) * 1 + scale: 1 + zero_point: 0 + quant_min: 0 + quant_max: 255 diff --git a/mart/datamodules/fiftyone.py b/mart/datamodules/fiftyone.py index 2c79774f..2ea40c56 100644 --- a/mart/datamodules/fiftyone.py +++ b/mart/datamodules/fiftyone.py @@ -65,7 +65,6 @@ def __init__( self.classes = self.filtered_dataset.default_classes self.labels_map_rev = {c: i for i, c in enumerate(self.classes)} - self.filtered_dataset.shuffle() print( f"FiftyOne dataset {dataset_name}, with {len(self.ids)} samples, successfully loaded." ) @@ -76,10 +75,11 @@ def __getitem__(self, index: int) -> Any: metadata = sample.metadata img = Image.open(sample.filepath).convert("RGB") - boxes = [] - labels = [] - area = [] - iscrowd = [] + target = {} + target["image_id"] = index + target["file_name"] = sample.filepath + target["annotations"] = [] + detections = sample[self.gt_field].detections for det in detections: category_id = self.labels_map_rev[det.label] @@ -88,18 +88,14 @@ def __getitem__(self, index: int) -> Any: metadata, category_id=category_id, ) - x, y, w, h = coco_obj.bbox - boxes.append([x, y, x + w, y + h]) - labels.append(coco_obj.category_id) - area.append(coco_obj.area) - iscrowd.append(coco_obj.iscrowd) - target = {} - target["image_id"] = torch.as_tensor([index]) - target["boxes"] = torch.as_tensor(boxes, dtype=torch.float32) - target["labels"] = torch.as_tensor(labels, dtype=torch.int64) - target["area"] = torch.as_tensor(area, dtype=torch.float32) - target["iscrowd"] = torch.as_tensor(iscrowd, dtype=torch.int64) + coco_annotation = coco_obj.to_anno_dict() + # If the detection object has segmentation information, verify that the + # segmentation field is not empty + if coco_obj.segmentation is not None and len(coco_annotation["segmentation"]) == 0: + continue + + target["annotations"].append(coco_annotation) if self.transforms is not None: img, target = self.transforms(img, target) From 54b75f1837dd02a4ebc3bfdad1ad364dd7f12a8d Mon Sep 17 00:00:00 2001 From: "Murillo Rojas, Luis" Date: Thu, 10 Aug 2023 15:54:24 +0000 Subject: [PATCH 03/12] Remove random transformation in validation and test datasets --- mart/configs/datamodule/fiftyone.yaml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/mart/configs/datamodule/fiftyone.yaml b/mart/configs/datamodule/fiftyone.yaml index 08bf969f..1fbdd9a6 100644 --- a/mart/configs/datamodule/fiftyone.yaml +++ b/mart/configs/datamodule/fiftyone.yaml @@ -36,8 +36,6 @@ val_dataset: transforms: - _target_: torchvision.transforms.ToTensor - _target_: mart.transforms.ConvertCocoPolysToMask - - _target_: mart.transforms.RandomHorizontalFlip - p: 0.5 - _target_: mart.transforms.Denormalize center: 0 scale: 255 @@ -60,8 +58,6 @@ test_dataset: transforms: - _target_: torchvision.transforms.ToTensor - _target_: mart.transforms.ConvertCocoPolysToMask - - _target_: mart.transforms.RandomHorizontalFlip - p: 0.5 - _target_: mart.transforms.Denormalize center: 0 scale: 255 From 8b5d54c137825aac2c0d84dccf65e4389dba7c21 Mon Sep 17 00:00:00 2001 From: "Murillo Rojas, Luis" Date: Thu, 10 Aug 2023 16:54:51 +0000 Subject: [PATCH 04/12] Set just filename to target --- mart/datamodules/fiftyone.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/mart/datamodules/fiftyone.py b/mart/datamodules/fiftyone.py index 2ea40c56..54ea9dcf 100644 --- a/mart/datamodules/fiftyone.py +++ b/mart/datamodules/fiftyone.py @@ -6,6 +6,7 @@ import logging import os +from pathlib import Path from typing import Any, Callable, List, Optional, Tuple import numpy as np @@ -73,11 +74,12 @@ def __getitem__(self, index: int) -> Any: sample_id = self.ids[index] sample = self.filtered_dataset[sample_id] metadata = sample.metadata - img = Image.open(sample.filepath).convert("RGB") + image_path = Path(sample.filepath) + img = Image.open(image_path).convert("RGB") target = {} target["image_id"] = index - target["file_name"] = sample.filepath + target["file_name"] = image_path.name target["annotations"] = [] detections = sample[self.gt_field].detections From 62be6a783b0acb3edaf21e19bed58225963cd496 Mon Sep 17 00:00:00 2001 From: "Murillo Rojas, Luis" Date: Thu, 10 Aug 2023 21:29:17 +0000 Subject: [PATCH 05/12] Add FiftyOne dataset zoo support --- mart/datamodules/fiftyone.py | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/mart/datamodules/fiftyone.py b/mart/datamodules/fiftyone.py index 54ea9dcf..3e3f581b 100644 --- a/mart/datamodules/fiftyone.py +++ b/mart/datamodules/fiftyone.py @@ -18,6 +18,7 @@ try: import fiftyone as fo import fiftyone.utils.coco as fouc + import fiftyone.zoo as foz except ImportError: logger.debug("fiftyone module is not installed!") @@ -36,17 +37,24 @@ def __init__( transform: Optional[Callable] = None, target_transform: Optional[Callable] = None, transforms: Optional[Callable] = None, + *args, + **kwargs, ) -> None: super().__init__("", transforms, transform, target_transform) self.gt_field = gt_field - # Verify the FiftyOne - fo_datasets = fo.list_datasets() - assert dataset_name in fo_datasets, f"Dataset {dataset_name} does not exist!" + # load FiftyOne dataset + if dataset_name in fo.list_datasets(): + self.dataset = fo.load_dataset(dataset_name) + elif dataset_name in foz.list_zoo_datasets(): + self.dataset = foz.load_zoo_dataset(dataset_name, *args, **kwargs) + else: + raise Exception( + f"Dataset {dataset_name} does not exist. To create a FiftyOne dataset, used the CLI command: https://docs.voxel51.com/cli/index.html#create-datasets" + ) - # Load FiftyOne dataset - self.dataset = fo.load_dataset(dataset_name) + # verify FiftyOne ground truth field self.dataset = self.dataset.exists(self.gt_field) # filter with tags From f27f3cae9a49fd62e725af82a1abef84f9eca02e Mon Sep 17 00:00:00 2001 From: "Murillo Rojas, Luis" Date: Thu, 10 Aug 2023 22:55:20 +0000 Subject: [PATCH 06/12] Add fiftyone datamodule example --- examples/fiftyone/README.md | 72 +++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 examples/fiftyone/README.md diff --git a/examples/fiftyone/README.md b/examples/fiftyone/README.md new file mode 100644 index 00000000..2b262ff8 --- /dev/null +++ b/examples/fiftyone/README.md @@ -0,0 +1,72 @@ +# Introduction + +This example shows how to use MART with the FiftyOne integration. FiftyOne is an open-source tool for building high quality dataset of images and videos. With this integration, MART delegates the data handling to FiftyOne. + +## Installation + +```bash +pip install git+https://github.com/IntelLabs/MART.git[fiftyone] +``` + +## How to run + +### Using the Dataset ZOO + +- First, identify the dataset available in the [FiftyOne Dataset Zoo](https://docs.voxel51.com/user_guide/dataset_zoo/datasets.html). Take into account the the current implementation support COCO like datasets. + +- Run the following command to use the ZOO dataset in MART: + +```bash +python -m mart experiment=COCO_TorchvisionFasterRCNN \ + trainer.max_steps=5105 \ + datamodule=fiftyone \ + datamodule.train_dataset.dataset_name="coco-2017" \ + datamodule.train_dataset.gt_field="ground_truth" \ + +datamodule.train_dataset.label_types=["segmentations"] \ + +datamodule.train_dataset.classes=["person","car","motorcycle"] \ + +datamodule.train_dataset.split="train" \ + +datamodule.train_dataset.max_samples=1000 \ + datamodule.val_dataset.dataset_name="coco-2017" \ + +datamodule.val_dataset.label_types=["segmentations"] \ + +datamodule.val_dataset.classes=["person","car","motorcycle"] \ + +datamodule.val_dataset.split="validation" \ + +datamodule.val_dataset.max_samples=50 \ + datamodule.test_dataset.dataset_name="coco-2017" \ + +datamodule.test_dataset.label_types=["segmentations"] \ + +datamodule.test_dataset.classes=["person","car","motorcycle"] \ + +datamodule.test_dataset.split="validation" \ + +datamodule.test_dataset.max_samples=50 \ + +datamodule.test_dataset.shuffle=true +``` + +To configure the dataset, consult the corresponding documentation to know the available config options (E.g. [coco-2017](https://docs.voxel51.com/user_guide/dataset_zoo/datasets.html#dataset-zoo-coco-2017)) + +### Using a custom dataset + +- Add an existing dataset into FiftyOne by running the following command: + +```bash +fiftyone datasets create \ + --name \ + --dataset-dir \ + --type fiftyone.types.COCODetectionDataset \ + --kwargs \ + data_path="" \ + labels_path= \ + persistent=true \ + include_id=true +``` + +- Use the custom dataset in MART with this command: + +```bash +python -m mart experiment=COCO_TorchvisionFasterRCNN \ + datamodule=fiftyone \ + datamodule.train_dataset.dataset_name="train_dataset" \ + datamodule.val_dataset.dataset_name="test_dataset" \ + datamodule.test_dataset.dataset_name="test_dataset" \ + datamodule.train_dataset.sample_tags=["tag_name"] \ + datamodule.train_dataset.label_tags=["tag_name_1","tag_name_2","tag_name_3"] +``` + +Notice that in the above example is possible to filter samples and annotations by using the FiftyOne tags. From 21660fdfe24ad94e1a505cca84f8587a846be762 Mon Sep 17 00:00:00 2001 From: "Murillo Rojas, Luis" Date: Fri, 11 Aug 2023 15:26:32 +0000 Subject: [PATCH 07/12] Revert dataset ZOO support --- mart/datamodules/fiftyone.py | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/mart/datamodules/fiftyone.py b/mart/datamodules/fiftyone.py index 3e3f581b..156c6589 100644 --- a/mart/datamodules/fiftyone.py +++ b/mart/datamodules/fiftyone.py @@ -18,7 +18,6 @@ try: import fiftyone as fo import fiftyone.utils.coco as fouc - import fiftyone.zoo as foz except ImportError: logger.debug("fiftyone module is not installed!") @@ -37,24 +36,19 @@ def __init__( transform: Optional[Callable] = None, target_transform: Optional[Callable] = None, transforms: Optional[Callable] = None, - *args, - **kwargs, ) -> None: super().__init__("", transforms, transform, target_transform) self.gt_field = gt_field - # load FiftyOne dataset - if dataset_name in fo.list_datasets(): - self.dataset = fo.load_dataset(dataset_name) - elif dataset_name in foz.list_zoo_datasets(): - self.dataset = foz.load_zoo_dataset(dataset_name, *args, **kwargs) - else: - raise Exception( - f"Dataset {dataset_name} does not exist. To create a FiftyOne dataset, used the CLI command: https://docs.voxel51.com/cli/index.html#create-datasets" - ) + # Verify the FiftyOne + fo_datasets = fo.list_datasets() + assert ( + dataset_name in fo_datasets + ), f"Dataset {dataset_name} does not exist. To create a FiftyOne dataset, used the CLI command: https://docs.voxel51.com/cli/index.html#create-datasets" - # verify FiftyOne ground truth field + # Load FiftyOne dataset + self.dataset = fo.load_dataset(dataset_name) self.dataset = self.dataset.exists(self.gt_field) # filter with tags From e31e0cb4f5a07db75df00da811942752a98db5a3 Mon Sep 17 00:00:00 2001 From: "Murillo Rojas, Luis" Date: Fri, 11 Aug 2023 15:37:31 +0000 Subject: [PATCH 08/12] Update FiftyOne example documentation --- examples/fiftyone/README.md | 81 +++++++++++++++---------------------- 1 file changed, 32 insertions(+), 49 deletions(-) diff --git a/examples/fiftyone/README.md b/examples/fiftyone/README.md index 2b262ff8..106f0264 100644 --- a/examples/fiftyone/README.md +++ b/examples/fiftyone/README.md @@ -8,65 +8,48 @@ This example shows how to use MART with the FiftyOne integration. FiftyOne is an pip install git+https://github.com/IntelLabs/MART.git[fiftyone] ``` -## How to run +# FiftyOne commands to load (index) datasets. -### Using the Dataset ZOO +Use COCO-2017 as an example. Unfortunately, FiftyOne does not support person-keypoints annotations yet. -- First, identify the dataset available in the [FiftyOne Dataset Zoo](https://docs.voxel51.com/user_guide/dataset_zoo/datasets.html). Take into account the the current implementation support COCO like datasets. - -- Run the following command to use the ZOO dataset in MART: +## Download and load zoo datasets ```bash -python -m mart experiment=COCO_TorchvisionFasterRCNN \ - trainer.max_steps=5105 \ - datamodule=fiftyone \ - datamodule.train_dataset.dataset_name="coco-2017" \ - datamodule.train_dataset.gt_field="ground_truth" \ - +datamodule.train_dataset.label_types=["segmentations"] \ - +datamodule.train_dataset.classes=["person","car","motorcycle"] \ - +datamodule.train_dataset.split="train" \ - +datamodule.train_dataset.max_samples=1000 \ - datamodule.val_dataset.dataset_name="coco-2017" \ - +datamodule.val_dataset.label_types=["segmentations"] \ - +datamodule.val_dataset.classes=["person","car","motorcycle"] \ - +datamodule.val_dataset.split="validation" \ - +datamodule.val_dataset.max_samples=50 \ - datamodule.test_dataset.dataset_name="coco-2017" \ - +datamodule.test_dataset.label_types=["segmentations"] \ - +datamodule.test_dataset.classes=["person","car","motorcycle"] \ - +datamodule.test_dataset.split="validation" \ - +datamodule.test_dataset.max_samples=50 \ - +datamodule.test_dataset.shuffle=true +fiftyone zoo datasets load \ +coco-2017 \ +-s train \ +-n coco-2017-instances-train \ +-k include_id=true label_types=detections,segmentations + +fiftyone zoo datasets load \ +coco-2017 \ +-s validation \ +-n coco-2017-instances-validation \ +-k include_id=true label_types=detections,segmentations ``` -To configure the dataset, consult the corresponding documentation to know the available config options (E.g. [coco-2017](https://docs.voxel51.com/user_guide/dataset_zoo/datasets.html#dataset-zoo-coco-2017)) - -### Using a custom dataset - -- Add an existing dataset into FiftyOne by running the following command: +## Load local datasets ```bash fiftyone datasets create \ - --name \ - --dataset-dir \ - --type fiftyone.types.COCODetectionDataset \ - --kwargs \ - data_path="" \ - labels_path= \ - persistent=true \ - include_id=true +--name coco-2017-instances-validation \ +--dataset-dir /path/to/datasets/coco/ \ +--type fiftyone.types.COCODetectionDataset \ +--kwargs \ +data_path="val2017" \ +labels_path=/path/to/datasets/coco/annotations/instances_val2017.json \ +persistent=true \ +include_id=true ``` -- Use the custom dataset in MART with this command: +## Use the FiftyOne datamodule -```bash -python -m mart experiment=COCO_TorchvisionFasterRCNN \ - datamodule=fiftyone \ - datamodule.train_dataset.dataset_name="train_dataset" \ - datamodule.val_dataset.dataset_name="test_dataset" \ - datamodule.test_dataset.dataset_name="test_dataset" \ - datamodule.train_dataset.sample_tags=["tag_name"] \ - datamodule.train_dataset.label_tags=["tag_name_1","tag_name_2","tag_name_3"] +```yaml +datamodule: + train_dataset: + dataset_name: coco-2017-instances-train + gt_field: segmentations + val_dataset: + dataset_name: coco-2017-instances-validation + gt_field: segmentations ``` - -Notice that in the above example is possible to filter samples and annotations by using the FiftyOne tags. From 7d6f7b2ae0b7639d24728d4072900b02c519f4f0 Mon Sep 17 00:00:00 2001 From: "Murillo Rojas, Luis" Date: Wed, 2 Aug 2023 05:27:51 +0000 Subject: [PATCH 09/12] Add FiftyOne callbacks --- mart/callbacks/__init__.py | 1 + mart/callbacks/fiftyone.py | 82 +++++++++++++++++++ .../fiftyone_evaluate_detections.yaml | 3 + .../callbacks/fiftyone_prediction_adder.yaml | 4 + mart/configs/datamodule/fiftyone.yaml | 24 ++++++ .../datamodule/fiftyone_perturbable_mask.yaml | 24 ++++++ .../FiftyOne_TorchvisionFasterRCNN.yaml | 22 +++++ mart/configs/lightning.yaml | 3 + .../model/torchvision_object_detection.yaml | 1 + mart/datamodules/fiftyone.py | 33 ++++++++ mart/datamodules/modular.py | 27 ++++++ mart/models/modular.py | 22 +++++ mart/tasks/lightning.py | 6 ++ 13 files changed, 252 insertions(+) create mode 100644 mart/callbacks/fiftyone.py create mode 100644 mart/configs/callbacks/fiftyone_evaluate_detections.yaml create mode 100644 mart/configs/callbacks/fiftyone_prediction_adder.yaml create mode 100644 mart/configs/experiment/FiftyOne_TorchvisionFasterRCNN.yaml diff --git a/mart/callbacks/__init__.py b/mart/callbacks/__init__.py index 8e117180..78ab693d 100644 --- a/mart/callbacks/__init__.py +++ b/mart/callbacks/__init__.py @@ -1,4 +1,5 @@ from .eval_mode import * +from .fiftyone import * from .gradients import * from .no_grad_mode import * from .progress_bar import * diff --git a/mart/callbacks/fiftyone.py b/mart/callbacks/fiftyone.py new file mode 100644 index 00000000..151c12f7 --- /dev/null +++ b/mart/callbacks/fiftyone.py @@ -0,0 +1,82 @@ +# +# Copyright (C) 2022 Intel Corporation +# +# SPDX-License-Identifier: BSD-3-Clause +# + +import logging +from typing import List + +from lightning.pytorch.callbacks import BasePredictionWriter, Callback + +from ..datamodules import FiftyOneDataset + +logger = logging.getLogger(__name__) +try: + import fiftyone as fo +except ImportError: + logger.debug("fiftyone module is not installed!") + +__all__ = ["FiftyOneEvaluateDetections", "FiftyOnePredictionAdder"] + + +class FiftyOneEvaluateDetections(Callback): + def __init__(self, run_id: str, gt_field: str = "ground_truth_detections") -> None: + self.run_id = run_id + self.gt_field = gt_field + + def on_predict_end(self, trainer, pl_module): + predict_dataset = trainer.datamodule.predict_dataset + assert isinstance(predict_dataset, FiftyOneDataset) + + eval_key = f"eval_{self.run_id}".replace("-", "") + eval_key = eval_key.replace("_", "") + results = predict_dataset.filtered_dataset.evaluate_detections( + f"prediction_{self.run_id}", + gt_field=self.gt_field, + eval_key=eval_key, + compute_mAP=True, + ) + + logger.info(f"Prediction mAP={results.mAP()}") + + # Get the 10 most common classes in the dataset + counts = predict_dataset.filtered_dataset.count_values(f"{self.gt_field}.detections.label") + classes_top10 = sorted(counts, key=counts.get, reverse=True)[:10] + + # Print a classification report for the top-10 classes + results.print_report(classes=classes_top10) + + +class FiftyOnePredictionAdder(BasePredictionWriter): + def __init__(self, output_dir: str, write_interval: List[str]) -> None: + super().__init__(write_interval) + self.run_id = f"prediction_{output_dir}" + + def _write_predictions(self, predictions, groundtruth_preds, dataset): + for pred, gt_pred in zip(predictions, groundtruth_preds): + filename = gt_pred["file_name"] + dataset.add_predictions(filename, pred, self.run_id) + + def write_on_batch_end( + self, trainer, pl_module, prediction, batch_indices, batch, batch_idx, dataloader_idx + ): + predict_dataset = trainer.datamodule.predict_dataset + assert isinstance(predict_dataset, FiftyOneDataset) + + self._write_predictions( + prediction[pl_module.output_preds_key], + prediction[pl_module.output_target_key], + predict_dataset, + ) + + def write_on_epoch_end(self, trainer, pl_module, predictions, batch_indices): + predict_dataset = trainer.datamodule.predict_dataset + assert isinstance(predict_dataset, FiftyOneDataset) + + for output in predictions: + self._write_predictions( + output[pl_module.output_preds_key], + output[pl_module.output_target_key], + predict_dataset, + ) diff --git a/mart/configs/callbacks/fiftyone_evaluate_detections.yaml b/mart/configs/callbacks/fiftyone_evaluate_detections.yaml new file mode 100644 index 00000000..e53e01c4 --- /dev/null +++ b/mart/configs/callbacks/fiftyone_evaluate_detections.yaml @@ -0,0 +1,3 @@ +fiftyone_evaluate_detections: + _target_: mart.callbacks.FiftyOneEvaluateDetections + run_id: ${now:%Y-%m-%d}_${now:%H-%M-%S} diff --git a/mart/configs/callbacks/fiftyone_prediction_adder.yaml b/mart/configs/callbacks/fiftyone_prediction_adder.yaml new file mode 100644 index 00000000..84dcb964 --- /dev/null +++ b/mart/configs/callbacks/fiftyone_prediction_adder.yaml @@ -0,0 +1,4 @@ +fiftyone_prediction_adder: + _target_: mart.callbacks.FiftyOnePredictionAdder + output_dir: ${now:%Y-%m-%d}_${now:%H-%M-%S} + write_interval: "epoch" diff --git a/mart/configs/datamodule/fiftyone.yaml b/mart/configs/datamodule/fiftyone.yaml index 1fbdd9a6..a0d31ed3 100644 --- a/mart/configs/datamodule/fiftyone.yaml +++ b/mart/configs/datamodule/fiftyone.yaml @@ -69,6 +69,30 @@ test_dataset: quant_min: 0 quant_max: 255 +predict_dataset: + _target_: mart.datamodules.fiftyone.FiftyOneDataset + dataset_name: ??? + gt_field: ${..train_dataset.gt_field} + sample_tags: [] + label_tags: [] + transforms: + _target_: mart.transforms.Compose + transforms: + - _target_: torchvision.transforms.ToTensor + - _target_: mart.transforms.ConvertCocoPolysToMask + - _target_: mart.transforms.RandomHorizontalFlip + p: 0.5 + - _target_: mart.transforms.Denormalize + center: 0 + scale: 255 + - _target_: torch.fake_quantize_per_tensor_affine + _partial_: true + # (x/1+0).round().clamp(0, 255) * 1 + scale: 1 + zero_point: 0 + quant_min: 0 + quant_max: 255 + num_workers: 2 collate_fn: _target_: hydra.utils.get_method diff --git a/mart/configs/datamodule/fiftyone_perturbable_mask.yaml b/mart/configs/datamodule/fiftyone_perturbable_mask.yaml index 69a7f622..8cc3314f 100644 --- a/mart/configs/datamodule/fiftyone_perturbable_mask.yaml +++ b/mart/configs/datamodule/fiftyone_perturbable_mask.yaml @@ -74,3 +74,27 @@ test_dataset: zero_point: 0 quant_min: 0 quant_max: 255 + +predict_dataset: + _target_: mart.datamodules.fiftyone.FiftyOneDataset + dataset_name: ??? + gt_field: ${..train_dataset.gt_field} + sample_tags: [] + label_tags: [] + transforms: + _target_: mart.transforms.Compose + transforms: + - _target_: torchvision.transforms.ToTensor + # ConvertCocoPolysToMask must be prior to ConvertInstanceSegmentationToPerturbable. + - _target_: mart.transforms.ConvertCocoPolysToMask + - _target_: mart.transforms.ConvertInstanceSegmentationToPerturbable + - _target_: mart.transforms.Denormalize + center: 0 + scale: 255 + - _target_: torch.fake_quantize_per_tensor_affine + _partial_: true + # (x/1+0).round().clamp(0, 255) * 1 + scale: 1 + zero_point: 0 + quant_min: 0 + quant_max: 255 diff --git a/mart/configs/experiment/FiftyOne_TorchvisionFasterRCNN.yaml b/mart/configs/experiment/FiftyOne_TorchvisionFasterRCNN.yaml new file mode 100644 index 00000000..8ba988e0 --- /dev/null +++ b/mart/configs/experiment/FiftyOne_TorchvisionFasterRCNN.yaml @@ -0,0 +1,22 @@ +# @package _global_ + +defaults: + - COCO_TorchvisionFasterRCNN + - override /datamodule: fiftyone + - override /callbacks: + [ + model_checkpoint, + lr_monitor, + fiftyone_prediction_adder, + fiftyone_evaluate_detections, + ] + +task_name: "FiftyOne_TorchvisionFasterRCNN" + +model: + predict_sequence: + seq010: + preprocessor: ["input"] + + seq020: + losses_and_detections: ["preprocessor", "target"] diff --git a/mart/configs/lightning.yaml b/mart/configs/lightning.yaml index 714250f3..0b5e0c09 100644 --- a/mart/configs/lightning.yaml +++ b/mart/configs/lightning.yaml @@ -42,6 +42,9 @@ fit: True # lightning chooses best model based on metric specified in checkpoint callback test: True +# run inference on the predict set. +predict: False + # Whether to resume training using configuration and checkpoint in specified directory resume: null diff --git a/mart/configs/model/torchvision_object_detection.yaml b/mart/configs/model/torchvision_object_detection.yaml index 534f0fc9..1a2ece9b 100644 --- a/mart/configs/model/torchvision_object_detection.yaml +++ b/mart/configs/model/torchvision_object_detection.yaml @@ -8,6 +8,7 @@ training_step_log: training_sequence: ??? validation_sequence: ??? test_sequence: ??? +predict_sequence: ??? output_preds_key: "losses_and_detections.eval" diff --git a/mart/datamodules/fiftyone.py b/mart/datamodules/fiftyone.py index 156c6589..cc9418d2 100644 --- a/mart/datamodules/fiftyone.py +++ b/mart/datamodules/fiftyone.py @@ -108,3 +108,36 @@ def __getitem__(self, index: int) -> Any: def __len__(self) -> int: return len(self.filtered_dataset) + + def add_predictions(self, sample_identifier: Any, preds: List[dict], field_name: str) -> None: + # get the sample that the detections will be added + sample = self.filtered_dataset[sample_identifier] + w = sample.metadata.width + h = sample.metadata.height + + # get the dataset classes + classes = self.filtered_dataset.default_classes + + # extract prediction values + labels = preds["labels"] + scores = preds["scores"] + boxes = preds["boxes"] + + # convert detections to FiftyOne format + detections = [] + for label, score, box in zip(labels, scores, boxes): + if label >= len(classes): + continue + + # Convert to [top-left-x, top-left-y, width, height] + # in relative coordinates in [0, 1] x [0, 1] + x1, y1, x2, y2 = box + rel_box = [x1 / w, y1 / h, (x2 - x1) / w, (y2 - y1) / h] + + detections.append( + fo.Detection(label=classes[label], bounding_box=rel_box, confidence=score) + ) + + # save detections to dataset + sample[field_name] = fo.Detections(detections=detections) + sample.save() diff --git a/mart/datamodules/modular.py b/mart/datamodules/modular.py index 3f7ea3f6..385e52dd 100644 --- a/mart/datamodules/modular.py +++ b/mart/datamodules/modular.py @@ -21,9 +21,11 @@ def __init__( train_dataset, val_dataset, test_dataset=None, + predict_dataset=None, train_sampler=None, val_sampler=None, test_sampler=None, + predict_sampler=None, num_workers=0, collate_fn=None, ims_per_batch=1, @@ -46,6 +48,9 @@ def __init__( self.test_dataset = test_dataset self.test_sampler = test_sampler + self.predict_dataset = predict_dataset + self.predict_sampler = predict_sampler + self.num_workers = num_workers self.collate_fn = collate_fn if not callable(self.collate_fn): @@ -78,6 +83,10 @@ def setup(self, stage=None): if not isinstance(self.test_dataset, (Dataset, type(None))): self.test_dataset = instantiate(self.test_dataset) + if stage == "predict" or stage is None: + if not isinstance(self.predict_dataset, (Dataset, type(None))): + self.predict_dataset = instantiate(self.predict_dataset) + def train_dataloader(self): batch_sampler = self.train_sampler if not isinstance(batch_sampler, (Sampler, type(None))): @@ -132,3 +141,21 @@ def test_dataloader(self): collate_fn=self.collate_fn, **kwargs, ) + + def predict_dataloader(self): + batch_sampler = self.predict_sampler + if not isinstance(batch_sampler, (Sampler, type(None))): + batch_sampler = instantiate(batch_sampler, self.predict_dataset) + + kwargs = {"batch_sampler": batch_sampler, "pin_memory": self.pin_memory} + + if batch_sampler is None: + kwargs["batch_size"] = self.batch_size + kwargs["shuffle"] = False + + return DataLoader( + self.predict_dataset, + num_workers=self.num_workers, + collate_fn=self.collate_fn, + **kwargs, + ) diff --git a/mart/models/modular.py b/mart/models/modular.py index 192204a2..df24d1bb 100644 --- a/mart/models/modular.py +++ b/mart/models/modular.py @@ -34,6 +34,8 @@ def __init__( test_sequence=None, test_step_log=None, test_metrics=None, + predict_sequence=None, + predict_step_log=None, load_state_dict=None, output_loss_key="loss", output_preds_key="preds", @@ -54,6 +56,8 @@ def __init__( validation_sequence = [validation_sequence[key] for key in sorted(validation_sequence)] if isinstance(test_sequence, dict): test_sequence = [test_sequence[key] for key in sorted(test_sequence)] + if isinstance(predict_sequence, dict): + predict_sequence = [predict_sequence[key] for key in sorted(predict_sequence)] # *_step() functions make some assumptions about the type of Module it can call. # That is, injecting a nn.Module generally won't work, so better to hardcode SequentialDict. @@ -62,6 +66,7 @@ def __init__( "training": training_sequence, "validation": validation_sequence, "test": test_sequence, + "predict": predict_sequence, } self.model = SequentialDict(modules, sequences) @@ -90,6 +95,11 @@ def __init__( self.test_step_log = test_step_log or {} self.test_metrics = test_metrics + # Be backwards compatible by turning list into dict where each item is its own key-value + if isinstance(predict_step_log, (list, tuple)): + predict_step_log = {item: item for item in predict_step_log} + self.predict_step_log = predict_step_log or {} + # Load state dict for specified modules. We flatten it because Hydra # commandlines converts dotted paths to nested dictionaries. if isinstance(load_state_dict, str): @@ -204,6 +214,18 @@ def on_test_epoch_end(self): self.log_metrics(metrics, prefix="test_metrics") + # + # Predict + # + def predict_step(self, batch, batch_idx): + input, target = batch + pred = self(input=input, target=target, model=self.model, step="predict") + + for log_name, output_key in self.predict_step_log.items(): + self.log(f"predict/{log_name}", pred[output_key]) + + return pred + # # Utilities # diff --git a/mart/tasks/lightning.py b/mart/tasks/lightning.py index 3539b81f..65d3ee59 100644 --- a/mart/tasks/lightning.py +++ b/mart/tasks/lightning.py @@ -84,6 +84,12 @@ def lightning(cfg: DictConfig) -> Tuple[Dict[str, Any], Dict[str, Any]]: test_metrics = trainer.callback_metrics + if cfg.get("predict"): + log.info("Starting predictions!") + trainer.predict( + model=model, datamodule=datamodule, ckpt_path=ckpt_path, return_predictions=False + ) + # merge train and test metrics metric_dict = {**train_metrics, **test_metrics} From 333d7e24ab9ee67e216111fd63a45d7be896c99c Mon Sep 17 00:00:00 2001 From: "Murillo Rojas, Luis" Date: Thu, 3 Aug 2023 15:55:43 +0000 Subject: [PATCH 10/12] Fix unit test --- mart/configs/model/torchvision_object_detection.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/mart/configs/model/torchvision_object_detection.yaml b/mart/configs/model/torchvision_object_detection.yaml index 1a2ece9b..534f0fc9 100644 --- a/mart/configs/model/torchvision_object_detection.yaml +++ b/mart/configs/model/torchvision_object_detection.yaml @@ -8,7 +8,6 @@ training_step_log: training_sequence: ??? validation_sequence: ??? test_sequence: ??? -predict_sequence: ??? output_preds_key: "losses_and_detections.eval" From fd904d3ef526dd9e4cd809267a513b50e0d8dfa6 Mon Sep 17 00:00:00 2001 From: "Murillo Rojas, Luis" Date: Thu, 3 Aug 2023 17:35:39 +0000 Subject: [PATCH 11/12] Add FiftyOneMistakenness callback --- mart/callbacks/fiftyone.py | 22 ++++++++++++++++++- .../callbacks/fiftyone_mistakenness.yaml | 3 +++ .../FiftyOne_TorchvisionFasterRCNN.yaml | 1 + 3 files changed, 25 insertions(+), 1 deletion(-) create mode 100644 mart/configs/callbacks/fiftyone_mistakenness.yaml diff --git a/mart/callbacks/fiftyone.py b/mart/callbacks/fiftyone.py index 151c12f7..d1eba3b2 100644 --- a/mart/callbacks/fiftyone.py +++ b/mart/callbacks/fiftyone.py @@ -14,10 +14,11 @@ logger = logging.getLogger(__name__) try: import fiftyone as fo + import fiftyone.brain as fob except ImportError: logger.debug("fiftyone module is not installed!") -__all__ = ["FiftyOneEvaluateDetections", "FiftyOnePredictionAdder"] +__all__ = ["FiftyOneEvaluateDetections", "FiftyOneMistakenness", "FiftyOnePredictionAdder"] class FiftyOneEvaluateDetections(Callback): @@ -48,6 +49,25 @@ def on_predict_end(self, trainer, pl_module): results.print_report(classes=classes_top10) +class FiftyOneMistakenness(Callback): + def __init__(self, run_id: str, gt_field: str = "ground_truth_detections") -> None: + self.prediction_field = f"prediction_{run_id}" + self.gt_field = gt_field + + def on_predict_start(self, trainer, pl_module): + self.predict_dataset = trainer.datamodule.predict_dataset + assert isinstance(self.predict_dataset, FiftyOneDataset) + + # reset mistakenness fields + if self.predict_dataset.dataset.has_brain_run("mistakenness"): + self.predict_dataset.dataset.delete_brain_run("mistakenness") + + def on_predict_end(self, trainer, pl_module): + fob.compute_mistakenness( + self.predict_dataset.filtered_dataset, self.prediction_field, label_field=self.gt_field + ) + + class FiftyOnePredictionAdder(BasePredictionWriter): def __init__(self, output_dir: str, write_interval: List[str]) -> None: super().__init__(write_interval) diff --git a/mart/configs/callbacks/fiftyone_mistakenness.yaml b/mart/configs/callbacks/fiftyone_mistakenness.yaml new file mode 100644 index 00000000..343d38ea --- /dev/null +++ b/mart/configs/callbacks/fiftyone_mistakenness.yaml @@ -0,0 +1,3 @@ +fiftyone_mistakenness: + _target_: mart.callbacks.FiftyOneMistakenness + run_id: ${now:%Y-%m-%d}_${now:%H-%M-%S} diff --git a/mart/configs/experiment/FiftyOne_TorchvisionFasterRCNN.yaml b/mart/configs/experiment/FiftyOne_TorchvisionFasterRCNN.yaml index 8ba988e0..111cb86d 100644 --- a/mart/configs/experiment/FiftyOne_TorchvisionFasterRCNN.yaml +++ b/mart/configs/experiment/FiftyOne_TorchvisionFasterRCNN.yaml @@ -9,6 +9,7 @@ defaults: lr_monitor, fiftyone_prediction_adder, fiftyone_evaluate_detections, + fiftyone_mistakenness, ] task_name: "FiftyOne_TorchvisionFasterRCNN" From 00bde066da1b8fba1d8606405c7ef21efd47c35c Mon Sep 17 00:00:00 2001 From: "Murillo Rojas, Luis" Date: Thu, 24 Aug 2023 20:41:27 +0000 Subject: [PATCH 12/12] Update fityone datamodule --- mart/configs/datamodule/fiftyone.yaml | 2 -- 1 file changed, 2 deletions(-) diff --git a/mart/configs/datamodule/fiftyone.yaml b/mart/configs/datamodule/fiftyone.yaml index a0d31ed3..f7710745 100644 --- a/mart/configs/datamodule/fiftyone.yaml +++ b/mart/configs/datamodule/fiftyone.yaml @@ -80,8 +80,6 @@ predict_dataset: transforms: - _target_: torchvision.transforms.ToTensor - _target_: mart.transforms.ConvertCocoPolysToMask - - _target_: mart.transforms.RandomHorizontalFlip - p: 0.5 - _target_: mart.transforms.Denormalize center: 0 scale: 255