Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 61 additions & 0 deletions tools/analysis_2d/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# analysis_2d

It provides a framework to developers in `AWML` to add analyses for 2D annotations in T4dataset easily.
With this framework, developers don't need to generate any `info` files or rewrite their data loading for the dataset.
They only need to follow `AnalysisCallbackInterface` to add the analyses they are interested in.

## Summary

- [Support priority](https://github.com/tier4/AWML/blob/main/docs/design/autoware_ml_design.md#support-priority): Tier B
- Supported dataset
- [x] T4dataset
- [] NuScenes
- Other supported feature
- [x] Distribution of categories
- [x] Distribution of attributes in each category
- [ ] Distribution of sizes
- [ ] Add unit tests

## Get started
### 1. Setup

- Please follow the [installation tutorial](/docs/tutorial/tutorial_detection_3d.md)to set up the environment.
- Run docker

```sh
docker run -it --rm --gpus all --shm-size=64g --name awml -p 6006:6006 -v $PWD/:/workspace -v $PWD/data:/workspace/data autoware-ml
```

### 2. Analysis
#### 2.1. Dataset analysis

Make sure the dataset follows the [T4dataset format](https://github.com/tier4/tier4_perception_dataset/blob/main/docs/t4_format_3d_detailed.md), note that it doesn't need any `info` file

```sh
# T4dataset (classification for traffic light)
python tools/analysis_2d/run.py --config_path autoware_ml/configs/classification2d/dataset/t4dataset/tlr_classifier_car.py --data_root_path data/t4dataset/ --out_dir data/t4dataset/analyses/
```

## For developer

1. Add a new analysis to inherit `AnalysisCallbackInterface` as a callback, and implement `run()`, for example, `tools/analysis_2d/callbacks/category_attribute.py`
2. Import the new analysis in `AnalysisRunner`, and add them to the list of `analysis_callbacks`, for example,

```python
self.analysis_callbacks: List[AnalysisCallbackInterface] = [
...
CategoryAttributeAnalysisCallback(
out_path=self.out_path,
category_name='green',
analysis_dir='green_attributes',
remapping_classes=self.remapping_classes),
# This is the new CategoryAttributeAnalysisCallback
CategoryAttributeAnalysisCallback(
out_path=self.out_path,
category_name='red',
analysis_dir='red_attributes'
),
]
```

## References
Empty file added tools/analysis_2d/__init__.py
Empty file.
107 changes: 107 additions & 0 deletions tools/analysis_2d/analysis_runner.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
from typing import Dict, List

from t4_devkit import Tier4

from tools.analysis_2d.data_classes import (
SampleData2D,
)
from tools.analysis_2d.utils import extract_tier4_sample_data
from tools.analysis_3d.analysis_runner import AnalysisRunner
from tools.analysis_3d.callbacks.callback_interface import AnalysisCallbackInterface
from tools.analysis_3d.callbacks.category import CategoryAnalysisCallback
from tools.analysis_3d.callbacks.category_attribute import CategoryAttributeAnalysisCallback


class AnalysisRunner2D(AnalysisRunner):
"""Runner to run list of analyses for the selected dataset."""

def __init__(
self,
data_root_path: str,
config_path: str,
out_path: str,
) -> None:
"""
:param data_root_path: Path where to save data.
:param config_path: Configuration path for a dataset.
:param out_path: Path where to save output.
"""
super().__init__(data_root_path, config_path, out_path)

# Override remapping_classes for 2D analysis and callbacks
self.remapping_classes = self.config.class_mappings
self.analysis_callbacks: List[AnalysisCallbackInterface] = [
CategoryAnalysisCallback(out_path=self.out_path, remapping_classes=self.remapping_classes),
CategoryAttributeAnalysisCallback(
out_path=self.out_path,
category_name="green",
analysis_dir="green_attributes",
remapping_classes=self.remapping_classes,
),
CategoryAttributeAnalysisCallback(
out_path=self.out_path,
category_name="red",
analysis_dir="red_attributes",
remapping_classes=self.remapping_classes,
),
CategoryAttributeAnalysisCallback(
out_path=self.out_path,
category_name="yellow",
analysis_dir="yellow_attributes",
remapping_classes=self.remapping_classes,
),
CategoryAttributeAnalysisCallback(
out_path=self.out_path,
category_name="left,red",
analysis_dir="left_red_attributes",
remapping_classes=self.remapping_classes,
),
CategoryAttributeAnalysisCallback(
out_path=self.out_path,
category_name="red,up_left",
analysis_dir="red_up_left_attributes",
remapping_classes=self.remapping_classes,
),
CategoryAttributeAnalysisCallback(
out_path=self.out_path,
category_name="red,right",
analysis_dir="red_right_attributes",
remapping_classes=self.remapping_classes,
),
CategoryAttributeAnalysisCallback(
out_path=self.out_path,
category_name="red,straight",
analysis_dir="red_straight_attributes",
remapping_classes=self.remapping_classes,
),
CategoryAttributeAnalysisCallback(
out_path=self.out_path,
category_name="left,red,straight",
analysis_dir="left_red_straight_attributes",
remapping_classes=self.remapping_classes,
),
CategoryAttributeAnalysisCallback(
out_path=self.out_path,
category_name="unknown",
analysis_dir="unknown_attributes",
remapping_classes=self.remapping_classes,
),
]

def _extract_sample_data(self, t4: Tier4) -> Dict[str, SampleData2D]:
"""
Extract data for every sample.
:param t4: Tier4 interface.
:return: A dict of {sample token: SampleData}.
"""
sample_data = {}
for sample in t4.sample:
# Extract sample data
tier4_sample_data = extract_tier4_sample_data(sample=sample, t4=t4)

# Convert to SampleData
sample_data[sample.token] = SampleData2D.create_sample_data(
sample_token=sample.token,
boxes=tier4_sample_data.boxes,
)
return sample_data
41 changes: 41 additions & 0 deletions tools/analysis_2d/data_classes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
from __future__ import annotations

from dataclasses import dataclass
from typing import List

from t4_devkit.dataclass import Box2D

from tools.analysis_3d.data_classes import DetectionBox, SampleData


@dataclass(frozen=True)
class Detection2DBox(DetectionBox):
"""2D boxes from detection."""

box: Box2D


@dataclass(frozen=True)
class SampleData2D(SampleData):
"""Dataclass to save data for a sample, for example, 2D bounding boxes."""

sample_token: str
detection_boxes: List[Detection2DBox]

@classmethod
def create_sample_data(
cls,
sample_token: str,
boxes: List[Box2D],
) -> SampleData2D:
"""
Create a SampleData2D given the params.
:param sample_token: Sample token to represent a sample (lidar frame).
:param boxes: List of 2D bounding boxes for the given sample token.
"""
detection_2d_boxes = [Detection2DBox(box=box, attrs=box.semantic_label.attributes) for box in boxes]

return cls(
sample_token=sample_token,
detection_boxes=detection_2d_boxes,
)
53 changes: 53 additions & 0 deletions tools/analysis_2d/run.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
"""Script to compute analysis of T4 datasets."""

import argparse

from mmengine.logging import print_log

from tools.analysis_2d.analysis_runner import AnalysisRunner2D


def parse_args():
"""Add args and parse them through CLI."""
parser = argparse.ArgumentParser(description="analysis of T4dataset in 2D")
parser.add_argument(
"--config_path",
type=str,
required=True,
help="config for T4dataset",
)
parser.add_argument(
"--data_root_path",
type=str,
required=True,
help="specify the root path of dataset",
)
parser.add_argument(
"-o",
"--out_dir",
type=str,
required=True,
help="output directory of info file",
)
args = parser.parse_args()
return args


def main():
"""Main enrtypoint to run the Runner."""
args = parse_args()
# Build AnalysesRunner
print_log("Building AnalysisRunner2D...", logger="current")
analysis_runner = AnalysisRunner2D(
data_root_path=args.data_root_path,
config_path=args.config_path,
out_path=args.out_dir,
)
print_log("Built AnalysisRunner!")

# Run AnalysesRunner
analysis_runner.run()


if __name__ == "__main__":
main()
Loading