agrobot_base/Python/OAK/datasets/multiespec_module/multispectral_client.py

342 lines
11 KiB
Python

import time
import json
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
from radiometric_controller import RadiometricController
class MultiSpectralClient:
def __init__(
self,
pi_host="192.168.105.6",
pc_host="192.168.105.5",
server_port=5000,
stream_port=6001,
timeout=10,
width=640,
height=480,
bayer="GBRG",
fps=15,
frame_type="RAW_BRUTO",
output_dtype="uint8",
capture_mode="AUTO",
raw_policy="allow_single",
module_calibration_json=None,
radiometric_enabled=False,
):
self.pi_host = pi_host
self.pc_host = pc_host
self.server_port = server_port
self.stream_port = stream_port
self.width = width
self.height = height
self.bayer = bayer
self.fps = fps
self.frame_type = frame_type
self.output_dtype = output_dtype
self.capture_mode = capture_mode
self.raw_policy = raw_policy
self.module_calibration_json = module_calibration_json
self.svc = MultiSpectralService(
host=pi_host,
port=server_port,
timeout=timeout,
)
self.receiver = StreamReceiver(
host="0.0.0.0",
port=stream_port,
)
self.core = RawProcessorCore(
sensor_width=width,
sensor_height=height,
bayer_pattern=bayer,
calibration_json_path=module_calibration_json,
)
self.preview = RawProcessorPreview(
sensor_width=width,
sensor_height=height,
bayer_pattern=bayer,
)
self.last_frame_id = None
self.status = None
self.begin_resp = None
self.applied_params = None
self.radiometric_enabled = bool(radiometric_enabled)
self.radiometric_controller = None
def __enter__(self):
self.start()
return self
def __exit__(self, exc_type, exc, tb):
self.stop()
def start(self, print_debug=True):
if print_debug:
print(f"[INFO] Verificando módulo em {self.pi_host}:{self.server_port}...")
if not self.svc.check_connection(2):
raise RuntimeError("Módulo não encontrado ou não respondeu ao ping.")
self.receiver.start()
time.sleep(0.5)
self.svc.connect()
if print_debug:
print("[OK] Módulo conectado.")
self._configure_module(print_debug=print_debug)
self._begin_module(print_debug=print_debug)
self._apply_module_params(print_debug=print_debug)
self._start_stream(print_debug=print_debug)
if self.radiometric_enabled:
self.enable_radiometric_controller()
return self
def enable_radiometric_controller(self):
self.radiometric_controller = RadiometricController(
client=self,
enabled=True,
config_json_path=self.module_calibration_json,
)
self.radiometric_controller.sync_from_camera_controls(self.applied_params["camera_settings"])
return self.radiometric_controller
def update_radiometry(self, decoded, meta=None):
if self.radiometric_controller is None:
return None
return self.radiometric_controller.update(decoded, meta)
def _configure_module(self, print_debug=True):
r0 = self.svc.set_resolution(self.width, self.height)
r1 = self.svc.set_bayer(self.bayer)
r2 = self.svc.set_fps(self.fps)
r3 = self.svc.set_capture_mode(self.capture_mode)
r4 = self.svc.set_frame_type(self.frame_type)
r5 = self.svc.set_output_dtype(self.output_dtype)
if print_debug:
print("SET RES:", r0)
print("SET BAYER:", r1)
print("SET FPS:", r2)
print("SET CAPTURE MODE:", r3)
print("SET FRAME TYPE:", r4)
print("SET OUTPUT DTYPE:", r5)
def _begin_module(self, print_debug=True):
self.begin_resp = self.svc.begin(
frame_type=self.frame_type,
output_dtype=self.output_dtype,
capture_mode=self.capture_mode,
)
self.status = self.svc.get_status()
self.svc.validate_module_ready(
self.status,
self.frame_type,
self.raw_policy,
self.capture_mode,
)
if print_debug:
print("BEGIN:", self.begin_resp)
print("STATUS:", json.dumps({
"status": self.status.get("status"),
"detected_mode": self.status.get("detected_mode"),
"camera_count_active": self.status.get("camera_count_active"),
"active_camera_ids": self.status.get("active_camera_ids"),
}, ensure_ascii=False))
def _apply_module_params(self, print_debug=True):
if not self.module_calibration_json:
return None
self.applied_params = self.svc.apply_camera_params_json(
self.module_calibration_json
)
if print_debug:
print("[OK] Parâmetros do módulo aplicados:")
print(json.dumps(self.applied_params.get("applied"), ensure_ascii=False, indent=2))
return self.applied_params
def _start_stream(self, print_debug=True):
resp = self.svc.start_stream(
self.pc_host,
self.stream_port,
fps=self.fps,
)
if print_debug:
print("START STREAM:", resp)
return resp
def get_latest(self):
return self.receiver.last_frame, self.receiver.last_meta
def get_next_frame(self, timeout=2.0):
t0 = time.perf_counter()
while time.perf_counter() - t0 < timeout:
frame = self.receiver.last_frame
meta = self.receiver.last_meta
if frame is None or meta is None:
time.sleep(0.001)
continue
frame_id = meta.get("frame_id")
if frame_id != self.last_frame_id:
self.last_frame_id = frame_id
return frame, meta
time.sleep(0.001)
raise TimeoutError("Timeout aguardando novo frame do stream.")
def get_next_decoded(self, timeout=2.0, update_radiometry=True):
frame, meta = self.get_next_frame(timeout=timeout)
decoded = self.core.decode_stream_cameras(frame, meta)
if update_radiometry:
self.update_radiometry(decoded, meta)
return frame, meta, decoded
def build_infer_tensor(self, frame, meta, channels_expected, target_size=None):
return self.core.build_infer_tensor_from_stream(
frame,
meta,
channels_expected=channels_expected,
target_size=target_size,
)
def build_infer_tensor_from_decoded(self, decoded, meta, channels_expected, target_size=None):
tensor = self.core.fuse_multispec_cameras(decoded, meta, channels_expected)
return self.core.resize_tensor_chw(tensor, target_size=target_size)
def build_preview_from_raw_payload(self, frame, meta: dict):
"""
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 = self.core.unpack_raw10_packed(packed)
preview_bgr = self.preview.raw16_to_preview_bgr(raw16, bit_depth=bit_depth)
payload_float = self.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 = self.core.unpack_raw10_packed(packed)
preview_bgr = self.preview.raw16_to_preview_bgr(raw16, bit_depth=bit_depth)
payload_float = self.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 stop(self):
try:
self.svc.stop_stream()
except Exception:
pass
try:
self.svc.stop()
except Exception:
pass
try:
self.svc.disconnect()
except Exception:
pass
try:
self.receiver.stop()
except Exception:
pass