Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactoring functions for simulator and fullfowardmodel #104

Merged
merged 15 commits into from
Nov 3, 2022
8 changes: 4 additions & 4 deletions config/auxtel.ini
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,11 @@ OBS_TRANSMISSION_SYSTEMATICS = 0.005
# observed object to choose between STAR, HG-AR, MONOCHROMATOR
OBS_OBJECT_TYPE = STAR
# telescope transmission file
OBS_TELESCOPE_TRANSMISSION = calexp_2020031500162-EMPTY_ronchi90lpmm-det000_auxtel_transmission.txt
OBS_TELESCOPE_TRANSMISSION = multispectra_holo4_003_HD142331_AuxTel_throughput.txt
# full instrument transmission file
OBS_FULL_INSTRUMENT_TRANSMISSON = calexp_2020031500162-EMPTY_ronchi90lpmm-det000_auxtel_transmission.txt
OBS_FULL_INSTRUMENT_TRANSMISSON = multispectra_holo4_003_HD142331_AuxTel_throughput.txt
# quantum efficiency of the detector file
OBS_QUANTUM_EFFICIENCY = calexp_2020031500162-EMPTY_ronchi90lpmm-det000_auxtel_transmission.txt
OBS_QUANTUM_EFFICIENCY = multispectra_holo4_003_HD142331_AuxTel_throughput.txt
# Camera (x,y) rotation angle with respect to (north-up, east-left) system
OBS_CAMERA_ROTATION = 0
# Camera (x,y) flip signs with respect to (north-up, east-left) system
Expand Down Expand Up @@ -110,7 +110,7 @@ PSF_TYPE = MoffatGauss
# the order of the polynomials to model wavelength dependence of the PSF shape parameters
PSF_POLY_ORDER = 2
# regularisation parameter for the chisq minimisation to extract the spectrum
PSF_FIT_REG_PARAM = 1
PSF_FIT_REG_PARAM = 0.1
# step size in pixels for the first transverse PSF1D fit
PSF_PIXEL_STEP_TRANSVERSE_FIT = 50
# PSF is not evaluated outside a region larger than max(PIXWIDTH_SIGNAL, PSF_FWHM_CLIP*fwhm) pixels
Expand Down
11 changes: 11 additions & 0 deletions spectractor/extractor/chromaticpsf.py
Original file line number Diff line number Diff line change
Expand Up @@ -768,6 +768,17 @@ def get_algebraic_distance_along_dispersion_axis(self, shift_x=0, shift_y=0):
return np.asarray(np.sign(self.table['Dx']) *
np.sqrt((self.table['Dx'] - shift_x) ** 2 + (self.table['Dy_disp_axis'] - shift_y) ** 2))

def update(self, psf_poly_params, x0, y0, angle, plot=False):
profile_params = self.from_poly_params_to_profile_params(psf_poly_params, apply_bounds=True)
self.fill_table_with_profile_params(profile_params)
Dx = np.arange(self.Nx) - x0 # distance in (x,y) spectrogram frame for column x
self.table["Dx"] = Dx
self.table['Dy_disp_axis'] = np.tan(angle * np.pi / 180) * self.table['Dx']
self.table['Dy'] = np.copy(self.table['y_c']) - y0
if plot:
self.plot_summary()
return profile_params

def plot_summary(self, truth=None):
fig, ax = plt.subplots(2, 1, sharex='all', figsize=(12, 6))
PSF_models = []
Expand Down
198 changes: 33 additions & 165 deletions spectractor/extractor/extractor.py

Large diffs are not rendered by default.

