From 5764f73e9c873aec17174f39df49daba539b540e Mon Sep 17 00:00:00 2001 From: Curtis McCully Date: Fri, 7 Jul 2023 16:54:44 -0400 Subject: [PATCH 01/19] Initial outline of the flux and telluric correction. --- banzai_floyds/dbs.py | 38 +++++++++ banzai_floyds/extract.py | 8 +- banzai_floyds/flux.py | 110 ++++++++++++++++++++++++++ banzai_floyds/frames.py | 1 + banzai_floyds/fringe.py | 4 +- banzai_floyds/main.py | 33 ++++++++ banzai_floyds/matched_filter.py | 38 +++++---- banzai_floyds/orders.py | 6 +- banzai_floyds/telluric.py | 95 ++++++++++++++++++++++ banzai_floyds/tests/test_e2e.py | 1 + banzai_floyds/tests/test_flux.py | 19 +++++ banzai_floyds/tests/test_telluric.py | 23 ++++++ banzai_floyds/tests/utils.py | 31 +++++++- banzai_floyds/utils/flux_utils.py | 14 ++++ banzai_floyds/utils/telluric_utils.py | 10 +++ banzai_floyds/wavelengths.py | 6 +- 16 files changed, 410 insertions(+), 27 deletions(-) create mode 100644 banzai_floyds/dbs.py create mode 100644 banzai_floyds/flux.py create mode 100644 banzai_floyds/telluric.py create mode 100644 banzai_floyds/tests/test_flux.py create mode 100644 banzai_floyds/tests/test_telluric.py create mode 100644 banzai_floyds/utils/flux_utils.py create mode 100644 banzai_floyds/utils/telluric_utils.py diff --git a/banzai_floyds/dbs.py b/banzai_floyds/dbs.py new file mode 100644 index 0000000..b5bda6c --- /dev/null +++ b/banzai_floyds/dbs.py @@ -0,0 +1,38 @@ +from banzai.dbs import Base +from sqlalchemy import Column, Integer, String, Float +from banzai.dbs import get_session +from astropy.coordinates import SkyCoord +from astropy import units + + +def get_standard(ra, dec, db_address, offset_threshold=5): + """ + Check if a position is in the table of flux standards + + ra: float + RA in decimal degrees + dec: float + Declination in decimal degrees + db_address: str + Database address in SQLAlchemy format + offset_threshold: float + Match radius in arcseconds + """ + found_standard = None + test_coordinate = SkyCoord(ra, dec, unit=(units.deg, units.deg)) + with get_session(db_address) as db_session: + standards = db_session.query(FluxStandard).all() + for standard in standards: + standard_coordinate = SkyCoord(standard.ra, standard.dec, unit=(units.deg, units.deg)) + if standard_coordinate.offset(test_coordinate) < (offset_threshold * units.arcsec): + found_standard = standard + return found_standard + + +class FluxStandard(Base): + __tablename__ = 'fluxstandards' + id = Column(Integer, primary_key=True, autoincrement=True) + filename = Column(String(100), unique=True) + location = Column(String(150)) + ra = Column(Float) + dec = Column(Float) diff --git a/banzai_floyds/extract.py b/banzai_floyds/extract.py index d7d2547..f179dc2 100644 --- a/banzai_floyds/extract.py +++ b/banzai_floyds/extract.py @@ -1,7 +1,7 @@ from banzai.stages import Stage import numpy as np from astropy.table import Table, vstack -from banzai_floyds.matched_filter import maximize_match_filter +from banzai_floyds.matched_filter import optimize_match_filter from numpy.polynomial.legendre import Legendre from banzai_floyds.utils.fitting_utils import gauss, fwhm_to_sigma, Legendre2d @@ -71,7 +71,7 @@ def fit_profile(data, profile_width=4): for data_to_fit in data.groups: # Pass a match filter (with correct s/n scaling) with a gaussian with a default width initial_guess = (data_to_fit['y_order'][np.argmax(data_to_fit['data'])], 0.05) - best_fit_center, _ = maximize_match_filter(initial_guess, data_to_fit['data'], data_to_fit['uncertainty'], + best_fit_center, _ = optimize_match_filter(initial_guess, data_to_fit['data'], data_to_fit['uncertainty'], profile_gauss_fixed_width, data_to_fit['y_order'], args=(fwhm_to_sigma(profile_width),)) # If the peak pixel of the match filter is > 2 times the median (or something like that) keep the point @@ -112,7 +112,7 @@ def fit_profile_width(data, profile_fits, poly_order=3, background_poly_order=2, initial_coeffs[0] = np.median(data_to_fit['data']) / data_to_fit['data'][peak] initial_guess = fwhm_to_sigma(default_width), *initial_coeffs - best_fit_sigma, *_ = maximize_match_filter(initial_guess, data_to_fit['data'], + best_fit_sigma, *_ = optimize_match_filter(initial_guess, data_to_fit['data'], data_to_fit['uncertainty'], background_fixed_profile_center, data_to_fit['y_order'], @@ -140,7 +140,7 @@ def fit_background(data, profile_centers, profile_widths, x_poly_order=2, y_poly # Pass a match filter (with correct s/n scaling) with a gaussian with a default width initial_coeffs = np.zeros((x_poly_order + 1) + y_poly_order) initial_coeffs[0] = np.median(data_to_fit['data']) / data_to_fit['data'][peak] - best_fit_coeffs = maximize_match_filter(initial_coeffs, data_to_fit['data'], + best_fit_coeffs = optimize_match_filter(initial_coeffs, data_to_fit['data'], data_to_fit['uncertainty'], background_fixed_profile, (data_to_fit['wavelength'], data_to_fit['y_order']), diff --git a/banzai_floyds/flux.py b/banzai_floyds/flux.py new file mode 100644 index 0000000..cfa35fc --- /dev/null +++ b/banzai_floyds/flux.py @@ -0,0 +1,110 @@ +from banzai.stages import Stage +from banzai.calibrations import CalibrationUser +from banzai_floyds.frames import FLOYDSCalibrationFrame +from banzai_floyds.dbs import get_standard +from banzai_floyds.utils import telluric_utils +import numpy as np +from numpy.polynomial.legendre import Legendre +from scipy.filter1d import SavGol +from banzai_floyds.utils.flux_utils import FluxStandard + + +class SensitivityCalibrationFrame(FLOYDSCalibrationFrame): + def calibration_type(self): + return 'SENSITVITY' + + @classmethod + def new(cls, wavelenghts, correction, meta): + make_calibration_name = file_utils.make_calibration_filename_function( + self.calibration_type, self.runtime_context) + + # use the most recent image in the stack to create the master filename + master_calibration_filename = make_calibration_name( + max(images, key=lambda x: datetime.strptime(x.epoch, '%Y%m%d'))) + return super(cls).__init__([ + ArrayData(data=data, + file_path=master_calibration_filename, + meta=meta, + name='TELLURIC') + ]) + + +class FluxSensitivity(Stage): + AIRMASS_COEFFICIENT = 1.0 + SENSITIVITY_POLY_DEGREE = {1: 5, 2: 3} + WAVELENGTH_DOMAIN = [3000, 11000] + + def do_stage(self, image): + standard_record = get_standard(image.ra, image.dec, self.runtime_context.db_address) + if standard_record is None: + return image + + # Load the standard from the archive + flux_standard = FluxStandard(standard_record, image.extracted) + + sensitivity = np.zeros_like(image.extracted['wavelength']) + # Red and blue respectively + for order_id in [1, 2]: + in_order = image.extract['order_id'] == order_id + data_to_fit = image.extracted[in_order] + # TODO: check this value to make sure we are past the dip in the senstivity function + wavelengths_to_fit = data_to_fit['wavelength'] > 4600.0 + for telluric_region in telluric_utils.TELLURIC_REGIONS: + wavelengths_to_fit = np.logical_and(wavelengths_to_fit, np.logical_not(np.logical_and(wavelengths_to_fit>= telluric_region['min_wavelength'], wavelengths_to_fit <= telluric_region['max_wavelength']))) + # Fit a low order polynomial to the data between the telluric regions in the red + sensitivity_polynomial = Legendre.fit(data_to_fit[wavelengths_to_fit]['wavlength'], + data_to_fit[wavelengths_to_fit]['flux'] / flux_standard.flux[wavelengths_to_fit], self.SENSITIVITY_POLY_DEGREE[order_id], + self.WAVLENGTH_DOMAIN, data_to_fit[wavelengths_to_fit] ** -2.0) + + # Divide the data by the flux standard in the blue + polynomial_wavelengths = data_to_fit[data_to_fit['wavelength'] > 5000]['wavelength'] + sensitivity[in_order][data_to_fit['wavelength'] > 5000] = sensitivity_polynomial(polynomial_wavelengths) + blue_wavelengths = data_to_fit['wavelength'] <= 5000 + # SavGol filter the ratio in the blue + sensitivity[in_order][blue_wavelengths] = SavGol(data_to_fit['flux'][blue_wavelengths] / flux_standard.flux[flux_standard.order_id==order_id][blue_wavelengths]) + + # Scale the flux standard to airmass = 1 + sensitivity = rescale_by_airmass(image.extracted['wavelength'][in_order], sensitivity, image.site.elevation, image.airmass) + + # Save the flux normalization to the db + sensitivity_frame = SensitivityCalibrationFrame() + sensitivity_frame.write(self.runtime_context) + image.sensitivity = sensitivity + return image + + +def rescale_by_airmass(wavelength, flux, elevation, airmass): + # IRAF has extinction curves for KPNO and CTIO. There are some features in the measured values but it is difficult + # to tell if they are real or noise. As such I just fit the data with a smooth function of the form + # a * ((x - x0)/x1) ** -alpha + # My best fit model for CTIO is a=4.18403051, x0=2433.97752773, x1=274.60088089, alpha=1.39522308 + # To convert this to our sites, we raise the function to the power of delta_airmass + # To estimate the delta airmass, we assume a very basic exponential model for the atmosphere + # rho = rho0 * exp(-h/H) where H is 10.4 km from the ideal gas law + # see https://en.wikipedia.org/wiki/Density_of_air#Exponential_approximation + # So the ratio of the airmass (total air column) is (1 - exp(-h1 / H)) / (1 - exp(-h2 / H)) + extinction_curve = 4.18403051 * ((wavelength - 2433.97752773) / 274.60088089) ** -1.39522308 + + # Convert the extinction curve from CTIO to our current site + # Note the elevation of ctio is 2198m + airmass_ratio = (1.0 - np.exp(-elevation / 10400.0)) / (1.0 - np.exp(-2198.0 / 10400.0)) + extinction_curve **= airmass_ratio + extinction_curve **= (airmass - 1) + return flux / (1 - extinction_curve) + + +class FluxCalibrator(CalibrationUser): + + def apply_master_calibration(self, image, master_calibration_image): + flux = [] + for order_id in [1, 2]: + in_order = image.extracted['order_id'] == order_id + # Divide the spectrum by the sensitivity function, correcting for airmass + order_flux = image.extracted['flux'] * master_calibration_image.sensitivity(image.extracted) + # TODO: Refactor this into a function + order_flux = rescale_by_airmass(image.extracted['wavelength'][in_order], order_flux, image.site.elevation, image.airmass) + flux.append(order_flux) + return image + + def calibration_type(self): + return 'SENSITIVITY' diff --git a/banzai_floyds/frames.py b/banzai_floyds/frames.py index 75f1ae2..8357cab 100644 --- a/banzai_floyds/frames.py +++ b/banzai_floyds/frames.py @@ -20,6 +20,7 @@ def __init__(self, hdu_list: list, file_path: str, frame_id: int = None, hdu_ord self.binned_data = None self._extracted = None self.fringe = None + self.sensitivity = None LCOObservationFrame.__init__(self, hdu_list, file_path, frame_id=frame_id, hdu_order=hdu_order) def get_1d_and_2d_spectra_products(self, runtime_context): diff --git a/banzai_floyds/fringe.py b/banzai_floyds/fringe.py index cbbace9..c373302 100644 --- a/banzai_floyds/fringe.py +++ b/banzai_floyds/fringe.py @@ -5,7 +5,7 @@ from banzai_floyds.utils.order_utils import get_order_2d_region from datetime import datetime from scipy.interpolate import CloughTocher2DInterpolator -from banzai_floyds.matched_filter import maximize_match_filter +from banzai_floyds.matched_filter import optimize_match_filter import numpy as np @@ -24,7 +24,7 @@ def find_fringe_offset(image, fringe_spline): red_order = trimmed_orders.data == 1 # Maximize the match filter with weight function using the fringe spline - return maximize_match_filter([0], image.data[red_order], image.uncertainty[red_order], fringe_weights, + return optimize_match_filter([0], image.data[red_order], image.uncertainty[red_order], fringe_weights, (x2d[red_order], y2d[red_order]), args=(fringe_spline,))[0] diff --git a/banzai_floyds/main.py b/banzai_floyds/main.py index d017652..feafc4a 100644 --- a/banzai_floyds/main.py +++ b/banzai_floyds/main.py @@ -1,5 +1,8 @@ from banzai_floyds import settings from banzai.main import parse_args, start_listener +import argparse +from banzai.main import add_settings_to_context +import requests def floyds_run_realtime_pipeline(): @@ -13,3 +16,33 @@ def floyds_run_realtime_pipeline(): runtime_context = parse_args(settings, extra_console_arguments=extra_console_arguments) start_listener(runtime_context) + + +def floyds_add_spectrophotometric_standard(): + parser = argparse.ArgumentParser(description="Add bad pixel mask from a given archive api") + parser.add_argument('--db-address', dest='db_address', + default='mysql://cmccully:password@localhost/test', + help='Database address: Should be in SQLAlchemy form') + args = parser.parse_args() + add_settings_to_context(args, settings) + # Query the archive for all bpm files + url = f'{settings.ARCHIVE_FRAME_URL}/?OBSTYPE=BPM&limit=1000' + archive_auth_header = settings.ARCHIVE_AUTH_HEADER + response = requests.get(url, headers=archive_auth_header) + response.raise_for_status() + results = response.json()['results'] + + # Load each one, saving the calibration info for each + frame_factory = import_utils.import_attribute(settings.FRAME_FACTORY)() + for frame in results: + frame['frameid'] = frame['id'] + try: + bpm_image = frame_factory.open(frame, args) + if bpm_image is not None: + bpm_image.is_master = True + dbs.save_calibration_info(bpm_image.to_db_record(DataProduct(None, filename=bpm_image.filename, + filepath=None)), + args.db_address) + except Exception: + logger.error(f"BPM not added to database: {logs.format_exception()}", + extra_tags={'filename': frame.get('filename')}) diff --git a/banzai_floyds/matched_filter.py b/banzai_floyds/matched_filter.py index d1480cf..3abd646 100644 --- a/banzai_floyds/matched_filter.py +++ b/banzai_floyds/matched_filter.py @@ -302,8 +302,8 @@ def matched_filter_hessian(theta, data, error, weights_function, weights_jacobia return filter_hessian -def maximize_match_filter(initial_guess, data, error, weights_function, x, weights_jacobian_function=None, - weights_hessian_function=None, args=None): +def optimize_match_filter(initial_guess, data, error, weights_function, x, weights_jacobian_function=None, + weights_hessian_function=None, args=None, minimize=False): """ Find the best fit parameters for a match filter model @@ -326,6 +326,8 @@ def maximize_match_filter(initial_guess, data, error, weights_function, x, weigh Should return an array that is the same shape as data args: tuple Any other static arguments that should be passed to the weights function. + minimize: Boolean + Minimize instead of maximize match filter signal? Returns ------- @@ -338,19 +340,27 @@ def maximize_match_filter(initial_guess, data, error, weights_function, x, weigh """ if args is None: args = () + if not minimize: + sign = -1.0 + else: + sign = 1.0 if weights_hessian_function is None and weights_jacobian_function is None: - best_fit = minimize(lambda *params: -matched_filter_metric(*params), initial_guess, - args=(data, error, weights_function, weights_jacobian_function, weights_hessian_function, x, - *args), method='Powell') + best_fit = minimize(lambda *params: sign * matched_filter_metric(*params), initial_guess, + args=(data, error, weights_function, + weights_jacobian_function, + weights_hessian_function, x, *args), + method='Powell') elif weights_hessian_function is None: - best_fit = minimize(lambda *params: -matched_filter_metric(*params), initial_guess, - args=(data, error, weights_function, weights_jacobian_function, weights_hessian_function, x, - *args), - method='BFGS', jac=lambda *params: -matched_filter_jacobian(*params)) + best_fit = minimize(lambda *params: sign * matched_filter_metric(*params), initial_guess, + args=(data, error, weights_function, weights_jacobian_function, + weights_hessian_function, x, *args), + method='BFGS', jac=lambda *params: sign * matched_filter_jacobian(*params)) else: - best_fit = minimize(lambda *params: -matched_filter_metric(*params), initial_guess, - args=(data, error, weights_function, weights_jacobian_function, weights_hessian_function, - x, *args), - method='Newton-CG', hess=lambda *params: -matched_filter_hessian(*params), - jac=lambda *params: -matched_filter_jacobian(*params), options={'eps': 1e-5}) + best_fit = minimize(lambda *params: sign * matched_filter_metric(*params), initial_guess, + args=(data, error, weights_function, weights_jacobian_function, + weights_hessian_function, x, *args), + method='Newton-CG', + hess=lambda *params: sign * matched_filter_hessian(*params), + jac=lambda *params: sign * matched_filter_jacobian(*params), + options={'eps': 1e-5}) return best_fit.x diff --git a/banzai_floyds/orders.py b/banzai_floyds/orders.py index 2daad41..fac452d 100644 --- a/banzai_floyds/orders.py +++ b/banzai_floyds/orders.py @@ -8,7 +8,7 @@ from astropy.io import fits from scipy.special import expit -from banzai_floyds.matched_filter import maximize_match_filter +from banzai_floyds.matched_filter import optimize_match_filter from copy import deepcopy @@ -320,7 +320,7 @@ def fit_order_curve(data, error, order_height, initial_coeffs, x, domain): # For this to work efficiently, you probably need a good initial guess. If we have that, we should define # a window of pixels around the initial guess to do the fit to optimize not fitting a bunch of zeros - best_fit_coeffs = maximize_match_filter(initial_coeffs, data, error, smooth_order_weights, + best_fit_coeffs = optimize_match_filter(initial_coeffs, data, error, smooth_order_weights, x, args=(order_height, domain,)) return Legendre(best_fit_coeffs, domain=domain) @@ -349,7 +349,7 @@ def fit_order_tweak(data, error, order_height, coeffs, x, domain): # For this to work efficiently, you probably need a good initial guess. If we have that, we should define # a window of pixels around the initial guess to do the fit to optimize not fitting a bunch of zeros - best_fit_offsets = maximize_match_filter([0.0], data, error, + best_fit_offsets = optimize_match_filter([0.0], data, error, order_tweak_weights, x, args=(coeffs, order_height, domain)) return best_fit_offsets diff --git a/banzai_floyds/telluric.py b/banzai_floyds/telluric.py new file mode 100644 index 0000000..f4320bc --- /dev/null +++ b/banzai_floyds/telluric.py @@ -0,0 +1,95 @@ +from banzai.stages import Stage +from banzai.calibrations import CalibrationUser +from banzai_floyds.dbs import get_standard +from banzai_floyds.utils.flux_utils import FluxStandard +from banzai_floyds.frames import FLOYDSCalibrationFrame +import numpy as np +from banzai_floyds.utils import telluric_utils +from banzai_floyds.matched_filter import optimize_match_filter +from banzai.data import ArrayData +from astropy.table import Table + + +class TelluricFrame(FLOYDSCalibrationFrame): + def calibration_type(self): + return 'TELLURIC' + + @classmethod + def new(cls, wavelenghts, correction, meta): + make_calibration_name = file_utils.make_calibration_filename_function(self.calibration_type, + self.runtime_context) + + # use the most recent image in the stack to create the master filename + master_calibration_filename = make_calibration_name(max(images, key=lambda x: datetime.strptime(x.epoch, '%Y%m%d') )) + return super(cls).__init__([ArrayData(data=data, file_path=master_calibration_filename, + meta=meta, name='TELLURIC')]) + + +class TelluricMaker(Stage): + def do_stage(self, image): + standard_record = get_standard(image.ra, image.dec, + self.runtime_context.db_address) + if standard_record is None: + return image + + flux_standard = FluxStandard(standard_record, image.extracted) + # Divide out the known flux of the source and the + # sensitivity corrected flux to get the telluric correction + # Only do the red order for the moment + in_order = image.extract['order_id'] == 1 + data = image.extracted[in_order] + correction = np.ones_like(data['wavelength']) + + for region in telluric_utils.TELLURIC_REGIONS: + telluric_wavelengths = np.logical_and(data['wavelength'] >= region['min_wavelength'], + data['wavelength'] <= region['max_wavelength']) + correction[telluric_wavelengths] = data[telluric_wavelengths]['flux'] / flux_standard.data['flux'][telluric_wavelengths] + + # Normalize to airmass = 1 + correction /= image.airmass + # Save the telluric correction to the db + telluric_frame = TelluricFrame(data['wavelength'], correction) + telluric_frame.write(self.runtime_context) + + image.telluric = correction + return image + + +def telluric_shift_weights(shift, x, correction, wavelengths): + return np.interp(x, wavelengths - shift, correction) + + +def telluric_model(params, x, shift, correction, wavelengths): + o2_scale, h20_scale = params + model = np.interp(x, wavelengths - shift, correction) + for region in telluric_utils.TELLURIC_REGIONS: + telluric_wavelengths = np.logical_and(x >= region['min_wavelength'], x <= region['max_wavelength']) + if region['molecule'] == 'O2': + model[telluric_wavelengths] *= o2_scale + elif region['molecule'] == 'H20': + model[telluric_wavelengths] *= h20_scale + return model + + +class TelluricCorrector(CalibrationUser): + def apply_master_calibration(self, image, master_calibration_image): + # Cross correlate the telluric correction with the spectrum to find the windspeed doppler shift + shift = optimize_match_filter([0.0], image.extracted['flux'], image.extracted['uncertainty'], telluric_shift_weights, + args=(master_calibration_image.data['correction'], master_calibration_image.data['wavelength'])) + + # Scale the ozone and O2 bands based on the airmass + o2_scale, h20_scale = optimize_match_filter([1.0, 1.0], image.extracted['flux'], image.extracted['uncertainty'], + telluric_model, + args=(shift, master_calibration_image.data['correction'], + master_calibration_image.data['wavelength']), + minimize=True) + # Scale the water bands by minimizing the match filter statistic between the telluric corrected spectrum + # and the telluric correction + image.extracted['flux'] /= telluric_model((o2_scale, h20_scale), shift, master_calibration_image.correction, master_calibration_image.wavelengths) + image.meta['TELSHIFT'] = shift + image.meta['TELO2SCL'] = o2_scale + image.meta['TELH20SC'] = h20_scale + return image + + def calibration_type(self): + return 'TELLURIC' diff --git a/banzai_floyds/tests/test_e2e.py b/banzai_floyds/tests/test_e2e.py index 71a7a79..e6ec2be 100644 --- a/banzai_floyds/tests/test_e2e.py +++ b/banzai_floyds/tests/test_e2e.py @@ -68,6 +68,7 @@ def expected_filenames(file_table): def init(mock_configdb): banzai.dbs.create_db(os.environ["DB_ADDRESS"]) banzai.dbs.populate_instrument_tables(db_address=os.environ["DB_ADDRESS"], configdb_address='http://fakeconfigdb') + banzai_floyds.dbs.ingest_standards() @pytest.mark.e2e diff --git a/banzai_floyds/tests/test_flux.py b/banzai_floyds/tests/test_flux.py new file mode 100644 index 0000000..4dbdd17 --- /dev/null +++ b/banzai_floyds/tests/test_flux.py @@ -0,0 +1,19 @@ +import numpy as np +from banzai_floyds.flux import FluxCalibrator, FluxSensitivity +from banzai import context +from banzai_floyds.tests.utils import generate_fake_extracted_frame + + +def test_flux_stage(): + frame, fake_sensitivity_frame = generate_fake_extracted_frame(sensitivity=True) + stage = FluxCalibrator(context.Context({})) + frame = stage.apply_master_calibration(frame, fake_sensitivity_frame) + np.testing.assert_allclose(frame.extracted['flux'], frame.input_flux) + + +def test_sensitvity_stage(): + frame, fake_sensitivity_frame = generate_fake_extracted_frame(sensitivity=True) + stage = FluxSensitivity(context.Context({'CALIBRATION_FRAME_CLASS': 'banzai_floyds.tests.utils.TestCalibrationFrame'})) + frame = stage.do_stage([frame]) + found_sensitivity = frame.sensitivity + np.testing.assert_allclose(found_sensitivity, fake_sensitivity_frame.data) diff --git a/banzai_floyds/tests/test_telluric.py b/banzai_floyds/tests/test_telluric.py new file mode 100644 index 0000000..75b4afc --- /dev/null +++ b/banzai_floyds/tests/test_telluric.py @@ -0,0 +1,23 @@ +import numpy as np +from banzai import context +from banzai_floyds.tests.utils import generate_fake_extracted_frame +from banzai_floyds.telluric import TelluricCorrector, TelluricMaker + + +def test_telluric_corrector(): + frame, fake_telluric_frame = generate_fake_extracted_frame( + sensitivity=True, airmass=1.2) + stage = TelluricCorrector(context.Context({})) + frame = stage.apply_master_calibration(frame, fake_telluric_frame) + np.testing.assert_allclose(frame.extracted['flux'], frame.input_flux) + + +def test_telluric_maker(): + frame, fake_telluric_frame = generate_fake_extracted_frame( + sensitivity=False, airmass=1.0, telluric=True) + stage = TelluricMaker(context.Context({ + 'CALIBRATION_FRAME_CLASS': + 'banzai_floyds.tests.utils.TestCalibrationFrame' + })) + frame = stage.do_stage([frame]) + np.testing.assert_allclose(frame.telluric, fake_telluric_frame.data) diff --git a/banzai_floyds/tests/utils.py b/banzai_floyds/tests/utils.py index b78d752..c959cce 100644 --- a/banzai_floyds/tests/utils.py +++ b/banzai_floyds/tests/utils.py @@ -1,6 +1,6 @@ import matplotlib.pyplot as plt from astropy.visualization import ZScaleInterval -from banzai_floyds.frames import FLOYDSObservationFrame +from banzai_floyds.frames import FLOYDSObservationFrame, FLOYDSCalibrationFrame from banzai_floyds.orders import Orders from banzai_floyds.utils.fitting_utils import fwhm_to_sigma, gauss from banzai_floyds.utils.wavelength_utils import WavelengthSolution @@ -193,3 +193,32 @@ def generate_fake_science_frame(include_sky=False, flat_spectrum=True, fringe=Fa frame.input_spectrum += strength * gauss(frame.input_spectrum_wavelengths, input_line, width) return frame + + +def generate_fake_extracted_frame(telluric=True, sensitivity=True): + wavelength_model1 = Legendre((7487.2, 2662.3, 20., -5., 1.), + domain=(0, 1700)) + wavelength_model2 = Legendre((4573.5, 1294.6, 15.), domain=(475, 1975)) + + data = Table + + sensitivity = line + + telluric = np.ones(data[['flux']]) + # Add the A and B bands + telluric /= gauss() + telluric /= gauss() + + data['flux'] /= sensitivity + data['flux'] *= telluric + frame = FLOYDSObservationFrame([ArrayData(data)]) + frame.telluric = telluric + frame.sensitivity = sensitivity + frame.input_telluric = telluric + frame.input_sensitivity = sensitivity + + +class TestCalibrationFrame(FLOYDSCalibrationFrame): + def write(self, context): + # Short circuit the write method so we don't actually write anything during testing + return diff --git a/banzai_floyds/utils/flux_utils.py b/banzai_floyds/utils/flux_utils.py new file mode 100644 index 0000000..9a52374 --- /dev/null +++ b/banzai_floyds/utils/flux_utils.py @@ -0,0 +1,14 @@ +from banzai_floyds.frames import FLOYDSCalibrationFrame + + +class FluxStandard(FLOYDSCalibrationFrame): + @classmethod + def new(cls, wavelenghts, correction, meta): + make_calibration_name = file_utils.make_calibration_filename_function(self.calibration_type, + self.runtime_context) + + # Put on the same wavelength grid as the extracted_data + + master_calibration_filename = make_calibration_name(max(images, key=lambda x: datetime.strptime(x.epoch, '%Y%m%d') )) + return super(cls).__init__([ArrayData(data=data, file_path=master_calibration_filename, + meta=meta, name='TELLURIC')]) diff --git a/banzai_floyds/utils/telluric_utils.py b/banzai_floyds/utils/telluric_utils.py new file mode 100644 index 0000000..572f174 --- /dev/null +++ b/banzai_floyds/utils/telluric_utils.py @@ -0,0 +1,10 @@ +# These regions were pulled from examing TelFit (Gulikson+14, https://iopscience.iop.org/article/10.1088/0004-6256/148/3/53) +# plots and comparing to MoelcFit (Smette+15, 10.1051/0004-6361/201423932) +TELLURIC_REGIONS = [{'wavlength_min': 5010.0, 'wavlength_max': 6110.0, 'molecule': 'O2'}, + {'wavlength_min': 6220.0, 'wavlength_max': 6400.0, 'molecule': 'O2'}, + {'wavlength_min': 6400.0, 'wavlength_max': 6700.0, 'molecule': 'H2O'}, + {'wavlength_min': 6800.0, 'wavlength_max': 7100.0, 'molecule': 'O2'}, + {'wavlength_min': 7100.0, 'wavlength_max': 7500.0, 'molecule': 'H2O'}, + {'wavlength_min': 7580.0, 'wavlength_max': 7770.0, 'molecule': 'O2'}, + {'wavlength_min': 7800.0, 'wavlength_max': 8690.0, 'molecule': 'H2O'}, + {'wavlength_min': 8730.0, 'wavlength_max': 10550.0, 'molecule': 'H2O'}] diff --git a/banzai_floyds/wavelengths.py b/banzai_floyds/wavelengths.py index ad622fc..8155669 100644 --- a/banzai_floyds/wavelengths.py +++ b/banzai_floyds/wavelengths.py @@ -4,7 +4,7 @@ from banzai.calibrations import CalibrationUser from banzai_floyds.matched_filter import matched_filter_metric from scipy.signal import find_peaks -from banzai_floyds.matched_filter import maximize_match_filter +from banzai_floyds.matched_filter import optimize_match_filter from banzai_floyds.frames import FLOYDSCalibrationFrame from banzai.data import ArrayData from banzai_floyds.utils.wavelength_utils import WavelengthSolution, tilt_coordinates @@ -123,7 +123,7 @@ def refine_peak_centers(data, error, peaks, line_width, domain=None): data_window = data[window] error_window = error[window] x = np.arange(-half_fit_window, half_fit_window + 1, dtype=float) - best_fit_center, best_fit_line_width = maximize_match_filter((0, line_sigma), data_window, error_window, + best_fit_center, best_fit_line_width = optimize_match_filter((0, line_sigma), data_window, error_window, centroiding_weights, x) centers.append(best_fit_center + peak) centers = np.array(centers) + min(domain) @@ -218,7 +218,7 @@ def full_wavelength_solution(data, error, x, y, initial_polynomial_coefficients, ------- best_fit_params: 1-d array: (best_fit_tilt, best_fit_line_width, *best_fit_polynomial_coefficients) """ - best_fit_params = maximize_match_filter((initial_tilt, initial_line_width, *initial_polynomial_coefficients), data, + best_fit_params = optimize_match_filter((initial_tilt, initial_line_width, *initial_polynomial_coefficients), data, error, full_wavelength_solution_weights, (x, y), args=(lines,)) return best_fit_params From 01f7115b3d44ad08306c48b6e55a1f6e3c081ecd Mon Sep 17 00:00:00 2001 From: Curtis McCully Date: Mon, 10 Jul 2023 14:51:03 -0400 Subject: [PATCH 02/19] Fixes to generating fake extracted data. --- banzai_floyds/flux.py | 4 +-- banzai_floyds/tests/test_flux.py | 3 +- banzai_floyds/tests/utils.py | 52 ++++++++++++++++++++++++-------- 3 files changed, 44 insertions(+), 15 deletions(-) diff --git a/banzai_floyds/flux.py b/banzai_floyds/flux.py index cfa35fc..0d36450 100644 --- a/banzai_floyds/flux.py +++ b/banzai_floyds/flux.py @@ -5,7 +5,7 @@ from banzai_floyds.utils import telluric_utils import numpy as np from numpy.polynomial.legendre import Legendre -from scipy.filter1d import SavGol +from scipy.signal import savgol_filter from banzai_floyds.utils.flux_utils import FluxStandard @@ -61,7 +61,7 @@ def do_stage(self, image): sensitivity[in_order][data_to_fit['wavelength'] > 5000] = sensitivity_polynomial(polynomial_wavelengths) blue_wavelengths = data_to_fit['wavelength'] <= 5000 # SavGol filter the ratio in the blue - sensitivity[in_order][blue_wavelengths] = SavGol(data_to_fit['flux'][blue_wavelengths] / flux_standard.flux[flux_standard.order_id==order_id][blue_wavelengths]) + sensitivity[in_order][blue_wavelengths] = savgol_filter(data_to_fit['flux'][blue_wavelengths] / flux_standard.flux[flux_standard.order_id==order_id][blue_wavelengths]) # Scale the flux standard to airmass = 1 sensitivity = rescale_by_airmass(image.extracted['wavelength'][in_order], sensitivity, image.site.elevation, image.airmass) diff --git a/banzai_floyds/tests/test_flux.py b/banzai_floyds/tests/test_flux.py index 4dbdd17..bcc3483 100644 --- a/banzai_floyds/tests/test_flux.py +++ b/banzai_floyds/tests/test_flux.py @@ -5,7 +5,8 @@ def test_flux_stage(): - frame, fake_sensitivity_frame = generate_fake_extracted_frame(sensitivity=True) + frame = generate_fake_extracted_frame(sensitivity=True) + fake_sensitivity_frame = frame.input_sensitivity stage = FluxCalibrator(context.Context({})) frame = stage.apply_master_calibration(frame, fake_sensitivity_frame) np.testing.assert_allclose(frame.extracted['flux'], frame.input_flux) diff --git a/banzai_floyds/tests/utils.py b/banzai_floyds/tests/utils.py index c959cce..dbdde4f 100644 --- a/banzai_floyds/tests/utils.py +++ b/banzai_floyds/tests/utils.py @@ -13,6 +13,9 @@ from astropy.io import ascii import pkg_resources from types import SimpleNamespace +from astropy.table import Table +from banzai.data import DataTable +from astropy.modeling.models import Polynomial1D SKYLINE_LIST = ascii.read(pkg_resources.resource_filename('banzai_floyds.tests', 'data/skylines.dat')) @@ -199,23 +202,48 @@ def generate_fake_extracted_frame(telluric=True, sensitivity=True): wavelength_model1 = Legendre((7487.2, 2662.3, 20., -5., 1.), domain=(0, 1700)) wavelength_model2 = Legendre((4573.5, 1294.6, 15.), domain=(475, 1975)) + read_noise = 4.0 + + # Let's use a parabola for the flux and linear sensitivity + order_pixels1 = np.arange(wavelength_model1.domain[0], wavelength_model1.domain[1] + 1, 1) + order_piexels2 = np.arange(wavelength_model2.domain[0], wavelength_model2.domain[1] + 1, 1) + wavelengths = np.hstack([wavelength_model1(order_pixels1), wavelength_model2(order_piexels2)]) + orders = np.hstack([np.ones_like(order_pixels1), 2 * np.ones_like(order_piexels2)]) + sensitivity_domain = (3000.0, 12000.0) + input_flux = Polynomial1D(2, domain=sensitivity_domain, c0=3, c2=-2)(wavelengths) * 3000.0 + + sensitivity_wavelengths = np.arange(sensitivity_domain[0], sensitivity_domain[1] + 1, 1) + sensitivity_model = Polynomial1D(1, domain=sensitivity_domain, c0=1.0, c1=0.24) + sensitivity = sensitivity_model(wavelengths) + sensitivity_data = Table({'sensitivity': sensitivity_model(sensitivity_wavelengths), + 'wavelengths': sensitivity_wavelengths}) + + telluric = np.ones_like(wavelengths) + # Add the A and B bands + telluric -= 40 * gauss(wavelengths, 6940.0, 35) + telluric -= 55 * gauss(wavelengths, 7650.0, 30) - data = Table + telluric_model = np.ones_like(sensitivity_wavelengths) + telluric_model -= 40 * gauss(sensitivity_wavelengths, 6940.0, 35) + telluric_model -= 55 * gauss(sensitivity_wavelengths, 7650.0, 30) - sensitivity = line + telluric_data = Table({'telluric': telluric_model, 'wavelengths': sensitivity_wavelengths}) - telluric = np.ones(data[['flux']]) - # Add the A and B bands - telluric /= gauss() - telluric /= gauss() - - data['flux'] /= sensitivity - data['flux'] *= telluric - frame = FLOYDSObservationFrame([ArrayData(data)]) - frame.telluric = telluric - frame.sensitivity = sensitivity + flux = input_flux / sensitivity * telluric + + flux = np.random.poisson(flux.astype(int)).astype(float) + flux += np.random.normal(read_noise, size=flux.shape) + flux_error = np.sqrt(read_noise**2 + np.abs(flux)) + data = Table({'wavelengths': wavelengths, 'flux': input_flux, 'fluxerror': flux_error, 'order_id': orders}) + + frame = FLOYDSObservationFrame([DataTable(data, name='EXTRACTED')], file_path='foo.fits') + frame.telluric = telluric_data + frame.sensitivity = sensitivity_data frame.input_telluric = telluric frame.input_sensitivity = sensitivity + frame.input_flux = input_flux + frame.extracted = data + return frame class TestCalibrationFrame(FLOYDSCalibrationFrame): From fa30d5d6f19835aa817e613396fb87c35490d754 Mon Sep 17 00:00:00 2001 From: Curtis McCully Date: Thu, 13 Jul 2023 17:05:10 -0400 Subject: [PATCH 03/19] Finally got the first flux calibration test working. --- banzai_floyds/flux.py | 64 ++++++------------------------- banzai_floyds/frames.py | 24 ++++++++++++ banzai_floyds/settings.py | 2 +- banzai_floyds/telluric.py | 19 --------- banzai_floyds/tests/test_flux.py | 9 ++--- banzai_floyds/tests/utils.py | 20 ++++++---- banzai_floyds/utils/flux_utils.py | 29 ++++++++------ 7 files changed, 70 insertions(+), 97 deletions(-) diff --git a/banzai_floyds/flux.py b/banzai_floyds/flux.py index 0d36450..4ac467d 100644 --- a/banzai_floyds/flux.py +++ b/banzai_floyds/flux.py @@ -1,32 +1,11 @@ from banzai.stages import Stage from banzai.calibrations import CalibrationUser -from banzai_floyds.frames import FLOYDSCalibrationFrame from banzai_floyds.dbs import get_standard from banzai_floyds.utils import telluric_utils import numpy as np from numpy.polynomial.legendre import Legendre from scipy.signal import savgol_filter -from banzai_floyds.utils.flux_utils import FluxStandard - - -class SensitivityCalibrationFrame(FLOYDSCalibrationFrame): - def calibration_type(self): - return 'SENSITVITY' - - @classmethod - def new(cls, wavelenghts, correction, meta): - make_calibration_name = file_utils.make_calibration_filename_function( - self.calibration_type, self.runtime_context) - - # use the most recent image in the stack to create the master filename - master_calibration_filename = make_calibration_name( - max(images, key=lambda x: datetime.strptime(x.epoch, '%Y%m%d'))) - return super(cls).__init__([ - ArrayData(data=data, - file_path=master_calibration_filename, - meta=meta, - name='TELLURIC') - ]) +from banzai_floyds.utils.flux_utils import rescale_by_airmass class FluxSensitivity(Stage): @@ -73,38 +52,17 @@ def do_stage(self, image): return image -def rescale_by_airmass(wavelength, flux, elevation, airmass): - # IRAF has extinction curves for KPNO and CTIO. There are some features in the measured values but it is difficult - # to tell if they are real or noise. As such I just fit the data with a smooth function of the form - # a * ((x - x0)/x1) ** -alpha - # My best fit model for CTIO is a=4.18403051, x0=2433.97752773, x1=274.60088089, alpha=1.39522308 - # To convert this to our sites, we raise the function to the power of delta_airmass - # To estimate the delta airmass, we assume a very basic exponential model for the atmosphere - # rho = rho0 * exp(-h/H) where H is 10.4 km from the ideal gas law - # see https://en.wikipedia.org/wiki/Density_of_air#Exponential_approximation - # So the ratio of the airmass (total air column) is (1 - exp(-h1 / H)) / (1 - exp(-h2 / H)) - extinction_curve = 4.18403051 * ((wavelength - 2433.97752773) / 274.60088089) ** -1.39522308 - - # Convert the extinction curve from CTIO to our current site - # Note the elevation of ctio is 2198m - airmass_ratio = (1.0 - np.exp(-elevation / 10400.0)) / (1.0 - np.exp(-2198.0 / 10400.0)) - extinction_curve **= airmass_ratio - extinction_curve **= (airmass - 1) - return flux / (1 - extinction_curve) - - -class FluxCalibrator(CalibrationUser): - +class StandardLoader(CalibrationUser): def apply_master_calibration(self, image, master_calibration_image): - flux = [] - for order_id in [1, 2]: - in_order = image.extracted['order_id'] == order_id - # Divide the spectrum by the sensitivity function, correcting for airmass - order_flux = image.extracted['flux'] * master_calibration_image.sensitivity(image.extracted) - # TODO: Refactor this into a function - order_flux = rescale_by_airmass(image.extracted['wavelength'][in_order], order_flux, image.site.elevation, image.airmass) - flux.append(order_flux) + image.sensitivity = master_calibration_image.sensitivity + image.telluric = master_calibration_image.telluric return image def calibration_type(self): - return 'SENSITIVITY' + return 'STANDARD' + + +class FluxCalibrator(Stage): + def do_stage(self, image): + image.apply_sensitivity() + return image diff --git a/banzai_floyds/frames.py b/banzai_floyds/frames.py index 8357cab..51b6886 100644 --- a/banzai_floyds/frames.py +++ b/banzai_floyds/frames.py @@ -8,6 +8,8 @@ from banzai_floyds.utils.fitting_utils import gauss import os from astropy.io import fits +from banzai_floyds.utils.flux_utils import rescale_by_airmass +from banzai.dbs import get_session, Site class FLOYDSObservationFrame(LCOObservationFrame): @@ -21,6 +23,7 @@ def __init__(self, hdu_list: list, file_path: str, frame_id: int = None, hdu_ord self._extracted = None self.fringe = None self.sensitivity = None + self.telluric = None LCOObservationFrame.__init__(self, hdu_list, file_path, frame_id=frame_id, hdu_order=hdu_order) def get_1d_and_2d_spectra_products(self, runtime_context): @@ -29,6 +32,8 @@ def get_1d_and_2d_spectra_products(self, runtime_context): os.path.join(self.get_output_directory(runtime_context), filename_1d)) fits_1d = frame_1d.to_fits(runtime_context) fits_1d['SPECTRUM1D'].name = 'SPECTRUM' + # TODO: Save telluric and sensitivity corrections that were applied + filename_2d = filename_1d.replace('-1d.fits', '-2d.fits') fits_1d[0].header['L1ID2D'] = filename_2d @@ -72,6 +77,10 @@ def profile(self, value): x, y = self.binned_data['x'].astype(int), self.binned_data['y'].astype(int) self.binned_data['weights'] = profile_data[y, x] + @property + def airmass(self): + return self.meta['AIRMASS'] + @property def background(self): return self['BACKGROUND'].data @@ -94,6 +103,13 @@ def extracted(self, value): self._extracted = value self.add_or_update(DataTable(value, name='EXTRACTED', meta=fits.Header({}))) + def apply_sensitivity(self): + for order_id in [1, 2]: + in_order = self.extracted['order_id'] == order_id + # Divide the spectrum by the sensitivity function, correcting for airmass + sensitivity = np.interp(self.extracted['wavelength'][in_order], self.sensitivity['wavelength'], self.sensitivity['sensitivity']) + self.extracted['flux'][in_order] *= sensitivity + class FLOYDSCalibrationFrame(LCOCalibrationFrame, FLOYDSObservationFrame): def __init__(self, hdu_list: list, file_path: str, frame_id: int = None, grouping_criteria: list = None, @@ -122,6 +138,10 @@ def is_empty_coordinate(coordinate): def open(self, path, runtime_context) -> Optional[ObservationFrame]: image = super().open(path, runtime_context) + # Get the elevation from the db of the site + with get_session(runtime_context.db_address) as db_session: + site = db_session.query(Site).filter(Site.name == self.site).first() + self.elevation = site.elevation # Set a default BIASSEC and TRIMSEC if they are unknown if image.meta.get('BIASSEC', 'UNKNOWN').lower() in ['unknown', 'n/a']: image.meta['BIASSEC'] = '[2049:2079,1:512]' @@ -140,4 +160,8 @@ def open(self, path, runtime_context) -> Optional[ObservationFrame]: image.wavelengths = WavelengthSolution.from_header(image['WAVELENGTHS'].meta, image.orders) if 'FRINGE' in image: image.fringe = image['FRINGE'].data + if 'TELLURIC' in image: + image.telluric = image['TELLURIC'].data + if 'SENSITIVITY' in image: + image.sensitivity = image['SENSITIVITY'].data return image diff --git a/banzai_floyds/settings.py b/banzai_floyds/settings.py index 25454d6..5bf6e7a 100644 --- a/banzai_floyds/settings.py +++ b/banzai_floyds/settings.py @@ -31,4 +31,4 @@ CALIBRATION_FRAME_CLASS = 'banzai_floyds.frames.FLOYDSCalibrationFrame' -CALIBRATION_IMAGE_TYPES = ['BIAS', 'DARK', 'SKYFLAT', 'BPM', 'LAMPFLAT', 'ARC'] +CALIBRATION_IMAGE_TYPES = ['BIAS', 'DARK', 'SKYFLAT', 'BPM', 'LAMPFLAT', 'ARC', 'STANDARD'] diff --git a/banzai_floyds/telluric.py b/banzai_floyds/telluric.py index f4320bc..62f1e4d 100644 --- a/banzai_floyds/telluric.py +++ b/banzai_floyds/telluric.py @@ -1,28 +1,9 @@ from banzai.stages import Stage from banzai.calibrations import CalibrationUser from banzai_floyds.dbs import get_standard -from banzai_floyds.utils.flux_utils import FluxStandard -from banzai_floyds.frames import FLOYDSCalibrationFrame import numpy as np from banzai_floyds.utils import telluric_utils from banzai_floyds.matched_filter import optimize_match_filter -from banzai.data import ArrayData -from astropy.table import Table - - -class TelluricFrame(FLOYDSCalibrationFrame): - def calibration_type(self): - return 'TELLURIC' - - @classmethod - def new(cls, wavelenghts, correction, meta): - make_calibration_name = file_utils.make_calibration_filename_function(self.calibration_type, - self.runtime_context) - - # use the most recent image in the stack to create the master filename - master_calibration_filename = make_calibration_name(max(images, key=lambda x: datetime.strptime(x.epoch, '%Y%m%d') )) - return super(cls).__init__([ArrayData(data=data, file_path=master_calibration_filename, - meta=meta, name='TELLURIC')]) class TelluricMaker(Stage): diff --git a/banzai_floyds/tests/test_flux.py b/banzai_floyds/tests/test_flux.py index bcc3483..dccdb62 100644 --- a/banzai_floyds/tests/test_flux.py +++ b/banzai_floyds/tests/test_flux.py @@ -5,15 +5,14 @@ def test_flux_stage(): - frame = generate_fake_extracted_frame(sensitivity=True) - fake_sensitivity_frame = frame.input_sensitivity + frame = generate_fake_extracted_frame() stage = FluxCalibrator(context.Context({})) - frame = stage.apply_master_calibration(frame, fake_sensitivity_frame) - np.testing.assert_allclose(frame.extracted['flux'], frame.input_flux) + frame = stage.do_stage(frame) + np.testing.assert_allclose(frame.extracted['flux'], frame.input_flux, rtol=0.05) def test_sensitvity_stage(): - frame, fake_sensitivity_frame = generate_fake_extracted_frame(sensitivity=True) + frame, fake_sensitivity_frame = generate_fake_extracted_frame() stage = FluxSensitivity(context.Context({'CALIBRATION_FRAME_CLASS': 'banzai_floyds.tests.utils.TestCalibrationFrame'})) frame = stage.do_stage([frame]) found_sensitivity = frame.sensitivity diff --git a/banzai_floyds/tests/utils.py b/banzai_floyds/tests/utils.py index dbdde4f..68d5e83 100644 --- a/banzai_floyds/tests/utils.py +++ b/banzai_floyds/tests/utils.py @@ -14,8 +14,9 @@ import pkg_resources from types import SimpleNamespace from astropy.table import Table -from banzai.data import DataTable +from banzai.data import HeaderOnly from astropy.modeling.models import Polynomial1D +from astropy.io import fits SKYLINE_LIST = ascii.read(pkg_resources.resource_filename('banzai_floyds.tests', 'data/skylines.dat')) @@ -198,7 +199,7 @@ def generate_fake_science_frame(include_sky=False, flat_spectrum=True, fringe=Fa return frame -def generate_fake_extracted_frame(telluric=True, sensitivity=True): +def generate_fake_extracted_frame(do_telluric=False): wavelength_model1 = Legendre((7487.2, 2662.3, 20., -5., 1.), domain=(0, 1700)) wavelength_model2 = Legendre((4573.5, 1294.6, 15.), domain=(475, 1975)) @@ -216,7 +217,7 @@ def generate_fake_extracted_frame(telluric=True, sensitivity=True): sensitivity_model = Polynomial1D(1, domain=sensitivity_domain, c0=1.0, c1=0.24) sensitivity = sensitivity_model(wavelengths) sensitivity_data = Table({'sensitivity': sensitivity_model(sensitivity_wavelengths), - 'wavelengths': sensitivity_wavelengths}) + 'wavelength': sensitivity_wavelengths}) telluric = np.ones_like(wavelengths) # Add the A and B bands @@ -227,22 +228,25 @@ def generate_fake_extracted_frame(telluric=True, sensitivity=True): telluric_model -= 40 * gauss(sensitivity_wavelengths, 6940.0, 35) telluric_model -= 55 * gauss(sensitivity_wavelengths, 7650.0, 30) - telluric_data = Table({'telluric': telluric_model, 'wavelengths': sensitivity_wavelengths}) + telluric_data = Table({'telluric': telluric_model, 'wavelength': sensitivity_wavelengths}) - flux = input_flux / sensitivity * telluric + flux = input_flux / sensitivity + if do_telluric: + flux *= telluric flux = np.random.poisson(flux.astype(int)).astype(float) flux += np.random.normal(read_noise, size=flux.shape) flux_error = np.sqrt(read_noise**2 + np.abs(flux)) - data = Table({'wavelengths': wavelengths, 'flux': input_flux, 'fluxerror': flux_error, 'order_id': orders}) + data = Table({'wavelength': wavelengths, 'flux': flux, 'fluxerror': flux_error, 'order_id': orders}) - frame = FLOYDSObservationFrame([DataTable(data, name='EXTRACTED')], file_path='foo.fits') + frame = FLOYDSObservationFrame([HeaderOnly(fits.Header({'AIRMASS': 1.0}))], file_path='foo.fits') frame.telluric = telluric_data frame.sensitivity = sensitivity_data frame.input_telluric = telluric frame.input_sensitivity = sensitivity frame.input_flux = input_flux - frame.extracted = data + frame.extracted = data # Use the elevation of CTIO which is what the telluric correction is scaled to + frame.elevation = 2198.0 return frame diff --git a/banzai_floyds/utils/flux_utils.py b/banzai_floyds/utils/flux_utils.py index 9a52374..a299766 100644 --- a/banzai_floyds/utils/flux_utils.py +++ b/banzai_floyds/utils/flux_utils.py @@ -1,14 +1,21 @@ -from banzai_floyds.frames import FLOYDSCalibrationFrame +import numpy as np -class FluxStandard(FLOYDSCalibrationFrame): - @classmethod - def new(cls, wavelenghts, correction, meta): - make_calibration_name = file_utils.make_calibration_filename_function(self.calibration_type, - self.runtime_context) +def rescale_by_airmass(wavelength, flux, elevation, airmass): + # IRAF has extinction curves for KPNO and CTIO. There are some features in the measured values but it is difficult + # to tell if they are real or noise. As such I just fit the data with a smooth function of the form + # a * ((x - x0)/x1) ** -alpha + # My best fit model for CTIO is a=4.18403051, x0=2433.97752773, x1=274.60088089, alpha=1.39522308 + # To convert this to our sites, we raise the function to the power of delta_airmass + # To estimate the delta airmass, we assume a very basic exponential model for the atmosphere + # rho = rho0 * exp(-h/H) where H is 10.4 km from the ideal gas law + # see https://en.wikipedia.org/wiki/Density_of_air#Exponential_approximation + # So the ratio of the airmass (total air column) is (1 - exp(-h1 / H)) / (1 - exp(-h2 / H)) + extinction_curve = 4.18403051 * ((wavelength - 2433.97752773) / 274.60088089) ** -1.39522308 - # Put on the same wavelength grid as the extracted_data - - master_calibration_filename = make_calibration_name(max(images, key=lambda x: datetime.strptime(x.epoch, '%Y%m%d') )) - return super(cls).__init__([ArrayData(data=data, file_path=master_calibration_filename, - meta=meta, name='TELLURIC')]) + # Convert the extinction curve from CTIO to our current site + # Note the elevation of ctio is 2198m + airmass_ratio = (1.0 - np.exp(-elevation / 10400.0)) / (1.0 - np.exp(-2198.0 / 10400.0)) + extinction_curve **= airmass_ratio + extinction_curve **= airmass + return flux / (1 - extinction_curve) From 24b1f6ec9003cb8d065a490289817b9797d3c3db Mon Sep 17 00:00:00 2001 From: Curtis McCully Date: Wed, 26 Jul 2023 16:27:09 -0400 Subject: [PATCH 04/19] Tests run at least now. --- banzai_floyds/dbs.py | 11 ++++-- banzai_floyds/flux.py | 29 ++++++++------- banzai_floyds/frames.py | 11 ++++++ banzai_floyds/matched_filter.py | 34 +++++++++--------- banzai_floyds/telluric.py | 51 ++++++++++++--------------- banzai_floyds/tests/test_flux.py | 22 +++++++++--- banzai_floyds/tests/test_telluric.py | 23 ++++++------ banzai_floyds/tests/utils.py | 2 +- banzai_floyds/utils/flux_utils.py | 7 ++-- banzai_floyds/utils/telluric_utils.py | 16 ++++----- 10 files changed, 115 insertions(+), 91 deletions(-) diff --git a/banzai_floyds/dbs.py b/banzai_floyds/dbs.py index b5bda6c..b6a23ed 100644 --- a/banzai_floyds/dbs.py +++ b/banzai_floyds/dbs.py @@ -3,6 +3,8 @@ from banzai.dbs import get_session from astropy.coordinates import SkyCoord from astropy import units +from banzai.utils.fits_utils import open_fits_file +from astropy.table import Table def get_standard(ra, dec, db_address, offset_threshold=5): @@ -26,13 +28,18 @@ def get_standard(ra, dec, db_address, offset_threshold=5): standard_coordinate = SkyCoord(standard.ra, standard.dec, unit=(units.deg, units.deg)) if standard_coordinate.offset(test_coordinate) < (offset_threshold * units.arcsec): found_standard = standard - return found_standard + if found_standard is not None: + found_standard = open_fits_file({'path': found_standard.filepath, 'frameid': found_standard.frame_id, + 'filename': found_standard.filename}) + + return Table(found_standard) class FluxStandard(Base): __tablename__ = 'fluxstandards' id = Column(Integer, primary_key=True, autoincrement=True) filename = Column(String(100), unique=True) - location = Column(String(150)) + filepath = Column(String(150)) + frameid = Column(Integer, nullable=True) ra = Column(Float) dec = Column(Float) diff --git a/banzai_floyds/flux.py b/banzai_floyds/flux.py index 4ac467d..66d5887 100644 --- a/banzai_floyds/flux.py +++ b/banzai_floyds/flux.py @@ -14,40 +14,39 @@ class FluxSensitivity(Stage): WAVELENGTH_DOMAIN = [3000, 11000] def do_stage(self, image): - standard_record = get_standard(image.ra, image.dec, self.runtime_context.db_address) - if standard_record is None: + flux_standard = get_standard(image.ra, image.dec, self.runtime_context.db_address) + if flux_standard is None: return image - - # Load the standard from the archive - flux_standard = FluxStandard(standard_record, image.extracted) - + sensitivity = np.zeros_like(image.extracted['wavelength']) # Red and blue respectively for order_id in [1, 2]: - in_order = image.extract['order_id'] == order_id + in_order = image.extracted['order_id'] == order_id data_to_fit = image.extracted[in_order] # TODO: check this value to make sure we are past the dip in the senstivity function wavelengths_to_fit = data_to_fit['wavelength'] > 4600.0 for telluric_region in telluric_utils.TELLURIC_REGIONS: - wavelengths_to_fit = np.logical_and(wavelengths_to_fit, np.logical_not(np.logical_and(wavelengths_to_fit>= telluric_region['min_wavelength'], wavelengths_to_fit <= telluric_region['max_wavelength']))) + wavelengths_to_fit = np.logical_and(wavelengths_to_fit, np.logical_not(np.logical_and(wavelengths_to_fit>= telluric_region['wavelength_min'], wavelengths_to_fit <= telluric_region['wavelength_max']))) + + expected_flux = np.interp(data_to_fit[wavelengths_to_fit]['wavelength'], flux_standard['wavelength'], flux_standard['flux']) # Fit a low order polynomial to the data between the telluric regions in the red - sensitivity_polynomial = Legendre.fit(data_to_fit[wavelengths_to_fit]['wavlength'], - data_to_fit[wavelengths_to_fit]['flux'] / flux_standard.flux[wavelengths_to_fit], self.SENSITIVITY_POLY_DEGREE[order_id], - self.WAVLENGTH_DOMAIN, data_to_fit[wavelengths_to_fit] ** -2.0) + sensitivity_polynomial = Legendre.fit(data_to_fit[wavelengths_to_fit]['wavelength'], + data_to_fit[wavelengths_to_fit]['flux'] / expected_flux, self.SENSITIVITY_POLY_DEGREE[order_id], + self.WAVELENGTH_DOMAIN, w=data_to_fit[wavelengths_to_fit]['fluxerror'] ** -2.0) # Divide the data by the flux standard in the blue polynomial_wavelengths = data_to_fit[data_to_fit['wavelength'] > 5000]['wavelength'] sensitivity[in_order][data_to_fit['wavelength'] > 5000] = sensitivity_polynomial(polynomial_wavelengths) blue_wavelengths = data_to_fit['wavelength'] <= 5000 # SavGol filter the ratio in the blue - sensitivity[in_order][blue_wavelengths] = savgol_filter(data_to_fit['flux'][blue_wavelengths] / flux_standard.flux[flux_standard.order_id==order_id][blue_wavelengths]) + expected_flux = np.interp(data_to_fit[blue_wavelengths]['wavelength'], flux_standard['wavelength'], flux_standard['flux']) + # We choose a window size of 7 which is just a little bigger than the resolution element + sensitivity[in_order][blue_wavelengths] = savgol_filter(data_to_fit['flux'][blue_wavelengths] / expected_flux, 7, 3) # Scale the flux standard to airmass = 1 - sensitivity = rescale_by_airmass(image.extracted['wavelength'][in_order], sensitivity, image.site.elevation, image.airmass) + sensitivity = rescale_by_airmass(image.extracted['wavelength'], sensitivity, image.elevation, image.airmass) # Save the flux normalization to the db - sensitivity_frame = SensitivityCalibrationFrame() - sensitivity_frame.write(self.runtime_context) image.sensitivity = sensitivity return image diff --git a/banzai_floyds/frames.py b/banzai_floyds/frames.py index 51b6886..cb872ca 100644 --- a/banzai_floyds/frames.py +++ b/banzai_floyds/frames.py @@ -109,6 +109,17 @@ def apply_sensitivity(self): # Divide the spectrum by the sensitivity function, correcting for airmass sensitivity = np.interp(self.extracted['wavelength'][in_order], self.sensitivity['wavelength'], self.sensitivity['sensitivity']) self.extracted['flux'][in_order] *= sensitivity + self.extracted['fluxerror'][in_order] *= sensitivity + self.extracted['fluxe'] = rescale_by_airmass(self.extracted['wavelength'], self.extracted['flux'], self.elevation, self.airmass) + self.extracted['fluxerror'] = rescale_by_airmass(self.extracted['wavelength'], self.extracted['fluxerror'], self.elevation, self.airmass) + + @property + def elevation(self): + return self.meta['ELEVATIO'] + + @elevation.setter + def elevation(self, value): + self.meta['ELEVATIO'] = value class FLOYDSCalibrationFrame(LCOCalibrationFrame, FLOYDSObservationFrame): diff --git a/banzai_floyds/matched_filter.py b/banzai_floyds/matched_filter.py index 3abd646..1e7a7ac 100644 --- a/banzai_floyds/matched_filter.py +++ b/banzai_floyds/matched_filter.py @@ -3,7 +3,7 @@ https://ui.adsabs.harvard.edu/abs/2017ApJ...836..187Z/abstract """ import numpy as np -from scipy.optimize import minimize +from scipy import optimize def matched_filter_signal(data, error, weights): @@ -345,22 +345,22 @@ def optimize_match_filter(initial_guess, data, error, weights_function, x, weigh else: sign = 1.0 if weights_hessian_function is None and weights_jacobian_function is None: - best_fit = minimize(lambda *params: sign * matched_filter_metric(*params), initial_guess, - args=(data, error, weights_function, - weights_jacobian_function, - weights_hessian_function, x, *args), - method='Powell') + best_fit = optimize.minimize(lambda *params: sign * matched_filter_metric(*params), initial_guess, + args=(data, error, weights_function, + weights_jacobian_function, + weights_hessian_function, x, *args), + method='Powell') elif weights_hessian_function is None: - best_fit = minimize(lambda *params: sign * matched_filter_metric(*params), initial_guess, - args=(data, error, weights_function, weights_jacobian_function, - weights_hessian_function, x, *args), - method='BFGS', jac=lambda *params: sign * matched_filter_jacobian(*params)) + best_fit = optimize.minimize(lambda *params: sign * matched_filter_metric(*params), initial_guess, + args=(data, error, weights_function, weights_jacobian_function, + weights_hessian_function, x, *args), + method='BFGS', jac=lambda *params: sign * matched_filter_jacobian(*params)) else: - best_fit = minimize(lambda *params: sign * matched_filter_metric(*params), initial_guess, - args=(data, error, weights_function, weights_jacobian_function, - weights_hessian_function, x, *args), - method='Newton-CG', - hess=lambda *params: sign * matched_filter_hessian(*params), - jac=lambda *params: sign * matched_filter_jacobian(*params), - options={'eps': 1e-5}) + best_fit = optimize.minimize(lambda *params: sign * matched_filter_metric(*params), initial_guess, + args=(data, error, weights_function, weights_jacobian_function, + weights_hessian_function, x, *args), + method='Newton-CG', + hess=lambda *params: sign * matched_filter_hessian(*params), + jac=lambda *params: sign * matched_filter_jacobian(*params), + options={'eps': 1e-5}) return best_fit.x diff --git a/banzai_floyds/telluric.py b/banzai_floyds/telluric.py index 62f1e4d..a601553 100644 --- a/banzai_floyds/telluric.py +++ b/banzai_floyds/telluric.py @@ -1,42 +1,41 @@ from banzai.stages import Stage -from banzai.calibrations import CalibrationUser from banzai_floyds.dbs import get_standard import numpy as np from banzai_floyds.utils import telluric_utils from banzai_floyds.matched_filter import optimize_match_filter +from astropy.table import Table class TelluricMaker(Stage): def do_stage(self, image): - standard_record = get_standard(image.ra, image.dec, - self.runtime_context.db_address) - if standard_record is None: + flux_standard = get_standard(image.ra, image.dec, + self.runtime_context.db_address) + if flux_standard is None: return image - flux_standard = FluxStandard(standard_record, image.extracted) # Divide out the known flux of the source and the # sensitivity corrected flux to get the telluric correction # Only do the red order for the moment - in_order = image.extract['order_id'] == 1 + in_order = image.extracted['order_id'] == 1 data = image.extracted[in_order] correction = np.ones_like(data['wavelength']) for region in telluric_utils.TELLURIC_REGIONS: - telluric_wavelengths = np.logical_and(data['wavelength'] >= region['min_wavelength'], - data['wavelength'] <= region['max_wavelength']) - correction[telluric_wavelengths] = data[telluric_wavelengths]['flux'] / flux_standard.data['flux'][telluric_wavelengths] - + telluric_wavelengths = np.logical_and(data['wavelength'] >= region['wavelength_min'], + data['wavelength'] <= region['wavelength_max']) + reference_flux = np.interp( + data[telluric_wavelengths]['wavelength'], + flux_standard['wavelength'], flux_standard['flux']) + correction[telluric_wavelengths] = data[telluric_wavelengths]['flux'] / reference_flux # Normalize to airmass = 1 correction /= image.airmass - # Save the telluric correction to the db - telluric_frame = TelluricFrame(data['wavelength'], correction) - telluric_frame.write(self.runtime_context) - image.telluric = correction + image.telluric = Table({'telluric': correction, 'wavelength': data['wavelength']}) return image def telluric_shift_weights(shift, x, correction, wavelengths): + shift, = shift return np.interp(x, wavelengths - shift, correction) @@ -44,7 +43,7 @@ def telluric_model(params, x, shift, correction, wavelengths): o2_scale, h20_scale = params model = np.interp(x, wavelengths - shift, correction) for region in telluric_utils.TELLURIC_REGIONS: - telluric_wavelengths = np.logical_and(x >= region['min_wavelength'], x <= region['max_wavelength']) + telluric_wavelengths = np.logical_and(x >= region['wavelength_min'], x <= region['wavelength_max']) if region['molecule'] == 'O2': model[telluric_wavelengths] *= o2_scale elif region['molecule'] == 'H20': @@ -52,25 +51,21 @@ def telluric_model(params, x, shift, correction, wavelengths): return model -class TelluricCorrector(CalibrationUser): - def apply_master_calibration(self, image, master_calibration_image): +class TelluricCorrector(Stage): + def do_stage(self, image): # Cross correlate the telluric correction with the spectrum to find the windspeed doppler shift - shift = optimize_match_filter([0.0], image.extracted['flux'], image.extracted['uncertainty'], telluric_shift_weights, - args=(master_calibration_image.data['correction'], master_calibration_image.data['wavelength'])) + shift = optimize_match_filter([0.0], image.extracted['flux'], image.extracted['fluxerror'], telluric_shift_weights, + image.extracted['wavelength'], args=(image.telluric['telluric'], image.telluric['wavelength'])) # Scale the ozone and O2 bands based on the airmass - o2_scale, h20_scale = optimize_match_filter([1.0, 1.0], image.extracted['flux'], image.extracted['uncertainty'], - telluric_model, - args=(shift, master_calibration_image.data['correction'], - master_calibration_image.data['wavelength']), + o2_scale, h20_scale = optimize_match_filter([1.0, 1.0], image.extracted['flux'], image.extracted['fluxerror'], + telluric_model, image.extracted['wavelength'], + args=(shift, image.telluric['telluric'], image.telluric['wavelength']), minimize=True) # Scale the water bands by minimizing the match filter statistic between the telluric corrected spectrum # and the telluric correction - image.extracted['flux'] /= telluric_model((o2_scale, h20_scale), shift, master_calibration_image.correction, master_calibration_image.wavelengths) - image.meta['TELSHIFT'] = shift + image.extracted['flux'] /= telluric_model((o2_scale, h20_scale), image.extracted['wavelength'], shift, image.telluric['telluric'], image.telluric['wavelength']) + image.meta['TELSHIFT'] = shift[0] image.meta['TELO2SCL'] = o2_scale image.meta['TELH20SC'] = h20_scale return image - - def calibration_type(self): - return 'TELLURIC' diff --git a/banzai_floyds/tests/test_flux.py b/banzai_floyds/tests/test_flux.py index dccdb62..08e4bf7 100644 --- a/banzai_floyds/tests/test_flux.py +++ b/banzai_floyds/tests/test_flux.py @@ -2,18 +2,30 @@ from banzai_floyds.flux import FluxCalibrator, FluxSensitivity from banzai import context from banzai_floyds.tests.utils import generate_fake_extracted_frame +import mock +from astropy.table import Table +from banzai_floyds.utils.flux_utils import rescale_by_airmass def test_flux_stage(): + np.random.seed(13482935) frame = generate_fake_extracted_frame() stage = FluxCalibrator(context.Context({})) frame = stage.do_stage(frame) np.testing.assert_allclose(frame.extracted['flux'], frame.input_flux, rtol=0.05) -def test_sensitvity_stage(): - frame, fake_sensitivity_frame = generate_fake_extracted_frame() - stage = FluxSensitivity(context.Context({'CALIBRATION_FRAME_CLASS': 'banzai_floyds.tests.utils.TestCalibrationFrame'})) - frame = stage.do_stage([frame]) +@mock.patch('banzai_floyds.flux.get_standard') +def test_sensitivity_stage(mock_standard): + frame = generate_fake_extracted_frame() + mock_standard.return_value = Table({'flux': frame.input_flux, 'wavelength': frame.extracted['wavelength']}) + stage = FluxSensitivity(context.Context({'CALIBRATION_FRAME_CLASS': 'banzai_floyds.tests.utils.TestCalibrationFrame', 'db_address': 'foo.sqlite'})) + frame = stage.do_stage(frame) found_sensitivity = frame.sensitivity - np.testing.assert_allclose(found_sensitivity, fake_sensitivity_frame.data) + np.testing.assert_allclose(found_sensitivity, frame.input_sensitivity) + + +def test_null_airmass_correction(): + wavelengths = np.arange(3000, 11000, 1) + corrected = rescale_by_airmass(wavelengths, np.ones_like(wavelengths), 2198.0, 1) + np.testing.assert_allclose(corrected, np.ones_like(wavelengths)) diff --git a/banzai_floyds/tests/test_telluric.py b/banzai_floyds/tests/test_telluric.py index 75b4afc..c22f90d 100644 --- a/banzai_floyds/tests/test_telluric.py +++ b/banzai_floyds/tests/test_telluric.py @@ -2,22 +2,21 @@ from banzai import context from banzai_floyds.tests.utils import generate_fake_extracted_frame from banzai_floyds.telluric import TelluricCorrector, TelluricMaker +from astropy.table import Table +import mock def test_telluric_corrector(): - frame, fake_telluric_frame = generate_fake_extracted_frame( - sensitivity=True, airmass=1.2) + frame = generate_fake_extracted_frame() stage = TelluricCorrector(context.Context({})) - frame = stage.apply_master_calibration(frame, fake_telluric_frame) + frame = stage.do_stage(frame) np.testing.assert_allclose(frame.extracted['flux'], frame.input_flux) -def test_telluric_maker(): - frame, fake_telluric_frame = generate_fake_extracted_frame( - sensitivity=False, airmass=1.0, telluric=True) - stage = TelluricMaker(context.Context({ - 'CALIBRATION_FRAME_CLASS': - 'banzai_floyds.tests.utils.TestCalibrationFrame' - })) - frame = stage.do_stage([frame]) - np.testing.assert_allclose(frame.telluric, fake_telluric_frame.data) +@mock.patch('banzai_floyds.telluric.get_standard') +def test_telluric_maker(mock_standard): + frame = generate_fake_extracted_frame() + mock_standard.return_value = Table({'flux': frame.input_flux, 'wavelength': frame.extracted['wavelength']}) + stage = TelluricMaker(context.Context({'CALIBRATION_FRAME_CLASS': 'banzai_floyds.tests.utils.TestCalibrationFrame', 'db_address': 'foo.sqlite'})) + frame = stage.do_stage(frame) + np.testing.assert_allclose(frame.telluric['telluric'], frame.input_telluric) diff --git a/banzai_floyds/tests/utils.py b/banzai_floyds/tests/utils.py index 68d5e83..333d997 100644 --- a/banzai_floyds/tests/utils.py +++ b/banzai_floyds/tests/utils.py @@ -242,7 +242,7 @@ def generate_fake_extracted_frame(do_telluric=False): frame = FLOYDSObservationFrame([HeaderOnly(fits.Header({'AIRMASS': 1.0}))], file_path='foo.fits') frame.telluric = telluric_data frame.sensitivity = sensitivity_data - frame.input_telluric = telluric + frame.input_telluric = telluric[orders == 1] frame.input_sensitivity = sensitivity frame.input_flux = input_flux frame.extracted = data # Use the elevation of CTIO which is what the telluric correction is scaled to diff --git a/banzai_floyds/utils/flux_utils.py b/banzai_floyds/utils/flux_utils.py index a299766..d591fcc 100644 --- a/banzai_floyds/utils/flux_utils.py +++ b/banzai_floyds/utils/flux_utils.py @@ -2,7 +2,7 @@ def rescale_by_airmass(wavelength, flux, elevation, airmass): - # IRAF has extinction curves for KPNO and CTIO. There are some features in the measured values but it is difficult + # IRAF has extinction curves for KPNO and CTIO. There are some features in the measured values but it is difficult # to tell if they are real or noise. As such I just fit the data with a smooth function of the form # a * ((x - x0)/x1) ** -alpha # My best fit model for CTIO is a=4.18403051, x0=2433.97752773, x1=274.60088089, alpha=1.39522308 @@ -17,5 +17,6 @@ def rescale_by_airmass(wavelength, flux, elevation, airmass): # Note the elevation of ctio is 2198m airmass_ratio = (1.0 - np.exp(-elevation / 10400.0)) / (1.0 - np.exp(-2198.0 / 10400.0)) extinction_curve **= airmass_ratio - extinction_curve **= airmass - return flux / (1 - extinction_curve) + transmission = 1 - extinction_curve + transmission **= airmass - 1 + return flux / transmission diff --git a/banzai_floyds/utils/telluric_utils.py b/banzai_floyds/utils/telluric_utils.py index 572f174..f6ffc5e 100644 --- a/banzai_floyds/utils/telluric_utils.py +++ b/banzai_floyds/utils/telluric_utils.py @@ -1,10 +1,10 @@ # These regions were pulled from examing TelFit (Gulikson+14, https://iopscience.iop.org/article/10.1088/0004-6256/148/3/53) # plots and comparing to MoelcFit (Smette+15, 10.1051/0004-6361/201423932) -TELLURIC_REGIONS = [{'wavlength_min': 5010.0, 'wavlength_max': 6110.0, 'molecule': 'O2'}, - {'wavlength_min': 6220.0, 'wavlength_max': 6400.0, 'molecule': 'O2'}, - {'wavlength_min': 6400.0, 'wavlength_max': 6700.0, 'molecule': 'H2O'}, - {'wavlength_min': 6800.0, 'wavlength_max': 7100.0, 'molecule': 'O2'}, - {'wavlength_min': 7100.0, 'wavlength_max': 7500.0, 'molecule': 'H2O'}, - {'wavlength_min': 7580.0, 'wavlength_max': 7770.0, 'molecule': 'O2'}, - {'wavlength_min': 7800.0, 'wavlength_max': 8690.0, 'molecule': 'H2O'}, - {'wavlength_min': 8730.0, 'wavlength_max': 10550.0, 'molecule': 'H2O'}] +TELLURIC_REGIONS = [{'wavelength_min': 5010.0, 'wavelength_max': 6110.0, 'molecule': 'O2'}, + {'wavelength_min': 6220.0, 'wavelength_max': 6400.0, 'molecule': 'O2'}, + {'wavelength_min': 6400.0, 'wavelength_max': 6700.0, 'molecule': 'H2O'}, + {'wavelength_min': 6800.0, 'wavelength_max': 7100.0, 'molecule': 'O2'}, + {'wavelength_min': 7100.0, 'wavelength_max': 7500.0, 'molecule': 'H2O'}, + {'wavelength_min': 7580.0, 'wavelength_max': 7770.0, 'molecule': 'O2'}, + {'wavelength_min': 7800.0, 'wavelength_max': 8690.0, 'molecule': 'H2O'}, + {'wavelength_min': 8730.0, 'wavelength_max': 10550.0, 'molecule': 'H2O'}] From b3315ff1106badf9e7e34be9c8494595859d9201 Mon Sep 17 00:00:00 2001 From: Curtis McCully Date: Mon, 31 Jul 2023 17:42:03 -0400 Subject: [PATCH 05/19] Tests finally pass --- banzai_floyds/flux.py | 19 ++++++++++++------- banzai_floyds/telluric.py | 27 +++++++++++++++++++++------ banzai_floyds/tests/test_flux.py | 2 +- banzai_floyds/tests/test_telluric.py | 10 ++++++---- banzai_floyds/tests/utils.py | 7 ++++--- banzai_floyds/utils/telluric_utils.py | 13 ++++++++++--- 6 files changed, 54 insertions(+), 24 deletions(-) diff --git a/banzai_floyds/flux.py b/banzai_floyds/flux.py index 66d5887..4643e9a 100644 --- a/banzai_floyds/flux.py +++ b/banzai_floyds/flux.py @@ -10,7 +10,7 @@ class FluxSensitivity(Stage): AIRMASS_COEFFICIENT = 1.0 - SENSITIVITY_POLY_DEGREE = {1: 5, 2: 3} + SENSITIVITY_POLY_DEGREE = {1: 3, 2: 3} WAVELENGTH_DOMAIN = [3000, 11000] def do_stage(self, image): @@ -18,7 +18,8 @@ def do_stage(self, image): if flux_standard is None: return image - sensitivity = np.zeros_like(image.extracted['wavelength']) + flux_standard.sort('wavelength') + sensitivity = np.zeros_like(image.extracted['wavelength'].data) # Red and blue respectively for order_id in [1, 2]: in_order = image.extracted['order_id'] == order_id @@ -26,23 +27,27 @@ def do_stage(self, image): # TODO: check this value to make sure we are past the dip in the senstivity function wavelengths_to_fit = data_to_fit['wavelength'] > 4600.0 for telluric_region in telluric_utils.TELLURIC_REGIONS: - wavelengths_to_fit = np.logical_and(wavelengths_to_fit, np.logical_not(np.logical_and(wavelengths_to_fit>= telluric_region['wavelength_min'], wavelengths_to_fit <= telluric_region['wavelength_max']))) + in_region = np.logical_and(data_to_fit['wavelength'] >= telluric_region['wavelength_min'], + data_to_fit['wavelength'] <= telluric_region['wavelength_max']) + wavelengths_to_fit = np.logical_and(wavelengths_to_fit, np.logical_not(in_region)) expected_flux = np.interp(data_to_fit[wavelengths_to_fit]['wavelength'], flux_standard['wavelength'], flux_standard['flux']) # Fit a low order polynomial to the data between the telluric regions in the red sensitivity_polynomial = Legendre.fit(data_to_fit[wavelengths_to_fit]['wavelength'], - data_to_fit[wavelengths_to_fit]['flux'] / expected_flux, self.SENSITIVITY_POLY_DEGREE[order_id], + expected_flux / data_to_fit[wavelengths_to_fit]['flux'], self.SENSITIVITY_POLY_DEGREE[order_id], self.WAVELENGTH_DOMAIN, w=data_to_fit[wavelengths_to_fit]['fluxerror'] ** -2.0) # Divide the data by the flux standard in the blue polynomial_wavelengths = data_to_fit[data_to_fit['wavelength'] > 5000]['wavelength'] - sensitivity[in_order][data_to_fit['wavelength'] > 5000] = sensitivity_polynomial(polynomial_wavelengths) + this_sensitivity = np.zeros_like(data_to_fit['wavelength'].data) + this_sensitivity[data_to_fit['wavelength'] > 5000] = sensitivity_polynomial(polynomial_wavelengths) blue_wavelengths = data_to_fit['wavelength'] <= 5000 # SavGol filter the ratio in the blue expected_flux = np.interp(data_to_fit[blue_wavelengths]['wavelength'], flux_standard['wavelength'], flux_standard['flux']) # We choose a window size of 7 which is just a little bigger than the resolution element - sensitivity[in_order][blue_wavelengths] = savgol_filter(data_to_fit['flux'][blue_wavelengths] / expected_flux, 7, 3) - + this_sensitivity[blue_wavelengths] = savgol_filter(expected_flux / data_to_fit['flux'][blue_wavelengths], 7, 3) + # We have to use this temp sensitivity variable because of how python does numpy array copying + sensitivity[in_order] = this_sensitivity # Scale the flux standard to airmass = 1 sensitivity = rescale_by_airmass(image.extracted['wavelength'], sensitivity, image.elevation, image.airmass) diff --git a/banzai_floyds/telluric.py b/banzai_floyds/telluric.py index a601553..1831c38 100644 --- a/banzai_floyds/telluric.py +++ b/banzai_floyds/telluric.py @@ -4,6 +4,8 @@ from banzai_floyds.utils import telluric_utils from banzai_floyds.matched_filter import optimize_match_filter from astropy.table import Table +from scipy.optimize import minimize +from scipy.signal import savgol_filter class TelluricMaker(Stage): @@ -13,9 +15,9 @@ def do_stage(self, image): if flux_standard is None: return image + flux_standard.sort('wavelength') # Divide out the known flux of the source and the # sensitivity corrected flux to get the telluric correction - # Only do the red order for the moment in_order = image.extracted['order_id'] == 1 data = image.extracted[in_order] correction = np.ones_like(data['wavelength']) @@ -27,6 +29,7 @@ def do_stage(self, image): data[telluric_wavelengths]['wavelength'], flux_standard['wavelength'], flux_standard['flux']) correction[telluric_wavelengths] = data[telluric_wavelengths]['flux'] / reference_flux + # Normalize to airmass = 1 correction /= image.airmass @@ -51,17 +54,29 @@ def telluric_model(params, x, shift, correction, wavelengths): return model +def spectrum_telluric_second_deriv(params, wavelength, flux, shift, telluric_correction, telluric_wavelengths): + telluric_corrected = flux / telluric_model(params, wavelength, shift, telluric_correction, telluric_wavelengths) + # We choose a window size of 7 which is just a little bigger than the resolution element + second_deriv = savgol_filter(telluric_corrected, 7, 3, deriv=2) + return np.abs(second_deriv).sum() + + class TelluricCorrector(Stage): def do_stage(self, image): + image.telluric.sort('wavelength') # Cross correlate the telluric correction with the spectrum to find the windspeed doppler shift shift = optimize_match_filter([0.0], image.extracted['flux'], image.extracted['fluxerror'], telluric_shift_weights, image.extracted['wavelength'], args=(image.telluric['telluric'], image.telluric['wavelength'])) - # Scale the ozone and O2 bands based on the airmass - o2_scale, h20_scale = optimize_match_filter([1.0, 1.0], image.extracted['flux'], image.extracted['fluxerror'], - telluric_model, image.extracted['wavelength'], - args=(shift, image.telluric['telluric'], image.telluric['wavelength']), - minimize=True) + + o2_scale, h20_scale = minimize(spectrum_telluric_second_deriv, [1.0, 1.0], args=(image.extracted['wavelength'], + image.extracted['flux'], + shift, + image.telluric['telluric'], + image.telluric['wavelength']), + method='Nelder-Mead').x + # TODO: Minimize the second derivative of the spectrum after the telluric correction is applied + o2_scale, h20_scale = 1.0, 1.0 # Scale the water bands by minimizing the match filter statistic between the telluric corrected spectrum # and the telluric correction image.extracted['flux'] /= telluric_model((o2_scale, h20_scale), image.extracted['wavelength'], shift, image.telluric['telluric'], image.telluric['wavelength']) diff --git a/banzai_floyds/tests/test_flux.py b/banzai_floyds/tests/test_flux.py index 08e4bf7..bca4a30 100644 --- a/banzai_floyds/tests/test_flux.py +++ b/banzai_floyds/tests/test_flux.py @@ -22,7 +22,7 @@ def test_sensitivity_stage(mock_standard): stage = FluxSensitivity(context.Context({'CALIBRATION_FRAME_CLASS': 'banzai_floyds.tests.utils.TestCalibrationFrame', 'db_address': 'foo.sqlite'})) frame = stage.do_stage(frame) found_sensitivity = frame.sensitivity - np.testing.assert_allclose(found_sensitivity, frame.input_sensitivity) + np.testing.assert_allclose(found_sensitivity, frame.input_sensitivity, atol=0.03) def test_null_airmass_correction(): diff --git a/banzai_floyds/tests/test_telluric.py b/banzai_floyds/tests/test_telluric.py index c22f90d..72426e2 100644 --- a/banzai_floyds/tests/test_telluric.py +++ b/banzai_floyds/tests/test_telluric.py @@ -7,16 +7,18 @@ def test_telluric_corrector(): - frame = generate_fake_extracted_frame() + np.random.seed(1325263) + frame = generate_fake_extracted_frame(do_telluric=True, do_sensitivity=False) stage = TelluricCorrector(context.Context({})) frame = stage.do_stage(frame) - np.testing.assert_allclose(frame.extracted['flux'], frame.input_flux) + np.testing.assert_allclose(frame.extracted['flux'], frame.input_flux, rtol=0.05) @mock.patch('banzai_floyds.telluric.get_standard') def test_telluric_maker(mock_standard): - frame = generate_fake_extracted_frame() + np.random.seed(124234153) + frame = generate_fake_extracted_frame(do_telluric=True, do_sensitivity=False) mock_standard.return_value = Table({'flux': frame.input_flux, 'wavelength': frame.extracted['wavelength']}) stage = TelluricMaker(context.Context({'CALIBRATION_FRAME_CLASS': 'banzai_floyds.tests.utils.TestCalibrationFrame', 'db_address': 'foo.sqlite'})) frame = stage.do_stage(frame) - np.testing.assert_allclose(frame.telluric['telluric'], frame.input_telluric) + np.testing.assert_allclose(frame.telluric['telluric'], frame.input_telluric, atol=0.05) diff --git a/banzai_floyds/tests/utils.py b/banzai_floyds/tests/utils.py index 333d997..e9be625 100644 --- a/banzai_floyds/tests/utils.py +++ b/banzai_floyds/tests/utils.py @@ -199,7 +199,7 @@ def generate_fake_science_frame(include_sky=False, flat_spectrum=True, fringe=Fa return frame -def generate_fake_extracted_frame(do_telluric=False): +def generate_fake_extracted_frame(do_telluric=False, do_sensitivity=True): wavelength_model1 = Legendre((7487.2, 2662.3, 20., -5., 1.), domain=(0, 1700)) wavelength_model2 = Legendre((4573.5, 1294.6, 15.), domain=(475, 1975)) @@ -229,8 +229,9 @@ def generate_fake_extracted_frame(do_telluric=False): telluric_model -= 55 * gauss(sensitivity_wavelengths, 7650.0, 30) telluric_data = Table({'telluric': telluric_model, 'wavelength': sensitivity_wavelengths}) - - flux = input_flux / sensitivity + flux = input_flux.copy() + if do_sensitivity: + flux /= sensitivity if do_telluric: flux *= telluric diff --git a/banzai_floyds/utils/telluric_utils.py b/banzai_floyds/utils/telluric_utils.py index f6ffc5e..a14b923 100644 --- a/banzai_floyds/utils/telluric_utils.py +++ b/banzai_floyds/utils/telluric_utils.py @@ -1,10 +1,17 @@ -# These regions were pulled from examing TelFit (Gulikson+14, https://iopscience.iop.org/article/10.1088/0004-6256/148/3/53) +# These regions were pulled from examing TelFit (Gulikson+14, +# https://iopscience.iop.org/article/10.1088/0004-6256/148/3/53) # plots and comparing to MoelcFit (Smette+15, 10.1051/0004-6361/201423932) -TELLURIC_REGIONS = [{'wavelength_min': 5010.0, 'wavelength_max': 6110.0, 'molecule': 'O2'}, +# Also see Matheson et al. 2000, AJ 120, 1499 +# I had to be pretty judicious on my choice of telluric regions so that there were anchor points for all the +# polynomial fits +TELLURIC_REGIONS = [{'wavelength_min': 5000.0, 'wavelength_max': 5155.0, 'molecule': 'O2'}, + {'wavelength_min': 5370.0, 'wavelength_max': 5545.0, 'molecule': 'O2'}, + {'wavelength_min': 5655.0, 'wavelength_max': 5815.0, 'molecule': 'O2'}, + {'wavelength_min': 5850.0, 'wavelength_max': 6050.0, 'molecule': 'H2O'}, {'wavelength_min': 6220.0, 'wavelength_max': 6400.0, 'molecule': 'O2'}, {'wavelength_min': 6400.0, 'wavelength_max': 6700.0, 'molecule': 'H2O'}, {'wavelength_min': 6800.0, 'wavelength_max': 7100.0, 'molecule': 'O2'}, {'wavelength_min': 7100.0, 'wavelength_max': 7500.0, 'molecule': 'H2O'}, {'wavelength_min': 7580.0, 'wavelength_max': 7770.0, 'molecule': 'O2'}, {'wavelength_min': 7800.0, 'wavelength_max': 8690.0, 'molecule': 'H2O'}, - {'wavelength_min': 8730.0, 'wavelength_max': 10550.0, 'molecule': 'H2O'}] + {'wavelength_min': 8730.0, 'wavelength_max': 9800.0, 'molecule': 'H2O'}] From 33b7fab62722d69dceb2ffe20dcf91e391d13dbc Mon Sep 17 00:00:00 2001 From: Joseph Chatelain Date: Mon, 31 Jul 2023 20:33:20 -0700 Subject: [PATCH 06/19] fix some styling --- banzai_floyds/flux.py | 21 ++++++++++++++------- banzai_floyds/frames.py | 14 +++++++++++--- banzai_floyds/telluric.py | 14 ++++++++------ banzai_floyds/tests/test_flux.py | 4 +++- banzai_floyds/tests/test_telluric.py | 3 ++- banzai_floyds/tests/test_wavelengths.py | 2 +- banzai_floyds/tests/utils.py | 3 +-- banzai_floyds/utils/telluric_utils.py | 4 ++-- 8 files changed, 42 insertions(+), 23 deletions(-) diff --git a/banzai_floyds/flux.py b/banzai_floyds/flux.py index 4643e9a..343b9a2 100644 --- a/banzai_floyds/flux.py +++ b/banzai_floyds/flux.py @@ -17,7 +17,7 @@ def do_stage(self, image): flux_standard = get_standard(image.ra, image.dec, self.runtime_context.db_address) if flux_standard is None: return image - + flux_standard.sort('wavelength') sensitivity = np.zeros_like(image.extracted['wavelength'].data) # Red and blue respectively @@ -30,12 +30,16 @@ def do_stage(self, image): in_region = np.logical_and(data_to_fit['wavelength'] >= telluric_region['wavelength_min'], data_to_fit['wavelength'] <= telluric_region['wavelength_max']) wavelengths_to_fit = np.logical_and(wavelengths_to_fit, np.logical_not(in_region)) - - expected_flux = np.interp(data_to_fit[wavelengths_to_fit]['wavelength'], flux_standard['wavelength'], flux_standard['flux']) + + expected_flux = np.interp(data_to_fit[wavelengths_to_fit]['wavelength'], + flux_standard['wavelength'], + flux_standard['flux']) # Fit a low order polynomial to the data between the telluric regions in the red sensitivity_polynomial = Legendre.fit(data_to_fit[wavelengths_to_fit]['wavelength'], - expected_flux / data_to_fit[wavelengths_to_fit]['flux'], self.SENSITIVITY_POLY_DEGREE[order_id], - self.WAVELENGTH_DOMAIN, w=data_to_fit[wavelengths_to_fit]['fluxerror'] ** -2.0) + expected_flux / data_to_fit[wavelengths_to_fit]['flux'], + self.SENSITIVITY_POLY_DEGREE[order_id], + self.WAVELENGTH_DOMAIN, + w=data_to_fit[wavelengths_to_fit]['fluxerror'] ** -2.0) # Divide the data by the flux standard in the blue polynomial_wavelengths = data_to_fit[data_to_fit['wavelength'] > 5000]['wavelength'] @@ -43,9 +47,12 @@ def do_stage(self, image): this_sensitivity[data_to_fit['wavelength'] > 5000] = sensitivity_polynomial(polynomial_wavelengths) blue_wavelengths = data_to_fit['wavelength'] <= 5000 # SavGol filter the ratio in the blue - expected_flux = np.interp(data_to_fit[blue_wavelengths]['wavelength'], flux_standard['wavelength'], flux_standard['flux']) + expected_flux = np.interp(data_to_fit[blue_wavelengths]['wavelength'], + flux_standard['wavelength'], + flux_standard['flux']) # We choose a window size of 7 which is just a little bigger than the resolution element - this_sensitivity[blue_wavelengths] = savgol_filter(expected_flux / data_to_fit['flux'][blue_wavelengths], 7, 3) + this_sensitivity[blue_wavelengths] = savgol_filter(expected_flux / data_to_fit['flux'][blue_wavelengths], + 7, 3) # We have to use this temp sensitivity variable because of how python does numpy array copying sensitivity[in_order] = this_sensitivity # Scale the flux standard to airmass = 1 diff --git a/banzai_floyds/frames.py b/banzai_floyds/frames.py index cb872ca..050ce8c 100644 --- a/banzai_floyds/frames.py +++ b/banzai_floyds/frames.py @@ -107,11 +107,19 @@ def apply_sensitivity(self): for order_id in [1, 2]: in_order = self.extracted['order_id'] == order_id # Divide the spectrum by the sensitivity function, correcting for airmass - sensitivity = np.interp(self.extracted['wavelength'][in_order], self.sensitivity['wavelength'], self.sensitivity['sensitivity']) + sensitivity = np.interp(self.extracted['wavelength'][in_order], + self.sensitivity['wavelength'], + self.sensitivity['sensitivity']) self.extracted['flux'][in_order] *= sensitivity self.extracted['fluxerror'][in_order] *= sensitivity - self.extracted['fluxe'] = rescale_by_airmass(self.extracted['wavelength'], self.extracted['flux'], self.elevation, self.airmass) - self.extracted['fluxerror'] = rescale_by_airmass(self.extracted['wavelength'], self.extracted['fluxerror'], self.elevation, self.airmass) + self.extracted['fluxe'] = rescale_by_airmass(self.extracted['wavelength'], + self.extracted['flux'], + self.elevation, + self.airmass) + self.extracted['fluxerror'] = rescale_by_airmass(self.extracted['wavelength'], + self.extracted['fluxerror'], + self.elevation, + self.airmass) @property def elevation(self): diff --git a/banzai_floyds/telluric.py b/banzai_floyds/telluric.py index 1831c38..cbf9a07 100644 --- a/banzai_floyds/telluric.py +++ b/banzai_floyds/telluric.py @@ -65,21 +65,23 @@ class TelluricCorrector(Stage): def do_stage(self, image): image.telluric.sort('wavelength') # Cross correlate the telluric correction with the spectrum to find the windspeed doppler shift - shift = optimize_match_filter([0.0], image.extracted['flux'], image.extracted['fluxerror'], telluric_shift_weights, - image.extracted['wavelength'], args=(image.telluric['telluric'], image.telluric['wavelength'])) + shift = optimize_match_filter([0.0], image.extracted['flux'], image.extracted['fluxerror'], + telluric_shift_weights, image.extracted['wavelength'], + args=(image.telluric['telluric'], image.telluric['wavelength'])) # Scale the ozone and O2 bands based on the airmass o2_scale, h20_scale = minimize(spectrum_telluric_second_deriv, [1.0, 1.0], args=(image.extracted['wavelength'], - image.extracted['flux'], + image.extracted['flux'], shift, - image.telluric['telluric'], - image.telluric['wavelength']), + image.telluric['telluric'], + image.telluric['wavelength']), method='Nelder-Mead').x # TODO: Minimize the second derivative of the spectrum after the telluric correction is applied o2_scale, h20_scale = 1.0, 1.0 # Scale the water bands by minimizing the match filter statistic between the telluric corrected spectrum # and the telluric correction - image.extracted['flux'] /= telluric_model((o2_scale, h20_scale), image.extracted['wavelength'], shift, image.telluric['telluric'], image.telluric['wavelength']) + image.extracted['flux'] /= telluric_model((o2_scale, h20_scale), image.extracted['wavelength'], shift, + image.telluric['telluric'], image.telluric['wavelength']) image.meta['TELSHIFT'] = shift[0] image.meta['TELO2SCL'] = o2_scale image.meta['TELH20SC'] = h20_scale diff --git a/banzai_floyds/tests/test_flux.py b/banzai_floyds/tests/test_flux.py index bca4a30..08bab91 100644 --- a/banzai_floyds/tests/test_flux.py +++ b/banzai_floyds/tests/test_flux.py @@ -19,7 +19,9 @@ def test_flux_stage(): def test_sensitivity_stage(mock_standard): frame = generate_fake_extracted_frame() mock_standard.return_value = Table({'flux': frame.input_flux, 'wavelength': frame.extracted['wavelength']}) - stage = FluxSensitivity(context.Context({'CALIBRATION_FRAME_CLASS': 'banzai_floyds.tests.utils.TestCalibrationFrame', 'db_address': 'foo.sqlite'})) + stage = FluxSensitivity(context.Context({'CALIBRATION_FRAME_CLASS': + 'banzai_floyds.tests.utils.TestCalibrationFrame', + 'db_address': 'foo.sqlite'})) frame = stage.do_stage(frame) found_sensitivity = frame.sensitivity np.testing.assert_allclose(found_sensitivity, frame.input_sensitivity, atol=0.03) diff --git a/banzai_floyds/tests/test_telluric.py b/banzai_floyds/tests/test_telluric.py index 72426e2..9dabd85 100644 --- a/banzai_floyds/tests/test_telluric.py +++ b/banzai_floyds/tests/test_telluric.py @@ -19,6 +19,7 @@ def test_telluric_maker(mock_standard): np.random.seed(124234153) frame = generate_fake_extracted_frame(do_telluric=True, do_sensitivity=False) mock_standard.return_value = Table({'flux': frame.input_flux, 'wavelength': frame.extracted['wavelength']}) - stage = TelluricMaker(context.Context({'CALIBRATION_FRAME_CLASS': 'banzai_floyds.tests.utils.TestCalibrationFrame', 'db_address': 'foo.sqlite'})) + stage = TelluricMaker(context.Context({'CALIBRATION_FRAME_CLASS': 'banzai_floyds.tests.utils.TestCalibrationFrame', + 'db_address': 'foo.sqlite'})) frame = stage.do_stage(frame) np.testing.assert_allclose(frame.telluric['telluric'], frame.input_telluric, atol=0.05) diff --git a/banzai_floyds/tests/test_wavelengths.py b/banzai_floyds/tests/test_wavelengths.py index 9a74f4a..a3701f2 100644 --- a/banzai_floyds/tests/test_wavelengths.py +++ b/banzai_floyds/tests/test_wavelengths.py @@ -1,4 +1,4 @@ -from banzai_floyds.wavelengths import linear_wavelength_solution, identify_peaks, correlate_peaks,\ +from banzai_floyds.wavelengths import linear_wavelength_solution, identify_peaks, correlate_peaks, refine_peak_centers, full_wavelength_solution, CalibrateWavelengths import numpy as np from astropy.table import Table diff --git a/banzai_floyds/tests/utils.py b/banzai_floyds/tests/utils.py index e9be625..185d180 100644 --- a/banzai_floyds/tests/utils.py +++ b/banzai_floyds/tests/utils.py @@ -16,7 +16,6 @@ from astropy.table import Table from banzai.data import HeaderOnly from astropy.modeling.models import Polynomial1D -from astropy.io import fits SKYLINE_LIST = ascii.read(pkg_resources.resource_filename('banzai_floyds.tests', 'data/skylines.dat')) @@ -231,7 +230,7 @@ def generate_fake_extracted_frame(do_telluric=False, do_sensitivity=True): telluric_data = Table({'telluric': telluric_model, 'wavelength': sensitivity_wavelengths}) flux = input_flux.copy() if do_sensitivity: - flux /= sensitivity + flux /= sensitivity if do_telluric: flux *= telluric diff --git a/banzai_floyds/utils/telluric_utils.py b/banzai_floyds/utils/telluric_utils.py index a14b923..d133d68 100644 --- a/banzai_floyds/utils/telluric_utils.py +++ b/banzai_floyds/utils/telluric_utils.py @@ -1,8 +1,8 @@ -# These regions were pulled from examing TelFit (Gulikson+14, +# These regions were pulled from examing TelFit (Gulikson+14, # https://iopscience.iop.org/article/10.1088/0004-6256/148/3/53) # plots and comparing to MoelcFit (Smette+15, 10.1051/0004-6361/201423932) # Also see Matheson et al. 2000, AJ 120, 1499 -# I had to be pretty judicious on my choice of telluric regions so that there were anchor points for all the +# I had to be pretty judicious on my choice of telluric regions so that there were anchor points for all the # polynomial fits TELLURIC_REGIONS = [{'wavelength_min': 5000.0, 'wavelength_max': 5155.0, 'molecule': 'O2'}, {'wavelength_min': 5370.0, 'wavelength_max': 5545.0, 'molecule': 'O2'}, From 963fe7f194ec9f69526ca4631819357727f77dcf Mon Sep 17 00:00:00 2001 From: Curtis McCully Date: Tue, 1 Aug 2023 09:59:06 -0400 Subject: [PATCH 07/19] More style fixes --- banzai_floyds/main.py | 6 ++++++ banzai_floyds/tests/test_e2e.py | 11 ++++++----- banzai_floyds/tests/test_flux.py | 2 +- banzai_floyds/tests/test_wavelengths.py | 4 ++-- 4 files changed, 15 insertions(+), 8 deletions(-) diff --git a/banzai_floyds/main.py b/banzai_floyds/main.py index feafc4a..9d60c90 100644 --- a/banzai_floyds/main.py +++ b/banzai_floyds/main.py @@ -3,6 +3,12 @@ import argparse from banzai.main import add_settings_to_context import requests +from banzai.utils import import_utils +from banzai import logs +from banzai.data import DataProduct +from banzai import dbs + +logger = logs.get_logger() def floyds_run_realtime_pipeline(): diff --git a/banzai_floyds/tests/test_e2e.py b/banzai_floyds/tests/test_e2e.py index e6ec2be..2b7e48b 100644 --- a/banzai_floyds/tests/test_e2e.py +++ b/banzai_floyds/tests/test_e2e.py @@ -16,7 +16,7 @@ from banzai.utils import file_utils from banzai.logs import get_logger from types import ModuleType -from banzai import dbs +import banzai_floyds.dbs logger = get_logger() @@ -174,7 +174,7 @@ def stack_flat_frames(self): runtime_context[setting] = getattr(settings, setting) observations = {'request': {'configuration': {'LAMPFLAT': {'instrument_configs': {'exposure_count': 1}}}}} - instruments = dbs.get_instruments_at_site(site, runtime_context['db_address']) + instruments = banzai.dbs.get_instruments_at_site(site, runtime_context['db_address']) for instrument in instruments: if 'FLOYDS' in instrument.type: instrument_id = instrument.id @@ -185,9 +185,10 @@ def stack_flat_frames(self): logger.info('Finished stacking LAMPFLATs') def test_if_fringe_frames_were_created(self): - with dbs.get_session(os.environ['DB_ADDRESS']) as db_session: - calibrations_in_db = db_session.query(dbs.CalibrationImage).filter(dbs.CalibrationImage.type == 'FRINGE') - calibrations_in_db = calibrations_in_db.filter(dbs.CalibrationImage.is_master).all() + with banzai.dbs.get_session(os.environ['DB_ADDRESS']) as db_session: + calibrations_in_db = db_session.query(banzai.dbs.CalibrationImage) + calibrations_in_db = calibrations_in_db.filter(banzai.dbs.CalibrationImage.type == 'FRINGE') + calibrations_in_db = calibrations_in_db.filter(banzai.dbs.CalibrationImage.is_master).all() assert len(calibrations_in_db) == 2 diff --git a/banzai_floyds/tests/test_flux.py b/banzai_floyds/tests/test_flux.py index 08bab91..c642bfe 100644 --- a/banzai_floyds/tests/test_flux.py +++ b/banzai_floyds/tests/test_flux.py @@ -20,7 +20,7 @@ def test_sensitivity_stage(mock_standard): frame = generate_fake_extracted_frame() mock_standard.return_value = Table({'flux': frame.input_flux, 'wavelength': frame.extracted['wavelength']}) stage = FluxSensitivity(context.Context({'CALIBRATION_FRAME_CLASS': - 'banzai_floyds.tests.utils.TestCalibrationFrame', + 'banzai_floyds.tests.utils.TestCalibrationFrame', 'db_address': 'foo.sqlite'})) frame = stage.do_stage(frame) found_sensitivity = frame.sensitivity diff --git a/banzai_floyds/tests/test_wavelengths.py b/banzai_floyds/tests/test_wavelengths.py index a3701f2..6a3716b 100644 --- a/banzai_floyds/tests/test_wavelengths.py +++ b/banzai_floyds/tests/test_wavelengths.py @@ -1,5 +1,5 @@ -from banzai_floyds.wavelengths import linear_wavelength_solution, identify_peaks, correlate_peaks, - refine_peak_centers, full_wavelength_solution, CalibrateWavelengths +from banzai_floyds.wavelengths import linear_wavelength_solution, identify_peaks, correlate_peaks +from banzai_floyds.wavelengths import refine_peak_centers, full_wavelength_solution, CalibrateWavelengths import numpy as np from astropy.table import Table from numpy.polynomial.legendre import Legendre From a0b64e0e49f198ca3f96c5043eefd8657420aad2 Mon Sep 17 00:00:00 2001 From: Curtis McCully Date: Wed, 2 Aug 2023 14:18:56 -0400 Subject: [PATCH 08/19] Added the flux standards and some tweaks to update to the most recent banzai version. --- Dockerfile | 2 +- banzai_floyds/data/README | 0 banzai_floyds/data/bdp28d4211.fits | Bin 0 -> 89280 bytes banzai_floyds/data/feige110.fits | Bin 0 -> 89280 bytes banzai_floyds/main.py | 4 +++- banzai_floyds/tests/test_e2e.py | 4 ++-- setup.cfg | 2 +- 7 files changed, 7 insertions(+), 5 deletions(-) create mode 100644 banzai_floyds/data/README create mode 100644 banzai_floyds/data/bdp28d4211.fits create mode 100644 banzai_floyds/data/feige110.fits diff --git a/Dockerfile b/Dockerfile index 07c89ab..6184642 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM docker.lco.global/banzai:1.10.1 +FROM docker.lco.global/banzai:1.11.0 USER root diff --git a/banzai_floyds/data/README b/banzai_floyds/data/README new file mode 100644 index 0000000..e69de29 diff --git a/banzai_floyds/data/bdp28d4211.fits b/banzai_floyds/data/bdp28d4211.fits new file mode 100644 index 0000000000000000000000000000000000000000..cc319dd11f794817b9dec79284b903d8aec8887c GIT binary patch literal 89280 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%0D(s(K^x0xO z7dSb{U(~5J;nXCO^O3*kc|eme^iB6@;M~Xl1!cFw zHdX`Y5%QmY$KO}KmmH7H*x!%4IdCdD{#vlVmpXai4stxSBH!s3(+VQmY=P5;e5YTW zyVLzL3^-r0@8r&c?S)T((}{eipOx;aj3L{%3;T>afA-5QfDBEii8NIPT zO89BTs>6^m0Q)0Di<~{WAY%yj`)u=yG$yYfj{S?CJlt?|4`k?J-!ubJ&XwPgF&6m> zUyXgsyn6;RCLmv7Nx+VPt(PHVD)JQ;O?)|~x*Rg5BVS>jd0t4)W5_VYe9ovLJB))*T+M~8tijD!TH+j5oBz@zF~bPn%1%)V+-~<(#N)2`$C2XW?52e z^N~%E;f4L&f|qyG?m@_<&sAR`d_*7s~! zcCiUEg0XLjYG_PWBV zaS_M6u>U&8$-5w<4EYQEJ2ZZKiXh`U@)zv+b>&Br5;Fei$$G3nbg6Vyg*If=VBgzU z;lkkN?SSCdW~^z0a9G$Y@siDpX1vf+^N0{Kpj^009EZ3`K% zvA@yb!sGcbAmcsu-`9C^sxt>NKI3?UF8!3Vbs(by$9u@h%4M_!GJfHBP1+irw*3IE z2KG$|HT?N90l2-e@2{WtN51?YCn`oh+XLLe*thiVl+rt~z#Wc#^M`&ISw0%Lqc9t` z^OvV+0@nchEjQQ83%3K;2>UJHejcAK0PbY$zfO;pw`l>_1o`vNjQbkZ{TjHY$d_MM zb^Ld68gS<#U;e2}Hmo`Ez+HrV`HxZsK?{_?U4nhmU;W-18vxe^`@(W~ADZfby9~3> zsM~cK*1%nf{gLHM#?E8{*BSed9llsD;sSRa_FIMp**b}(BuZ5;O}%Zh9Jm3Px6Io# zvyUfmk756o@*`Q@eBg#-|5pEMqZccI8;$+0Id_kpYyfTo_FH&A&CS* zEbJdF&#lUv1KeEX&wKN}Q}^e6;1(i(UiqMX{BthAJ&XK#4}x}l9dH4-CCHc8^rd*{ zmHD@*1pH0}m{d4l`3Cn={9Q)UV{=7EF1h}uUf1}}eWn%zv-~HvwJDM*! z1Kdy8Hz!{HUN;rE->^^TX@@|^2Dm@5PorsB_cXHoe`Ch;5w9oq>yGzHYnutYzSuvx zY0$wmdj`e&1JRR&`xLQPa=mETu*k_R#av{MScm~+Fyxi=x`B>l?VxMiH zv2)@e;7!85zDMrq&W#1$G~~;@ynoJ?RW`tziT%ARLWEBDfJf=)5P4&I_V@Pl0^UO8 z&&B5@JPYK{y^`v?o;V9UE9C!c{^i+W|A)SDFXQh5&jH6fab3ct9`3+%#y&h>^VVWt z=JgnDhYsMmVqeV~m8Sg!;JIPGAN9`tl^S^4aJ)m0o_L{}4ZNMmpZo6h%6DASzYqJ< zdO3Jn9S7b4>_5C}Z?MKg;Q3?U!9nfu6WV|mgySt9Q>b;t9(ZBcx3}xvghNk&7ma-; za6)5yB=F*~&s^(ew$FRuC1EzBP2cz_1bFG#Zy8-npHc=q7WU)w7#?b1B6sbZIpZE4 z1)d1|+3Tx{$@!Eg!@fy_cZth>0Z)y6J*L{aJue5|N#r|ui#Tx1U=i?6p?FW?^8wyj zm{qRQxdFVd$e$B8dEJEs6X1Qv{=a_S zjr~U7di)r(0Qg$iXR&wR+wd;n_eH*(S3f2d$hg4Q#=b^2zkNq4@P{H_j*nG8dxK2i zk3hbhQI;~=a!ufm#@s9We!!jsz#oU>pZrW}G<*9z zxdQyML4w3HkF7dRu5LZwBy_uy69k#Z!JN zfuDwb+V<7+*-wGbM82%Xrq||)#=z$yUzT{y`y-=Q13we_vbK9$U45bld@1JL?AQsR zWPhuWKWqJAUZScV_&La*wPcAh*{Bov`Pe^xRLjH%5x_6P{tfhd(d^~GKaYJb(!P52 zCxL$%`HWx(%3KGn}0#r-lK2sAN2w(~tE zSOkLJ$gjfBX9WY0U-i=^3Lc7nPApFyw~`|Api&N5pI0!!?_E!|%gVFLnN?2l#SJ3W630((=$2pQnLf1NQmnTd3dm2f-HXb81-r)1GWE56tVQRl3}p3j#0f z52!gJZ3qX!ZtUM|UVApq76iW7-!oD7_^odsIE?&C{5)9@hPv1d5f{WO1I?Zy|ss$h@LwAh?Ho@}I+J9hJTWK_iY=D}U01p86nY z#=fu5-s<-x$MXy9`|>2R?nD^~USn>WGb}#r00`b=|LYc;+YfGl;4_X_%c$>W?Pd^k zVE>e}F}k%CAozvbqu-j9#W~&})WE(5N4r@C6G7Mu`#6hIzBUpd?2qGR)hx9*Xad5) z`2I69jwnt~0^x8RZ(4)GMsqy~M`7L@=&cwY1wsSt_fqi=&b|*qBkV7l9vn-~Kf=k_ z-z_pY>@om^CfNUy6ThE5M|aK1E0C#;SlTcvuV>kE z$Q*{>&!p(PXQBgSjz;e%{WAR5(OGvPb3En;>Bcj|$3v#^U-!S^!CUcT$i(Z3j2L*f z`dJEOE=2DyT_HbPbMF9TTC?IzdT0~Uxa(qZ>jlVMUUfN2XFnmmu}B;>-y1SlW4;?0 z``gqVGB0vfw(E6T)q;`mM5%j1NGLvuBep3G; zBp0?z6CckaB&S`_t%3=8RmM<)lKh)KxX!? zq*q#bge0N(kZ%wTG7EU}=PsRuWW4Tuo8Tjmd5)jcclH!Q+Au56Ju={PO{JLNcP?HDS+o$ZW40(A-oD@WAuCGazZQ$ja^ax0z~~Wk8%%eR9S&&V9|L_#dAVD zdtb(IaXW~HI=7G=K!~^WA3J^mA4IyCg+_#IwIPT`z3rjB{2U<`j~QsxuRn+kFyr$i zkscjzr=v=R>OEP>xv^bXRUVTFJ zCg_9%-ycLv5R3PmFsQR6eb$ELa9T--odQ1%%_84_X>sHD>J~!uaNygsu5=JBKeu(B zWe6eKU$-sdOivIwuI<^nB8d=L9dOWH9}6Pq_rjOb|OF^G1HexXcC+?Ohi^4p<`d_MpmZ!^!sdZ5y3=@dzQ(cBr}K zZV#eEZGwUsg9wqH)HP5|#v71eyK~_fLNxi^kt#(Uh=Rs9Y1Et{L}OP)?6VVsD0G77 zFjz&17Ji(vJH;485f_^MzjYBJ-gK)`!?HmXv%ZTQAVlWT?T@GSjRR2v<|(V1n4(u8 zO8WTc3k`%&v|8Kn;5iVbB~tk=Je6L#_sLceF)-iCJHPZ84@4ZYJ4TBLVWgGABb{d; z;$4h-F)5eG)Ria)Md*Vl6R~Js&hnX?Bp{M*6s65*CNguM&A&r_9$BRLJnr+0R6@9> zL-_EE2t-+k1?@xU^|dAY?_`pN@ZDuX@L`TdkS-fU1(Tk;blf3?-J_+Dum(g$m6W5MBLJ zMm88BsOZT&m2Cl{>&ez-m2HG@NMax8CdbRoTG+vq(5g2riCGW8K*eqWf3K;k=pP)D)O}5XcvT=$1v-_5RXL6U*syt z$<77w=uRsC`PR2D+l5{L@mLy_|GW*M+{M+eK|CIDhNQIrxb6lJPd53fd)klSuHHtQ z)KLav<7-s@bHhdzZ>yXM;u)CVoSoP|Yz~NL|5;8}61;Ky&fW3J0r5P}qD0b&aVmH*tZq@FXTrhynRzp-tAaWx>eok7h98J!2*dwC{;cv;1m zm)v?H!?KF?TDTF!4i(gVkP$xH?aRyQAYSE3%?I3JH@6;-)&=pJpH%*H-N*j4UfT}h z^@ua-9z0vYlYw{>lgj@L@0g+&>eV3LI*ZExj3o<(PIL|j@wQo1{%5#fUDr=19mG2- zsr=9AXts*qL-yD1ZB+hqhri2+IYh?ivz^NSjOmJ;RoBV>J@AXle@@o~*EdHqK&y*&mI^*d2TX;^T-pyJiM{E+OMd>89ob4sFH}<6r$jOyf}b&l&7GD*BQ! zh*_8q{un0PXar)e88siUA3japx4s+1f}2$Sb9g$zcK08HSd2OMgx;s^N)XF;Q1d~C zuD@Xhhm22+_D7ti5~F07g&;nGdHaUpUo1>OoX4f|pCcQ-c7#JbhzsXX`OkSede5AS zUm!j^*Y0q{1A=q-#n5n{ND!aDMdd#yWL(PWAx$8@w3Etr&Z4mg*WdaM;xf$3X0J*7 zTnOR{O)CF0#y1WX&iDr6Dzu--SW#zbcZmS;ZNwZiu(+P-0peP;AK{E$ljicc%*0^t8m1KW6VBRDfTYHfP;>WvVKJMR5u>GDq-};%n|EF41{si*yUVJng#j$ zU&z^t+z+sCXhsbxJP+cIn#~)QWE1S|x($=edw}@oUTQzUs-AMaWI8$CfA^&FpS6nE z`L$;bNVIxU-=F&j_ZCw70oKy&L&}V6AnCvGncj}+1ncksqnlDCNCsg(*ENu-o&u7g zwbXpTvc9MtJ>xn^boWu~KPzTX&ECW=km&hnk_kqz8p21(c6x$jOmA-ebb?@Oyq;RE z=>rl&5taX}9i>`9^7|l}DEc!&5iDu?qoCyrKr(ePwf?h>=ig4-M1aJkp2~mL?*-Ma z`XwNlRUbrtMY#11j5^-I>Z4Z$E;ewWIB z*1_Au4ac~G#PJ@vf%GSsA7c09MT`K+>H~&Xc?yEHKD+q$v@npY>-T2^L$DTxP3b%5 z4oF-vkF%>PTfYG$Tcj^0oqS2K#3Qd5S@1yOL4;hg4<}e*&FyQghl9jx3AtSuNiaWY z#P&-r0Ljk#;d6bi5zNgWYCRTJf@JSOz1w}<3FgV%E8|T=K(hZJzuCWoVA;go@m^a9 zlEVXR$Qg@Zc^E$8bR~l%K>lZfC77A+wN^3Y{RLq*x1Tsa;`b0+P&!nHK{p35G#QBu8-*B+@_2fd@gi$;wY3{2L@nzYBXpe-N~8v!_NQ^+A$7 z=;iEAF+q2kyYyabF-USTD`M8@J|M?$ftp&68NDleru=FINs)CVoCzlwP7y0U*Jgp_ z9Oezvv$8^OfTZLRwH`BEN2p>M(?N12V5zw33c;Aaa(%nQZIF})Q2EH%a>UioJqaY0 zL#Xwb!4+g3ukr`U%`9p?X4rBbr-&AVq#CocQ-Fc98A$4F$?i8J7_S{u-we?M$^9m3 zJ!a%vjw;s*0!bt0eT(Vic9Y}z$&o81avg&H@{XNvyaXiAhc@j#VMEZLKW%m}kb&eC zV#ez3+w^(QK+>8`t;dXzlas7A4FkysGW^R?1f#dXaCaXvUq2(JAGP+EdS`;9{mI3E z>xBgUwejRz3UYjXKT55?4D;Tvt>>)+Nf&0fpeyH#j6te7EaHA+JHZ%TRM77`4Wzw> zQ~Al5|H$uoJ|_ zu)}o}0n*92)Ot+cR`SrG{4hwTEvMFFda>TP*=H7lbjCAkJ*JoRd(S^w2h!Qksr8s1 zcEWw7o(T`uo@Tk`kDG4)otX_BKc%pUOwNeqdA7s@@>A zT|w=~>H6=C9Z&8CsXgX?V~cKzrhwGp1+^a2t><)yZPEbgs!(eEr8V%pyjRZ!sS7!L z#@7(Ey|&fGeQiOyVHC9<(-c!|n3f8VZYn6Tm_L-DF(>~bPQC}}R?L6>{`L(@O_b&5HX9(m2dbvy<30dq8?T;`dDZHw0~L-}#frYk@S?fZU#s zCTPPgcz$s{Af+Qt&zvZKt1JO2>ooO!@bm*$Qo@#5f|R?8s`t|#=tf6&+JRJv*@@38 zybuCX@tdEo_8liu8%7?QI4=UEa#W9}roIk5sXhf#bu^WaX~PE2QGM$R(i3B-^*3$v z^=E6x`++p?%#!T+K|~ru7A?-u*~9v6-{F=7IE$5tWZ= zKSm5Q(>n{&cju{mOrPa9`w1vP`VrN~={nIljOwKz{fam(t1YN(>1UZ z@AC&)f6Tjg1(v+J1+qcwsr_+U+}o&|iasD4it6FCeT%ebp8pIoUCc{E-kIf*KD|UL zAJgW3Y?FscKsE;RMu)AZE}4VOh+KWh`Z(>-um}G14IBn>bMzbLsK{n?zm7l4v7qN zO;kRno^g(UXmbfbZ=jgY2*|wI5FLbrE`wFag;SV=5n$ zzx_0O;hGMzpfYMdoXmV*k}&8X$U?7D`Is^*+-hZ^0kfO>j?v1WAWKfC@-byq@9G1ydx0$7ggVbl{=rOGu$Jt9Cg#r|W*_Je zGEO;_kI8L%tU>jgLB@AC_u4duNWOp4>xd`wOXn`_kBa&1Tf+w!k1X(tn%EzSB3d_cb2$1E@pz<+^@jLx^=sb`Wp!zh)`ReWK z12REYg!#s&dA~mY0@*oFD*uvcE<5KB^8i`Nk3WB4f=Isnabh273dpYfr1CM@;la$% zvL_%bXHe%=Nk11q>(*HUvPx8+CTkgl)v*tQ>=xpr_KXV#yUG5lzE0(15~nWNM%4kb zdQ`6_?cFe7Umx=KAE0_QY2nK?&sI8utnnAM9wv=joLx8g5XhcjhLja+#I+!Mj_TE< z=^ew{_L2Sb3e~5_?@ltfezh57Z!tepHiv!o1lflwDj$>fWa(Q3lH=j?pWEq6B+b*= zFwN*V$iAWa^!W4Z`bV4Zf$Te~PmdS3w6!mu39>F!j~*}R=`ybHJIFOL-xheRajOA& zFI1l%KN7fc^HL*__eJ&SajRyA{f7vUYu}{S!{dW=e|qTd2KkUb>F7-yx9D}f$A}n^ z@Av_m?9)!&AfJZn(Ztq>m+utgKt2=m4b}E*hFd^xrb*>rVx^eA z;P7~m&+kF)R};N1kDt&5AYa6%*2l!Pzg`D^z6)|<9%*(yemXv zV3lNbI{E&#=sY};zNM)>c|XYQcT@S7Xh%<-(K`*~D-kEuMeZ@H9}RNno>cxN9NqtG z)T+H8cR}@N{I7uB#jUX*-yo#+tMRt|m-X1t3*?&@Q27{lgf1I1^DD@=qIxuLTzv2J z4PhYPRzt0aabk;U7V@_s_eSUGaV>eY9rk7*-_wiAzj*DYE|Y>}ko#h`KW^4Kq!Hu? z$>t{ar}6QhzPE2Y33C5MRQ@FlU)z$YegN{Li>Uo-LK6LD{-gyU53Zx~FM;d4>Q*G# zzu~AJO*pRM@$SAm$fJFz{b_=$1&5X$3G(mlDva=vRE~fG?zG=v8x2uyt zo{G8Dt@zTzp&+M|(*t>49G^lrystryKQ`tQisjpid_m4d^y5dU*`2|!j##OJJ{eT$^^2`0H z{EJhVdVIJv0pwRvy%;xbjMpT+9w4ufQR`vs>n#_~o$3qn8%wD5FHUQlt?{6KAismT zA2DSAOS1oKA5i<#xKS>yZ#~^We(w;q{>3>inQ?n6c|Q*^&puvcx`Z6BO{iXsWz-HB z!Q2J%rwS_nVsDh?#`m8E@)lGt#=cmkFWATc`5U0}FIM#9gRhKiulJaJ%Nkeck^T4a zA(fA@CnnTfD;xvzuc$tZ!{-h1PRvsinmley1NpB(R6fSpmsq{rKOE%0Rbe-1i;1{U zeGfWI3lv(Ij~KeHxkCd*A5CCB$VV9gim9kRjJ>k6!!^(i6w{AT`4=nIyx6t+7br{vsq@>| zOP-UfZcGHlTvQ*%miu!8YHL8TAe&kbV^6ppF?mkLYmVy0Sh@AwPcvVE!jham$a*o> zYMJYyBbA`AenRD6?0_C}Q`!hn*fmqv@Re_bE?`~^875;26C z(L0AGP(+4M=QT0f*B56ye7J|AnWB9CMc3oeHdNo zr(K%34ixG6{6FV4(KV?~U6$ngGaab&nrM7JqTu{F-djLKca{{Fsmb=`V?M?A+#5)M zLKH@w*F>xPXx%S52MU=UbzT!))TQHUBLIa;k9wXG9VpO$q$bC6_9^PTChA-6nw!&J zf+F`6b)FhMsq^=(6;nZR%8@#+iFVjzxo7@QP!zqS&TFEVMDKmZNe0EaSJd;A=;0D@ z`_ux8i+{@RSwytqjR{4XWdD?s(;HbIM!j<1{x<9}D6Z*K=QUAxzM55!dk>1r)70~m zsIrYcPvp-A#Vsf5ye6u8X9zcJ6)0*}QRk^qKZa-(f3pKc{hJuQ=`&1u|BMmfTDQ}b$%L^Uy<2hOt#PSBIWh=O$vqYn zuMkJ~ZnC_c231@{CZsw8Bgcf&kTd`8btqatd0sDfUD z;#)M8e^JN3=pKCB7Zg9x^U^5)r&mw&$oucc?C5$lO{WBunrEr=(^V6s^l6{7ni$FPHEp=WR zC49U|9yK16Q!pDJzjWqsDJZ9Xkg2Y$BO9F7#4{Ja{17HkbHE{EIG-$9vvHWL4*m)AN>qlE7S&yKMUcUr<`Dr}8iI+L5ET zj_m@a?MLdoG%|DWZt>GCptQ#v{_I{>YYr$^CQ^S7E%HqLF$4EQpmd%<6{gx zY(cqpBDG(PjK6v~eE(BWZn#M0Uu3XnYnkaiP;TBhhD=Z*(mi#d$wo&|x?|pX;I78w zNKkG=^;+ZvwOgMmCs2ALj`;F!hlN8HDEFXxEwbm9)DbPlp!7x0FC!O=Z8lat0_7ph ziz51NeY6vl{-|Dy?Ek_cdF4V-9!1YHBL~_3IyRC6$`Dk)Md~@q8Wx6tG8{e6jMUJz zXKd~X%4k%tMGiW$jq0_C_a<5%g{`3EqWUc2$E$rIItHK=V$NSWm6-evloC|0MP!>6 zrH``&r2^G!5huqQ6dor)nT6`Lh|5MdT@KMfc>>jI5$#{*Rdv1qWxgAgf00^mY*)rq zfbuk|*CL7*ZEb@Ipgh|_ou5RUI5<7}7ulW{(y084s5)c(YOXFQFQa-b;^OF^SIb>N zdDVn^ei4DckEOhh>a~c-XG5y5k>lw`Iki4TghjYN`#A`dcTl|+;ZSt#htW4s*11#b zRm8bv8roboDDR&7JfN7{Io_0s4Ow_V~vi?t^t+JpY5VI5iV=&H*&;oP%T6C zS@`{!oxL>&fXYFWTA#vCcIh_xfW!?%x5Jx>UGXTGImz)DaZK=oPp1dqh(j+3A|TunVc2)Ef}|7q_lP#wWM zYs7@DN(HEn?V;AE@Szjt-E3BZDh%_$_$NnO$atc9QtMNAUxupU=2lR}qWUb{AaiQF z2ic#ALhAWm_?DY1ct?kVDrF(HKMWuEYnrWXC#cfV^SrQetM?C#Y5)}zaVWFh<S=?Dy+>t0$-gf0|dH2wQjOdSM0uDlz6uW|MNSw}VP1qSmJ{|MeFAPYwr_ zY7wvZRB_?l~C(bsI=$Jrjva@bqzhw z3*A#}KS+Nxs4CI(yO0jwkdtS>fa(_JBR}qREF;Hj4SJpz+&S#Ne?Tgz?xN>+A-zLP zuWYLWRRj8aogumIm*(j6K=nvUtxus|O~)#1#)7I@My*ewl`ADfT!o-|v4mQmLcOyU zA1{;r`3gPH3lV+W@oLt3P`$-$Yr0^N?

Sq33xa`g6r&V*^0-Wk7LK=u{$P$b;8y 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 literal 0 HcmV?d00001 diff --git a/banzai_floyds/main.py b/banzai_floyds/main.py index 9d60c90..aef03a6 100644 --- a/banzai_floyds/main.py +++ b/banzai_floyds/main.py @@ -7,8 +7,10 @@ from banzai import logs from banzai.data import DataProduct from banzai import dbs +import logging -logger = logs.get_logger() + +logger = logging.getLogger('banzai') def floyds_run_realtime_pipeline(): diff --git a/banzai_floyds/tests/test_e2e.py b/banzai_floyds/tests/test_e2e.py index 2b7e48b..0cdb536 100644 --- a/banzai_floyds/tests/test_e2e.py +++ b/banzai_floyds/tests/test_e2e.py @@ -14,12 +14,12 @@ import banzai.main from banzai_floyds import settings from banzai.utils import file_utils -from banzai.logs import get_logger from types import ModuleType import banzai_floyds.dbs +import logging -logger = get_logger() +logger = logging.getLogger('banzai') app.conf.update(CELERY_TASK_ALWAYS_EAGER=True) diff --git a/setup.cfg b/setup.cfg index af36f6f..a6c99aa 100644 --- a/setup.cfg +++ b/setup.cfg @@ -18,7 +18,7 @@ python_requires = >=3.7 setup_requires = setuptools_scm install_requires = astropy >= 4.3 - banzai @ git+https://github.com/lcogt/banzai.git@feature/logging-adapter + banzai @ git+https://github.com/lcogt/banzai.git astroscrappy matplotlib From 1f29b2f49d8a09140a11c4ca8368d4ae1f34f935 Mon Sep 17 00:00:00 2001 From: Curtis McCully Date: Wed, 2 Aug 2023 14:57:06 -0400 Subject: [PATCH 09/19] Initial changes to get order detection e2e test running. --- banzai_floyds/data/{ => standards}/README | 0 .../data/{ => standards}/bdp28d4211.fits | Bin 89280 -> 89280 bytes .../data/{ => standards}/feige110.fits | Bin 89280 -> 89280 bytes banzai_floyds/dbs.py | 17 +++++++++++++++++ banzai_floyds/frames.py | 4 ---- banzai_floyds/tests/test_e2e.py | 2 +- setup.cfg | 1 + 7 files changed, 19 insertions(+), 5 deletions(-) rename banzai_floyds/data/{ => standards}/README (100%) rename banzai_floyds/data/{ => standards}/bdp28d4211.fits (99%) rename banzai_floyds/data/{ => standards}/feige110.fits (99%) diff --git a/banzai_floyds/data/README b/banzai_floyds/data/standards/README similarity index 100% rename from banzai_floyds/data/README rename to banzai_floyds/data/standards/README diff --git a/banzai_floyds/data/bdp28d4211.fits b/banzai_floyds/data/standards/bdp28d4211.fits similarity index 99% rename from banzai_floyds/data/bdp28d4211.fits rename to banzai_floyds/data/standards/bdp28d4211.fits index cc319dd11f794817b9dec79284b903d8aec8887c..c797bce547fabd8614834c9915e1740ce2173a2e 100644 GIT binary patch delta 116 zcmX@Gll8z()(sAfnn8{VU|z?i_hGwRw25?}gps(PPnyjGVlA5kLS&&gpGsqFF q8mwHw*u+xL($d(-($EY7fXaaiV5-$OJ1{12Hal={ci?7h2m}BuW*TY$ delta 26 icmX@Gll8z()(sAfn;$SHa86=c(Comy-GQ62ArJtS3kksh diff --git a/banzai_floyds/dbs.py b/banzai_floyds/dbs.py index b6a23ed..5572c3c 100644 --- a/banzai_floyds/dbs.py +++ b/banzai_floyds/dbs.py @@ -5,6 +5,10 @@ from astropy import units from banzai.utils.fits_utils import open_fits_file from astropy.table import Table +import pkg_resources +from glob import glob +import os +from astropy.io import fits def get_standard(ra, dec, db_address, offset_threshold=5): @@ -43,3 +47,16 @@ class FluxStandard(Base): frameid = Column(Integer, nullable=True) ra = Column(Float) dec = Column(Float) + + +def ingest_standards(db_address): + standard_files = glob(pkg_resources.resource_filename('banzai_floyds.tests', 'data/standards/*.fits')) + for standard_file in standard_files: + standard_hdu = fits.open(standard_file) + standard_record = FluxStandard(filename=os.path.basename(standard_file), + filepath=os.path.dirname(standard_file), + ra=standard_hdu[0].header['RA'], + dec=standard_hdu[0].header['DEC']) + with get_session(db_address) as db_session: + db_session.add(standard_record) + db_session.commit() diff --git a/banzai_floyds/frames.py b/banzai_floyds/frames.py index 050ce8c..16faebf 100644 --- a/banzai_floyds/frames.py +++ b/banzai_floyds/frames.py @@ -157,10 +157,6 @@ def is_empty_coordinate(coordinate): def open(self, path, runtime_context) -> Optional[ObservationFrame]: image = super().open(path, runtime_context) - # Get the elevation from the db of the site - with get_session(runtime_context.db_address) as db_session: - site = db_session.query(Site).filter(Site.name == self.site).first() - self.elevation = site.elevation # Set a default BIASSEC and TRIMSEC if they are unknown if image.meta.get('BIASSEC', 'UNKNOWN').lower() in ['unknown', 'n/a']: image.meta['BIASSEC'] = '[2049:2079,1:512]' diff --git a/banzai_floyds/tests/test_e2e.py b/banzai_floyds/tests/test_e2e.py index 0cdb536..7a0ed02 100644 --- a/banzai_floyds/tests/test_e2e.py +++ b/banzai_floyds/tests/test_e2e.py @@ -68,7 +68,7 @@ def expected_filenames(file_table): def init(mock_configdb): banzai.dbs.create_db(os.environ["DB_ADDRESS"]) banzai.dbs.populate_instrument_tables(db_address=os.environ["DB_ADDRESS"], configdb_address='http://fakeconfigdb') - banzai_floyds.dbs.ingest_standards() + banzai_floyds.dbs.ingest_standards(os.environ["DB_ADDRESS"]) @pytest.mark.e2e diff --git a/setup.cfg b/setup.cfg index a6c99aa..4e3c5fc 100644 --- a/setup.cfg +++ b/setup.cfg @@ -36,6 +36,7 @@ console_scripts = [options.package_data] banzai_floyds = data/* + data/standards/* tests/data/* [tool:pytest] From daa98141e98e2b82f06a96c273fbe407fb430ac3 Mon Sep 17 00:00:00 2001 From: Joseph Chatelain Date: Thu, 3 Aug 2023 10:42:34 -0700 Subject: [PATCH 10/19] Write readme outline for creating new standards --- banzai_floyds/data/standards/README | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/banzai_floyds/data/standards/README b/banzai_floyds/data/standards/README index e69de29..bc9c694 100644 --- a/banzai_floyds/data/standards/README +++ b/banzai_floyds/data/standards/README @@ -0,0 +1,15 @@ +Relevant links: +https://www.eso.org/sci/observing/tools/standards/spectra/stanlis.html +https://ftp.eso.org/pub/usg/standards/ctiostan/ +https://ftp.eso.org/pub/stecf/standards/okestan/ + +TODO: More involved detail is needed about how this data is actually accessed and used. Specifically WHAT standards are needed. + +Convert flux units +TODO: What are the final desired Units + +Save table as fits file in banzai_floyds/data +TODO: We should Possibly write a small bit of utility code that handles this and results in the correct final format for the fits file + +Add RA/DEC to fits header in decimal degrees +TODO: Include this step in the utility code above. \ No newline at end of file From 8348c11d7ce6e273c7972e84d3b1f1b481d4db17 Mon Sep 17 00:00:00 2001 From: Curtis McCully Date: Mon, 7 Aug 2023 11:00:22 -0400 Subject: [PATCH 11/19] Added build folder to docker ignore so that we don't get weird versions of things. --- .dockerignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.dockerignore b/.dockerignore index fab4cb7..7f435c8 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,2 +1,3 @@ .tox banzai_floyds.egg-info +build From 3ad8ddf940427c81947a3851d216335846630bf8 Mon Sep 17 00:00:00 2001 From: Curtis McCully Date: Sat, 12 Aug 2023 13:58:30 -0400 Subject: [PATCH 12/19] Initial set of fixes to get e2e tests working through making fringe frames. --- .dockerignore | 2 + banzai_floyds/data/orders/coj-order-1.reg | 5 + banzai_floyds/data/orders/coj-order-2.reg | 4 + banzai_floyds/fringe.py | 4 + banzai_floyds/performance.py | 5 + banzai_floyds/settings.py | 8 +- docs/banzai_floyds/ExampleReduction.ipynb | 490 ++++++++++++++++++++++ setup.cfg | 2 +- 8 files changed, 517 insertions(+), 3 deletions(-) create mode 100644 banzai_floyds/data/orders/coj-order-1.reg create mode 100644 banzai_floyds/data/orders/coj-order-2.reg create mode 100644 banzai_floyds/performance.py create mode 100644 docs/banzai_floyds/ExampleReduction.ipynb diff --git a/.dockerignore b/.dockerignore index 7f435c8..3a62977 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,3 +1,5 @@ .tox banzai_floyds.egg-info build +tmp +test_data diff --git a/banzai_floyds/data/orders/coj-order-1.reg b/banzai_floyds/data/orders/coj-order-1.reg new file mode 100644 index 0000000..cf9b062 --- /dev/null +++ b/banzai_floyds/data/orders/coj-order-1.reg @@ -0,0 +1,5 @@ +# Region file format: DS9 version 4.1 +global color=green dashlist=8 3 width=1 font="helvetica 10 normal roman" select=1 highlite=1 dash=0 fixed=0 edit=1 move=1 delete=1 include=1 source=1 +physical +polygon(662.5,491.16667,717.5,477,783.33333,463.66667,875.83333,448.66667,1007.5,435.33333,1126.6667,430.33333,1197.5,430.33333,1285,433.66667,1384.1667,438.66667,1478.75,445.86111,1561,454.16667,1631,461.16667,1687,468.16667,1778.0556,481.97222,1868.3333,496.55556,1947,510.16667,2048,511.16667,2048,432.16667,1899,405.16667,1823,393.16667,1772,385.16667,1738.4722,379.88889,1735.6944,381.27778,1734.3056,379.88889,1701.6667,376.41667,1698.1944,374.33333,1657.9167,370.86111,1614.8611,366,1579.4444,361.83333,1519.7222,355.58333,1464.8611,350.72222,1422.2222,347.22222,1400.6944,346.52778,1372.9167,344.44444,1333.3333,340.97222,1290.9722,339.58333,1250.6944,338.88889,1206.25,338.88889,1145.1389,339.58333,1105.5556,340.27778,1039.5833,343.75,939.58333,350.69444,886.80556,357.63889,856.94444,362.5,827.77778,366.66667,791.66667,373.61111,739.58333,384.02778,693.05556,394.44444,668.75,400.69444,643.05556,407.63889,552.77778,434.02778,470.11111,468.08333,399.97222,511.83333,584,511.16667,603.33333,507) +polygon(1.4352,189.16,107,171,212,154,318,142,442,137,735,154,875,166,1020,182,1338,228,1745,301,1749,206,1444,150,1335,133,1124,102,991,84,920,77,684,55,524,50,412,49,306,54,165,66,75,83,1,103) diff --git a/banzai_floyds/data/orders/coj-order-2.reg b/banzai_floyds/data/orders/coj-order-2.reg new file mode 100644 index 0000000..cd10253 --- /dev/null +++ b/banzai_floyds/data/orders/coj-order-2.reg @@ -0,0 +1,4 @@ +# Region file format: DS9 version 4.1 +global color=green dashlist=8 3 width=1 font="helvetica 10 normal roman" select=1 highlite=1 dash=0 fixed=0 edit=1 move=1 delete=1 include=1 source=1 +physical +polygon(662.5,491.16667,717.5,477,783.33333,463.66667,875.83333,448.66667,1007.5,435.33333,1126.6667,430.33333,1197.5,430.33333,1285,433.66667,1384.1667,438.66667,1478.75,445.86111,1561,454.16667,1631,461.16667,1687,468.16667,1778.0556,481.97222,1868.3333,496.55556,1947,510.16667,2048,511.16667,2048,432.16667,1899,405.16667,1823,393.16667,1772,385.16667,1738.4722,379.88889,1735.6944,381.27778,1734.3056,379.88889,1701.6667,376.41667,1698.1944,374.33333,1657.9167,370.86111,1614.8611,366,1579.4444,361.83333,1519.7222,355.58333,1464.8611,350.72222,1422.2222,347.22222,1400.6944,346.52778,1372.9167,344.44444,1333.3333,340.97222,1290.9722,339.58333,1250.6944,338.88889,1206.25,338.88889,1145.1389,339.58333,1105.5556,340.27778,1039.5833,343.75,939.58333,350.69444,886.80556,357.63889,856.94444,362.5,827.77778,366.66667,791.66667,373.61111,739.58333,384.02778,693.05556,394.44444,668.75,400.69444,643.05556,407.63889,552.77778,434.02778,470.11111,468.08333,399.97222,511.83333,584,511.16667,603.33333,507) diff --git a/banzai_floyds/fringe.py b/banzai_floyds/fringe.py index c373302..84069c5 100644 --- a/banzai_floyds/fringe.py +++ b/banzai_floyds/fringe.py @@ -43,6 +43,10 @@ class FringeMaker(CalibrationMaker): def calibration_type(self): return 'LAMPFLAT' + @property + def process_by_group(self): + return True + def make_master_calibration_frame(self, images): if images[0].fringe is not None: reference_fringe = images[0].fringe diff --git a/banzai_floyds/performance.py b/banzai_floyds/performance.py new file mode 100644 index 0000000..137fc72 --- /dev/null +++ b/banzai_floyds/performance.py @@ -0,0 +1,5 @@ +from matplotlib import pyplot +import pkg_resources + + + diff --git a/banzai_floyds/settings.py b/banzai_floyds/settings.py index 5bf6e7a..7eb08c7 100644 --- a/banzai_floyds/settings.py +++ b/banzai_floyds/settings.py @@ -8,7 +8,7 @@ 'banzai_floyds.orders.OrderLoader', 'banzai_floyds.orders.OrderTweaker', 'banzai_floyds.wavelengths.WavelengthSolutionLoader', - 'banzai_floyds.finge.FringeCorrector', + 'banzai_floyds.fringe.FringeCorrector', 'banzai_floyds.extract.Extractor' ] @@ -18,11 +18,15 @@ LAST_STAGE = { 'SPECTRUM': None, - 'LAMPFLAT': None, + 'LAMPFLAT': 'banzai_floyds.wavelengths.WavelengthSolutionLoader', 'ARC': 'banzai_floyds.orders.OrderTweaker', 'SKYFLAT': 'banzai.uncertainty.PoissonInitializer' } +CALIBRATION_STACKER_STAGES['LAMPFLAT'] = ['banzai_floyds.fringe.FringeMaker'] +CALIBRATION_MIN_FRAMES['LAMPFLAT'] = 2 +CALIBRATION_FILENAME_FUNCTIONS['LAMPFLAT'] = ('banzai.utils.file_utils.config_to_filename',) + EXTRA_STAGES = {'SPECTRUM': None, 'LAMPFLAT': None, 'ARC': ['banzai_floyds.wavelengths.CalibrateWavelengths'], 'SKYFLAT': ['banzai_floyds.orders.OrderSolver']} diff --git a/docs/banzai_floyds/ExampleReduction.ipynb b/docs/banzai_floyds/ExampleReduction.ipynb new file mode 100644 index 0000000..20ee758 --- /dev/null +++ b/docs/banzai_floyds/ExampleReduction.ipynb @@ -0,0 +1,490 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "125d0bc6", + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "os.environ['OPENTSDB_PYTHON_METRICS_TEST_MODE'] = 'True'" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "161b33dc", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "2023-08-11 10:41:28.370 WARNING: warnings: /Users/cmccully/miniconda3/envs/banzai-floyds/lib/python3.10/site-packages/requests/__init__.py:102: RequestsDependencyWarning: urllib3 (1.26.14) or chardet (5.1.0)/charset_normalizer (2.0.4) doesn't match a supported version!\n", + " warnings.warn(\"urllib3 ({}) or chardet ({})/charset_normalizer ({}) doesn't match a supported \"\n", + "\n", + "2023-08-11 10:41:28.919 INFO: utils: Note: NumExpr detected 10 cores but \"NUMEXPR_MAX_THREADS\" not set, so enforcing safe limit of 8.\n", + "2023-08-11 10:41:28.920 INFO: utils: NumExpr defaulting to 8 threads.\n" + ] + } + ], + "source": [ + "from banzai.calibrations import make_master_calibrations\n", + "import requests\n", + "from banzai_floyds import settings\n", + "import banzai.dbs\n", + "from banzai.utils.stage_utils import run_pipeline_stages\n", + "import logging\n", + "from banzai.logs import set_log_level\n", + "from glob import glob\n", + "\n", + "import pkg_resources\n", + "import banzai_floyds.dbs\n", + "from astropy.io import ascii, fits\n", + "from banzai.utils.fits_utils import download_from_s3" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "103fd876", + "metadata": {}, + "outputs": [], + "source": [ + "os.environ['DB_ADDRESS'] = 'sqlite:///test_data/test.db'\n", + "\n", + "settings.processed_path= os.path.join(os.getcwd(), 'test_data')\n", + "settings.fpack=True\n", + "settings.db_address = os.environ['DB_ADDRESS']\n", + "settings.reduction_level = 91\n", + "settings.ARCHIVE_API_ROOT = 'https://archive-api.lco.global/'\n", + "settings.RAW_DATA_API_ROOT = 'https://archive-api.lco.global/'\n", + "settings.RAW_DATA_FRAME_URL = 'https://archive-api.lco.global/frames'\n", + "settings.ARCHIVE_FRAME_URL = 'https://archive-api.lco.global/frames'" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "caa18273", + "metadata": {}, + "outputs": [], + "source": [ + "# set up the context object.\n", + "import banzai.main\n", + "context = banzai.main.parse_args(settings, parse_system_args=False)" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "329a1e76", + "metadata": {}, + "outputs": [], + "source": [ + "os.makedirs('test_data', exist_ok=True)" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "id": "74d0d7f8", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "2023-08-08 14:43:23.983 WARNING: warnings: /Users/cmccully/miniconda3/envs/banzai-floyds/lib/python3.10/site-packages/requests/__init__.py:102: RequestsDependencyWarning: urllib3 (1.26.14) or chardet (5.1.0)/charset_normalizer (2.0.4) doesn't match a supported version!\n", + " warnings.warn(\"urllib3 ({}) or chardet ({})/charset_normalizer ({}) doesn't match a supported \"\n", + "\n", + "2023-08-08 14:43:24.617 INFO: utils: Note: NumExpr detected 10 cores but \"NUMEXPR_MAX_THREADS\" not set, so enforcing safe limit of 8.\n", + "2023-08-08 14:43:24.617 INFO: utils: NumExpr defaulting to 8 threads.\n", + "2023-08-08 14:43:25.455 WARNING: warnings: /Users/cmccully/miniconda3/envs/banzai-floyds/lib/python3.10/site-packages/requests/__init__.py:102: RequestsDependencyWarning: urllib3 (1.26.14) or chardet (5.1.0)/charset_normalizer (2.0.4) doesn't match a supported version!\n", + " warnings.warn(\"urllib3 ({}) or chardet ({})/charset_normalizer ({}) doesn't match a supported \"\n", + "\n", + "2023-08-08 14:43:25.927 INFO: utils: Note: NumExpr detected 10 cores but \"NUMEXPR_MAX_THREADS\" not set, so enforcing safe limit of 8.\n", + "2023-08-08 14:43:25.927 INFO: utils: NumExpr defaulting to 8 threads.\n", + "2023-08-08 14:43:26.641 WARNING: warnings: /Users/cmccully/miniconda3/envs/banzai-floyds/lib/python3.10/site-packages/requests/__init__.py:102: RequestsDependencyWarning: urllib3 (1.26.14) or chardet (5.1.0)/charset_normalizer (2.0.4) doesn't match a supported version!\n", + " warnings.warn(\"urllib3 ({}) or chardet ({})/charset_normalizer ({}) doesn't match a supported \"\n", + "\n", + "2023-08-08 14:43:27.069 INFO: utils: Note: NumExpr detected 10 cores but \"NUMEXPR_MAX_THREADS\" not set, so enforcing safe limit of 8.\n", + "2023-08-08 14:43:27.069 INFO: utils: NumExpr defaulting to 8 threads.\n", + "2023-08-08 14:43:27.720 WARNING: warnings: /Users/cmccully/miniconda3/envs/banzai-floyds/lib/python3.10/site-packages/requests/__init__.py:102: RequestsDependencyWarning: urllib3 (1.26.14) or chardet (5.1.0)/charset_normalizer (2.0.4) doesn't match a supported version!\n", + " warnings.warn(\"urllib3 ({}) or chardet ({})/charset_normalizer ({}) doesn't match a supported \"\n", + "\n", + "2023-08-08 14:43:28.152 INFO: utils: Note: NumExpr detected 10 cores but \"NUMEXPR_MAX_THREADS\" not set, so enforcing safe limit of 8.\n", + "2023-08-08 14:43:28.152 INFO: utils: NumExpr defaulting to 8 threads.\n" + ] + } + ], + "source": [ + "banzai.dbs.create_db(os.environ[\"DB_ADDRESS\"])\n", + "os.system(f'banzai_add_site --site ogg --latitude 20.7069444444 --longitude -156.258055556 --elevation 3065 --timezone -10 --db-address={os.environ[\"DB_ADDRESS\"]}')\n", + "os.system(f'banzai_add_site --site coj --latitude -31.272932 --longitude 149.070648 --elevation 1116 --timezone 10 --db-address={os.environ[\"DB_ADDRESS\"]}')\n", + "os.system(f'banzai_add_instrument --site ogg --camera en06 --name floyds01 --instrument-type 2m0-FLOYDS-SciCam --db-address={os.environ[\"DB_ADDRESS\"]}')\n", + "os.system(f'banzai_add_instrument --site coj --camera en12 --name floyds02 --instrument-type 2m0-FLOYDS-SciCam --db-address={os.environ[\"DB_ADDRESS\"]}')\n", + "banzai_floyds.dbs.ingest_standards(os.environ[\"DB_ADDRESS\"])" + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "id": "3bbc2051", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "2023-08-08 14:50:21.963 INFO: fits_utils: Downloading file ogg2m001-en06-20190329-0018-x00.fits.fz from archive. ID: 29878203. | {\"filename\": \"ogg2m001-en06-20190329-0018-x00.fits.fz\", \"attempt_number\": 1, \"processName\": \"MainProcess\"}\n", + "2023-08-08 14:50:24.537 INFO: stages: Running banzai.stages.OverscanSubtractor | {\"filename\": \"ogg2m001-en06-20190329-0018-f00.fits.fz\", \"site\": \"ogg\", \"instrument\": \"floyds01\", \"epoch\": \"20190329\", \"request_num\": \"UNSPECIFIED\", \"obstype\": \"SKYFLAT\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", + "2023-08-08 14:50:24.540 INFO: stages: Running banzai.stages.Trimmer | {\"filename\": \"ogg2m001-en06-20190329-0018-f00.fits.fz\", \"site\": \"ogg\", \"instrument\": \"floyds01\", \"epoch\": \"20190329\", \"request_num\": \"UNSPECIFIED\", \"obstype\": \"SKYFLAT\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", + "2023-08-08 14:50:24.541 INFO: trim: Trimming image | {\"filename\": \"ogg2m001-en06-20190329-0018-f00.fits.fz\", \"site\": \"ogg\", \"instrument\": \"floyds01\", \"epoch\": \"20190329\", \"request_num\": \"UNSPECIFIED\", \"obstype\": \"SKYFLAT\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", + "2023-08-08 14:50:24.622 INFO: stages: Running banzai.stages.GainNormalizer | {\"filename\": \"ogg2m001-en06-20190329-0018-f00.fits.fz\", \"site\": \"ogg\", \"instrument\": \"floyds01\", \"epoch\": \"20190329\", \"request_num\": \"UNSPECIFIED\", \"obstype\": \"SKYFLAT\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", + "2023-08-08 14:50:24.623 INFO: gain: Multiplying by gain | {\"filename\": \"ogg2m001-en06-20190329-0018-f00.fits.fz\", \"site\": \"ogg\", \"instrument\": \"floyds01\", \"epoch\": \"20190329\", \"request_num\": \"UNSPECIFIED\", \"obstype\": \"SKYFLAT\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", + "2023-08-08 14:50:24.625 INFO: stages: Running banzai.stages.PoissonInitializer | {\"filename\": \"ogg2m001-en06-20190329-0018-f00.fits.fz\", \"site\": \"ogg\", \"instrument\": \"floyds01\", \"epoch\": \"20190329\", \"request_num\": \"UNSPECIFIED\", \"obstype\": \"SKYFLAT\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", + "2023-08-08 14:50:24.630 INFO: stages: Running banzai.stages.OrderSolver | {\"filename\": \"ogg2m001-en06-20190329-0018-f00.fits.fz\", \"site\": \"ogg\", \"instrument\": \"floyds01\", \"epoch\": \"20190329\", \"request_num\": \"UNSPECIFIED\", \"obstype\": \"SKYFLAT\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", + "2023-08-08 14:50:29.758 INFO: fits_utils: Downloading file coj2m002-en12-20220313-0002-x00.fits.fz from archive. ID: 49224839. | {\"filename\": \"coj2m002-en12-20220313-0002-x00.fits.fz\", \"attempt_number\": 1, \"processName\": \"MainProcess\"}\n", + "2023-08-08 14:50:31.650 INFO: stages: Running banzai.stages.OverscanSubtractor | {\"filename\": \"coj2m002-en12-20220313-0002-f00.fits.fz\", \"site\": \"coj\", \"instrument\": \"floyds02\", \"epoch\": \"20220313\", \"request_num\": \"UNSPECIFIED\", \"obstype\": \"SKYFLAT\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", + "2023-08-08 14:50:31.652 INFO: stages: Running banzai.stages.Trimmer | {\"filename\": \"coj2m002-en12-20220313-0002-f00.fits.fz\", \"site\": \"coj\", \"instrument\": \"floyds02\", \"epoch\": \"20220313\", \"request_num\": \"UNSPECIFIED\", \"obstype\": \"SKYFLAT\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", + "2023-08-08 14:50:31.653 INFO: trim: Trimming image | {\"filename\": \"coj2m002-en12-20220313-0002-f00.fits.fz\", \"site\": \"coj\", \"instrument\": \"floyds02\", \"epoch\": \"20220313\", \"request_num\": \"UNSPECIFIED\", \"obstype\": \"SKYFLAT\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", + "2023-08-08 14:50:31.705 INFO: stages: Running banzai.stages.GainNormalizer | {\"filename\": \"coj2m002-en12-20220313-0002-f00.fits.fz\", \"site\": \"coj\", \"instrument\": \"floyds02\", \"epoch\": \"20220313\", \"request_num\": \"UNSPECIFIED\", \"obstype\": \"SKYFLAT\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", + "2023-08-08 14:50:31.706 INFO: gain: Multiplying by gain | {\"filename\": \"coj2m002-en12-20220313-0002-f00.fits.fz\", \"site\": \"coj\", \"instrument\": \"floyds02\", \"epoch\": \"20220313\", \"request_num\": \"UNSPECIFIED\", \"obstype\": \"SKYFLAT\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", + "2023-08-08 14:50:31.708 INFO: stages: Running banzai.stages.PoissonInitializer | {\"filename\": \"coj2m002-en12-20220313-0002-f00.fits.fz\", \"site\": \"coj\", \"instrument\": \"floyds02\", \"epoch\": \"20220313\", \"request_num\": \"UNSPECIFIED\", \"obstype\": \"SKYFLAT\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", + "2023-08-08 14:50:31.715 INFO: stages: Running banzai.stages.OrderSolver | {\"filename\": \"coj2m002-en12-20220313-0002-f00.fits.fz\", \"site\": \"coj\", \"instrument\": \"floyds02\", \"epoch\": \"20220313\", \"request_num\": \"UNSPECIFIED\", \"obstype\": \"SKYFLAT\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n" + ] + } + ], + "source": [ + "skyflat_files = ascii.read(pkg_resources.resource_filename('banzai_floyds.tests', 'data/test_skyflat.dat'))\n", + "for skyflat in skyflat_files:\n", + " skyflat_info = dict(skyflat)\n", + " context = banzai.main.parse_args(settings, parse_system_args=False)\n", + " skyflat_hdu = fits.open(download_from_s3(skyflat_info, context))\n", + "\n", + " # Munge the data to be OBSTYPE SKYFLAT\n", + " skyflat_hdu['SCI'].header['OBSTYPE'] = 'SKYFLAT'\n", + " skyflat_name = skyflat_info[\"filename\"].replace(\"x00.fits\", \"f00.fits\")\n", + " filename = os.path.join('test_data', f'{skyflat_name}')\n", + " skyflat_hdu.writeto(filename, overwrite=True)\n", + " skyflat_hdu.close()\n", + " run_pipeline_stages([{'path': filename}], context)" + ] + }, + { + "cell_type": "code", + "execution_count": 41, + "id": "dd2912c3-c513-49b5-b13d-4f3e39d0915b", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "404eae88-c53b-48f6-ae9c-4720bd7cbc38", + "metadata": {}, + "outputs": [], + "source": [ + "DATA_FILELIST = pkg_resources.resource_filename('banzai_floyds.tests', 'data/test_data.dat')\n", + "test_data = ascii.read(DATA_FILELIST)" + ] + }, + { + "cell_type": "code", + "execution_count": 41, + "id": "6013453f-2085-4f47-bf60-d1b53cd75e5c", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "2023-08-08 15:16:34.678 INFO: fits_utils: Downloading file coj2m002-en12-20200813-0015-a00.fits.fz from archive. ID: 33851480. | {\"filename\": \"coj2m002-en12-20200813-0015-a00.fits.fz\", \"attempt_number\": 1, \"processName\": \"MainProcess\"}\n", + "2023-08-08 15:16:36.681 INFO: stages: Running banzai.stages.OverscanSubtractor | {\"filename\": \"coj2m002-en12-20200813-0015-a00.fits.fz\", \"site\": \"coj\", \"instrument\": \"floyds02\", \"epoch\": \"20200813\", \"request_num\": \"2207712\", \"obstype\": \"ARC\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", + "2023-08-08 15:16:36.684 INFO: stages: Running banzai.stages.Trimmer | {\"filename\": \"coj2m002-en12-20200813-0015-a00.fits.fz\", \"site\": \"coj\", \"instrument\": \"floyds02\", \"epoch\": \"20200813\", \"request_num\": \"2207712\", \"obstype\": \"ARC\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", + "2023-08-08 15:16:36.684 INFO: trim: Trimming image | {\"filename\": \"coj2m002-en12-20200813-0015-a00.fits.fz\", \"site\": \"coj\", \"instrument\": \"floyds02\", \"epoch\": \"20200813\", \"request_num\": \"2207712\", \"obstype\": \"ARC\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", + "2023-08-08 15:16:36.752 INFO: stages: Running banzai.stages.GainNormalizer | {\"filename\": \"coj2m002-en12-20200813-0015-a00.fits.fz\", \"site\": \"coj\", \"instrument\": \"floyds02\", \"epoch\": \"20200813\", \"request_num\": \"2207712\", \"obstype\": \"ARC\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", + "2023-08-08 15:16:36.753 INFO: gain: Multiplying by gain | {\"filename\": \"coj2m002-en12-20200813-0015-a00.fits.fz\", \"site\": \"coj\", \"instrument\": \"floyds02\", \"epoch\": \"20200813\", \"request_num\": \"2207712\", \"obstype\": \"ARC\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", + "2023-08-08 15:16:36.755 INFO: stages: Running banzai.stages.PoissonInitializer | {\"filename\": \"coj2m002-en12-20200813-0015-a00.fits.fz\", \"site\": \"coj\", \"instrument\": \"floyds02\", \"epoch\": \"20200813\", \"request_num\": \"2207712\", \"obstype\": \"ARC\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", + "2023-08-08 15:16:36.761 INFO: stages: Running banzai.stages.OrderLoader | {\"filename\": \"coj2m002-en12-20200813-0015-a00.fits.fz\", \"site\": \"coj\", \"instrument\": \"floyds02\", \"epoch\": \"20200813\", \"request_num\": \"2207712\", \"obstype\": \"ARC\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", + "2023-08-08 15:16:36.877 INFO: calibrations: Applying master calibration | {\"filename\": \"coj2m002-en12-20200813-0015-a00.fits.fz\", \"site\": \"coj\", \"instrument\": \"floyds02\", \"epoch\": \"20200813\", \"request_num\": \"2207712\", \"obstype\": \"ARC\", \"filter\": \"air\", \"master_calibration\": \"coj2m002-en12-20220313-0002-f91.fits.fz\", \"processName\": \"MainProcess\"}\n", + "2023-08-08 15:16:36.880 INFO: stages: Running banzai.stages.OrderTweaker | {\"filename\": \"coj2m002-en12-20200813-0015-a00.fits.fz\", \"site\": \"coj\", \"instrument\": \"floyds02\", \"epoch\": \"20200813\", \"request_num\": \"2207712\", \"obstype\": \"ARC\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", + "2023-08-08 15:16:37.567 INFO: stages: Running banzai.stages.CalibrateWavelengths | {\"filename\": \"coj2m002-en12-20200813-0015-a00.fits.fz\", \"site\": \"coj\", \"instrument\": \"floyds02\", \"epoch\": \"20200813\", \"request_num\": \"2207712\", \"obstype\": \"ARC\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", + "2023-08-08 15:16:45.125 INFO: fits_utils: Downloading file coj2m002-en12-20200813-0028-a00.fits.fz from archive. ID: 33852388. | {\"filename\": \"coj2m002-en12-20200813-0028-a00.fits.fz\", \"attempt_number\": 1, \"processName\": \"MainProcess\"}\n", + "2023-08-08 15:16:47.100 INFO: stages: Running banzai.stages.OverscanSubtractor | {\"filename\": \"coj2m002-en12-20200813-0028-a00.fits.fz\", \"site\": \"coj\", \"instrument\": \"floyds02\", \"epoch\": \"20200813\", \"request_num\": \"2207853\", \"obstype\": \"ARC\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", + "2023-08-08 15:16:47.102 INFO: stages: Running banzai.stages.Trimmer | {\"filename\": \"coj2m002-en12-20200813-0028-a00.fits.fz\", \"site\": \"coj\", \"instrument\": \"floyds02\", \"epoch\": \"20200813\", \"request_num\": \"2207853\", \"obstype\": \"ARC\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", + "2023-08-08 15:16:47.103 INFO: trim: Trimming image | {\"filename\": \"coj2m002-en12-20200813-0028-a00.fits.fz\", \"site\": \"coj\", \"instrument\": \"floyds02\", \"epoch\": \"20200813\", \"request_num\": \"2207853\", \"obstype\": \"ARC\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", + "2023-08-08 15:16:47.181 INFO: stages: Running banzai.stages.GainNormalizer | {\"filename\": \"coj2m002-en12-20200813-0028-a00.fits.fz\", \"site\": \"coj\", \"instrument\": \"floyds02\", \"epoch\": \"20200813\", \"request_num\": \"2207853\", \"obstype\": \"ARC\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", + "2023-08-08 15:16:47.181 INFO: gain: Multiplying by gain | {\"filename\": \"coj2m002-en12-20200813-0028-a00.fits.fz\", \"site\": \"coj\", \"instrument\": \"floyds02\", \"epoch\": \"20200813\", \"request_num\": \"2207853\", \"obstype\": \"ARC\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", + "2023-08-08 15:16:47.183 INFO: stages: Running banzai.stages.PoissonInitializer | {\"filename\": \"coj2m002-en12-20200813-0028-a00.fits.fz\", \"site\": \"coj\", \"instrument\": \"floyds02\", \"epoch\": \"20200813\", \"request_num\": \"2207853\", \"obstype\": \"ARC\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", + "2023-08-08 15:16:47.189 INFO: stages: Running banzai.stages.OrderLoader | {\"filename\": \"coj2m002-en12-20200813-0028-a00.fits.fz\", \"site\": \"coj\", \"instrument\": \"floyds02\", \"epoch\": \"20200813\", \"request_num\": \"2207853\", \"obstype\": \"ARC\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", + "2023-08-08 15:16:47.304 INFO: calibrations: Applying master calibration | {\"filename\": \"coj2m002-en12-20200813-0028-a00.fits.fz\", \"site\": \"coj\", \"instrument\": \"floyds02\", \"epoch\": \"20200813\", \"request_num\": \"2207853\", \"obstype\": \"ARC\", \"filter\": \"air\", \"master_calibration\": \"coj2m002-en12-20220313-0002-f91.fits.fz\", \"processName\": \"MainProcess\"}\n", + "2023-08-08 15:16:47.307 INFO: stages: Running banzai.stages.OrderTweaker | {\"filename\": \"coj2m002-en12-20200813-0028-a00.fits.fz\", \"site\": \"coj\", \"instrument\": \"floyds02\", \"epoch\": \"20200813\", \"request_num\": \"2207853\", \"obstype\": \"ARC\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", + "2023-08-08 15:16:47.430 INFO: stages: Running banzai.stages.CalibrateWavelengths | {\"filename\": \"coj2m002-en12-20200813-0028-a00.fits.fz\", \"site\": \"coj\", \"instrument\": \"floyds02\", \"epoch\": \"20200813\", \"request_num\": \"2207853\", \"obstype\": \"ARC\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", + "2023-08-08 15:16:55.788 INFO: fits_utils: Downloading file ogg2m001-en06-20200822-0009-a00.fits.fz from archive. ID: 33989264. | {\"filename\": \"ogg2m001-en06-20200822-0009-a00.fits.fz\", \"attempt_number\": 1, \"processName\": \"MainProcess\"}\n", + "2023-08-08 15:16:57.925 INFO: stages: Running banzai.stages.OverscanSubtractor | {\"filename\": \"ogg2m001-en06-20200822-0009-a00.fits.fz\", \"site\": \"ogg\", \"instrument\": \"floyds01\", \"epoch\": \"20200822\", \"request_num\": \"2217414\", \"obstype\": \"ARC\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", + "2023-08-08 15:16:57.928 INFO: stages: Running banzai.stages.Trimmer | {\"filename\": \"ogg2m001-en06-20200822-0009-a00.fits.fz\", \"site\": \"ogg\", \"instrument\": \"floyds01\", \"epoch\": \"20200822\", \"request_num\": \"2217414\", \"obstype\": \"ARC\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", + "2023-08-08 15:16:57.928 INFO: trim: Trimming image | {\"filename\": \"ogg2m001-en06-20200822-0009-a00.fits.fz\", \"site\": \"ogg\", \"instrument\": \"floyds01\", \"epoch\": \"20200822\", \"request_num\": \"2217414\", \"obstype\": \"ARC\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", + "2023-08-08 15:16:57.998 INFO: stages: Running banzai.stages.GainNormalizer | {\"filename\": \"ogg2m001-en06-20200822-0009-a00.fits.fz\", \"site\": \"ogg\", \"instrument\": \"floyds01\", \"epoch\": \"20200822\", \"request_num\": \"2217414\", \"obstype\": \"ARC\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", + "2023-08-08 15:16:57.998 INFO: gain: Multiplying by gain | {\"filename\": \"ogg2m001-en06-20200822-0009-a00.fits.fz\", \"site\": \"ogg\", \"instrument\": \"floyds01\", \"epoch\": \"20200822\", \"request_num\": \"2217414\", \"obstype\": \"ARC\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", + "2023-08-08 15:16:58.000 INFO: stages: Running banzai.stages.PoissonInitializer | {\"filename\": \"ogg2m001-en06-20200822-0009-a00.fits.fz\", \"site\": \"ogg\", \"instrument\": \"floyds01\", \"epoch\": \"20200822\", \"request_num\": \"2217414\", \"obstype\": \"ARC\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", + "2023-08-08 15:16:58.006 INFO: stages: Running banzai.stages.OrderLoader | {\"filename\": \"ogg2m001-en06-20200822-0009-a00.fits.fz\", \"site\": \"ogg\", \"instrument\": \"floyds01\", \"epoch\": \"20200822\", \"request_num\": \"2217414\", \"obstype\": \"ARC\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", + "2023-08-08 15:16:58.119 INFO: calibrations: Applying master calibration | {\"filename\": \"ogg2m001-en06-20200822-0009-a00.fits.fz\", \"site\": \"ogg\", \"instrument\": \"floyds01\", \"epoch\": \"20200822\", \"request_num\": \"2217414\", \"obstype\": \"ARC\", \"filter\": \"air\", \"master_calibration\": \"ogg2m001-en06-20190329-0018-f91.fits.fz\", \"processName\": \"MainProcess\"}\n", + "2023-08-08 15:16:58.122 INFO: stages: Running banzai.stages.OrderTweaker | {\"filename\": \"ogg2m001-en06-20200822-0009-a00.fits.fz\", \"site\": \"ogg\", \"instrument\": \"floyds01\", \"epoch\": \"20200822\", \"request_num\": \"2217414\", \"obstype\": \"ARC\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", + "2023-08-08 15:16:58.232 INFO: stages: Running banzai.stages.CalibrateWavelengths | {\"filename\": \"ogg2m001-en06-20200822-0009-a00.fits.fz\", \"site\": \"ogg\", \"instrument\": \"floyds01\", \"epoch\": \"20200822\", \"request_num\": \"2217414\", \"obstype\": \"ARC\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", + "2023-08-08 15:16:58.792 WARNING: warnings: /Users/cmccully/miniconda3/envs/banzai-floyds/lib/python3.10/site-packages/numpy/polynomial/legendre.py:1412: RankWarning: The fit may be poorly conditioned\n", + " return pu._fit(legvander, x, y, deg, rcond, full, w)\n", + "\n", + "2023-08-08 15:17:07.290 INFO: fits_utils: Downloading file ogg2m001-en06-20200822-0028-a00.fits.fz from archive. ID: 33995029. | {\"filename\": \"ogg2m001-en06-20200822-0028-a00.fits.fz\", \"attempt_number\": 1, \"processName\": \"MainProcess\"}\n", + "2023-08-08 15:17:09.257 INFO: stages: Running banzai.stages.OverscanSubtractor | {\"filename\": \"ogg2m001-en06-20200822-0028-a00.fits.fz\", \"site\": \"ogg\", \"instrument\": \"floyds01\", \"epoch\": \"20200822\", \"request_num\": \"2217762\", \"obstype\": \"ARC\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", + "2023-08-08 15:17:09.261 INFO: stages: Running banzai.stages.Trimmer | {\"filename\": \"ogg2m001-en06-20200822-0028-a00.fits.fz\", \"site\": \"ogg\", \"instrument\": \"floyds01\", \"epoch\": \"20200822\", \"request_num\": \"2217762\", \"obstype\": \"ARC\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", + "2023-08-08 15:17:09.262 INFO: trim: Trimming image | {\"filename\": \"ogg2m001-en06-20200822-0028-a00.fits.fz\", \"site\": \"ogg\", \"instrument\": \"floyds01\", \"epoch\": \"20200822\", \"request_num\": \"2217762\", \"obstype\": \"ARC\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", + "2023-08-08 15:17:09.317 INFO: stages: Running banzai.stages.GainNormalizer | {\"filename\": \"ogg2m001-en06-20200822-0028-a00.fits.fz\", \"site\": \"ogg\", \"instrument\": \"floyds01\", \"epoch\": \"20200822\", \"request_num\": \"2217762\", \"obstype\": \"ARC\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", + "2023-08-08 15:17:09.317 INFO: gain: Multiplying by gain | {\"filename\": \"ogg2m001-en06-20200822-0028-a00.fits.fz\", \"site\": \"ogg\", \"instrument\": \"floyds01\", \"epoch\": \"20200822\", \"request_num\": \"2217762\", \"obstype\": \"ARC\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", + "2023-08-08 15:17:09.319 INFO: stages: Running banzai.stages.PoissonInitializer | {\"filename\": \"ogg2m001-en06-20200822-0028-a00.fits.fz\", \"site\": \"ogg\", \"instrument\": \"floyds01\", \"epoch\": \"20200822\", \"request_num\": \"2217762\", \"obstype\": \"ARC\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", + "2023-08-08 15:17:09.325 INFO: stages: Running banzai.stages.OrderLoader | {\"filename\": \"ogg2m001-en06-20200822-0028-a00.fits.fz\", \"site\": \"ogg\", \"instrument\": \"floyds01\", \"epoch\": \"20200822\", \"request_num\": \"2217762\", \"obstype\": \"ARC\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", + "2023-08-08 15:17:09.434 INFO: calibrations: Applying master calibration | {\"filename\": \"ogg2m001-en06-20200822-0028-a00.fits.fz\", \"site\": \"ogg\", \"instrument\": \"floyds01\", \"epoch\": \"20200822\", \"request_num\": \"2217762\", \"obstype\": \"ARC\", \"filter\": \"air\", \"master_calibration\": \"ogg2m001-en06-20190329-0018-f91.fits.fz\", \"processName\": \"MainProcess\"}\n", + "2023-08-08 15:17:09.438 INFO: stages: Running banzai.stages.OrderTweaker | {\"filename\": \"ogg2m001-en06-20200822-0028-a00.fits.fz\", \"site\": \"ogg\", \"instrument\": \"floyds01\", \"epoch\": \"20200822\", \"request_num\": \"2217762\", \"obstype\": \"ARC\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", + "2023-08-08 15:17:09.789 INFO: stages: Running banzai.stages.CalibrateWavelengths | {\"filename\": \"ogg2m001-en06-20200822-0028-a00.fits.fz\", \"site\": \"ogg\", \"instrument\": \"floyds01\", \"epoch\": \"20200822\", \"request_num\": \"2217762\", \"obstype\": \"ARC\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n" + ] + } + ], + "source": [ + "for row in test_data:\n", + " if 'a00.fits' in row['filename']:\n", + " archive_record = requests.get(f'{context.ARCHIVE_FRAME_URL}/{row[\"frameid\"]}').json()\n", + " archive_record['frameid'] = archive_record['id']\n", + " run_pipeline_stages([archive_record], context)" + ] + }, + { + "cell_type": "code", + "execution_count": 47, + "id": "683f69eb", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "2023-08-08 15:33:56.376 INFO: fits_utils: Downloading file coj2m002-en12-20200813-0016-w00.fits.fz from archive. ID: 33851505. | {\"filename\": \"coj2m002-en12-20200813-0016-w00.fits.fz\", \"attempt_number\": 1, \"processName\": \"MainProcess\"}\n", + "2023-08-08 15:33:58.482 INFO: stages: Running banzai.stages.OverscanSubtractor | {\"filename\": \"coj2m002-en12-20200813-0016-w00.fits.fz\", \"site\": \"coj\", \"instrument\": \"floyds02\", \"epoch\": \"20200813\", \"request_num\": \"2207712\", \"obstype\": \"LAMPFLAT\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", + "2023-08-08 15:33:58.484 INFO: stages: Running banzai.stages.Trimmer | {\"filename\": \"coj2m002-en12-20200813-0016-w00.fits.fz\", \"site\": \"coj\", \"instrument\": \"floyds02\", \"epoch\": \"20200813\", \"request_num\": \"2207712\", \"obstype\": \"LAMPFLAT\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", + "2023-08-08 15:33:58.485 INFO: trim: Trimming image | {\"filename\": \"coj2m002-en12-20200813-0016-w00.fits.fz\", \"site\": \"coj\", \"instrument\": \"floyds02\", \"epoch\": \"20200813\", \"request_num\": \"2207712\", \"obstype\": \"LAMPFLAT\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", + "2023-08-08 15:33:58.553 INFO: stages: Running banzai.stages.GainNormalizer | {\"filename\": \"coj2m002-en12-20200813-0016-w00.fits.fz\", \"site\": \"coj\", \"instrument\": \"floyds02\", \"epoch\": \"20200813\", \"request_num\": \"2207712\", \"obstype\": \"LAMPFLAT\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", + "2023-08-08 15:33:58.553 INFO: gain: Multiplying by gain | {\"filename\": \"coj2m002-en12-20200813-0016-w00.fits.fz\", \"site\": \"coj\", \"instrument\": \"floyds02\", \"epoch\": \"20200813\", \"request_num\": \"2207712\", \"obstype\": \"LAMPFLAT\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", + "2023-08-08 15:33:58.555 INFO: stages: Running banzai.stages.PoissonInitializer | {\"filename\": \"coj2m002-en12-20200813-0016-w00.fits.fz\", \"site\": \"coj\", \"instrument\": \"floyds02\", \"epoch\": \"20200813\", \"request_num\": \"2207712\", \"obstype\": \"LAMPFLAT\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", + "2023-08-08 15:33:58.562 INFO: stages: Running banzai.stages.OrderLoader | {\"filename\": \"coj2m002-en12-20200813-0016-w00.fits.fz\", \"site\": \"coj\", \"instrument\": \"floyds02\", \"epoch\": \"20200813\", \"request_num\": \"2207712\", \"obstype\": \"LAMPFLAT\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", + "2023-08-08 15:33:58.676 INFO: calibrations: Applying master calibration | {\"filename\": \"coj2m002-en12-20200813-0016-w00.fits.fz\", \"site\": \"coj\", \"instrument\": \"floyds02\", \"epoch\": \"20200813\", \"request_num\": \"2207712\", \"obstype\": \"LAMPFLAT\", \"filter\": \"air\", \"master_calibration\": \"coj2m002-en12-20220313-0002-f91.fits.fz\", \"processName\": \"MainProcess\"}\n", + "2023-08-08 15:33:58.680 INFO: stages: Running banzai.stages.OrderTweaker | {\"filename\": \"coj2m002-en12-20200813-0016-w00.fits.fz\", \"site\": \"coj\", \"instrument\": \"floyds02\", \"epoch\": \"20200813\", \"request_num\": \"2207712\", \"obstype\": \"LAMPFLAT\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", + "2023-08-08 15:33:58.908 INFO: stages: Running banzai.stages.WavelengthSolutionLoader | {\"filename\": \"coj2m002-en12-20200813-0016-w00.fits.fz\", \"site\": \"coj\", \"instrument\": \"floyds02\", \"epoch\": \"20200813\", \"request_num\": \"2207712\", \"obstype\": \"LAMPFLAT\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", + "2023-08-08 15:33:59.067 INFO: calibrations: Applying master calibration | {\"filename\": \"coj2m002-en12-20200813-0016-w00.fits.fz\", \"site\": \"coj\", \"instrument\": \"floyds02\", \"epoch\": \"20200813\", \"request_num\": \"2207712\", \"obstype\": \"LAMPFLAT\", \"filter\": \"air\", \"master_calibration\": \"coj2m002-en12-20200813-0015-a91.fits.fz\", \"processName\": \"MainProcess\"}\n", + "2023-08-08 15:33:59.890 INFO: fits_utils: Downloading file coj2m002-en12-20200813-0027-w00.fits.fz from archive. ID: 33852359. | {\"filename\": \"coj2m002-en12-20200813-0027-w00.fits.fz\", \"attempt_number\": 1, \"processName\": \"MainProcess\"}\n", + "2023-08-08 15:34:02.051 INFO: stages: Running banzai.stages.OverscanSubtractor | {\"filename\": \"coj2m002-en12-20200813-0027-w00.fits.fz\", \"site\": \"coj\", \"instrument\": \"floyds02\", \"epoch\": \"20200813\", \"request_num\": \"2207853\", \"obstype\": \"LAMPFLAT\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", + "2023-08-08 15:34:02.054 INFO: stages: Running banzai.stages.Trimmer | {\"filename\": \"coj2m002-en12-20200813-0027-w00.fits.fz\", \"site\": \"coj\", \"instrument\": \"floyds02\", \"epoch\": \"20200813\", \"request_num\": \"2207853\", \"obstype\": \"LAMPFLAT\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", + "2023-08-08 15:34:02.054 INFO: trim: Trimming image | {\"filename\": \"coj2m002-en12-20200813-0027-w00.fits.fz\", \"site\": \"coj\", \"instrument\": \"floyds02\", \"epoch\": \"20200813\", \"request_num\": \"2207853\", \"obstype\": \"LAMPFLAT\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", + "2023-08-08 15:34:02.118 INFO: stages: Running banzai.stages.GainNormalizer | {\"filename\": \"coj2m002-en12-20200813-0027-w00.fits.fz\", \"site\": \"coj\", \"instrument\": \"floyds02\", \"epoch\": \"20200813\", \"request_num\": \"2207853\", \"obstype\": \"LAMPFLAT\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", + "2023-08-08 15:34:02.119 INFO: gain: Multiplying by gain | {\"filename\": \"coj2m002-en12-20200813-0027-w00.fits.fz\", \"site\": \"coj\", \"instrument\": \"floyds02\", \"epoch\": \"20200813\", \"request_num\": \"2207853\", \"obstype\": \"LAMPFLAT\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", + "2023-08-08 15:34:02.121 INFO: stages: Running banzai.stages.PoissonInitializer | {\"filename\": \"coj2m002-en12-20200813-0027-w00.fits.fz\", \"site\": \"coj\", \"instrument\": \"floyds02\", \"epoch\": \"20200813\", \"request_num\": \"2207853\", \"obstype\": \"LAMPFLAT\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", + "2023-08-08 15:34:02.127 INFO: stages: Running banzai.stages.OrderLoader | {\"filename\": \"coj2m002-en12-20200813-0027-w00.fits.fz\", \"site\": \"coj\", \"instrument\": \"floyds02\", \"epoch\": \"20200813\", \"request_num\": \"2207853\", \"obstype\": \"LAMPFLAT\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", + "2023-08-08 15:34:02.251 INFO: calibrations: Applying master calibration | {\"filename\": \"coj2m002-en12-20200813-0027-w00.fits.fz\", \"site\": \"coj\", \"instrument\": \"floyds02\", \"epoch\": \"20200813\", \"request_num\": \"2207853\", \"obstype\": \"LAMPFLAT\", \"filter\": \"air\", \"master_calibration\": \"coj2m002-en12-20220313-0002-f91.fits.fz\", \"processName\": \"MainProcess\"}\n", + "2023-08-08 15:34:02.254 INFO: stages: Running banzai.stages.OrderTweaker | {\"filename\": \"coj2m002-en12-20200813-0027-w00.fits.fz\", \"site\": \"coj\", \"instrument\": \"floyds02\", \"epoch\": \"20200813\", \"request_num\": \"2207853\", \"obstype\": \"LAMPFLAT\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", + "2023-08-08 15:34:02.455 INFO: stages: Running banzai.stages.WavelengthSolutionLoader | {\"filename\": \"coj2m002-en12-20200813-0027-w00.fits.fz\", \"site\": \"coj\", \"instrument\": \"floyds02\", \"epoch\": \"20200813\", \"request_num\": \"2207853\", \"obstype\": \"LAMPFLAT\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", + "2023-08-08 15:34:02.629 INFO: calibrations: Applying master calibration | {\"filename\": \"coj2m002-en12-20200813-0027-w00.fits.fz\", \"site\": \"coj\", \"instrument\": \"floyds02\", \"epoch\": \"20200813\", \"request_num\": \"2207853\", \"obstype\": \"LAMPFLAT\", \"filter\": \"air\", \"master_calibration\": \"coj2m002-en12-20200813-0028-a91.fits.fz\", \"processName\": \"MainProcess\"}\n", + "2023-08-08 15:34:03.471 INFO: fits_utils: Downloading file ogg2m001-en06-20200822-0008-w00.fits.fz from archive. ID: 33989173. | {\"filename\": \"ogg2m001-en06-20200822-0008-w00.fits.fz\", \"attempt_number\": 1, \"processName\": \"MainProcess\"}\n", + "2023-08-08 15:34:05.535 INFO: stages: Running banzai.stages.OverscanSubtractor | {\"filename\": \"ogg2m001-en06-20200822-0008-w00.fits.fz\", \"site\": \"ogg\", \"instrument\": \"floyds01\", \"epoch\": \"20200822\", \"request_num\": \"2217414\", \"obstype\": \"LAMPFLAT\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", + "2023-08-08 15:34:05.538 INFO: stages: Running banzai.stages.Trimmer | {\"filename\": \"ogg2m001-en06-20200822-0008-w00.fits.fz\", \"site\": \"ogg\", \"instrument\": \"floyds01\", \"epoch\": \"20200822\", \"request_num\": \"2217414\", \"obstype\": \"LAMPFLAT\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", + "2023-08-08 15:34:05.539 INFO: trim: Trimming image | {\"filename\": \"ogg2m001-en06-20200822-0008-w00.fits.fz\", \"site\": \"ogg\", \"instrument\": \"floyds01\", \"epoch\": \"20200822\", \"request_num\": \"2217414\", \"obstype\": \"LAMPFLAT\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", + "2023-08-08 15:34:05.616 INFO: stages: Running banzai.stages.GainNormalizer | {\"filename\": \"ogg2m001-en06-20200822-0008-w00.fits.fz\", \"site\": \"ogg\", \"instrument\": \"floyds01\", \"epoch\": \"20200822\", \"request_num\": \"2217414\", \"obstype\": \"LAMPFLAT\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", + "2023-08-08 15:34:05.617 INFO: gain: Multiplying by gain | {\"filename\": \"ogg2m001-en06-20200822-0008-w00.fits.fz\", \"site\": \"ogg\", \"instrument\": \"floyds01\", \"epoch\": \"20200822\", \"request_num\": \"2217414\", \"obstype\": \"LAMPFLAT\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", + "2023-08-08 15:34:05.619 INFO: stages: Running banzai.stages.PoissonInitializer | {\"filename\": \"ogg2m001-en06-20200822-0008-w00.fits.fz\", \"site\": \"ogg\", \"instrument\": \"floyds01\", \"epoch\": \"20200822\", \"request_num\": \"2217414\", \"obstype\": \"LAMPFLAT\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", + "2023-08-08 15:34:05.625 INFO: stages: Running banzai.stages.OrderLoader | {\"filename\": \"ogg2m001-en06-20200822-0008-w00.fits.fz\", \"site\": \"ogg\", \"instrument\": \"floyds01\", \"epoch\": \"20200822\", \"request_num\": \"2217414\", \"obstype\": \"LAMPFLAT\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", + "2023-08-08 15:34:05.733 INFO: calibrations: Applying master calibration | {\"filename\": \"ogg2m001-en06-20200822-0008-w00.fits.fz\", \"site\": \"ogg\", \"instrument\": \"floyds01\", \"epoch\": \"20200822\", \"request_num\": \"2217414\", \"obstype\": \"LAMPFLAT\", \"filter\": \"air\", \"master_calibration\": \"ogg2m001-en06-20190329-0018-f91.fits.fz\", \"processName\": \"MainProcess\"}\n", + "2023-08-08 15:34:05.737 INFO: stages: Running banzai.stages.OrderTweaker | {\"filename\": \"ogg2m001-en06-20200822-0008-w00.fits.fz\", \"site\": \"ogg\", \"instrument\": \"floyds01\", \"epoch\": \"20200822\", \"request_num\": \"2217414\", \"obstype\": \"LAMPFLAT\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", + "2023-08-08 15:34:06.056 INFO: stages: Running banzai.stages.WavelengthSolutionLoader | {\"filename\": \"ogg2m001-en06-20200822-0008-w00.fits.fz\", \"site\": \"ogg\", \"instrument\": \"floyds01\", \"epoch\": \"20200822\", \"request_num\": \"2217414\", \"obstype\": \"LAMPFLAT\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", + "2023-08-08 15:34:06.218 INFO: calibrations: Applying master calibration | {\"filename\": \"ogg2m001-en06-20200822-0008-w00.fits.fz\", \"site\": \"ogg\", \"instrument\": \"floyds01\", \"epoch\": \"20200822\", \"request_num\": \"2217414\", \"obstype\": \"LAMPFLAT\", \"filter\": \"air\", \"master_calibration\": \"ogg2m001-en06-20200822-0009-a91.fits.fz\", \"processName\": \"MainProcess\"}\n", + "2023-08-08 15:34:07.042 INFO: fits_utils: Downloading file ogg2m001-en06-20200822-0029-w00.fits.fz from archive. ID: 33995044. | {\"filename\": \"ogg2m001-en06-20200822-0029-w00.fits.fz\", \"attempt_number\": 1, \"processName\": \"MainProcess\"}\n", + "2023-08-08 15:34:09.032 INFO: stages: Running banzai.stages.OverscanSubtractor | {\"filename\": \"ogg2m001-en06-20200822-0029-w00.fits.fz\", \"site\": \"ogg\", \"instrument\": \"floyds01\", \"epoch\": \"20200822\", \"request_num\": \"2217762\", \"obstype\": \"LAMPFLAT\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", + "2023-08-08 15:34:09.035 INFO: stages: Running banzai.stages.Trimmer | {\"filename\": \"ogg2m001-en06-20200822-0029-w00.fits.fz\", \"site\": \"ogg\", \"instrument\": \"floyds01\", \"epoch\": \"20200822\", \"request_num\": \"2217762\", \"obstype\": \"LAMPFLAT\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", + "2023-08-08 15:34:09.035 INFO: trim: Trimming image | {\"filename\": \"ogg2m001-en06-20200822-0029-w00.fits.fz\", \"site\": \"ogg\", \"instrument\": \"floyds01\", \"epoch\": \"20200822\", \"request_num\": \"2217762\", \"obstype\": \"LAMPFLAT\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", + "2023-08-08 15:34:09.095 INFO: stages: Running banzai.stages.GainNormalizer | {\"filename\": \"ogg2m001-en06-20200822-0029-w00.fits.fz\", \"site\": \"ogg\", \"instrument\": \"floyds01\", \"epoch\": \"20200822\", \"request_num\": \"2217762\", \"obstype\": \"LAMPFLAT\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", + "2023-08-08 15:34:09.096 INFO: gain: Multiplying by gain | {\"filename\": \"ogg2m001-en06-20200822-0029-w00.fits.fz\", \"site\": \"ogg\", \"instrument\": \"floyds01\", \"epoch\": \"20200822\", \"request_num\": \"2217762\", \"obstype\": \"LAMPFLAT\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", + "2023-08-08 15:34:09.098 INFO: stages: Running banzai.stages.PoissonInitializer | {\"filename\": \"ogg2m001-en06-20200822-0029-w00.fits.fz\", \"site\": \"ogg\", \"instrument\": \"floyds01\", \"epoch\": \"20200822\", \"request_num\": \"2217762\", \"obstype\": \"LAMPFLAT\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", + "2023-08-08 15:34:09.104 INFO: stages: Running banzai.stages.OrderLoader | {\"filename\": \"ogg2m001-en06-20200822-0029-w00.fits.fz\", \"site\": \"ogg\", \"instrument\": \"floyds01\", \"epoch\": \"20200822\", \"request_num\": \"2217762\", \"obstype\": \"LAMPFLAT\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", + "2023-08-08 15:34:09.210 INFO: calibrations: Applying master calibration | {\"filename\": \"ogg2m001-en06-20200822-0029-w00.fits.fz\", \"site\": \"ogg\", \"instrument\": \"floyds01\", \"epoch\": \"20200822\", \"request_num\": \"2217762\", \"obstype\": \"LAMPFLAT\", \"filter\": \"air\", \"master_calibration\": \"ogg2m001-en06-20190329-0018-f91.fits.fz\", \"processName\": \"MainProcess\"}\n", + "2023-08-08 15:34:09.214 INFO: stages: Running banzai.stages.OrderTweaker | {\"filename\": \"ogg2m001-en06-20200822-0029-w00.fits.fz\", \"site\": \"ogg\", \"instrument\": \"floyds01\", \"epoch\": \"20200822\", \"request_num\": \"2217762\", \"obstype\": \"LAMPFLAT\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", + "2023-08-08 15:34:09.322 INFO: stages: Running banzai.stages.WavelengthSolutionLoader | {\"filename\": \"ogg2m001-en06-20200822-0029-w00.fits.fz\", \"site\": \"ogg\", \"instrument\": \"floyds01\", \"epoch\": \"20200822\", \"request_num\": \"2217762\", \"obstype\": \"LAMPFLAT\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", + "2023-08-08 15:34:09.474 INFO: calibrations: Applying master calibration | {\"filename\": \"ogg2m001-en06-20200822-0029-w00.fits.fz\", \"site\": \"ogg\", \"instrument\": \"floyds01\", \"epoch\": \"20200822\", \"request_num\": \"2217762\", \"obstype\": \"LAMPFLAT\", \"filter\": \"air\", \"master_calibration\": \"ogg2m001-en06-20200822-0028-a91.fits.fz\", \"processName\": \"MainProcess\"}\n" + ] + } + ], + "source": [ + "for row in test_data:\n", + " if 'w00.fits' in row['filename']:\n", + " archive_record = requests.get(f'{context.ARCHIVE_FRAME_URL}/{row[\"frameid\"]}').json()\n", + " archive_record['frameid'] = archive_record['id']\n", + " run_pipeline_stages([archive_record], context)" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "93283289", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "2023-08-11 10:41:33.342 INFO: calibrations: Making master frames | {\"type\": \"2m0-FLOYDS-SciCam\", \"site\": \"ogg\", \"camera\": \"en06\", \"obstype\": \"LAMPFLAT\", \"min_date\": \"2020-01-01\", \"max_date\": \"2021-01-01\", \"processName\": \"MainProcess\"}\n", + "2023-08-11 10:41:33.706 INFO: stages: Running banzai.stages.FringeMaker | {\"filename\": \"ogg2m001-en06-20200822-0008-w91.fits.fz\", \"site\": \"ogg\", \"instrument\": \"floyds01\", \"epoch\": \"20200822\", \"request_num\": \"2217414\", \"obstype\": \"LAMPFLAT\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", + "2023-08-11 10:41:40.038 WARNING: warnings: /Users/cmccully/miniconda3/envs/banzai-floyds/lib/python3.10/site-packages/astropy/io/fits/card.py:1008: VerifyWarning: Card is too long, comment will be truncated.\n", + " warnings.warn('Card is too long, comment will be truncated.',\n", + "\n", + "2023-08-11 10:41:40.161 INFO: calibrations: Finished | {\"processName\": \"MainProcess\"}\n", + "2023-08-11 10:41:40.161 INFO: calibrations: Making master frames | {\"type\": \"2m0-FLOYDS-SciCam\", \"site\": \"coj\", \"camera\": \"en12\", \"obstype\": \"LAMPFLAT\", \"min_date\": \"2020-01-01\", \"max_date\": \"2021-01-01\", \"processName\": \"MainProcess\"}\n", + "2023-08-11 10:41:40.360 INFO: stages: Running banzai.stages.FringeMaker | {\"filename\": \"coj2m002-en12-20200813-0016-w91.fits.fz\", \"site\": \"coj\", \"instrument\": \"floyds02\", \"epoch\": \"20200813\", \"request_num\": \"2207712\", \"obstype\": \"LAMPFLAT\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", + "2023-08-11 10:41:47.990 INFO: calibrations: Finished | {\"processName\": \"MainProcess\"}\n" + ] + } + ], + "source": [ + "coj_floyds = banzai.dbs.get_instruments_at_site('coj', settings.db_address)[0]\n", + "ogg_floyds = banzai.dbs.get_instruments_at_site('ogg', settings.db_address)[0]\n", + "make_master_calibrations(ogg_floyds, 'LAMPFLAT', '2020-01-01', '2021-01-01', context)\n", + "make_master_calibrations(coj_floyds, 'LAMPFLAT', '2020-01-01', '2021-01-01', context)" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "dd129631", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "2023-08-11 10:43:22.170 INFO: fits_utils: Downloading file coj2m002-en12-20200813-0014-e00.fits.fz from archive. ID: 33851447. | {\"filename\": \"coj2m002-en12-20200813-0014-e00.fits.fz\", \"attempt_number\": 1, \"processName\": \"MainProcess\"}\n", + "2023-08-11 10:43:24.241 INFO: stages: Running banzai.stages.OverscanSubtractor | {\"filename\": \"coj2m002-en12-20200813-0014-e00.fits.fz\", \"site\": \"coj\", \"instrument\": \"floyds02\", \"epoch\": \"20200813\", \"request_num\": \"2207712\", \"obstype\": \"SPECTRUM\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", + "2023-08-11 10:43:24.244 INFO: stages: Running banzai.stages.Trimmer | {\"filename\": \"coj2m002-en12-20200813-0014-e00.fits.fz\", \"site\": \"coj\", \"instrument\": \"floyds02\", \"epoch\": \"20200813\", \"request_num\": \"2207712\", \"obstype\": \"SPECTRUM\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", + "2023-08-11 10:43:24.245 INFO: trim: Trimming image | {\"filename\": \"coj2m002-en12-20200813-0014-e00.fits.fz\", \"site\": \"coj\", \"instrument\": \"floyds02\", \"epoch\": \"20200813\", \"request_num\": \"2207712\", \"obstype\": \"SPECTRUM\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", + "2023-08-11 10:43:24.313 INFO: stages: Running banzai.stages.GainNormalizer | {\"filename\": \"coj2m002-en12-20200813-0014-e00.fits.fz\", \"site\": \"coj\", \"instrument\": \"floyds02\", \"epoch\": \"20200813\", \"request_num\": \"2207712\", \"obstype\": \"SPECTRUM\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", + "2023-08-11 10:43:24.314 INFO: gain: Multiplying by gain | {\"filename\": \"coj2m002-en12-20200813-0014-e00.fits.fz\", \"site\": \"coj\", \"instrument\": \"floyds02\", \"epoch\": \"20200813\", \"request_num\": \"2207712\", \"obstype\": \"SPECTRUM\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", + "2023-08-11 10:43:24.317 INFO: stages: Running banzai.stages.PoissonInitializer | {\"filename\": \"coj2m002-en12-20200813-0014-e00.fits.fz\", \"site\": \"coj\", \"instrument\": \"floyds02\", \"epoch\": \"20200813\", \"request_num\": \"2207712\", \"obstype\": \"SPECTRUM\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", + "2023-08-11 10:43:24.326 INFO: stages: Running banzai.stages.OrderLoader | {\"filename\": \"coj2m002-en12-20200813-0014-e00.fits.fz\", \"site\": \"coj\", \"instrument\": \"floyds02\", \"epoch\": \"20200813\", \"request_num\": \"2207712\", \"obstype\": \"SPECTRUM\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", + "2023-08-11 10:43:24.442 INFO: calibrations: Applying master calibration | {\"filename\": \"coj2m002-en12-20200813-0014-e00.fits.fz\", \"site\": \"coj\", \"instrument\": \"floyds02\", \"epoch\": \"20200813\", \"request_num\": \"2207712\", \"obstype\": \"SPECTRUM\", \"filter\": \"air\", \"master_calibration\": \"coj2m002-en12-20220313-0002-f91.fits.fz\", \"processName\": \"MainProcess\"}\n", + "2023-08-11 10:43:24.445 INFO: stages: Running banzai.stages.OrderTweaker | {\"filename\": \"coj2m002-en12-20200813-0014-e00.fits.fz\", \"site\": \"coj\", \"instrument\": \"floyds02\", \"epoch\": \"20200813\", \"request_num\": \"2207712\", \"obstype\": \"SPECTRUM\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", + "2023-08-11 10:43:25.103 INFO: stages: Running banzai.stages.WavelengthSolutionLoader | {\"filename\": \"coj2m002-en12-20200813-0014-e00.fits.fz\", \"site\": \"coj\", \"instrument\": \"floyds02\", \"epoch\": \"20200813\", \"request_num\": \"2207712\", \"obstype\": \"SPECTRUM\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", + "2023-08-11 10:43:25.248 INFO: calibrations: Applying master calibration | {\"filename\": \"coj2m002-en12-20200813-0014-e00.fits.fz\", \"site\": \"coj\", \"instrument\": \"floyds02\", \"epoch\": \"20200813\", \"request_num\": \"2207712\", \"obstype\": \"SPECTRUM\", \"filter\": \"air\", \"master_calibration\": \"coj2m002-en12-20200813-0015-a91.fits.fz\", \"processName\": \"MainProcess\"}\n", + "2023-08-11 10:43:25.253 INFO: stages: Running banzai.stages.FringeCorrector | {\"filename\": \"coj2m002-en12-20200813-0014-e00.fits.fz\", \"site\": \"coj\", \"instrument\": \"floyds02\", \"epoch\": \"20200813\", \"request_num\": \"2207712\", \"obstype\": \"SPECTRUM\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", + "2023-08-11 10:43:25.257 ERROR: stages: ['Traceback (most recent call last):\\n', ' File \"/Users/cmccully/miniconda3/envs/banzai-floyds/lib/python3.10/site-packages/banzai/stages.py\", line 52, in run\\n processed_image = self.do_stage(image_set)\\n', ' File \"/Users/cmccully/miniconda3/envs/banzai-floyds/lib/python3.10/site-packages/banzai_floyds/fringe.py\", line 94, in do_stage\\n fringe_spline = fit_smooth_fringe_spline(image.fringe, image.fringe > 0)\\n', \"TypeError: '>' not supported between instances of 'NoneType' and 'int'\\n\"] | {\"processName\": \"MainProcess\"}\n", + "2023-08-11 10:43:25.260 ERROR: stage_utils: Reduction stopped | {\"filename\": [{\"id\": 33851447, \"basename\": \"coj2m002-en12-20200813-0014-e00\", \"observation_date\": \"2020-08-13T13:48:59.623000Z\", \"observation_day\": \"2020-08-13\", \"proposal_id\": \"KEY2020B-002\", \"instrument_id\": \"en12\", \"target_name\": \"SN2020llx\", \"reduction_level\": 0, \"site_id\": \"coj\", \"telescope_id\": \"2m0a\", \"exposure_time\": 2700.042, \"primary_optical_element\": \"air\", \"public_date\": \"2021-08-13T13:48:59.623000Z\", \"configuration_type\": \"SPECTRUM\", \"observation_id\": 125183323, \"request_id\": 2207712, \"version_set\": [{\"id\": 35499843, \"key\": \"2tOvIFVDEcYVqCqCS12rJ_rH_lWcO8u5\", \"md5\": \"2dbc83ebc951cc5b23e320012ad10381\", \"extension\": \".fits.fz\", \"url\": \"https://archive-lco-global.s3.amazonaws.com/coj/en12/20200813/raw/coj2m002-en12-20200813-0014-e00.fits.fz?versionId=2tOvIFVDEcYVqCqCS12rJ_rH_lWcO8u5&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIA6FT4CXR464A32PW2%2F20230811%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20230811T144322Z&X-Amz-Expires=172800&X-Amz-SignedHeaders=host&X-Amz-Signature=bb9b108162b90f77ff893b0f4aeffd7f486d0b9a640420fdda0a56ee7dfe77b7\", \"created\": \"2020-08-13T14:34:26.463915Z\"}], \"url\": \"https://archive-lco-global.s3.amazonaws.com/coj/en12/20200813/raw/coj2m002-en12-20200813-0014-e00.fits.fz?versionId=2tOvIFVDEcYVqCqCS12rJ_rH_lWcO8u5&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIA6FT4CXR464A32PW2%2F20230811%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20230811T144322Z&X-Amz-Expires=172800&X-Amz-SignedHeaders=host&X-Amz-Signature=bb9b108162b90f77ff893b0f4aeffd7f486d0b9a640420fdda0a56ee7dfe77b7\", \"filename\": \"coj2m002-en12-20200813-0014-e00.fits.fz\", \"DATE_OBS\": \"2020-08-13T13:48:59.623000Z\", \"DAY_OBS\": \"2020-08-13\", \"PROPID\": \"KEY2020B-002\", \"INSTRUME\": \"en12\", \"OBJECT\": \"SN2020llx\", \"RLEVEL\": 0, \"SITEID\": \"coj\", \"TELID\": \"2m0a\", \"EXPTIME\": 2700.042, \"FILTER\": \"air\", \"L1PUBDAT\": \"2021-08-13T13:48:59.623000Z\", \"OBSTYPE\": \"SPECTRUM\", \"BLKUID\": 125183323, \"REQNUM\": 2207712, \"area\": {\"type\": \"Polygon\", \"coordinates\": [[[-31.907329928415663, -55.50729263395645], [-31.98944606986987, -55.51845324585246], [-31.909090983448834, -55.70753127607035], [-31.826601085701895, -55.696316605678504], [-31.907329928415663, -55.50729263395645]]]}, \"related_frames\": [33860652], \"frameid\": 33851447}], \"processName\": \"MainProcess\"}\n", + "2023-08-11 10:43:25.883 INFO: fits_utils: Downloading file coj2m002-en12-20200813-0026-e00.fits.fz from archive. ID: 33852323. | {\"filename\": \"coj2m002-en12-20200813-0026-e00.fits.fz\", \"attempt_number\": 1, \"processName\": \"MainProcess\"}\n", + "2023-08-11 10:43:27.727 INFO: stages: Running banzai.stages.OverscanSubtractor | {\"filename\": \"coj2m002-en12-20200813-0026-e00.fits.fz\", \"site\": \"coj\", \"instrument\": \"floyds02\", \"epoch\": \"20200813\", \"request_num\": \"2207853\", \"obstype\": \"SPECTRUM\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", + "2023-08-11 10:43:27.731 INFO: stages: Running banzai.stages.Trimmer | {\"filename\": \"coj2m002-en12-20200813-0026-e00.fits.fz\", \"site\": \"coj\", \"instrument\": \"floyds02\", \"epoch\": \"20200813\", \"request_num\": \"2207853\", \"obstype\": \"SPECTRUM\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", + "2023-08-11 10:43:27.732 INFO: trim: Trimming image | {\"filename\": \"coj2m002-en12-20200813-0026-e00.fits.fz\", \"site\": \"coj\", \"instrument\": \"floyds02\", \"epoch\": \"20200813\", \"request_num\": \"2207853\", \"obstype\": \"SPECTRUM\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", + "2023-08-11 10:43:27.796 INFO: stages: Running banzai.stages.GainNormalizer | {\"filename\": \"coj2m002-en12-20200813-0026-e00.fits.fz\", \"site\": \"coj\", \"instrument\": \"floyds02\", \"epoch\": \"20200813\", \"request_num\": \"2207853\", \"obstype\": \"SPECTRUM\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", + "2023-08-11 10:43:27.797 INFO: gain: Multiplying by gain | {\"filename\": \"coj2m002-en12-20200813-0026-e00.fits.fz\", \"site\": \"coj\", \"instrument\": \"floyds02\", \"epoch\": \"20200813\", \"request_num\": \"2207853\", \"obstype\": \"SPECTRUM\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", + "2023-08-11 10:43:27.801 INFO: stages: Running banzai.stages.PoissonInitializer | {\"filename\": \"coj2m002-en12-20200813-0026-e00.fits.fz\", \"site\": \"coj\", \"instrument\": \"floyds02\", \"epoch\": \"20200813\", \"request_num\": \"2207853\", \"obstype\": \"SPECTRUM\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", + "2023-08-11 10:43:27.814 INFO: stages: Running banzai.stages.OrderLoader | {\"filename\": \"coj2m002-en12-20200813-0026-e00.fits.fz\", \"site\": \"coj\", \"instrument\": \"floyds02\", \"epoch\": \"20200813\", \"request_num\": \"2207853\", \"obstype\": \"SPECTRUM\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", + "2023-08-11 10:43:27.921 INFO: calibrations: Applying master calibration | {\"filename\": \"coj2m002-en12-20200813-0026-e00.fits.fz\", \"site\": \"coj\", \"instrument\": \"floyds02\", \"epoch\": \"20200813\", \"request_num\": \"2207853\", \"obstype\": \"SPECTRUM\", \"filter\": \"air\", \"master_calibration\": \"coj2m002-en12-20220313-0002-f91.fits.fz\", \"processName\": \"MainProcess\"}\n", + "2023-08-11 10:43:27.926 INFO: stages: Running banzai.stages.OrderTweaker | {\"filename\": \"coj2m002-en12-20200813-0026-e00.fits.fz\", \"site\": \"coj\", \"instrument\": \"floyds02\", \"epoch\": \"20200813\", \"request_num\": \"2207853\", \"obstype\": \"SPECTRUM\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", + "2023-08-11 10:43:28.487 INFO: stages: Running banzai.stages.WavelengthSolutionLoader | {\"filename\": \"coj2m002-en12-20200813-0026-e00.fits.fz\", \"site\": \"coj\", \"instrument\": \"floyds02\", \"epoch\": \"20200813\", \"request_num\": \"2207853\", \"obstype\": \"SPECTRUM\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", + "2023-08-11 10:43:28.727 INFO: calibrations: Applying master calibration | {\"filename\": \"coj2m002-en12-20200813-0026-e00.fits.fz\", \"site\": \"coj\", \"instrument\": \"floyds02\", \"epoch\": \"20200813\", \"request_num\": \"2207853\", \"obstype\": \"SPECTRUM\", \"filter\": \"air\", \"master_calibration\": \"coj2m002-en12-20200813-0028-a91.fits.fz\", \"processName\": \"MainProcess\"}\n", + "2023-08-11 10:43:28.735 INFO: stages: Running banzai.stages.FringeCorrector | {\"filename\": \"coj2m002-en12-20200813-0026-e00.fits.fz\", \"site\": \"coj\", \"instrument\": \"floyds02\", \"epoch\": \"20200813\", \"request_num\": \"2207853\", \"obstype\": \"SPECTRUM\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", + "2023-08-11 10:43:28.736 ERROR: stages: ['Traceback (most recent call last):\\n', ' File \"/Users/cmccully/miniconda3/envs/banzai-floyds/lib/python3.10/site-packages/banzai/stages.py\", line 52, in run\\n processed_image = self.do_stage(image_set)\\n', ' File \"/Users/cmccully/miniconda3/envs/banzai-floyds/lib/python3.10/site-packages/banzai_floyds/fringe.py\", line 94, in do_stage\\n fringe_spline = fit_smooth_fringe_spline(image.fringe, image.fringe > 0)\\n', \"TypeError: '>' not supported between instances of 'NoneType' and 'int'\\n\"] | {\"processName\": \"MainProcess\"}\n", + "2023-08-11 10:43:28.741 ERROR: stage_utils: Reduction stopped | {\"filename\": [{\"id\": 33852323, \"basename\": \"coj2m002-en12-20200813-0026-e00\", \"observation_date\": \"2020-08-13T15:30:17.028000Z\", \"observation_day\": \"2020-08-13\", \"proposal_id\": \"COJ_calib\", \"instrument_id\": \"en12\", \"target_name\": \"FEIGE110\", \"reduction_level\": 0, \"site_id\": \"coj\", \"telescope_id\": \"2m0a\", \"exposure_time\": 300.057, \"primary_optical_element\": \"air\", \"public_date\": \"2020-08-13T15:30:17.028000Z\", \"configuration_type\": \"SPECTRUM\", \"observation_id\": 125217141, \"request_id\": 2207853, \"version_set\": [{\"id\": 35500719, \"key\": \"U_E3364414oi4kL58gzYogBe1td55jP5\", \"md5\": \"b6a1cd29d3eef313518688a7ef1edcd7\", \"extension\": \".fits.fz\", \"url\": \"https://archive-lco-global.s3.amazonaws.com/coj/en12/20200813/raw/coj2m002-en12-20200813-0026-e00.fits.fz?versionId=U_E3364414oi4kL58gzYogBe1td55jP5&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIA6FT4CXR464A32PW2%2F20230811%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20230811T144325Z&X-Amz-Expires=172800&X-Amz-SignedHeaders=host&X-Amz-Signature=979fc680846865825bd7297ec3584a9b1e8cc54f4b0631c077bd609ddaaf6231\", \"created\": \"2020-08-13T15:35:43.616569Z\"}], \"url\": \"https://archive-lco-global.s3.amazonaws.com/coj/en12/20200813/raw/coj2m002-en12-20200813-0026-e00.fits.fz?versionId=U_E3364414oi4kL58gzYogBe1td55jP5&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIA6FT4CXR464A32PW2%2F20230811%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20230811T144325Z&X-Amz-Expires=172800&X-Amz-SignedHeaders=host&X-Amz-Signature=979fc680846865825bd7297ec3584a9b1e8cc54f4b0631c077bd609ddaaf6231\", \"filename\": \"coj2m002-en12-20200813-0026-e00.fits.fz\", \"DATE_OBS\": \"2020-08-13T15:30:17.028000Z\", \"DAY_OBS\": \"2020-08-13\", \"PROPID\": \"COJ_calib\", \"INSTRUME\": \"en12\", \"OBJECT\": \"FEIGE110\", \"RLEVEL\": 0, \"SITEID\": \"coj\", \"TELID\": \"2m0a\", \"EXPTIME\": 300.057, \"FILTER\": \"air\", \"L1PUBDAT\": \"2020-08-13T15:30:17.028000Z\", \"OBSTYPE\": \"SPECTRUM\", \"BLKUID\": 125217141, \"REQNUM\": 2207853, \"area\": {\"type\": \"Polygon\", \"coordinates\": [[[-10.054610170738442, -5.203641989198259], [-10.008739010819625, -5.217802111177702], [-9.950940499795593, -5.032031245360507], [-9.996799693671221, -5.017875344333659], [-10.054610170738442, -5.203641989198259]]]}, \"related_frames\": [33860645], \"frameid\": 33852323}], \"processName\": \"MainProcess\"}\n", + "2023-08-11 10:43:29.403 INFO: fits_utils: Downloading file ogg2m001-en06-20200822-0007-e00.fits.fz from archive. ID: 33989111. | {\"filename\": \"ogg2m001-en06-20200822-0007-e00.fits.fz\", \"attempt_number\": 1, \"processName\": \"MainProcess\"}\n", + "2023-08-11 10:43:31.264 INFO: stages: Running banzai.stages.OverscanSubtractor | {\"filename\": \"ogg2m001-en06-20200822-0007-e00.fits.fz\", \"site\": \"ogg\", \"instrument\": \"floyds01\", \"epoch\": \"20200822\", \"request_num\": \"2217414\", \"obstype\": \"SPECTRUM\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", + "2023-08-11 10:43:31.267 INFO: stages: Running banzai.stages.Trimmer | {\"filename\": \"ogg2m001-en06-20200822-0007-e00.fits.fz\", \"site\": \"ogg\", \"instrument\": \"floyds01\", \"epoch\": \"20200822\", \"request_num\": \"2217414\", \"obstype\": \"SPECTRUM\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", + "2023-08-11 10:43:31.267 INFO: trim: Trimming image | {\"filename\": \"ogg2m001-en06-20200822-0007-e00.fits.fz\", \"site\": \"ogg\", \"instrument\": \"floyds01\", \"epoch\": \"20200822\", \"request_num\": \"2217414\", \"obstype\": \"SPECTRUM\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", + "2023-08-11 10:43:31.332 INFO: stages: Running banzai.stages.GainNormalizer | {\"filename\": \"ogg2m001-en06-20200822-0007-e00.fits.fz\", \"site\": \"ogg\", \"instrument\": \"floyds01\", \"epoch\": \"20200822\", \"request_num\": \"2217414\", \"obstype\": \"SPECTRUM\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", + "2023-08-11 10:43:31.332 INFO: gain: Multiplying by gain | {\"filename\": \"ogg2m001-en06-20200822-0007-e00.fits.fz\", \"site\": \"ogg\", \"instrument\": \"floyds01\", \"epoch\": \"20200822\", \"request_num\": \"2217414\", \"obstype\": \"SPECTRUM\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", + "2023-08-11 10:43:31.334 INFO: stages: Running banzai.stages.PoissonInitializer | {\"filename\": \"ogg2m001-en06-20200822-0007-e00.fits.fz\", \"site\": \"ogg\", \"instrument\": \"floyds01\", \"epoch\": \"20200822\", \"request_num\": \"2217414\", \"obstype\": \"SPECTRUM\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", + "2023-08-11 10:43:31.341 INFO: stages: Running banzai.stages.OrderLoader | {\"filename\": \"ogg2m001-en06-20200822-0007-e00.fits.fz\", \"site\": \"ogg\", \"instrument\": \"floyds01\", \"epoch\": \"20200822\", \"request_num\": \"2217414\", \"obstype\": \"SPECTRUM\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", + "2023-08-11 10:43:31.455 INFO: calibrations: Applying master calibration | {\"filename\": \"ogg2m001-en06-20200822-0007-e00.fits.fz\", \"site\": \"ogg\", \"instrument\": \"floyds01\", \"epoch\": \"20200822\", \"request_num\": \"2217414\", \"obstype\": \"SPECTRUM\", \"filter\": \"air\", \"master_calibration\": \"ogg2m001-en06-20190329-0018-f91.fits.fz\", \"processName\": \"MainProcess\"}\n", + "2023-08-11 10:43:31.458 INFO: stages: Running banzai.stages.OrderTweaker | {\"filename\": \"ogg2m001-en06-20200822-0007-e00.fits.fz\", \"site\": \"ogg\", \"instrument\": \"floyds01\", \"epoch\": \"20200822\", \"request_num\": \"2217414\", \"obstype\": \"SPECTRUM\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", + "2023-08-11 10:43:31.575 INFO: stages: Running banzai.stages.WavelengthSolutionLoader | {\"filename\": \"ogg2m001-en06-20200822-0007-e00.fits.fz\", \"site\": \"ogg\", \"instrument\": \"floyds01\", \"epoch\": \"20200822\", \"request_num\": \"2217414\", \"obstype\": \"SPECTRUM\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", + "2023-08-11 10:43:31.725 INFO: calibrations: Applying master calibration | {\"filename\": \"ogg2m001-en06-20200822-0007-e00.fits.fz\", \"site\": \"ogg\", \"instrument\": \"floyds01\", \"epoch\": \"20200822\", \"request_num\": \"2217414\", \"obstype\": \"SPECTRUM\", \"filter\": \"air\", \"master_calibration\": \"ogg2m001-en06-20200822-0009-a91.fits.fz\", \"processName\": \"MainProcess\"}\n", + "2023-08-11 10:43:31.729 INFO: stages: Running banzai.stages.FringeCorrector | {\"filename\": \"ogg2m001-en06-20200822-0007-e00.fits.fz\", \"site\": \"ogg\", \"instrument\": \"floyds01\", \"epoch\": \"20200822\", \"request_num\": \"2217414\", \"obstype\": \"SPECTRUM\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", + "2023-08-11 10:43:31.730 ERROR: stages: ['Traceback (most recent call last):\\n', ' File \"/Users/cmccully/miniconda3/envs/banzai-floyds/lib/python3.10/site-packages/banzai/stages.py\", line 52, in run\\n processed_image = self.do_stage(image_set)\\n', ' File \"/Users/cmccully/miniconda3/envs/banzai-floyds/lib/python3.10/site-packages/banzai_floyds/fringe.py\", line 94, in do_stage\\n fringe_spline = fit_smooth_fringe_spline(image.fringe, image.fringe > 0)\\n', \"TypeError: '>' not supported between instances of 'NoneType' and 'int'\\n\"] | {\"processName\": \"MainProcess\"}\n", + "2023-08-11 10:43:31.732 ERROR: stage_utils: Reduction stopped | {\"filename\": [{\"id\": 33989111, \"basename\": \"ogg2m001-en06-20200822-0007-e00\", \"observation_date\": \"2020-08-23T07:47:30.834000Z\", \"observation_day\": \"2020-08-22\", \"proposal_id\": \"OGG_calib\", \"instrument_id\": \"en06\", \"target_name\": \"BD+284211\", \"reduction_level\": 0, \"site_id\": \"ogg\", \"telescope_id\": \"2m0a\", \"exposure_time\": 300.092, \"primary_optical_element\": \"air\", \"public_date\": \"2020-08-23T07:47:30.834000Z\", \"configuration_type\": \"SPECTRUM\", \"observation_id\": 128781982, \"request_id\": 2217414, \"version_set\": [{\"id\": 35637555, \"key\": \"tHM9lKaFWZHfU6IsncajREh58Q3rx1SY\", \"md5\": \"04c0b01d4782228f010318da2e904c20\", \"extension\": \".fits.fz\", \"url\": \"https://archive-lco-global.s3.amazonaws.com/ogg/en06/20200822/raw/ogg2m001-en06-20200822-0007-e00.fits.fz?versionId=tHM9lKaFWZHfU6IsncajREh58Q3rx1SY&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIA6FT4CXR464A32PW2%2F20230811%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20230811T144329Z&X-Amz-Expires=172800&X-Amz-SignedHeaders=host&X-Amz-Signature=680c56b617efdaa353df365bf2510358e5de63ef5775bf1ba1e89513d4bdca49\", \"created\": \"2020-08-23T07:52:56.003866Z\"}], \"url\": \"https://archive-lco-global.s3.amazonaws.com/ogg/en06/20200822/raw/ogg2m001-en06-20200822-0007-e00.fits.fz?versionId=tHM9lKaFWZHfU6IsncajREh58Q3rx1SY&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIA6FT4CXR464A32PW2%2F20230811%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20230811T144329Z&X-Amz-Expires=172800&X-Amz-SignedHeaders=host&X-Amz-Signature=680c56b617efdaa353df365bf2510358e5de63ef5775bf1ba1e89513d4bdca49\", \"filename\": \"ogg2m001-en06-20200822-0007-e00.fits.fz\", \"DATE_OBS\": \"2020-08-23T07:47:30.834000Z\", \"DAY_OBS\": \"2020-08-22\", \"PROPID\": \"OGG_calib\", \"INSTRUME\": \"en06\", \"OBJECT\": \"BD+284211\", \"RLEVEL\": 0, \"SITEID\": \"ogg\", \"TELID\": \"2m0a\", \"EXPTIME\": 300.092, \"FILTER\": \"air\", \"L1PUBDAT\": \"2020-08-23T07:47:30.834000Z\", \"OBSTYPE\": \"SPECTRUM\", \"BLKUID\": 128781982, \"REQNUM\": 2217414, \"area\": {\"type\": \"Polygon\", \"coordinates\": [[[-32.25670055123709, 28.89368781099744], [-32.25716860741153, 28.84585784844757], [-32.03511758625558, 28.844009520860254], [-32.034547367193966, 28.891838665841313], [-32.25670055123709, 28.89368781099744]]]}, \"related_frames\": [34004078], \"frameid\": 33989111}], \"processName\": \"MainProcess\"}\n", + "2023-08-11 10:43:32.365 INFO: fits_utils: Downloading file ogg2m001-en06-20200822-0027-e00.fits.fz from archive. ID: 33994991. | {\"filename\": \"ogg2m001-en06-20200822-0027-e00.fits.fz\", \"attempt_number\": 1, \"processName\": \"MainProcess\"}\n", + "2023-08-11 10:43:34.275 INFO: stages: Running banzai.stages.OverscanSubtractor | {\"filename\": \"ogg2m001-en06-20200822-0027-e00.fits.fz\", \"site\": \"ogg\", \"instrument\": \"floyds01\", \"epoch\": \"20200822\", \"request_num\": \"2217762\", \"obstype\": \"SPECTRUM\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", + "2023-08-11 10:43:34.279 INFO: stages: Running banzai.stages.Trimmer | {\"filename\": \"ogg2m001-en06-20200822-0027-e00.fits.fz\", \"site\": \"ogg\", \"instrument\": \"floyds01\", \"epoch\": \"20200822\", \"request_num\": \"2217762\", \"obstype\": \"SPECTRUM\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", + "2023-08-11 10:43:34.279 INFO: trim: Trimming image | {\"filename\": \"ogg2m001-en06-20200822-0027-e00.fits.fz\", \"site\": \"ogg\", \"instrument\": \"floyds01\", \"epoch\": \"20200822\", \"request_num\": \"2217762\", \"obstype\": \"SPECTRUM\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", + "2023-08-11 10:43:34.349 INFO: stages: Running banzai.stages.GainNormalizer | {\"filename\": \"ogg2m001-en06-20200822-0027-e00.fits.fz\", \"site\": \"ogg\", \"instrument\": \"floyds01\", \"epoch\": \"20200822\", \"request_num\": \"2217762\", \"obstype\": \"SPECTRUM\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", + "2023-08-11 10:43:34.349 INFO: gain: Multiplying by gain | {\"filename\": \"ogg2m001-en06-20200822-0027-e00.fits.fz\", \"site\": \"ogg\", \"instrument\": \"floyds01\", \"epoch\": \"20200822\", \"request_num\": \"2217762\", \"obstype\": \"SPECTRUM\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", + "2023-08-11 10:43:34.351 INFO: stages: Running banzai.stages.PoissonInitializer | {\"filename\": \"ogg2m001-en06-20200822-0027-e00.fits.fz\", \"site\": \"ogg\", \"instrument\": \"floyds01\", \"epoch\": \"20200822\", \"request_num\": \"2217762\", \"obstype\": \"SPECTRUM\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", + "2023-08-11 10:43:34.357 INFO: stages: Running banzai.stages.OrderLoader | {\"filename\": \"ogg2m001-en06-20200822-0027-e00.fits.fz\", \"site\": \"ogg\", \"instrument\": \"floyds01\", \"epoch\": \"20200822\", \"request_num\": \"2217762\", \"obstype\": \"SPECTRUM\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", + "2023-08-11 10:43:34.465 INFO: calibrations: Applying master calibration | {\"filename\": \"ogg2m001-en06-20200822-0027-e00.fits.fz\", \"site\": \"ogg\", \"instrument\": \"floyds01\", \"epoch\": \"20200822\", \"request_num\": \"2217762\", \"obstype\": \"SPECTRUM\", \"filter\": \"air\", \"master_calibration\": \"ogg2m001-en06-20190329-0018-f91.fits.fz\", \"processName\": \"MainProcess\"}\n", + "2023-08-11 10:43:34.468 INFO: stages: Running banzai.stages.OrderTweaker | {\"filename\": \"ogg2m001-en06-20200822-0027-e00.fits.fz\", \"site\": \"ogg\", \"instrument\": \"floyds01\", \"epoch\": \"20200822\", \"request_num\": \"2217762\", \"obstype\": \"SPECTRUM\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", + "2023-08-11 10:43:34.820 INFO: stages: Running banzai.stages.WavelengthSolutionLoader | {\"filename\": \"ogg2m001-en06-20200822-0027-e00.fits.fz\", \"site\": \"ogg\", \"instrument\": \"floyds01\", \"epoch\": \"20200822\", \"request_num\": \"2217762\", \"obstype\": \"SPECTRUM\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", + "2023-08-11 10:43:34.969 INFO: calibrations: Applying master calibration | {\"filename\": \"ogg2m001-en06-20200822-0027-e00.fits.fz\", \"site\": \"ogg\", \"instrument\": \"floyds01\", \"epoch\": \"20200822\", \"request_num\": \"2217762\", \"obstype\": \"SPECTRUM\", \"filter\": \"air\", \"master_calibration\": \"ogg2m001-en06-20200822-0028-a91.fits.fz\", \"processName\": \"MainProcess\"}\n", + "2023-08-11 10:43:34.974 INFO: stages: Running banzai.stages.FringeCorrector | {\"filename\": \"ogg2m001-en06-20200822-0027-e00.fits.fz\", \"site\": \"ogg\", \"instrument\": \"floyds01\", \"epoch\": \"20200822\", \"request_num\": \"2217762\", \"obstype\": \"SPECTRUM\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", + "2023-08-11 10:43:34.974 ERROR: stages: ['Traceback (most recent call last):\\n', ' File \"/Users/cmccully/miniconda3/envs/banzai-floyds/lib/python3.10/site-packages/banzai/stages.py\", line 52, in run\\n processed_image = self.do_stage(image_set)\\n', ' File \"/Users/cmccully/miniconda3/envs/banzai-floyds/lib/python3.10/site-packages/banzai_floyds/fringe.py\", line 94, in do_stage\\n fringe_spline = fit_smooth_fringe_spline(image.fringe, image.fringe > 0)\\n', \"TypeError: '>' not supported between instances of 'NoneType' and 'int'\\n\"] | {\"processName\": \"MainProcess\"}\n", + "2023-08-11 10:43:34.977 ERROR: stage_utils: Reduction stopped | {\"filename\": [{\"id\": 33994991, \"basename\": \"ogg2m001-en06-20200822-0027-e00\", \"observation_date\": \"2020-08-23T11:58:34.949000Z\", \"observation_day\": \"2020-08-22\", \"proposal_id\": \"LCO2020B-003\", \"instrument_id\": \"en06\", \"target_name\": \"HD 201767\", \"reduction_level\": 0, \"site_id\": \"ogg\", \"telescope_id\": \"2m0a\", \"exposure_time\": 179.982, \"primary_optical_element\": \"air\", \"public_date\": \"2021-08-23T11:58:34.949000Z\", \"configuration_type\": \"SPECTRUM\", \"observation_id\": 128859340, \"request_id\": 2217762, \"version_set\": [{\"id\": 35643435, \"key\": \"NhRv8PqPHqOuG7I8TuEY9csQFIHV58YY\", \"md5\": \"c17ae87b6c8409bded46cf95f68eb6ff\", \"extension\": \".fits.fz\", \"url\": \"https://archive-lco-global.s3.amazonaws.com/ogg/en06/20200822/raw/ogg2m001-en06-20200822-0027-e00.fits.fz?versionId=NhRv8PqPHqOuG7I8TuEY9csQFIHV58YY&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIA6FT4CXR464A32PW2%2F20230811%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20230811T144332Z&X-Amz-Expires=172800&X-Amz-SignedHeaders=host&X-Amz-Signature=eb52fffcfb1011fa9c9ba252e59db978afaf2b689b80a2c11bcfd6c021bee6cd\", \"created\": \"2020-08-23T12:02:00.096001Z\"}], \"url\": \"https://archive-lco-global.s3.amazonaws.com/ogg/en06/20200822/raw/ogg2m001-en06-20200822-0027-e00.fits.fz?versionId=NhRv8PqPHqOuG7I8TuEY9csQFIHV58YY&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIA6FT4CXR464A32PW2%2F20230811%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20230811T144332Z&X-Amz-Expires=172800&X-Amz-SignedHeaders=host&X-Amz-Signature=eb52fffcfb1011fa9c9ba252e59db978afaf2b689b80a2c11bcfd6c021bee6cd\", \"filename\": \"ogg2m001-en06-20200822-0027-e00.fits.fz\", \"DATE_OBS\": \"2020-08-23T11:58:34.949000Z\", \"DAY_OBS\": \"2020-08-22\", \"PROPID\": \"LCO2020B-003\", \"INSTRUME\": \"en06\", \"OBJECT\": \"HD 201767\", \"RLEVEL\": 0, \"SITEID\": \"ogg\", \"TELID\": \"2m0a\", \"EXPTIME\": 179.982, \"FILTER\": \"air\", \"L1PUBDAT\": \"2021-08-23T11:58:34.949000Z\", \"OBSTYPE\": \"SPECTRUM\", \"BLKUID\": 128859340, \"REQNUM\": 2217762, \"area\": {\"type\": \"Polygon\", \"coordinates\": [[[-41.95558118295355, -11.510770454986625], [-41.98302358528434, -11.4712285118447], [-42.147169060224996, -11.580533642453789], [-42.11973905825192, -11.620091003845015], [-41.95558118295355, -11.510770454986625]]]}, \"related_frames\": [34004077], \"frameid\": 33994991}], \"processName\": \"MainProcess\"}\n" + ] + } + ], + "source": [ + "for row in test_data:\n", + " if 'e00.fits' in row['filename']:\n", + " archive_record = requests.get(f'{context.ARCHIVE_FRAME_URL}/{row[\"frameid\"]}').json()\n", + " archive_record['frameid'] = archive_record['id']\n", + " run_pipeline_stages([archive_record], context)" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "23cdadf2", + "metadata": {}, + "outputs": [ + { + "name": "stdin", + "output_type": "stream", + "text": [ + "ipdb> c\n" + ] + } + ], + "source": [ + "foo()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9cbde2a0-e966-4f85-afc1-a30aadd7b98c", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.9" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/setup.cfg b/setup.cfg index 4e3c5fc..e5146c6 100644 --- a/setup.cfg +++ b/setup.cfg @@ -18,7 +18,7 @@ python_requires = >=3.7 setup_requires = setuptools_scm install_requires = astropy >= 4.3 - banzai @ git+https://github.com/lcogt/banzai.git + banzai @ git+https://github.com/lcogt/banzai.git@fix/stage-grouping astroscrappy matplotlib From e1239a92feafc6861969f7de680cd04889148bc0 Mon Sep 17 00:00:00 2001 From: Curtis McCully Date: Mon, 30 Oct 2023 16:50:25 -0400 Subject: [PATCH 13/19] Fixes to pep style stuff --- Jenkinsfile | 38 ++++ banzai_floyds/extract.py | 6 +- banzai_floyds/flux.py | 7 + banzai_floyds/frames.py | 4 +- banzai_floyds/fringe.py | 4 +- banzai_floyds/settings.py | 8 +- banzai_floyds/tests/test_e2e.py | 26 +++ banzai_floyds/wavelengths.py | 2 +- docs/banzai_floyds/ExampleReduction.ipynb | 207 ++++++++++++++-------- setup.cfg | 2 +- 10 files changed, 218 insertions(+), 86 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 574447e..18f249c 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -136,6 +136,44 @@ pipeline { } } } + stage('Test-Arc-Frame-Creation') { + agent { + label 'helm' + } + environment { + // store stage start time in the environment so it has stage scope + START_TIME = sh(script: 'date +%s', returnStdout: true).trim() + } + when { + anyOf { + branch 'PR-*' + expression { return params.forceEndToEnd } + } + } + steps { + script { + withKubeConfig([credentialsId: "build-kube-config"]) { + sh("kubectl exec ${podName} -c banzai-floyds-e2e-listener -- " + + "pytest -s --durations=0 --junitxml=/home/archive/pytest-arc-frames.xml " + + "-m arc_frames /lco/banzai-floyds/") + } + } + } + post { + always { + script { + withKubeConfig([credentialsId: "build-kube-config"]) { + env.LOGS_SINCE = sh(script: 'expr `date +%s` - ${START_TIME}', returnStdout: true).trim() + sh("kubectl logs ${podName} --since=${LOGS_SINCE}s --all-containers") + sh("kubectl cp -c banzai-floyds-e2e-listener ${podName}:/home/archive/pytest-arc-frames.xml " + + "pytest-arc-frames.xml") + junit "pytest-arc-frames.xml" + } + } + } + } + } + stage('Test-Science-Frame-Creation') { agent { label 'helm' diff --git a/banzai_floyds/extract.py b/banzai_floyds/extract.py index f179dc2..f16aa66 100644 --- a/banzai_floyds/extract.py +++ b/banzai_floyds/extract.py @@ -106,7 +106,9 @@ def fit_profile_width(data, profile_fits, poly_order=3, background_poly_order=2, # Short circuit if the trace is not significantly brighter than the background in this bin if peak_snr < 2.0 * median_snr: continue - + + # TODO: Only fit the profile width where it is much larger than the background value, + # otherwise use a heuristic width # Pass a match filter (with correct s/n scaling) with a gaussian with a default width initial_coeffs = np.zeros(background_poly_order + 1) initial_coeffs[0] = np.median(data_to_fit['data']) / data_to_fit['data'][peak] @@ -140,6 +142,8 @@ def fit_background(data, profile_centers, profile_widths, x_poly_order=2, y_poly # Pass a match filter (with correct s/n scaling) with a gaussian with a default width initial_coeffs = np.zeros((x_poly_order + 1) + y_poly_order) initial_coeffs[0] = np.median(data_to_fit['data']) / data_to_fit['data'][peak] + # TODO: Fit the background with a totally fixed profile, and no need to iterate + # since our filter is linear best_fit_coeffs = optimize_match_filter(initial_coeffs, data_to_fit['data'], data_to_fit['uncertainty'], background_fixed_profile, diff --git a/banzai_floyds/flux.py b/banzai_floyds/flux.py index 343b9a2..8717e76 100644 --- a/banzai_floyds/flux.py +++ b/banzai_floyds/flux.py @@ -72,6 +72,13 @@ def apply_master_calibration(self, image, master_calibration_image): def calibration_type(self): return 'STANDARD' + def on_missing_master_calibration(self, image): + flux_standard = get_standard(image.ra, image.dec, self.runtime_context.db_address) + if flux_standard is not None: + return super().on_missing_master_calibration(image) + else: + return image + class FluxCalibrator(Stage): def do_stage(self, image): diff --git a/banzai_floyds/frames.py b/banzai_floyds/frames.py index 16faebf..19ceac1 100644 --- a/banzai_floyds/frames.py +++ b/banzai_floyds/frames.py @@ -9,7 +9,6 @@ import os from astropy.io import fits from banzai_floyds.utils.flux_utils import rescale_by_airmass -from banzai.dbs import get_session, Site class FLOYDSObservationFrame(LCOObservationFrame): @@ -28,10 +27,11 @@ def __init__(self, hdu_list: list, file_path: str, frame_id: int = None, hdu_ord def get_1d_and_2d_spectra_products(self, runtime_context): filename_1d = self.get_output_filename(runtime_context).replace('.fits', '-1d.fits') + self.meta.pop('EXTNAME') frame_1d = LCOObservationFrame([HeaderOnly(self.meta.copy()), self['EXTRACTED']], os.path.join(self.get_output_directory(runtime_context), filename_1d)) fits_1d = frame_1d.to_fits(runtime_context) - fits_1d['SPECTRUM1D'].name = 'SPECTRUM' + fits_1d['EXTRACTED'].name = 'SPECTRUM' # TODO: Save telluric and sensitivity corrections that were applied filename_2d = filename_1d.replace('-1d.fits', '-2d.fits') diff --git a/banzai_floyds/fringe.py b/banzai_floyds/fringe.py index 84069c5..8b96f44 100644 --- a/banzai_floyds/fringe.py +++ b/banzai_floyds/fringe.py @@ -82,10 +82,10 @@ def make_master_calibration_frame(self, images): grouping = self.runtime_context.CALIBRATION_SET_CRITERIA.get(images[0].obstype, []) master_frame_class = import_utils.import_attribute(self.runtime_context.CALIBRATION_FRAME_CLASS) hdu_order = self.runtime_context.MASTER_CALIBRATION_EXTENSION_ORDER.get(self.calibration_type) - super_frame = master_frame_class.init_master_frame(images, master_calibration_filename, grouping_criteria=grouping, hdu_order=hdu_order) super_frame.primary_hdu.data[:, :] = super_fringe[:, :] + super_frame.primary_hdu.name = 'FRINGE' return super_frame @@ -112,7 +112,7 @@ def on_missing_master_calibration(self, image): @property def calibration_type(self): - return 'FRINGE' + return 'LAMPFLAT' def apply_master_calibration(self, image, master_calibration_image): image.fringe = master_calibration_image.fringe diff --git a/banzai_floyds/settings.py b/banzai_floyds/settings.py index 7eb08c7..84633f4 100644 --- a/banzai_floyds/settings.py +++ b/banzai_floyds/settings.py @@ -8,8 +8,12 @@ 'banzai_floyds.orders.OrderLoader', 'banzai_floyds.orders.OrderTweaker', 'banzai_floyds.wavelengths.WavelengthSolutionLoader', + 'banzai_floyds.fringe.FringeLoader', 'banzai_floyds.fringe.FringeCorrector', - 'banzai_floyds.extract.Extractor' + 'banzai_floyds.extract.Extractor', + 'banzai_floyds.flux.StandardLoader' + 'banzai_floyds.flux.FluxSensitivity', + 'banzai_floyds.flux.FluxCalibrator' ] FRAME_SELECTION_CRITERIA = [('type', 'contains', 'FLOYDS')] @@ -18,7 +22,7 @@ LAST_STAGE = { 'SPECTRUM': None, - 'LAMPFLAT': 'banzai_floyds.wavelengths.WavelengthSolutionLoader', + 'LAMPFLAT': 'banzai_floyds.fringe.FringeLoader', 'ARC': 'banzai_floyds.orders.OrderTweaker', 'SKYFLAT': 'banzai.uncertainty.PoissonInitializer' } diff --git a/banzai_floyds/tests/test_e2e.py b/banzai_floyds/tests/test_e2e.py index 7a0ed02..cd13e40 100644 --- a/banzai_floyds/tests/test_e2e.py +++ b/banzai_floyds/tests/test_e2e.py @@ -192,6 +192,32 @@ def test_if_fringe_frames_were_created(self): assert len(calibrations_in_db) == 2 +@pytest.mark.e2e +@pytest.mark.standards +class TestStandardFileCreation: + @pytest.fixture(autouse=True) + def process_standards(self): + logger.info('Reducing individual frames') + + exchange = Exchange(os.getenv('FITS_EXCHANGE', 'fits_files'), type='fanout') + test_data = ascii.read(DATA_FILELIST) + with Connection(os.getenv('FITS_BROKER')) as conn: + producer = conn.Producer(exchange=exchange) + for row in test_data: + archive_record = requests.get(f'{os.getenv("API_ROOT")}frames/{row["frameid"]}').json() + archive_record['frameid'] = archive_record['id'] + producer.publish(archive_record) + producer.release() + + celery_join() + logger.info('Finished reducing individual frames') + + def test_if_standards_were_created(self): + test_data = ascii.read(DATA_FILELIST) + for expected_file in expected_filenames(test_data): + assert os.path.exists(expected_file) + + @pytest.mark.e2e @pytest.mark.science_frames class TestScienceFileCreation: diff --git a/banzai_floyds/wavelengths.py b/banzai_floyds/wavelengths.py index 8155669..cc99c14 100644 --- a/banzai_floyds/wavelengths.py +++ b/banzai_floyds/wavelengths.py @@ -248,7 +248,7 @@ class CalibrateWavelengths(Stage): LINES = arc_lines_table() # All in angstroms, measured by Curtis McCully # FWHM is , 5 pixels - INITIAL_LINE_WIDTHS = {1: 15.6, 2: 8.6} + INITIAL_LINE_WIDTHS = {1: 10, 2: 6} INITIAL_DISPERSIONS = {1: 3.51, 2: 1.72} # Tilts in degrees measured counterclockwise (right-handed coordinates) INITIAL_LINE_TILTS = {1: 8., 2: 8.} diff --git a/docs/banzai_floyds/ExampleReduction.ipynb b/docs/banzai_floyds/ExampleReduction.ipynb index 20ee758..cdb83fc 100644 --- a/docs/banzai_floyds/ExampleReduction.ipynb +++ b/docs/banzai_floyds/ExampleReduction.ipynb @@ -21,11 +21,11 @@ "name": "stderr", "output_type": "stream", "text": [ - "2023-08-11 10:41:28.370 WARNING: warnings: /Users/cmccully/miniconda3/envs/banzai-floyds/lib/python3.10/site-packages/requests/__init__.py:102: RequestsDependencyWarning: urllib3 (1.26.14) or chardet (5.1.0)/charset_normalizer (2.0.4) doesn't match a supported version!\n", + "2023-09-07 16:29:54.713 WARNING: warnings: /Users/cmccully/miniconda3/envs/banzai-floyds/lib/python3.10/site-packages/requests/__init__.py:102: RequestsDependencyWarning: urllib3 (1.26.14) or chardet (5.1.0)/charset_normalizer (2.0.4) doesn't match a supported version!\n", " warnings.warn(\"urllib3 ({}) or chardet ({})/charset_normalizer ({}) doesn't match a supported \"\n", "\n", - "2023-08-11 10:41:28.919 INFO: utils: Note: NumExpr detected 10 cores but \"NUMEXPR_MAX_THREADS\" not set, so enforcing safe limit of 8.\n", - "2023-08-11 10:41:28.920 INFO: utils: NumExpr defaulting to 8 threads.\n" + "2023-09-07 16:29:55.372 INFO: utils: Note: NumExpr detected 10 cores but \"NUMEXPR_MAX_THREADS\" not set, so enforcing safe limit of 8.\n", + "2023-09-07 16:29:55.373 INFO: utils: NumExpr defaulting to 8 threads.\n" ] } ], @@ -183,7 +183,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 5, "id": "404eae88-c53b-48f6-ae9c-4720bd7cbc38", "metadata": {}, "outputs": [], @@ -331,7 +331,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 7, "id": "93283289", "metadata": {}, "outputs": [ @@ -339,15 +339,15 @@ "name": "stderr", "output_type": "stream", "text": [ - "2023-08-11 10:41:33.342 INFO: calibrations: Making master frames | {\"type\": \"2m0-FLOYDS-SciCam\", \"site\": \"ogg\", \"camera\": \"en06\", \"obstype\": \"LAMPFLAT\", \"min_date\": \"2020-01-01\", \"max_date\": \"2021-01-01\", \"processName\": \"MainProcess\"}\n", - "2023-08-11 10:41:33.706 INFO: stages: Running banzai.stages.FringeMaker | {\"filename\": \"ogg2m001-en06-20200822-0008-w91.fits.fz\", \"site\": \"ogg\", \"instrument\": \"floyds01\", \"epoch\": \"20200822\", \"request_num\": \"2217414\", \"obstype\": \"LAMPFLAT\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", - "2023-08-11 10:41:40.038 WARNING: warnings: /Users/cmccully/miniconda3/envs/banzai-floyds/lib/python3.10/site-packages/astropy/io/fits/card.py:1008: VerifyWarning: Card is too long, comment will be truncated.\n", + "2023-09-05 11:31:12.412 INFO: calibrations: Making master frames | {\"type\": \"2m0-FLOYDS-SciCam\", \"site\": \"ogg\", \"camera\": \"en06\", \"obstype\": \"LAMPFLAT\", \"min_date\": \"2020-01-01\", \"max_date\": \"2021-01-01\", \"processName\": \"MainProcess\"}\n", + "2023-09-05 11:31:12.578 INFO: stages: Running banzai.stages.FringeMaker | {\"filename\": \"ogg2m001-en06-20200822-0008-w91.fits.fz\", \"site\": \"ogg\", \"instrument\": \"floyds01\", \"epoch\": \"20200822\", \"request_num\": \"2217414\", \"obstype\": \"LAMPFLAT\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", + "2023-09-05 11:31:17.945 WARNING: warnings: /Users/cmccully/miniconda3/envs/banzai-floyds/lib/python3.10/site-packages/astropy/io/fits/card.py:1008: VerifyWarning: Card is too long, comment will be truncated.\n", " warnings.warn('Card is too long, comment will be truncated.',\n", "\n", - "2023-08-11 10:41:40.161 INFO: calibrations: Finished | {\"processName\": \"MainProcess\"}\n", - "2023-08-11 10:41:40.161 INFO: calibrations: Making master frames | {\"type\": \"2m0-FLOYDS-SciCam\", \"site\": \"coj\", \"camera\": \"en12\", \"obstype\": \"LAMPFLAT\", \"min_date\": \"2020-01-01\", \"max_date\": \"2021-01-01\", \"processName\": \"MainProcess\"}\n", - "2023-08-11 10:41:40.360 INFO: stages: Running banzai.stages.FringeMaker | {\"filename\": \"coj2m002-en12-20200813-0016-w91.fits.fz\", \"site\": \"coj\", \"instrument\": \"floyds02\", \"epoch\": \"20200813\", \"request_num\": \"2207712\", \"obstype\": \"LAMPFLAT\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", - "2023-08-11 10:41:47.990 INFO: calibrations: Finished | {\"processName\": \"MainProcess\"}\n" + "2023-09-05 11:31:18.125 INFO: calibrations: Finished | {\"processName\": \"MainProcess\"}\n", + "2023-09-05 11:31:18.126 INFO: calibrations: Making master frames | {\"type\": \"2m0-FLOYDS-SciCam\", \"site\": \"coj\", \"camera\": \"en12\", \"obstype\": \"LAMPFLAT\", \"min_date\": \"2020-01-01\", \"max_date\": \"2021-01-01\", \"processName\": \"MainProcess\"}\n", + "2023-09-05 11:31:18.266 INFO: stages: Running banzai.stages.FringeMaker | {\"filename\": \"coj2m002-en12-20200813-0016-w91.fits.fz\", \"site\": \"coj\", \"instrument\": \"floyds02\", \"epoch\": \"20200813\", \"request_num\": \"2207712\", \"obstype\": \"LAMPFLAT\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", + "2023-09-05 11:31:25.037 INFO: calibrations: Finished | {\"processName\": \"MainProcess\"}\n" ] } ], @@ -360,7 +360,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 6, "id": "dd129631", "metadata": {}, "outputs": [ @@ -368,66 +368,61 @@ "name": "stderr", "output_type": "stream", "text": [ - "2023-08-11 10:43:22.170 INFO: fits_utils: Downloading file coj2m002-en12-20200813-0014-e00.fits.fz from archive. ID: 33851447. | {\"filename\": \"coj2m002-en12-20200813-0014-e00.fits.fz\", \"attempt_number\": 1, \"processName\": \"MainProcess\"}\n", - "2023-08-11 10:43:24.241 INFO: stages: Running banzai.stages.OverscanSubtractor | {\"filename\": \"coj2m002-en12-20200813-0014-e00.fits.fz\", \"site\": \"coj\", \"instrument\": \"floyds02\", \"epoch\": \"20200813\", \"request_num\": \"2207712\", \"obstype\": \"SPECTRUM\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", - "2023-08-11 10:43:24.244 INFO: stages: Running banzai.stages.Trimmer | {\"filename\": \"coj2m002-en12-20200813-0014-e00.fits.fz\", \"site\": \"coj\", \"instrument\": \"floyds02\", \"epoch\": \"20200813\", \"request_num\": \"2207712\", \"obstype\": \"SPECTRUM\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", - "2023-08-11 10:43:24.245 INFO: trim: Trimming image | {\"filename\": \"coj2m002-en12-20200813-0014-e00.fits.fz\", \"site\": \"coj\", \"instrument\": \"floyds02\", \"epoch\": \"20200813\", \"request_num\": \"2207712\", \"obstype\": \"SPECTRUM\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", - "2023-08-11 10:43:24.313 INFO: stages: Running banzai.stages.GainNormalizer | {\"filename\": \"coj2m002-en12-20200813-0014-e00.fits.fz\", \"site\": \"coj\", \"instrument\": \"floyds02\", \"epoch\": \"20200813\", \"request_num\": \"2207712\", \"obstype\": \"SPECTRUM\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", - "2023-08-11 10:43:24.314 INFO: gain: Multiplying by gain | {\"filename\": \"coj2m002-en12-20200813-0014-e00.fits.fz\", \"site\": \"coj\", \"instrument\": \"floyds02\", \"epoch\": \"20200813\", \"request_num\": \"2207712\", \"obstype\": \"SPECTRUM\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", - "2023-08-11 10:43:24.317 INFO: stages: Running banzai.stages.PoissonInitializer | {\"filename\": \"coj2m002-en12-20200813-0014-e00.fits.fz\", \"site\": \"coj\", \"instrument\": \"floyds02\", \"epoch\": \"20200813\", \"request_num\": \"2207712\", \"obstype\": \"SPECTRUM\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", - "2023-08-11 10:43:24.326 INFO: stages: Running banzai.stages.OrderLoader | {\"filename\": \"coj2m002-en12-20200813-0014-e00.fits.fz\", \"site\": \"coj\", \"instrument\": \"floyds02\", \"epoch\": \"20200813\", \"request_num\": \"2207712\", \"obstype\": \"SPECTRUM\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", - "2023-08-11 10:43:24.442 INFO: calibrations: Applying master calibration | {\"filename\": \"coj2m002-en12-20200813-0014-e00.fits.fz\", \"site\": \"coj\", \"instrument\": \"floyds02\", \"epoch\": \"20200813\", \"request_num\": \"2207712\", \"obstype\": \"SPECTRUM\", \"filter\": \"air\", \"master_calibration\": \"coj2m002-en12-20220313-0002-f91.fits.fz\", \"processName\": \"MainProcess\"}\n", - "2023-08-11 10:43:24.445 INFO: stages: Running banzai.stages.OrderTweaker | {\"filename\": \"coj2m002-en12-20200813-0014-e00.fits.fz\", \"site\": \"coj\", \"instrument\": \"floyds02\", \"epoch\": \"20200813\", \"request_num\": \"2207712\", \"obstype\": \"SPECTRUM\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", - "2023-08-11 10:43:25.103 INFO: stages: Running banzai.stages.WavelengthSolutionLoader | {\"filename\": \"coj2m002-en12-20200813-0014-e00.fits.fz\", \"site\": \"coj\", \"instrument\": \"floyds02\", \"epoch\": \"20200813\", \"request_num\": \"2207712\", \"obstype\": \"SPECTRUM\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", - "2023-08-11 10:43:25.248 INFO: calibrations: Applying master calibration | {\"filename\": \"coj2m002-en12-20200813-0014-e00.fits.fz\", \"site\": \"coj\", \"instrument\": \"floyds02\", \"epoch\": \"20200813\", \"request_num\": \"2207712\", \"obstype\": \"SPECTRUM\", \"filter\": \"air\", \"master_calibration\": \"coj2m002-en12-20200813-0015-a91.fits.fz\", \"processName\": \"MainProcess\"}\n", - "2023-08-11 10:43:25.253 INFO: stages: Running banzai.stages.FringeCorrector | {\"filename\": \"coj2m002-en12-20200813-0014-e00.fits.fz\", \"site\": \"coj\", \"instrument\": \"floyds02\", \"epoch\": \"20200813\", \"request_num\": \"2207712\", \"obstype\": \"SPECTRUM\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", - "2023-08-11 10:43:25.257 ERROR: stages: ['Traceback (most recent call last):\\n', ' File \"/Users/cmccully/miniconda3/envs/banzai-floyds/lib/python3.10/site-packages/banzai/stages.py\", line 52, in run\\n processed_image = self.do_stage(image_set)\\n', ' File \"/Users/cmccully/miniconda3/envs/banzai-floyds/lib/python3.10/site-packages/banzai_floyds/fringe.py\", line 94, in do_stage\\n fringe_spline = fit_smooth_fringe_spline(image.fringe, image.fringe > 0)\\n', \"TypeError: '>' not supported between instances of 'NoneType' and 'int'\\n\"] | {\"processName\": \"MainProcess\"}\n", - "2023-08-11 10:43:25.260 ERROR: stage_utils: Reduction stopped | {\"filename\": [{\"id\": 33851447, \"basename\": \"coj2m002-en12-20200813-0014-e00\", \"observation_date\": \"2020-08-13T13:48:59.623000Z\", \"observation_day\": \"2020-08-13\", \"proposal_id\": \"KEY2020B-002\", \"instrument_id\": \"en12\", \"target_name\": \"SN2020llx\", \"reduction_level\": 0, \"site_id\": \"coj\", \"telescope_id\": \"2m0a\", \"exposure_time\": 2700.042, \"primary_optical_element\": \"air\", \"public_date\": \"2021-08-13T13:48:59.623000Z\", \"configuration_type\": \"SPECTRUM\", \"observation_id\": 125183323, \"request_id\": 2207712, \"version_set\": [{\"id\": 35499843, \"key\": \"2tOvIFVDEcYVqCqCS12rJ_rH_lWcO8u5\", \"md5\": \"2dbc83ebc951cc5b23e320012ad10381\", \"extension\": \".fits.fz\", \"url\": \"https://archive-lco-global.s3.amazonaws.com/coj/en12/20200813/raw/coj2m002-en12-20200813-0014-e00.fits.fz?versionId=2tOvIFVDEcYVqCqCS12rJ_rH_lWcO8u5&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIA6FT4CXR464A32PW2%2F20230811%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20230811T144322Z&X-Amz-Expires=172800&X-Amz-SignedHeaders=host&X-Amz-Signature=bb9b108162b90f77ff893b0f4aeffd7f486d0b9a640420fdda0a56ee7dfe77b7\", \"created\": \"2020-08-13T14:34:26.463915Z\"}], \"url\": \"https://archive-lco-global.s3.amazonaws.com/coj/en12/20200813/raw/coj2m002-en12-20200813-0014-e00.fits.fz?versionId=2tOvIFVDEcYVqCqCS12rJ_rH_lWcO8u5&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIA6FT4CXR464A32PW2%2F20230811%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20230811T144322Z&X-Amz-Expires=172800&X-Amz-SignedHeaders=host&X-Amz-Signature=bb9b108162b90f77ff893b0f4aeffd7f486d0b9a640420fdda0a56ee7dfe77b7\", \"filename\": \"coj2m002-en12-20200813-0014-e00.fits.fz\", \"DATE_OBS\": \"2020-08-13T13:48:59.623000Z\", \"DAY_OBS\": \"2020-08-13\", \"PROPID\": \"KEY2020B-002\", \"INSTRUME\": \"en12\", \"OBJECT\": \"SN2020llx\", \"RLEVEL\": 0, \"SITEID\": \"coj\", \"TELID\": \"2m0a\", \"EXPTIME\": 2700.042, \"FILTER\": \"air\", \"L1PUBDAT\": \"2021-08-13T13:48:59.623000Z\", \"OBSTYPE\": \"SPECTRUM\", \"BLKUID\": 125183323, \"REQNUM\": 2207712, \"area\": {\"type\": \"Polygon\", \"coordinates\": [[[-31.907329928415663, -55.50729263395645], [-31.98944606986987, -55.51845324585246], [-31.909090983448834, -55.70753127607035], [-31.826601085701895, -55.696316605678504], [-31.907329928415663, -55.50729263395645]]]}, \"related_frames\": [33860652], \"frameid\": 33851447}], \"processName\": \"MainProcess\"}\n", - "2023-08-11 10:43:25.883 INFO: fits_utils: Downloading file coj2m002-en12-20200813-0026-e00.fits.fz from archive. ID: 33852323. | {\"filename\": \"coj2m002-en12-20200813-0026-e00.fits.fz\", \"attempt_number\": 1, \"processName\": \"MainProcess\"}\n", - "2023-08-11 10:43:27.727 INFO: stages: Running banzai.stages.OverscanSubtractor | {\"filename\": \"coj2m002-en12-20200813-0026-e00.fits.fz\", \"site\": \"coj\", \"instrument\": \"floyds02\", \"epoch\": \"20200813\", \"request_num\": \"2207853\", \"obstype\": \"SPECTRUM\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", - "2023-08-11 10:43:27.731 INFO: stages: Running banzai.stages.Trimmer | {\"filename\": \"coj2m002-en12-20200813-0026-e00.fits.fz\", \"site\": \"coj\", \"instrument\": \"floyds02\", \"epoch\": \"20200813\", \"request_num\": \"2207853\", \"obstype\": \"SPECTRUM\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", - "2023-08-11 10:43:27.732 INFO: trim: Trimming image | {\"filename\": \"coj2m002-en12-20200813-0026-e00.fits.fz\", \"site\": \"coj\", \"instrument\": \"floyds02\", \"epoch\": \"20200813\", \"request_num\": \"2207853\", \"obstype\": \"SPECTRUM\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", - "2023-08-11 10:43:27.796 INFO: stages: Running banzai.stages.GainNormalizer | {\"filename\": \"coj2m002-en12-20200813-0026-e00.fits.fz\", \"site\": \"coj\", \"instrument\": \"floyds02\", \"epoch\": \"20200813\", \"request_num\": \"2207853\", \"obstype\": \"SPECTRUM\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", - "2023-08-11 10:43:27.797 INFO: gain: Multiplying by gain | {\"filename\": \"coj2m002-en12-20200813-0026-e00.fits.fz\", \"site\": \"coj\", \"instrument\": \"floyds02\", \"epoch\": \"20200813\", \"request_num\": \"2207853\", \"obstype\": \"SPECTRUM\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", - "2023-08-11 10:43:27.801 INFO: stages: Running banzai.stages.PoissonInitializer | {\"filename\": \"coj2m002-en12-20200813-0026-e00.fits.fz\", \"site\": \"coj\", \"instrument\": \"floyds02\", \"epoch\": \"20200813\", \"request_num\": \"2207853\", \"obstype\": \"SPECTRUM\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", - "2023-08-11 10:43:27.814 INFO: stages: Running banzai.stages.OrderLoader | {\"filename\": \"coj2m002-en12-20200813-0026-e00.fits.fz\", \"site\": \"coj\", \"instrument\": \"floyds02\", \"epoch\": \"20200813\", \"request_num\": \"2207853\", \"obstype\": \"SPECTRUM\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", - "2023-08-11 10:43:27.921 INFO: calibrations: Applying master calibration | {\"filename\": \"coj2m002-en12-20200813-0026-e00.fits.fz\", \"site\": \"coj\", \"instrument\": \"floyds02\", \"epoch\": \"20200813\", \"request_num\": \"2207853\", \"obstype\": \"SPECTRUM\", \"filter\": \"air\", \"master_calibration\": \"coj2m002-en12-20220313-0002-f91.fits.fz\", \"processName\": \"MainProcess\"}\n", - "2023-08-11 10:43:27.926 INFO: stages: Running banzai.stages.OrderTweaker | {\"filename\": \"coj2m002-en12-20200813-0026-e00.fits.fz\", \"site\": \"coj\", \"instrument\": \"floyds02\", \"epoch\": \"20200813\", \"request_num\": \"2207853\", \"obstype\": \"SPECTRUM\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", - "2023-08-11 10:43:28.487 INFO: stages: Running banzai.stages.WavelengthSolutionLoader | {\"filename\": \"coj2m002-en12-20200813-0026-e00.fits.fz\", \"site\": \"coj\", \"instrument\": \"floyds02\", \"epoch\": \"20200813\", \"request_num\": \"2207853\", \"obstype\": \"SPECTRUM\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", - "2023-08-11 10:43:28.727 INFO: calibrations: Applying master calibration | {\"filename\": \"coj2m002-en12-20200813-0026-e00.fits.fz\", \"site\": \"coj\", \"instrument\": \"floyds02\", \"epoch\": \"20200813\", \"request_num\": \"2207853\", \"obstype\": \"SPECTRUM\", \"filter\": \"air\", \"master_calibration\": \"coj2m002-en12-20200813-0028-a91.fits.fz\", \"processName\": \"MainProcess\"}\n", - "2023-08-11 10:43:28.735 INFO: stages: Running banzai.stages.FringeCorrector | {\"filename\": \"coj2m002-en12-20200813-0026-e00.fits.fz\", \"site\": \"coj\", \"instrument\": \"floyds02\", \"epoch\": \"20200813\", \"request_num\": \"2207853\", \"obstype\": \"SPECTRUM\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", - "2023-08-11 10:43:28.736 ERROR: stages: ['Traceback (most recent call last):\\n', ' File \"/Users/cmccully/miniconda3/envs/banzai-floyds/lib/python3.10/site-packages/banzai/stages.py\", line 52, in run\\n processed_image = self.do_stage(image_set)\\n', ' File \"/Users/cmccully/miniconda3/envs/banzai-floyds/lib/python3.10/site-packages/banzai_floyds/fringe.py\", line 94, in do_stage\\n fringe_spline = fit_smooth_fringe_spline(image.fringe, image.fringe > 0)\\n', \"TypeError: '>' not supported between instances of 'NoneType' and 'int'\\n\"] | {\"processName\": \"MainProcess\"}\n", - "2023-08-11 10:43:28.741 ERROR: stage_utils: Reduction stopped | {\"filename\": [{\"id\": 33852323, \"basename\": \"coj2m002-en12-20200813-0026-e00\", \"observation_date\": \"2020-08-13T15:30:17.028000Z\", \"observation_day\": \"2020-08-13\", \"proposal_id\": \"COJ_calib\", \"instrument_id\": \"en12\", \"target_name\": \"FEIGE110\", \"reduction_level\": 0, \"site_id\": \"coj\", \"telescope_id\": \"2m0a\", \"exposure_time\": 300.057, \"primary_optical_element\": \"air\", \"public_date\": \"2020-08-13T15:30:17.028000Z\", \"configuration_type\": \"SPECTRUM\", \"observation_id\": 125217141, \"request_id\": 2207853, \"version_set\": [{\"id\": 35500719, \"key\": \"U_E3364414oi4kL58gzYogBe1td55jP5\", \"md5\": \"b6a1cd29d3eef313518688a7ef1edcd7\", \"extension\": \".fits.fz\", \"url\": \"https://archive-lco-global.s3.amazonaws.com/coj/en12/20200813/raw/coj2m002-en12-20200813-0026-e00.fits.fz?versionId=U_E3364414oi4kL58gzYogBe1td55jP5&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIA6FT4CXR464A32PW2%2F20230811%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20230811T144325Z&X-Amz-Expires=172800&X-Amz-SignedHeaders=host&X-Amz-Signature=979fc680846865825bd7297ec3584a9b1e8cc54f4b0631c077bd609ddaaf6231\", \"created\": \"2020-08-13T15:35:43.616569Z\"}], \"url\": \"https://archive-lco-global.s3.amazonaws.com/coj/en12/20200813/raw/coj2m002-en12-20200813-0026-e00.fits.fz?versionId=U_E3364414oi4kL58gzYogBe1td55jP5&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIA6FT4CXR464A32PW2%2F20230811%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20230811T144325Z&X-Amz-Expires=172800&X-Amz-SignedHeaders=host&X-Amz-Signature=979fc680846865825bd7297ec3584a9b1e8cc54f4b0631c077bd609ddaaf6231\", \"filename\": \"coj2m002-en12-20200813-0026-e00.fits.fz\", \"DATE_OBS\": \"2020-08-13T15:30:17.028000Z\", \"DAY_OBS\": \"2020-08-13\", \"PROPID\": \"COJ_calib\", \"INSTRUME\": \"en12\", \"OBJECT\": \"FEIGE110\", \"RLEVEL\": 0, \"SITEID\": \"coj\", \"TELID\": \"2m0a\", \"EXPTIME\": 300.057, \"FILTER\": \"air\", \"L1PUBDAT\": \"2020-08-13T15:30:17.028000Z\", \"OBSTYPE\": \"SPECTRUM\", \"BLKUID\": 125217141, \"REQNUM\": 2207853, \"area\": {\"type\": \"Polygon\", \"coordinates\": [[[-10.054610170738442, -5.203641989198259], [-10.008739010819625, -5.217802111177702], [-9.950940499795593, -5.032031245360507], [-9.996799693671221, -5.017875344333659], [-10.054610170738442, -5.203641989198259]]]}, \"related_frames\": [33860645], \"frameid\": 33852323}], \"processName\": \"MainProcess\"}\n", - "2023-08-11 10:43:29.403 INFO: fits_utils: Downloading file ogg2m001-en06-20200822-0007-e00.fits.fz from archive. ID: 33989111. | {\"filename\": \"ogg2m001-en06-20200822-0007-e00.fits.fz\", \"attempt_number\": 1, \"processName\": \"MainProcess\"}\n", - "2023-08-11 10:43:31.264 INFO: stages: Running banzai.stages.OverscanSubtractor | {\"filename\": \"ogg2m001-en06-20200822-0007-e00.fits.fz\", \"site\": \"ogg\", \"instrument\": \"floyds01\", \"epoch\": \"20200822\", \"request_num\": \"2217414\", \"obstype\": \"SPECTRUM\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", - "2023-08-11 10:43:31.267 INFO: stages: Running banzai.stages.Trimmer | {\"filename\": \"ogg2m001-en06-20200822-0007-e00.fits.fz\", \"site\": \"ogg\", \"instrument\": \"floyds01\", \"epoch\": \"20200822\", \"request_num\": \"2217414\", \"obstype\": \"SPECTRUM\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", - "2023-08-11 10:43:31.267 INFO: trim: Trimming image | {\"filename\": \"ogg2m001-en06-20200822-0007-e00.fits.fz\", \"site\": \"ogg\", \"instrument\": \"floyds01\", \"epoch\": \"20200822\", \"request_num\": \"2217414\", \"obstype\": \"SPECTRUM\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", - "2023-08-11 10:43:31.332 INFO: stages: Running banzai.stages.GainNormalizer | {\"filename\": \"ogg2m001-en06-20200822-0007-e00.fits.fz\", \"site\": \"ogg\", \"instrument\": \"floyds01\", \"epoch\": \"20200822\", \"request_num\": \"2217414\", \"obstype\": \"SPECTRUM\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", - "2023-08-11 10:43:31.332 INFO: gain: Multiplying by gain | {\"filename\": \"ogg2m001-en06-20200822-0007-e00.fits.fz\", \"site\": \"ogg\", \"instrument\": \"floyds01\", \"epoch\": \"20200822\", \"request_num\": \"2217414\", \"obstype\": \"SPECTRUM\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", - "2023-08-11 10:43:31.334 INFO: stages: Running banzai.stages.PoissonInitializer | {\"filename\": \"ogg2m001-en06-20200822-0007-e00.fits.fz\", \"site\": \"ogg\", \"instrument\": \"floyds01\", \"epoch\": \"20200822\", \"request_num\": \"2217414\", \"obstype\": \"SPECTRUM\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", - "2023-08-11 10:43:31.341 INFO: stages: Running banzai.stages.OrderLoader | {\"filename\": \"ogg2m001-en06-20200822-0007-e00.fits.fz\", \"site\": \"ogg\", \"instrument\": \"floyds01\", \"epoch\": \"20200822\", \"request_num\": \"2217414\", \"obstype\": \"SPECTRUM\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", - "2023-08-11 10:43:31.455 INFO: calibrations: Applying master calibration | {\"filename\": \"ogg2m001-en06-20200822-0007-e00.fits.fz\", \"site\": \"ogg\", \"instrument\": \"floyds01\", \"epoch\": \"20200822\", \"request_num\": \"2217414\", \"obstype\": \"SPECTRUM\", \"filter\": \"air\", \"master_calibration\": \"ogg2m001-en06-20190329-0018-f91.fits.fz\", \"processName\": \"MainProcess\"}\n", - "2023-08-11 10:43:31.458 INFO: stages: Running banzai.stages.OrderTweaker | {\"filename\": \"ogg2m001-en06-20200822-0007-e00.fits.fz\", \"site\": \"ogg\", \"instrument\": \"floyds01\", \"epoch\": \"20200822\", \"request_num\": \"2217414\", \"obstype\": \"SPECTRUM\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", - "2023-08-11 10:43:31.575 INFO: stages: Running banzai.stages.WavelengthSolutionLoader | {\"filename\": \"ogg2m001-en06-20200822-0007-e00.fits.fz\", \"site\": \"ogg\", \"instrument\": \"floyds01\", \"epoch\": \"20200822\", \"request_num\": \"2217414\", \"obstype\": \"SPECTRUM\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", - "2023-08-11 10:43:31.725 INFO: calibrations: Applying master calibration | {\"filename\": \"ogg2m001-en06-20200822-0007-e00.fits.fz\", \"site\": \"ogg\", \"instrument\": \"floyds01\", \"epoch\": \"20200822\", \"request_num\": \"2217414\", \"obstype\": \"SPECTRUM\", \"filter\": \"air\", \"master_calibration\": \"ogg2m001-en06-20200822-0009-a91.fits.fz\", \"processName\": \"MainProcess\"}\n", - "2023-08-11 10:43:31.729 INFO: stages: Running banzai.stages.FringeCorrector | {\"filename\": \"ogg2m001-en06-20200822-0007-e00.fits.fz\", \"site\": \"ogg\", \"instrument\": \"floyds01\", \"epoch\": \"20200822\", \"request_num\": \"2217414\", \"obstype\": \"SPECTRUM\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", - "2023-08-11 10:43:31.730 ERROR: stages: ['Traceback (most recent call last):\\n', ' File \"/Users/cmccully/miniconda3/envs/banzai-floyds/lib/python3.10/site-packages/banzai/stages.py\", line 52, in run\\n processed_image = self.do_stage(image_set)\\n', ' File \"/Users/cmccully/miniconda3/envs/banzai-floyds/lib/python3.10/site-packages/banzai_floyds/fringe.py\", line 94, in do_stage\\n fringe_spline = fit_smooth_fringe_spline(image.fringe, image.fringe > 0)\\n', \"TypeError: '>' not supported between instances of 'NoneType' and 'int'\\n\"] | {\"processName\": \"MainProcess\"}\n", - "2023-08-11 10:43:31.732 ERROR: stage_utils: Reduction stopped | {\"filename\": [{\"id\": 33989111, \"basename\": \"ogg2m001-en06-20200822-0007-e00\", \"observation_date\": \"2020-08-23T07:47:30.834000Z\", \"observation_day\": \"2020-08-22\", \"proposal_id\": \"OGG_calib\", \"instrument_id\": \"en06\", \"target_name\": \"BD+284211\", \"reduction_level\": 0, \"site_id\": \"ogg\", \"telescope_id\": \"2m0a\", \"exposure_time\": 300.092, \"primary_optical_element\": \"air\", \"public_date\": \"2020-08-23T07:47:30.834000Z\", \"configuration_type\": \"SPECTRUM\", \"observation_id\": 128781982, \"request_id\": 2217414, \"version_set\": [{\"id\": 35637555, \"key\": \"tHM9lKaFWZHfU6IsncajREh58Q3rx1SY\", \"md5\": \"04c0b01d4782228f010318da2e904c20\", \"extension\": \".fits.fz\", \"url\": \"https://archive-lco-global.s3.amazonaws.com/ogg/en06/20200822/raw/ogg2m001-en06-20200822-0007-e00.fits.fz?versionId=tHM9lKaFWZHfU6IsncajREh58Q3rx1SY&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIA6FT4CXR464A32PW2%2F20230811%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20230811T144329Z&X-Amz-Expires=172800&X-Amz-SignedHeaders=host&X-Amz-Signature=680c56b617efdaa353df365bf2510358e5de63ef5775bf1ba1e89513d4bdca49\", \"created\": \"2020-08-23T07:52:56.003866Z\"}], \"url\": \"https://archive-lco-global.s3.amazonaws.com/ogg/en06/20200822/raw/ogg2m001-en06-20200822-0007-e00.fits.fz?versionId=tHM9lKaFWZHfU6IsncajREh58Q3rx1SY&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIA6FT4CXR464A32PW2%2F20230811%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20230811T144329Z&X-Amz-Expires=172800&X-Amz-SignedHeaders=host&X-Amz-Signature=680c56b617efdaa353df365bf2510358e5de63ef5775bf1ba1e89513d4bdca49\", \"filename\": \"ogg2m001-en06-20200822-0007-e00.fits.fz\", \"DATE_OBS\": \"2020-08-23T07:47:30.834000Z\", \"DAY_OBS\": \"2020-08-22\", \"PROPID\": \"OGG_calib\", \"INSTRUME\": \"en06\", \"OBJECT\": \"BD+284211\", \"RLEVEL\": 0, \"SITEID\": \"ogg\", \"TELID\": \"2m0a\", \"EXPTIME\": 300.092, \"FILTER\": \"air\", \"L1PUBDAT\": \"2020-08-23T07:47:30.834000Z\", \"OBSTYPE\": \"SPECTRUM\", \"BLKUID\": 128781982, \"REQNUM\": 2217414, \"area\": {\"type\": \"Polygon\", \"coordinates\": [[[-32.25670055123709, 28.89368781099744], [-32.25716860741153, 28.84585784844757], [-32.03511758625558, 28.844009520860254], [-32.034547367193966, 28.891838665841313], [-32.25670055123709, 28.89368781099744]]]}, \"related_frames\": [34004078], \"frameid\": 33989111}], \"processName\": \"MainProcess\"}\n", - "2023-08-11 10:43:32.365 INFO: fits_utils: Downloading file ogg2m001-en06-20200822-0027-e00.fits.fz from archive. ID: 33994991. | {\"filename\": \"ogg2m001-en06-20200822-0027-e00.fits.fz\", \"attempt_number\": 1, \"processName\": \"MainProcess\"}\n", - "2023-08-11 10:43:34.275 INFO: stages: Running banzai.stages.OverscanSubtractor | {\"filename\": \"ogg2m001-en06-20200822-0027-e00.fits.fz\", \"site\": \"ogg\", \"instrument\": \"floyds01\", \"epoch\": \"20200822\", \"request_num\": \"2217762\", \"obstype\": \"SPECTRUM\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", - "2023-08-11 10:43:34.279 INFO: stages: Running banzai.stages.Trimmer | {\"filename\": \"ogg2m001-en06-20200822-0027-e00.fits.fz\", \"site\": \"ogg\", \"instrument\": \"floyds01\", \"epoch\": \"20200822\", \"request_num\": \"2217762\", \"obstype\": \"SPECTRUM\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", - "2023-08-11 10:43:34.279 INFO: trim: Trimming image | {\"filename\": \"ogg2m001-en06-20200822-0027-e00.fits.fz\", \"site\": \"ogg\", \"instrument\": \"floyds01\", \"epoch\": \"20200822\", \"request_num\": \"2217762\", \"obstype\": \"SPECTRUM\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", - "2023-08-11 10:43:34.349 INFO: stages: Running banzai.stages.GainNormalizer | {\"filename\": \"ogg2m001-en06-20200822-0027-e00.fits.fz\", \"site\": \"ogg\", \"instrument\": \"floyds01\", \"epoch\": \"20200822\", \"request_num\": \"2217762\", \"obstype\": \"SPECTRUM\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", - "2023-08-11 10:43:34.349 INFO: gain: Multiplying by gain | {\"filename\": \"ogg2m001-en06-20200822-0027-e00.fits.fz\", \"site\": \"ogg\", \"instrument\": \"floyds01\", \"epoch\": \"20200822\", \"request_num\": \"2217762\", \"obstype\": \"SPECTRUM\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", - "2023-08-11 10:43:34.351 INFO: stages: Running banzai.stages.PoissonInitializer | {\"filename\": \"ogg2m001-en06-20200822-0027-e00.fits.fz\", \"site\": \"ogg\", \"instrument\": \"floyds01\", \"epoch\": \"20200822\", \"request_num\": \"2217762\", \"obstype\": \"SPECTRUM\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", - "2023-08-11 10:43:34.357 INFO: stages: Running banzai.stages.OrderLoader | {\"filename\": \"ogg2m001-en06-20200822-0027-e00.fits.fz\", \"site\": \"ogg\", \"instrument\": \"floyds01\", \"epoch\": \"20200822\", \"request_num\": \"2217762\", \"obstype\": \"SPECTRUM\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", - "2023-08-11 10:43:34.465 INFO: calibrations: Applying master calibration | {\"filename\": \"ogg2m001-en06-20200822-0027-e00.fits.fz\", \"site\": \"ogg\", \"instrument\": \"floyds01\", \"epoch\": \"20200822\", \"request_num\": \"2217762\", \"obstype\": \"SPECTRUM\", \"filter\": \"air\", \"master_calibration\": \"ogg2m001-en06-20190329-0018-f91.fits.fz\", \"processName\": \"MainProcess\"}\n", - "2023-08-11 10:43:34.468 INFO: stages: Running banzai.stages.OrderTweaker | {\"filename\": \"ogg2m001-en06-20200822-0027-e00.fits.fz\", \"site\": \"ogg\", \"instrument\": \"floyds01\", \"epoch\": \"20200822\", \"request_num\": \"2217762\", \"obstype\": \"SPECTRUM\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", - "2023-08-11 10:43:34.820 INFO: stages: Running banzai.stages.WavelengthSolutionLoader | {\"filename\": \"ogg2m001-en06-20200822-0027-e00.fits.fz\", \"site\": \"ogg\", \"instrument\": \"floyds01\", \"epoch\": \"20200822\", \"request_num\": \"2217762\", \"obstype\": \"SPECTRUM\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", - "2023-08-11 10:43:34.969 INFO: calibrations: Applying master calibration | {\"filename\": \"ogg2m001-en06-20200822-0027-e00.fits.fz\", \"site\": \"ogg\", \"instrument\": \"floyds01\", \"epoch\": \"20200822\", \"request_num\": \"2217762\", \"obstype\": \"SPECTRUM\", \"filter\": \"air\", \"master_calibration\": \"ogg2m001-en06-20200822-0028-a91.fits.fz\", \"processName\": \"MainProcess\"}\n", - "2023-08-11 10:43:34.974 INFO: stages: Running banzai.stages.FringeCorrector | {\"filename\": \"ogg2m001-en06-20200822-0027-e00.fits.fz\", \"site\": \"ogg\", \"instrument\": \"floyds01\", \"epoch\": \"20200822\", \"request_num\": \"2217762\", \"obstype\": \"SPECTRUM\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", - "2023-08-11 10:43:34.974 ERROR: stages: ['Traceback (most recent call last):\\n', ' File \"/Users/cmccully/miniconda3/envs/banzai-floyds/lib/python3.10/site-packages/banzai/stages.py\", line 52, in run\\n processed_image = self.do_stage(image_set)\\n', ' File \"/Users/cmccully/miniconda3/envs/banzai-floyds/lib/python3.10/site-packages/banzai_floyds/fringe.py\", line 94, in do_stage\\n fringe_spline = fit_smooth_fringe_spline(image.fringe, image.fringe > 0)\\n', \"TypeError: '>' not supported between instances of 'NoneType' and 'int'\\n\"] | {\"processName\": \"MainProcess\"}\n", - "2023-08-11 10:43:34.977 ERROR: stage_utils: Reduction stopped | {\"filename\": [{\"id\": 33994991, \"basename\": \"ogg2m001-en06-20200822-0027-e00\", \"observation_date\": \"2020-08-23T11:58:34.949000Z\", \"observation_day\": \"2020-08-22\", \"proposal_id\": \"LCO2020B-003\", \"instrument_id\": \"en06\", \"target_name\": \"HD 201767\", \"reduction_level\": 0, \"site_id\": \"ogg\", \"telescope_id\": \"2m0a\", \"exposure_time\": 179.982, \"primary_optical_element\": \"air\", \"public_date\": \"2021-08-23T11:58:34.949000Z\", \"configuration_type\": \"SPECTRUM\", \"observation_id\": 128859340, \"request_id\": 2217762, \"version_set\": [{\"id\": 35643435, \"key\": \"NhRv8PqPHqOuG7I8TuEY9csQFIHV58YY\", \"md5\": \"c17ae87b6c8409bded46cf95f68eb6ff\", \"extension\": \".fits.fz\", \"url\": \"https://archive-lco-global.s3.amazonaws.com/ogg/en06/20200822/raw/ogg2m001-en06-20200822-0027-e00.fits.fz?versionId=NhRv8PqPHqOuG7I8TuEY9csQFIHV58YY&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIA6FT4CXR464A32PW2%2F20230811%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20230811T144332Z&X-Amz-Expires=172800&X-Amz-SignedHeaders=host&X-Amz-Signature=eb52fffcfb1011fa9c9ba252e59db978afaf2b689b80a2c11bcfd6c021bee6cd\", \"created\": \"2020-08-23T12:02:00.096001Z\"}], \"url\": \"https://archive-lco-global.s3.amazonaws.com/ogg/en06/20200822/raw/ogg2m001-en06-20200822-0027-e00.fits.fz?versionId=NhRv8PqPHqOuG7I8TuEY9csQFIHV58YY&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIA6FT4CXR464A32PW2%2F20230811%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20230811T144332Z&X-Amz-Expires=172800&X-Amz-SignedHeaders=host&X-Amz-Signature=eb52fffcfb1011fa9c9ba252e59db978afaf2b689b80a2c11bcfd6c021bee6cd\", \"filename\": \"ogg2m001-en06-20200822-0027-e00.fits.fz\", \"DATE_OBS\": \"2020-08-23T11:58:34.949000Z\", \"DAY_OBS\": \"2020-08-22\", \"PROPID\": \"LCO2020B-003\", \"INSTRUME\": \"en06\", \"OBJECT\": \"HD 201767\", \"RLEVEL\": 0, \"SITEID\": \"ogg\", \"TELID\": \"2m0a\", \"EXPTIME\": 179.982, \"FILTER\": \"air\", \"L1PUBDAT\": \"2021-08-23T11:58:34.949000Z\", \"OBSTYPE\": \"SPECTRUM\", \"BLKUID\": 128859340, \"REQNUM\": 2217762, \"area\": {\"type\": \"Polygon\", \"coordinates\": [[[-41.95558118295355, -11.510770454986625], [-41.98302358528434, -11.4712285118447], [-42.147169060224996, -11.580533642453789], [-42.11973905825192, -11.620091003845015], [-41.95558118295355, -11.510770454986625]]]}, \"related_frames\": [34004077], \"frameid\": 33994991}], \"processName\": \"MainProcess\"}\n" + "2023-09-07 16:30:20.894 INFO: fits_utils: Downloading file coj2m002-en12-20200813-0014-e00.fits.fz from archive. ID: 33851447. | {\"filename\": \"coj2m002-en12-20200813-0014-e00.fits.fz\", \"attempt_number\": 1, \"processName\": \"MainProcess\"}\n", + "2023-09-07 16:30:22.475 INFO: stages: Running banzai.stages.OverscanSubtractor | {\"filename\": \"coj2m002-en12-20200813-0014-e00.fits.fz\", \"site\": \"coj\", \"instrument\": \"floyds02\", \"epoch\": \"20200813\", \"request_num\": \"2207712\", \"obstype\": \"SPECTRUM\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", + "2023-09-07 16:30:22.478 INFO: stages: Running banzai.stages.Trimmer | {\"filename\": \"coj2m002-en12-20200813-0014-e00.fits.fz\", \"site\": \"coj\", \"instrument\": \"floyds02\", \"epoch\": \"20200813\", \"request_num\": \"2207712\", \"obstype\": \"SPECTRUM\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", + "2023-09-07 16:30:22.478 INFO: trim: Trimming image | {\"filename\": \"coj2m002-en12-20200813-0014-e00.fits.fz\", \"site\": \"coj\", \"instrument\": \"floyds02\", \"epoch\": \"20200813\", \"request_num\": \"2207712\", \"obstype\": \"SPECTRUM\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", + "2023-09-07 16:30:22.538 INFO: stages: Running banzai.stages.GainNormalizer | {\"filename\": \"coj2m002-en12-20200813-0014-e00.fits.fz\", \"site\": \"coj\", \"instrument\": \"floyds02\", \"epoch\": \"20200813\", \"request_num\": \"2207712\", \"obstype\": \"SPECTRUM\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", + "2023-09-07 16:30:22.538 INFO: gain: Multiplying by gain | {\"filename\": \"coj2m002-en12-20200813-0014-e00.fits.fz\", \"site\": \"coj\", \"instrument\": \"floyds02\", \"epoch\": \"20200813\", \"request_num\": \"2207712\", \"obstype\": \"SPECTRUM\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", + "2023-09-07 16:30:22.541 INFO: stages: Running banzai.stages.PoissonInitializer | {\"filename\": \"coj2m002-en12-20200813-0014-e00.fits.fz\", \"site\": \"coj\", \"instrument\": \"floyds02\", \"epoch\": \"20200813\", \"request_num\": \"2207712\", \"obstype\": \"SPECTRUM\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", + "2023-09-07 16:30:22.548 INFO: stages: Running banzai.stages.OrderLoader | {\"filename\": \"coj2m002-en12-20200813-0014-e00.fits.fz\", \"site\": \"coj\", \"instrument\": \"floyds02\", \"epoch\": \"20200813\", \"request_num\": \"2207712\", \"obstype\": \"SPECTRUM\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", + "2023-09-07 16:30:22.666 INFO: calibrations: Applying master calibration | {\"filename\": \"coj2m002-en12-20200813-0014-e00.fits.fz\", \"site\": \"coj\", \"instrument\": \"floyds02\", \"epoch\": \"20200813\", \"request_num\": \"2207712\", \"obstype\": \"SPECTRUM\", \"filter\": \"air\", \"master_calibration\": \"coj2m002-en12-20220313-0002-f91.fits.fz\", \"processName\": \"MainProcess\"}\n", + "2023-09-07 16:30:22.669 INFO: stages: Running banzai.stages.OrderTweaker | {\"filename\": \"coj2m002-en12-20200813-0014-e00.fits.fz\", \"site\": \"coj\", \"instrument\": \"floyds02\", \"epoch\": \"20200813\", \"request_num\": \"2207712\", \"obstype\": \"SPECTRUM\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", + "2023-09-07 16:30:23.050 INFO: stages: Running banzai.stages.WavelengthSolutionLoader | {\"filename\": \"coj2m002-en12-20200813-0014-e00.fits.fz\", \"site\": \"coj\", \"instrument\": \"floyds02\", \"epoch\": \"20200813\", \"request_num\": \"2207712\", \"obstype\": \"SPECTRUM\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", + "2023-09-07 16:30:23.210 INFO: calibrations: Applying master calibration | {\"filename\": \"coj2m002-en12-20200813-0014-e00.fits.fz\", \"site\": \"coj\", \"instrument\": \"floyds02\", \"epoch\": \"20200813\", \"request_num\": \"2207712\", \"obstype\": \"SPECTRUM\", \"filter\": \"air\", \"master_calibration\": \"coj2m002-en12-20200813-0015-a91.fits.fz\", \"processName\": \"MainProcess\"}\n", + "2023-09-07 16:30:23.217 INFO: stages: Running banzai.stages.FringeLoader | {\"filename\": \"coj2m002-en12-20200813-0014-e00.fits.fz\", \"site\": \"coj\", \"instrument\": \"floyds02\", \"epoch\": \"20200813\", \"request_num\": \"2207712\", \"obstype\": \"SPECTRUM\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", + "2023-09-07 16:30:23.300 INFO: calibrations: Applying master calibration | {\"filename\": \"coj2m002-en12-20200813-0014-e00.fits.fz\", \"site\": \"coj\", \"instrument\": \"floyds02\", \"epoch\": \"20200813\", \"request_num\": \"2207712\", \"obstype\": \"SPECTRUM\", \"filter\": \"air\", \"master_calibration\": \"coj2m002-en12-20200813-lampflat-0.1MHz_2.0preamp.fits.fz\", \"processName\": \"MainProcess\"}\n", + "2023-09-07 16:30:23.302 INFO: stages: Running banzai.stages.FringeCorrector | {\"filename\": \"coj2m002-en12-20200813-0014-e00.fits.fz\", \"site\": \"coj\", \"instrument\": \"floyds02\", \"epoch\": \"20200813\", \"request_num\": \"2207712\", \"obstype\": \"SPECTRUM\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", + "2023-09-07 16:30:27.850 INFO: stages: Running banzai.stages.Extractor | {\"filename\": \"coj2m002-en12-20200813-0014-e00.fits.fz\", \"site\": \"coj\", \"instrument\": \"floyds02\", \"epoch\": \"20200813\", \"request_num\": \"2207712\", \"obstype\": \"SPECTRUM\", \"filter\": \"air\", \"processName\": \"MainProcess\"}\n", + "2023-09-07 17:50:20.029 WARNING: warnings: /Users/cmccully/miniconda3/envs/banzai-floyds/lib/python3.10/site-packages/astropy/io/fits/card.py:1008: VerifyWarning: Card is too long, comment will be truncated.\n", + " warnings.warn('Card is too long, comment will be truncated.',\n", + "\n" + ] + }, + { + "ename": "ConnectionError", + "evalue": "HTTPSConnectionPool(host='archive-api.lco.global', port=443): Max retries exceeded with url: /frames/33852323 (Caused by NewConnectionError(': Failed to establish a new connection: [Errno 8] nodename nor servname provided, or not known'))", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mgaierror\u001b[0m Traceback (most recent call last)", + "File \u001b[0;32m~/miniconda3/envs/banzai-floyds/lib/python3.10/site-packages/urllib3/connection.py:174\u001b[0m, in \u001b[0;36mHTTPConnection._new_conn\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m 173\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[0;32m--> 174\u001b[0m conn \u001b[38;5;241m=\u001b[39m \u001b[43mconnection\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mcreate_connection\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 175\u001b[0m \u001b[43m \u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_dns_host\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mport\u001b[49m\u001b[43m)\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mtimeout\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mextra_kw\u001b[49m\n\u001b[1;32m 176\u001b[0m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 178\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m SocketTimeout:\n", + "File \u001b[0;32m~/miniconda3/envs/banzai-floyds/lib/python3.10/site-packages/urllib3/util/connection.py:72\u001b[0m, in \u001b[0;36mcreate_connection\u001b[0;34m(address, timeout, source_address, socket_options)\u001b[0m\n\u001b[1;32m 68\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m six\u001b[38;5;241m.\u001b[39mraise_from(\n\u001b[1;32m 69\u001b[0m LocationParseError(\u001b[38;5;124mu\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124m'\u001b[39m\u001b[38;5;132;01m%s\u001b[39;00m\u001b[38;5;124m'\u001b[39m\u001b[38;5;124m, label empty or too long\u001b[39m\u001b[38;5;124m\"\u001b[39m \u001b[38;5;241m%\u001b[39m host), \u001b[38;5;28;01mNone\u001b[39;00m\n\u001b[1;32m 70\u001b[0m )\n\u001b[0;32m---> 72\u001b[0m \u001b[38;5;28;01mfor\u001b[39;00m res \u001b[38;5;129;01min\u001b[39;00m \u001b[43msocket\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mgetaddrinfo\u001b[49m\u001b[43m(\u001b[49m\u001b[43mhost\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mport\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mfamily\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43msocket\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mSOCK_STREAM\u001b[49m\u001b[43m)\u001b[49m:\n\u001b[1;32m 73\u001b[0m af, socktype, proto, canonname, sa \u001b[38;5;241m=\u001b[39m res\n", + "File \u001b[0;32m~/miniconda3/envs/banzai-floyds/lib/python3.10/socket.py:955\u001b[0m, in \u001b[0;36mgetaddrinfo\u001b[0;34m(host, port, family, type, proto, flags)\u001b[0m\n\u001b[1;32m 954\u001b[0m addrlist \u001b[38;5;241m=\u001b[39m []\n\u001b[0;32m--> 955\u001b[0m \u001b[38;5;28;01mfor\u001b[39;00m res \u001b[38;5;129;01min\u001b[39;00m \u001b[43m_socket\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mgetaddrinfo\u001b[49m\u001b[43m(\u001b[49m\u001b[43mhost\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mport\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mfamily\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;28;43mtype\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mproto\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mflags\u001b[49m\u001b[43m)\u001b[49m:\n\u001b[1;32m 956\u001b[0m af, socktype, proto, canonname, sa \u001b[38;5;241m=\u001b[39m res\n", + "\u001b[0;31mgaierror\u001b[0m: [Errno 8] nodename nor servname provided, or not known", + "\nDuring handling of the above exception, another exception occurred:\n", + "\u001b[0;31mNewConnectionError\u001b[0m Traceback (most recent call last)", + "File \u001b[0;32m~/miniconda3/envs/banzai-floyds/lib/python3.10/site-packages/urllib3/connectionpool.py:703\u001b[0m, in \u001b[0;36mHTTPConnectionPool.urlopen\u001b[0;34m(self, method, url, body, headers, retries, redirect, assert_same_host, timeout, pool_timeout, release_conn, chunked, body_pos, **response_kw)\u001b[0m\n\u001b[1;32m 702\u001b[0m \u001b[38;5;66;03m# Make the request on the httplib connection object.\u001b[39;00m\n\u001b[0;32m--> 703\u001b[0m httplib_response \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_make_request\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 704\u001b[0m \u001b[43m \u001b[49m\u001b[43mconn\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 705\u001b[0m \u001b[43m \u001b[49m\u001b[43mmethod\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 706\u001b[0m \u001b[43m \u001b[49m\u001b[43murl\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 707\u001b[0m \u001b[43m \u001b[49m\u001b[43mtimeout\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mtimeout_obj\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 708\u001b[0m \u001b[43m \u001b[49m\u001b[43mbody\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mbody\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 709\u001b[0m \u001b[43m \u001b[49m\u001b[43mheaders\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mheaders\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 710\u001b[0m \u001b[43m \u001b[49m\u001b[43mchunked\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mchunked\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 711\u001b[0m \u001b[43m\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 713\u001b[0m \u001b[38;5;66;03m# If we're going to release the connection in ``finally:``, then\u001b[39;00m\n\u001b[1;32m 714\u001b[0m \u001b[38;5;66;03m# the response doesn't need to know about the connection. Otherwise\u001b[39;00m\n\u001b[1;32m 715\u001b[0m \u001b[38;5;66;03m# it will also try to release it and we'll have a double-release\u001b[39;00m\n\u001b[1;32m 716\u001b[0m \u001b[38;5;66;03m# mess.\u001b[39;00m\n", + "File \u001b[0;32m~/miniconda3/envs/banzai-floyds/lib/python3.10/site-packages/urllib3/connectionpool.py:386\u001b[0m, in \u001b[0;36mHTTPConnectionPool._make_request\u001b[0;34m(self, conn, method, url, timeout, chunked, **httplib_request_kw)\u001b[0m\n\u001b[1;32m 385\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[0;32m--> 386\u001b[0m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_validate_conn\u001b[49m\u001b[43m(\u001b[49m\u001b[43mconn\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 387\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m (SocketTimeout, BaseSSLError) \u001b[38;5;28;01mas\u001b[39;00m e:\n\u001b[1;32m 388\u001b[0m \u001b[38;5;66;03m# Py2 raises this as a BaseSSLError, Py3 raises it as socket timeout.\u001b[39;00m\n", + "File \u001b[0;32m~/miniconda3/envs/banzai-floyds/lib/python3.10/site-packages/urllib3/connectionpool.py:1042\u001b[0m, in \u001b[0;36mHTTPSConnectionPool._validate_conn\u001b[0;34m(self, conn)\u001b[0m\n\u001b[1;32m 1041\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;28mgetattr\u001b[39m(conn, \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124msock\u001b[39m\u001b[38;5;124m\"\u001b[39m, \u001b[38;5;28;01mNone\u001b[39;00m): \u001b[38;5;66;03m# AppEngine might not have `.sock`\u001b[39;00m\n\u001b[0;32m-> 1042\u001b[0m \u001b[43mconn\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mconnect\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 1044\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m conn\u001b[38;5;241m.\u001b[39mis_verified:\n", + "File \u001b[0;32m~/miniconda3/envs/banzai-floyds/lib/python3.10/site-packages/urllib3/connection.py:358\u001b[0m, in \u001b[0;36mHTTPSConnection.connect\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m 356\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21mconnect\u001b[39m(\u001b[38;5;28mself\u001b[39m):\n\u001b[1;32m 357\u001b[0m \u001b[38;5;66;03m# Add certificate verification\u001b[39;00m\n\u001b[0;32m--> 358\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39msock \u001b[38;5;241m=\u001b[39m conn \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_new_conn\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 359\u001b[0m hostname \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mhost\n", + "File \u001b[0;32m~/miniconda3/envs/banzai-floyds/lib/python3.10/site-packages/urllib3/connection.py:186\u001b[0m, in \u001b[0;36mHTTPConnection._new_conn\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m 185\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m SocketError \u001b[38;5;28;01mas\u001b[39;00m e:\n\u001b[0;32m--> 186\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m NewConnectionError(\n\u001b[1;32m 187\u001b[0m \u001b[38;5;28mself\u001b[39m, \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mFailed to establish a new connection: \u001b[39m\u001b[38;5;132;01m%s\u001b[39;00m\u001b[38;5;124m\"\u001b[39m \u001b[38;5;241m%\u001b[39m e\n\u001b[1;32m 188\u001b[0m )\n\u001b[1;32m 190\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m conn\n", + "\u001b[0;31mNewConnectionError\u001b[0m: : Failed to establish a new connection: [Errno 8] nodename nor servname provided, or not known", + "\nDuring handling of the above exception, another exception occurred:\n", + "\u001b[0;31mMaxRetryError\u001b[0m Traceback (most recent call last)", + "File \u001b[0;32m~/miniconda3/envs/banzai-floyds/lib/python3.10/site-packages/requests/adapters.py:439\u001b[0m, in \u001b[0;36mHTTPAdapter.send\u001b[0;34m(self, request, stream, timeout, verify, cert, proxies)\u001b[0m\n\u001b[1;32m 438\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m chunked:\n\u001b[0;32m--> 439\u001b[0m resp \u001b[38;5;241m=\u001b[39m \u001b[43mconn\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43murlopen\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 440\u001b[0m \u001b[43m \u001b[49m\u001b[43mmethod\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mrequest\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mmethod\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 441\u001b[0m \u001b[43m \u001b[49m\u001b[43murl\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43murl\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 442\u001b[0m \u001b[43m \u001b[49m\u001b[43mbody\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mrequest\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mbody\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 443\u001b[0m \u001b[43m \u001b[49m\u001b[43mheaders\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mrequest\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mheaders\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 444\u001b[0m \u001b[43m \u001b[49m\u001b[43mredirect\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;28;43;01mFalse\u001b[39;49;00m\u001b[43m,\u001b[49m\n\u001b[1;32m 445\u001b[0m \u001b[43m \u001b[49m\u001b[43massert_same_host\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;28;43;01mFalse\u001b[39;49;00m\u001b[43m,\u001b[49m\n\u001b[1;32m 446\u001b[0m \u001b[43m \u001b[49m\u001b[43mpreload_content\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;28;43;01mFalse\u001b[39;49;00m\u001b[43m,\u001b[49m\n\u001b[1;32m 447\u001b[0m \u001b[43m \u001b[49m\u001b[43mdecode_content\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;28;43;01mFalse\u001b[39;49;00m\u001b[43m,\u001b[49m\n\u001b[1;32m 448\u001b[0m \u001b[43m \u001b[49m\u001b[43mretries\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mmax_retries\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 449\u001b[0m \u001b[43m \u001b[49m\u001b[43mtimeout\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mtimeout\u001b[49m\n\u001b[1;32m 450\u001b[0m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 452\u001b[0m \u001b[38;5;66;03m# Send the request.\u001b[39;00m\n\u001b[1;32m 453\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n", + "File \u001b[0;32m~/miniconda3/envs/banzai-floyds/lib/python3.10/site-packages/urllib3/connectionpool.py:787\u001b[0m, in \u001b[0;36mHTTPConnectionPool.urlopen\u001b[0;34m(self, method, url, body, headers, retries, redirect, assert_same_host, timeout, pool_timeout, release_conn, chunked, body_pos, **response_kw)\u001b[0m\n\u001b[1;32m 785\u001b[0m e \u001b[38;5;241m=\u001b[39m ProtocolError(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mConnection aborted.\u001b[39m\u001b[38;5;124m\"\u001b[39m, e)\n\u001b[0;32m--> 787\u001b[0m retries \u001b[38;5;241m=\u001b[39m \u001b[43mretries\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mincrement\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 788\u001b[0m \u001b[43m \u001b[49m\u001b[43mmethod\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43murl\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43merror\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43me\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43m_pool\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43m_stacktrace\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43msys\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mexc_info\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\u001b[43m[\u001b[49m\u001b[38;5;241;43m2\u001b[39;49m\u001b[43m]\u001b[49m\n\u001b[1;32m 789\u001b[0m \u001b[43m\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 790\u001b[0m retries\u001b[38;5;241m.\u001b[39msleep()\n", + "File \u001b[0;32m~/miniconda3/envs/banzai-floyds/lib/python3.10/site-packages/urllib3/util/retry.py:592\u001b[0m, in \u001b[0;36mRetry.increment\u001b[0;34m(self, method, url, response, error, _pool, _stacktrace)\u001b[0m\n\u001b[1;32m 591\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m new_retry\u001b[38;5;241m.\u001b[39mis_exhausted():\n\u001b[0;32m--> 592\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m MaxRetryError(_pool, url, error \u001b[38;5;129;01mor\u001b[39;00m ResponseError(cause))\n\u001b[1;32m 594\u001b[0m log\u001b[38;5;241m.\u001b[39mdebug(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mIncremented Retry for (url=\u001b[39m\u001b[38;5;124m'\u001b[39m\u001b[38;5;132;01m%s\u001b[39;00m\u001b[38;5;124m'\u001b[39m\u001b[38;5;124m): \u001b[39m\u001b[38;5;132;01m%r\u001b[39;00m\u001b[38;5;124m\"\u001b[39m, url, new_retry)\n", + "\u001b[0;31mMaxRetryError\u001b[0m: HTTPSConnectionPool(host='archive-api.lco.global', port=443): Max retries exceeded with url: /frames/33852323 (Caused by NewConnectionError(': Failed to establish a new connection: [Errno 8] nodename nor servname provided, or not known'))", + "\nDuring handling of the above exception, another exception occurred:\n", + "\u001b[0;31mConnectionError\u001b[0m Traceback (most recent call last)", + "Cell \u001b[0;32mIn[6], line 3\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[38;5;28;01mfor\u001b[39;00m row \u001b[38;5;129;01min\u001b[39;00m test_data:\n\u001b[1;32m 2\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;124m'\u001b[39m\u001b[38;5;124me00.fits\u001b[39m\u001b[38;5;124m'\u001b[39m \u001b[38;5;129;01min\u001b[39;00m row[\u001b[38;5;124m'\u001b[39m\u001b[38;5;124mfilename\u001b[39m\u001b[38;5;124m'\u001b[39m]:\n\u001b[0;32m----> 3\u001b[0m archive_record \u001b[38;5;241m=\u001b[39m \u001b[43mrequests\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mget\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;124;43mf\u001b[39;49m\u001b[38;5;124;43m'\u001b[39;49m\u001b[38;5;132;43;01m{\u001b[39;49;00m\u001b[43mcontext\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mARCHIVE_FRAME_URL\u001b[49m\u001b[38;5;132;43;01m}\u001b[39;49;00m\u001b[38;5;124;43m/\u001b[39;49m\u001b[38;5;132;43;01m{\u001b[39;49;00m\u001b[43mrow\u001b[49m\u001b[43m[\u001b[49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mframeid\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m]\u001b[49m\u001b[38;5;132;43;01m}\u001b[39;49;00m\u001b[38;5;124;43m'\u001b[39;49m\u001b[43m)\u001b[49m\u001b[38;5;241m.\u001b[39mjson()\n\u001b[1;32m 4\u001b[0m archive_record[\u001b[38;5;124m'\u001b[39m\u001b[38;5;124mframeid\u001b[39m\u001b[38;5;124m'\u001b[39m] \u001b[38;5;241m=\u001b[39m archive_record[\u001b[38;5;124m'\u001b[39m\u001b[38;5;124mid\u001b[39m\u001b[38;5;124m'\u001b[39m]\n\u001b[1;32m 5\u001b[0m run_pipeline_stages([archive_record], context)\n", + "File \u001b[0;32m~/miniconda3/envs/banzai-floyds/lib/python3.10/site-packages/requests/api.py:75\u001b[0m, in \u001b[0;36mget\u001b[0;34m(url, params, **kwargs)\u001b[0m\n\u001b[1;32m 64\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21mget\u001b[39m(url, params\u001b[38;5;241m=\u001b[39m\u001b[38;5;28;01mNone\u001b[39;00m, \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mkwargs):\n\u001b[1;32m 65\u001b[0m \u001b[38;5;124mr\u001b[39m\u001b[38;5;124;03m\"\"\"Sends a GET request.\u001b[39;00m\n\u001b[1;32m 66\u001b[0m \n\u001b[1;32m 67\u001b[0m \u001b[38;5;124;03m :param url: URL for the new :class:`Request` object.\u001b[39;00m\n\u001b[0;32m (...)\u001b[0m\n\u001b[1;32m 72\u001b[0m \u001b[38;5;124;03m :rtype: requests.Response\u001b[39;00m\n\u001b[1;32m 73\u001b[0m \u001b[38;5;124;03m \"\"\"\u001b[39;00m\n\u001b[0;32m---> 75\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43mrequest\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;124;43m'\u001b[39;49m\u001b[38;5;124;43mget\u001b[39;49m\u001b[38;5;124;43m'\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43murl\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mparams\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mparams\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n", + "File \u001b[0;32m~/miniconda3/envs/banzai-floyds/lib/python3.10/site-packages/requests/api.py:61\u001b[0m, in \u001b[0;36mrequest\u001b[0;34m(method, url, **kwargs)\u001b[0m\n\u001b[1;32m 57\u001b[0m \u001b[38;5;66;03m# By using the 'with' statement we are sure the session is closed, thus we\u001b[39;00m\n\u001b[1;32m 58\u001b[0m \u001b[38;5;66;03m# avoid leaving sockets open which can trigger a ResourceWarning in some\u001b[39;00m\n\u001b[1;32m 59\u001b[0m \u001b[38;5;66;03m# cases, and look like a memory leak in others.\u001b[39;00m\n\u001b[1;32m 60\u001b[0m \u001b[38;5;28;01mwith\u001b[39;00m sessions\u001b[38;5;241m.\u001b[39mSession() \u001b[38;5;28;01mas\u001b[39;00m session:\n\u001b[0;32m---> 61\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43msession\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mrequest\u001b[49m\u001b[43m(\u001b[49m\u001b[43mmethod\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mmethod\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43murl\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43murl\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n", + "File \u001b[0;32m~/miniconda3/envs/banzai-floyds/lib/python3.10/site-packages/requests/sessions.py:542\u001b[0m, in \u001b[0;36mSession.request\u001b[0;34m(self, method, url, params, data, headers, cookies, files, auth, timeout, allow_redirects, proxies, hooks, stream, verify, cert, json)\u001b[0m\n\u001b[1;32m 537\u001b[0m send_kwargs \u001b[38;5;241m=\u001b[39m {\n\u001b[1;32m 538\u001b[0m \u001b[38;5;124m'\u001b[39m\u001b[38;5;124mtimeout\u001b[39m\u001b[38;5;124m'\u001b[39m: timeout,\n\u001b[1;32m 539\u001b[0m \u001b[38;5;124m'\u001b[39m\u001b[38;5;124mallow_redirects\u001b[39m\u001b[38;5;124m'\u001b[39m: allow_redirects,\n\u001b[1;32m 540\u001b[0m }\n\u001b[1;32m 541\u001b[0m send_kwargs\u001b[38;5;241m.\u001b[39mupdate(settings)\n\u001b[0;32m--> 542\u001b[0m resp \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43msend\u001b[49m\u001b[43m(\u001b[49m\u001b[43mprep\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43msend_kwargs\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 544\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m resp\n", + "File \u001b[0;32m~/miniconda3/envs/banzai-floyds/lib/python3.10/site-packages/requests/sessions.py:655\u001b[0m, in \u001b[0;36mSession.send\u001b[0;34m(self, request, **kwargs)\u001b[0m\n\u001b[1;32m 652\u001b[0m start \u001b[38;5;241m=\u001b[39m preferred_clock()\n\u001b[1;32m 654\u001b[0m \u001b[38;5;66;03m# Send the request\u001b[39;00m\n\u001b[0;32m--> 655\u001b[0m r \u001b[38;5;241m=\u001b[39m \u001b[43madapter\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43msend\u001b[49m\u001b[43m(\u001b[49m\u001b[43mrequest\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 657\u001b[0m \u001b[38;5;66;03m# Total elapsed time of the request (approximately)\u001b[39;00m\n\u001b[1;32m 658\u001b[0m elapsed \u001b[38;5;241m=\u001b[39m preferred_clock() \u001b[38;5;241m-\u001b[39m start\n", + "File \u001b[0;32m~/miniconda3/envs/banzai-floyds/lib/python3.10/site-packages/requests/adapters.py:516\u001b[0m, in \u001b[0;36mHTTPAdapter.send\u001b[0;34m(self, request, stream, timeout, verify, cert, proxies)\u001b[0m\n\u001b[1;32m 512\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28misinstance\u001b[39m(e\u001b[38;5;241m.\u001b[39mreason, _SSLError):\n\u001b[1;32m 513\u001b[0m \u001b[38;5;66;03m# This branch is for urllib3 v1.22 and later.\u001b[39;00m\n\u001b[1;32m 514\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m SSLError(e, request\u001b[38;5;241m=\u001b[39mrequest)\n\u001b[0;32m--> 516\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mConnectionError\u001b[39;00m(e, request\u001b[38;5;241m=\u001b[39mrequest)\n\u001b[1;32m 518\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m ClosedPoolError \u001b[38;5;28;01mas\u001b[39;00m e:\n\u001b[1;32m 519\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mConnectionError\u001b[39;00m(e, request\u001b[38;5;241m=\u001b[39mrequest)\n", + "\u001b[0;31mConnectionError\u001b[0m: HTTPSConnectionPool(host='archive-api.lco.global', port=443): Max retries exceeded with url: /frames/33852323 (Caused by NewConnectionError(': Failed to establish a new connection: [Errno 8] nodename nor servname provided, or not known'))" ] } ], @@ -441,28 +436,86 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 7, "id": "23cdadf2", "metadata": {}, "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "> \u001b[0;32m/Users/cmccully/miniconda3/envs/banzai-floyds/lib/python3.10/site-packages/requests/adapters.py\u001b[0m(516)\u001b[0;36msend\u001b[0;34m()\u001b[0m\n", + "\u001b[0;32m 514 \u001b[0;31m \u001b[0;32mraise\u001b[0m \u001b[0mSSLError\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0me\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mrequest\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mrequest\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[0;32m 515 \u001b[0;31m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[0;32m--> 516 \u001b[0;31m \u001b[0;32mraise\u001b[0m \u001b[0mConnectionError\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0me\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mrequest\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mrequest\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[0;32m 517 \u001b[0;31m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[0;32m 518 \u001b[0;31m \u001b[0;32mexcept\u001b[0m \u001b[0mClosedPoolError\u001b[0m \u001b[0;32mas\u001b[0m \u001b[0me\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0m\n" + ] + }, { "name": "stdin", "output_type": "stream", "text": [ - "ipdb> c\n" + "ipdb> u/\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "*** Invalid frame count (/)\n" + ] + }, + { + "name": "stdin", + "output_type": "stream", + "text": [ + "ipdb> q\n" ] } ], "source": [ - "foo()" + "%debug" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 11, "id": "9cbde2a0-e966-4f85-afc1-a30aadd7b98c", "metadata": {}, "outputs": [], + "source": [ + "from astropy.io import fits" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "5ea3fd2a-70da-4c29-98e3-6b35d918187d", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "h = fits.Header({'foo': 'bar'})\n", + "h.pop('foo')\n", + "h" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "37dc0b4f-80d2-484e-bc83-058d6981db69", + "metadata": {}, + "outputs": [], "source": [] } ], diff --git a/setup.cfg b/setup.cfg index e5146c6..4e3c5fc 100644 --- a/setup.cfg +++ b/setup.cfg @@ -18,7 +18,7 @@ python_requires = >=3.7 setup_requires = setuptools_scm install_requires = astropy >= 4.3 - banzai @ git+https://github.com/lcogt/banzai.git@fix/stage-grouping + banzai @ git+https://github.com/lcogt/banzai.git astroscrappy matplotlib From 3109a337c68e64bd3061d9582afc3978031c5ab5 Mon Sep 17 00:00:00 2001 From: Curtis McCully Date: Mon, 30 Oct 2023 17:04:56 -0400 Subject: [PATCH 14/19] More fixes to pep style stuff --- banzai_floyds/extract.py | 4 ++-- banzai_floyds/flux.py | 2 +- banzai_floyds/performance.py | 5 ----- banzai_floyds/settings.py | 6 +++--- 4 files changed, 6 insertions(+), 11 deletions(-) delete mode 100644 banzai_floyds/performance.py diff --git a/banzai_floyds/extract.py b/banzai_floyds/extract.py index f16aa66..e6def27 100644 --- a/banzai_floyds/extract.py +++ b/banzai_floyds/extract.py @@ -106,7 +106,7 @@ def fit_profile_width(data, profile_fits, poly_order=3, background_poly_order=2, # Short circuit if the trace is not significantly brighter than the background in this bin if peak_snr < 2.0 * median_snr: continue - + # TODO: Only fit the profile width where it is much larger than the background value, # otherwise use a heuristic width # Pass a match filter (with correct s/n scaling) with a gaussian with a default width @@ -143,7 +143,7 @@ def fit_background(data, profile_centers, profile_widths, x_poly_order=2, y_poly initial_coeffs = np.zeros((x_poly_order + 1) + y_poly_order) initial_coeffs[0] = np.median(data_to_fit['data']) / data_to_fit['data'][peak] # TODO: Fit the background with a totally fixed profile, and no need to iterate - # since our filter is linear + # since our filter is linear best_fit_coeffs = optimize_match_filter(initial_coeffs, data_to_fit['data'], data_to_fit['uncertainty'], background_fixed_profile, diff --git a/banzai_floyds/flux.py b/banzai_floyds/flux.py index 8717e76..e32531a 100644 --- a/banzai_floyds/flux.py +++ b/banzai_floyds/flux.py @@ -76,7 +76,7 @@ def on_missing_master_calibration(self, image): flux_standard = get_standard(image.ra, image.dec, self.runtime_context.db_address) if flux_standard is not None: return super().on_missing_master_calibration(image) - else: + else: return image diff --git a/banzai_floyds/performance.py b/banzai_floyds/performance.py deleted file mode 100644 index 137fc72..0000000 --- a/banzai_floyds/performance.py +++ /dev/null @@ -1,5 +0,0 @@ -from matplotlib import pyplot -import pkg_resources - - - diff --git a/banzai_floyds/settings.py b/banzai_floyds/settings.py index 84633f4..91be33f 100644 --- a/banzai_floyds/settings.py +++ b/banzai_floyds/settings.py @@ -27,9 +27,9 @@ 'SKYFLAT': 'banzai.uncertainty.PoissonInitializer' } -CALIBRATION_STACKER_STAGES['LAMPFLAT'] = ['banzai_floyds.fringe.FringeMaker'] -CALIBRATION_MIN_FRAMES['LAMPFLAT'] = 2 -CALIBRATION_FILENAME_FUNCTIONS['LAMPFLAT'] = ('banzai.utils.file_utils.config_to_filename',) +CALIBRATION_STACKER_STAGES['LAMPFLAT'] = ['banzai_floyds.fringe.FringeMaker'] # noqa: F405 +CALIBRATION_MIN_FRAMES['LAMPFLAT'] = 2 # noqa: F405 +CALIBRATION_FILENAME_FUNCTIONS['LAMPFLAT'] = ('banzai.utils.file_utils.config_to_filename',) # noqa: F405 EXTRA_STAGES = {'SPECTRUM': None, 'LAMPFLAT': None, 'ARC': ['banzai_floyds.wavelengths.CalibrateWavelengths'], From a65fe4f5737df63b8296d05668d3d509a4877397 Mon Sep 17 00:00:00 2001 From: Curtis McCully Date: Mon, 30 Oct 2023 17:06:47 -0400 Subject: [PATCH 15/19] More fixes to pep style stuff --- banzai_floyds/settings.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/banzai_floyds/settings.py b/banzai_floyds/settings.py index 91be33f..65436ae 100644 --- a/banzai_floyds/settings.py +++ b/banzai_floyds/settings.py @@ -27,9 +27,9 @@ 'SKYFLAT': 'banzai.uncertainty.PoissonInitializer' } -CALIBRATION_STACKER_STAGES['LAMPFLAT'] = ['banzai_floyds.fringe.FringeMaker'] # noqa: F405 -CALIBRATION_MIN_FRAMES['LAMPFLAT'] = 2 # noqa: F405 -CALIBRATION_FILENAME_FUNCTIONS['LAMPFLAT'] = ('banzai.utils.file_utils.config_to_filename',) # noqa: F405 +CALIBRATION_STACKER_STAGES['LAMPFLAT'] = ['banzai_floyds.fringe.FringeMaker'] # noqa: F405 +CALIBRATION_MIN_FRAMES['LAMPFLAT'] = 2 # noqa: F405 +CALIBRATION_FILENAME_FUNCTIONS['LAMPFLAT'] = ('banzai.utils.file_utils.config_to_filename',) # noqa: F405 EXTRA_STAGES = {'SPECTRUM': None, 'LAMPFLAT': None, 'ARC': ['banzai_floyds.wavelengths.CalibrateWavelengths'], From f050de9b25a25e0a5ede949121a61c4a02f38a4d Mon Sep 17 00:00:00 2001 From: Curtis McCully Date: Mon, 30 Oct 2023 17:07:45 -0400 Subject: [PATCH 16/19] Removed Jenkinsfile as we don't want to use it anymore --- Jenkinsfile | 222 ---------------------------------------------------- 1 file changed, 222 deletions(-) delete mode 100644 Jenkinsfile diff --git a/Jenkinsfile b/Jenkinsfile deleted file mode 100644 index 18f249c..0000000 --- a/Jenkinsfile +++ /dev/null @@ -1,222 +0,0 @@ -#!/usr/bin/env groovy -@Library('lco-shared-libs@0.1.3') _ - -pipeline { - agent any - parameters { - booleanParam( - name: 'forceEndToEnd', - defaultValue: false, - description: 'When true, forces the end-to-end tests to always run.') - } - environment { - dockerImage = null - PROJ_NAME = projName() - GIT_DESCRIPTION = sh(script: 'git describe', returnStdout: true).trim() - DOCKER_IMG = dockerImageName("${LCO_DOCK_REG}", "${PROJ_NAME}", "${GIT_DESCRIPTION}") - } - options { - timeout(time: 8, unit: 'HOURS') - lock resource: 'BANZAIFLOYDSLock' - } - stages { - stage('Build image') { - steps { - script { - dockerImage = docker.build("${DOCKER_IMG}", "--pull .") - } - } - } - stage('Push image') { - steps { - script { - dockerImage.push("${GIT_DESCRIPTION}") - } - } - } - stage('DeployTestStack') { - agent { - label 'helm' - } - when { - anyOf { - branch 'PR-*' - branch 'dev' - expression { return params.forceEndToEnd } - } - } - steps { - script { - withKubeConfig([credentialsId: "build-kube-config"]) { - sh('helm repo update') - final cmd = " helm --namespace build delete banzai-floyds-e2e &> cleanup.txt" - final status = sh(script: cmd, returnStatus: true) - final output = readFile('cleanup.txt').trim() - sh(script: "rm -f cleanup.txt", returnStatus: true) - echo output - sh('helm --namespace build upgrade --install banzai-floyds-e2e helm-chart/banzai-floyds-e2e ' + - '--set image.tag="${GIT_DESCRIPTION}" --force --wait --timeout=3600s') - - podName = sh(script: 'kubectl get po -l app.kubernetes.io/instance=banzai-floyds-e2e ' + - '--sort-by=.status.startTime -o jsonpath="{.items[-1].metadata.name}"', - returnStdout: true).trim() - - } - } - } - } - stage('Test-Order-Detection') { - environment { - // store stage start time in the environment so it has stage scope - START_TIME = sh(script: 'date +%s', returnStdout: true).trim() - } - when { - anyOf { - branch 'PR-*' - expression { return params.forceEndToEnd } - } - } - steps { - script { - withKubeConfig([credentialsId: "build-kube-config"]) { - sh("kubectl exec ${podName} -c banzai-floyds-e2e-listener -- " + - "pytest -s --durations=0 --junitxml=/home/archive/pytest-order-detection.xml " + - "-m detect_orders /lco/banzai-floyds/") - } - } - } - post { - always { - script { - withKubeConfig([credentialsId: "build-kube-config"]) { - env.LOGS_SINCE = sh(script: 'expr `date +%s` - ${START_TIME}', returnStdout: true).trim() - sh("kubectl logs ${podName} --since=${LOGS_SINCE}s --all-containers") - sh("kubectl cp -c banzai-floyds-e2e-listener ${podName}:/home/archive/pytest-order-detection.xml " + - "pytest-order-detection.xml") - junit "pytest-order-detection.xml" - } - } - } - } - } - stage('Test-Arc-Frame-Creation') { - agent { - label 'helm' - } - environment { - // store stage start time in the environment so it has stage scope - START_TIME = sh(script: 'date +%s', returnStdout: true).trim() - } - when { - anyOf { - branch 'PR-*' - expression { return params.forceEndToEnd } - } - } - steps { - script { - withKubeConfig([credentialsId: "build-kube-config"]) { - sh("kubectl exec ${podName} -c banzai-floyds-e2e-listener -- " + - "pytest -s --durations=0 --junitxml=/home/archive/pytest-arc-frames.xml " + - "-m arc_frames /lco/banzai-floyds/") - } - } - } - post { - always { - script { - withKubeConfig([credentialsId: "build-kube-config"]) { - env.LOGS_SINCE = sh(script: 'expr `date +%s` - ${START_TIME}', returnStdout: true).trim() - sh("kubectl logs ${podName} --since=${LOGS_SINCE}s --all-containers") - sh("kubectl cp -c banzai-floyds-e2e-listener ${podName}:/home/archive/pytest-arc-frames.xml " + - "pytest-arc-frames.xml") - junit "pytest-arc-frames.xml" - } - } - } - } - } - stage('Test-Arc-Frame-Creation') { - agent { - label 'helm' - } - environment { - // store stage start time in the environment so it has stage scope - START_TIME = sh(script: 'date +%s', returnStdout: true).trim() - } - when { - anyOf { - branch 'PR-*' - expression { return params.forceEndToEnd } - } - } - steps { - script { - withKubeConfig([credentialsId: "build-kube-config"]) { - sh("kubectl exec ${podName} -c banzai-floyds-e2e-listener -- " + - "pytest -s --durations=0 --junitxml=/home/archive/pytest-arc-frames.xml " + - "-m arc_frames /lco/banzai-floyds/") - } - } - } - post { - always { - script { - withKubeConfig([credentialsId: "build-kube-config"]) { - env.LOGS_SINCE = sh(script: 'expr `date +%s` - ${START_TIME}', returnStdout: true).trim() - sh("kubectl logs ${podName} --since=${LOGS_SINCE}s --all-containers") - sh("kubectl cp -c banzai-floyds-e2e-listener ${podName}:/home/archive/pytest-arc-frames.xml " + - "pytest-arc-frames.xml") - junit "pytest-arc-frames.xml" - } - } - } - } - } - - stage('Test-Science-Frame-Creation') { - agent { - label 'helm' - } - environment { - // store stage start time in the environment so it has stage scope - START_TIME = sh(script: 'date +%s', returnStdout: true).trim() - } - when { - anyOf { - branch 'PR-*' - expression { return params.forceEndToEnd } - } - } - steps { - script { - withKubeConfig([credentialsId: "build-kube-config"]) { - sh("kubectl exec ${podName} -c banzai-floyds-e2e-listener -- " + - "pytest -s --durations=0 --junitxml=/home/archive/pytest-science-frames.xml " + - "-m science_frames /lco/banzai-floyds/") - } - } - } - post { - always { - script { - withKubeConfig([credentialsId: "build-kube-config"]) { - env.LOGS_SINCE = sh(script: 'expr `date +%s` - ${START_TIME}', returnStdout: true).trim() - sh("kubectl logs ${podName} --since=${LOGS_SINCE}s --all-containers") - sh("kubectl cp -c banzai-floyds-e2e-listener ${podName}:/home/archive/pytest-science-frames.xml " + - "pytest-science-frames.xml") - junit "pytest-science-frames.xml" - } - } - } - success { - script { - withKubeConfig([credentialsId: "build-kube-config"]) { - sh("helm --namespace build delete banzai-floyds-e2e || true") - } - } - } - } - } - } -} From 1f451054bef68de7bd90fb477bb7dc8fa3bf5bf6 Mon Sep 17 00:00:00 2001 From: Curtis McCully Date: Mon, 30 Oct 2023 17:12:49 -0400 Subject: [PATCH 17/19] Removed older pythons from running tests. --- .github/workflows/unit_tests.yml | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml index c31b513..f4d6490 100644 --- a/.github/workflows/unit_tests.yml +++ b/.github/workflows/unit_tests.yml @@ -26,14 +26,9 @@ jobs: include: - name: Code style checks os: ubuntu-latest - python: '3.8' + python: '3.9' toxenv: codestyle - - name: Python 3.7 with minimal dependencies - os: ubuntu-latest - python: '3.7' - toxenv: py37-test - - name: Python 3.9 with minimal dependencies os: ubuntu-latest python: '3.9' @@ -46,22 +41,22 @@ jobs: - name: Python 3.8 with all optional dependencies and coverage checking os: ubuntu-latest - python: '3.8' + python: '3.9' toxenv: py38-test-alldeps-cov - name: OS X - Python 3.8 with all optional dependencies os: macos-latest - python: '3.8' - toxenv: py38-test-alldeps + python: '3.9' + toxenv: py39-test-alldeps - name: Windows - Python 3.8 with all optional dependencies os: windows-latest - python: '3.8' - toxenv: py38-test-alldeps + python: '3.9' + toxenv: py39-test-alldeps - name: Test building of Sphinx docs os: ubuntu-latest - python: '3.8' + python: '3.9' toxenv: build_docs steps: From 1ded1de6ef696a0e75531f65767743d883a3cda3 Mon Sep 17 00:00:00 2001 From: Curtis McCully Date: Mon, 30 Oct 2023 17:14:00 -0400 Subject: [PATCH 18/19] Typo fix in test titles --- .github/workflows/unit_tests.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml index f4d6490..b22bb48 100644 --- a/.github/workflows/unit_tests.yml +++ b/.github/workflows/unit_tests.yml @@ -39,17 +39,17 @@ jobs: python: '3.10' toxenv: py310-test - - name: Python 3.8 with all optional dependencies and coverage checking + - name: Python 3.9 with all optional dependencies and coverage checking os: ubuntu-latest python: '3.9' toxenv: py38-test-alldeps-cov - - name: OS X - Python 3.8 with all optional dependencies + - name: OS X - Python 3.9 with all optional dependencies os: macos-latest python: '3.9' toxenv: py39-test-alldeps - - name: Windows - Python 3.8 with all optional dependencies + - name: Windows - Python 3.9 with all optional dependencies os: windows-latest python: '3.9' toxenv: py39-test-alldeps From 5abf2073042730dc2effbe6f25ce804b924d67ac Mon Sep 17 00:00:00 2001 From: Curtis McCully Date: Mon, 30 Oct 2023 17:16:36 -0400 Subject: [PATCH 19/19] Typo fix in test titles --- .github/workflows/unit_tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml index b22bb48..6cb07a7 100644 --- a/.github/workflows/unit_tests.yml +++ b/.github/workflows/unit_tests.yml @@ -42,7 +42,7 @@ jobs: - name: Python 3.9 with all optional dependencies and coverage checking os: ubuntu-latest python: '3.9' - toxenv: py38-test-alldeps-cov + toxenv: py39-test-alldeps-cov - name: OS X - Python 3.9 with all optional dependencies os: macos-latest