diff --git a/Python/OAK/datasets/multiespec_module/_0_capture.py b/Python/OAK/datasets/multiespec_module/_0_capture.py index 7f1d9a7e0..88de24f73 100644 --- a/Python/OAK/datasets/multiespec_module/_0_capture.py +++ b/Python/OAK/datasets/multiespec_module/_0_capture.py @@ -17,6 +17,13 @@ STREAM_PORT = 6001 PI_HOST = "192.168.105.6" PC_HOST = "192.168.105.5" +with open("config.json", "r", encoding="utf-8") as f: + config = json.load(f) + +MODELO = config.get("camera", ".") +RAW_SIZE = config.get("raw_size", [1296, 1028]) # [W, H] +CAMERA_PARAMS = config.get("camera_params_json") + # ========================= # Helpers gerais @@ -280,22 +287,6 @@ def build_preview_from_raw_payload( raise RuntimeError(f"Tipo de frame não suportado para preview: {type(frame)}") -def resolve_effective_capture_mode(frame_type: str, raw_policy: str, requested_mode: str) -> str: - """ - Decide o modo real que será pedido ao módulo. - - Nova regra: - - Se o usuário pediu explicitamente SINGLE/DOUBLE/TRIPLE, respeitamos. - - Se pediu AUTO, deixamos AUTO ir para o módulo. - - A única exceção opcional é MULTISPEC + require_triple implícito, - mas mesmo assim podemos deixar o módulo resolver se preferirmos. - """ - if requested_mode in ("SINGLE", "DOUBLE", "TRIPLE"): - return requested_mode - - # requested_mode == AUTO - return "AUTO" - # ========================= # MAIN # ========================= @@ -314,8 +305,8 @@ def main(): parser.add_argument("--stream_port", type=int, default=STREAM_PORT, help="Porta TCP do receiver de stream.") parser.add_argument("--server_port", type=int, default=5000, help="Porta TCP do servidor de comandos no Pi.") parser.add_argument("--fps", type=int, default=20, help="FPS desejado.") - parser.add_argument("--width", type=int, default=640, help="Largura óptica da câmera.") - parser.add_argument("--height", type=int, default=480, help="Altura óptica da câmera.") + parser.add_argument("--width", type=int, default=RAW_SIZE[0], help="Largura óptica da câmera.") + parser.add_argument("--height", type=int, default=RAW_SIZE[1], help="Altura óptica da câmera.") parser.add_argument("--interval", type=float, default=1.0, help="Intervalo em segundos para auto-save quando ligado.") parser.add_argument("--preview_upscale", type=int, default=2, help="Fator de upscale visual do preview.") parser.add_argument("--bayer", default="GBRG", choices=["GBRG", "GRBG", "RGGB", "BGGR"], help="Padrão Bayer das câmeras.") @@ -323,14 +314,11 @@ def main(): parser.add_argument("--frame_type", default="RAW_BRUTO", choices=["RAW_BRUTO", "RGB", "MULTISPEC"], help="Tipo de payload pedido ao Pi.") parser.add_argument("--capture_mode", default="AUTO", choices=["AUTO", "SINGLE", "DOUBLE", "TRIPLE"], help="Modo de captura desejado no módulo.") parser.add_argument("--raw_policy", default="allow_single", choices=["allow_single", "require_triple"], help="Quando frame_type=RAW_BRUTO, define se o script aceita 1 câmera ou exige 3.") + parser.add_argument("--camera_params_json", default=CAMERA_PARAMS, help="JSON salvo pelo calibrador de sensores com parâmetros fixos por câmera.") args = parser.parse_args() - effective_capture_mode = resolve_effective_capture_mode( - frame_type=args.frame_type, - raw_policy=args.raw_policy, - requested_mode=args.capture_mode, - ) + effective_capture_mode = args.capture_mode raw_w = args.width raw_h = args.height @@ -451,44 +439,17 @@ def main(): validate_module_ready(status, args.frame_type, args.raw_policy, effective_capture_mode) + params_resp = svc.apply_camera_params_json(args.camera_params_json) + if params_resp is not None: + camera_settings = params_resp["camera_settings"] + applied_camera_controls = params_resp["applied"] + print("[OK] Parâmetros fixos das câmeras aplicados:") + print(json.dumps(applied_camera_controls, ensure_ascii=False, indent=2)) + else: + print("[OK] Parâmetros fixos das câmeras não aplicados") + print("START STREAM:", svc.start_stream(args.pc_host, args.stream_port, fps=args.fps)) - selected_cam = "cam2" # padrão: RGB - camera_controls = { - "cam0": { - "ae_enable": False, - "awb_enable": False, - "exposure_time_us": 15000, - "analogue_gain": 1.0, - "colour_gains": None, - }, - "cam1": { - "ae_enable": False, - "awb_enable": False, - "exposure_time_us": 15000, - "analogue_gain": 1.0, - "colour_gains": None, - }, - "cam2": { - "ae_enable": True, - "awb_enable": True, - "exposure_time_us": 15000, - "analogue_gain": 1.0, - "colour_gains": [1.0, 1.0], - }, - } - - for cam_id in ("cam0", "cam1", "cam2"): - try: - camera_ctrl = svc.get_camera_controls(cam_id) - camera_controls[cam_id]["ae_enable"] = bool(camera_ctrl.get("ae_enable", camera_controls[cam_id]["ae_enable"])) - camera_controls[cam_id]["awb_enable"] = bool(camera_ctrl.get("awb_enable", camera_controls[cam_id]["awb_enable"])) - camera_controls[cam_id]["exposure_time_us"] = camera_ctrl.get("exposure_time_us", camera_controls[cam_id]["exposure_time_us"]) - camera_controls[cam_id]["analogue_gain"] = camera_ctrl.get("analogue_gain", camera_controls[cam_id]["analogue_gain"]) - camera_controls[cam_id]["colour_gains"] = camera_ctrl.get("colour_gains", camera_controls[cam_id]["colour_gains"]) - except Exception as e: - print(f"[WARN] Falha ao ler controles de {cam_id}: {e}") - while True: t0 = time.time() @@ -612,15 +573,14 @@ def main(): t_view_fps = time.time() active_sources = meta.get("payload_sources") - ctrl = camera_controls[selected_cam] lines = [ f"CANA: {args.cana} | HORA: {args.horario} | Pasta: {os.path.basename(session_dir)}", f"Type={meta.get('frame_type')} | CaptureMode={effective_capture_mode} | RAW policy={args.raw_policy}", f"Sources={active_sources} | FPS_STREAM={fps_stream:.1f} | FPS_VIEW={fps_view:.1f}", f"frame_id={meta.get('frame_id')} | layout={meta.get('output_layout')} | dtype={meta.get('dtype') or meta.get('output_dtype')}", f"codec={meta.get('codec_name', meta.get('codec_family', '-'))} | comp={meta.get('dt_comp', 0):.4f}s | send={meta.get('dt_send_payload_prev', 0):.4f}s", - f"CTRL_CAM={selected_cam} | AE={'ON' if ctrl['ae_enable'] else 'OFF'} | AWB={'ON' if ctrl['awb_enable'] else 'OFF'} | EXP={ctrl['exposure_time_us']} | GAIN={ctrl['analogue_gain']}", - "Keys: 1=cam0(RE) | 2=cam1(NIR) | 3=cam2(RGB) | C/SPACE=save | A=auto-save | E=AE | W=AWB | I/K=exp | O/L=gain | R=reset | M=preview | Q/Esc=quit", + f"CAM_PARAMS={os.path.basename(args.camera_params_json)} | controles fixos aplicados", + "Keys: C/SPACE=save | A=auto-save | M=preview | Q/Esc=quit" ] overlay_hud(preview_show, lines, base_h=raw_h) @@ -666,8 +626,8 @@ def main(): "capture_mode_effective": effective_capture_mode, "raw_policy": args.raw_policy, "stream_meta": last_meta_stream, - "camera_controls": camera_controls, - "selected_control_camera": selected_cam, + "applied_camera_controls": applied_camera_controls, + "camera_params_json": args.camera_params_json, "note": "autosave", "raw_preview_reference_camera": preview_source_id, } @@ -700,115 +660,6 @@ def main(): last_msg = f"Preview UPSCALE -> {preview_upscale}" last_msg_t = time.time() - elif k in (ord("e"), ord("E")): - ctrl = camera_controls[selected_cam] - ctrl["ae_enable"] = not ctrl["ae_enable"] - resp = svc.set_ae_enable(selected_cam, ctrl["ae_enable"]) - ctrl["ae_enable"] = bool(resp.get("ae_enable", ctrl["ae_enable"])) - last_msg = f"{selected_cam} AE -> {'ON' if ctrl['ae_enable'] else 'OFF'}" - last_msg_t = time.time() - - elif k in (ord("w"), ord("W")): - ctrl = camera_controls[selected_cam] - - if selected_cam != "cam2": - last_msg = "AWB só se aplica à cam2 / RGB" - last_msg_t = time.time() - else: - ctrl["awb_enable"] = not ctrl["awb_enable"] - resp = svc.set_awb_enable(selected_cam, ctrl["awb_enable"]) - ctrl["awb_enable"] = bool(resp.get("awb_enable", ctrl["awb_enable"])) - last_msg = f"{selected_cam} AWB -> {'ON' if ctrl['awb_enable'] else 'OFF'}" - last_msg_t = time.time() - - elif k in (ord("i"), ord("I")): - ctrl = camera_controls[selected_cam] - - if ctrl["exposure_time_us"] is None: - ctrl["exposure_time_us"] = 15000 - else: - ctrl["exposure_time_us"] = min(int(ctrl["exposure_time_us"] * 1.15), 200000) - - if ctrl["ae_enable"]: - ctrl["ae_enable"] = False - svc.set_ae_enable(selected_cam, False) - - resp = svc.set_exposure_time(selected_cam, int(ctrl["exposure_time_us"])) - ctrl["exposure_time_us"] = resp.get("exposure_time_us", ctrl["exposure_time_us"]) - - last_msg = f"{selected_cam} ExposureTime -> {ctrl['exposure_time_us']} us" - last_msg_t = time.time() - - elif k in (ord("k"), ord("K")): - ctrl = camera_controls[selected_cam] - - if ctrl["exposure_time_us"] is None: - ctrl["exposure_time_us"] = 15000 - else: - ctrl["exposure_time_us"] = max(int(ctrl["exposure_time_us"] / 1.15), 100) - - if ctrl["ae_enable"]: - ctrl["ae_enable"] = False - svc.set_ae_enable(selected_cam, False) - - resp = svc.set_exposure_time(selected_cam, int(ctrl["exposure_time_us"])) - ctrl["exposure_time_us"] = resp.get("exposure_time_us", ctrl["exposure_time_us"]) - - last_msg = f"{selected_cam} ExposureTime -> {ctrl['exposure_time_us']} us" - last_msg_t = time.time() - - elif k in (ord("o"), ord("O")): - ctrl = camera_controls[selected_cam] - - if ctrl["analogue_gain"] is None: - ctrl["analogue_gain"] = 1.0 - else: - ctrl["analogue_gain"] = min(float(ctrl["analogue_gain"]) * 1.10, 32.0) - - if ctrl["ae_enable"]: - ctrl["ae_enable"] = False - svc.set_ae_enable(selected_cam, False) - - resp = svc.set_analogue_gain(selected_cam, float(ctrl["analogue_gain"])) - ctrl["analogue_gain"] = resp.get("analogue_gain", ctrl["analogue_gain"]) - - last_msg = f"{selected_cam} AnalogueGain -> {ctrl['analogue_gain']:.2f}" - last_msg_t = time.time() - - elif k in (ord("l"), ord("L")): - ctrl = camera_controls[selected_cam] - - if ctrl["analogue_gain"] is None: - ctrl["analogue_gain"] = 1.0 - else: - ctrl["analogue_gain"] = max(float(ctrl["analogue_gain"]) / 1.10, 1.0) - - if ctrl["ae_enable"]: - ctrl["ae_enable"] = False - svc.set_ae_enable(selected_cam, False) - - resp = svc.set_analogue_gain(selected_cam, float(ctrl["analogue_gain"])) - ctrl["analogue_gain"] = resp.get("analogue_gain", ctrl["analogue_gain"]) - - last_msg = f"{selected_cam} AnalogueGain -> {ctrl['analogue_gain']:.2f}" - last_msg_t = time.time() - - elif k in (ord("r"), ord("R")): - ctrl = camera_controls[selected_cam] - - svc.clear_exposure_time(selected_cam) - svc.clear_analogue_gain(selected_cam) - - if selected_cam == "cam2": - svc.clear_colour_gains(selected_cam) - - ctrl["exposure_time_us"] = None - ctrl["analogue_gain"] = None - ctrl["colour_gains"] = None if selected_cam == "cam2" else ctrl["colour_gains"] - - last_msg = f"{selected_cam} manual controls resetados" - last_msg_t = time.time() - elif k in (ord("c"), ord("C"), 32): if can_save: frame_type_save = last_meta_stream.get("frame_type") @@ -825,8 +676,8 @@ def main(): "capture_mode_effective": effective_capture_mode, "raw_policy": args.raw_policy, "stream_meta": last_meta_stream, - "camera_controls": camera_controls, - "selected_control_camera": selected_cam, + "applied_camera_controls": applied_camera_controls, + "camera_params_json": args.camera_params_json, "note": "manual", "raw_preview_reference_camera": preview_source_id, } @@ -844,21 +695,6 @@ def main(): last_msg = "SALVO (manual)" last_msg_t = time.time() - elif k == ord("1"): - selected_cam = "cam0" - last_msg = "Controle ativo -> cam0 / RE" - last_msg_t = time.time() - - elif k == ord("2"): - selected_cam = "cam1" - last_msg = "Controle ativo -> cam1 / NIR" - last_msg_t = time.time() - - elif k == ord("3"): - selected_cam = "cam2" - last_msg = "Controle ativo -> cam2 / RGB" - last_msg_t = time.time() - dt_loop = time.time() - t0 if dt_loop < 0.001: time.sleep(0.001) diff --git a/Python/OAK/datasets/multiespec_module/_9_test_segformer.py b/Python/OAK/datasets/multiespec_module/_9_test_segformer.py index 83863bade..e8cbfaf56 100644 --- a/Python/OAK/datasets/multiespec_module/_9_test_segformer.py +++ b/Python/OAK/datasets/multiespec_module/_9_test_segformer.py @@ -99,6 +99,7 @@ def main(): parser.add_argument("--alpha", type=float, default=0.45, help="Alpha do overlay da máscara") parser.add_argument("--camera_frame_type", type=str, default="MULTISPEC", choices=["RAW_BRUTO", "RGB", "MULTISPEC"]) parser.add_argument("--camera_capture_mode", type=str, default="AUTO", choices=["AUTO", "SINGLE", "DOUBLE", "TRIPLE"]) + parser.add_argument("--camera_params_json", default=None, help="JSON salvo pelo calibrador de sensores com parâmetros fixos por câmera.") args = parser.parse_args() @@ -113,6 +114,9 @@ def main(): MODEL_NAME = config["model_name"] modelo_folder = config["modelo"] + CAMERA_PARAMS = config.get("camera_params_json") + if args.camera_params_json is not None: + CAMERA_PARAMS = args.camera_params_json CHANNELS = int(config.get("channels", 5)) FUSION_MODE = config.get("fusion_mode", "stacked") RESOLUCAO = config["resolucao"] @@ -235,6 +239,15 @@ def main(): capture_mode=camera_capture_mode, )) + params_resp = svc.apply_camera_params_json(CAMERA_PARAMS) + if params_resp is not None: + camera_settings = params_resp["camera_settings"] + applied_camera_controls = params_resp["applied"] + print("[OK] Parâmetros fixos das câmeras aplicados:") + print(json.dumps(applied_camera_controls, ensure_ascii=False, indent=2)) + else: + print("[OK] Parâmetros fixos das câmeras não aplicados") + print("START STREAM:", svc.start_stream(PC_HOST, STREAM_PORT, fps=15)) win = "CAMERA MULTISPEC + SEGFORMER (Q=quit)" diff --git a/Python/OAK/datasets/multiespec_module/config.json b/Python/OAK/datasets/multiespec_module/config.json index 9364e44db..68f433ee9 100644 --- a/Python/OAK/datasets/multiespec_module/config.json +++ b/Python/OAK/datasets/multiespec_module/config.json @@ -15,5 +15,6 @@ "use_ndvi": false, "backbone": "nvidia/mit-b1", "fusion_mode": "stacked", - "stats_source_tag": "stacked_raw4" + "stats_source_tag": "stacked_raw4", + "camera_params_json": "camera_params.json" } \ No newline at end of file diff --git a/Python/OAK/datasets/multiespec_module/multispectral_service.py b/Python/OAK/datasets/multiespec_module/multispectral_service.py index 29837a472..211917ef5 100644 --- a/Python/OAK/datasets/multiespec_module/multispectral_service.py +++ b/Python/OAK/datasets/multiespec_module/multispectral_service.py @@ -3,6 +3,7 @@ import json import numpy as np import base64 import time +import os from typing import Optional @@ -428,4 +429,79 @@ class MultiSpectralService: }) def get_sensor_modes(self): - return self._send_command({"cmd": "get_sensor_modes"}) \ No newline at end of file + return self._send_command({"cmd": "get_sensor_modes"}) + + def load_camera_params_json(self, path: str) -> dict: + if not path or not os.path.isfile(path): + raise FileNotFoundError(f"Arquivo de parâmetros das câmeras não encontrado: {path}") + + with open(path, "r", encoding="utf-8") as f: + data = json.load(f) + + settings = data.get("camera_settings") + if not isinstance(settings, dict): + raise RuntimeError("JSON de calibração inválido: chave camera_settings ausente") + + return settings + + def apply_camera_settings(self, camera_settings: dict) -> dict: + applied = {} + + for cam_id in ("cam0", "cam1", "cam2"): + ctrl = camera_settings.get(cam_id) + if not ctrl: + applied[cam_id] = { + "ok": False, + "error": "sem parâmetros no JSON", + } + continue + + ae = bool(ctrl.get("ae_enable", False)) + awb = bool(ctrl.get("awb_enable", False)) + exp = ctrl.get("exposure_time_us") + gain = ctrl.get("analogue_gain") + colour_gains = ctrl.get("colour_gains") + + result = {} + + result["ae"] = self.set_ae_enable(cam_id, ae) + + if cam_id == "cam2": + result["awb"] = self.set_awb_enable(cam_id, awb) + + if not ae: + if exp is not None: + result["exposure"] = self.set_exposure_time(cam_id, int(exp)) + if gain is not None: + result["gain"] = self.set_analogue_gain(cam_id, float(gain)) + + if cam_id == "cam2" and not awb and colour_gains is not None: + r_gain, b_gain = colour_gains + result["colour_gains"] = self.set_colour_gains(cam_id, float(r_gain), float(b_gain)) + + applied[cam_id] = { + "ok": True, + "requested": ctrl, + "responses": result, + } + + return applied + + def apply_camera_params_json(self, path: str) -> dict: + if path is None: + return { + "ok": False, + "path": None, + "camera_settings": None, + "applied": None, + } + + camera_settings = self.load_camera_params_json(path) + applied = self.apply_camera_settings(camera_settings) + + return { + "ok": True, + "path": path, + "camera_settings": camera_settings, + "applied": applied, + } diff --git a/Python/raspi/calibration/mock/nir.png b/Python/raspi/calibration/mock/nir.png new file mode 100644 index 000000000..4ae29a75b Binary files /dev/null and b/Python/raspi/calibration/mock/nir.png differ diff --git a/Python/raspi/calibration/mock/re.png b/Python/raspi/calibration/mock/re.png new file mode 100644 index 000000000..f2b9cbc64 Binary files /dev/null and b/Python/raspi/calibration/mock/re.png differ diff --git a/Python/raspi/calibration/mock/rgb.png b/Python/raspi/calibration/mock/rgb.png new file mode 100644 index 000000000..6562b613b Binary files /dev/null and b/Python/raspi/calibration/mock/rgb.png differ diff --git a/Python/raspi/calibration/offline_samples/20260423_210500_000.json b/Python/raspi/calibration/offline_samples/20260423_210500_000.json new file mode 100644 index 000000000..e0f152e2f --- /dev/null +++ b/Python/raspi/calibration/offline_samples/20260423_210500_000.json @@ -0,0 +1,107 @@ +{ + "ts": "2026-04-24T00:27:10.376", + "schema": "multispec_offline_sample_v1", + "frame_type": "RAW_BRUTO", + "sensor_width": 2048, + "sensor_height": 1365, + "bayer_pattern": "GBRG", + "capture_mode_requested": "MOCK", + "capture_mode_effective": "MOCK", + "raw_policy": "allow_single", + "saved_payload_type": "raw_native_multi", + "saved_payload_paths": { + "cam0": "20260423_210500_000_cam0.bin", + "cam1": "20260423_210500_000_cam1.bin", + "cam2": "20260423_210500_000_cam2.bin" + }, + "saved_payload_shapes": { + "cam0": [ + 1365, + 2560 + ], + "cam1": [ + 1365, + 2560 + ], + "cam2": [ + 1365, + 2048, + 3 + ] + }, + "saved_payload_dtypes": { + "cam0": "uint8", + "cam1": "uint8", + "cam2": "uint8" + }, + "saved_preview_path": "20260423_210500_000.png", + "stream_meta": { + "frame_id": 1, + "frame_type": "RAW_BRUTO", + "output_layout": "multi_payload", + "payload_sources": [ + "cam0", + "cam1", + "cam2" + ], + "camera_frames": { + "cam0": { + "id": "cam0", + "role": "re", + "width": 2560, + "height": 1365, + "real_width": 2048, + "bit_depth": 10, + "bayer_pattern": "GBRG", + "mock": true + }, + "cam1": { + "id": "cam1", + "role": "nir", + "width": 2560, + "height": 1365, + "real_width": 2048, + "bit_depth": 10, + "bayer_pattern": "GBRG", + "mock": true + }, + "cam2": { + "id": "cam2", + "role": "rgb", + "width": 2048, + "height": 1365, + "bit_depth": 8, + "mock": true + } + }, + "codec_name": "offline_mock", + "dtype": "uint8" + }, + "camera_settings": { + "cam0": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 15000, + "analogue_gain": 1.0, + "colour_gains": null + }, + "cam1": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 15000, + "analogue_gain": 1.0, + "colour_gains": null + }, + "cam2": { + "ae_enable": true, + "awb_enable": true, + "exposure_time_us": 15000, + "analogue_gain": 1.0, + "colour_gains": [ + 1.0, + 1.0 + ] + } + }, + "note": "offline mock sample generated from RGB/RE/NIR PNGs for sensor_calibration_tool testing" +} \ No newline at end of file diff --git a/Python/raspi/calibration/offline_samples/20260423_210500_000.png b/Python/raspi/calibration/offline_samples/20260423_210500_000.png new file mode 100644 index 000000000..faa1bf8c0 Binary files /dev/null and b/Python/raspi/calibration/offline_samples/20260423_210500_000.png differ diff --git a/Python/raspi/calibration/offline_samples/20260423_210500_000_cam0.bin b/Python/raspi/calibration/offline_samples/20260423_210500_000_cam0.bin new file mode 100644 index 000000000..d734f78b5 Binary files /dev/null and b/Python/raspi/calibration/offline_samples/20260423_210500_000_cam0.bin differ diff --git a/Python/raspi/calibration/offline_samples/20260423_210500_000_cam1.bin b/Python/raspi/calibration/offline_samples/20260423_210500_000_cam1.bin new file mode 100644 index 000000000..045236765 Binary files /dev/null and b/Python/raspi/calibration/offline_samples/20260423_210500_000_cam1.bin differ diff --git a/Python/raspi/calibration/offline_samples/20260423_210500_000_cam2.bin b/Python/raspi/calibration/offline_samples/20260423_210500_000_cam2.bin new file mode 100644 index 000000000..c39991d77 Binary files /dev/null and b/Python/raspi/calibration/offline_samples/20260423_210500_000_cam2.bin differ diff --git a/Python/raspi/calibration/sensor_calibration.json b/Python/raspi/calibration/sensor_calibration.json new file mode 100644 index 000000000..6d0e76c99 --- /dev/null +++ b/Python/raspi/calibration/sensor_calibration.json @@ -0,0 +1,8209 @@ +{ + "schema": "multispec_camera_params_v1", + "saved_at": "2026-04-23 22:26:09", + "pi_host": "192.168.105.6", + "pc_host": "192.168.105.5", + "stream_port": 6001, + "frame_type": "RAW_BRUTO", + "capture_mode_requested": "AUTO", + "capture_mode_effective": "AUTO", + "raw_policy": "allow_single", + "sensor_width": 640, + "sensor_height": 480, + "bayer_pattern": "GBRG", + "camera_settings": { + "cam0": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 100, + "analogue_gain": 1.0, + "colour_gains": null + }, + "cam1": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 100, + "analogue_gain": 1.0, + "colour_gains": null + }, + "cam2": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 15000, + "analogue_gain": 1.0, + "colour_gains": [ + 1.0, + 1.0 + ] + } + }, + "rois": { + "cam2": [ + { + "name": "erva", + "type": "polygon", + "points": [ + [ + 314, + 1028 + ], + [ + 322, + 952 + ], + [ + 262, + 963 + ], + [ + 262, + 885 + ], + [ + 343, + 833 + ], + [ + 314, + 814 + ], + [ + 367, + 752 + ], + [ + 384, + 714 + ], + [ + 412, + 714 + ], + [ + 427, + 695 + ], + [ + 465, + 695 + ], + [ + 512, + 695 + ], + [ + 482, + 663 + ], + [ + 459, + 620 + ], + [ + 533, + 657 + ], + [ + 557, + 628 + ], + [ + 565, + 668 + ], + [ + 619, + 644 + ], + [ + 655, + 652 + ], + [ + 629, + 714 + ], + [ + 629, + 736 + ], + [ + 663, + 766 + ], + [ + 706, + 825 + ], + [ + 700, + 841 + ], + [ + 614, + 836 + ], + [ + 657, + 879 + ], + [ + 666, + 915 + ], + [ + 672, + 979 + ], + [ + 659, + 982 + ], + [ + 663, + 1039 + ], + [ + 591, + 988 + ], + [ + 591, + 1025 + ], + [ + 557, + 1007 + ], + [ + 538, + 1023 + ], + [ + 491, + 1066 + ], + [ + 454, + 1036 + ], + [ + 427, + 982 + ], + [ + 369, + 1023 + ] + ], + "color": [ + 0, + 255, + 255 + ] + }, + { + "name": "cana", + "type": "polygon", + "points": [ + [ + 843, + 950 + ], + [ + 685, + 473 + ], + [ + 787, + 647 + ], + [ + 747, + 519 + ], + [ + 785, + 549 + ], + [ + 815, + 584 + ], + [ + 787, + 387 + ], + [ + 836, + 503 + ], + [ + 847, + 460 + ], + [ + 809, + 363 + ], + [ + 892, + 465 + ], + [ + 900, + 327 + ], + [ + 930, + 465 + ], + [ + 994, + 357 + ], + [ + 956, + 490 + ], + [ + 1007, + 419 + ], + [ + 981, + 552 + ], + [ + 1056, + 455 + ], + [ + 1005, + 633 + ], + [ + 981, + 909 + ], + [ + 943, + 944 + ] + ], + "color": [ + 0, + 255, + 0 + ] + }, + { + "name": "solo", + "type": "polygon", + "points": [ + [ + 49, + 1307 + ], + [ + 55, + 1123 + ], + [ + 356, + 1112 + ], + [ + 358, + 1318 + ] + ], + "color": [ + 255, + 255, + 0 + ] + } + ], + "cam0": [ + { + "name": "erva", + "type": "polygon", + "points": [ + [ + 309, + 1042 + ], + [ + 258, + 920 + ], + [ + 320, + 777 + ], + [ + 401, + 685 + ], + [ + 497, + 685 + ], + [ + 448, + 625 + ], + [ + 484, + 620 + ], + [ + 531, + 636 + ], + [ + 567, + 617 + ], + [ + 572, + 660 + ], + [ + 614, + 636 + ], + [ + 653, + 644 + ], + [ + 634, + 687 + ], + [ + 661, + 728 + ], + [ + 717, + 823 + ], + [ + 678, + 831 + ], + [ + 627, + 828 + ], + [ + 683, + 885 + ], + [ + 683, + 936 + ], + [ + 661, + 979 + ], + [ + 661, + 1028 + ], + [ + 604, + 1036 + ], + [ + 548, + 1012 + ], + [ + 499, + 1066 + ], + [ + 448, + 1047 + ], + [ + 429, + 1023 + ], + [ + 369, + 1023 + ] + ], + "color": [ + 0, + 255, + 255 + ] + }, + { + "name": "cana", + "type": "polygon", + "points": [ + [ + 868, + 952 + ], + [ + 802, + 760 + ], + [ + 727, + 598 + ], + [ + 668, + 511 + ], + [ + 747, + 590 + ], + [ + 678, + 430 + ], + [ + 762, + 576 + ], + [ + 779, + 547 + ], + [ + 753, + 441 + ], + [ + 813, + 582 + ], + [ + 789, + 422 + ], + [ + 774, + 352 + ], + [ + 841, + 519 + ], + [ + 841, + 406 + ], + [ + 841, + 365 + ], + [ + 883, + 498 + ], + [ + 896, + 298 + ], + [ + 924, + 411 + ], + [ + 939, + 457 + ], + [ + 981, + 373 + ], + [ + 962, + 514 + ], + [ + 981, + 460 + ], + [ + 1011, + 409 + ], + [ + 979, + 538 + ], + [ + 1041, + 465 + ], + [ + 1026, + 563 + ], + [ + 992, + 766 + ], + [ + 994, + 928 + ], + [ + 941, + 952 + ] + ], + "color": [ + 0, + 255, + 0 + ] + }, + { + "name": "solo", + "type": "polygon", + "points": [ + [ + 45, + 1328 + ], + [ + 45, + 1115 + ], + [ + 365, + 1123 + ], + [ + 378, + 1326 + ] + ], + "color": [ + 255, + 255, + 0 + ] + } + ], + "cam1": [ + { + "name": "erva", + "type": "polygon", + "points": [ + [ + 314, + 1105 + ], + [ + 245, + 965 + ], + [ + 284, + 851 + ], + [ + 386, + 743 + ], + [ + 427, + 694 + ], + [ + 510, + 716 + ], + [ + 459, + 648 + ], + [ + 459, + 621 + ], + [ + 518, + 626 + ], + [ + 533, + 648 + ], + [ + 563, + 621 + ], + [ + 565, + 662 + ], + [ + 627, + 640 + ], + [ + 668, + 653 + ], + [ + 636, + 699 + ], + [ + 661, + 716 + ], + [ + 715, + 762 + ], + [ + 721, + 797 + ], + [ + 678, + 800 + ], + [ + 687, + 851 + ], + [ + 646, + 856 + ], + [ + 685, + 883 + ], + [ + 723, + 902 + ], + [ + 736, + 962 + ], + [ + 713, + 967 + ], + [ + 672, + 967 + ], + [ + 687, + 1059 + ], + [ + 634, + 1067 + ], + [ + 580, + 1086 + ], + [ + 535, + 1078 + ], + [ + 499, + 1119 + ], + [ + 439, + 1046 + ], + [ + 382, + 1067 + ] + ], + "color": [ + 0, + 255, + 255 + ] + }, + { + "name": "cana", + "type": "polygon", + "points": [ + [ + 911, + 948 + ], + [ + 855, + 913 + ], + [ + 809, + 783 + ], + [ + 783, + 697 + ], + [ + 738, + 597 + ], + [ + 689, + 499 + ], + [ + 768, + 610 + ], + [ + 781, + 605 + ], + [ + 766, + 521 + ], + [ + 749, + 450 + ], + [ + 742, + 426 + ], + [ + 813, + 551 + ], + [ + 809, + 461 + ], + [ + 759, + 367 + ], + [ + 832, + 464 + ], + [ + 826, + 415 + ], + [ + 864, + 459 + ], + [ + 843, + 375 + ], + [ + 815, + 326 + ], + [ + 887, + 434 + ], + [ + 900, + 350 + ], + [ + 913, + 448 + ], + [ + 945, + 386 + ], + [ + 937, + 467 + ], + [ + 986, + 391 + ], + [ + 964, + 502 + ], + [ + 996, + 432 + ], + [ + 975, + 515 + ], + [ + 1020, + 442 + ], + [ + 996, + 534 + ], + [ + 1052, + 478 + ], + [ + 1013, + 580 + ], + [ + 1075, + 505 + ], + [ + 1020, + 618 + ], + [ + 998, + 729 + ], + [ + 979, + 867 + ], + [ + 930, + 921 + ] + ], + "color": [ + 0, + 255, + 0 + ] + }, + { + "name": "solo", + "type": "polygon", + "points": [ + [ + 30, + 1308 + ], + [ + 34, + 1162 + ], + [ + 384, + 1159 + ], + [ + 365, + 1343 + ] + ], + "color": [ + 255, + 255, + 0 + ] + } + ] + }, + "snapshots": [ + { + "timestamp": "2026-04-23 22:04:21", + "camera": "cam0", + "camera_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 15000, + "analogue_gain": 1.0, + "colour_gains": null + }, + "scene_health": { + "mean": 0.46242380142211914, + "std": 0.2910088896751404, + "p05": 0.0332355834543705, + "p95": 0.9384164214134216, + "pct_saturated": 1.3829627403846154, + "pct_dark": 3.406164148351648, + "comment": "ok" + }, + "rois": [ + { + "name": "erva", + "type": "polygon", + "metrics": { + "valid": true, + "mean": 0.4457639753818512, + "std": 0.27479854226112366, + "min": 0.0, + "max": 0.9872922897338867, + "p05": 0.04887585714459419, + "p95": 0.9247311949729919, + "pct_saturated": 1.2417743381211603, + "pct_dark": 2.9754487221529917, + "pixels": 137223 + }, + "points": [ + [ + 309, + 1042 + ], + [ + 258, + 920 + ], + [ + 320, + 777 + ], + [ + 401, + 685 + ], + [ + 497, + 685 + ], + [ + 448, + 625 + ], + [ + 484, + 620 + ], + [ + 531, + 636 + ], + [ + 567, + 617 + ], + [ + 572, + 660 + ], + [ + 614, + 636 + ], + [ + 653, + 644 + ], + [ + 634, + 687 + ], + [ + 661, + 728 + ], + [ + 717, + 823 + ], + [ + 678, + 831 + ], + [ + 627, + 828 + ], + [ + 683, + 885 + ], + [ + 683, + 936 + ], + [ + 661, + 979 + ], + [ + 661, + 1028 + ], + [ + 604, + 1036 + ], + [ + 548, + 1012 + ], + [ + 499, + 1066 + ], + [ + 448, + 1047 + ], + [ + 429, + 1023 + ], + [ + 369, + 1023 + ] + ] + } + ] + }, + { + "timestamp": "2026-04-23 22:04:23", + "camera": "cam1", + "camera_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 15000, + "analogue_gain": 1.0, + "colour_gains": null + }, + "scene_health": { + "mean": 0.5154572129249573, + "std": 0.30768442153930664, + "p05": 0.0332355834543705, + "p95": 0.9540566802024841, + "pct_saturated": 1.8862322573260073, + "pct_dark": 3.57103508470696, + "comment": "ok" + }, + "rois": [ + { + "name": "erva", + "type": "polygon", + "metrics": { + "valid": true, + "mean": 0.4611518681049347, + "std": 0.25701242685317993, + "min": 0.0, + "max": 0.9872922897338867, + "p05": 0.06451612710952759, + "p95": 0.9227761626243591, + "pct_saturated": 1.0718586659773144, + "pct_dark": 2.2389236016973197, + "pixels": 158603 + }, + "points": [ + [ + 314, + 1105 + ], + [ + 245, + 965 + ], + [ + 284, + 851 + ], + [ + 386, + 743 + ], + [ + 427, + 694 + ], + [ + 510, + 716 + ], + [ + 459, + 648 + ], + [ + 459, + 621 + ], + [ + 518, + 626 + ], + [ + 533, + 648 + ], + [ + 563, + 621 + ], + [ + 565, + 662 + ], + [ + 627, + 640 + ], + [ + 668, + 653 + ], + [ + 636, + 699 + ], + [ + 661, + 716 + ], + [ + 715, + 762 + ], + [ + 721, + 797 + ], + [ + 678, + 800 + ], + [ + 687, + 851 + ], + [ + 646, + 856 + ], + [ + 685, + 883 + ], + [ + 723, + 902 + ], + [ + 736, + 962 + ], + [ + 713, + 967 + ], + [ + 672, + 967 + ], + [ + 687, + 1059 + ], + [ + 634, + 1067 + ], + [ + 580, + 1086 + ], + [ + 535, + 1078 + ], + [ + 499, + 1119 + ], + [ + 439, + 1046 + ], + [ + 382, + 1067 + ] + ] + } + ] + }, + { + "timestamp": "2026-04-23 22:05:13", + "camera": "cam2", + "camera_settings": { + "ae_enable": true, + "awb_enable": true, + "exposure_time_us": 15000, + "analogue_gain": 1.0, + "colour_gains": [ + 1.0, + 1.0 + ] + }, + "scene_health": { + "mean": 0.40037205815315247, + "std": 0.2858090400695801, + "p05": 0.0117647061124444, + "p95": 0.9058823585510254, + "pct_saturated": 0.009240976037851038, + "pct_dark": 6.297564197954824, + "comment": "ok" + }, + "rois": [ + { + "name": "erva", + "type": "polygon", + "metrics": { + "valid": true, + "mean": 0.2773081362247467, + "std": 0.19852407276630402, + "min": 0.0, + "max": 0.9607843160629272, + "p05": 0.003921568859368563, + "p95": 0.6039215922355652, + "pct_saturated": 0.0, + "pct_dark": 9.312112611594372, + "pixels": 358329 + }, + "points": [ + [ + 314, + 1028 + ], + [ + 322, + 952 + ], + [ + 262, + 963 + ], + [ + 262, + 885 + ], + [ + 343, + 833 + ], + [ + 314, + 814 + ], + [ + 367, + 752 + ], + [ + 384, + 714 + ], + [ + 412, + 714 + ], + [ + 427, + 695 + ], + [ + 465, + 695 + ], + [ + 512, + 695 + ], + [ + 482, + 663 + ], + [ + 459, + 620 + ], + [ + 533, + 657 + ], + [ + 557, + 628 + ], + [ + 565, + 668 + ], + [ + 619, + 644 + ], + [ + 655, + 652 + ], + [ + 629, + 714 + ], + [ + 629, + 736 + ], + [ + 663, + 766 + ], + [ + 706, + 825 + ], + [ + 700, + 841 + ], + [ + 614, + 836 + ], + [ + 657, + 879 + ], + [ + 666, + 915 + ], + [ + 672, + 979 + ], + [ + 659, + 982 + ], + [ + 663, + 1039 + ], + [ + 591, + 988 + ], + [ + 591, + 1025 + ], + [ + 557, + 1007 + ], + [ + 538, + 1023 + ], + [ + 491, + 1066 + ], + [ + 454, + 1036 + ], + [ + 427, + 982 + ], + [ + 369, + 1023 + ] + ] + } + ] + }, + { + "timestamp": "2026-04-23 22:05:59", + "camera": "cam2", + "camera_settings": { + "ae_enable": true, + "awb_enable": true, + "exposure_time_us": 15000, + "analogue_gain": 1.0, + "colour_gains": [ + 1.0, + 1.0 + ] + }, + "scene_health": { + "mean": 0.40037205815315247, + "std": 0.2858090400695801, + "p05": 0.0117647061124444, + "p95": 0.9058823585510254, + "pct_saturated": 0.009240976037851038, + "pct_dark": 6.297564197954824, + "comment": "ok" + }, + "rois": [ + { + "name": "erva", + "type": "polygon", + "metrics": { + "valid": true, + "mean": 0.2773081362247467, + "std": 0.19852407276630402, + "min": 0.0, + "max": 0.9607843160629272, + "p05": 0.003921568859368563, + "p95": 0.6039215922355652, + "pct_saturated": 0.0, + "pct_dark": 9.312112611594372, + "pixels": 358329 + }, + "points": [ + [ + 314, + 1028 + ], + [ + 322, + 952 + ], + [ + 262, + 963 + ], + [ + 262, + 885 + ], + [ + 343, + 833 + ], + [ + 314, + 814 + ], + [ + 367, + 752 + ], + [ + 384, + 714 + ], + [ + 412, + 714 + ], + [ + 427, + 695 + ], + [ + 465, + 695 + ], + [ + 512, + 695 + ], + [ + 482, + 663 + ], + [ + 459, + 620 + ], + [ + 533, + 657 + ], + [ + 557, + 628 + ], + [ + 565, + 668 + ], + [ + 619, + 644 + ], + [ + 655, + 652 + ], + [ + 629, + 714 + ], + [ + 629, + 736 + ], + [ + 663, + 766 + ], + [ + 706, + 825 + ], + [ + 700, + 841 + ], + [ + 614, + 836 + ], + [ + 657, + 879 + ], + [ + 666, + 915 + ], + [ + 672, + 979 + ], + [ + 659, + 982 + ], + [ + 663, + 1039 + ], + [ + 591, + 988 + ], + [ + 591, + 1025 + ], + [ + 557, + 1007 + ], + [ + 538, + 1023 + ], + [ + 491, + 1066 + ], + [ + 454, + 1036 + ], + [ + 427, + 982 + ], + [ + 369, + 1023 + ] + ] + }, + { + "name": "cana", + "type": "polygon", + "metrics": { + "valid": true, + "mean": 0.3092897832393646, + "std": 0.25642046332359314, + "min": 0.0, + "max": 0.9803921580314636, + "p05": 0.003921568859368563, + "p95": 0.8196078538894653, + "pct_saturated": 0.0011790541038451902, + "pct_dark": 13.049770821358564, + "pixels": 339255 + }, + "points": [ + [ + 843, + 950 + ], + [ + 685, + 473 + ], + [ + 787, + 647 + ], + [ + 747, + 519 + ], + [ + 785, + 549 + ], + [ + 815, + 584 + ], + [ + 787, + 387 + ], + [ + 836, + 503 + ], + [ + 847, + 460 + ], + [ + 809, + 363 + ], + [ + 892, + 465 + ], + [ + 900, + 327 + ], + [ + 930, + 465 + ], + [ + 994, + 357 + ], + [ + 956, + 490 + ], + [ + 1007, + 419 + ], + [ + 981, + 552 + ], + [ + 1056, + 455 + ], + [ + 1005, + 633 + ], + [ + 981, + 909 + ], + [ + 943, + 944 + ] + ] + } + ] + }, + { + "timestamp": "2026-04-23 22:06:49", + "camera": "cam0", + "camera_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 15000, + "analogue_gain": 1.0, + "colour_gains": null + }, + "scene_health": { + "mean": 0.46242380142211914, + "std": 0.2910088896751404, + "p05": 0.0332355834543705, + "p95": 0.9384164214134216, + "pct_saturated": 1.3829627403846154, + "pct_dark": 3.406164148351648, + "comment": "ok" + }, + "rois": [ + { + "name": "erva", + "type": "polygon", + "metrics": { + "valid": true, + "mean": 0.4457639753818512, + "std": 0.27479854226112366, + "min": 0.0, + "max": 0.9872922897338867, + "p05": 0.04887585714459419, + "p95": 0.9247311949729919, + "pct_saturated": 1.2417743381211603, + "pct_dark": 2.9754487221529917, + "pixels": 137223 + }, + "points": [ + [ + 309, + 1042 + ], + [ + 258, + 920 + ], + [ + 320, + 777 + ], + [ + 401, + 685 + ], + [ + 497, + 685 + ], + [ + 448, + 625 + ], + [ + 484, + 620 + ], + [ + 531, + 636 + ], + [ + 567, + 617 + ], + [ + 572, + 660 + ], + [ + 614, + 636 + ], + [ + 653, + 644 + ], + [ + 634, + 687 + ], + [ + 661, + 728 + ], + [ + 717, + 823 + ], + [ + 678, + 831 + ], + [ + 627, + 828 + ], + [ + 683, + 885 + ], + [ + 683, + 936 + ], + [ + 661, + 979 + ], + [ + 661, + 1028 + ], + [ + 604, + 1036 + ], + [ + 548, + 1012 + ], + [ + 499, + 1066 + ], + [ + 448, + 1047 + ], + [ + 429, + 1023 + ], + [ + 369, + 1023 + ] + ] + }, + { + "name": "cana", + "type": "polygon", + "metrics": { + "valid": true, + "mean": 0.4643779695034027, + "std": 0.29758161306381226, + "min": 0.0, + "max": 0.9872922897338867, + "p05": 0.034213099628686905, + "p95": 0.9403714537620544, + "pct_saturated": 1.6609685605526576, + "pct_dark": 3.2972649090652757, + "pixels": 113488 + }, + "points": [ + [ + 868, + 952 + ], + [ + 802, + 760 + ], + [ + 727, + 598 + ], + [ + 668, + 511 + ], + [ + 747, + 590 + ], + [ + 678, + 430 + ], + [ + 762, + 576 + ], + [ + 779, + 547 + ], + [ + 753, + 441 + ], + [ + 813, + 582 + ], + [ + 789, + 422 + ], + [ + 774, + 352 + ], + [ + 841, + 519 + ], + [ + 841, + 406 + ], + [ + 841, + 365 + ], + [ + 883, + 498 + ], + [ + 896, + 298 + ], + [ + 924, + 411 + ], + [ + 939, + 457 + ], + [ + 981, + 373 + ], + [ + 962, + 514 + ], + [ + 981, + 460 + ], + [ + 1011, + 409 + ], + [ + 979, + 538 + ], + [ + 1041, + 465 + ], + [ + 1026, + 563 + ], + [ + 992, + 766 + ], + [ + 994, + 928 + ], + [ + 941, + 952 + ] + ] + } + ] + }, + { + "timestamp": "2026-04-23 22:07:36", + "camera": "cam1", + "camera_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 15000, + "analogue_gain": 1.0, + "colour_gains": null + }, + "scene_health": { + "mean": 0.5154572129249573, + "std": 0.30768442153930664, + "p05": 0.0332355834543705, + "p95": 0.9540566802024841, + "pct_saturated": 1.8862322573260073, + "pct_dark": 3.57103508470696, + "comment": "ok" + }, + "rois": [ + { + "name": "erva", + "type": "polygon", + "metrics": { + "valid": true, + "mean": 0.4611518681049347, + "std": 0.25701242685317993, + "min": 0.0, + "max": 0.9872922897338867, + "p05": 0.06451612710952759, + "p95": 0.9227761626243591, + "pct_saturated": 1.0718586659773144, + "pct_dark": 2.2389236016973197, + "pixels": 158603 + }, + "points": [ + [ + 314, + 1105 + ], + [ + 245, + 965 + ], + [ + 284, + 851 + ], + [ + 386, + 743 + ], + [ + 427, + 694 + ], + [ + 510, + 716 + ], + [ + 459, + 648 + ], + [ + 459, + 621 + ], + [ + 518, + 626 + ], + [ + 533, + 648 + ], + [ + 563, + 621 + ], + [ + 565, + 662 + ], + [ + 627, + 640 + ], + [ + 668, + 653 + ], + [ + 636, + 699 + ], + [ + 661, + 716 + ], + [ + 715, + 762 + ], + [ + 721, + 797 + ], + [ + 678, + 800 + ], + [ + 687, + 851 + ], + [ + 646, + 856 + ], + [ + 685, + 883 + ], + [ + 723, + 902 + ], + [ + 736, + 962 + ], + [ + 713, + 967 + ], + [ + 672, + 967 + ], + [ + 687, + 1059 + ], + [ + 634, + 1067 + ], + [ + 580, + 1086 + ], + [ + 535, + 1078 + ], + [ + 499, + 1119 + ], + [ + 439, + 1046 + ], + [ + 382, + 1067 + ] + ] + }, + { + "name": "cana", + "type": "polygon", + "metrics": { + "valid": true, + "mean": 0.4849100708961487, + "std": 0.2785700559616089, + "min": 0.0009775171056389809, + "max": 0.9872922897338867, + "p05": 0.0498533733189106, + "p95": 0.939393937587738, + "pct_saturated": 1.341215417356349, + "pct_dark": 2.7486403405060296, + "pixels": 105725 + }, + "points": [ + [ + 911, + 948 + ], + [ + 855, + 913 + ], + [ + 809, + 783 + ], + [ + 783, + 697 + ], + [ + 738, + 597 + ], + [ + 689, + 499 + ], + [ + 768, + 610 + ], + [ + 781, + 605 + ], + [ + 766, + 521 + ], + [ + 749, + 450 + ], + [ + 742, + 426 + ], + [ + 813, + 551 + ], + [ + 809, + 461 + ], + [ + 759, + 367 + ], + [ + 832, + 464 + ], + [ + 826, + 415 + ], + [ + 864, + 459 + ], + [ + 843, + 375 + ], + [ + 815, + 326 + ], + [ + 887, + 434 + ], + [ + 900, + 350 + ], + [ + 913, + 448 + ], + [ + 945, + 386 + ], + [ + 937, + 467 + ], + [ + 986, + 391 + ], + [ + 964, + 502 + ], + [ + 996, + 432 + ], + [ + 975, + 515 + ], + [ + 1020, + 442 + ], + [ + 996, + 534 + ], + [ + 1052, + 478 + ], + [ + 1013, + 580 + ], + [ + 1075, + 505 + ], + [ + 1020, + 618 + ], + [ + 998, + 729 + ], + [ + 979, + 867 + ], + [ + 930, + 921 + ] + ] + } + ] + }, + { + "timestamp": "2026-04-23 22:08:13", + "camera": "cam0", + "camera_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 15000, + "analogue_gain": 1.0, + "colour_gains": null + }, + "scene_health": { + "mean": 0.46242380142211914, + "std": 0.2910088896751404, + "p05": 0.0332355834543705, + "p95": 0.9384164214134216, + "pct_saturated": 1.3829627403846154, + "pct_dark": 3.406164148351648, + "comment": "ok" + }, + "rois": [ + { + "name": "erva", + "type": "polygon", + "metrics": { + "valid": true, + "mean": 0.4457639753818512, + "std": 0.27479854226112366, + "min": 0.0, + "max": 0.9872922897338867, + "p05": 0.04887585714459419, + "p95": 0.9247311949729919, + "pct_saturated": 1.2417743381211603, + "pct_dark": 2.9754487221529917, + "pixels": 137223 + }, + "points": [ + [ + 309, + 1042 + ], + [ + 258, + 920 + ], + [ + 320, + 777 + ], + [ + 401, + 685 + ], + [ + 497, + 685 + ], + [ + 448, + 625 + ], + [ + 484, + 620 + ], + [ + 531, + 636 + ], + [ + 567, + 617 + ], + [ + 572, + 660 + ], + [ + 614, + 636 + ], + [ + 653, + 644 + ], + [ + 634, + 687 + ], + [ + 661, + 728 + ], + [ + 717, + 823 + ], + [ + 678, + 831 + ], + [ + 627, + 828 + ], + [ + 683, + 885 + ], + [ + 683, + 936 + ], + [ + 661, + 979 + ], + [ + 661, + 1028 + ], + [ + 604, + 1036 + ], + [ + 548, + 1012 + ], + [ + 499, + 1066 + ], + [ + 448, + 1047 + ], + [ + 429, + 1023 + ], + [ + 369, + 1023 + ] + ] + }, + { + "name": "cana", + "type": "polygon", + "metrics": { + "valid": true, + "mean": 0.4643779695034027, + "std": 0.29758161306381226, + "min": 0.0, + "max": 0.9872922897338867, + "p05": 0.034213099628686905, + "p95": 0.9403714537620544, + "pct_saturated": 1.6609685605526576, + "pct_dark": 3.2972649090652757, + "pixels": 113488 + }, + "points": [ + [ + 868, + 952 + ], + [ + 802, + 760 + ], + [ + 727, + 598 + ], + [ + 668, + 511 + ], + [ + 747, + 590 + ], + [ + 678, + 430 + ], + [ + 762, + 576 + ], + [ + 779, + 547 + ], + [ + 753, + 441 + ], + [ + 813, + 582 + ], + [ + 789, + 422 + ], + [ + 774, + 352 + ], + [ + 841, + 519 + ], + [ + 841, + 406 + ], + [ + 841, + 365 + ], + [ + 883, + 498 + ], + [ + 896, + 298 + ], + [ + 924, + 411 + ], + [ + 939, + 457 + ], + [ + 981, + 373 + ], + [ + 962, + 514 + ], + [ + 981, + 460 + ], + [ + 1011, + 409 + ], + [ + 979, + 538 + ], + [ + 1041, + 465 + ], + [ + 1026, + 563 + ], + [ + 992, + 766 + ], + [ + 994, + 928 + ], + [ + 941, + 952 + ] + ] + }, + { + "name": "solo", + "type": "polygon", + "metrics": { + "valid": true, + "mean": 0.4692467749118805, + "std": 0.2457830160856247, + "min": 0.0, + "max": 0.9872922897338867, + "p05": 0.04887585714459419, + "p95": 0.8602150678634644, + "pct_saturated": 0.45446574701893855, + "pct_dark": 2.324935702595277, + "pixels": 68432 + }, + "points": [ + [ + 45, + 1328 + ], + [ + 45, + 1115 + ], + [ + 365, + 1123 + ], + [ + 378, + 1326 + ] + ] + } + ] + }, + { + "timestamp": "2026-04-23 22:08:31", + "camera": "cam1", + "camera_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 15000, + "analogue_gain": 1.0, + "colour_gains": null + }, + "scene_health": { + "mean": 0.5154572129249573, + "std": 0.30768442153930664, + "p05": 0.0332355834543705, + "p95": 0.9540566802024841, + "pct_saturated": 1.8862322573260073, + "pct_dark": 3.57103508470696, + "comment": "ok" + }, + "rois": [ + { + "name": "erva", + "type": "polygon", + "metrics": { + "valid": true, + "mean": 0.4611518681049347, + "std": 0.25701242685317993, + "min": 0.0, + "max": 0.9872922897338867, + "p05": 0.06451612710952759, + "p95": 0.9227761626243591, + "pct_saturated": 1.0718586659773144, + "pct_dark": 2.2389236016973197, + "pixels": 158603 + }, + "points": [ + [ + 314, + 1105 + ], + [ + 245, + 965 + ], + [ + 284, + 851 + ], + [ + 386, + 743 + ], + [ + 427, + 694 + ], + [ + 510, + 716 + ], + [ + 459, + 648 + ], + [ + 459, + 621 + ], + [ + 518, + 626 + ], + [ + 533, + 648 + ], + [ + 563, + 621 + ], + [ + 565, + 662 + ], + [ + 627, + 640 + ], + [ + 668, + 653 + ], + [ + 636, + 699 + ], + [ + 661, + 716 + ], + [ + 715, + 762 + ], + [ + 721, + 797 + ], + [ + 678, + 800 + ], + [ + 687, + 851 + ], + [ + 646, + 856 + ], + [ + 685, + 883 + ], + [ + 723, + 902 + ], + [ + 736, + 962 + ], + [ + 713, + 967 + ], + [ + 672, + 967 + ], + [ + 687, + 1059 + ], + [ + 634, + 1067 + ], + [ + 580, + 1086 + ], + [ + 535, + 1078 + ], + [ + 499, + 1119 + ], + [ + 439, + 1046 + ], + [ + 382, + 1067 + ] + ] + }, + { + "name": "cana", + "type": "polygon", + "metrics": { + "valid": true, + "mean": 0.4849100708961487, + "std": 0.2785700559616089, + "min": 0.0009775171056389809, + "max": 0.9872922897338867, + "p05": 0.0498533733189106, + "p95": 0.939393937587738, + "pct_saturated": 1.341215417356349, + "pct_dark": 2.7486403405060296, + "pixels": 105725 + }, + "points": [ + [ + 911, + 948 + ], + [ + 855, + 913 + ], + [ + 809, + 783 + ], + [ + 783, + 697 + ], + [ + 738, + 597 + ], + [ + 689, + 499 + ], + [ + 768, + 610 + ], + [ + 781, + 605 + ], + [ + 766, + 521 + ], + [ + 749, + 450 + ], + [ + 742, + 426 + ], + [ + 813, + 551 + ], + [ + 809, + 461 + ], + [ + 759, + 367 + ], + [ + 832, + 464 + ], + [ + 826, + 415 + ], + [ + 864, + 459 + ], + [ + 843, + 375 + ], + [ + 815, + 326 + ], + [ + 887, + 434 + ], + [ + 900, + 350 + ], + [ + 913, + 448 + ], + [ + 945, + 386 + ], + [ + 937, + 467 + ], + [ + 986, + 391 + ], + [ + 964, + 502 + ], + [ + 996, + 432 + ], + [ + 975, + 515 + ], + [ + 1020, + 442 + ], + [ + 996, + 534 + ], + [ + 1052, + 478 + ], + [ + 1013, + 580 + ], + [ + 1075, + 505 + ], + [ + 1020, + 618 + ], + [ + 998, + 729 + ], + [ + 979, + 867 + ], + [ + 930, + 921 + ] + ] + }, + { + "name": "solo", + "type": "polygon", + "metrics": { + "valid": true, + "mean": 0.5673046112060547, + "std": 0.251604288816452, + "min": 0.0009775171056389809, + "max": 0.9872922897338867, + "p05": 0.09384164214134216, + "p95": 0.9227761626243591, + "pct_saturated": 0.8950423047339346, + "pct_dark": 1.6135235298230892, + "pixels": 57204 + }, + "points": [ + [ + 30, + 1308 + ], + [ + 34, + 1162 + ], + [ + 384, + 1159 + ], + [ + 365, + 1343 + ] + ] + } + ] + }, + { + "timestamp": "2026-04-23 22:09:12", + "camera": "cam1", + "camera_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 14000, + "analogue_gain": 1.0, + "colour_gains": null + }, + "scene_health": { + "mean": 0.5154572129249573, + "std": 0.30768442153930664, + "p05": 0.0332355834543705, + "p95": 0.9540566802024841, + "pct_saturated": 1.8862322573260073, + "pct_dark": 3.57103508470696, + "comment": "ok" + }, + "rois": [ + { + "name": "erva", + "type": "polygon", + "metrics": { + "valid": true, + "mean": 0.4611518681049347, + "std": 0.25701242685317993, + "min": 0.0, + "max": 0.9872922897338867, + "p05": 0.06451612710952759, + "p95": 0.9227761626243591, + "pct_saturated": 1.0718586659773144, + "pct_dark": 2.2389236016973197, + "pixels": 158603 + }, + "points": [ + [ + 314, + 1105 + ], + [ + 245, + 965 + ], + [ + 284, + 851 + ], + [ + 386, + 743 + ], + [ + 427, + 694 + ], + [ + 510, + 716 + ], + [ + 459, + 648 + ], + [ + 459, + 621 + ], + [ + 518, + 626 + ], + [ + 533, + 648 + ], + [ + 563, + 621 + ], + [ + 565, + 662 + ], + [ + 627, + 640 + ], + [ + 668, + 653 + ], + [ + 636, + 699 + ], + [ + 661, + 716 + ], + [ + 715, + 762 + ], + [ + 721, + 797 + ], + [ + 678, + 800 + ], + [ + 687, + 851 + ], + [ + 646, + 856 + ], + [ + 685, + 883 + ], + [ + 723, + 902 + ], + [ + 736, + 962 + ], + [ + 713, + 967 + ], + [ + 672, + 967 + ], + [ + 687, + 1059 + ], + [ + 634, + 1067 + ], + [ + 580, + 1086 + ], + [ + 535, + 1078 + ], + [ + 499, + 1119 + ], + [ + 439, + 1046 + ], + [ + 382, + 1067 + ] + ] + }, + { + "name": "cana", + "type": "polygon", + "metrics": { + "valid": true, + "mean": 0.4849100708961487, + "std": 0.2785700559616089, + "min": 0.0009775171056389809, + "max": 0.9872922897338867, + "p05": 0.0498533733189106, + "p95": 0.939393937587738, + "pct_saturated": 1.341215417356349, + "pct_dark": 2.7486403405060296, + "pixels": 105725 + }, + "points": [ + [ + 911, + 948 + ], + [ + 855, + 913 + ], + [ + 809, + 783 + ], + [ + 783, + 697 + ], + [ + 738, + 597 + ], + [ + 689, + 499 + ], + [ + 768, + 610 + ], + [ + 781, + 605 + ], + [ + 766, + 521 + ], + [ + 749, + 450 + ], + [ + 742, + 426 + ], + [ + 813, + 551 + ], + [ + 809, + 461 + ], + [ + 759, + 367 + ], + [ + 832, + 464 + ], + [ + 826, + 415 + ], + [ + 864, + 459 + ], + [ + 843, + 375 + ], + [ + 815, + 326 + ], + [ + 887, + 434 + ], + [ + 900, + 350 + ], + [ + 913, + 448 + ], + [ + 945, + 386 + ], + [ + 937, + 467 + ], + [ + 986, + 391 + ], + [ + 964, + 502 + ], + [ + 996, + 432 + ], + [ + 975, + 515 + ], + [ + 1020, + 442 + ], + [ + 996, + 534 + ], + [ + 1052, + 478 + ], + [ + 1013, + 580 + ], + [ + 1075, + 505 + ], + [ + 1020, + 618 + ], + [ + 998, + 729 + ], + [ + 979, + 867 + ], + [ + 930, + 921 + ] + ] + }, + { + "name": "solo", + "type": "polygon", + "metrics": { + "valid": true, + "mean": 0.5673046112060547, + "std": 0.251604288816452, + "min": 0.0009775171056389809, + "max": 0.9872922897338867, + "p05": 0.09384164214134216, + "p95": 0.9227761626243591, + "pct_saturated": 0.8950423047339346, + "pct_dark": 1.6135235298230892, + "pixels": 57204 + }, + "points": [ + [ + 30, + 1308 + ], + [ + 34, + 1162 + ], + [ + 384, + 1159 + ], + [ + 365, + 1343 + ] + ] + } + ] + } + ], + "notes": "", + "calibration_guidance_log": [ + { + "timestamp": "2026-04-23 22:08:42", + "camera": "cam1", + "result": { + "status": "adjust", + "action": "decrease_exposure", + "reason": "Vegetação saturando (1.21%). Reduzir exposição.", + "channel": "cam1", + "class_metrics": { + "erva": { + "count": 1, + "mean": 0.4611518681049347, + "std": 0.25701242685317993, + "p05": 0.06451612710952759, + "p95": 0.9227761626243591, + "pct_saturated": 1.0718586659773144, + "pct_dark": 2.2389236016973197, + "pixels": 158603 + }, + "cana": { + "count": 1, + "mean": 0.4849100708961487, + "std": 0.2785700559616089, + "p05": 0.0498533733189106, + "p95": 0.939393937587738, + "pct_saturated": 1.341215417356349, + "pct_dark": 2.7486403405060296, + "pixels": 105725 + }, + "solo": { + "count": 1, + "mean": 0.5673046112060547, + "std": 0.251604288816452, + "p05": 0.09384164214134216, + "p95": 0.9227761626243591, + "pct_saturated": 0.8950423047339346, + "pct_dark": 1.6135235298230892, + "pixels": 57204 + } + }, + "veg_mean": 0.4730309695005417, + "solo_mean": 0.5673046112060547, + "separation": -0.094273641705513, + "veg_p95": 0.9310850501060486, + "veg_sat": 1.2065370416668317, + "before_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 15000, + "analogue_gain": 1.0, + "colour_gains": null + }, + "after_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 14000, + "analogue_gain": 1.0, + "colour_gains": null + } + } + }, + { + "timestamp": "2026-04-23 22:14:17", + "camera": "cam1", + "result": { + "status": "adjust", + "action": "decrease_exposure", + "reason": "Vegetação saturando (1.21%). Reduzir exposição.", + "channel": "cam1", + "class_metrics": { + "erva": { + "count": 1, + "mean": 0.4611518681049347, + "std": 0.25701242685317993, + "p05": 0.06451612710952759, + "p95": 0.9227761626243591, + "pct_saturated": 1.0718586659773144, + "pct_dark": 2.2389236016973197, + "pixels": 158603 + }, + "cana": { + "count": 1, + "mean": 0.4849100708961487, + "std": 0.2785700559616089, + "p05": 0.0498533733189106, + "p95": 0.939393937587738, + "pct_saturated": 1.341215417356349, + "pct_dark": 2.7486403405060296, + "pixels": 105725 + }, + "solo": { + "count": 1, + "mean": 0.5673046112060547, + "std": 0.251604288816452, + "p05": 0.09384164214134216, + "p95": 0.9227761626243591, + "pct_saturated": 0.8950423047339346, + "pct_dark": 1.6135235298230892, + "pixels": 57204 + } + }, + "veg_mean": 0.4730309695005417, + "solo_mean": 0.5673046112060547, + "separation": -0.094273641705513, + "veg_p95": 0.9310850501060486, + "veg_sat": 1.2065370416668317, + "before_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 14000, + "analogue_gain": 1.0, + "colour_gains": null + }, + "after_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 13000, + "analogue_gain": 1.0, + "colour_gains": null + } + } + }, + { + "timestamp": "2026-04-23 22:14:17", + "camera": "cam1", + "result": { + "status": "adjust", + "action": "decrease_exposure", + "reason": "Vegetação saturando (1.21%). Reduzir exposição.", + "channel": "cam1", + "class_metrics": { + "erva": { + "count": 1, + "mean": 0.4611518681049347, + "std": 0.25701242685317993, + "p05": 0.06451612710952759, + "p95": 0.9227761626243591, + "pct_saturated": 1.0718586659773144, + "pct_dark": 2.2389236016973197, + "pixels": 158603 + }, + "cana": { + "count": 1, + "mean": 0.4849100708961487, + "std": 0.2785700559616089, + "p05": 0.0498533733189106, + "p95": 0.939393937587738, + "pct_saturated": 1.341215417356349, + "pct_dark": 2.7486403405060296, + "pixels": 105725 + }, + "solo": { + "count": 1, + "mean": 0.5673046112060547, + "std": 0.251604288816452, + "p05": 0.09384164214134216, + "p95": 0.9227761626243591, + "pct_saturated": 0.8950423047339346, + "pct_dark": 1.6135235298230892, + "pixels": 57204 + } + }, + "veg_mean": 0.4730309695005417, + "solo_mean": 0.5673046112060547, + "separation": -0.094273641705513, + "veg_p95": 0.9310850501060486, + "veg_sat": 1.2065370416668317, + "before_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 14000, + "analogue_gain": 1.0, + "colour_gains": null + }, + "after_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 13000, + "analogue_gain": 1.0, + "colour_gains": null + } + } + }, + { + "timestamp": "2026-04-23 22:15:49", + "camera": "cam1", + "result": { + "status": "adjust", + "action": "decrease_exposure", + "reason": "Vegetação saturando (1.21%). Reduzir exposição.", + "channel": "cam1", + "class_metrics": { + "erva": { + "count": 1, + "mean": 0.4611518681049347, + "std": 0.25701242685317993, + "p05": 0.06451612710952759, + "p95": 0.9227761626243591, + "pct_saturated": 1.0718586659773144, + "pct_dark": 2.2389236016973197, + "pixels": 158603 + }, + "cana": { + "count": 1, + "mean": 0.4849100708961487, + "std": 0.2785700559616089, + "p05": 0.0498533733189106, + "p95": 0.939393937587738, + "pct_saturated": 1.341215417356349, + "pct_dark": 2.7486403405060296, + "pixels": 105725 + }, + "solo": { + "count": 1, + "mean": 0.5673046112060547, + "std": 0.251604288816452, + "p05": 0.09384164214134216, + "p95": 0.9227761626243591, + "pct_saturated": 0.8950423047339346, + "pct_dark": 1.6135235298230892, + "pixels": 57204 + } + }, + "veg_mean": 0.4730309695005417, + "solo_mean": 0.5673046112060547, + "separation": -0.094273641705513, + "veg_p95": 0.9310850501060486, + "veg_sat": 1.2065370416668317, + "before_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 13000, + "analogue_gain": 1.0, + "colour_gains": null + }, + "after_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 12000, + "analogue_gain": 1.0, + "colour_gains": null + } + } + }, + { + "timestamp": "2026-04-23 22:15:49", + "camera": "cam1", + "result": { + "status": "adjust", + "action": "decrease_exposure", + "reason": "Vegetação saturando (1.21%). Reduzir exposição.", + "channel": "cam1", + "class_metrics": { + "erva": { + "count": 1, + "mean": 0.4611518681049347, + "std": 0.25701242685317993, + "p05": 0.06451612710952759, + "p95": 0.9227761626243591, + "pct_saturated": 1.0718586659773144, + "pct_dark": 2.2389236016973197, + "pixels": 158603 + }, + "cana": { + "count": 1, + "mean": 0.4849100708961487, + "std": 0.2785700559616089, + "p05": 0.0498533733189106, + "p95": 0.939393937587738, + "pct_saturated": 1.341215417356349, + "pct_dark": 2.7486403405060296, + "pixels": 105725 + }, + "solo": { + "count": 1, + "mean": 0.5673046112060547, + "std": 0.251604288816452, + "p05": 0.09384164214134216, + "p95": 0.9227761626243591, + "pct_saturated": 0.8950423047339346, + "pct_dark": 1.6135235298230892, + "pixels": 57204 + } + }, + "veg_mean": 0.4730309695005417, + "solo_mean": 0.5673046112060547, + "separation": -0.094273641705513, + "veg_p95": 0.9310850501060486, + "veg_sat": 1.2065370416668317, + "before_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 13000, + "analogue_gain": 1.0, + "colour_gains": null + }, + "after_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 12000, + "analogue_gain": 1.0, + "colour_gains": null + } + } + }, + { + "timestamp": "2026-04-23 22:15:52", + "camera": "cam1", + "result": { + "status": "adjust", + "action": "decrease_exposure", + "reason": "Vegetação saturando (1.21%). Reduzir exposição.", + "channel": "cam1", + "class_metrics": { + "erva": { + "count": 1, + "mean": 0.4611518681049347, + "std": 0.25701242685317993, + "p05": 0.06451612710952759, + "p95": 0.9227761626243591, + "pct_saturated": 1.0718586659773144, + "pct_dark": 2.2389236016973197, + "pixels": 158603 + }, + "cana": { + "count": 1, + "mean": 0.4849100708961487, + "std": 0.2785700559616089, + "p05": 0.0498533733189106, + "p95": 0.939393937587738, + "pct_saturated": 1.341215417356349, + "pct_dark": 2.7486403405060296, + "pixels": 105725 + }, + "solo": { + "count": 1, + "mean": 0.5673046112060547, + "std": 0.251604288816452, + "p05": 0.09384164214134216, + "p95": 0.9227761626243591, + "pct_saturated": 0.8950423047339346, + "pct_dark": 1.6135235298230892, + "pixels": 57204 + } + }, + "veg_mean": 0.4730309695005417, + "solo_mean": 0.5673046112060547, + "separation": -0.094273641705513, + "veg_p95": 0.9310850501060486, + "veg_sat": 1.2065370416668317, + "before_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 12000, + "analogue_gain": 1.0, + "colour_gains": null + }, + "after_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 11000, + "analogue_gain": 1.0, + "colour_gains": null + } + } + }, + { + "timestamp": "2026-04-23 22:15:52", + "camera": "cam1", + "result": { + "status": "adjust", + "action": "decrease_exposure", + "reason": "Vegetação saturando (1.21%). Reduzir exposição.", + "channel": "cam1", + "class_metrics": { + "erva": { + "count": 1, + "mean": 0.4611518681049347, + "std": 0.25701242685317993, + "p05": 0.06451612710952759, + "p95": 0.9227761626243591, + "pct_saturated": 1.0718586659773144, + "pct_dark": 2.2389236016973197, + "pixels": 158603 + }, + "cana": { + "count": 1, + "mean": 0.4849100708961487, + "std": 0.2785700559616089, + "p05": 0.0498533733189106, + "p95": 0.939393937587738, + "pct_saturated": 1.341215417356349, + "pct_dark": 2.7486403405060296, + "pixels": 105725 + }, + "solo": { + "count": 1, + "mean": 0.5673046112060547, + "std": 0.251604288816452, + "p05": 0.09384164214134216, + "p95": 0.9227761626243591, + "pct_saturated": 0.8950423047339346, + "pct_dark": 1.6135235298230892, + "pixels": 57204 + } + }, + "veg_mean": 0.4730309695005417, + "solo_mean": 0.5673046112060547, + "separation": -0.094273641705513, + "veg_p95": 0.9310850501060486, + "veg_sat": 1.2065370416668317, + "before_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 12000, + "analogue_gain": 1.0, + "colour_gains": null + }, + "after_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 11000, + "analogue_gain": 1.0, + "colour_gains": null + } + } + }, + { + "timestamp": "2026-04-23 22:15:53", + "camera": "cam1", + "result": { + "status": "adjust", + "action": "decrease_exposure", + "reason": "Vegetação saturando (1.21%). Reduzir exposição.", + "channel": "cam1", + "class_metrics": { + "erva": { + "count": 1, + "mean": 0.4611518681049347, + "std": 0.25701242685317993, + "p05": 0.06451612710952759, + "p95": 0.9227761626243591, + "pct_saturated": 1.0718586659773144, + "pct_dark": 2.2389236016973197, + "pixels": 158603 + }, + "cana": { + "count": 1, + "mean": 0.4849100708961487, + "std": 0.2785700559616089, + "p05": 0.0498533733189106, + "p95": 0.939393937587738, + "pct_saturated": 1.341215417356349, + "pct_dark": 2.7486403405060296, + "pixels": 105725 + }, + "solo": { + "count": 1, + "mean": 0.5673046112060547, + "std": 0.251604288816452, + "p05": 0.09384164214134216, + "p95": 0.9227761626243591, + "pct_saturated": 0.8950423047339346, + "pct_dark": 1.6135235298230892, + "pixels": 57204 + } + }, + "veg_mean": 0.4730309695005417, + "solo_mean": 0.5673046112060547, + "separation": -0.094273641705513, + "veg_p95": 0.9310850501060486, + "veg_sat": 1.2065370416668317, + "before_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 11000, + "analogue_gain": 1.0, + "colour_gains": null + }, + "after_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 10000, + "analogue_gain": 1.0, + "colour_gains": null + } + } + }, + { + "timestamp": "2026-04-23 22:15:53", + "camera": "cam1", + "result": { + "status": "adjust", + "action": "decrease_exposure", + "reason": "Vegetação saturando (1.21%). Reduzir exposição.", + "channel": "cam1", + "class_metrics": { + "erva": { + "count": 1, + "mean": 0.4611518681049347, + "std": 0.25701242685317993, + "p05": 0.06451612710952759, + "p95": 0.9227761626243591, + "pct_saturated": 1.0718586659773144, + "pct_dark": 2.2389236016973197, + "pixels": 158603 + }, + "cana": { + "count": 1, + "mean": 0.4849100708961487, + "std": 0.2785700559616089, + "p05": 0.0498533733189106, + "p95": 0.939393937587738, + "pct_saturated": 1.341215417356349, + "pct_dark": 2.7486403405060296, + "pixels": 105725 + }, + "solo": { + "count": 1, + "mean": 0.5673046112060547, + "std": 0.251604288816452, + "p05": 0.09384164214134216, + "p95": 0.9227761626243591, + "pct_saturated": 0.8950423047339346, + "pct_dark": 1.6135235298230892, + "pixels": 57204 + } + }, + "veg_mean": 0.4730309695005417, + "solo_mean": 0.5673046112060547, + "separation": -0.094273641705513, + "veg_p95": 0.9310850501060486, + "veg_sat": 1.2065370416668317, + "before_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 11000, + "analogue_gain": 1.0, + "colour_gains": null + }, + "after_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 10000, + "analogue_gain": 1.0, + "colour_gains": null + } + } + }, + { + "timestamp": "2026-04-23 22:15:54", + "camera": "cam1", + "result": { + "status": "adjust", + "action": "decrease_exposure", + "reason": "Vegetação saturando (1.21%). Reduzir exposição.", + "channel": "cam1", + "class_metrics": { + "erva": { + "count": 1, + "mean": 0.4611518681049347, + "std": 0.25701242685317993, + "p05": 0.06451612710952759, + "p95": 0.9227761626243591, + "pct_saturated": 1.0718586659773144, + "pct_dark": 2.2389236016973197, + "pixels": 158603 + }, + "cana": { + "count": 1, + "mean": 0.4849100708961487, + "std": 0.2785700559616089, + "p05": 0.0498533733189106, + "p95": 0.939393937587738, + "pct_saturated": 1.341215417356349, + "pct_dark": 2.7486403405060296, + "pixels": 105725 + }, + "solo": { + "count": 1, + "mean": 0.5673046112060547, + "std": 0.251604288816452, + "p05": 0.09384164214134216, + "p95": 0.9227761626243591, + "pct_saturated": 0.8950423047339346, + "pct_dark": 1.6135235298230892, + "pixels": 57204 + } + }, + "veg_mean": 0.4730309695005417, + "solo_mean": 0.5673046112060547, + "separation": -0.094273641705513, + "veg_p95": 0.9310850501060486, + "veg_sat": 1.2065370416668317, + "before_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 10000, + "analogue_gain": 1.0, + "colour_gains": null + }, + "after_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 9000, + "analogue_gain": 1.0, + "colour_gains": null + } + } + }, + { + "timestamp": "2026-04-23 22:15:54", + "camera": "cam1", + "result": { + "status": "adjust", + "action": "decrease_exposure", + "reason": "Vegetação saturando (1.21%). Reduzir exposição.", + "channel": "cam1", + "class_metrics": { + "erva": { + "count": 1, + "mean": 0.4611518681049347, + "std": 0.25701242685317993, + "p05": 0.06451612710952759, + "p95": 0.9227761626243591, + "pct_saturated": 1.0718586659773144, + "pct_dark": 2.2389236016973197, + "pixels": 158603 + }, + "cana": { + "count": 1, + "mean": 0.4849100708961487, + "std": 0.2785700559616089, + "p05": 0.0498533733189106, + "p95": 0.939393937587738, + "pct_saturated": 1.341215417356349, + "pct_dark": 2.7486403405060296, + "pixels": 105725 + }, + "solo": { + "count": 1, + "mean": 0.5673046112060547, + "std": 0.251604288816452, + "p05": 0.09384164214134216, + "p95": 0.9227761626243591, + "pct_saturated": 0.8950423047339346, + "pct_dark": 1.6135235298230892, + "pixels": 57204 + } + }, + "veg_mean": 0.4730309695005417, + "solo_mean": 0.5673046112060547, + "separation": -0.094273641705513, + "veg_p95": 0.9310850501060486, + "veg_sat": 1.2065370416668317, + "before_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 10000, + "analogue_gain": 1.0, + "colour_gains": null + }, + "after_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 9000, + "analogue_gain": 1.0, + "colour_gains": null + } + } + }, + { + "timestamp": "2026-04-23 22:15:55", + "camera": "cam1", + "result": { + "status": "adjust", + "action": "decrease_exposure", + "reason": "Vegetação saturando (1.21%). Reduzir exposição.", + "channel": "cam1", + "class_metrics": { + "erva": { + "count": 1, + "mean": 0.4611518681049347, + "std": 0.25701242685317993, + "p05": 0.06451612710952759, + "p95": 0.9227761626243591, + "pct_saturated": 1.0718586659773144, + "pct_dark": 2.2389236016973197, + "pixels": 158603 + }, + "cana": { + "count": 1, + "mean": 0.4849100708961487, + "std": 0.2785700559616089, + "p05": 0.0498533733189106, + "p95": 0.939393937587738, + "pct_saturated": 1.341215417356349, + "pct_dark": 2.7486403405060296, + "pixels": 105725 + }, + "solo": { + "count": 1, + "mean": 0.5673046112060547, + "std": 0.251604288816452, + "p05": 0.09384164214134216, + "p95": 0.9227761626243591, + "pct_saturated": 0.8950423047339346, + "pct_dark": 1.6135235298230892, + "pixels": 57204 + } + }, + "veg_mean": 0.4730309695005417, + "solo_mean": 0.5673046112060547, + "separation": -0.094273641705513, + "veg_p95": 0.9310850501060486, + "veg_sat": 1.2065370416668317, + "before_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 9000, + "analogue_gain": 1.0, + "colour_gains": null + }, + "after_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 8000, + "analogue_gain": 1.0, + "colour_gains": null + } + } + }, + { + "timestamp": "2026-04-23 22:15:55", + "camera": "cam1", + "result": { + "status": "adjust", + "action": "decrease_exposure", + "reason": "Vegetação saturando (1.21%). Reduzir exposição.", + "channel": "cam1", + "class_metrics": { + "erva": { + "count": 1, + "mean": 0.4611518681049347, + "std": 0.25701242685317993, + "p05": 0.06451612710952759, + "p95": 0.9227761626243591, + "pct_saturated": 1.0718586659773144, + "pct_dark": 2.2389236016973197, + "pixels": 158603 + }, + "cana": { + "count": 1, + "mean": 0.4849100708961487, + "std": 0.2785700559616089, + "p05": 0.0498533733189106, + "p95": 0.939393937587738, + "pct_saturated": 1.341215417356349, + "pct_dark": 2.7486403405060296, + "pixels": 105725 + }, + "solo": { + "count": 1, + "mean": 0.5673046112060547, + "std": 0.251604288816452, + "p05": 0.09384164214134216, + "p95": 0.9227761626243591, + "pct_saturated": 0.8950423047339346, + "pct_dark": 1.6135235298230892, + "pixels": 57204 + } + }, + "veg_mean": 0.4730309695005417, + "solo_mean": 0.5673046112060547, + "separation": -0.094273641705513, + "veg_p95": 0.9310850501060486, + "veg_sat": 1.2065370416668317, + "before_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 9000, + "analogue_gain": 1.0, + "colour_gains": null + }, + "after_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 8000, + "analogue_gain": 1.0, + "colour_gains": null + } + } + }, + { + "timestamp": "2026-04-23 22:15:56", + "camera": "cam1", + "result": { + "status": "adjust", + "action": "decrease_exposure", + "reason": "Vegetação saturando (1.21%). Reduzir exposição.", + "channel": "cam1", + "class_metrics": { + "erva": { + "count": 1, + "mean": 0.4611518681049347, + "std": 0.25701242685317993, + "p05": 0.06451612710952759, + "p95": 0.9227761626243591, + "pct_saturated": 1.0718586659773144, + "pct_dark": 2.2389236016973197, + "pixels": 158603 + }, + "cana": { + "count": 1, + "mean": 0.4849100708961487, + "std": 0.2785700559616089, + "p05": 0.0498533733189106, + "p95": 0.939393937587738, + "pct_saturated": 1.341215417356349, + "pct_dark": 2.7486403405060296, + "pixels": 105725 + }, + "solo": { + "count": 1, + "mean": 0.5673046112060547, + "std": 0.251604288816452, + "p05": 0.09384164214134216, + "p95": 0.9227761626243591, + "pct_saturated": 0.8950423047339346, + "pct_dark": 1.6135235298230892, + "pixels": 57204 + } + }, + "veg_mean": 0.4730309695005417, + "solo_mean": 0.5673046112060547, + "separation": -0.094273641705513, + "veg_p95": 0.9310850501060486, + "veg_sat": 1.2065370416668317, + "before_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 8000, + "analogue_gain": 1.0, + "colour_gains": null + }, + "after_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 7000, + "analogue_gain": 1.0, + "colour_gains": null + } + } + }, + { + "timestamp": "2026-04-23 22:15:56", + "camera": "cam1", + "result": { + "status": "adjust", + "action": "decrease_exposure", + "reason": "Vegetação saturando (1.21%). Reduzir exposição.", + "channel": "cam1", + "class_metrics": { + "erva": { + "count": 1, + "mean": 0.4611518681049347, + "std": 0.25701242685317993, + "p05": 0.06451612710952759, + "p95": 0.9227761626243591, + "pct_saturated": 1.0718586659773144, + "pct_dark": 2.2389236016973197, + "pixels": 158603 + }, + "cana": { + "count": 1, + "mean": 0.4849100708961487, + "std": 0.2785700559616089, + "p05": 0.0498533733189106, + "p95": 0.939393937587738, + "pct_saturated": 1.341215417356349, + "pct_dark": 2.7486403405060296, + "pixels": 105725 + }, + "solo": { + "count": 1, + "mean": 0.5673046112060547, + "std": 0.251604288816452, + "p05": 0.09384164214134216, + "p95": 0.9227761626243591, + "pct_saturated": 0.8950423047339346, + "pct_dark": 1.6135235298230892, + "pixels": 57204 + } + }, + "veg_mean": 0.4730309695005417, + "solo_mean": 0.5673046112060547, + "separation": -0.094273641705513, + "veg_p95": 0.9310850501060486, + "veg_sat": 1.2065370416668317, + "before_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 8000, + "analogue_gain": 1.0, + "colour_gains": null + }, + "after_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 7000, + "analogue_gain": 1.0, + "colour_gains": null + } + } + }, + { + "timestamp": "2026-04-23 22:15:56", + "camera": "cam1", + "result": { + "status": "adjust", + "action": "decrease_exposure", + "reason": "Vegetação saturando (1.21%). Reduzir exposição.", + "channel": "cam1", + "class_metrics": { + "erva": { + "count": 1, + "mean": 0.4611518681049347, + "std": 0.25701242685317993, + "p05": 0.06451612710952759, + "p95": 0.9227761626243591, + "pct_saturated": 1.0718586659773144, + "pct_dark": 2.2389236016973197, + "pixels": 158603 + }, + "cana": { + "count": 1, + "mean": 0.4849100708961487, + "std": 0.2785700559616089, + "p05": 0.0498533733189106, + "p95": 0.939393937587738, + "pct_saturated": 1.341215417356349, + "pct_dark": 2.7486403405060296, + "pixels": 105725 + }, + "solo": { + "count": 1, + "mean": 0.5673046112060547, + "std": 0.251604288816452, + "p05": 0.09384164214134216, + "p95": 0.9227761626243591, + "pct_saturated": 0.8950423047339346, + "pct_dark": 1.6135235298230892, + "pixels": 57204 + } + }, + "veg_mean": 0.4730309695005417, + "solo_mean": 0.5673046112060547, + "separation": -0.094273641705513, + "veg_p95": 0.9310850501060486, + "veg_sat": 1.2065370416668317, + "before_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 7000, + "analogue_gain": 1.0, + "colour_gains": null + }, + "after_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 6000, + "analogue_gain": 1.0, + "colour_gains": null + } + } + }, + { + "timestamp": "2026-04-23 22:15:56", + "camera": "cam1", + "result": { + "status": "adjust", + "action": "decrease_exposure", + "reason": "Vegetação saturando (1.21%). Reduzir exposição.", + "channel": "cam1", + "class_metrics": { + "erva": { + "count": 1, + "mean": 0.4611518681049347, + "std": 0.25701242685317993, + "p05": 0.06451612710952759, + "p95": 0.9227761626243591, + "pct_saturated": 1.0718586659773144, + "pct_dark": 2.2389236016973197, + "pixels": 158603 + }, + "cana": { + "count": 1, + "mean": 0.4849100708961487, + "std": 0.2785700559616089, + "p05": 0.0498533733189106, + "p95": 0.939393937587738, + "pct_saturated": 1.341215417356349, + "pct_dark": 2.7486403405060296, + "pixels": 105725 + }, + "solo": { + "count": 1, + "mean": 0.5673046112060547, + "std": 0.251604288816452, + "p05": 0.09384164214134216, + "p95": 0.9227761626243591, + "pct_saturated": 0.8950423047339346, + "pct_dark": 1.6135235298230892, + "pixels": 57204 + } + }, + "veg_mean": 0.4730309695005417, + "solo_mean": 0.5673046112060547, + "separation": -0.094273641705513, + "veg_p95": 0.9310850501060486, + "veg_sat": 1.2065370416668317, + "before_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 7000, + "analogue_gain": 1.0, + "colour_gains": null + }, + "after_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 6000, + "analogue_gain": 1.0, + "colour_gains": null + } + } + }, + { + "timestamp": "2026-04-23 22:15:57", + "camera": "cam1", + "result": { + "status": "adjust", + "action": "decrease_exposure", + "reason": "Vegetação saturando (1.21%). Reduzir exposição.", + "channel": "cam1", + "class_metrics": { + "erva": { + "count": 1, + "mean": 0.4611518681049347, + "std": 0.25701242685317993, + "p05": 0.06451612710952759, + "p95": 0.9227761626243591, + "pct_saturated": 1.0718586659773144, + "pct_dark": 2.2389236016973197, + "pixels": 158603 + }, + "cana": { + "count": 1, + "mean": 0.4849100708961487, + "std": 0.2785700559616089, + "p05": 0.0498533733189106, + "p95": 0.939393937587738, + "pct_saturated": 1.341215417356349, + "pct_dark": 2.7486403405060296, + "pixels": 105725 + }, + "solo": { + "count": 1, + "mean": 0.5673046112060547, + "std": 0.251604288816452, + "p05": 0.09384164214134216, + "p95": 0.9227761626243591, + "pct_saturated": 0.8950423047339346, + "pct_dark": 1.6135235298230892, + "pixels": 57204 + } + }, + "veg_mean": 0.4730309695005417, + "solo_mean": 0.5673046112060547, + "separation": -0.094273641705513, + "veg_p95": 0.9310850501060486, + "veg_sat": 1.2065370416668317, + "before_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 6000, + "analogue_gain": 1.0, + "colour_gains": null + }, + "after_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 5000, + "analogue_gain": 1.0, + "colour_gains": null + } + } + }, + { + "timestamp": "2026-04-23 22:15:57", + "camera": "cam1", + "result": { + "status": "adjust", + "action": "decrease_exposure", + "reason": "Vegetação saturando (1.21%). Reduzir exposição.", + "channel": "cam1", + "class_metrics": { + "erva": { + "count": 1, + "mean": 0.4611518681049347, + "std": 0.25701242685317993, + "p05": 0.06451612710952759, + "p95": 0.9227761626243591, + "pct_saturated": 1.0718586659773144, + "pct_dark": 2.2389236016973197, + "pixels": 158603 + }, + "cana": { + "count": 1, + "mean": 0.4849100708961487, + "std": 0.2785700559616089, + "p05": 0.0498533733189106, + "p95": 0.939393937587738, + "pct_saturated": 1.341215417356349, + "pct_dark": 2.7486403405060296, + "pixels": 105725 + }, + "solo": { + "count": 1, + "mean": 0.5673046112060547, + "std": 0.251604288816452, + "p05": 0.09384164214134216, + "p95": 0.9227761626243591, + "pct_saturated": 0.8950423047339346, + "pct_dark": 1.6135235298230892, + "pixels": 57204 + } + }, + "veg_mean": 0.4730309695005417, + "solo_mean": 0.5673046112060547, + "separation": -0.094273641705513, + "veg_p95": 0.9310850501060486, + "veg_sat": 1.2065370416668317, + "before_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 6000, + "analogue_gain": 1.0, + "colour_gains": null + }, + "after_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 5000, + "analogue_gain": 1.0, + "colour_gains": null + } + } + }, + { + "timestamp": "2026-04-23 22:15:58", + "camera": "cam1", + "result": { + "status": "adjust", + "action": "decrease_exposure", + "reason": "Vegetação saturando (1.21%). Reduzir exposição.", + "channel": "cam1", + "class_metrics": { + "erva": { + "count": 1, + "mean": 0.4611518681049347, + "std": 0.25701242685317993, + "p05": 0.06451612710952759, + "p95": 0.9227761626243591, + "pct_saturated": 1.0718586659773144, + "pct_dark": 2.2389236016973197, + "pixels": 158603 + }, + "cana": { + "count": 1, + "mean": 0.4849100708961487, + "std": 0.2785700559616089, + "p05": 0.0498533733189106, + "p95": 0.939393937587738, + "pct_saturated": 1.341215417356349, + "pct_dark": 2.7486403405060296, + "pixels": 105725 + }, + "solo": { + "count": 1, + "mean": 0.5673046112060547, + "std": 0.251604288816452, + "p05": 0.09384164214134216, + "p95": 0.9227761626243591, + "pct_saturated": 0.8950423047339346, + "pct_dark": 1.6135235298230892, + "pixels": 57204 + } + }, + "veg_mean": 0.4730309695005417, + "solo_mean": 0.5673046112060547, + "separation": -0.094273641705513, + "veg_p95": 0.9310850501060486, + "veg_sat": 1.2065370416668317, + "before_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 5000, + "analogue_gain": 1.0, + "colour_gains": null + }, + "after_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 4000, + "analogue_gain": 1.0, + "colour_gains": null + } + } + }, + { + "timestamp": "2026-04-23 22:15:58", + "camera": "cam1", + "result": { + "status": "adjust", + "action": "decrease_exposure", + "reason": "Vegetação saturando (1.21%). Reduzir exposição.", + "channel": "cam1", + "class_metrics": { + "erva": { + "count": 1, + "mean": 0.4611518681049347, + "std": 0.25701242685317993, + "p05": 0.06451612710952759, + "p95": 0.9227761626243591, + "pct_saturated": 1.0718586659773144, + "pct_dark": 2.2389236016973197, + "pixels": 158603 + }, + "cana": { + "count": 1, + "mean": 0.4849100708961487, + "std": 0.2785700559616089, + "p05": 0.0498533733189106, + "p95": 0.939393937587738, + "pct_saturated": 1.341215417356349, + "pct_dark": 2.7486403405060296, + "pixels": 105725 + }, + "solo": { + "count": 1, + "mean": 0.5673046112060547, + "std": 0.251604288816452, + "p05": 0.09384164214134216, + "p95": 0.9227761626243591, + "pct_saturated": 0.8950423047339346, + "pct_dark": 1.6135235298230892, + "pixels": 57204 + } + }, + "veg_mean": 0.4730309695005417, + "solo_mean": 0.5673046112060547, + "separation": -0.094273641705513, + "veg_p95": 0.9310850501060486, + "veg_sat": 1.2065370416668317, + "before_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 5000, + "analogue_gain": 1.0, + "colour_gains": null + }, + "after_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 4000, + "analogue_gain": 1.0, + "colour_gains": null + } + } + }, + { + "timestamp": "2026-04-23 22:15:58", + "camera": "cam1", + "result": { + "status": "adjust", + "action": "decrease_exposure", + "reason": "Vegetação saturando (1.21%). Reduzir exposição.", + "channel": "cam1", + "class_metrics": { + "erva": { + "count": 1, + "mean": 0.4611518681049347, + "std": 0.25701242685317993, + "p05": 0.06451612710952759, + "p95": 0.9227761626243591, + "pct_saturated": 1.0718586659773144, + "pct_dark": 2.2389236016973197, + "pixels": 158603 + }, + "cana": { + "count": 1, + "mean": 0.4849100708961487, + "std": 0.2785700559616089, + "p05": 0.0498533733189106, + "p95": 0.939393937587738, + "pct_saturated": 1.341215417356349, + "pct_dark": 2.7486403405060296, + "pixels": 105725 + }, + "solo": { + "count": 1, + "mean": 0.5673046112060547, + "std": 0.251604288816452, + "p05": 0.09384164214134216, + "p95": 0.9227761626243591, + "pct_saturated": 0.8950423047339346, + "pct_dark": 1.6135235298230892, + "pixels": 57204 + } + }, + "veg_mean": 0.4730309695005417, + "solo_mean": 0.5673046112060547, + "separation": -0.094273641705513, + "veg_p95": 0.9310850501060486, + "veg_sat": 1.2065370416668317, + "before_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 4000, + "analogue_gain": 1.0, + "colour_gains": null + }, + "after_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 3000, + "analogue_gain": 1.0, + "colour_gains": null + } + } + }, + { + "timestamp": "2026-04-23 22:15:58", + "camera": "cam1", + "result": { + "status": "adjust", + "action": "decrease_exposure", + "reason": "Vegetação saturando (1.21%). Reduzir exposição.", + "channel": "cam1", + "class_metrics": { + "erva": { + "count": 1, + "mean": 0.4611518681049347, + "std": 0.25701242685317993, + "p05": 0.06451612710952759, + "p95": 0.9227761626243591, + "pct_saturated": 1.0718586659773144, + "pct_dark": 2.2389236016973197, + "pixels": 158603 + }, + "cana": { + "count": 1, + "mean": 0.4849100708961487, + "std": 0.2785700559616089, + "p05": 0.0498533733189106, + "p95": 0.939393937587738, + "pct_saturated": 1.341215417356349, + "pct_dark": 2.7486403405060296, + "pixels": 105725 + }, + "solo": { + "count": 1, + "mean": 0.5673046112060547, + "std": 0.251604288816452, + "p05": 0.09384164214134216, + "p95": 0.9227761626243591, + "pct_saturated": 0.8950423047339346, + "pct_dark": 1.6135235298230892, + "pixels": 57204 + } + }, + "veg_mean": 0.4730309695005417, + "solo_mean": 0.5673046112060547, + "separation": -0.094273641705513, + "veg_p95": 0.9310850501060486, + "veg_sat": 1.2065370416668317, + "before_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 4000, + "analogue_gain": 1.0, + "colour_gains": null + }, + "after_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 3000, + "analogue_gain": 1.0, + "colour_gains": null + } + } + }, + { + "timestamp": "2026-04-23 22:15:59", + "camera": "cam1", + "result": { + "status": "adjust", + "action": "decrease_exposure", + "reason": "Vegetação saturando (1.21%). Reduzir exposição.", + "channel": "cam1", + "class_metrics": { + "erva": { + "count": 1, + "mean": 0.4611518681049347, + "std": 0.25701242685317993, + "p05": 0.06451612710952759, + "p95": 0.9227761626243591, + "pct_saturated": 1.0718586659773144, + "pct_dark": 2.2389236016973197, + "pixels": 158603 + }, + "cana": { + "count": 1, + "mean": 0.4849100708961487, + "std": 0.2785700559616089, + "p05": 0.0498533733189106, + "p95": 0.939393937587738, + "pct_saturated": 1.341215417356349, + "pct_dark": 2.7486403405060296, + "pixels": 105725 + }, + "solo": { + "count": 1, + "mean": 0.5673046112060547, + "std": 0.251604288816452, + "p05": 0.09384164214134216, + "p95": 0.9227761626243591, + "pct_saturated": 0.8950423047339346, + "pct_dark": 1.6135235298230892, + "pixels": 57204 + } + }, + "veg_mean": 0.4730309695005417, + "solo_mean": 0.5673046112060547, + "separation": -0.094273641705513, + "veg_p95": 0.9310850501060486, + "veg_sat": 1.2065370416668317, + "before_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 3000, + "analogue_gain": 1.0, + "colour_gains": null + }, + "after_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 2000, + "analogue_gain": 1.0, + "colour_gains": null + } + } + }, + { + "timestamp": "2026-04-23 22:15:59", + "camera": "cam1", + "result": { + "status": "adjust", + "action": "decrease_exposure", + "reason": "Vegetação saturando (1.21%). Reduzir exposição.", + "channel": "cam1", + "class_metrics": { + "erva": { + "count": 1, + "mean": 0.4611518681049347, + "std": 0.25701242685317993, + "p05": 0.06451612710952759, + "p95": 0.9227761626243591, + "pct_saturated": 1.0718586659773144, + "pct_dark": 2.2389236016973197, + "pixels": 158603 + }, + "cana": { + "count": 1, + "mean": 0.4849100708961487, + "std": 0.2785700559616089, + "p05": 0.0498533733189106, + "p95": 0.939393937587738, + "pct_saturated": 1.341215417356349, + "pct_dark": 2.7486403405060296, + "pixels": 105725 + }, + "solo": { + "count": 1, + "mean": 0.5673046112060547, + "std": 0.251604288816452, + "p05": 0.09384164214134216, + "p95": 0.9227761626243591, + "pct_saturated": 0.8950423047339346, + "pct_dark": 1.6135235298230892, + "pixels": 57204 + } + }, + "veg_mean": 0.4730309695005417, + "solo_mean": 0.5673046112060547, + "separation": -0.094273641705513, + "veg_p95": 0.9310850501060486, + "veg_sat": 1.2065370416668317, + "before_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 3000, + "analogue_gain": 1.0, + "colour_gains": null + }, + "after_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 2000, + "analogue_gain": 1.0, + "colour_gains": null + } + } + }, + { + "timestamp": "2026-04-23 22:16:00", + "camera": "cam1", + "result": { + "status": "adjust", + "action": "decrease_exposure", + "reason": "Vegetação saturando (1.21%). Reduzir exposição.", + "channel": "cam1", + "class_metrics": { + "erva": { + "count": 1, + "mean": 0.4611518681049347, + "std": 0.25701242685317993, + "p05": 0.06451612710952759, + "p95": 0.9227761626243591, + "pct_saturated": 1.0718586659773144, + "pct_dark": 2.2389236016973197, + "pixels": 158603 + }, + "cana": { + "count": 1, + "mean": 0.4849100708961487, + "std": 0.2785700559616089, + "p05": 0.0498533733189106, + "p95": 0.939393937587738, + "pct_saturated": 1.341215417356349, + "pct_dark": 2.7486403405060296, + "pixels": 105725 + }, + "solo": { + "count": 1, + "mean": 0.5673046112060547, + "std": 0.251604288816452, + "p05": 0.09384164214134216, + "p95": 0.9227761626243591, + "pct_saturated": 0.8950423047339346, + "pct_dark": 1.6135235298230892, + "pixels": 57204 + } + }, + "veg_mean": 0.4730309695005417, + "solo_mean": 0.5673046112060547, + "separation": -0.094273641705513, + "veg_p95": 0.9310850501060486, + "veg_sat": 1.2065370416668317, + "before_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 2000, + "analogue_gain": 1.0, + "colour_gains": null + }, + "after_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 1000, + "analogue_gain": 1.0, + "colour_gains": null + } + } + }, + { + "timestamp": "2026-04-23 22:16:00", + "camera": "cam1", + "result": { + "status": "adjust", + "action": "decrease_exposure", + "reason": "Vegetação saturando (1.21%). Reduzir exposição.", + "channel": "cam1", + "class_metrics": { + "erva": { + "count": 1, + "mean": 0.4611518681049347, + "std": 0.25701242685317993, + "p05": 0.06451612710952759, + "p95": 0.9227761626243591, + "pct_saturated": 1.0718586659773144, + "pct_dark": 2.2389236016973197, + "pixels": 158603 + }, + "cana": { + "count": 1, + "mean": 0.4849100708961487, + "std": 0.2785700559616089, + "p05": 0.0498533733189106, + "p95": 0.939393937587738, + "pct_saturated": 1.341215417356349, + "pct_dark": 2.7486403405060296, + "pixels": 105725 + }, + "solo": { + "count": 1, + "mean": 0.5673046112060547, + "std": 0.251604288816452, + "p05": 0.09384164214134216, + "p95": 0.9227761626243591, + "pct_saturated": 0.8950423047339346, + "pct_dark": 1.6135235298230892, + "pixels": 57204 + } + }, + "veg_mean": 0.4730309695005417, + "solo_mean": 0.5673046112060547, + "separation": -0.094273641705513, + "veg_p95": 0.9310850501060486, + "veg_sat": 1.2065370416668317, + "before_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 2000, + "analogue_gain": 1.0, + "colour_gains": null + }, + "after_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 1000, + "analogue_gain": 1.0, + "colour_gains": null + } + } + }, + { + "timestamp": "2026-04-23 22:16:01", + "camera": "cam1", + "result": { + "status": "adjust", + "action": "decrease_exposure", + "reason": "Vegetação saturando (1.21%). Reduzir exposição.", + "channel": "cam1", + "class_metrics": { + "erva": { + "count": 1, + "mean": 0.4611518681049347, + "std": 0.25701242685317993, + "p05": 0.06451612710952759, + "p95": 0.9227761626243591, + "pct_saturated": 1.0718586659773144, + "pct_dark": 2.2389236016973197, + "pixels": 158603 + }, + "cana": { + "count": 1, + "mean": 0.4849100708961487, + "std": 0.2785700559616089, + "p05": 0.0498533733189106, + "p95": 0.939393937587738, + "pct_saturated": 1.341215417356349, + "pct_dark": 2.7486403405060296, + "pixels": 105725 + }, + "solo": { + "count": 1, + "mean": 0.5673046112060547, + "std": 0.251604288816452, + "p05": 0.09384164214134216, + "p95": 0.9227761626243591, + "pct_saturated": 0.8950423047339346, + "pct_dark": 1.6135235298230892, + "pixels": 57204 + } + }, + "veg_mean": 0.4730309695005417, + "solo_mean": 0.5673046112060547, + "separation": -0.094273641705513, + "veg_p95": 0.9310850501060486, + "veg_sat": 1.2065370416668317, + "before_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 1000, + "analogue_gain": 1.0, + "colour_gains": null + }, + "after_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 100, + "analogue_gain": 1.0, + "colour_gains": null + } + } + }, + { + "timestamp": "2026-04-23 22:16:01", + "camera": "cam1", + "result": { + "status": "adjust", + "action": "decrease_exposure", + "reason": "Vegetação saturando (1.21%). Reduzir exposição.", + "channel": "cam1", + "class_metrics": { + "erva": { + "count": 1, + "mean": 0.4611518681049347, + "std": 0.25701242685317993, + "p05": 0.06451612710952759, + "p95": 0.9227761626243591, + "pct_saturated": 1.0718586659773144, + "pct_dark": 2.2389236016973197, + "pixels": 158603 + }, + "cana": { + "count": 1, + "mean": 0.4849100708961487, + "std": 0.2785700559616089, + "p05": 0.0498533733189106, + "p95": 0.939393937587738, + "pct_saturated": 1.341215417356349, + "pct_dark": 2.7486403405060296, + "pixels": 105725 + }, + "solo": { + "count": 1, + "mean": 0.5673046112060547, + "std": 0.251604288816452, + "p05": 0.09384164214134216, + "p95": 0.9227761626243591, + "pct_saturated": 0.8950423047339346, + "pct_dark": 1.6135235298230892, + "pixels": 57204 + } + }, + "veg_mean": 0.4730309695005417, + "solo_mean": 0.5673046112060547, + "separation": -0.094273641705513, + "veg_p95": 0.9310850501060486, + "veg_sat": 1.2065370416668317, + "before_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 1000, + "analogue_gain": 1.0, + "colour_gains": null + }, + "after_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 100, + "analogue_gain": 1.0, + "colour_gains": null + } + } + }, + { + "timestamp": "2026-04-23 22:16:02", + "camera": "cam1", + "result": { + "status": "adjust", + "action": "decrease_exposure", + "reason": "Vegetação saturando (1.21%). Reduzir exposição.", + "channel": "cam1", + "class_metrics": { + "erva": { + "count": 1, + "mean": 0.4611518681049347, + "std": 0.25701242685317993, + "p05": 0.06451612710952759, + "p95": 0.9227761626243591, + "pct_saturated": 1.0718586659773144, + "pct_dark": 2.2389236016973197, + "pixels": 158603 + }, + "cana": { + "count": 1, + "mean": 0.4849100708961487, + "std": 0.2785700559616089, + "p05": 0.0498533733189106, + "p95": 0.939393937587738, + "pct_saturated": 1.341215417356349, + "pct_dark": 2.7486403405060296, + "pixels": 105725 + }, + "solo": { + "count": 1, + "mean": 0.5673046112060547, + "std": 0.251604288816452, + "p05": 0.09384164214134216, + "p95": 0.9227761626243591, + "pct_saturated": 0.8950423047339346, + "pct_dark": 1.6135235298230892, + "pixels": 57204 + } + }, + "veg_mean": 0.4730309695005417, + "solo_mean": 0.5673046112060547, + "separation": -0.094273641705513, + "veg_p95": 0.9310850501060486, + "veg_sat": 1.2065370416668317, + "before_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 100, + "analogue_gain": 1.0, + "colour_gains": null + }, + "after_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 100, + "analogue_gain": 1.0, + "colour_gains": null + } + } + }, + { + "timestamp": "2026-04-23 22:16:02", + "camera": "cam1", + "result": { + "status": "adjust", + "action": "decrease_exposure", + "reason": "Vegetação saturando (1.21%). Reduzir exposição.", + "channel": "cam1", + "class_metrics": { + "erva": { + "count": 1, + "mean": 0.4611518681049347, + "std": 0.25701242685317993, + "p05": 0.06451612710952759, + "p95": 0.9227761626243591, + "pct_saturated": 1.0718586659773144, + "pct_dark": 2.2389236016973197, + "pixels": 158603 + }, + "cana": { + "count": 1, + "mean": 0.4849100708961487, + "std": 0.2785700559616089, + "p05": 0.0498533733189106, + "p95": 0.939393937587738, + "pct_saturated": 1.341215417356349, + "pct_dark": 2.7486403405060296, + "pixels": 105725 + }, + "solo": { + "count": 1, + "mean": 0.5673046112060547, + "std": 0.251604288816452, + "p05": 0.09384164214134216, + "p95": 0.9227761626243591, + "pct_saturated": 0.8950423047339346, + "pct_dark": 1.6135235298230892, + "pixels": 57204 + } + }, + "veg_mean": 0.4730309695005417, + "solo_mean": 0.5673046112060547, + "separation": -0.094273641705513, + "veg_p95": 0.9310850501060486, + "veg_sat": 1.2065370416668317, + "before_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 100, + "analogue_gain": 1.0, + "colour_gains": null + }, + "after_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 100, + "analogue_gain": 1.0, + "colour_gains": null + } + } + }, + { + "timestamp": "2026-04-23 22:16:03", + "camera": "cam1", + "result": { + "status": "adjust", + "action": "decrease_exposure", + "reason": "Vegetação saturando (1.21%). Reduzir exposição.", + "channel": "cam1", + "class_metrics": { + "erva": { + "count": 1, + "mean": 0.4611518681049347, + "std": 0.25701242685317993, + "p05": 0.06451612710952759, + "p95": 0.9227761626243591, + "pct_saturated": 1.0718586659773144, + "pct_dark": 2.2389236016973197, + "pixels": 158603 + }, + "cana": { + "count": 1, + "mean": 0.4849100708961487, + "std": 0.2785700559616089, + "p05": 0.0498533733189106, + "p95": 0.939393937587738, + "pct_saturated": 1.341215417356349, + "pct_dark": 2.7486403405060296, + "pixels": 105725 + }, + "solo": { + "count": 1, + "mean": 0.5673046112060547, + "std": 0.251604288816452, + "p05": 0.09384164214134216, + "p95": 0.9227761626243591, + "pct_saturated": 0.8950423047339346, + "pct_dark": 1.6135235298230892, + "pixels": 57204 + } + }, + "veg_mean": 0.4730309695005417, + "solo_mean": 0.5673046112060547, + "separation": -0.094273641705513, + "veg_p95": 0.9310850501060486, + "veg_sat": 1.2065370416668317, + "before_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 100, + "analogue_gain": 1.0, + "colour_gains": null + }, + "after_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 100, + "analogue_gain": 1.0, + "colour_gains": null + } + } + }, + { + "timestamp": "2026-04-23 22:16:03", + "camera": "cam1", + "result": { + "status": "adjust", + "action": "decrease_exposure", + "reason": "Vegetação saturando (1.21%). Reduzir exposição.", + "channel": "cam1", + "class_metrics": { + "erva": { + "count": 1, + "mean": 0.4611518681049347, + "std": 0.25701242685317993, + "p05": 0.06451612710952759, + "p95": 0.9227761626243591, + "pct_saturated": 1.0718586659773144, + "pct_dark": 2.2389236016973197, + "pixels": 158603 + }, + "cana": { + "count": 1, + "mean": 0.4849100708961487, + "std": 0.2785700559616089, + "p05": 0.0498533733189106, + "p95": 0.939393937587738, + "pct_saturated": 1.341215417356349, + "pct_dark": 2.7486403405060296, + "pixels": 105725 + }, + "solo": { + "count": 1, + "mean": 0.5673046112060547, + "std": 0.251604288816452, + "p05": 0.09384164214134216, + "p95": 0.9227761626243591, + "pct_saturated": 0.8950423047339346, + "pct_dark": 1.6135235298230892, + "pixels": 57204 + } + }, + "veg_mean": 0.4730309695005417, + "solo_mean": 0.5673046112060547, + "separation": -0.094273641705513, + "veg_p95": 0.9310850501060486, + "veg_sat": 1.2065370416668317, + "before_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 100, + "analogue_gain": 1.0, + "colour_gains": null + }, + "after_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 100, + "analogue_gain": 1.0, + "colour_gains": null + } + } + }, + { + "timestamp": "2026-04-23 22:16:05", + "camera": "cam1", + "result": { + "status": "adjust", + "action": "decrease_exposure", + "reason": "Vegetação saturando (1.21%). Reduzir exposição.", + "channel": "cam1", + "class_metrics": { + "erva": { + "count": 1, + "mean": 0.4611518681049347, + "std": 0.25701242685317993, + "p05": 0.06451612710952759, + "p95": 0.9227761626243591, + "pct_saturated": 1.0718586659773144, + "pct_dark": 2.2389236016973197, + "pixels": 158603 + }, + "cana": { + "count": 1, + "mean": 0.4849100708961487, + "std": 0.2785700559616089, + "p05": 0.0498533733189106, + "p95": 0.939393937587738, + "pct_saturated": 1.341215417356349, + "pct_dark": 2.7486403405060296, + "pixels": 105725 + }, + "solo": { + "count": 1, + "mean": 0.5673046112060547, + "std": 0.251604288816452, + "p05": 0.09384164214134216, + "p95": 0.9227761626243591, + "pct_saturated": 0.8950423047339346, + "pct_dark": 1.6135235298230892, + "pixels": 57204 + } + }, + "veg_mean": 0.4730309695005417, + "solo_mean": 0.5673046112060547, + "separation": -0.094273641705513, + "veg_p95": 0.9310850501060486, + "veg_sat": 1.2065370416668317, + "before_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 100, + "analogue_gain": 1.0, + "colour_gains": null + }, + "after_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 100, + "analogue_gain": 1.0, + "colour_gains": null + } + } + }, + { + "timestamp": "2026-04-23 22:16:05", + "camera": "cam1", + "result": { + "status": "adjust", + "action": "decrease_exposure", + "reason": "Vegetação saturando (1.21%). Reduzir exposição.", + "channel": "cam1", + "class_metrics": { + "erva": { + "count": 1, + "mean": 0.4611518681049347, + "std": 0.25701242685317993, + "p05": 0.06451612710952759, + "p95": 0.9227761626243591, + "pct_saturated": 1.0718586659773144, + "pct_dark": 2.2389236016973197, + "pixels": 158603 + }, + "cana": { + "count": 1, + "mean": 0.4849100708961487, + "std": 0.2785700559616089, + "p05": 0.0498533733189106, + "p95": 0.939393937587738, + "pct_saturated": 1.341215417356349, + "pct_dark": 2.7486403405060296, + "pixels": 105725 + }, + "solo": { + "count": 1, + "mean": 0.5673046112060547, + "std": 0.251604288816452, + "p05": 0.09384164214134216, + "p95": 0.9227761626243591, + "pct_saturated": 0.8950423047339346, + "pct_dark": 1.6135235298230892, + "pixels": 57204 + } + }, + "veg_mean": 0.4730309695005417, + "solo_mean": 0.5673046112060547, + "separation": -0.094273641705513, + "veg_p95": 0.9310850501060486, + "veg_sat": 1.2065370416668317, + "before_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 100, + "analogue_gain": 1.0, + "colour_gains": null + }, + "after_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 100, + "analogue_gain": 1.0, + "colour_gains": null + } + } + }, + { + "timestamp": "2026-04-23 22:25:46", + "camera": "cam0", + "result": { + "status": "adjust", + "action": "decrease_exposure", + "reason": "Vegetação saturando (1.45%). Reduzir exposição.", + "channel": "cam0", + "class_metrics": { + "erva": { + "count": 1, + "mean": 0.4457639753818512, + "std": 0.27479854226112366, + "p05": 0.04887585714459419, + "p95": 0.9247311949729919, + "pct_saturated": 1.2417743381211603, + "pct_dark": 2.9754487221529917, + "pixels": 137223 + }, + "cana": { + "count": 1, + "mean": 0.4643779695034027, + "std": 0.29758161306381226, + "p05": 0.034213099628686905, + "p95": 0.9403714537620544, + "pct_saturated": 1.6609685605526576, + "pct_dark": 3.2972649090652757, + "pixels": 113488 + }, + "solo": { + "count": 1, + "mean": 0.4692467749118805, + "std": 0.2457830160856247, + "p05": 0.04887585714459419, + "p95": 0.8602150678634644, + "pct_saturated": 0.45446574701893855, + "pct_dark": 2.324935702595277, + "pixels": 68432 + } + }, + "veg_mean": 0.45507097244262695, + "solo_mean": 0.4692467749118805, + "separation": -0.01417580246925354, + "veg_p95": 0.9325513243675232, + "veg_sat": 1.451371449336909, + "before_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 15000, + "analogue_gain": 1.0, + "colour_gains": null + }, + "after_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 14000, + "analogue_gain": 1.0, + "colour_gains": null + } + } + }, + { + "timestamp": "2026-04-23 22:25:46", + "camera": "cam0", + "result": { + "status": "adjust", + "action": "decrease_exposure", + "reason": "Vegetação saturando (1.45%). Reduzir exposição.", + "channel": "cam0", + "class_metrics": { + "erva": { + "count": 1, + "mean": 0.4457639753818512, + "std": 0.27479854226112366, + "p05": 0.04887585714459419, + "p95": 0.9247311949729919, + "pct_saturated": 1.2417743381211603, + "pct_dark": 2.9754487221529917, + "pixels": 137223 + }, + "cana": { + "count": 1, + "mean": 0.4643779695034027, + "std": 0.29758161306381226, + "p05": 0.034213099628686905, + "p95": 0.9403714537620544, + "pct_saturated": 1.6609685605526576, + "pct_dark": 3.2972649090652757, + "pixels": 113488 + }, + "solo": { + "count": 1, + "mean": 0.4692467749118805, + "std": 0.2457830160856247, + "p05": 0.04887585714459419, + "p95": 0.8602150678634644, + "pct_saturated": 0.45446574701893855, + "pct_dark": 2.324935702595277, + "pixels": 68432 + } + }, + "veg_mean": 0.45507097244262695, + "solo_mean": 0.4692467749118805, + "separation": -0.01417580246925354, + "veg_p95": 0.9325513243675232, + "veg_sat": 1.451371449336909, + "before_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 15000, + "analogue_gain": 1.0, + "colour_gains": null + }, + "after_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 14000, + "analogue_gain": 1.0, + "colour_gains": null + } + } + }, + { + "timestamp": "2026-04-23 22:25:47", + "camera": "cam0", + "result": { + "status": "adjust", + "action": "decrease_exposure", + "reason": "Vegetação saturando (1.45%). Reduzir exposição.", + "channel": "cam0", + "class_metrics": { + "erva": { + "count": 1, + "mean": 0.4457639753818512, + "std": 0.27479854226112366, + "p05": 0.04887585714459419, + "p95": 0.9247311949729919, + "pct_saturated": 1.2417743381211603, + "pct_dark": 2.9754487221529917, + "pixels": 137223 + }, + "cana": { + "count": 1, + "mean": 0.4643779695034027, + "std": 0.29758161306381226, + "p05": 0.034213099628686905, + "p95": 0.9403714537620544, + "pct_saturated": 1.6609685605526576, + "pct_dark": 3.2972649090652757, + "pixels": 113488 + }, + "solo": { + "count": 1, + "mean": 0.4692467749118805, + "std": 0.2457830160856247, + "p05": 0.04887585714459419, + "p95": 0.8602150678634644, + "pct_saturated": 0.45446574701893855, + "pct_dark": 2.324935702595277, + "pixels": 68432 + } + }, + "veg_mean": 0.45507097244262695, + "solo_mean": 0.4692467749118805, + "separation": -0.01417580246925354, + "veg_p95": 0.9325513243675232, + "veg_sat": 1.451371449336909, + "before_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 14000, + "analogue_gain": 1.0, + "colour_gains": null + }, + "after_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 13000, + "analogue_gain": 1.0, + "colour_gains": null + } + } + }, + { + "timestamp": "2026-04-23 22:25:47", + "camera": "cam0", + "result": { + "status": "adjust", + "action": "decrease_exposure", + "reason": "Vegetação saturando (1.45%). Reduzir exposição.", + "channel": "cam0", + "class_metrics": { + "erva": { + "count": 1, + "mean": 0.4457639753818512, + "std": 0.27479854226112366, + "p05": 0.04887585714459419, + "p95": 0.9247311949729919, + "pct_saturated": 1.2417743381211603, + "pct_dark": 2.9754487221529917, + "pixels": 137223 + }, + "cana": { + "count": 1, + "mean": 0.4643779695034027, + "std": 0.29758161306381226, + "p05": 0.034213099628686905, + "p95": 0.9403714537620544, + "pct_saturated": 1.6609685605526576, + "pct_dark": 3.2972649090652757, + "pixels": 113488 + }, + "solo": { + "count": 1, + "mean": 0.4692467749118805, + "std": 0.2457830160856247, + "p05": 0.04887585714459419, + "p95": 0.8602150678634644, + "pct_saturated": 0.45446574701893855, + "pct_dark": 2.324935702595277, + "pixels": 68432 + } + }, + "veg_mean": 0.45507097244262695, + "solo_mean": 0.4692467749118805, + "separation": -0.01417580246925354, + "veg_p95": 0.9325513243675232, + "veg_sat": 1.451371449336909, + "before_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 14000, + "analogue_gain": 1.0, + "colour_gains": null + }, + "after_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 13000, + "analogue_gain": 1.0, + "colour_gains": null + } + } + }, + { + "timestamp": "2026-04-23 22:25:48", + "camera": "cam0", + "result": { + "status": "adjust", + "action": "decrease_exposure", + "reason": "Vegetação saturando (1.45%). Reduzir exposição.", + "channel": "cam0", + "class_metrics": { + "erva": { + "count": 1, + "mean": 0.4457639753818512, + "std": 0.27479854226112366, + "p05": 0.04887585714459419, + "p95": 0.9247311949729919, + "pct_saturated": 1.2417743381211603, + "pct_dark": 2.9754487221529917, + "pixels": 137223 + }, + "cana": { + "count": 1, + "mean": 0.4643779695034027, + "std": 0.29758161306381226, + "p05": 0.034213099628686905, + "p95": 0.9403714537620544, + "pct_saturated": 1.6609685605526576, + "pct_dark": 3.2972649090652757, + "pixels": 113488 + }, + "solo": { + "count": 1, + "mean": 0.4692467749118805, + "std": 0.2457830160856247, + "p05": 0.04887585714459419, + "p95": 0.8602150678634644, + "pct_saturated": 0.45446574701893855, + "pct_dark": 2.324935702595277, + "pixels": 68432 + } + }, + "veg_mean": 0.45507097244262695, + "solo_mean": 0.4692467749118805, + "separation": -0.01417580246925354, + "veg_p95": 0.9325513243675232, + "veg_sat": 1.451371449336909, + "before_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 13000, + "analogue_gain": 1.0, + "colour_gains": null + }, + "after_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 12000, + "analogue_gain": 1.0, + "colour_gains": null + } + } + }, + { + "timestamp": "2026-04-23 22:25:48", + "camera": "cam0", + "result": { + "status": "adjust", + "action": "decrease_exposure", + "reason": "Vegetação saturando (1.45%). Reduzir exposição.", + "channel": "cam0", + "class_metrics": { + "erva": { + "count": 1, + "mean": 0.4457639753818512, + "std": 0.27479854226112366, + "p05": 0.04887585714459419, + "p95": 0.9247311949729919, + "pct_saturated": 1.2417743381211603, + "pct_dark": 2.9754487221529917, + "pixels": 137223 + }, + "cana": { + "count": 1, + "mean": 0.4643779695034027, + "std": 0.29758161306381226, + "p05": 0.034213099628686905, + "p95": 0.9403714537620544, + "pct_saturated": 1.6609685605526576, + "pct_dark": 3.2972649090652757, + "pixels": 113488 + }, + "solo": { + "count": 1, + "mean": 0.4692467749118805, + "std": 0.2457830160856247, + "p05": 0.04887585714459419, + "p95": 0.8602150678634644, + "pct_saturated": 0.45446574701893855, + "pct_dark": 2.324935702595277, + "pixels": 68432 + } + }, + "veg_mean": 0.45507097244262695, + "solo_mean": 0.4692467749118805, + "separation": -0.01417580246925354, + "veg_p95": 0.9325513243675232, + "veg_sat": 1.451371449336909, + "before_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 13000, + "analogue_gain": 1.0, + "colour_gains": null + }, + "after_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 12000, + "analogue_gain": 1.0, + "colour_gains": null + } + } + }, + { + "timestamp": "2026-04-23 22:25:49", + "camera": "cam0", + "result": { + "status": "adjust", + "action": "decrease_exposure", + "reason": "Vegetação saturando (1.45%). Reduzir exposição.", + "channel": "cam0", + "class_metrics": { + "erva": { + "count": 1, + "mean": 0.4457639753818512, + "std": 0.27479854226112366, + "p05": 0.04887585714459419, + "p95": 0.9247311949729919, + "pct_saturated": 1.2417743381211603, + "pct_dark": 2.9754487221529917, + "pixels": 137223 + }, + "cana": { + "count": 1, + "mean": 0.4643779695034027, + "std": 0.29758161306381226, + "p05": 0.034213099628686905, + "p95": 0.9403714537620544, + "pct_saturated": 1.6609685605526576, + "pct_dark": 3.2972649090652757, + "pixels": 113488 + }, + "solo": { + "count": 1, + "mean": 0.4692467749118805, + "std": 0.2457830160856247, + "p05": 0.04887585714459419, + "p95": 0.8602150678634644, + "pct_saturated": 0.45446574701893855, + "pct_dark": 2.324935702595277, + "pixels": 68432 + } + }, + "veg_mean": 0.45507097244262695, + "solo_mean": 0.4692467749118805, + "separation": -0.01417580246925354, + "veg_p95": 0.9325513243675232, + "veg_sat": 1.451371449336909, + "before_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 12000, + "analogue_gain": 1.0, + "colour_gains": null + }, + "after_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 11000, + "analogue_gain": 1.0, + "colour_gains": null + } + } + }, + { + "timestamp": "2026-04-23 22:25:49", + "camera": "cam0", + "result": { + "status": "adjust", + "action": "decrease_exposure", + "reason": "Vegetação saturando (1.45%). Reduzir exposição.", + "channel": "cam0", + "class_metrics": { + "erva": { + "count": 1, + "mean": 0.4457639753818512, + "std": 0.27479854226112366, + "p05": 0.04887585714459419, + "p95": 0.9247311949729919, + "pct_saturated": 1.2417743381211603, + "pct_dark": 2.9754487221529917, + "pixels": 137223 + }, + "cana": { + "count": 1, + "mean": 0.4643779695034027, + "std": 0.29758161306381226, + "p05": 0.034213099628686905, + "p95": 0.9403714537620544, + "pct_saturated": 1.6609685605526576, + "pct_dark": 3.2972649090652757, + "pixels": 113488 + }, + "solo": { + "count": 1, + "mean": 0.4692467749118805, + "std": 0.2457830160856247, + "p05": 0.04887585714459419, + "p95": 0.8602150678634644, + "pct_saturated": 0.45446574701893855, + "pct_dark": 2.324935702595277, + "pixels": 68432 + } + }, + "veg_mean": 0.45507097244262695, + "solo_mean": 0.4692467749118805, + "separation": -0.01417580246925354, + "veg_p95": 0.9325513243675232, + "veg_sat": 1.451371449336909, + "before_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 12000, + "analogue_gain": 1.0, + "colour_gains": null + }, + "after_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 11000, + "analogue_gain": 1.0, + "colour_gains": null + } + } + }, + { + "timestamp": "2026-04-23 22:25:49", + "camera": "cam0", + "result": { + "status": "adjust", + "action": "decrease_exposure", + "reason": "Vegetação saturando (1.45%). Reduzir exposição.", + "channel": "cam0", + "class_metrics": { + "erva": { + "count": 1, + "mean": 0.4457639753818512, + "std": 0.27479854226112366, + "p05": 0.04887585714459419, + "p95": 0.9247311949729919, + "pct_saturated": 1.2417743381211603, + "pct_dark": 2.9754487221529917, + "pixels": 137223 + }, + "cana": { + "count": 1, + "mean": 0.4643779695034027, + "std": 0.29758161306381226, + "p05": 0.034213099628686905, + "p95": 0.9403714537620544, + "pct_saturated": 1.6609685605526576, + "pct_dark": 3.2972649090652757, + "pixels": 113488 + }, + "solo": { + "count": 1, + "mean": 0.4692467749118805, + "std": 0.2457830160856247, + "p05": 0.04887585714459419, + "p95": 0.8602150678634644, + "pct_saturated": 0.45446574701893855, + "pct_dark": 2.324935702595277, + "pixels": 68432 + } + }, + "veg_mean": 0.45507097244262695, + "solo_mean": 0.4692467749118805, + "separation": -0.01417580246925354, + "veg_p95": 0.9325513243675232, + "veg_sat": 1.451371449336909, + "before_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 11000, + "analogue_gain": 1.0, + "colour_gains": null + }, + "after_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 10000, + "analogue_gain": 1.0, + "colour_gains": null + } + } + }, + { + "timestamp": "2026-04-23 22:25:49", + "camera": "cam0", + "result": { + "status": "adjust", + "action": "decrease_exposure", + "reason": "Vegetação saturando (1.45%). Reduzir exposição.", + "channel": "cam0", + "class_metrics": { + "erva": { + "count": 1, + "mean": 0.4457639753818512, + "std": 0.27479854226112366, + "p05": 0.04887585714459419, + "p95": 0.9247311949729919, + "pct_saturated": 1.2417743381211603, + "pct_dark": 2.9754487221529917, + "pixels": 137223 + }, + "cana": { + "count": 1, + "mean": 0.4643779695034027, + "std": 0.29758161306381226, + "p05": 0.034213099628686905, + "p95": 0.9403714537620544, + "pct_saturated": 1.6609685605526576, + "pct_dark": 3.2972649090652757, + "pixels": 113488 + }, + "solo": { + "count": 1, + "mean": 0.4692467749118805, + "std": 0.2457830160856247, + "p05": 0.04887585714459419, + "p95": 0.8602150678634644, + "pct_saturated": 0.45446574701893855, + "pct_dark": 2.324935702595277, + "pixels": 68432 + } + }, + "veg_mean": 0.45507097244262695, + "solo_mean": 0.4692467749118805, + "separation": -0.01417580246925354, + "veg_p95": 0.9325513243675232, + "veg_sat": 1.451371449336909, + "before_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 11000, + "analogue_gain": 1.0, + "colour_gains": null + }, + "after_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 10000, + "analogue_gain": 1.0, + "colour_gains": null + } + } + }, + { + "timestamp": "2026-04-23 22:25:50", + "camera": "cam0", + "result": { + "status": "adjust", + "action": "decrease_exposure", + "reason": "Vegetação saturando (1.45%). Reduzir exposição.", + "channel": "cam0", + "class_metrics": { + "erva": { + "count": 1, + "mean": 0.4457639753818512, + "std": 0.27479854226112366, + "p05": 0.04887585714459419, + "p95": 0.9247311949729919, + "pct_saturated": 1.2417743381211603, + "pct_dark": 2.9754487221529917, + "pixels": 137223 + }, + "cana": { + "count": 1, + "mean": 0.4643779695034027, + "std": 0.29758161306381226, + "p05": 0.034213099628686905, + "p95": 0.9403714537620544, + "pct_saturated": 1.6609685605526576, + "pct_dark": 3.2972649090652757, + "pixels": 113488 + }, + "solo": { + "count": 1, + "mean": 0.4692467749118805, + "std": 0.2457830160856247, + "p05": 0.04887585714459419, + "p95": 0.8602150678634644, + "pct_saturated": 0.45446574701893855, + "pct_dark": 2.324935702595277, + "pixels": 68432 + } + }, + "veg_mean": 0.45507097244262695, + "solo_mean": 0.4692467749118805, + "separation": -0.01417580246925354, + "veg_p95": 0.9325513243675232, + "veg_sat": 1.451371449336909, + "before_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 10000, + "analogue_gain": 1.0, + "colour_gains": null + }, + "after_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 9000, + "analogue_gain": 1.0, + "colour_gains": null + } + } + }, + { + "timestamp": "2026-04-23 22:25:50", + "camera": "cam0", + "result": { + "status": "adjust", + "action": "decrease_exposure", + "reason": "Vegetação saturando (1.45%). Reduzir exposição.", + "channel": "cam0", + "class_metrics": { + "erva": { + "count": 1, + "mean": 0.4457639753818512, + "std": 0.27479854226112366, + "p05": 0.04887585714459419, + "p95": 0.9247311949729919, + "pct_saturated": 1.2417743381211603, + "pct_dark": 2.9754487221529917, + "pixels": 137223 + }, + "cana": { + "count": 1, + "mean": 0.4643779695034027, + "std": 0.29758161306381226, + "p05": 0.034213099628686905, + "p95": 0.9403714537620544, + "pct_saturated": 1.6609685605526576, + "pct_dark": 3.2972649090652757, + "pixels": 113488 + }, + "solo": { + "count": 1, + "mean": 0.4692467749118805, + "std": 0.2457830160856247, + "p05": 0.04887585714459419, + "p95": 0.8602150678634644, + "pct_saturated": 0.45446574701893855, + "pct_dark": 2.324935702595277, + "pixels": 68432 + } + }, + "veg_mean": 0.45507097244262695, + "solo_mean": 0.4692467749118805, + "separation": -0.01417580246925354, + "veg_p95": 0.9325513243675232, + "veg_sat": 1.451371449336909, + "before_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 10000, + "analogue_gain": 1.0, + "colour_gains": null + }, + "after_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 9000, + "analogue_gain": 1.0, + "colour_gains": null + } + } + }, + { + "timestamp": "2026-04-23 22:25:50", + "camera": "cam0", + "result": { + "status": "adjust", + "action": "decrease_exposure", + "reason": "Vegetação saturando (1.45%). Reduzir exposição.", + "channel": "cam0", + "class_metrics": { + "erva": { + "count": 1, + "mean": 0.4457639753818512, + "std": 0.27479854226112366, + "p05": 0.04887585714459419, + "p95": 0.9247311949729919, + "pct_saturated": 1.2417743381211603, + "pct_dark": 2.9754487221529917, + "pixels": 137223 + }, + "cana": { + "count": 1, + "mean": 0.4643779695034027, + "std": 0.29758161306381226, + "p05": 0.034213099628686905, + "p95": 0.9403714537620544, + "pct_saturated": 1.6609685605526576, + "pct_dark": 3.2972649090652757, + "pixels": 113488 + }, + "solo": { + "count": 1, + "mean": 0.4692467749118805, + "std": 0.2457830160856247, + "p05": 0.04887585714459419, + "p95": 0.8602150678634644, + "pct_saturated": 0.45446574701893855, + "pct_dark": 2.324935702595277, + "pixels": 68432 + } + }, + "veg_mean": 0.45507097244262695, + "solo_mean": 0.4692467749118805, + "separation": -0.01417580246925354, + "veg_p95": 0.9325513243675232, + "veg_sat": 1.451371449336909, + "before_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 9000, + "analogue_gain": 1.0, + "colour_gains": null + }, + "after_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 8000, + "analogue_gain": 1.0, + "colour_gains": null + } + } + }, + { + "timestamp": "2026-04-23 22:25:50", + "camera": "cam0", + "result": { + "status": "adjust", + "action": "decrease_exposure", + "reason": "Vegetação saturando (1.45%). Reduzir exposição.", + "channel": "cam0", + "class_metrics": { + "erva": { + "count": 1, + "mean": 0.4457639753818512, + "std": 0.27479854226112366, + "p05": 0.04887585714459419, + "p95": 0.9247311949729919, + "pct_saturated": 1.2417743381211603, + "pct_dark": 2.9754487221529917, + "pixels": 137223 + }, + "cana": { + "count": 1, + "mean": 0.4643779695034027, + "std": 0.29758161306381226, + "p05": 0.034213099628686905, + "p95": 0.9403714537620544, + "pct_saturated": 1.6609685605526576, + "pct_dark": 3.2972649090652757, + "pixels": 113488 + }, + "solo": { + "count": 1, + "mean": 0.4692467749118805, + "std": 0.2457830160856247, + "p05": 0.04887585714459419, + "p95": 0.8602150678634644, + "pct_saturated": 0.45446574701893855, + "pct_dark": 2.324935702595277, + "pixels": 68432 + } + }, + "veg_mean": 0.45507097244262695, + "solo_mean": 0.4692467749118805, + "separation": -0.01417580246925354, + "veg_p95": 0.9325513243675232, + "veg_sat": 1.451371449336909, + "before_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 9000, + "analogue_gain": 1.0, + "colour_gains": null + }, + "after_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 8000, + "analogue_gain": 1.0, + "colour_gains": null + } + } + }, + { + "timestamp": "2026-04-23 22:25:51", + "camera": "cam0", + "result": { + "status": "adjust", + "action": "decrease_exposure", + "reason": "Vegetação saturando (1.45%). Reduzir exposição.", + "channel": "cam0", + "class_metrics": { + "erva": { + "count": 1, + "mean": 0.4457639753818512, + "std": 0.27479854226112366, + "p05": 0.04887585714459419, + "p95": 0.9247311949729919, + "pct_saturated": 1.2417743381211603, + "pct_dark": 2.9754487221529917, + "pixels": 137223 + }, + "cana": { + "count": 1, + "mean": 0.4643779695034027, + "std": 0.29758161306381226, + "p05": 0.034213099628686905, + "p95": 0.9403714537620544, + "pct_saturated": 1.6609685605526576, + "pct_dark": 3.2972649090652757, + "pixels": 113488 + }, + "solo": { + "count": 1, + "mean": 0.4692467749118805, + "std": 0.2457830160856247, + "p05": 0.04887585714459419, + "p95": 0.8602150678634644, + "pct_saturated": 0.45446574701893855, + "pct_dark": 2.324935702595277, + "pixels": 68432 + } + }, + "veg_mean": 0.45507097244262695, + "solo_mean": 0.4692467749118805, + "separation": -0.01417580246925354, + "veg_p95": 0.9325513243675232, + "veg_sat": 1.451371449336909, + "before_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 8000, + "analogue_gain": 1.0, + "colour_gains": null + }, + "after_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 7000, + "analogue_gain": 1.0, + "colour_gains": null + } + } + }, + { + "timestamp": "2026-04-23 22:25:51", + "camera": "cam0", + "result": { + "status": "adjust", + "action": "decrease_exposure", + "reason": "Vegetação saturando (1.45%). Reduzir exposição.", + "channel": "cam0", + "class_metrics": { + "erva": { + "count": 1, + "mean": 0.4457639753818512, + "std": 0.27479854226112366, + "p05": 0.04887585714459419, + "p95": 0.9247311949729919, + "pct_saturated": 1.2417743381211603, + "pct_dark": 2.9754487221529917, + "pixels": 137223 + }, + "cana": { + "count": 1, + "mean": 0.4643779695034027, + "std": 0.29758161306381226, + "p05": 0.034213099628686905, + "p95": 0.9403714537620544, + "pct_saturated": 1.6609685605526576, + "pct_dark": 3.2972649090652757, + "pixels": 113488 + }, + "solo": { + "count": 1, + "mean": 0.4692467749118805, + "std": 0.2457830160856247, + "p05": 0.04887585714459419, + "p95": 0.8602150678634644, + "pct_saturated": 0.45446574701893855, + "pct_dark": 2.324935702595277, + "pixels": 68432 + } + }, + "veg_mean": 0.45507097244262695, + "solo_mean": 0.4692467749118805, + "separation": -0.01417580246925354, + "veg_p95": 0.9325513243675232, + "veg_sat": 1.451371449336909, + "before_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 8000, + "analogue_gain": 1.0, + "colour_gains": null + }, + "after_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 7000, + "analogue_gain": 1.0, + "colour_gains": null + } + } + }, + { + "timestamp": "2026-04-23 22:25:51", + "camera": "cam0", + "result": { + "status": "adjust", + "action": "decrease_exposure", + "reason": "Vegetação saturando (1.45%). Reduzir exposição.", + "channel": "cam0", + "class_metrics": { + "erva": { + "count": 1, + "mean": 0.4457639753818512, + "std": 0.27479854226112366, + "p05": 0.04887585714459419, + "p95": 0.9247311949729919, + "pct_saturated": 1.2417743381211603, + "pct_dark": 2.9754487221529917, + "pixels": 137223 + }, + "cana": { + "count": 1, + "mean": 0.4643779695034027, + "std": 0.29758161306381226, + "p05": 0.034213099628686905, + "p95": 0.9403714537620544, + "pct_saturated": 1.6609685605526576, + "pct_dark": 3.2972649090652757, + "pixels": 113488 + }, + "solo": { + "count": 1, + "mean": 0.4692467749118805, + "std": 0.2457830160856247, + "p05": 0.04887585714459419, + "p95": 0.8602150678634644, + "pct_saturated": 0.45446574701893855, + "pct_dark": 2.324935702595277, + "pixels": 68432 + } + }, + "veg_mean": 0.45507097244262695, + "solo_mean": 0.4692467749118805, + "separation": -0.01417580246925354, + "veg_p95": 0.9325513243675232, + "veg_sat": 1.451371449336909, + "before_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 7000, + "analogue_gain": 1.0, + "colour_gains": null + }, + "after_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 6000, + "analogue_gain": 1.0, + "colour_gains": null + } + } + }, + { + "timestamp": "2026-04-23 22:25:51", + "camera": "cam0", + "result": { + "status": "adjust", + "action": "decrease_exposure", + "reason": "Vegetação saturando (1.45%). Reduzir exposição.", + "channel": "cam0", + "class_metrics": { + "erva": { + "count": 1, + "mean": 0.4457639753818512, + "std": 0.27479854226112366, + "p05": 0.04887585714459419, + "p95": 0.9247311949729919, + "pct_saturated": 1.2417743381211603, + "pct_dark": 2.9754487221529917, + "pixels": 137223 + }, + "cana": { + "count": 1, + "mean": 0.4643779695034027, + "std": 0.29758161306381226, + "p05": 0.034213099628686905, + "p95": 0.9403714537620544, + "pct_saturated": 1.6609685605526576, + "pct_dark": 3.2972649090652757, + "pixels": 113488 + }, + "solo": { + "count": 1, + "mean": 0.4692467749118805, + "std": 0.2457830160856247, + "p05": 0.04887585714459419, + "p95": 0.8602150678634644, + "pct_saturated": 0.45446574701893855, + "pct_dark": 2.324935702595277, + "pixels": 68432 + } + }, + "veg_mean": 0.45507097244262695, + "solo_mean": 0.4692467749118805, + "separation": -0.01417580246925354, + "veg_p95": 0.9325513243675232, + "veg_sat": 1.451371449336909, + "before_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 7000, + "analogue_gain": 1.0, + "colour_gains": null + }, + "after_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 6000, + "analogue_gain": 1.0, + "colour_gains": null + } + } + }, + { + "timestamp": "2026-04-23 22:25:52", + "camera": "cam0", + "result": { + "status": "adjust", + "action": "decrease_exposure", + "reason": "Vegetação saturando (1.45%). Reduzir exposição.", + "channel": "cam0", + "class_metrics": { + "erva": { + "count": 1, + "mean": 0.4457639753818512, + "std": 0.27479854226112366, + "p05": 0.04887585714459419, + "p95": 0.9247311949729919, + "pct_saturated": 1.2417743381211603, + "pct_dark": 2.9754487221529917, + "pixels": 137223 + }, + "cana": { + "count": 1, + "mean": 0.4643779695034027, + "std": 0.29758161306381226, + "p05": 0.034213099628686905, + "p95": 0.9403714537620544, + "pct_saturated": 1.6609685605526576, + "pct_dark": 3.2972649090652757, + "pixels": 113488 + }, + "solo": { + "count": 1, + "mean": 0.4692467749118805, + "std": 0.2457830160856247, + "p05": 0.04887585714459419, + "p95": 0.8602150678634644, + "pct_saturated": 0.45446574701893855, + "pct_dark": 2.324935702595277, + "pixels": 68432 + } + }, + "veg_mean": 0.45507097244262695, + "solo_mean": 0.4692467749118805, + "separation": -0.01417580246925354, + "veg_p95": 0.9325513243675232, + "veg_sat": 1.451371449336909, + "before_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 6000, + "analogue_gain": 1.0, + "colour_gains": null + }, + "after_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 5000, + "analogue_gain": 1.0, + "colour_gains": null + } + } + }, + { + "timestamp": "2026-04-23 22:25:52", + "camera": "cam0", + "result": { + "status": "adjust", + "action": "decrease_exposure", + "reason": "Vegetação saturando (1.45%). Reduzir exposição.", + "channel": "cam0", + "class_metrics": { + "erva": { + "count": 1, + "mean": 0.4457639753818512, + "std": 0.27479854226112366, + "p05": 0.04887585714459419, + "p95": 0.9247311949729919, + "pct_saturated": 1.2417743381211603, + "pct_dark": 2.9754487221529917, + "pixels": 137223 + }, + "cana": { + "count": 1, + "mean": 0.4643779695034027, + "std": 0.29758161306381226, + "p05": 0.034213099628686905, + "p95": 0.9403714537620544, + "pct_saturated": 1.6609685605526576, + "pct_dark": 3.2972649090652757, + "pixels": 113488 + }, + "solo": { + "count": 1, + "mean": 0.4692467749118805, + "std": 0.2457830160856247, + "p05": 0.04887585714459419, + "p95": 0.8602150678634644, + "pct_saturated": 0.45446574701893855, + "pct_dark": 2.324935702595277, + "pixels": 68432 + } + }, + "veg_mean": 0.45507097244262695, + "solo_mean": 0.4692467749118805, + "separation": -0.01417580246925354, + "veg_p95": 0.9325513243675232, + "veg_sat": 1.451371449336909, + "before_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 6000, + "analogue_gain": 1.0, + "colour_gains": null + }, + "after_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 5000, + "analogue_gain": 1.0, + "colour_gains": null + } + } + }, + { + "timestamp": "2026-04-23 22:25:52", + "camera": "cam0", + "result": { + "status": "adjust", + "action": "decrease_exposure", + "reason": "Vegetação saturando (1.45%). Reduzir exposição.", + "channel": "cam0", + "class_metrics": { + "erva": { + "count": 1, + "mean": 0.4457639753818512, + "std": 0.27479854226112366, + "p05": 0.04887585714459419, + "p95": 0.9247311949729919, + "pct_saturated": 1.2417743381211603, + "pct_dark": 2.9754487221529917, + "pixels": 137223 + }, + "cana": { + "count": 1, + "mean": 0.4643779695034027, + "std": 0.29758161306381226, + "p05": 0.034213099628686905, + "p95": 0.9403714537620544, + "pct_saturated": 1.6609685605526576, + "pct_dark": 3.2972649090652757, + "pixels": 113488 + }, + "solo": { + "count": 1, + "mean": 0.4692467749118805, + "std": 0.2457830160856247, + "p05": 0.04887585714459419, + "p95": 0.8602150678634644, + "pct_saturated": 0.45446574701893855, + "pct_dark": 2.324935702595277, + "pixels": 68432 + } + }, + "veg_mean": 0.45507097244262695, + "solo_mean": 0.4692467749118805, + "separation": -0.01417580246925354, + "veg_p95": 0.9325513243675232, + "veg_sat": 1.451371449336909, + "before_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 5000, + "analogue_gain": 1.0, + "colour_gains": null + }, + "after_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 4000, + "analogue_gain": 1.0, + "colour_gains": null + } + } + }, + { + "timestamp": "2026-04-23 22:25:52", + "camera": "cam0", + "result": { + "status": "adjust", + "action": "decrease_exposure", + "reason": "Vegetação saturando (1.45%). Reduzir exposição.", + "channel": "cam0", + "class_metrics": { + "erva": { + "count": 1, + "mean": 0.4457639753818512, + "std": 0.27479854226112366, + "p05": 0.04887585714459419, + "p95": 0.9247311949729919, + "pct_saturated": 1.2417743381211603, + "pct_dark": 2.9754487221529917, + "pixels": 137223 + }, + "cana": { + "count": 1, + "mean": 0.4643779695034027, + "std": 0.29758161306381226, + "p05": 0.034213099628686905, + "p95": 0.9403714537620544, + "pct_saturated": 1.6609685605526576, + "pct_dark": 3.2972649090652757, + "pixels": 113488 + }, + "solo": { + "count": 1, + "mean": 0.4692467749118805, + "std": 0.2457830160856247, + "p05": 0.04887585714459419, + "p95": 0.8602150678634644, + "pct_saturated": 0.45446574701893855, + "pct_dark": 2.324935702595277, + "pixels": 68432 + } + }, + "veg_mean": 0.45507097244262695, + "solo_mean": 0.4692467749118805, + "separation": -0.01417580246925354, + "veg_p95": 0.9325513243675232, + "veg_sat": 1.451371449336909, + "before_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 5000, + "analogue_gain": 1.0, + "colour_gains": null + }, + "after_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 4000, + "analogue_gain": 1.0, + "colour_gains": null + } + } + }, + { + "timestamp": "2026-04-23 22:25:53", + "camera": "cam0", + "result": { + "status": "adjust", + "action": "decrease_exposure", + "reason": "Vegetação saturando (1.45%). Reduzir exposição.", + "channel": "cam0", + "class_metrics": { + "erva": { + "count": 1, + "mean": 0.4457639753818512, + "std": 0.27479854226112366, + "p05": 0.04887585714459419, + "p95": 0.9247311949729919, + "pct_saturated": 1.2417743381211603, + "pct_dark": 2.9754487221529917, + "pixels": 137223 + }, + "cana": { + "count": 1, + "mean": 0.4643779695034027, + "std": 0.29758161306381226, + "p05": 0.034213099628686905, + "p95": 0.9403714537620544, + "pct_saturated": 1.6609685605526576, + "pct_dark": 3.2972649090652757, + "pixels": 113488 + }, + "solo": { + "count": 1, + "mean": 0.4692467749118805, + "std": 0.2457830160856247, + "p05": 0.04887585714459419, + "p95": 0.8602150678634644, + "pct_saturated": 0.45446574701893855, + "pct_dark": 2.324935702595277, + "pixels": 68432 + } + }, + "veg_mean": 0.45507097244262695, + "solo_mean": 0.4692467749118805, + "separation": -0.01417580246925354, + "veg_p95": 0.9325513243675232, + "veg_sat": 1.451371449336909, + "before_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 4000, + "analogue_gain": 1.0, + "colour_gains": null + }, + "after_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 3000, + "analogue_gain": 1.0, + "colour_gains": null + } + } + }, + { + "timestamp": "2026-04-23 22:25:53", + "camera": "cam0", + "result": { + "status": "adjust", + "action": "decrease_exposure", + "reason": "Vegetação saturando (1.45%). Reduzir exposição.", + "channel": "cam0", + "class_metrics": { + "erva": { + "count": 1, + "mean": 0.4457639753818512, + "std": 0.27479854226112366, + "p05": 0.04887585714459419, + "p95": 0.9247311949729919, + "pct_saturated": 1.2417743381211603, + "pct_dark": 2.9754487221529917, + "pixels": 137223 + }, + "cana": { + "count": 1, + "mean": 0.4643779695034027, + "std": 0.29758161306381226, + "p05": 0.034213099628686905, + "p95": 0.9403714537620544, + "pct_saturated": 1.6609685605526576, + "pct_dark": 3.2972649090652757, + "pixels": 113488 + }, + "solo": { + "count": 1, + "mean": 0.4692467749118805, + "std": 0.2457830160856247, + "p05": 0.04887585714459419, + "p95": 0.8602150678634644, + "pct_saturated": 0.45446574701893855, + "pct_dark": 2.324935702595277, + "pixels": 68432 + } + }, + "veg_mean": 0.45507097244262695, + "solo_mean": 0.4692467749118805, + "separation": -0.01417580246925354, + "veg_p95": 0.9325513243675232, + "veg_sat": 1.451371449336909, + "before_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 4000, + "analogue_gain": 1.0, + "colour_gains": null + }, + "after_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 3000, + "analogue_gain": 1.0, + "colour_gains": null + } + } + }, + { + "timestamp": "2026-04-23 22:25:53", + "camera": "cam0", + "result": { + "status": "adjust", + "action": "decrease_exposure", + "reason": "Vegetação saturando (1.45%). Reduzir exposição.", + "channel": "cam0", + "class_metrics": { + "erva": { + "count": 1, + "mean": 0.4457639753818512, + "std": 0.27479854226112366, + "p05": 0.04887585714459419, + "p95": 0.9247311949729919, + "pct_saturated": 1.2417743381211603, + "pct_dark": 2.9754487221529917, + "pixels": 137223 + }, + "cana": { + "count": 1, + "mean": 0.4643779695034027, + "std": 0.29758161306381226, + "p05": 0.034213099628686905, + "p95": 0.9403714537620544, + "pct_saturated": 1.6609685605526576, + "pct_dark": 3.2972649090652757, + "pixels": 113488 + }, + "solo": { + "count": 1, + "mean": 0.4692467749118805, + "std": 0.2457830160856247, + "p05": 0.04887585714459419, + "p95": 0.8602150678634644, + "pct_saturated": 0.45446574701893855, + "pct_dark": 2.324935702595277, + "pixels": 68432 + } + }, + "veg_mean": 0.45507097244262695, + "solo_mean": 0.4692467749118805, + "separation": -0.01417580246925354, + "veg_p95": 0.9325513243675232, + "veg_sat": 1.451371449336909, + "before_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 3000, + "analogue_gain": 1.0, + "colour_gains": null + }, + "after_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 2000, + "analogue_gain": 1.0, + "colour_gains": null + } + } + }, + { + "timestamp": "2026-04-23 22:25:53", + "camera": "cam0", + "result": { + "status": "adjust", + "action": "decrease_exposure", + "reason": "Vegetação saturando (1.45%). Reduzir exposição.", + "channel": "cam0", + "class_metrics": { + "erva": { + "count": 1, + "mean": 0.4457639753818512, + "std": 0.27479854226112366, + "p05": 0.04887585714459419, + "p95": 0.9247311949729919, + "pct_saturated": 1.2417743381211603, + "pct_dark": 2.9754487221529917, + "pixels": 137223 + }, + "cana": { + "count": 1, + "mean": 0.4643779695034027, + "std": 0.29758161306381226, + "p05": 0.034213099628686905, + "p95": 0.9403714537620544, + "pct_saturated": 1.6609685605526576, + "pct_dark": 3.2972649090652757, + "pixels": 113488 + }, + "solo": { + "count": 1, + "mean": 0.4692467749118805, + "std": 0.2457830160856247, + "p05": 0.04887585714459419, + "p95": 0.8602150678634644, + "pct_saturated": 0.45446574701893855, + "pct_dark": 2.324935702595277, + "pixels": 68432 + } + }, + "veg_mean": 0.45507097244262695, + "solo_mean": 0.4692467749118805, + "separation": -0.01417580246925354, + "veg_p95": 0.9325513243675232, + "veg_sat": 1.451371449336909, + "before_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 3000, + "analogue_gain": 1.0, + "colour_gains": null + }, + "after_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 2000, + "analogue_gain": 1.0, + "colour_gains": null + } + } + }, + { + "timestamp": "2026-04-23 22:25:54", + "camera": "cam0", + "result": { + "status": "adjust", + "action": "decrease_exposure", + "reason": "Vegetação saturando (1.45%). Reduzir exposição.", + "channel": "cam0", + "class_metrics": { + "erva": { + "count": 1, + "mean": 0.4457639753818512, + "std": 0.27479854226112366, + "p05": 0.04887585714459419, + "p95": 0.9247311949729919, + "pct_saturated": 1.2417743381211603, + "pct_dark": 2.9754487221529917, + "pixels": 137223 + }, + "cana": { + "count": 1, + "mean": 0.4643779695034027, + "std": 0.29758161306381226, + "p05": 0.034213099628686905, + "p95": 0.9403714537620544, + "pct_saturated": 1.6609685605526576, + "pct_dark": 3.2972649090652757, + "pixels": 113488 + }, + "solo": { + "count": 1, + "mean": 0.4692467749118805, + "std": 0.2457830160856247, + "p05": 0.04887585714459419, + "p95": 0.8602150678634644, + "pct_saturated": 0.45446574701893855, + "pct_dark": 2.324935702595277, + "pixels": 68432 + } + }, + "veg_mean": 0.45507097244262695, + "solo_mean": 0.4692467749118805, + "separation": -0.01417580246925354, + "veg_p95": 0.9325513243675232, + "veg_sat": 1.451371449336909, + "before_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 2000, + "analogue_gain": 1.0, + "colour_gains": null + }, + "after_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 1000, + "analogue_gain": 1.0, + "colour_gains": null + } + } + }, + { + "timestamp": "2026-04-23 22:25:54", + "camera": "cam0", + "result": { + "status": "adjust", + "action": "decrease_exposure", + "reason": "Vegetação saturando (1.45%). Reduzir exposição.", + "channel": "cam0", + "class_metrics": { + "erva": { + "count": 1, + "mean": 0.4457639753818512, + "std": 0.27479854226112366, + "p05": 0.04887585714459419, + "p95": 0.9247311949729919, + "pct_saturated": 1.2417743381211603, + "pct_dark": 2.9754487221529917, + "pixels": 137223 + }, + "cana": { + "count": 1, + "mean": 0.4643779695034027, + "std": 0.29758161306381226, + "p05": 0.034213099628686905, + "p95": 0.9403714537620544, + "pct_saturated": 1.6609685605526576, + "pct_dark": 3.2972649090652757, + "pixels": 113488 + }, + "solo": { + "count": 1, + "mean": 0.4692467749118805, + "std": 0.2457830160856247, + "p05": 0.04887585714459419, + "p95": 0.8602150678634644, + "pct_saturated": 0.45446574701893855, + "pct_dark": 2.324935702595277, + "pixels": 68432 + } + }, + "veg_mean": 0.45507097244262695, + "solo_mean": 0.4692467749118805, + "separation": -0.01417580246925354, + "veg_p95": 0.9325513243675232, + "veg_sat": 1.451371449336909, + "before_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 2000, + "analogue_gain": 1.0, + "colour_gains": null + }, + "after_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 1000, + "analogue_gain": 1.0, + "colour_gains": null + } + } + }, + { + "timestamp": "2026-04-23 22:25:54", + "camera": "cam0", + "result": { + "status": "adjust", + "action": "decrease_exposure", + "reason": "Vegetação saturando (1.45%). Reduzir exposição.", + "channel": "cam0", + "class_metrics": { + "erva": { + "count": 1, + "mean": 0.4457639753818512, + "std": 0.27479854226112366, + "p05": 0.04887585714459419, + "p95": 0.9247311949729919, + "pct_saturated": 1.2417743381211603, + "pct_dark": 2.9754487221529917, + "pixels": 137223 + }, + "cana": { + "count": 1, + "mean": 0.4643779695034027, + "std": 0.29758161306381226, + "p05": 0.034213099628686905, + "p95": 0.9403714537620544, + "pct_saturated": 1.6609685605526576, + "pct_dark": 3.2972649090652757, + "pixels": 113488 + }, + "solo": { + "count": 1, + "mean": 0.4692467749118805, + "std": 0.2457830160856247, + "p05": 0.04887585714459419, + "p95": 0.8602150678634644, + "pct_saturated": 0.45446574701893855, + "pct_dark": 2.324935702595277, + "pixels": 68432 + } + }, + "veg_mean": 0.45507097244262695, + "solo_mean": 0.4692467749118805, + "separation": -0.01417580246925354, + "veg_p95": 0.9325513243675232, + "veg_sat": 1.451371449336909, + "before_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 1000, + "analogue_gain": 1.0, + "colour_gains": null + }, + "after_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 100, + "analogue_gain": 1.0, + "colour_gains": null + } + } + }, + { + "timestamp": "2026-04-23 22:25:54", + "camera": "cam0", + "result": { + "status": "adjust", + "action": "decrease_exposure", + "reason": "Vegetação saturando (1.45%). Reduzir exposição.", + "channel": "cam0", + "class_metrics": { + "erva": { + "count": 1, + "mean": 0.4457639753818512, + "std": 0.27479854226112366, + "p05": 0.04887585714459419, + "p95": 0.9247311949729919, + "pct_saturated": 1.2417743381211603, + "pct_dark": 2.9754487221529917, + "pixels": 137223 + }, + "cana": { + "count": 1, + "mean": 0.4643779695034027, + "std": 0.29758161306381226, + "p05": 0.034213099628686905, + "p95": 0.9403714537620544, + "pct_saturated": 1.6609685605526576, + "pct_dark": 3.2972649090652757, + "pixels": 113488 + }, + "solo": { + "count": 1, + "mean": 0.4692467749118805, + "std": 0.2457830160856247, + "p05": 0.04887585714459419, + "p95": 0.8602150678634644, + "pct_saturated": 0.45446574701893855, + "pct_dark": 2.324935702595277, + "pixels": 68432 + } + }, + "veg_mean": 0.45507097244262695, + "solo_mean": 0.4692467749118805, + "separation": -0.01417580246925354, + "veg_p95": 0.9325513243675232, + "veg_sat": 1.451371449336909, + "before_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 1000, + "analogue_gain": 1.0, + "colour_gains": null + }, + "after_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 100, + "analogue_gain": 1.0, + "colour_gains": null + } + } + }, + { + "timestamp": "2026-04-23 22:25:55", + "camera": "cam0", + "result": { + "status": "adjust", + "action": "decrease_exposure", + "reason": "Vegetação saturando (1.45%). Reduzir exposição.", + "channel": "cam0", + "class_metrics": { + "erva": { + "count": 1, + "mean": 0.4457639753818512, + "std": 0.27479854226112366, + "p05": 0.04887585714459419, + "p95": 0.9247311949729919, + "pct_saturated": 1.2417743381211603, + "pct_dark": 2.9754487221529917, + "pixels": 137223 + }, + "cana": { + "count": 1, + "mean": 0.4643779695034027, + "std": 0.29758161306381226, + "p05": 0.034213099628686905, + "p95": 0.9403714537620544, + "pct_saturated": 1.6609685605526576, + "pct_dark": 3.2972649090652757, + "pixels": 113488 + }, + "solo": { + "count": 1, + "mean": 0.4692467749118805, + "std": 0.2457830160856247, + "p05": 0.04887585714459419, + "p95": 0.8602150678634644, + "pct_saturated": 0.45446574701893855, + "pct_dark": 2.324935702595277, + "pixels": 68432 + } + }, + "veg_mean": 0.45507097244262695, + "solo_mean": 0.4692467749118805, + "separation": -0.01417580246925354, + "veg_p95": 0.9325513243675232, + "veg_sat": 1.451371449336909, + "before_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 100, + "analogue_gain": 1.0, + "colour_gains": null + }, + "after_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 100, + "analogue_gain": 1.0, + "colour_gains": null + } + } + }, + { + "timestamp": "2026-04-23 22:25:55", + "camera": "cam0", + "result": { + "status": "adjust", + "action": "decrease_exposure", + "reason": "Vegetação saturando (1.45%). Reduzir exposição.", + "channel": "cam0", + "class_metrics": { + "erva": { + "count": 1, + "mean": 0.4457639753818512, + "std": 0.27479854226112366, + "p05": 0.04887585714459419, + "p95": 0.9247311949729919, + "pct_saturated": 1.2417743381211603, + "pct_dark": 2.9754487221529917, + "pixels": 137223 + }, + "cana": { + "count": 1, + "mean": 0.4643779695034027, + "std": 0.29758161306381226, + "p05": 0.034213099628686905, + "p95": 0.9403714537620544, + "pct_saturated": 1.6609685605526576, + "pct_dark": 3.2972649090652757, + "pixels": 113488 + }, + "solo": { + "count": 1, + "mean": 0.4692467749118805, + "std": 0.2457830160856247, + "p05": 0.04887585714459419, + "p95": 0.8602150678634644, + "pct_saturated": 0.45446574701893855, + "pct_dark": 2.324935702595277, + "pixels": 68432 + } + }, + "veg_mean": 0.45507097244262695, + "solo_mean": 0.4692467749118805, + "separation": -0.01417580246925354, + "veg_p95": 0.9325513243675232, + "veg_sat": 1.451371449336909, + "before_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 100, + "analogue_gain": 1.0, + "colour_gains": null + }, + "after_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 100, + "analogue_gain": 1.0, + "colour_gains": null + } + } + }, + { + "timestamp": "2026-04-23 22:25:57", + "camera": "cam0", + "result": { + "status": "adjust", + "action": "decrease_exposure", + "reason": "Vegetação saturando (1.45%). Reduzir exposição.", + "channel": "cam0", + "class_metrics": { + "erva": { + "count": 1, + "mean": 0.4457639753818512, + "std": 0.27479854226112366, + "p05": 0.04887585714459419, + "p95": 0.9247311949729919, + "pct_saturated": 1.2417743381211603, + "pct_dark": 2.9754487221529917, + "pixels": 137223 + }, + "cana": { + "count": 1, + "mean": 0.4643779695034027, + "std": 0.29758161306381226, + "p05": 0.034213099628686905, + "p95": 0.9403714537620544, + "pct_saturated": 1.6609685605526576, + "pct_dark": 3.2972649090652757, + "pixels": 113488 + }, + "solo": { + "count": 1, + "mean": 0.4692467749118805, + "std": 0.2457830160856247, + "p05": 0.04887585714459419, + "p95": 0.8602150678634644, + "pct_saturated": 0.45446574701893855, + "pct_dark": 2.324935702595277, + "pixels": 68432 + } + }, + "veg_mean": 0.45507097244262695, + "solo_mean": 0.4692467749118805, + "separation": -0.01417580246925354, + "veg_p95": 0.9325513243675232, + "veg_sat": 1.451371449336909, + "before_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 100, + "analogue_gain": 1.0, + "colour_gains": null + }, + "after_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 100, + "analogue_gain": 1.0, + "colour_gains": null + } + } + }, + { + "timestamp": "2026-04-23 22:25:57", + "camera": "cam0", + "result": { + "status": "adjust", + "action": "decrease_exposure", + "reason": "Vegetação saturando (1.45%). Reduzir exposição.", + "channel": "cam0", + "class_metrics": { + "erva": { + "count": 1, + "mean": 0.4457639753818512, + "std": 0.27479854226112366, + "p05": 0.04887585714459419, + "p95": 0.9247311949729919, + "pct_saturated": 1.2417743381211603, + "pct_dark": 2.9754487221529917, + "pixels": 137223 + }, + "cana": { + "count": 1, + "mean": 0.4643779695034027, + "std": 0.29758161306381226, + "p05": 0.034213099628686905, + "p95": 0.9403714537620544, + "pct_saturated": 1.6609685605526576, + "pct_dark": 3.2972649090652757, + "pixels": 113488 + }, + "solo": { + "count": 1, + "mean": 0.4692467749118805, + "std": 0.2457830160856247, + "p05": 0.04887585714459419, + "p95": 0.8602150678634644, + "pct_saturated": 0.45446574701893855, + "pct_dark": 2.324935702595277, + "pixels": 68432 + } + }, + "veg_mean": 0.45507097244262695, + "solo_mean": 0.4692467749118805, + "separation": -0.01417580246925354, + "veg_p95": 0.9325513243675232, + "veg_sat": 1.451371449336909, + "before_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 100, + "analogue_gain": 1.0, + "colour_gains": null + }, + "after_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 100, + "analogue_gain": 1.0, + "colour_gains": null + } + } + }, + { + "timestamp": "2026-04-23 22:26:04", + "camera": "cam2", + "result": { + "status": "ok", + "action": "keep", + "reason": "RGB parece aceitável.", + "channel": "cam2", + "scene_health": { + "mean": 0.40037205815315247, + "std": 0.2858090400695801, + "p05": 0.0117647061124444, + "p95": 0.9058823585510254, + "pct_saturated": 0.009240976037851038, + "pct_dark": 6.297564197954824, + "comment": "ok" + }, + "class_metrics": { + "erva": { + "count": 1, + "mean": 0.2773081362247467, + "std": 0.19852407276630402, + "p05": 0.003921568859368563, + "p95": 0.6039215922355652, + "pct_saturated": 0.0, + "pct_dark": 9.312112611594372, + "pixels": 358329 + }, + "cana": { + "count": 1, + "mean": 0.3092897832393646, + "std": 0.25642046332359314, + "p05": 0.003921568859368563, + "p95": 0.8196078538894653, + "pct_saturated": 0.0011790541038451902, + "pct_dark": 13.049770821358564, + "pixels": 339255 + }, + "solo": { + "count": 1, + "mean": 0.2286829948425293, + "std": 0.1858908087015152, + "p05": 0.019607843831181526, + "p95": 0.5843137502670288, + "pct_saturated": 0.008336529002784402, + "pct_dark": 5.343715090784801, + "pixels": 179931 + } + }, + "before_settings": { + "ae_enable": true, + "awb_enable": true, + "exposure_time_us": 15000, + "analogue_gain": 1.0, + "colour_gains": [ + 1.0, + 1.0 + ] + }, + "after_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 15000, + "analogue_gain": 1.0, + "colour_gains": [ + 1.0, + 1.0 + ] + } + } + }, + { + "timestamp": "2026-04-23 22:26:04", + "camera": "cam2", + "result": { + "status": "ok", + "action": "keep", + "reason": "RGB parece aceitável.", + "channel": "cam2", + "scene_health": { + "mean": 0.40037205815315247, + "std": 0.2858090400695801, + "p05": 0.0117647061124444, + "p95": 0.9058823585510254, + "pct_saturated": 0.009240976037851038, + "pct_dark": 6.297564197954824, + "comment": "ok" + }, + "class_metrics": { + "erva": { + "count": 1, + "mean": 0.2773081362247467, + "std": 0.19852407276630402, + "p05": 0.003921568859368563, + "p95": 0.6039215922355652, + "pct_saturated": 0.0, + "pct_dark": 9.312112611594372, + "pixels": 358329 + }, + "cana": { + "count": 1, + "mean": 0.3092897832393646, + "std": 0.25642046332359314, + "p05": 0.003921568859368563, + "p95": 0.8196078538894653, + "pct_saturated": 0.0011790541038451902, + "pct_dark": 13.049770821358564, + "pixels": 339255 + }, + "solo": { + "count": 1, + "mean": 0.2286829948425293, + "std": 0.1858908087015152, + "p05": 0.019607843831181526, + "p95": 0.5843137502670288, + "pct_saturated": 0.008336529002784402, + "pct_dark": 5.343715090784801, + "pixels": 179931 + } + }, + "before_settings": { + "ae_enable": true, + "awb_enable": true, + "exposure_time_us": 15000, + "analogue_gain": 1.0, + "colour_gains": [ + 1.0, + 1.0 + ] + }, + "after_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 15000, + "analogue_gain": 1.0, + "colour_gains": [ + 1.0, + 1.0 + ] + } + } + }, + { + "timestamp": "2026-04-23 22:26:05", + "camera": "cam2", + "result": { + "status": "ok", + "action": "keep", + "reason": "RGB parece aceitável.", + "channel": "cam2", + "scene_health": { + "mean": 0.40037205815315247, + "std": 0.2858090400695801, + "p05": 0.0117647061124444, + "p95": 0.9058823585510254, + "pct_saturated": 0.009240976037851038, + "pct_dark": 6.297564197954824, + "comment": "ok" + }, + "class_metrics": { + "erva": { + "count": 1, + "mean": 0.2773081362247467, + "std": 0.19852407276630402, + "p05": 0.003921568859368563, + "p95": 0.6039215922355652, + "pct_saturated": 0.0, + "pct_dark": 9.312112611594372, + "pixels": 358329 + }, + "cana": { + "count": 1, + "mean": 0.3092897832393646, + "std": 0.25642046332359314, + "p05": 0.003921568859368563, + "p95": 0.8196078538894653, + "pct_saturated": 0.0011790541038451902, + "pct_dark": 13.049770821358564, + "pixels": 339255 + }, + "solo": { + "count": 1, + "mean": 0.2286829948425293, + "std": 0.1858908087015152, + "p05": 0.019607843831181526, + "p95": 0.5843137502670288, + "pct_saturated": 0.008336529002784402, + "pct_dark": 5.343715090784801, + "pixels": 179931 + } + }, + "before_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 15000, + "analogue_gain": 1.0, + "colour_gains": [ + 1.0, + 1.0 + ] + }, + "after_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 15000, + "analogue_gain": 1.0, + "colour_gains": [ + 1.0, + 1.0 + ] + } + } + }, + { + "timestamp": "2026-04-23 22:26:05", + "camera": "cam2", + "result": { + "status": "ok", + "action": "keep", + "reason": "RGB parece aceitável.", + "channel": "cam2", + "scene_health": { + "mean": 0.40037205815315247, + "std": 0.2858090400695801, + "p05": 0.0117647061124444, + "p95": 0.9058823585510254, + "pct_saturated": 0.009240976037851038, + "pct_dark": 6.297564197954824, + "comment": "ok" + }, + "class_metrics": { + "erva": { + "count": 1, + "mean": 0.2773081362247467, + "std": 0.19852407276630402, + "p05": 0.003921568859368563, + "p95": 0.6039215922355652, + "pct_saturated": 0.0, + "pct_dark": 9.312112611594372, + "pixels": 358329 + }, + "cana": { + "count": 1, + "mean": 0.3092897832393646, + "std": 0.25642046332359314, + "p05": 0.003921568859368563, + "p95": 0.8196078538894653, + "pct_saturated": 0.0011790541038451902, + "pct_dark": 13.049770821358564, + "pixels": 339255 + }, + "solo": { + "count": 1, + "mean": 0.2286829948425293, + "std": 0.1858908087015152, + "p05": 0.019607843831181526, + "p95": 0.5843137502670288, + "pct_saturated": 0.008336529002784402, + "pct_dark": 5.343715090784801, + "pixels": 179931 + } + }, + "before_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 15000, + "analogue_gain": 1.0, + "colour_gains": [ + 1.0, + 1.0 + ] + }, + "after_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 15000, + "analogue_gain": 1.0, + "colour_gains": [ + 1.0, + 1.0 + ] + } + } + }, + { + "timestamp": "2026-04-23 22:26:06", + "camera": "cam2", + "result": { + "status": "ok", + "action": "keep", + "reason": "RGB parece aceitável.", + "channel": "cam2", + "scene_health": { + "mean": 0.40037205815315247, + "std": 0.2858090400695801, + "p05": 0.0117647061124444, + "p95": 0.9058823585510254, + "pct_saturated": 0.009240976037851038, + "pct_dark": 6.297564197954824, + "comment": "ok" + }, + "class_metrics": { + "erva": { + "count": 1, + "mean": 0.2773081362247467, + "std": 0.19852407276630402, + "p05": 0.003921568859368563, + "p95": 0.6039215922355652, + "pct_saturated": 0.0, + "pct_dark": 9.312112611594372, + "pixels": 358329 + }, + "cana": { + "count": 1, + "mean": 0.3092897832393646, + "std": 0.25642046332359314, + "p05": 0.003921568859368563, + "p95": 0.8196078538894653, + "pct_saturated": 0.0011790541038451902, + "pct_dark": 13.049770821358564, + "pixels": 339255 + }, + "solo": { + "count": 1, + "mean": 0.2286829948425293, + "std": 0.1858908087015152, + "p05": 0.019607843831181526, + "p95": 0.5843137502670288, + "pct_saturated": 0.008336529002784402, + "pct_dark": 5.343715090784801, + "pixels": 179931 + } + }, + "before_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 15000, + "analogue_gain": 1.0, + "colour_gains": [ + 1.0, + 1.0 + ] + }, + "after_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 15000, + "analogue_gain": 1.0, + "colour_gains": [ + 1.0, + 1.0 + ] + } + } + }, + { + "timestamp": "2026-04-23 22:26:06", + "camera": "cam2", + "result": { + "status": "ok", + "action": "keep", + "reason": "RGB parece aceitável.", + "channel": "cam2", + "scene_health": { + "mean": 0.40037205815315247, + "std": 0.2858090400695801, + "p05": 0.0117647061124444, + "p95": 0.9058823585510254, + "pct_saturated": 0.009240976037851038, + "pct_dark": 6.297564197954824, + "comment": "ok" + }, + "class_metrics": { + "erva": { + "count": 1, + "mean": 0.2773081362247467, + "std": 0.19852407276630402, + "p05": 0.003921568859368563, + "p95": 0.6039215922355652, + "pct_saturated": 0.0, + "pct_dark": 9.312112611594372, + "pixels": 358329 + }, + "cana": { + "count": 1, + "mean": 0.3092897832393646, + "std": 0.25642046332359314, + "p05": 0.003921568859368563, + "p95": 0.8196078538894653, + "pct_saturated": 0.0011790541038451902, + "pct_dark": 13.049770821358564, + "pixels": 339255 + }, + "solo": { + "count": 1, + "mean": 0.2286829948425293, + "std": 0.1858908087015152, + "p05": 0.019607843831181526, + "p95": 0.5843137502670288, + "pct_saturated": 0.008336529002784402, + "pct_dark": 5.343715090784801, + "pixels": 179931 + } + }, + "before_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 15000, + "analogue_gain": 1.0, + "colour_gains": [ + 1.0, + 1.0 + ] + }, + "after_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 15000, + "analogue_gain": 1.0, + "colour_gains": [ + 1.0, + 1.0 + ] + } + } + }, + { + "timestamp": "2026-04-23 22:26:08", + "camera": "cam2", + "result": { + "status": "ok", + "action": "keep", + "reason": "RGB parece aceitável.", + "channel": "cam2", + "scene_health": { + "mean": 0.40037205815315247, + "std": 0.2858090400695801, + "p05": 0.0117647061124444, + "p95": 0.9058823585510254, + "pct_saturated": 0.009240976037851038, + "pct_dark": 6.297564197954824, + "comment": "ok" + }, + "class_metrics": { + "erva": { + "count": 1, + "mean": 0.2773081362247467, + "std": 0.19852407276630402, + "p05": 0.003921568859368563, + "p95": 0.6039215922355652, + "pct_saturated": 0.0, + "pct_dark": 9.312112611594372, + "pixels": 358329 + }, + "cana": { + "count": 1, + "mean": 0.3092897832393646, + "std": 0.25642046332359314, + "p05": 0.003921568859368563, + "p95": 0.8196078538894653, + "pct_saturated": 0.0011790541038451902, + "pct_dark": 13.049770821358564, + "pixels": 339255 + }, + "solo": { + "count": 1, + "mean": 0.2286829948425293, + "std": 0.1858908087015152, + "p05": 0.019607843831181526, + "p95": 0.5843137502670288, + "pct_saturated": 0.008336529002784402, + "pct_dark": 5.343715090784801, + "pixels": 179931 + } + }, + "before_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 15000, + "analogue_gain": 1.0, + "colour_gains": [ + 1.0, + 1.0 + ] + }, + "after_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 15000, + "analogue_gain": 1.0, + "colour_gains": [ + 1.0, + 1.0 + ] + } + } + }, + { + "timestamp": "2026-04-23 22:26:08", + "camera": "cam2", + "result": { + "status": "ok", + "action": "keep", + "reason": "RGB parece aceitável.", + "channel": "cam2", + "scene_health": { + "mean": 0.40037205815315247, + "std": 0.2858090400695801, + "p05": 0.0117647061124444, + "p95": 0.9058823585510254, + "pct_saturated": 0.009240976037851038, + "pct_dark": 6.297564197954824, + "comment": "ok" + }, + "class_metrics": { + "erva": { + "count": 1, + "mean": 0.2773081362247467, + "std": 0.19852407276630402, + "p05": 0.003921568859368563, + "p95": 0.6039215922355652, + "pct_saturated": 0.0, + "pct_dark": 9.312112611594372, + "pixels": 358329 + }, + "cana": { + "count": 1, + "mean": 0.3092897832393646, + "std": 0.25642046332359314, + "p05": 0.003921568859368563, + "p95": 0.8196078538894653, + "pct_saturated": 0.0011790541038451902, + "pct_dark": 13.049770821358564, + "pixels": 339255 + }, + "solo": { + "count": 1, + "mean": 0.2286829948425293, + "std": 0.1858908087015152, + "p05": 0.019607843831181526, + "p95": 0.5843137502670288, + "pct_saturated": 0.008336529002784402, + "pct_dark": 5.343715090784801, + "pixels": 179931 + } + }, + "before_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 15000, + "analogue_gain": 1.0, + "colour_gains": [ + 1.0, + 1.0 + ] + }, + "after_settings": { + "ae_enable": false, + "awb_enable": false, + "exposure_time_us": 15000, + "analogue_gain": 1.0, + "colour_gains": [ + 1.0, + 1.0 + ] + } + } + } + ] +} \ No newline at end of file diff --git a/Python/raspi/cam_3/multispectral_service.py b/Python/raspi/cam_3/multispectral_service.py index 29837a472..7ff661a12 100644 --- a/Python/raspi/cam_3/multispectral_service.py +++ b/Python/raspi/cam_3/multispectral_service.py @@ -3,6 +3,7 @@ import json import numpy as np import base64 import time +import os from typing import Optional @@ -428,4 +429,80 @@ class MultiSpectralService: }) def get_sensor_modes(self): - return self._send_command({"cmd": "get_sensor_modes"}) \ No newline at end of file + return self._send_command({"cmd": "get_sensor_modes"}) + + + def load_camera_params_json(self, path: str) -> dict: + if not path or not os.path.isfile(path): + raise FileNotFoundError(f"Arquivo de parâmetros das câmeras não encontrado: {path}") + + with open(path, "r", encoding="utf-8") as f: + data = json.load(f) + + settings = data.get("camera_settings") + if not isinstance(settings, dict): + raise RuntimeError("JSON de calibração inválido: chave camera_settings ausente") + + return settings + + def apply_camera_settings(self, camera_settings: dict) -> dict: + applied = {} + + for cam_id in ("cam0", "cam1", "cam2"): + ctrl = camera_settings.get(cam_id) + if not ctrl: + applied[cam_id] = { + "ok": False, + "error": "sem parâmetros no JSON", + } + continue + + ae = bool(ctrl.get("ae_enable", False)) + awb = bool(ctrl.get("awb_enable", False)) + exp = ctrl.get("exposure_time_us") + gain = ctrl.get("analogue_gain") + colour_gains = ctrl.get("colour_gains") + + result = {} + + result["ae"] = self.set_ae_enable(cam_id, ae) + + if cam_id == "cam2": + result["awb"] = self.set_awb_enable(cam_id, awb) + + if not ae: + if exp is not None: + result["exposure"] = self.set_exposure_time(cam_id, int(exp)) + if gain is not None: + result["gain"] = self.set_analogue_gain(cam_id, float(gain)) + + if cam_id == "cam2" and not awb and colour_gains is not None: + r_gain, b_gain = colour_gains + result["colour_gains"] = self.set_colour_gains(cam_id, float(r_gain), float(b_gain)) + + applied[cam_id] = { + "ok": True, + "requested": ctrl, + "responses": result, + } + + return applied + + def apply_camera_params_json(self, path: str) -> dict: + if path is None: + return { + "ok": False, + "path": None, + "camera_settings": None, + "applied": None, + } + + camera_settings = self.load_camera_params_json(path) + applied = self.apply_camera_settings(camera_settings) + + return { + "ok": True, + "path": path, + "camera_settings": camera_settings, + "applied": applied, + } diff --git a/Python/raspi/sensor_calibration_tool.py b/Python/raspi/sensor_calibration_tool.py index 9e5fd9f8e..018356f04 100644 --- a/Python/raspi/sensor_calibration_tool.py +++ b/Python/raspi/sensor_calibration_tool.py @@ -84,12 +84,6 @@ def validate_module_ready(status: dict, frame_type: str, raw_policy: str, captur raise RuntimeError(f"frame_type desconhecido para validação: {frame_type}") -def resolve_effective_capture_mode(requested_mode: str) -> str: - if requested_mode in ("SINGLE", "DOUBLE", "TRIPLE"): - return requested_mode - return "AUTO" - - def build_empty_panel(shape_hw: tuple[int, int], title: str) -> np.ndarray: h, w = shape_hw img = np.zeros((h, w, 3), dtype=np.uint8) @@ -178,14 +172,99 @@ def compute_scene_health(img01: np.ndarray) -> dict: def draw_rois(panel_bgr: np.ndarray, rois: list[dict]): for idx, roi in enumerate(rois): + color = roi.get("color", color_for_index(idx)) + label = roi.get("name", f"roi_{idx+1}") + + if roi.get("type") == "polygon": + pts = np.array(roi.get("points", []), dtype=np.int32) + if len(pts) >= 2: + cv2.polylines(panel_bgr, [pts], isClosed=True, color=color, thickness=2) + if len(pts) >= 1: + x, y = pts[0] + cv2.putText( + panel_bgr, + label, + (int(x) + 4, max(18, int(y) - 6)), + cv2.FONT_HERSHEY_SIMPLEX, + 0.5, + color, + 2, + cv2.LINE_AA, + ) + continue + rect = roi.get("rect") if rect is None: continue + x0, y0, x1, y1 = rect - color = roi.get("color", color_for_index(idx)) cv2.rectangle(panel_bgr, (x0, y0), (x1, y1), color, 2) - label = roi.get("name", f"roi_{idx+1}") - cv2.putText(panel_bgr, label, (x0 + 4, max(18, y0 - 6)), cv2.FONT_HERSHEY_SIMPLEX, 0.5, color, 2, cv2.LINE_AA) + cv2.putText( + panel_bgr, + label, + (x0 + 4, max(18, y0 - 6)), + cv2.FONT_HERSHEY_SIMPLEX, + 0.5, + color, + 2, + cv2.LINE_AA, + ) + + +def compute_stats_from_polygon_roi(img01: np.ndarray, points: list) -> dict: + h, w = img01.shape[:2] + + if len(points) < 3: + return compute_stats_from_roi(img01, (0, 0, 0, 0)) + + pts = np.array(points, dtype=np.int32) + mask = np.zeros((h, w), dtype=np.uint8) + cv2.fillPoly(mask, [pts], 255) + + arr = img01[mask > 0].astype(np.float32).reshape(-1) + + if arr.size == 0: + return { + "valid": False, "mean": 0.0, "std": 0.0, + "min": 0.0, "max": 0.0, + "p05": 0.0, "p95": 0.0, + "pct_saturated": 0.0, + "pct_dark": 0.0, + "pixels": 0, + } + + return { + "valid": True, + "mean": float(arr.mean()), + "std": float(arr.std()), + "min": float(arr.min()), + "max": float(arr.max()), + "p05": float(np.percentile(arr, 5)), + "p95": float(np.percentile(arr, 95)), + "pct_saturated": float((arr >= 0.98).mean() * 100.0), + "pct_dark": float((arr <= 0.02).mean() * 100.0), + "pixels": int(arr.size), + } + + +def compute_stats_for_roi(img01: np.ndarray, roi: dict) -> dict: + if roi.get("type") == "polygon": + return compute_stats_from_polygon_roi(img01, roi.get("points", [])) + + return compute_stats_from_roi(img01, roi.get("rect", (0, 0, 0, 0))) + + +def draw_current_polygon(panel_bgr: np.ndarray, points: list): + if not points: + return + + pts = np.array(points, dtype=np.int32) + + for p in pts: + cv2.circle(panel_bgr, tuple(p), 4, (0, 255, 255), -1) + + if len(pts) >= 2: + cv2.polylines(panel_bgr, [pts], isClosed=False, color=(0, 255, 255), thickness=1) # ============================================================ @@ -252,6 +331,146 @@ class StreamDecoder: return decoded +# ============================================================ +# MOCK +# ============================================================ + +def load_mock_image_rgb(path: str, fallback_shape=(480, 640)): + if not path: + h, w = fallback_shape + img = np.zeros((h, w, 3), dtype=np.float32) + return img + + bgr = cv2.imread(path, cv2.IMREAD_COLOR) + if bgr is None: + raise RuntimeError(f"Falha ao carregar mock RGB: {path}") + + rgb = bgr[:, :, ::-1].astype(np.float32) / 255.0 + return rgb + + +def load_mock_image_gray(path: str, fallback_shape=(480, 640)): + if not path: + h, w = fallback_shape + return np.zeros((h, w), dtype=np.float32) + + gray = cv2.imread(path, cv2.IMREAD_GRAYSCALE) + if gray is None: + raise RuntimeError(f"Falha ao carregar mock mono: {path}") + + return gray.astype(np.float32) / 255.0 + + +def build_mock_decoded(args): + shape = (args.height, args.width) + + cam2 = load_mock_image_rgb(args.mock_cam2, fallback_shape=shape) + cam0 = load_mock_image_gray(args.mock_cam0, fallback_shape=shape) + cam1 = load_mock_image_gray(args.mock_cam1, fallback_shape=shape) + + return { + "cam2": {"name": "RGB", "image": cam2, "meta": {"mock": True}}, + "cam0": {"name": "RE", "image": cam0, "meta": {"mock": True}}, + "cam1": {"name": "NIR", "image": cam1, "meta": {"mock": True}}, + } + + +# ============================================================ +# Análise de dados offline +# ============================================================ + +def ts_name() -> str: + return datetime.now().strftime("%Y%m%d_%H%M%S_%f")[:-3] + + +def save_offline_sample( + base_dir: str, + preview_bgr: np.ndarray, + meta: dict, + packed_raw_by_camera: dict, +): + os.makedirs(base_dir, exist_ok=True) + name = ts_name() + + png_path = os.path.join(base_dir, f"{name}.png") + json_path = os.path.join(base_dir, f"{name}.json") + + payload_files = {} + payload_shapes = {} + payload_dtypes = {} + + for cam_id, arr in packed_raw_by_camera.items(): + path = os.path.join(base_dir, f"{name}_{cam_id}.bin") + arr.tofile(path) + + payload_files[cam_id] = os.path.basename(path) + payload_shapes[cam_id] = list(arr.shape) + payload_dtypes[cam_id] = str(arr.dtype) + + meta_save = dict(meta) + meta_save["saved_payload_type"] = "raw_native_multi" + meta_save["saved_payload_paths"] = payload_files + meta_save["saved_payload_shapes"] = payload_shapes + meta_save["saved_payload_dtypes"] = payload_dtypes + meta_save["saved_preview_path"] = os.path.basename(png_path) + + cv2.imwrite(png_path, preview_bgr) + + with open(json_path, "w", encoding="utf-8") as f: + json.dump(meta_save, f, ensure_ascii=False, indent=2) + + return png_path, json_path + + +def load_offline_sample_decoded(json_path: str, decoder: StreamDecoder): + if not os.path.isfile(json_path): + raise FileNotFoundError(f"Sample offline não encontrado: {json_path}") + + with open(json_path, "r", encoding="utf-8") as f: + meta = json.load(f) + + base_dir = os.path.dirname(json_path) + payload_paths = meta.get("saved_payload_paths") or {} + payload_shapes = meta.get("saved_payload_shapes") or {} + payload_dtypes = meta.get("saved_payload_dtypes") or {} + + if not payload_paths: + raise RuntimeError("Sample offline inválido: saved_payload_paths ausente") + + frame = {} + + for cam_id, rel_path in payload_paths.items(): + bin_path = os.path.join(base_dir, rel_path) + if not os.path.isfile(bin_path): + raise FileNotFoundError(f"Payload não encontrado para {cam_id}: {bin_path}") + + dtype_str = payload_dtypes.get(cam_id, "uint8") + shape = payload_shapes.get(cam_id) + + if shape is None: + raise RuntimeError(f"Shape ausente para {cam_id}") + + arr = np.fromfile(bin_path, dtype=np.dtype(dtype_str)).reshape(tuple(shape)) + frame[cam_id] = arr + + stream_meta = meta.get("stream_meta") or meta + + # Garante campos mínimos usados pelo decoder. + stream_meta.setdefault("camera_frames", meta.get("camera_frames", {})) + stream_meta.setdefault("frame_type", "RAW_BRUTO") + + decoded = decoder.decode_stream_cameras(frame, stream_meta) + + preview_path = meta.get("saved_preview_path") + preview_bgr = None + if preview_path: + preview_full = os.path.join(base_dir, preview_path) + if os.path.isfile(preview_full): + preview_bgr = cv2.imread(preview_full, cv2.IMREAD_COLOR) + + return decoded, stream_meta, frame, preview_bgr + + # ============================================================ # Persistência dos parâmetros/snapshots # ============================================================ @@ -277,6 +496,7 @@ def default_payload(args, effective_capture_mode: str): "cam2": {}, }, "snapshots": [], + "calibration_guidance_log": [], } @@ -290,6 +510,7 @@ def load_payload(path: str, args, effective_capture_mode: str): data.setdefault("schema", "manual_sensor_calibration_v1") data.setdefault("camera_settings", {"cam0": {}, "cam1": {}, "cam2": {}}) data.setdefault("snapshots", []) + data.setdefault("calibration_guidance_log", []) return data @@ -301,6 +522,260 @@ def save_payload(path: str, data: dict): json.dump(data, f, ensure_ascii=False, indent=2) +def build_camera_params_payload(args, effective_capture_mode, camera_controls, rois=None, snapshots=None, guidance_log=None): + return { + "schema": "multispec_camera_params_v1", + "saved_at": now_str(), + "pi_host": args.pi_host, + "pc_host": args.pc_host, + "stream_port": args.stream_port, + "frame_type": "RAW_BRUTO", + "capture_mode_requested": args.capture_mode, + "capture_mode_effective": effective_capture_mode, + "raw_policy": args.raw_policy, + "sensor_width": args.width, + "sensor_height": args.height, + "bayer_pattern": args.bayer, + "camera_settings": json.loads(json.dumps(camera_controls)), + "rois": rois or {}, + "snapshots": snapshots or [], + "notes": args.notes or "", + "calibration_guidance_log": guidance_log or [], + } + + +# ============================================================ +# Guia automática de ajustes dos controles +# ============================================================ + +def normalize_class_name(name: str) -> str: + s = (name or "").strip().lower() + if s.startswith("cana"): + return "cana" + if s.startswith("erva"): + return "erva" + if s.startswith("solo") or s.startswith("chao") or s.startswith("chão"): + return "solo" + if s.startswith("palha"): + return "palha" + return s + + +def collect_roi_metrics_by_class(img01: np.ndarray, rois_for_cam: list[dict]) -> dict: + grouped = {} + + for roi in rois_for_cam: + cls = normalize_class_name(roi.get("name", "")) + if not cls: + continue + + stats = compute_stats_for_roi(img01, roi) + if not stats.get("valid"): + continue + + grouped.setdefault(cls, []).append(stats) + + summary = {} + for cls, items in grouped.items(): + summary[cls] = { + "count": len(items), + "mean": float(np.mean([x["mean"] for x in items])), + "std": float(np.mean([x["std"] for x in items])), + "p05": float(np.mean([x["p05"] for x in items])), + "p95": float(np.mean([x["p95"] for x in items])), + "pct_saturated": float(np.mean([x["pct_saturated"] for x in items])), + "pct_dark": float(np.mean([x["pct_dark"] for x in items])), + "pixels": int(sum(x["pixels"] for x in items)), + } + + return summary + + +def mean_of_classes(summary: dict, classes: list[str], key: str = "mean"): + vals = [summary[c][key] for c in classes if c in summary] + if not vals: + return None + return float(np.mean(vals)) + + +def analyze_spectral_guidance(selected_cam: str, img01: np.ndarray, rois_for_cam: list[dict], ctrl: dict, exp_step: int, gain_step: float) -> dict: + summary = collect_roi_metrics_by_class(img01, rois_for_cam) + + veg_mean = mean_of_classes(summary, ["cana", "erva"], "mean") + veg_p95 = mean_of_classes(summary, ["cana", "erva"], "p95") + veg_sat = mean_of_classes(summary, ["cana", "erva"], "pct_saturated") + solo_mean = mean_of_classes(summary, ["solo", "palha"], "mean") + + before = json.loads(json.dumps(ctrl)) + new_ctrl = json.loads(json.dumps(ctrl)) + action = "keep" + status = "ok" + reason = "Parâmetros parecem aceitáveis." + + if veg_mean is None: + return { + "status": "need_rois", + "action": "none", + "reason": "Crie pelo menos uma ROI de cana ou erva para analisar canal espectral.", + "class_metrics": summary, + "before_settings": before, + "after_settings": new_ctrl, + } + + separation = None + if solo_mean is not None: + separation = float(veg_mean - solo_mean) + + exp = new_ctrl.get("exposure_time_us") + gain = new_ctrl.get("analogue_gain") + + if exp is None: + exp = 15000 + if gain is None: + gain = 1.0 + + new_ctrl["ae_enable"] = False + new_ctrl["awb_enable"] = False + + # 1) Proteção contra estouro + MIN_EXP_US = 100 + MIN_GAIN = 1.0 + + if veg_sat is not None and veg_sat > 1.0: + if exp > MIN_EXP_US: + new_ctrl["exposure_time_us"] = int(max(exp - exp_step, MIN_EXP_US)) + action = "decrease_exposure" + status = "adjust" + reason = f"Vegetação saturando ({veg_sat:.2f}%). Reduzir exposição." + + elif gain > MIN_GAIN: + new_ctrl["analogue_gain"] = float(max(gain / (1.0 + gain_step), MIN_GAIN)) + action = "decrease_gain" + status = "adjust" + reason = ( + f"Vegetação saturando ({veg_sat:.2f}%), mas exposição já está no mínimo. " + "Reduzir ganho." + ) + + else: + action = "keep" + status = "limit" + reason = ( + f"Vegetação saturando ({veg_sat:.2f}%), mas exposição e ganho já estão no mínimo. " + "Não há ajuste possível por software." + ) + + # 2) Vegetação pouco iluminada + elif veg_p95 is not None and veg_p95 < 0.75: + new_ctrl["exposure_time_us"] = int(min(exp + exp_step, 200000)) + action = "increase_exposure" + status = "adjust" + reason = f"p95 da vegetação baixo ({veg_p95:.3f}). Aumentar exposição." + + # 3) Vegetação muito perto do teto + elif veg_p95 is not None and veg_p95 > 0.96: + new_ctrl["exposure_time_us"] = int(max(exp - exp_step, 100)) + action = "decrease_exposure" + status = "adjust" + reason = f"p95 da vegetação alto ({veg_p95:.3f}). Reduzir exposição." + + # 4) Separação ruim + elif separation is not None and separation < 0.25: + if veg_p95 is not None and veg_p95 < 0.90: + new_ctrl["exposure_time_us"] = int(min(exp + exp_step, 200000)) + action = "increase_exposure" + status = "adjust" + reason = f"Separação baixa ({separation:.3f}) e há margem no p95. Aumentar exposição." + else: + new_ctrl["analogue_gain"] = float(min(gain * (1.0 + gain_step), 32.0)) + action = "increase_gain" + status = "adjust" + reason = f"Separação baixa ({separation:.3f}) sem muita margem de exposição. Aumentar ganho levemente." + + return { + "status": status, + "action": action, + "reason": reason, + "channel": selected_cam, + "class_metrics": summary, + "veg_mean": veg_mean, + "solo_mean": solo_mean, + "separation": separation, + "veg_p95": veg_p95, + "veg_sat": veg_sat, + "before_settings": before, + "after_settings": new_ctrl, + } + + +def analyze_rgb_guidance(selected_cam: str, img01: np.ndarray, rois_for_cam: list[dict], ctrl: dict, exp_step: int, gain_step: float) -> dict: + scene = compute_scene_health(img01) + summary = collect_roi_metrics_by_class(img01, rois_for_cam) + + before = json.loads(json.dumps(ctrl)) + new_ctrl = json.loads(json.dumps(ctrl)) + action = "keep" + status = "ok" + reason = "RGB parece aceitável." + + exp = new_ctrl.get("exposure_time_us") + gain = new_ctrl.get("analogue_gain") + + if exp is None: + exp = 15000 + if gain is None: + gain = 1.0 + + # Para RGB calibrado fixo: desligar AE/AWB quando for aplicar preset final. + new_ctrl["ae_enable"] = False + new_ctrl["awb_enable"] = False + + if scene["pct_saturated"] > 2.0 or scene["p95"] > 0.97: + new_ctrl["exposure_time_us"] = int(max(exp - exp_step, 100)) + action = "decrease_exposure" + status = "adjust" + reason = f"RGB muito próximo de saturar. sat={scene['pct_saturated']:.2f}%, p95={scene['p95']:.3f}." + + elif scene["pct_dark"] > 20.0 and scene["p95"] < 0.85: + new_ctrl["exposure_time_us"] = int(min(exp + exp_step, 200000)) + action = "increase_exposure" + status = "adjust" + reason = f"RGB escuro. dark={scene['pct_dark']:.2f}%, p95={scene['p95']:.3f}." + + elif scene["std"] < 0.08: + new_ctrl["analogue_gain"] = float(min(gain * (1.0 + gain_step), 32.0)) + action = "increase_gain" + status = "adjust" + reason = f"RGB com baixo contraste global. std={scene['std']:.3f}." + + return { + "status": status, + "action": action, + "reason": reason, + "channel": selected_cam, + "scene_health": scene, + "class_metrics": summary, + "before_settings": before, + "after_settings": new_ctrl, + } + + +def run_guidance_analysis(selected_cam: str, img01: np.ndarray, rois_for_cam: list[dict], ctrl: dict, exp_step: int, gain_step: float) -> dict: + if img01 is None: + return { + "status": "error", + "action": "none", + "reason": "Sem imagem ativa para análise.", + "before_settings": json.loads(json.dumps(ctrl)), + "after_settings": json.loads(json.dumps(ctrl)), + } + + if selected_cam == "cam2": + return analyze_rgb_guidance(selected_cam, img01, rois_for_cam, ctrl, exp_step, gain_step) + + return analyze_spectral_guidance(selected_cam, img01, rois_for_cam, ctrl, exp_step, gain_step) + + # ============================================================ # Main # ============================================================ @@ -326,9 +801,18 @@ def main(): parser.add_argument("--out_json", default="calibration/sensor_calibration.json") parser.add_argument("--load_json", default="") parser.add_argument("--notes", default="") + parser.add_argument("--mock", action="store_true") + parser.add_argument("--mock_cam0", default="", help="Imagem mock para cam0 / RE") + parser.add_argument("--mock_cam1", default="", help="Imagem mock para cam1 / NIR") + parser.add_argument("--mock_cam2", default="", help="Imagem mock para cam2 / RGB") + parser.add_argument("--offline_sample_json", default="", help="JSON de sample salvo para análise offline") + parser.add_argument("--offline_save_dir", default="calibration/offline_samples", help="Pasta para salvar frames brutos offline") args = parser.parse_args() - effective_capture_mode = resolve_effective_capture_mode(args.capture_mode) + offline_mode = bool(args.offline_sample_json) + live_mode = not args.mock and not offline_mode + + effective_capture_mode = args.capture_mode receiver = StreamReceiver(host="0.0.0.0", port=args.stream_port) svc = MultiSpectralService(host=args.pi_host, port=args.server_port, timeout=10) @@ -349,6 +833,13 @@ def main(): last_stream_frame_id = None decoded_last = {} + last_meta_stream = None + last_raw_frame = None + last_preview_bgr = None + + guidance_log = data_payload.get("calibration_guidance_log", []) + last_guidance = guidance_log[-1]["result"] if guidance_log else None + window_name = "Sensor Calibration Tool" cv2.namedWindow(window_name, cv2.WINDOW_NORMAL) @@ -390,15 +881,13 @@ def main(): "cam1": [], } - drawing_roi = False - roi_start = None - roi_current = None + current_polygon_points = [] def get_active_rect_for_mouse(): return panel_rects.get(selected_cam) def on_mouse(event, x, y, flags, param): - nonlocal drawing_roi, roi_start, roi_current, last_msg, last_msg_t + nonlocal current_polygon_points, last_msg, last_msg_t rect = get_active_rect_for_mouse() if rect is None: @@ -413,46 +902,21 @@ def main(): ly = int(y - y0) if event == cv2.EVENT_LBUTTONDOWN: - drawing_roi = True - roi_start = (lx, ly) - roi_current = (lx, ly) - - elif event == cv2.EVENT_MOUSEMOVE and drawing_roi: - roi_current = (lx, ly) - - elif event == cv2.EVENT_LBUTTONUP and drawing_roi: - drawing_roi = False - roi_current = (lx, ly) - - if roi_start is None: - return - - rx0, ry0 = roi_start - rx1, ry1 = roi_current - rx0, rx1 = sorted((rx0, rx1)) - ry0, ry1 = sorted((ry0, ry1)) - - if (rx1 - rx0) < 8 or (ry1 - ry0) < 8: - last_msg = "ROI muito pequena, ignorada" - last_msg_t = time.time() - roi_start = None - roi_current = None - return - - name = f"roi_{len(rois[selected_cam]) + 1}" - roi = { - "name": name, - "rect": (rx0, ry0, rx1, ry1), - "color": color_for_index(len(rois[selected_cam])), - } - rois[selected_cam].append(roi) - last_msg = f"ROI criada em {selected_cam}: {name}" + current_polygon_points.append((lx, ly)) + last_msg = f"{selected_cam}: ponto #{len(current_polygon_points)} adicionado" last_msg_t = time.time() - roi_start = None - roi_current = None cv2.setMouseCallback(window_name, on_mouse) + if args.mock: + decoded_last = build_mock_decoded(args) + + if offline_mode: + decoded_last, last_meta_stream, last_raw_frame, last_preview_bgr = load_offline_sample_decoded( + args.offline_sample_json, + decoder, + ) + def apply_controls_to_selected_cam(): nonlocal last_msg, last_msg_t ctrl = camera_controls[selected_cam] @@ -493,12 +957,20 @@ def main(): roi_entries = [] for roi in rois[selected_cam]: - stats = compute_stats_from_roi(active_img, roi["rect"]) - roi_entries.append({ + stats = compute_stats_for_roi(active_img, roi) + + entry = { "name": roi["name"], - "rect": list(map(int, roi["rect"])), + "type": roi.get("type", "rect"), "metrics": stats, - }) + } + + if roi.get("type") == "polygon": + entry["points"] = [[int(x), int(y)] for x, y in roi.get("points", [])] + else: + entry["rect"] = list(map(int, roi["rect"])) + + roi_entries.append(entry) snap = { "timestamp": now_str(), @@ -510,99 +982,110 @@ def main(): return snap try: - receiver.start() - time.sleep(0.5) + if live_mode: + receiver.start() + time.sleep(0.5) - print(f"[INFO] Verificando conexão com o módulo em {args.pi_host}:{args.server_port}...") - if not svc.check_connection(2): - raise RuntimeError("Módulo não encontrado ou não respondeu ao ping.") - print("[OK] Módulo conectado e respondendo.") + print(f"[INFO] Verificando conexão com o módulo em {args.pi_host}:{args.server_port}...") + if not svc.check_connection(2): + raise RuntimeError("Módulo não encontrado ou não respondeu ao ping.") + print("[OK] Módulo conectado e respondendo.") - svc.connect() + svc.connect() - print("SET CAM0 RES:", svc.set_camera_resolution(0, args.width, args.height)) - print("SET CAM1 RES:", svc.set_camera_resolution(1, args.width, args.height)) - print("SET CAM2 RES:", svc.set_camera_resolution(2, args.width, args.height)) - print("SET CAM0 BAYER:", svc.set_camera_bayer(0, args.bayer)) - print("SET CAM1 BAYER:", svc.set_camera_bayer(1, args.bayer)) - print("SET FPS:", svc.set_fps(args.fps)) - print("SET CAPTURE MODE:", svc.set_capture_mode(effective_capture_mode)) - print("SET FRAME TYPE:", svc.set_frame_type("RAW_BRUTO")) - print("SET OUTPUT DTYPE:", svc.set_output_dtype("float32")) + print("SET CAM0 RES:", svc.set_camera_resolution(0, args.width, args.height)) + print("SET CAM1 RES:", svc.set_camera_resolution(1, args.width, args.height)) + print("SET CAM2 RES:", svc.set_camera_resolution(2, args.width, args.height)) + print("SET CAM0 BAYER:", svc.set_camera_bayer(0, args.bayer)) + print("SET CAM1 BAYER:", svc.set_camera_bayer(1, args.bayer)) + print("SET FPS:", svc.set_fps(args.fps)) + print("SET CAPTURE MODE:", svc.set_capture_mode(effective_capture_mode)) + print("SET FRAME TYPE:", svc.set_frame_type("RAW_BRUTO")) + print("SET OUTPUT DTYPE:", svc.set_output_dtype("float32")) - begin_resp = svc.begin(frame_type="RAW_BRUTO", output_dtype="float32", capture_mode=effective_capture_mode) - print("BEGIN:", begin_resp) + begin_resp = svc.begin(frame_type="RAW_BRUTO", output_dtype="float32", capture_mode=effective_capture_mode) + print("BEGIN:", begin_resp) - status = svc.get_status() - print("STATUS:", json.dumps({ - "status": status.get("status"), - "detected_mode": status.get("detected_mode"), - "camera_count_active": status.get("camera_count_active"), - "active_camera_ids": status.get("active_camera_ids"), - }, ensure_ascii=False)) + status = svc.get_status() + print("STATUS:", json.dumps({ + "status": status.get("status"), + "detected_mode": status.get("detected_mode"), + "camera_count_active": status.get("camera_count_active"), + "active_camera_ids": status.get("active_camera_ids"), + }, ensure_ascii=False)) - validate_module_ready(status, "RAW_BRUTO", args.raw_policy, effective_capture_mode) - print("START STREAM:", svc.start_stream(args.pc_host, args.stream_port, fps=args.fps)) + validate_module_ready(status, "RAW_BRUTO", args.raw_policy, effective_capture_mode) + print("START STREAM:", svc.start_stream(args.pc_host, args.stream_port, fps=args.fps)) + else: + last_msg = "MODO OFFLINE ativo" if offline_mode else "MODO MOCK ativo" + last_msg_t = time.time() - try: - for cam_id in ("cam0", "cam1", "cam2"): - initial_ctrl = svc.get_camera_controls(cam_id) + if live_mode: + try: + for cam_id, _ in camera_controls.items(): + initial_ctrl = svc.get_camera_controls(cam_id) - camera_controls[cam_id]["ae_enable"] = bool( - initial_ctrl.get("ae_enable", camera_controls[cam_id]["ae_enable"]) - ) - camera_controls[cam_id]["awb_enable"] = bool( - initial_ctrl.get("awb_enable", camera_controls[cam_id]["awb_enable"]) - ) + camera_controls[cam_id]["ae_enable"] = bool( + initial_ctrl.get("ae_enable", camera_controls[cam_id]["ae_enable"]) + ) + camera_controls[cam_id]["awb_enable"] = bool( + initial_ctrl.get("awb_enable", camera_controls[cam_id]["awb_enable"]) + ) - exp_val = initial_ctrl.get("exposure_time_us", camera_controls[cam_id]["exposure_time_us"]) - if exp_val is not None: - exp_val = int(exp_val) - camera_controls[cam_id]["exposure_time_us"] = exp_val + exp_val = initial_ctrl.get("exposure_time_us", camera_controls[cam_id]["exposure_time_us"]) + if exp_val is not None: + exp_val = int(exp_val) + camera_controls[cam_id]["exposure_time_us"] = exp_val - gain_val = initial_ctrl.get("analogue_gain", camera_controls[cam_id]["analogue_gain"]) - if gain_val is not None: - gain_val = float(gain_val) - camera_controls[cam_id]["analogue_gain"] = gain_val + gain_val = initial_ctrl.get("analogue_gain", camera_controls[cam_id]["analogue_gain"]) + if gain_val is not None: + gain_val = float(gain_val) + camera_controls[cam_id]["analogue_gain"] = gain_val - camera_controls[cam_id]["colour_gains"] = initial_ctrl.get( - "colour_gains", - camera_controls[cam_id]["colour_gains"] - ) - except Exception as e: - print(f"[WARN] Falha ao ler controles iniciais: {e}") + camera_controls[cam_id]["colour_gains"] = initial_ctrl.get( + "colour_gains", + camera_controls[cam_id]["colour_gains"] + ) + except Exception as e: + print(f"[WARN] Falha ao ler controles iniciais: {e}") while True: t0 = time.time() - meta = receiver.last_meta - frame = receiver.last_frame + if live_mode: + meta = receiver.last_meta + frame = receiver.last_frame - if meta is not None and frame is not None and meta.get("frame_id") != last_frame_id: - last_frame_id = meta["frame_id"] + if meta is not None and frame is not None and meta.get("frame_id") != last_frame_id: + last_frame_id = meta["frame_id"] - if not isinstance(frame, dict): - raise RuntimeError("Este calibrador espera RAW_BRUTO multi-payload como dict de câmeras.") + if not isinstance(frame, dict): + raise RuntimeError("Este calibrador espera RAW_BRUTO multi-payload como dict de câmeras.") - decoded_last = decoder.decode_stream_cameras(frame, meta) + decoded_last = decoder.decode_stream_cameras(frame, meta) + last_meta_stream = dict(meta) + last_raw_frame = {cam_id: arr.copy() for cam_id, arr in frame.items()} - curr_frame_id = meta.get("frame_id") - if curr_frame_id is not None and last_stream_frame_id != curr_frame_id: - stream_frames_accum += 1 - last_stream_frame_id = curr_frame_id + curr_frame_id = meta.get("frame_id") + if curr_frame_id is not None and last_stream_frame_id != curr_frame_id: + stream_frames_accum += 1 + last_stream_frame_id = curr_frame_id - dt_stream = time.time() - t_stream_fps - if dt_stream >= 1.0: - fps_stream = stream_frames_accum / dt_stream - stream_frames_accum = 0 - t_stream_fps = time.time() + dt_stream = time.time() - t_stream_fps + if dt_stream >= 1.0: + fps_stream = stream_frames_accum / dt_stream + stream_frames_accum = 0 + t_stream_fps = time.time() - view_frames += 1 - dt_view = time.time() - t_view_fps - if dt_view >= 1.0: - fps_view = view_frames / dt_view - view_frames = 0 - t_view_fps = time.time() + view_frames += 1 + dt_view = time.time() - t_view_fps + if dt_view >= 1.0: + fps_view = view_frames / dt_view + view_frames = 0 + t_view_fps = time.time() + else: + fps_stream = 0.0 + fps_view = 0.0 if decoded_last: rgb01 = decoded_last.get("cam2", {}).get("image") @@ -623,11 +1106,9 @@ def main(): draw_rois(re_panel, rois["cam0"]) draw_rois(nir_panel, rois["cam1"]) - if drawing_roi and roi_start is not None and roi_current is not None: - active_panel = {"cam2": rgb_panel, "cam0": re_panel, "cam1": nir_panel}.get(selected_cam) - if active_panel is not None: - color = (0, 255, 255) - cv2.rectangle(active_panel, roi_start, roi_current, color, 1) + active_panel = {"cam2": rgb_panel, "cam0": re_panel, "cam1": nir_panel}.get(selected_cam) + if active_panel is not None: + draw_current_polygon(active_panel, current_polygon_points) overlay_hud(rgb_panel, ["RGB (cam2)", f"ativo={selected_cam == 'cam2'}"]) overlay_hud(re_panel, ["RE (cam0)", f"ativo={selected_cam == 'cam0'}"]) @@ -644,6 +1125,10 @@ def main(): rgb_panel = fit_panel(rgb_panel) re_panel = fit_panel(re_panel) nir_panel = fit_panel(nir_panel) + if rgb01 is not None: + last_preview_bgr = to_bgr_u8_from_rgb01(rgb01) + else: + last_preview_bgr = rgb_panel.copy() data_panel = np.zeros((ph, pw, 3), dtype=np.uint8) panel_rects["cam2"] = (0, 0, pw, ph) @@ -676,21 +1161,32 @@ def main(): else: lines.append("sem stats da cena") + if last_guidance is not None: + lines.extend([ + "-", + f"GUIDE: {last_guidance.get('status')} | {last_guidance.get('action')}", + f"{last_guidance.get('reason', '')[:46]}", + ]) + + sep = last_guidance.get("separation") + if sep is not None: + lines.append(f"sep_veg_solo={sep:.3f}") + lines.append("-") lines.append(f"ROIs: {len(rois[selected_cam])}") for idx, roi in enumerate(rois[selected_cam][:6]): if active_img is None: break - stats = compute_stats_from_roi(active_img, roi["rect"]) + stats = compute_stats_for_roi(active_img, roi) lines.append(f"{roi['name']}: mean={stats['mean']:.3f} std={stats['std']:.3f}") lines.append(f" p95={stats['p95']:.3f} sat={stats['pct_saturated']:.1f}% dark={stats['pct_dark']:.1f}%") lines.extend([ "-", "1=RGB | 2=RE | 3=NIR | E=AE | B=AWB", - "I/K exp +/- | O/L gain +/- | A aplica", - "mouse: arrasta ROI | U desfaz ROI | C limpa ROIs", - "SPACE salva JSON | S salva snapshot | Q sai", + "I/K exp +/- | O/L gain +/- | G guia | A aplica", + "mouse: clique pontos | ENTER fecha ROI | U desfaz ponto/ROI | X limpa poligono", + "F salva frame bruto | SPACE salva PARAMS | S snapshot | Q sai", ]) x0, y0, _, _ = panel_rects["data"] @@ -774,13 +1270,55 @@ def main(): ) last_msg = f"GAIN {selected_cam} -> {camera_controls[selected_cam]['analogue_gain']:.2f}" last_msg_t = time.time() + elif k in (ord("g"), ord("G")): + active_img = decoded_last.get(selected_cam, {}).get("image") + ctrl = camera_controls[selected_cam] + + result = run_guidance_analysis( + selected_cam=selected_cam, + img01=active_img, + rois_for_cam=rois[selected_cam], + ctrl=ctrl, + exp_step=args.exp_step, + gain_step=args.gain_step, + ) + + last_guidance = result + + guidance_entry = { + "timestamp": now_str(), + "camera": selected_cam, + "result": result, + } + + guidance_log.append(guidance_entry) + data_payload.setdefault("calibration_guidance_log", []).append(guidance_entry) + + after = result.get("after_settings") + if isinstance(after, dict): + camera_controls[selected_cam].update(after) + + last_msg = f"GUIDE {selected_cam}: {result.get('action')} | {result.get('status')}" + last_msg_t = time.time() elif k in (ord("a"), ord("A")): - apply_controls_to_selected_cam() + if live_mode: + apply_controls_to_selected_cam() + else: + last_msg = "Controles só aplicam no modo ao vivo" + last_msg_t = time.time() elif k in (ord("u"), ord("U")): - if rois[selected_cam]: + if current_polygon_points: + current_polygon_points.pop() + last_msg = f"Ponto removido | restantes={len(current_polygon_points)}" + last_msg_t = time.time() + elif rois[selected_cam]: removed = rois[selected_cam].pop() last_msg = f"ROI removida: {removed['name']}" last_msg_t = time.time() + elif k in (ord("x"), ord("X")): + current_polygon_points = [] + last_msg = "Polígono atual limpo" + last_msg_t = time.time() elif k in (ord("c"), ord("C")): rois[selected_cam] = [] last_msg = f"ROIs limpas em {selected_cam}" @@ -793,33 +1331,100 @@ def main(): else: last_msg = "Sem frame ativo para snapshot" last_msg_t = time.time() - elif k == 32: - data_payload["camera_settings"] = json.loads(json.dumps(camera_controls)) - save_payload(args.out_json, data_payload) - last_msg = f"JSON salvo em: {args.out_json}" + elif k in (ord("f"), ord("F")): + if not live_mode: + last_msg = "Salvar frame bruto só faz sentido no modo ao vivo" + last_msg_t = time.time() + elif last_raw_frame is None or last_meta_stream is None: + last_msg = "Sem frame bruto atual para salvar" + last_msg_t = time.time() + else: + preview_to_save = last_preview_bgr + if preview_to_save is None: + preview_to_save = np.zeros((args.height, args.width, 3), dtype=np.uint8) + + meta_save = { + "ts": datetime.now().isoformat(timespec="milliseconds"), + "schema": "multispec_offline_sample_v1", + "frame_type": "RAW_BRUTO", + "sensor_width": args.width, + "sensor_height": args.height, + "bayer_pattern": args.bayer, + "capture_mode_requested": args.capture_mode, + "capture_mode_effective": effective_capture_mode, + "raw_policy": args.raw_policy, + "stream_meta": last_meta_stream, + "camera_settings": json.loads(json.dumps(camera_controls)), + "note": "offline_sample_from_sensor_calibration_tool", + } + + png_path, json_path = save_offline_sample( + base_dir=args.offline_save_dir, + preview_bgr=preview_to_save, + meta=meta_save, + packed_raw_by_camera=last_raw_frame, + ) + + last_msg = f"FRAME salvo offline: {os.path.basename(json_path)}" + last_msg_t = time.time() + elif k == 32: # SPACE + payload_to_save = build_camera_params_payload( + args=args, + effective_capture_mode=effective_capture_mode, + camera_controls=camera_controls, + rois=rois, + snapshots=data_payload.get("snapshots", []), + guidance_log=guidance_log, + ) + + save_payload(args.out_json, payload_to_save) + data_payload = payload_to_save + last_msg = f"PARAMS salvos em: {args.out_json}" last_msg_t = time.time() + elif k == 13: # ENTER + if len(current_polygon_points) < 3: + last_msg = "ROI poligonal precisa de pelo menos 3 pontos" + last_msg_t = time.time() + else: + name = input(f"Nome da ROI para {selected_cam}: ").strip() + if not name: + name = f"roi_{len(rois[selected_cam]) + 1}" + + roi = { + "name": name, + "type": "polygon", + "points": list(current_polygon_points), + "color": color_for_index(len(rois[selected_cam])), + } + + rois[selected_cam].append(roi) + current_polygon_points = [] + + last_msg = f"ROI criada em {selected_cam}: {name}" + last_msg_t = time.time() dt_loop = time.time() - t0 if dt_loop < 0.001: time.sleep(0.001) finally: - try: - print("STOP STREAM:", svc.stop_stream()) - except Exception: - pass - try: - print("STOP:", svc.stop()) - except Exception: - pass - try: - svc.disconnect() - except Exception: - pass - try: - receiver.stop() - except Exception: - pass + if live_mode: + try: + print("STOP STREAM:", svc.stop_stream()) + except Exception: + pass + try: + print("STOP:", svc.stop()) + except Exception: + pass + try: + svc.disconnect() + except Exception: + pass + try: + receiver.stop() + except Exception: + pass cv2.destroyAllWindows() print("Fim da calibração dos sensores.")