ERIS Pipeline Reference Manual 1.9.2
eris_nix_lss_startrace.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 <libgen.h>
36#include <string.h>
37
38#include "eris_utils.h"
39#include "eris_nix_utils.h"
40#include "eris_nix_lss_utils.h"
41#include "eris_pfits.h"
42#include "eris_dfs.h"
43#include "eris_nix_dfs.h"
44#include "eris_utils.h"
45#include <hdrl.h>
46
47#include <cpl.h>
48
49/*-----------------------------------------------------------------------------
50 Static variables
51 -----------------------------------------------------------------------------*/
52
53static const char eris_nix_lss_startrace_description[] =
54"This recipe reduces a set of "ERIS_NIX_SKYSUB_OBJECT_LSS_JITTER_PRO_CATG" or\n"
55ERIS_NIX_SKYSUB_STD_LSS_JITTER_PRO_CATG" frames to produce a "
56ERIS_NIX_MASTER_STARTRACE_PRO_CATG" file.\n"
57"\n"
58"This file can be used to rectify the 2d spectra so that the slit axis\n"
59"is horizontal and the dispersion axis vertical and wavelength\n"
60"calibrated.\n"
61"\n"
62"Input files:\n"
63"\n"
64" DO CATG Explanation Req. #Frames\n"
65" ------- ----------- --- -------\n"
66" "ERIS_NIX_SKYSUB_OBJECT_LSS_JITTER_PRO_CATG
67 " sky-subtracted Y >5\n"
68" target frames.\n"
69" or\n"
70" "ERIS_NIX_SKYSUB_OBJECT_LSS_JITTER_PRO_CATG
71 " sky-subtracted Y >5\n"
72" standard frames.\n"
73"\n"
74"Output files:\n"
75"\n"
76" DO CATG Explanation \n"
77" ------- ----------- \n"
78" "ERIS_NIX_MASTER_STARTRACE_PRO_CATG
79 " file containing the\n"
80" warp polynomials\n"
81" for correcting the\n"
82" LSS spectrum.\n"
83" "ERIS_NIX_LSS_TRACE_FIT
84 " debug files containing\n"
85" the startrace image\n"
86" and the polynomial fit\n"
87" to the trace. This can\n"
88" be plotted with the\n"
89" 'plot_trace' method\n"
90" of 'startrace_plot.py'\n"
91" "ERIS_NIX_LSS_STARTRACE_SKY
92 " debug file containing\n"
93" the median 2d sky\n"
94" background spectrum\n"
95" of the observations.\n"
96" This can be plotted\n"
97" with the 'plot_lines'\n"
98" method of\n"
99" 'startrace_plot.py'\n"
100"\n"
101" The "ERIS_NIX_MASTER_STARTRACE_PRO_CATG
102 " result will be in a FITS file named \n"
103" 'master_wave.<first input filename>', with extensions:\n"
104" - I_POLYNOMIAL, a table with polynomial coefficients for\n"
105" mapping x,lambda => x_actual.\n"
106" - J_POLYNOMIAL, a table with polynomial coefficients for\n"
107" mapping x,lambda => y_actual.\n"
108"\n"
109" There will be one "ERIS_NIX_LSS_TRACE_FIT" debug files \n"
110" for each input image, named 'startrace.<input filename>.\n"
111"\n"
112" The "ERIS_NIX_LSS_STARTRACE_SKY" file will be named\n"
113" 'startrace_sky.<first input filename>.fits'.\n"
114"\n"
115" Notes on the method.\n"
116" It is assumed that the jitter pattern moves the object up and\n"
117" down a line along the slit direction. The reference position\n"
118" and the offset of each jitter from it are calculated as\n"
119" follows:\n"
120" 1/ The reference is jitter 0. Often this lies near the \n"
121" middle of the pattern and the object is manually \n"
122" positioned near the middle of the slit.\n"
123" 2/ Derive the slit-line PA (0 = N, increasing E) from the\n"
124" extrema of the jitter centres.\n"
125" 3/ Report how far each jitter pointing lies from the\n"
126" slit-line.\n"
127" 4/ Estimate the offset of each jitter relative to the\n"
128" reference jitter along the slit-line.\n"
129"\n"
130" Divide the slit response into the jitter images. The slit\n"
131" response is calculated by collapsing the background sky\n"
132" spectrum in the dispersion direction then normalising by\n"
133" its median value.\n"
134"\n"
135" Measure the star trace in each frame. The trace is the \n"
136" spectrum of the star dispersed over the detector. For each\n"
137" frame proceed as follows:\n"
138" 1/ Get a rough position for the trace. This is done\n"
139" by locating the maximum signal across a slice of the\n"
140" detector that has few bad pixels and is well\n"
141" illuminated.\n"
142" 2/ Cycle up and down from the 'start' slice, fitting\n"
143" a parabola to the points straddling the rough centre\n"
144" of the trace to get a more accurate position. The\n"
145" rough trace position for the next slice is set to\n"
146" the result for the current slice, so that the process\n"
147" can follow the trace as it slowly slants across the\n"
148" detector columns.\n"
149" 3/ 'Capture' the trace by fitting a polynomial to the line\n"
150" of measured peaks for each slice.\n"
151"\n"
152" Calculate the median of the sky backgrounds of all frames.\n"
153"\n"
154" Measure several atmospheric features/lines across the 2d\n"
155" background sky spectrum. The process is similar to that\n"
156" for measuring the traces:\n"
157" 1/ Use a library of lines (currently hardwired into the\n"
158" the code) identifying the line wavelength, y coord\n"
159" at x=1024, and line width.\n"
160" 2/ For each line, cycle left and right from the 'start'\n"
161" column, fitting a parabola to the points straddling\n"
162" the rough centre of the feature to get a more accurate\n"
163" position. Unlike following a trace the rough position\n"
164" for the next column is always set to that for x=1024.\n"
165" This is because some of the spectral feaures are weak\n"
166" and the fit unreliable, and the lines do not wander as\n"
167" much as the traces.\n"
168" 3/ Fit a polynomial to the line of measured peaks along\n"
169" each feature.\n"
170"\n"
171" Do 2d calibration fit between ideal position and actual:\n"
172" x,lambda => x_actual\n"
173" x,lambda => y_actual\n"
174"\n"
175" The calibrated 2d spectrum will have 2048 pixels running\n"
176" from 3.045um[0] to 4.107[2047] in dispersion [y], and 2048\n"
177" pixels with 0.013arcsec pixels, centred at 1024 in slit\n"
178" offset [x].\n"
179"\n"
180" With trace behaviour described by the polynomial fits\n"
181" derived earlier and dispersive behaviour by the polynomial\n"
182" fits to known atmospheric features:\n"
183" 1/ Construct the slit offset, wavelength => x_actual and\n"
184" offset,wavelength => y_actual sample arrays to be fit.\n"
185" Do this by populating the sample arrays with the\n"
186" intersection x,y of the fits to each trace and\n"
187" atmospheric line.\n"
188" 2/ Do a 2d polynomial fit to x,lambda => x_actual.\n"
189" Do a 2d polynomial fit to x,lambda => y_actual.\n"
190"\n"
191" Save the results to FITS.\n"
192"\n";
193
194#define RECIPE_NAME "eris.eris_nix_lss_startrace"
195
196/*-----------------------------------------------------------------------------
197 Private function prototypes
198 -----------------------------------------------------------------------------*/
199
200cpl_recipe_define(eris_nix_lss_startrace, ERIS_BINARY_VERSION,
201 "John Lightfoot",
202 PACKAGE_BUGREPORT, "2017",
203 "Calculate a MASTER_WAVE",
204 eris_nix_lss_startrace_description);
205
206/*-----------------------------------------------------------------------------
207 Function code
208 -----------------------------------------------------------------------------*/
209
210/*----------------------------------------------------------------------------*/
218/*----------------------------------------------------------------------------*/
219
220static cpl_error_code eris_nix_lss_startrace_fill_parameterlist(
221 cpl_parameterlist *self) {
222
223 if (cpl_error_get_code() != CPL_ERROR_NONE) return cpl_error_get_code();
224
225 /* the flux threshold - frames with a median above this will be
226 deemed 'saturated' and not used */
227
228 cpl_parameter * p = NULL;
229 p = cpl_parameter_new_value(RECIPE_NAME".saturation_threshold",
230 CPL_TYPE_DOUBLE, "frame saturation threshold", RECIPE_NAME, 5000.0);
231 cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "saturation_threshold");
232 cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
233 cpl_parameterlist_append(self, p);
234
235 /* cleanup */
236
237 return 0;
238}
239
240/*----------------------------------------------------------------------------*/
247/*----------------------------------------------------------------------------*/
248
249static int eris_nix_lss_startrace(cpl_frameset * frameset,
250 const cpl_parameterlist * parlist) {
251
252 cpl_propertylist * applist = NULL;
253 cpl_image * contrib = NULL;
254 cpl_vector * crval1_vector = NULL;
255 cpl_vector * crval2_vector = NULL;
256 located_imagelist * jitters = NULL;
257 cpl_polynomial ** line_polys = NULL;
258 mef_extension_list * mefs = NULL;
259 located_imagelist * object_jitters = NULL;
260 hdrl_imagelist * skylist = NULL;
261 hdrl_image * sky_background = NULL;
262 cpl_vector * slit_offset = NULL;
263 cpl_matrix * startrace_pos = NULL;
264 located_imagelist * std_jitters = NULL;
265 cpl_polynomial ** trace_polys = NULL;
266 cpl_frameset * used = NULL;
267
268 /* check input parameters */
269
270 cpl_ensure_code(frameset, CPL_ERROR_NULL_INPUT);
271 cpl_ensure_code(parlist, CPL_ERROR_NULL_INPUT);
272
273 /* set the msg verbosity level from environment variable CPL_MSG_LEVEL */
274
275 cpl_msg_set_level_from_env();
276 cpl_msg_severity severity = cpl_msg_get_level();
277 cpl_msg_info(cpl_func, "level %d", (int) severity);
278
279 /* check for invalid input */
280 if(eris_files_dont_exist(frameset) != CPL_ERROR_NONE) {
281 return CPL_ERROR_BAD_FILE_FORMAT;
282 }
283 enu_check_error_code("Could not retrieve input parameters");
284
285 /* identify the RAW and CALIB frames in the input frameset */
286
287 eris_nix_dfs_set_groups(frameset);
288 enu_check_error_code("Could not identify RAW and CALIB frames");
289
290 used = cpl_frameset_new();
291
292 /* read in the sky-subtracted data */
293
294 object_jitters = enu_limlist_load_from_frameset(frameset,
295 ERIS_NIX_SKYSUB_OBJECT_LSS_JITTER_PRO_CATG,
296 used);
297 std_jitters = enu_limlist_load_from_frameset(frameset,
298 ERIS_NIX_SKYSUB_STD_LSS_JITTER_PRO_CATG,
299 used);
300 enu_check_error_code("Error loading frameset");
301
302 if (object_jitters->size > 0) {
303 enu_check(std_jitters->size == 0, CPL_ERROR_ILLEGAL_INPUT,
304 "SoF contains both object and std data");
305 jitters = object_jitters;
306 enu_located_imagelist_delete(std_jitters);
307 std_jitters = NULL;
308 } else if (std_jitters->size > 0) {
309 jitters = std_jitters;
310 enu_located_imagelist_delete(object_jitters);
311 object_jitters = NULL;
312 }
313 enu_check(jitters!=NULL, CPL_ERROR_ILLEGAL_INPUT,
314 "SoF contains no valid data");
315
316 /* NB jitter centering code may be largely duplicated in straighten recipe */
317 /* estimate the centre of the jitter pattern
318 .. the jitter centres should lie along a line on the sky
319 .. as the object is move up and down the slit.
320 .. Consequently, find the ends of this line by looking at
321 .. extreme points. */
322
323 crval1_vector = cpl_vector_new(jitters->size);
324 crval2_vector = cpl_vector_new(jitters->size);
325 for (cpl_size i = 0; i < jitters->size; i++) {
326 double crval1 = cpl_propertylist_get_double(
327 jitters->limages[i]->plist,
328 "CRVAL1");
329 cpl_vector_set(crval1_vector, i, crval1);
330 double crval2 = cpl_propertylist_get_double(
331 jitters->limages[i]->plist,
332 "CRVAL2");
333 cpl_vector_set(crval2_vector, i, crval2);
334 cpl_msg_info(cpl_func, "Jitter %d at RA=%10.7f Dec=%10.7f",
335 (int)i, crval1, crval2);
336 }
337
338 double lat = cpl_vector_get_mean(crval2_vector);
339 double coslat = cos(lat / 57.2958);
340 cpl_msg_info(cpl_func, "lat %f coslat %f", lat, coslat);
341
342 cpl_size crval1_maxpos = cpl_vector_get_maxpos(crval1_vector);
343 double crval1_max = cpl_vector_get(crval1_vector, crval1_maxpos);
344 cpl_size crval1_minpos = cpl_vector_get_minpos(crval1_vector);
345 double crval1_min = cpl_vector_get(crval1_vector, crval1_minpos);
346
347 cpl_size crval2_maxpos = cpl_vector_get_maxpos(crval2_vector);
348 double crval2_max = cpl_vector_get(crval2_vector, crval2_maxpos);
349 cpl_size crval2_minpos = cpl_vector_get_minpos(crval2_vector);
350 double crval2_min = cpl_vector_get(crval2_vector, crval2_minpos);
351
352 double enda[2] = {0.0, 0.0};
353 double endb[2] = {0.0, 0.0};
354 if (fabs(crval1_max - crval1_min) > fabs(crval2_max - crval2_min)) {
355 enda[0] = crval1_min;
356 enda[1] = cpl_vector_get(crval2_vector, crval1_minpos);
357 endb[0] = crval1_max;
358 endb[1] = cpl_vector_get(crval2_vector, crval2_maxpos);
359 } else {
360 enda[0] = cpl_vector_get(crval1_vector, crval2_minpos);
361 enda[1] = crval2_min;
362 endb[0] = cpl_vector_get(crval1_vector, crval2_maxpos);
363 endb[1] = crval2_max;
364 }
365 cpl_msg_info(cpl_func, "Jitter line end points (RA, DEC): "
366 "(%10.7f, %10.7f) and (%10.7f, %10.7f)",
367 enda[0], enda[1], endb[0], endb[1]);
368
369 /* define the reference position as a point halfway along the
370 slit line pattern */
371
372 double centre[2] = {(enda[0] + endb[0]) / 2.0,
373 (enda[1] + endb[1]) / 2.0};
374
375 /* get direction of slit line, assume axis increases to left */
376
377 double slit_line[2] = {(endb[0]-enda[0]) * coslat, endb[1]-enda[1]};
378 double slit_norm = sqrt(pow(slit_line[0],2) + pow(slit_line[1],2));
379 slit_line[0] /= slit_norm;
380 slit_line[1] /= slit_norm;
381 enu_check_error_code("error finding line of slit");
382 cpl_msg_info(cpl_func, "..slit direction vector (long, lat) = (%f, %f)",
383 slit_line[0], slit_line[1]);
384
385 /* calculate offset of each jitter along slit line, not sure how best
386 to do this but vectors seem simplest */
387
388 double cd1_1 = cpl_propertylist_get_double(jitters->limages[0]->
389 plist, "CD1_1");
390 double cd2_1 = cpl_propertylist_get_double(jitters->limages[0]->
391 plist, "CD2_1");
392 double pixsize = sqrt(cd1_1 * cd1_1 + cd2_1 * cd2_1);
393
394 slit_offset = cpl_vector_new(jitters->size);
395 for (cpl_size i = 0; i < jitters->size; i++) {
396 double offset = -1000.0;
397 /* be wary of 0 components in slit_line direction vector */
398 if (fabs(slit_line[0]) > 1e-6) {
399 offset = (cpl_vector_get(crval1_vector, i) - centre[0]) * coslat /
400 slit_line[0];
401 } else if (fabs(slit_line[1]) > 1e-6) {
402 offset = (cpl_vector_get(crval2_vector, i) - centre[1]) /
403 slit_line[1];
404 }
405
406 /* check how far each jitter is from the notional slit line
407 ..are the numbers reasonable? */
408 double remainder[2] = {0.0,0.0};
409 remainder[0] = ((cpl_vector_get(crval1_vector, i) - centre[0]) * coslat -
410 (offset * slit_line[0])) / pixsize;
411 remainder[1] = ((cpl_vector_get(crval2_vector, i) - centre[1]) -
412 (offset * slit_line[1])) / pixsize;
413 if (fabs(remainder[0]) > 1 || fabs(remainder[1]) > 1) {
414 cpl_msg_warning(cpl_func, "...offset remainder (%f, %f) pixels",
415 remainder[0], remainder[1]);
416 cpl_msg_warning(cpl_func, "...slit offset does not fall on line");
417 }
418
419 cpl_vector_set(slit_offset, i, offset);
420 cpl_msg_info(cpl_func, "..slit offset %d %f", (int)i, offset);
421 }
422 enu_check_error_code("error calculating slit offsets");
423
424 /* calculate nominal slit-offset for each jitter */
425 /* 0 offset would put object at column 1024 */
426
427 cpl_vector * nominal_offset = cpl_vector_new(jitters->size);
428 for (cpl_size i = 0; i < jitters->size; i++) {
429 cpl_vector_set(nominal_offset, i, 1024.0 - cpl_vector_get(
430 slit_offset, i) / pixsize);
431 }
432 cpl_msg_info(cpl_func, "Nominal offset:");
433 cpl_vector_dump(nominal_offset, NULL);
434
435 /* calculate slit response and divide into jitter images */
436
438
439 /* measure the star trace in each frame */
440
441 cpl_size ntrace = jitters->size;
442 trace_polys = cpl_calloc(ntrace, sizeof(cpl_polynomial *));
443
444 const cpl_size nx = hdrl_image_get_size_x(jitters->limages[0]->himage);
445 const cpl_size ny = hdrl_image_get_size_y(jitters->limages[0]->himage);
446
447 /* horizontal start slice index chosen to avoid big patches of bad
448 pixels */
449
450 const cpl_size start_slice = 1800;
451 startrace_pos = cpl_matrix_new(ny, ntrace);
452 skylist = hdrl_imagelist_new();
453
454 /* loop through the jitters, fitting the trace in each, and
455 accumulating data for the median sky background */
456
457 for (cpl_size j = 0; j < ntrace; j++) {
458
459 /* accumulate the sky backgrounds as we go for use later */
460
461 hdrl_image * imcopy = hdrl_image_duplicate(
462 jitters->limages[j]->bkg);
463 hdrl_imagelist_set(skylist, imcopy,
464 hdrl_imagelist_get_size(skylist));
465
466 /* get a rough position for the trace at start_slice. This
467 should be within 4 pixels or so of the correct answer to
468 cover the trace peak or the refined fit will not work.
469 Try to be robust - the choice of start_slice should help
470 by being on a well-behaved, well illuminated part of the
471 detector */
472
473 cpl_msg_info(cpl_func, "jitter %d", (int)j);
474 char * name_copy = cpl_strdup(cpl_frame_get_filename(
475 jitters->limages[j]->frame));
476 cpl_msg_info(cpl_func, "%s", basename(name_copy));
477 cpl_free(name_copy);
478 cpl_msg_info(cpl_func, "nominal offset %7.2f",
479 cpl_vector_get(nominal_offset, j));
480
481 cpl_vector * maximum_pos = cpl_vector_new(10);
482 cpl_vector * maximum_val = cpl_vector_new(10);
483 for (cpl_size iy = start_slice-5; iy < start_slice+5; iy++) {
484 cpl_vector * slice1d = cpl_vector_new_from_image_row(
486 jitters->limages[j]->himage),
487 iy+1);
488 cpl_vector_set(maximum_pos, iy-(start_slice-5),
489 cpl_vector_get_maxpos(slice1d));
490 cpl_vector_set(maximum_val, iy-(start_slice-5),
491 cpl_vector_get_max(slice1d));
492
493 cpl_vector_delete(slice1d);
494 }
495 //cpl_vector_dump(maximum_pos, NULL);
496 //cpl_vector_dump(maximum_val, NULL);
497 double start_guess_pos = cpl_vector_get_median(maximum_pos);
498 double trace_val = cpl_vector_get_median(maximum_val);
499
500 cpl_vector_delete(maximum_pos);
501 cpl_vector_delete(maximum_val);
502
503 /* now go for a more accurate value with a fit for each
504 y-slice - moving up and down from start_slice */
505
506 double guess_pos = start_guess_pos;
507
508 /* ..working up from start_slice */
509
510 for (cpl_size iy = start_slice; iy < ny; iy++) {
511 cpl_vector * slice1d = cpl_vector_new_from_image_row(
513 jitters->limages[j]->himage),
514 iy+1);
515
516 double fitted_pos = enlu_linepos_1d(slice1d, guess_pos, 4);
517 if (!isnan(fitted_pos) &&
518 (cpl_vector_get(slice1d, (int)fitted_pos) > 0.1 * trace_val)) {
519
520 /* fit didn't fail and also fit position looks to
521 contain some flux */
522
523 /* set the fit peak as the measured position of the line,
524 set next_guess_pos to the actual fitted_pos so that
525 it can follow slow variations with iy */
526
527 cpl_matrix_set(startrace_pos, iy, j, fitted_pos);
528 guess_pos = fitted_pos;
529 cpl_msg_info(cpl_func, "fitted pos %d %7.2f",
530 (int)iy, fitted_pos);
531 } else {
532 cpl_matrix_set(startrace_pos, iy, j, fitted_pos);
533 }
534 cpl_vector_delete(slice1d);
535 }
536 enu_check_error_code("error following trace up");
537
538 /* ..and working down from start_slice */
539
540 guess_pos = start_guess_pos;
541
542 for (cpl_size iy = start_slice; iy >= 0; iy--) {
543
544 cpl_vector * slice1d = cpl_vector_new_from_image_row(
546 jitters->limages[j]->himage),
547 iy+1);
548
549 double fitted_pos = enlu_linepos_1d(slice1d, guess_pos, 4);
550 cpl_vector_delete(slice1d);
551
552 if (!isnan(fitted_pos) &&
553 (cpl_vector_get(slice1d, (int)fitted_pos) > 0.1 * trace_val)) {
554 cpl_matrix_set(startrace_pos, iy, j, fitted_pos);
555 guess_pos = fitted_pos;
556 cpl_msg_info(cpl_func, "fitted pos %d %7.2f",
557 (int)iy, fitted_pos);
558 } else {
559 cpl_matrix_set(startrace_pos, iy, j, fitted_pos);
560 }
561 }
562 enu_check_error_code("error following trace down");
563
564 /* now capture trace by fitting poly to peak through y */
565
566 trace_polys[j] = cpl_polynomial_new(1);
567 cpl_vector * spectrum = cpl_vector_new(ny);
568 {
569 cpl_matrix * samppos1d = cpl_matrix_new(1, ny);
570 cpl_vector * fitvals = cpl_vector_new(ny);
571 const cpl_boolean sampsym = CPL_FALSE;
572 const cpl_size maxdeg1d = 3;
573
574 cpl_size isamp = 0;
575
576 for (cpl_size iy = 0; iy < ny; iy++) {
577 double fitted_pos = cpl_matrix_get(startrace_pos, iy, j);
578 if (!isnan(fitted_pos)) {
579 cpl_matrix_set(samppos1d, 0, isamp, (double)iy);
580 cpl_vector_set(fitvals, isamp, fitted_pos);
581 isamp++;
582 }
583 }
584
585 cpl_matrix_set_size(samppos1d, 1, isamp);
586 cpl_vector_set_size(fitvals, isamp);
587 //for (cpl_size i=0; i<isamp; i++) {
588 // cpl_msg_info(cpl_func, "%3.1f %7.2f",
589 // cpl_matrix_get(samppos1d, 0, i),
590 // cpl_vector_get(fitvals, i));
591 //}
592
593 cpl_polynomial_fit(trace_polys[j], samppos1d, &sampsym,
594 fitvals, NULL, CPL_FALSE, NULL, &maxdeg1d);
595
596 cpl_polynomial_dump(trace_polys[j], stdout);
597 }
598 enu_check_error_code("error fitting trace");
599
600 /* calculate the sky bkg spectrum along the trace */
601
602 {
603 cpl_image * double_image = cpl_image_cast(
605// jitters->limages[j]->himage),
606 jitters->limages[j]->bkg),
607 CPL_TYPE_DOUBLE);
608 double * data = cpl_image_get_data(double_image);
609
610 cpl_image * double_confidence = cpl_image_cast(
611 jitters->limages[j]->confidence,
612 CPL_TYPE_DOUBLE);
613 double * confidence = cpl_image_get_data(double_confidence);
614
615 for (cpl_size iy = 0; iy < ny; iy++) {
616 double pos = cpl_polynomial_eval_1d(trace_polys[j], (double)iy, NULL);
617 double val = 0.0;
618 double sumwt = 0.0;
619
620// /* Lanczos with a = 2. wt = sinc(x) * sinc(x/a) */
621//
622// for (cpl_size dy=-2; dy <= 2; dy++) {
623// for (cpl_size dx=-2; dx <= 2; dx++) {
624// cpl_size jdatum = iy + dy;
625// cpl_size idatum = (int)pos + dx;
626
627// if (jdatum >= 0 && jdatum < ny &&
628// idatum >= 0 && idatum < nx) {
629
630// /* point to interpolate to is (pos, iy) */
631
632// double r = sqrt(pow(pos-idatum, 2) + pow(dy, 2));
633// double wt = 0.0;
634// if (r < 1.0e-10) {
635// wt = 1.0;
636// } else if (r < 2.0) {
637// wt = 2.0 * sin(M_PI * r) * sin(M_PI * r / 2.0) /
638// pow(M_PI * r, 2);
639// }
640
641// val+= (wt * (data[jdatum * nx + idatum] *
642// confidence[jdatum * nx + idatum]));
643// sumwt += (wt * confidence[jdatum * nx + idatum]);
644
652// }
653// }
654// }
655
656 /* average along row centred on pos */
657 for (cpl_size dx=-20; dx <= 20; dx++) {
658 cpl_size idatum = (int)pos + dx;
659
660 if (idatum >= 0 && idatum < nx) {
661 double wt = 1.0;
662 val+= (wt * (data[iy * nx + idatum] *
663 confidence[iy * nx + idatum]));
664 sumwt += (wt * confidence[idatum * nx + idatum]);
665
666// if (iy==914) {
667// cpl_msg_info(cpl_func, "%f %d %d %f %f %f %f %d %d %f %f",
668// pos, (int)dx, (int)dy, r, wt, val, sumwt,
669// (int)idatum, (int)jdatum,
670// data[jdatum * nx + idatum],
671// confidence[jdatum * nx + idatum]);
672// }
673 }
674 }
675
676 if (fabs(sumwt) > 0) {
677 val /= sumwt;
678 }
679 cpl_vector_set(spectrum, iy, val);
680 }
681
682 cpl_image_delete(double_image);
683
684 /* save the trace info */
685
686 char * out_fname = enu_repreface(cpl_frame_get_filename(
687 jitters->limages[j]->frame),
688 "startrace");
689 enlu_trace_save(ERIS_NIX_LSS_TRACE_FIT,
690 jitters->limages[j]->himage,
691 jitters->limages[j]->confidence,
692 1, (const cpl_polynomial **) &(trace_polys[j]),
693 1, (const cpl_vector **) &spectrum,
694 0, NULL,
695 frameset,
696 parlist,
697 out_fname,
698 RECIPE_NAME);
699 cpl_free(out_fname);
700 }
701 enu_check_error_code("error calculating trace spectrum / saving trace");
702 }
703
704 /* Find the median of the sky backgrounds */
705
706 hdrl_imagelist_collapse(skylist, HDRL_COLLAPSE_MEDIAN, &sky_background,
707 &contrib);
708 enu_check_error_code("error collapsing sky backgrounds");
709
710 /* now trace atmospheric features/lines */
711
712 // used wavelengths.py to try and cross match lines with ESO skycalc spectrum
713 #define NLINES 6
714 double wavelength[NLINES] = {3.262,
715 3.316,
716 3.392,
717 3.428,
718 3.456,
719 3.901};
720 double expected_iy[NLINES] = {364,
721 471,
722 617,
723 688,
724 738,
725 1600};
726 double half_width[NLINES] = {10.0,
727 10.0,
728 10.0,
729 10.0,
730 10.0,
731 50.0};
732
733 line_polys = cpl_calloc(NLINES, sizeof(cpl_polynomial *));
734
735 {
736 /* subtract a slope from the sky spectrum to flatten it - this makes
737 it easier for the fitting code to 'follow' absorption features */
738
739 for (cpl_size iy = 1; iy <= ny; iy++) {
740 for (cpl_size ix = 1; ix <= nx; ix++) {
741 int reject = 0;
742 hdrl_value value = hdrl_image_get_pixel(sky_background,
743 ix, iy,
744 &reject);
745 if (reject==0) {
746 value.data = value.data - (10.0 + 71.0 * (iy-207.0) / 1626);
747 hdrl_image_set_pixel(sky_background, ix, iy, value);
748 }
749 }
750 }
751
752 /* Loop through the absorption features expected in the sky spectrum.
753 ..start in the middle of the slit length where we think we know
754 roughly where the feature is
755 ..loop up and down the slit and follow the line position by fitting
756 it at each position
757 ..whether to the expected position or that from the last fit as the
758 guess for next line fit is uncertain. Using the last result as the
759 guess for next is unstable in patchy parts of the detector - for
760 now stick with the expected position as more robust */
761
762 cpl_matrix * line_pos = cpl_matrix_new(nx, NLINES);
763
764 for (cpl_size line = 0; line < NLINES; line++) {
765 double guess_pos = expected_iy[line];
766 cpl_size xstart = 1024;
767
768 /* now go for a more accurate value with a fit for each
769 slice - moving away from the start_slice in each
770 direction */
771
772 /* ..working up from start_slice */
773
774 for (cpl_size ix = xstart; ix < nx; ix++) {
775
776 cpl_vector * slice1d = cpl_vector_new_from_image_column(
778 sky_background),
779 ix+1);
780
781 double fitted_pos = enlu_linepos_1d(slice1d, guess_pos,
782 half_width[line]);
783 cpl_matrix_set(line_pos, ix, line, fitted_pos);
784
785 if (!isnan(fitted_pos)) {
786 cpl_msg_debug(cpl_func, "line %d %e %e", (int)ix,
787 guess_pos, fitted_pos);
788
789 /* NO, stick with the initial guess as more robust */
790
791 /* set the fit peak as the measured position of the line,
792 set next_guess_pos to the actual fitted_pos so that
793 it can follow slow variations with iy */
794 //guess_pos = fitted_pos;
795 }
796 cpl_vector_delete(slice1d);
797 }
798
799 /* ..and working down from start_slice */
800
801 for (cpl_size ix = xstart; ix >=0; ix--) {
802
803 cpl_vector * slice1d = cpl_vector_new_from_image_column(
805 sky_background),
806 ix+1);
807
808 double fitted_pos = enlu_linepos_1d(slice1d, guess_pos,
809 half_width[line]);
810 cpl_matrix_set(line_pos, ix, line, fitted_pos);
811 if (!isnan(fitted_pos)) {
812 cpl_msg_debug(cpl_func, "line %d %e %e", (int)ix,
813 guess_pos, fitted_pos);
814 //guess_pos = fitted_pos;
815 }
816 cpl_vector_delete(slice1d);
817 }
818
819 /* now fit a curve to the line of fitted peaks along the feature */
820
821 line_polys[line] = cpl_polynomial_new(1);
822 {
823 cpl_matrix * samppos1d = cpl_matrix_new(1, nx);
824 cpl_vector * fitvals = cpl_vector_new(nx);
825 const cpl_boolean sampsym = CPL_FALSE;
826 const cpl_size maxdeg1d = 2;
827
828 cpl_size isamp = 0;
829
830 for (cpl_size ix = 0; ix < nx; ix++) {
831 double fitted_pos = cpl_matrix_get(line_pos, ix, line);
832 if (!isnan(fitted_pos)) {
833 cpl_matrix_set(samppos1d, 0, isamp, (double)ix);
834 cpl_vector_set(fitvals, isamp, fitted_pos);
835 isamp++;
836 }
837 }
838
839 cpl_matrix_set_size(samppos1d, 1, isamp);
840 cpl_vector_set_size(fitvals, isamp);
841 cpl_polynomial_fit(line_polys[line], samppos1d, &sampsym, fitvals, NULL,
842 CPL_FALSE, NULL, &maxdeg1d);
843
844 cpl_msg_info(cpl_func, "line %d poly", (int)line);
845 cpl_polynomial_dump(line_polys[line], stdout);
846 }
847 }
848 enu_check_error_code("error following line features");
849
850 /* save the spectral features info */
851
852 char * out_fname = enu_repreface(cpl_frame_get_filename(
853 jitters->limages[0]->frame),
854 "startrace_sky");
855 enlu_trace_save(ERIS_NIX_LSS_STARTRACE_SKY,
856 sky_background,
857 jitters->limages[0]->confidence,
858 0, NULL,
859 0, NULL,
860 NLINES, (const cpl_polynomial **) line_polys,
861 frameset,
862 parlist,
863 out_fname,
864 RECIPE_NAME);
865 cpl_free(out_fname);
866 enu_check_error_code("error saving line features");
867 }
868
869 /* now do 2d calibration fit between ideal position and actual:
870 x,lambda => x_actual
871 x,lambda => y_actual
872 This direction of mapping is required by cpl_image_warp_polynomial
873 which is the routine used to do the actual correction. */
874
875 /* The ideal detector has 2048 pixels running from 3.045um(0)
876 to 4.107(2047) in dispersion (y), and 2048 pixels with
877 pixel size 0.013arcsec with centred at 1024 in slit
878 offset (x) */
879
880 /* trace_polys are polynomials describing x_actual along traces */
881
882 /* construct the sample position and wavelength arrays
883 for the fit - these must contain no NaNs or the fit routine
884 will fail */
885
886 cpl_size nsamples = NLINES * ntrace;
887 cpl_size isample = 0;
888
889 cpl_matrix * ij_nominal = cpl_matrix_new(2, nsamples);
890 cpl_vector * i_actual = cpl_vector_new(nsamples);
891 cpl_vector * j_actual = cpl_vector_new(nsamples);
892
893 for (cpl_size trace = 0; trace < ntrace; trace++) {
894
895 /* nominal ix of trace for jitter i */
896
897 double i_nominal = cpl_vector_get(nominal_offset, trace);
898
899 /* loop through the calibration lines */
900
901 for (cpl_size line = 0; line < NLINES; line++) {
902 /* nominal position of line from desired wcs */
903 double lambda = wavelength[line];
904 double j_nominal = ny * (lambda - 3.045) / (4.107 - 3.045);
905
906 /* actual i,j is where this trace intersects this line */
907
908 cpl_size jtrace_min = -1;
909 double mindist = DBL_MAX;
910 for (cpl_size jtrace=0; jtrace < ny; jtrace++) {
911 double itrace = cpl_polynomial_eval_1d(trace_polys[trace],
912 (double)jtrace, NULL);
913 double jline = cpl_polynomial_eval_1d(line_polys[line],
914 itrace, NULL);
915 double dist = fabs(jline - jtrace);
916 if (dist < mindist) {
917 jtrace_min = jtrace;
918 mindist = dist;
919 }
920 }
921
922 double i_actual_val = 0.0;
923 double j_actual_val = 0.0;
924 if (jtrace_min != -1) {
925 j_actual_val = jtrace_min;
926 i_actual_val = cpl_polynomial_eval_1d(trace_polys[trace],
927 j_actual_val, NULL);
928 }
929
930 //cpl_msg_info(cpl_func, "%d %d %4.2f %4.1f %4.1f %4.1f",
931 // (int)trace, (int)line,
932 // j_nominal, j_actual_val,
933 // i_nominal, i_actual_val);
934
935 /* construct i_nominal, j_nominal -> i_actual */
936
937 cpl_matrix_set(ij_nominal, 0, isample, i_nominal);
938 cpl_matrix_set(ij_nominal, 1, isample, j_nominal);
939 cpl_vector_set(i_actual, isample, i_actual_val);
940
941 /* and i_nominal, j_nominal -> j_actual */
942
943 cpl_vector_set(j_actual, isample, j_actual_val);
944
945 isample++;
946 }
947 }
948
949 /* solve the i polynomial */
950
951 cpl_polynomial * i_cal = cpl_polynomial_new(2);
952 const cpl_size maxdeg2d[] = {2, 2};
953 cpl_polynomial_fit(i_cal, ij_nominal, NULL, i_actual, NULL,
954 CPL_TRUE, NULL, maxdeg2d);
955
956 /* report results and save to table */
957
958 cpl_msg_info(cpl_func, "The i calibration polynomial:");
959 cpl_polynomial_dump(i_cal, stdout);
960 cpl_table * i_table = enlu_warp_poly_save_to_table(i_cal);
961
962 /* likewise for the j polynomial */
963
964 cpl_polynomial * j_cal = cpl_polynomial_new(2);
965 cpl_polynomial_fit(j_cal, ij_nominal, NULL, j_actual, NULL,
966 CPL_TRUE, NULL, maxdeg2d);
967 cpl_msg_info(cpl_func, "The j calibration polynomial:");
968 cpl_polynomial_dump(j_cal, stdout);
969 cpl_table * j_table = enlu_warp_poly_save_to_table(j_cal);
970
971 /* evaluate the calibration polynomial over the image */
972
973 //wave_cal_image = cpl_image_new(nx, ny, CPL_TYPE_DOUBLE);
974 //for (cpl_size iy = 1; iy < ny+1; iy++) {
975 // for (cpl_size ix = 1; ix < nx+1; ix++) {
976 // cpl_vector * pos = cpl_vector_new(2);
977 // cpl_vector_set(pos, 0, (double) iy);
978 // cpl_vector_set(pos, 1, (double) ix);
979 // double val = cpl_polynomial_eval(wave_cal, pos);
980 // cpl_image_set(wave_cal_image, ix, iy, val);
981 // cpl_vector_delete(pos);
982 // }
983 //}
984
985 /* evaluate the fit residual over the image */
986
987 //cpl_vector * wave_residual = cpl_vector_new(cpl_vector_get_size(
988 // lambda_pos));
989 //cpl_vector_fill_polynomial_fit_residual(wave_residual, lambda_pos,
990 // NULL, wave_cal, line_pos_fit,
991 // NULL);
992 //const double residual_mad = cpl_vector_get_median_const(wave_residual);
993 //cpl_msg_info(cpl_func, "MAD of residuals = %4.2f", residual_mad);
994
995 /* construct the propertylist to be saved with the MASTER_STARTRACE */
996
997 applist = cpl_propertylist_new();
998 cpl_propertylist_append_string(applist, CPL_DFS_PRO_CATG,
999 ERIS_NIX_MASTER_STARTRACE_PRO_CATG);
1000
1001 /* add QC parameters */
1002
1003 //cpl_propertylist_append_double(applist, "ESO QC CAL RESID",
1004 // residual_mad);
1005 //enu_check_error_code("error constructing output propertylist");
1006
1007
1008 /* save the MASTER_STARTRACE to a DFS-compliant MEF file */
1009
1011
1012 /* MEF for i and j polynomials */
1013
1014 mefs->mef[0] = enu_mef_new_table("I_POLYNOMIAL", i_table, NULL);
1015 mefs->mef[1] = enu_mef_new_table("J_POLYNOMIAL", j_table, NULL);
1016
1017 /* MEF for LINE_RESIDUAL, a table with the fit residual at each line
1018 position in each spectral slice */
1019
1020 //mef_table2 = cpl_table_new(nsamples);
1021 //cpl_table_new_column(mef_table2, "x", CPL_TYPE_DOUBLE);
1022 //cpl_table_new_column(mef_table2, "ypos", CPL_TYPE_DOUBLE);
1023 //cpl_table_new_column(mef_table2, "lambda_residual", CPL_TYPE_DOUBLE);
1024 //for (cpl_size isample = 0; isample < nsamples; isample++) {
1025 // cpl_table_set(mef_table2, "x", isample,
1026 // cpl_matrix_get(line_pos_fit, 1, isample));
1027 // cpl_table_set(mef_table2, "ypos", isample,
1028 // cpl_matrix_get(line_pos_fit, 0, isample));
1029 // cpl_table_set(mef_table2, "lambda_residual", isample,
1030 // cpl_vector_get(wave_residual, isample));
1031 //}
1032 //mefs->mef[3] = enu_mef_new_table("LINE_RESIDUAL", mef_table2, NULL);
1033
1034 /* generate name of output file */
1035
1036 char * out_fname = enu_repreface(cpl_frame_get_filename(
1037 jitters->limages[0]->frame),
1038 "master_wave");
1039 cpl_frameset * provenance = cpl_frameset_new();
1040 for (cpl_size j = 0; j<jitters->size; j++) {
1041 cpl_frameset_insert(provenance, cpl_frame_duplicate(
1042 jitters->limages[j]->frame));
1043 }
1044
1045 cpl_msg_info(cpl_func, "..writing %s", out_fname);
1047 parlist,
1048 provenance,
1049 CPL_TRUE,
1050 NULL,
1051 NULL,
1052 mefs,
1053 RECIPE_NAME,
1054 NULL,
1055 applist,
1056 NULL,
1057 PACKAGE "/" PACKAGE_VERSION,
1058 out_fname);
1059
1060 cpl_frameset_delete(provenance);
1061 cpl_free(out_fname);
1062 enu_check_error_code("Failed to save MASTER_WAVE");
1063
1064cleanup:
1065 if (trace_polys != NULL) {
1066 for (cpl_size j=0; j < jitters->size; j++) {
1067 cpl_polynomial_delete(trace_polys[j]);
1068 }
1069 cpl_free(trace_polys);
1070 }
1071 if (line_polys != NULL) {
1072 for (cpl_size line=0; line < NLINES; line++) {
1073 cpl_polynomial_delete(line_polys[line]);
1074 }
1075 cpl_free(line_polys);
1076 }
1077 cpl_propertylist_delete(applist);
1078 cpl_image_delete(contrib);
1079 cpl_vector_delete(crval1_vector);
1080 cpl_vector_delete(crval2_vector);
1081 enu_located_imagelist_delete(object_jitters);
1082 enu_located_imagelist_delete(std_jitters);
1083 if (ij_nominal != NULL) {
1084 cpl_matrix_delete(ij_nominal);
1085 }
1087 hdrl_imagelist_delete(skylist);
1088 hdrl_image_delete(sky_background);
1089 cpl_vector_delete(slit_offset);
1090 cpl_matrix_delete(startrace_pos);
1091 cpl_frameset_delete(used);
1092
1093 return (int)cpl_error_get_code();
1094}
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
cpl_error_code enlu_divide_slit_response(located_imagelist *jitters)
Divide LSS 2d-spectra by the slit response.
double enlu_linepos_1d(const cpl_vector *spectrum1d, const double guess_pos, const cpl_size half_width)
Fit line peak.
cpl_table * enlu_warp_poly_save_to_table(const cpl_polynomial *poly)
Save an LSS polynomial to a cpl_table.
cpl_error_code enlu_trace_save(const char *pro_catg, const hdrl_image *image, const cpl_image *confidence, const cpl_size ntraces, const cpl_polynomial *traces[ntraces], const cpl_size nspectra, const cpl_vector *spectra[nspectra], const cpl_size nlines, const cpl_polynomial *lines[nlines], cpl_frameset *frameset, const cpl_parameterlist *parlist, const char *filename, const char *recipe_name)
Save a trace result.
void enu_located_imagelist_delete(located_imagelist *limlist)
Delete a located_imagelist and its contents.
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.
char * enu_repreface(const char *filename, const char *preface)
Preface a raw filename with a string.
void enu_mef_extension_list_delete(mef_extension_list *list)
Delete a mef_extension_list and its contents.
cpl_error_code eris_files_dont_exist(cpl_frameset *frameset)
Check if all SOF files exist.
Definition: eris_utils.c:867
hdrl_value hdrl_image_get_pixel(const hdrl_image *self, cpl_size xpos, cpl_size ypos, int *pis_rejected)
get pixel values of hdrl_image
Definition: hdrl_image.c:559
cpl_error_code hdrl_image_set_pixel(hdrl_image *self, cpl_size xpos, cpl_size ypos, hdrl_value value)
set pixel values of hdrl_image
Definition: hdrl_image.c:594
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
const cpl_image * hdrl_image_get_image_const(const hdrl_image *himg)
get data as cpl image
Definition: hdrl_image.c:118
void hdrl_image_delete(hdrl_image *himg)
delete hdrl_image
Definition: hdrl_image.c:379
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.