Source code for GUIBRUSHR.GUI.Input_Output_Panels.Input_Panels.TabPanels.FrameGenerationHRData.FrameGenerationHRData

"""
High-Resolution Spectroscopy generation GUI Panel

This module provides a comprehensive graphical user interface for configuring and
executing synthetic high-resolution spectroscopic observations of exoplanets.

The GUI panel allows users to:
- Configure target parameters and observational setup
- Set atmospheric conditions and instrumental parameters  
- Control PWV (precipitable water vapor) modeling
- Select radiation transfer modes and cross-correlation models
- Execute dataset generations with real-time progress monitoring
- View diagnostic plots and results

Classes:
    UpdatableImage: Dynamic image widget for displaying generation results
    FrameGenerationHRData: Main GUI panel for HR generation configuration

Functions:
    ask_stellar_params: Convenience wrapper for stellar parameter dialog
"""

import pickle
import subprocess
from pathlib import Path
from tkinter import messagebox
import tkinter as tk

from GUIBRUSHR.GUI.DataInterface.DataInterface import get_target_nights, get_target_instruments, get_target_list, \
    get_list_cross_corr_models, get_target_info
from GUIBRUSHR.GUI.DataInterface.DBSQLite3 import DBSQLite3
from GUIBRUSHR.GUI.Input_Output_Panels.Input_Panels.TabPanels.FrameGenerationHRData.shuffle_HR import shuffle_HR
from GUIBRUSHR.GUI.Input_Output_Panels.Input_Panels.TabPanels.FrameGenerationHRData.MathematicalFunctions import MathematicalFunctions
from GUIBRUSHR.GUI.LAYOUT.MyPanel import MyPanel
from GUIBRUSHR.GUI.LAYOUT.MyTabPanel import MyTabPanel
from GUIBRUSHR.GUI.LAYOUT.ScaleManager import ScaleManager
from GUIBRUSHR.GUI.WIDGET.MyButton import MyButton
from GUIBRUSHR.GUI.WIDGET.MyCheckBox import MyCheckBox
from GUIBRUSHR.GUI.WIDGET.MyDropDown import MyDropdown
from GUIBRUSHR.GUI.WIDGET.MyEntry import MyEntry
from GUIBRUSHR.GUI.WIDGET.MyImage import MyImage
from GUIBRUSHR.GUI.WIDGET.MyLabel import MyLabel
from GUIBRUSHR.GUI.WIDGET.MyTextField import MyTextField
from GUIBRUSHR.GUI.WIDGET.MyPDFWindow import MyPDFWindow
from GUIBRUSHR.General_Constants.Classes.Target import Target
from GUIBRUSHR.General_Constants.Classes.HelpButton import HelpButton
from GUIBRUSHR.General_Constants.FunctionsAndConstants.Constant_Variables import ConstantVariables
from GUIBRUSHR.General_Constants.FunctionsAndConstants.general_functions import create_path_night, \
    download_star_spectrum


