Merge branch 'main' into LeYOLO-Docs-Page

LeYOLO-Docs-Page
Ultralytics Assistant 3 months ago committed by GitHub
commit 3402ea8268
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 2
      .github/workflows/ci.yaml
  2. 2
      .github/workflows/docs.yml
  3. 83
      .github/workflows/publish.yml
  4. 2
      docs/build_docs.py
  5. 166
      docs/mkdocs_github_authors.yaml
  6. 2
      examples/heatmaps.ipynb
  7. 2
      examples/object_counting.ipynb
  8. 3
      pyproject.toml
  9. 5
      tests/test_explorer.py
  10. 2
      tests/test_python.py
  11. 2
      ultralytics/__init__.py
  12. 2
      ultralytics/data/converter.py
  13. 2
      ultralytics/data/dataset.py
  14. 2
      ultralytics/data/split_dota.py
  15. 54
      ultralytics/engine/exporter.py
  16. 2
      ultralytics/engine/predictor.py
  17. 5
      ultralytics/engine/validator.py
  18. 17
      ultralytics/hub/utils.py
  19. 2
      ultralytics/models/fastsam/predict.py
  20. 2
      ultralytics/models/sam/modules/blocks.py
  21. 4
      ultralytics/nn/modules/block.py
  22. 4
      ultralytics/nn/modules/head.py
  23. 2
      ultralytics/solutions/parking_management.py
  24. 6
      ultralytics/utils/benchmarks.py
  25. 11
      ultralytics/utils/checks.py
  26. 2
      ultralytics/utils/metrics.py
  27. 2
      ultralytics/utils/ops.py
  28. 7
      ultralytics/utils/torch_utils.py

@ -156,7 +156,7 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
os: [ubuntu-latest, macos-14] os: [ubuntu-latest, macos-14, windows-latest]
python-version: ["3.11"] python-version: ["3.11"]
torch: [latest] torch: [latest]
include: include:

@ -46,7 +46,7 @@ jobs:
run: pip install ruff black tqdm mkdocs-material "mkdocstrings[python]" mkdocs-jupyter mkdocs-redirects mkdocs-ultralytics-plugin mkdocs-macros-plugin run: pip install ruff black tqdm mkdocs-material "mkdocstrings[python]" mkdocs-jupyter mkdocs-redirects mkdocs-ultralytics-plugin mkdocs-macros-plugin
- name: Ruff fixes - name: Ruff fixes
continue-on-error: true continue-on-error: true
run: ruff check --fix --fix-unsafe --select D --ignore=D100,D104,D203,D205,D212,D213,D401,D406,D407,D413 . run: ruff check --fix --unsafe-fixes --select D --ignore=D100,D104,D203,D205,D212,D213,D401,D406,D407,D413 .
- name: Update Docs Reference Section and Push Changes - name: Update Docs Reference Section and Push Changes
if: github.event_name == 'pull_request_target' if: github.event_name == 'pull_request_target'
run: | run: |

