ERIS Pipeline Reference Manual 1.8.15
eris_nix_lss_utils.c
1/* $Id$
2 *
3 * This file is part of the ERIS/NIX 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 * $Rev$
25 */
26
27#ifdef HAVE_CONFIG_H
28#include <config.h>
29#endif
30
31/*-----------------------------------------------------------------------------
32 Includes
33 -----------------------------------------------------------------------------*/
34
35#include "eris_nix_lss_utils.h"
36#include "eris_nix_dfs.h"
37#include "eris_nix_utils.h"
38
39/*----------------------------------------------------------------------------*/
43/*----------------------------------------------------------------------------*/
44
47/*----------------------------------------------------------------------------*/
61/*----------------------------------------------------------------------------*/
62
63cpl_error_code enlu_divide_slit_response(located_imagelist * jitters) {
64
65 /* check inputs */
66
67 if (cpl_error_get_code() != CPL_ERROR_NONE) return cpl_error_get_code();
68 cpl_ensure_code(jitters, CPL_ERROR_NULL_INPUT);
69
70 cpl_msg_info(cpl_func, "..dividing by slit response");
71
72 for (cpl_size j = 0; j < jitters->size; j++) {
73
74 /* Get copy of background, remove dodgy values */
75
76 cpl_image * bkg_copy = cpl_image_duplicate(
78 jitters->limages[j]->bkg));
79 cpl_image_reject_value(bkg_copy, CPL_VALUE_NOTFINITE);
80
81 /* collapse the background along the dispersion axis to get
82 the slit response profile (sky spectrum should be the same
83 for all slit offsets) */
84
85 cpl_image * bkg_collapse = cpl_image_collapse_median_create(bkg_copy,
86 0, 0, 0);
87
88 /* normalise, bit fiddly as spectrum covers less than half of image
89 ..mask out unilluminated pixels */
90
91 double maxval = cpl_image_get_max(bkg_collapse);
92 cpl_mask_threshold_image(cpl_image_get_bpm(bkg_collapse),
93 bkg_collapse,
94 0.7 * maxval,
95 DBL_MAX,
96 CPL_BINARY_0);
97 cpl_image_fill_rejected(bkg_collapse, 0.0);
98
99 /* ..normalise by median. Do both image and sky background but
100 leave confidence alone - the slit profile is assumed
101 noiseless so confidence unaffected (?) */
102
103 double medval = cpl_image_get_median(bkg_collapse);
104 cpl_image_divide_scalar(bkg_collapse, medval);
105
106 enlu_divide_slit_response_worker(jitters->limages[j]->himage,
107 bkg_collapse);
108 enlu_divide_slit_response_worker(jitters->limages[j]->bkg,
109 bkg_collapse);
110
111
112 /* sanity check, plotted slit profile should be flat */
113/*
114 {
115 cpl_image * bkg_copy = cpl_image_duplicate(
116 hdrl_image_get_image(
117 jitters->limages[j]->bkg));
118 cpl_image_reject_value(bkg_copy, CPL_VALUE_NOTFINITE);
119
120 cpl_image * bkg_collapse = cpl_image_collapse_median_create(bkg_copy,
121 0, 0, 0);
122
123 double maxval = cpl_image_get_max(bkg_collapse);
124 cpl_mask_threshold_image(cpl_image_get_bpm(bkg_collapse),
125 bkg_collapse,
126 0.7 * maxval,
127 DBL_MAX,
128 CPL_BINARY_0);
129 cpl_image_fill_rejected(bkg_collapse, 0.0);
130
131 char* filename = cpl_sprintf("bkg_vector_%d.fits", (int)j);
132 cpl_vector * bkg_vector = cpl_vector_new_from_image_row(bkg_collapse, 1);
133 cpl_vector_save(bkg_vector,
134 filename, CPL_TYPE_DOUBLE, NULL, CPL_IO_CREATE);
135 cpl_free(filename);
136 cpl_vector_delete(bkg_vector);
137 }
138*/
139
140 cpl_image_delete(bkg_collapse);
141 cpl_image_delete(bkg_copy);
142 }
143
144 return cpl_error_get_code();
145}
146
147
148/*----------------------------------------------------------------------------*/
155/*----------------------------------------------------------------------------*/
156
157cpl_error_code enlu_divide_slit_response_worker(hdrl_image * himage_2d,
158 cpl_image * response_1d) {
159
160 /* check inputs */
161
162 if (cpl_error_get_code() != CPL_ERROR_NONE) return cpl_error_get_code();
163 cpl_ensure_code(himage_2d, CPL_ERROR_NULL_INPUT);
164 cpl_ensure_code(response_1d, CPL_ERROR_NULL_INPUT);
165
166 /*cpl_msg_info(cpl_func, "enlu_divide_slit_response_worker called");*/
167
168 /* do it the simple but slow way, using hdrl and cpl to access the
169 pixels one by one */
170
171 cpl_size nx = hdrl_image_get_size_x(himage_2d);
172 cpl_size ny = hdrl_image_get_size_y(himage_2d);
173
174 for (cpl_size i = 1; i <= nx; i++) {
175 int slit_ok = 0;
176 double slit = cpl_image_get(response_1d, i, 1, &slit_ok);
177 for (cpl_size j = 1; j <= ny; j++) {
178 int data_ok = 0;
179 hdrl_value data = hdrl_image_get_pixel(himage_2d, i, j, &data_ok);
180
181 if (data_ok==0 && slit_ok==0) {
182 data.data = data.data / slit;
183 data.error = data.error / slit;
184 } else {
185 data = (hdrl_value) {0.0, 0.0};
186 data_ok = 1;
187 }
188 hdrl_image_set_pixel(himage_2d, i, j, data);
189 if (data_ok == 1) {
190 hdrl_image_reject(himage_2d, i, j);
191 }
192 }
193 }
194
195 return cpl_error_get_code();
196}
197
198
199/*----------------------------------------------------------------------------*/
239/*----------------------------------------------------------------------------*/
240
241cpl_matrix * enlu_linepos_2d(const hdrl_image * spectrum2d,
242 const cpl_size slice_index,
243 const cpl_vector * guess_pos) {
244
245 cpl_matrix * line_pos = NULL;
246 cpl_vector * next_guess_pos = NULL;
247 cpl_vector * spectrum1d = NULL;
248
249 if (cpl_error_get_code() != CPL_ERROR_NONE) return NULL;
250
251 cpl_ensure(spectrum2d, CPL_ERROR_NULL_INPUT, NULL);
252 cpl_ensure(guess_pos, CPL_ERROR_NULL_INPUT, NULL);
253 cpl_ensure(cpl_vector_get_size(guess_pos) > 0,
254 CPL_ERROR_ILLEGAL_INPUT, NULL);
255
256 cpl_size nlines = cpl_vector_get_size(guess_pos);
257
258 const cpl_size nx = hdrl_image_get_size_x(spectrum2d);
259 line_pos = cpl_matrix_new(nx, nlines);
260 cpl_matrix_fill(line_pos, NAN);
261
262 /* loop through slices on RH side of image. Remember that cpl_image
263 indeces are 1-based, cpl_vector and cpl_matrix 0-based - fab! */
264
265 next_guess_pos = cpl_vector_duplicate(guess_pos);
266
267 for (cpl_size ix = slice_index; ix < nx; ix++) {
268
269 /* fit the lines in this slice to get accurate peak positions */
270
271 spectrum1d = cpl_vector_new_from_image_column(
272 hdrl_image_get_image_const(spectrum2d), ix+1);
273
274 for (cpl_size line_id=0; line_id < nlines; line_id++) {
275 double line_guess_pos = cpl_vector_get(next_guess_pos, line_id);
276 double fitted_pos = enlu_linepos_1d(spectrum1d, line_guess_pos, 4);
277 if (!isnan(fitted_pos)) {
278
279 /* set the fit peak as the measured position of the line,
280 set next_guess_pos to the actual line positions so that
281 it can follow slow variations with ix */
282
283 cpl_matrix_set(line_pos, ix, line_id, fitted_pos);
284 cpl_vector_set(next_guess_pos, line_id, fitted_pos);
285 }
286 }
287 cpl_vector_delete(spectrum1d);
288 }
289 cpl_vector_delete(next_guess_pos);
290
291 /* now loop through slices on LH side of image */
292
293 next_guess_pos = cpl_vector_duplicate(guess_pos);
294
295 for (cpl_size ix = slice_index-1; ix >= 0; ix--) {
296 spectrum1d = cpl_vector_new_from_image_column(
297 hdrl_image_get_image_const(spectrum2d), ix+1);
298
299 for (cpl_size line_id=0; line_id < nlines; line_id++) {
300 double line_guess_pos = cpl_vector_get(next_guess_pos, line_id);
301 double fitted_pos = enlu_linepos_1d(spectrum1d, line_guess_pos, 4);
302 if (!isnan(fitted_pos)) {
303 cpl_matrix_set(line_pos, ix, line_id, fitted_pos);
304 cpl_vector_set(next_guess_pos, line_id, fitted_pos);
305 }
306 }
307 cpl_vector_delete(spectrum1d);
308 }
309 cpl_vector_delete(next_guess_pos);
310
311 /* Return NULL on error */
312
313 if (cpl_error_get_code() != CPL_ERROR_NONE) {
314 cpl_matrix_delete(line_pos);
315 line_pos = NULL;
316 }
317
318 return line_pos;
319}
320
321
322/*----------------------------------------------------------------------------*/
341/*----------------------------------------------------------------------------*/
342
343double enlu_linepos_1d(const cpl_vector * spectrum1d,
344 const double guess_pos,
345 const cpl_size half_width) {
346
347 if (cpl_error_get_code() != CPL_ERROR_NONE) return NAN;
348 cpl_ensure(spectrum1d, CPL_ERROR_NULL_INPUT, NAN);
349
350 cpl_polynomial * line_fit = NULL;
351 double result = NAN;
352
353 cpl_size istart = (cpl_size) guess_pos - half_width;
354 cpl_size istop = (cpl_size) guess_pos + half_width;
355 cpl_size npos = istop - istart + 1;
356
357 /* set up pixel positions of chunk */
358
359 cpl_matrix * pos_chunk = cpl_matrix_new(1, npos);
360 for (cpl_size i = 0; i < npos; i++) {
361 cpl_matrix_set(pos_chunk, 0, i, (double) (i + istart));
362 }
363
364 /* get spectrum data for chunk, spec_ok goes False if any points
365 bad (=0) or are NaN, the fitting routine can't handle NaNs */
366
367 cpl_vector * spectrum_chunk = cpl_vector_extract(spectrum1d,
368 istart, istop, 1);
369 const double * spectrum_chunk_data = cpl_vector_get_data_const(
370 spectrum_chunk);
371 int spec_ok = 1;
372 for (cpl_size i = 0; i < npos; i++) {
373 spec_ok = spec_ok &&
374 spectrum_chunk_data[i] != 0.0 &&
375 !isnan(spectrum_chunk_data[i]);
376 }
377
378 /* fit a parabola to the line peak if the data are good */
379
380 if (spec_ok) {
381 line_fit = cpl_polynomial_new(1);
382 const cpl_size maxdeg1d = 2;
383 cpl_polynomial_fit(line_fit, pos_chunk, NULL, spectrum_chunk, NULL,
384 CPL_FALSE, NULL, &maxdeg1d);
385
386 /* set the fit peak as the measured position of the line,
387 set next_guess_pos to the actual line positions so that
388 it can follow slow variations with ix */
389
390 cpl_size pow = 1;
391 double poly_b = cpl_polynomial_get_coeff(line_fit, &pow);
392 pow = 2;
393 double poly_c = cpl_polynomial_get_coeff(line_fit, &pow);
394 result = -poly_b / (2.0 * poly_c);
395
396 /* if the fitted line centre is outside the expected range
397 then something has probably gone wrong - errant pixel or
398 something - ignore it */
399
400 if (fabs(result - guess_pos) > half_width) {
401 result = NAN;
402 }
403 }
404
405 cpl_matrix_delete(pos_chunk);
406 cpl_vector_delete(spectrum_chunk);
407 cpl_polynomial_delete(line_fit);
408
409 if (cpl_error_get_code() != CPL_ERROR_NONE) {
410 result = NAN;
411 }
412
413 return result;
414}
415
416
417/*----------------------------------------------------------------------------*/
430/*----------------------------------------------------------------------------*/
431
432cpl_error_code enlu_trace_save(const char * pro_catg,
433 const hdrl_image * image,
434 const cpl_image * confidence,
435 const cpl_size ntraces,
436 const cpl_polynomial * traces[ntraces],
437 const cpl_size nspectra,
438 const cpl_vector * spectra[nspectra],
439 const cpl_size nlines,
440 const cpl_polynomial * lines[nlines],
441 cpl_frameset * frameset,
442 const cpl_parameterlist * parlist,
443 const char * filename,
444 const char * recipe_name) {
445
446 /* checks */
447
448 if (cpl_error_get_code() != CPL_ERROR_NONE) return cpl_error_get_code();
449 cpl_ensure_code(pro_catg, CPL_ERROR_NULL_INPUT);
450 cpl_ensure_code(image, CPL_ERROR_NULL_INPUT);
451 cpl_ensure_code(frameset, CPL_ERROR_NULL_INPUT);
452 cpl_ensure_code(parlist, CPL_ERROR_NULL_INPUT);
453 cpl_ensure_code(filename, CPL_ERROR_NULL_INPUT);
454
455 mef_extension_list * mefs = NULL;
456 cpl_propertylist * plist = NULL;
457
458 /* make output propertylist */
459
460 plist = cpl_propertylist_new();
461 cpl_propertylist_append_string(plist, CPL_DFS_PRO_CATG, pro_catg);
462
463 /* add QC parameters */
464 /* TBD */
465
466 enu_check_error_code("error constructing output propertylist");
467
468 /* save the trace to a DFS-compliant MEF file */
469
470 /* .. how many mefs? */
471
472 cpl_size nmefs = ntraces + nspectra + nlines + 1;
473 mefs = enu_mef_extension_list_new(nmefs);
474 cpl_size imef = 0;
475 mefs->mef[imef] = enu_mef_new_image("CONFIDENCE", confidence, NULL);
476 imef++;
477 for (cpl_size i = 0; i < ntraces; i++) {
478 cpl_propertylist * mef_plist = cpl_propertylist_new();
479 cpl_propertylist_update_string(mef_plist, "DIR", "Y");
480 mefs->mef[imef] = enu_mef_new_table(
481 "TRACE",
483 mef_plist);
484 imef++;
485 }
486 for (cpl_size i = 0; i < nspectra; i++) {
487 mefs->mef[imef] = enu_mef_new_vector("SPECTRUM", spectra[i], NULL);
488 imef++;
489 }
490 for (cpl_size i = 0; i < nlines; i++) {
491 cpl_propertylist * mef_plist = cpl_propertylist_new();
492 cpl_propertylist_update_string(mef_plist, "DIR", "X");
493 mefs->mef[imef] = enu_mef_new_table(
494 "LINE",
496 mef_plist);
497 imef++;
498 }
499
500 enu_dfs_save_himage(frameset,
501 parlist,
502 frameset,
503 CPL_TRUE,
504 image,
505 NULL,
506 mefs,
507 recipe_name,
508 NULL,
509 plist,
510 NULL,
511 PACKAGE "/" PACKAGE_VERSION,
512 filename);
513
514 cleanup:
516 cpl_propertylist_delete(plist);
517
518 return cpl_error_get_code();
519}
520
521
522/*----------------------------------------------------------------------------*/
535/*----------------------------------------------------------------------------*/
536
537cpl_polynomial * enlu_warp_poly_load_from_table(const cpl_table * table) {
538
539 if (cpl_error_get_code() != CPL_ERROR_NONE) return NULL;
540 cpl_ensure(table, CPL_ERROR_NULL_INPUT, NULL);
541
542 /* only handle polynomial degree 2, keeps things simpler */
543
544 cpl_polynomial * result = cpl_polynomial_new(2);
545
546 /* expect table columns 1.dim.power, 2.dim.power, coefficient */
547
548 enu_check(cpl_table_has_column(table, "1.dim.power"),
549 CPL_ERROR_ILLEGAL_INPUT,
550 "table does not have column '1.dim.power'");
551 enu_check(cpl_table_has_column(table, "2.dim.power"),
552 CPL_ERROR_ILLEGAL_INPUT,
553 "table does not have column '2.dim.power'");
554 enu_check(cpl_table_has_column(table, "coefficient"),
555 CPL_ERROR_ILLEGAL_INPUT,
556 "table does not have column 'coefficient'");
557
558 /* loop through the rows and set the polynomial coefficients */
559
560 cpl_size nrow = cpl_table_get_nrow(table);
561 for (cpl_size row=0; row<nrow; row++) {
562 int null1 = 0;
563 int pow1 = cpl_table_get_int(table, "1.dim.power", row, &null1);
564 int null2 = 0;
565 int pow2 = cpl_table_get_int(table, "2.dim.power", row, &null2);
566 int nullcoeff = 0;
567 double coeff = cpl_table_get_double(table, "coefficient", row,
568 &nullcoeff);
569 enu_check(!(null1 || null2 || nullcoeff), CPL_ERROR_ILLEGAL_INPUT,
570 "table contains NULL values");
571
572 cpl_size pows[2] = {pow1, pow2};
573 cpl_polynomial_set_coeff(result, pows, coeff);
574 }
575
576 cleanup:
577 if (cpl_error_get_code() != CPL_ERROR_NONE) {
578 cpl_polynomial_delete(result);
579 result = NULL;
580 }
581 return result;
582}
583
584
585/*----------------------------------------------------------------------------*/
597/*----------------------------------------------------------------------------*/
598
599cpl_table * enlu_warp_poly_save_to_table(const cpl_polynomial * poly) {
600
601 if (cpl_error_get_code() != CPL_ERROR_NONE) return NULL;
602 cpl_ensure(poly, CPL_ERROR_NULL_INPUT, NULL);
603
604 cpl_table * result = NULL;
605
606 /* Is the polynomial 1 or 2d? */
607
608 const cpl_size dimension = cpl_polynomial_get_dimension(poly);
609 enu_check(dimension==1 || dimension==2, CPL_ERROR_ILLEGAL_INPUT,
610 "can only handle polynomial with dimension 1 or 2");
611
612 if (dimension == 1) {
613
614 const cpl_size degree = cpl_polynomial_get_degree(poly);
615 result = cpl_table_new(degree + 1);
616
617 /* construct table columns */
618
619 cpl_table_new_column(result, "1.dim.power", CPL_TYPE_INT);
620 cpl_table_new_column(result, "coefficient", CPL_TYPE_DOUBLE);
621
622 cpl_size nrows = 0;
623 for (cpl_size dim1_power=0; dim1_power<=degree; dim1_power++) {
624 double coeff = cpl_polynomial_get_coeff(poly, &dim1_power);
625 if (coeff != 0.0) {
626 cpl_table_set_int(result, "1.dim.power", nrows, dim1_power);
627 cpl_table_set_double(result, "coefficient", nrows, coeff);
628 nrows++;
629 }
630 }
631 cpl_table_set_size(result, nrows);
632
633 } else if (dimension == 2) {
634
635 const cpl_size degree = cpl_polynomial_get_degree(poly);
636 result = cpl_table_new((degree + 1) * (degree + 1));
637
638 /* construct table columns */
639
640 cpl_table_new_column(result, "1.dim.power", CPL_TYPE_INT);
641 cpl_table_new_column(result, "2.dim.power", CPL_TYPE_INT);
642 cpl_table_new_column(result, "coefficient", CPL_TYPE_DOUBLE);
643
644 cpl_size nrows = 0;
645 for (int dim2_power=0; dim2_power<=degree; dim2_power++) {
646 for (int dim1_power=0; dim1_power<=degree; dim1_power++) {
647 cpl_size pows[2] = {dim1_power, dim2_power};
648 double coeff = cpl_polynomial_get_coeff(poly, pows);
649 if (coeff != 0.0) {
650 cpl_table_set_int(result, "1.dim.power", nrows, dim1_power);
651 cpl_table_set_int(result, "2.dim.power", nrows, dim2_power);
652 cpl_table_set_double(result, "coefficient", nrows, coeff);
653 nrows++;
654 }
655 }
656 }
657 cpl_table_set_size(result, nrows);
658 }
659
660 cleanup:
661
662 if (cpl_error_get_code() != CPL_ERROR_NONE) {
663 cpl_table_delete(result);
664 result = NULL;
665 }
666 return result;
667}
668
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_worker(hdrl_image *himage_2d, cpl_image *response_1d)
Worker function to divide a 2d image by a 1d response.
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_matrix * enlu_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_polynomial * enlu_warp_poly_load_from_table(const cpl_table *table)
Load a LSS warp polynomial from 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.
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.
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.
mef_extension * enu_mef_new_vector(const char *name, const cpl_vector *vector, const cpl_propertylist *plist)
Create a mef_extension struct holding a cpl_vector.
void enu_mef_extension_list_delete(mef_extension_list *list)
Delete a mef_extension_list and its contents.
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
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_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