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