From 28f5fda397a09087632539e3579634d50d198673 Mon Sep 17 00:00:00 2001 From: VALLONGOL Date: Mon, 19 Jan 2026 13:56:46 +0100 Subject: [PATCH] sistemate funzioni di registrazione di sar e mfd --- VideoReceiverSFP/core/dump_manager.py | 31 +++++++++++++-- VideoReceiverSFP/core/payload_dispatcher.py | 5 ++- VideoReceiverSFP/core/sfp_module.py | 21 +++++----- VideoReceiverSFP/core/test_orchestrator.py | 43 +++++++++++++++++++++ VideoReceiverSFP/gui/viewer_sar.py | 20 ++++++++-- todos.md | 8 ++-- 6 files changed, 106 insertions(+), 22 deletions(-) diff --git a/VideoReceiverSFP/core/dump_manager.py b/VideoReceiverSFP/core/dump_manager.py index 34b0f19..6c98489 100644 --- a/VideoReceiverSFP/core/dump_manager.py +++ b/VideoReceiverSFP/core/dump_manager.py @@ -43,9 +43,12 @@ class DumpManager: 'frame': deque() } # Video recording state - self._video_writers: Dict[str, cv2.VideoWriter] = {} + self._video_writers = {} self._video_start_times: Dict[str, float] = {} self._last_frame_timestamps: Dict[str, float] = {} + self._video_fps_map: Dict[str, float] = {} + # Maximum duplicate frames to write when spacing is large (safety cap) + self._max_duplicate_frames = 1000 # prune existing files at startup for cat in list(self._saved.keys()): @@ -109,6 +112,11 @@ class DumpManager: return False self._video_writers[category] = writer self._video_start_times[category] = time.time() + # store configured fps per category + try: + self._video_fps_map[category] = float(fps) + except Exception: + self._video_fps_map[category] = float(20) self._last_frame_timestamps[category] = time.time() logger.info("Started video recording: %s (%dx%d @ %d FPS)", path, width, height, fps) return True @@ -116,7 +124,7 @@ class DumpManager: logger.exception("Error starting video recording") return False - def write_video_frame(self, frame: Image.Image | np.ndarray, category: str): + def write_video_frame(self, frame, category: str): """Write a frame to the active video writer, converting from PIL/RGB if needed.""" if category not in self._video_writers: return @@ -130,7 +138,24 @@ class DumpManager: # Ensure correct format (uint8, BGR) if cv_frame.dtype != np.uint8: cv_frame = cv_frame.astype(np.uint8) - writer.write(cv_frame) + # Determine how many frames to write to reflect real arrival timing. + now = time.time() + last = self._last_frame_timestamps.get(category, now) + fps = float(self._video_fps_map.get(category, 20.0)) + # delta seconds since last written frame + delta = max(0.0, now - last) + # compute duplicates: at least 1 frame + try: + dup = max(1, int(round(delta * fps))) + except Exception: + dup = 1 + # cap duplicates to avoid runaway file sizes + if dup > self._max_duplicate_frames: + dup = self._max_duplicate_frames + for _ in range(dup): + writer.write(cv_frame) + # update last timestamp to now + self._last_frame_timestamps[category] = now except Exception: logger.exception("Error writing frame to video %s", category) diff --git a/VideoReceiverSFP/core/payload_dispatcher.py b/VideoReceiverSFP/core/payload_dispatcher.py index 3f5c77e..05772e5 100644 --- a/VideoReceiverSFP/core/payload_dispatcher.py +++ b/VideoReceiverSFP/core/payload_dispatcher.py @@ -91,11 +91,12 @@ def dispatch_frame(module: Any, frame: Any, leader: Optional[Any]) -> None: except Exception: pass if w and h: - started = module._dump_manager.start_video_record('sar', w, h, fps=getattr(module, '_video_fps', 20)) + 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, getattr(module, '_video_fps', 20)) + logging.getLogger().info("VideoReceiverSFP: started SAR video writer (%dx%d @ %s FPS)", w, h, sar_fps) except Exception: pass except Exception: diff --git a/VideoReceiverSFP/core/sfp_module.py b/VideoReceiverSFP/core/sfp_module.py index aa1c7dc..0aede76 100644 --- a/VideoReceiverSFP/core/sfp_module.py +++ b/VideoReceiverSFP/core/sfp_module.py @@ -123,6 +123,8 @@ class SfpConnectorModule: self._sar_video_active = False # desired video fps when starting writers (default 20) self._video_fps = 20 + # default SAR recording fps (respect arrival timing, lower default) + self._sar_video_fps = 1 try: from .config import DUMP_KEEP_COUNT, DUMPS_DIR self._dump_keep = int(DUMP_KEEP_COUNT) @@ -1017,13 +1019,14 @@ class SfpConnectorModule: except Exception: pass if w and h: - started = self._dump_manager.start_video_record('sar', w, h, fps=getattr(self, '_video_fps', 20)) - if started: - self._sar_video_active = True - try: - logging.getLogger().info("VideoReceiverSFP: started SAR video writer (%dx%d @ %s FPS)", w, h, getattr(self, '_video_fps', 20)) - except Exception: - pass + sar_fps = getattr(self, '_sar_video_fps', getattr(self, '_video_fps', 20)) + started = self._dump_manager.start_video_record('sar', w, h, fps=sar_fps) + if started: + self._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") # write frame if active @@ -1219,8 +1222,8 @@ class SfpConnectorModule: try: self._record_sar_video = bool(enabled) if fps is not None: - self._video_fps = int(fps) - logging.getLogger().info("VideoReceiverSFP: record_sar_video set to %s (fps=%s)", self._record_sar_video, self._video_fps) + self._sar_video_fps = int(fps) + logging.getLogger().info("VideoReceiverSFP: record_sar_video set to %s (fps=%s)", self._record_sar_video, getattr(self, '_sar_video_fps', getattr(self, '_video_fps', 20))) if not self._record_sar_video and getattr(self, '_dump_manager', None) is not None: try: self._dump_manager.stop_video_record('sar') diff --git a/VideoReceiverSFP/core/test_orchestrator.py b/VideoReceiverSFP/core/test_orchestrator.py index 462d812..4f179db 100644 --- a/VideoReceiverSFP/core/test_orchestrator.py +++ b/VideoReceiverSFP/core/test_orchestrator.py @@ -39,6 +39,10 @@ def run_orchestrator(): # Module Initialization module = SfpConnectorModule() init_cfg = {"fps": args.fps} + # Ensure recording does not start automatically at app launch; recordings + # should only start after the user presses Start Recording. + init_cfg['record_mfd_video'] = False + init_cfg['record_sar_video'] = False if args.normalize and args.normalize != 'none': init_cfg['normalize'] = args.normalize if args.image_type != 'ALL': @@ -222,6 +226,45 @@ def run_orchestrator(): start_rec_btn.config(command=on_start_recording) stop_rec_btn.config(command=on_stop_recording) + # Periodically update recording status indicator based on module state + def _update_record_status(): + try: + armed = bool(getattr(module, '_record_mfd_video', False) or getattr(module, '_record_sar_video', False)) + active_mfd = bool(getattr(module, '_mfd_video_active', False)) + active_sar = bool(getattr(module, '_sar_video_active', False)) + if active_mfd or active_sar: + parts = [] + if active_mfd: + parts.append('MFD') + if active_sar: + parts.append('SAR') + rec_status_var.set(f"Recording ({'/'.join(parts)})") + rec_status_label.config(foreground="red") + start_rec_btn.config(state=tk.DISABLED) + stop_rec_btn.config(state=tk.NORMAL) + elif armed: + rec_status_var.set("Armed (waiting for first frame)") + rec_status_label.config(foreground="orange") + start_rec_btn.config(state=tk.DISABLED) + stop_rec_btn.config(state=tk.NORMAL) + else: + rec_status_var.set("Not Recording") + rec_status_label.config(foreground="gray") + start_rec_btn.config(state=tk.NORMAL) + stop_rec_btn.config(state=tk.DISABLED) + except Exception: + pass + try: + root.after(500, _update_record_status) + except Exception: + pass + + # kickoff periodic status updates + try: + root.after(500, _update_record_status) + except Exception: + pass + root.minsize(1000, 600) logging.info("VideoReceiverSFP: GUI initialized with dynamic layout") diff --git a/VideoReceiverSFP/gui/viewer_sar.py b/VideoReceiverSFP/gui/viewer_sar.py index 7480d23..28bf5bc 100644 --- a/VideoReceiverSFP/gui/viewer_sar.py +++ b/VideoReceiverSFP/gui/viewer_sar.py @@ -53,6 +53,7 @@ class SfpSarViewer: self._brightness_var = tk.IntVar(value=0) self._contrast_var = tk.IntVar(value=0) self._save_png_var = tk.BooleanVar(value=False) + self._autocontrast = False try: self._context_menu = tk.Menu(self._root, tearoff=0) @@ -108,10 +109,15 @@ class SfpSarViewer: pass def _on_autocontrast(self): - if self._on_sar_param_changed: - self._on_sar_param_changed("autocontrast", None, True) - with self._lock: - self._params_changed = True + # Toggle local autocontrast state and notify upstream + try: + with self._lock: + self._autocontrast = not self._autocontrast + self._params_changed = True + if self._on_sar_param_changed: + self._on_sar_param_changed("autocontrast", None, bool(self._autocontrast)) + except Exception: + pass def _on_save_current_image(self): """Save the current equalized SAR image to file.""" @@ -230,6 +236,12 @@ class SfpSarViewer: if Image is None or ImageEnhance is None: return img try: + # Apply autocontrast first if enabled + try: + if self._autocontrast and ImageOps is not None: + img = ImageOps.autocontrast(img) + except Exception: + pass with self._lock: bf = max(0.0, 1.0 + (self._current_brightness / 100.0)) cf = max(0.0, 1.0 + (self._current_contrast / 100.0)) diff --git a/todos.md b/todos.md index e77fca6..3c24d30 100644 --- a/todos.md +++ b/todos.md @@ -5,16 +5,16 @@ - [ ] VRSFP: aggiungere la possibilità di modificare la configurazione rutime, com e poter aggiungere la possibilità di salvare le immagini invece che il video o altro: creare una funzione setConfig che permetta di modificare runtime la configurazione del modulo. La stessa informazioni come formato deve essere la stessa che viene passata in fase di inizializzazione. Visto che la configurazione possa essere modificata anche in altre parti sarebbe utile anche a vere una funzione che restituisca il dizionario della configurazione come get_config(). -- [ ] VRSFP: poter aggiungere le funzioni di start salvataggio video e stop salvataggio video come api +- [x] VRSFP: poter aggiungere le funzioni di start salvataggio video e stop salvataggio video come api - [ ] VRSFP: poter impostare runtime il nome e la posizione del file immagine o video prodotto - [ ] VRSFP: avere una funzione che restituisce nome e path dell'ultimo file salvato per mfd, sar e video - [x] VRSFP: aggiungere un log stile modulo aggiuntivo e collegarlo al modulo che poi sarà comune a tutti i moduli -- [ ] VRSFP: aggiungere un pannello al test_orchestator dove poter aggiungere i controlli per testare le funzionalità di registrazione ondemand ecc -- [ ] VRSFP: poter impostare a priori la dimensione in pixel della finestra mfd e della finestra sar. Per la finestra mfd, se viene indicato "0, 0 " viene usata la dimensione dell'immagine che viene spedita. se è diversa da 0,0 viene ridmensionata la finestra. Invece per il sar la dimensione è sempre quella specificata e deve sempre essere diversa da 0,0. +- [x] VRSFP: aggiungere un pannello al test_orchestator dove poter aggiungere i controlli per testare le funzionalità di registrazione ondemand ecc +- [x] VRSFP: poter impostare a priori la dimensione in pixel della finestra mfd e della finestra sar. Per la finestra mfd, se viene indicato "0, 0 " viene usata la dimensione dell'immagine che viene spedita. se è diversa da 0,0 viene ridmensionata la finestra. Invece per il sar la dimensione è sempre quella specificata e deve sempre essere diversa da 0,0. # FIXME List -- [ ] \ No newline at end of file +- [x] sistemare la funzione di autocostrans nella immagine sar. \ No newline at end of file