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()
}
# 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)
# 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)

View File

@ -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:

View File

@ -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,11 +1019,12 @@ 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))
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, getattr(self, '_video_fps', 20))
logging.getLogger().info("VideoReceiverSFP: started SAR video writer (%dx%d @ %s FPS)", w, h, sar_fps)
except Exception:
pass
except Exception:
@ -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')

View File

@ -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")

View File

@ -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)
# 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))

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