@ -34,7 +34,7 @@ jobs:
- name: Install dependencies - name: Install dependencies
run: | run: |
python -m pip install --upgrade pip wheel python -m pip install --upgrade pip wheel
pip install openai requests build twine toml pip install requests build twine toml
- name: Check PyPI version - name: Check PyPI version
shell: python shell: python
run: | run: |
@ -79,8 +79,8 @@ jobs:
publish = True # First release publish = True # First release
os.system(f'echo "increment={publish}" >> $GITHUB_OUTPUT') os.system(f'echo "increment={publish}" >> $GITHUB_OUTPUT')
os.system(f'echo "version={local_version}" >> $GITHUB_OUTPUT') os.system(f'echo "current_tag=v{local_version}" >> $GITHUB_OUTPUT')
os.system(f'echo "previous_version={online_version or "N/A"}" >> $GITHUB_OUTPUT') os.system(f'echo "previous_tag=v{online_version}" >> $GITHUB_OUTPUT')
if publish: if publish:
print('Ready to publish new version to PyPI ✅.') print('Ready to publish new version to PyPI ✅.')
@ -88,81 +88,18 @@ jobs:
- name: Publish new tag - name: Publish new tag
if: (github.event_name == 'push' || github.event.inputs.pypi == 'true') && steps.check_pypi.outputs.increment == 'True' if: (github.event_name == 'push' || github.event.inputs.pypi == 'true') && steps.check_pypi.outputs.increment == 'True'
run: | run: |
git tag -a "v${{ steps.check_pypi.outputs.version }}" -m "$(git log -1 --pretty=%B)" # i.e. "v0.1.2 commit message" git tag -a "${{ steps.check_pypi.outputs.current_tag }}" -m "$(git log -1 --pretty=%B)" # i.e. "v0.1.2 commit message"
git push origin "v${{ steps.check_pypi.outputs.version }}" git push origin "${{ steps.check_pypi.outputs.current_tag }}"
- name: Publish new release - name: Publish new release
if: (github.event_name == 'push' || github.event.inputs.pypi == 'true') && steps.check_pypi.outputs.increment == 'True' if: (github.event_name == 'push' || github.event.inputs.pypi == 'true') && steps.check_pypi.outputs.increment == 'True'
env: env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
GITHUB_TOKEN: ${{ secrets.PERSONAL_ACCESS_TOKEN || secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.PERSONAL_ACCESS_TOKEN || secrets.GITHUB_TOKEN }}
CURRENT_TAG: ${{ steps.check_pypi.outputs.version }} CURRENT_TAG: ${{ steps.check_pypi.outputs.current_tag }}
PREVIOUS_TAG: ${{ steps.check_pypi.outputs.previous_version }} PREVIOUS_TAG: ${{ steps.check_pypi.outputs.previous_tag }}
shell: python
run: | run: |
import openai curl -s "https://raw.githubusercontent.com/ultralytics/actions/main/utils/summarize_release.py" | python -
import os shell: bash
import requests
import json
import subprocess
# Retrieve environment variables
OPENAI_API_KEY = os.getenv('OPENAI_API_KEY')
GITHUB_TOKEN = os.getenv('GITHUB_TOKEN')
CURRENT_TAG = os.getenv('CURRENT_TAG')
PREVIOUS_TAG = os.getenv('PREVIOUS_TAG')
# Check for required environment variables
if not all([OPENAI_API_KEY, GITHUB_TOKEN, CURRENT_TAG, PREVIOUS_TAG]):
raise ValueError("One or more required environment variables are missing.")
latest_tag = f"v{CURRENT_TAG}"
previous_tag = f"v{PREVIOUS_TAG}"
repo = os.getenv('GITHUB_REPOSITORY')
headers = {"Authorization": f"token {GITHUB_TOKEN}", "Accept": "application/vnd.github.v3.diff"}
# Get the diff between the tags
url = f"https://api.github.com/repos/{repo}/compare/{previous_tag}...{latest_tag}"
response = requests.get(url, headers=headers)
diff = response.text if response.status_code == 200 else f"Failed to get diff: {response.content}"
# Get summary
messages = [
{
"role": "system",
"content": "You are an Ultralytics AI assistant skilled in software development and technical communication. Your task is to summarize GitHub releases in a way that is detailed, accurate, and understandable to both expert developers and non-expert users. Focus on highlighting the key changes and their impact in simple and intuitive terms."
},
{
"role": "user",
"content": f"Summarize the updates made in the '{latest_tag}' tag, focusing on major changes, their purpose, and potential impact. Keep the summary clear and suitable for a broad audience. Add emojis to enliven the summary. Reply directly with a summary along these example guidelines, though feel free to adjust as appropriate:\n\n"
f"## 🌟 Summary (single-line synopsis)\n"
f"## 📊 Key Changes (bullet points highlighting any major changes)\n"
f"## 🎯 Purpose & Impact (bullet points explaining any benefits and potential impact to users)\n"
f"\n\nHere's the release diff:\n\n{diff[:300000]}",
}
]
client = openai.OpenAI(api_key=OPENAI_API_KEY)
completion = client.chat.completions.create(model="gpt-4o-2024-08-06", messages=messages)
summary = completion.choices[0].message.content.strip()
# Get the latest commit message
commit_message = subprocess.run(['git', 'log', '-1', '--pretty=%B'], check=True, text=True, capture_output=True).stdout.split("\n")[0].strip()
# Prepare release data
release = {
'tag_name': latest_tag,
'name': f"{latest_tag} - {commit_message}",
'body': summary,
'draft': False,
'prerelease': False
}
# Create the release on GitHub
release_url = f"https://api.github.com/repos/{repo}/releases"
release_response = requests.post(release_url, headers=headers, data=json.dumps(release))
if release_response.status_code == 201:
print(f'Successfully created release {latest_tag}')
else:
print(f'Failed to create release {latest_tag}: {release_response.content}')
- name: Publish to PyPI - name: Publish to PyPI
continue-on-error: true continue-on-error: true
if: (github.event_name == 'push' || github.event.inputs.pypi == 'true') && steps.check_pypi.outputs.increment == 'True' if: (github.event_name == 'push' || github.event.inputs.pypi == 'true') && steps.check_pypi.outputs.increment == 'True'
@ -193,7 +130,7 @@ jobs:
uses: slackapi/slack-github-action@v1.26.0 uses: slackapi/slack-github-action@v1.26.0
with: with:
payload: | payload: |
{"text": "<!channel> GitHub Actions success for ${{ github.workflow }} ✅\n\n\n*Repository:* https://github.com/${{ github.repository }}\n*Action:* https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}\n*Author:* ${{ github.actor }}\n*Event:* NEW '${{ github.repository }} v${{ steps.check_pypi.outputs.version }}' pip package published 😃\n*Job Status:* ${{ job.status }}\n*Pull Request:* <https://github.com/${{ github.repository }}/pull/${{ env.PR_NUMBER }}> ${{ env.PR_TITLE }}\n"} {"text": "<!channel> GitHub Actions success for ${{ github.workflow }} ✅\n\n\n*Repository:* https://github.com/${{ github.repository }}\n*Action:* https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}\n*Author:* ${{ github.actor }}\n*Event:* NEW '${{ github.repository }} ${{ steps.check_pypi.outputs.current_tag }}' pip package published 😃\n*Job Status:* ${{ job.status }}\n*Pull Request:* <https://github.com/${{ github.repository }}/pull/${{ env.PR_NUMBER }}> ${{ env.PR_TITLE }}\n"}
env: env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL_YOLO }} SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL_YOLO }}
- name: Notify on Slack (Failure) - name: Notify on Slack (Failure)

@ -164,7 +164,7 @@ def update_docs_html():
# Convert plaintext links to HTML hyperlinks # Convert plaintext links to HTML hyperlinks
files_modified = 0 files_modified = 0
for html_file in tqdm(SITE.rglob("*.html"), desc="Converting plaintext links"): for html_file in tqdm(SITE.rglob("*.html"), desc="Converting plaintext links"):
with open(html_file, "r", encoding="utf-8") as file: with open(html_file, encoding="utf-8") as file:
content = file.read() content = file.read()
updated_content = convert_plaintext_links_to_html(content) updated_content = convert_plaintext_links_to_html(content)
if updated_content != content: if updated_content != content:

@ -1,46 +1,120 @@
116908874+jk4e@users.noreply.github.com: jk4e 116908874+jk4e@users.noreply.github.com:
1185102784@qq.com: Laughing-q avatar: https://avatars.githubusercontent.com/u/116908874?v=4
130829914+IvorZhu331@users.noreply.github.com: IvorZhu331 username: jk4e
135830346+UltralyticsAssistant@users.noreply.github.com: UltralyticsAssistant 130829914+IvorZhu331@users.noreply.github.com:
1579093407@qq.com: YOLOv5-Magic avatar: https://avatars.githubusercontent.com/u/130829914?v=4
17216799+ouphi@users.noreply.github.com: ouphi username: IvorZhu331
17316848+maianumerosky@users.noreply.github.com: maianumerosky 135830346+UltralyticsAssistant@users.noreply.github.com:
34196005+fcakyon@users.noreply.github.com: fcakyon avatar: https://avatars.githubusercontent.com/u/135830346?v=4
37276661+capjamesg@users.noreply.github.com: capjamesg username: UltralyticsAssistant
39910262+ChaoningZhang@users.noreply.github.com: ChaoningZhang 1579093407@qq.com:
40165666+berry-ding@users.noreply.github.com: berry-ding avatar: https://avatars.githubusercontent.com/u/160490334?v=4
47978446+sergiuwaxmann@users.noreply.github.com: sergiuwaxmann username: YOLOv5-Magic
48149018+zhixuwei@users.noreply.github.com: zhixuwei 17216799+ouphi@users.noreply.github.com:
49699333+dependabot[bot]@users.noreply.github.com: dependabot avatar: https://avatars.githubusercontent.com/u/17216799?v=4
52826299+Chayanonjackal@users.noreply.github.com: Chayanonjackal username: ouphi
53246858+hasanghaffari93@users.noreply.github.com: hasanghaffari93 17316848+maianumerosky@users.noreply.github.com:
60036186+mfloto@users.noreply.github.com: mfloto avatar: https://avatars.githubusercontent.com/u/17316848?v=4
61612323+Laughing-q@users.noreply.github.com: Laughing-q username: maianumerosky
62214284+Burhan-Q@users.noreply.github.com: Burhan-Q 34196005+fcakyon@users.noreply.github.com:
68285002+Kayzwer@users.noreply.github.com: Kayzwer avatar: https://avatars.githubusercontent.com/u/34196005?v=4
75611662+tensorturtle@users.noreply.github.com: tensorturtle username: fcakyon
78843978+Skillnoob@users.noreply.github.com: Skillnoob 37276661+capjamesg@users.noreply.github.com:
79740115+0xSynapse@users.noreply.github.com: 0xSynapse avatar: https://avatars.githubusercontent.com/u/37276661?v=4
Francesco.mttl@gmail.com: ambitious-octopus username: capjamesg
abirami.vina@gmail.com: abirami-vina 39910262+ChaoningZhang@users.noreply.github.com:
ahmelsamahy@gmail.com: Ahelsamahy avatar: https://avatars.githubusercontent.com/u/39910262?v=4
andrei.kochin@intel.com: andrei-kochin username: ChaoningZhang
ayush.chaurarsia@gmail.com: AyushExel 40165666+berry-ding@users.noreply.github.com:
chr043416@gmail.com: RizwanMunawar avatar: https://avatars.githubusercontent.com/u/40165666?v=4
glenn.jocher@ultralytics.com: glenn-jocher username: berry-ding
hnliu_2@stu.xidian.edu.cn: null 47978446+sergiuwaxmann@users.noreply.github.com:
jpedrofonseca_94@hotmail.com: null avatar: https://avatars.githubusercontent.com/u/47978446?v=4
k-2feng@hotmail.com: null username: sergiuwaxmann
lakshantha@ultralytics.com: lakshanthad 48149018+zhixuwei@users.noreply.github.com:
lakshanthad@yahoo.com: lakshanthad avatar: https://avatars.githubusercontent.com/u/48149018?v=4
muhammadrizwanmunawar123@gmail.com: RizwanMunawar username: zhixuwei
not.committed.yet: null 49699333+dependabot[bot]@users.noreply.github.com:
plashchynski@gmail.com: plashchynski avatar: https://avatars.githubusercontent.com/u/27347476?v=4
priytosh.revolution@live.com: priytosh-tripathi username: dependabot[bot]
rulosanti@gmail.com: null 53246858+hasanghaffari93@users.noreply.github.com:
shuizhuyuanluo@126.com: null avatar: https://avatars.githubusercontent.com/u/53246858?v=4
sometimesocrazy@gmail.com: null username: hasanghaffari93
stormsson@users.noreply.github.com: stormsson 60036186+mfloto@users.noreply.github.com:
waxmann.sergiu@me.com: sergiuwaxmann avatar: https://avatars.githubusercontent.com/u/60036186?v=4
web@ultralytics.com: UltralyticsAssistant username: mfloto
xinwang614@gmail.com: GreatV 61612323+Laughing-q@users.noreply.github.com:
avatar: https://avatars.githubusercontent.com/u/61612323?v=4
username: Laughing-q
62214284+Burhan-Q@users.noreply.github.com:
avatar: https://avatars.githubusercontent.com/u/62214284?v=4
username: Burhan-Q
68285002+Kayzwer@users.noreply.github.com:
avatar: https://avatars.githubusercontent.com/u/68285002?v=4
username: Kayzwer
75611662+tensorturtle@users.noreply.github.com:
avatar: https://avatars.githubusercontent.com/u/75611662?v=4
username: tensorturtle
78843978+Skillnoob@users.noreply.github.com:
avatar: https://avatars.githubusercontent.com/u/78843978?v=4
username: Skillnoob
79740115+0xSynapse@users.noreply.github.com:
avatar: https://avatars.githubusercontent.com/u/79740115?v=4
username: 0xSynapse
Francesco.mttl@gmail.com:
avatar: https://avatars.githubusercontent.com/u/3855193?v=4
username: ambitious-octopus
abirami.vina@gmail.com:
avatar: https://avatars.githubusercontent.com/u/25847604?v=4
username: abirami-vina
ahmelsamahy@gmail.com:
avatar: https://avatars.githubusercontent.com/u/10195309?v=4
username: Ahelsamahy
andrei.kochin@intel.com:
avatar: https://avatars.githubusercontent.com/u/72827868?v=4
username: andrei-kochin
ayush.chaurarsia@gmail.com:
avatar: https://avatars.githubusercontent.com/u/15766192?v=4
username: AyushExel
chr043416@gmail.com:
avatar: https://avatars.githubusercontent.com/u/62513924?v=4
username: RizwanMunawar
glenn.jocher@ultralytics.com:
avatar: https://avatars.githubusercontent.com/u/26833433?v=4
username: glenn-jocher
hnliu_2@stu.xidian.edu.cn:
avatar: null
username: null
jpedrofonseca_94@hotmail.com:
avatar: null
username: null
k-2feng@hotmail.com:
avatar: null
username: null
lakshanthad@yahoo.com:
avatar: https://avatars.githubusercontent.com/u/20147381?v=4
username: lakshanthad
muhammadrizwanmunawar123@gmail.com:
avatar: https://avatars.githubusercontent.com/u/62513924?v=4
username: RizwanMunawar
plashchynski@gmail.com:
avatar: https://avatars.githubusercontent.com/u/30833?v=4
username: plashchynski
priytosh.revolution@live.com:
avatar: https://avatars.githubusercontent.com/u/19519529?v=4
username: priytosh-tripathi
rulosanti@gmail.com:
avatar: null
username: null
shuizhuyuanluo@126.com:
avatar: null
username: null
sometimesocrazy@gmail.com:
avatar: null
username: null
stormsson@users.noreply.github.com:
avatar: https://avatars.githubusercontent.com/u/1133032?v=4
username: stormsson
xinwang614@gmail.com:
avatar: https://avatars.githubusercontent.com/u/17264618?v=4
username: GreatV