67 changes: 16 additions & 51 deletions spectractor/extractor/images.py
Original file line number Diff line number Diff line change
Expand Up @@ -205,40 +205,9 @@ def rebin(self):
>>> im.target_guess
array([405., 295.])
"""

old_data=np.copy(self.data)
old_shape=old_data.shape

new_shape = np.asarray(self.data.shape) // parameters.CCD_REBIN
self.data = rebin(self.data, new_shape)
self.stat_errors = np.sqrt(rebin(self.stat_errors ** 2, new_shape))
new_shape = self.data.shape

if parameters.DEBUG and parameters.DISPLAY:
fig, ax = plt.subplots(1, 3, figsize=(18, 5))

im0 = ax[0].imshow(old_data, origin='lower', norm=LogNorm())
fig.colorbar(im0, ax=ax[0],orientation = 'horizontal')
ax[0].grid()
ax[0].set_title(f"original data : {old_shape}")


im1=ax[1].imshow(self.data,origin='lower',norm=LogNorm())
fig.colorbar(im1, ax=ax[1],orientation = 'horizontal')
ax[1].grid()
ax[1].set_title(f"rebinned data : {new_shape}")

im2=ax[2].imshow(self.stat_errors,origin='lower',norm=LogNorm())
fig.colorbar(im2, ax=ax[2],orientation = 'horizontal')
ax[2].grid()
ax[2].set_title("rebinned stat errors")

if parameters.LSST_SAVEFIGPATH: # pragma: no cover
plt.gcf().savefig(os.path.join(parameters.LSST_SAVEFIGPATH, 'rebinned_image.pdf'), transparent=True)
if parameters.DISPLAY: # pragma: no cover
plt.show()


if self.target_guess is not None:
self.target_guess = np.asarray(self.target_guess) / parameters.CCD_REBIN

Expand Down Expand Up @@ -554,6 +523,7 @@ def plot_image(self, ax=None, scale="lin", title="", units="", plot_stats=False,
if parameters.PdfPages:
parameters.PdfPages.savefig()


def load_CTIO_image(image):
"""Specific routine to load CTIO fits files and load their data and properties for Spectractor.

