Add crop manager for staging and training image lifecycle
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
4c9ebe029d
commit
45007dcac2
48
tests/unit/test_crop_manager.py
Normal file
48
tests/unit/test_crop_manager.py
Normal file
@ -0,0 +1,48 @@
|
||||
"""Tests for detection crop saving and staging cleanup."""
|
||||
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
import numpy as np
|
||||
|
||||
from vigilar.detection.crop_manager import CropManager
|
||||
|
||||
|
||||
class TestCropManager:
|
||||
def test_save_crop(self, tmp_path):
|
||||
manager = CropManager(staging_dir=str(tmp_path / "staging"),
|
||||
training_dir=str(tmp_path / "training"))
|
||||
crop = np.zeros((100, 80, 3), dtype=np.uint8)
|
||||
path = manager.save_staging_crop(crop, species="cat", camera_id="kitchen")
|
||||
assert Path(path).exists()
|
||||
assert "cat" in path
|
||||
assert "kitchen" in path
|
||||
|
||||
def test_promote_to_training(self, tmp_path):
|
||||
manager = CropManager(staging_dir=str(tmp_path / "staging"),
|
||||
training_dir=str(tmp_path / "training"))
|
||||
crop = np.zeros((100, 80, 3), dtype=np.uint8)
|
||||
staging_path = manager.save_staging_crop(crop, species="cat", camera_id="kitchen")
|
||||
training_path = manager.promote_to_training(staging_path, pet_name="angel")
|
||||
assert Path(training_path).exists()
|
||||
assert "angel" in training_path
|
||||
assert not Path(staging_path).exists()
|
||||
|
||||
def test_cleanup_old_crops(self, tmp_path):
|
||||
staging = tmp_path / "staging"
|
||||
staging.mkdir(parents=True)
|
||||
|
||||
old_file = staging / "old_crop.jpg"
|
||||
old_file.write_bytes(b"fake")
|
||||
old_time = time.time() - 10 * 86400
|
||||
import os
|
||||
os.utime(old_file, (old_time, old_time))
|
||||
|
||||
new_file = staging / "new_crop.jpg"
|
||||
new_file.write_bytes(b"fake")
|
||||
|
||||
manager = CropManager(staging_dir=str(staging), training_dir=str(tmp_path / "training"))
|
||||
deleted = manager.cleanup_expired(retention_days=7)
|
||||
assert deleted == 1
|
||||
assert not old_file.exists()
|
||||
assert new_file.exists()
|
||||
48
vigilar/detection/crop_manager.py
Normal file
48
vigilar/detection/crop_manager.py
Normal file
@ -0,0 +1,48 @@
|
||||
"""Manage detection crop images for training and staging."""
|
||||
|
||||
import logging
|
||||
import shutil
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
import cv2
|
||||
import numpy as np
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class CropManager:
|
||||
def __init__(self, staging_dir: str, training_dir: str):
|
||||
self._staging_dir = Path(staging_dir)
|
||||
self._training_dir = Path(training_dir)
|
||||
|
||||
def save_staging_crop(self, crop: np.ndarray, species: str, camera_id: str) -> str:
|
||||
self._staging_dir.mkdir(parents=True, exist_ok=True)
|
||||
timestamp = int(time.time() * 1000)
|
||||
filename = f"{species}_{camera_id}_{timestamp}.jpg"
|
||||
filepath = self._staging_dir / filename
|
||||
cv2.imwrite(str(filepath), crop)
|
||||
return str(filepath)
|
||||
|
||||
def promote_to_training(self, staging_path: str, pet_name: str) -> str:
|
||||
pet_dir = self._training_dir / pet_name.lower()
|
||||
pet_dir.mkdir(parents=True, exist_ok=True)
|
||||
src = Path(staging_path)
|
||||
dst = pet_dir / src.name
|
||||
shutil.move(str(src), str(dst))
|
||||
return str(dst)
|
||||
|
||||
def cleanup_expired(self, retention_days: int = 7) -> int:
|
||||
if not self._staging_dir.exists():
|
||||
return 0
|
||||
|
||||
cutoff = time.time() - retention_days * 86400
|
||||
deleted = 0
|
||||
for filepath in self._staging_dir.iterdir():
|
||||
if filepath.is_file() and filepath.stat().st_mtime < cutoff:
|
||||
filepath.unlink()
|
||||
deleted += 1
|
||||
|
||||
if deleted:
|
||||
log.info("Cleaned up %d expired staging crops", deleted)
|
||||
return deleted
|
||||
Loading…
Reference in New Issue
Block a user