diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 372b81e869..c079a5a075 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -156,7 +156,7 @@ jobs: strategy: fail-fast: false matrix: - os: [ubuntu-latest, macos-14] + os: [ubuntu-latest, macos-14, windows-latest] python-version: ["3.11"] torch: [latest] include: diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index c2aca22092..d1c9810ced 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -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 - name: Ruff fixes 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 if: github.event_name == 'pull_request_target' run: | diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 4f7fd5b680..e0bb3fa38e 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -34,7 +34,7 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip wheel - pip install openai requests build twine toml + pip install requests build twine toml - name: Check PyPI version shell: python run: | @@ -79,8 +79,8 @@ jobs: 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') + os.system(f'echo "current_tag=v{local_version}" >> $GITHUB_OUTPUT') + os.system(f'echo "previous_tag=v{online_version}" >> $GITHUB_OUTPUT') if publish: print('Ready to publish new version to PyPI ✅.') @@ -88,81 +88,18 @@ jobs: - name: Publish new tag if: (github.event_name == 'push' || github.event.inputs.pypi == 'true') && steps.check_pypi.outputs.increment == 'True' run: | - 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 }}" + 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 "${{ steps.check_pypi.outputs.current_tag }}" - name: Publish new release if: (github.event_name == 'push' || github.event.inputs.pypi == 'true') && steps.check_pypi.outputs.increment == 'True' env: OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} GITHUB_TOKEN: ${{ secrets.PERSONAL_ACCESS_TOKEN || secrets.GITHUB_TOKEN }} - CURRENT_TAG: ${{ steps.check_pypi.outputs.version }} - PREVIOUS_TAG: ${{ steps.check_pypi.outputs.previous_version }} - shell: python + CURRENT_TAG: ${{ steps.check_pypi.outputs.current_tag }} + PREVIOUS_TAG: ${{ steps.check_pypi.outputs.previous_tag }} run: | - import openai - import os - 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}') + curl -s "https://raw.githubusercontent.com/ultralytics/actions/main/utils/summarize_release.py" | python - + shell: bash - name: Publish to PyPI continue-on-error: 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 with: payload: | - {"text": " 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:* ${{ env.PR_TITLE }}\n"} + {"text": " 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:* ${{ env.PR_TITLE }}\n"} env: SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL_YOLO }} - name: Notify on Slack (Failure) diff --git a/docs/build_docs.py b/docs/build_docs.py index 77e808a7dc..e342312bd6 100644 --- a/docs/build_docs.py +++ b/docs/build_docs.py @@ -164,7 +164,7 @@ def update_docs_html(): # Convert plaintext links to HTML hyperlinks files_modified = 0 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() updated_content = convert_plaintext_links_to_html(content) if updated_content != content: diff --git a/docs/mkdocs_github_authors.yaml b/docs/mkdocs_github_authors.yaml index b02dd37caf..6453f86569 100644 --- a/docs/mkdocs_github_authors.yaml +++ b/docs/mkdocs_github_authors.yaml @@ -1,46 +1,120 @@ -116908874+jk4e@users.noreply.github.com: jk4e -1185102784@qq.com: Laughing-q -130829914+IvorZhu331@users.noreply.github.com: IvorZhu331 -135830346+UltralyticsAssistant@users.noreply.github.com: UltralyticsAssistant -1579093407@qq.com: YOLOv5-Magic -17216799+ouphi@users.noreply.github.com: ouphi -17316848+maianumerosky@users.noreply.github.com: maianumerosky -34196005+fcakyon@users.noreply.github.com: fcakyon -37276661+capjamesg@users.noreply.github.com: capjamesg -39910262+ChaoningZhang@users.noreply.github.com: ChaoningZhang -40165666+berry-ding@users.noreply.github.com: berry-ding -47978446+sergiuwaxmann@users.noreply.github.com: sergiuwaxmann -48149018+zhixuwei@users.noreply.github.com: zhixuwei -49699333+dependabot[bot]@users.noreply.github.com: dependabot -52826299+Chayanonjackal@users.noreply.github.com: Chayanonjackal -53246858+hasanghaffari93@users.noreply.github.com: hasanghaffari93 -60036186+mfloto@users.noreply.github.com: mfloto -61612323+Laughing-q@users.noreply.github.com: Laughing-q -62214284+Burhan-Q@users.noreply.github.com: Burhan-Q -68285002+Kayzwer@users.noreply.github.com: Kayzwer -75611662+tensorturtle@users.noreply.github.com: tensorturtle -78843978+Skillnoob@users.noreply.github.com: Skillnoob -79740115+0xSynapse@users.noreply.github.com: 0xSynapse -Francesco.mttl@gmail.com: ambitious-octopus -abirami.vina@gmail.com: abirami-vina -ahmelsamahy@gmail.com: Ahelsamahy -andrei.kochin@intel.com: andrei-kochin -ayush.chaurarsia@gmail.com: AyushExel -chr043416@gmail.com: RizwanMunawar -glenn.jocher@ultralytics.com: glenn-jocher -hnliu_2@stu.xidian.edu.cn: null -jpedrofonseca_94@hotmail.com: null -k-2feng@hotmail.com: null -lakshantha@ultralytics.com: lakshanthad -lakshanthad@yahoo.com: lakshanthad -muhammadrizwanmunawar123@gmail.com: RizwanMunawar -not.committed.yet: null -plashchynski@gmail.com: plashchynski -priytosh.revolution@live.com: priytosh-tripathi -rulosanti@gmail.com: null -shuizhuyuanluo@126.com: null -sometimesocrazy@gmail.com: null -stormsson@users.noreply.github.com: stormsson -waxmann.sergiu@me.com: sergiuwaxmann -web@ultralytics.com: UltralyticsAssistant -xinwang614@gmail.com: GreatV +116908874+jk4e@users.noreply.github.com: + avatar: https://avatars.githubusercontent.com/u/116908874?v=4 + username: jk4e +130829914+IvorZhu331@users.noreply.github.com: + avatar: https://avatars.githubusercontent.com/u/130829914?v=4 + username: IvorZhu331 +135830346+UltralyticsAssistant@users.noreply.github.com: + avatar: https://avatars.githubusercontent.com/u/135830346?v=4 + username: UltralyticsAssistant +1579093407@qq.com: + avatar: https://avatars.githubusercontent.com/u/160490334?v=4 + username: YOLOv5-Magic +17216799+ouphi@users.noreply.github.com: + avatar: https://avatars.githubusercontent.com/u/17216799?v=4 + username: ouphi +17316848+maianumerosky@users.noreply.github.com: + avatar: https://avatars.githubusercontent.com/u/17316848?v=4 + username: maianumerosky +34196005+fcakyon@users.noreply.github.com: + avatar: https://avatars.githubusercontent.com/u/34196005?v=4 + username: fcakyon +37276661+capjamesg@users.noreply.github.com: + avatar: https://avatars.githubusercontent.com/u/37276661?v=4 + username: capjamesg +39910262+ChaoningZhang@users.noreply.github.com: + avatar: https://avatars.githubusercontent.com/u/39910262?v=4 + username: ChaoningZhang +40165666+berry-ding@users.noreply.github.com: + avatar: https://avatars.githubusercontent.com/u/40165666?v=4 + username: berry-ding +47978446+sergiuwaxmann@users.noreply.github.com: + avatar: https://avatars.githubusercontent.com/u/47978446?v=4 + username: sergiuwaxmann +48149018+zhixuwei@users.noreply.github.com: + avatar: https://avatars.githubusercontent.com/u/48149018?v=4 + username: zhixuwei +49699333+dependabot[bot]@users.noreply.github.com: + avatar: https://avatars.githubusercontent.com/u/27347476?v=4 + username: dependabot[bot] +53246858+hasanghaffari93@users.noreply.github.com: + avatar: https://avatars.githubusercontent.com/u/53246858?v=4 + username: hasanghaffari93 +60036186+mfloto@users.noreply.github.com: + avatar: https://avatars.githubusercontent.com/u/60036186?v=4 + username: mfloto +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 diff --git a/examples/heatmaps.ipynb b/examples/heatmaps.ipynb index 6102e41cbd..1f590b8cb0 100644 --- a/examples/heatmaps.ipynb +++ b/examples/heatmaps.ipynb @@ -116,7 +116,7 @@ " colormap=cv2.COLORMAP_PARULA,\n", " view_img=True,\n", " shape=\"circle\",\n", - " classes_names=model.names,\n", + " names=model.names,\n", ")\n", "\n", "while cap.isOpened():\n", diff --git a/examples/object_counting.ipynb b/examples/object_counting.ipynb index 58e3d6cf48..8c3d0ba6e8 100644 --- a/examples/object_counting.ipynb +++ b/examples/object_counting.ipynb @@ -129,7 +129,7 @@ "counter = solutions.ObjectCounter(\n", " view_img=True, # Display the image during processing\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", " line_thickness=2, # Thickness of the lines drawn\n", ")\n", diff --git a/pyproject.toml b/pyproject.toml index d99f78ebc3..9fae825d80 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -71,6 +71,7 @@ dependencies = [ "pyyaml>=5.3.1", "requests>=2.23.0", "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", "torchvision>=0.9.0", "tqdm>=4.64.0", # progress bars @@ -93,7 +94,7 @@ dev = [ "mkdocstrings[python]", "mkdocs-jupyter", # notebooks "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 ] export = [ diff --git a/tests/test_explorer.py b/tests/test_explorer.py index 81a48c144e..b13bb86828 100644 --- a/tests/test_explorer.py +++ b/tests/test_explorer.py @@ -5,9 +5,11 @@ import pytest from ultralytics import Explorer from ultralytics.utils import ASSETS +from ultralytics.utils.torch_utils import TORCH_1_13 @pytest.mark.slow +@pytest.mark.skipif(not TORCH_1_13, reason="Explorer requires torch>=1.13") def test_similarity(): """Test the correctness and response length of similarity calculations and SQL queries in the Explorer.""" exp = Explorer(data="coco8.yaml") @@ -25,6 +27,7 @@ def test_similarity(): @pytest.mark.slow +@pytest.mark.skipif(not TORCH_1_13, reason="Explorer requires torch>=1.13") def test_det(): """Test detection functionalities and verify embedding table includes bounding boxes.""" exp = Explorer(data="coco8.yaml", model="yolov8n.pt") @@ -38,6 +41,7 @@ def test_det(): @pytest.mark.slow +@pytest.mark.skipif(not TORCH_1_13, reason="Explorer requires torch>=1.13") def test_seg(): """Test segmentation functionalities and ensure the embedding table includes segmentation masks.""" exp = Explorer(data="coco8-seg.yaml", model="yolov8n-seg.pt") @@ -50,6 +54,7 @@ def test_seg(): @pytest.mark.slow +@pytest.mark.skipif(not TORCH_1_13, reason="Explorer requires torch>=1.13") def test_pose(): """Test pose estimation functionality and verify the embedding table includes keypoints.""" exp = Explorer(data="coco8-pose.yaml", model="yolov8n-pose.pt") diff --git a/tests/test_python.py b/tests/test_python.py index 7af4c9f408..f15dd48eff 100644 --- a/tests/test_python.py +++ b/tests/test_python.py @@ -252,6 +252,8 @@ def test_labels_and_crops(): for r in results: im_name = Path(r.path).stem 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 labels = save_path / f"labels/{im_name}.txt" assert labels.exists() diff --git a/ultralytics/__init__.py b/ultralytics/__init__.py index bfa52de390..e1bbd65619 100644 --- a/ultralytics/__init__.py +++ b/ultralytics/__init__.py @@ -1,6 +1,6 @@ # Ultralytics YOLO 🚀, AGPL-3.0 license -__version__ = "8.2.84" +__version__ = "8.2.86" import os diff --git a/ultralytics/data/converter.py b/ultralytics/data/converter.py index 0dab1fe399..400e928bc0 100644 --- a/ultralytics/data/converter.py +++ b/ultralytics/data/converter.py @@ -490,7 +490,7 @@ def convert_dota_to_yolo_obb(dota_root_path: str): normalized_coords = [ 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") for phase in ["train", "val"]: diff --git a/ultralytics/data/dataset.py b/ultralytics/data/dataset.py index ca2fb2662c..7216fa006a 100644 --- a/ultralytics/data/dataset.py +++ b/ultralytics/data/dataset.py @@ -296,7 +296,7 @@ class GroundingDataset(YOLODataset): """Loads annotations from a JSON file, filters, and normalizes bounding boxes for each image.""" labels = [] LOGGER.info("Loading annotation file...") - with open(self.json_file, "r") as f: + with open(self.json_file) as f: annotations = json.load(f) images = {f'{x["id"]:d}': x for x in annotations["images"]} img_to_anns = defaultdict(list) diff --git a/ultralytics/data/split_dota.py b/ultralytics/data/split_dota.py index 1460a46d9a..f9acffe9bb 100644 --- a/ultralytics/data/split_dota.py +++ b/ultralytics/data/split_dota.py @@ -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: 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") diff --git a/ultralytics/engine/exporter.py b/ultralytics/engine/exporter.py index a4c81e24e9..33f245d8d4 100644 --- a/ultralytics/engine/exporter.py +++ b/ultralytics/engine/exporter.py @@ -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)") return f, model 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 return outer_func @@ -204,8 +204,8 @@ class Exporter: self.args.half = False 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 - if self.args.int8 and (engine or xml): - self.args.dynamic = True # enforce dynamic to export TensorRT INT8; ensures ONNX is dynamic + if self.args.int8 and engine: + self.args.dynamic = True # enforce dynamic to export TensorRT INT8 if self.args.optimize: 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'" @@ -248,6 +248,7 @@ class Exporter: m.dynamic = self.args.dynamic m.export = True m.format = self.args.format + m.max_det = self.args.max_det elif isinstance(m, C2f) and not is_tf_format: # EdgeTPU does not support FlexSplitV while split provides cleaner ONNX graph m.forward = m.forward_split @@ -353,18 +354,20 @@ class Exporter: """Build and return a dataloader suitable for calibration of INT8 models.""" 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) + # TensorRT INT8 calibration should use 2x batch size + batch = self.args.batch * (2 if self.args.format == "engine" else 1) dataset = YOLODataset( data[self.args.split or "val"], data=data, task=self.model.task, imgsz=self.imgsz[0], augment=False, - batch_size=self.args.batch * 2, # NOTE TensorRT INT8 calibration should use 2x batch size + batch_size=batch, ) n = len(dataset) if n < 300: 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 def export_torchscript(self, prefix=colorstr("TorchScript:")): @@ -420,7 +423,6 @@ class Exporter: # Checks model_onnx = onnx.load(f) # load onnx model - # onnx.checker.check_model(model_onnx) # check onnx model # Simplify if self.args.simplify: @@ -430,10 +432,6 @@ class Exporter: LOGGER.info(f"{prefix} slimming with onnxslim {onnxslim.__version__}...") 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: LOGGER.warning(f"{prefix} simplifier failure: {e}") @@ -677,7 +675,6 @@ class Exporter: def export_engine(self, prefix=colorstr("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'" - # self.args.simplify = True f_onnx, _ = self.export_onnx() # run before TRT import https://github.com/ultralytics/ultralytics/issues/7016 try: @@ -784,7 +781,7 @@ class Exporter: # Load dataset w/ builder (for batching) and calibrate config.int8_calibrator = EngineCalibrator( 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")), ) @@ -867,8 +864,6 @@ class Exporter: f.mkdir() images = [batch["img"].permute(0, 2, 3, 1) for batch in self.get_int8_calibration_dataloader(prefix)] 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_data = [["images", tmp_file, [[[[0, 0, 0]]]], [[[[255, 255, 255]]]]]] else: @@ -996,20 +991,7 @@ class Exporter: if " " in 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 - # 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) + # Add metadata yaml_save(Path(f) / "metadata.yaml", self.metadata) # add metadata.yaml return f, None @@ -1102,27 +1084,11 @@ class Exporter: names = self.metadata["names"] 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.type.multiArrayType.shape assert len(names) == nc, f"{len(names)} names found for nc={nc}" # check # Define output shapes (missing) out0.type.multiArrayType.shape[:] = out0_shape # (3780, 80) 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 = ct.models.MLModel(spec, weights_dir=weights_dir) diff --git a/ultralytics/engine/predictor.py b/ultralytics/engine/predictor.py index c5e1d16dea..8ace18f611 100644 --- a/ultralytics/engine/predictor.py +++ b/ultralytics/engine/predictor.py @@ -328,7 +328,7 @@ class BasePredictor: 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}")) - string += "%gx%g " % im.shape[2:] + string += "{:g}x{:g} ".format(*im.shape[2:]) result = self.results[i] result.save_dir = self.save_dir.__str__() # used in other locations string += f"{result.verbose()}{result.speed['inference']:.1f}ms" diff --git a/ultralytics/engine/validator.py b/ultralytics/engine/validator.py index 5a91e5b8b5..655f2455ca 100644 --- a/ultralytics/engine/validator.py +++ b/ultralytics/engine/validator.py @@ -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 else: LOGGER.info( - "Speed: %.1fms preprocess, %.1fms inference, %.1fms loss, %.1fms postprocess per image" - % tuple(self.speed.values()) + "Speed: {:.1f}ms preprocess, {:.1f}ms inference, {:.1f}ms loss, {:.1f}ms postprocess per image".format( + *tuple(self.speed.values()) + ) ) if self.args.save_json and self.jdict: with open(str(self.save_dir / "predictions.json"), "w") as f: diff --git a/ultralytics/hub/utils.py b/ultralytics/hub/utils.py index 6005609f81..2fc956fb34 100644 --- a/ultralytics/hub/utils.py +++ b/ultralytics/hub/utils.py @@ -55,23 +55,22 @@ def request_with_credentials(url: str) -> any: display.display( display.Javascript( - """ - window._hub_tmp = new Promise((resolve, reject) => { + f""" + window._hub_tmp = new Promise((resolve, reject) => {{ const timeout = setTimeout(() => reject("Failed authenticating existing browser session"), 5000) - fetch("%s", { + fetch("{url}", {{ method: 'POST', credentials: 'include' - }) + }}) .then((response) => resolve(response.json())) - .then((json) => { + .then((json) => {{ clearTimeout(timeout); - }).catch((err) => { + }}).catch((err) => {{ clearTimeout(timeout); reject(err); - }); - }); + }}); + }}); """ - % url ) ) return output.eval_js("_hub_tmp") diff --git a/ultralytics/models/fastsam/predict.py b/ultralytics/models/fastsam/predict.py index 8095239e3c..0dce968a9b 100644 --- a/ultralytics/models/fastsam/predict.py +++ b/ultralytics/models/fastsam/predict.py @@ -100,7 +100,7 @@ class FastSAMPredictor(SegmentationPredictor): texts = [texts] crop_ims, filter_idx = [], [] 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: filter_idx.append(i) continue diff --git a/ultralytics/models/sam/modules/blocks.py b/ultralytics/models/sam/modules/blocks.py index 008185a29d..0615037467 100644 --- a/ultralytics/models/sam/modules/blocks.py +++ b/ultralytics/models/sam/modules/blocks.py @@ -35,7 +35,7 @@ class DropPath(nn.Module): def __init__(self, drop_prob=0.0, scale_by_keep=True): """Initialize DropPath module for stochastic depth regularization during training.""" - super(DropPath, self).__init__() + super().__init__() self.drop_prob = drop_prob self.scale_by_keep = scale_by_keep diff --git a/ultralytics/nn/modules/block.py b/ultralytics/nn/modules/block.py index ec236dff7f..07be2b8845 100644 --- a/ultralytics/nn/modules/block.py +++ b/ultralytics/nn/modules/block.py @@ -672,7 +672,7 @@ class CBLinear(nn.Module): def __init__(self, c1, c2s, k=1, s=1, p=None, g=1): """Initializes the CBLinear module, passing inputs unchanged.""" - super(CBLinear, self).__init__() + super().__init__() self.c2s = c2s 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): """Initializes CBFuse module with layer index for selective feature fusion.""" - super(CBFuse, self).__init__() + super().__init__() self.idx = idx def forward(self, xs): diff --git a/ultralytics/nn/modules/head.py b/ultralytics/nn/modules/head.py index ed0b90f807..1a02e2b258 100644 --- a/ultralytics/nn/modules/head.py +++ b/ultralytics/nn/modules/head.py @@ -144,12 +144,12 @@ class Detect(nn.Module): (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]. """ - 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) index = scores.amax(dim=-1).topk(min(max_det, anchors))[1].unsqueeze(-1) boxes = boxes.gather(dim=1, index=index.repeat(1, 1, 4)) 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 return torch.cat([boxes[i, index // nc], scores[..., None], (index % nc)[..., None].float()], dim=-1) diff --git a/ultralytics/solutions/parking_management.py b/ultralytics/solutions/parking_management.py index 7ee9f9a126..19a8ef1689 100644 --- a/ultralytics/solutions/parking_management.py +++ b/ultralytics/solutions/parking_management.py @@ -210,7 +210,7 @@ class ParkingManagement: Args: 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) def process_data(self, json_data, im0, boxes, clss): diff --git a/ultralytics/utils/benchmarks.py b/ultralytics/utils/benchmarks.py index c33a89064e..212c2f7bfe 100644 --- a/ultralytics/utils/benchmarks.py +++ b/ultralytics/utils/benchmarks.py @@ -198,7 +198,7 @@ class RF100Benchmark: os.mkdir("ultralytics-benchmarks") 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: try: _, url, workspace, project, version = re.split("/+", line.strip()) @@ -222,7 +222,7 @@ class RF100Benchmark: Args: path (str): YAML file path. """ - with open(path, "r") as file: + with open(path) as file: yaml_data = yaml.safe_load(file) yaml_data["train"] = "train/images" yaml_data["val"] = "valid/images" @@ -242,7 +242,7 @@ class RF100Benchmark: skip_symbols = ["🚀", "⚠️", "💡", "❌"] with open(yaml_path) as stream: 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() eval_lines = [] for line in lines: diff --git a/ultralytics/utils/checks.py b/ultralytics/utils/checks.py index b2ccf8d05d..80c13ad787 100644 --- a/ultralytics/utils/checks.py +++ b/ultralytics/utils/checks.py @@ -29,11 +29,13 @@ from ultralytics.utils import ( IS_PIP_PACKAGE, LINUX, LOGGER, + MACOS, ONLINE, PYTHON_VERSION, ROOT, TORCHVISION_VERSION, USER_CONFIG_DIR, + WINDOWS, Retry, SimpleNamespace, ThreadingLocked, @@ -224,6 +226,14 @@ def check_version( if not required: # if required is '' or None 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 = "" version = "" result = True @@ -422,6 +432,7 @@ def check_torchvision(): """ # Compatibility table compatibility_table = { + "2.4": ["0.19"], "2.3": ["0.18"], "2.2": ["0.17"], "2.1": ["0.16"], diff --git a/ultralytics/utils/metrics.py b/ultralytics/utils/metrics.py index ec1295aefc..fc9862dd36 100644 --- a/ultralytics/utils/metrics.py +++ b/ultralytics/utils/metrics.py @@ -460,7 +460,7 @@ def plot_pr_curve(px, py, ap, save_dir=Path("pr_curve.png"), names={}, on_plot=N else: 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_ylabel("Precision") ax.set_xlim(0, 1) diff --git a/ultralytics/utils/ops.py b/ultralytics/utils/ops.py index afe2f70223..b76168f95e 100644 --- a/ultralytics/utils/ops.py +++ b/ultralytics/utils/ops.py @@ -218,7 +218,7 @@ def non_max_suppression( classes = torch.tensor(classes, device=prediction.device) 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: output = [pred[(pred[:, 5:6] == classes).any(1)] for pred in output] return output diff --git a/ultralytics/utils/torch_utils.py b/ultralytics/utils/torch_utils.py index 68d9fcf273..c2338e184b 100644 --- a/ultralytics/utils/torch_utils.py +++ b/ultralytics/utils/torch_utils.py @@ -1,4 +1,5 @@ # Ultralytics YOLO 🚀, AGPL-3.0 license + import contextlib import gc import math @@ -24,6 +25,7 @@ from ultralytics.utils import ( NUM_THREADS, PYTHON_VERSION, TORCHVISION_VERSION, + WINDOWS, __version__, 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_13 = check_version(TORCHVISION_VERSION, "0.13.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