# ----------------------------------------------------------------------
# Custom updatable image widget
# ----------------------------------------------------------------------
[docs] class UpdatableImage(MyImage): """ A specialized image widget that supports dynamic updating of displayed images. This class extends MyImage to provide the ability to change the displayed image at runtime, which is useful for showing updated plots, diagnostic images, or simulation results without recreating the widget. Inherits from MyImage and maintains all its functionality while adding dynamic update capabilities through PIL/Pillow image processing. """
[docs] def __init__(self, parent: tk.Widget, row: int, column: int, width: int, height: int, initial_path: str, **kwargs) -> None: """ Initialize the updatable image widget. Parameters ---------- parent : tk.Widget Parent widget container row : int Row position in the parent's grid layout column : int Column position in the parent's grid layout width : int Desired image width in pixels height : int Desired image height in pixels initial_path : str Path to the initial image file to display **kwargs Additional keyword arguments passed to MyImage parent class Notes ----- The widget automatically resizes images to fit the specified dimensions while maintaining aspect ratio handling as configured in the parent class. """ super().__init__(parent, row, column, width, height, initial_path, **kwargs)
[docs] def update_image(self, new_path: str) -> None: """ Update the displayed image with a new image file. Dynamically loads and displays a new image while preserving the widget's position and configuration. The new image is automatically resized to match the widget's dimensions. Parameters ---------- new_path : str Path to the new image file to display. Supports common formats including PNG, JPEG, GIF, BMP, and TIFF. Raises ------ FileNotFoundError If the specified image file cannot be found PIL.UnidentifiedImageError If the file is not a valid image format Exception For other image loading or display errors Notes ----- The method handles both canvas-based and label-based image display mechanisms automatically. A reference to the new image is maintained to prevent garbage collection. """ try: from PIL import Image, ImageTk # Create new PhotoImage new_image = ImageTk.PhotoImage( Image.open(new_path).resize((int(self.width), int(self.height))) ) # Update the image if hasattr(self, 'canvas'): # Using canvas self.canvas.delete("all") self.canvas.create_image(0, 0, anchor=tk.NW, image=new_image) self.image = new_image # Keep reference else: # Using label - need to find the label widget for child in self.winfo_children(): if isinstance(child, tk.Label): child.configure(image=new_image) child.image = new_image # Keep reference break self.image = new_image except Exception as e: print(f"Error updating image: {e}")
[docs] class FrameGenerationHRData(MyPanel): """ Comprehensive GUI panel for configuring and executing high-resolution spectroscopy generations. This panel provides a complete user interface for setting up synthetic exoplanet observations with full control over: **Core Configuration:** - Target selection and orbital parameters - Instrument selection and spectral orders - Radiation transfer modes (transmission/emission) - Cross-correlation model selection **Atmospheric Modeling:** - PWV (precipitable water vapor) functions and time series - Observatory parameters and atmospheric conditions - Telluric absorption modeling with SkyCalc integration - Airmass calculations and observational coordinates **Advanced Options:** - Wavelength coverage and resolution settings - Stellar parameter configuration via dialogs - Discontinuous vs continuous PWV modeling - Real-time simulation monitoring and progress tracking **Output Management:** - FITS file generation with proper headers - Diagnostic plot creation and PDF viewing - Result visualization and quality assessment - Background process management for long generations The panel integrates with the broader GUIBRUSHR framework and maintains compatibility with standard astronomical data reduction pipelines. Attributes ---------- synthetic_process : subprocess.Popen or None Background process handle for running generations synthetic_running : bool Flag indicating if a generation is currently executing math_functions : MathematicalFunctions Utility class for PWV and atmospheric function calculations """
[docs] def __init__( self, parent: tk.Widget, color: str, row: int, column: int, path_default: str, frame_input, window: tk.Toplevel, width_GUI: int, height_frame: int, **kwargs ) -> None: """ Initialize the FrameGenerationHRData panel with all UI components and configurations. Parameters ---------- parent : tk.Widget Parent widget container for this panel color : str Background color for the panel (hex color code or named color) row : int Row position in the parent widget's grid layout column : int Column position in the parent widget's grid layout path_default : str Root path to GUIBRUSHR directory structure containing models, telluric grids, and configuration files frame_input : FrameInput Input frame object containing target folder path and related configuration information window : tk.Toplevel Main application window for parent references and modal dialogs width_GUI : int Total width of the GUI in pixels for layout calculations height_frame : int Height allocated for this frame in pixels **kwargs : dict Additional keyword arguments passed to MyPanel parent class Notes ----- The initialization process: 1. Sets up all UI component references and instance variables 2. Loads initial target list from the specified folder 3. Initializes dropdown menus with available options 4. Creates atmospheric parameter panels and controls 5. Sets up background process monitoring 6. Configures mathematical function utilities The panel automatically adapts to available targets and instruments found in the specified data directory structure. """ super().__init__(parent, color, row, column, **kwargs) # === Core UI component references === self.target_information = None self.dropdown_observatory_coords = None self.terminator_night = None # Night termination handler self.dropdown_target = None # Target selection dropdown self.dropdown_hr_instrument = None # HR instrument dropdown self.list_hr_instrument = None # Available HR instruments list self.dropdown_cross_corr = None # Cross-correlation model dropdown # === Framework and window references === self.parent = parent self.color = color self.path_folder_targets = frame_input.frame_path.path_folder_targets.get_text() self.window = window self.path_default = path_default self.frame_input = frame_input self.width_GUI = width_GUI self.height_frame = height_frame # === File and path management variables === self.folder_retrieval = None # Folder retrieval settings self.prefix_file = None # File prefix for outputs self.underscore = None # Underscore formatting option self.p = None # Processing parameter self.filepng = None # PNG file path for plots self.lista_parametri = None # Parameter list storage self.path_imm = None # Image path reference self.last_plot_reduced = None # Last reduced plot reference # === Background process management === self.synthetic_process = None # Subprocess handle for generations self.synthetic_running = False # generation execution flag # === Mathematical function utilities === self.math_functions = MathematicalFunctions() # === Initialize UI components === # Load available targets from data directory target_list = get_target_list(self.path_folder_targets) # Set up all UI panels and controls self._initialize_ui_components(target_list) self._initialize_atmospheric_parameters() # Global exception handling flag self.global_exc = 1
def _initialize_ui_components(self, target_list: list) -> None: """ Initialize all primary UI components with their default values and layout. Creates the main control panels and dropdown menus for target selection, instrument configuration, and basic generation parameters. This method establishes the foundation UI elements that other initialization methods build upon. Parameters ---------- target_list : list List of available target names loaded from the data directory Notes ----- The initialization order is important as later dropdowns depend on selections made in earlier ones. The method creates: 1. Main controls panel with light blue background 2. Target selection dropdown with auto-population 3. Instrument selection based on first target 4. Nights text field with available observation nights 5. Radiation transfer mode selection """ # Create main panel container with light blue background for visual separation self.main_controls_panel = MyPanel(self, "#F0F8FF", 0, 1, columnspan=12, rowspan=3) # Help button for main controls help_text_main = { "Target": "Select the exoplanet target for which to generate simulated high-resolution spectra. The target must be previously configured in the database with all orbital and physical parameters.", "Nights": "Observation dates to simulate, in YYYY-MM-DD format. Multiple nights can be comma-separated (e.g., '2023-05-15,2023-05-16'). Each night generates a separate simulated dataset with time-dependent atmospheric conditions.", "Rad. Transf. Mode": "Radiative transfer mode: 'Transmission' simulates primary transit observations (planetary atmosphere backlit by star, showing absorption features). 'Emission' simulates secondary eclipse observations (planetary thermal emission and reflected starlight).", "HR Instrument": "High-resolution spectrograph to simulate. The instrument's resolving power, wavelength coverage, and line spread function will be applied to the synthetic spectra to match real observational characteristics.", "Model CC": "Atmospheric model for the planetary spectrum to inject. Select from pre-computed petitRADTRANS models in the target's Models directory. Different models represent various atmospheric compositions, temperature profiles, or cloud properties.", "Order Selection": "Mainly used for internal file structure: Specify which spectral orders to search.", "Tell rm method": "Mainly used for internal file structure: Telluric removal algorithm." } HelpButton( self.main_controls_panel, 0, 0, "Main Controls Help", help_text_main, columnspan=1 ) # Target selection dropdown self.dropdown_target = MyDropdown(self.main_controls_panel, 0, 1, target_list, "Target:", color=self.main_controls_panel.cget("bg"), columnspan=2) # Get initial instrument list and nights self.list_hr_instrument = get_target_instruments( self.path_folder_targets, target_list[0], "HR", "Transmission", Path(__file__).name ) nights = get_target_nights( self.path_folder_targets, target_list[0], "Transmission", self.list_hr_instrument[0], 0 ) # Nights text field self.textfield_nights = MyTextField(self.main_controls_panel, 0, 3, nights, "Nights:", color=self.main_controls_panel.cget("bg"), columnspan=4, width=80) # Radiation transfer mode dropdown self.dropdown_rad_mode = MyDropdown( self.main_controls_panel, 0, 7, ConstantVariables.LIST_RAD_MODE, "Rad. Transf. Mode:", initial_value=0, color=self.main_controls_panel.cget("bg"), columnspan=2 ) # HR instrument dropdown self.dropdown_hr_instrument = MyDropdown( self.main_controls_panel, 0, 9, self.list_hr_instrument, "HR Instrument:", color=self.main_controls_panel.cget("bg"), columnspan=2 ) # Cross-correlation model dropdown dict_list_cc = get_list_cross_corr_models(self.path_folder_targets, target_list[0], "Transmission") self.dropdown_cross_corr = MyDropdown( self.main_controls_panel, 0, 11, list(sorted(dict_list_cc.keys())), "Model CC", initial_value=0, color=self.main_controls_panel.cget("bg"), columnspan=2 ) # Order selection text field self.textfield_order_selection = MyTextField( self.main_controls_panel, 1, 1, "full", "Order Selection \n(only for shuffle):", color=self.main_controls_panel.cget("bg"), columnspan=3, width=20 ) # Telluric removal method dropdown self.dropdown_tell_rm_method = MyDropdown( self.main_controls_panel, 1, 4, ConstantVariables.LIST_TELL_TABLE, "Tell rm method \n(only for shuffle):", color=self.main_controls_panel.cget("bg"), columnspan=2 ) # Set up callbacks self.dropdown_hr_instrument.set_callback(self.update_nights) self.dropdown_rad_mode.set_callback(self.update_from_target_or_rad_mode) self.dropdown_target.set_callback(self.update_from_target_or_rad_mode) def _initialize_atmospheric_parameters(self): """ Initialize atmospheric parameter panels and widgets using tab structure. Creates a tabbed interface for organizing atmospheric and observational parameters. Sets up PWV parameters panel and creates additional panels for atmospheric, observational, wavelength, observatory, and coordinates parameters within the tab structure. """ # Create tab manager for organizing atmospheric and observational parameters self.param_tab_manager = MyTabPanel( parent=self, tab_list=ConstantVariables.list_tab_simulated_hr, row=4, column=4, width=int(self.width_GUI / 2), rowspan=15, ) # First tab - Basic Configuration with PWV parameters and generation controls # Create a panel inside the first tab for PWV parameters self.pwv_panel = MyPanel(self.param_tab_manager.tab_list[0], "#E8F4FD", 0, 1, columnspan=10, rowspan=3) # Help button for PWV parameters help_text_pwv = { "dt": "Time step in seconds for PWV evolution. Smaller values create more detailed temporal variations. Recommended: 1 second for high-fidelity simulations.", "pace": "PWV correlation timescale parameter in seconds.", "s": "Shape parameter for the PWV distribution function. Controls the steepness of PWV variations. Typical values: 0.5-1.0. Default: 0.69598.", "scale": "cale parameter for the random component, by default 0.69598. Controls the amplitude of PWV variations.", "tExp": "Exposure time in seconds for each spectral frame. Used to average PWV over the exposure duration, simulating realistic integration effects. Typical: 300-3600 seconds.", "Seed": "Random number generator seed for reproducible PWV time series. Using the same seed with identical parameters produces identical PWV variations. Set to any integer for reproducibility." } HelpButton( self.pwv_panel, 0, 0, "PWV Parameters Help", help_text_pwv, columnspan=1 ) # Header for PWV parameters MyLabel( self.pwv_panel, 0, 1, color=self.pwv_panel.cget("bg"), label_text="PWV PARAMETERS", font=ScaleManager.get().font_label_large if ScaleManager.get() else ("Sans", 12, "bold"), columnspan=10 ) # PWV parameter entry widgets arranged in a single row self.entry_dt = MyEntry( self.pwv_panel, 1, 1, "1", label_text="dt:", color=self.pwv_panel.cget("bg"), columnspan=2 ) self.entry_pace = MyEntry( self.pwv_panel, 1, 3, "6e5", label_text="pace:", color=self.pwv_panel.cget("bg"), columnspan=2 ) self.entry_s = MyEntry( self.pwv_panel, 1, 5, "0.69598", label_text="s:", color=self.pwv_panel.cget("bg"), columnspan=2 ) self.entry_scale = MyEntry( self.pwv_panel, 1, 7, "3.0862", label_text="scale:", color=self.pwv_panel.cget("bg"), columnspan=2 ) self.entry_tExp = MyEntry( self.pwv_panel, 1, 9, "1800", label_text="tExp:", color=self.pwv_panel.cget("bg"), columnspan=2 ) # Add seed entry parameter for reproducible random number generation self.entry_seed = MyEntry( self.pwv_panel, 1, 11, "42", label_text="Seed:", color=self.pwv_panel.cget("bg"), columnspan=2 ) # Create generation parameters panel under PWV panel in first tab self.generation_params_panel = MyPanel(self.param_tab_manager.tab_list[0], "#F0F8FF", 3, 1, columnspan=10, rowspan=2) # Help button for generation parameters help_text_sim = { "Gain": "Detector gain factor (electrons/ADU). Multiplies the photon counts to convert to detector units. Typical CCD gains: 1-5.", "Error Type": "'Error from reduction': Uses real error estimates from actual data reduction. 'Synthetic error': Generates theoretical noise based on photon statistics. Use synthetic for idealized generations.", "Noise Factor": "Multiplicative factor for error bars. Increases (>1) or decreases (<1) the noise level relative to theoretical/measured values. Use to simulate better/worse observing conditions. Default: 1 (realistic noise).", "Save Plots": "When enabled, generates and saves diagnostic plots during generation including. Useful for quality assessment but increases processing time.", "Include 0.1 PWV": "When enabled, adds an additional simulated night with very low PWV (0.1 mm), representing exceptionally dry conditions. This value should not be used due to possible errors from the original skycalc simulator." } HelpButton( self.generation_params_panel, 0, 0, "Generation Parameters Help", help_text_sim, columnspan=1 ) # Header for generation parameters MyLabel( self.generation_params_panel, 0, 1, color=self.generation_params_panel.cget("bg"), label_text="GENERATIONS PARAMETERS", font=ScaleManager.get().font_label_large if ScaleManager.get() else ("Sans", 12, "bold"), columnspan=10 ) # Gain entry - Controls the gain factor for the generation self.entry_gain = MyEntry( self.generation_params_panel, 1, 1, "1.0", "Gain:", color=self.generation_params_panel.cget("bg"), columnspan=2 ) # Error type dropdown - Controls the type of error generation self.dropdown_error_type = MyDropdown( self.generation_params_panel, 1, 3, ["Error from reduction", "Synthetic error"], "Error Type:", color=self.generation_params_panel.cget("bg"), columnspan=2 ) # Noise factor entry - Controls the amplitude of synthetic noise self.entry_noise_factor = MyEntry( self.generation_params_panel, 1, 5, "1", "Noise Factor:", color=self.generation_params_panel.cget("bg"), columnspan=2 ) # Checkboxes self.checkbox_save_plots = MyCheckBox( self.generation_params_panel, 1, 7, "Save Plots:" ) self.checkbox_include_0_1 = MyCheckBox( self.generation_params_panel, 1, 9, "Include 0.1 PWV:" ) # Second tab contains the advanced parameters with a nested tab manager # Create a nested tab manager inside the "Advanced Parameters" tab self.advanced_tab_manager = MyTabPanel( parent=self.param_tab_manager.tab_list[1], tab_list=ConstantVariables.list_tab_simulated_hr_advanced, row=0, column=0, width=int(self.width_GUI / 2), rowspan=15, ) # Create panels within their respective tabs in the advanced tab manager self.atm_panel = MyPanel(self.advanced_tab_manager.tab_list[0], "#E8F4FD", 0, 0, columnspan=15, rowspan=15) self.obs_panel = MyPanel(self.advanced_tab_manager.tab_list[1], "#FDF4E8", 0, 0, columnspan=15, rowspan=15) self.wl_panel = MyPanel(self.advanced_tab_manager.tab_list[2], "#E8FDF4", 0, 0, columnspan=15, rowspan=15) self.observatory_panel = MyPanel(self.advanced_tab_manager.tab_list[3], "#F4E8FD", 0, 0, columnspan=15, rowspan=15) # Keep coordinates panel in main frame for now self.coordinates_panel = MyPanel(self, "#FDE8F4", 4, 5, columnspan=8, rowspan=2) # Initialize each panel self._initialize_atmospheric_panel() self._initialize_observational_panel() self._initialize_wavelength_panel() self._initialize_observatory_panel() self._initialize_coordinates_panel() # Add generation controls (dropdown and run button) in main frame self._initialize_generation_controls() def _initialize_generation_controls(self): """ Initialize generation mode dropdown and run button in main frame. Creates the generation mode selection and run button that need to stay in the main frame for easy access. """ # Create a new panel for generation controls in main frame self.main_generation_controls_panel = MyPanel(self, "#F0F8FF", 6, 5, columnspan=8, rowspan=1) # Help button for generation controls help_text_sim_ctrl = { "Generation Mode": "Select the generation type:\n\n'Synthetic': Generates fully synthetic data with continuous PWV evolution using the specified PWV function. Creates realistic time-varying telluric absorption.\n\n'Synthetic Discontinuous PWV': Similar to Synthetic but with discontinuous PWV jumps between exposures instead of smooth evolution. Simpler but less realistic.\n\n'Shuffle': Injects planetary signal into existing reduced observational data where each phases is shuffled. Requires real data to already exist for the selected target/night/instrument combination." } HelpButton( self.main_generation_controls_panel, 0, 0, "Generation Mode Help", help_text_sim_ctrl, columnspan=1 ) # Generation mode dropdown self.dropdown_generation_mode = MyDropdown( self.main_generation_controls_panel, 0, 1, ["Synthetic", "Synthetic Discontinuous PWV", "Shuffle"], "Generation Mode:", color=self.main_generation_controls_panel.cget("bg"), columnspan=3 ) # Run button self.button_run = MyButton( self.main_generation_controls_panel, 0, 4, 'RUN', '#4CAF50', self.run_selected_generation, color_panel=self.main_generation_controls_panel.cget("bg"), columnspan=1, sticky="we" ) def _initialize_atmospheric_panel(self): """ Initialize atmospheric parameters panel. Creates the atmospheric parameters section with PWV functions, airmass settings, atmospheric conditions, and PWV image display. Includes validation information and parameter organization. """ # Help button for atmospheric parameters help_text_atm = { "Airmass": "Air mass of the observation. -999: retrieved from almanac (computed automatically based on target position and time). 0: calculated by SkyCalc algorithm. Positive values: constant airmass throughout the night. Typical values: 1.0 (zenith) to 2.5 (low altitude).", "PWV Function": "Mathematical function describing precipitable water vapor (PWV) time evolution. Different functions simulate various atmospheric conditions and temporal variability patterns. PWV affects telluric absorption strength, particularly water vapor lines.", "Monthly Solar Flux": "Monthly average solar radio flux at 10.7 cm (F10.7 index) in solar flux units (sfu). -999: retrieved from almanac for the observation date. Values typically range 50-300 sfu. Affects upper atmospheric ionization and airglow emission.", "PWV Mode": "'pwv': Use the selected PWV function for temporal evolution. 'season': Use seasonal average PWV value appropriate for the observatory and observation date. PWV mode determines how atmospheric water vapor is modeled.", "Season": "Season code for seasonal PWV modeling (0-3). Only relevant if 'PWV Mode' is set to 'season'. Values: 0=Winter, 1=Spring, 2=Summer, 3=Fall. Different seasons have characteristic PWV ranges.", "Time": "Time offset in hours from midnight for PWV function evaluation. Used to set the starting point of the PWV time series. Typically 0 to start at midnight UTC.", "Initial X": "Initial phase value for PWV function (in degrees or radians depending on function). Defines the starting point of the PWV evolution curve. Used to offset the PWV time series.", "Final X": "Final phase value for PWV function (in degrees or radians). Defines the ending point of the PWV evolution. Together with Initial X, sets the range of PWV variation to explore." } HelpButton( self.atm_panel, 0, 0, "Atmospheric Parameters Help", help_text_atm, columnspan=1 ) # Section header MyLabel( self.atm_panel, 0, 1, color=self.atm_panel.cget("bg"), label_text="ATMOSPHERIC PARAMETERS", font=ScaleManager.get().font_label_large if ScaleManager.get() else ("Sans", 12, "bold"), columnspan=10 ) # Airmass info label MyLabel( self.atm_panel, 1, 1, color=self.atm_panel.cget("bg"), label_text="Airmass = -999 means retrieved by almanac, 0 means calculated by algorithm, other values are constant along the night", font=ScaleManager.get().font_label_italic if ScaleManager.get() else ("Sans", 9, "italic"), columnspan=10 ) # Parameters arranged in a 2-column layout for better readability # First row of parameters self.entry_airmass = MyEntry( self.atm_panel, 2, 1, "-999", label_text="Airmass:", color=self.atm_panel.cget("bg"), columnspan=3 ) self.dropdown_pwv = MyDropdown( self.atm_panel, 2, 4, self.math_functions.list_functions(), "PWV Function:", color=self.atm_panel.cget("bg"), columnspan=3 ) self.dropdown_pwv.set_callback(self.update_pwv_image) # Second row of parameters self.entry_msolflux = MyEntry( self.atm_panel, 3, 1, "-999", label_text="Monthly Solar Flux:", color=self.atm_panel.cget("bg"), columnspan=3 ) self.dropdown_pwv_mode = MyDropdown( self.atm_panel, 3, 4, ["pwv", "season"], "PWV Mode:", color=self.atm_panel.cget("bg"), columnspan=3 ) # Third row of parameters self.entry_season = MyEntry( self.atm_panel, 4, 1, "0", label_text="Season:", color=self.atm_panel.cget("bg"), columnspan=3 ) self.entry_time = MyEntry( self.atm_panel, 4, 4, "0", label_text="Time:", color=self.atm_panel.cget("bg"), columnspan=3 ) # Fourth row - new X-axis range parameters self.entry_initial_x = MyEntry( self.atm_panel, 5, 1, "-90", label_text="Initial X:", color=self.atm_panel.cget("bg"), columnspan=3 ) self.entry_final_x = MyEntry( self.atm_panel, 5, 4, "-75", label_text="Final X:", color=self.atm_panel.cget("bg"), columnspan=3 ) # PWV Image display positioned below parameters for better layout default_image_path = Path( self.path_default, "GUIBRUSHR", "Files", "pwv_functions_plots", self.math_functions.list_functions()[0] + ".png" ) self.pwv_image = UpdatableImage( self.atm_panel, 6, 1, 500, 350, str(default_image_path), columnspan=6, rowspan=4, label=False ) def _initialize_observational_panel(self): """ Initialize observational parameters panel. Sets up moon parameters, light source controls, coordinate settings, atmospheric emission options, and thermal emission parameters. Provides information about parameter retrieval methods. """ # Help button for observational parameters help_text_obs = { "Include Moon": "Y: Include scattered moonlight in sky background. N: Exclude moon contribution. Scattered moonlight significantly increases sky background, especially in optical wavelengths.", "Moon-Sun Sep [deg]": "Angular separation between Moon and Sun in degrees. -999: computed from almanac. Range 0-180°. Determines lunar phase (0°=New Moon, 180°=Full Moon). Full moon has maximum scattered light.", "Moon-Target Sep [deg]": "Angular separation between Moon and target in degrees. -999: computed from almanac. Closer moon increases scattered light contamination in target spectra.", "Moon Altitude [deg]": "Moon altitude above horizon in degrees. -999: computed from almanac. Higher moon increases sky brightness. Negative values mean moon is below horizon.", "Moon-Earth Distance": "Moon-Earth distance in Earth radii or AU. -999: computed from almanac. Affects apparent lunar brightness (closer = brighter).", "Include Starlight": "Y: Include scattered starlight background. N: Exclude. Integrated starlight contributes to diffuse sky background, particularly important in dark sky conditions.", "Include Zodiacal": "Y: Include zodiacal light (sunlight scattered by interplanetary dust). N: Exclude. Most prominent at ecliptic, contributes to sky background particularly in infrared.", "Ecliptic Longitude [deg]": "Ecliptic longitude of target in degrees. -999: computed from target coordinates. Used for zodiacal light calculation. Range 0-360°.", "Ecliptic Latitude [deg]": "Ecliptic latitude of target in degrees. -999: computed from target coordinates. Zodiacal light peaks at ecliptic plane (latitude = 0°).", "Lower Atmosphere": "Y: Include lower atmosphere emission (troposphere). N: Exclude. Lower atmospheric emission dominates in mid-infrared, particularly from H2O and CO2.", "Upper Atmosphere": "Y: Include upper atmosphere/stratosphere emission. N: Exclude. Contributes thermal emission and scattered sunlight at high altitudes.", "Airglow Continuum": "Y: Include airglow continuum emission. N: Exclude. Airglow from atmospheric chemiluminescence adds diffuse background, particularly in optical/NIR.", "Thermal Emission": "Y: Include telescope/instrument thermal emission. N: Exclude (default). Thermal emission from warm surfaces dominates background in mid-infrared.", "Therm T1/T2/T3 [K]": "Temperatures in Kelvin for thermal emission components. Multiple blackbody components model telescope optics, enclosure, and ambient surfaces at different temperatures.", "Therm E1/E2/E3": "Emissivities (0-1) for each thermal component. Product of emissivity and solid angle subtended. Higher values = stronger thermal contribution." } HelpButton( self.obs_panel, 0, 0, "Observational Parameters Help", help_text_obs, columnspan=1 ) # Section header MyLabel( self.obs_panel, 0, 1, color=self.obs_panel.cget("bg"), label_text="OBSERVATIONAL PARAMETERS", font=ScaleManager.get().font_label_large if ScaleManager.get() else ("Sans", 12, "bold"), columnspan=12 ) # Info label about -999 values MyLabel( self.obs_panel, 1, 1, color=self.obs_panel.cget("bg"), label_text="Parameters with value -999 are retrieved by the almanac. If you want to use a constant value, set it.", font=ScaleManager.get().font_label_italic if ScaleManager.get() else ("Sans", 9, "italic"), columnspan=12 ) # Moon parameters section (reorganized for better readability) self.dropdown_incl_moon = MyDropdown( self.obs_panel, 2, 1, ["Y", "N"], "Include Moon:", color=self.obs_panel.cget("bg"), columnspan=2 ) self.entry_moon_sun_sep = MyEntry( self.obs_panel, 2, 3, "-999", label_text="Moon-Sun Sep [deg]:", color=self.obs_panel.cget("bg"), columnspan=3 ) self.entry_moon_target_sep = MyEntry( self.obs_panel, 2, 6, "-999", label_text="Moon-Target Sep [deg]:", color=self.obs_panel.cget("bg"), columnspan=3 ) # Moon parameters continued self.entry_moon_alt = MyEntry( self.obs_panel, 3, 1, "-999", label_text="Moon Altitude [deg]:", color=self.obs_panel.cget("bg"), columnspan=4 ) self.entry_moon_earth_dist = MyEntry( self.obs_panel, 3, 5, "-999", label_text="Moon-Earth Distance:", color=self.obs_panel.cget("bg"), columnspan=4 ) # Light sources section self.dropdown_incl_starlight = MyDropdown( self.obs_panel, 4, 1, ["Y", "N"], "Include Starlight:", color=self.obs_panel.cget("bg"), columnspan=2 ) self.dropdown_incl_zodiacal = MyDropdown( self.obs_panel, 4, 3, ["Y", "N"], "Include Zodiacal:", color=self.obs_panel.cget("bg"), columnspan=2 ) # Ecliptic coordinates self.entry_ecl_lon = MyEntry( self.obs_panel, 5, 1, "-999", label_text="Ecliptic Longitude [deg]:", color=self.obs_panel.cget("bg"), columnspan=4 ) self.entry_ecl_lat = MyEntry( self.obs_panel, 5, 5, "-999", label_text="Ecliptic Latitude [deg]:", color=self.obs_panel.cget("bg"), columnspan=4 ) # Atmospheric emission section self.dropdown_incl_loweratm = MyDropdown( self.obs_panel, 6, 1, ["Y", "N"], "Lower Atmosphere:", color=self.obs_panel.cget("bg"), columnspan=3 ) self.dropdown_incl_upperatm = MyDropdown( self.obs_panel, 6, 4, ["Y", "N"], "Upper Atmosphere:", color=self.obs_panel.cget("bg"), columnspan=3 ) self.dropdown_incl_airglow = MyDropdown( self.obs_panel, 7, 1, ["Y", "N"], "Airglow Continuum:", color=self.obs_panel.cget("bg"), columnspan=3 ) # Thermal emission section self.dropdown_incl_therm = MyDropdown( self.obs_panel, 8, 1, ["Y", "N"], "Thermal Emission:", initial_value=1, color=self.obs_panel.cget("bg"), columnspan=3 ) # Thermal component 1 self.entry_therm_t1 = MyEntry( self.obs_panel, 9, 1, "0.0", label_text="Therm T1 [K]:", color=self.obs_panel.cget("bg"), columnspan=3 ) self.entry_therm_e1 = MyEntry( self.obs_panel, 9, 4, "0.0", label_text="Therm E1:", color=self.obs_panel.cget("bg"), columnspan=3 ) # Thermal component 2 self.entry_therm_t2 = MyEntry( self.obs_panel, 10, 1, "0.0", label_text="Therm T2 [K]:", color=self.obs_panel.cget("bg"), columnspan=3 ) self.entry_therm_e2 = MyEntry( self.obs_panel, 10, 4, "0.0", label_text="Therm E2:", color=self.obs_panel.cget("bg"), columnspan=3 ) # Thermal component 3 self.entry_therm_t3 = MyEntry( self.obs_panel, 11, 1, "0.0", label_text="Therm T3 [K]:", color=self.obs_panel.cget("bg"), columnspan=3 ) self.entry_therm_e3 = MyEntry( self.obs_panel, 11, 4, "0.0", label_text="Therm E3:", color=self.obs_panel.cget("bg"), columnspan=3 ) def _initialize_wavelength_panel(self): """ Initialize wavelength parameters panel. Configures wavelength coverage settings, resolution parameters, grid mode selection, and Line Spread Function (LSF) options. """ # Help button for wavelength parameters help_text_wl = { "Vacuum/Air": "'vac': Wavelengths in vacuum. 'air': Wavelengths in air (corrected for refractive index). Most high-resolution spectrographs work in vacuum, low-resolution often in air wavelengths.", "Min Wavelength [nm]": "Minimum wavelength for telluric spectrum calculation in nanometers. Defines blue edge of spectral coverage. Example: 300 nm (UV) to 950 nm (optical/NIR boundary).", "Max Wavelength [nm]": "Maximum wavelength for telluric calculation in nanometers. Defines red edge of spectral coverage. Example: 2500 nm covers optical + NIR up to K-band.", "Wavelength Resolution Multiplier": "Multiplier for spectral resolution when using 'fixed_spectral_resolution' mode. Higher values = finer wavelength sampling. Typical: 2-10. Increases computation time linearly.", "Grid Mode": "'fixed_spectral_resolution': Constant R = λ/Δλ (like echelle spectrographs). 'fixed_wavelength_step': Constant Δλ spacing (like grating spectrographs). Choose based on instrument type.", "Wavelength Delta [nm]": "Wavelength step size in nanometers for 'fixed_wavelength_step' mode. Determines wavelength sampling. Smaller values = higher sampling but longer computation. Typical: 0.01-0.1 nm.", "LSF Type": "Line Spread Function type. 'none': No instrumental broadening (infinite resolution). 'Gaussian': Symmetric Gaussian profile (most spectrographs). 'Boxcar': Rectangular profile (for some imaging spectrographs).", "LSF Gauss FWHM": "Full Width at Half Maximum of Gaussian LSF in wavelength units. Defines instrumental resolution broadening. Must match instrument specifications. Only used if LSF Type = 'Gaussian'.", "LSF Boxcar FWHM": "Full width of boxcar (rectangular) LSF in wavelength units. Simulates pixel-limited resolution. Only used if LSF Type = 'Boxcar'." } HelpButton( self.wl_panel, 0, 0, "Wavelength Parameters Help", help_text_wl, columnspan=1 ) # Section header MyLabel( self.wl_panel, 0, 1, color=self.wl_panel.cget("bg"), label_text="WAVELENGTH PARAMETERS", font=ScaleManager.get().font_label_large if ScaleManager.get() else ("Sans", 12, "bold"), columnspan=10 ) # Grid mode info label MyLabel( self.wl_panel, 1, 1, color=self.wl_panel.cget("bg"), label_text="For 'fixed_spectral_resolution' use 'Wavelength Resolution Multiplier', otherwise use 'Wavelength Delta [nm]'", font=ScaleManager.get().font_label_italic if ScaleManager.get() else ("Sans", 9, "italic"), columnspan=10 ) # Wavelength coverage section self.dropdown_vacair = MyDropdown( self.wl_panel, 2, 1, ["vac", "air"], "Vacuum/Air:", color=self.wl_panel.cget("bg"), columnspan=2 ) self.entry_wmin = MyEntry( self.wl_panel, 2, 3, "300.0", label_text="Min Wavelength [nm]:", color=self.wl_panel.cget("bg"), columnspan=3 ) self.entry_wmax = MyEntry( self.wl_panel, 2, 6, "2000.0", label_text="Max Wavelength [nm]:", color=self.wl_panel.cget("bg"), columnspan=3 ) # Grid mode section self.dropdown_wgrid_mode = MyDropdown( self.wl_panel, 3, 1, ["fixed_spectral_resolution", "fixed_wavelength_step"], "Grid Mode:", color=self.wl_panel.cget("bg"), columnspan=4 ) # Resolution parameters self.entry_wres = MyEntry( self.wl_panel, 4, 1, "4", label_text="Wavelength Resolution Multiplier:", color=self.wl_panel.cget("bg"), columnspan=4 ) self.entry_wdelta = MyEntry( self.wl_panel, 4, 5, "0.1", label_text="Wavelength Delta [nm]:", color=self.wl_panel.cget("bg"), columnspan=4 ) # Line Spread Function section self.dropdown_lsf_type = MyDropdown( self.wl_panel, 5, 1, ["none", "Gaussian", "Boxcar"], "LSF Type:", color=self.wl_panel.cget("bg"), columnspan=3 ) self.entry_lsf_gauss_fwhm = MyEntry( self.wl_panel, 6, 1, "5.0", label_text="LSF Gauss FWHM:", color=self.wl_panel.cget("bg"), columnspan=4 ) self.entry_lsf_boxcar_fwhm = MyEntry( self.wl_panel, 6, 5, "5.0", label_text="LSF Boxcar FWHM:", color=self.wl_panel.cget("bg"), columnspan=4 ) def _initialize_observatory_panel(self): """ Initialize observatory parameters panel. Sets up observatory selection and temperature flag configuration. """ # Help button for observatory parameters help_text_observatory = { "Observatory": "Predefined observatory site for SkyCalc atmospheric modeling. Options: 'lasilla': La Silla Observatory (2400m, Chile), 'paranal': Paranal/VLT (2635m, Chile), '3060m': Generic 3060m altitude site. Different sites have different atmospheric profiles (PWV, pressure, temperature).", "Temperature Flag": "Temperature flag for atmospheric modeling. 1: Use site-specific temperature profile from almanac/climatology. 0: Use fixed temperature value. Temperature affects atmospheric density profile and refraction." } HelpButton( self.observatory_panel, 0, 0, "Observatory Parameters Help", help_text_observatory, columnspan=1 ) # Section header MyLabel( self.observatory_panel, 0, 1, color=self.observatory_panel.cget("bg"), label_text="OBSERVATORY PARAMETERS", font=ScaleManager.get().font_label_large if ScaleManager.get() else ("Sans", 12, "bold"), columnspan=10 ) # Observatory selection self.dropdown_observatory = MyDropdown( self.observatory_panel, 1, 1, ["lasilla", "paranal", "3060m"], "Observatory:", color=self.observatory_panel.cget("bg"), columnspan=4 ) # Temperature flag self.entry_temp_flag = MyEntry( self.observatory_panel, 2, 1, "1", label_text="Temperature Flag:", color=self.observatory_panel.cget("bg"), columnspan=4 ) def _initialize_coordinates_panel(self): """ Initialize coordinates parameters panel with vertical layout. Creates entry fields for equatorial coordinates (RA, Dec) and observatory coordinates (latitude, longitude, altitude). """ # Help button for coordinates parameters help_text_coords = { "RA [deg]": "Right Ascension of target in decimal degrees (J2000 epoch). Range 0-360°. Used for airmass calculation, barycentric corrections, and almanac computations. Automatically populated from target database but can be overridden.", "Dec [deg]": "Declination of target in decimal degrees (J2000 epoch). Range -90° to +90°. Together with RA defines target position on celestial sphere. Critical for accurate airmass and barycentric velocity calculations.", "Select Observatory": "Choose observatory from database. Pre-configured observatories have accurate geodetic coordinates. Selecting an observatory automatically populates latitude, longitude, and altitude fields. Required for barycentric corrections and almanac calculations.", "Observatory Lat [deg]": "Observatory geodetic latitude in decimal degrees. North positive (+), South negative (-). Range -90° to +90°. Critical for Earth rotation velocity corrections and accurate airmass calculations.", "Observatory Long [deg]": "Observatory geodetic longitude in decimal degrees. East positive (+), West negative (-). Range -180° to +180°. Used for calculating Local Sidereal Time and Earth rotation effects on radial velocities.", "Observatory Alt [m]": "Observatory altitude above sea level in meters. Affects atmospheric pressure/density profiles, extinction, and atmospheric refraction. Higher altitude = less atmosphere = less extinction and better transparency." } HelpButton( self.coordinates_panel, 0, 0, "Coordinates Parameters Help", help_text_coords, columnspan=1 ) # Section header MyLabel( self.coordinates_panel, 0, 1, color=self.coordinates_panel.cget("bg"), label_text="COORDINATES PARAMETERS", font=ScaleManager.get().font_label_large if ScaleManager.get() else ("Sans", 12, "bold"), columnspan=6 ) self.target_information = get_target_info(self.path_folder_targets, self.dropdown_target.get_value(), self.dropdown_rad_mode.get_value()) # Arrange coordinate parameters vertically (one below the other) # Equatorial coordinates self.entry_ra = MyEntry( self.coordinates_panel, 1, 1, str(self.target_information.ra), label_text="RA [deg]:", color=self.coordinates_panel.cget("bg"), columnspan=3 ) self.entry_dec = MyEntry( self.coordinates_panel, 2, 1, str(self.target_information.dec), label_text="Dec [deg]:", color=self.coordinates_panel.cget("bg"), columnspan=3 ) # Observatory selection dropdown # Get observatories from database DB = DBSQLite3(self.path_default) observatories, lat, long, alt = DB.get_observatories() DB.close_DB() self.dropdown_observatory_coords = MyDropdown( self.coordinates_panel, 3, 1, observatories, "Select Observatory:", color=self.coordinates_panel.cget("bg"), columnspan=3 ) self.dropdown_observatory_coords.set_callback(self.update_observatory_coordinates) lat = str(lat[0]) if lat is not None else "0" long = str(long[0]) if long is not None else "0" alt = str(alt[0]) if alt is not None else "0" # Observatory coordinates self.entry_obs_lat = MyEntry( self.coordinates_panel, 4, 1, lat, label_text="Observatory Lat [deg]:", color=self.coordinates_panel.cget("bg"), columnspan=3 ) self.entry_obs_long = MyEntry( self.coordinates_panel, 5, 1, long, label_text="Observatory Long [deg]:", color=self.coordinates_panel.cget("bg"), columnspan=3 ) self.entry_obs_alt = MyEntry( self.coordinates_panel, 6, 1, alt, label_text="Observatory Alt [m]:", color=self.coordinates_panel.cget("bg"), columnspan=3 )
[docs] def update_observatory_coordinates(self): """ Update observatory coordinates when dropdown selection changes. Retrieves the selected observatory's coordinates from the database and updates the latitude, longitude, and altitude entry fields. """ selected_observatory = self.dropdown_observatory_coords.get_value() if selected_observatory and selected_observatory != "None": DB = DBSQLite3(self.path_default) observatory_data = DB.get_observatory_by_key(selected_observatory) DB.close_DB() if observatory_data: # Update the entry fields with observatory data self.entry_obs_lat.set_value(str(observatory_data[1])) # latitude self.entry_obs_long.set_value(str(observatory_data[2])) # longitude self.entry_obs_alt.set_value(str(observatory_data[3])) # altitude print(f"Updated coordinates for observatory: {selected_observatory}") else: print(f"No data found for observatory: {selected_observatory}") else: print("No observatory selected or 'None' selected")
[docs] def update_observatory_list(self, observatories): """ Update the observatory dropdown with a new list of observatories. Recreates the observatory dropdown widget with new observatory options and maintains the callback function. Parameters ---------- observatories : list List of observatory names to populate the dropdown """ try: # Clean the existing dropdown widget self.dropdown_observatory_coords.clean_widget() # Recreate the dropdown with new observatory list self.dropdown_observatory_coords = MyDropdown( self.coordinates_panel, 3, 1, observatories, "Select Observatory:", color=self.coordinates_panel.cget("bg"), columnspan=3 ) # Restore the callback function self.dropdown_observatory_coords.set_callback(self.update_observatory_coordinates) print(f"Updated observatory dropdown with {len(observatories)} observatories") except Exception as e: print(f"Error updating observatory dropdown: {e}")
[docs] def get_atmospheric_parameters(self): """ Get all atmospheric parameters as a dictionary. Collects all atmospheric, observational, wavelength, and observatory parameters from the GUI widgets. Parameters with value -999 are excluded from the final dictionary as they indicate values to be retrieved automatically. Returns ------- dict Dictionary containing all atmospheric parameter values with -999 values excluded """ print("Collecting atmospheric parameters...") # Collect all parameters first all_params = { # Atmospheric parameters "airmass": float(self.entry_airmass.get_value()), "pwv_mode": self.dropdown_pwv_mode.get_value(), "season": int(self.entry_season.get_value()), "time": int(self.entry_time.get_value()), "pwv": self.dropdown_pwv.get_value(), "pwv_func": self.dropdown_pwv.get_value(), "msolflux": float(self.entry_msolflux.get_value()), "initial_x": float(self.entry_initial_x.get_value()), "final_x": float(self.entry_final_x.get_value()), # Observational parameters "incl_moon": self.dropdown_incl_moon.get_value(), "moon_sun_sep": float(self.entry_moon_sun_sep.get_value()), "moon_target_sep": float(self.entry_moon_target_sep.get_value()), "moon_alt": float(self.entry_moon_alt.get_value()), "moon_earth_dist": float(self.entry_moon_earth_dist.get_value()), "incl_starlight": self.dropdown_incl_starlight.get_value(), "incl_zodiacal": self.dropdown_incl_zodiacal.get_value(), "ecl_lon": float(self.entry_ecl_lon.get_value()), "ecl_lat": float(self.entry_ecl_lat.get_value()), "incl_loweratm": self.dropdown_incl_loweratm.get_value(), "incl_upperatm": self.dropdown_incl_upperatm.get_value(), "incl_airglow": self.dropdown_incl_airglow.get_value(), "incl_therm": self.dropdown_incl_therm.get_value(), "therm_t1": float(self.entry_therm_t1.get_value()), "therm_e1": float(self.entry_therm_e1.get_value()), "therm_t2": float(self.entry_therm_t2.get_value()), "therm_e2": float(self.entry_therm_e2.get_value()), "therm_t3": float(self.entry_therm_t3.get_value()), "therm_e3": float(self.entry_therm_e3.get_value()), # Wavelength parameters "vacair": self.dropdown_vacair.get_value(), "wmin": float(self.entry_wmin.get_value()), "wmax": float(self.entry_wmax.get_value()), "wgrid_mode": self.dropdown_wgrid_mode.get_value(), "wdelta": float(self.entry_wdelta.get_value()), "wres_mul": float(self.entry_wres.get_value()), "lsf_type": self.dropdown_lsf_type.get_value(), "lsf_gauss_fwhm": float(self.entry_lsf_gauss_fwhm.get_value()), "lsf_boxcar_fwhm": float(self.entry_lsf_boxcar_fwhm.get_value()), # Observatory parameters "observatory": self.dropdown_observatory.get_value(), "temp_flag": int(self.entry_temp_flag.get_value()) } # Filter out parameters with value -999 filtered_params = {} excluded_count = 0 for key, value in all_params.items(): if isinstance(value, (int, float)) and value == -999: excluded_count += 1 print(f" Excluding parameter '{key}' (value = -999)") else: filtered_params[key] = value print(f" Total parameters: {len(all_params)}") print(f" Excluded parameters: {excluded_count}") print(f" Final parameters: {len(filtered_params)}") return filtered_params
[docs] def get_almanac_parameters(self): """ Get coordinate parameters as an almanac dictionary. Collects equatorial coordinates and observatory position parameters needed for astronomical calculations and almanac computations. Returns ------- dict Dictionary containing coordinate parameters including RA, Dec, observatory latitude, longitude, and altitude """ print("Collecting almanac parameters...") return { # Equatorial coordinates "ra": float(self.entry_ra.get_value()), "dec": float(self.entry_dec.get_value()), # Observatory coordinates "obs_lat": float(self.entry_obs_lat.get_value()), "obs_long": float(self.entry_obs_long.get_value()), "obs_alt": float(self.entry_obs_alt.get_value()) }
[docs] def get_pwv_parameters(self): """ Get PWV parameters from the first tab entry widgets. Collects PWV (precipitable water vapor) modeling parameters used for atmospheric simulation and telluric absorption calculations. Returns ------- dict Dictionary containing PWV parameters including dt, pace, s, scale, tExp, and seed values """ print("Collecting PWV parameters...") return { "dt": float(self.entry_dt.get_value()), "pace": float(self.entry_pace.get_value()), "s": float(self.entry_s.get_value()), "scale": float(self.entry_scale.get_value()), "tExp": float(self.entry_tExp.get_value()), "seed": int(self.entry_seed.get_value()) }
[docs] def get_row(self): """ Get the currently selected row information from the filter table. Retrieves target information and array values from the currently selected row in the filter table interface. Returns ------- tuple (target, array_values, selected_name) or (None, None, None) if no row is selected or an error occurs """ try: target = self.dropdown_target.get_value() arr_values = self.frame_input.frame_filter_table.frame_table.table.get_arr_values() selected_name = arr_values[ConstantVariables.DICT_FILTER_TABLE["ID"]["index"]] except Exception as e: messagebox.showerror("Choose a row", str(e)) return None, None, None return target, arr_values, selected_name
[docs] def update_from_target_or_rad_mode(self): """ Update night information for the current target and instrument configuration. Called when the target selection or radiation transfer mode changes. Updates the available nights, instruments, and cross-correlation models to match the new selection. """ if self.global_exc == 0: return self.update_nights_instrument() self.update_CC_model() self.update_nights()
[docs] def update_nights(self): """ Update HR instrument settings and available nights. Called when the HR instrument selection changes. Updates the nights text field with available observation nights for the selected target and instrument combination. """ if self.global_exc == 0: return instrumenti = self.dropdown_hr_instrument.get_value() target_id = self.dropdown_target.get_value() nights = get_target_nights( self.path_folder_targets, target_id, self.dropdown_rad_mode.get_value(), instrumenti, 0 ) self.target_information = get_target_info(self.path_folder_targets, target_id, self.dropdown_rad_mode.get_value()) self.entry_ra.set_value(str(self.target_information.ra)) self.entry_dec.set_value(str(self.target_information.dec)) self.textfield_nights.insert_text(nights)
[docs] def update_nights_instrument(self): """ Update the HR instrument dropdown based on current target and mode. Refreshes the list of available HR instruments for the selected target and radiation transfer mode. Recreates the dropdown widget with updated options. """ if self.global_exc == 0: return target_id = self.dropdown_target.get_value() # Update HR instruments self.list_hr_instrument = get_target_instruments( self.path_folder_targets, target_id, "HR", self.dropdown_rad_mode.get_value(), Path(__file__).name ) self.dropdown_hr_instrument.clean_widget() self.dropdown_hr_instrument = MyDropdown( self.main_controls_panel, 0, 9, self.list_hr_instrument, "HR Instrument:", color=self.main_controls_panel.cget("bg"), columnspan=2 ) self.dropdown_hr_instrument.set_callback(self.update_nights)
[docs] def update_CC_model(self): """ Update the cross-correlation model dropdown. Refreshes the list of available cross-correlation models based on the current target and radiation transfer mode selection. """ target_id = self.dropdown_target.get_value() # Update cross-correlation models self.dropdown_cross_corr.clean_widget() dict_list_cc = get_list_cross_corr_models( self.path_folder_targets, target_id, self.dropdown_rad_mode.get_value() ) self.dropdown_cross_corr = MyDropdown( self.main_controls_panel, 0, 11, list(sorted(dict_list_cc.keys())), "Model CC", initial_value=0, color=self.main_controls_panel.cget("bg"), columnspan=2 )
[docs] def run_selected_generation(self): """ Run the selected generation based on dropdown choice. Checks the generation mode dropdown and calls the appropriate generation function. Supports synthetic generations with continuous or discontinuous PWV, and shuffle generations. """ selected_mode = self.dropdown_generation_mode.get_value() print(f"Selected generation mode: {selected_mode}") if selected_mode == "Synthetic": print("Running Synthetic generation with PWV parameters...") self.run_synthetic(False) elif selected_mode == "Synthetic Discontinuous PWV": print("Running Synthetic Discontinuous PWV generation...") self.run_synthetic(True) elif selected_mode == "Shuffle": print("Running shuffle generation...") self.run_shuffle() else: print(f"Unknown generation mode: {selected_mode}")
[docs] def modify_target_list(self, target_list): """ Update the target dropdown with a new list of targets. Recreates the target dropdown widget with new target options and updates the callback function. Parameters ---------- target_list : list List of target names to populate the dropdown """ self.dropdown_target.clean_widget() self.dropdown_target = MyDropdown( self.main_controls_panel, 0, 1, target_list, "Target:", color=self.main_controls_panel.cget("bg"), columnspan=2 ) self.dropdown_target.set_callback(self.update_from_target_or_rad_mode)
[docs] def run_shuffle(self): """ Run the HR data shuffle with the current configuration. Collects all current settings and parameters from the GUI and executes the HR data shuffle using the shuffle_HR function. This method runs synchronously and blocks until completion. """ print("=== Starting HR Data shuffle ===") self.terminator_night = "sim" target_obj = Target( str(Path(self.path_folder_targets, self.dropdown_target.get_value(), "info.yaml")), self.dropdown_rad_mode.get_value() ) print(f"Target: {target_obj.name}") print(f"Radiation Transfer Mode: {self.dropdown_rad_mode.get_value()}") with DBSQLite3(ConstantVariables.path_default) as DB: instrument_obj = DB.get_instrument_by_key(self.dropdown_hr_instrument.get_value()) print(f"HR Instrument: {self.dropdown_hr_instrument.get_value()}") print(f"Nights: {self.textfield_nights.get_text()}") print(f"Cross-correlation Model: {self.dropdown_cross_corr.get_value()}") print(f"Order Selection: {self.textfield_order_selection.get_text()}") print(f"Telluric Removal Method: {self.dropdown_tell_rm_method.get_value()}") print("Running shuffle...") shuffle_HR( target_obj, self.path_folder_targets, self.dropdown_rad_mode.get_value(), instrument_obj, self.textfield_nights.get_text(), self.dropdown_cross_corr.get_value(), self.textfield_order_selection.get_text(), self.dropdown_tell_rm_method.get_value(), ) print("=== HR Data shuffle Completed ===\n") messagebox.showinfo( "Shuffle HR Generation Complete", "The shuffle HR generation has completed successfully!" )
[docs] def run_synthetic(self, discontinuos=True): """ Run the synthetic HR data generation in background with the current configuration. Handles stellar spectrum download if needed and runs the synthetic generation in a background subprocess. Manages parameter collection, stellar spectrum acquisition, and background process monitoring. Parameters ---------- discontinuos : bool, optional Whether to use discontinuous PWV modeling, by default True """ self.terminator_night = "syn" print("=== Starting Background Synthetic HR Data Generation ===") target_obj = Target( str(Path(self.path_folder_targets, self.dropdown_target.get_value(), "info.yaml")), self.dropdown_rad_mode.get_value() ) print(f"Target: {target_obj.name}") print(f"Radiation Transfer Mode: {self.dropdown_rad_mode.get_value()}") with DBSQLite3(ConstantVariables.path_default) as DB: instrument_obj = DB.get_instrument_by_key(self.dropdown_hr_instrument.get_value()) print(f"HR Instrument: {self.dropdown_hr_instrument.get_value()}") folder_stellar_spc = str(Path(self.path_folder_targets, target_obj.name, "Star_Spectrum")) path_phoenixf = download_star_spectrum(self, target_obj, folder_stellar_spc) if path_phoenixf is None: return # Get atmospheric parameters print("Collecting atmospheric and almanac parameters...") atmospheric_params = self.get_atmospheric_parameters() almanac_params = self.get_almanac_parameters() pwv_params = self.get_pwv_parameters() print(f"Atmospheric parameters collected: {len(atmospheric_params)} parameters") print(f"Almanac parameters collected: {len(almanac_params)} parameters") if discontinuos: print(f"PWV parameters not collected") else: print(f"PWV parameters collected: {len(pwv_params)} parameters") # Save parameters to pickle file print("Saving parameters to pickle file...") pickle_path = self._save_synthetic_hr_data( target_obj, instrument_obj, path_phoenixf, atmospheric_params, almanac_params, pwv_params, discontinuos ) # Disable buttons and start background process print("Starting background subprocess...") self._disable_buttons() self._start_synthetic_subprocess(pickle_path) print("=== Background Synthetic HR Generation Started ===") print("The generation is running in the background. You will be notified when it completes.")
[docs] def update_pwv_image(self): """ Update the PWV image when the dropdown selection changes. Loads and displays the appropriate PWV function plot based on the selected function. Falls back to default logo if the specific function image is not found. """ try: selected_function = self.dropdown_pwv.get_value() print(f"Updating PWV image for function: {selected_function}") image_path = Path(self.path_default, "GUIBRUSHR", "Files", "pwv_functions_plots", f"{selected_function}.png") if image_path.exists(): self.pwv_image.update_image(str(image_path)) print(f"PWV image updated successfully: {selected_function}") else: # If image doesn't exist, use default logo default_image_path = Path(self.path_default, "GUIBRUSHR", "Files", "logo.png") if default_image_path.exists(): self.pwv_image.update_image(str(default_image_path)) print(f"Using default logo (function image not found): {selected_function}") else: print(f"Neither function image nor default logo found. Function: {selected_function}") print(f"PWV function image not found: {image_path}") except Exception as e: print(f"Error updating PWV image: {e}")
def _disable_buttons(self): """ Disable buttons during background execution. Prevents user interaction with generation controls while a background process is running. """ try: self.button_run.set_status('disabled') except Exception as _: pass # Handle cases where buttons might not exist yet def _enable_buttons(self): """ Enable buttons after background execution completes. Re-enables user interaction with generation controls after background process completion. """ try: self.button_run.set_status('normal') except Exception as _: pass # Handle cases where buttons might not exist yet def _save_synthetic_hr_data(self, target_obj, instrument_obj, path_phoenixf, atmospheric_params, almanac_params, pwv_params, discontinuos): """ Save all parameters needed for synthetic HR generation to pickle file. Creates a temporary directory and saves all generation parameters to a pickle file for use by the background subprocess. Parameters ---------- target_obj : Target Target object containing target information instrument_obj : Instrument Instrument object containing instrument configuration path_phoenixf : str Path to stellar spectrum file atmospheric_params : dict Atmospheric parameters dictionary almanac_params : dict Almanac parameters dictionary pwv_params : dict PWV parameters dictionary discontinuos : bool Whether to use discontinuous PWV mode Returns ------- str Path to the created pickle file containing all parameters Notes ----- All parameters are organized into a single dictionary structure for improved organization and maintainability. The dictionary contains core generation parameters, GUI settings, and processing options. """ # Create a temp directory for the background process temp_dir = Path(self.path_folder_targets, target_obj.name, "temp_synthetic") temp_dir.mkdir(exist_ok=True) pickle_path = temp_dir / "synthetic_hr_params.pkl" # Organize all parameters into a single dictionary generation_params = { # Core framework parameters 'path_default': self.path_default, 'target_obj': target_obj, 'path_folder_targets': self.path_folder_targets, 'rad_mode': self.dropdown_rad_mode.get_value(), 'instrument_obj': instrument_obj, # Observation parameters 'nights_text': self.textfield_nights.get_text(), 'cross_corr_model': self.dropdown_cross_corr.get_value(), 'order_selection': self.textfield_order_selection.get_text(), 'tell_rm_method': self.dropdown_tell_rm_method.get_value(), 'path_stellar_spectrum': path_phoenixf, # Physical model parameters 'atmospheric_params': atmospheric_params, 'almanac_params': almanac_params, 'pwv_params': pwv_params, # Processing options 'discontinuous': discontinuos, 'save_plots': self.checkbox_save_plots.get_value(), 'noise_factor': float(self.entry_noise_factor.get_value()), 'gain': float(self.entry_gain.get_value()), 'include_0_1': self.checkbox_include_0_1.get_value(), 'error_type': self.dropdown_error_type.get_value(), # Fixed parameters 'eccentricity': False # Always False for now } # Save the complete parameter dictionary with open(str(pickle_path), "wb") as fo: pickle.dump(generation_params, fo) return str(pickle_path) def _start_synthetic_subprocess(self, pickle_path): """ Start the synthetic HR generation subprocess. Launches the synthetic_HR.py script as a subprocess with the pickle file containing all parameters. Sets up process monitoring. Parameters ---------- pickle_path : str Path to the pickle file containing all generation parameters """ script_path = str(Path( self.path_default, "GUIBRUSHR", "GUI", "Input_Output_Panels", "Input_Panels", "TabPanels", "FrameGenerationHRData", "synthetic_HR.py" )) self.synthetic_process = subprocess.Popen( ["python3", script_path, pickle_path, self.path_default], stderr=subprocess.STDOUT, universal_newlines=True, ) self.synthetic_running = True self.window.after(500, self.check_synthetic_proc)
[docs] def check_synthetic_proc(self): """ Check if the synthetic HR subprocess has completed. Monitors the subprocess status and handles completion or failure. Re-enables buttons and shows appropriate messages when the process finishes. Schedules itself to run again if the process is still active. """ if self.synthetic_process.poll() is not None: # Subprocess completed self.synthetic_running = False self._enable_buttons() # Show completion message returncode = self.synthetic_process.returncode path_instrument = create_path_night( self.path_folder_targets, self.dropdown_target.get_value(), self.dropdown_rad_mode.get_value(), self.dropdown_hr_instrument.get_value() ) nights = self.textfield_nights.get_text() night = nights.split(",") path_night = str(Path(path_instrument, night[-1])) path_night = path_night + self.terminator_night path_pdf = str(Path(path_night, "aligned_matrix.pdf")) if returncode == 0: messagebox.showinfo( "Synthetic HR Generation Complete", "The synthetic HR generation has completed successfully!" ) MyPDFWindow(path_pdf=path_pdf, title="Synthetic HR Data") else: messagebox.showerror( "Synthetic HR Generation Error", f"The synthetic HR generation failed with return code {returncode}. Check console for details." ) else: # Subprocess still running - check again later self.window.after(500, self.check_synthetic_proc)