import os import time import json import argparse from datetime import datetime import cv2 import numpy as np from multispectral_service import MultiSpectralService from stream_receiver import StreamReceiver from pi.raw_processor_core import RawProcessorCore from pi.raw_processor_preview import RawProcessorPreview STREAM_PORT = 6001 PI_HOST = "192.168.105.6" PC_HOST = "192.168.105.5" # ========================= # Helpers gerais # ========================= def ts_name() -> str: return datetime.now().strftime("%Y%m%d_%H%M%S_%f")[:-3] def overlay_hud( img_bgr: np.ndarray, lines: list[str], base_h: int = 720, base_font_scale: float = 0.75, base_line_step: int = 28, ): h, w = img_bgr.shape[:2] scale = h / float(base_h) scale = max(scale, 0.4) font_scale = base_font_scale * scale line_step = int(base_line_step * scale) thick_outline = max(1, int(3 * scale)) thick_text = max(1, int(2 * scale)) y = int(24 * scale) x = int(12 * scale) for s in lines: cv2.putText(img_bgr, s, (x, y), cv2.FONT_HERSHEY_SIMPLEX, font_scale, (0, 0, 0), thick_outline, cv2.LINE_AA) cv2.putText(img_bgr, s, (x, y), cv2.FONT_HERSHEY_SIMPLEX, font_scale, (255, 255, 255), thick_text, cv2.LINE_AA) y += line_step def save_sample( base_dir: str, frame_type: str, preview_bgr: np.ndarray, meta: dict, raw_payload: np.ndarray | None = None, packed_raw: np.ndarray | None = None, packed_raw_by_camera: dict | None = None, ): 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") if frame_type in ("RGB", "MULTISPEC"): if raw_payload is None: raise ValueError(f"raw_payload não pode ser None quando frame_type='{frame_type}'") payload_path = os.path.join(base_dir, f"{name}.raw") raw_payload.astype(np.float32).tofile(payload_path) meta["saved_payload_type"] = frame_type.lower() meta["saved_payload_path"] = os.path.basename(payload_path) meta["saved_payload_dtype"] = "float32" meta["saved_payload_shape"] = list(raw_payload.shape) elif frame_type == "RAW_BRUTO": if packed_raw_by_camera is not None: 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["saved_payload_type"] = "raw_native_multi" meta["saved_payload_paths"] = payload_files meta["saved_payload_shapes"] = payload_shapes meta["saved_payload_dtypes"] = payload_dtypes else: if packed_raw is None: raise ValueError("packed_raw não pode ser None quando frame_type='RAW_BRUTO'") payload_path = os.path.join(base_dir, f"{name}.bin") packed_raw.tofile(payload_path) meta["saved_payload_type"] = "raw_native_single" meta["saved_payload_path"] = os.path.basename(payload_path) meta["saved_payload_dtype"] = str(packed_raw.dtype) meta["saved_payload_shape"] = list(packed_raw.shape) else: raise ValueError(f"frame_type não suportado para save: {frame_type}") cv2.imwrite(png_path, preview_bgr) with open(json_path, "w", encoding="utf-8") as f: json.dump(meta, f, ensure_ascii=False, indent=2) return png_path, json_path def get_camera_map_from_status(status: dict) -> dict: result = {} for cam in status.get("cameras", []): result[cam.get("id")] = cam return result def validate_module_ready(status: dict, frame_type: str, raw_policy: str, capture_mode: str): if not status.get("ok", True): raise RuntimeError(f"Status inválido retornado pelo módulo: {status}") active_ids = list(status.get("active_camera_ids", [])) active_count = int(status.get("camera_count_active", 0)) if frame_type == "RGB": if "cam2" not in active_ids: raise RuntimeError( "Modo RGB requer cam2 ativa (USB RGB), mas o módulo não reportou cam2 como ativa." ) return if frame_type == "MULTISPEC": if capture_mode == "TRIPLE": missing = [cid for cid in ("cam0", "cam1", "cam2") if cid not in active_ids] if missing: raise RuntimeError( f"Modo MULTISPEC/TRIPLE requer cam0, cam1 e cam2 ativas. " f"Faltando: {missing}. Ativas atuais: {active_ids}" ) return if capture_mode == "DOUBLE": has_rgb = "cam2" in active_ids has_spec = ("cam0" in active_ids) or ("cam1" in active_ids) if not has_rgb or not has_spec: raise RuntimeError( f"Modo MULTISPEC/DOUBLE requer cam2 + (cam0 ou cam1). " f"Ativas atuais: {active_ids}" ) return # AUTO ou outros casos has_rgb = "cam2" in active_ids has_spec = ("cam0" in active_ids) or ("cam1" in active_ids) if not (has_rgb and has_spec): raise RuntimeError( f"Modo MULTISPEC requer pelo menos RGB + 1 canal espectral. " f"Ativas atuais: {active_ids}" ) return if frame_type == "RAW_BRUTO": if raw_policy == "require_triple": missing = [cid for cid in ("cam0", "cam1", "cam2") if cid not in active_ids] if missing: raise RuntimeError( f"RAW_BRUTO com política require_triple exige três câmeras ativas. " f"Faltando: {missing}. Ativas atuais: {active_ids}" ) else: if active_count < 1: raise RuntimeError("RAW_BRUTO requer ao menos uma câmera ativa, mas nenhuma foi detectada.") return raise RuntimeError(f"frame_type desconhecido para validação: {frame_type}") def build_preview_from_raw_payload( frame, meta: dict, processor_core: RawProcessorCore, processor_preview: RawProcessorPreview, ): """ Gera preview priorizando a câmera RGB (cam2). Se cam2 não estiver presente, cai para fallback usando a primeira câmera mono disponível. Retorna: preview_bgr payload_float_preview preview_source_id """ payload_sources = meta.get("payload_sources", []) or [] # Caso multi-payload: tenta usar cam2 primeiro if isinstance(frame, dict): if "cam2" in frame: rgb_frame = frame["cam2"] if rgb_frame.ndim != 3 or rgb_frame.shape[2] != 3: raise RuntimeError(f"cam2 recebida mas inválida para preview RGB: shape={rgb_frame.shape}") preview_bgr = rgb_frame.copy() payload_float = rgb_frame[:, :, ::-1].astype(np.float32) / 255.0 payload_float = np.transpose(payload_float, (2, 0, 1)) return preview_bgr, payload_float, "cam2" # fallback: usa a primeira câmera mono disponível fallback_id = None for cid in ("cam0", "cam1"): if cid in frame: fallback_id = cid break if fallback_id is None: raise RuntimeError("Nenhuma câmera disponível no payload para gerar preview") packed = frame[fallback_id] if packed.ndim == 3 and packed.shape[2] == 1: packed = packed[:, :, 0] cam_frames = meta.get("camera_frames", {}) or {} cam_meta = cam_frames.get(fallback_id, {}) bit_depth = int(cam_meta.get("bit_depth", 10)) raw16 = processor_core.unpack_raw10_packed(packed) preview_bgr = processor_preview.raw16_to_preview_bgr(raw16, bit_depth=bit_depth) payload_float = processor_core.build_training_rgb( raw16, output_dtype="float32", bit_depth=bit_depth, ) return preview_bgr, payload_float, fallback_id # Caso single-payload if isinstance(frame, np.ndarray): # Se vier HWC/3ch, tratamos como RGB USB if frame.ndim == 3 and frame.shape[2] == 3: preview_bgr = frame.copy() payload_float = frame[:, :, ::-1].astype(np.float32) / 255.0 payload_float = np.transpose(payload_float, (2, 0, 1)) return preview_bgr, payload_float, "cam2" # Se vier mono packed, fallback antigo packed = frame if packed.ndim == 3 and packed.shape[2] == 1: packed = packed[:, :, 0] source_camera = meta.get("source_camera") or {} bit_depth = int(source_camera.get("bit_depth", meta.get("source_bit_depth", 10))) raw16 = processor_core.unpack_raw10_packed(packed) preview_bgr = processor_preview.raw16_to_preview_bgr(raw16, bit_depth=bit_depth) payload_float = processor_core.build_training_rgb( raw16, output_dtype="float32", bit_depth=bit_depth, ) return preview_bgr, payload_float, source_camera.get("id", "unknown") 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 # ========================= def main(): parser = argparse.ArgumentParser( description="Captura de dataset usando módulo multispectral Pi + StreamReceiver.", formatter_class=argparse.ArgumentDefaultsHelpFormatter, ) parser.add_argument("--cana", required=True, choices=["baixa", "media", "alta"], help="Estado da cana no momento da coleta.") parser.add_argument("--horario", required=True, choices=["cedo", "meio_dia", "entardecer", "nublado"], help="Janela de iluminação / horário da coleta.") parser.add_argument("--out_root", default="dataset", help="Pasta raiz do dataset.") parser.add_argument("--pi_host", default=PI_HOST, help="IP do servidor no Raspberry Pi.") parser.add_argument("--pc_host", default=PC_HOST, help="IP local do notebook/PC que receberá o stream.") 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("--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.") parser.add_argument("--output_dtype", default="float32", choices=["uint8", "uint16", "float32"], help="Dtype do payload processado no Pi.") 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.") 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, ) raw_w = args.width raw_h = args.height session_dir = os.path.join( args.out_root, "brutas", f"cana_{args.cana}", args.horario, datetime.now().strftime("%Y%m%d"), ) os.makedirs(session_dir, exist_ok=True) print("============================================") print("Coleta de dataset - Módulo Multiespectral") print(f"Cana : {args.cana}") print(f"Horário : {args.horario}") print(f"Saída : {session_dir}") print(f"Sensor : {raw_w}x{raw_h} | Bayer={args.bayer}") print(f"FrameType : {args.frame_type}") print(f"CaptureMode : {args.capture_mode} -> efetivo={effective_capture_mode}") print(f"RAW policy : {args.raw_policy}") print("============================================") auto_save = False last_auto_t = 0.0 preview_upscale = args.preview_upscale t_view_fps = time.time() view_frames = 0 fps_view = 0.0 t_stream_fps = time.time() last_stream_frame_id = None stream_frames_accum = 0 fps_stream = 0.0 last_msg = "" last_msg_t = 0.0 receiver = StreamReceiver(host="0.0.0.0", port=args.stream_port) svc = MultiSpectralService(host=args.pi_host, port=args.server_port, timeout=10) 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.") window_name = "Dataset Capture (C/SPACE=save | A=auto-save | M=preview scale | Q=quit)" cv2.namedWindow(window_name, cv2.WINDOW_NORMAL) processor_core_cam0 = RawProcessorCore( sensor_width=raw_w, sensor_height=raw_h, bayer_pattern=args.bayer, ) processor_preview_cam0 = RawProcessorPreview( sensor_width=raw_w, sensor_height=raw_h, bayer_pattern=args.bayer, ) last_frame_id = -1 last_payload_float = None last_packed_raw = None last_packed_raw_by_camera = None last_preview_bgr = None last_meta_stream = None try: receiver.start() time.sleep(0.5) svc.connect() if args.frame_type in ("RAW_BRUTO", "MULTISPEC"): modes_resp = svc.get_sensor_modes() if not modes_resp.get("ok"): print(f"[WARN] Falha ao obter sensor_modes: {modes_resp}") else: for mode in modes_resp.get("sensor_modes", []): print( f"[cam={mode.get('camera_id')} mode={mode.get('mode_index')}] " f"size={mode.get('size')} " f"format={mode.get('format')} " f"bit_depth={mode.get('bit_depth')} " f"fps={mode.get('fps')}" ) else: print("[INFO] get_sensor_modes pulado para frame_type=RGB") print("SET CAM0 RES:", svc.set_camera_resolution(0, raw_w, raw_h)) print("SET CAM1 RES:", svc.set_camera_resolution(1, raw_w, raw_h)) print("SET CAM2 RES:", svc.set_camera_resolution(2, raw_w, raw_h)) 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(args.frame_type)) print("SET OUTPUT DTYPE:", svc.set_output_dtype(args.output_dtype)) begin_resp = svc.begin( frame_type=args.frame_type, output_dtype=args.output_dtype, 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)) validate_module_ready(status, args.frame_type, args.raw_policy, effective_capture_mode) print("START STREAM:", svc.start_stream(args.pc_host, args.stream_port, fps=args.fps)) camera_ctrl = svc.get_camera_controls() ae_enabled = bool(camera_ctrl.get("ae_enable", True)) awb_enabled = bool(camera_ctrl.get("awb_enable", True)) manual_exposure_us = camera_ctrl.get("exposure_time_us", None) manual_gain = camera_ctrl.get("analogue_gain", None) manual_colour_gains = camera_ctrl.get("colour_gains", None) while True: t0 = time.time() 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"] try: frame_type = meta.get("frame_type", "RAW_BRUTO") dtype_str = meta.get("dtype") or meta.get("output_dtype", "uint8") preview_source_id = "cam2" if frame_type == "RAW_BRUTO": if isinstance(frame, dict): packed_by_camera = frame preview_bgr, raw3_preview, preview_source_id = build_preview_from_raw_payload( frame=frame, meta=meta, processor_core=processor_core_cam0, processor_preview=processor_preview_cam0, ) last_packed_raw = None last_packed_raw_by_camera = {cam_id: arr.copy() for cam_id, arr in packed_by_camera.items()} last_payload_float = raw3_preview.copy() else: preview_bgr, raw3_preview, preview_source_id = build_preview_from_raw_payload( frame=frame, meta=meta, processor_core=processor_core_cam0, processor_preview=processor_preview_cam0, ) last_packed_raw = frame.copy() last_packed_raw_by_camera = None last_payload_float = raw3_preview.copy() elif frame_type == "RGB": rgb_chw = frame if not isinstance(rgb_chw, np.ndarray) or rgb_chw.ndim != 3: raise RuntimeError(f"Frame RGB inválido: type={type(rgb_chw)}") if dtype_str == "uint8": payload_float = rgb_chw.astype(np.float32) / 255.0 elif dtype_str == "float32": payload_float = rgb_chw.astype(np.float32) elif dtype_str == "uint16": payload_float = rgb_chw.astype(np.float32) / 65535.0 else: raise RuntimeError(f"dtype RGB não suportado: {dtype_str}") preview_rgb = np.transpose(payload_float, (1, 2, 0)) preview_bgr = cv2.cvtColor( np.clip(preview_rgb * 255.0, 0, 255).astype(np.uint8), cv2.COLOR_RGB2BGR ) last_payload_float = payload_float.copy() last_packed_raw = None last_packed_raw_by_camera = None elif frame_type == "MULTISPEC": multispec_chw = frame if not isinstance(multispec_chw, np.ndarray) or multispec_chw.ndim != 3 or multispec_chw.shape[0] not in (4, 5): raise RuntimeError(f"Frame MULTISPEC inválido: shape={getattr(multispec_chw, 'shape', None)}") if dtype_str == "uint8": payload_float = multispec_chw.astype(np.float32) / 255.0 elif dtype_str == "float32": payload_float = multispec_chw.astype(np.float32) elif dtype_str == "uint16": payload_float = multispec_chw.astype(np.float32) / 65535.0 else: raise RuntimeError(f"dtype MULTISPEC não suportado: {dtype_str}") preview_rgb = np.transpose(payload_float[:3], (1, 2, 0)) preview_bgr = cv2.cvtColor( np.clip(preview_rgb * 255.0, 0, 255).astype(np.uint8), cv2.COLOR_RGB2BGR ) last_payload_float = payload_float.copy() last_packed_raw = None last_packed_raw_by_camera = None else: raise RuntimeError(f"frame_type não suportado neste script: {frame_type}") if preview_upscale and preview_upscale > 1: preview_show = cv2.resize( preview_bgr, (preview_bgr.shape[1] * preview_upscale, preview_bgr.shape[0] * preview_upscale), interpolation=cv2.INTER_NEAREST, ) else: preview_show = preview_bgr.copy() curr_frame_id = meta.get("frame_id") if curr_frame_id is not None: if 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() 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() active_sources = meta.get("payload_sources") 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"AE={'ON' if ae_enabled else 'OFF'} | AWB={'ON' if awb_enabled else 'OFF'} | EXP={manual_exposure_us} | GAIN={manual_gain}", "Keys: C/SPACE=save | A=auto-save | E=AE | W=AWB | I/K=exp | O/L=gain | R=reset | M=preview | Q/Esc=quit", ] overlay_hud(preview_show, lines, base_h=raw_h) if last_msg and (time.time() - last_msg_t) < 2.0: cv2.putText(preview_show, last_msg, (12, preview_show.shape[0] - 18), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 255, 0), 2, cv2.LINE_AA) cv2.imshow(window_name, preview_show) last_preview_bgr = preview_bgr.copy() last_meta_stream = dict(meta) except Exception as e: err = np.zeros((500, 1200, 3), dtype=np.uint8) cv2.putText(err, f"Erro ao processar frame: {e}", (20, 60), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 0, 255), 2, cv2.LINE_AA) cv2.imshow(window_name, err) print(f"[ERRO FRAME] {e}") now = time.time() can_save = ( last_meta_stream is not None and last_preview_bgr is not None and ( (last_meta_stream.get("frame_type") in ("RGB", "MULTISPEC") and last_payload_float is not None) or (last_meta_stream.get("frame_type") == "RAW_BRUTO" and (last_packed_raw is not None or last_packed_raw_by_camera is not None)) ) ) if auto_save and can_save and (now - last_auto_t) >= args.interval: frame_type_save = last_meta_stream.get("frame_type") meta_save = { "ts": datetime.now().isoformat(timespec="milliseconds"), "cana": args.cana, "horario": args.horario, "sensor_width": raw_w, "sensor_height": raw_h, "bayer_pattern": args.bayer, "fps_target": args.fps, "frame_type": frame_type_save, "capture_mode_requested": args.capture_mode, "capture_mode_effective": effective_capture_mode, "raw_policy": args.raw_policy, "stream_meta": last_meta_stream, "note": "autosave", "raw_preview_reference_camera": preview_source_id, } save_sample( session_dir, frame_type=frame_type_save, preview_bgr=last_preview_bgr, meta=meta_save, raw_payload=last_payload_float, packed_raw=last_packed_raw, packed_raw_by_camera=last_packed_raw_by_camera, ) last_msg = "SALVO (auto)" last_msg_t = now last_auto_t = now k = cv2.waitKey(1) & 0xFF if k in (ord("q"), ord("Q"), 27): break elif k in (ord("a"), ord("A")): auto_save = not auto_save last_msg = f"AutoSave -> {'ON' if auto_save else 'OFF'}" last_msg_t = time.time() elif k in (ord("m"), ord("M")): preview_upscale = 0 if preview_upscale else args.preview_upscale last_msg = f"Preview UPSCALE -> {preview_upscale}" last_msg_t = time.time() elif k in (ord("e"), ord("E")): ae_enabled = not ae_enabled resp = svc.set_ae_enable(ae_enabled) ae_enabled = bool(resp.get("ae_enable", ae_enabled)) last_msg = f"AE -> {'ON' if ae_enabled else 'OFF'}" last_msg_t = time.time() elif k in (ord("w"), ord("W")): awb_enabled = not awb_enabled resp = svc.set_awb_enable(awb_enabled) awb_enabled = bool(resp.get("awb_enable", awb_enabled)) last_msg = f"AWB -> {'ON' if awb_enabled else 'OFF'}" last_msg_t = time.time() elif k in (ord("i"), ord("I")): if manual_exposure_us is None: manual_exposure_us = 15000 else: manual_exposure_us = min(int(manual_exposure_us * 1.15), 200000) if ae_enabled: ae_enabled = False svc.set_ae_enable(False) resp = svc.set_exposure_time(int(manual_exposure_us)) manual_exposure_us = resp.get("exposure_time_us", manual_exposure_us) last_msg = f"ExposureTime -> {manual_exposure_us} us" last_msg_t = time.time() elif k in (ord("k"), ord("K")): if manual_exposure_us is None: manual_exposure_us = 15000 else: manual_exposure_us = max(int(manual_exposure_us / 1.15), 100) if ae_enabled: ae_enabled = False svc.set_ae_enable(False) resp = svc.set_exposure_time(int(manual_exposure_us)) manual_exposure_us = resp.get("exposure_time_us", manual_exposure_us) last_msg = f"ExposureTime -> {manual_exposure_us} us" last_msg_t = time.time() elif k in (ord("o"), ord("O")): if manual_gain is None: manual_gain = 1.0 else: manual_gain = min(float(manual_gain) * 1.10, 32.0) if ae_enabled: ae_enabled = False svc.set_ae_enable(False) resp = svc.set_analogue_gain(float(manual_gain)) manual_gain = resp.get("analogue_gain", manual_gain) last_msg = f"AnalogueGain -> {manual_gain:.2f}" last_msg_t = time.time() elif k in (ord("l"), ord("L")): if manual_gain is None: manual_gain = 1.0 else: manual_gain = max(float(manual_gain) / 1.10, 1.0) if ae_enabled: ae_enabled = False svc.set_ae_enable(False) resp = svc.set_analogue_gain(float(manual_gain)) manual_gain = resp.get("analogue_gain", manual_gain) last_msg = f"AnalogueGain -> {manual_gain:.2f}" last_msg_t = time.time() elif k in (ord("r"), ord("R")): svc.clear_exposure_time() svc.clear_analogue_gain() svc.clear_colour_gains() manual_exposure_us = None manual_gain = None manual_colour_gains = None last_msg = "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") meta_save = { "ts": datetime.now().isoformat(timespec="milliseconds"), "cana": args.cana, "horario": args.horario, "sensor_width": raw_w, "sensor_height": raw_h, "bayer_pattern": args.bayer, "fps_target": args.fps, "frame_type": frame_type_save, "capture_mode_requested": args.capture_mode, "capture_mode_effective": effective_capture_mode, "raw_policy": args.raw_policy, "stream_meta": last_meta_stream, "camera_controls": { "ae_enable": ae_enabled, "awb_enable": awb_enabled, "exposure_time_us": manual_exposure_us, "analogue_gain": manual_gain, "colour_gains": manual_colour_gains, }, "note": "manual", "raw_preview_reference_camera": preview_source_id, } save_sample( session_dir, frame_type=frame_type_save, preview_bgr=last_preview_bgr, meta=meta_save, raw_payload=last_payload_float, packed_raw=last_packed_raw, packed_raw_by_camera=last_packed_raw_by_camera, ) last_msg = "SALVO (manual)" 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 svc.disconnect() receiver.stop() cv2.destroyAllWindows() print("Fim da captura.") if __name__ == "__main__": main()