diff --git a/.github/ISSUE_TEMPLATE/bug-report.yml b/.github/ISSUE_TEMPLATE/bug-report.yml
index d611ca78..e0c85ee3 100644
--- a/.github/ISSUE_TEMPLATE/bug-report.yml
+++ b/.github/ISSUE_TEMPLATE/bug-report.yml
@@ -25,13 +25,14 @@ body:
Please select the part of YOLOv8 where you found the bug.
multiple: true
options:
- - "Training"
- - "Validation"
- - "Detection"
+ - "Install"
+ - "Train"
+ - "Val"
+ - "Predict"
- "Export"
- - "PyTorch Hub"
- "Multi-GPU"
- - "Evolution"
+ - "Augmentation"
+ - "Hyperparameter Tuning"
- "Integrations"
- "Other"
validations:
@@ -51,9 +52,19 @@ body:
label: Environment
description: Please specify the software and hardware you used to produce the bug.
placeholder: |
- - YOLO: Ultralytics YOLOv8.0.21 🚀 Python-3.8.10 torch-1.13.1+cu117 CUDA:0 (A100-SXM-80GB, 81251MiB)
- - OS: Ubuntu 20.04
- - Python: 3.8.10
+ Paste output of `yolo checks` or `ultralytics.checks()` commands:
+ ```
+ Ultralytics YOLOv8.0.181 🚀 Python-3.11.2 torch-2.0.1 CPU (Apple M2)
+ Setup complete ✅ (8 CPUs, 16.0 GB RAM, 266.5/460.4 GB disk)
+
+ OS macOS-13.5.2
+ Environment Jupyter
+ Python 3.11.2
+ Install git
+ RAM 16.00 GB
+ CPU Apple M2
+ CUDA None
+ ```
validations:
required: false
diff --git a/docker/Dockerfile-runner b/docker/Dockerfile-runner
index a10af7a6..c0f8659b 100644
--- a/docker/Dockerfile-runner
+++ b/docker/Dockerfile-runner
@@ -9,8 +9,8 @@ FROM ultralytics/ultralytics:latest
WORKDIR /actions-runner
# Download and unpack the latest runner from https://github.com/actions/runner
-RUN FILENAME=actions-runner-linux-x64-2.308.0.tar.gz && \
- curl -o $FILENAME -L https://github.com/actions/runner/releases/download/v2.308.0/$FILENAME && \
+RUN FILENAME=actions-runner-linux-x64-2.309.0.tar.gz && \
+ curl -o $FILENAME -L https://github.com/actions/runner/releases/download/v2.309.0/$FILENAME && \
tar xzf $FILENAME && \
rm $FILENAME
diff --git a/docs/reference/utils/checks.md b/docs/reference/utils/checks.md
index e4493562..42db921c 100644
--- a/docs/reference/utils/checks.md
+++ b/docs/reference/utils/checks.md
@@ -9,6 +9,14 @@ keywords: Ultralytics, utility checks, ASCII, check_version, pip_update, check_p
Full source code for this file is available at [https://github.com/ultralytics/ultralytics/blob/main/ultralytics/utils/checks.py](https://github.com/ultralytics/ultralytics/blob/main/ultralytics/utils/checks.py). Help us fix any issues you see by submitting a [Pull Request](https://docs.ultralytics.com/help/contributing/) 🛠️. Thank you 🙏!
+---
+## ::: ultralytics.utils.checks.parse_requirements
+
+
+---
+## ::: ultralytics.utils.checks.parse_version
+
+
---
## ::: ultralytics.utils.checks.is_ascii
@@ -69,6 +77,10 @@ keywords: Ultralytics, utility checks, ASCII, check_version, pip_update, check_p
## ::: ultralytics.utils.checks.check_yolo
+---
+## ::: ultralytics.utils.checks.collect_system_info
+
+
---
## ::: ultralytics.utils.checks.check_amp
diff --git a/ultralytics/__init__.py b/ultralytics/__init__.py
index a84d2558..83e43fb5 100644
--- a/ultralytics/__init__.py
+++ b/ultralytics/__init__.py
@@ -1,6 +1,6 @@
# Ultralytics YOLO 🚀, AGPL-3.0 license
-__version__ = '8.0.181'
+__version__ = '8.0.182'
from ultralytics.models import RTDETR, SAM, YOLO
from ultralytics.models.fastsam import FastSAM
diff --git a/ultralytics/cfg/__init__.py b/ultralytics/cfg/__init__.py
index 1e72e2db..eb252ed1 100644
--- a/ultralytics/cfg/__init__.py
+++ b/ultralytics/cfg/__init__.py
@@ -333,7 +333,7 @@ def entrypoint(debug=''):
special = {
'help': lambda: LOGGER.info(CLI_HELP_MSG),
- 'checks': checks.check_yolo,
+ 'checks': checks.collect_system_info,
'version': lambda: LOGGER.info(__version__),
'settings': lambda: handle_yolo_settings(args[1:]),
'cfg': lambda: yaml_print(DEFAULT_CFG_PATH),
diff --git a/ultralytics/engine/exporter.py b/ultralytics/engine/exporter.py
index 5c43edc6..28828df1 100644
--- a/ultralytics/engine/exporter.py
+++ b/ultralytics/engine/exporter.py
@@ -143,6 +143,9 @@ class Exporter:
_callbacks (list, optional): List of callback functions. Defaults to None.
"""
self.args = get_cfg(cfg, overrides)
+ if self.args.format.lower() in ('coreml', 'mlmodel'): # fix attempt for protobuf<3.20.x errors
+ os.environ['PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION'] = 'python' # must run before TensorBoard callback
+
self.callbacks = _callbacks or callbacks.get_default_callbacks()
callbacks.add_integration_callbacks(self)
@@ -155,7 +158,6 @@ class Exporter:
if format in ('tensorrt', 'trt'): # 'engine' aliases
format = 'engine'
if format in ('mlmodel', 'mlpackage', 'mlprogram', 'apple', 'ios', 'coreml'): # 'coreml' aliases
- os.environ['PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION'] = 'python' # fix attempt for protobuf<3.20.x errors
format = 'coreml'
fmts = tuple(export_formats()['Argument'][1:]) # available export formats
flags = [x == format for x in fmts]
diff --git a/ultralytics/models/fastsam/prompt.py b/ultralytics/models/fastsam/prompt.py
index 9d5ae253..d09e978c 100644
--- a/ultralytics/models/fastsam/prompt.py
+++ b/ultralytics/models/fastsam/prompt.py
@@ -143,7 +143,7 @@ class FastSAMPrompt:
save_path = Path(output) / result_name
save_path.parent.mkdir(exist_ok=True, parents=True)
- image = Image.frombytes('RGB', fig.canvas.get_width_height(), fig.canvas.tostring_rgb())
+ image = Image.frombytes('RGB', fig.canvas.get_width_height(), fig.canvas.buffer_rgba())
image.save(save_path)
plt.close()
pbar.set_description(f'Saving {result_name} to {save_path}')
diff --git a/ultralytics/utils/callbacks/base.py b/ultralytics/utils/callbacks/base.py
index 2e676bf3..ace8bfbf 100644
--- a/ultralytics/utils/callbacks/base.py
+++ b/ultralytics/utils/callbacks/base.py
@@ -213,11 +213,6 @@ def add_integration_callbacks(instance):
from .wb import callbacks as wb_cb
callbacks_list.extend([clear_cb, comet_cb, dvc_cb, mlflow_cb, neptune_cb, tune_cb, tb_cb, wb_cb])
- # Load export callbacks (patch to avoid CoreML protobuf error)
- if 'Exporter' in instance.__class__.__name__:
- from .tensorboard import callbacks as tb_cb
- callbacks_list.append(tb_cb)
-
# Add the callbacks to the callbacks dictionary
for callbacks in callbacks_list:
for k, v in callbacks.items():
diff --git a/ultralytics/utils/checks.py b/ultralytics/utils/checks.py
index e3c6bc76..ddd6ccc8 100644
--- a/ultralytics/utils/checks.py
+++ b/ultralytics/utils/checks.py
@@ -9,20 +9,60 @@ import platform
import re
import shutil
import subprocess
+import sys
import time
+from importlib.metadata import PackageNotFoundError, version
from pathlib import Path
from typing import Optional
import cv2
import numpy as np
-import pkg_resources as pkg
import requests
import torch
from matplotlib import font_manager
-from ultralytics.utils import (ASSETS, AUTOINSTALL, LINUX, LOGGER, ONLINE, ROOT, USER_CONFIG_DIR, ThreadingLocked,
- TryExcept, clean_url, colorstr, downloads, emojis, is_colab, is_docker, is_jupyter,
- is_kaggle, is_online, is_pip_package, url2file)
+from ultralytics.utils import (ASSETS, AUTOINSTALL, LINUX, LOGGER, ONLINE, ROOT, USER_CONFIG_DIR, SimpleNamespace,
+ ThreadingLocked, TryExcept, clean_url, colorstr, downloads, emojis, is_colab, is_docker,
+ is_jupyter, is_kaggle, is_online, is_pip_package, url2file)
+
+
+def parse_requirements(file_path=ROOT.parent / 'requirements.txt'):
+ """
+ Parse a requirements.txt file, ignoring lines that start with '#' and any text after '#'.
+
+ Args:
+ file_path (Path): Path to the requirements.txt file.
+
+ Returns:
+ (List[Dict[str, str]]): List of parsed requirements as dictionaries with `name` and `specifier` keys.
+ """
+
+ requirements = []
+ for line in Path(file_path).read_text().splitlines():
+ line = line.strip()
+ if line and not line.startswith('#'):
+ line = line.split('#')[0].strip() # ignore inline comments
+ match = re.match(r'([a-zA-Z0-9-_]+)([<>!=~]+.*)?', line)
+ if match:
+ requirements.append(SimpleNamespace(name=match[1], specifier=match[2].strip() if match[2] else ''))
+
+ return requirements
+
+
+def parse_version(v='0.0.0') -> tuple:
+ """
+ Convert a version string to a tuple of integers, also returning any extra non-numeric string attached to the version.
+
+ Args:
+ v (str): Version string, i.e. '2.0.1+cpu'
+
+ Returns:
+ (tuple): Tuple of integers representing the numeric part of the version and the extra string, i.e. (2, 0, 1)
+ """
+ correct = [True if x == '.' else x.isdigit() for x in v] # first non-number index
+ if False in correct:
+ v = v[:correct.index(False)]
+ return tuple(map(int, v.split('.'))) # '2.0.1+cpu' -> (2, 0, 1)
def is_ascii(s) -> bool:
@@ -121,24 +161,33 @@ def check_version(current: str = '0.0.0',
# check if current version is between 20.04 (inclusive) and 22.04 (exclusive)
check_version(current='21.10', required='>20.04,<22.04')
"""
- current = pkg.parse_version(current)
+ if not required:
+ return True # in case required is '' or None
+
+ # import pkg_resources as pkg
+ # current = pkg.parse_version(current)
+ current = parse_version(current) # '1.2.3' -> (1, 2, 3)
+
constraints = re.findall(r'([<>!=]{1,2}\s*\d+\.\d+)', required) or [f'>={required}']
result = True
for constraint in constraints:
- op, version = re.match(r'([<>!=]{1,2})\s*(\d+\.\d+)', constraint).groups()
- version = pkg.parse_version(version)
- if op == '==' and current != version:
+ op, v = re.match(r'([<>!=]{1,2})\s*(\d+\.\d+)', constraint).groups()
+
+ # v = pkg.parse_version(v)
+ v = parse_version(v) # '1.2.3' -> (1, 2, 3)
+
+ if op == '==' and current != v:
result = False
- elif op == '!=' and current == version:
+ elif op == '!=' and current == v:
result = False
- elif op == '>=' and not (current >= version):
+ elif op == '>=' and not (current >= v):
result = False
- elif op == '<=' and not (current <= version):
+ elif op == '<=' and not (current <= v):
result = False
- elif op == '>' and not (current > version):
+ elif op == '>' and not (current > v):
result = False
- elif op == '<' and not (current < version):
+ elif op == '<' and not (current < v):
result = False
if not result:
warning_message = f'WARNING ⚠️ {name}{op}{required} is required, but {name}=={current} is currently installed'
@@ -177,7 +226,7 @@ def check_pip_update_available():
with contextlib.suppress(Exception):
from ultralytics import __version__
latest = check_latest_pypi_version()
- if pkg.parse_version(__version__) < pkg.parse_version(latest): # update is available
+ if check_version(__version__, f'<{latest}'): # check if current version is < latest version
LOGGER.info(f'New https://pypi.org/project/ultralytics/{latest} available 😃 '
f"Update with 'pip install -U ultralytics'")
return True
@@ -253,29 +302,25 @@ def check_requirements(requirements=ROOT.parent / 'requirements.txt', exclude=()
check_requirements(['numpy', 'ultralytics>=8.0.0'])
```
"""
+
prefix = colorstr('red', 'bold', 'requirements:')
check_python() # check python version
check_torchvision() # check torch-torchvision compatibility
if isinstance(requirements, Path): # requirements.txt file
file = requirements.resolve()
assert file.exists(), f'{prefix} {file} not found, check failed.'
- with file.open() as f:
- requirements = [f'{x.name}{x.specifier}' for x in pkg.parse_requirements(f) if x.name not in exclude]
+ requirements = [f'{x.name}{x.specifier}' for x in parse_requirements(file) if x.name not in exclude]
elif isinstance(requirements, str):
requirements = [requirements]
pkgs = []
for r in requirements:
r_stripped = r.split('/')[-1].replace('.git', '') # replace git+https://org/repo.git -> 'repo'
+ match = re.match(r'([a-zA-Z0-9-_]+)([<>!=~]+.*)?', r_stripped)
+ name, required = match[1], match[2].strip() if match[2] else ''
try:
- pkg.require(r_stripped) # exception if requirements not met
- except pkg.DistributionNotFound:
- try: # attempt to import (slower but more accurate)
- import importlib
- importlib.import_module(next(pkg.parse_requirements(r_stripped)).name)
- except ImportError:
- pkgs.append(r)
- except pkg.VersionConflict:
+ assert check_version(version(name), required) # exception if requirements not met
+ except (AssertionError, PackageNotFoundError):
pkgs.append(r)
s = ' '.join(f'"{x}"' for x in pkgs) # console string
@@ -430,6 +475,30 @@ def check_yolo(verbose=True, device=''):
LOGGER.info(f'Setup complete ✅ {s}')
+def collect_system_info():
+ """Collect and print relevant system information including OS, Python, RAM, CPU, and CUDA."""
+
+ import psutil
+
+ from ultralytics.utils import ENVIRONMENT, is_git_dir
+ from ultralytics.utils.torch_utils import get_cpu_info
+
+ ram_info = psutil.virtual_memory().total / (1024 ** 3) # Convert bytes to GB
+ check_yolo()
+ LOGGER.info(f"\n{'OS':<20}{platform.platform()}\n"
+ f"{'Environment':<20}{ENVIRONMENT}\n"
+ f"{'Python':<20}{sys.version.split()[0]}\n"
+ f"{'Install':<20}{'git' if is_git_dir() else 'pip' if is_pip_package() else 'other'}\n"
+ f"{'RAM':<20}{ram_info:.2f} GB\n"
+ f"{'CPU':<20}{get_cpu_info()}\n"
+ f"{'CUDA':<20}{torch.version.cuda if torch and torch.cuda.is_available() else None}\n")
+
+ for r in parse_requirements():
+ current = version(r.name)
+ is_met = '✅ ' if check_version(current, r.specifier) else '❌ '
+ LOGGER.info(f'{r.name:<20}{is_met}{current}{r.specifier}')
+
+
def check_amp(model):
"""
This function checks the PyTorch Automatic Mixed Precision (AMP) functionality of a YOLOv8 model.
diff --git a/ultralytics/utils/torch_utils.py b/ultralytics/utils/torch_utils.py
index b1e7b3fe..9a35946a 100644
--- a/ultralytics/utils/torch_utils.py
+++ b/ultralytics/utils/torch_utils.py
@@ -76,11 +76,11 @@ def select_device(device='', batch=0, newline=False, verbose=True):
verbose (bool, optional): If True, logs the device information. Defaults to True.
Returns:
- torch.device: Selected device.
+ (torch.device): Selected device.
Raises:
ValueError: If the specified device is not available or if the batch size is not a multiple of the number of
- devices when using multiple GPUs.
+ devices when using multiple GPUs.
Examples:
>>> select_device('cuda:0')