From 28569ced8a3fc10c6c91201c4f6dc8bca6df57cb Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Tue, 19 Sep 2023 20:16:38 +0200 Subject: [PATCH] `ultralytics 8.0.182` remove deprecated `pkg_resources` (#4979) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .github/ISSUE_TEMPLATE/bug-report.yml | 27 ++++-- docker/Dockerfile-runner | 4 +- docs/reference/utils/checks.md | 12 +++ ultralytics/__init__.py | 2 +- ultralytics/cfg/__init__.py | 2 +- ultralytics/engine/exporter.py | 4 +- ultralytics/models/fastsam/prompt.py | 2 +- ultralytics/utils/callbacks/base.py | 5 -- ultralytics/utils/checks.py | 117 ++++++++++++++++++++------ ultralytics/utils/torch_utils.py | 4 +- 10 files changed, 134 insertions(+), 45 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug-report.yml b/.github/ISSUE_TEMPLATE/bug-report.yml index d611ca7876..e0c85ee393 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 a10af7a6ef..c0f8659b7c 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 e44935626d..42db921cfd 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 a84d25585c..83e43fb54a 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 1e72e2db17..eb252ed11a 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 5c43edc60b..28828df1af 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 9d5ae253c9..d09e978c96 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 2e676bf373..ace8bfbf63 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 e3c6bc76c6..ddd6ccc8e7 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 b1e7b3feae..9a35946a74 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')