refactor: consolidate counting config through src/config_loader

- config.yaml gains counting.max_reid_frames (200) + debounce_frames (8);
  CountingConfig + parsing + pinned-value test added
- predict.py load_zones() delegates to read_zone_polygons() (geometry only);
  legacy zones.json knob reads removed (config.yaml is canonical)
- run_prediction wires 5 more live knobs from cfg.counting.* (entry/exit
  overlap, tolerance_missing_frames, max_reid_frames, debounce_frames)
- delete deprecated src/config.py (v3 keys) + tests/test_config.py;
  src/main.py bridges onto unified config so the deprecated entrypoint
  keeps working
- 8 never-read globals (JARAK_ABSORBSI_GHOST, CAMERA_NOISE_DEADBAND, ...)
  left untouched — flagged for Phase 2 dead-code cleanup

Verified: pytest 21 passed; full pipeline runs on synthetic video
(engines load, 30 frames, clean finish).
This commit is contained in:
andrew committed 2026-09-17 12:59:54 +07:00
1 parent 0467e0ce68
commit 12b70a3431
7 files changed
+94 -128

No files matched your search

+26 -33
View File
@@ -30,7 +30,6 @@ from src.config_loader import (
load_config,
read_zone_polygons,
resolve_active_mode,
LEGACY_ZONE_KNOBS,
)
# --- SQLite Database & State Configuration ---
@@ -649,43 +648,30 @@ DEFAULT_TRUCK = []
def load_zones():
global ZONA_PALET_REF, ZONA_TRUCK_REF, GARIS_COUNTING_REF, DUPLICATE_CIRCLE_RADIUS_REF
global MIN_VALID_AREA_REF, JARAK_TOLERANSI_DUPLIKAT_REF, MAX_REID_TRANSIT_DISTANCE_REF
global CIRCLE_STAY_TIMEOUT_SEC, INFERENCE_STRIDE, CONFIRM_DELAY_SEC, EXIT_CONFIRM_DELAY_SEC
"""Load zone geometry from zones.json (polygons only — knobs live in config.yaml).
Delegates to src.config_loader.read_zone_polygons: legacy knob keys in
zones.json are ignored with a warning (config.yaml counting.* wins).
ZONA_COUNTING_REF is derived from the truck polygon (bottom quarter),
not from the file's "counting" key — preserved pre-YAML behaviour.
"""
global ZONA_PALET_REF, ZONA_TRUCK_REF, GARIS_COUNTING_REF
global ZONA_COUNTING_REF, left_limit_ref, right_limit_ref, EXTERNAL_STREAM_URL_REF
if os.path.exists(ZONES_JSON_PATH):
try:
with open(ZONES_JSON_PATH, 'r') as f:
data = json.load(f)
ZONA_PALET_REF = np.array(data.get('palet', []), dtype=np.int32)
ZONA_TRUCK_REF = np.array(data.get('truck', []), dtype=np.int32)
ZONA_COUNTING_REF = get_bottom_quarter(ZONA_TRUCK_REF)
left_limit_ref = float(data.get('left_limit', 0.05))
right_limit_ref = float(data.get('right_limit', 0.95))
GARIS_COUNTING_REF = ZONA_TRUCK_REF.copy()
DUPLICATE_CIRCLE_RADIUS_REF = data.get('duplicate_circle_radius', 30)
MIN_VALID_AREA_REF = data.get('min_valid_area', 15000)
JARAK_TOLERANSI_DUPLIKAT_REF = data.get('jarak_toleransi_duplikat', 20)
MAX_REID_TRANSIT_DISTANCE_REF = data.get('max_reid_transit_distance', 400)
CIRCLE_STAY_TIMEOUT_SEC = data.get('circle_stay_timeout_sec', 10.0)
INFERENCE_STRIDE = data.get('inference_stride', 2)
CONFIRM_DELAY_SEC = data.get('confirm_delay_sec', 0.5)
EXIT_CONFIRM_DELAY_SEC = data.get('exit_confirm_delay_sec', 6.0)
EXTERNAL_STREAM_URL_REF = data.get('external_stream_url', 'http://192.168.192.96:8888/cam/')
if not EXTERNAL_STREAM_URL_REF:
EXTERNAL_STREAM_URL_REF = 'http://192.168.192.96:8888/cam/'
# Knob keys below are LEGACY: config.yaml counting.* is canonical and
# run_prediction overrides these globals from it. Kept reading here
# only so import-time defaults stay sane; warn once to guide migration.
_legacy_knobs = sorted(LEGACY_ZONE_KNOBS & set(data.keys()))
if _legacy_knobs:
print(f"[WARN] zones.json knob keys {_legacy_knobs} diabaikan — "
f"pakai config.yaml (counting.*) sebagai gantinya.")
print("[INFO] Berhasil memuat koordinat zona dan parameter kalibrasi dari zones.json")
return
zones = read_zone_polygons(ZONES_JSON_PATH)
ZONA_PALET_REF = np.array(zones.get('palet', []), dtype=np.int32)
ZONA_TRUCK_REF = np.array(zones.get('truck', []), dtype=np.int32)
ZONA_COUNTING_REF = get_bottom_quarter(ZONA_TRUCK_REF)
left_limit_ref = float(zones.get('left_limit', 0.05))
right_limit_ref = float(zones.get('right_limit', 0.95))
GARIS_COUNTING_REF = ZONA_TRUCK_REF.copy()
EXTERNAL_STREAM_URL_REF = zones.get('external_stream_url', '') or 'http://192.168.192.96:8888/cam/'
print("[INFO] Berhasil memuat koordinat zona dari zones.json (via src.config_loader)")
return
except Exception as e:
print(f"[WARNING] Gagal memuat zones.json ({e}), menggunakan default.")
ZONA_PALET_REF = np.array(DEFAULT_PALET, dtype=np.int32)
ZONA_TRUCK_REF = np.array(DEFAULT_TRUCK, dtype=np.int32)
ZONA_COUNTING_REF = get_bottom_quarter(ZONA_TRUCK_REF)
@@ -1247,6 +1233,8 @@ def run_prediction(model_path, source_path,
global width, height, CONFIRM_DELAY_SEC, EXIT_CONFIRM_DELAY_SEC
global active_batch_info, system_state
global DUPLICATE_CIRCLE_RADIUS, MIN_VALID_AREA, JARAK_TOLERANSI_DUPLIKAT, MAX_REID_TRANSIT_DISTANCE
global ENTRY_OVERLAP_THRESHOLD, EXIT_OVERLAP_THRESHOLD, TOLERANSI_FRAME_HILANG
global MAX_REID_FRAMES, DEBOUNCE_FRAMES
global DB_PATH, STATE_FILE, BATCH_MODE_FILE, LIVE_STREAM_FRAME_PATH
global CAMERA_NAME, OBJECT_LABEL, DAILY_CUTOFF_TIME, BATCH_MERGE_THRESHOLD_SECONDS
global CIRCLE_STAY_TIMEOUT_SEC, _CFG
@@ -1293,6 +1281,11 @@ def run_prediction(model_path, source_path,
JARAK_TOLERANSI_DUPLIKAT = cfg.counting.jarak_toleransi_duplikat
MAX_REID_TRANSIT_DISTANCE = cfg.counting.max_reid_transit_distance
CIRCLE_STAY_TIMEOUT_SEC = cfg.counting.circle_stay_timeout_sec
ENTRY_OVERLAP_THRESHOLD = cfg.counting.entry_overlap_threshold
EXIT_OVERLAP_THRESHOLD = cfg.counting.exit_overlap_threshold
TOLERANSI_FRAME_HILANG = cfg.counting.tolerance_missing_frames
MAX_REID_FRAMES = cfg.counting.max_reid_frames
DEBOUNCE_FRAMES = cfg.counting.debounce_frames
# Legacy batch_mode.json no longer drives the mode — nudge once if stale.
_legacy_warn = check_legacy_batch_mode(cfg, BATCH_MODE_FILE)