Commit 41b18fd8 authored by zhe chen's avatar zhe chen
Browse files

Use pre-commit to reformat code


Use pre-commit to reformat code
parent ff20ea39
# ============================================================================== # ==============================================================================
# Binaries and/or source for the following packages or projects # Binaries and/or source for the following packages or projects
# are presented under one or more of the following open source licenses: # are presented under one or more of the following open source licenses:
# preprocess.py The OpenLane-V2 Dataset Authors Apache License, Version 2.0 # preprocess.py The OpenLane-V2 Dataset Authors Apache License, Version 2.0
# #
......
...@@ -2,39 +2,44 @@ ...@@ -2,39 +2,44 @@
## Download ## Download
The files mentioned below can also be downloaded via [OpenDataLab](https://opendatalab.com/OpenLane-V2/download). The files mentioned below can also be downloaded via [OpenDataLab](https://opendatalab.com/OpenLane-V2/download). It is
It is recommended to use provided [command line interface](https://opendatalab.com/OpenLane-V2/cli) for acceleration. recommended to use provided [command line interface](https://opendatalab.com/OpenLane-V2/cli) for acceleration.
| Subset | Split | Google Drive <img src="https://ssl.gstatic.com/docs/doclist/images/drive_2022q3_32dp.png" alt="Google Drive" width="18"/> | Baidu Yun <img src="https://nd-static.bdstatic.com/m-static/v20-main/favicon-main.ico" alt="Baidu Yun" width="18"/> | md5 | Size | | Subset | Split | Google Drive <img src="https://ssl.gstatic.com/docs/doclist/images/drive_2022q3_32dp.png" alt="Google Drive" width="18"/> | Baidu Yun <img src="https://nd-static.bdstatic.com/m-static/v20-main/favicon-main.ico" alt="Baidu Yun" width="18"/> | md5 | Size |
| --- | --- | --- | --- | --- | --- | | -------- | ----------- | ------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- | -------------------------------- | ------ |
| subset_A | sample |[sample](https://drive.google.com/file/d/1Ni-L6u1MGKJRAfUXm39PdBIxdk_ntdc6/view?usp=share_link) | [sample](https://pan.baidu.com/s/1ncqwDtuihKTBZROL5vdCAQ?pwd=psev) | 21c607fa5a1930275b7f1409b25042a0 | ~300M | | subset_A | sample | [sample](https://drive.google.com/file/d/1Ni-L6u1MGKJRAfUXm39PdBIxdk_ntdc6/view?usp=share_link) | [sample](https://pan.baidu.com/s/1ncqwDtuihKTBZROL5vdCAQ?pwd=psev) | 21c607fa5a1930275b7f1409b25042a0 | ~300M |
| subset_A | all | [info](https://drive.google.com/file/d/1t47lNF4H3WhSsAqgsl9lSLIeO0p6n8p4/view?usp=share_link) | [info](https://pan.baidu.com/s/1uXpX4hqlMJLm0W6l12dJ-A?pwd=6rzj) |95bf28ccf22583d20434d75800be065d | ~8.8G | | subset_A | all | [info](https://drive.google.com/file/d/1t47lNF4H3WhSsAqgsl9lSLIeO0p6n8p4/view?usp=share_link) | [info](https://pan.baidu.com/s/1uXpX4hqlMJLm0W6l12dJ-A?pwd=6rzj) | 95bf28ccf22583d20434d75800be065d | ~8.8G |
| | train | [image_0](https://drive.google.com/file/d/1jio4Gj3dNlXmSzebO6D7Uy5oz4EaTNTq/view?usp=share_link) | [image_0](https://pan.baidu.com/s/12aV4CoT8znEY12q4M8XFiw?pwd=m204) | 8ade7daeec1b64f8ab91a50c81d812f6 | ~14.0G | | | train | [image_0](https://drive.google.com/file/d/1jio4Gj3dNlXmSzebO6D7Uy5oz4EaTNTq/view?usp=share_link) | [image_0](https://pan.baidu.com/s/12aV4CoT8znEY12q4M8XFiw?pwd=m204) | 8ade7daeec1b64f8ab91a50c81d812f6 | ~14.0G |
| | | [image_1](https://drive.google.com/file/d/1IgnvZ2UljL49AzNV6CGNGFLQo6tjNFJq/view?usp=share_link) | [image_1](https://pan.baidu.com/s/1SArnlA2_Om9o0xcGd6-EwA?pwd=khx8) | c78e776f79e2394d2d5d95b7b5985e0f | ~14.3G | | | | [image_1](https://drive.google.com/file/d/1IgnvZ2UljL49AzNV6CGNGFLQo6tjNFJq/view?usp=share_link) | [image_1](https://pan.baidu.com/s/1SArnlA2_Om9o0xcGd6-EwA?pwd=khx8) | c78e776f79e2394d2d5d95b7b5985e0f | ~14.3G |
| | | [image_2](https://drive.google.com/file/d/1ViEsK5hukjMGfOm_HrCiQPkGArWrT91o/view?usp=share_link) | [image_2](https://pan.baidu.com/s/1ZghG7gwJqFrGxCEcUffp8A?pwd=0xgm) | 4bf09079144aa54cb4dcd5ff6e00cf79 | ~14.2G | | | | [image_2](https://drive.google.com/file/d/1ViEsK5hukjMGfOm_HrCiQPkGArWrT91o/view?usp=share_link) | [image_2](https://pan.baidu.com/s/1ZghG7gwJqFrGxCEcUffp8A?pwd=0xgm) | 4bf09079144aa54cb4dcd5ff6e00cf79 | ~14.2G |
| | | [image_3](https://drive.google.com/file/d/1r3NYauV0JIghSmEihTxto0MMoyoh4waK/view?usp=share_link) | [image_3](https://pan.baidu.com/s/1ogwmXwS9u-B9nhtHlBTz5g?pwd=sqeg) | fd9e64345445975f462213b209632aee | ~14.4G | | | | [image_3](https://drive.google.com/file/d/1r3NYauV0JIghSmEihTxto0MMoyoh4waK/view?usp=share_link) | [image_3](https://pan.baidu.com/s/1ogwmXwS9u-B9nhtHlBTz5g?pwd=sqeg) | fd9e64345445975f462213b209632aee | ~14.4G |
| | | [image_4](https://drive.google.com/file/d/1aBe5yxNBew11YRRu-srQNwc5OloyKP4r/view?usp=share_link) | [image_4](https://pan.baidu.com/s/1tMAmUcZH2SzCiJoxwgk87w?pwd=i1au) | ae07e48c88ea2c3f6afbdf5ff71e9821 | ~14.5G | | | | [image_4](https://drive.google.com/file/d/1aBe5yxNBew11YRRu-srQNwc5OloyKP4r/view?usp=share_link) | [image_4](https://pan.baidu.com/s/1tMAmUcZH2SzCiJoxwgk87w?pwd=i1au) | ae07e48c88ea2c3f6afbdf5ff71e9821 | ~14.5G |
| | | [image_5](https://drive.google.com/file/d/1Or-Nmsq4SU24KNe-cn9twVYVprYPUd_y/view?usp=share_link) | [image_5](https://pan.baidu.com/s/1sRyrhcSz-izW2U5x3UACSA?pwd=nzxx) | df62c1f6e6b3fb2a2a0868c78ab19c92 | ~14.2G | | | | [image_5](https://drive.google.com/file/d/1Or-Nmsq4SU24KNe-cn9twVYVprYPUd_y/view?usp=share_link) | [image_5](https://pan.baidu.com/s/1sRyrhcSz-izW2U5x3UACSA?pwd=nzxx) | df62c1f6e6b3fb2a2a0868c78ab19c92 | ~14.2G |
| | | [image_6](https://drive.google.com/file/d/1mSWU-2nMzCO5PGF7yF9scoPntWl7ItfZ/view?usp=share_link) | [image_6](https://pan.baidu.com/s/1P3zn_L6EIGUHb43qWOJYWg?pwd=4wei) | 7bff1ce30329235f8e0f25f6f6653b8f | ~14.4G | | | | [image_6](https://drive.google.com/file/d/1mSWU-2nMzCO5PGF7yF9scoPntWl7ItfZ/view?usp=share_link) | [image_6](https://pan.baidu.com/s/1P3zn_L6EIGUHb43qWOJYWg?pwd=4wei) | 7bff1ce30329235f8e0f25f6f6653b8f | ~14.4G |
| | val | [image_7](https://drive.google.com/file/d/19N5q-zbjE2QWngAT9xfqgOR3DROTAln0/view?usp=share_link) | [image_7](https://pan.baidu.com/s/1rRkPWg-zG2ygsbMhwXjPKg?pwd=qsvb) | c73af4a7aef2692b96e4e00795120504 | ~21.0G | | | val | [image_7](https://drive.google.com/file/d/19N5q-zbjE2QWngAT9xfqgOR3DROTAln0/view?usp=share_link) | [image_7](https://pan.baidu.com/s/1rRkPWg-zG2ygsbMhwXjPKg?pwd=qsvb) | c73af4a7aef2692b96e4e00795120504 | ~21.0G |
| | test | [image_8](https://drive.google.com/file/d/1CvT9w0q8vPldfaajI5YsAqM0ZINT1vJv/view?usp=share_link) | [image_8](https://pan.baidu.com/s/10zjKeuAw350fwTYAeuSLxg?pwd=99ch) | fb2f61e7309e0b48e2697e085a66a259 | ~21.2G | | | test | [image_8](https://drive.google.com/file/d/1CvT9w0q8vPldfaajI5YsAqM0ZINT1vJv/view?usp=share_link) | [image_8](https://pan.baidu.com/s/10zjKeuAw350fwTYAeuSLxg?pwd=99ch) | fb2f61e7309e0b48e2697e085a66a259 | ~21.2G |
| subset_B | coming soon | - | - | - | - | | subset_B | coming soon | - | - | - | - |
For files in Google Drive, you can use the following command by replacing `[FILE_ID]` and `[FILE_NAME]` accordingly: For files in Google Drive, you can use the following command by replacing `[FILE_ID]` and `[FILE_NAME]` accordingly:
```sh ```sh
wget --load-cookies /tmp/cookies.txt "https://docs.google.com/uc?export=download&confirm=$(wget --quiet --save-cookies /tmp/cookies.txt --keep-session-cookies --no-check-certificate 'https://docs.google.com/uc?export=download&id=[FILE_ID]' -O- | sed -rn 's/.*confirm=([0-9A-Za-z_]+).*/\1\n/p')&id=[FILE_ID]" -O [FILE_NAME] wget --load-cookies /tmp/cookies.txt "https://docs.google.com/uc?export=download&confirm=$(wget --quiet --save-cookies /tmp/cookies.txt --keep-session-cookies --no-check-certificate 'https://docs.google.com/uc?export=download&id=[FILE_ID]' -O- | sed -rn 's/.*confirm=([0-9A-Za-z_]+).*/\1\n/p')&id=[FILE_ID]" -O [FILE_NAME]
``` ```
## Preprocess ## Preprocess
The dataset is preprocessed into pickle files representing different collections, which then be used for training models or evaluation:
The dataset is preprocessed into pickle files representing different collections, which then be used for training models
or evaluation:
```sh ```sh
cd data cd data
python OpenLane-V2/preprocess.py python OpenLane-V2/preprocess.py
``` ```
## Hierarchy ## Hierarchy
The hierarchy of folder `OpenLane-V2/` is described below: The hierarchy of folder `OpenLane-V2/` is described below:
``` ```
└── OpenLane-V2 └── OpenLane-V2
├── train ├── train
...@@ -61,12 +66,14 @@ The hierarchy of folder `OpenLane-V2/` is described below: ...@@ -61,12 +66,14 @@ The hierarchy of folder `OpenLane-V2/` is described below:
``` ```
- `[segment_id]` specifies a sequence of frames, and `[timestamp]` specifies a single frame in a sequence. - `[segment_id]` specifies a sequence of frames, and `[timestamp]` specifies a single frame in a sequence.
- `image/` contains images captured by various cameras, and `info/` contains meta data and annotations of a single frame. - `image/` contains images captured by various cameras, and `info/` contains meta data and annotations of a single
frame.
- `data_dict_[xxx].json` notes the split of train / val / test under the subset of data. - `data_dict_[xxx].json` notes the split of train / val / test under the subset of data.
## Meta Data ## Meta Data
The json files under the `info/` folder contain meta data and annotations for each frame.
Each file is formatted as follows: The json files under the `info/` folder contain meta data and annotations for each frame. Each file is formatted as
follows:
``` ```
{ {
...@@ -84,19 +91,20 @@ Each file is formatted as follows: ...@@ -84,19 +91,20 @@ Each file is formatted as follows:
'intrinsic': <dict> -- intrinsic parameters of the camera 'intrinsic': <dict> -- intrinsic parameters of the camera
}, },
... ...
} }
'pose': <dict> -- ego pose 'pose': <dict> -- ego pose
'annotation': <dict> -- anntations for the current frame 'annotation': <dict> -- anntations for the current frame
} }
``` ```
## Annotations ## Annotations
For a single frame, annotations are formatted as follow: For a single frame, annotations are formatted as follow:
``` ```
{ {
'lane_centerline': [ (n lane centerlines in the current frame) 'lane_centerline': [ (n lane centerlines in the current frame)
{ {
'id': <int> -- unique ID in the current frame 'id': <int> -- unique ID in the current frame
'points': <float> [n, 3] -- 3D coordiate 'points': <float> [n, 3] -- 3D coordiate
'confidence': <float> -- confidence, only for prediction 'confidence': <float> -- confidence, only for prediction
...@@ -104,7 +112,7 @@ For a single frame, annotations are formatted as follow: ...@@ -104,7 +112,7 @@ For a single frame, annotations are formatted as follow:
... ...
], ],
'traffic_element': [ (k traffic elements in the current frame) 'traffic_element': [ (k traffic elements in the current frame)
{ {
'id': <int> -- unique ID in the current frame 'id': <int> -- unique ID in the current frame
'category': <int> -- traffic element category 'category': <int> -- traffic element category
1: 'traffic_light', 1: 'traffic_light',
...@@ -133,11 +141,11 @@ For a single frame, annotations are formatted as follow: ...@@ -133,11 +141,11 @@ For a single frame, annotations are formatted as follow:
} }
``` ```
- `id` is the identifier of a lane centerline or traffic element and is consistent in a sequence. - `id` is the identifier of a lane centerline or traffic element and is consistent in a sequence. For predictions, it
For predictions, it can be randomly assigned but unique in a single frame. can be randomly assigned but unique in a single frame.
- `topology_lclc` and `topology_lcte` are adjacent matrices, where row and column are sorted according to the order of the lists `lane_centerline` and `traffic_element`. - `topology_lclc` and `topology_lcte` are adjacent matrices, where row and column are sorted according to the order of
It is a MUST to keep the ordering the same for correct evaluation. the lists `lane_centerline` and `traffic_element`. It is a MUST to keep the ordering the same for correct evaluation.
For ground truth, only 0 or 1 is a valid boolean value for an element in the matrix. For ground truth, only 0 or 1 is a valid boolean value for an element in the matrix. For predictions, the value varies
For predictions, the value varies from 0 to 1, representing the confidence of the predicted relationship. from 0 to 1, representing the confidence of the predicted relationship.
- #lane_centerline and #traffic_element are not required to be equal between ground truth and predictions. - # lane_centerline and #traffic_element are not required to be equal between ground truth and predictions.
In the process of evaluation, a matching of ground truth and predictions is determined. In the process of evaluation, a matching of ground truth and predictions is determined.
...@@ -2,10 +2,11 @@ ...@@ -2,10 +2,11 @@
## Criterion ## Criterion
The road structure cognition task is defined as inputting the surrounding view images, reconstructing the high-precision map of the self-vehicle, and outputting the recognition result of the direction of the self-vehicle. The road structure cognition task is defined as inputting the surrounding view images, reconstructing the high-precision
The specific expansion is to input the surrounding view images of the vehicle, HDMap; the output is the lane centerlines, the traffic signs, the topology of the lane centerlines, and the correspondence between lanes centerlines and traffic signs. map of the self-vehicle, and outputting the recognition result of the direction of the self-vehicle. The specific
Below are examples of visualizing annotations and relationships between different elements on 2D images. expansion is to input the surrounding view images of the vehicle, HDMap; the output is the lane centerlines, the traffic
signs, the topology of the lane centerlines, and the correspondence between lanes centerlines and traffic signs. Below
are examples of visualizing annotations and relationships between different elements on 2D images.
![image](https://user-images.githubusercontent.com/47048022/209953048-f8ded0da-6005-45b7-8e3d-501dbd422058.png) ![image](https://user-images.githubusercontent.com/47048022/209953048-f8ded0da-6005-45b7-8e3d-501dbd422058.png)
![image](https://user-images.githubusercontent.com/47048022/209954207-7b8a1b5a-8243-41d5-91fe-f2de5949107e.png) ![image](https://user-images.githubusercontent.com/47048022/209954207-7b8a1b5a-8243-41d5-91fe-f2de5949107e.png)
# Devkit # Devkit
Here we describe the API provided by the OpenLane-V2 devkit. Here we describe the API provided by the OpenLane-V2 devkit.
## openlanev2.dataset ## openlanev2.dataset
### Collection ### Collection
is a collection of frames.
#### `Collection.get_frame_via_identifier(identifier : tuple) -> Frame` is a collection of frames.
#### `Collection.get_frame_via_identifier(identifier : tuple) -> Frame`
Returns a frame with the given identifier (split, segment_id, timestamp). Returns a frame with the given identifier (split, segment_id, timestamp).
#### `Collection.get_frame_via_index(index : int) -> (tuple, Frame)` #### `Collection.get_frame_via_index(index : int) -> (tuple, Frame)`
Returns a frame with the given index. Returns a frame with the given index.
### Frame ### Frame
is a data structure containing meta data of a frame. is a data structure containing meta data of a frame.
#### `Frame.get_camera_list() -> list`
#### `Frame.get_camera_list() -> list`
Retuens a list of camera names. Retuens a list of camera names.
#### `Frame.get_pose() -> dict` #### `Frame.get_pose() -> dict`
Retuens the pose of ego vehicle. Retuens the pose of ego vehicle.
#### `Frame.get_image_path(camera : str) -> str` #### `Frame.get_image_path(camera : str) -> str`
Retuens the image path given a camera. Retuens the image path given a camera.
#### `Frame.get_rgb_image(camera : str) -> np.ndarray` #### `Frame.get_rgb_image(camera : str) -> np.ndarray`
Retuens the RGB image given a camera. Retuens the RGB image given a camera.
#### `Frame.get_intrinsic(camera : str) -> dict` #### `Frame.get_intrinsic(camera : str) -> dict`
Retuens the intrinsic given a camera. Retuens the intrinsic given a camera.
#### `Frame.get_extrinsic(camera : str) -> dict` #### `Frame.get_extrinsic(camera : str) -> dict`
Retuens the extrinsic given a camera. Retuens the extrinsic given a camera.
#### `Frame.get_annotations() -> dict` #### `Frame.get_annotations() -> dict`
Retuens annotations of the current frame. Retuens annotations of the current frame.
#### `Frame.get_annotations_lane_centerlines() -> dict` #### `Frame.get_annotations_lane_centerlines() -> dict`
Retuens lane centerline annotations of the current frame. Retuens lane centerline annotations of the current frame.
#### `Frame.get_annotations_traffic_elements() -> dict` #### `Frame.get_annotations_traffic_elements() -> dict`
Retuens traffic element annotations of the current frame. Retuens traffic element annotations of the current frame.
#### `Frame.get_annotations_topology_lclc() -> list` #### `Frame.get_annotations_topology_lclc() -> list`
Retuens the adjacent matrix of topology_lclc. Retuens the adjacent matrix of topology_lclc.
#### `Frame.get_annotations_topology_lcte() -> list` #### `Frame.get_annotations_topology_lcte() -> list`
Retuens the adjacent matrix of topology_lcte. Retuens the adjacent matrix of topology_lcte.
## openlanev2.evaluation ## openlanev2.evaluation
#### `evaluate(ground_truth, predictions) -> dict` #### `evaluate(ground_truth, predictions) -> dict`
Given the ground truth and predictions, which are formatted dict or the path to pickle storing the dict that ground truth is preprocessed pickle file and predictions are formatted as described [here](./submission.md#format), this function returns a dict storing all metrics defined by our task.
Given the ground truth and predictions, which are formatted dict or the path to pickle storing the dict that ground
truth is preprocessed pickle file and predictions are formatted as described [here](./submission.md#format), this
function returns a dict storing all metrics defined by our task.
## openlanev2.io ## openlanev2.io
This subpackage wraps all IO operations of the OpenLane-V2 devkit.
It can be modified for different IO operations. This subpackage wraps all IO operations of the OpenLane-V2 devkit. It can be modified for different IO operations.
## openlanev2.preprocessing ## openlanev2.preprocessing
#### `collect(root_path : str, data_dict : dict, collection : str, point_interval : int = 1) -> None` #### `collect(root_path : str, data_dict : dict, collection : str, point_interval : int = 1) -> None`
Given a data_dict storing identifiers of frames, this function collects meta data the frames and stores it into a pickle file for efficient IO for the following operations.
Given a data_dict storing identifiers of frames, this function collects meta data the frames and stores it into a pickle
file for efficient IO for the following operations.
#### `check_results(results : dict) -> None` #### `check_results(results : dict) -> None`
Check format of results. Check format of results.
## openlanev2.visualization ## openlanev2.visualization
This subpackage provides tools for visualization. Please refer to the [tutorial](../tutorial.ipynb) for examples. This subpackage provides tools for visualization. Please refer to the [tutorial](../tutorial.ipynb) for examples.
# Metrics # Metrics
## OpenLane-V2 Score ## OpenLane-V2 Score
To evaluate performances on different aspects of the task, several metrics are adopted: To evaluate performances on different aspects of the task, several metrics are adopted:
- $\text{DET}_{l}$ for mAP on directed lane centerlines,
- $\text{DET}_{t}$ for mAP on traffic elements, - $\\text{DET}\_{l}$ for mAP on directed lane centerlines,
- $\text{TOP}_{ll}$ for mAP on topology among lane centerlines, - $\\text{DET}\_{t}$ for mAP on traffic elements,
- $\text{TOP}_{lt}$ for mAP on topology between lane centerlines and traffic elements. - $\\text{TOP}\_{ll}$ for mAP on topology among lane centerlines,
- $\\text{TOP}\_{lt}$ for mAP on topology between lane centerlines and traffic elements.
We consolidate the above metrics by computing an average of them, resulting in the **OpenLane-V2 Score (OLS)**. We consolidate the above metrics by computing an average of them, resulting in the **OpenLane-V2 Score (OLS)**.
### Lane Centerline ### Lane Centerline
We adopt the average precision (AP) but define a match of lane centerlines by considering the discrete Frechet distance in the 3D space.
The mAP for lane centerlines is averaged over match thresholds of $\\{1.0, 2.0, 3.0\\}$ on the similarity measure. We adopt the average precision (AP) but define a match of lane centerlines by considering the discrete Frechet distance
in the 3D space. The mAP for lane centerlines is averaged over match thresholds of $\\{1.0, 2.0, 3.0\\}$ on the
similarity measure.
### Traffic Element ### Traffic Element
Similarly, we use AP to evaluate the task of traffic element detection.
We consider IoU distance as the affinity measure with a match threshold of $0.75$. Similarly, we use AP to evaluate the task of traffic element detection. We consider IoU distance as the affinity measure
Besides, traffic elements have their own attribute. with a match threshold of $0.75$. Besides, traffic elements have their own attribute. For instance, a traffic light can
For instance, a traffic light can be red or green, which indicates the drivable state of the lane. be red or green, which indicates the drivable state of the lane. Therefore, the mAP is then averaged over attributes.
Therefore, the mAP is then averaged over attributes.
### Topology ### Topology
The topology metrics estimate the goodness of the relationship among lane centerlines and the relationship between lane centerlines and traffic elements.
To formulate the task of topology prediction as a link prediction problem, we first determine a match of ground truth and predicted vertices (lane centerlines and traffic elements) in the relationship graph.
We choose Frechet and IoU distance for the lane centerline and traffic element respectively.
Also, the metric is average over different recalls.
We adopt mAP from link prediction, which is defined as a mean of APs over all vertices. The topology metrics estimate the goodness of the relationship among lane centerlines and the relationship between lane
Two vertices are regarded as connected if the predicted confidence of the edge is greater than $0.5$. centerlines and traffic elements. To formulate the task of topology prediction as a link prediction problem, we first
The AP of a vertex is obtained by ranking all predicted edges and calculating the accumulative mean of the precisions: determine a match of ground truth and predicted vertices (lane centerlines and traffic elements) in the relationship
graph. We choose Frechet and IoU distance for the lane centerline and traffic element respectively. Also, the metric is
average over different recalls.
$$ We adopt mAP from link prediction, which is defined as a mean of APs over all vertices. Two vertices are regarded as
mAP = \frac{1}{|V|} \sum_{v \in V} \frac{\sum_{\hat{n} \in \hat{N}(v)} P(\hat{n}) \mathbb{1}(\hat{n} \in N(v))}{|N(v)|}, connected if the predicted confidence of the edge is greater than $0.5$. The AP of a vertex is obtained by ranking all
$$ predicted edges and calculating the accumulative mean of the precisions:
$$ mAP = \\frac{1}{|V|} \\sum\_{v \\in V} \\frac{\\sum\_{\\hat{n} \\in \\hat{N}(v)} P(\\hat{n}) \\mathbb{1}(\\hat{n}
\\in N(v))}{|N(v)|}, $$
where $N(v)$ denotes ordered list of neighbors of vertex $v$ ranked by confidence and $P(v)$ is the precision of the $i$-th vertex $v$ in the ordered list. where $N(v)$ denotes ordered list of neighbors of vertex $v$ ranked by confidence and $P(v)$ is the precision of the
$i$-th vertex $v$ in the ordered list.
Given ground truth and predicted connectivity of lane centerlines, the mAP is calculated on $G^{l} = (V^{l}, E^{l})$ and $\hat{G}^{l} = (\hat{V}^{l}, \hat{E}^{l})$. Given ground truth and predicted connectivity of lane centerlines, the mAP is calculated on $G^{l} = (V^{l}, E^{l})$ and
As the given graphs are directed, e.g., the ending point of a lane centerline is connected to the starting point of the next lane centerline, we take the mean of mAP over graphs with only in-going or out-going edges. $\\hat{G}^{l} = (\\hat{V}^{l}, \\hat{E}^{l})$. As the given graphs are directed, e.g., the ending point of a lane
centerline is connected to the starting point of the next lane centerline, we take the mean of mAP over graphs with only
in-going or out-going edges.
To evaluate the predicted topology between lane centerlines and traffic elements, we ignore the relationship among lane centerlines. To evaluate the predicted topology between lane centerlines and traffic elements, we ignore the relationship among lane
The relationship among traffic elements is also not taken into consideration. centerlines. The relationship among traffic elements is also not taken into consideration. Thus this can be seen as a
Thus this can be seen as a link prediction problem on a bipartite undirected graph that $G = (V^{l} \cup V^{t}, E)$ and $\hat{G} = (\hat{V}^{l} \cup \hat{V}^{t}, \hat{E})$. link prediction problem on a bipartite undirected graph that $G = (V^{l} \\cup V^{t}, E)$ and $\\hat{G} = (\\hat{V}^{l}
\\cup \\hat{V}^{t}, \\hat{E})$.
## Distances ## Distances
To measure the similarity between ground truth and predicted instances, we adopt Frechet and IoU distances for directed curves and 2D bounding boxes respectively.
To measure the similarity between ground truth and predicted instances, we adopt Frechet and IoU distances for directed
curves and 2D bounding boxes respectively.
### Frechet Distance ### Frechet Distance
Discrete Frechet distance measures the geometric similarity of two ordered lists of points.
Given a pair of curves, namely a ground truth $v = (p_1, ..., p_n)$ and a prediction $\hat{v} = (\hat{p}_1, ..., \hat{p}_k)$, a coupling $L$ is a sequence of distinct pairs between $v$ and $\hat{v}$: Discrete Frechet distance measures the geometric similarity of two ordered lists of points. Given a pair of curves,
namely a ground truth $v = (p_1, ..., p_n)$ and a prediction $\\hat{v} = (\\hat{p}\_1, ..., \\hat{p}\_k)$, a coupling
$L$ is a sequence of distinct pairs between $v$ and $\\hat{v}$:
$$ $$
(p_{a_1} \ , \ \hat{p}_{b_1} \ ), ..., (p_{a_m} \ , \ \hat{p}_{b_m} \ ), (p\_{a_1} \\ , \\ \\hat{p}_{b_1} \\ ), ..., (p_{a_m} \\ , \\ \\hat{p}\_{b_m} \\ ), $$
$$
where $a_1, ..., a_m$ and $b_1, ..., b_m$ are nondecreasing surjection such that $1 = a_1 \leq a_i \leq a_j \leq a_m = n$ and $1 = b_1 \leq b_i \leq b_j \leq b_m = k$ for all $i < j$. Then the norm $||L||$ of a coupling $L$ is the distance of the most dissimilar pair in $L$ that: where $a_1, ..., a_m$ and $b_1, ..., b_m$ are nondecreasing surjection such that $1 = a_1 \\leq a_i \\leq a_j \\leq a_m
= n$ and $1 = b_1 \\leq b_i \\leq b_j \\leq b_m = k$ for all $i \< j$. Then the norm $||L||$ of a coupling $L$ is the
distance of the most dissimilar pair in $L$ that:
$$ $$ ||L|| = \\mathop{max}_{i=1, ..., m} D(p_{a_i} \\ , \\ \\hat{p}\_{b_i} \\ ). $$
||L|| = \mathop{max}_{i=1, ..., m} D(p_{a_i} \ , \ \hat{p}_{b_i} \ ).
$$
The Frechet distance of a pair of curves is the minimum norm of all possible coupling that: The Frechet distance of a pair of curves is the minimum norm of all possible coupling that:
$$ $$ D\_{Frechet}(v, \\hat{v}) = min\\{||L|| \\ | \\ for \\ all \\ possible \\ coupling \\ L\\}. $$
D_{Frechet}(v, \hat{v}) = min\\{||L|| \ | \ for \ all \ possible \ coupling \ L\\}.
$$
### IoU Distance ### IoU Distance
To preserve consistency to the distance mentioned above, we modify the common IoU (Intersection over Union) measure that:
$$ To preserve consistency to the distance mentioned above, we modify the common IoU (Intersection over Union) measure
D_{IoU}(X, \hat{X}) = 1 - \frac{|X \cap \hat{X}|}{|X \cup \hat{X}|}, that:
$$
$$ D\_{IoU}(X, \\hat{X}) = 1 - \\frac{|X \\cap \\hat{X}|}{|X \\cup \\hat{X}|}, $$
where $X$ and $\hat{X}$ is the ground truth and predicted bounding box respectively. where $X$ and $\\hat{X}$ is the ground truth and predicted bounding box respectively.
...@@ -3,13 +3,17 @@ ...@@ -3,13 +3,17 @@
## `subset_A` ## `subset_A`
### Temporal Consistency ### Temporal Consistency
![image](https://user-images.githubusercontent.com/29263416/228440318-f24136e5-7a26-4b28-bb74-6a448c900756.png) ![image](https://user-images.githubusercontent.com/29263416/228440318-f24136e5-7a26-4b28-bb74-6a448c900756.png)
### Instance Distribution ### Instance Distribution
![image](https://user-images.githubusercontent.com/29263416/228441160-19d399c8-548c-4bef-8909-06ffcd0c027b.png) ![image](https://user-images.githubusercontent.com/29263416/228441160-19d399c8-548c-4bef-8909-06ffcd0c027b.png)
### Centerline Property ### Centerline Property
![image](https://user-images.githubusercontent.com/29263416/228442761-5895e5b4-6d3a-4b90-8dbb-4ecfab98190e.png) ![image](https://user-images.githubusercontent.com/29263416/228442761-5895e5b4-6d3a-4b90-8dbb-4ecfab98190e.png)
### Topology Distribution ### Topology Distribution
![image](https://user-images.githubusercontent.com/29263416/228443434-a74085dc-28f8-400d-99a2-f0c67b49bf66.png) ![image](https://user-images.githubusercontent.com/29263416/228443434-a74085dc-28f8-400d-99a2-f0c67b49bf66.png)
# Submission # Submission
## Format ## Format
The submitted results are required to be stored in a pickle file, which is a dict of identifier and [formatted predictions](../data/README.md#annotations) of a frame:
The submitted results are required to be stored in a pickle file, which is a dict of identifier
and [formatted predictions](../data/README.md#annotations) of a frame:
``` ```
{ {
...@@ -16,15 +18,19 @@ The submitted results are required to be stored in a pickle file, which is a dic ...@@ -16,15 +18,19 @@ The submitted results are required to be stored in a pickle file, which is a dic
'traffic_element': ... 'traffic_element': ...
'topology_lclc': ... 'topology_lclc': ...
'topology_lcte': ... 'topology_lcte': ...
}, },
... ...
} }
} }
``` ```
*: For validation, `from iso3166 import countries; countries.get(str)` can be used.
\*: For validation, `from iso3166 import countries; countries.get(str)` can be used.
## Steps ## Steps
1. Create a team on [EvalAI](https://eval.ai/web/challenges/challenge-page/1925). 1. Create a team on [EvalAI](https://eval.ai/web/challenges/challenge-page/1925).
2. Click the 'Participate' tag, then choose a team for participation. 2. Click the 'Participate' tag, then choose a team for participation.
3. Choose the phase 'Test Phase (CVPR 2023 Autonomous Driving Challenge)' and upload the file formatted as mentioned above. 3. Choose the phase 'Test Phase (CVPR 2023 Autonomous Driving Challenge)' and upload the file formatted as mentioned
4. Check if the submitted file is valid, which is indicated by the 'Status' under the tag of 'My Submissions'. A valid submission would provide performance scores. above.
4. Check if the submitted file is valid, which is indicated by the 'Status' under the tag of 'My Submissions'. A valid
submission would provide performance scores.
from .collection import Collection
from .frame import Frame
\ No newline at end of file
# ============================================================================== # ==============================================================================
# Binaries and/or source for the following packages or projects # Binaries and/or source for the following packages or projects
# are presented under one or more of the following open source licenses: # are presented under one or more of the following open source licenses:
# collection.py The OpenLane-V2 Dataset Authors Apache License, Version 2.0 # collection.py The OpenLane-V2 Dataset Authors Apache License, Version 2.0
# #
...@@ -20,16 +20,17 @@ ...@@ -20,16 +20,17 @@
# limitations under the License. # limitations under the License.
# ============================================================================== # ==============================================================================
from .frame import Frame
from ..io import io from ..io import io
from .frame import Frame
class Collection: class Collection:
r""" r"""
A collection of frames. A collection of frames.
""" """
def __init__(self, data_root : str, meta_root : str, collection : str) -> None:
def __init__(self, data_root: str, meta_root: str, collection: str) -> None:
r""" r"""
Parameters Parameters
---------- ----------
...@@ -47,7 +48,7 @@ class Collection: ...@@ -47,7 +48,7 @@ class Collection:
self.frames = {k: Frame(data_root, v) for k, v in meta.items()} self.frames = {k: Frame(data_root, v) for k, v in meta.items()}
self.keys = list(self.frames.keys()) self.keys = list(self.frames.keys())
def get_frame_via_identifier(self, identifier : tuple) -> Frame: def get_frame_via_identifier(self, identifier: tuple) -> Frame:
r""" r"""
Returns a frame with the given identifier (split, segment_id, timestamp). Returns a frame with the given identifier (split, segment_id, timestamp).
...@@ -64,7 +65,7 @@ class Collection: ...@@ -64,7 +65,7 @@ class Collection:
""" """
return self.frames[identifier] return self.frames[identifier]
def get_frame_via_index(self, index : int) -> (tuple, Frame): def get_frame_via_index(self, index: int) -> (tuple, Frame):
r""" r"""
Returns a frame with the given index. Returns a frame with the given index.
......
# ============================================================================== # ==============================================================================
# Binaries and/or source for the following packages or projects # Binaries and/or source for the following packages or projects
# are presented under one or more of the following open source licenses: # are presented under one or more of the following open source licenses:
# frame.py The OpenLane-V2 Dataset Authors Apache License, Version 2.0 # frame.py The OpenLane-V2 Dataset Authors Apache License, Version 2.0
# #
...@@ -31,7 +31,8 @@ class Frame: ...@@ -31,7 +31,8 @@ class Frame:
A data structure containing meta data of a frame. A data structure containing meta data of a frame.
""" """
def __init__(self, root_path : str, meta : dict) -> None:
def __init__(self, root_path: str, meta: dict) -> None:
r""" r"""
Parameters Parameters
---------- ----------
...@@ -67,7 +68,7 @@ class Frame: ...@@ -67,7 +68,7 @@ class Frame:
""" """
return self.meta['pose'] return self.meta['pose']
def get_image_path(self, camera : str) -> str: def get_image_path(self, camera: str) -> str:
r""" r"""
Retuens the image path given a camera. Retuens the image path given a camera.
...@@ -83,7 +84,7 @@ class Frame: ...@@ -83,7 +84,7 @@ class Frame:
""" """
return f'{self.root_path}/{self.meta["sensor"][camera]["image_path"]}' return f'{self.root_path}/{self.meta["sensor"][camera]["image_path"]}'
def get_rgb_image(self, camera : str) -> np.ndarray: def get_rgb_image(self, camera: str) -> np.ndarray:
r""" r"""
Retuens the RGB image given a camera. Retuens the RGB image given a camera.
...@@ -100,7 +101,7 @@ class Frame: ...@@ -100,7 +101,7 @@ class Frame:
image_path = self.get_image_path(camera) image_path = self.get_image_path(camera)
return cv2.cvtColor(io.cv2_imread(image_path), cv2.COLOR_BGR2RGB) return cv2.cvtColor(io.cv2_imread(image_path), cv2.COLOR_BGR2RGB)
def get_intrinsic(self, camera : str) -> dict: def get_intrinsic(self, camera: str) -> dict:
r""" r"""
Retuens the intrinsic given a camera. Retuens the intrinsic given a camera.
...@@ -116,7 +117,7 @@ class Frame: ...@@ -116,7 +117,7 @@ class Frame:
""" """
return self.meta['sensor'][camera]['intrinsic'] return self.meta['sensor'][camera]['intrinsic']
def get_extrinsic(self, camera : str) -> dict: def get_extrinsic(self, camera: str) -> dict:
r""" r"""
Retuens the extrinsic given a camera. Retuens the extrinsic given a camera.
...@@ -193,7 +194,7 @@ class Frame: ...@@ -193,7 +194,7 @@ class Frame:
------- -------
list list
[#lane_centerline, #traffic_element]. [#lane_centerline, #traffic_element].
""" """
result = self.get_annotations() result = self.get_annotations()
return result['topology_lcte'] if result is not None else result return result['topology_lcte'] if result is not None else result
from .evaluate import evaluate
\ No newline at end of file
# ============================================================================== # ==============================================================================
# Binaries and/or source for the following packages or projects # Binaries and/or source for the following packages or projects
# are presented under one or more of the following open source licenses: # are presented under one or more of the following open source licenses:
# distance.py The OpenLane-V2 Dataset Authors Apache License, Version 2.0 # distance.py The OpenLane-V2 Dataset Authors Apache License, Version 2.0
# #
...@@ -25,7 +25,8 @@ from scipy.spatial.distance import cdist, euclidean ...@@ -25,7 +25,8 @@ from scipy.spatial.distance import cdist, euclidean
from similaritymeasures import frechet_dist from similaritymeasures import frechet_dist
def pairwise(xs: list, ys: list, distance_function: callable, mask: np.ndarray = None, relax: bool = False) -> np.ndarray: def pairwise(xs: list, ys: list, distance_function: callable, mask: np.ndarray = None,
relax: bool = False) -> np.ndarray:
r""" r"""
Calculate pairwise distance. Calculate pairwise distance.
...@@ -58,6 +59,7 @@ def pairwise(xs: list, ys: list, distance_function: callable, mask: np.ndarray = ...@@ -58,6 +59,7 @@ def pairwise(xs: list, ys: list, distance_function: callable, mask: np.ndarray =
result[i][j] = distance_function(x, y) * relaxation_factor result[i][j] = distance_function(x, y) * relaxation_factor
return result return result
def chamfer_distance(gt: np.ndarray, pred: np.ndarray) -> float: def chamfer_distance(gt: np.ndarray, pred: np.ndarray) -> float:
r""" r"""
Calculate Chamfer distance. Calculate Chamfer distance.
...@@ -118,6 +120,7 @@ def frechet_distance(gt: np.ndarray, pred: np.ndarray) -> float: ...@@ -118,6 +120,7 @@ def frechet_distance(gt: np.ndarray, pred: np.ndarray) -> float:
return frechet_dist(pred, gt, p=2) return frechet_dist(pred, gt, p=2)
def iou_distance(gt: np.ndarray, pred: np.ndarray) -> float: def iou_distance(gt: np.ndarray, pred: np.ndarray) -> float:
r""" r"""
Calculate IoU distance, Calculate IoU distance,
...@@ -144,6 +147,7 @@ def iou_distance(gt: np.ndarray, pred: np.ndarray) -> float: ...@@ -144,6 +147,7 @@ def iou_distance(gt: np.ndarray, pred: np.ndarray) -> float:
bymax = min(pred[1][1], gt[1][1]) bymax = min(pred[1][1], gt[1][1])
inter = max((bxmax - bxmin), 0) * max((bymax - bymin), 0) inter = max((bxmax - bxmin), 0) * max((bymax - bymin), 0)
union = (pred[1][0] - pred[0][0]) * (pred[1][1] - pred[0][1]) + (gt[1][0] - gt[0][0]) * (gt[1][1] - gt[0][1]) - inter union = (pred[1][0] - pred[0][0]) * (pred[1][1] - pred[0][1]) + (gt[1][0] - gt[0][0]) * (
gt[1][1] - gt[0][1]) - inter
return 1 - inter / union return 1 - inter / union
# ============================================================================== # ==============================================================================
# Binaries and/or source for the following packages or projects # Binaries and/or source for the following packages or projects
# are presented under one or more of the following open source licenses: # are presented under one or more of the following open source licenses:
# evaluate.py The OpenLane-V2 Dataset Authors Apache License, Version 2.0 # evaluate.py The OpenLane-V2 Dataset Authors Apache License, Version 2.0
# #
...@@ -23,12 +23,12 @@ ...@@ -23,12 +23,12 @@
import numpy as np import numpy as np
from tqdm import tqdm from tqdm import tqdm
from .f_score import f1
from .distance import pairwise, chamfer_distance, frechet_distance, iou_distance
from ..io import io from ..io import io
from ..preprocessing import check_results from ..preprocessing import check_results
from ..utils import TRAFFIC_ELEMENT_ATTRIBUTE from ..utils import TRAFFIC_ELEMENT_ATTRIBUTE
from .distance import (chamfer_distance, frechet_distance, iou_distance,
pairwise)
from .f_score import f1
THRESHOLDS_FRECHET = [1.0, 2.0, 3.0] THRESHOLDS_FRECHET = [1.0, 2.0, 3.0]
THRESHOLDS_IOU = [0.75] THRESHOLDS_IOU = [0.75]
...@@ -50,7 +50,7 @@ def _pr_curve(recalls, precisions): ...@@ -50,7 +50,7 @@ def _pr_curve(recalls, precisions):
------- -------
float float
average precision average precision
Notes Notes
----- -----
Adapted from https://github.com/Mrmoore98/VectorMapNet_code/blob/mian/plugin/datasets/evaluation/precision_recall/average_precision_gen.py#L12 Adapted from https://github.com/Mrmoore98/VectorMapNet_code/blob/mian/plugin/datasets/evaluation/precision_recall/average_precision_gen.py#L12
...@@ -71,6 +71,7 @@ def _pr_curve(recalls, precisions): ...@@ -71,6 +71,7 @@ def _pr_curve(recalls, precisions):
return ap[0] return ap[0]
def _tpfp(gts, preds, confidences, distance_matrix, distance_threshold): def _tpfp(gts, preds, confidences, distance_matrix, distance_threshold):
r""" r"""
Generate lists of tp and fp on given distance threshold. Generate lists of tp and fp on given distance threshold.
...@@ -129,9 +130,10 @@ def _tpfp(gts, preds, confidences, distance_matrix, distance_threshold): ...@@ -129,9 +130,10 @@ def _tpfp(gts, preds, confidences, distance_matrix, distance_threshold):
fp[i] = 1 fp[i] = 1
else: else:
fp[i] = 1 fp[i] = 1
return tp, fp, idx_match_gt return tp, fp, idx_match_gt
def _inject(num_gt, pred, tp, idx_match_gt, confidence, distance_threshold, object_type): def _inject(num_gt, pred, tp, idx_match_gt, confidence, distance_threshold, object_type):
r""" r"""
Inject tp matching into predictions. Inject tp matching into predictions.
...@@ -174,6 +176,7 @@ def _inject(num_gt, pred, tp, idx_match_gt, confidence, distance_threshold, obje ...@@ -174,6 +176,7 @@ def _inject(num_gt, pred, tp, idx_match_gt, confidence, distance_threshold, obje
pred[f'{object_type}_{distance_threshold}_confidence'] = confidence pred[f'{object_type}_{distance_threshold}_confidence'] = confidence
pred[f'{object_type}_{distance_threshold}_confidence_thresholds'] = confidence_thresholds pred[f'{object_type}_{distance_threshold}_confidence_thresholds'] = confidence_thresholds
def _AP(gts, preds, distance_matrixs, distance_threshold, object_type, filter, inject): def _AP(gts, preds, distance_matrixs, distance_threshold, object_type, filter, inject):
r""" r"""
Calculate AP on given distance threshold. Calculate AP on given distance threshold.
...@@ -213,10 +216,10 @@ def _AP(gts, preds, distance_matrixs, distance_threshold, object_type, filter, i ...@@ -213,10 +216,10 @@ def _AP(gts, preds, distance_matrixs, distance_threshold, object_type, filter, i
filtered_distance_matrix = filtered_distance_matrix[[filter(gt) for gt in gts[token][object_type]], :] filtered_distance_matrix = filtered_distance_matrix[[filter(gt) for gt in gts[token][object_type]], :]
filtered_distance_matrix = filtered_distance_matrix[:, [filter(pred) for pred in preds[token][object_type]]] filtered_distance_matrix = filtered_distance_matrix[:, [filter(pred) for pred in preds[token][object_type]]]
tp, fp, idx_match_gt = _tpfp( tp, fp, idx_match_gt = _tpfp(
gts=gt, gts=gt,
preds=pred, preds=pred,
confidences=confidence, confidences=confidence,
distance_matrix=filtered_distance_matrix, distance_matrix=filtered_distance_matrix,
distance_threshold=distance_threshold, distance_threshold=distance_threshold,
) )
tps.append(tp) tps.append(tp)
...@@ -249,6 +252,7 @@ def _AP(gts, preds, distance_matrixs, distance_threshold, object_type, filter, i ...@@ -249,6 +252,7 @@ def _AP(gts, preds, distance_matrixs, distance_threshold, object_type, filter, i
precisions = tps / np.maximum((tps + fps), eps) precisions = tps / np.maximum((tps + fps), eps)
return _pr_curve(recalls=recalls, precisions=precisions) return _pr_curve(recalls=recalls, precisions=precisions)
def _mAP_over_threshold(gts, preds, distance_matrixs, distance_thresholds, object_type, filter, inject): def _mAP_over_threshold(gts, preds, distance_matrixs, distance_thresholds, object_type, filter, inject):
r""" r"""
Calculate mAP over distance thresholds. Calculate mAP over distance thresholds.
...@@ -277,15 +281,16 @@ def _mAP_over_threshold(gts, preds, distance_matrixs, distance_thresholds, objec ...@@ -277,15 +281,16 @@ def _mAP_over_threshold(gts, preds, distance_matrixs, distance_thresholds, objec
""" """
return np.asarray([_AP( return np.asarray([_AP(
gts=gts, gts=gts,
preds=preds, preds=preds,
distance_matrixs=distance_matrixs, distance_matrixs=distance_matrixs,
distance_threshold=distance_threshold, distance_threshold=distance_threshold,
object_type=object_type, object_type=object_type,
filter=filter, filter=filter,
inject=inject, inject=inject,
) for distance_threshold in distance_thresholds]) ) for distance_threshold in distance_thresholds])
def _average_precision_per_vertex(gts, preds, confidences): def _average_precision_per_vertex(gts, preds, confidences):
r""" r"""
Calculate average precision for a vertex. Calculate average precision for a vertex.
...@@ -334,7 +339,8 @@ def _average_precision_per_vertex(gts, preds, confidences): ...@@ -334,7 +339,8 @@ def _average_precision_per_vertex(gts, preds, confidences):
precisions = tp / np.maximum((tp + fp), eps) precisions = tp / np.maximum((tp + fp), eps)
return np.dot(precisions, rel) / num_gts return np.dot(precisions, rel) / num_gts
def _AP_directerd(gts, preds): def _AP_directerd(gts, preds):
r""" r"""
Calculate average precision on the given adjacent matrixs, Calculate average precision on the given adjacent matrixs,
...@@ -375,6 +381,7 @@ def _AP_directerd(gts, preds): ...@@ -375,6 +381,7 @@ def _AP_directerd(gts, preds):
return acc return acc
def _AP_undirecterd(gts, preds): def _AP_undirecterd(gts, preds):
r""" r"""
Calculate average precision on the given adjacent matrixs, Calculate average precision on the given adjacent matrixs,
...@@ -403,7 +410,7 @@ def _AP_undirecterd(gts, preds): ...@@ -403,7 +410,7 @@ def _AP_undirecterd(gts, preds):
confidence = pred[pred > THRESHOLD_RELATIONSHIP_CONFIDENCE] confidence = pred[pred > THRESHOLD_RELATIONSHIP_CONFIDENCE]
pred = indices[pred > THRESHOLD_RELATIONSHIP_CONFIDENCE] pred = indices[pred > THRESHOLD_RELATIONSHIP_CONFIDENCE]
acc.append(_average_precision_per_vertex(gt, pred, confidence)) acc.append(_average_precision_per_vertex(gt, pred, confidence))
indices = np.arange(gts.shape[0]) indices = np.arange(gts.shape[0])
for gt, pred in zip(gts.T, preds.T): for gt, pred in zip(gts.T, preds.T):
gt = indices[gt.astype(bool)] gt = indices[gt.astype(bool)]
...@@ -413,6 +420,7 @@ def _AP_undirecterd(gts, preds): ...@@ -413,6 +420,7 @@ def _AP_undirecterd(gts, preds):
return acc return acc
def _mAP_topology_lclc(gts, preds, distance_thresholds): def _mAP_topology_lclc(gts, preds, distance_thresholds):
r""" r"""
Calculate mAP on topology among lane centerlines. Calculate mAP on topology among lane centerlines.
...@@ -441,7 +449,8 @@ def _mAP_topology_lclc(gts, preds, distance_thresholds): ...@@ -441,7 +449,8 @@ def _mAP_topology_lclc(gts, preds, distance_thresholds):
idx_match_gt = preds[token][f'lane_centerline_{distance_threshold}_idx_match_gt'] idx_match_gt = preds[token][f'lane_centerline_{distance_threshold}_idx_match_gt']
confidence = preds[token][f'lane_centerline_{distance_threshold}_confidence'] confidence = preds[token][f'lane_centerline_{distance_threshold}_confidence']
confidence_thresholds = preds[token][f'lane_centerline_{distance_threshold}_confidence_thresholds'] confidence_thresholds = preds[token][f'lane_centerline_{distance_threshold}_confidence_thresholds']
gt_pred = {m: i for i, (m, c) in enumerate(zip(idx_match_gt, confidence)) if c >= confidence_thresholds[r] and not np.isnan(m)} gt_pred = {m: i for i, (m, c) in enumerate(zip(idx_match_gt, confidence)) if
c >= confidence_thresholds[r] and not np.isnan(m)}
gts_topology_lclc = gts[token]['topology_lclc'] gts_topology_lclc = gts[token]['topology_lclc']
if 0 in gts_topology_lclc.shape: if 0 in gts_topology_lclc.shape:
...@@ -452,12 +461,14 @@ def _mAP_topology_lclc(gts, preds, distance_thresholds): ...@@ -452,12 +461,14 @@ def _mAP_topology_lclc(gts, preds, distance_thresholds):
for j in range(preds_topology_lclc.shape[1]): for j in range(preds_topology_lclc.shape[1]):
if i in gt_pred and j in gt_pred: if i in gt_pred and j in gt_pred:
preds_topology_lclc[i][j] = preds_topology_lclc_unmatched[gt_pred[i]][gt_pred[j]] preds_topology_lclc[i][j] = preds_topology_lclc_unmatched[gt_pred[i]][gt_pred[j]]
preds_topology_lclc[np.isnan(preds_topology_lclc)] = 1 - gts_topology_lclc[np.isnan(preds_topology_lclc)] preds_topology_lclc[np.isnan(preds_topology_lclc)] = 1 - gts_topology_lclc[
np.isnan(preds_topology_lclc)]
acc.append(_AP_directerd(gts=gts_topology_lclc, preds=preds_topology_lclc)) acc.append(_AP_directerd(gts=gts_topology_lclc, preds=preds_topology_lclc))
return np.hstack(acc).mean() return np.hstack(acc).mean()
def _mAP_topology_lcte(gts, preds, distance_thresholds): def _mAP_topology_lcte(gts, preds, distance_thresholds):
r""" r"""
Calculate mAP on topology between lane centerlines and traffic elements. Calculate mAP on topology between lane centerlines and traffic elements.
...@@ -484,17 +495,23 @@ def _mAP_topology_lcte(gts, preds, distance_thresholds): ...@@ -484,17 +495,23 @@ def _mAP_topology_lcte(gts, preds, distance_thresholds):
for token in gts.keys(): for token in gts.keys():
preds_topology_lcte_unmatched = preds[token]['topology_lcte'] preds_topology_lcte_unmatched = preds[token]['topology_lcte']
idx_match_gt_lane_centerline = preds[token][f'lane_centerline_{distance_threshold_lane_centerline}_idx_match_gt'] idx_match_gt_lane_centerline = preds[token][
confidence_lane_centerline = preds[token][f'lane_centerline_{distance_threshold_lane_centerline}_confidence'] f'lane_centerline_{distance_threshold_lane_centerline}_idx_match_gt']
confidence_thresholds_lane_centerline = preds[token][f'lane_centerline_{distance_threshold_lane_centerline}_confidence_thresholds'] confidence_lane_centerline = preds[token][
f'lane_centerline_{distance_threshold_lane_centerline}_confidence']
confidence_thresholds_lane_centerline = preds[token][
f'lane_centerline_{distance_threshold_lane_centerline}_confidence_thresholds']
gt_pred_lane_centerline = { gt_pred_lane_centerline = {
m: i for i, (m, c) in enumerate(zip(idx_match_gt_lane_centerline, confidence_lane_centerline)) \ m: i for i, (m, c) in enumerate(zip(idx_match_gt_lane_centerline, confidence_lane_centerline)) \
if c >= confidence_thresholds_lane_centerline[r] and not np.isnan(m) if c >= confidence_thresholds_lane_centerline[r] and not np.isnan(m)
} }
idx_match_gt_traffic_element = preds[token][f'traffic_element_{distance_threshold_traffic_element}_idx_match_gt'] idx_match_gt_traffic_element = preds[token][
confidence_traffic_element = preds[token][f'traffic_element_{distance_threshold_traffic_element}_confidence'] f'traffic_element_{distance_threshold_traffic_element}_idx_match_gt']
confidence_thresholds_traffic_element = preds[token][f'traffic_element_{distance_threshold_traffic_element}_confidence_thresholds'] confidence_traffic_element = preds[token][
f'traffic_element_{distance_threshold_traffic_element}_confidence']
confidence_thresholds_traffic_element = preds[token][
f'traffic_element_{distance_threshold_traffic_element}_confidence_thresholds']
gt_pred_traffic_element = { gt_pred_traffic_element = {
m: i for i, (m, c) in enumerate(zip(idx_match_gt_traffic_element, confidence_traffic_element)) \ m: i for i, (m, c) in enumerate(zip(idx_match_gt_traffic_element, confidence_traffic_element)) \
if c >= confidence_thresholds_traffic_element[r] and not np.isnan(m) if c >= confidence_thresholds_traffic_element[r] and not np.isnan(m)
...@@ -508,13 +525,16 @@ def _mAP_topology_lcte(gts, preds, distance_thresholds): ...@@ -508,13 +525,16 @@ def _mAP_topology_lcte(gts, preds, distance_thresholds):
for i in range(preds_topology_lcte.shape[0]): for i in range(preds_topology_lcte.shape[0]):
for j in range(preds_topology_lcte.shape[1]): for j in range(preds_topology_lcte.shape[1]):
if i in gt_pred_lane_centerline and j in gt_pred_traffic_element: if i in gt_pred_lane_centerline and j in gt_pred_traffic_element:
preds_topology_lcte[i][j] = preds_topology_lcte_unmatched[gt_pred_lane_centerline[i]][gt_pred_traffic_element[j]] preds_topology_lcte[i][j] = preds_topology_lcte_unmatched[gt_pred_lane_centerline[i]][
preds_topology_lcte[np.isnan(preds_topology_lcte)] = 1 - gts_topology_lcte[np.isnan(preds_topology_lcte)] gt_pred_traffic_element[j]]
preds_topology_lcte[np.isnan(preds_topology_lcte)] = 1 - gts_topology_lcte[
np.isnan(preds_topology_lcte)]
acc.append(_AP_undirecterd(gts=gts_topology_lcte, preds=preds_topology_lcte)) acc.append(_AP_undirecterd(gts=gts_topology_lcte, preds=preds_topology_lcte))
return np.hstack(acc).mean() return np.hstack(acc).mean()
def evaluate(ground_truth, predictions, verbose=True): def evaluate(ground_truth, predictions, verbose=True):
r""" r"""
Evaluate the road structure cognition task. Evaluate the road structure cognition task.
...@@ -536,7 +556,7 @@ def evaluate(ground_truth, predictions, verbose=True): ...@@ -536,7 +556,7 @@ def evaluate(ground_truth, predictions, verbose=True):
One of pred_path and pred_dict must be None, One of pred_path and pred_dict must be None,
these two arguments provide flexibility for formatting the results only. these two arguments provide flexibility for formatting the results only.
""" """
if isinstance(ground_truth, str): if isinstance(ground_truth, str):
ground_truth = io.pickle_load(ground_truth) ground_truth = io.pickle_load(ground_truth)
...@@ -546,7 +566,7 @@ def evaluate(ground_truth, predictions, verbose=True): ...@@ -546,7 +566,7 @@ def evaluate(ground_truth, predictions, verbose=True):
else: else:
if isinstance(predictions, str): if isinstance(predictions, str):
predictions = io.pickle_load(predictions) predictions = io.pickle_load(predictions)
check_results(predictions) # check results format check_results(predictions) # check results format
predictions = predictions['results'] predictions = predictions['results']
gts = {} gts = {}
...@@ -565,7 +585,7 @@ def evaluate(ground_truth, predictions, verbose=True): ...@@ -565,7 +585,7 @@ def evaluate(ground_truth, predictions, verbose=True):
assert set(gts.keys()) == set(preds.keys()), '#frame differs' assert set(gts.keys()) == set(preds.keys()), '#frame differs'
""" """
calculate distances between gts and preds calculate distances between gts and preds
""" """
distance_matrixs = { distance_matrixs = {
...@@ -574,7 +594,6 @@ def evaluate(ground_truth, predictions, verbose=True): ...@@ -574,7 +594,6 @@ def evaluate(ground_truth, predictions, verbose=True):
} }
for token in tqdm(gts.keys(), desc='calculating distances:', ncols=80, disable=not verbose): for token in tqdm(gts.keys(), desc='calculating distances:', ncols=80, disable=not verbose):
mask = pairwise( mask = pairwise(
[gt['points'] for gt in gts[token]['lane_centerline']], [gt['points'] for gt in gts[token]['lane_centerline']],
[pred['points'] for pred in preds[token]['lane_centerline']], [pred['points'] for pred in preds[token]['lane_centerline']],
...@@ -610,33 +629,33 @@ def evaluate(ground_truth, predictions, verbose=True): ...@@ -610,33 +629,33 @@ def evaluate(ground_truth, predictions, verbose=True):
""" """
metrics['OpenLane-V2 Score']['DET_l'] = _mAP_over_threshold( metrics['OpenLane-V2 Score']['DET_l'] = _mAP_over_threshold(
gts=gts, gts=gts,
preds=preds, preds=preds,
distance_matrixs=distance_matrixs['frechet'], distance_matrixs=distance_matrixs['frechet'],
distance_thresholds=THRESHOLDS_FRECHET, distance_thresholds=THRESHOLDS_FRECHET,
object_type='lane_centerline', object_type='lane_centerline',
filter=lambda _: True, filter=lambda _: True,
inject=True, # save tp for eval on graph inject=True, # save tp for eval on graph
).mean() ).mean()
metrics['OpenLane-V2 Score']['DET_t'] = np.hstack([_mAP_over_threshold( metrics['OpenLane-V2 Score']['DET_t'] = np.hstack([_mAP_over_threshold(
gts=gts, gts=gts,
preds=preds, preds=preds,
distance_matrixs=distance_matrixs['iou'], distance_matrixs=distance_matrixs['iou'],
distance_thresholds=THRESHOLDS_IOU, distance_thresholds=THRESHOLDS_IOU,
object_type='traffic_element', object_type='traffic_element',
filter=lambda x: x['attribute'] == idx, filter=lambda x: x['attribute'] == idx,
inject=False, inject=False,
) for idx in TRAFFIC_ELEMENT_ATTRIBUTE.values()]).mean() ) for idx in TRAFFIC_ELEMENT_ATTRIBUTE.values()]).mean()
_mAP_over_threshold( _mAP_over_threshold(
gts=gts, gts=gts,
preds=preds, preds=preds,
distance_matrixs=distance_matrixs['iou'], distance_matrixs=distance_matrixs['iou'],
distance_thresholds=THRESHOLDS_IOU, distance_thresholds=THRESHOLDS_IOU,
object_type='traffic_element', object_type='traffic_element',
filter=lambda _: True, filter=lambda _: True,
inject=True, # save tp for eval on graph inject=True, # save tp for eval on graph
) )
metrics['OpenLane-V2 Score']['TOP_ll'] = _mAP_topology_lclc(gts, preds, THRESHOLDS_FRECHET) metrics['OpenLane-V2 Score']['TOP_ll'] = _mAP_topology_lclc(gts, preds, THRESHOLDS_FRECHET)
metrics['OpenLane-V2 Score']['TOP_lt'] = _mAP_topology_lcte( metrics['OpenLane-V2 Score']['TOP_lt'] = _mAP_topology_lcte(
......
# ============================================================================== # ==============================================================================
# Binaries and/or source for the following packages or projects # Binaries and/or source for the following packages or projects
# are presented under one or more of the following open source licenses: # are presented under one or more of the following open source licenses:
# f_score.py The OpenLane-V2 Dataset Authors Apache License, Version 2.0 # f_score.py The OpenLane-V2 Dataset Authors Apache License, Version 2.0
# #
...@@ -24,7 +24,7 @@ ...@@ -24,7 +24,7 @@
# ============================================================================== # ==============================================================================
""" """
Description: This code is to evaluate 3D lane detection. The optimal matching between ground-truth set and predicted Description: This code is to evaluate 3D lane detection. The optimal matching between ground-truth set and predicted
set of lanes are sought via solving a min cost flow. set of lanes are sought via solving a min cost flow.
Evaluation metrics includes: Evaluation metrics includes:
F-scores F-scores
...@@ -34,10 +34,9 @@ Evaluation metrics includes: ...@@ -34,10 +34,9 @@ Evaluation metrics includes:
z error far (0 - 100 m) z error far (0 - 100 m)
""" """
import numpy as np import numpy as np
from scipy.interpolate import interp1d
from ortools.graph import pywrapgraph from ortools.graph import pywrapgraph
from scipy.interpolate import interp1d
def resample_laneline_in_x(input_lane, steps, out_vis=False): def resample_laneline_in_x(input_lane, steps, out_vis=False):
...@@ -51,16 +50,16 @@ def resample_laneline_in_x(input_lane, steps, out_vis=False): ...@@ -51,16 +50,16 @@ def resample_laneline_in_x(input_lane, steps, out_vis=False):
""" """
# at least two points are included # at least two points are included
assert(input_lane.shape[0] >= 2) assert (input_lane.shape[0] >= 2)
x_min = np.min(input_lane[:, 0])-5 x_min = np.min(input_lane[:, 0]) - 5
x_max = np.max(input_lane[:, 0])+5 x_max = np.max(input_lane[:, 0]) + 5
if input_lane.shape[1] < 3: if input_lane.shape[1] < 3:
input_lane = np.concatenate([input_lane, np.zeros([input_lane.shape[0], 1], dtype=np.float32)], axis=1) input_lane = np.concatenate([input_lane, np.zeros([input_lane.shape[0], 1], dtype=np.float32)], axis=1)
f_y = interp1d(input_lane[:, 0], input_lane[:, 1], fill_value="extrapolate") f_y = interp1d(input_lane[:, 0], input_lane[:, 1], fill_value='extrapolate')
f_z = interp1d(input_lane[:, 0], input_lane[:, 2], fill_value="extrapolate") f_z = interp1d(input_lane[:, 0], input_lane[:, 2], fill_value='extrapolate')
y_values = f_y(steps) y_values = f_y(steps)
z_values = f_z(steps) z_values = f_z(steps)
...@@ -70,6 +69,7 @@ def resample_laneline_in_x(input_lane, steps, out_vis=False): ...@@ -70,6 +69,7 @@ def resample_laneline_in_x(input_lane, steps, out_vis=False):
return y_values, z_values, output_visibility.astype(np.float32) + 1e-9 return y_values, z_values, output_visibility.astype(np.float32) + 1e-9
return y_values, z_values return y_values, z_values
def SolveMinCostFlow(adj_mat, cost_mat): def SolveMinCostFlow(adj_mat, cost_mat):
""" """
Solving an Assignment Problem with MinCostFlow" Solving an Assignment Problem with MinCostFlow"
...@@ -87,16 +87,20 @@ def SolveMinCostFlow(adj_mat, cost_mat): ...@@ -87,16 +87,20 @@ def SolveMinCostFlow(adj_mat, cost_mat):
cnt_nonzero_col = int(np.sum(np.sum(adj_mat, axis=0) > 0)) cnt_nonzero_col = int(np.sum(np.sum(adj_mat, axis=0) > 0))
# prepare directed graph for the flow # prepare directed graph for the flow
start_nodes = np.zeros(cnt_1, dtype=np.int32).tolist() +\ start_nodes = np.zeros(cnt_1, dtype=np.int32).tolist() + \
np.repeat(np.array(range(1, cnt_1+1)), cnt_2).tolist() + \ np.repeat(np.array(range(1, cnt_1 + 1)), cnt_2).tolist() + \
[i for i in range(cnt_1+1, cnt_1 + cnt_2 + 1)] [i for i in range(cnt_1 + 1, cnt_1 + cnt_2 + 1)]
end_nodes = [i for i in range(1, cnt_1+1)] + \ end_nodes = [i for i in range(1, cnt_1 + 1)] + \
np.repeat(np.array([i for i in range(cnt_1+1, cnt_1 + cnt_2 + 1)]).reshape([1, -1]), cnt_1, axis=0).flatten().tolist() + \ np.repeat(np.array([i for i in range(cnt_1 + 1, cnt_1 + cnt_2 + 1)]).reshape([1, -1]), cnt_1,
axis=0).flatten().tolist() + \
[cnt_1 + cnt_2 + 1 for i in range(cnt_2)] [cnt_1 + cnt_2 + 1 for i in range(cnt_2)]
capacities = np.ones(cnt_1, dtype=np.int32).tolist() + adj_mat.flatten().astype(np.int32).tolist() + np.ones(cnt_2, dtype=np.int32).tolist() capacities = np.ones(cnt_1, dtype=np.int32).tolist() + adj_mat.flatten().astype(np.int32).tolist() + np.ones(cnt_2,
costs = (np.zeros(cnt_1, dtype=np.int32).tolist() + cost_mat.flatten().astype(np.int32).tolist() + np.zeros(cnt_2, dtype=np.int32).tolist()) dtype=np.int32).tolist()
costs = (np.zeros(cnt_1, dtype=np.int32).tolist() + cost_mat.flatten().astype(np.int32).tolist() + np.zeros(cnt_2,
dtype=np.int32).tolist())
# Define an array of supplies at each node. # Define an array of supplies at each node.
supplies = [min(cnt_nonzero_row, cnt_nonzero_col)] + np.zeros(cnt_1 + cnt_2, dtype=np.int32).tolist() + [-min(cnt_nonzero_row, cnt_nonzero_col)] supplies = [min(cnt_nonzero_row, cnt_nonzero_col)] + np.zeros(cnt_1 + cnt_2, dtype=np.int32).tolist() + [
-min(cnt_nonzero_row, cnt_nonzero_col)]
# supplies = [min(cnt_1, cnt_2)] + np.zeros(cnt_1 + cnt_2, dtype=np.int).tolist() + [-min(cnt_1, cnt_2)] # supplies = [min(cnt_1, cnt_2)] + np.zeros(cnt_1 + cnt_2, dtype=np.int).tolist() + [-min(cnt_1, cnt_2)]
source = 0 source = 0
sink = cnt_1 + cnt_2 + 1 sink = cnt_1 + cnt_2 + 1
...@@ -118,7 +122,7 @@ def SolveMinCostFlow(adj_mat, cost_mat): ...@@ -118,7 +122,7 @@ def SolveMinCostFlow(adj_mat, cost_mat):
for arc in range(min_cost_flow.NumArcs()): for arc in range(min_cost_flow.NumArcs()):
# Can ignore arcs leading out of source or into sink. # Can ignore arcs leading out of source or into sink.
if min_cost_flow.Tail(arc)!=source and min_cost_flow.Head(arc)!=sink: if min_cost_flow.Tail(arc) != source and min_cost_flow.Head(arc) != sink:
# Arcs in the solution have a flow value of 1. Their start and end nodes # Arcs in the solution have a flow value of 1. Their start and end nodes
# give an assignment of worker to task. # give an assignment of worker to task.
...@@ -128,16 +132,17 @@ def SolveMinCostFlow(adj_mat, cost_mat): ...@@ -128,16 +132,17 @@ def SolveMinCostFlow(adj_mat, cost_mat):
# min_cost_flow.Tail(arc)-1, # min_cost_flow.Tail(arc)-1,
# min_cost_flow.Head(arc)-cnt_1-1, # min_cost_flow.Head(arc)-cnt_1-1,
# min_cost_flow.UnitCost(arc))) # min_cost_flow.UnitCost(arc)))
match_results.append([min_cost_flow.Tail(arc)-1, match_results.append([min_cost_flow.Tail(arc) - 1,
min_cost_flow.Head(arc)-cnt_1-1, min_cost_flow.Head(arc) - cnt_1 - 1,
min_cost_flow.UnitCost(arc)]) min_cost_flow.UnitCost(arc)])
else: else:
print('There was an issue with the min cost flow input.') print('There was an issue with the min cost flow input.')
return match_results return match_results
class LaneEval(object): class LaneEval(object):
def __init__(self): def __init__(self):
self.x_samples = np.linspace(-50, 50, num=100, endpoint=False) self.x_samples = np.linspace(-50, 50, num=100, endpoint=False)
self.dist_th = 1.5 self.dist_th = 1.5
self.ratio_th = 0.75 self.ratio_th = 0.75
...@@ -158,20 +163,21 @@ class LaneEval(object): ...@@ -158,20 +163,21 @@ class LaneEval(object):
""" """
r_lane, p_lane, c_lane = 0., 0., 0. r_lane, p_lane, c_lane = 0., 0., 0.
gt_lanes = [lane for lane in gt_lanes if lane.shape[0] > 1] gt_lanes = [lane for lane in gt_lanes if lane.shape[0] > 1]
# only consider those pred lanes overlapping with sampling range # only consider those pred lanes overlapping with sampling range
pred_category = [pred_category[k] for k, lane in enumerate(pred_lanes) pred_category = [pred_category[k] for k, lane in enumerate(pred_lanes)
if lane[0, 0] < self.x_samples[-1] and lane[-1, 0] > self.x_samples[0]] if lane[0, 0] < self.x_samples[-1] and lane[-1, 0] > self.x_samples[0]]
pred_lanes = [lane for lane in pred_lanes if lane[0, 0] < self.x_samples[-1] and lane[-1, 0] > self.x_samples[0]] pred_lanes = [lane for lane in pred_lanes if
lane[0, 0] < self.x_samples[-1] and lane[-1, 0] > self.x_samples[0]]
pred_category = [pred_category[k] for k, lane in enumerate(pred_lanes) if lane.shape[0] > 1] pred_category = [pred_category[k] for k, lane in enumerate(pred_lanes) if lane.shape[0] > 1]
pred_lanes = [lane for lane in pred_lanes if lane.shape[0] > 1] pred_lanes = [lane for lane in pred_lanes if lane.shape[0] > 1]
# only consider those gt lanes overlapping with sampling range # only consider those gt lanes overlapping with sampling range
gt_category = [gt_category[k] for k, lane in enumerate(gt_lanes) gt_category = [gt_category[k] for k, lane in enumerate(gt_lanes)
if lane[0, 0] < self.x_samples[-1] and lane[-1, 0] > self.x_samples[0]] if lane[0, 0] < self.x_samples[-1] and lane[-1, 0] > self.x_samples[0]]
gt_lanes = [lane for lane in gt_lanes if lane[0, 0] < self.x_samples[-1] and lane[-1, 0] > self.x_samples[0]] gt_lanes = [lane for lane in gt_lanes if lane[0, 0] < self.x_samples[-1] and lane[-1, 0] > self.x_samples[0]]
gt_category = [gt_category[k] for k, lane in enumerate(gt_lanes) if lane.shape[0] > 1] gt_category = [gt_category[k] for k, lane in enumerate(gt_lanes) if lane.shape[0] > 1]
...@@ -187,7 +193,8 @@ class LaneEval(object): ...@@ -187,7 +193,8 @@ class LaneEval(object):
for i in range(cnt_gt): for i in range(cnt_gt):
min_x = np.min(np.array(gt_lanes[i])[:, 0]) min_x = np.min(np.array(gt_lanes[i])[:, 0])
max_x = np.max(np.array(gt_lanes[i])[:, 0]) max_x = np.max(np.array(gt_lanes[i])[:, 0])
y_values, z_values, visibility_vec = resample_laneline_in_x(np.array(gt_lanes[i]), self.x_samples, out_vis=True) y_values, z_values, visibility_vec = resample_laneline_in_x(np.array(gt_lanes[i]), self.x_samples,
out_vis=True)
gt_lanes[i] = np.vstack([y_values, z_values]).T gt_lanes[i] = np.vstack([y_values, z_values]).T
gt_visibility_mat[i, :] = np.logical_and(self.x_samples >= min_x, self.x_samples <= max_x) gt_visibility_mat[i, :] = np.logical_and(self.x_samples >= min_x, self.x_samples <= max_x)
gt_visibility_mat[i, :] = np.logical_and(gt_visibility_mat[i, :], visibility_vec) gt_visibility_mat[i, :] = np.logical_and(gt_visibility_mat[i, :], visibility_vec)
...@@ -198,7 +205,8 @@ class LaneEval(object): ...@@ -198,7 +205,8 @@ class LaneEval(object):
# pred_lane = prune_3d_lane_by_range(np.array(pred_lanes[i]), self.x_min, self.x_max) # pred_lane = prune_3d_lane_by_range(np.array(pred_lanes[i]), self.x_min, self.x_max)
min_x = np.min(np.array(pred_lanes[i])[:, 0]) min_x = np.min(np.array(pred_lanes[i])[:, 0])
max_x = np.max(np.array(pred_lanes[i])[:, 0]) max_x = np.max(np.array(pred_lanes[i])[:, 0])
y_values, z_values, visibility_vec = resample_laneline_in_x(np.array(pred_lanes[i]), self.x_samples, out_vis=True) y_values, z_values, visibility_vec = resample_laneline_in_x(np.array(pred_lanes[i]), self.x_samples,
out_vis=True)
pred_lanes[i] = np.vstack([y_values, z_values]).T pred_lanes[i] = np.vstack([y_values, z_values]).T
pred_visibility_mat[i, :] = np.logical_and(self.x_samples >= min_x, self.x_samples <= max_x) pred_visibility_mat[i, :] = np.logical_and(self.x_samples >= min_x, self.x_samples <= max_x)
pred_visibility_mat[i, :] = np.logical_and(pred_visibility_mat[i, :], visibility_vec) pred_visibility_mat[i, :] = np.logical_and(pred_visibility_mat[i, :], visibility_vec)
...@@ -230,7 +238,7 @@ class LaneEval(object): ...@@ -230,7 +238,7 @@ class LaneEval(object):
both_visible_indices = np.logical_and(gt_visibility_mat[i, :] >= 0.5, pred_visibility_mat[j, :] >= 0.5) both_visible_indices = np.logical_and(gt_visibility_mat[i, :] >= 0.5, pred_visibility_mat[j, :] >= 0.5)
both_invisible_indices = np.logical_and(gt_visibility_mat[i, :] < 0.5, pred_visibility_mat[j, :] < 0.5) both_invisible_indices = np.logical_and(gt_visibility_mat[i, :] < 0.5, pred_visibility_mat[j, :] < 0.5)
other_indices = np.logical_not(np.logical_or(both_visible_indices, both_invisible_indices)) other_indices = np.logical_not(np.logical_or(both_visible_indices, both_invisible_indices))
euclidean_dist = np.sqrt(y_dist ** 2 + z_dist ** 2) euclidean_dist = np.sqrt(y_dist ** 2 + z_dist ** 2)
euclidean_dist[both_invisible_indices] = 0 euclidean_dist[both_invisible_indices] = 0
euclidean_dist[other_indices] = self.dist_th euclidean_dist[other_indices] = self.dist_th
...@@ -242,7 +250,7 @@ class LaneEval(object): ...@@ -242,7 +250,7 @@ class LaneEval(object):
# using num_match_mat as cost does not work? # using num_match_mat as cost does not work?
# make sure cost is not set to 0 when it's smaller than 1 # make sure cost is not set to 0 when it's smaller than 1
cost_ = np.sum(euclidean_dist) cost_ = np.sum(euclidean_dist)
if cost_<1 and cost_>0: if cost_ < 1 and cost_ > 0:
cost_ = 1 cost_ = 1
else: else:
cost_ = (cost_).astype(int) cost_ = (cost_).astype(int)
...@@ -272,8 +280,9 @@ class LaneEval(object): ...@@ -272,8 +280,9 @@ class LaneEval(object):
p_lane += 1 p_lane += 1
match_pred_ids.append(pred_i) match_pred_ids.append(pred_i)
if pred_category != []: if pred_category != []:
if pred_category[pred_i] == gt_category[gt_i] or (pred_category[pred_i]==20 and gt_category[gt_i]==21): if pred_category[pred_i] == gt_category[gt_i] or (
c_lane += 1 # category matched num pred_category[pred_i] == 20 and gt_category[gt_i] == 21):
c_lane += 1 # category matched num
return r_lane, p_lane, c_lane, cnt_gt, cnt_pred, match_num return r_lane, p_lane, c_lane, cnt_gt, cnt_pred, match_num
def bench_one_submit(self, gts, preds): def bench_one_submit(self, gts, preds):
...@@ -329,7 +338,7 @@ class LaneEval(object): ...@@ -329,7 +338,7 @@ class LaneEval(object):
# N to N matching of lanelines # N to N matching of lanelines
r_lane, p_lane, c_lane, cnt_gt, cnt_pred, match_num = self.bench(pred_lanes, r_lane, p_lane, c_lane, cnt_gt, cnt_pred, match_num = self.bench(pred_lanes,
pred_category, pred_category,
gt_lanes, gt_lanes,
gt_category, gt_category,
) )
...@@ -344,18 +353,18 @@ class LaneEval(object): ...@@ -344,18 +353,18 @@ class LaneEval(object):
laneline_z_error_close = np.array(laneline_z_error_close) laneline_z_error_close = np.array(laneline_z_error_close)
laneline_z_error_far = np.array(laneline_z_error_far) laneline_z_error_far = np.array(laneline_z_error_far)
if np.sum(laneline_stats[:, 3])!= 0: if np.sum(laneline_stats[:, 3]) != 0:
R_lane = np.sum(laneline_stats[:, 0]) / (np.sum(laneline_stats[:, 3])) R_lane = np.sum(laneline_stats[:, 0]) / (np.sum(laneline_stats[:, 3]))
else: else:
R_lane = np.sum(laneline_stats[:, 0]) / (np.sum(laneline_stats[:, 3]) + 1e-6) # recall = TP / (TP+FN) R_lane = np.sum(laneline_stats[:, 0]) / (np.sum(laneline_stats[:, 3]) + 1e-6) # recall = TP / (TP+FN)
if np.sum(laneline_stats[:, 4]) != 0: if np.sum(laneline_stats[:, 4]) != 0:
P_lane = np.sum(laneline_stats[:, 1]) / (np.sum(laneline_stats[:, 4])) P_lane = np.sum(laneline_stats[:, 1]) / (np.sum(laneline_stats[:, 4]))
else: else:
P_lane = np.sum(laneline_stats[:, 1]) / (np.sum(laneline_stats[:, 4]) + 1e-6) # precision = TP / (TP+FP) P_lane = np.sum(laneline_stats[:, 1]) / (np.sum(laneline_stats[:, 4]) + 1e-6) # precision = TP / (TP+FP)
if np.sum(laneline_stats[:, 5]) != 0: if np.sum(laneline_stats[:, 5]) != 0:
C_lane = np.sum(laneline_stats[:, 2]) / (np.sum(laneline_stats[:, 5])) C_lane = np.sum(laneline_stats[:, 2]) / (np.sum(laneline_stats[:, 5]))
else: else:
C_lane = np.sum(laneline_stats[:, 2]) / (np.sum(laneline_stats[:, 5]) + 1e-6) # category_accuracy C_lane = np.sum(laneline_stats[:, 2]) / (np.sum(laneline_stats[:, 5]) + 1e-6) # category_accuracy
if R_lane + P_lane != 0: if R_lane + P_lane != 0:
F_lane = 2 * R_lane * P_lane / (R_lane + P_lane) F_lane = 2 * R_lane * P_lane / (R_lane + P_lane)
else: else:
...@@ -365,4 +374,5 @@ class LaneEval(object): ...@@ -365,4 +374,5 @@ class LaneEval(object):
return output_stats[0] return output_stats[0]
f1 = LaneEval() f1 = LaneEval()
# ============================================================================== # ==============================================================================
# Binaries and/or source for the following packages or projects # Binaries and/or source for the following packages or projects
# are presented under one or more of the following open source licenses: # are presented under one or more of the following open source licenses:
# io.py The OpenLane-V2 Dataset Authors Apache License, Version 2.0 # io.py The OpenLane-V2 Dataset Authors Apache License, Version 2.0
# #
...@@ -20,10 +20,11 @@ ...@@ -20,10 +20,11 @@
# limitations under the License. # limitations under the License.
# ============================================================================== # ==============================================================================
import os
import cv2
import json import json
import os
import pickle import pickle
import cv2
import numpy as np import numpy as np
...@@ -31,12 +32,13 @@ class IO: ...@@ -31,12 +32,13 @@ class IO:
r""" r"""
Wrapping io in openlanev2, Wrapping io in openlanev2,
can be modified for different file systems. can be modified for different file systems.
""" """
def __init__(self) -> None: def __init__(self) -> None:
pass pass
def os_listdir(self, path : str) -> list: def os_listdir(self, path: str) -> list:
r""" r"""
Parameters Parameters
---------- ----------
...@@ -47,9 +49,9 @@ class IO: ...@@ -47,9 +49,9 @@ class IO:
list list
""" """
return os.listdir(path) return os.listdir(path)
def cv2_imread(self, path : str) -> np.ndarray: def cv2_imread(self, path: str) -> np.ndarray:
r""" r"""
Parameters Parameters
---------- ----------
...@@ -60,9 +62,9 @@ class IO: ...@@ -60,9 +62,9 @@ class IO:
np.ndarray np.ndarray
""" """
return cv2.imread(path) return cv2.imread(path)
def json_load(self, path : str) -> dict: def json_load(self, path: str) -> dict:
r""" r"""
Parameters Parameters
---------- ----------
...@@ -77,7 +79,7 @@ class IO: ...@@ -77,7 +79,7 @@ class IO:
result = json.load(f) result = json.load(f)
return result return result
def pickle_dump(self, path : str, obj : object) -> None: def pickle_dump(self, path: str, obj: object) -> None:
r""" r"""
Parameters Parameters
---------- ----------
...@@ -88,7 +90,7 @@ class IO: ...@@ -88,7 +90,7 @@ class IO:
with open(path, 'wb') as f: with open(path, 'wb') as f:
pickle.dump(obj, f) pickle.dump(obj, f)
def pickle_load(self, path : str) -> object: def pickle_load(self, path: str) -> object:
r""" r"""
Parameters Parameters
---------- ----------
...@@ -103,4 +105,5 @@ class IO: ...@@ -103,4 +105,5 @@ class IO:
result = pickle.load(f) result = pickle.load(f)
return result return result
io = IO() io = IO()
from .collect import collect
from .check import check_results
\ No newline at end of file
# ============================================================================== # ==============================================================================
# Binaries and/or source for the following packages or projects # Binaries and/or source for the following packages or projects
# are presented under one or more of the following open source licenses: # are presented under one or more of the following open source licenses:
# check.py The OpenLane-V2 Dataset Authors Apache License, Version 2.0 # check.py The OpenLane-V2 Dataset Authors Apache License, Version 2.0
# #
...@@ -20,12 +20,13 @@ ...@@ -20,12 +20,13 @@
# limitations under the License. # limitations under the License.
# ============================================================================== # ==============================================================================
from functools import reduce
import numpy as np import numpy as np
from iso3166 import countries from iso3166 import countries
from functools import reduce
def check_results(results : dict) -> None: def check_results(results: dict) -> None:
r""" r"""
Check format of results. Check format of results.
...@@ -93,35 +94,42 @@ def check_results(results : dict) -> None: ...@@ -93,35 +94,42 @@ def check_results(results : dict) -> None:
points = instance['points'] points = instance['points']
if not isinstance(points, np.ndarray): if not isinstance(points, np.ndarray):
raise Exception(f'Type of value in key [results/{token}/predictions/{key}/{instance["id"]}] should be np.ndarray') raise Exception(
f'Type of value in key [results/{token}/predictions/{key}/{instance["id"]}] should be np.ndarray')
points = np.array(points) points = np.array(points)
if key == 'lane_centerline' and not (points.ndim == 2 and points.shape[1] == 3): if key == 'lane_centerline' and not (points.ndim == 2 and points.shape[1] == 3):
raise Exception(f'Shape of points in instance [results/{token}/predictions/{key}/{instance["id"]}] should be (#points, 3) but not {points.shape}') raise Exception(
f'Shape of points in instance [results/{token}/predictions/{key}/{instance["id"]}] should be (#points, 3) but not {points.shape}')
if key == 'traffic_element' and not (points.ndim == 2 and points.shape == (2, 2)): if key == 'traffic_element' and not (points.ndim == 2 and points.shape == (2, 2)):
raise Exception(f'Shape of points in instance [results/{token}/predictions/{key}/{instance["id"]}] should be (2, 2) but not {points.shape}') raise Exception(
f'Shape of points in instance [results/{token}/predictions/{key}/{instance["id"]}] should be (2, 2) but not {points.shape}')
ids[key] = [instance['id'] for instance in predictions[key]] ids[key] = [instance['id'] for instance in predictions[key]]
ids_check = reduce(lambda x, y: x + y, ids.values(), []) ids_check = reduce(lambda x, y: x + y, ids.values(), [])
if len(set(ids_check)) != len(ids_check): if len(set(ids_check)) != len(ids_check):
raise Exception(f'IDs are not unique in [results/{token}/predictions]') raise Exception(f'IDs are not unique in [results/{token}/predictions]')
if 'topology_lclc' not in predictions: if 'topology_lclc' not in predictions:
raise Exception(f'Miss key [results/{token}/predictions/topology_lclc].') raise Exception(f'Miss key [results/{token}/predictions/topology_lclc].')
topology_lclc = predictions['topology_lclc'] topology_lclc = predictions['topology_lclc']
if not isinstance(topology_lclc, np.ndarray): if not isinstance(topology_lclc, np.ndarray):
raise Exception(f'Type of value in key [results/{token}/predictions/topology_lclc] should be np.ndarray') raise Exception(f'Type of value in key [results/{token}/predictions/topology_lclc] should be np.ndarray')
topology_lclc = np.array(topology_lclc) topology_lclc = np.array(topology_lclc)
if not (topology_lclc.ndim == 2 and topology_lclc.shape[0] == len(ids['lane_centerline']) and topology_lclc.shape[1] == len(ids['lane_centerline'])): if not (topology_lclc.ndim == 2 and topology_lclc.shape[0] == len(ids['lane_centerline']) and
raise Exception(f'Shape of adjacent matrix of [results/{token}/predictions/topology_lclc] should be (#lane_centerline, #lane_centerline) but not {topology_lclc.shape}') topology_lclc.shape[1] == len(ids['lane_centerline'])):
raise Exception(
f'Shape of adjacent matrix of [results/{token}/predictions/topology_lclc] should be (#lane_centerline, #lane_centerline) but not {topology_lclc.shape}')
if 'topology_lcte' not in predictions: if 'topology_lcte' not in predictions:
raise Exception(f'Miss key [results/{token}/predictions/topology_lcte].') raise Exception(f'Miss key [results/{token}/predictions/topology_lcte].')
topology_lcte = predictions['topology_lcte'] topology_lcte = predictions['topology_lcte']
if not isinstance(topology_lcte, np.ndarray): if not isinstance(topology_lcte, np.ndarray):
raise Exception(f'Type of value in key [results/{token}/predictions/topology_lcte] should be np.ndarray') raise Exception(f'Type of value in key [results/{token}/predictions/topology_lcte] should be np.ndarray')
topology_lcte = np.array(topology_lcte) topology_lcte = np.array(topology_lcte)
if not (topology_lcte.ndim == 2 and topology_lcte.shape[0] == len(ids['lane_centerline']) and topology_lcte.shape[1] == len(ids['traffic_element'])): if not (topology_lcte.ndim == 2 and topology_lcte.shape[0] == len(ids['lane_centerline']) and
raise Exception(f'Shape of adjacent matrix of [results/{token}/predictions/topology_lcte] should be (#lane_centerline, #traffic_element) but not {topology_lcte.shape}') topology_lcte.shape[1] == len(ids['traffic_element'])):
raise Exception(
f'Shape of adjacent matrix of [results/{token}/predictions/topology_lcte] should be (#lane_centerline, #traffic_element) but not {topology_lcte.shape}')
return valid return valid
# ============================================================================== # ==============================================================================
# Binaries and/or source for the following packages or projects # Binaries and/or source for the following packages or projects
# are presented under one or more of the following open source licenses: # are presented under one or more of the following open source licenses:
# collect.py The OpenLane-V2 Dataset Authors Apache License, Version 2.0 # collect.py The OpenLane-V2 Dataset Authors Apache License, Version 2.0
# #
...@@ -26,7 +26,7 @@ from tqdm import tqdm ...@@ -26,7 +26,7 @@ from tqdm import tqdm
from ..io import io from ..io import io
def collect(root_path : str, data_dict : dict, collection : str, point_interval : int = 1) -> None: def collect(root_path: str, data_dict: dict, collection: str, point_interval: int = 1) -> None:
r""" r"""
Load meta data of data in data_dict, Load meta data of data in data_dict,
and store in a .pkl with split as file name. and store in a .pkl with split as file name.
...@@ -43,14 +43,15 @@ def collect(root_path : str, data_dict : dict, collection : str, point_interval ...@@ -43,14 +43,15 @@ def collect(root_path : str, data_dict : dict, collection : str, point_interval
not subsampling as default. not subsampling as default.
""" """
data_list = [(split, segment_id, timestamp.split('.')[0]) \ data_list = [
(split, segment_id, timestamp.split('.')[0]) \
for split, segment_ids in data_dict.items() \ for split, segment_ids in data_dict.items() \
for segment_id, timestamps in segment_ids.items() \ for segment_id, timestamps in segment_ids.items() \
for timestamp in timestamps for timestamp in timestamps
] ]
meta = { meta = {
(split, segment_id, timestamp): io.json_load(f'{root_path}/{split}/{segment_id}/info/{timestamp}.json') \ (split, segment_id, timestamp): io.json_load(f'{root_path}/{split}/{segment_id}/info/{timestamp}.json') \
for split, segment_id, timestamp in tqdm(data_list, desc=f'collecting {collection}', ncols=80) for split, segment_id, timestamp in tqdm(data_list, desc=f'collecting {collection}', ncols=80)
} }
for identifier, frame in meta.items(): for identifier, frame in meta.items():
...@@ -64,10 +65,14 @@ def collect(root_path : str, data_dict : dict, collection : str, point_interval ...@@ -64,10 +65,14 @@ def collect(root_path : str, data_dict : dict, collection : str, point_interval
if 'annotation' not in frame: if 'annotation' not in frame:
continue continue
for i, lane_centerline in enumerate(frame['annotation']['lane_centerline']): for i, lane_centerline in enumerate(frame['annotation']['lane_centerline']):
meta[identifier]['annotation']['lane_centerline'][i]['points'] = np.array(lane_centerline['points'][::point_interval], dtype=np.float32) meta[identifier]['annotation']['lane_centerline'][i]['points'] = np.array(
lane_centerline['points'][::point_interval], dtype=np.float32)
for i, traffic_element in enumerate(frame['annotation']['traffic_element']): for i, traffic_element in enumerate(frame['annotation']['traffic_element']):
meta[identifier]['annotation']['traffic_element'][i]['points'] = np.array(traffic_element['points'], dtype=np.float32) meta[identifier]['annotation']['traffic_element'][i]['points'] = np.array(traffic_element['points'],
meta[identifier]['annotation']['topology_lclc'] = np.array(meta[identifier]['annotation']['topology_lclc'], dtype=np.int8) dtype=np.float32)
meta[identifier]['annotation']['topology_lcte'] = np.array(meta[identifier]['annotation']['topology_lcte'], dtype=np.int8) meta[identifier]['annotation']['topology_lclc'] = np.array(meta[identifier]['annotation']['topology_lclc'],
dtype=np.int8)
meta[identifier]['annotation']['topology_lcte'] = np.array(meta[identifier]['annotation']['topology_lcte'],
dtype=np.int8)
io.pickle_dump(f'{root_path}/{collection}.pkl', meta) io.pickle_dump(f'{root_path}/{collection}.pkl', meta)
# ============================================================================== # ==============================================================================
# Binaries and/or source for the following packages or projects # Binaries and/or source for the following packages or projects
# are presented under one or more of the following open source licenses: # are presented under one or more of the following open source licenses:
# utils.py The OpenLane-V2 Dataset Authors Apache License, Version 2.0 # utils.py The OpenLane-V2 Dataset Authors Apache License, Version 2.0
# #
...@@ -21,19 +21,19 @@ ...@@ -21,19 +21,19 @@
# ============================================================================== # ==============================================================================
TRAFFIC_ELEMENT_ATTRIBUTE = { TRAFFIC_ELEMENT_ATTRIBUTE = {
'unknown': 0, 'unknown': 0,
'red': 1, 'red': 1,
'green': 2, 'green': 2,
'yellow': 3, 'yellow': 3,
'go_straight': 4, 'go_straight': 4,
'turn_left': 5, 'turn_left': 5,
'turn_right': 6, 'turn_right': 6,
'no_left_turn': 7, 'no_left_turn': 7,
'no_right_turn': 8, 'no_right_turn': 8,
'u_turn': 9, 'u_turn': 9,
'no_u_turn': 10, 'no_u_turn': 10,
'slight_left': 11, 'slight_left': 11,
'slight_right': 12, 'slight_right': 12,
} }
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment