354 lines
16 KiB
Python
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() |