henry000 commited on
Commit
0eb8792
1 Parent(s): 25c6c9f

Upload folder using huggingface_hub

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .github/workflows/deploy.yaml +69 -0
  2. .github/workflows/develop.yaml +52 -0
  3. .github/workflows/docker.yaml +42 -0
  4. .gitignore +1 -0
  5. .readthedocs.yaml +8 -0
  6. README.md +69 -18
  7. demo/hf_demo.py +9 -9
  8. docker/.dockerignore +32 -0
  9. docker/Dockerfile +64 -0
  10. docker/requirements-locked.txt +5 -0
  11. docs/0_get_start/0_quick_start.md +1 -0
  12. docs/0_get_start/1_installations.md +4 -0
  13. docs/0_get_start/2_git.md +3 -0
  14. docs/0_get_start/3_pypi.md +3 -0
  15. docs/0_get_start/4_docker.md +3 -0
  16. docs/0_get_start/5_conda.md +3 -0
  17. docs/1_tutorials/0_train.md +5 -0
  18. docs/1_tutorials/1_validation.md +5 -0
  19. docs/2_model_zoo/0_object_detection.md +5 -0
  20. docs/2_model_zoo/1_segmentation.md +5 -0
  21. docs/2_model_zoo/2_classification.md +5 -0
  22. docs/3_custom/0_model.md +1 -0
  23. docs/3_custom/1_data_augment.md +1 -0
  24. docs/3_custom/2_loss.md +1 -0
  25. docs/3_custom/3_task.md +1 -0
  26. docs/4_deploy/1_deploy.md +3 -0
  27. docs/4_deploy/2_onnx.md +1 -0
  28. docs/4_deploy/3_tensorrt.md +1 -0
  29. docs/HOWTO.md +24 -0
  30. docs/Makefile +20 -0
  31. docs/conf.py +37 -0
  32. docs/index.rst +81 -0
  33. docs/make.bat +35 -0
  34. docs/requirements.txt +3 -0
  35. examples/notebook_inference.ipynb +0 -0
  36. examples/notebook_smallobject.ipynb +57 -20
  37. requirements-dev.txt +5 -0
  38. requirements.txt +1 -3
  39. tests/conftest.py +118 -0
  40. tests/test_model/test_module.py +0 -7
  41. tests/test_model/test_yolo.py +21 -1
  42. tests/test_tools/test_data_augmentation.py +71 -0
  43. tests/test_tools/test_data_loader.py +69 -0
  44. tests/test_tools/test_dataset_preparation.py +30 -0
  45. tests/test_tools/test_drawer.py +29 -0
  46. tests/test_tools/test_loss_functions.py +56 -0
  47. tests/test_tools/test_solver.py +80 -0
  48. tests/test_utils/test_bounding_box_utils.py +183 -0
  49. tests/test_utils/test_module_utils.py +73 -0
  50. 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
- [![Hugging Face Spaces](https://img.shields.io/badge/%F0%9F%A4%97%20Hugging%20Face-Spaces-blue)](https://huggingface.co/spaces/henry000/YOLO)
12
- > [!IMPORTANT]
 
 
 
 
 
 
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 enter directly in the terminal:
22
  ```shell
23
- pip install git+git@github.com:WongKinYiu/YOLO.git
24
- yolo task=inference task.source=0 # source could be a single file, video, image folder, webcam ID
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 with YOLOv9, clone this repository and install the required dependencies:
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 `data/config.yaml` to point to your dataset.
72
  2. Run the training script:
73
  ```shell
74
- python yolo/lazy.py task=train task.data.batch_size=8 model=v9-c
 
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 evaluate the model performance, use:
85
  ```shell
86
- python yolo/lazy.py task=inference weight=weights/v9-c.pt model=v9-c task.fast_inference=deploy # use deploy weight
87
- python yolo/lazy.py task=inference # if cloned from GitHub
88
- yolo task=inference task.data.source={Any} # if pip installed
 
 
 
 
 
 
 
 
89
  ```
90
 
91
- ### Validation [WIP]
92
- To validate the model performance, use:
93
  ```shell
94
- # Work In Progress...
 
95
  ```
96
 
97
  ## Contributing
98
- Contributions to the YOLOv9 project are welcome! See [CONTRIBUTING](docs/CONTRIBUTING.md) for guidelines on how to contribute.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
- Vec2Box,
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
- v2b = Vec2Box(model, IMAGE_SIZE, device)
34
- class_list = OmegaConf.load("yolo/config/general.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, v2b, class_list, post_proccess
41
  if model_name != DEFAULT_MODEL:
42
- model = load_model(model_name, device)
43
- v2b = Vec2Box(model, IMAGE_SIZE, device)
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(v2b, nms_config)
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": null,
6
  "metadata": {},
7
  "outputs": [],
8
  "source": [
@@ -12,7 +12,7 @@
12
  },
13
  {
14
  "cell_type": "code",
15
- "execution_count": null,
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
- "from yolo.config.config import NMSConfig\n",
34
- "from yolo import AugmentationComposer, bbox_nms, Config, create_model, custom_logger, draw_bboxes, Vec2Box"
 
 
 
 
 
 
 
 
 
 
35
  ]
36
  },
37
  {
38
  "cell_type": "code",
39
- "execution_count": null,
40
  "metadata": {},
41
  "outputs": [],
42
  "source": [
@@ -55,32 +64,49 @@
55
  },
56
  {
57
  "cell_type": "code",
58
- "execution_count": null,
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
- " vec2box = Vec2Box(model, cfg.image_size, device)"
 
 
67
  ]
68
  },
69
  {
70
  "cell_type": "code",
71
- "execution_count": null,
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": null,
84
  "metadata": {},
85
  "outputs": [],
86
  "source": [
@@ -104,9 +130,21 @@
104
  },
105
  {
106
  "cell_type": "code",
107
- "execution_count": null,
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
- "pred_bbox = (pred_bbox / rev_tensor[0] - rev_tensor[None, None, 1:]) "
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
- pytest
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 test_build_model():
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",