Skip to content

Commit d60c18f

Browse files
committed
Merge pull request #8 from PyTs1n9/fix/issue-6
Accept export UI, cache checks, decord compatibility and previously integrated SQLite/UUID fixes. Preserve port and GPU behavior; exclude local database; fix cross-platform launcher. Release v1.5.16.
2 parents 1b52fb9 + 9b85095 commit d60c18f

15 files changed

Lines changed: 155 additions & 8 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,14 @@
11
# Changelog
22

3+
## v1.5.16 (2026-09-09)
4+
5+
- Merge: PR #8 by PyTs1n9 with maintainer compatibility adjustments.
6+
- Feat: add full-history YOLO Seg export to the training panel.
7+
- Fix: register the bundled decord fallback on macOS/Windows.
8+
- Fix: check required model cache files before skipping downloads.
9+
- Fix: resolve frontend launch commands across Windows and Unix platforms.
10+
- Retain existing port and GPU behavior; exclude the contributor's local database.
11+
312
## v1.5.15 (2026-09-09)
413

514
- Fix: normalize repository string IDs to UUIDs for SQLite reads and annotation writes (PyTs1n9, PR #8).

‎backend/app/services/locate_anything.py‎

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,15 @@ def _ensure_decord_stub():
2929
if _decord_loaded:
3030
return
3131
if sys.platform in ("darwin", "win32"):
32-
stub_dir = str(Path(settings.resolved_model_dir) / "stubs")
33-
if Path(stub_dir).exists() and stub_dir not in sys.path:
34-
sys.path.insert(0, stub_dir)
32+
try:
33+
import decord # type: ignore[import-not-found] # noqa: F401
34+
except ImportError:
35+
import decord_stub
36+
37+
# Transformers checks optional imports before loading remote model
38+
# code. Register the bundled image-only compatibility shim under
39+
# the package name it expects.
40+
sys.modules["decord"] = decord_stub
3541
_decord_loaded = True
3642

3743

@@ -393,6 +399,7 @@ def detect(image_path: str | Path, categories: list[str]) -> dict:
393399
try:
394400
result = worker.detect(img, categories)
395401
raw_text = result["answer"]
402+
logger.debug("Raw model response: %r", raw_text)
396403
except Exception as exc:
397404
raise InferenceError(f"Model inference failed: {exc}") from exc
398405

‎backend/pyproject.toml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "locate-anything-backend"
3-
version = "1.5.15"
3+
version = "1.5.16"
44
description = "Backend API for VLM-AutoYOLO - VLM-powered auto-labeling and YOLO training platform"
55
requires-python = ">=3.12"
66
dependencies = [

‎backend/tests/test_cli.py‎

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,38 @@
1616
SPEC.loader.exec_module(cli)
1717

1818

19+
def test_partial_model_cache_downloads(monkeypatch, tmp_path):
20+
model = tmp_path / "model"
21+
model.mkdir()
22+
(model / "config.json").write_text("{}")
23+
calls = []
24+
monkeypatch.setattr(cli, "BACKEND", tmp_path)
25+
monkeypatch.setattr(cli, "run", lambda *a, **k: calls.append(a))
26+
cli.download_vlm()
27+
assert calls
28+
29+
30+
@pytest.mark.parametrize("windows", [True, False])
31+
def test_frontend_launch_resolves_tool(monkeypatch, windows):
32+
import urllib.request
33+
from types import SimpleNamespace
34+
35+
calls = []
36+
monkeypatch.setattr(cli, "IS_WIN", windows)
37+
monkeypatch.setattr(cli, "is_port_open", lambda _: False)
38+
monkeypatch.setattr(cli.time, "sleep", lambda _: None)
39+
monkeypatch.setattr(cli.shutil, "which", lambda _: "pnpm.cmd" if windows else "/bin/pnpm")
40+
monkeypatch.setattr(urllib.request, "urlopen", lambda *a, **k: None)
41+
monkeypatch.setattr(
42+
cli.subprocess,
43+
"Popen",
44+
lambda cmd, **k: calls.append(cmd) or SimpleNamespace(poll=lambda: None),
45+
)
46+
cli.start_frontend()
47+
expected = ["cmd", "/d", "/c", "pnpm.cmd"] if windows else ["/bin/pnpm"]
48+
assert calls[0] == [*expected, "dev", "--port", str(cli.FRONTEND_PORT)]
49+
50+
1951
def test_failed_migration_stops_setup(monkeypatch, capsys):
2052
monkeypatch.setattr(cli, "ALEMBIC", sys.executable)
2153
with pytest.raises(subprocess.CalledProcessError):

‎backend/tests/test_locate_anything.py‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,27 @@
55
from app.services.locate_anything import parse_boxes
66

77

8+
def test_bundled_decord_fallback(monkeypatch):
9+
import builtins
10+
import sys
11+
12+
from app.services import locate_anything
13+
14+
original = builtins.__import__
15+
16+
def without_decord(name, *args, **kwargs):
17+
if name == "decord":
18+
raise ImportError("decord unavailable")
19+
return original(name, *args, **kwargs)
20+
21+
monkeypatch.setattr(builtins, "__import__", without_decord)
22+
monkeypatch.setattr(sys, "platform", "win32")
23+
monkeypatch.delitem(sys.modules, "decord", raising=False)
24+
monkeypatch.setattr(locate_anything, "_decord_loaded", False)
25+
locate_anything._ensure_decord_stub()
26+
assert sys.modules["decord"].VideoReader
27+
28+
829
def test_parse_boxes_single():
930
raw = "<ref>cat</ref><box><100><200><300><400></box>"
1031
boxes = parse_boxes(raw, 1000, 1000)

‎cli.py‎

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -214,10 +214,29 @@ def check_sam3_token():
214214
def download_vlm():
215215
step("VLM model (LocateAnything-3B, ~6GB)...")
216216
model_dir = BACKEND / "model"
217-
if model_dir.exists() and any(model_dir.iterdir()):
217+
required_files = (
218+
"config.json",
219+
"model.safetensors.index.json",
220+
"model-00001-of-00002.safetensors",
221+
"model-00002-of-00002.safetensors",
222+
"modeling_locateanything.py",
223+
"modeling_qwen2.py",
224+
"modeling_vit.py",
225+
"preprocessor_config.json",
226+
"processing_locateanything.py",
227+
"processor_config.json",
228+
"special_tokens_map.json",
229+
"tokenizer_config.json",
230+
"vocab.json",
231+
)
232+
if (
233+
model_dir.exists()
234+
and not any(model_dir.glob("*.segments.json"))
235+
and all((model_dir / name).is_file() for name in required_files)
236+
):
218237
ok("already cached")
219238
return
220-
print(f" Downloading to {model_dir}... (~10-30 min)")
239+
print(f" Downloading missing model files to {model_dir}... (~10-30 min)")
221240
try:
222241
run(
223242
[str(PYTHON), "-c",
@@ -316,7 +335,7 @@ def start_frontend():
316335
warn(f"Port {FRONTEND_PORT} already in use. Try: FRONTEND_PORT={FRONTEND_PORT + 1} python3 cli.py start")
317336
return None
318337
proc = subprocess.Popen(
319-
["pnpm", "dev", "--port", str(FRONTEND_PORT)],
338+
_tool_command("pnpm", "dev", "--port", str(FRONTEND_PORT)),
320339
cwd=FRONTEND,
321340
env={**os.environ, "VITE_BACKEND_PORT": str(BACKEND_PORT)},
322341
stdout=subprocess.DEVNULL,
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# PR #8 integration review
2+
3+
Accept the full-history export UI, bundled decord fallback and model cache
4+
validation, alongside the already accepted SQLite/UUID fixes. Retain existing
5+
export API defaults. Resolve frontend launching through the cross-platform
6+
helper rather than hardcoding pnpm.cmd. Keep port 8000 and exclude local DB.
7+
8+
Do not apply automatic CPU offload or reduce generation length/image size:
9+
the custom model generation loop directly calls vision/MLP modules and needs
10+
hardware validation for dispatch; reducing tokens caps the detection output
11+
for all devices. These are separate tuning changes without accuracy evidence.
12+
13+
Acceptance: export button calls full-history API, bundled stub imports when
14+
decord is absent, incomplete caches trigger download, frontend launcher works
15+
on both platforms. Preserve original PR ancestry in the merge commit.
16+
17+
RED: four backend failures (cache, two launcher platforms, decord) and missing
18+
frontend export button. GREEN: 83 backend tests passed, 20 skipped; 43 frontend
19+
tests passed. Backend lint/format and frontend lint/TypeScript/build passed.
20+
REFACTOR: import ordering and test formatting. GPU inference on physical CUDA
21+
was not verified, so GPU tuning is excluded. No persisted schema change beyond previously
22+
accepted compatibility fixes. Rollback via reverting merge; do not overwrite DB.

‎frontend/package.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "frontend",
33
"private": true,
4-
"version": "1.5.15",
4+
"version": "1.5.16",
55
"type": "module",
66
"scripts": {
77
"dev": "vite",

‎frontend/src/auto-imports.d.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ declare global {
8585
const downloadModelUrl: typeof import('./services/api').downloadModelUrl
8686
const downloadOnnxUrl: typeof import('./services/api').downloadOnnxUrl
8787
const downloadYoloTxt: typeof import('./lib/yoloExport').downloadYoloTxt
88+
const exportAll: typeof import('./services/api').exportAll
8889
const exportBatch: typeof import('./services/api').exportBatch
8990
const exportSingleUrl: typeof import('./services/api').exportSingleUrl
9091
const extractKeyframes: typeof import('./services/api').extractKeyframes

‎frontend/src/components/TrainingPanel/index.test.tsx‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,11 @@ vi.mock("@/store/useAppStore", () => ({
1010
}));
1111

1212
describe("TrainingPanel", () => {
13+
it("offers full-history export when records exist outside the loaded page", () => {
14+
const { getByRole } = render(<TrainingPanel detections={[]} total={100}
15+
hasNextPage={true} isFetchingNextPage={false} fetchNextPage={() => {}} />);
16+
expect(getByRole("button", { name: /downloadAllYoloSeg|Export All YOLO Seg|导出全部/ })).toBeTruthy();
17+
});
1318
it("renders without crashing", () => {
1419
// Note: Provide basic props if needed, or mock stores/hooks
1520
// This is a basic boilerplate test

0 commit comments

Comments
 (0)