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()
|
'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)
|
||||||
|
|
||||||
|
|||||||
@ -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:
|
||||||
|
|||||||
@ -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')
|
||||||
|
|||||||
@ -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")
|
||||||
|
|
||||||
|
|||||||
@ -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))
|
||||||
|
|||||||
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: 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.
|
||||||
Loading…
Reference in New Issue
Block a user