"""Dispatch logic for decoded frames (MFD/SAR/generic). Provides `dispatch_frame(module, frame, leader)` which encapsulates recording, saving previews, and invoking registered callbacks. """ from typing import Any, Optional import logging import os import time import pathlib try: from PIL import Image, ImageOps, ImageEnhance except Exception: Image = None ImageOps = None ImageEnhance = None def dispatch_frame(module: Any, frame: Any, leader: Optional[Any]) -> None: """Handle a decoded frame: start/ write video, save previews, and call callbacks. `module` is the `SfpConnectorModule` instance. """ try: # Determine image type image_type_id = None try: if leader is not None: image_type_id = int(leader.HEADER_DATA.TYPE) except Exception: image_type_id = None # SAR path if image_type_id == getattr(module, 'IMAGE_TYPE_SAR', 2): try: if Image is not None and hasattr(frame, 'copy'): sar_img = frame.copy() if getattr(module, '_sar_autocontrast', False) and ImageOps is not None: try: sar_img = ImageOps.autocontrast(sar_img) except Exception: pass try: bf = max(0.0, 1.0 + (float(getattr(module, '_sar_brightness', 0)) / 100.0)) cf = max(0.0, 1.0 + (float(getattr(module, '_sar_contrast', 0)) / 100.0)) if bf != 1.0 and ImageEnhance is not None: sar_img = ImageEnhance.Brightness(sar_img).enhance(bf) if cf != 1.0 and ImageEnhance is not None: sar_img = ImageEnhance.Contrast(sar_img).enhance(cf) except Exception: pass # save sar png if getattr(module, '_sar_save_png', False): try: if getattr(module, '_dump_manager', None) is not None: saved = module._dump_manager.save_preview_from_pil(sar_img, category='sar', fmt='png') if saved: logging.getLogger().info("VideoReceiverSFP: saved sar png %s", saved) else: ts = time.strftime('%Y%m%d_%H%M%S') + (f"_{int(time.time() * 1000) % 1000:03d}") dumps_dir = getattr(module, '_dumps_dir', os.path.join(os.getcwd(), 'dumps')) os.makedirs(dumps_dir, exist_ok=True) pngpath = os.path.join(dumps_dir, f'VideoReceiverSFP_sar_{ts}.png') sar_img.save(pngpath) module._saved_sar_pngs.append(pngpath) while len(module._saved_sar_pngs) > module._dump_keep: old = module._saved_sar_pngs.popleft() try: pathlib.Path(old).unlink() except Exception: pass except Exception: logging.getLogger().exception("VideoReceiverSFP: failed saving sar png frame") # sar video try: if getattr(module, '_record_sar_video', False) and getattr(module, '_dump_manager', None) is not None: if not getattr(module, '_sar_video_active', False): try: w, h = None, None if Image is not None and hasattr(sar_img, 'size'): w, h = sar_img.size else: try: import numpy as _np arr = _np.asarray(sar_img) if arr is not None and arr.ndim >= 2: h, w = arr.shape[0], arr.shape[1] except Exception: pass if w and h: sar_fps = getattr(module, '_sar_video_fps', getattr(module, '_video_fps', 20)) started = module._dump_manager.start_video_record('sar', w, h, fps=sar_fps) if started: module._sar_video_active = True try: logging.getLogger().info("VideoReceiverSFP: started SAR video writer (%dx%d @ %s FPS)", w, h, sar_fps) except Exception: pass except Exception: logging.getLogger().exception("VideoReceiverSFP: failed to start sar video writer") if getattr(module, '_sar_video_active', False): try: module._dump_manager.write_video_frame(sar_img, 'sar') except Exception: logging.getLogger().exception("VideoReceiverSFP: failed writing sar frame to video") except Exception: pass # deliver to callbacks for scb in list(getattr(module, '_sar_callbacks', [])): try: scb(sar_img) except Exception: pass except Exception: logging.getLogger().exception("VideoReceiverSFP: error processing SAR image for SAR callbacks") # MFD path if image_type_id == getattr(module, 'IMAGE_TYPE_MFD', 1): try: try: if getattr(module, '_record_mfd_video', False) and getattr(module, '_dump_manager', None) is not None: if not getattr(module, '_mfd_video_active', False): try: w, h = None, None if Image is not None and hasattr(frame, 'size'): w, h = frame.size else: try: import numpy as _np arr = _np.asarray(frame) if arr is not None and arr.ndim >= 2: h, w = arr.shape[0], arr.shape[1] except Exception: pass if w and h: started = module._dump_manager.start_video_record('mfd', w, h, fps=getattr(module, '_video_fps', 20)) if started: module._mfd_video_active = True try: logging.getLogger().info("VideoReceiverSFP: started MFD video writer (%dx%d @ %s FPS)", w, h, getattr(module, '_video_fps', 20)) except Exception: pass except Exception: logging.getLogger().exception('VideoReceiverSFP: failed to start mfd video writer') if getattr(module, '_mfd_video_active', False): try: module._dump_manager.write_video_frame(frame, 'mfd') except Exception: logging.getLogger().exception('VideoReceiverSFP: failed writing mfd frame to video') try: if not getattr(module, '_mfd_callbacks', False): logging.getLogger().debug('VideoReceiverSFP: mfd frames are being recorded but no MFD callbacks are registered') except Exception: pass except Exception: pass for mcb in list(getattr(module, '_mfd_callbacks', [])): try: mcb(frame) except Exception: pass except Exception: pass # generic callbacks and saving try: is_mfd = (image_type_id == getattr(module, 'IMAGE_TYPE_MFD', 1)) except Exception: is_mfd = False for cb in list(getattr(module, '_callbacks', [])): try: # Only the MFD generic save is controlled by _save_png. SAR has its # own _sar_save_png flag handled earlier. Save MFD previews under # the 'mfd' category so get_last_saved_mfd() can find them. if is_mfd and getattr(module, '_save_png', False) and Image is not None and hasattr(frame, 'save'): try: if getattr(module, '_dump_manager', None) is not None: saved = module._dump_manager.save_preview_from_pil(frame, category='mfd', fmt='png') if saved: logging.getLogger().info("VideoReceiverSFP: saved MFD preview to %s", saved) else: ts = time.strftime('%Y%m%d_%H%M%S') + (f"_{int(time.time() * 1000) % 1000:03d}") dumps_dir = getattr(module, '_dumps_dir', os.path.join(os.getcwd(), 'dumps')) os.makedirs(dumps_dir, exist_ok=True) pngpath = os.path.join(dumps_dir, f'VideoReceiverSFP_mfd_{ts}.png') frame.save(pngpath) module._saved_pngs.append(pngpath) while len(module._saved_pngs) > module._dump_keep: old = module._saved_pngs.popleft() try: pathlib.Path(old).unlink() except Exception: pass except Exception: logging.getLogger().exception('VideoReceiverSFP: failed to save MFD preview png') try: cb(frame) except Exception: pass except Exception: pass except Exception: logging.getLogger().exception('VideoReceiverSFP: dispatch_frame unexpected error')