Upload folder using huggingface_hub
Browse filesThis view is limited to 50 files because it contains too many changes.
See raw diff
- .github/workflows/deploy.yaml +69 -0
- .github/workflows/develop.yaml +52 -0
- .github/workflows/docker.yaml +42 -0
- .gitignore +1 -0
- .readthedocs.yaml +8 -0
- README.md +69 -18
- demo/hf_demo.py +9 -9
- docker/.dockerignore +32 -0
- docker/Dockerfile +64 -0
- docker/requirements-locked.txt +5 -0
- docs/0_get_start/0_quick_start.md +1 -0
- docs/0_get_start/1_installations.md +4 -0
- docs/0_get_start/2_git.md +3 -0
- docs/0_get_start/3_pypi.md +3 -0
- docs/0_get_start/4_docker.md +3 -0
- docs/0_get_start/5_conda.md +3 -0
- docs/1_tutorials/0_train.md +5 -0
- docs/1_tutorials/1_validation.md +5 -0
- docs/2_model_zoo/0_object_detection.md +5 -0
- docs/2_model_zoo/1_segmentation.md +5 -0
- docs/2_model_zoo/2_classification.md +5 -0
- docs/3_custom/0_model.md +1 -0
- docs/3_custom/1_data_augment.md +1 -0
- docs/3_custom/2_loss.md +1 -0
- docs/3_custom/3_task.md +1 -0
- docs/4_deploy/1_deploy.md +3 -0
- docs/4_deploy/2_onnx.md +1 -0
- docs/4_deploy/3_tensorrt.md +1 -0
- docs/HOWTO.md +24 -0
- docs/Makefile +20 -0
- docs/conf.py +37 -0
- docs/index.rst +81 -0
- docs/make.bat +35 -0
- docs/requirements.txt +3 -0
- examples/notebook_inference.ipynb +0 -0
- examples/notebook_smallobject.ipynb +57 -20
- requirements-dev.txt +5 -0
- requirements.txt +1 -3
- tests/conftest.py +118 -0
- tests/test_model/test_module.py +0 -7
- tests/test_model/test_yolo.py +21 -1
- tests/test_tools/test_data_augmentation.py +71 -0
- tests/test_tools/test_data_loader.py +69 -0
- tests/test_tools/test_dataset_preparation.py +30 -0
- tests/test_tools/test_drawer.py +29 -0
- tests/test_tools/test_loss_functions.py +56 -0
- tests/test_tools/test_solver.py +80 -0
- tests/test_utils/test_bounding_box_utils.py +183 -0
- tests/test_utils/test_module_utils.py +73 -0
- yolo/__init__.py +3 -1
.github/workflows/deploy.yaml
ADDED
@@ -0,0 +1,69 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
name: Deploy Mode Validation & Inference
|
2 |
+
|
3 |
+
on:
|
4 |
+
push:
|
5 |
+
branches: [main]
|
6 |
+
pull_request:
|
7 |
+
branches: [main]
|
8 |
+
|
9 |
+
jobs:
|
10 |
+
deploy:
|
11 |
+
runs-on: ${{ matrix.operating-system }}
|
12 |
+
|
13 |
+
strategy:
|
14 |
+
matrix:
|
15 |
+
operating-system: [ubuntu-latest, macos-latest]
|
16 |
+
python-version: [3.8, '3.10', '3.12']
|
17 |
+
fail-fast: false
|
18 |
+
|
19 |
+
steps:
|
20 |
+
- name: Checkout repository
|
21 |
+
uses: actions/checkout@v2
|
22 |
+
|
23 |
+
- name: Set up Python
|
24 |
+
uses: actions/setup-python@v2
|
25 |
+
with:
|
26 |
+
python-version: ${{ matrix.python-version }}
|
27 |
+
|
28 |
+
- name: Cache pip dependencies
|
29 |
+
uses: actions/cache@v2
|
30 |
+
with:
|
31 |
+
path: ~/.cache/pip
|
32 |
+
key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}-${{ matrix.python-version }}
|
33 |
+
restore-keys: |
|
34 |
+
${{ runner.os }}-pip-${{ matrix.python-version }}
|
35 |
+
${{ runner.os }}-pip-
|
36 |
+
|
37 |
+
- name: Install dependencies
|
38 |
+
run: |
|
39 |
+
python -m pip install --upgrade pip
|
40 |
+
pip install -r requirements.txt
|
41 |
+
|
42 |
+
- name: Install YOLO package
|
43 |
+
run: pip install -e .
|
44 |
+
|
45 |
+
- name: Cache model weights
|
46 |
+
id: cache-weights
|
47 |
+
uses: actions/cache@v2
|
48 |
+
with:
|
49 |
+
path: weights
|
50 |
+
key: ${{ runner.os }}-weights
|
51 |
+
restore-keys: |
|
52 |
+
${{ runner.os }}-weights
|
53 |
+
|
54 |
+
- name: Run Validation
|
55 |
+
run: |
|
56 |
+
python yolo/lazy.py task=validation dataset=mock
|
57 |
+
python yolo/lazy.py task=validation dataset=mock model=v9-s
|
58 |
+
python yolo/lazy.py task=validation dataset=mock name=AnyNameYouWant
|
59 |
+
|
60 |
+
- name: Run Inference
|
61 |
+
run: |
|
62 |
+
python yolo/lazy.py task=inference
|
63 |
+
python yolo/lazy.py task=inference model=v7
|
64 |
+
python yolo/lazy.py task=inference +quite=True
|
65 |
+
python yolo/lazy.py task=inference name=AnyNameYouWant
|
66 |
+
python yolo/lazy.py task=inference image_size=\[480,640]
|
67 |
+
python yolo/lazy.py task=inference task.nms.min_confidence=0.1
|
68 |
+
python yolo/lazy.py task=inference task.fast_inference=deploy
|
69 |
+
python yolo/lazy.py task=inference task.data.source=tests/data/images/val
|
.github/workflows/develop.yaml
ADDED
@@ -0,0 +1,52 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
name: Developer Mode Build & Test
|
2 |
+
|
3 |
+
on:
|
4 |
+
push:
|
5 |
+
branches: [main]
|
6 |
+
pull_request:
|
7 |
+
branches: [main]
|
8 |
+
|
9 |
+
jobs:
|
10 |
+
build_and_test:
|
11 |
+
runs-on: ubuntu-latest
|
12 |
+
|
13 |
+
steps:
|
14 |
+
- name: Checkout repository
|
15 |
+
uses: actions/checkout@v3
|
16 |
+
|
17 |
+
- name: Set up Python 3.10
|
18 |
+
uses: actions/setup-python@v4
|
19 |
+
with:
|
20 |
+
python-version: '3.10'
|
21 |
+
|
22 |
+
- name: Cache pip dependencies
|
23 |
+
uses: actions/cache@v2
|
24 |
+
with:
|
25 |
+
path: ~/.cache/pip
|
26 |
+
key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
|
27 |
+
restore-keys: |
|
28 |
+
${{ runner.os }}-pip-
|
29 |
+
|
30 |
+
- name: Install dependencies
|
31 |
+
run: |
|
32 |
+
python -m pip install --upgrade pip
|
33 |
+
pip install -r requirements-dev.txt
|
34 |
+
|
35 |
+
- name: Install pre-commit
|
36 |
+
run: pip install pre-commit
|
37 |
+
|
38 |
+
- name: Cache pre-commit environment
|
39 |
+
uses: actions/cache@v2
|
40 |
+
with:
|
41 |
+
path: ~/.cache/pre-commit
|
42 |
+
key: ${{ runner.os }}-precommit-${{ hashFiles('**/.pre-commit-config.yaml') }}
|
43 |
+
restore-keys: |
|
44 |
+
${{ runner.os }}-precommit-
|
45 |
+
|
46 |
+
- name: Run pre-commit hooks
|
47 |
+
run: pre-commit run --all-files
|
48 |
+
|
49 |
+
- name: Test with pytest
|
50 |
+
run: |
|
51 |
+
pip install pytest pytest-cov
|
52 |
+
pytest --cov=yolo
|
.github/workflows/docker.yaml
ADDED
@@ -0,0 +1,42 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
name: Docker Image Deploy to DockerHub
|
2 |
+
|
3 |
+
on:
|
4 |
+
push:
|
5 |
+
branches: [main]
|
6 |
+
pull_request:
|
7 |
+
branches: [main]
|
8 |
+
|
9 |
+
jobs:
|
10 |
+
build:
|
11 |
+
runs-on: ubuntu-latest
|
12 |
+
|
13 |
+
steps:
|
14 |
+
- name: Checkout repository
|
15 |
+
uses: actions/checkout@v3
|
16 |
+
|
17 |
+
- name: Set up Docker Buildx
|
18 |
+
uses: docker/setup-buildx-action@v3
|
19 |
+
|
20 |
+
- name: Log in to Docker Hub
|
21 |
+
uses: docker/login-action@v2
|
22 |
+
with:
|
23 |
+
username: henrytsui000
|
24 |
+
password: ${{ secrets.DOCKER_PASSWORD }}
|
25 |
+
|
26 |
+
- name: Extract metadata (tags, labels) for Docker
|
27 |
+
id: meta
|
28 |
+
uses: docker/metadata-action@v4
|
29 |
+
with:
|
30 |
+
images: henrytsui000/yolo
|
31 |
+
|
32 |
+
- name: Build and push Docker image
|
33 |
+
uses: docker/build-push-action@v4
|
34 |
+
with:
|
35 |
+
context: .
|
36 |
+
file: docker/Dockerfile
|
37 |
+
push: true
|
38 |
+
tags: ${{ steps.meta.outputs.tags }}
|
39 |
+
labels: ${{ steps.meta.outputs.labels }}
|
40 |
+
|
41 |
+
- name: Image digest
|
42 |
+
run: echo ${{ steps.meta.outputs.digest }}
|
.gitignore
CHANGED
@@ -114,6 +114,7 @@ dmypy.json
|
|
114 |
runs
|
115 |
/data
|
116 |
/datasets
|
|
|
117 |
|
118 |
# Datasets and model checkpoints
|
119 |
*.pth
|
|
|
114 |
runs
|
115 |
/data
|
116 |
/datasets
|
117 |
+
*/data
|
118 |
|
119 |
# Datasets and model checkpoints
|
120 |
*.pth
|
.readthedocs.yaml
ADDED
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
version: 2
|
2 |
+
|
3 |
+
sphinx:
|
4 |
+
configuration: docs/conf.py
|
5 |
+
|
6 |
+
python:
|
7 |
+
install:
|
8 |
+
- requirements: docs/requirements.txt
|
README.md
CHANGED
@@ -6,22 +6,29 @@ sdk_version: 4.36.1
|
|
6 |
---
|
7 |
# YOLO: Official Implementation of YOLOv9, YOLOv7
|
8 |
|
|
|
9 |
![GitHub License](https://img.shields.io/github/license/WongKinYiu/YOLO)
|
10 |
![WIP](https://img.shields.io/badge/status-WIP-orange)
|
11 |
-
|
12 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
13 |
> This project is currently a Work In Progress and may undergo significant changes. It is not recommended for use in production environments until further notice. Please check back regularly for updates.
|
14 |
>
|
15 |
-
> Use of this code is at your own risk and discretion. It is advisable to consult with the project owner before deploying or integrating into any critical systems.
|
16 |
|
17 |
Welcome to the official implementation of YOLOv7 and YOLOv9. This repository will contains the complete codebase, pre-trained models, and detailed instructions for training and deploying YOLOv9.
|
18 |
|
19 |
## TL;DR
|
20 |
- This is the official YOLO model implementation with an MIT License.
|
21 |
-
- For quick deployment: you can
|
22 |
```shell
|
23 |
-
pip install git+
|
24 |
-
yolo task
|
25 |
```
|
26 |
|
27 |
## Introduction
|
@@ -29,7 +36,7 @@ yolo task=inference task.source=0 # source could be a single file, video, image
|
|
29 |
- [**YOLOv7**: Trainable Bag-of-Freebies Sets New State-of-the-Art for Real-Time Object Detectors](https://arxiv.org/abs/2207.02696)
|
30 |
|
31 |
## Installation
|
32 |
-
To get started
|
33 |
```shell
|
34 |
git clone git@github.com:WongKinYiu/YOLO.git
|
35 |
cd YOLO
|
@@ -66,12 +73,13 @@ pip install -r requirements.txt
|
|
66 |
These are simple examples. For more customization details, please refer to [Notebooks](examples) and lower-level modifications **[HOWTO](docs/HOWTO.md)**.
|
67 |
|
68 |
## Training
|
69 |
-
To train YOLO on your dataset:
|
70 |
|
71 |
-
1. Modify the configuration file `
|
72 |
2. Run the training script:
|
73 |
```shell
|
74 |
-
python yolo/lazy.py task=train
|
|
|
75 |
```
|
76 |
|
77 |
### Transfer Learning
|
@@ -81,27 +89,70 @@ python yolo/lazy.py task=train task.data.batch_size=8 model=v9-c dataset={datase
|
|
81 |
```
|
82 |
|
83 |
### Inference
|
84 |
-
To
|
85 |
```shell
|
86 |
-
python yolo/lazy.py
|
87 |
-
python yolo/lazy.py task=inference #
|
88 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
89 |
```
|
90 |
|
91 |
-
### Validation
|
92 |
-
To validate
|
93 |
```shell
|
94 |
-
|
|
|
95 |
```
|
96 |
|
97 |
## Contributing
|
98 |
-
Contributions to the
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
99 |
|
100 |
## Star History
|
101 |
[![Star History Chart](https://api.star-history.com/svg?repos=WongKinYiu/YOLO&type=Date)](https://star-history.com/#WongKinYiu/YOLO&Date)
|
102 |
|
103 |
## Citations
|
104 |
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
105 |
@misc{wang2024yolov9,
|
106 |
title={YOLOv9: Learning What You Want to Learn Using Programmable Gradient Information},
|
107 |
author={Chien-Yao Wang and I-Hau Yeh and Hong-Yuan Mark Liao},
|
|
|
6 |
---
|
7 |
# YOLO: Official Implementation of YOLOv9, YOLOv7
|
8 |
|
9 |
+
[![Documentation Status](https://readthedocs.org/projects/yolo-docs/badge/?version=latest)](https://yolo-docs.readthedocs.io/en/latest/?badge=latest)
|
10 |
![GitHub License](https://img.shields.io/github/license/WongKinYiu/YOLO)
|
11 |
![WIP](https://img.shields.io/badge/status-WIP-orange)
|
12 |
+
![](https://img.shields.io/github/actions/workflow/status/WongKinYiu/YOLO/deploy.yaml)
|
13 |
+
|
14 |
+
[![PWC](https://img.shields.io/endpoint.svg?url=https://paperswithcode.com/badge/yolov9-learning-what-you-want-to-learn-using/real-time-object-detection-on-coco)](https://paperswithcode.com/sota/real-time-object-detection-on-coco)
|
15 |
+
|
16 |
+
[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)]()
|
17 |
+
[![Hugging Face Spaces](https://img.shields.io/badge/%F0%9F%A4%97%20Hugging%20Face-Spaces-green)](https://huggingface.co/spaces/henry000/YOLO)
|
18 |
+
|
19 |
+
<!-- > [!IMPORTANT]
|
20 |
> This project is currently a Work In Progress and may undergo significant changes. It is not recommended for use in production environments until further notice. Please check back regularly for updates.
|
21 |
>
|
22 |
+
> Use of this code is at your own risk and discretion. It is advisable to consult with the project owner before deploying or integrating into any critical systems. -->
|
23 |
|
24 |
Welcome to the official implementation of YOLOv7 and YOLOv9. This repository will contains the complete codebase, pre-trained models, and detailed instructions for training and deploying YOLOv9.
|
25 |
|
26 |
## TL;DR
|
27 |
- This is the official YOLO model implementation with an MIT License.
|
28 |
+
- For quick deployment: you can directly install by pip+git:
|
29 |
```shell
|
30 |
+
pip install git+https://github.com/WongKinYiu/YOLO.git
|
31 |
+
yolo task.data.source=0 # source could be a single file, video, image folder, webcam ID
|
32 |
```
|
33 |
|
34 |
## Introduction
|
|
|
36 |
- [**YOLOv7**: Trainable Bag-of-Freebies Sets New State-of-the-Art for Real-Time Object Detectors](https://arxiv.org/abs/2207.02696)
|
37 |
|
38 |
## Installation
|
39 |
+
To get started using YOLOv9's developer mode, we recommand you clone this repository and install the required dependencies:
|
40 |
```shell
|
41 |
git clone git@github.com:WongKinYiu/YOLO.git
|
42 |
cd YOLO
|
|
|
73 |
These are simple examples. For more customization details, please refer to [Notebooks](examples) and lower-level modifications **[HOWTO](docs/HOWTO.md)**.
|
74 |
|
75 |
## Training
|
76 |
+
To train YOLO on your machine/dataset:
|
77 |
|
78 |
+
1. Modify the configuration file `yolo/config/dataset/**.yaml` to point to your dataset.
|
79 |
2. Run the training script:
|
80 |
```shell
|
81 |
+
python yolo/lazy.py task=train dataset=** use_wandb=True
|
82 |
+
python yolo/lazy.py task=train task.data.batch_size=8 model=v9-c weight=False # or more args
|
83 |
```
|
84 |
|
85 |
### Transfer Learning
|
|
|
89 |
```
|
90 |
|
91 |
### Inference
|
92 |
+
To use a model for object detection, use:
|
93 |
```shell
|
94 |
+
python yolo/lazy.py # if cloned from GitHub
|
95 |
+
python yolo/lazy.py task=inference \ # default is inference
|
96 |
+
name=AnyNameYouWant \ # AnyNameYouWant
|
97 |
+
device=cpu \ # hardware cuda, cpu, mps
|
98 |
+
model=v9-s \ # model version: v9-c, m, s
|
99 |
+
task.nms.min_confidence=0.1 \ # nms config
|
100 |
+
task.fast_inference=onnx \ # onnx, trt, deploy
|
101 |
+
task.data.source=data/toy/images/train \ # file, dir, webcam
|
102 |
+
+quite=True \ # Quite Output
|
103 |
+
yolo task.data.source={Any Source} # if pip installed
|
104 |
+
yolo task=inference task.data.source={Any}
|
105 |
```
|
106 |
|
107 |
+
### Validation
|
108 |
+
To validate model performance, or generate a json file in COCO format:
|
109 |
```shell
|
110 |
+
python yolo/lazy.py task=validation
|
111 |
+
python yolo/lazy.py task=validation dataset=toy
|
112 |
```
|
113 |
|
114 |
## Contributing
|
115 |
+
Contributions to the YOLO project are welcome! See [CONTRIBUTING](docs/CONTRIBUTING.md) for guidelines on how to contribute.
|
116 |
+
|
117 |
+
### TODO Diagrams
|
118 |
+
```mermaid
|
119 |
+
flowchart TB
|
120 |
+
subgraph Features
|
121 |
+
Taskv7-->Segmentation["#35 Segmentation"]
|
122 |
+
Taskv7-->Classification["#34 Classification"]
|
123 |
+
Taskv9-->Segmentation
|
124 |
+
Taskv9-->Classification
|
125 |
+
Trainv7
|
126 |
+
end
|
127 |
+
subgraph Model
|
128 |
+
MODELv7-->v7-X
|
129 |
+
MODELv7-->v7-E6
|
130 |
+
MODELv7-->v7-E6E
|
131 |
+
MODELv9-->v9-T
|
132 |
+
MODELv9-->v9-S
|
133 |
+
MODELv9-->v9-E
|
134 |
+
end
|
135 |
+
subgraph Bugs
|
136 |
+
Fix-->Fix1["#12 mAP > 1"]
|
137 |
+
Fix-->Fix2["v9 Gradient Bump"]
|
138 |
+
Reply-->Reply1["#39"]
|
139 |
+
Reply-->Reply2["#36"]
|
140 |
+
end
|
141 |
+
```
|
142 |
|
143 |
## Star History
|
144 |
[![Star History Chart](https://api.star-history.com/svg?repos=WongKinYiu/YOLO&type=Date)](https://star-history.com/#WongKinYiu/YOLO&Date)
|
145 |
|
146 |
## Citations
|
147 |
```
|
148 |
+
@misc{wang2022yolov7,
|
149 |
+
title={YOLOv7: Trainable bag-of-freebies sets new state-of-the-art for real-time object detectors},
|
150 |
+
author={Chien-Yao Wang and Alexey Bochkovskiy and Hong-Yuan Mark Liao},
|
151 |
+
year={2022},
|
152 |
+
eprint={2207.02696},
|
153 |
+
archivePrefix={arXiv},
|
154 |
+
primaryClass={id='cs.CV' full_name='Computer Vision and Pattern Recognition' is_active=True alt_name=None in_archive='cs' is_general=False description='Covers image processing, computer vision, pattern recognition, and scene understanding. Roughly includes material in ACM Subject Classes I.2.10, I.4, and I.5.'}
|
155 |
+
}
|
156 |
@misc{wang2024yolov9,
|
157 |
title={YOLOv9: Learning What You Want to Learn Using Programmable Gradient Information},
|
158 |
author={Chien-Yao Wang and I-Hau Yeh and Hong-Yuan Mark Liao},
|
demo/hf_demo.py
CHANGED
@@ -11,7 +11,7 @@ from yolo import (
|
|
11 |
AugmentationComposer,
|
12 |
NMSConfig,
|
13 |
PostProccess,
|
14 |
-
|
15 |
create_model,
|
16 |
draw_bboxes,
|
17 |
)
|
@@ -25,22 +25,22 @@ def load_model(model_name, device):
|
|
25 |
model_cfg.model.auxiliary = {}
|
26 |
model = create_model(model_cfg, True)
|
27 |
model.to(device).eval()
|
28 |
-
return model
|
29 |
|
30 |
|
31 |
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
32 |
-
model = load_model(DEFAULT_MODEL, device)
|
33 |
-
|
34 |
-
class_list = OmegaConf.load("yolo/config/
|
35 |
|
36 |
transform = AugmentationComposer([])
|
37 |
|
38 |
|
39 |
def predict(model_name, image, nms_confidence, nms_iou):
|
40 |
-
global DEFAULT_MODEL, model, device,
|
41 |
if model_name != DEFAULT_MODEL:
|
42 |
-
model = load_model(model_name, device)
|
43 |
-
|
44 |
DEFAULT_MODEL = model_name
|
45 |
|
46 |
image_tensor, _, rev_tensor = transform(image)
|
@@ -49,7 +49,7 @@ def predict(model_name, image, nms_confidence, nms_iou):
|
|
49 |
rev_tensor = rev_tensor.to(device)[None]
|
50 |
|
51 |
nms_config = NMSConfig(nms_confidence, nms_iou)
|
52 |
-
post_proccess = PostProccess(
|
53 |
|
54 |
with torch.no_grad():
|
55 |
predict = model(image_tensor)
|
|
|
11 |
AugmentationComposer,
|
12 |
NMSConfig,
|
13 |
PostProccess,
|
14 |
+
create_converter,
|
15 |
create_model,
|
16 |
draw_bboxes,
|
17 |
)
|
|
|
25 |
model_cfg.model.auxiliary = {}
|
26 |
model = create_model(model_cfg, True)
|
27 |
model.to(device).eval()
|
28 |
+
return model, model_cfg
|
29 |
|
30 |
|
31 |
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
32 |
+
model, model_cfg = load_model(DEFAULT_MODEL, device)
|
33 |
+
converter = create_converter(model_cfg.name, model, model_cfg.anchor, IMAGE_SIZE, device)
|
34 |
+
class_list = OmegaConf.load("yolo/config/dataset/coco.yaml").class_list
|
35 |
|
36 |
transform = AugmentationComposer([])
|
37 |
|
38 |
|
39 |
def predict(model_name, image, nms_confidence, nms_iou):
|
40 |
+
global DEFAULT_MODEL, model, device, converter, class_list, post_proccess
|
41 |
if model_name != DEFAULT_MODEL:
|
42 |
+
model, model_cfg = load_model(model_name, device)
|
43 |
+
converter = create_converter(model_cfg.name, model, model_cfg.anchor, IMAGE_SIZE, device)
|
44 |
DEFAULT_MODEL = model_name
|
45 |
|
46 |
image_tensor, _, rev_tensor = transform(image)
|
|
|
49 |
rev_tensor = rev_tensor.to(device)[None]
|
50 |
|
51 |
nms_config = NMSConfig(nms_confidence, nms_iou)
|
52 |
+
post_proccess = PostProccess(converter, nms_config)
|
53 |
|
54 |
with torch.no_grad():
|
55 |
predict = model(image_tensor)
|
docker/.dockerignore
ADDED
@@ -0,0 +1,32 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
.devcontainer
|
2 |
+
.vscode
|
3 |
+
.git
|
4 |
+
.github
|
5 |
+
.gitignore
|
6 |
+
.pre-commit-config.yaml
|
7 |
+
Dockerfile
|
8 |
+
LICENSE
|
9 |
+
CITATION.cff
|
10 |
+
README.md
|
11 |
+
CODE_OF_CONDUCT.md
|
12 |
+
CONTRIBUTING.md
|
13 |
+
SECURITY.md
|
14 |
+
|
15 |
+
data
|
16 |
+
demo
|
17 |
+
docs
|
18 |
+
examples
|
19 |
+
runs
|
20 |
+
tests
|
21 |
+
yolo
|
22 |
+
build
|
23 |
+
dist
|
24 |
+
|
25 |
+
*.pt
|
26 |
+
*.pth
|
27 |
+
*.onnx
|
28 |
+
*.hef
|
29 |
+
*.engine
|
30 |
+
*.profile
|
31 |
+
|
32 |
+
*.png
|
docker/Dockerfile
ADDED
@@ -0,0 +1,64 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
ARG BASE_IMG=nvidia/cuda:12.4.1-devel-ubuntu22.04
|
2 |
+
FROM ${BASE_IMG}
|
3 |
+
|
4 |
+
ARG USERNAME=user
|
5 |
+
ARG WORKDIR=/home/${USERNAME}/YOLO
|
6 |
+
|
7 |
+
SHELL ["/bin/bash", "-c"]
|
8 |
+
|
9 |
+
|
10 |
+
ENV CUDA_HOME=/usr/local/cuda
|
11 |
+
ENV PATH=${PATH}:${CUDA_HOME}/bin
|
12 |
+
ENV LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:${CUDA_HOME}/lib64
|
13 |
+
|
14 |
+
RUN apt-get update \
|
15 |
+
&& apt-get install -y \
|
16 |
+
sudo \
|
17 |
+
curl \
|
18 |
+
gcc \
|
19 |
+
git \
|
20 |
+
make \
|
21 |
+
wget \
|
22 |
+
zlib1g \
|
23 |
+
protobuf-compiler \
|
24 |
+
libgl1-mesa-dev \
|
25 |
+
graphviz \
|
26 |
+
python-is-python3 \
|
27 |
+
python3-pip \
|
28 |
+
&& apt clean \
|
29 |
+
&& rm -rf /var/lib/apt/lists/*
|
30 |
+
|
31 |
+
# Make user
|
32 |
+
RUN echo "root:root" | chpasswd \
|
33 |
+
&& useradd \
|
34 |
+
--create-home \
|
35 |
+
--home-dir /home/${USERNAME} \
|
36 |
+
--shell /bin/bash \
|
37 |
+
--user-group \
|
38 |
+
--groups adm,sudo \
|
39 |
+
${USERNAME} \
|
40 |
+
&& echo "${USERNAME}:${USERNAME}" | chpasswd \
|
41 |
+
&& cat /dev/null > /etc/sudoers.d/${USERNAME} \
|
42 |
+
&& echo "%${USERNAME} ALL=(ALL) NOPASSWD: ALL" >> \
|
43 |
+
/etc/sudoers.d/${USERNAME} \
|
44 |
+
&& mkdir -p ${WORKDIR} \
|
45 |
+
&& chown ${USERNAME}:${USERNAME} ${WORKDIR}
|
46 |
+
|
47 |
+
USER ${USERNAME}
|
48 |
+
WORKDIR ${WORKDIR}
|
49 |
+
|
50 |
+
COPY docker/requirements-locked.txt /app/requirements-locked.txt
|
51 |
+
COPY requirements.txt /app/requirements.txt
|
52 |
+
|
53 |
+
# Install any needed packages specified in requirements.txt
|
54 |
+
RUN pip install --no-cache-dir -r /app/requirements-locked.txt
|
55 |
+
RUN pip install --no-cache-dir -r /app/requirements.txt
|
56 |
+
|
57 |
+
|
58 |
+
RUN git clone https://github.com/WongKinYiu/YOLO.git .
|
59 |
+
|
60 |
+
# Ensure pip-installed packages are available in the PATH
|
61 |
+
RUN echo 'export PATH=${PATH}:${HOME}/.local/bin' >> ~/.bashrc
|
62 |
+
|
63 |
+
# Optional: Source .bashrc to apply changes in the current session
|
64 |
+
RUN source ~/.bashrc
|
docker/requirements-locked.txt
ADDED
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
pycocotools==2.0.7
|
2 |
+
torch==2.2.1
|
3 |
+
torchvision==0.17.1
|
4 |
+
setuptools>=60.0
|
5 |
+
numpy==1.23.5
|
docs/0_get_start/0_quick_start.md
ADDED
@@ -0,0 +1 @@
|
|
|
|
|
1 |
+
# Quick Start
|
docs/0_get_start/1_installations.md
ADDED
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Install YOLO
|
2 |
+
|
3 |
+
## GPU (Cuda / MPS)
|
4 |
+
## CPU only
|
docs/0_get_start/2_git.md
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
# Install via Git& GitHub
|
2 |
+
|
3 |
+
## [WIP]
|
docs/0_get_start/3_pypi.md
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
# Install via PyPi(pip install)
|
2 |
+
|
3 |
+
## [WIP]
|
docs/0_get_start/4_docker.md
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
# Install in Docker
|
2 |
+
|
3 |
+
## [WIP]
|
docs/0_get_start/5_conda.md
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
# Install in Conda env
|
2 |
+
|
3 |
+
## [WIP]
|
docs/1_tutorials/0_train.md
ADDED
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Train
|
2 |
+
|
3 |
+
## Train on COCO2017
|
4 |
+
|
5 |
+
## Train on Cusom Dataset
|
docs/1_tutorials/1_validation.md
ADDED
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Validation
|
2 |
+
|
3 |
+
## Validation on COCO2017
|
4 |
+
|
5 |
+
## Validation on Custom Dataset
|
docs/2_model_zoo/0_object_detection.md
ADDED
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Object Detection
|
2 |
+
|
3 |
+
## YOLOv7
|
4 |
+
|
5 |
+
## YOLOv9
|
docs/2_model_zoo/1_segmentation.md
ADDED
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Segmentations
|
2 |
+
|
3 |
+
## YOLOv7
|
4 |
+
|
5 |
+
## YOLOv9
|
docs/2_model_zoo/2_classification.md
ADDED
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Classification
|
2 |
+
|
3 |
+
## YOLOv7
|
4 |
+
|
5 |
+
## YOLOv9
|
docs/3_custom/0_model.md
ADDED
@@ -0,0 +1 @@
|
|
|
|
|
1 |
+
# Model
|
docs/3_custom/1_data_augment.md
ADDED
@@ -0,0 +1 @@
|
|
|
|
|
1 |
+
# Data Augment
|
docs/3_custom/2_loss.md
ADDED
@@ -0,0 +1 @@
|
|
|
|
|
1 |
+
# Loss
|
docs/3_custom/3_task.md
ADDED
@@ -0,0 +1 @@
|
|
|
|
|
1 |
+
# Task
|
docs/4_deploy/1_deploy.md
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
# Deploy YOLOv9
|
2 |
+
|
3 |
+
# Deploy YOLOv7
|
docs/4_deploy/2_onnx.md
ADDED
@@ -0,0 +1 @@
|
|
|
|
|
1 |
+
# Compile to ONNX
|
docs/4_deploy/3_tensorrt.md
ADDED
@@ -0,0 +1 @@
|
|
|
|
|
1 |
+
# Compile to TensorRT
|
docs/HOWTO.md
CHANGED
@@ -2,6 +2,30 @@
|
|
2 |
|
3 |
To facilitate easy customization of the YOLO model, we've structured the codebase to allow for changes through configuration files and minimal code adjustments. This guide will walk you through the steps to customize various components of the model including the architecture, blocks, data loaders, and loss functions.
|
4 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
5 |
## Custom Model Architecture
|
6 |
|
7 |
You can change the model architecture simply by modifying the YAML configuration file. Here's how:
|
|
|
2 |
|
3 |
To facilitate easy customization of the YOLO model, we've structured the codebase to allow for changes through configuration files and minimal code adjustments. This guide will walk you through the steps to customize various components of the model including the architecture, blocks, data loaders, and loss functions.
|
4 |
|
5 |
+
## Examples
|
6 |
+
|
7 |
+
```shell
|
8 |
+
# Train
|
9 |
+
python yolo/lazy.py task=train dataset=dev use_wandb=True
|
10 |
+
|
11 |
+
# Validate
|
12 |
+
python yolo/lazy.py task=validation
|
13 |
+
python yolo/lazy.py task=validation model=v9-s
|
14 |
+
python yolo/lazy.py task=validation dataset=toy
|
15 |
+
python yolo/lazy.py task=validation dataset=toy name=validation
|
16 |
+
|
17 |
+
# Inference
|
18 |
+
python yolo/lazy.py task=inference
|
19 |
+
python yolo/lazy.py task=inference device=cpu
|
20 |
+
python yolo/lazy.py task=inference +quite=True
|
21 |
+
python yolo/lazy.py task=inference name=AnyNameYouWant
|
22 |
+
python yolo/lazy.py task=inference image_size=\[480,640]
|
23 |
+
python yolo/lazy.py task=inference task.nms.min_confidence=0.1
|
24 |
+
python yolo/lazy.py task=inference task.fast_inference=deploy
|
25 |
+
python yolo/lazy.py task=inference task.fast_inference=onnx device=cpu
|
26 |
+
python yolo/lazy.py task=inference task.data.source=data/toy/images/train
|
27 |
+
```
|
28 |
+
|
29 |
## Custom Model Architecture
|
30 |
|
31 |
You can change the model architecture simply by modifying the YAML configuration file. Here's how:
|
docs/Makefile
ADDED
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Minimal makefile for Sphinx documentation
|
2 |
+
#
|
3 |
+
|
4 |
+
# You can set these variables from the command line, and also
|
5 |
+
# from the environment for the first two.
|
6 |
+
SPHINXOPTS ?=
|
7 |
+
SPHINXBUILD ?= sphinx-build
|
8 |
+
SOURCEDIR = .
|
9 |
+
BUILDDIR = _build
|
10 |
+
|
11 |
+
# Put it first so that "make" without argument is like "make help".
|
12 |
+
help:
|
13 |
+
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
14 |
+
|
15 |
+
.PHONY: help Makefile
|
16 |
+
|
17 |
+
# Catch-all target: route all unknown targets to Sphinx using the new
|
18 |
+
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
|
19 |
+
%: Makefile
|
20 |
+
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
docs/conf.py
ADDED
@@ -0,0 +1,37 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Configuration file for the Sphinx documentation builder.
|
2 |
+
#
|
3 |
+
# For the full list of built-in configuration values, see the documentation:
|
4 |
+
# https://www.sphinx-doc.org/en/master/usage/configuration.html
|
5 |
+
|
6 |
+
# -- Project information -----------------------------------------------------
|
7 |
+
# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
|
8 |
+
|
9 |
+
project = "YOLO-docs"
|
10 |
+
copyright = "2024, Kin-Yiu, Wong and Hao-Tang, Tsui"
|
11 |
+
author = "Kin-Yiu, Wong and Hao-Tang, Tsui"
|
12 |
+
|
13 |
+
# -- General configuration ---------------------------------------------------
|
14 |
+
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
|
15 |
+
|
16 |
+
extensions = [
|
17 |
+
"sphinx.ext.autodoc",
|
18 |
+
"sphinx_rtd_theme",
|
19 |
+
"myst_parser",
|
20 |
+
]
|
21 |
+
|
22 |
+
myst_enable_extensions = [
|
23 |
+
"dollarmath",
|
24 |
+
"amsmath",
|
25 |
+
"deflist",
|
26 |
+
]
|
27 |
+
html_theme = "sphinx_rtd_theme"
|
28 |
+
|
29 |
+
|
30 |
+
templates_path = ["_templates"]
|
31 |
+
exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]
|
32 |
+
|
33 |
+
|
34 |
+
# -- Options for HTML output -------------------------------------------------
|
35 |
+
# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output
|
36 |
+
|
37 |
+
html_static_path = ["_static"]
|
docs/index.rst
ADDED
@@ -0,0 +1,81 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
YOLO documentation
|
2 |
+
=======================
|
3 |
+
|
4 |
+
Introduction
|
5 |
+
------------
|
6 |
+
|
7 |
+
YOLO (You Only Look Once) is a state-of-the-art, real-time object detection system that is designed for both efficiency and accuracy. This documentation provides comprehensive guidance on how to set up, configure, and effectively use YOLO for object detection tasks.
|
8 |
+
|
9 |
+
**Note: This project and some sections of this documentation are currently a work in progress.**
|
10 |
+
|
11 |
+
Project Features
|
12 |
+
----------------
|
13 |
+
|
14 |
+
- **Real-time Processing**: YOLO can process images in real-time with high accuracy, making it suitable for applications that require instant detection.
|
15 |
+
- **Multitasking Capabilities**: Our enhanced version of YOLO supports multitasking, allowing it to handle multiple object detection tasks simultaneously.
|
16 |
+
- **Open Source**: YOLO is open source, released under the MIT License, encouraging a broad community of developers to contribute and build upon the existing framework.
|
17 |
+
|
18 |
+
Documentation Contents
|
19 |
+
----------------------
|
20 |
+
|
21 |
+
Explore our documentation:
|
22 |
+
|
23 |
+
|
24 |
+
.. toctree::
|
25 |
+
:maxdepth: 2
|
26 |
+
:caption: Get Started
|
27 |
+
|
28 |
+
0_get_start/0_quick_start.md
|
29 |
+
0_get_start/1_installations.md
|
30 |
+
0_get_start/2_git.md
|
31 |
+
0_get_start/3_pypi.md
|
32 |
+
0_get_start/4_docker.md
|
33 |
+
0_get_start/5_conda.md
|
34 |
+
|
35 |
+
.. toctree::
|
36 |
+
:maxdepth: 2
|
37 |
+
:caption: Tutorials
|
38 |
+
|
39 |
+
1_tutorials/0_train.md
|
40 |
+
1_tutorials/1_validation.md
|
41 |
+
|
42 |
+
|
43 |
+
.. toctree::
|
44 |
+
:maxdepth: 2
|
45 |
+
:caption: Model Zoo
|
46 |
+
|
47 |
+
2_model_zoo/0_object_detection.md
|
48 |
+
2_model_zoo/1_segmentation.md
|
49 |
+
2_model_zoo/2_classification.md
|
50 |
+
|
51 |
+
.. toctree::
|
52 |
+
:maxdepth: 2
|
53 |
+
:caption: Custom YOLO
|
54 |
+
|
55 |
+
3_custom/0_model.md
|
56 |
+
3_custom/1_data_augment.md
|
57 |
+
3_custom/2_loss.md
|
58 |
+
3_custom/3_task.md
|
59 |
+
|
60 |
+
|
61 |
+
.. toctree::
|
62 |
+
:maxdepth: 2
|
63 |
+
:caption: Deploy
|
64 |
+
|
65 |
+
4_deploy/1_deploy.md
|
66 |
+
4_deploy/2_onnx.md
|
67 |
+
4_deploy/3_tensorrt.md
|
68 |
+
|
69 |
+
|
70 |
+
.. toctree::
|
71 |
+
:maxdepth: 2
|
72 |
+
:caption: Deploy
|
73 |
+
|
74 |
+
4_deploy/1_deploy.md
|
75 |
+
4_deploy/2_onnx.md
|
76 |
+
4_deploy/3_tensorrt.md
|
77 |
+
|
78 |
+
License
|
79 |
+
-------
|
80 |
+
|
81 |
+
YOLO is provided under the MIT License, which allows extensive freedom for reuse and distribution. See the LICENSE file for full license text.
|
docs/make.bat
ADDED
@@ -0,0 +1,35 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
@ECHO OFF
|
2 |
+
|
3 |
+
pushd %~dp0
|
4 |
+
|
5 |
+
REM Command file for Sphinx documentation
|
6 |
+
|
7 |
+
if "%SPHINXBUILD%" == "" (
|
8 |
+
set SPHINXBUILD=sphinx-build
|
9 |
+
)
|
10 |
+
set SOURCEDIR=.
|
11 |
+
set BUILDDIR=_build
|
12 |
+
|
13 |
+
%SPHINXBUILD% >NUL 2>NUL
|
14 |
+
if errorlevel 9009 (
|
15 |
+
echo.
|
16 |
+
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
|
17 |
+
echo.installed, then set the SPHINXBUILD environment variable to point
|
18 |
+
echo.to the full path of the 'sphinx-build' executable. Alternatively you
|
19 |
+
echo.may add the Sphinx directory to PATH.
|
20 |
+
echo.
|
21 |
+
echo.If you don't have Sphinx installed, grab it from
|
22 |
+
echo.https://www.sphinx-doc.org/
|
23 |
+
exit /b 1
|
24 |
+
)
|
25 |
+
|
26 |
+
if "%1" == "" goto help
|
27 |
+
|
28 |
+
%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
|
29 |
+
goto end
|
30 |
+
|
31 |
+
:help
|
32 |
+
%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
|
33 |
+
|
34 |
+
:end
|
35 |
+
popd
|
docs/requirements.txt
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
sphinx
|
2 |
+
sphinx_rtd_theme
|
3 |
+
myst-parser
|
examples/notebook_inference.ipynb
CHANGED
The diff for this file is too large to render.
See raw diff
|
|
examples/notebook_smallobject.ipynb
CHANGED
@@ -2,7 +2,7 @@
|
|
2 |
"cells": [
|
3 |
{
|
4 |
"cell_type": "code",
|
5 |
-
"execution_count":
|
6 |
"metadata": {},
|
7 |
"outputs": [],
|
8 |
"source": [
|
@@ -12,7 +12,7 @@
|
|
12 |
},
|
13 |
{
|
14 |
"cell_type": "code",
|
15 |
-
"execution_count":
|
16 |
"metadata": {},
|
17 |
"outputs": [],
|
18 |
"source": [
|
@@ -22,7 +22,6 @@
|
|
22 |
"import torch\n",
|
23 |
"from hydra import compose, initialize\n",
|
24 |
"from PIL import Image \n",
|
25 |
-
"from einops import rearrange\n",
|
26 |
"\n",
|
27 |
"# Ensure that the necessary repository is cloned and installed. You may need to run: \n",
|
28 |
"# git clone git@github.com:WongKinYiu/YOLO.git\n",
|
@@ -30,13 +29,23 @@
|
|
30 |
"# pip install .\n",
|
31 |
"project_root = Path().resolve().parent\n",
|
32 |
"sys.path.append(str(project_root))\n",
|
33 |
-
"
|
34 |
-
"from yolo import
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
35 |
]
|
36 |
},
|
37 |
{
|
38 |
"cell_type": "code",
|
39 |
-
"execution_count":
|
40 |
"metadata": {},
|
41 |
"outputs": [],
|
42 |
"source": [
|
@@ -55,32 +64,49 @@
|
|
55 |
},
|
56 |
{
|
57 |
"cell_type": "code",
|
58 |
-
"execution_count":
|
59 |
"metadata": {},
|
60 |
-
"outputs": [
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
61 |
"source": [
|
62 |
"with initialize(config_path=CONFIG_PATH, version_base=None, job_name=\"notebook_job\"):\n",
|
63 |
" cfg: Config = compose(config_name=CONFIG_NAME, overrides=[\"task=inference\", f\"task.data.source={IMAGE_PATH}\", f\"model={MODEL}\"])\n",
|
64 |
" model = create_model(cfg.model, class_num=CLASS_NUM).to(device)\n",
|
65 |
" transform = AugmentationComposer([], cfg.image_size)\n",
|
66 |
-
"
|
|
|
|
|
67 |
]
|
68 |
},
|
69 |
{
|
70 |
"cell_type": "code",
|
71 |
-
"execution_count":
|
72 |
"metadata": {},
|
73 |
"outputs": [],
|
74 |
"source": [
|
75 |
"pil_image = Image.open(IMAGE_PATH)\n",
|
76 |
"image, bbox, rev_tensor = transform(pil_image)\n",
|
77 |
"image = image.to(device)[None]\n",
|
78 |
-
"rev_tensor = rev_tensor.to(device)"
|
79 |
]
|
80 |
},
|
81 |
{
|
82 |
"cell_type": "code",
|
83 |
-
"execution_count":
|
84 |
"metadata": {},
|
85 |
"outputs": [],
|
86 |
"source": [
|
@@ -104,9 +130,21 @@
|
|
104 |
},
|
105 |
{
|
106 |
"cell_type": "code",
|
107 |
-
"execution_count":
|
108 |
"metadata": {},
|
109 |
-
"outputs": [
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
110 |
"source": [
|
111 |
"with torch.no_grad():\n",
|
112 |
" total_image, total_shift = slide_image(image)\n",
|
@@ -114,7 +152,9 @@
|
|
114 |
" pred_class, _, pred_bbox = vec2box(predict[\"Main\"])\n",
|
115 |
"pred_bbox[1:] = (pred_bbox[1: ] + total_shift[:, None]) / SLIDE\n",
|
116 |
"pred_bbox = pred_bbox.view(1, -1, 4)\n",
|
117 |
-
"pred_class = pred_class.view(1, -1, 80)"
|
|
|
|
|
118 |
]
|
119 |
},
|
120 |
{
|
@@ -123,7 +163,7 @@
|
|
123 |
"metadata": {},
|
124 |
"outputs": [],
|
125 |
"source": [
|
126 |
-
"
|
127 |
]
|
128 |
},
|
129 |
{
|
@@ -131,10 +171,7 @@
|
|
131 |
"execution_count": null,
|
132 |
"metadata": {},
|
133 |
"outputs": [],
|
134 |
-
"source": [
|
135 |
-
"predict_box = bbox_nms(pred_class, pred_bbox, NMSConfig(0.5, 0.5))\n",
|
136 |
-
"draw_bboxes(pil_image, predict_box, idx2label=cfg.class_list)"
|
137 |
-
]
|
138 |
}
|
139 |
],
|
140 |
"metadata": {
|
|
|
2 |
"cells": [
|
3 |
{
|
4 |
"cell_type": "code",
|
5 |
+
"execution_count": 1,
|
6 |
"metadata": {},
|
7 |
"outputs": [],
|
8 |
"source": [
|
|
|
12 |
},
|
13 |
{
|
14 |
"cell_type": "code",
|
15 |
+
"execution_count": 2,
|
16 |
"metadata": {},
|
17 |
"outputs": [],
|
18 |
"source": [
|
|
|
22 |
"import torch\n",
|
23 |
"from hydra import compose, initialize\n",
|
24 |
"from PIL import Image \n",
|
|
|
25 |
"\n",
|
26 |
"# Ensure that the necessary repository is cloned and installed. You may need to run: \n",
|
27 |
"# git clone git@github.com:WongKinYiu/YOLO.git\n",
|
|
|
29 |
"# pip install .\n",
|
30 |
"project_root = Path().resolve().parent\n",
|
31 |
"sys.path.append(str(project_root))\n",
|
32 |
+
"\n",
|
33 |
+
"from yolo import (\n",
|
34 |
+
" AugmentationComposer, \n",
|
35 |
+
" Config, \n",
|
36 |
+
" NMSConfig, \n",
|
37 |
+
" PostProccess,\n",
|
38 |
+
" bbox_nms, \n",
|
39 |
+
" create_model, \n",
|
40 |
+
" create_converter, \n",
|
41 |
+
" custom_logger, \n",
|
42 |
+
" draw_bboxes, \n",
|
43 |
+
")"
|
44 |
]
|
45 |
},
|
46 |
{
|
47 |
"cell_type": "code",
|
48 |
+
"execution_count": 3,
|
49 |
"metadata": {},
|
50 |
"outputs": [],
|
51 |
"source": [
|
|
|
64 |
},
|
65 |
{
|
66 |
"cell_type": "code",
|
67 |
+
"execution_count": 4,
|
68 |
"metadata": {},
|
69 |
+
"outputs": [
|
70 |
+
{
|
71 |
+
"name": "stderr",
|
72 |
+
"output_type": "stream",
|
73 |
+
"text": [
|
74 |
+
"\u001b[38;2;0;51;133m[07/31 12:22:22]\u001b[0m \u001b[1m INFO \u001b[0m| \u001b[1m🚜 Building YOLO\u001b[0m\n",
|
75 |
+
"\u001b[38;2;0;51;133m[07/31 12:22:22]\u001b[0m \u001b[1m INFO \u001b[0m| \u001b[1m 🏗️ Building backbone\u001b[0m\n",
|
76 |
+
"\u001b[38;2;0;51;133m[07/31 12:22:22]\u001b[0m \u001b[1m INFO \u001b[0m| \u001b[1m 🏗️ Building neck\u001b[0m\n",
|
77 |
+
"\u001b[38;2;0;51;133m[07/31 12:22:22]\u001b[0m \u001b[1m INFO \u001b[0m| \u001b[1m 🏗️ Building head\u001b[0m\n",
|
78 |
+
"\u001b[38;2;0;51;133m[07/31 12:22:22]\u001b[0m \u001b[1m INFO \u001b[0m| \u001b[1m 🏗️ Building detection\u001b[0m\n",
|
79 |
+
"\u001b[38;2;0;51;133m[07/31 12:22:22]\u001b[0m \u001b[1m INFO \u001b[0m| \u001b[1m 🏗️ Building auxiliary\u001b[0m\n",
|
80 |
+
"\u001b[38;2;0;51;133m[07/31 12:22:22]\u001b[0m \u001b[1m INFO \u001b[0m| \u001b[1m✅ Success load model & weight\u001b[0m\n",
|
81 |
+
"\u001b[38;2;0;51;133m[07/31 12:22:22]\u001b[0m \u001b[1m INFO \u001b[0m| \u001b[1m🈶 Found stride of model [8, 16, 32]\u001b[0m\n"
|
82 |
+
]
|
83 |
+
}
|
84 |
+
],
|
85 |
"source": [
|
86 |
"with initialize(config_path=CONFIG_PATH, version_base=None, job_name=\"notebook_job\"):\n",
|
87 |
" cfg: Config = compose(config_name=CONFIG_NAME, overrides=[\"task=inference\", f\"task.data.source={IMAGE_PATH}\", f\"model={MODEL}\"])\n",
|
88 |
" model = create_model(cfg.model, class_num=CLASS_NUM).to(device)\n",
|
89 |
" transform = AugmentationComposer([], cfg.image_size)\n",
|
90 |
+
" converter = create_converter(cfg.model.name, model, cfg.model.anchor, cfg.image_size, device)\n",
|
91 |
+
" post_proccess = PostProccess(converter, NMSConfig(0.5, 0.9))\n",
|
92 |
+
" "
|
93 |
]
|
94 |
},
|
95 |
{
|
96 |
"cell_type": "code",
|
97 |
+
"execution_count": 5,
|
98 |
"metadata": {},
|
99 |
"outputs": [],
|
100 |
"source": [
|
101 |
"pil_image = Image.open(IMAGE_PATH)\n",
|
102 |
"image, bbox, rev_tensor = transform(pil_image)\n",
|
103 |
"image = image.to(device)[None]\n",
|
104 |
+
"rev_tensor = rev_tensor.to(device)[None]"
|
105 |
]
|
106 |
},
|
107 |
{
|
108 |
"cell_type": "code",
|
109 |
+
"execution_count": 6,
|
110 |
"metadata": {},
|
111 |
"outputs": [],
|
112 |
"source": [
|
|
|
130 |
},
|
131 |
{
|
132 |
"cell_type": "code",
|
133 |
+
"execution_count": 7,
|
134 |
"metadata": {},
|
135 |
+
"outputs": [
|
136 |
+
{
|
137 |
+
"ename": "NameError",
|
138 |
+
"evalue": "name 'vec2box' is not defined",
|
139 |
+
"output_type": "error",
|
140 |
+
"traceback": [
|
141 |
+
"\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
|
142 |
+
"\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)",
|
143 |
+
"Cell \u001b[0;32mIn[7], line 4\u001b[0m\n\u001b[1;32m 2\u001b[0m total_image, total_shift \u001b[38;5;241m=\u001b[39m slide_image(image)\n\u001b[1;32m 3\u001b[0m predict \u001b[38;5;241m=\u001b[39m model(total_image)\n\u001b[0;32m----> 4\u001b[0m pred_class, _, pred_bbox \u001b[38;5;241m=\u001b[39m \u001b[43mvec2box\u001b[49m(predict[\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mMain\u001b[39m\u001b[38;5;124m\"\u001b[39m])\n\u001b[1;32m 5\u001b[0m pred_bbox[\u001b[38;5;241m1\u001b[39m:] \u001b[38;5;241m=\u001b[39m (pred_bbox[\u001b[38;5;241m1\u001b[39m: ] \u001b[38;5;241m+\u001b[39m total_shift[:, \u001b[38;5;28;01mNone\u001b[39;00m]) \u001b[38;5;241m/\u001b[39m SLIDE\n\u001b[1;32m 6\u001b[0m pred_bbox \u001b[38;5;241m=\u001b[39m pred_bbox\u001b[38;5;241m.\u001b[39mview(\u001b[38;5;241m1\u001b[39m, \u001b[38;5;241m-\u001b[39m\u001b[38;5;241m1\u001b[39m, \u001b[38;5;241m4\u001b[39m)\n",
|
144 |
+
"\u001b[0;31mNameError\u001b[0m: name 'vec2box' is not defined"
|
145 |
+
]
|
146 |
+
}
|
147 |
+
],
|
148 |
"source": [
|
149 |
"with torch.no_grad():\n",
|
150 |
" total_image, total_shift = slide_image(image)\n",
|
|
|
152 |
" pred_class, _, pred_bbox = vec2box(predict[\"Main\"])\n",
|
153 |
"pred_bbox[1:] = (pred_bbox[1: ] + total_shift[:, None]) / SLIDE\n",
|
154 |
"pred_bbox = pred_bbox.view(1, -1, 4)\n",
|
155 |
+
"pred_class = pred_class.view(1, -1, 80)\n",
|
156 |
+
"pred_bbox = (pred_bbox - rev_tensor[:, None, 1:]) / rev_tensor[:, 0:1, None]\n",
|
157 |
+
"predict_box = bbox_nms(pred_class, pred_bbox, NMSConfig(0.3, 0.5))\n"
|
158 |
]
|
159 |
},
|
160 |
{
|
|
|
163 |
"metadata": {},
|
164 |
"outputs": [],
|
165 |
"source": [
|
166 |
+
"draw_bboxes(pil_image, predict_box, idx2label=cfg.class_list)"
|
167 |
]
|
168 |
},
|
169 |
{
|
|
|
171 |
"execution_count": null,
|
172 |
"metadata": {},
|
173 |
"outputs": [],
|
174 |
+
"source": []
|
|
|
|
|
|
|
175 |
}
|
176 |
],
|
177 |
"metadata": {
|
requirements-dev.txt
ADDED
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
-r requirements.txt
|
2 |
+
pytest
|
3 |
+
pytest-cov
|
4 |
+
pre-commit
|
5 |
+
pycocotools
|
requirements.txt
CHANGED
@@ -5,11 +5,9 @@ loguru
|
|
5 |
numpy
|
6 |
opencv-python
|
7 |
Pillow
|
8 |
-
|
9 |
-
pyyaml
|
10 |
requests
|
11 |
rich
|
12 |
torch
|
13 |
torchvision
|
14 |
-
tqdm
|
15 |
wandb
|
|
|
5 |
numpy
|
6 |
opencv-python
|
7 |
Pillow
|
8 |
+
pycocotools
|
|
|
9 |
requests
|
10 |
rich
|
11 |
torch
|
12 |
torchvision
|
|
|
13 |
wandb
|
tests/conftest.py
ADDED
@@ -0,0 +1,118 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import sys
|
2 |
+
from pathlib import Path
|
3 |
+
|
4 |
+
import pytest
|
5 |
+
import torch
|
6 |
+
from hydra import compose, initialize
|
7 |
+
|
8 |
+
project_root = Path(__file__).resolve().parent.parent
|
9 |
+
sys.path.append(str(project_root))
|
10 |
+
|
11 |
+
from yolo import Anc2Box, Config, Vec2Box, create_converter, create_model
|
12 |
+
from yolo.model.yolo import YOLO
|
13 |
+
from yolo.tools.data_loader import StreamDataLoader, YoloDataLoader
|
14 |
+
from yolo.tools.dataset_preparation import prepare_dataset
|
15 |
+
from yolo.utils.logging_utils import ProgressLogger, set_seed
|
16 |
+
|
17 |
+
|
18 |
+
def pytest_configure(config):
|
19 |
+
config.addinivalue_line("markers", "requires_cuda: mark test to run only if CUDA is available")
|
20 |
+
|
21 |
+
|
22 |
+
def get_cfg(overrides=[]) -> Config:
|
23 |
+
config_path = "../yolo/config"
|
24 |
+
with initialize(config_path=config_path, version_base=None):
|
25 |
+
cfg: Config = compose(config_name="config", overrides=overrides)
|
26 |
+
set_seed(cfg.lucky_number)
|
27 |
+
return cfg
|
28 |
+
|
29 |
+
|
30 |
+
@pytest.fixture(scope="session")
|
31 |
+
def train_cfg() -> Config:
|
32 |
+
return get_cfg(overrides=["task=train", "dataset=mock"])
|
33 |
+
|
34 |
+
|
35 |
+
@pytest.fixture(scope="session")
|
36 |
+
def validation_cfg():
|
37 |
+
return get_cfg(overrides=["task=validation", "dataset=mock"])
|
38 |
+
|
39 |
+
|
40 |
+
@pytest.fixture(scope="session")
|
41 |
+
def inference_cfg():
|
42 |
+
return get_cfg(overrides=["task=inference"])
|
43 |
+
|
44 |
+
|
45 |
+
@pytest.fixture(scope="session")
|
46 |
+
def inference_v7_cfg():
|
47 |
+
return get_cfg(overrides=["task=inference", "model=v7"])
|
48 |
+
|
49 |
+
|
50 |
+
@pytest.fixture(scope="session")
|
51 |
+
def device():
|
52 |
+
return torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
53 |
+
|
54 |
+
|
55 |
+
@pytest.fixture(scope="session")
|
56 |
+
def train_progress_logger(train_cfg: Config):
|
57 |
+
progress_logger = ProgressLogger(train_cfg, exp_name=train_cfg.name)
|
58 |
+
return progress_logger
|
59 |
+
|
60 |
+
|
61 |
+
@pytest.fixture(scope="session")
|
62 |
+
def validation_progress_logger(validation_cfg: Config):
|
63 |
+
progress_logger = ProgressLogger(validation_cfg, exp_name=validation_cfg.name)
|
64 |
+
return progress_logger
|
65 |
+
|
66 |
+
|
67 |
+
@pytest.fixture(scope="session")
|
68 |
+
def model(train_cfg: Config, device) -> YOLO:
|
69 |
+
model = create_model(train_cfg.model)
|
70 |
+
return model.to(device)
|
71 |
+
|
72 |
+
|
73 |
+
@pytest.fixture(scope="session")
|
74 |
+
def model_v7(inference_v7_cfg: Config, device) -> YOLO:
|
75 |
+
model = create_model(inference_v7_cfg.model)
|
76 |
+
return model.to(device)
|
77 |
+
|
78 |
+
|
79 |
+
@pytest.fixture(scope="session")
|
80 |
+
def vec2box(train_cfg: Config, model: YOLO, device) -> Vec2Box:
|
81 |
+
vec2box = create_converter(train_cfg.model.name, model, train_cfg.model.anchor, train_cfg.image_size, device)
|
82 |
+
return vec2box
|
83 |
+
|
84 |
+
|
85 |
+
@pytest.fixture(scope="session")
|
86 |
+
def anc2box(inference_v7_cfg: Config, model: YOLO, device) -> Anc2Box:
|
87 |
+
anc2box = create_converter(
|
88 |
+
inference_v7_cfg.model.name, model, inference_v7_cfg.model.anchor, inference_v7_cfg.image_size, device
|
89 |
+
)
|
90 |
+
return anc2box
|
91 |
+
|
92 |
+
|
93 |
+
@pytest.fixture(scope="session")
|
94 |
+
def train_dataloader(train_cfg: Config):
|
95 |
+
prepare_dataset(train_cfg.dataset, task="train")
|
96 |
+
return YoloDataLoader(train_cfg.task.data, train_cfg.dataset, train_cfg.task.task)
|
97 |
+
|
98 |
+
|
99 |
+
@pytest.fixture(scope="session")
|
100 |
+
def validation_dataloader(validation_cfg: Config):
|
101 |
+
prepare_dataset(validation_cfg.dataset, task="val")
|
102 |
+
return YoloDataLoader(validation_cfg.task.data, validation_cfg.dataset, validation_cfg.task.task)
|
103 |
+
|
104 |
+
|
105 |
+
@pytest.fixture(scope="session")
|
106 |
+
def file_stream_data_loader(inference_cfg: Config):
|
107 |
+
return StreamDataLoader(inference_cfg.task.data)
|
108 |
+
|
109 |
+
|
110 |
+
@pytest.fixture(scope="session")
|
111 |
+
def file_stream_data_loader_v7(inference_v7_cfg: Config):
|
112 |
+
return StreamDataLoader(inference_v7_cfg.task.data)
|
113 |
+
|
114 |
+
|
115 |
+
@pytest.fixture(scope="session")
|
116 |
+
def directory_stream_data_loader(inference_cfg: Config):
|
117 |
+
inference_cfg.task.data.source = "tests/data/images/train"
|
118 |
+
return StreamDataLoader(inference_cfg.task.data)
|
tests/test_model/test_module.py
CHANGED
@@ -43,13 +43,6 @@ def test_adown():
|
|
43 |
assert out.shape == (1, OUT_CHANNELS, 32, 32)
|
44 |
|
45 |
|
46 |
-
def test_adown():
|
47 |
-
adown = ADown(IN_CHANNELS, OUT_CHANNELS)
|
48 |
-
x = torch.randn(1, IN_CHANNELS, 64, 64)
|
49 |
-
out = adown(x)
|
50 |
-
assert out.shape == (1, OUT_CHANNELS, 32, 32)
|
51 |
-
|
52 |
-
|
53 |
def test_cblinear():
|
54 |
cblinear = CBLinear(IN_CHANNELS, [5, 5])
|
55 |
x = torch.randn(1, IN_CHANNELS, 64, 64)
|
|
|
43 |
assert out.shape == (1, OUT_CHANNELS, 32, 32)
|
44 |
|
45 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
46 |
def test_cblinear():
|
47 |
cblinear = CBLinear(IN_CHANNELS, [5, 5])
|
48 |
x = torch.randn(1, IN_CHANNELS, 64, 64)
|
tests/test_model/test_yolo.py
CHANGED
@@ -16,7 +16,7 @@ config_path = "../../yolo/config"
|
|
16 |
config_name = "config"
|
17 |
|
18 |
|
19 |
-
def
|
20 |
with initialize(config_path=config_path, version_base=None):
|
21 |
cfg: Config = compose(config_name=config_name)
|
22 |
|
@@ -26,6 +26,26 @@ def test_build_model():
|
|
26 |
assert len(model.model) == 39
|
27 |
|
28 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
29 |
@pytest.fixture
|
30 |
def cfg() -> Config:
|
31 |
with initialize(config_path="../../yolo/config", version_base=None):
|
|
|
16 |
config_name = "config"
|
17 |
|
18 |
|
19 |
+
def test_build_model_v9c():
|
20 |
with initialize(config_path=config_path, version_base=None):
|
21 |
cfg: Config = compose(config_name=config_name)
|
22 |
|
|
|
26 |
assert len(model.model) == 39
|
27 |
|
28 |
|
29 |
+
def test_build_model_v9m():
|
30 |
+
with initialize(config_path=config_path, version_base=None):
|
31 |
+
cfg: Config = compose(config_name=config_name, overrides=[f"model=v9-m"])
|
32 |
+
|
33 |
+
OmegaConf.set_struct(cfg.model, False)
|
34 |
+
cfg.weight = None
|
35 |
+
model = YOLO(cfg.model)
|
36 |
+
assert len(model.model) == 39
|
37 |
+
|
38 |
+
|
39 |
+
def test_build_model_v7():
|
40 |
+
with initialize(config_path=config_path, version_base=None):
|
41 |
+
cfg: Config = compose(config_name=config_name, overrides=[f"model=v7"])
|
42 |
+
|
43 |
+
OmegaConf.set_struct(cfg.model, False)
|
44 |
+
cfg.weight = None
|
45 |
+
model = YOLO(cfg.model)
|
46 |
+
assert len(model.model) == 106
|
47 |
+
|
48 |
+
|
49 |
@pytest.fixture
|
50 |
def cfg() -> Config:
|
51 |
with initialize(config_path="../../yolo/config", version_base=None):
|
tests/test_tools/test_data_augmentation.py
ADDED
@@ -0,0 +1,71 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import sys
|
2 |
+
from pathlib import Path
|
3 |
+
|
4 |
+
import torch
|
5 |
+
from PIL import Image
|
6 |
+
from torchvision.transforms import functional as TF
|
7 |
+
|
8 |
+
project_root = Path(__file__).resolve().parent.parent.parent
|
9 |
+
sys.path.append(str(project_root))
|
10 |
+
|
11 |
+
from yolo.tools.data_augmentation import (
|
12 |
+
AugmentationComposer,
|
13 |
+
HorizontalFlip,
|
14 |
+
Mosaic,
|
15 |
+
VerticalFlip,
|
16 |
+
)
|
17 |
+
|
18 |
+
|
19 |
+
def test_horizontal_flip():
|
20 |
+
# Create a mock image and bounding boxes
|
21 |
+
img = Image.new("RGB", (100, 100), color="red")
|
22 |
+
boxes = torch.tensor([[1, 0.05, 0.1, 0.7, 0.9]]) # class, xmin, ymin, xmax, ymax
|
23 |
+
|
24 |
+
flip_transform = HorizontalFlip(prob=1) # Set probability to 1 to ensure flip
|
25 |
+
flipped_img, flipped_boxes = flip_transform(img, boxes)
|
26 |
+
|
27 |
+
# Assert image is flipped by comparing it to a manually flipped image
|
28 |
+
assert TF.hflip(img) == flipped_img
|
29 |
+
|
30 |
+
# Assert bounding boxes are flipped correctly
|
31 |
+
expected_boxes = torch.tensor([[1, 0.3, 0.1, 0.95, 0.9]])
|
32 |
+
assert torch.allclose(flipped_boxes, expected_boxes), "Bounding boxes were not flipped correctly"
|
33 |
+
|
34 |
+
|
35 |
+
def test_compose():
|
36 |
+
# Define two mock transforms that simply return the inputs
|
37 |
+
def mock_transform(image, boxes):
|
38 |
+
return image, boxes
|
39 |
+
|
40 |
+
compose = AugmentationComposer([mock_transform, mock_transform])
|
41 |
+
img = Image.new("RGB", (640, 640), color="blue")
|
42 |
+
boxes = torch.tensor([[0, 0.2, 0.2, 0.8, 0.8]])
|
43 |
+
|
44 |
+
transformed_img, transformed_boxes, rev_tensor = compose(img, boxes)
|
45 |
+
tensor_img = TF.pil_to_tensor(img).to(torch.float32) / 255
|
46 |
+
|
47 |
+
assert (transformed_img == tensor_img).all(), "Image should not be altered"
|
48 |
+
assert torch.equal(transformed_boxes, boxes), "Boxes should not be altered"
|
49 |
+
|
50 |
+
|
51 |
+
def test_mosaic():
|
52 |
+
img = Image.new("RGB", (100, 100), color="green")
|
53 |
+
boxes = torch.tensor([[0, 0.25, 0.25, 0.75, 0.75]])
|
54 |
+
|
55 |
+
# Mock parent with image_size and get_more_data method
|
56 |
+
class MockParent:
|
57 |
+
image_size = (100, 100)
|
58 |
+
|
59 |
+
def get_more_data(self, num_images):
|
60 |
+
return [(img, boxes) for _ in range(num_images)]
|
61 |
+
|
62 |
+
mosaic = Mosaic(prob=1) # Ensure mosaic is applied
|
63 |
+
mosaic.set_parent(MockParent())
|
64 |
+
|
65 |
+
mosaic_img, mosaic_boxes = mosaic(img, boxes)
|
66 |
+
|
67 |
+
# Checks here would depend on the exact expected behavior of the mosaic function,
|
68 |
+
# such as dimensions and content of the output image and boxes.
|
69 |
+
|
70 |
+
assert mosaic_img.size == (100, 100), "Mosaic image size should be same"
|
71 |
+
assert len(mosaic_boxes) > 0, "Should have some bounding boxes"
|
tests/test_tools/test_data_loader.py
ADDED
@@ -0,0 +1,69 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import sys
|
2 |
+
from pathlib import Path
|
3 |
+
|
4 |
+
project_root = Path(__file__).resolve().parent.parent.parent
|
5 |
+
sys.path.append(str(project_root))
|
6 |
+
|
7 |
+
from yolo.config.config import Config
|
8 |
+
from yolo.tools.data_loader import StreamDataLoader, YoloDataLoader, create_dataloader
|
9 |
+
|
10 |
+
|
11 |
+
def test_create_dataloader_cache(train_cfg: Config):
|
12 |
+
train_cfg.task.data.shuffle = False
|
13 |
+
train_cfg.task.data.batch_size = 2
|
14 |
+
|
15 |
+
cache_file = Path("tests/data/train.cache")
|
16 |
+
cache_file.unlink(missing_ok=True)
|
17 |
+
|
18 |
+
make_cache_loader = create_dataloader(train_cfg.task.data, train_cfg.dataset)
|
19 |
+
load_cache_loader = create_dataloader(train_cfg.task.data, train_cfg.dataset)
|
20 |
+
m_batch_size, m_images, _, m_reverse_tensors, m_image_paths = next(iter(make_cache_loader))
|
21 |
+
l_batch_size, l_images, _, l_reverse_tensors, l_image_paths = next(iter(load_cache_loader))
|
22 |
+
assert m_batch_size == l_batch_size
|
23 |
+
assert m_images.shape == l_images.shape
|
24 |
+
assert m_reverse_tensors.shape == l_reverse_tensors.shape
|
25 |
+
assert m_image_paths == l_image_paths
|
26 |
+
|
27 |
+
|
28 |
+
def test_training_data_loader_correctness(train_dataloader: YoloDataLoader):
|
29 |
+
"""Test that the training data loader produces correctly shaped data and metadata."""
|
30 |
+
batch_size, images, _, reverse_tensors, image_paths = next(iter(train_dataloader))
|
31 |
+
assert batch_size == 2
|
32 |
+
assert images.shape == (2, 3, 640, 640)
|
33 |
+
assert reverse_tensors.shape == (2, 5)
|
34 |
+
expected_paths = [
|
35 |
+
Path("tests/data/images/train/000000050725.jpg"),
|
36 |
+
Path("tests/data/images/train/000000167848.jpg"),
|
37 |
+
]
|
38 |
+
assert list(image_paths) == list(expected_paths)
|
39 |
+
|
40 |
+
|
41 |
+
def test_validation_data_loader_correctness(validation_dataloader: YoloDataLoader):
|
42 |
+
batch_size, images, targets, reverse_tensors, image_paths = next(iter(validation_dataloader))
|
43 |
+
assert batch_size == 4
|
44 |
+
assert images.shape == (4, 3, 640, 640)
|
45 |
+
assert targets.shape == (4, 18, 5)
|
46 |
+
assert reverse_tensors.shape == (4, 5)
|
47 |
+
expected_paths = [
|
48 |
+
Path("tests/data/images/val/000000151480.jpg"),
|
49 |
+
Path("tests/data/images/val/000000284106.jpg"),
|
50 |
+
Path("tests/data/images/val/000000323571.jpg"),
|
51 |
+
Path("tests/data/images/val/000000570456.jpg"),
|
52 |
+
]
|
53 |
+
assert list(image_paths) == list(expected_paths)
|
54 |
+
|
55 |
+
|
56 |
+
def test_file_stream_data_loader_frame(file_stream_data_loader: StreamDataLoader):
|
57 |
+
"""Test the frame output from the file stream data loader."""
|
58 |
+
frame, rev_tensor, origin_frame = next(iter(file_stream_data_loader))
|
59 |
+
assert frame.shape == (1, 3, 640, 640)
|
60 |
+
assert rev_tensor.shape == (1, 5)
|
61 |
+
assert origin_frame.size == (1024, 768)
|
62 |
+
|
63 |
+
|
64 |
+
def test_directory_stream_data_loader_frame(directory_stream_data_loader: StreamDataLoader):
|
65 |
+
"""Test the frame output from the directory stream data loader."""
|
66 |
+
frame, rev_tensor, origin_frame = next(iter(directory_stream_data_loader))
|
67 |
+
assert frame.shape == (1, 3, 640, 640)
|
68 |
+
assert rev_tensor.shape == (1, 5)
|
69 |
+
assert origin_frame.size == (480, 640) or origin_frame.size == (512, 640)
|
tests/test_tools/test_dataset_preparation.py
ADDED
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
import shutil
|
3 |
+
import sys
|
4 |
+
from pathlib import Path
|
5 |
+
|
6 |
+
project_root = Path(__file__).resolve().parent.parent.parent
|
7 |
+
sys.path.append(str(project_root))
|
8 |
+
|
9 |
+
from yolo.config.config import Config
|
10 |
+
from yolo.tools.dataset_preparation import prepare_dataset, prepare_weight
|
11 |
+
|
12 |
+
|
13 |
+
def test_prepare_dataset(train_cfg: Config):
|
14 |
+
dataset_path = Path("tests/data")
|
15 |
+
if dataset_path.exists():
|
16 |
+
shutil.rmtree(dataset_path)
|
17 |
+
prepare_dataset(train_cfg.dataset, task="train")
|
18 |
+
prepare_dataset(train_cfg.dataset, task="val")
|
19 |
+
|
20 |
+
images_path = Path("tests/data/images")
|
21 |
+
for data_type in images_path.iterdir():
|
22 |
+
assert len(os.listdir(data_type)) == 5
|
23 |
+
|
24 |
+
annotations_path = Path("tests/data/annotations")
|
25 |
+
assert "instances_val.json" in os.listdir(annotations_path)
|
26 |
+
assert "instances_train.json" in os.listdir(annotations_path)
|
27 |
+
|
28 |
+
|
29 |
+
def test_prepare_weight():
|
30 |
+
prepare_weight()
|
tests/test_tools/test_drawer.py
ADDED
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import sys
|
2 |
+
from pathlib import Path
|
3 |
+
|
4 |
+
from PIL import Image
|
5 |
+
from torch import tensor
|
6 |
+
|
7 |
+
project_root = Path(__file__).resolve().parent.parent.parent
|
8 |
+
sys.path.append(str(project_root))
|
9 |
+
|
10 |
+
from yolo.config.config import Config
|
11 |
+
from yolo.model.yolo import YOLO
|
12 |
+
from yolo.tools.drawer import draw_bboxes, draw_model
|
13 |
+
|
14 |
+
|
15 |
+
def test_draw_model_by_config(train_cfg: Config):
|
16 |
+
"""Test the drawing of a model based on a configuration."""
|
17 |
+
draw_model(model_cfg=train_cfg.model)
|
18 |
+
|
19 |
+
|
20 |
+
def test_draw_model_by_model(model: YOLO):
|
21 |
+
"""Test the drawing of a YOLO model."""
|
22 |
+
draw_model(model=model)
|
23 |
+
|
24 |
+
|
25 |
+
def test_draw_bboxes():
|
26 |
+
"""Test drawing bounding boxes on an image."""
|
27 |
+
predictions = tensor([[0, 60, 60, 160, 160, 0.5], [0, 40, 40, 120, 120, 0.5]])
|
28 |
+
pil_image = Image.open("tests/data/images/train/000000050725.jpg")
|
29 |
+
draw_bboxes(pil_image, [predictions])
|
tests/test_tools/test_loss_functions.py
ADDED
@@ -0,0 +1,56 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import sys
|
2 |
+
from pathlib import Path
|
3 |
+
|
4 |
+
import pytest
|
5 |
+
import torch
|
6 |
+
from hydra import compose, initialize
|
7 |
+
|
8 |
+
project_root = Path(__file__).resolve().parent.parent.parent
|
9 |
+
sys.path.append(str(project_root))
|
10 |
+
|
11 |
+
from yolo.config.config import Config
|
12 |
+
from yolo.model.yolo import create_model
|
13 |
+
from yolo.tools.loss_functions import DualLoss, create_loss_function
|
14 |
+
from yolo.utils.bounding_box_utils import Vec2Box
|
15 |
+
|
16 |
+
|
17 |
+
@pytest.fixture
|
18 |
+
def cfg() -> Config:
|
19 |
+
with initialize(config_path="../../yolo/config", version_base=None):
|
20 |
+
cfg = compose(config_name="config", overrides=["task=train"])
|
21 |
+
return cfg
|
22 |
+
|
23 |
+
|
24 |
+
@pytest.fixture
|
25 |
+
def model(cfg: Config):
|
26 |
+
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
27 |
+
model = create_model(cfg.model, weight_path=None)
|
28 |
+
return model.to(device)
|
29 |
+
|
30 |
+
|
31 |
+
@pytest.fixture
|
32 |
+
def vec2box(cfg: Config, model):
|
33 |
+
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
34 |
+
return Vec2Box(model, cfg.model.anchor, cfg.image_size, device)
|
35 |
+
|
36 |
+
|
37 |
+
@pytest.fixture
|
38 |
+
def loss_function(cfg, vec2box) -> DualLoss:
|
39 |
+
return create_loss_function(cfg, vec2box)
|
40 |
+
|
41 |
+
|
42 |
+
@pytest.fixture
|
43 |
+
def data():
|
44 |
+
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
45 |
+
targets = torch.zeros(1, 20, 5, device=device)
|
46 |
+
predicts = [torch.zeros(1, 8400, *cn, device=device) for cn in [(80,), (4, 16), (4,)]]
|
47 |
+
return predicts, targets
|
48 |
+
|
49 |
+
|
50 |
+
def test_yolo_loss(loss_function, data):
|
51 |
+
predicts, targets = data
|
52 |
+
loss, loss_dict = loss_function(predicts, predicts, targets)
|
53 |
+
assert torch.isnan(loss)
|
54 |
+
assert torch.isnan(loss_dict["BoxLoss"])
|
55 |
+
assert torch.isnan(loss_dict["DFLoss"])
|
56 |
+
assert torch.isinf(loss_dict["BCELoss"])
|
tests/test_tools/test_solver.py
ADDED
@@ -0,0 +1,80 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import sys
|
2 |
+
from pathlib import Path
|
3 |
+
|
4 |
+
import pytest
|
5 |
+
from torch import allclose, tensor
|
6 |
+
|
7 |
+
project_root = Path(__file__).resolve().parent.parent.parent
|
8 |
+
sys.path.append(str(project_root))
|
9 |
+
|
10 |
+
from yolo.config.config import Config
|
11 |
+
from yolo.model.yolo import YOLO
|
12 |
+
from yolo.tools.data_loader import StreamDataLoader, YoloDataLoader
|
13 |
+
from yolo.tools.solver import ModelTester, ModelTrainer, ModelValidator
|
14 |
+
from yolo.utils.bounding_box_utils import Anc2Box, Vec2Box
|
15 |
+
|
16 |
+
|
17 |
+
@pytest.fixture
|
18 |
+
def model_validator(validation_cfg: Config, model: YOLO, vec2box: Vec2Box, validation_progress_logger, device):
|
19 |
+
validator = ModelValidator(
|
20 |
+
validation_cfg.task, validation_cfg.dataset, model, vec2box, validation_progress_logger, device
|
21 |
+
)
|
22 |
+
return validator
|
23 |
+
|
24 |
+
|
25 |
+
def test_model_validator_initialization(model_validator: ModelValidator):
|
26 |
+
assert isinstance(model_validator.model, YOLO)
|
27 |
+
assert hasattr(model_validator, "solve")
|
28 |
+
|
29 |
+
|
30 |
+
def test_model_validator_solve_mock_dataset(model_validator: ModelValidator, validation_dataloader: YoloDataLoader):
|
31 |
+
mAPs = model_validator.solve(validation_dataloader)
|
32 |
+
except_mAPs = {"mAP.5": tensor(0.6969), "mAP.5:.95": tensor(0.4195)}
|
33 |
+
assert allclose(mAPs["mAP.5"], except_mAPs["mAP.5"], rtol=0.1)
|
34 |
+
print(mAPs)
|
35 |
+
assert allclose(mAPs["mAP.5:.95"], except_mAPs["mAP.5:.95"], rtol=0.1)
|
36 |
+
|
37 |
+
|
38 |
+
@pytest.fixture
|
39 |
+
def model_tester(inference_cfg: Config, model: YOLO, vec2box: Vec2Box, validation_progress_logger, device):
|
40 |
+
tester = ModelTester(inference_cfg, model, vec2box, validation_progress_logger, device)
|
41 |
+
return tester
|
42 |
+
|
43 |
+
|
44 |
+
@pytest.fixture
|
45 |
+
def modelv7_tester(inference_v7_cfg: Config, model_v7: YOLO, anc2box: Anc2Box, validation_progress_logger, device):
|
46 |
+
tester = ModelTester(inference_v7_cfg, model_v7, anc2box, validation_progress_logger, device)
|
47 |
+
return tester
|
48 |
+
|
49 |
+
|
50 |
+
def test_model_tester_initialization(model_tester: ModelTester):
|
51 |
+
assert isinstance(model_tester.model, YOLO)
|
52 |
+
assert hasattr(model_tester, "solve")
|
53 |
+
|
54 |
+
|
55 |
+
def test_model_tester_solve_single_image(model_tester: ModelTester, file_stream_data_loader: StreamDataLoader):
|
56 |
+
model_tester.solve(file_stream_data_loader)
|
57 |
+
|
58 |
+
|
59 |
+
def test_modelv7_tester_solve_single_image(modelv7_tester: ModelTester, file_stream_data_loader_v7: StreamDataLoader):
|
60 |
+
modelv7_tester.solve(file_stream_data_loader_v7)
|
61 |
+
|
62 |
+
|
63 |
+
@pytest.fixture
|
64 |
+
def model_trainer(train_cfg: Config, model: YOLO, vec2box: Vec2Box, train_progress_logger, device):
|
65 |
+
train_cfg.task.epoch = 2
|
66 |
+
trainer = ModelTrainer(train_cfg, model, vec2box, train_progress_logger, device, use_ddp=False)
|
67 |
+
return trainer
|
68 |
+
|
69 |
+
|
70 |
+
def test_model_trainer_initialization(model_trainer: ModelTrainer):
|
71 |
+
|
72 |
+
assert isinstance(model_trainer.model, YOLO)
|
73 |
+
assert hasattr(model_trainer, "solve")
|
74 |
+
assert model_trainer.optimizer is not None
|
75 |
+
assert model_trainer.scheduler is not None
|
76 |
+
assert model_trainer.loss_fn is not None
|
77 |
+
|
78 |
+
|
79 |
+
# def test_model_trainer_solve_mock_dataset(model_trainer: ModelTrainer, train_dataloader: YoloDataLoader):
|
80 |
+
# model_trainer.solve(train_dataloader)
|
tests/test_utils/test_bounding_box_utils.py
ADDED
@@ -0,0 +1,183 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import sys
|
2 |
+
from pathlib import Path
|
3 |
+
|
4 |
+
import pytest
|
5 |
+
import torch
|
6 |
+
from hydra import compose, initialize
|
7 |
+
from torch import allclose, float32, isclose, tensor
|
8 |
+
|
9 |
+
project_root = Path(__file__).resolve().parent.parent.parent
|
10 |
+
sys.path.append(str(project_root))
|
11 |
+
from yolo import Config, NMSConfig, create_model
|
12 |
+
from yolo.config.config import AnchorConfig
|
13 |
+
from yolo.utils.bounding_box_utils import (
|
14 |
+
Anc2Box,
|
15 |
+
Vec2Box,
|
16 |
+
bbox_nms,
|
17 |
+
calculate_iou,
|
18 |
+
calculate_map,
|
19 |
+
generate_anchors,
|
20 |
+
transform_bbox,
|
21 |
+
)
|
22 |
+
|
23 |
+
EPS = 1e-4
|
24 |
+
|
25 |
+
|
26 |
+
@pytest.fixture
|
27 |
+
def dummy_bboxes():
|
28 |
+
bbox1 = tensor([[50, 80, 150, 140], [30, 20, 100, 80]], dtype=float32)
|
29 |
+
bbox2 = tensor([[90, 70, 160, 160], [40, 40, 90, 120]], dtype=float32)
|
30 |
+
return bbox1, bbox2
|
31 |
+
|
32 |
+
|
33 |
+
def test_calculate_iou_2d(dummy_bboxes):
|
34 |
+
bbox1, bbox2 = dummy_bboxes
|
35 |
+
iou = calculate_iou(bbox1, bbox2)
|
36 |
+
expected_iou = tensor([[0.4138, 0.1905], [0.0096, 0.3226]])
|
37 |
+
assert iou.shape == (2, 2)
|
38 |
+
assert allclose(iou, expected_iou, atol=EPS)
|
39 |
+
|
40 |
+
|
41 |
+
def test_calculate_iou_3d(dummy_bboxes):
|
42 |
+
bbox1, bbox2 = dummy_bboxes
|
43 |
+
iou = calculate_iou(bbox1[None], bbox2[None])
|
44 |
+
expected_iou = tensor([[0.4138, 0.1905], [0.0096, 0.3226]])
|
45 |
+
assert iou.shape == (1, 2, 2)
|
46 |
+
assert allclose(iou, expected_iou, atol=EPS)
|
47 |
+
|
48 |
+
|
49 |
+
def test_calculate_diou(dummy_bboxes):
|
50 |
+
bbox1, bbox2 = dummy_bboxes
|
51 |
+
iou = calculate_iou(bbox1, bbox2, "diou")
|
52 |
+
expected_diou = tensor([[0.3816, 0.0943], [-0.2048, 0.2622]])
|
53 |
+
|
54 |
+
assert iou.shape == (2, 2)
|
55 |
+
assert allclose(iou, expected_diou, atol=EPS)
|
56 |
+
|
57 |
+
|
58 |
+
def test_calculate_ciou(dummy_bboxes):
|
59 |
+
bbox1, bbox2 = dummy_bboxes
|
60 |
+
iou = calculate_iou(bbox1, bbox2, metrics="ciou")
|
61 |
+
# TODO: check result!
|
62 |
+
expected_ciou = tensor([[0.3769, 0.0853], [-0.2050, 0.2602]])
|
63 |
+
assert iou.shape == (2, 2)
|
64 |
+
assert allclose(iou, expected_ciou, atol=EPS)
|
65 |
+
|
66 |
+
bbox1 = tensor([[50, 80, 150, 140], [30, 20, 100, 80]], dtype=float32)
|
67 |
+
bbox2 = tensor([[90, 70, 160, 160], [40, 40, 90, 120]], dtype=float32)
|
68 |
+
|
69 |
+
|
70 |
+
def test_transform_bbox_xywh_to_Any(dummy_bboxes):
|
71 |
+
bbox1, _ = dummy_bboxes
|
72 |
+
transformed_bbox = transform_bbox(bbox1, "xywh -> xyxy")
|
73 |
+
expected_bbox = tensor([[50.0, 80.0, 200.0, 220.0], [30.0, 20.0, 130.0, 100.0]])
|
74 |
+
assert allclose(transformed_bbox, expected_bbox)
|
75 |
+
|
76 |
+
|
77 |
+
def test_transform_bbox_xycwh_to_Any(dummy_bboxes):
|
78 |
+
bbox1, bbox2 = dummy_bboxes
|
79 |
+
transformed_bbox = transform_bbox(bbox1, "xycwh -> xycwh")
|
80 |
+
assert allclose(transformed_bbox, bbox1)
|
81 |
+
|
82 |
+
transformed_bbox = transform_bbox(bbox2, "xyxy -> xywh")
|
83 |
+
expected_bbox = tensor([[90.0, 70.0, 70.0, 90.0], [40.0, 40.0, 50.0, 80.0]])
|
84 |
+
assert allclose(transformed_bbox, expected_bbox)
|
85 |
+
|
86 |
+
|
87 |
+
def test_transform_bbox_xyxy_to_Any(dummy_bboxes):
|
88 |
+
bbox1, bbox2 = dummy_bboxes
|
89 |
+
transformed_bbox = transform_bbox(bbox1, "xyxy -> xyxy")
|
90 |
+
assert allclose(transformed_bbox, bbox1)
|
91 |
+
|
92 |
+
transformed_bbox = transform_bbox(bbox2, "xyxy -> xycwh")
|
93 |
+
expected_bbox = tensor([[125.0, 115.0, 70.0, 90.0], [65.0, 80.0, 50.0, 80.0]])
|
94 |
+
assert allclose(transformed_bbox, expected_bbox)
|
95 |
+
|
96 |
+
|
97 |
+
def test_transform_bbox_invalid_format(dummy_bboxes):
|
98 |
+
bbox, _ = dummy_bboxes
|
99 |
+
|
100 |
+
# Test invalid input format
|
101 |
+
with pytest.raises(ValueError, match="Invalid input or output format"):
|
102 |
+
transform_bbox(bbox, "invalid->xyxy")
|
103 |
+
|
104 |
+
# Test invalid output format
|
105 |
+
with pytest.raises(ValueError, match="Invalid input or output format"):
|
106 |
+
transform_bbox(bbox, "xywh->invalid")
|
107 |
+
|
108 |
+
|
109 |
+
def test_generate_anchors():
|
110 |
+
image_size = [256, 256]
|
111 |
+
strides = [8, 16, 32]
|
112 |
+
anchors, scalers = generate_anchors(image_size, strides)
|
113 |
+
assert anchors.shape[0] == scalers.shape[0]
|
114 |
+
assert anchors.shape[1] == 2
|
115 |
+
|
116 |
+
|
117 |
+
def test_vec2box_autoanchor():
|
118 |
+
with initialize(config_path="../../yolo/config", version_base=None):
|
119 |
+
cfg: Config = compose(config_name="config", overrides=["model=v9-m"])
|
120 |
+
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
121 |
+
model = create_model(cfg.model, weight_path=None).to(device)
|
122 |
+
vec2box = Vec2Box(model, cfg.model.anchor, cfg.image_size, device)
|
123 |
+
assert vec2box.strides == [8, 16, 32]
|
124 |
+
|
125 |
+
vec2box.update((320, 640))
|
126 |
+
assert vec2box.anchor_grid.shape == (4200, 2)
|
127 |
+
assert vec2box.scaler.shape == tuple([4200])
|
128 |
+
|
129 |
+
|
130 |
+
def test_anc2box_autoanchor(inference_v7_cfg: Config):
|
131 |
+
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
132 |
+
model = create_model(inference_v7_cfg.model, weight_path=None).to(device)
|
133 |
+
anchor_cfg: AnchorConfig = inference_v7_cfg.model.anchor.copy()
|
134 |
+
del anchor_cfg.strides
|
135 |
+
anc2box = Anc2Box(model, anchor_cfg, inference_v7_cfg.image_size, device)
|
136 |
+
assert anc2box.strides == [8, 16, 32]
|
137 |
+
|
138 |
+
anc2box.update((320, 640))
|
139 |
+
anchor_grids_shape = [anchor_grid.shape for anchor_grid in anc2box.anchor_grids]
|
140 |
+
assert anchor_grids_shape == [
|
141 |
+
torch.Size([1, 1, 80, 80, 2]),
|
142 |
+
torch.Size([1, 1, 40, 40, 2]),
|
143 |
+
torch.Size([1, 1, 20, 20, 2]),
|
144 |
+
]
|
145 |
+
assert anc2box.anchor_scale.shape == torch.Size([3, 1, 3, 1, 1, 2])
|
146 |
+
|
147 |
+
|
148 |
+
def test_bbox_nms():
|
149 |
+
cls_dist = tensor(
|
150 |
+
[[[0.1, 0.7, 0.2], [0.6, 0.3, 0.1]], [[0.4, 0.4, 0.2], [0.5, 0.4, 0.1]]] # Example class distribution
|
151 |
+
)
|
152 |
+
bbox = tensor(
|
153 |
+
[[[50, 50, 100, 100], [60, 60, 110, 110]], [[40, 40, 90, 90], [70, 70, 120, 120]]], # Example bounding boxes
|
154 |
+
dtype=float32,
|
155 |
+
)
|
156 |
+
nms_cfg = NMSConfig(min_confidence=0.5, min_iou=0.5)
|
157 |
+
|
158 |
+
expected_output = [
|
159 |
+
tensor(
|
160 |
+
[
|
161 |
+
[1.0000, 50.0000, 50.0000, 100.0000, 100.0000, 0.6682],
|
162 |
+
[0.0000, 60.0000, 60.0000, 110.0000, 110.0000, 0.6457],
|
163 |
+
]
|
164 |
+
)
|
165 |
+
]
|
166 |
+
|
167 |
+
output = bbox_nms(cls_dist, bbox, nms_cfg)
|
168 |
+
|
169 |
+
for out, exp in zip(output, expected_output):
|
170 |
+
assert allclose(out, exp, atol=1e-4), f"Output: {out} Expected: {exp}"
|
171 |
+
|
172 |
+
|
173 |
+
def test_calculate_map():
|
174 |
+
predictions = tensor([[0, 60, 60, 160, 160, 0.5], [0, 40, 40, 120, 120, 0.5]]) # [class, x1, y1, x2, y2]
|
175 |
+
ground_truths = tensor([[0, 50, 50, 150, 150], [0, 30, 30, 100, 100]]) # [class, x1, y1, x2, y2]
|
176 |
+
|
177 |
+
mAP = calculate_map(predictions, ground_truths)
|
178 |
+
|
179 |
+
expected_ap50 = tensor(0.5)
|
180 |
+
expected_ap50_95 = tensor(0.2)
|
181 |
+
|
182 |
+
assert isclose(mAP["mAP.5"], expected_ap50, atol=1e-5), f"AP50 mismatch"
|
183 |
+
assert isclose(mAP["mAP.5:.95"], expected_ap50_95, atol=1e-5), f"Mean AP mismatch"
|
tests/test_utils/test_module_utils.py
ADDED
@@ -0,0 +1,73 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import re
|
2 |
+
import sys
|
3 |
+
from pathlib import Path
|
4 |
+
|
5 |
+
import pytest
|
6 |
+
from torch import nn
|
7 |
+
|
8 |
+
project_root = Path(__file__).resolve().parent.parent.parent
|
9 |
+
sys.path.append(str(project_root))
|
10 |
+
from yolo.utils.module_utils import (
|
11 |
+
auto_pad,
|
12 |
+
create_activation_function,
|
13 |
+
divide_into_chunks,
|
14 |
+
)
|
15 |
+
|
16 |
+
|
17 |
+
@pytest.mark.parametrize(
|
18 |
+
"kernel_size, dilation, expected",
|
19 |
+
[
|
20 |
+
(3, 1, (1, 1)),
|
21 |
+
((3, 3), (1, 1), (1, 1)),
|
22 |
+
(3, (2, 2), (2, 2)),
|
23 |
+
((5, 5), 1, (2, 2)),
|
24 |
+
((3, 5), (2, 1), (2, 2)),
|
25 |
+
],
|
26 |
+
)
|
27 |
+
def test_auto_pad(kernel_size, dilation, expected):
|
28 |
+
assert auto_pad(kernel_size, dilation) == expected, "auto_pad does not calculate padding correctly"
|
29 |
+
|
30 |
+
|
31 |
+
@pytest.mark.parametrize(
|
32 |
+
"activation_name, expected_type",
|
33 |
+
[("ReLU", nn.ReLU), ("leakyrelu", nn.LeakyReLU), ("none", nn.Identity), (None, nn.Identity), (False, nn.Identity)],
|
34 |
+
)
|
35 |
+
def test_get_activation(activation_name, expected_type):
|
36 |
+
result = create_activation_function(activation_name)
|
37 |
+
assert isinstance(result, expected_type), f"get_activation does not return correct type for {activation_name}"
|
38 |
+
|
39 |
+
|
40 |
+
def test_get_activation_invalid():
|
41 |
+
with pytest.raises(ValueError):
|
42 |
+
create_activation_function("unsupported_activation")
|
43 |
+
|
44 |
+
|
45 |
+
def test_divide_into_chunks():
|
46 |
+
input_list = [0, 1, 2, 3, 4, 5]
|
47 |
+
chunk_num = 2
|
48 |
+
expected_output = [[0, 1, 2], [3, 4, 5]]
|
49 |
+
assert divide_into_chunks(input_list, chunk_num) == expected_output
|
50 |
+
|
51 |
+
|
52 |
+
def test_divide_into_chunks_non_divisible_length():
|
53 |
+
input_list = [0, 1, 2, 3, 4, 5]
|
54 |
+
chunk_num = 4
|
55 |
+
with pytest.raises(
|
56 |
+
ValueError,
|
57 |
+
match=re.escape("The length of the input list (6) must be exactly divisible by the number of chunks (4)."),
|
58 |
+
):
|
59 |
+
divide_into_chunks(input_list, chunk_num)
|
60 |
+
|
61 |
+
|
62 |
+
def test_divide_into_chunks_single_chunk():
|
63 |
+
input_list = [0, 1, 2, 3, 4, 5]
|
64 |
+
chunk_num = 1
|
65 |
+
expected_output = [[0, 1, 2, 3, 4, 5]]
|
66 |
+
assert divide_into_chunks(input_list, chunk_num) == expected_output
|
67 |
+
|
68 |
+
|
69 |
+
def test_divide_into_chunks_equal_chunks():
|
70 |
+
input_list = [0, 1, 2, 3, 4, 5, 6, 7]
|
71 |
+
chunk_num = 4
|
72 |
+
expected_output = [[0, 1], [2, 3], [4, 5], [6, 7]]
|
73 |
+
assert divide_into_chunks(input_list, chunk_num) == expected_output
|
yolo/__init__.py
CHANGED
@@ -3,7 +3,7 @@ from yolo.model.yolo import create_model
|
|
3 |
from yolo.tools.data_loader import AugmentationComposer, create_dataloader
|
4 |
from yolo.tools.drawer import draw_bboxes
|
5 |
from yolo.tools.solver import ModelTester, ModelTrainer, ModelValidator
|
6 |
-
from yolo.utils.bounding_box_utils import Vec2Box, bbox_nms
|
7 |
from yolo.utils.deploy_utils import FastModelLoader
|
8 |
from yolo.utils.logging_utils import custom_logger
|
9 |
from yolo.utils.model_utils import PostProccess
|
@@ -16,7 +16,9 @@ all = [
|
|
16 |
"validate_log_directory",
|
17 |
"draw_bboxes",
|
18 |
"Vec2Box",
|
|
|
19 |
"bbox_nms",
|
|
|
20 |
"AugmentationComposer",
|
21 |
"create_dataloader",
|
22 |
"FastModelLoader",
|
|
|
3 |
from yolo.tools.data_loader import AugmentationComposer, create_dataloader
|
4 |
from yolo.tools.drawer import draw_bboxes
|
5 |
from yolo.tools.solver import ModelTester, ModelTrainer, ModelValidator
|
6 |
+
from yolo.utils.bounding_box_utils import Anc2Box, Vec2Box, bbox_nms, create_converter
|
7 |
from yolo.utils.deploy_utils import FastModelLoader
|
8 |
from yolo.utils.logging_utils import custom_logger
|
9 |
from yolo.utils.model_utils import PostProccess
|
|
|
16 |
"validate_log_directory",
|
17 |
"draw_bboxes",
|
18 |
"Vec2Box",
|
19 |
+
"Anc2Box",
|
20 |
"bbox_nms",
|
21 |
+
"create_converter",
|
22 |
"AugmentationComposer",
|
23 |
"create_dataloader",
|
24 |
"FastModelLoader",
|