Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 16 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

All notable changes to this project will be documented in this file.

## Unreleased
## 1.5.0

### Added

Expand All @@ -24,6 +24,21 @@ All notable changes to this project will be documented in this file.
`start --preserve-existing` mirrors the SDK flag; `job -p ws/project`
resolves the workspace the same way `start` does.

### Changed

- HEIC/HEIF decoding is an optional extra: `pip install "roboflow[heic]"`
installs `pillow-heif>=1.7.0`, and `import roboflow` registers its Pillow
opener when it is installed. The default install no longer depends on
`pi-heif`, which is discontinued upstream: its final release, 1.4.0, bundles
libheif 1.23.0, which is affected by the security advisories fixed in libheif
1.23.2 and 1.23.3 (including CVE-2026-84383). `roboflow` no longer registers
`pi-heif` even when it is still installed.
- Uploads and `Project.check_valid_image()` handle HEIC without the extra.
Decoding a local HEIC file, for example with `model.predict("photo.heic")`,
needs it.
- The extra is opt-in because pillow-heif's binary wheels bundle the x265
encoder, which makes them GPL-2.0 (#398).

## 1.4.1

### Added
Expand Down
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,14 @@ For desktop features, use:
pip install "roboflow[desktop]"
```

To decode HEIC/HEIF images locally (for example `model.predict("photo.heic")`), use:

```bash
pip install "roboflow[heic]"
```

This installs [pillow-heif](https://pypi.org/project/pillow-heif/), whose binary wheels are GPL-2.0 licensed because they bundle the x265 encoder. Uploading HEIC images works without it, since the server decodes them.


<details>
<summary>Lightweight install (roboflow-slim)</summary>
Expand Down
2 changes: 2 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,8 @@ module = [
"IPython.display.*",
# ipywidgets is an optional dependency
"ipywidgets.*",
# pillow-heif is an optional dependency ([heic] extra)
"pillow_heif.*",
"requests_toolbelt.*",
"rfdetr.*",
"torch.*",
Expand Down
3 changes: 0 additions & 3 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,6 @@ matplotlib
numpy>=1.18.5,<2.4
opencv-python-headless>=4.10.0 # relax exact pin to avoid downstream conflicts (#349)
Pillow>=7.1.2
# https://github.com/roboflow/roboflow-python/issues/390
# pi-heif 1.x requires Python 3.10+
pi-heif<2; python_version >= "3.10"
pillow-avif-plugin<2
python-dateutil
python-dotenv
Expand Down
2 changes: 1 addition & 1 deletion roboflow/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
CLIPModel = None # type: ignore[assignment,misc]
GazeModel = None # type: ignore[assignment,misc]

__version__ = "1.4.3"
__version__ = "1.5.0"


def check_key(api_key, model, notebook, num_retries=0):
Expand Down
6 changes: 3 additions & 3 deletions roboflow/util/image_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@
import yaml
from PIL import Image

# pi-heif requires Python 3.10+
# HEIC/HEIF decoding is optional: pip install "roboflow[heic]"
try:
import pi_heif # type: ignore[import-untyped,import-not-found]
import pillow_heif

pi_heif.register_heif_opener(thumbnails=False) # Register for HEIF/HEIC
pillow_heif.register_heif_opener(thumbnails=False) # Register for HEIF/HEIC
except ImportError:
pass
pillow_avif = pillow_avif # Reference pillow_avif to not remove import by accident
Expand Down
3 changes: 3 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@
# create optional [desktop]
extras_require={
"desktop": ["opencv-python==4.8.0.74"],
# Local HEIC/HEIF decoding. Opt-in because pillow-heif's binary wheels
# bundle the x265 encoder, which makes them GPL-2.0 (#398).
"heic": ["pillow-heif>=1.7.0"],
"dev": [
"mypy",
"responses",
Expand Down
24 changes: 24 additions & 0 deletions tests/util/test_image_utils.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import importlib
import sys
import unittest
from unittest import mock

import responses

from roboflow.util import image_utils
from roboflow.util.image_utils import check_image_path, check_image_url, load_labelmap


Expand Down Expand Up @@ -36,6 +40,26 @@ def test_url_not_found(self):
self.assertFalse(check_image_url(url))


class TestHeifOpenerRegistration(unittest.TestCase):
def tearDown(self):
importlib.reload(image_utils)

def _reload_with_modules(self, modules):
with mock.patch.dict(sys.modules, modules):
importlib.reload(image_utils)

def test_registers_pillow_heif_when_installed(self):
pillow_heif = mock.MagicMock()
self._reload_with_modules({"pillow_heif": pillow_heif})
pillow_heif.register_heif_opener.assert_called_once_with(thumbnails=False)

def test_does_not_register_pi_heif(self):
pi_heif = mock.MagicMock()
# None in sys.modules makes `import pillow_heif` raise ImportError
self._reload_with_modules({"pillow_heif": None, "pi_heif": pi_heif})
pi_heif.register_heif_opener.assert_not_called()


class TestLoadLabelmap(unittest.TestCase):
def test_yaml_dict_names(self):
labelmap = load_labelmap("tests/annotations/dict_names.yaml")
Expand Down
Loading