diff --git a/docker/Dockerfile b/docker/Dockerfile index b039f927..867ec6be 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -34,7 +34,7 @@ RUN pip install --no-cache -e ".[export]" albumentations comet pycocotools lance RUN yolo export model=tmp/yolov8n.pt format=edgetpu imgsz=32 RUN yolo export model=tmp/yolov8n.pt format=ncnn imgsz=32 # Requires <= Python 3.10, bug with paddlepaddle==2.5.0 https://github.com/PaddlePaddle/X2Paddle/issues/991 -RUN pip install --no-cache paddlepaddle==2.4.2 x2paddle +RUN pip install --no-cache paddlepaddle>=2.6.0 x2paddle # Fix error: `np.bool` was a deprecated alias for the builtin `bool` segmentation error in Tests RUN pip install --no-cache numpy==1.23.5 # Remove exported models diff --git a/docker/Dockerfile-python b/docker/Dockerfile-python index 55b56ebe..b2a8a162 100644 --- a/docker/Dockerfile-python +++ b/docker/Dockerfile-python @@ -32,7 +32,7 @@ RUN pip install --no-cache -e ".[export]" lancedb --extra-index-url https://down RUN yolo export model=tmp/yolov8n.pt format=edgetpu imgsz=32 RUN yolo export model=tmp/yolov8n.pt format=ncnn imgsz=32 # Requires <= Python 3.10, bug with paddlepaddle==2.5.0 https://github.com/PaddlePaddle/X2Paddle/issues/991 -RUN pip install --no-cache paddlepaddle==2.4.2 x2paddle +RUN pip install --no-cache paddlepaddle>=2.6.0 x2paddle # Remove exported models RUN rm -rf tmp diff --git a/docs/overrides/stylesheets/style.css b/docs/overrides/stylesheets/style.css index 7fe3511b..9c58913d 100644 --- a/docs/overrides/stylesheets/style.css +++ b/docs/overrides/stylesheets/style.css @@ -48,3 +48,10 @@ div.highlight { .md-header .md-select:hover .md-select__inner { max-height: 75vh; } + +/* Update the background of the banner (same as the one on the Ultralytics website) */ +.md-banner { + background-image: url(https://assets-global.website-files.com/646dd1f1a3703e451ba81ecc/659fc4f4163e480e7ec280d0_banner.webp); + background-size: cover; + background-position: center; +} diff --git a/pyproject.toml b/pyproject.toml index 4d174f15..f4a35493 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -98,11 +98,9 @@ dev = [ ] export = [ "onnx>=1.12.0", # ONNX export - "coremltools>=7.0", # CoreML export + "coremltools>=7.0; platform_system != 'Windows'", # CoreML only supported on macOS and Linux "openvino-dev>=2023.0", # OpenVINO export "tensorflow<=2.13.1", # TF bug https://github.com/ultralytics/ultralytics/issues/5161 - "jax<=0.4.21", # tensorflowjs bug https://github.com/google/jax/issues/18978 - "jaxlib<=0.4.21", # tensorflowjs bug https://github.com/google/jax/issues/18978 "tensorflowjs>=3.9.0", # TF.js export, automatically installs tensorflow ] diff --git a/tests/test_python.py b/tests/test_python.py index e20b7bf2..792eb07b 100644 --- a/tests/test_python.py +++ b/tests/test_python.py @@ -463,6 +463,21 @@ def test_utils_files(): print(new_path) +@pytest.mark.slow +def test_utils_patches_torch_save(): + """Test torch_save backoff when _torch_save throws RuntimeError.""" + from unittest.mock import patch, MagicMock + from ultralytics.utils.patches import torch_save + + mock = MagicMock(side_effect=RuntimeError) + + with patch('ultralytics.utils.patches._torch_save', new=mock): + with pytest.raises(RuntimeError): + torch_save(torch.zeros(1), TMP / 'test.pt') + + assert mock.call_count == 4, "torch_save was not attempted the expected number of times" + + def test_nn_modules_conv(): """Test Convolutional Neural Network modules.""" from ultralytics.nn.modules.conv import CBAM, Conv2, ConvTranspose, DWConvTranspose2d, Focus diff --git a/ultralytics/__init__.py b/ultralytics/__init__.py index 007cb8b5..ca09cf93 100644 --- a/ultralytics/__init__.py +++ b/ultralytics/__init__.py @@ -1,6 +1,6 @@ # Ultralytics YOLO 🚀, AGPL-3.0 license -__version__ = "8.1.0" +__version__ = "8.1.1" from ultralytics.data.explorer.explorer import Explorer from ultralytics.models import RTDETR, SAM, YOLO diff --git a/ultralytics/engine/exporter.py b/ultralytics/engine/exporter.py index 940f04cc..2fd1a143 100644 --- a/ultralytics/engine/exporter.py +++ b/ultralytics/engine/exporter.py @@ -555,6 +555,7 @@ class Exporter: import coremltools as ct # noqa LOGGER.info(f"\n{prefix} starting export with coremltools {ct.__version__}...") + assert not WINDOWS, "CoreML export is not supported on Windows, please run on macOS or Linux." f = self.file.with_suffix(".mlmodel" if mlmodel else ".mlpackage") if f.is_dir(): shutil.rmtree(f) diff --git a/ultralytics/solutions/object_counter.py b/ultralytics/solutions/object_counter.py index 1257553f..b7459443 100644 --- a/ultralytics/solutions/object_counter.py +++ b/ultralytics/solutions/object_counter.py @@ -182,25 +182,29 @@ class ObjectCounter: track_line, color=self.track_color, track_thickness=self.track_thickness ) + prev_position = self.track_history[track_id][-2] if len(self.track_history[track_id]) > 1 else None + # Count objects if len(self.reg_pts) == 4: - if self.counting_region.contains(Point(track_line[-1])): - if track_id not in self.counting_list: - self.counting_list.append(track_id) - if box[0] < self.counting_region.centroid.x: - self.out_counts += 1 - else: - self.in_counts += 1 + if prev_position is not None: + if self.counting_region.contains(Point(track_line[-1])): + if track_id not in self.counting_list: + self.counting_list.append(track_id) + if (box[0] - prev_position[0]) * (self.counting_region.centroid.x - prev_position[0]) > 0: + self.in_counts += 1 + else: + self.out_counts += 1 elif len(self.reg_pts) == 2: - distance = Point(track_line[-1]).distance(self.counting_region) - if distance < self.line_dist_thresh: - if track_id not in self.counting_list: - self.counting_list.append(track_id) - if box[0] < self.counting_region.centroid.x: - self.out_counts += 1 - else: - self.in_counts += 1 + if prev_position is not None: + distance = Point(track_line[-1]).distance(self.counting_region) + if distance < self.line_dist_thresh: + if track_id not in self.counting_list: + self.counting_list.append(track_id) + if (box[0] - prev_position[0]) * (self.counting_region.centroid.x - prev_position[0]) > 0: + self.in_counts += 1 + else: + self.out_counts += 1 incount_label = "In Count : " + f"{self.in_counts}" outcount_label = "OutCount : " + f"{self.out_counts}" diff --git a/ultralytics/utils/benchmarks.py b/ultralytics/utils/benchmarks.py index 66621f53..38561ac4 100644 --- a/ultralytics/utils/benchmarks.py +++ b/ultralytics/utils/benchmarks.py @@ -85,10 +85,12 @@ def benchmark( emoji, filename = "❌", None # export defaults try: assert i != 9 or LINUX, "Edge TPU export only supported on Linux" - if i == 10: + if i == 5: + assert MACOS or LINUX, "CoreML export only supported on macOS and Linux" + elif i == 10: assert MACOS or LINUX, "TF.js export only supported on macOS and Linux" - elif i == 11: - assert sys.version_info < (3, 11), "PaddlePaddle export only supported on Python<=3.10" + # elif i == 11: + # assert sys.version_info < (3, 11), "PaddlePaddle export only supported on Python<=3.10" if "cpu" in device.type: assert cpu, "inference not supported on CPU" if "cuda" in device.type: diff --git a/ultralytics/utils/patches.py b/ultralytics/utils/patches.py index f9c5bb1b..703ec19d 100644 --- a/ultralytics/utils/patches.py +++ b/ultralytics/utils/patches.py @@ -1,6 +1,7 @@ # Ultralytics YOLO 🚀, AGPL-3.0 license """Monkey patches to update/extend functionality of existing functions.""" +import time from pathlib import Path import cv2 @@ -61,7 +62,8 @@ _torch_save = torch.save # copy to avoid recursion errors def torch_save(*args, **kwargs): """ - Use dill (if exists) to serialize the lambda functions where pickle does not do this. + Use dill (if exists) to serialize the lambda functions where pickle does not do this. Also adds 3 retries with + exponential standoff in case of save failure to improve robustness to transient issues. Args: *args (tuple): Positional arguments to pass to torch.save. @@ -74,4 +76,11 @@ def torch_save(*args, **kwargs): if "pickle_module" not in kwargs: kwargs["pickle_module"] = pickle # noqa - return _torch_save(*args, **kwargs) + + for i in range(4): # 3 retries + try: + return _torch_save(*args, **kwargs) + except RuntimeError: # unable to save, possibly waiting for device to flush or anti-virus to finish scanning + if i == 3: + raise + time.sleep((2**i) / 2) # exponential standoff 0.5s, 1.0s, 2.0s