2026-04-22 20:08:49 +00:00
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 "
2026-04-24 01:42:43 +00:00
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 " )
2026-04-22 20:08:49 +00:00
# =========================
# 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 ) } " )
# =========================
# 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. " )
2026-04-24 01:42:43 +00:00
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. " )
2026-04-22 20:08:49 +00:00
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. " )
2026-04-24 01:42:43 +00:00
parser . add_argument ( " --camera_params_json " , default = CAMERA_PARAMS , help = " JSON salvo pelo calibrador de sensores com parâmetros fixos por câmera. " )
2026-04-22 20:08:49 +00:00
args = parser . parse_args ( )
2026-04-24 01:42:43 +00:00
effective_capture_mode = args . capture_mode
2026-04-22 20:08:49 +00:00
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 } ... " )
2026-04-23 12:16:58 +00:00
if not svc . check_connection ( 2 ) :
2026-04-22 20:08:49 +00:00
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 )
2026-04-24 01:42:43 +00:00
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 " )
2026-04-22 20:08:49 +00:00
2026-04-24 01:42:43 +00:00
print ( " START STREAM: " , svc . start_stream ( args . pc_host , args . stream_port , fps = args . fps ) )
2026-04-22 20:08:49 +00:00
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 " ,
2026-04-24 01:42:43 +00:00
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 "
2026-04-22 20:08:49 +00:00
]
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 ,
2026-04-24 01:42:43 +00:00
" applied_camera_controls " : applied_camera_controls ,
" camera_params_json " : args . camera_params_json ,
2026-04-22 20:08:49 +00:00
" 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 ( " 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 ,
2026-04-24 01:42:43 +00:00
" applied_camera_controls " : applied_camera_controls ,
" camera_params_json " : args . camera_params_json ,
2026-04-22 20:08:49 +00:00
" 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 ( )