ERIS Pipeline Reference Manual 1.8.15
eris_nix_lss_straighten.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 <math.h>
37#include <string.h>
38
39#include "casu_utils.h"
40#include "casu_mods.h"
41#include "casu_stats.h"
42
43#include "eris_utils.h"
44#include "eris_nix_utils.h"
45#include "eris_pfits.h"
46#include "eris_dfs.h"
47#include "eris_nix_dfs.h"
48#include "eris_utils.h"
49#include "eris_nix_lss_utils.h"
50#include <hdrl.h>
51
52#include <cpl.h>
53
54/*-----------------------------------------------------------------------------
55 Static variables
56 -----------------------------------------------------------------------------*/
57
58static const char eris_nix_lss_straighten_description[] =
59"This recipe uses a CPL warping routine to correct the wavelength and\n"
60"slit offset coordinates of the input ERIS/NIX long-slit spectra. The\n"
61"aim for the output spectra is to have slit offset along the horizontal\n"
62"axis and dispersion along the vertical.\n"
63"\n"
64"Areas of the detector not illuminated by the slit have their data and\n"
65"confidence values set to 0.\n"
66"\n"
67"Input files:\n"
68"\n"
69" DO CATG Explanation Req. #Frames\n"
70" ------- ----------- --- -------\n"
71" "ERIS_NIX_SKYSUB_OBJECT_LSS_JITTER_PRO_CATG
72 " sky-subtracted frames Y 1-n\n"
73" to be straightened.\n"
74" or\n"
75" "ERIS_NIX_SKYSUB_STD_LSS_JITTER_PRO_CATG
76 " sky-subtracted Y 1-n\n"
77" standard frames\n"
78" to be stacked.\n"
79" "ERIS_NIX_MASTER_STARTRACE_PRO_CATG
80 " file containing the Y 1\n"
81" warp polynomials\n"
82" for correcting the\n"
83" LSS spectrum.\n"
84"\n"
85"Output files:\n"
86"\n"
87" DO CATG Explanation \n"
88" ------- ----------- \n"
89" "ERIS_NIX_CORRECTED_OBJECT_LSS_JITTER_PRO_CATG
90 " the straightened 2d-spectrum of the\n"
91" target.\n"
92" or\n"
93" "ERIS_NIX_CORRECTED_STD_LSS_JITTER_PRO_CATG
94 " the straightened 2d-spectrum of the\n"
95" standard.\n"
96"\n"
97" The output image will be in a FITS file named \n"
98" 'corrected.<first input filename>', with extensions:\n"
99" - DATA, with the corrected data.\n"
100" - ERR, with the corrected error plane.\n"
101" - DQ, with the corrected image quality\n"
102" - CONFIDENCE, with the corrected confidence plane.\n"
103" - BKG_DATA, with corrected background spectrum.\n"
104" - BKG_ERR, with the corrected background error plane.\n"
105" - BKG_CONF, with the corrected background confidence.\n"
106"\n"
107" Notes on the method.\n"
108" It is assumed that the jitter pattern moves the object up and\n"
109" down the slit-line. The reference position and the offset of\n"
110" each jitter from it are calculated as follows:\n"
111" 1/ The reference is jitter 0. Often this lies near the \n"
112" middle of the pattern and the object is manually \n"
113" positioned near the middle of the slit.\n"
114" 2/ Derive the slit-line PA (0 = N, increasing E) from the\n"
115" extrema of the jitter centres.\n"
116" 3/ Report how far each jitter pointing lies from the\n"
117" slit-line.\n"
118" 4/ Estimate the offset of each jitter relative to the\n"
119" reference jitter along the slit-line.\n"
120"\n"
121" Correct the spectra to:\n"
122" Calibrate the wavelengths and straighten the spectrum by\n"
123" removing slit/dispersion curvature from the image.\n"
124" Shift spectra so that an object appears in the same column\n"
125" as it would in the reference jitter.\n"
126"\n"
127" This is done for each jitter by:\n"
128" 1/ Estimating the shift required to place the object in\n"
129" the same column as in the reference jitter.\n"
130" a/ Take a copy of the spectrum.\n"
131" b/ Apply the warp polynomials to straighten the copy.\n"
132" c/ Collapse the copy along the dispersion axis.\n"
133" d/ Find the position of the star.\n"
134" e/ Calculate the offset required to move the star to\n"
135" its position in the reference jitter.\n"
136" 2/ Modify the warp polynomials to apply the required shift\n"
137" with the straightening and wavelength calibration.\n"
138" 3/ Apply the shifted warp polynomials to:\n"
139" ..the data plane\n"
140" ..the error plane\n"
141" ..the background spectrum\n"
142" ..the confidence array\n"
143"\n"
144" Set the confidence to 0 outside the illuminated range\n"
145" of the shifted spectrum - to reduce edge effects when\n"
146" stacking them later.\n"
147"\n"
148" Save the straightened spectra. For each spectrum:\n"
149" 1/ Add FITS keywords to describe the WCS of the 2d\n"
150" spectrum.\n"
151" 2/ Save the spectrum to FITS\n"
152"\n";
153
154 static cpl_polynomial * eris_nix_lss_shift_polynomial(
155 const cpl_polynomial * polynomial,
156 const double xshift);
157
158
159#define RECIPE_NAME "eris.eris_nix_lss_straighten"
160
161/*-----------------------------------------------------------------------------
162 Private function prototypes
163 -----------------------------------------------------------------------------*/
164
165cpl_recipe_define(eris_nix_lss_straighten, ERIS_BINARY_VERSION,
166 "John Lightfoot",
167 PACKAGE_BUGREPORT, "2017",
168 "Straighten and calibrate "
169 ERIS_NIX_SKYSUB_OBJECT_LSS_JITTER_PRO_CATG
170 " frames",
171 eris_nix_lss_straighten_description);
172
173/*-----------------------------------------------------------------------------
174 Function code
175 -----------------------------------------------------------------------------*/
176
177/*----------------------------------------------------------------------------*/
185/*----------------------------------------------------------------------------*/
186
187static cpl_error_code eris_nix_lss_straighten_fill_parameterlist(
188 cpl_parameterlist * self) {
189
190 if (cpl_error_get_code() != CPL_ERROR_NONE) return cpl_error_get_code();
191
192 cpl_parameter * p = NULL;
193
194 p = cpl_parameter_new_value(RECIPE_NAME".debug_data",
195 CPL_TYPE_BOOL,
196 "write debugging data",
197 RECIPE_NAME, CPL_FALSE);
198 cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "debug_data");
199 cpl_parameterlist_append(self, p);
200
201 return 0;
202}
203
204
205/*----------------------------------------------------------------------------*/
220/*----------------------------------------------------------------------------*/
221
222cpl_polynomial * eris_nix_lss_shift_polynomial(
223 const cpl_polynomial * polynomial,
224 const double xshift) {
225
226 if (cpl_error_get_code() != CPL_ERROR_NONE) return NULL;
227 cpl_ensure(polynomial, CPL_ERROR_NULL_INPUT, NULL);
228
229 cpl_polynomial * result = cpl_polynomial_duplicate(polynomial);
230
231 cpl_size pows00[2] = {0, 0};
232 double coeff00 = cpl_polynomial_get_coeff(result, pows00);
233 cpl_size pows10[2] = {1, 0};
234 double coeff10 = cpl_polynomial_get_coeff(result, pows10);
235 cpl_size pows20[2] = {2, 0};
236 double coeff20 = cpl_polynomial_get_coeff(result, pows20);
237
238 cpl_size pows01[2] = {0, 1};
239 double coeff01 = cpl_polynomial_get_coeff(result, pows01);
240 cpl_size pows11[2] = {1, 1};
241 double coeff11 = cpl_polynomial_get_coeff(result, pows11);
242 cpl_size pows21[2] = {2, 1};
243 double coeff21 = cpl_polynomial_get_coeff(result, pows21);
244
245 cpl_size pows02[2] = {0, 2};
246 double coeff02 = cpl_polynomial_get_coeff(result, pows02);
247 cpl_size pows12[2] = {1, 2};
248 double coeff12 = cpl_polynomial_get_coeff(result, pows12);
249 cpl_size pows22[2] = {2, 2};
250 double coeff22 = cpl_polynomial_get_coeff(result, pows22);
251
252 double shift_coeff00 = coeff00 - coeff10 * xshift + coeff20 * pow(xshift,2);
253 cpl_polynomial_set_coeff(result, pows00, shift_coeff00);
254 double shift_coeff10 = coeff10 - coeff20 * 2.0 * xshift;
255 cpl_polynomial_set_coeff(result, pows10, shift_coeff10);
256 /* shift_coeff20 = coeff20 so no change needed */
257
258 double shift_coeff01 = coeff01 - coeff11 * xshift + coeff21 * pow(xshift,2);
259 cpl_polynomial_set_coeff(result, pows01, shift_coeff01);
260 double shift_coeff11 = coeff11 - coeff21 * 2.0 * xshift;
261 cpl_polynomial_set_coeff(result, pows11, shift_coeff11);
262 /* shift_coeff21 = coeff21 so no change needed */
263
264 double shift_coeff02 = coeff02 - coeff12 * xshift + coeff22 * pow(xshift,2);
265 cpl_polynomial_set_coeff(result, pows02, shift_coeff02);
266 double shift_coeff12 = coeff12 - coeff22 * 2.0 * xshift;
267 cpl_polynomial_set_coeff(result, pows12, shift_coeff12);
268 /* shift_coeff22 = coeff22 so no change needed */
269
270 //cpl_polynomial_dump(result, stdout);
271
272 /* tidy up on error */
273
274 if (cpl_error_get_code() != CPL_ERROR_NONE) {
275 cpl_polynomial_delete(result);
276 result = NULL;
277 }
278 return result;
279}
280
281
282/*----------------------------------------------------------------------------*/
289/*----------------------------------------------------------------------------*/
290
291static int eris_nix_lss_straighten(cpl_frameset * frameset,
292 const cpl_parameterlist * parlist) {
293
294 cpl_vector * crval1_vector = NULL;
295 cpl_vector * crval2_vector = NULL;
296 located_imagelist * jitters = NULL;
297 cpl_table * jitter_table = NULL;
298 located_imagelist * object_jitters = NULL;
299 const char * out_catg = NULL;
300 const cpl_parameter * p = NULL;
301 cpl_vector * profile = NULL;
302 cpl_vector * slit_offset = NULL;
303 mef_extension_list * startrace_mefs = NULL;
304 cpl_propertylist * startrace_plist = NULL;
305 cpl_polynomial * startrace_poly_i = NULL;
306 cpl_polynomial * startrace_poly_j = NULL;
307 located_imagelist * std_jitters = NULL;
308 cpl_frameset * used = NULL;
309
310 enu_check_error_code("%s():%d: An error is already set: %s",
311 cpl_func, __LINE__, cpl_error_get_where());
312
313 /* Check input parameters */
314
315 cpl_ensure_code(frameset, CPL_ERROR_NULL_INPUT);
316 cpl_ensure_code(parlist, CPL_ERROR_NULL_INPUT);
317
318 /* Set the msg verbosity level from environment variable CPL_MSG_LEVEL */
319
320 cpl_msg_set_level_from_env();
321 cpl_msg_severity severity = cpl_msg_get_level();
322 cpl_msg_info(cpl_func, "level %d", (int) severity);
323
324 /* check for invalid input */
325 if(eris_files_dont_exist(frameset) != CPL_ERROR_NONE) {
326 return CPL_ERROR_BAD_FILE_FORMAT;
327 }
328
329 /* Retrieve input parameters */
330
331 p = cpl_parameterlist_find_const(parlist, RECIPE_NAME".debug_data");
332 int debug_data = cpl_parameter_get_bool(p);
333 enu_check_error_code("Could not retrieve input parameters");
334
335 /* Identify the RAW and CALIB frames in the input frameset */
336
337 eris_nix_dfs_set_groups(frameset);
338 enu_check_error_code("Could not identify RAW and CALIB frames");
339
340 /* read in the sky-subtracted data */
341
342 used = cpl_frameset_new();
343 object_jitters = enu_limlist_load_from_frameset(frameset,
344 ERIS_NIX_SKYSUB_OBJECT_LSS_JITTER_PRO_CATG, used);
345 std_jitters = enu_limlist_load_from_frameset(frameset,
346 ERIS_NIX_SKYSUB_STD_LSS_JITTER_PRO_CATG, used);
347 enu_check_error_code("Error loading frameset");
348
349 if (object_jitters->size > 0) {
350 enu_check(std_jitters->size == 0, CPL_ERROR_ILLEGAL_INPUT,
351 "SoF contains both object and std data");
352 jitters = object_jitters;
353 object_jitters = NULL;
354 out_catg = ERIS_NIX_CORRECTED_OBJECT_LSS_JITTER_PRO_CATG;
355 cpl_msg_info(cpl_func, "Read in %d "
356 ERIS_NIX_SKYSUB_OBJECT_LSS_JITTER_PRO_CATG "frames",
357 (int)(jitters->size));
358 } else if (std_jitters->size > 0) {
359 jitters = std_jitters;
360 std_jitters = NULL;
361 out_catg = ERIS_NIX_CORRECTED_STD_LSS_JITTER_PRO_CATG;
362 cpl_msg_info(cpl_func, "Read in %d "
363 ERIS_NIX_SKYSUB_STD_LSS_JITTER_PRO_CATG "frames",
364 (int)(jitters->size));
365 }
366
367 /* read in the MASTER_STARTRACE file from the sof */
368
369 cpl_frameset_iterator * frameset_iter = cpl_frameset_iterator_new(frameset);
370 for (cpl_frame * frame = NULL;
371 (frame = cpl_frameset_iterator_get(frameset_iter)) &&
372 (cpl_error_get_code() == CPL_ERROR_NONE);
373 cpl_frameset_iterator_advance(frameset_iter, 1)) {
374
375 const char * tag = cpl_frame_get_tag(frame);
376
377 if (!strcmp(tag, ERIS_NIX_MASTER_STARTRACE_PRO_CATG)) {
378 const char * filename = cpl_frame_get_filename(frame);
379 startrace_mefs = enu_load_mef_components(filename,
380 &startrace_plist);
381 cpl_msg_info(cpl_func, "Read MASTER_STARTRACE %s", filename);
382 break;
383 }
384 }
385 enu_check(startrace_mefs != NULL, CPL_ERROR_DATA_NOT_FOUND,
386 "SoF contains no "ERIS_NIX_MASTER_STARTRACE_PRO_CATG
387 " data");
388
389 /* estimate the centre of the jitter pattern
390 .. The jitter centres should form a line on the sky
391 .. as the object is moved up and down the slit.
392 .. Find the ends of this line by looking at the extreme points. */
393
394 cpl_msg_info(cpl_func, "Finding centre of jitter pattern");
395 crval1_vector = cpl_vector_new(jitters->size);
396 crval2_vector = cpl_vector_new(jitters->size);
397 for (cpl_size i = 0; i < jitters->size; i++) {
398 double crval1 = cpl_propertylist_get_double(
399 jitters->limages[i]->plist,
400 "CRVAL1");
401 cpl_vector_set(crval1_vector, i, crval1);
402 double crval2 = cpl_propertylist_get_double(
403 jitters->limages[i]->plist,
404 "CRVAL2");
405 cpl_vector_set(crval2_vector, i, crval2);
406 cpl_msg_info(cpl_func, "..jitter %d at RA=%10.7f Dec=%10.7f",
407 (int)i, crval1, crval2);
408 }
409
410 cpl_size crval1_maxpos = cpl_vector_get_maxpos(crval1_vector);
411 double crval1_max = cpl_vector_get(crval1_vector, crval1_maxpos);
412 cpl_size crval1_minpos = cpl_vector_get_minpos(crval1_vector);
413 double crval1_min = cpl_vector_get(crval1_vector, crval1_minpos);
414
415 cpl_size crval2_maxpos = cpl_vector_get_maxpos(crval2_vector);
416 double crval2_max = cpl_vector_get(crval2_vector, crval2_maxpos);
417 cpl_size crval2_minpos = cpl_vector_get_minpos(crval2_vector);
418 double crval2_min = cpl_vector_get(crval2_vector, crval2_minpos);
419
420 /* for the coord that changes most, assume the line ends are at
421 the extreme values */
422 double enda[2] = {0.0, 0.0};
423 double endb[2] = {0.0, 0.0};
424 if (fabs(crval1_max - crval1_min) > fabs(crval2_max - crval2_min)) {
425 enda[0] = crval1_min;
426 enda[1] = cpl_vector_get(crval2_vector, crval1_minpos);
427 endb[0] = crval1_max;
428 endb[1] = cpl_vector_get(crval2_vector, crval1_maxpos);
429 } else {
430 enda[0] = cpl_vector_get(crval1_vector, crval2_minpos);
431 enda[1] = crval2_min;
432 endb[0] = cpl_vector_get(crval1_vector, crval2_maxpos);
433 endb[1] = crval2_max;
434 }
435 cpl_msg_info(cpl_func, "Jitter line end points (RA, DEC): "
436 "(%10.7f, %10.7f) and (%10.7f, %10.7f)",
437 enda[0], enda[1], endb[0], endb[1]);
438
439 double centre[2] = {0.0, 0.0};
440
441 /* define reference position as that of first jitter.
442 In the test data it seems that the object was manually
443 centred in the slit (i.e. near column 1024) for jitter 0 */
444
445 centre[0] = cpl_vector_get(crval1_vector, 0);
446 centre[1] = cpl_vector_get(crval2_vector, 0);
447
448// alternatively...
449// /* define the reference position as a point halfway along the
450// slit-line jitter pattern */
451
452// centre[0] = (enda[0] + endb[0]) / 2.0;
453// centre[1] = (enda[1] + endb[1]) / 2.0;
454
455 cpl_msg_info(cpl_func, "Jitter centre %f %f", centre[0], centre[1]);
456 double coslat = cos(centre[1] * CPL_MATH_PI / 180.0);
457
458 /* direction of slit line, assume axis increases to left */
459
460 double slit_line[2] = {(endb[0]-enda[0]) * coslat, endb[1]-enda[1]};
461 double slit_norm = sqrt(pow(slit_line[0],2) + pow(slit_line[1],2));
462 slit_line[0] /= slit_norm;
463 slit_line[1] /= slit_norm;
464 double slit_pa = atan2(slit_line[0], slit_line[1]);
465 cpl_msg_info(cpl_func, "..slit direction vector (long, lat) = (%f, %f)",
466 slit_line[0], slit_line[1]);
467 cpl_msg_info(cpl_func, "..slit PA = %f deg", slit_pa * 180.0 / CPL_MATH_PI);
468
469 double cd1_1 = cpl_propertylist_get_double(jitters->limages[0]->
470 plist, "CD1_1");
471 double cd2_1 = cpl_propertylist_get_double(jitters->limages[0]->
472 plist, "CD2_1");
473 /* pixsize in deg */
474 double pixsize = sqrt(cd1_1 * cd1_1 + cd2_1 * cd2_1);
475 cpl_msg_info(cpl_func, "pixel size %f deg", pixsize);
476
477 /* how do the jitter centres line up */
478
479 slit_offset = cpl_vector_new(jitters->size);
480 for (cpl_size i = 0; i < jitters->size; i++) {
481 double offset = -1000.0;
482 /* be wary of 0 components in slit_line direction vector */
483 if (fabs(slit_line[0]) > slit_line[1]) {
484 offset = (cpl_vector_get(crval1_vector, i) - centre[0]) *
485 coslat / slit_line[0];
486 } else {
487 offset = (cpl_vector_get(crval2_vector, i) - centre[1]) /
488 slit_line[1];
489 }
490
491 /* how far away is the point from the line between endpoints? */
492
493 double distance[2] = {0.0,0.0};
494 distance[0] = ((cpl_vector_get(crval1_vector, i) - centre[0]) *
495 coslat -
496 (offset * slit_line[0])) / pixsize;
497 distance[1] = ((cpl_vector_get(crval2_vector, i) - centre[1]) -
498 (offset * slit_line[1])) / pixsize;
499 //cpl_msg_info(cpl_func, "%f %f %f %f",
500 // cpl_vector_get(crval2_vector, i),
501 // centre[1], offset, slit_line[1]);
502 cpl_msg_warning(cpl_func,
503 "...distance from jitter line (%f, %f) pixels",
504 distance[0], distance[1]);
505
506 cpl_vector_set(slit_offset, i, offset);
507 cpl_msg_info(cpl_func, "..slit offset jitter %d %f deg",
508 (int)i, offset);
509 }
510
511 /* calculate nominal offset from reference position for each */
512 /* jitter */
513
514 cpl_vector * nominal_offset = cpl_vector_new(jitters->size);
515 for (cpl_size i = 0; i < jitters->size; i++) {
516 cpl_vector_set(nominal_offset, i,
517 cpl_vector_get(slit_offset, i) / pixsize);
518 }
519 cpl_msg_info(cpl_func, "Nominal offset of each jitter from "
520 "reference (pixels):");
521 cpl_vector_dump(nominal_offset, NULL);
522 enu_check_error_code("Error calculating nominal slit offsets");
523
524 /* read the wavelength cal and dispersion direction polynomials */
525
526 cpl_table * poly_table = cpl_table_duplicate(
527 (cpl_table *) startrace_mefs->mef[0]->data);
528 startrace_poly_i = enlu_warp_poly_load_from_table(poly_table);
529 cpl_table_delete(poly_table);
530
531 poly_table = cpl_table_duplicate(
532 (cpl_table *) startrace_mefs->mef[1]->data);
533 startrace_poly_j = enlu_warp_poly_load_from_table(poly_table);
534 cpl_table_delete(poly_table);
535 enu_check_error_code("Error reading warp polynomials");
536
537 /* correct the frames for spectral and startrace distortion */
538
539 /* profile is Lanczos2, which works well for images, not sure about
540 spectral direction. ISAAC uses default profile, tanh.
541
542 Also can use different profiles for spatial and spectral, should
543 examine this - TBD */
544
545 profile = cpl_vector_new(CPL_KERNEL_DEF_SAMPLES);
546 cpl_vector_fill_kernel_profile(profile, CPL_KERNEL_LANCZOS,
547 CPL_KERNEL_DEF_WIDTH);
548
549 if (debug_data) {
550
551 /* use the sky background spectrum to check the wavelength
552 calibration */
553
554 /* ..warp the background of the first jitter, this will remove any
555 curvature in the wavelength scale */
556
557 cpl_image * bkg_copy = cpl_image_duplicate(hdrl_image_get_image_const(
558 jitters->limages[0]->bkg));
559 cpl_image_warp_polynomial(bkg_copy,
561 jitters->limages[0]->bkg),
562 startrace_poly_i, startrace_poly_j,
563 profile, CPL_KERNEL_DEF_WIDTH,
564 profile, CPL_KERNEL_DEF_WIDTH);
565 cpl_image_reject_value(bkg_copy, CPL_VALUE_NOTFINITE);
566
567 cpl_image_save(bkg_copy, "bkg_warped.fits", CPL_TYPE_UNSPECIFIED,
568 NULL, CPL_IO_CREATE);
569
570 /* ..collapse the 2d-spectrum along the slit direction */
571
572 cpl_image * bkg_collapse = cpl_image_collapse_median_create(
573 bkg_copy, 1, 600, 550);
574 cpl_vector * bkg_vector = cpl_vector_new_from_image_column(
575 bkg_collapse, 1);
576 cpl_vector_save(bkg_vector,
577 "bkg_vector.fits",
578 CPL_TYPE_DOUBLE,
579 NULL,
580 CPL_IO_CREATE);
581
582 cpl_vector_delete(bkg_vector);
583 cpl_image_delete(bkg_collapse);
584 cpl_image_delete(bkg_copy);
585 }
586
587 /* correct the frames */
588
589 double actual_offset_0 = 0.0;
590
591 for (cpl_size j = 0; j < jitters->size; j++) {
592
593 /* first, find the object in the slit - if there is one */
594
595 /* ..make a copy of the raw data */
596
597 cpl_image * copy_image = cpl_image_duplicate(hdrl_image_get_image_const(
598 jitters->limages[j]->himage));
599
600 /* ..straighten the raw data copy back into the jitter himage */
601
602 cpl_image_warp_polynomial(hdrl_image_get_image(
603 jitters->limages[j]->himage), copy_image,
604 startrace_poly_i, startrace_poly_j,
605 profile, CPL_KERNEL_DEF_WIDTH,
606 profile, CPL_KERNEL_DEF_WIDTH);
607
608 if (debug_data) {
609 char* filename = cpl_sprintf("warp_%d.fits", (int)j);
610 cpl_image_save(hdrl_image_get_image(jitters->limages[j]->himage),
611 filename, CPL_TYPE_UNSPECIFIED, NULL,
612 CPL_IO_CREATE);
613 cpl_free(filename);
614 }
615
616 /* ..find the position of the object in the straightened result
617 ....1. collapse along dispersion */
618
619 /* flag pixels at edge of frame to prevent possible outliers */
620 cpl_size border = 100;
621 cpl_size nx = hdrl_image_get_size_x(jitters->limages[j]->himage);
622 cpl_size ny = hdrl_image_get_size_y(jitters->limages[j]->himage);
623
624 for(cpl_size jj=1; jj<= ny; jj++) {
625 for(cpl_size i=1; i <= border; i++) {
626 cpl_image_reject(hdrl_image_get_image(jitters->
627 limages[j]->himage), i, jj);
628 }
629
630 for(cpl_size i=nx-border; i<= nx; i++) {
631 cpl_image_reject(hdrl_image_get_image(jitters->
632 limages[j]->himage), i, jj);
633 }
634
635 }
636
637 cpl_image * collapse_image = cpl_image_collapse_create(
638 hdrl_image_get_image(jitters->
639 limages[j]->himage), 0);
640 cpl_vector * collapse = cpl_vector_new_from_image_row(
641 collapse_image, 1);
642
643 /* ....2. find position of object in slit */
644
645 /* ....3. could use nominal offset as a first guess if the
646 reference jitter had the object in column 0 but it won't
647 - instead use a robust maximum estimate as the first
648 guess. Nominal and actual offsets should move in a
649 similar pattern if all is working as expected */
650
651 /* double guess = cpl_vector_get(nominal_offset, j); */
652 //cpl_vector* lowpass = cpl_vector_filter_lowpass_create(collapse,CPL_LOWPASS_GAUSSIAN,2);
653 double guess = (double) cpl_vector_get_maxpos(collapse);
654 cpl_msg_debug(cpl_func, "..maxpos %d", (int)guess);
655 //cpl_vector_save(lowpass, "lowpass.fits", CPL_TYPE_DOUBLE, NULL, CPL_IO_DEFAULT);
656 //cpl_vector_save(collapse, "collapse.fits", CPL_TYPE_DOUBLE, NULL, CPL_IO_DEFAULT);
657 double actual_offset = enlu_linepos_1d(collapse, guess, 6);
658 double nominal = cpl_vector_get(nominal_offset, j);
659 if (j == 0) {
660 actual_offset_0 = actual_offset;
661 cpl_msg_info(cpl_func, "..object in jitter 0 is at %f pixels",
662 actual_offset_0);
663 }
664 cpl_msg_info(cpl_func, "multiplying nominal offset by -1 as "
665 "increasing RA decreases detector column");
666 cpl_msg_info(cpl_func, "..object slit offsets (nominal, "
667 "actual_j) %f %f",
668 -1.0 * nominal, actual_offset);
669
670 /* ..calculate the offset shift required to place the object
671 at the column it is seen in jitter 0. Add the offset shift
672 to the warping description and do the straighten again.
673 This should remove the warp _and_ register the jitters with
674 respect to jitter 0. The aim is to avoid more resampling
675 steps than are necessary as each one probably loses
676 information */
677
678 double xshift = (actual_offset_0 - actual_offset);
679 cpl_msg_info(cpl_func, "..shift to place object at jitter 0 "
680 "position is %5.2f pixels",
681 xshift);
682
683 cpl_polynomial * shifted_poly_i = eris_nix_lss_shift_polynomial(
684 startrace_poly_i, xshift);
685 cpl_polynomial * shifted_poly_j = eris_nix_lss_shift_polynomial(
686 startrace_poly_j, xshift);
687
688 /* ..warp original data */
689
690 cpl_image_warp_polynomial(hdrl_image_get_image(
691 jitters->limages[j]->himage), copy_image,
692 shifted_poly_i, shifted_poly_j,
693 profile, CPL_KERNEL_DEF_WIDTH,
694 profile, CPL_KERNEL_DEF_WIDTH);
695 cpl_image_delete(copy_image);
696
697 if (debug_data) {
698 char* filename = cpl_sprintf("warp2_%d.fits", (int)j);
699 cpl_image_save(hdrl_image_get_image(jitters->limages[j]->himage),
700 filename, CPL_TYPE_UNSPECIFIED, NULL,
701 CPL_IO_CREATE);
702 cpl_free(filename);
703 }
704
705 /* ..warp the error plane - the resampling weights will
706 not be applied correctly */
707
708 cpl_image* copy_error = cpl_image_duplicate(hdrl_image_get_error_const(
709 jitters->limages[j]->himage));
710 cpl_image_warp_polynomial(hdrl_image_get_error(
711 jitters->limages[j]->himage), copy_error,
712 shifted_poly_i, shifted_poly_j,
713 profile, CPL_KERNEL_DEF_WIDTH,
714 profile, CPL_KERNEL_DEF_WIDTH);
715 cpl_image_delete(copy_error);
716
717 /* ..warp background */
718
719 cpl_image* copy_bkg = cpl_image_duplicate(hdrl_image_get_image_const(
720 jitters->limages[j]->bkg));
721 cpl_image_warp_polynomial(hdrl_image_get_image(
722 jitters->limages[j]->bkg), copy_bkg,
723 shifted_poly_i, shifted_poly_j,
724 profile, CPL_KERNEL_DEF_WIDTH,
725 profile, CPL_KERNEL_DEF_WIDTH);
726 cpl_image_delete(copy_bkg);
727
728 /* ..warp background error - wrong weights again */
729
730 cpl_image* copy_bkg_err = cpl_image_duplicate(hdrl_image_get_error_const(
731 jitters->limages[j]->bkg));
732 cpl_image_warp_polynomial(hdrl_image_get_error(
733 jitters->limages[j]->bkg), copy_bkg_err,
734 shifted_poly_i, shifted_poly_j,
735 profile, CPL_KERNEL_DEF_WIDTH,
736 profile, CPL_KERNEL_DEF_WIDTH);
737 cpl_image_delete(copy_bkg_err);
738
739 /* ..warp confidence */
740
741 cpl_image* copy_conf = cpl_image_duplicate(jitters->limages[j]->confidence);
742 cpl_image_warp_polynomial(jitters->limages[j]->confidence, copy_conf,
743 shifted_poly_i, shifted_poly_j,
744 profile, CPL_KERNEL_DEF_WIDTH,
745 profile, CPL_KERNEL_DEF_WIDTH);
746 cpl_image_delete(copy_conf);
747
748 /* tidy up */
749
750 cpl_polynomial_delete(shifted_poly_i);
751 cpl_polynomial_delete(shifted_poly_j);
752 enu_check_error_code("Error resampling data");
753
754 /* last, set confidence to zero outside the illuminated x-range
755 of the detector (593, 1507 are hardwired for now) */
756
757 cpl_size left_edge = 593 + (cpl_size)xshift;
758 cpl_size right_edge = 1507 + (cpl_size)xshift;
759 for (cpl_size ii=0;
760 ii < hdrl_image_get_size_x(jitters->limages[j]->himage); ii++) {
761
762 if (ii > left_edge && ii < right_edge) continue;
763 for (cpl_size jj=0;
764 jj < hdrl_image_get_size_y(jitters->limages[j]->himage);
765 jj++) {
766 cpl_image_set(jitters->limages[j]->confidence, ii+1, jj+1,
767 0.0);
768 hdrl_image_set_pixel(jitters->limages[j]->himage, ii+1, jj+1,
769 (hdrl_value){0.0, 0.0});
770 hdrl_image_reject(jitters->limages[j]->himage, ii+1, jj+1);
771 }
772 }
773 enu_normalise_confidence(jitters->limages[j]->confidence);
774 enu_check_error_code("Error zeroing confidence outside "
775 "illuminated area");
776 }
777
778 /* save the corrected jitter spectra to FITS files */
779
780 for (cpl_size i = 0; i < jitters->size; i++) {
781
782 /* Set up the wcs to reflect the corrected spectrum.
783 Borrows from long-slit example in
784 Calabretta and Greisen 2002 A&A 395, 1077 (example on p.
785 1115-16).
786
787 In our case the pixel coords are p1,p2 = slit-offset,wavelength
788 NB this is different to the example in the paper.
789
790 The transformation is to RA, Dec, lambda. Using a CD matrix:
791
792 RA CRVAL1 CD1_1 CD1_2 CD1_3 p1 - CRPIX1
793 lambda = CRVAL2 + CD2_1 CD2_2 CD2_3 x p2 - CRPIX2
794 Dec CRVAL3 + CD3_1 CD3_2 CD3_3 p3 - CRPIX3
795
796 The wavelength transformation is 3.045um at 0 to 4.107um at 2047
797 so:
798
799 CRPIX2 = 0.0
800 CRVAL2 = 3.045
801 CD2_1 = 0
802 CD2_2 = 0
803 cd2_3 = (4.107 - 3.045) / 2047
804
805 The slit offset to RA, Dec transformation combines the RA,Dec of the
806 slit centre with the P.A. of the slit rotation (assumed 0 at N,
807 increasing towrds increasing RA), so:
808
809 CRPIX1 = 1024
810 CRVAL1 = RA centre
811 CD1_1 = sin(PA) * pixscale
812 RA = CRVAL1 + CD1_1 * (p1 - CRPIX1)
813
814 and
815
816 CRVAL3 = Dec centre
817 CD3_1 = cos(PA) * pixscale
818 Dec = CRVAL3 + CD3_1 * (p1 - CRPIX3)
819
820 The p3 axis is degenerate so CD entries for it can have
821 arbitrary values.
822
823 CD1_3 = CD2_3 = CD3_3 = 1
824 */
825
826 cpl_propertylist_update_int(jitters->limages[i]->plist, "NAXIS", 3);
827 cpl_propertylist_update_string(jitters->limages[i]->plist, "CTYPE1",
828 "RA---TAN");
829 cpl_propertylist_update_string(jitters->limages[i]->plist, "CTYPE2",
830 "WAVE");
831 cpl_propertylist_update_string(jitters->limages[i]->plist, "CTYPE3",
832 "DEC--TAN");
833 cpl_propertylist_update_double(jitters->limages[i]->plist, "CRPIX1",
834 1024.0);
835 cpl_propertylist_update_double(jitters->limages[i]->plist, "CRPIX2",
836 0.0);
837 cpl_propertylist_update_double(jitters->limages[i]->plist, "CRPIX3",
838 0.0);
839 cpl_propertylist_update_double(jitters->limages[i]->plist, "CRVAL1",
840 centre[0]);
841 cpl_propertylist_update_double(jitters->limages[i]->plist, "CRVAL2",
842 3.045);
843 cpl_propertylist_update_double(jitters->limages[i]->plist, "CRVAL3",
844 centre[1]);
845
846 cpl_propertylist_update_double(jitters->limages[i]->plist, "CD1_1",
847 sin(slit_pa) * pixsize);
848 cpl_propertylist_update_double(jitters->limages[i]->plist, "CD1_2",
849 0.0);
850 cpl_propertylist_update_double(jitters->limages[i]->plist, "CD1_3",
851 1.0);
852
853 cpl_propertylist_update_double(jitters->limages[i]->plist, "CD2_1",
854 0.0);
855 cpl_propertylist_update_double(jitters->limages[i]->plist, "CD2_2",
856 (4.107 - 3.045) / 2047);
857 cpl_propertylist_update_double(jitters->limages[i]->plist, "CD2_3",
858 0.0);
859
860 cpl_propertylist_update_double(jitters->limages[i]->plist, "CD3_1",
861 cos(slit_pa) * pixsize);
862 cpl_propertylist_update_double(jitters->limages[i]->plist, "CD3_2",
863 0.0);
864 cpl_propertylist_update_double(jitters->limages[i]->plist, "CD3_3",
865 1.0);
866
867 cpl_propertylist_update_string(jitters->limages[i]->plist, "CUNIT1",
868 "DEG");
869 cpl_propertylist_update_string(jitters->limages[i]->plist, "CUNIT2",
870 "um");
871 cpl_propertylist_update_string(jitters->limages[i]->plist, "CUNIT3",
872 "DEG");
873
874 cpl_propertylist * applist = cpl_propertylist_new();
875 cpl_propertylist_update_string(applist, CPL_DFS_PRO_CATG, out_catg);
876 cpl_propertylist_update_string(applist, "PRODCATG",
877 "ANCILLARY.2DSPECTRUM");
878
879 /* data needs to be converted from dims [nx,ny] to [nx,ny,1] to
880 save in FITS format - FITS requires the addition of a degenerate
881 axis */
882
883 if (debug_data) {
884 char* filename = cpl_sprintf("warp3_%d.fits", (int)i);
885 cpl_image_save(hdrl_image_get_image(jitters->limages[i]->himage),
886 filename, CPL_TYPE_UNSPECIFIED, NULL,
887 CPL_IO_CREATE);
888 cpl_free(filename);
889 }
890
891 hdrl_imagelist * himlist1 = hdrl_imagelist_new();
892 hdrl_imagelist_set(himlist1, jitters->limages[i]->himage, 0);
893 // FIX error expention (PIPE-12651)
894 //jitters->limages[i]->himage = NULL;
895 //jitters->limages[i]->himagelist = himlist1;
896 //jitters->limages[i]->himage = NULL;
897 jitters->limages[i]->himagelist = NULL;
898
899 /* generate name of output file, provenance */
900
901 char * out_fname = enu_repreface(cpl_frame_get_filename(
902 jitters->limages[i]->frame),
903 "straightened");
904
905 cpl_frameset * provenance = cpl_frameset_new();
906 cpl_frameset_insert(provenance, cpl_frame_duplicate(
907 jitters->limages[i]->frame));
908
909 cpl_msg_info(cpl_func, "..writing %s", out_fname);
910 enu_dfs_save_limage(frameset,
911 parlist,
912 provenance,
913 CPL_TRUE,
914 jitters->limages[i],
915 RECIPE_NAME,
916 jitters->limages[i]->frame,
917 applist,
918 PACKAGE "/" PACKAGE_VERSION,
919 out_fname);
920
921 cpl_free(out_fname);
922 cpl_frameset_delete(provenance);
923 cpl_propertylist_delete(applist);
924 }
925
926cleanup:
927 cpl_vector_delete(crval1_vector);
928 cpl_vector_delete(crval2_vector);
930 cpl_table_delete(jitter_table);
931 enu_located_imagelist_delete(object_jitters);
932 cpl_vector_delete(profile);
933 cpl_vector_delete(slit_offset);
934 enu_mef_extension_list_delete(startrace_mefs);
935 cpl_propertylist_delete(startrace_plist);
936 cpl_polynomial_delete(startrace_poly_i);
937 cpl_polynomial_delete(startrace_poly_j);
938 enu_located_imagelist_delete(std_jitters);
939 cpl_frameset_delete(used);
940
941 return (int) cpl_error_get_code();
942}
cpl_error_code enu_dfs_save_limage(cpl_frameset *allframes, const cpl_parameterlist *parlist, const cpl_frameset *provenance, const cpl_boolean prov_raw, const located_image *limage, const char *recipe, const cpl_frame *inherit, cpl_propertylist *applist, const char *pipe_id, const char *filename)
Save a located image structure to a MEF.
Definition: eris_nix_dfs.c:909
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
double enlu_linepos_1d(const cpl_vector *spectrum1d, const double guess_pos, const cpl_size half_width)
Fit line peak.
cpl_polynomial * enlu_warp_poly_load_from_table(const cpl_table *table)
Load a LSS warp polynomial from a cpl_table.
void enu_located_imagelist_delete(located_imagelist *limlist)
Delete a located_imagelist and its contents.
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.
cpl_error_code enu_normalise_confidence(cpl_image *confidence)
Normalise confidence array so that mean of good pixels is 100.
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.
mef_extension_list * enu_load_mef_components(const char *filename, cpl_propertylist **plist)
Load components of a multi-extension FITS file.
cpl_error_code eris_files_dont_exist(cpl_frameset *frameset)
Check if all SOF files exist.
Definition: eris_utils.c:867
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
cpl_image * hdrl_image_get_error(hdrl_image *himg)
get error as cpl image
Definition: hdrl_image.c:131
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_error_const(const hdrl_image *himg)
get error as cpl image
Definition: hdrl_image.c:144
cpl_image * hdrl_image_get_image(hdrl_image *himg)
get data as cpl image
Definition: hdrl_image.c:105
cpl_error_code hdrl_image_reject(hdrl_image *self, cpl_size xpos, cpl_size ypos)
mark pixel as bad
Definition: hdrl_image.c:427
const cpl_image * hdrl_image_get_image_const(const hdrl_image *himg)
get data as cpl image
Definition: hdrl_image.c:118
cpl_error_code hdrl_imagelist_set(hdrl_imagelist *himlist, hdrl_image *himg, cpl_size pos)
Insert an image into an imagelist.
hdrl_imagelist * hdrl_imagelist_new(void)
Create an empty imagelist.