SXXXXXXX_ControlPanel/app.py
VALLONGOL 1fcb8a3ead - gestione categorie su mfd
- sar con rotazione
- chiusura forzata applicazione
- applicazione lut lineare su mfd
2025-04-03 16:20:56 +02:00

354 lines
16 KiB
Python

# app.py
"""
This is the main module of the Grifo E Control Panel application.
It orchestrates the interaction between the UI, image processing, and display modules.
"""
import threading
import time
import tkinter as tk
import cv2
import numpy as np
import screeninfo
import queue
import os
import logging
import config # Import the config module
from ui import ControlPanel, StatusBar, create_main_window # Import the UI module
from image_processing import load_image, apply_brightness_contrast, apply_color_palette, normalize_image, resize_image # Import the image processing module
from display import DisplayManager # Import the display module
from utils import put_queue # Import the utils module
# Configure logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
class App:
"""
The main application class for the Grifo E Control Panel.
"""
def __init__(self, root):
"""
Initializes the App.
Args:
root (tk.Tk): The main Tkinter window.
"""
self.root = root
self.root.title("Grifo E Control Panel") # Set application title
self.root.minsize(config.TKINTER_MIN_WIDTH, config.TKINTER_MIN_HEIGHT) # Set minimum size
# --- Queues for SAR Images and Mouse Coordinates ---
self.sar_queue = queue.Queue(maxsize=5)
self.mouse_queue = queue.Queue(maxsize=20)
# --- Get Screen Information ---
screen = screeninfo.get_monitors()[0] # Use the primary screen
screen_width = screen.width
screen_height = screen.height
# --- Window Placement (Initial values) ---
self.tkinter_x = 10
self.tkinter_y = config.MFD_HEIGHT + 20 # Place Tkinter window below MFD
self.root.geometry(f"+{self.tkinter_x}+{self.tkinter_y}")
self.mfd_x = self.tkinter_x
self.mfd_y = 10
self.sar_x = self.tkinter_x + config.TKINTER_MIN_WIDTH + 20 # Place SAR window to the right of Tkinter
self.sar_y = 10
# --- Initial SAR Contrast and Brightness ---
self.sar_contrast = config.DEFAULT_SAR_CONTRAST
self.sar_brightness = config.DEFAULT_SAR_BRIGHTNESS
self.sar_palette = config.DEFAULT_SAR_PALETTE
# --- Initial SAR Size ---
self.sar_display_width = config.INITIAL_SAR_WIDTH
self.sar_display_height = config.INITIAL_SAR_HEIGHT
# --- Translation Offset (for simulation) ---
self.mfd_x_offset = 0
self.sar_x_offset = 0
# --- FPS Counter ---
self.mfd_frame_count = 0
self.mfd_start_time = time.time()
self.sar_update_time = time.time() # Time of last SAR update
self.sar_frame_count = 0
self.mfd_fps = 0.0 # Initial MFD FPS
self.sar_fps = 0.0 # Initial SAR FPS
self.last_mouse_update = time.time()
# --- Initial Image Data ---
self.mfd_image_data = load_image(config.MFD_IMAGE_PATH, expected_dtype=np.uint8)
self.sar_image_data = load_image(config.SAR_IMAGE_PATH, expected_dtype=config.SAR_DATA_TYPE)
self.current_sar = normalize_image(self.sar_image_data) # Current SAR for display
self.adjusted_sar = self.current_sar.copy() # Create a copy for contrast/brightness adjustments
# --- Window Initialization Flags ---
self.mfd_window_initialized = False
self.sar_window_initialized = False
self.sar_mouse_callback_set = False # Flag to track if the callback has been set
# --- Test Image Buffers ---
self.test_mfd_image = np.zeros((config.MFD_HEIGHT, config.MFD_WIDTH), dtype=np.uint8)
self.test_sar_image = np.zeros((config.SAR_HEIGHT, config.SAR_WIDTH), dtype=config.SAR_DATA_TYPE)
# --- LUT Initialization ---
self.brightness_contrast_lut = None # Initialize LUT
self.update_brightness_contrast_lut() # Initial LUT calculation
# --- UI Components ---
self.statusbar = StatusBar(self.root)
self.control_panel = ControlPanel(self.root, self)
# --- Initialize resized_sar ---
self.resized_sar = np.zeros((config.INITIAL_SAR_HEIGHT, config.INITIAL_SAR_WIDTH), dtype=np.uint8) # Initialize with a placeholder
# --- Display Manager ---
self.display_manager = DisplayManager(self, self.sar_queue, self.mouse_queue, self.sar_x, self.sar_y, self.mfd_x, self.mfd_y)
# --- Image Loading Thread ---
self.image_loading_thread = threading.Thread(target=self.load_images_and_generate_test_data, daemon=True)
self.image_loading_thread.start()
# --- Start Image Display ---
self.update_mfd()
self.update_sar()
self.process_sar_queue()
self.process_mouse_queue()
def load_images_and_generate_test_data(self):
"""Loads images and generates test data in a separate thread."""
self.set_status("Loading images...")
self.mfd_image_data = load_image(config.MFD_IMAGE_PATH, expected_dtype=np.uint8)
self.sar_image_data = load_image(config.SAR_IMAGE_PATH, expected_dtype=config.SAR_DATA_TYPE)
self.generate_test_images() # Generate initial test images
self.set_initial_sar_image() # Set the initial SAR image
self.update_sar() # Update the SAR display after loading
self.set_status("Ready") # Back to ready status
def set_initial_sar_image(self):
"""Sets the initial SAR image for display."""
self.current_sar = normalize_image(self.sar_image_data) # Current SAR for display
self.adjusted_sar = self.current_sar.copy() # Create a copy for contrast/brightness adjustments
self.resized_sar = resize_image(self.adjusted_sar, self.sar_display_width, self.sar_display_height)
if self.resized_sar is not None and self.resized_sar.size > 0:
print(f"Initial SAR image size: {self.resized_sar.shape}")
else:
print("Error: Resized SAR image has invalid dimensions.")
self.resized_sar = np.zeros((config.INITIAL_SAR_HEIGHT, config.INITIAL_SAR_WIDTH), dtype=np.uint8) # Provide a placeholder image
def generate_test_images(self):
"""Generates random test images for MFD and SAR."""
self.test_mfd_image = np.random.randint(0, 256, size=(config.MFD_HEIGHT, config.MFD_WIDTH), dtype=np.uint8)
self.test_sar_image = np.random.randint(0, 65536, size=(config.SAR_HEIGHT, config.SAR_WIDTH), dtype=config.SAR_DATA_TYPE).astype(
config.SAR_DATA_TYPE)
self.resized_sar = resize_image(normalize_image(self.test_sar_image), self.sar_display_width, self.sar_display_height)
if self.resized_sar is not None and self.resized_sar.size > 0:
print(f"Test SAR image size: {self.resized_sar.shape}")
else:
print("Error: Resized test SAR image has invalid dimensions.")
self.resized_sar = np.zeros((config.INITIAL_SAR_HEIGHT, config.INITIAL_SAR_WIDTH), dtype=np.uint8) # Provide a placeholder image
def update_mfd(self):
"""Updates the MFD image with translation and calculates FPS, limiting to MFD_FPS."""
try:
start_time = time.time()
if self.control_panel.test_image_var.get() == 1:
# Use test image and simulate translation
self.mfd_x_offset = (self.mfd_x_offset + 1) % config.MFD_WIDTH
translated_image = np.roll(self.test_mfd_image, -self.mfd_x_offset, axis=1)
delay = max(1, int(1000 / config.MFD_FPS)) # Target delay for MFD_FPS
else:
# Use loaded image
translated_image = self.mfd_image_data
delay = 1 # Update as fast as possible when not in test mode
self.display_manager.show_mfd_image(translated_image)
# Calculate FPS
self.mfd_frame_count += 1
elapsed_time = time.time() - self.mfd_start_time
if elapsed_time >= 1.0: # Update FPS every second
self.mfd_fps = self.mfd_frame_count / elapsed_time
self.mfd_start_time = time.time()
self.mfd_frame_count = 0
self.update_status() # Update status bar
self.root.after(delay, self.update_mfd) # Schedule the next update
except Exception as e:
print(f"Error updating MFD: {e}")
logging.exception(f"Error updating MFD: {e}")
def update_sar(self):
"""Updates the SAR image with translation."""
try:
if self.control_panel.test_image_var.get() == 1:
# Use test image and simulate translation
self.sar_x_offset = (self.sar_x_offset + 1) % self.sar_display_width
translated_image = np.roll(self.resized_sar, -self.sar_x_offset, axis=1) # shift columns
else:
# Use loaded image
translated_image = self.resized_sar
self.display_manager.show_sar_image(translated_image)
# Calculate SAR FPS
self.sar_frame_count += 1
elapsed_time = time.time() - self.sar_update_time
if elapsed_time >= 1.0:
self.sar_fps = self.sar_frame_count / elapsed_time
self.sar_update_time = time.time()
self.sar_frame_count = 0
self.update_status() # Update status bar
self.root.after(5000, self.update_sar)
except Exception as e:
print(f"Error updating SAR: {e}")
logging.exception(f"Error updating SAR: {e}")
def update_sar_size(self, event=None):
"""Updates the SAR image size based on the combobox selection."""
selected_size = self.control_panel.sar_size_combo.get()
if selected_size == "1:1":
self.sar_display_width = config.SAR_WIDTH
self.sar_display_height = config.SAR_HEIGHT
elif selected_size == "1:2":
self.sar_display_width = config.INITIAL_SAR_WIDTH
self.sar_display_height = config.INITIAL_SAR_HEIGHT
elif selected_size == "1:3":
self.sar_display_width = config.SAR_WIDTH // 3
self.sar_display_height = config.SAR_HEIGHT // 3
elif selected_size == "1:5":
self.sar_display_width = config.SAR_WIDTH // 5
self.sar_display_height = config.SAR_HEIGHT // 5
elif selected_size == "1:10":
self.sar_display_width = config.SAR_WIDTH // 10
self.sar_display_height = config.SAR_HEIGHT // 10
if self.control_panel.test_image_var.get() == 1:
self.generate_test_images() #Regenerate the test images
else:
self.set_initial_sar_image()
# Resize the adjusted image
self.resized_sar = resize_image(self.adjusted_sar, self.sar_display_width, self.sar_display_height)
print(f"New SAR size: {self.sar_display_width}x{self.sar_display_height}")
self.update_sar() # Update display
# Update SAR window position in case the size changed
self.sar_x = self.tkinter_x + config.TKINTER_MIN_WIDTH + 20 # Place SAR window to the right of Tkinter
self.sar_y = 10
def update_contrast(self, value):
"""Updates the SAR image contrast based on the slider value."""
try:
self.sar_contrast = float(value)
self.update_brightness_contrast_lut()
self.update_sar_display()
except ValueError:
pass
def update_brightness(self, value):
"""Updates the SAR image brightness based on the slider value."""
try:
self.sar_brightness = int(float(value))
self.update_brightness_contrast_lut()
self.update_sar_display()
except ValueError:
pass
def update_sar_palette(self, event=None):
"""Updates the SAR image palette based on the combobox selection."""
self.sar_palette = self.control_panel.palette_combo.get()
self.update_sar_display()
def update_brightness_contrast_lut(self):
"""Calculates the Look-Up Table (LUT) for brightness and contrast adjustment."""
self.brightness_contrast_lut = np.array([
np.clip(i * self.sar_contrast + self.sar_brightness, 0, 255).astype(np.uint8) for i in range(256)
])
def update_sar_display(self):
"""Applies contrast, brightness, and palette adjustments to the SAR image and triggers SAR update."""
# Apply contrast and brightness using LUT
if self.control_panel.test_image_var.get() == 1:
adjusted_image = apply_brightness_contrast(normalize_image(self.test_sar_image), self.sar_brightness, self.sar_contrast)
else:
adjusted_image = apply_brightness_contrast(self.current_sar, self.sar_brightness, self.sar_contrast)
# Apply color Palette
if self.sar_palette != "GRAY":
adjusted_image = apply_color_palette(adjusted_image, self.sar_palette)
self.adjusted_sar = adjusted_image.copy() # Store the adjusted image
# Resize the adjusted image
self.resized_sar = resize_image(self.adjusted_sar, self.sar_display_width, self.sar_display_height)
# Force image display update
put_queue(self.sar_queue, self.resized_sar)
def set_sar_info(self, message):
"""Updates the SAR info label in Tkinter."""
self.control_panel.sar_info_label.config(text=message)
def update_status(self, ):
"""Updates the status bar with MFD and SAR FPS (and seconds per image for SAR)."""
status_text = f"Ready | MFD FPS: {self.mfd_fps:.2f} | SAR FPS: {self.sar_fps:.2f}"
# Add seconds per image for SAR
if self.sar_fps > 0:
seconds_per_image = 1 / self.sar_fps
status_text += f" ({seconds_per_image:.2f} s/img)"
else:
status_text += " (N/A s/img)"
self.root.after(0, self.statusbar.set_status_text, status_text)
def set_status(self, message):
"""Updates the status bar."""
self.root.after(0, self.statusbar.set_status_text, message)
def set_new_sar_image(self, image):
"""Function to set a new Sar image"""
self.sar_image_data = image
self.current_sar = normalize_image(self.sar_image_data)
self.adjusted_sar = self.current_sar.copy() # Update the adjusted image too
self.resized_sar = resize_image(self.adjusted_sar, self.sar_display_width, self.sar_display_height)
# Force update to image
put_queue(self.sar_queue, self.resized_sar)
def update_mouse_latlon_label(self, latitude, longitude):
"""Updates the Tkinter label with the mouse latitude and longitude."""
self.control_panel.set_mouse_coordinates(latitude, longitude)
def process_sar_queue(self):
"""This function get the new Sar image from queue and displays"""
self.display_manager.process_sar_queue()
self.root.after(1, self.process_sar_queue) # Loop in the main Thread
def process_mouse_queue(self):
"""Processes the mouse coordinate queue and updates the Tkinter label."""
self.display_manager.process_mouse_queue()
self.root.after(1, self.process_mouse_queue)
def update_image_mode(self):
"""Updates the image mode based on the checkbox value and reload images if needed."""
if self.control_panel.test_image_var.get() == 1:
self.generate_test_images() # Generate test images when test mode is enabled
else:
self.set_initial_sar_image() # Reset the SAR image
self.update_mfd()
self.update_sar()
if __name__ == "__main__":
root = create_main_window("Grifo E Control Panel", config.TKINTER_MIN_WIDTH, config.TKINTER_MIN_HEIGHT, 10, config.MFD_HEIGHT + 20)
app = App(root)
root.mainloop()
cv2.destroyAllWindows()