ERIS Pipeline Reference Manual 1.9.2
eris_nix_wave_lamp.c
1/* $Id$
2 *
3 * This file is part of the ERIS Pipeline
4 * Copyright (C) 2017 European Southern Observatory
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
19 */
20
21/*
22 * $Author$
23 * $Date$
24 * $Revision$
25 */
26
27#ifdef HAVE_CONFIG_H
28#include <config.h>
29#endif
30
31/*-----------------------------------------------------------------------------
32 Includes
33 -----------------------------------------------------------------------------*/
34
35#include <string.h>
36
37#include "eris_utils.h"
38#include "eris_nix_utils.h"
39#include "eris_nix_wavecal_utils.h"
40#include "eris_pfits.h"
41#include "eris_dfs.h"
42#include "eris_nix_dfs.h"
43#include "eris_nix_master_bpm.h"
44#include "eris_nix_master_dark.h"
45#include "eris_nix_gain_linearity.h"
46#include <hdrl.h>
47
48#include <cpl.h>
49
50/*-----------------------------------------------------------------------------
51 Static variables
52 -----------------------------------------------------------------------------*/
53
54static const char eris_nix_wave_lamp_description[] =
55"This recipe reduces a set of WAVE_LAMP_ON/WAVE_LAMP_OFF frame pairs to \n"
56"produce:\n"
57"- A MASTER_WAVE.\n"
58"The recipes needs:\n"
59"- a series of tagged WAVE_LAMP_ON / WAVE_LAMP_OFF frame pairs.\n"
60"- a tagged DETMON coefficients cube file, e.g.\n"
61"-- detmon_ir_lg_coeffs_cube.fits "ERIS_NIX_COEFFS_CUBE_PRO_CATG "\n"
62"- a tagged MASTER_DARK for matching detector configuration.\n"
63"The output will be a fits file containing:\n"
64"- A MASTER_WAVE.\n"
65"\n";
66
67#define RECIPE_NAME "eris.eris_nix_wave_lamp"
68
69/*-----------------------------------------------------------------------------
70 Private function prototypes
71 -----------------------------------------------------------------------------*/
72
73cpl_recipe_define(eris_nix_wave_lamp, ERIS_BINARY_VERSION, "John Lightfoot",
74 PACKAGE_BUGREPORT, "2017",
75 "Calculate a MASTER_WAVE",
76 eris_nix_wave_lamp_description);
77
78/*-----------------------------------------------------------------------------
79 Function code
80 -----------------------------------------------------------------------------*/
81
82/*----------------------------------------------------------------------------*/
90/*----------------------------------------------------------------------------*/
91
92static cpl_error_code eris_nix_wave_lamp_fill_parameterlist(
93 cpl_parameterlist *self) {
94
95 if (cpl_error_get_code() != CPL_ERROR_NONE) return cpl_error_get_code();
96
97 /* the flux threshold - frames with a median above this will be
98 deemed 'saturated' and not used */
99
100 cpl_parameter * p = NULL;
101 p = cpl_parameter_new_value(RECIPE_NAME".saturation_threshold",
102 CPL_TYPE_DOUBLE, "frame saturation threshold", RECIPE_NAME, 5000.0);
103 cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "saturation_threshold");
104 cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
105 cpl_parameterlist_append(self, p);
106
107 /* cleanup */
108
109 return 0;
110}
111
112/*----------------------------------------------------------------------------*/
119/*----------------------------------------------------------------------------*/
120
121static int eris_nix_wave_lamp(cpl_frameset * frameset,
122 const cpl_parameterlist * parlist) {
123
124 cpl_image * contrib = NULL;
125 hdrl_imagelist * diff_himlist = NULL;
126 const gain_linearity * gain_lin = NULL;
127 cpl_vector * lambda_pos =NULL;
128 cpl_matrix * line_pos = NULL;
129 master_bpm * master_bpm = NULL;
130 master_dark * master_dark = NULL;
131 master_flat * master_flat_hifreq = NULL;
132 hdrl_image * master_wave = NULL;
133 mef_extension_list * mefs = NULL;
134 cpl_table * mef_table = NULL;
135 cpl_table * mef_table2 = NULL;
136 located_imagelist * off_limlist = NULL;
137 cpl_mask * old_mask = NULL;
138 located_imagelist * on_limlist = NULL;
139 cpl_frameset * used_frameset = NULL;
140 cpl_image * wave_cal_image = NULL;
141 cpl_propertylist * wave_plist = NULL;
142
143 enu_check_error_code("%s():%d: An error is already set: %s",
144 cpl_func, __LINE__, cpl_error_get_where());
145
146 /* check input parameters */
147
148 cpl_ensure_code(frameset, CPL_ERROR_NULL_INPUT);
149 cpl_ensure_code(parlist, CPL_ERROR_NULL_INPUT);
150
151 /* set the msg verbosity level from environment variable CPL_MSG_LEVEL */
152
153 cpl_msg_set_level_from_env();
154 cpl_msg_severity severity = cpl_msg_get_level();
155 cpl_msg_info(cpl_func, "level %d", (int) severity);
156
157 cpl_msg_info(cpl_func, "fill_rejected parameter hardwired to True");
158 int fill_rejected = CPL_TRUE;
159
160 cpl_size x_probe = 1024;
161 cpl_size y_probe = 1024;
162
163 enu_check_error_code("Could not retrieve input parameters");
164
165 /* identify the RAW and CALIB frames in the input frameset */
166
167 eris_nix_dfs_set_groups(frameset);
168 enu_check_error_code("Could not identify RAW and CALIB frames");
169
170 used_frameset = cpl_frameset_new();
171
172 /* read the gain and linearity information, if available */
173
174 gain_lin = engl_gain_linearity_load_from_frameset(frameset,
175 ERIS_NIX_GAIN_PRO_CATG, ERIS_NIX_COEFFS_CUBE_PRO_CATG,
176 ERIS_NIX_NL_BPM_PRO_CATG, CPL_FALSE, used_frameset);
177 enu_check_error_code("error trying to read gain/linearity information");
178
179 /* read the master_dark if necessary (for linearization) */
180
181 if (gain_lin) {
182 master_dark = en_master_dark_load_from_frameset(frameset,
183 ERIS_NIX_MASTER_DARK_IMG_PRO_CATG, used_frameset);
184 enu_check_error_code("failed to read master dark from SoF");
185 }
186
187 /* read the master_bpm and high-freq flatfield */
188
189 master_bpm = en_master_bpm_load_from_frameset(frameset,
190 ERIS_NIX_MASTER_BPM_SKY_PRO_CATG, used_frameset, CPL_TRUE);
191 enu_check_error_code("failed to read master BPM from SoF");
192 master_flat_hifreq = en_master_flat_load_from_frameset(
193 frameset, ERIS_NIX_MASTER_FLAT_LSS_HIFREQ_PRO_CATG,
194 used_frameset, CPL_FALSE);
195 enu_check_error_code("failed to read master FLAT from SoF");
196
197 /* read in the lamp-on and lamp-off data */
198
199 on_limlist = enu_limlist_load_from_frameset(frameset,
200 ERIS_NIX_RAW_WAVE_LAMP_ON_DO_CATG, used_frameset);
201 off_limlist = enu_limlist_load_from_frameset(frameset,
202 ERIS_NIX_RAW_WAVE_LAMP_OFF_DO_CATG, used_frameset);
203
204 enu_check(on_limlist->size > 0, CPL_ERROR_ILLEGAL_INPUT,
205 "SoF contains no WAVE_LAMP_ON data");
206 enu_check(on_limlist->size == off_limlist->size, CPL_ERROR_ILLEGAL_INPUT,
207 "SoF does not contain WAVE_LAMP_ON/WAVE_LAMP_OFF pairs");
208
209 /* Do basic calibration of frames */
210
211 diff_himlist = hdrl_imagelist_new();
212 cpl_msg_info(cpl_func, "calculating lamp_on - lamp_off");
213 int flag_all_errors = ~(int)0;
214 enu_basic_calibrate(on_limlist, master_dark, gain_lin, master_flat_hifreq,
215 NULL, master_bpm, flag_all_errors, fill_rejected,
216 x_probe, y_probe);
217 enu_basic_calibrate(off_limlist, master_dark, gain_lin, master_flat_hifreq,
218 NULL, master_bpm, flag_all_errors, fill_rejected,
219 x_probe, y_probe);
220 enu_check_error_code("error performing basic calibration of frames");
221
222 /* difference the on and off images */
223
224 for (int on = 0; on < on_limlist->size; on++) {
225 hdrl_image_sub_image(on_limlist->limages[on]->himage,
226 off_limlist->limages[on]->himage);
227 hdrl_imagelist_set(diff_himlist,
228 hdrl_image_duplicate(on_limlist->
229 limages[on]->himage),
230 hdrl_imagelist_get_size(diff_himlist));
231 }
232
233 /* The raw lamp image is the mean of the differences */
234
235 hdrl_imagelist_collapse(diff_himlist, HDRL_COLLAPSE_MEAN, &master_wave,
236 &contrib);
237
238 /* construct vectors with the wavelengths and rough positions of the
239 expected lines. The numbers here are hard-wired for the NaCO test
240 data. For NIX the spectrum seen should be pretty consistent so
241 should just need to replace these numbers from lab measurements. */
242
243 cpl_size nlines = 12;
244 cpl_vector * line_guess_pos = cpl_vector_new(nlines);
245 cpl_vector * line_lambda = cpl_vector_new(nlines);
246 cpl_vector_set(line_guess_pos, 0, (double) 71);
247 cpl_vector_set(line_lambda, 0, 20621.9);
248 cpl_vector_set(line_guess_pos, 1, (double) 95);
249 cpl_vector_set(line_lambda, 1, 20740.0); /*blend*/
250 cpl_vector_set(line_guess_pos, 2, (double) 112);
251 cpl_vector_set(line_lambda, 2, 20816.7); /*blend*/
252 cpl_vector_set(line_guess_pos, 3, (double) 148);
253 cpl_vector_set(line_lambda, 3, 20991.8);
254 cpl_vector_set(line_guess_pos, 4, (double) 220);
255 cpl_vector_set(line_lambda, 4, 21338.7);
256 cpl_vector_set(line_guess_pos, 5, (double) 260);
257 cpl_vector_set(line_lambda, 5, 21540.1);
258 cpl_vector_set(line_guess_pos, 6, (double) 363);
259 cpl_vector_set(line_lambda, 6, 22045.6);
260 cpl_vector_set(line_guess_pos, 7, (double) 371);
261 cpl_vector_set(line_lambda, 7, 22083.2);
262 cpl_vector_set(line_guess_pos, 8, (double) 583);
263 cpl_vector_set(line_lambda, 8, 23139.5);
264 cpl_vector_set(line_guess_pos, 9, (double) 724);
265 cpl_vector_set(line_lambda, 9, 23851.5);
266 cpl_vector_set(line_guess_pos, 10, (double) 748);
267 cpl_vector_set(line_lambda, 10, 23973.1);
268 cpl_vector_set(line_guess_pos, 11, (double) 975);
269 cpl_vector_set(line_lambda, 11, 25132.1);
270
271 const cpl_size nx = hdrl_image_get_size_x(master_wave);
272 const cpl_size ny = hdrl_image_get_size_y(master_wave);
273 const cpl_size wave_cal_x = nx / 2;
274 line_pos = enwu_linepos_2d(master_wave, wave_cal_x, line_guess_pos);
275
276 /* now do 2d calibration fit between x, y pos of peak and lambda */
277
278 /* ..first construct the sample pos and wavelength arrays for the fit
279 - these must have no NaNs or the fit routine will fail */
280
281 cpl_size nsamples = 0;
282 for (cpl_size line_id = 0; line_id < nlines; line_id++) {
283 for (cpl_size ix = 0; ix < nx; ix++) {
284 double val = cpl_matrix_get(line_pos, ix, line_id);
285 if (!isnan(val)) {
286 nsamples++;
287 }
288 }
289 }
290
291 cpl_matrix * line_pos_fit = cpl_matrix_new(2, nsamples);
292 lambda_pos = cpl_vector_new(nsamples);
293 cpl_size isample = 0;
294 for (cpl_size line_id = 0; line_id < nlines; line_id++) {
295 for (cpl_size ix = 0; ix < nx; ix++) {
296 double val = cpl_matrix_get(line_pos, ix, line_id);
297 if (!isnan(val)) {
298 cpl_matrix_set(line_pos_fit, 0, isample, val);
299 cpl_matrix_set(line_pos_fit, 1, isample, (double) ix);
300 cpl_vector_set(lambda_pos, isample,
301 cpl_vector_get(line_lambda, line_id));
302 isample++;
303 }
304 }
305 }
306
307 cpl_polynomial * wave_cal = cpl_polynomial_new(2);
308 const cpl_size maxdeg2d[] = {4, 2};
309 cpl_polynomial_fit(wave_cal, line_pos_fit, NULL, lambda_pos, NULL,
310 CPL_TRUE, NULL, maxdeg2d);
311 cpl_msg_info(cpl_func, "The wavelength calibration polynomial:");
312 cpl_polynomial_dump(wave_cal, stdout);
313
314 /* evaluate the calibration polynomial over the image */
315
316 wave_cal_image = cpl_image_new(nx, ny, CPL_TYPE_DOUBLE);
317 for (cpl_size iy = 1; iy < ny+1; iy++) {
318 for (cpl_size ix = 1; ix < nx+1; ix++) {
319 cpl_vector * pos = cpl_vector_new(2);
320 cpl_vector_set(pos, 0, (double) iy);
321 cpl_vector_set(pos, 1, (double) ix);
322 double val = cpl_polynomial_eval(wave_cal, pos);
323 cpl_image_set(wave_cal_image, ix, iy, val);
324 cpl_vector_delete(pos);
325 }
326 }
327
328 /* evaluate the fit residual over the image */
329
330 cpl_vector * wave_residual = cpl_vector_new(cpl_vector_get_size(
331 lambda_pos));
332 cpl_vector_fill_polynomial_fit_residual(wave_residual, lambda_pos,
333 NULL, wave_cal, line_pos_fit,
334 NULL);
335 const double residual_mad = cpl_vector_get_median_const(wave_residual);
336 cpl_msg_info(cpl_func, "MAD of residuals = %4.2f", residual_mad);
337
338 /* construct the propertylist to be saved with the MASTER_WAVE */
339
340 wave_plist = cpl_propertylist_new();
341 cpl_propertylist_append_string(wave_plist, CPL_DFS_PRO_CATG,
342 ERIS_NIX_MASTER_WAVE_PRO_CATG);
343
344 /* add QC parameters */
345
346 cpl_propertylist_append_double(wave_plist, "ESO QC CAL RESID",
347 residual_mad);
348 enu_check_error_code("error constructing output propertylist");
349
350 /* save the MASTER_WAVE to a DFS-compliant MEF file */
351
353
354 /* MEF for confidence */
355
356 mefs->mef[0] = enu_mef_new_image("CONFIDENCE", cpl_image_duplicate(
357 master_flat_hifreq->confidence), NULL);
358
359 /* MEF for LINE_POSITION - a table with the fitted peak position
360 for each line in each spectrum slice across the array */
361
362 mef_table = cpl_table_new(nsamples);
363 cpl_table_new_column(mef_table, "x", CPL_TYPE_DOUBLE);
364 cpl_table_new_column(mef_table, "ypos", CPL_TYPE_DOUBLE);
365 cpl_table_new_column(mef_table, "line_lambda", CPL_TYPE_DOUBLE);
366 for (cpl_size isample = 0; isample < nsamples; isample++) {
367 cpl_table_set(mef_table, "x", isample,
368 cpl_matrix_get(line_pos_fit, 1, isample));
369 cpl_table_set(mef_table, "ypos", isample,
370 cpl_matrix_get(line_pos_fit, 0, isample));
371 cpl_table_set(mef_table, "line_lambda", isample,
372 cpl_vector_get(lambda_pos, isample));
373 }
374 mefs->mef[1] = enu_mef_new_table("LINE_POSITION", mef_table, NULL);
375
376 /* MEF for WAVE_CAL, containing an image with the calibrated wavelength
377 of each pixel in the 2d spectrum */
378
379 mefs->mef[2] = enu_mef_new_image("WAVE_CAL", cpl_image_duplicate(
380 wave_cal_image), NULL);
381
382 /* MEF for LINE_RESIDUAL, a table with the fit residual at each line
383 position in each spectral slice */
384
385 mef_table2 = cpl_table_new(nsamples);
386 cpl_table_new_column(mef_table2, "x", CPL_TYPE_DOUBLE);
387 cpl_table_new_column(mef_table2, "ypos", CPL_TYPE_DOUBLE);
388 cpl_table_new_column(mef_table2, "lambda_residual", CPL_TYPE_DOUBLE);
389 for (cpl_size isample = 0; isample < nsamples; isample++) {
390 cpl_table_set(mef_table2, "x", isample,
391 cpl_matrix_get(line_pos_fit, 1, isample));
392 cpl_table_set(mef_table2, "ypos", isample,
393 cpl_matrix_get(line_pos_fit, 0, isample));
394 cpl_table_set(mef_table2, "lambda_residual", isample,
395 cpl_vector_get(wave_residual, isample));
396 }
397 mefs->mef[3] = enu_mef_new_table("LINE_RESIDUAL", mef_table2, NULL);
398
399 enu_dfs_save_himage(frameset, parlist, used_frameset, master_wave,
400 on_limlist->limages[0]->frame, mefs,
401 RECIPE_NAME, wave_plist, PACKAGE "/" PACKAGE_VERSION,
402 "master_wave.fits");
403
404 enu_check_error_code("Failed to save MASTER_WAVE");
405
406cleanup:
407 cpl_image_delete(contrib);
408 hdrl_imagelist_delete(diff_himlist);
409 engl_gain_linearity_delete((gain_linearity *) gain_lin);
410 cpl_matrix_delete(line_pos);
411 cpl_vector_delete(lambda_pos);
412 en_master_bpm_delete(master_bpm);
413 en_master_dark_delete(master_dark);
414 en_master_flat_delete(master_flat_hifreq);
416 cpl_table_delete(mef_table);
417 cpl_table_delete(mef_table2);
418 enu_located_imagelist_delete(off_limlist);
419 cpl_mask_delete(old_mask);
421 cpl_frameset_delete(used_frameset);
422 cpl_image_delete(wave_cal_image);
423 cpl_propertylist_delete(wave_plist);
424
425 return (int)cpl_error_get_code();
426}
cpl_error_code eris_nix_dfs_set_groups(cpl_frameset *set)
Set the group as RAW or CALIB in a frameset.
Definition: eris_nix_dfs.c:58
cpl_error_code enu_dfs_save_himage(cpl_frameset *allframes, const cpl_parameterlist *parlist, const cpl_frameset *provenance, const cpl_boolean prov_raw, const hdrl_image *image, const hdrl_imagelist *imagelist, const mef_extension_list *mefs, const char *recipe, const cpl_frame *inherit_frame, const cpl_propertylist *applist, const cpl_propertylist *wcs_plist, const char *pipe_id, const char *filename)
Save an hdrl_image/imagelist as a DFS-compliant MEF pipeline product.
Definition: eris_nix_dfs.c:423
void en_master_bpm_delete(master_bpm *target)
Delete a 'master_bpm' struct.
master_dark * en_master_dark_load_from_frameset(const cpl_frameset *frameset, const char *tag, cpl_frameset *used)
Load a 'master_dark' struct from a frameset.
void en_master_dark_delete(master_dark *target)
Delete a 'master_dark' struct.
void enu_located_imagelist_delete(located_imagelist *limlist)
Delete a located_imagelist and its contents.
mef_extension * enu_mef_new_image(const char *name, const cpl_image *data, const cpl_propertylist *plist)
Create a mef_extension to hold a cpl_image.
cpl_error_code enu_basic_calibrate(located_image *limage, const int read_offsets, const cpl_table *refine_wcs, const master_dark *mdark, const gain_linearity *gain_lin, const master_flat *flatfield_1, const master_flat *flatfield_2, const master_bpm *mbad_pix_map, const int flag_mask, const char *fill_rejected, const double fill_value, const cpl_size x_probe, const cpl_size y_probe)
Do basic calibration of located_image (single or cube)
mef_extension * enu_mef_new_table(const char *name, const cpl_table *table, const cpl_propertylist *plist)
Create a mef_extension struct holding a cpl_table.
mef_extension_list * enu_mef_extension_list_new(cpl_size size)
Construct a new mef_extension_list.
located_imagelist * enu_limlist_load_from_frameset(cpl_frameset *frameset, const char *tag, cpl_frameset *used)
Load tagged data from a frameset into a located_imagelist.
void enu_mef_extension_list_delete(mef_extension_list *list)
Delete a mef_extension_list and its contents.
cpl_matrix * enwu_linepos_2d(const hdrl_image *spectrum2d, const cpl_size slice_index, const cpl_vector *guess_pos)
Fit the line peaks of a wave calibration spectrum.
cpl_error_code hdrl_image_sub_image(hdrl_image *self, const hdrl_image *other)
Subtract two images, store the result in the first image.
hdrl_image * hdrl_image_duplicate(const hdrl_image *himg)
copy hdrl_image
Definition: hdrl_image.c:391
cpl_size hdrl_image_get_size_y(const hdrl_image *self)
return size of Y dimension of image
Definition: hdrl_image.c:540
cpl_size hdrl_image_get_size_x(const hdrl_image *self)
return size of X dimension of image
Definition: hdrl_image.c:525
cpl_error_code hdrl_imagelist_set(hdrl_imagelist *himlist, hdrl_image *himg, cpl_size pos)
Insert an image into an imagelist.
void hdrl_imagelist_delete(hdrl_imagelist *himlist)
Free all memory used by a hdrl_imagelist object including the images.
cpl_size hdrl_imagelist_get_size(const hdrl_imagelist *himlist)
Get the number of images in the imagelist.
cpl_error_code hdrl_imagelist_collapse(const hdrl_imagelist *himlist, const hdrl_parameter *param, hdrl_image **out, cpl_image **contrib)
collapsing of image list
hdrl_imagelist * hdrl_imagelist_new(void)
Create an empty imagelist.