@ -116,7 +116,7 @@
" colormap=cv2.COLORMAP_PARULA,\n", " colormap=cv2.COLORMAP_PARULA,\n",
" view_img=True,\n", " view_img=True,\n",
" shape=\"circle\",\n", " shape=\"circle\",\n",
" classes_names=model.names,\n", " names=model.names,\n",
")\n", ")\n",
"\n", "\n",
"while cap.isOpened():\n", "while cap.isOpened():\n",

@ -129,7 +129,7 @@
"counter = solutions.ObjectCounter(\n", "counter = solutions.ObjectCounter(\n",
" view_img=True, # Display the image during processing\n", " view_img=True, # Display the image during processing\n",
" reg_pts=line_points, # Region of interest points\n", " reg_pts=line_points, # Region of interest points\n",
" classes_names=model.names, # Class names from the YOLO model\n", " names=model.names, # Class names from the YOLO model\n",
" draw_tracks=True, # Draw tracking lines for objects\n", " draw_tracks=True, # Draw tracking lines for objects\n",
" line_thickness=2, # Thickness of the lines drawn\n", " line_thickness=2, # Thickness of the lines drawn\n",
")\n", ")\n",

@ -71,6 +71,7 @@ dependencies = [
"pyyaml>=5.3.1", "pyyaml>=5.3.1",
"requests>=2.23.0", "requests>=2.23.0",
"scipy>=1.4.1", "scipy>=1.4.1",
"torch>=1.8.0,<2.4.0; sys_platform == 'win32'", # Windows CPU errors https://github.com/ultralytics/ultralytics/issues/15049
"torch>=1.8.0", "torch>=1.8.0",
"torchvision>=0.9.0", "torchvision>=0.9.0",
"tqdm>=4.64.0", # progress bars "tqdm>=4.64.0", # progress bars
@ -93,7 +94,7 @@ dev = [
"mkdocstrings[python]", "mkdocstrings[python]",
"mkdocs-jupyter", # notebooks "mkdocs-jupyter", # notebooks
"mkdocs-redirects", # 301 redirects "mkdocs-redirects", # 301 redirects
"mkdocs-ultralytics-plugin>=0.1.2", # for meta descriptions and images, dates and authors "mkdocs-ultralytics-plugin>=0.1.6", # for meta descriptions and images, dates and authors
"mkdocs-macros-plugin>=1.0.5" # duplicating content (i.e. export tables) in multiple places "mkdocs-macros-plugin>=1.0.5" # duplicating content (i.e. export tables) in multiple places
] ]
export = [ export = [

@ -5,9 +5,11 @@ import pytest
from ultralytics import Explorer from ultralytics import Explorer
from ultralytics.utils import ASSETS from ultralytics.utils import ASSETS
from ultralytics.utils.torch_utils import TORCH_1_13
@pytest.mark.slow @pytest.mark.slow
@pytest.mark.skipif(not TORCH_1_13, reason="Explorer requires torch>=1.13")
def test_similarity(): def test_similarity():
"""Test the correctness and response length of similarity calculations and SQL queries in the Explorer.""" """Test the correctness and response length of similarity calculations and SQL queries in the Explorer."""
exp = Explorer(data="coco8.yaml") exp = Explorer(data="coco8.yaml")
@ -25,6 +27,7 @@ def test_similarity():
@pytest.mark.slow @pytest.mark.slow
@pytest.mark.skipif(not TORCH_1_13, reason="Explorer requires torch>=1.13")
def test_det(): def test_det():
"""Test detection functionalities and verify embedding table includes bounding boxes.""" """Test detection functionalities and verify embedding table includes bounding boxes."""
exp = Explorer(data="coco8.yaml", model="yolov8n.pt") exp = Explorer(data="coco8.yaml", model="yolov8n.pt")
@ -38,6 +41,7 @@ def test_det():
@pytest.mark.slow @pytest.mark.slow
@pytest.mark.skipif(not TORCH_1_13, reason="Explorer requires torch>=1.13")
def test_seg(): def test_seg():
"""Test segmentation functionalities and ensure the embedding table includes segmentation masks.""" """Test segmentation functionalities and ensure the embedding table includes segmentation masks."""
exp = Explorer(data="coco8-seg.yaml", model="yolov8n-seg.pt") exp = Explorer(data="coco8-seg.yaml", model="yolov8n-seg.pt")
@ -50,6 +54,7 @@ def test_seg():
@pytest.mark.slow @pytest.mark.slow
@pytest.mark.skipif(not TORCH_1_13, reason="Explorer requires torch>=1.13")
def test_pose(): def test_pose():
"""Test pose estimation functionality and verify the embedding table includes keypoints.""" """Test pose estimation functionality and verify the embedding table includes keypoints."""
exp = Explorer(data="coco8-pose.yaml", model="yolov8n-pose.pt") exp = Explorer(data="coco8-pose.yaml", model="yolov8n-pose.pt")

@ -252,6 +252,8 @@ def test_labels_and_crops():
for r in results: for r in results:
im_name = Path(r.path).stem im_name = Path(r.path).stem
cls_idxs = r.boxes.cls.int().tolist() cls_idxs = r.boxes.cls.int().tolist()
# Check correct detections
assert cls_idxs == ([0, 0, 5, 0, 7] if r.path.endswith("bus.jpg") else [0, 0]) # bus.jpg and zidane.jpg classes
# Check label path # Check label path
labels = save_path / f"labels/{im_name}.txt" labels = save_path / f"labels/{im_name}.txt"
assert labels.exists() assert labels.exists()

@ -1,6 +1,6 @@
# Ultralytics YOLO 🚀, AGPL-3.0 license # Ultralytics YOLO 🚀, AGPL-3.0 license
__version__ = "8.2.84" __version__ = "8.2.86"
import os import os

@ -490,7 +490,7 @@ def convert_dota_to_yolo_obb(dota_root_path: str):
normalized_coords = [ normalized_coords = [
coords[i] / image_width if i % 2 == 0 else coords[i] / image_height for i in range(8) coords[i] / image_width if i % 2 == 0 else coords[i] / image_height for i in range(8)
] ]
formatted_coords = ["{:.6g}".format(coord) for coord in normalized_coords] formatted_coords = [f"{coord:.6g}" for coord in normalized_coords]
g.write(f"{class_idx} {' '.join(formatted_coords)}\n") g.write(f"{class_idx} {' '.join(formatted_coords)}\n")
for phase in ["train", "val"]: for phase in ["train", "val"]:

@ -296,7 +296,7 @@ class GroundingDataset(YOLODataset):
"""Loads annotations from a JSON file, filters, and normalizes bounding boxes for each image.""" """Loads annotations from a JSON file, filters, and normalizes bounding boxes for each image."""
labels = [] labels = []
LOGGER.info("Loading annotation file...") LOGGER.info("Loading annotation file...")
with open(self.json_file, "r") as f: with open(self.json_file) as f:
annotations = json.load(f) annotations = json.load(f)
images = {f'{x["id"]:d}': x for x in annotations["images"]} images = {f'{x["id"]:d}': x for x in annotations["images"]}
img_to_anns = defaultdict(list) img_to_anns = defaultdict(list)

@ -193,7 +193,7 @@ def crop_and_save(anno, windows, window_objs, im_dir, lb_dir, allow_background_i
with open(Path(lb_dir) / f"{new_name}.txt", "w") as f: with open(Path(lb_dir) / f"{new_name}.txt", "w") as f:
for lb in label: for lb in label:
formatted_coords = ["{:.6g}".format(coord) for coord in lb[1:]] formatted_coords = [f"{coord:.6g}" for coord in lb[1:]]
f.write(f"{int(lb[0])} {' '.join(formatted_coords)}\n") f.write(f"{int(lb[0])} {' '.join(formatted_coords)}\n")

@ -138,7 +138,7 @@ def try_export(inner_func):
LOGGER.info(f"{prefix} export success ✅ {dt.t:.1f}s, saved as '{f}' ({file_size(f):.1f} MB)") LOGGER.info(f"{prefix} export success ✅ {dt.t:.1f}s, saved as '{f}' ({file_size(f):.1f} MB)")
return f, model return f, model
except Exception as e: except Exception as e:
LOGGER.info(f"{prefix} export failure ❌ {dt.t:.1f}s: {e}") LOGGER.error(f"{prefix} export failure ❌ {dt.t:.1f}s: {e}")
raise e raise e
return outer_func return outer_func
@ -204,8 +204,8 @@ class Exporter:
self.args.half = False self.args.half = False
assert not self.args.dynamic, "half=True not compatible with dynamic=True, i.e. use only one." assert not self.args.dynamic, "half=True not compatible with dynamic=True, i.e. use only one."
self.imgsz = check_imgsz(self.args.imgsz, stride=model.stride, min_dim=2) # check image size self.imgsz = check_imgsz(self.args.imgsz, stride=model.stride, min_dim=2) # check image size
if self.args.int8 and (engine or xml): if self.args.int8 and engine:
self.args.dynamic = True # enforce dynamic to export TensorRT INT8; ensures ONNX is dynamic self.args.dynamic = True # enforce dynamic to export TensorRT INT8
if self.args.optimize: if self.args.optimize:
assert not ncnn, "optimize=True not compatible with format='ncnn', i.e. use optimize=False" assert not ncnn, "optimize=True not compatible with format='ncnn', i.e. use optimize=False"
assert self.device.type == "cpu", "optimize=True not compatible with cuda devices, i.e. use device='cpu'" assert self.device.type == "cpu", "optimize=True not compatible with cuda devices, i.e. use device='cpu'"
@ -248,6 +248,7 @@ class Exporter:
m.dynamic = self.args.dynamic m.dynamic = self.args.dynamic
m.export = True m.export = True
m.format = self.args.format m.format = self.args.format
m.max_det = self.args.max_det
elif isinstance(m, C2f) and not is_tf_format: elif isinstance(m, C2f) and not is_tf_format:
# EdgeTPU does not support FlexSplitV while split provides cleaner ONNX graph # EdgeTPU does not support FlexSplitV while split provides cleaner ONNX graph
m.forward = m.forward_split m.forward = m.forward_split
@ -353,18 +354,20 @@ class Exporter:
"""Build and return a dataloader suitable for calibration of INT8 models.""" """Build and return a dataloader suitable for calibration of INT8 models."""
LOGGER.info(f"{prefix} collecting INT8 calibration images from 'data={self.args.data}'") LOGGER.info(f"{prefix} collecting INT8 calibration images from 'data={self.args.data}'")
data = (check_cls_dataset if self.model.task == "classify" else check_det_dataset)(self.args.data) data = (check_cls_dataset if self.model.task == "classify" else check_det_dataset)(self.args.data)
# TensorRT INT8 calibration should use 2x batch size
batch = self.args.batch * (2 if self.args.format == "engine" else 1)
dataset = YOLODataset( dataset = YOLODataset(
data[self.args.split or "val"], data[self.args.split or "val"],
data=data, data=data,
task=self.model.task, task=self.model.task,
imgsz=self.imgsz[0], imgsz=self.imgsz[0],
augment=False, augment=False,
batch_size=self.args.batch * 2, # NOTE TensorRT INT8 calibration should use 2x batch size batch_size=batch,
) )
n = len(dataset) n = len(dataset)
if n < 300: if n < 300:
LOGGER.warning(f"{prefix} WARNING ⚠ >300 images recommended for INT8 calibration, found {n} images.") LOGGER.warning(f"{prefix} WARNING ⚠ >300 images recommended for INT8 calibration, found {n} images.")
return build_dataloader(dataset, batch=self.args.batch * 2, workers=0) # required for batch loading return build_dataloader(dataset, batch=batch, workers=0) # required for batch loading
@try_export @try_export
def export_torchscript(self, prefix=colorstr("TorchScript:")): def export_torchscript(self, prefix=colorstr("TorchScript:")):
@ -420,7 +423,6 @@ class Exporter:
# Checks # Checks
model_onnx = onnx.load(f) # load onnx model model_onnx = onnx.load(f) # load onnx model
# onnx.checker.check_model(model_onnx) # check onnx model
# Simplify # Simplify
if self.args.simplify: if self.args.simplify:
@ -430,10 +432,6 @@ class Exporter:
LOGGER.info(f"{prefix} slimming with onnxslim {onnxslim.__version__}...") LOGGER.info(f"{prefix} slimming with onnxslim {onnxslim.__version__}...")
model_onnx = onnxslim.slim(model_onnx) model_onnx = onnxslim.slim(model_onnx)
# ONNX Simplifier (deprecated as must be compiled with 'cmake' in aarch64 and Conda CI environments)
# import onnxsim
# model_onnx, check = onnxsim.simplify(model_onnx)
# assert check, "Simplified ONNX model could not be validated"
except Exception as e: except Exception as e:
LOGGER.warning(f"{prefix} simplifier failure: {e}") LOGGER.warning(f"{prefix} simplifier failure: {e}")
@ -677,7 +675,6 @@ class Exporter:
def export_engine(self, prefix=colorstr("TensorRT:")): def export_engine(self, prefix=colorstr("TensorRT:")):
"""YOLOv8 TensorRT export https://developer.nvidia.com/tensorrt.""" """YOLOv8 TensorRT export https://developer.nvidia.com/tensorrt."""
assert self.im.device.type != "cpu", "export running on CPU but must be on GPU, i.e. use 'device=0'" assert self.im.device.type != "cpu", "export running on CPU but must be on GPU, i.e. use 'device=0'"
# self.args.simplify = True
f_onnx, _ = self.export_onnx() # run before TRT import https://github.com/ultralytics/ultralytics/issues/7016 f_onnx, _ = self.export_onnx() # run before TRT import https://github.com/ultralytics/ultralytics/issues/7016
try: try:
@ -784,7 +781,7 @@ class Exporter:
# Load dataset w/ builder (for batching) and calibrate # Load dataset w/ builder (for batching) and calibrate
config.int8_calibrator = EngineCalibrator( config.int8_calibrator = EngineCalibrator(
dataset=self.get_int8_calibration_dataloader(prefix), dataset=self.get_int8_calibration_dataloader(prefix),
batch=2 * self.args.batch, batch=2 * self.args.batch, # TensorRT INT8 calibration should use 2x batch size
cache=str(self.file.with_suffix(".cache")), cache=str(self.file.with_suffix(".cache")),
) )
@ -867,8 +864,6 @@ class Exporter:
f.mkdir() f.mkdir()
images = [batch["img"].permute(0, 2, 3, 1) for batch in self.get_int8_calibration_dataloader(prefix)] images = [batch["img"].permute(0, 2, 3, 1) for batch in self.get_int8_calibration_dataloader(prefix)]
images = torch.cat(images, 0).float() images = torch.cat(images, 0).float()
# mean = images.view(-1, 3).mean(0) # imagenet mean [123.675, 116.28, 103.53]
# std = images.view(-1, 3).std(0) # imagenet std [58.395, 57.12, 57.375]
np.save(str(tmp_file), images.numpy().astype(np.float32)) # BHWC np.save(str(tmp_file), images.numpy().astype(np.float32)) # BHWC
np_data = [["images", tmp_file, [[[[0, 0, 0]]]], [[[[255, 255, 255]]]]]] np_data = [["images", tmp_file, [[[[0, 0, 0]]]], [[[[255, 255, 255]]]]]]
else: else:
@ -996,20 +991,7 @@ class Exporter:
if " " in f: if " " in f:
LOGGER.warning(f"{prefix} WARNING ⚠ your model may not work correctly with spaces in path '{f}'.") LOGGER.warning(f"{prefix} WARNING ⚠ your model may not work correctly with spaces in path '{f}'.")
# f_json = Path(f) / 'model.json' # *.json path # Add metadata
# with open(f_json, 'w') as j: # sort JSON Identity_* in ascending order
# subst = re.sub(
# r'{"outputs": {"Identity.?.?": {"name": "Identity.?.?"}, '
# r'"Identity.?.?": {"name": "Identity.?.?"}, '
# r'"Identity.?.?": {"name": "Identity.?.?"}, '
# r'"Identity.?.?": {"name": "Identity.?.?"}}}',
# r'{"outputs": {"Identity": {"name": "Identity"}, '
# r'"Identity_1": {"name": "Identity_1"}, '
# r'"Identity_2": {"name": "Identity_2"}, '
# r'"Identity_3": {"name": "Identity_3"}}}',
# f_json.read_text(),
# )
# j.write(subst)
yaml_save(Path(f) / "metadata.yaml", self.metadata) # add metadata.yaml yaml_save(Path(f) / "metadata.yaml", self.metadata) # add metadata.yaml
return f, None return f, None
@ -1102,27 +1084,11 @@ class Exporter:
names = self.metadata["names"] names = self.metadata["names"]
nx, ny = spec.description.input[0].type.imageType.width, spec.description.input[0].type.imageType.height nx, ny = spec.description.input[0].type.imageType.width, spec.description.input[0].type.imageType.height
_, nc = out0_shape # number of anchors, number of classes _, nc = out0_shape # number of anchors, number of classes
# _, nc = out0.type.multiArrayType.shape
assert len(names) == nc, f"{len(names)} names found for nc={nc}" # check assert len(names) == nc, f"{len(names)} names found for nc={nc}" # check
# Define output shapes (missing) # Define output shapes (missing)
out0.type.multiArrayType.shape[:] = out0_shape # (3780, 80) out0.type.multiArrayType.shape[:] = out0_shape # (3780, 80)
out1.type.multiArrayType.shape[:] = out1_shape # (3780, 4) out1.type.multiArrayType.shape[:] = out1_shape # (3780, 4)
# spec.neuralNetwork.preprocessing[0].featureName = '0'
# Flexible input shapes
# from coremltools.models.neural_network import flexible_shape_utils
# s = [] # shapes
# s.append(flexible_shape_utils.NeuralNetworkImageSize(320, 192))
# s.append(flexible_shape_utils.NeuralNetworkImageSize(640, 384)) # (height, width)
# flexible_shape_utils.add_enumerated_image_sizes(spec, feature_name='image', sizes=s)
# r = flexible_shape_utils.NeuralNetworkImageSizeRange() # shape ranges
# r.add_height_range((192, 640))
# r.add_width_range((192, 640))
# flexible_shape_utils.update_image_size_range(spec, feature_name='image', size_range=r)
# Print
# print(spec.description)
# Model from spec # Model from spec
model = ct.models.MLModel(spec, weights_dir=weights_dir) model = ct.models.MLModel(spec, weights_dir=weights_dir)

@ -328,7 +328,7 @@ class BasePredictor:
frame = int(match[1]) if match else None # 0 if frame undetermined frame = int(match[1]) if match else None # 0 if frame undetermined
self.txt_path = self.save_dir / "labels" / (p.stem + ("" if self.dataset.mode == "image" else f"_{frame}")) self.txt_path = self.save_dir / "labels" / (p.stem + ("" if self.dataset.mode == "image" else f"_{frame}"))
string += "%gx%g " % im.shape[2:] string += "{:g}x{:g} ".format(*im.shape[2:])
result = self.results[i] result = self.results[i]
result.save_dir = self.save_dir.__str__() # used in other locations result.save_dir = self.save_dir.__str__() # used in other locations
string += f"{result.verbose()}{result.speed['inference']:.1f}ms" string += f"{result.verbose()}{result.speed['inference']:.1f}ms"

@ -202,8 +202,9 @@ class BaseValidator:
return {k: round(float(v), 5) for k, v in results.items()} # return results as 5 decimal place floats return {k: round(float(v), 5) for k, v in results.items()} # return results as 5 decimal place floats
else: else:
LOGGER.info( LOGGER.info(
"Speed: %.1fms preprocess, %.1fms inference, %.1fms loss, %.1fms postprocess per image" "Speed: {:.1f}ms preprocess, {:.1f}ms inference, {:.1f}ms loss, {:.1f}ms postprocess per image".format(
% tuple(self.speed.values()) *tuple(self.speed.values())
)
) )
if self.args.save_json and self.jdict: if self.args.save_json and self.jdict:
with open(str(self.save_dir / "predictions.json"), "w") as f: with open(str(self.save_dir / "predictions.json"), "w") as f:

@ -55,23 +55,22 @@ def request_with_credentials(url: str) -> any:
display.display( display.display(
display.Javascript( display.Javascript(
""" f"""
window._hub_tmp = new Promise((resolve, reject) => { window._hub_tmp = new Promise((resolve, reject) => {{
const timeout = setTimeout(() => reject("Failed authenticating existing browser session"), 5000) const timeout = setTimeout(() => reject("Failed authenticating existing browser session"), 5000)
fetch("%s", { fetch("{url}", {{
method: 'POST', method: 'POST',
credentials: 'include' credentials: 'include'
}) }})
.then((response) => resolve(response.json())) .then((response) => resolve(response.json()))
.then((json) => { .then((json) => {{
clearTimeout(timeout); clearTimeout(timeout);
}).catch((err) => { }}).catch((err) => {{
clearTimeout(timeout); clearTimeout(timeout);
reject(err); reject(err);
}); }});
}); }});
""" """
% url
) )
) )
return output.eval_js("_hub_tmp") return output.eval_js("_hub_tmp")

@ -100,7 +100,7 @@ class FastSAMPredictor(SegmentationPredictor):
texts = [texts] texts = [texts]
crop_ims, filter_idx = [], [] crop_ims, filter_idx = [], []
for i, b in enumerate(result.boxes.xyxy.tolist()): for i, b in enumerate(result.boxes.xyxy.tolist()):
x1, y1, x2, y2 = [int(x) for x in b] x1, y1, x2, y2 = (int(x) for x in b)
if masks[i].sum() <= 100: if masks[i].sum() <= 100:
filter_idx.append(i) filter_idx.append(i)
continue continue

@ -35,7 +35,7 @@ class DropPath(nn.Module):
def __init__(self, drop_prob=0.0, scale_by_keep=True): def __init__(self, drop_prob=0.0, scale_by_keep=True):
"""Initialize DropPath module for stochastic depth regularization during training.""" """Initialize DropPath module for stochastic depth regularization during training."""
super(DropPath, self).__init__() super().__init__()
self.drop_prob = drop_prob self.drop_prob = drop_prob
self.scale_by_keep = scale_by_keep self.scale_by_keep = scale_by_keep

@ -672,7 +672,7 @@ class CBLinear(nn.Module):
def __init__(self, c1, c2s, k=1, s=1, p=None, g=1): def __init__(self, c1, c2s, k=1, s=1, p=None, g=1):
"""Initializes the CBLinear module, passing inputs unchanged.""" """Initializes the CBLinear module, passing inputs unchanged."""
super(CBLinear, self).__init__() super().__init__()
self.c2s = c2s self.c2s = c2s
self.conv = nn.Conv2d(c1, sum(c2s), k, s, autopad(k, p), groups=g, bias=True) self.conv = nn.Conv2d(c1, sum(c2s), k, s, autopad(k, p), groups=g, bias=True)
@ -686,7 +686,7 @@ class CBFuse(nn.Module):
def __init__(self, idx): def __init__(self, idx):
"""Initializes CBFuse module with layer index for selective feature fusion.""" """Initializes CBFuse module with layer index for selective feature fusion."""
super(CBFuse, self).__init__() super().__init__()
self.idx = idx self.idx = idx
def forward(self, xs): def forward(self, xs):

@ -144,12 +144,12 @@ class Detect(nn.Module):
(torch.Tensor): Processed predictions with shape (batch_size, min(max_det, num_anchors), 6) and last (torch.Tensor): Processed predictions with shape (batch_size, min(max_det, num_anchors), 6) and last
dimension format [x, y, w, h, max_class_prob, class_index]. dimension format [x, y, w, h, max_class_prob, class_index].
""" """
batch_size, anchors, predictions = preds.shape # i.e. shape(16,8400,84) batch_size, anchors, _ = preds.shape # i.e. shape(16,8400,84)
boxes, scores = preds.split([4, nc], dim=-1) boxes, scores = preds.split([4, nc], dim=-1)
index = scores.amax(dim=-1).topk(min(max_det, anchors))[1].unsqueeze(-1) index = scores.amax(dim=-1).topk(min(max_det, anchors))[1].unsqueeze(-1)
boxes = boxes.gather(dim=1, index=index.repeat(1, 1, 4)) boxes = boxes.gather(dim=1, index=index.repeat(1, 1, 4))
scores = scores.gather(dim=1, index=index.repeat(1, 1, nc)) scores = scores.gather(dim=1, index=index.repeat(1, 1, nc))
scores, index = scores.flatten(1).topk(max_det) scores, index = scores.flatten(1).topk(min(max_det, anchors))
i = torch.arange(batch_size)[..., None] # batch indices i = torch.arange(batch_size)[..., None] # batch indices
return torch.cat([boxes[i, index // nc], scores[..., None], (index % nc)[..., None].float()], dim=-1) return torch.cat([boxes[i, index // nc], scores[..., None], (index % nc)[..., None].float()], dim=-1)

@ -210,7 +210,7 @@ class ParkingManagement:
Args: Args:
json_file (str): file that have all parking slot points json_file (str): file that have all parking slot points
""" """
with open(json_file, "r") as f: with open(json_file) as f:
return json.load(f) return json.load(f)
def process_data(self, json_data, im0, boxes, clss): def process_data(self, json_data, im0, boxes, clss):

@ -198,7 +198,7 @@ class RF100Benchmark:
os.mkdir("ultralytics-benchmarks") os.mkdir("ultralytics-benchmarks")
safe_download("https://github.com/ultralytics/assets/releases/download/v0.0.0/datasets_links.txt") safe_download("https://github.com/ultralytics/assets/releases/download/v0.0.0/datasets_links.txt")
with open(ds_link_txt, "r") as file: with open(ds_link_txt) as file:
for line in file: for line in file:
try: try:
_, url, workspace, project, version = re.split("/+", line.strip()) _, url, workspace, project, version = re.split("/+", line.strip())
@ -222,7 +222,7 @@ class RF100Benchmark:
Args: Args:
path (str): YAML file path. path (str): YAML file path.
""" """
with open(path, "r") as file: with open(path) as file:
yaml_data = yaml.safe_load(file) yaml_data = yaml.safe_load(file)
yaml_data["train"] = "train/images" yaml_data["train"] = "train/images"
yaml_data["val"] = "valid/images" yaml_data["val"] = "valid/images"
@ -242,7 +242,7 @@ class RF100Benchmark:
skip_symbols = ["🚀", "", "💡", ""] skip_symbols = ["🚀", "", "💡", ""]
with open(yaml_path) as stream: with open(yaml_path) as stream:
class_names = yaml.safe_load(stream)["names"] class_names = yaml.safe_load(stream)["names"]
with open(val_log_file, "r", encoding="utf-8") as f: with open(val_log_file, encoding="utf-8") as f:
lines = f.readlines() lines = f.readlines()
eval_lines = [] eval_lines = []
for line in lines: for line in lines:

@ -29,11 +29,13 @@ from ultralytics.utils import (
IS_PIP_PACKAGE, IS_PIP_PACKAGE,
LINUX, LINUX,
LOGGER, LOGGER,
MACOS,
ONLINE, ONLINE,
PYTHON_VERSION, PYTHON_VERSION,
ROOT, ROOT,
TORCHVISION_VERSION, TORCHVISION_VERSION,
USER_CONFIG_DIR, USER_CONFIG_DIR,
WINDOWS,
Retry, Retry,
SimpleNamespace, SimpleNamespace,
ThreadingLocked, ThreadingLocked,
@ -224,6 +226,14 @@ def check_version(
if not required: # if required is '' or None if not required: # if required is '' or None
return True return True
if "sys_platform" in required: # i.e. required='<2.4.0,>=1.8.0; sys_platform == "win32"'
if (
(WINDOWS and "win32" not in required)
or (LINUX and "linux" not in required)
or (MACOS and "macos" not in required and "darwin" not in required)
):
return True
op = "" op = ""
version = "" version = ""
result = True result = True
@ -422,6 +432,7 @@ def check_torchvision():
""" """
# Compatibility table # Compatibility table
compatibility_table = { compatibility_table = {
"2.4": ["0.19"],
"2.3": ["0.18"], "2.3": ["0.18"],
"2.2": ["0.17"], "2.2": ["0.17"],
"2.1": ["0.16"], "2.1": ["0.16"],

@ -460,7 +460,7 @@ def plot_pr_curve(px, py, ap, save_dir=Path("pr_curve.png"), names={}, on_plot=N
else: else:
ax.plot(px, py, linewidth=1, color="grey") # plot(recall, precision) ax.plot(px, py, linewidth=1, color="grey") # plot(recall, precision)
ax.plot(px, py.mean(1), linewidth=3, color="blue", label="all classes %.3f mAP@0.5" % ap[:, 0].mean()) ax.plot(px, py.mean(1), linewidth=3, color="blue", label=f"all classes {ap[:, 0].mean():.3f} mAP@0.5")
ax.set_xlabel("Recall") ax.set_xlabel("Recall")
ax.set_ylabel("Precision") ax.set_ylabel("Precision")
ax.set_xlim(0, 1) ax.set_xlim(0, 1)

@ -218,7 +218,7 @@ def non_max_suppression(
classes = torch.tensor(classes, device=prediction.device) classes = torch.tensor(classes, device=prediction.device)
if prediction.shape[-1] == 6: # end-to-end model (BNC, i.e. 1,300,6) if prediction.shape[-1] == 6: # end-to-end model (BNC, i.e. 1,300,6)
output = [pred[pred[:, 4] > conf_thres] for pred in prediction] output = [pred[pred[:, 4] > conf_thres][:max_det] for pred in prediction]
if classes is not None: if classes is not None:
output = [pred[(pred[:, 5:6] == classes).any(1)] for pred in output] output = [pred[(pred[:, 5:6] == classes).any(1)] for pred in output]
return output return output

@ -1,4 +1,5 @@
# Ultralytics YOLO 🚀, AGPL-3.0 license # Ultralytics YOLO 🚀, AGPL-3.0 license
import contextlib import contextlib
import gc import gc
import math import math
@ -24,6 +25,7 @@ from ultralytics.utils import (
NUM_THREADS, NUM_THREADS,
PYTHON_VERSION, PYTHON_VERSION,
TORCHVISION_VERSION, TORCHVISION_VERSION,
WINDOWS,
__version__, __version__,
colorstr, colorstr,
) )
@ -42,6 +44,11 @@ TORCHVISION_0_10 = check_version(TORCHVISION_VERSION, "0.10.0")
TORCHVISION_0_11 = check_version(TORCHVISION_VERSION, "0.11.0") TORCHVISION_0_11 = check_version(TORCHVISION_VERSION, "0.11.0")
TORCHVISION_0_13 = check_version(TORCHVISION_VERSION, "0.13.0") TORCHVISION_0_13 = check_version(TORCHVISION_VERSION, "0.13.0")
TORCHVISION_0_18 = check_version(TORCHVISION_VERSION, "0.18.0") TORCHVISION_0_18 = check_version(TORCHVISION_VERSION, "0.18.0")
if WINDOWS and torch.__version__[:3] == "2.4": # reject all versions of 2.4 on Windows
LOGGER.warning(
"WARNING ⚠ Known issue with torch>=2.4.0 on Windows with CPU, recommend downgrading to torch<=2.3.1 to resolve "
"https://github.com/ultralytics/ultralytics/issues/15049"
)
@contextmanager @contextmanager

Loading…
Cancel
Save