prima versione della compact
This commit is contained in:
parent
14b69f1e3d
commit
b17de5f03c
2
.vscode/launch.json
vendored
2
.vscode/launch.json
vendored
@ -26,7 +26,7 @@
|
|||||||
"request": "launch",
|
"request": "launch",
|
||||||
"module": "VideoReceiverSFP",
|
"module": "VideoReceiverSFP",
|
||||||
"console": "integratedTerminal",
|
"console": "integratedTerminal",
|
||||||
"args": ["--host", "127.0.0.1", "--port", "55556"] //,"--verbose"]//, "--duration", "30"]//, "--type", "MFD"]
|
"args": ["--host", "127.0.0.1", "--port", "55556", "--compact"] //,"--verbose"]//, "--duration", "30"]//, "--type", "MFD"]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@ -189,6 +189,50 @@ class SfpConnectorModule:
|
|||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def _load_next_frame(self):
|
||||||
|
"""Load next frame from `self._sim_dir` for simulation mode.
|
||||||
|
|
||||||
|
Returns a PIL.Image (RGB) or None if no frame available.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
if not self._sim_dir:
|
||||||
|
return None
|
||||||
|
# Cache file list
|
||||||
|
if not hasattr(self, '_sim_files') or self._sim_files is None:
|
||||||
|
p = pathlib.Path(self._sim_dir)
|
||||||
|
if not p.exists() or not p.is_dir():
|
||||||
|
return None
|
||||||
|
files = []
|
||||||
|
for ext in ('*.png', '*.jpg', '*.jpeg', '*.bmp', '*.tif', '*.tiff'):
|
||||||
|
files.extend(sorted(p.glob(ext)))
|
||||||
|
self._sim_files = [str(x) for x in files]
|
||||||
|
if not self._sim_files:
|
||||||
|
return None
|
||||||
|
|
||||||
|
if not self._sim_files:
|
||||||
|
return None
|
||||||
|
|
||||||
|
idx = int(self._frame_idx) % len(self._sim_files)
|
||||||
|
path = self._sim_files[idx]
|
||||||
|
try:
|
||||||
|
if Image is not None:
|
||||||
|
img = Image.open(path).convert('RGB')
|
||||||
|
else:
|
||||||
|
# Fallback: return raw bytes
|
||||||
|
with open(path, 'rb') as fh:
|
||||||
|
return fh.read()
|
||||||
|
except Exception:
|
||||||
|
logging.getLogger().exception('VideoReceiverSFP: failed to open simulated frame %s', path)
|
||||||
|
# Skip this file
|
||||||
|
self._frame_idx = (self._frame_idx + 1) % max(1, len(self._sim_files))
|
||||||
|
return None
|
||||||
|
|
||||||
|
self._frame_idx = (self._frame_idx + 1) % max(1, len(self._sim_files))
|
||||||
|
return img
|
||||||
|
except Exception:
|
||||||
|
logging.getLogger().exception('VideoReceiverSFP: _load_next_frame encountered an error')
|
||||||
|
return None
|
||||||
|
|
||||||
def _ensure_mfd_lut(self):
|
def _ensure_mfd_lut(self):
|
||||||
if self._mfd_lut is not None:
|
if self._mfd_lut is not None:
|
||||||
return
|
return
|
||||||
|
|||||||
@ -23,6 +23,7 @@ def run_orchestrator():
|
|||||||
parser.add_argument("--no-gui", action="store_true", help="Run in headless mode (no GUI)")
|
parser.add_argument("--no-gui", action="store_true", help="Run in headless mode (no GUI)")
|
||||||
parser.add_argument("--normalize", choices=["none", "cp", "autocontrast"], default="none", help="Normalize raw frames before display: 'cp' uses controlpanel.normalize_image if available, 'autocontrast' uses PIL fallback")
|
parser.add_argument("--normalize", choices=["none", "cp", "autocontrast"], default="none", help="Normalize raw frames before display: 'cp' uses controlpanel.normalize_image if available, 'autocontrast' uses PIL fallback")
|
||||||
parser.add_argument("--verbose", action="store_true", help="Enable verbose DEBUG logging")
|
parser.add_argument("--verbose", action="store_true", help="Enable verbose DEBUG logging")
|
||||||
|
parser.add_argument("--compact", action="store_true", help="Start viewers in compact mode (hide controls, accessible via right-click)")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--image-type', '--type', dest='image_type', type=str, choices=['MFD', 'SAR', 'ALL'], default='ALL',
|
'--image-type', '--type', dest='image_type', type=str, choices=['MFD', 'SAR', 'ALL'], default='ALL',
|
||||||
help='Filter for a specific image type (MFD or SAR). Default is ALL.'
|
help='Filter for a specific image type (MFD or SAR). Default is ALL.'
|
||||||
@ -150,8 +151,8 @@ def run_orchestrator():
|
|||||||
logging.getLogger().exception('Failed to set SAR parameter on module')
|
logging.getLogger().exception('Failed to set SAR parameter on module')
|
||||||
|
|
||||||
# Instantiate embedded viewers inside the Labelframes
|
# Instantiate embedded viewers inside the Labelframes
|
||||||
viewer = SfpViewerWithParams(on_mfd_param_changed=on_mfd_param_changed, master=left_box)
|
viewer = SfpViewerWithParams(on_mfd_param_changed=on_mfd_param_changed, master=left_box, compact=args.compact)
|
||||||
sar_viewer = SfpSarViewer(on_sar_param_changed=on_sar_param_changed, master=right_box)
|
sar_viewer = SfpSarViewer(on_sar_param_changed=on_sar_param_changed, master=right_box, compact=args.compact)
|
||||||
logging.info("VideoReceiverSFP: embedded MFD and SAR viewers created")
|
logging.info("VideoReceiverSFP: embedded MFD and SAR viewers created")
|
||||||
|
|
||||||
except ImportError:
|
except ImportError:
|
||||||
|
|||||||
@ -19,7 +19,7 @@ except Exception:
|
|||||||
|
|
||||||
|
|
||||||
class SfpSarViewer:
|
class SfpSarViewer:
|
||||||
def __init__(self, window_title: str = "SFP SAR Viewer", on_sar_param_changed: Optional[Callable] = None, master: Optional[tk.Widget] = None):
|
def __init__(self, window_title: str = "SFP SAR Viewer", on_sar_param_changed: Optional[Callable] = None, master: Optional[tk.Widget] = None, compact: bool = False):
|
||||||
# Support embedding inside an existing master (Labelframe/frame). If no master provided,
|
# Support embedding inside an existing master (Labelframe/frame). If no master provided,
|
||||||
# create a standalone Tk root as before.
|
# create a standalone Tk root as before.
|
||||||
if master is None:
|
if master is None:
|
||||||
@ -32,6 +32,8 @@ class SfpSarViewer:
|
|||||||
self._owns_root = False
|
self._owns_root = False
|
||||||
|
|
||||||
self._on_sar_param_changed = on_sar_param_changed
|
self._on_sar_param_changed = on_sar_param_changed
|
||||||
|
# Compact mode: hide controls by default and expose via right-click
|
||||||
|
self._compact = bool(compact)
|
||||||
|
|
||||||
# Build UI: image on left, controls on right
|
# Build UI: image on left, controls on right
|
||||||
main = ttk.Frame(self._root)
|
main = ttk.Frame(self._root)
|
||||||
@ -46,42 +48,56 @@ class SfpSarViewer:
|
|||||||
self._img_canvas.pack(fill="both", expand=True)
|
self._img_canvas.pack(fill="both", expand=True)
|
||||||
self._canvas_image_id = None
|
self._canvas_image_id = None
|
||||||
|
|
||||||
# Right controls
|
# Right controls (hidden in compact mode)
|
||||||
ctrl_frame = ttk.Labelframe(main, text="SAR Controls", padding=8)
|
|
||||||
ctrl_frame.grid(row=0, column=1, sticky="nsew", padx=(8,0))
|
|
||||||
|
|
||||||
# Brightness slider (-100..100)
|
|
||||||
ttk.Label(ctrl_frame, text="Brightness").grid(row=0, column=0, sticky="w")
|
|
||||||
self._brightness_var = tk.IntVar(value=0)
|
self._brightness_var = tk.IntVar(value=0)
|
||||||
b_slider = ttk.Scale(ctrl_frame, from_=-100, to=100, orient="horizontal",
|
|
||||||
variable=self._brightness_var, command=self._on_brightness_changed)
|
|
||||||
b_slider.grid(row=1, column=0, sticky="ew", pady=(0,6))
|
|
||||||
|
|
||||||
# Contrast slider (-100..100)
|
|
||||||
ttk.Label(ctrl_frame, text="Contrast").grid(row=2, column=0, sticky="w")
|
|
||||||
self._contrast_var = tk.IntVar(value=0)
|
self._contrast_var = tk.IntVar(value=0)
|
||||||
c_slider = ttk.Scale(ctrl_frame, from_=-100, to=100, orient="horizontal",
|
|
||||||
variable=self._contrast_var, command=self._on_contrast_changed)
|
|
||||||
c_slider.grid(row=3, column=0, sticky="ew", pady=(0,6))
|
|
||||||
|
|
||||||
# Autocontrast button
|
|
||||||
auto_btn = ttk.Button(ctrl_frame, text="AutoContrast", command=self._on_autocontrast)
|
|
||||||
auto_btn.grid(row=4, column=0, sticky="ew", pady=(4,8))
|
|
||||||
|
|
||||||
# Show metadata button
|
|
||||||
meta_btn = ttk.Button(ctrl_frame, text="Show Metadata", command=self._on_show_metadata)
|
|
||||||
meta_btn.grid(row=5, column=0, sticky="ew", pady=(4,8))
|
|
||||||
|
|
||||||
# Save current equalized image button
|
|
||||||
save_img_btn = ttk.Button(ctrl_frame, text="Save Image", command=self._on_save_current_image)
|
|
||||||
save_img_btn.grid(row=6, column=0, sticky="ew", pady=(4,8))
|
|
||||||
|
|
||||||
# Save PNG toggle
|
|
||||||
self._save_png_var = tk.BooleanVar(value=False)
|
self._save_png_var = tk.BooleanVar(value=False)
|
||||||
save_cb = ttk.Checkbutton(ctrl_frame, text="Save .png", variable=self._save_png_var, command=self._on_save_png_toggled)
|
if not self._compact:
|
||||||
save_cb.grid(row=7, column=0, sticky="w", pady=(2,2))
|
ctrl_frame = ttk.Labelframe(main, text="SAR Controls", padding=8)
|
||||||
|
ctrl_frame.grid(row=0, column=1, sticky="nsew", padx=(8,0))
|
||||||
|
|
||||||
ctrl_frame.columnconfigure(0, weight=1)
|
# Brightness slider (-100..100)
|
||||||
|
ttk.Label(ctrl_frame, text="Brightness").grid(row=0, column=0, sticky="w")
|
||||||
|
b_slider = ttk.Scale(ctrl_frame, from_=-100, to=100, orient="horizontal",
|
||||||
|
variable=self._brightness_var, command=self._on_brightness_changed)
|
||||||
|
b_slider.grid(row=1, column=0, sticky="ew", pady=(0,6))
|
||||||
|
|
||||||
|
# Contrast slider (-100..100)
|
||||||
|
ttk.Label(ctrl_frame, text="Contrast").grid(row=2, column=0, sticky="w")
|
||||||
|
c_slider = ttk.Scale(ctrl_frame, from_=-100, to=100, orient="horizontal",
|
||||||
|
variable=self._contrast_var, command=self._on_contrast_changed)
|
||||||
|
c_slider.grid(row=3, column=0, sticky="ew", pady=(0,6))
|
||||||
|
|
||||||
|
# Autocontrast button
|
||||||
|
auto_btn = ttk.Button(ctrl_frame, text="AutoContrast", command=self._on_autocontrast)
|
||||||
|
auto_btn.grid(row=4, column=0, sticky="ew", pady=(4,8))
|
||||||
|
|
||||||
|
# Show metadata button
|
||||||
|
meta_btn = ttk.Button(ctrl_frame, text="Show Metadata", command=self._on_show_metadata)
|
||||||
|
meta_btn.grid(row=5, column=0, sticky="ew", pady=(4,8))
|
||||||
|
|
||||||
|
# Save current equalized image button
|
||||||
|
save_img_btn = ttk.Button(ctrl_frame, text="Save Image", command=self._on_save_current_image)
|
||||||
|
save_img_btn.grid(row=6, column=0, sticky="ew", pady=(4,8))
|
||||||
|
|
||||||
|
# Save PNG toggle
|
||||||
|
save_cb = ttk.Checkbutton(ctrl_frame, text="Save .png", variable=self._save_png_var, command=self._on_save_png_toggled)
|
||||||
|
save_cb.grid(row=7, column=0, sticky="w", pady=(2,2))
|
||||||
|
else:
|
||||||
|
# Compact mode: provide context menu on canvas to open controls and metadata
|
||||||
|
try:
|
||||||
|
self._context_menu = tk.Menu(self._root, tearoff=0)
|
||||||
|
self._context_menu.add_command(label="Open Controls", command=self._open_controls_window)
|
||||||
|
self._context_menu.add_command(label="Show Metadata", command=self._on_show_metadata)
|
||||||
|
self._img_canvas.bind("<Button-3>", self._on_canvas_right_click)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if not self._compact:
|
||||||
|
try:
|
||||||
|
ctrl_frame.columnconfigure(0, weight=1)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
main.rowconfigure(0, weight=1)
|
main.rowconfigure(0, weight=1)
|
||||||
main.rowconfigure(1, weight=0)
|
main.rowconfigure(1, weight=0)
|
||||||
main.columnconfigure(0, weight=3)
|
main.columnconfigure(0, weight=3)
|
||||||
@ -177,6 +193,49 @@ class SfpSarViewer:
|
|||||||
if self._on_sar_param_changed:
|
if self._on_sar_param_changed:
|
||||||
self._on_sar_param_changed("save_png", None, val)
|
self._on_sar_param_changed("save_png", None, val)
|
||||||
|
|
||||||
|
def _on_canvas_right_click(self, event):
|
||||||
|
try:
|
||||||
|
if hasattr(self, '_context_menu') and self._context_menu:
|
||||||
|
self._context_menu.tk_popup(event.x_root, event.y_root)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def _open_controls_window(self):
|
||||||
|
"""Open a Toplevel window with SAR controls (used in compact mode)."""
|
||||||
|
try:
|
||||||
|
win = tk.Toplevel(self._root)
|
||||||
|
win.title('SAR Controls')
|
||||||
|
frm = ttk.Frame(win, padding=8)
|
||||||
|
frm.pack(fill='both', expand=True)
|
||||||
|
|
||||||
|
ttk.Label(frm, text="Brightness").grid(row=0, column=0, sticky="w")
|
||||||
|
b_slider = ttk.Scale(frm, from_=-100, to=100, orient="horizontal",
|
||||||
|
variable=self._brightness_var, command=self._on_brightness_changed)
|
||||||
|
b_slider.grid(row=1, column=0, sticky="ew", pady=(0,6))
|
||||||
|
|
||||||
|
ttk.Label(frm, text="Contrast").grid(row=2, column=0, sticky="w")
|
||||||
|
c_slider = ttk.Scale(frm, from_=-100, to=100, orient="horizontal",
|
||||||
|
variable=self._contrast_var, command=self._on_contrast_changed)
|
||||||
|
c_slider.grid(row=3, column=0, sticky="ew", pady=(0,6))
|
||||||
|
|
||||||
|
auto_btn = ttk.Button(frm, text="AutoContrast", command=self._on_autocontrast)
|
||||||
|
auto_btn.grid(row=4, column=0, sticky="ew", pady=(4,8))
|
||||||
|
|
||||||
|
meta_btn = ttk.Button(frm, text="Show Metadata", command=self._on_show_metadata)
|
||||||
|
meta_btn.grid(row=5, column=0, sticky="ew", pady=(4,8))
|
||||||
|
|
||||||
|
save_img_btn = ttk.Button(frm, text="Save Image", command=self._on_save_current_image)
|
||||||
|
save_img_btn.grid(row=6, column=0, sticky="ew", pady=(4,8))
|
||||||
|
|
||||||
|
save_cb = ttk.Checkbutton(frm, text="Save .png", variable=self._save_png_var, command=self._on_save_png_toggled)
|
||||||
|
save_cb.grid(row=7, column=0, sticky="w", pady=(2,2))
|
||||||
|
|
||||||
|
frm.columnconfigure(0, weight=1)
|
||||||
|
win.transient(self._root)
|
||||||
|
win.lift()
|
||||||
|
except Exception:
|
||||||
|
logging.exception("Failed to open SAR controls window")
|
||||||
|
|
||||||
def _on_show_metadata(self):
|
def _on_show_metadata(self):
|
||||||
try:
|
try:
|
||||||
if self._meta_window is None or not getattr(self._meta_window, 'winfo_exists', lambda: False)():
|
if self._meta_window is None or not getattr(self._meta_window, 'winfo_exists', lambda: False)():
|
||||||
|
|||||||
@ -30,7 +30,8 @@ class SfpViewerWithParams:
|
|||||||
def __init__(self,
|
def __init__(self,
|
||||||
window_title: str = "SFP Viewer with MFD Params",
|
window_title: str = "SFP Viewer with MFD Params",
|
||||||
on_mfd_param_changed: Optional[Callable] = None,
|
on_mfd_param_changed: Optional[Callable] = None,
|
||||||
master: Optional[tk.Widget] = None):
|
master: Optional[tk.Widget] = None,
|
||||||
|
compact: bool = False):
|
||||||
"""Initialize viewer with MFD parameter controls.
|
"""Initialize viewer with MFD parameter controls.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@ -49,6 +50,8 @@ class SfpViewerWithParams:
|
|||||||
|
|
||||||
# Callback for parameter changes
|
# Callback for parameter changes
|
||||||
self._on_mfd_param_changed = on_mfd_param_changed
|
self._on_mfd_param_changed = on_mfd_param_changed
|
||||||
|
# Compact mode: hide controls by default and expose via right-click
|
||||||
|
self._compact = bool(compact)
|
||||||
|
|
||||||
# MFD state manager (local instance)
|
# MFD state manager (local instance)
|
||||||
if MfdState:
|
if MfdState:
|
||||||
@ -112,10 +115,18 @@ class SfpViewerWithParams:
|
|||||||
self._img_label = tk.Label(self._img_canvas, bg="black")
|
self._img_label = tk.Label(self._img_canvas, bg="black")
|
||||||
self._img_canvas.create_window(242, 242, window=self._img_label)
|
self._img_canvas.create_window(242, 242, window=self._img_label)
|
||||||
|
|
||||||
# Right: MFD Parameters
|
# Right: MFD Parameters (hidden in compact mode)
|
||||||
if self._mfd_state:
|
if not self._compact and self._mfd_state:
|
||||||
params_frame = self._build_mfd_params_frame(main_frame)
|
params_frame = self._build_mfd_params_frame(main_frame)
|
||||||
params_frame.grid(row=0, column=1, sticky="nsew")
|
params_frame.grid(row=0, column=1, sticky="nsew")
|
||||||
|
else:
|
||||||
|
# Create a context menu on the image canvas to open controls when compact
|
||||||
|
try:
|
||||||
|
self._context_menu = tk.Menu(self._root, tearoff=0)
|
||||||
|
self._context_menu.add_command(label="Open Controls", command=self._open_controls_window)
|
||||||
|
self._img_canvas.bind("<Button-3>", self._on_canvas_right_click)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
# Bottom: Status bar
|
# Bottom: Status bar
|
||||||
@ -319,6 +330,27 @@ class SfpViewerWithParams:
|
|||||||
if self._on_mfd_param_changed:
|
if self._on_mfd_param_changed:
|
||||||
self._on_mfd_param_changed("save_bin", None, val)
|
self._on_mfd_param_changed("save_bin", None, val)
|
||||||
|
|
||||||
|
def _on_canvas_right_click(self, event):
|
||||||
|
try:
|
||||||
|
# Show context menu
|
||||||
|
if hasattr(self, '_context_menu') and self._context_menu:
|
||||||
|
self._context_menu.tk_popup(event.x_root, event.y_root)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def _open_controls_window(self):
|
||||||
|
"""Open a Toplevel window containing the MFD parameter controls."""
|
||||||
|
try:
|
||||||
|
win = tk.Toplevel(self._root)
|
||||||
|
win.title("MFD Controls")
|
||||||
|
# Build controls inside this window using same builder
|
||||||
|
frame = self._build_mfd_params_frame(win)
|
||||||
|
frame.pack(fill='both', expand=True, padx=6, pady=6)
|
||||||
|
win.transient(self._root)
|
||||||
|
win.lift()
|
||||||
|
except Exception:
|
||||||
|
logging.exception("Failed to open MFD controls window")
|
||||||
|
|
||||||
def show_frame(self, frame: Any) -> None:
|
def show_frame(self, frame: Any) -> None:
|
||||||
"""Queue a frame for display (thread-safe)."""
|
"""Queue a frame for display (thread-safe)."""
|
||||||
try:
|
try:
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user