sistemate funzioni di registrazione di sar e mfd
This commit is contained in:
parent
2694eda666
commit
28f5fda397
@ -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)
|
||||
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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')
|
||||
|
||||
@ -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")
|
||||
|
||||
|
||||
@ -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))
|
||||
|
||||
8
todos.md
8
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
|
||||
|
||||
- [ ]
|
||||
- [x] sistemare la funzione di autocostrans nella immagine sar.
|
||||
Loading…
Reference in New Issue
Block a user