sistemate funzioni di registrazione di sar e mfd

This commit is contained in:
VALLONGOL 2026-01-19 13:56:46 +01:00
parent 2694eda666
commit 28f5fda397
6 changed files with 106 additions and 22 deletions

View File

@ -43,9 +43,12 @@ class DumpManager:
'frame': deque() 'frame': deque()
} }
# Video recording state # Video recording state
self._video_writers: Dict[str, cv2.VideoWriter] = {} self._video_writers = {}
self._video_start_times: Dict[str, float] = {} self._video_start_times: Dict[str, float] = {}
self._last_frame_timestamps: 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 # prune existing files at startup
for cat in list(self._saved.keys()): for cat in list(self._saved.keys()):
@ -109,6 +112,11 @@ class DumpManager:
return False return False
self._video_writers[category] = writer self._video_writers[category] = writer
self._video_start_times[category] = time.time() 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() self._last_frame_timestamps[category] = time.time()
logger.info("Started video recording: %s (%dx%d @ %d FPS)", path, width, height, fps) logger.info("Started video recording: %s (%dx%d @ %d FPS)", path, width, height, fps)
return True return True
@ -116,7 +124,7 @@ class DumpManager:
logger.exception("Error starting video recording") logger.exception("Error starting video recording")
return False 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.""" """Write a frame to the active video writer, converting from PIL/RGB if needed."""
if category not in self._video_writers: if category not in self._video_writers:
return return
@ -130,7 +138,24 @@ class DumpManager:
# Ensure correct format (uint8, BGR) # Ensure correct format (uint8, BGR)
if cv_frame.dtype != np.uint8: if cv_frame.dtype != np.uint8:
cv_frame = cv_frame.astype(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: except Exception:
logger.exception("Error writing frame to video %s", category) logger.exception("Error writing frame to video %s", category)

View File

@ -91,11 +91,12 @@ def dispatch_frame(module: Any, frame: Any, leader: Optional[Any]) -> None:
except Exception: except Exception:
pass pass
if w and h: 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: if started:
module._sar_video_active = True module._sar_video_active = True
try: 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: except Exception:
pass pass
except Exception: except Exception:

View File

@ -123,6 +123,8 @@ class SfpConnectorModule:
self._sar_video_active = False self._sar_video_active = False
# desired video fps when starting writers (default 20) # desired video fps when starting writers (default 20)
self._video_fps = 20 self._video_fps = 20
# default SAR recording fps (respect arrival timing, lower default)
self._sar_video_fps = 1
try: try:
from .config import DUMP_KEEP_COUNT, DUMPS_DIR from .config import DUMP_KEEP_COUNT, DUMPS_DIR
self._dump_keep = int(DUMP_KEEP_COUNT) self._dump_keep = int(DUMP_KEEP_COUNT)
@ -1017,13 +1019,14 @@ class SfpConnectorModule:
except Exception: except Exception:
pass pass
if w and h: if w and h:
started = self._dump_manager.start_video_record('sar', w, h, fps=getattr(self, '_video_fps', 20)) sar_fps = getattr(self, '_sar_video_fps', getattr(self, '_video_fps', 20))
if started: started = self._dump_manager.start_video_record('sar', w, h, fps=sar_fps)
self._sar_video_active = True if started:
try: self._sar_video_active = True
logging.getLogger().info("VideoReceiverSFP: started SAR video writer (%dx%d @ %s FPS)", w, h, getattr(self, '_video_fps', 20)) try:
except Exception: logging.getLogger().info("VideoReceiverSFP: started SAR video writer (%dx%d @ %s FPS)", w, h, sar_fps)
pass except Exception:
pass
except Exception: except Exception:
logging.getLogger().exception("VideoReceiverSFP: failed to start sar video writer") logging.getLogger().exception("VideoReceiverSFP: failed to start sar video writer")
# write frame if active # write frame if active
@ -1219,8 +1222,8 @@ class SfpConnectorModule:
try: try:
self._record_sar_video = bool(enabled) self._record_sar_video = bool(enabled)
if fps is not None: if fps is not None:
self._video_fps = int(fps) self._sar_video_fps = int(fps)
logging.getLogger().info("VideoReceiverSFP: record_sar_video set to %s (fps=%s)", self._record_sar_video, self._video_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: if not self._record_sar_video and getattr(self, '_dump_manager', None) is not None:
try: try:
self._dump_manager.stop_video_record('sar') self._dump_manager.stop_video_record('sar')

View File

@ -39,6 +39,10 @@ def run_orchestrator():
# Module Initialization # Module Initialization
module = SfpConnectorModule() module = SfpConnectorModule()
init_cfg = {"fps": args.fps} 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': if args.normalize and args.normalize != 'none':
init_cfg['normalize'] = args.normalize init_cfg['normalize'] = args.normalize
if args.image_type != 'ALL': if args.image_type != 'ALL':
@ -222,6 +226,45 @@ def run_orchestrator():
start_rec_btn.config(command=on_start_recording) start_rec_btn.config(command=on_start_recording)
stop_rec_btn.config(command=on_stop_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) root.minsize(1000, 600)
logging.info("VideoReceiverSFP: GUI initialized with dynamic layout") logging.info("VideoReceiverSFP: GUI initialized with dynamic layout")

View File

@ -53,6 +53,7 @@ class SfpSarViewer:
self._brightness_var = tk.IntVar(value=0) self._brightness_var = tk.IntVar(value=0)
self._contrast_var = tk.IntVar(value=0) self._contrast_var = tk.IntVar(value=0)
self._save_png_var = tk.BooleanVar(value=False) self._save_png_var = tk.BooleanVar(value=False)
self._autocontrast = False
try: try:
self._context_menu = tk.Menu(self._root, tearoff=0) self._context_menu = tk.Menu(self._root, tearoff=0)
@ -108,10 +109,15 @@ class SfpSarViewer:
pass pass
def _on_autocontrast(self): def _on_autocontrast(self):
if self._on_sar_param_changed: # Toggle local autocontrast state and notify upstream
self._on_sar_param_changed("autocontrast", None, True) try:
with self._lock: with self._lock:
self._params_changed = True 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): def _on_save_current_image(self):
"""Save the current equalized SAR image to file.""" """Save the current equalized SAR image to file."""
@ -230,6 +236,12 @@ class SfpSarViewer:
if Image is None or ImageEnhance is None: if Image is None or ImageEnhance is None:
return img return img
try: 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: with self._lock:
bf = max(0.0, 1.0 + (self._current_brightness / 100.0)) bf = max(0.0, 1.0 + (self._current_brightness / 100.0))
cf = max(0.0, 1.0 + (self._current_contrast / 100.0)) cf = max(0.0, 1.0 + (self._current_contrast / 100.0))

View File

@ -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: 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: 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 - [ ] 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 - [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 - [x] 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: 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 # FIXME List
- [ ] - [x] sistemare la funzione di autocostrans nella immagine sar.