MUSE Pipeline Reference Manual  2.1.1
muse_sky_old.c
1 /* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim:set sw=2 sts=2 et cin: */
3 /*
4  * This file is part of the MUSE Instrument Pipeline
5  * Copyright (C) 2008-2014 European Southern Observatory
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20  */
21 
22 /* This is the old code that works with lsf_param */
23 
24 #ifdef HAVE_CONFIG_H
25 #include <config.h>
26 #endif
27 
28 #include <math.h>
29 #include <string.h>
30 
31 #include "muse_sky.h"
32 #include "muse_sky_old.h"
33 #include "muse_instrument.h"
34 #include "muse_lsf_params.h"
35 #include "muse_optimize.h"
36 #include "muse_utils.h"
37 #include "muse_data_format_z.h"
38 
43 /*----------------------------------------------------------------------------*/
50 /*----------------------------------------------------------------------------*/
51 typedef struct {
53  cpl_array *line_strength;
55  cpl_array *continuum;
57  muse_lsf_params *lsf;
58 } muse_sky_master_params;
59 
60 
61 /*----------------------------------------------------------------------------*/
69 /*----------------------------------------------------------------------------*/
70 static muse_sky_master_params *
71 muse_sky_master_params_new(cpl_size n_groups, cpl_size n_continuum) {
72  muse_sky_master_params *res = cpl_calloc(1, sizeof(muse_sky_master_params));
73  res->line_strength = cpl_array_new(n_groups, CPL_TYPE_DOUBLE);
74  cpl_array_fill_window_double(res->line_strength, 0, n_groups, 1.0);
75  res->continuum = cpl_array_new(n_continuum, CPL_TYPE_DOUBLE);
76  cpl_array_fill_window_double(res->continuum, 0, n_continuum, 0.0);
77  return res;
78 }
79 
80 /*----------------------------------------------------------------------------*/
86 /*----------------------------------------------------------------------------*/
87 static void
88 muse_sky_master_params_delete(muse_sky_master_params *aParams) {
89  if (aParams != NULL) {
90  cpl_array_delete(aParams->line_strength);
91  cpl_array_delete(aParams->continuum);
92  muse_lsf_params_delete(aParams->lsf);
93  cpl_free(aParams);
94  }
95 }
96 
97 /*----------------------------------------------------------------------------*/
109 /*----------------------------------------------------------------------------*/
110 static muse_sky_master_params *
111 muse_sky_master_apply_sky_parametrization(const cpl_array *aPar,
112  cpl_size *offset,
113  int ngroups) {
114  muse_sky_master_params *p = muse_sky_master_params_new(ngroups, 0);
115 
116  int i;
117  for (i = 0; i < ngroups; i++) {
118  double s = (cpl_array_get(aPar, (*offset)++, NULL));
119  cpl_array_set(p->line_strength, i, s*s);
120  }
121  return p;
122 }
123 
124 /*----------------------------------------------------------------------------*/
134 /*----------------------------------------------------------------------------*/
135 static cpl_array *
136 muse_sky_master_sky_firstguess(int ngroups) {
137  cpl_array *pars = cpl_array_new(0 + ngroups, CPL_TYPE_DOUBLE);
138  cpl_size offset = 0;
139 
140  // Line strengths
141  int i;
142  for (i = 0; i < ngroups; i++) {
143  cpl_array_set(pars, offset++, 1e-1);
144  }
145 
146  if (offset != cpl_array_get_size(pars)) {
147  cpl_msg_error(__func__,
148  "inconsistent array: size %li; filled with %li values",
149  (long)cpl_array_get_size(pars), (long)offset);
150  }
151  return pars;
152 }
153 
154 /*----------------------------------------------------------------------------*/
165 /*----------------------------------------------------------------------------*/
166 static muse_lsf_params *
167 muse_sky_master_apply_lsf_parametrization(const cpl_array *aPar,
168  cpl_size *offset)
169 {
170  muse_lsf_params *lsf = muse_lsf_params_new(1, 3, 1);
171  cpl_array_set(lsf->sensitivity, 0, 1.0);
172  lsf->offset = cpl_array_get(aPar, (*offset)++, NULL);
173  lsf->refraction = 1.0 + cpl_array_get(aPar, (*offset)++, NULL);
174  cpl_array_set(lsf->lsf_width, 0,
175  cpl_array_get(aPar, (*offset)++, NULL));
176 #if 1
177  cpl_array_set(lsf->lsf_width, 1,
178  cpl_array_get(aPar, (*offset)++, NULL));
179  cpl_array_set(lsf->lsf_width, 2,
180  cpl_array_get(aPar, (*offset)++, NULL));
181 #endif
182  cpl_size i;
183  for (i = 0; i < MAX_HERMIT_ORDER; i++) {
184  cpl_array_set(lsf->hermit[i], 0,
185  cpl_array_get(aPar, (*offset)++, NULL));
186  }
187  return lsf;
188 }
189 
190 /*----------------------------------------------------------------------------*/
199 /*----------------------------------------------------------------------------*/
200 static cpl_array *
201 muse_sky_master_lsf_firstguess(void) {
202  cpl_array *pars = cpl_array_new(5 + MAX_HERMIT_ORDER, CPL_TYPE_DOUBLE);
203  cpl_size offset = 0;
204 
205  // wavelength calibration offset
206  cpl_array_set(pars, offset++, 0.0);
207  // Relative refraction - 1
208  cpl_array_set(pars, offset++, 0.0);
209 
210  // LSF width
211  cpl_array_set(pars, offset++, 1.0);
212 #if 1
213  cpl_array_set(pars, offset++, 0);
214  cpl_array_set(pars, offset++, 0);
215 #endif
216  // Hermitean coefficients
217  cpl_size i;
218  for (i = 0; i < MAX_HERMIT_ORDER; i++) {
219  cpl_array_set(pars, offset++, 0.0);
220  }
221  if (offset != cpl_array_get_size(pars)) {
222  cpl_msg_error(__func__,
223  "inconsistent array: size %ld, filled with %ld values",
224  (long)cpl_array_get_size(pars), (long)offset);
225  }
226  return pars;
227 }
228 
229 /*----------------------------------------------------------------------------*/
240 /*----------------------------------------------------------------------------*/
241 static muse_sky_master_params *
242 muse_sky_master_apply_parametrization(const cpl_array *aPar, int ngroups) {
243  cpl_size offset = 0;
244  muse_sky_master_params *p = muse_sky_master_apply_sky_parametrization(aPar, &offset,
245  ngroups);
246  p->lsf = muse_sky_master_apply_lsf_parametrization(aPar, &offset);
247 
248  if (offset != cpl_array_get_size(aPar)) {
249  cpl_msg_error(__func__,
250  "inconsistent array: size %ld, read with %ld values",
251  (long)cpl_array_get_size(aPar), (long)offset);
252  cpl_error_set(__func__, CPL_ERROR_ILLEGAL_INPUT);
253  muse_sky_master_params_delete(p);
254  return NULL;
255  }
256  return p;
257 }
258 
259 /*----------------------------------------------------------------------------*/
270 /*----------------------------------------------------------------------------*/
271 
272 static cpl_array *
273 simulate_master_sky_parameters(const cpl_table *aLines,
274  cpl_size aNgroups,
275  const cpl_array *aLambda,
276  const cpl_array *aPar) {
277  muse_sky_master_params *p
278  = muse_sky_master_apply_parametrization(aPar, aNgroups);
279 
280  cpl_array *continuum = cpl_array_duplicate(aLambda);
281  muse_cplarray_poly1d(continuum, p->continuum);
282 
283  cpl_table *lines = cpl_table_duplicate(aLines);
284  muse_sky_lines_apply_strength(lines, p->line_strength);
285  double maxflux = cpl_table_get_column_max(lines, "flux");
286  muse_sky_lines_cut(lines, 1e-4 * maxflux);
287 
288  cpl_array *simulated = muse_lsf_params_spectrum(aLambda, lines, p->lsf);
289  cpl_array_add(simulated, continuum);
290 
291  cpl_array_delete(continuum);
292  cpl_table_delete(lines);
293  muse_sky_master_params_delete(p);
294  return simulated;
295 
296 }
297 
298 typedef struct {
299  const cpl_array *lambda; // wavelength
300  const cpl_array *values; // data values
301  const cpl_array *stat; // data statistics
302  const cpl_table *sky; // constant sky physics data
303  const cpl_size ngroups; // number of groups in the data
304 } muse_master_fit_struct;
305 
306 
307 /*----------------------------------------------------------------------------*/
316 /*----------------------------------------------------------------------------*/
317 static cpl_error_code
318 muse_sky_master_eval(void *aData, cpl_array *aPar, cpl_array *aRetval) {
319  cpl_size size = cpl_array_get_size(aRetval);
320  muse_master_fit_struct *data = aData;
321  cpl_array *simulated
322  = simulate_master_sky_parameters(data->sky, data->ngroups,
323  data->lambda, aPar);
324 
325  cpl_array_subtract(simulated, data->values);
326  cpl_array *dsimulated = muse_cplarray_diff(simulated, 1);
327  cpl_array_divide(dsimulated, data->stat);
328 
329  cpl_array_fill_window_double(aRetval, 0, size, 0.0);
330  memcpy(cpl_array_get_data_double(aRetval),
331  cpl_array_get_data_double_const(dsimulated),
332  size * sizeof(double));
333 
334  cpl_array_delete(simulated);
335  cpl_array_delete(dsimulated);
336 
337  return CPL_ERROR_NONE;
338 }
339 
340 /*----------------------------------------------------------------------------*/
357 /*----------------------------------------------------------------------------*/
358 
359 static cpl_error_code
360 muse_sky_master_correct_firstguess(const cpl_table *aLines,
361  cpl_size aNgroups,
362  const cpl_array *aLambda,
363  const cpl_array *aData,
364  cpl_array *aPars) {
365 
366  // evaluate the first guess
367  cpl_array *simulated
368  = simulate_master_sky_parameters(aLines, aNgroups, aLambda, aPars);
369 
370  cpl_size offset = 0;
371  muse_sky_master_params *msp
372  = muse_sky_master_apply_sky_parametrization(aPars, &offset, aNgroups);
373  cpl_table *lines = cpl_table_duplicate(aLines);
374  muse_sky_lines_apply_strength(lines, msp->line_strength);
375  muse_sky_master_params_delete(msp);
376  cpl_size i_group;
377  double delta = 0.0;
378  for (i_group = 0; i_group < aNgroups; i_group++) {
379  // take the strongest line of each group
380  cpl_table_unselect_all(lines);
381  cpl_table_or_selected_int(lines, "group", CPL_EQUAL_TO, i_group);
382  cpl_table *gtable = cpl_table_extract_selected(lines);
383  cpl_size row;
384  cpl_table_get_column_maxpos(gtable, "flux", &row);
385  double wavelength = cpl_table_get_double(gtable, "lambda", row, NULL);
386  cpl_table_delete(gtable);
387 
388  // divide measured data and first guess result
389  cpl_size i_lbda1 = muse_cplarray_find_sorted(aLambda, wavelength - 2.0);
390  cpl_size i_lbda2 = muse_cplarray_find_sorted(aLambda, wavelength + 2.0);
391  double y_data = 0;
392  double y_sim = 0;
393  double avg_data = 0;
394  double avg_sim = 0;
395  cpl_size i_lbda;
396  for (i_lbda = i_lbda1; i_lbda <= i_lbda2; i_lbda++) {
397  double lbda = cpl_array_get(aLambda, i_lbda, NULL);
398  double wy_data = cpl_array_get(aData, i_lbda, NULL);
399  double wy_sim = cpl_array_get(simulated, i_lbda, NULL);
400  y_data += wy_data;
401  avg_data += wy_data * lbda;
402  y_sim += wy_sim;
403  avg_sim += wy_sim * lbda;
404  }
405 
406  // take this as correction factor
407  if (y_sim > 0) {
408  cpl_array_set(aPars, i_group,
409  cpl_array_get(aPars, i_group, NULL)*sqrt(y_data/y_sim));
410  avg_data /= y_data;
411  avg_sim /= y_sim;
412  delta += avg_data - avg_sim;
413  }
414  }
415  cpl_array_set(aPars, aNgroups,
416  cpl_array_get(aPars, aNgroups, NULL) + delta/aNgroups);
417  cpl_table_delete(lines);
418  cpl_array_delete(simulated);
419 
420  return CPL_ERROR_NONE;
421 }
422 
423 /*----------------------------------------------------------------------------*/
434 /*----------------------------------------------------------------------------*/
435 cpl_error_code
436 muse_sky_lines_fit_old(cpl_table *aSpectrum, cpl_table *aLines)
437 {
438  cpl_ensure_code(aSpectrum, CPL_ERROR_NULL_INPUT);
439  cpl_ensure_code(aLines, CPL_ERROR_NULL_INPUT);
440 
441  cpl_array *lambda = muse_cpltable_extract_column(aSpectrum, "lambda");
442  cpl_array *data = muse_cpltable_extract_column(aSpectrum, "data");
443  cpl_array *stat2 = muse_cpltable_extract_column(aSpectrum, "stat");
444 
445  cpl_size nstat = cpl_array_get_size(stat2);
446  cpl_ensure_code(nstat > 0, CPL_ERROR_DATA_NOT_FOUND);
447  cpl_array *stat = cpl_array_extract(stat2, 0, nstat - 1);
448  cpl_array *s2 = cpl_array_extract(stat2, 1, nstat);
449  cpl_array_add(stat, s2);
450  cpl_array_delete(s2);
451  cpl_array_power(stat, 0.5);
452 
453  muse_master_fit_struct fit_data = {
454  lambda,
455  data,
456  stat,
457  aLines,
458  cpl_table_get_column_max(aLines, "group") + 1
459  };
460 
461  cpl_array *pars = muse_sky_master_sky_firstguess(fit_data.ngroups);
462  cpl_array *dpars = muse_sky_master_lsf_firstguess();
463  cpl_array_insert(pars, dpars, cpl_array_get_size(pars));
464  cpl_array_delete(dpars);
465 
466  // run the correction twice: in the first run, mainly the offset for
467  // the wavelength is calculated. In the second step, the lines strengts
468  // are corrected with a better guess of the window (with the offset from
469  // the first step). Also the wavelength offset is corrected.
470  muse_sky_master_correct_firstguess(aLines, fit_data.ngroups, lambda, data, pars);
471  muse_sky_master_correct_firstguess(aLines, fit_data.ngroups, lambda, data, pars);
472  muse_sky_master_correct_firstguess(aLines, fit_data.ngroups, lambda, data, pars);
473 
474  cpl_size size = cpl_array_get_size(lambda);
475  // do the fit, ignoring possible errors
476  int debug = getenv("MUSE_DEBUG_LSF_FIT")
477  && atoi(getenv("MUSE_DEBUG_LSF_FIT")) > 0;
479  -1, -1, -1, -1, // default ftol, xtol, gtol, maxiter
480  debug
481  };
482  /* this potentially takes a long time, better output something */
483  cpl_msg_info(__func__, "Starting master sky fit");
484  cpl_error_code r = muse_cpl_optimize_lvmq(&fit_data, pars, size-1,
485  muse_sky_master_eval, &ctrl);
486  if (r != CPL_ERROR_NONE) {
487  cpl_msg_error(__func__, "Master sky fit failed with error code %i: %s",
488  r, cpl_error_get_message());
489  } else {
490  cpl_msg_info(__func__, "Master sky fit finished successfully.");
491  }
492 
493  muse_sky_master_params *p = muse_sky_master_apply_parametrization(pars, fit_data.ngroups);
494 
495  cpl_array_delete(pars);
496 
497  muse_sky_lines_apply_strength(aLines, p->line_strength);
498  cpl_propertylist *order = cpl_propertylist_new();
499 // cpl_propertylist_append_bool(order, "lambda", FALSE);
500  cpl_propertylist_append_bool(order, "flux", TRUE);
501  cpl_table_sort(aLines, order);
502  cpl_propertylist_delete(order);
503 
504  cpl_msg_info(__func__, "refraction index=1%s%g, offset=%f Angstrom",
505  p->lsf->refraction < 1?"-":"+",
506  fabs(p->lsf->refraction-1), p->lsf->offset);
507 
508  cpl_array_delete(stat);
509  muse_sky_master_params_delete(p);
510 
511  cpl_array_unwrap(lambda);
512  cpl_array_unwrap(data);
513  cpl_array_unwrap(stat2);
514 
515  return CPL_ERROR_NONE;
516 }
531 cpl_error_code
532 muse_sky_subtract_lines_old(muse_pixtable *aPixtable, cpl_table *aLines,
533  muse_lsf_params **aLsfParams)
534 {
535  cpl_ensure_code(aPixtable != NULL, CPL_ERROR_NULL_INPUT);
536  cpl_ensure_code(aPixtable->table != NULL, CPL_ERROR_NULL_INPUT);
537  cpl_ensure_code(aLines != NULL, CPL_ERROR_NULL_INPUT);
538 
539  muse_pixtable **slice_pixtable = muse_pixtable_extracted_get_slices(aPixtable);
540  cpl_size n_slices = muse_pixtable_extracted_get_size(slice_pixtable);
541  cpl_size i_slice;
542  cpl_msg_info(__func__, "Starting sky subtraction of %"CPL_SIZE_FORMAT" slices",
543  n_slices);
544  cpl_boolean debug = getenv("MUSE_DEBUG_SKY")
545  && atoi(getenv("MUSE_DEBUG_SKY")) > 0;
546  #pragma omp parallel for default(none) /* as req. by Ralf */ \
547  shared(aLsfParams, aLines, debug, n_slices, slice_pixtable)
548  for (i_slice = 0; i_slice < n_slices; i_slice++) {
549  uint32_t origin
550  = (uint32_t)cpl_table_get_int(slice_pixtable[i_slice]->table,
551  MUSE_PIXTABLE_ORIGIN, 0, NULL);
552  int ifu = muse_pixtable_origin_get_ifu(origin);
553  int slice = muse_pixtable_origin_get_slice(origin);
554  muse_lsf_params *slice_params = muse_lsf_params_get(aLsfParams, ifu, slice);
555  if ((slice_params == NULL) && (aLines != NULL)){
556  cpl_msg_warning(__func__, "No LSF params for slice #%i.%i."
557  " Ignoring lines in sky subtraction for this slice.",
558  ifu, slice);
559  }
560 
561  cpl_size nrows = muse_pixtable_get_nrow(slice_pixtable[i_slice]);
562  if (debug) {
563  cpl_msg_debug(__func__, "Sky subtraction of %li pixels for slice #%i.%i",
564  (long)nrows, ifu, slice);
565  }
566  cpl_errorstate prestate = cpl_errorstate_get();
567  muse_pixtable *slice_pt = slice_pixtable[i_slice];
568  cpl_propertylist *order = cpl_propertylist_new();
569  cpl_propertylist_append_bool(order, MUSE_PIXTABLE_LAMBDA, CPL_FALSE);
570  cpl_table_sort(slice_pt->table, order);
571  cpl_propertylist_delete(order);
572 
573  cpl_table_cast_column(slice_pt->table, MUSE_PIXTABLE_LAMBDA,
574  "lambda_double", CPL_TYPE_DOUBLE);
575  cpl_array *lambda = muse_cpltable_extract_column(slice_pt->table, "lambda_double");
576  cpl_table_unwrap(slice_pt->table, "lambda_double");
577  cpl_array *spectrum = muse_lsf_params_spectrum(lambda, aLines, slice_params);
578 
579  cpl_array *data = muse_cpltable_extract_column(slice_pt->table,
581  cpl_array_subtract(data, spectrum);
582 
583  cpl_size ii;
584  for (ii = 0; ii < cpl_array_get_size(data); ii++) {
585  if (!cpl_array_is_valid(spectrum, ii)) {
586  cpl_table_set_invalid(slice_pt->table, MUSE_PIXTABLE_DATA, ii);
587  }
588  }
589  cpl_array_unwrap(data);
590  cpl_array_delete(spectrum);
591  cpl_array_delete(lambda);
592 
593  if (!cpl_errorstate_is_equal(prestate)) {
594  cpl_errorstate_dump(prestate, CPL_FALSE, NULL);
595  cpl_errorstate_set(prestate);
596  }
597  }
598  muse_pixtable_extracted_delete(slice_pixtable);
599 
600  if (aPixtable->header) {
601  /* add the status header */
602  cpl_propertylist_update_bool(aPixtable->header, MUSE_HDR_PT_SKYSUB,
603  CPL_TRUE);
604  cpl_propertylist_set_comment(aPixtable->header, MUSE_HDR_PT_SKYSUB,
605  MUSE_HDR_PT_SKYSUB_COMMENT);
606  }
607  return CPL_ERROR_NONE;
608 }
609 
cpl_array * muse_lsf_params_spectrum(const cpl_array *aLambda, cpl_table *aLines, const muse_lsf_params *aLsfParams)
Create spectrum for a single slice.
void muse_pixtable_extracted_delete(muse_pixtable **aPixtables)
Delete a pixel table array.
unsigned short muse_pixtable_origin_get_slice(uint32_t aOrigin)
Get the slice number from the encoded 32bit origin number.
cpl_size muse_pixtable_extracted_get_size(muse_pixtable **aPixtables)
Get the size of an array of extracted pixel tables.
cpl_error_code muse_sky_lines_apply_strength(cpl_table *, const cpl_array *)
Apply the line strengths to the lines.
void muse_lsf_params_delete(muse_lsf_params *aParams)
Delete an allocated muse_lsf_params structure.
muse_pixtable ** muse_pixtable_extracted_get_slices(muse_pixtable *aPixtable)
Extract one pixel table per IFU and slice.
cpl_size muse_pixtable_get_nrow(const muse_pixtable *aPixtable)
get the number of rows within the pixel table
cpl_error_code muse_cpl_optimize_lvmq(void *aData, cpl_array *aPar, int aSize, muse_cpl_evaluate_func *aFunction, muse_cpl_optimize_control_t *aCtrl)
Minimize a function with the Levenberg-Marquardt algorithm.
muse_lsf_params * muse_lsf_params_get(muse_lsf_params **aParams, int aIFU, int aSlice)
Get the slice LSF parameters for one slice.
cpl_error_code muse_cplarray_poly1d(cpl_array *aArray, const cpl_array *aCoeff)
Apply a polynomial to an array.
cpl_table * table
The pixel table.
#define MUSE_PIXTABLE_DATA
Definition: muse_pixtable.h:48
cpl_array * hermit[MAX_HERMIT_ORDER]
coefficients for the damped gauss-hermitean parametrization
muse_lsf_params * muse_lsf_params_new(cpl_size n_sensit, cpl_size n_lsf_width, cpl_size n_hermit)
Create a new lsf_params structure.
cpl_array * sensitivity
Relative detector sensitivity parametrization.
Structure definition of MUSE pixel table.
cpl_error_code muse_sky_subtract_lines_old(muse_pixtable *aPixtable, cpl_table *aLines, muse_lsf_params **aLsfParams)
Subtract sky lines from a pixtable.
Definition: muse_sky_old.c:532
cpl_array * lsf_width
LSF width.
cpl_array * muse_cpltable_extract_column(cpl_table *aTable, const char *aColumn)
Create an array from a section of a column.
#define MUSE_HDR_PT_SKYSUB
Optimization control parameters.
Definition: muse_optimize.h:32
#define MUSE_PIXTABLE_ORIGIN
Definition: muse_pixtable.h:54
cpl_array * muse_cplarray_diff(const cpl_array *aArray, int aOffset)
Build the difference of any element and one of the next elements.
unsigned short muse_pixtable_origin_get_ifu(uint32_t aOrigin)
Get the IFU number from the encoded 32bit origin number.
cpl_error_code muse_sky_lines_cut(cpl_table *, double)
Remove all lines below a certain flux limit.
#define MUSE_PIXTABLE_LAMBDA
Definition: muse_pixtable.h:53
cpl_error_code muse_sky_lines_fit_old(cpl_table *aSpectrum, cpl_table *aLines)
Fit all entries of the pixel table to the master sky.
Definition: muse_sky_old.c:436
Structure definition of detector (slice) parameters.
cpl_size muse_cplarray_find_sorted(const cpl_array *aArray, double aValue)
Find a row in an array.
cpl_propertylist * header
The FITS header.