Merge branch 'main' into benchmark-format-args

benchmark-format-args
Francesco Mattioli 3 months ago committed by GitHub
commit 0212e3328d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 2
      .github/workflows/cla.yml
  2. 10
      .github/workflows/docs.yml
  3. 113
      .github/workflows/publish.yml
  4. 2
      docs/build_docs.py
  5. 11
      docs/en/datasets/detect/visdrone.md
  6. 2
      examples/YOLOv8-CPP-Inference/main.cpp
  7. 2
      ultralytics/__init__.py
  8. 6
      ultralytics/nn/modules/head.py

@ -26,7 +26,7 @@ jobs:
steps: steps:
- name: CLA Assistant - name: CLA Assistant
if: (github.event.comment.body == 'recheck' || github.event.comment.body == 'I have read the CLA Document and I sign the CLA') || github.event_name == 'pull_request_target' if: (github.event.comment.body == 'recheck' || github.event.comment.body == 'I have read the CLA Document and I sign the CLA') || github.event_name == 'pull_request_target'
uses: contributor-assistant/github-action@v2.4.0 uses: contributor-assistant/github-action@v2.5.1
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Must be repository secret PAT # Must be repository secret PAT

@ -76,6 +76,10 @@ jobs:
cp -R ../site/* . cp -R ../site/* .
echo "$INDEXNOW_KEY" > "$INDEXNOW_KEY.txt" echo "$INDEXNOW_KEY" > "$INDEXNOW_KEY.txt"
git add . git add .
LATEST_HASH=$(git rev-parse --short=7 HEAD) if git diff --staged --quiet; then
git commit -m "Update Docs for 'ultralytics ${{ steps.check_pypi.outputs.version }} - $LATEST_HASH'" echo "No changes to commit"
git push https://$PERSONAL_ACCESS_TOKEN@github.com/ultralytics/docs.git gh-pages else
LATEST_HASH=$(git rev-parse --short=7 HEAD)
git commit -m "Update Docs for 'ultralytics ${{ steps.check_pypi.outputs.version }} - $LATEST_HASH'"
git push https://$PERSONAL_ACCESS_TOKEN@github.com/ultralytics/docs.git gh-pages
fi

@ -16,12 +16,12 @@ jobs:
publish: publish:
if: github.repository == 'ultralytics/ultralytics' && github.actor == 'glenn-jocher' if: github.repository == 'ultralytics/ultralytics' && github.actor == 'glenn-jocher'
name: Publish name: Publish
runs-on: macos-14 runs-on: ubuntu-latest
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v4 uses: actions/checkout@v4
with: with:
fetch-depth: "0" # pulls all commits (needed correct last updated dates in Docs) token: ${{ secrets.PERSONAL_ACCESS_TOKEN || secrets.GITHUB_TOKEN }} # use your PAT here
- name: Git config - name: Git config
run: | run: |
git config --global user.name "UltralyticsAssistant" git config --global user.name "UltralyticsAssistant"
@ -29,45 +29,72 @@ jobs:
- name: Set up Python environment - name: Set up Python environment
uses: actions/setup-python@v5 uses: actions/setup-python@v5
with: with:
python-version: "3.11" python-version: "3.x"
cache: "pip" # caching pip dependencies cache: "pip" # caching pip dependencies
- name: Install dependencies - name: Install dependencies
run: | run: |
python -m pip install --upgrade pip wheel build twine python -m pip install --upgrade pip wheel
pip install -e ".[dev]" openai --extra-index-url https://download.pytorch.org/whl/cpu pip install openai requests build twine toml
- name: Check PyPI version - name: Check PyPI version
shell: python shell: python
run: | run: |
import os import os
import ultralytics import requests
from ultralytics.utils.checks import check_latest_pypi_version import toml
latest_pypi_version = check_latest_pypi_version()
v_local = tuple(map(int, ultralytics.__version__.split('.'))) # Load version and package name from pyproject.toml
v_pypi = tuple(map(int, latest_pypi_version.split('.'))) pyproject = toml.load('pyproject.toml')
print(f'Local version is {v_local}') package_name = pyproject['project']['name']
print(f'PyPI version is {v_pypi}') local_version = pyproject['project'].get('version', 'dynamic')
d = [a - b for a, b in zip(v_local, v_pypi)] # diff
increment_patch = (d[0] == d[1] == 0) and (0 < d[2] < 3) # publish if patch version increments by 1 or 2 # If version is dynamic, extract it from the specified file
increment_minor = (d[0] == 0) and (d[1] == 1) and v_local[2] == 0 # publish if minor version increments if local_version == 'dynamic':
increment = increment_patch or increment_minor version_attr = pyproject['tool']['setuptools']['dynamic']['version']['attr']
os.system(f'echo "increment={increment}" >> $GITHUB_OUTPUT') module_path, attr_name = version_attr.rsplit('.', 1)
os.system(f'echo "version={ultralytics.__version__}" >> $GITHUB_OUTPUT') with open(f"{module_path.replace('.', '/')}/__init__.py") as f:
os.system(f'echo "previous_version={latest_pypi_version}" >> $GITHUB_OUTPUT') local_version = next(line.split('=')[1].strip().strip("'\"") for line in f if line.startswith(attr_name))
if increment:
print('Local version is higher than PyPI version. Publishing new version to PyPI ✅.') print(f"Local Version: {local_version}")
# Get online version from PyPI
response = requests.get(f"https://pypi.org/pypi/{package_name}/json")
online_version = response.json()['info']['version'] if response.status_code == 200 else None
print(f"Online Version: {online_version or 'Not Found'}")
# Determine if a new version should be published
publish = False
if online_version:
local_ver = tuple(map(int, local_version.split('.')))
online_ver = tuple(map(int, online_version.split('.')))
major_diff = local_ver[0] - online_ver[0]
minor_diff = local_ver[1] - online_ver[1]
patch_diff = local_ver[2] - online_ver[2]
publish = (
(major_diff == 0 and minor_diff == 0 and 0 < patch_diff <= 2) or
(major_diff == 0 and minor_diff == 1 and local_ver[2] == 0) or
(major_diff == 1 and local_ver[1] == 0 and local_ver[2] == 0)
)
else:
publish = True # First release
os.system(f'echo "increment={publish}" >> $GITHUB_OUTPUT')
os.system(f'echo "version={local_version}" >> $GITHUB_OUTPUT')
os.system(f'echo "previous_version={online_version or "N/A"}" >> $GITHUB_OUTPUT')
if publish:
print('Ready to publish new version to PyPI ✅.')
id: check_pypi id: check_pypi
- 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)" || true # i.e. "v0.1.2 commit message" git tag -a "v${{ steps.check_pypi.outputs.version }}" -m "$(git log -1 --pretty=%B)" # i.e. "v0.1.2 commit message"
git push origin "v${{ steps.check_pypi.outputs.version }}" || true git push origin "v${{ steps.check_pypi.outputs.version }}"
- 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_AZURE_API_KEY: ${{ secrets.OPENAI_AZURE_API_KEY }} OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
OPENAI_AZURE_ENDPOINT: ${{ secrets.OPENAI_AZURE_ENDPOINT }} GITHUB_TOKEN: ${{ secrets.PERSONAL_ACCESS_TOKEN || secrets.GITHUB_TOKEN }}
OPENAI_AZURE_API_VERSION: ${{ secrets.OPENAI_AZURE_API_VERSION }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CURRENT_TAG: ${{ steps.check_pypi.outputs.version }} CURRENT_TAG: ${{ steps.check_pypi.outputs.version }}
PREVIOUS_TAG: ${{ steps.check_pypi.outputs.previous_version }} PREVIOUS_TAG: ${{ steps.check_pypi.outputs.previous_version }}
shell: python shell: python
@ -79,26 +106,18 @@ jobs:
import subprocess import subprocess
# Retrieve environment variables # Retrieve environment variables
OPENAI_AZURE_API_KEY = os.getenv('OPENAI_AZURE_API_KEY') OPENAI_API_KEY = os.getenv('OPENAI_API_KEY')
OPENAI_AZURE_ENDPOINT = os.getenv('OPENAI_AZURE_ENDPOINT')
OPENAI_AZURE_API_VERSION = os.getenv('OPENAI_AZURE_API_VERSION')
GITHUB_TOKEN = os.getenv('GITHUB_TOKEN') GITHUB_TOKEN = os.getenv('GITHUB_TOKEN')
CURRENT_TAG = os.getenv('CURRENT_TAG') CURRENT_TAG = os.getenv('CURRENT_TAG')
PREVIOUS_TAG = os.getenv('PREVIOUS_TAG') PREVIOUS_TAG = os.getenv('PREVIOUS_TAG')
# Check for required environment variables # Check for required environment variables
if not all([OPENAI_AZURE_API_KEY, OPENAI_AZURE_ENDPOINT, OPENAI_AZURE_API_VERSION, GITHUB_TOKEN, CURRENT_TAG, PREVIOUS_TAG]): if not all([OPENAI_API_KEY, GITHUB_TOKEN, CURRENT_TAG, PREVIOUS_TAG]):
print(OPENAI_AZURE_API_KEY)
print(OPENAI_AZURE_ENDPOINT)
print(OPENAI_AZURE_API_VERSION)
print(GITHUB_TOKEN)
print(CURRENT_TAG)
print(PREVIOUS_TAG)
raise ValueError("One or more required environment variables are missing.") raise ValueError("One or more required environment variables are missing.")
latest_tag = f"v{CURRENT_TAG}" latest_tag = f"v{CURRENT_TAG}"
previous_tag = f"v{PREVIOUS_TAG}" previous_tag = f"v{PREVIOUS_TAG}"
repo = 'ultralytics/ultralytics' repo = os.getenv('GITHUB_REPOSITORY')
headers = {"Authorization": f"token {GITHUB_TOKEN}", "Accept": "application/vnd.github.v3.diff"} headers = {"Authorization": f"token {GITHUB_TOKEN}", "Accept": "application/vnd.github.v3.diff"}
# Get the diff between the tags # Get the diff between the tags
@ -106,29 +125,23 @@ jobs:
response = requests.get(url, headers=headers) response = requests.get(url, headers=headers)
diff = response.text if response.status_code == 200 else f"Failed to get diff: {response.content}" diff = response.text if response.status_code == 200 else f"Failed to get diff: {response.content}"
# Set up client # Get summary
client = openai.AzureOpenAI(
api_key=OPENAI_AZURE_API_KEY,
api_version=OPENAI_AZURE_API_VERSION,
azure_endpoint=OPENAI_AZURE_ENDPOINT
)
messages = [ messages = [
{ {
"role": "system", "role": "system",
"content": "You are an Ultralytics AI assistant skilled in software development and technical communication. Your task is to summarize GitHub releases from Ultralytics 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." "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", "role": "user",
"content": f"Summarize the updates made in the [Ultralytics](https://ultralytics.com) '{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" "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"## 🌟 Summary (single-line synopsis)\n"
f"## 📊 Key Changes (bullet points highlighting any major changes)\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"## 🎯 Purpose & Impact (bullet points explaining any benefits and potential impact to users)\n"
f"\n\nHere's the release diff:\n\n{diff[:300000]}", 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-05-13", messages=messages) completion = client.chat.completions.create(model="gpt-4o-2024-08-06", messages=messages)
summary = completion.choices[0].message.content.strip() summary = completion.choices[0].message.content.strip()
# Get the latest commit message # Get the latest commit message
@ -177,7 +190,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 'ultralytics ${{ 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 }} 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"}
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)

@ -214,7 +214,7 @@ def convert_plaintext_links_to_html(content):
def remove_macros(): def remove_macros():
# Delete the /macros directory and sitemap.xml.gz from the built site """Removes the /macros directory and related entries in sitemap.xml from the built site."""
shutil.rmtree(SITE / "macros", ignore_errors=True) shutil.rmtree(SITE / "macros", ignore_errors=True)
(SITE / "sitemap.xml.gz").unlink(missing_ok=True) (SITE / "sitemap.xml.gz").unlink(missing_ok=True)

@ -8,6 +8,17 @@ keywords: VisDrone, drone dataset, computer vision, object detection, object tra
The [VisDrone Dataset](https://github.com/VisDrone/VisDrone-Dataset) is a large-scale benchmark created by the AISKYEYE team at the Lab of Machine Learning and Data Mining, Tianjin University, China. It contains carefully annotated ground truth data for various computer vision tasks related to drone-based image and video analysis. The [VisDrone Dataset](https://github.com/VisDrone/VisDrone-Dataset) is a large-scale benchmark created by the AISKYEYE team at the Lab of Machine Learning and Data Mining, Tianjin University, China. It contains carefully annotated ground truth data for various computer vision tasks related to drone-based image and video analysis.
<p align="center">
<br>
<iframe loading="lazy" width="720" height="405" src="https://www.youtube.com/embed/28JV4rbzklM"
title="YouTube video player" frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
allowfullscreen>
</iframe>
<br>
<strong>Watch:</strong> How to Train Ultralytics YOLO Models on the VisDrone Dataset for Drone Image Analysis
</p>
VisDrone is composed of 288 video clips with 261,908 frames and 10,209 static images, captured by various drone-mounted cameras. The dataset covers a wide range of aspects, including location (14 different cities across China), environment (urban and rural), objects (pedestrians, vehicles, bicycles, etc.), and density (sparse and crowded scenes). The dataset was collected using various drone platforms under different scenarios and weather and lighting conditions. These frames are manually annotated with over 2.6 million bounding boxes of targets such as pedestrians, cars, bicycles, and tricycles. Attributes like scene visibility, object class, and occlusion are also provided for better data utilization. VisDrone is composed of 288 video clips with 261,908 frames and 10,209 static images, captured by various drone-mounted cameras. The dataset covers a wide range of aspects, including location (14 different cities across China), environment (urban and rural), objects (pedestrians, vehicles, bicycles, etc.), and density (sparse and crowded scenes). The dataset was collected using various drone platforms under different scenarios and weather and lighting conditions. These frames are manually annotated with over 2.6 million bounding boxes of targets such as pedestrians, cars, bicycles, and tricycles. Attributes like scene visibility, object class, and occlusion are also provided for better data utilization.
## Dataset Structure ## Dataset Structure

@ -24,7 +24,7 @@ int main(int argc, char **argv)
// //
// Note that in this example the classes are hard-coded and 'classes.txt' is a place holder. // Note that in this example the classes are hard-coded and 'classes.txt' is a place holder.
Inference inf(projectBasePath + "/yolov8s.onnx", cv::Size(640, 480), "classes.txt", runOnGPU); Inference inf(projectBasePath + "/yolov8s.onnx", cv::Size(640, 640), "classes.txt", runOnGPU);
std::vector<std::string> imageNames; std::vector<std::string> imageNames;
imageNames.push_back(projectBasePath + "/ultralytics/assets/bus.jpg"); imageNames.push_back(projectBasePath + "/ultralytics/assets/bus.jpg");

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

@ -8,6 +8,7 @@ import torch
import torch.nn as nn import torch.nn as nn
from torch.nn.init import constant_, xavier_uniform_ from torch.nn.init import constant_, xavier_uniform_
from ultralytics.utils import MACOS
from ultralytics.utils.tal import TORCH_1_10, dist2bbox, dist2rbox, make_anchors from ultralytics.utils.tal import TORCH_1_10, dist2bbox, dist2rbox, make_anchors
from .block import DFL, BNContrastiveHead, ContrastiveHead, Proto from .block import DFL, BNContrastiveHead, ContrastiveHead, Proto
@ -151,13 +152,16 @@ class Detect(nn.Module):
boxes = torch.gather(boxes, dim=1, index=index.repeat(1, 1, boxes.shape[-1])) boxes = torch.gather(boxes, dim=1, index=index.repeat(1, 1, boxes.shape[-1]))
scores = torch.gather(scores, dim=1, index=index.repeat(1, 1, scores.shape[-1])) scores = torch.gather(scores, dim=1, index=index.repeat(1, 1, scores.shape[-1]))
# NOTE: simplify but result slightly lower mAP # NOTE: simplify result but slightly lower mAP
# scores, labels = scores.max(dim=-1) # scores, labels = scores.max(dim=-1)
# return torch.cat([boxes, scores.unsqueeze(-1), labels.unsqueeze(-1)], dim=-1) # return torch.cat([boxes, scores.unsqueeze(-1), labels.unsqueeze(-1)], dim=-1)
scores, index = torch.topk(scores.flatten(1), max_det, axis=-1) scores, index = torch.topk(scores.flatten(1), max_det, axis=-1)
labels = index % nc labels = index % nc
index = index // nc index = index // nc
# Set int64 dtype for MPS and CoreML compatibility to avoid 'gather_along_axis' ops error
if MACOS:
index = index.to(torch.int64)
boxes = boxes.gather(dim=1, index=index.unsqueeze(-1).repeat(1, 1, boxes.shape[-1])) boxes = boxes.gather(dim=1, index=index.unsqueeze(-1).repeat(1, 1, boxes.shape[-1]))
return torch.cat([boxes, scores.unsqueeze(-1), labels.unsqueeze(-1).to(boxes.dtype)], dim=-1) return torch.cat([boxes, scores.unsqueeze(-1), labels.unsqueeze(-1).to(boxes.dtype)], dim=-1)

Loading…
Cancel
Save