diff --git a/app.py b/app.py index 665b306..4e9eea0 100644 --- a/app.py +++ b/app.py @@ -29,7 +29,7 @@ app.config["MAX_CONTENT_LENGTH"] = 2 * 1024 * 1024 * 1024 # 2GB MODELS_DIR = os.getenv("MODELS_DIR", "./models") UPLOAD_DIR = os.getenv("UPLOAD_DIR", "./uploads") -OUTPUT_DIR = os.getenv("OUTPUT_DIR", "./output") +OUTPUT_DIR = os.path.abspath(os.getenv("OUTPUT_DIR", "./output")) os.makedirs(UPLOAD_DIR, exist_ok=True) os.makedirs(OUTPUT_DIR, exist_ok=True) diff --git a/src/zone_config.py b/src/zone_config.py index 2e96cad..9bdb64c 100644 --- a/src/zone_config.py +++ b/src/zone_config.py @@ -22,31 +22,38 @@ def save_zone_config(config: dict[str, Any], path: str) -> None: def build_static_roi(zone: dict[str, Any], video_w: int, video_h: int) -> TruckROI: - """Build a TruckROI from a zone config dict. + """Build a TruckROI from a zone config dict, scaling from reference resolution. The zone dict should have: - truck_area: [[x1,y1], [x2,y2], [x3,y3], [x4,y4]] (4-point polygon) - count_line: {"x_start": int, "x_end": int, "y": int} - Coordinates are in the video's native resolution (no scaling needed - if the zone was calibrated for this resolution). + - reference_resolution: [w, h] (optional, default [1920, 1080]) + + Coordinates are stored at reference_resolution and scaled to actual video_w/video_h. """ truck_pts = zone.get("truck_area", []) count_line = zone.get("count_line", {}) + ref_w, ref_h = zone.get("reference_resolution", [1920, 1080]) - # Compute bounding box of truck_area polygon + # Scale factors from reference resolution to actual video + sx = video_w / ref_w if ref_w else 1.0 + sy = video_h / ref_h if ref_h else 1.0 + + # Compute bounding box of truck_area polygon (scaled) if truck_pts and len(truck_pts) >= 4: - xs = [p[0] for p in truck_pts] - ys = [p[1] for p in truck_pts] + xs = [int(p[0] * sx) for p in truck_pts] + ys = [int(p[1] * sy) for p in truck_pts] x1 = max(0, min(xs)) y1 = max(0, min(ys)) x2 = min(video_w, max(xs)) y2 = min(video_h, max(ys)) else: - # Fallback: use 50% of frame x1, y1 = int(video_w * 0.3), int(video_h * 0.1) x2, y2 = int(video_w * 0.75), int(video_h * 0.95) - line_y = count_line.get("y", int((y1 + y2) / 2)) + line_y = int(count_line.get("y", (y1 + y2) // 2) * sy) + line_x_start = int(count_line.get("x_start", x1) * sx) + line_x_end = int(count_line.get("x_end", x2) * sx) return TruckROI( x1=x1, y1=y1, x2=x2, y2=y2, diff --git a/tests/test_zone_config.py b/tests/test_zone_config.py index d7397ff..849894c 100644 --- a/tests/test_zone_config.py +++ b/tests/test_zone_config.py @@ -17,6 +17,7 @@ def test_build_static_roi_from_zone_config(): zone = { "truck_area": [[100, 50], [110, 300], [400, 310], [390, 40]], "count_line": {"x_start": 110, "x_end": 400, "y": 150}, + "reference_resolution": [640, 480], } roi = build_static_roi(zone, video_w=640, video_h=480) assert roi.line_y == 150 @@ -36,6 +37,18 @@ def test_build_static_roi_scales_coords(): assert roi.x2 == 1404 +def test_build_static_roi_scales_from_1080p_to_720p(): + zone = { + "truck_area": [[600, 385], [609, 1076], [1404, 1078], [1381, 343]], + "count_line": {"x_start": 577, "x_end": 1401, "y": 330}, + "reference_resolution": [1920, 1080], + } + roi = build_static_roi(zone, video_w=1280, video_h=720) + assert roi.line_y == int(330 * 720 / 1080) + assert roi.x1 == int(600 * 1280 / 1920) + assert roi.x2 == int(1404 * 1280 / 1920) + + def test_list_zone_presets(tmp_path): cfg = {"presets": {"p1": {"description": "Zone 1"}, "p2": {"description": "Zone 2"}}} p = tmp_path / "zones.json" diff --git a/zones.json b/zones.json index 8ee4ab2..7d8c75e 100644 --- a/zones.json +++ b/zones.json @@ -3,11 +3,20 @@ "default": { "truck_area": [[600, 385], [609, 1076], [1404, 1078], [1381, 343]], "counting_area": [[574, 50], [586, 1077], [1418, 1076], [1397, 50]], + "palet": [[612, 628], [1454, 630], [1458, 1074], [608, 1071]], "count_line": {"x_start": 577, "x_end": 1401, "y": 330}, + "reference_resolution": [1920, 1080], "left_limit": 0.27578, "right_limit": 0.72578, "duplicate_circle_radius": 30, - "description": "Default zone for 1920x1080 camera" + "min_valid_area": 15000, + "jarak_toleransi_duplikat": 30, + "max_reid_transit_distance": 400, + "circle_stay_timeout_sec": 10.0, + "inference_stride": 2, + "confirm_delay_sec": 0.5, + "exit_confirm_delay_sec": 6.0, + "description": "Default zone from karung-counting-feedmill-semarang (1920x1080 native)" } }, "active_preset": "default"