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
28 changes: 23 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,11 +71,29 @@ quarantined into one — is the **ultralytics** distribution, which is AGPL-3.0.

What it pulls in is a different question, and worth stating plainly: rtmlib
requires opencv, and opencv's *bundled FFmpeg* is **GPL-3.0-or-later on macOS
wheels** (built `--enable-gpl` with libx264/libx265) though LGPL-2.1-or-later
on manylinux and Windows. This is measured from the shipped binaries — the
wheels' own `LICENSE-3RD-PARTY.txt` never mentions x264. `paces[media]`
already brings such a wheel, so `[pose]` adds a second copy rather than a
higher tier. `paces.pose.check_pose_requirements()` reports what you have.
wheels of the versions measured (4.12.0.88 / 4.13.0.92)** (built `--enable-gpl`
with libx264/libx265) though LGPL-2.1-or-later on manylinux and Windows — a
per-version fact, not a per-platform one: the 5.0.0.93 macOS x86_64 wheel ships
no FFmpeg at all. This is measured from the shipped binaries — the wheels' own
`LICENSE-3RD-PARTY.txt` never mentions x264. `paces[media]` already brings such
a wheel, so `[pose]` adds a second copy rather than a higher tier. Worse:
rtmlib's own metadata requires *both* `opencv-python` *and*
`opencv-contrib-python`, unpinned, so a plain `pip install paces[media,pose]`
ends up with two distributions owning one `cv2` — harmless until either is
uninstalled, at which point the survivor's `cv2` can be left with files
missing (issue #20). rtmlib only calls plain `cv2` APIs (`VideoCapture`,
`dnn.readNetFromONNX`, drawing helpers — nothing contrib-only), so `[media]`'s
`opencv-contrib-python` already covers it; to keep the closure
single-provider, install in two steps instead of one:

```bash
pip install "paces[media]" # opencv-contrib-python, the fleet's one cv2
pip install --no-deps "rtmlib>=0.0.16,<0.1" # skip rtmlib's own opencv-* re-declaration
pip install onnxruntime tqdm # rtmlib's other real deps (numpy already arrives via opencv)
```

`paces.pose.check_pose_requirements()` reports what you have, and names the
repair command if both providers are already present.

## How it thinks

Expand Down
13 changes: 11 additions & 2 deletions paces/pose.py
Original file line number Diff line number Diff line change
Expand Up @@ -412,7 +412,12 @@ def check_pose_requirements() -> dict:
``opencv-contrib-python``, while the fleet standardises on the contrib
superset (mixing's single cv2 provider). An install can therefore end up
with two distributions owning one ``cv2`` package. Reported, not fatal:
it works until one of them is uninstalled, and a reader deserves to know.
it works until one of them is uninstalled, and a reader deserves to know
— and, since neither package's uninstall is safe once both are present
(each claims files the other also claims), the note names the repair
command rather than only the collision. See README's "Auto-crop" section
for the install recipe that avoids the pair in the first place
(issue #20).

Downloads nothing: weights arrive on first inference, not on this check.
"""
Expand Down Expand Up @@ -449,7 +454,11 @@ def _version(distribution: str) -> str | None:
notes.append(
"two distributions provide cv2 "
f"({', '.join(cv2_providers)}) — rtmlib declares both; uninstalling "
"either can leave the other's cv2 broken"
"either can leave the other's cv2 broken. Repair: "
"pip uninstall -y opencv-python opencv-contrib-python && "
"pip install --force-reinstall opencv-contrib-python — not sticky: "
"the next plain `pip install paces[pose]` re-adds opencv-python, "
"so use README's Auto-crop install recipe to avoid it recurring"
)
return {
"ok": rtmlib_version is not None and onnxruntime_version is not None,
Expand Down
7 changes: 6 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,12 @@ media = [
# unaudited wheel in silently.
# Not mirrored into [dev] on purpose: the tests are offline and stay on fake
# locators (no weights, no network), and CI has no reason to take on the second
# cv2 provider. `check_pose_requirements()` reports that pair when it happens.
# cv2 provider. `check_pose_requirements()` reports that pair when it happens
# and names the repair command; README's "Auto-crop" section has the two-step
# install recipe that avoids the pair in the first place (issue #20). Not
# fixable from here: rtmlib declares both wheels unconditionally, with no
# marker paces can override, so a single-provider closure needs either that
# recipe or an upstream rtmlib change — not a pin in this file.
pose = [
"rtmlib>=0.0.16,<0.1",
"onnxruntime",
Expand Down
30 changes: 30 additions & 0 deletions tests/test_pose.py
Original file line number Diff line number Diff line change
Expand Up @@ -439,6 +439,36 @@ def test_check_pose_requirements_reports_channels_without_downloading():
assert any("paces[pose]" in note for note in report["notes"])


def test_check_pose_requirements_names_the_repair_when_both_cv2_providers_present(
monkeypatch,
):
# issue #20: rtmlib declares both opencv-python and opencv-contrib-python.
# Naming the collision isn't enough — a reader who hits it needs the
# repair command, not just the two package names, since neither package's
# uninstall is safe once both are present (each claims the other's files).
import importlib.metadata

installed = {
"rtmlib": "0.0.16",
"onnxruntime": "1.2.3",
"opencv-python": "4.12.0.88",
"opencv-contrib-python": "4.13.0.92",
}

def fake_version(distribution, *args, **kwargs):
try:
return installed[distribution]
except KeyError:
raise importlib.metadata.PackageNotFoundError(distribution)

monkeypatch.setattr(importlib.metadata, "version", fake_version)
report = check_pose_requirements()
assert report["cv2_providers"] == ["opencv-python", "opencv-contrib-python"]
assert any(
"pip install --force-reinstall" in note for note in report["notes"]
)


# ── the licence perimeter (packaging facts, guarded because nothing else is) ─


Expand Down
Loading