Expand Down Expand Up @@ -742,6 +712,7 @@ def load_AUXTEL_image(image): # pragma: no cover
parameters.OBS_CAMERA_ROTATION -= 360
if parameters.OBS_CAMERA_ROTATION < -360:
parameters.OBS_CAMERA_ROTATION += 360
image.header["CAM_ROT"] = parameters.OBS_CAMERA_ROTATION
if "CD2_1" in hdu_list[1].header:
rotation_wcs = 180 / np.pi * np.arctan2(hdu_list[1].header["CD2_1"], hdu_list[1].header["CD1_1"]) + 90
if not np.isclose(rotation_wcs % 360, parameters.OBS_CAMERA_ROTATION % 360, atol=2):
Expand Down Expand Up @@ -834,8 +805,6 @@ def find_target(image, guess=None, rotated=False, widths=[parameters.XWINDOW, pa
else:
my_logger.info(f"\n\tNo WCS {wcs_file_name} available, use 2D fit to find target pixel position.")



if parameters.SPECTRACTOR_FIT_TARGET_CENTROID == "fit" or rotated:
if target_pixcoords[0] == -1 and target_pixcoords[1] == -1:
if guess is None:
Expand All @@ -852,23 +821,19 @@ def find_target(image, guess=None, rotated=False, widths=[parameters.XWINDOW, pa
widths=(Dx, Dy))
sub_image_x0, sub_image_y0 = x0, y0

if parameters.DEBUG:

if parameters.DISPLAY:

fig, ax = plt.subplots(1, 2, figsize=(12, 5))
im0 = ax[0].imshow(sub_image_subtracted,origin="lower",norm=LogNorm())
fig.colorbar(im0, ax=ax[0])
ax[0].set_title("sub_image_subtracted")
im1=ax[1].imshow(sub_errors, origin="lower", norm=LogNorm())
ax[1].set_title("sub_image_errors")
fig.colorbar(im1, ax=ax[1])

if parameters.LSST_SAVEFIGPATH: # pragma: no cover
plt.gcf().savefig(os.path.join(parameters.LSST_SAVEFIGPATH, 'sub_image_subtracted.pdf'),
transparent=True)
if parameters.DISPLAY: # pragma: no cover
plt.show()
if parameters.DEBUG and parameters.DISPLAY:
fig, ax = plt.subplots(1, 2, figsize=(12, 5))
im0 = ax[0].imshow(sub_image_subtracted, origin="lower", norm=LogNorm())
fig.colorbar(im0, ax=ax[0])
ax[0].set_title("sub_image_subtracted")
im1 = ax[1].imshow(sub_errors, origin="lower", norm=LogNorm())
ax[1].set_title("sub_image_errors")
fig.colorbar(im1, ax=ax[1])
if parameters.LSST_SAVEFIGPATH: # pragma: no cover
plt.gcf().savefig(os.path.join(parameters.LSST_SAVEFIGPATH, 'sub_image_subtracted.pdf'),
transparent=True)
if parameters.DISPLAY: # pragma: no cover
plt.show()

for i in range(niter):
# find the target
Expand Down Expand Up @@ -990,7 +955,7 @@ def find_target_init(image, guess, rotated=False, widths=[parameters.XWINDOW, pa
sub_image_subtracted = sub_image - bkgd_2D(X, Y)

# SDC : very important clipping negative signal, avoiding crash later
sub_image_subtracted = np.where(sub_image_subtracted<0,0,sub_image_subtracted)
sub_image_subtracted = np.where(sub_image_subtracted < 0, 0, sub_image_subtracted)

saturated_pixels = np.where(sub_image >= image.saturation)
if len(saturated_pixels[0]) > 0:
Expand Down
90 changes: 88 additions & 2 deletions spectractor/extractor/spectrum.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import astropy

from spectractor import parameters
from spectractor.config import set_logger, load_config, apply_rebinning_to_parameters
from spectractor.config import set_logger, load_config
from spectractor.extractor.dispersers import Hologram
from spectractor.extractor.targets import load_target
from spectractor.tools import (ensure_dir, load_fits, plot_image_simple,
Expand All @@ -34,6 +34,8 @@
'humidity': 'OUTHUM',
'lambda_ref': 'LBDA_REF',
'parallactic_angle': 'PARANGLE',
'filter_label': 'FILTER',
'camera_angle': 'CAM_ROT'
}


Expand Down Expand Up @@ -86,6 +88,8 @@ class Spectrum:
Dispersion axis angle in the image in degrees, positive if anticlockwise.
parallactic_angle: float
Parallactic angle in degrees.
camera_angle: float
The North-West axe angle with respect to the camera horizontal axis in degrees.
lines: Lines
Lines instance that contains data on the emission or absorption lines to be searched and fitted in the spectrum.
header: Fits.Header
Expand Down Expand Up @@ -201,6 +205,7 @@ def __init__(self, file_name="", image=None, order=1, target=None, config="", fa
self.chromatic_psf = ChromaticPSF(self.psf, Nx=1, Ny=1, deg=1, saturation=1)
self.rotation_angle = 0
self.parallactic_angle = None
self.camera_angle = 0
self.spectrogram = None
self.spectrogram_bgd = None
self.spectrogram_bgd_rms = None
Expand Down Expand Up @@ -247,6 +252,7 @@ def __init__(self, file_name="", image=None, order=1, target=None, config="", fa
self.units = image.units
self.gain = image.gain
self.rotation_angle = image.rotation_angle
self.camera_angle = parameters.OBS_CAMERA_ROTATION
self.my_logger.info('\n\tSpectrum info copied from image')
self.dec = image.dec
self.hour_angle = image.hour_angle
Expand Down Expand Up @@ -618,9 +624,12 @@ def load_spectrum(self, input_file_name, spectrogram_file_name_override=None,
for attribute, header_key in fits_mappings.items():
if (item := self.header.get(header_key)) is not None:
setattr(self, attribute, item)
# print(f'set {attribute} to {item}')
else:
print(f'Failed to set spectrum attribute {attribute} using header {header_key}')
if "CAM_ROT" in self.header:
parameters.OBS_CAMERA_ROTATION = float(self.header["CAM_ROT"])
else:
self.my_logger.warning("No information about camera rotation in Spectrum header.")

# set the more complex items by hand here
if target := self.header.get('TARGET'):
Expand Down Expand Up @@ -746,6 +755,83 @@ def load_chromatic_psf(self, input_file_name):
else:
self.my_logger.warning(f'\n\tSpectrogram file {input_file_name} not found')

def compute_dispersion_in_spectrogram(self, D, shift_x, shift_y, angle, niter=3, with_adr=True):
"""Compute the dispersion relation in a spectrogram, using grating dispersion model and ADR.
Origin is the order 0 centroid.

Parameters
----------
D: float
The distance between the CCD and the disperser in mm.
shift_x: float
Shift in the x axis direction for order 0 position in pixel.
shift_y: float
Shift in the y axis direction for order 0 position in pixel.
angle: float
Main dispersion axis angle in degrees.
niter: int, optional
Number of iterations to compute ADR (default: 3).
with_adr: bool, optional
If True, add ADR effect to grating dispersion model (default: True).

Returns
-------
lambdas: array_like
Wavelength array for parameters.SPECTRUM_ORDER diffraction.
lambdas_order2: array_like
Wavelength array for parameters.SPECTRUM_ORDER+1 diffraction.
dispersion_law: array_like
Complex array coding the 2D dispersion relation in the spectrogram for parameters.SPECTRUM_ORDER diffraction.
dispersion_law_order2: array_like
Complex array coding the 2D dispersion relation in the spectrogram for parameters.SPECTRUM_ORDER+1 diffraction.

"""
# Distance in x and y with respect to the true order 0 position at lambda_ref
Dx = np.arange(self.spectrogram_Nx) - self.spectrogram_x0 - shift_x # distance in (x,y) spectrogram frame for column x
Dy_disp_axis = np.tan(angle * np.pi / 180) * Dx # disp axis height in spectrogram frame for x
distance = np.sign(Dx) * np.sqrt(Dx * Dx + Dy_disp_axis * Dy_disp_axis) # algebraic distance along dispersion axis

# Wavelengths using the order 0 shifts (ADR has no impact as it shifts order 0 and order p equally)
new_x0 = [self.x0[0] + shift_x, self.x0[1] + shift_y]
# First guess of wavelengths
self.disperser.D = np.copy(D)
lambdas = self.disperser.grating_pixel_to_lambda(distance, new_x0, order=self.order)
lambdas_order2 = self.disperser.grating_pixel_to_lambda(distance, new_x0, order=self.order+np.sign(self.order))

# Evaluate ADR
adr_x = np.zeros_like(Dx)
adr_y = np.zeros_like(Dy_disp_axis)
for k in range(niter):
adr_ra, adr_dec = adr_calib(lambdas, self.adr_params, parameters.OBS_LATITUDE,
lambda_ref=self.lambda_ref)
adr_x, adr_y = flip_and_rotate_adr_to_image_xy_coordinates(adr_ra, adr_dec, dispersion_axis_angle=0)
adr_u, adr_v = flip_and_rotate_adr_to_image_xy_coordinates(adr_ra, adr_dec, dispersion_axis_angle=angle)

# Evaluate ADR for order 2
adr_ra, adr_dec = adr_calib(lambdas_order2, self.adr_params, parameters.OBS_LATITUDE,
lambda_ref=self.lambda_ref)
# adr_x_2, adr_y_2 = flip_and_rotate_adr_to_image_xy_coordinates(adr_ra, adr_dec, dispersion_axis_angle=0)
adr_u_2, adr_v_2 = flip_and_rotate_adr_to_image_xy_coordinates(adr_ra, adr_dec, dispersion_axis_angle=angle)

# Compute lambdas at pixel column x
lambdas = self.disperser.grating_pixel_to_lambda(distance - adr_u, new_x0, order=self.order)
lambdas_order2 = self.disperser.grating_pixel_to_lambda(distance - adr_u_2, new_x0, order=self.order+np.sign(self.order))

# Compute lambdas at pixel column x
# lambdas = self.disperser.grating_pixel_to_lambda(distance - 0*adr_u, new_x0, order=1)
# Position (not distance) in pixel of wavelength lambda order 1 centroid in the (x,y) spectrogram frame
dispersion_law = (Dx + shift_x + with_adr * adr_x) + 1j * (Dy_disp_axis + with_adr * adr_y + shift_y)

# Compute lambdas at pixel column x
# lambdas_order2 = self.disperser.grating_pixel_to_lambda(distance - 0*adr_u, new_x0, order=2)
# Position (not distance) in pixel of wavelength lambda order 2 centroid in the (x,y) spectrogram frame
distance_order2 = self.disperser.grating_lambda_to_pixel(lambdas, x0=new_x0, order=self.order+np.sign(self.order))
Dx_order2 = distance_order2 * np.cos(angle * np.pi / 180)
Dy_disp_axis_order2 = distance_order2 * np.sin(angle * np.pi / 180)
dispersion_law_order2 = (Dx_order2 + shift_x + with_adr * adr_x) + \
1j * (Dy_disp_axis_order2 + with_adr * adr_y + shift_y)
return lambdas, lambdas_order2, dispersion_law, dispersion_law_order2


def detect_lines(lines, lambdas, spec, spec_err=None, cov_matrix=None, fwhm_func=None, snr_minlevel=3, ax=None,
calibration_lines_only=False,
Expand Down
Loading