From 0f493d856d295569ddb9fc523b8565203e20bdc2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Neveu?= Date: Tue, 12 May 2026 20:28:49 -0400 Subject: [PATCH 1/8] add notes for dispresers --- spectractor/extractor/dispersers/HoloAmAg_7.5/NOTES | 6 ++++++ spectractor/extractor/dispersers/Thor300_7.5/NOTES | 5 +++++ spectractor/extractor/dispersers/blue300lpmm_qn1_old/NOTES | 1 + 3 files changed, 12 insertions(+) create mode 100644 spectractor/extractor/dispersers/HoloAmAg_7.5/NOTES create mode 100644 spectractor/extractor/dispersers/Thor300_7.5/NOTES create mode 100644 spectractor/extractor/dispersers/blue300lpmm_qn1_old/NOTES diff --git a/spectractor/extractor/dispersers/HoloAmAg_7.5/NOTES b/spectractor/extractor/dispersers/HoloAmAg_7.5/NOTES new file mode 100644 index 000000000..5a040c389 --- /dev/null +++ b/spectractor/extractor/dispersers/HoloAmAg_7.5/NOTES @@ -0,0 +1,6 @@ +- ratio_order_2over1.txt: fit of a model to Sylvie's data from CTIO spectra observed with a blue filter +- transmission.txt: fit on CTIO data 2017/30/05 normalized with multispectra_Thor300_HD111980_CTIO_throughput_prod7.5.txt +../CTIODataJune2017_reduced_RG715_v2_prod7.5/data_30may17_A2=0.1/multispectra_HoloAmAg_HD111980_HoloAmAg_throughput.txt +(see jupyter notebook Multispectra.ipynb) +- N.txt: interpolation of Neff estimated with CTIO scan of the hologram +- hologram_*.txt: same \ No newline at end of file diff --git a/spectractor/extractor/dispersers/Thor300_7.5/NOTES b/spectractor/extractor/dispersers/Thor300_7.5/NOTES new file mode 100644 index 000000000..ae03ab296 --- /dev/null +++ b/spectractor/extractor/dispersers/Thor300_7.5/NOTES @@ -0,0 +1,5 @@ +- ratio_order_2over1.txt: fit of a model to Sylvie's data from CTIO spectra observed with a blue filter +- transmission.txt: model best fit on a mix of measurements coming from the Thorlabs data sheet (300-450nm), Laurent's measurement +at DCCD=200mm (450-920nm), Arthur's measurement at DCCD=58mm (1000>920nm) and Thorlabs data sheet again (>1000nm) +(see jupyter notebook Disperser_efficiencies.ipynb) +- N.txt: estimation with CTIO data diff --git a/spectractor/extractor/dispersers/blue300lpmm_qn1_old/NOTES b/spectractor/extractor/dispersers/blue300lpmm_qn1_old/NOTES new file mode 100644 index 000000000..ca743dd96 --- /dev/null +++ b/spectractor/extractor/dispersers/blue300lpmm_qn1_old/NOTES @@ -0,0 +1 @@ +Blue quad notch filter \ No newline at end of file From b59d1ad60a8563a447700414dd26ede0f2ffb4fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Neveu?= Date: Tue, 12 May 2026 20:37:41 -0400 Subject: [PATCH 2/8] save spectrogram and chromaticpsf and lines HDUs only in DEBUG mode; load them only in DEBUG mode with no fast_load --- spectractor/extractor/spectrum.py | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/spectractor/extractor/spectrum.py b/spectractor/extractor/spectrum.py index cb761cd9d..2ff359023 100644 --- a/spectractor/extractor/spectrum.py +++ b/spectractor/extractor/spectrum.py @@ -173,7 +173,7 @@ class Spectrum: Size of the spectrogram along the y axis. """ - def __init__(self, file_name="", image=None, order=1, target=None, config="", fast_load=False, + def __init__(self, file_name="", image=None, order=1, target=None, config="", fast_load=True, spectrogram_file_name_override=None, psf_file_name_override=None,): """ Class used to store information and methods relative to spectra and their extraction. @@ -633,6 +633,13 @@ def save_spectrum(self, output_file_name, overwrite=False): Path of the output fits file. overwrite: bool If True overwrite the output file if needed (default: False). + + NOTES + ----- + - The spectrum is saved in the first extension of the fits file, with the wavelength array in the first column, the spectrum in the second column and the error in the third column. + - The header of the first extension contains the relevant parameters of the spectrum and its extraction. + - The config parameters are saved in the CONFIG extension of the fits file, and the lines table is saved in the LINES extension of the fits file. + - Only in DEBUG mode, the spectrogram data and the PSF table are saved in the fits file in the S_DATA and PSF_TAB extensions respectively. Examples -------- @@ -677,10 +684,12 @@ def save_spectrum(self, output_file_name, overwrite=False): hdu1.header[header_key] = value # print(f"Set header key {header_key} to {value} from attr {attribute}") - extnames = ["SPECTRUM", "SPEC_COV", "ORDER2", "ORDER0"] # spectrum data - extnames += ["S_DATA", "S_ERR", "S_BGD", "S_BGD_ER", "S_FIT", "S_RES", "S_FLAT", "S_STAR", "S_MASK"] - extnames += ["PSF_TAB"] # PSF parameter table - extnames += ["LINES"] # spectroscopic line table + extnames = ["SPECTRUM", "SPEC_COV", "ORDER2"] # spectrum data + if parameters.DEBUG: + extnames += ["ORDER0"] # spectrum order0 timestamps + extnames += ["S_DATA", "S_ERR", "S_BGD", "S_BGD_ER", "S_FIT", "S_RES", "S_FLAT", "S_STAR", "S_MASK"] + extnames += ["PSF_TAB"] # PSF parameter table + extnames += ["LINES"] # spectroscopic line table extnames += ["CONFIG"] # config parameters hdus = {"SPECTRUM": hdu1} for k, extname in enumerate(extnames): @@ -821,7 +830,7 @@ def save_spectrogram(self, output_file_name, overwrite=False): # pragma: no cov self.my_logger.info('\n\tSpectrogram saved in %s' % output_file_name) def load_spectrum(self, input_file_name, spectrogram_file_name_override=None, - psf_file_name_override=None, fast_load=False): + psf_file_name_override=None, fast_load=True): """Load the spectrum from a fits file (data, error and wavelengths). Parameters @@ -833,7 +842,8 @@ def load_spectrum(self, input_file_name, spectrogram_file_name_override=None, psf_file_name_override : str Manually specify a path to the psf file. fast_load: bool, optional - If True, only the spectrum is loaded (not the PSF nor the spectrogram data) (default: False). + If True, only the spectrum is loaded (not the PSF nor the spectrogram data) (default: True). + If False, load other hdus only in DEBUG mode. Examples -------- @@ -1093,11 +1103,11 @@ def load_spectrum_latest(self, input_file_name): if 'PSF_REG' in self.header and float(self.header["PSF_REG"]) > 0: self.chromatic_psf.opt_reg = float(self.header["PSF_REG"]) - if not self.fast_load: + self.cov_matrix = hdu_list["SPEC_COV"].data + _, self.data_next_order, self.err_next_order = hdu_list["ORDER2"].data + if not self.fast_load and parameters.DEBUG: with (fits.open(input_file_name) as hdu_list): # load other spectrum info - self.cov_matrix = hdu_list["SPEC_COV"].data - _, self.data_next_order, self.err_next_order = hdu_list["ORDER2"].data self.target.image = hdu_list["ORDER0"].data self.target.image_x0 = float(hdu_list["ORDER0"].header["IM_X0"]) self.target.image_y0 = float(hdu_list["ORDER0"].header["IM_Y0"]) From 34502cfdd337cc06f2237ff2f49bcb8d74c06bd0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Neveu?= Date: Tue, 12 May 2026 21:27:25 -0400 Subject: [PATCH 3/8] catch warnings from astrometry --- spectractor/astrometry.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/spectractor/astrometry.py b/spectractor/astrometry.py index 6e5bae24c..2d4170f15 100644 --- a/spectractor/astrometry.py +++ b/spectractor/astrometry.py @@ -1,5 +1,6 @@ import os import sys +import warnings from copy import deepcopy import subprocess import shutil @@ -153,7 +154,9 @@ def get_gaia_coords_after_proper_motion(gaia_catalog, date_obs): np.array(gaia_catalog['dec']) * np.pi / 180), pm_dec=gaia_catalog['pmdec'].filled(0), distance=Distance(parallax=parallax * u.mas, allow_negative=True)) - gaia_coords_after_proper_motion = gaia_stars.apply_space_motion(new_obstime=Time(date_obs)) + with warnings.catch_warnings(): + warnings.filterwarnings("ignore", message=".*distance overridden.*", category=Warning) + gaia_coords_after_proper_motion = gaia_stars.apply_space_motion(new_obstime=Time(date_obs)) return gaia_coords_after_proper_motion From 59f213149c3d70bbb694473c2466a303d9c74498 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Neveu?= Date: Tue, 12 May 2026 21:27:47 -0400 Subject: [PATCH 4/8] remove unnecessary matplotlib import --- spectractor/parameters.py | 1 - 1 file changed, 1 deletion(-) diff --git a/spectractor/parameters.py b/spectractor/parameters.py index a29fefbf2..1bc4d7543 100644 --- a/spectractor/parameters.py +++ b/spectractor/parameters.py @@ -1,5 +1,4 @@ import os -import matplotlib as mpl import numpy as np # These parameters are the default values adapted to CTIO From c78f5a19f55deb0846d502d489a8308c1f77cf69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Neveu?= Date: Tue, 12 May 2026 21:28:18 -0400 Subject: [PATCH 5/8] correct matplitliib and tight layout --- spectractor/tools.py | 23 ++--------------------- 1 file changed, 2 insertions(+), 21 deletions(-) diff --git a/spectractor/tools.py b/spectractor/tools.py index 063aa7d48..f585d4874 100644 --- a/spectractor/tools.py +++ b/spectractor/tools.py @@ -52,25 +52,6 @@ def _safe_tight_layout(self, *args, **kwargs): plt.Figure.tight_layout = _safe_tight_layout -# Monkey-patch matplotlib tight_layout to handle LaTeX parsing errors -# This is needed for matplotlib versions in LSST environment that don't fully support LaTeX -_original_tight_layout = plt.Figure.tight_layout - - -def _safe_tight_layout(self, *args, **kwargs): - """Wrapper for Figure.tight_layout that catches ValueError from LaTeX parsing errors.""" - try: - return _original_tight_layout(self, *args, **kwargs) - except ValueError: - # Silently ignore LaTeX parsing errors in tight_layout - # This typically happens with Greek letters and complex math expressions - # in axis labels or titles when matplotlib's mathtext parser has issues - pass - - -plt.Figure.tight_layout = _safe_tight_layout - - # do not increase speed: # @njit(fastmath=True, cache=True) def gauss(x, A, x0, sigma): @@ -1896,11 +1877,11 @@ def plot_image_simple(ax, data, scale="lin", title="", units="Image units", cmap >>> if parameters.DISPLAY: plt.show() """ if cmap is not None and isinstance(cmap, str): - colormap = copy.copy(cm.get_cmap(cmap)) + colormap = copy.copy(matplotlib.colormaps[cmap]) elif isinstance(cmap, matplotlib.colors.Colormap): colormap = cmap else: - colormap = copy.copy(cm.get_cmap('viridis')) + colormap = copy.copy(matplotlib.colormaps['viridis']) cmap_nan = copy.copy(colormap) cmap_nan.set_bad(color='lightgrey') From 6f650293c4fb0b59bf2843bad1305504b2fbabe5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Neveu?= Date: Tue, 12 May 2026 21:28:48 -0400 Subject: [PATCH 6/8] debug config_dir default path --- spectractor/config.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/spectractor/config.py b/spectractor/config.py index 870e0625e..bd0cbdb53 100644 --- a/spectractor/config.py +++ b/spectractor/config.py @@ -110,16 +110,19 @@ def load_config(config_filename, rebin=True): """ my_logger = set_logger(__name__) mypath = os.path.dirname(__file__) - if not os.path.isfile(os.path.join(mypath, parameters.CONFIG_DIR, "default.ini")): + # Use the bundled config/ directory relative to this file, not parameters.CONFIG_DIR + # (parameters.CONFIG_DIR can be overwritten at runtime, e.g. by loading a spectrum FITS file) + config_dir = os.path.join(mypath, "config") + if not os.path.isfile(os.path.join(config_dir, "default.ini")): raise FileNotFoundError('Config file default.ini does not exist.') # Load the configuration file - from_config_to_parameters(os.path.join(mypath, parameters.CONFIG_DIR, "default.ini")) + from_config_to_parameters(os.path.join(config_dir, "default.ini")) if not os.path.isfile(config_filename): - if not os.path.isfile(os.path.join(mypath, parameters.CONFIG_DIR, config_filename)): + if not os.path.isfile(os.path.join(config_dir, config_filename)): raise FileNotFoundError(f'Config file {config_filename} does not exist.') else: - config_filename = os.path.join(mypath, parameters.CONFIG_DIR, config_filename) + config_filename = os.path.join(config_dir, config_filename) # Load the configuration file my_logger.info(f"\n\tLoading {config_filename} with {parameters.VERBOSE=}...") from_config_to_parameters(config_filename) @@ -160,7 +163,7 @@ def load_config(config_filename, rebin=True): txt = "" # default.ini should be the config file with the most parameters config = configparser.ConfigParser() - config.read(os.path.join(mypath, parameters.CONFIG_DIR, "default.ini")) + config.read(os.path.join(config_dir, "default.ini")) for section in config.sections(): txt += f"Section: {section}\n" for options in config.options(section): From 485eafe718895b909a0b06ca5ef4337835839829 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Neveu?= Date: Tue, 12 May 2026 21:30:04 -0400 Subject: [PATCH 7/8] revert changes to get light outputs because fit_spectrogram needs the spectrogram and its backgorund estimate to work. These outputs are not debug only but ARE USED in generic processes --- spectractor/extractor/spectrum.py | 31 +++++++++++-------------------- 1 file changed, 11 insertions(+), 20 deletions(-) diff --git a/spectractor/extractor/spectrum.py b/spectractor/extractor/spectrum.py index 2ff359023..b86db57f5 100644 --- a/spectractor/extractor/spectrum.py +++ b/spectractor/extractor/spectrum.py @@ -173,7 +173,7 @@ class Spectrum: Size of the spectrogram along the y axis. """ - def __init__(self, file_name="", image=None, order=1, target=None, config="", fast_load=True, + def __init__(self, file_name="", image=None, order=1, target=None, config="", fast_load=False, spectrogram_file_name_override=None, psf_file_name_override=None,): """ Class used to store information and methods relative to spectra and their extraction. @@ -634,13 +634,6 @@ def save_spectrum(self, output_file_name, overwrite=False): overwrite: bool If True overwrite the output file if needed (default: False). - NOTES - ----- - - The spectrum is saved in the first extension of the fits file, with the wavelength array in the first column, the spectrum in the second column and the error in the third column. - - The header of the first extension contains the relevant parameters of the spectrum and its extraction. - - The config parameters are saved in the CONFIG extension of the fits file, and the lines table is saved in the LINES extension of the fits file. - - Only in DEBUG mode, the spectrogram data and the PSF table are saved in the fits file in the S_DATA and PSF_TAB extensions respectively. - Examples -------- >>> import os @@ -685,11 +678,10 @@ def save_spectrum(self, output_file_name, overwrite=False): # print(f"Set header key {header_key} to {value} from attr {attribute}") extnames = ["SPECTRUM", "SPEC_COV", "ORDER2"] # spectrum data - if parameters.DEBUG: - extnames += ["ORDER0"] # spectrum order0 timestamps - extnames += ["S_DATA", "S_ERR", "S_BGD", "S_BGD_ER", "S_FIT", "S_RES", "S_FLAT", "S_STAR", "S_MASK"] - extnames += ["PSF_TAB"] # PSF parameter table - extnames += ["LINES"] # spectroscopic line table + extnames += ["ORDER0"] # spectrum order0 timestamps + extnames += ["S_DATA", "S_ERR", "S_BGD", "S_BGD_ER", "S_FIT", "S_RES", "S_FLAT", "S_STAR", "S_MASK"] + extnames += ["PSF_TAB"] # PSF parameter table + extnames += ["LINES"] # spectroscopic line table extnames += ["CONFIG"] # config parameters hdus = {"SPECTRUM": hdu1} for k, extname in enumerate(extnames): @@ -830,7 +822,7 @@ def save_spectrogram(self, output_file_name, overwrite=False): # pragma: no cov self.my_logger.info('\n\tSpectrogram saved in %s' % output_file_name) def load_spectrum(self, input_file_name, spectrogram_file_name_override=None, - psf_file_name_override=None, fast_load=True): + psf_file_name_override=None, fast_load=False): """Load the spectrum from a fits file (data, error and wavelengths). Parameters @@ -842,8 +834,7 @@ def load_spectrum(self, input_file_name, spectrogram_file_name_override=None, psf_file_name_override : str Manually specify a path to the psf file. fast_load: bool, optional - If True, only the spectrum is loaded (not the PSF nor the spectrogram data) (default: True). - If False, load other hdus only in DEBUG mode. + If True, only the spectrum is loaded (not the PSF nor the spectrogram data) (default: False). Examples -------- @@ -1103,10 +1094,10 @@ def load_spectrum_latest(self, input_file_name): if 'PSF_REG' in self.header and float(self.header["PSF_REG"]) > 0: self.chromatic_psf.opt_reg = float(self.header["PSF_REG"]) - self.cov_matrix = hdu_list["SPEC_COV"].data - _, self.data_next_order, self.err_next_order = hdu_list["ORDER2"].data - if not self.fast_load and parameters.DEBUG: + if not self.fast_load: with (fits.open(input_file_name) as hdu_list): + self.cov_matrix = hdu_list["SPEC_COV"].data + _, self.data_next_order, self.err_next_order = hdu_list["ORDER2"].data # load other spectrum info self.target.image = hdu_list["ORDER0"].data self.target.image_x0 = float(hdu_list["ORDER0"].header["IM_X0"]) @@ -1127,7 +1118,7 @@ def load_spectrum_latest(self, input_file_name): if self.spectrogram_mask is not None: self.spectrogram_mask = self.spectrogram_mask.astype(bool) self.chromatic_psf.init_from_table(Table.read(hdu_list["PSF_TAB"]), - saturation=self.spectrogram_saturation) + saturation=self.spectrogram_saturation) self.lines.table = Table.read(hdu_list["LINES"], unit_parse_strict="silent") def load_spectrogram(self, input_file_name): # pragma: no cover From bd9a0b9a5b791510add00fc03a0837bee3aae936 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Neveu?= Date: Tue, 12 May 2026 21:34:39 -0400 Subject: [PATCH 8/8] parameters.py use mpl --- spectractor/parameters.py | 1 + 1 file changed, 1 insertion(+) diff --git a/spectractor/parameters.py b/spectractor/parameters.py index 1bc4d7543..a29fefbf2 100644 --- a/spectractor/parameters.py +++ b/spectractor/parameters.py @@ -1,4 +1,5 @@ import os +import matplotlib as mpl import numpy as np # These parameters are the default values adapted to CTIO