build: add reproducible mmdet patch script
Add an idempotent patch-mmdet-version-gate entrypoint that rewrites the installed mmdet mmcv compatibility assert into a warning so the local rebuilt mmcv wheel can be reused after uv sync. Cover the source rewrite with focused tests and document the post-sync command in the README so the local environment patch is reproducible instead of being a one-off manual edit inside .venv.
This commit is contained in:
@@ -44,6 +44,7 @@ References:
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
uv sync --group dev --group detection
|
uv sync --group dev --group detection
|
||||||
|
uv run patch-mmdet-version-gate
|
||||||
uv run pose-tracking-exp run_detection --config detection.toml camera0 camera1
|
uv run pose-tracking-exp run_detection --config detection.toml camera0 camera1
|
||||||
uv run pose-tracking-exp run_detection --source video --output-dir data/detections --config detection.toml cam0=/data/cam0.mp4 cam1=/data/cam1.mp4
|
uv run pose-tracking-exp run_detection --source video --output-dir data/detections --config detection.toml cam0=/data/cam0.mp4 cam1=/data/cam1.mp4
|
||||||
```
|
```
|
||||||
@@ -58,6 +59,7 @@ The default backend is `yolo_rtmpose`, and the heavy runtime dependencies live i
|
|||||||
Checkpoint paths are explicit config fields; the code does not hardcode local checkpoint locations.
|
Checkpoint paths are explicit config fields; the code does not hardcode local checkpoint locations.
|
||||||
The only inferred path is the MMPose config path, which is resolved relative to the installed `mmpose` package when `pose_config_path` is omitted.
|
The only inferred path is the MMPose config path, which is resolved relative to the installed `mmpose` package when `pose_config_path` is omitted.
|
||||||
For offline video runs, the default sink is parquet and writes one `*_detected.parquet` file per source. `run_tracking` can consume that directory directly as replay input.
|
For offline video runs, the default sink is parquet and writes one `*_detected.parquet` file per source. `run_tracking` can consume that directory directly as replay input.
|
||||||
|
`uv run patch-mmdet-version-gate` is an idempotent local-environment patch for the current `mmdet` compatibility assert against the rebuilt `mmcv` wheel. Re-run it after `uv sync` if the environment is recreated.
|
||||||
|
|
||||||
Example `detection.toml`:
|
Example `detection.toml`:
|
||||||
|
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ detection = [
|
|||||||
|
|
||||||
[project.scripts]
|
[project.scripts]
|
||||||
pose-tracking-exp = "pose_tracking_exp.cli:main"
|
pose-tracking-exp = "pose_tracking_exp.cli:main"
|
||||||
|
patch-mmdet-version-gate = "pose_tracking_exp.detection.patch_mmdet:main"
|
||||||
|
|
||||||
[tool.hatch.build.targets.wheel]
|
[tool.hatch.build.targets.wheel]
|
||||||
packages = ["src/pose_tracking_exp"]
|
packages = ["src/pose_tracking_exp"]
|
||||||
|
|||||||
@@ -6,6 +6,11 @@ from pose_tracking_exp.detection.config import (
|
|||||||
resolve_instances,
|
resolve_instances,
|
||||||
)
|
)
|
||||||
from pose_tracking_exp.detection.factory import build_pose_shim
|
from pose_tracking_exp.detection.factory import build_pose_shim
|
||||||
|
from pose_tracking_exp.detection.patch_mmdet import (
|
||||||
|
patch_mmdet_init_file,
|
||||||
|
patch_mmdet_init_text,
|
||||||
|
resolve_mmdet_init_path,
|
||||||
|
)
|
||||||
from pose_tracking_exp.detection.runner import (
|
from pose_tracking_exp.detection.runner import (
|
||||||
SimpleMovingAverage,
|
SimpleMovingAverage,
|
||||||
SourceSlot,
|
SourceSlot,
|
||||||
@@ -39,6 +44,9 @@ __all__ = [
|
|||||||
"ParquetPoseSink",
|
"ParquetPoseSink",
|
||||||
"PoseBatchRequest",
|
"PoseBatchRequest",
|
||||||
"PoseDetections",
|
"PoseDetections",
|
||||||
|
"patch_mmdet_init_file",
|
||||||
|
"patch_mmdet_init_text",
|
||||||
|
"resolve_mmdet_init_path",
|
||||||
"SimpleMovingAverage",
|
"SimpleMovingAverage",
|
||||||
"SourceFrame",
|
"SourceFrame",
|
||||||
"SourceSlot",
|
"SourceSlot",
|
||||||
|
|||||||
@@ -0,0 +1,93 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import importlib.util
|
||||||
|
from pathlib import Path
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
|
||||||
|
PATCH_SENTINEL = "Proceeding anyway because this environment intentionally uses the local build."
|
||||||
|
|
||||||
|
MMCV_ASSERT_RE = re.compile(
|
||||||
|
r"assert\s*\(mmcv_version >= digit_version\(mmcv_minimum_version\)\s*"
|
||||||
|
r"and mmcv_version < digit_version\(mmcv_maximum_version\)\), \\\n"
|
||||||
|
r"\s*f'MMCV==\{mmcv\.__version__\} is used but incompatible\. ' \\\n"
|
||||||
|
r"\s*f'Please install mmcv>=\{mmcv_minimum_version\}, ' \\\n"
|
||||||
|
r"\s*f'<\{mmcv_maximum_version\}\.'",
|
||||||
|
re.MULTILINE,
|
||||||
|
)
|
||||||
|
|
||||||
|
WARN_IMPORT = "from warnings import warn\n"
|
||||||
|
WARN_BLOCK = """if not (
|
||||||
|
mmcv_version >= digit_version(mmcv_minimum_version)
|
||||||
|
and mmcv_version < digit_version(mmcv_maximum_version)
|
||||||
|
):
|
||||||
|
warn(
|
||||||
|
f'MMCV=={mmcv.__version__} is outside the tested mmdet range '
|
||||||
|
f'[{mmcv_minimum_version}, {mmcv_maximum_version}). '
|
||||||
|
'Proceeding anyway because this environment intentionally uses the local build.',
|
||||||
|
stacklevel=1,
|
||||||
|
)"""
|
||||||
|
|
||||||
|
|
||||||
|
def patch_mmdet_init_text(source: str) -> tuple[str, bool]:
|
||||||
|
if PATCH_SENTINEL in source:
|
||||||
|
return source, False
|
||||||
|
|
||||||
|
if WARN_IMPORT not in source:
|
||||||
|
import_anchor = "from mmengine.utils import digit_version\n"
|
||||||
|
if import_anchor not in source:
|
||||||
|
raise RuntimeError("Could not find the mmengine digit_version import anchor in mmdet.__init__.")
|
||||||
|
source = source.replace(import_anchor, import_anchor + WARN_IMPORT, 1)
|
||||||
|
|
||||||
|
patched, count = MMCV_ASSERT_RE.subn(WARN_BLOCK, source, count=1)
|
||||||
|
if count != 1:
|
||||||
|
raise RuntimeError("Could not find the expected mmcv compatibility assert in mmdet.__init__.")
|
||||||
|
return patched, True
|
||||||
|
|
||||||
|
|
||||||
|
def resolve_mmdet_init_path() -> Path:
|
||||||
|
spec = importlib.util.find_spec("mmdet")
|
||||||
|
if spec is None or spec.origin is None:
|
||||||
|
raise RuntimeError("Could not locate the installed `mmdet` package.")
|
||||||
|
path = Path(spec.origin)
|
||||||
|
if path.name != "__init__.py":
|
||||||
|
raise RuntimeError(f"Expected mmdet to resolve to __init__.py, got: {path}")
|
||||||
|
return path
|
||||||
|
|
||||||
|
|
||||||
|
def patch_mmdet_init_file(path: Path) -> bool:
|
||||||
|
source = path.read_text(encoding="utf-8")
|
||||||
|
patched, changed = patch_mmdet_init_text(source)
|
||||||
|
if changed:
|
||||||
|
path.write_text(patched, encoding="utf-8")
|
||||||
|
return changed
|
||||||
|
|
||||||
|
|
||||||
|
def build_parser() -> argparse.ArgumentParser:
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
description="Patch the installed mmdet version gate to allow the local mmcv 2.2.0 build.",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--path",
|
||||||
|
type=Path,
|
||||||
|
default=None,
|
||||||
|
help="Optional explicit path to mmdet/__init__.py. Defaults to the installed package.",
|
||||||
|
)
|
||||||
|
return parser
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
parser = build_parser()
|
||||||
|
args = parser.parse_args()
|
||||||
|
path = args.path if args.path is not None else resolve_mmdet_init_path()
|
||||||
|
changed = patch_mmdet_init_file(path)
|
||||||
|
if changed:
|
||||||
|
print(f"Patched mmdet version gate at {path}")
|
||||||
|
else:
|
||||||
|
print(f"mmdet version gate already patched at {path}")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from pose_tracking_exp.detection.patch_mmdet import PATCH_SENTINEL, patch_mmdet_init_file, patch_mmdet_init_text
|
||||||
|
|
||||||
|
|
||||||
|
UNPATCHED_INIT = """# Copyright (c) OpenMMLab. All rights reserved.
|
||||||
|
import mmcv
|
||||||
|
import mmengine
|
||||||
|
from mmengine.utils import digit_version
|
||||||
|
|
||||||
|
from .version import __version__, version_info
|
||||||
|
|
||||||
|
mmcv_minimum_version = '2.0.0rc4'
|
||||||
|
mmcv_maximum_version = '2.2.0'
|
||||||
|
mmcv_version = digit_version(mmcv.__version__)
|
||||||
|
|
||||||
|
mmengine_minimum_version = '0.7.1'
|
||||||
|
mmengine_maximum_version = '1.0.0'
|
||||||
|
mmengine_version = digit_version(mmengine.__version__)
|
||||||
|
|
||||||
|
assert (mmcv_version >= digit_version(mmcv_minimum_version)
|
||||||
|
and mmcv_version < digit_version(mmcv_maximum_version)), \\
|
||||||
|
f'MMCV=={mmcv.__version__} is used but incompatible. ' \\
|
||||||
|
f'Please install mmcv>={mmcv_minimum_version}, ' \\
|
||||||
|
f'<{mmcv_maximum_version}.'
|
||||||
|
|
||||||
|
assert (mmengine_version >= digit_version(mmengine_minimum_version)
|
||||||
|
and mmengine_version < digit_version(mmengine_maximum_version)), \\
|
||||||
|
f'MMEngine=={mmengine.__version__} is used but incompatible. ' \\
|
||||||
|
f'Please install mmengine>={mmengine_minimum_version}, ' \\
|
||||||
|
f'<{mmengine_maximum_version}.'
|
||||||
|
|
||||||
|
__all__ = ['__version__', 'version_info', 'digit_version']
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def test_patch_mmdet_init_text_rewrites_mmcv_assert() -> None:
|
||||||
|
patched, changed = patch_mmdet_init_text(UNPATCHED_INIT)
|
||||||
|
|
||||||
|
assert changed is True
|
||||||
|
assert PATCH_SENTINEL in patched
|
||||||
|
assert "from warnings import warn" in patched
|
||||||
|
assert "assert (mmengine_version >= digit_version(mmengine_minimum_version)" in patched
|
||||||
|
|
||||||
|
|
||||||
|
def test_patch_mmdet_init_file_is_idempotent(tmp_path: Path) -> None:
|
||||||
|
init_path = tmp_path / "__init__.py"
|
||||||
|
init_path.write_text(UNPATCHED_INIT, encoding="utf-8")
|
||||||
|
|
||||||
|
assert patch_mmdet_init_file(init_path) is True
|
||||||
|
once = init_path.read_text(encoding="utf-8")
|
||||||
|
assert PATCH_SENTINEL in once
|
||||||
|
|
||||||
|
assert patch_mmdet_init_file(init_path) is False
|
||||||
|
twice = init_path.read_text(encoding="utf-8")
|
||||||
|
assert once == twice
|
||||||
Reference in New Issue
Block a user