diff --git a/.github/workflows/ci-testing.yml b/.github/workflows/ci-testing.yml index b00335cc43b9..d33336213c0c 100644 --- a/.github/workflows/ci-testing.yml +++ b/.github/workflows/ci-testing.yml @@ -14,7 +14,7 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest, macos-latest, windows-latest] - python-version: [3.8] + python-version: [3.7, 3.8] model: ['yolov5s'] # models to test # Timeout: https://stackoverflow.com/a/59076067/4521646 @@ -53,23 +53,44 @@ jobs: - name: Download data run: | - python -c "from utils.google_utils import * ; gdrive_download('1n_oKgR81BJtqk75b00eAjdv03qVCQn2f', 'coco128.zip')" + python -c "from yolo5.utils.google_utils import * ; gdrive_download('1n_oKgR81BJtqk75b00eAjdv03qVCQn2f', 'coco128.zip')" mv ./coco128 ../ + - name: PyTest + run: | + pip install -q coverage pytest pytest-flake8 + # todo: make code PEP8 complient and uncoment this Flake8 check + coverage run --source yolo5 -m pytest yolo5 --doctest-modules # --flake8 + coverage report + - name: Tests workflow run: | export PYTHONPATH="$PWD" # to run *.py. files in subdirectories di=cpu # inference devices # define device - # train - python train.py --img 256 --batch 8 --weights weights/${{ matrix.model }}.pt --cfg models/${{ matrix.model }}.yaml --epochs 1 --device $di + python yolo5/train.py --img 256 --batch 8 --weights ./weights/${{ matrix.model }}.pt --cfg ./models/${{ matrix.model }}.yaml --epochs 1 --device $di # detect - python detect.py --weights weights/${{ matrix.model }}.pt --device $di - python detect.py --weights runs/exp0/weights/last.pt --device $di + python yolo5/detect.py --weights ./weights/${{ matrix.model }}.pt --device $di + python yolo5/detect.py --weights ./runs/exp0/weights/last.pt --device $di # test - python test.py --img 256 --batch 8 --weights weights/${{ matrix.model }}.pt --device $di - python test.py --img 256 --batch 8 --weights runs/exp0/weights/last.pt --device $di - - python models/yolo.py --cfg models/${{ matrix.model }}.yaml # inspect - python models/export.py --img 256 --batch 1 --weights weights/${{ matrix.model }}.pt # export + python yolo5/test.py --img 256 --batch 8 --weights ./weights/${{ matrix.model }}.pt --device $di + python yolo5/test.py --img 256 --batch 8 --weights ./runs/exp0/weights/last.pt --device $di + # inspect + python yolo5/models/yolo.py --cfg ./models/${{ matrix.model }}.yaml + # export + python yolo5/export.py --img 256 --batch 1 --weights ./weights/${{ matrix.model }}.pt shell: bash + + - name: Test Install + run: | + pip install -q check-manifest twine + + check-manifest + python setup.py check --metadata --strict + + # Build the package + python setup.py sdist bdist_wheel + twine check ./dist/* + + # Dry run install + python setup.py install --dry-run diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 000000000000..6cef8dcf6d70 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,39 @@ +# Manifest syntax https://docs.python.org/2/distutils/sourcedist.html +graft wheelhouse + +recursive-exclude __pycache__ *.pyc *.pyo *.orig + +# Include the README +include *.md + +# Include the license file +include LICENSE + +# Include the Requirements +include requirements.txt + +# Include package +recursive-include yolo5 *.py *.yaml + +# Include setup +include setup.* + +# Exclude testing +exclude pytest.* +recursive-exclude tests * +exclude tests + +# Exclude experiments +exclude */*.sh +exclude *.ipynb +exclude Dockerfile .dockerignore + +prune .git +prune .github +prune run +prune venv +prune build +prune docs +prune tests +prune data +prune inference diff --git a/README.md b/README.md index 65ce5f301add..d6e61d0d6dec 100755 --- a/README.md +++ b/README.md @@ -41,6 +41,13 @@ Python 3.7 or later with all `requirements.txt` dependencies installed, includin $ pip install -U -r requirements.txt ``` +## Installation + +You can install this package straight with pip +```bash +pip install git+https://github.com/ultralytics/yolov5.git +``` + ## Tutorials @@ -68,19 +75,19 @@ YOLOv5 may be run in any of the following up-to-date verified environments (with Inference can be run on most common media formats. Model [checkpoints](https://drive.google.com/open?id=1Drs_Aiu7xx6S-ix95f9kNsA6ueKRpN2J) are downloaded automatically if available. Results are saved to `./inference/output`. ```bash -$ python detect.py --source 0 # webcam - file.jpg # image - file.mp4 # video - path/ # directory - path/*.jpg # glob - rtsp://170.93.143.139/rtplive/470011e600ef003a004ee33696235daa # rtsp stream - http://112.50.243.8/PLTV/88888888/224/3221225900/1.m3u8 # http stream +$ python yolo5/detect.py --source 0 # webcam + file.jpg # image + file.mp4 # video + path/ # directory + path/*.jpg # glob + rtsp://170.93.143.139/rtplive/470011e600ef003a004ee33696235daa # rtsp stream + http://112.50.243.8/PLTV/88888888/224/3221225900/1.m3u8 # http stream ``` To run inference on examples in the `./inference/images` folder: ```bash -$ python detect.py --source ./inference/images/ --weights yolov5s.pt --conf 0.4 +$ python yolo5/detect.py --source ./inference/images/ --weights yolov5s.pt --conf 0.4 Namespace(agnostic_nms=False, augment=False, classes=None, conf_thres=0.4, device='', fourcc='mp4v', half=False, img_size=640, iou_thres=0.5, output='inference/output', save_txt=False, source='./inference/images/', view_img=False, weights='yolov5s.pt') Using CUDA device0 _CudaDeviceProperties(name='Tesla P100-PCIE-16GB', total_memory=16280MB) @@ -99,10 +106,10 @@ Results saved to /content/yolov5/inference/output Download [COCO](https://github.com/ultralytics/yolov5/blob/master/data/get_coco2017.sh), install [Apex](https://github.com/NVIDIA/apex) and run command below. Training times for YOLOv5s/m/l/x are 2/4/6/8 days on a single V100 (multi-GPU times faster). Use the largest `--batch-size` your GPU allows (batch sizes shown for 16 GB devices). ```bash -$ python train.py --data coco.yaml --cfg yolov5s.yaml --weights '' --batch-size 64 - yolov5m 48 - yolov5l 32 - yolov5x 16 +$ python yolo5/train.py --data coco.yaml --cfg yolov5s.yaml --weights '' --batch-size 64 + yolov5m 48 + yolov5l 32 + yolov5x 16 ``` diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 000000000000..5874854a05c8 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,30 @@ +[metadata] +# This includes the license file(s) in the wheel. +# https://wheel.readthedocs.io/en/stable/user_guide.html#including-license-files-in-the-generated-wheel-file +license_files = LICENSE + +[bdist_wheel] +# This flag says to generate wheels that support both Python 2 and Python +# 3. If your code will not run unchanged on both Python 2 and 3, you will +# need to generate separate wheels for each Python version that you +# support. Removing this line (or setting universal to 0) will prevent +# bdist_wheel from trying to make a universal wheel. For more see: +# https://packaging.python.org/guides/distributing-packages-using-setuptools/#wheels +universal=1 + +[flake8] +# http://flake8.pycqa.org/en/latest/user/configuration.html +ignore = E501 +max-line-length = 100 +exclude = *.egg,build,temp +select = E,W,F +doctests = True +verbose = 2 +# max-complexity = 10 + +[tool:pytest] +log_cli = 1 +log_cli_level = CRITICAL +log_file = pytest.log +log_file_level = DEBUG +filterwarnings = ignore::FutureWarning diff --git a/setup.py b/setup.py new file mode 100644 index 000000000000..af135f947a48 --- /dev/null +++ b/setup.py @@ -0,0 +1,136 @@ +"""A setuptools based setup module. + +See: +https://packaging.python.org/guides/distributing-packages-using-setuptools/ +https://github.com/pypa/sampleproject +""" + +# Always prefer setuptools over distutils +from os import path +from setuptools import setup, find_packages + +import yolo5 + +PATH_HERE = path.abspath(path.dirname(__file__)) + +with open(path.join(PATH_HERE, 'requirements.txt')) as fp: + install_reqs = [r.rstrip() for r in fp.readlines() if not r.startswith('#')] + +# Get the long description from the README file +with open(path.join(PATH_HERE, 'README.md'), encoding='utf-8') as fp: + long_description = fp.read() + +# Arguments marked as "Required" below must be included for upload to PyPI. +# Fields marked as "Optional" may be commented out. + +setup( + # This is the name of your project. The first time you publish this + # package, this name will be registered for you. It will determine how + # users can install this project, e.g.: + # + # $ pip install YOLOv5 + # + # And where it will live on PyPI: https://pypi.org/project/YOLOv5/ + name='YOLOv5', # Required + version=yolo5.__version__, # Required + description=yolo5.__doc__, # Optional + + # This is an optional longer description of your project that represents + # the body of text which users will see when they visit PyPI. + long_description=yolo5.__doc_long__, # Optional + + # Denotes that our long_description is in Markdown; valid values are + # text/plain, text/x-rst, and text/markdown + long_description_content_type='text/markdown', # Optional (see note above) + + # This should be a valid link to your project's main homepage. + url='https://github.com/ultralytics/yolov5', # Optional + + # This should be your name or the name of the organization which owns the project. + author='Ultralytics', # Optional + # This should be a valid email address corresponding to the author listed above. + author_email='glenn.jocher@ultralytics.com', # Optional + + # Classifiers help users find your project by categorizing it. + # For a list of valid classifiers, see https://pypi.org/classifiers/ + classifiers=[ # Optional + # How mature is this project? Common values are + # 3 - Alpha + # 4 - Beta + # 5 - Production/Stable + 'Development Status :: 5 - Production/Stable', + + # Indicate who your project is intended for + 'Intended Audience :: Developers', + + # Operation system + 'Operating System :: OS Independent', + + # Topics + 'Topic :: Education', + 'Topic :: Scientific/Engineering', + 'Topic :: Scientific/Engineering :: Artificial Intelligence', + 'Topic :: Scientific/Engineering :: Image Recognition', + + # Pick your license as you wish + 'License :: OSI Approved :: Apache Software License', + + # Specify the Python versions you support here. In particular, ensure + # that you indicate whether you support Python 2, Python 3 or both. + # These classifiers are *not* checked by 'pip install'. See instead + # 'python_requires' below. + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', + ], + + # This field adds keywords for your project which will appear on the + # project page. What does your project relate to? + keywords='YOLO object-detection', # Optional + + # You can just specify package directories manually here if your project is + # simple. Or you can use find_packages(). + packages=find_packages(exclude=['contrib', 'docs', 'tests']), # Required + + # Specify which Python versions you support. In contrast to the + # 'Programming Language' classifiers above, 'pip install' will check this + # and refuse to install the project if the version does not match. If you + # do not support Python 2, you can simplify this to '>=3.5' or similar, see + # https://packaging.python.org/guides/distributing-packages-using-setuptools/#python-requires + python_requires='>=3.6, <4', + install_requires=install_reqs, + + # This field lists other packages that your project depends on to run. + # Any package you put here will be installed by pip when your project is + # installed, so they must be valid existing projects. + # install_requires=[], # Optional + + # Although 'package_data' is the preferred approach, in some case you may + # need to place data files outside of your packages. See: + # http://docs.python.org/3.4/distutils/setupscript.html#installing-additional-files + # + # In this case, 'data_file' will be installed into '/my_data' + data_files=[('models', + [f'yolo5/models/yolov5{n}.yaml' for n in 'smlx'] + + ['yolo5/models/hub/yolov3-spp.yaml'] + + [f'yolo5/models/hub/yolov5-{n}.yaml' for n in ('fpn', 'panet')] + )], # Optional + + # To provide executable scripts, use entry points in preference to the + # "scripts" keyword. Entry points provide cross-platform support and allow + # `pip` to create the appropriate form of executable for the target + # platform. + # entry_points={ # Optional + # 'console_scripts': [ + # 'sandbox=sandbox:main', + # ], + # }, + + # List additional URLs that are relevant to your project as a dict. + project_urls={ # Optional + 'Bug Reports': 'https://github.com/ultralytics/yolov5/issues', + 'Funding': 'https://www.ultralytics.com', + 'Source': 'https://github.com/ultralytics/yolov5/', + }, +) diff --git a/yolo5/__init__.py b/yolo5/__init__.py new file mode 100644 index 000000000000..a934385017fe --- /dev/null +++ b/yolo5/__init__.py @@ -0,0 +1,12 @@ +__version__ = '2.0rc1' +__doc__ = "YOLO v5 - object detection network" +__doc_long__ = """ +# YOLO v5 + +This repository represents Ultralytics open-source research into future object detection methods, + and incorporates our lessons learned and best practices evolved over training thousands of models + on custom client datasets with our previous YOLO repository https://github.com/ultralytics/yolov3. + +**All code and models are under active development, and are subject to modification or deletion without notice.** + Use at your own risk. +""" \ No newline at end of file diff --git a/detect.py b/yolo5/detect.py similarity index 95% rename from detect.py rename to yolo5/detect.py index ca7b96773173..b3f0ef087dab 100644 --- a/detect.py +++ b/yolo5/detect.py @@ -10,10 +10,10 @@ import torch.backends.cudnn as cudnn from numpy import random -from models.experimental import attempt_load -from utils.datasets import LoadStreams, LoadImages -from utils.general import check_img_size, non_max_suppression, apply_classifier, scale_coords, xyxy2xywh, plot_one_box -from utils.torch_utils import select_device, load_classifier, time_synchronized +from yolo5.models.experimental import attempt_load +from yolo5.utils.datasets import LoadStreams, LoadImages +from yolo5.utils.general import check_img_size, non_max_suppression, apply_classifier, scale_coords, xyxy2xywh, plot_one_box +from yolo5.utils.torch_utils import select_device, load_classifier, time_synchronized def detect(save_img=False): diff --git a/models/export.py b/yolo5/export.py similarity index 97% rename from models/export.py rename to yolo5/export.py index 7ccd392bcde3..9e95bf6b4d40 100644 --- a/models/export.py +++ b/yolo5/export.py @@ -8,17 +8,10 @@ import torch -from utils.google_utils import attempt_download +from yolo5.utils.google_utils import attempt_download -if __name__ == '__main__': - parser = argparse.ArgumentParser() - parser.add_argument('--weights', type=str, default='./yolov5s.pt', help='weights path') - parser.add_argument('--img-size', nargs='+', type=int, default=[640, 640], help='image size') - parser.add_argument('--batch-size', type=int, default=1, help='batch size') - opt = parser.parse_args() - opt.img_size *= 2 if len(opt.img_size) == 1 else 1 # expand - print(opt) +def main(opt): # Input img = torch.zeros((opt.batch_size, 3, *opt.img_size)) # image size(1,3,320,192) iDetection @@ -72,3 +65,14 @@ # Finish print('\nExport complete. Visualize with https://github.com/lutzroeder/netron.') + +if __name__ == '__main__': + parser = argparse.ArgumentParser() + parser.add_argument('--weights', type=str, default='./yolov5s.pt', help='weights path') + parser.add_argument('--img-size', nargs='+', type=int, default=[640, 640], help='image size') + parser.add_argument('--batch-size', type=int, default=1, help='batch size') + opt = parser.parse_args() + opt.img_size *= 2 if len(opt.img_size) == 1 else 1 # expand + print(opt) + + main(opt) diff --git a/hubconf.py b/yolo5/hubconf.py similarity index 94% rename from hubconf.py rename to yolo5/hubconf.py index 98416a5b8563..69c412b3af86 100644 --- a/hubconf.py +++ b/yolo5/hubconf.py @@ -2,7 +2,7 @@ Usage: import torch - model = torch.hub.load('ultralytics/yolov5', 'yolov5s', pretrained=True, channels=3, classes=80) + model = torch.hub.load('ultralytics/yolo5', 'yolov5s', pretrained=True, channels=3, classes=80) """ dependencies = ['torch', 'yaml'] @@ -10,8 +10,8 @@ import torch -from models.yolo import Model -from utils.google_utils import attempt_download +from yolo5.models.yolo import Model +from yolo5.utils.google_utils import attempt_download def create(name, pretrained, channels, classes): diff --git a/models/__init__.py b/yolo5/models/__init__.py similarity index 100% rename from models/__init__.py rename to yolo5/models/__init__.py diff --git a/models/common.py b/yolo5/models/common.py similarity index 100% rename from models/common.py rename to yolo5/models/common.py diff --git a/models/experimental.py b/yolo5/models/experimental.py similarity index 98% rename from models/experimental.py rename to yolo5/models/experimental.py index 1b99ce47db53..0438e0c30803 100644 --- a/models/experimental.py +++ b/yolo5/models/experimental.py @@ -4,8 +4,8 @@ import torch import torch.nn as nn -from models.common import Conv, DWConv -from utils.google_utils import attempt_download +from yolo5.models.common import Conv, DWConv +from yolo5.utils.google_utils import attempt_download class CrossConv(nn.Module): diff --git a/models/hub/yolov3-spp.yaml b/yolo5/models/hub/yolov3-spp.yaml similarity index 100% rename from models/hub/yolov3-spp.yaml rename to yolo5/models/hub/yolov3-spp.yaml diff --git a/models/hub/yolov5-fpn.yaml b/yolo5/models/hub/yolov5-fpn.yaml similarity index 100% rename from models/hub/yolov5-fpn.yaml rename to yolo5/models/hub/yolov5-fpn.yaml diff --git a/models/hub/yolov5-panet.yaml b/yolo5/models/hub/yolov5-panet.yaml similarity index 100% rename from models/hub/yolov5-panet.yaml rename to yolo5/models/hub/yolov5-panet.yaml diff --git a/models/yolo.py b/yolo5/models/yolo.py similarity index 97% rename from models/yolo.py rename to yolo5/models/yolo.py index baa4e1afb758..e092153dd87d 100644 --- a/models/yolo.py +++ b/yolo5/models/yolo.py @@ -6,10 +6,10 @@ import torch import torch.nn as nn -from models.common import Conv, Bottleneck, SPP, DWConv, Focus, BottleneckCSP, Concat -from models.experimental import MixConv2d, CrossConv, C3 -from utils.general import check_anchor_order, make_divisible, check_file -from utils.torch_utils import ( +from yolo5.models.common import Conv, Bottleneck, SPP, DWConv, Focus, BottleneckCSP, Concat +from yolo5.models.experimental import MixConv2d, CrossConv, C3 +from yolo5.utils.general import check_anchor_order, make_divisible, check_file +from yolo5.utils.torch_utils import ( time_synchronized, fuse_conv_and_bn, model_info, scale_img, initialize_weights, select_device) diff --git a/models/yolov5l.yaml b/yolo5/models/yolov5l.yaml similarity index 100% rename from models/yolov5l.yaml rename to yolo5/models/yolov5l.yaml diff --git a/models/yolov5m.yaml b/yolo5/models/yolov5m.yaml similarity index 100% rename from models/yolov5m.yaml rename to yolo5/models/yolov5m.yaml diff --git a/models/yolov5s.yaml b/yolo5/models/yolov5s.yaml similarity index 100% rename from models/yolov5s.yaml rename to yolo5/models/yolov5s.yaml diff --git a/models/yolov5x.yaml b/yolo5/models/yolov5x.yaml similarity index 100% rename from models/yolov5x.yaml rename to yolo5/models/yolov5x.yaml diff --git a/test.py b/yolo5/test.py similarity index 98% rename from test.py rename to yolo5/test.py index 7d4df0822b28..c00cd8bbba82 100644 --- a/test.py +++ b/yolo5/test.py @@ -10,12 +10,12 @@ import yaml from tqdm import tqdm -from models.experimental import attempt_load -from utils.datasets import create_dataloader -from utils.general import ( +from yolo5.models.experimental import attempt_load +from yolo5.utils.datasets import create_dataloader +from yolo5.utils.general import ( coco80_to_coco91_class, check_file, check_img_size, compute_loss, non_max_suppression, scale_coords, xyxy2xywh, clip_coords, plot_images, xywh2xyxy, box_iou, output_to_target, ap_per_class) -from utils.torch_utils import select_device, time_synchronized +from yolo5.utils.torch_utils import select_device, time_synchronized def test(data, diff --git a/train.py b/yolo5/train.py similarity index 98% rename from train.py rename to yolo5/train.py index 4b8f1f7c9cbb..8d763455afac 100644 --- a/train.py +++ b/yolo5/train.py @@ -18,15 +18,15 @@ from torch.utils.tensorboard import SummaryWriter from tqdm import tqdm -import test # import test.py to get mAP after each epoch -from models.yolo import Model -from utils.datasets import create_dataloader -from utils.general import ( +from yolo5 import test # import test.py to get mAP after each epoch +from yolo5.models.yolo import Model +from yolo5.utils.datasets import create_dataloader +from yolo5.utils.general import ( check_img_size, torch_distributed_zero_first, labels_to_class_weights, plot_labels, check_anchors, labels_to_image_weights, compute_loss, plot_images, fitness, strip_optimizer, plot_results, get_latest_run, check_git_status, check_file, increment_dir, print_mutation) -from utils.google_utils import attempt_download -from utils.torch_utils import init_seeds, ModelEMA, select_device +from yolo5.utils.google_utils import attempt_download +from yolo5.utils.torch_utils import init_seeds, ModelEMA, select_device # Hyperparameters hyp = {'lr0': 0.01, # initial learning rate (SGD=1E-2, Adam=1E-3) diff --git a/utils/__init__.py b/yolo5/utils/__init__.py similarity index 100% rename from utils/__init__.py rename to yolo5/utils/__init__.py diff --git a/utils/activations.py b/yolo5/utils/activations.py similarity index 100% rename from utils/activations.py rename to yolo5/utils/activations.py diff --git a/utils/datasets.py b/yolo5/utils/datasets.py similarity index 99% rename from utils/datasets.py rename to yolo5/utils/datasets.py index 169a518e0697..773db8d22885 100755 --- a/utils/datasets.py +++ b/yolo5/utils/datasets.py @@ -14,7 +14,7 @@ from torch.utils.data import Dataset from tqdm import tqdm -from utils.general import xyxy2xywh, xywh2xyxy, torch_distributed_zero_first +from yolo5.utils.general import xyxy2xywh, xywh2xyxy, torch_distributed_zero_first help_url = 'https://github.com/ultralytics/yolov5/wiki/Train-Custom-Data' img_formats = ['.bmp', '.jpg', '.jpeg', '.png', '.tif', '.tiff', '.dng'] diff --git a/utils/general.py b/yolo5/utils/general.py similarity index 99% rename from utils/general.py rename to yolo5/utils/general.py index ca1225a09e48..cdd7586dade7 100755 --- a/utils/general.py +++ b/yolo5/utils/general.py @@ -22,7 +22,7 @@ from scipy.signal import butter, filtfilt from tqdm import tqdm -from utils.torch_utils import init_seeds, is_parallel +from yolo5.utils.torch_utils import init_seeds, is_parallel # Set printoptions torch.set_printoptions(linewidth=320, precision=5, profile='long') @@ -763,7 +763,7 @@ def print_results(k): if isinstance(path, str): # *.yaml file with open(path) as f: data_dict = yaml.load(f, Loader=yaml.FullLoader) # model dict - from utils.datasets import LoadImagesAndLabels + from yolo5.utils.datasets import LoadImagesAndLabels dataset = LoadImagesAndLabels(data_dict['train'], augment=True, rect=True) else: dataset = path # dataset diff --git a/utils/google_utils.py b/yolo5/utils/google_utils.py similarity index 100% rename from utils/google_utils.py rename to yolo5/utils/google_utils.py diff --git a/utils/torch_utils.py b/yolo5/utils/torch_utils.py similarity index 99% rename from utils/torch_utils.py rename to yolo5/utils/torch_utils.py index f61d29ec191f..e87b6db40115 100644 --- a/utils/torch_utils.py +++ b/yolo5/utils/torch_utils.py @@ -11,6 +11,11 @@ def init_seeds(seed=0): + """ + Example: + + >>> init_seeds(42) + """ torch.manual_seed(seed) # Speed-reproducibility tradeoff https://pytorch.org/docs/stable/notes/randomness.html