FORS Pipeline Reference Manual  5.0.13
moses.c
1 /* $Id: moses.c,v 1.116 2013-10-15 09:27:38 cgarcia Exp $
2  *
3  * This file is part of the MOSES library
4  * Copyright (C) 2002-2010 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: cgarcia $
23  * $Date: 2013-10-15 09:27:38 $
24  * $Revision: 1.116 $
25  * $Name: not supported by cvs2svn $
26  */
27 
28 #ifdef HAVE_CONFIG_H
29 #include <config.h>
30 #endif
31 
32 #include <stdio.h>
33 #include <stdlib.h>
34 #include <string.h>
35 #include <unistd.h>
36 #include <math.h>
37 #include <time.h>
38 
39 #include <fors_utils.h>
40 #include <moses.h>
41 
42 /* Prototypes */
43 static cpl_polynomial *read_global_distortion(cpl_table *global, cpl_size row);
44 
45 
46 /* Cheating: moving here the cpl_tools_get_median_float() prototype,
47  * even if cpl_tool.h is not public. It should be removed as soon as
48  * an image median filtering with generic kernel will be implemented
49  * in the CPL, or as soon as this module will be moved into the CPL. */
50 /* FIXME: This has to be removed!! */
51 
52 float cpl_tools_get_median_float(float *, cpl_size);
53 
54 #define MAX_COLNAME (80)
55 #define STRETCH_FACTOR (1.20)
56 
57 // Related to mos_identify_peaks(), used in multiplex mode
58 
59 static int mos_multiplex = -1;
60 static int mos_region_size = 800;
61 
62 static double default_lines_hi[] = { /* Default sky line catalog */
63  5577.338, /* for high res data */
64  5889.953,
65  5895.923,
66  5915.301,
67  5932.862,
68  5953.420,
69  6257.961,
70  6287.434,
71  6300.304,
72  6306.869,
73  6363.780,
74  6498.729,
75  6533.044,
76  6553.617,
77  6841.945,
78  6863.955,
79  6870.994,
80  6889.288,
81  6900.833,
82  6912.623,
83  6923.220,
84  6939.521,
85  6969.930,
86  7003.858,
87  7244.907,
88  7276.405,
89  7284.439,
90  7316.282,
91  7329.148,
92  7340.885,
93  7358.659,
94  7571.746,
95  7750.640,
96  7759.996,
97  7794.112,
98  7808.467,
99  7821.503,
100  7841.266,
101  7913.708,
102  7949.204,
103  7964.650,
104  7993.332,
105  8014.059,
106  8310.719,
107  8344.602,
108  8382.392,
109  8399.170,
110  8415.231,
111  8430.174,
112  8452.250,
113  8493.389,
114  8791.186,
115  8827.096,
116  8885.850,
117  8903.114,
118  8943.395,
119  8988.366
120  };
121 
122 static double default_lines_lo[] = { /* Default sky line catalog */
123  5577.338, /* for low res data */
124  6300.304,
125  6863.955,
126  7571.746,
127  7964.650,
128  7993.332
129  };
130 
131 
141 /*
142  * The following macros and function for finding the k-th smallest
143  * value on a float array will be accessible from cpl_tools once
144  * this module will be moved into the CPL.
145  */
146 
147 /****
148 
149 #define PIX_SWAP(a,b) { register float t=(a);(a)=(b);(b)=t; }
150 
151 static float kthSmallest(float a[], int n, int k)
152 {
153  register int i,j,l,m;
154  register float x;
155 
156  l = 0;
157  m = n-1;
158  while (l < m) {
159  x = a[k];
160  i = l;
161  j = m;
162  do {
163  while (a[i] < x) {
164  i++;
165  }
166  while (x < a[j]) {
167  j--;
168  }
169  if (i <= j) {
170  PIX_SWAP(a[i],a[j]);
171  i++;
172  j--;
173  }
174  } while (i <= j);
175 
176  if (j < k) {
177  l = i;
178  }
179  if (k < i) {
180  m = j;
181  }
182 
183  }
184  return(a[k]);
185 }
186 
187 #define medianWirth(a, n) kthSmallest(a, n, (((n)&1) ? ((n)/2) : (((n)/2)-1)))
188 
189 ****/
190 
191 /*
192  * Return random number with gaussian distribution (mean = 0, variance = 1)
193  * (Box-Mueller method). The mos_randg() argument is either true or false,
194  * indicating whether to "seed" or not the sequence of generated random
195  * numbers. The "seeding" is performed just at the first mos_randg(1) call,
196  * and at further calls the input argument is ignored. This function
197  * generates two random numbers at each call, returning the first one
198  * at odd calls, and the second one at even calls.
199  */
200 
201 static void mos_seed(void)
202 {
203  srand((unsigned int)time((time_t *)0));
204 }
205 
206 static double mos_randg(int seme)
207 {
208  static int doit = 1;
209  static int gotit = 1;
210  double x1, x2, w, y1;
211  static double y2;
212 
213  if (gotit && seme) {
214  mos_seed();
215  gotit = 0;
216  }
217 
218  if (doit) {
219  doit = 0;
220  do {
221  x1 = 2.0 * (double)rand() / RAND_MAX - 1.0;
222  x2 = 2.0 * (double)rand() / RAND_MAX - 1.0;
223  w = x1 * x1 + x2 * x2;
224  } while (w >= 1.0 || w == 0.0);
225 
226  w = sqrt( (-2.0 * log(w)) / w);
227 
228  y1 = x1 * w;
229  y2 = x2 * w;
230  return y1;
231  }
232 
233  doit = 1;
234  return y2;
235 }
236 
237 /*
238  * This function contained a dependency on the VIMOS library
239  * (function medianPixelvalue()): it should be removed as soon as an
240  * image median filtering with generic kernel will be implemented
241  * in the CPL. Currently it has been solved by a direct call to
242  * a cpl_tool function.
243  */
244 
245 static cpl_image *mos_image_vertical_median_filter(cpl_image *ima_in,
246  int filtsizey, int refrow,
247  int above, int below, int step)
248 {
249 
250  const char *func = "mos_image_general_median_filter";
251 
252  cpl_image *filt_img = NULL;
253  int col, row;
254  float *buf = NULL;
255  float *data;
256  float *fdata;
257  int upright_y, loleft_y;
258  int j;
259  int yIsEven = !(filtsizey - (filtsizey/2)*2);
260  int f2y;
261  int nx = cpl_image_get_size_x(ima_in);
262  int ny = cpl_image_get_size_y(ima_in);
263  int firstRow;
264 
265 
266  if (yIsEven) filtsizey++;
267 
268  if (ny <= filtsizey) {
269  cpl_msg_error(func,
270  "Median filter size: %d, image size: %d", filtsizey, ny);
271  return NULL;
272  }
273 
274  f2y = filtsizey / 2;
275 
276  filt_img = cpl_image_duplicate(ima_in);
277  buf = cpl_malloc(filtsizey * sizeof(float));
278  data = cpl_image_get_data(ima_in);
279  fdata = cpl_image_get_data(filt_img);
280 
281  firstRow = refrow - step * (below / step);
282  if (firstRow < f2y)
283  firstRow += step;
284 
285  for (col = 0; col < nx; col++) {
286  for (row = firstRow; row < refrow + above; row += step) {
287  if (row >= ny - f2y)
288  break;
289  loleft_y = row - f2y;
290  upright_y = row + f2y + 1;
291  for (j = loleft_y; j < upright_y; j++)
292  buf[j - loleft_y] = data[col + j * nx];
293 
294  fdata[col + row * nx] = cpl_tools_get_median_float(buf, filtsizey);
295  }
296  }
297 
298  cpl_free(buf);
299 
300  return filt_img;
301 
302 }
303 
304 
305 /*
306  * The following static function is used to find an accurate position
307  * of a peak within a short interval (however at least 5 pixels long).
308  * The basic idea is to find the baricenter of all the pixel values
309  * that pass a threshold level between the median value and the maximum
310  * value within the examined interval (in case such levels are equal,
311  * the input is considered flat and no position is returned). At least
312  * minPoints must pass this threshold, or no position is computed. To
313  * evaluate the significance of the computed baricenter, the variance
314  * of the contributing positions (relative to the found baricenter) is
315  * also evaluated, and compared with the expected variance for a uniform
316  * distribution of positions. If the observed variance is greater than
317  * 80% of the variance of the uniform distribution, the found position
318  * is rejected.
319  */
320 
321 static int peakPosition(const float *data, int size, float *position,
322  int minPoints)
323 {
324  int i;
325  int count = 0;
326  float *copy;
327  float max, median, level, pos, variance, uniformVariance;
328  double sum, weights;
329 
330 
331  if (data == NULL)
332  return 1;
333 
334  if (size < 5) /* Hardcoded, I know... */
335  return 1;
336 
337 
338  /*
339  * Find median level
340  */
341 
342  copy = (float *) cpl_malloc(size*sizeof(float));
343  for (i = 0; i < size; i++)
344  copy[i] = data[i];
345  median = cpl_tools_get_median_float(copy, size);
346  cpl_free(copy);
347 
348 
349  /*
350  * Find max
351  */
352 
353  max = data[0];
354  for (i = 1; i < size; i++)
355  if (data[i] > max)
356  max = data[i];
357 
358 
359  /*
360  * If the max equals the median we have a flat input, therefore
361  * no peak is found.
362  */
363 
364  if (max-median < 0.00001)
365  return 1;
366 
367 
368  /*
369  * Discrimination level: only pixels with values above this
370  * level are considered in baricenter calculation.
371  */
372 
373  level = (max + median) / 2;
374 
375 
376  /*
377  * Of the values above this level compute the baricenter and
378  * then the variance of the positions used. Note that the weights
379  * are taken as the difference between the pixels values and
380  * the median level (supposedly the background).
381  */
382 
383  count = 0;
384  for (i = 0, sum = 0., weights = 0.; i < size; i++) {
385  if (data[i] > level) {
386  count++;
387  weights += (data[i] - median);
388  sum += i * (data[i] - median);
389  }
390  }
391 
392 
393  /*
394  * If too few values are above threshold, refuse the position
395  * as insignificant
396  */
397 
398  if (count < minPoints)
399  return 1;
400 
401  pos = sum / weights;
402  for (i = 0, sum = 0., weights = 0.; i < size; i++) {
403  if (data[i] > level) {
404  weights++;
405  sum += (i - pos) * (i - pos);
406  }
407  }
408  variance = sqrt(sum / weights);
409 
410 
411  /*
412  * The "uniform variance" is the variance that should be obtained
413  * in the case of uniform distribution of the points positions in
414  * the selected interval. If the real variance is comparable with
415  * this value, the peak is considered not found.
416  */
417 
418  uniformVariance = sqrt(size*size/3 - pos*size + pos*pos);
419 
420  if (variance > 0.8 * uniformVariance)
421  return 1;
422 
423  *position = pos + 0.5;
424 
425  return 0;
426 }
427 
428 
429 /*
430  * The following static function determines the quantity dx to be
431  * added to the position of the highest pixel of a fiber profile,
432  * to get the true position of the profile maximum. All is needed
433  * is the maximum observed value v2 in the profile, and the observed
434  * values v1 and v3 of the previous and the next pixels in the profile.
435  *
436  * The following ratio is defined:
437  *
438  * R = 0.5 (v3 - v1) / (2*v2 - v3 - v1)
439  *
440  * This is a conventional ratio that wouldn't diverge for any set of
441  * pixel values, and that would not depend on the presence of background
442  * (with the assumption that the background level is the same for the
443  * three pixels). R has also been chosen in such a way that its value
444  * is already quite close to the real dx. It should be noted that the
445  * following condition should be fulfilled:
446  *
447  * v1 <= v2 and v3 < v2
448  * or
449  * v1 < v2 and v3 <= v2
450  *
451  * This implies that dx varies between -0.5 and 0.5 pixels. In such
452  * boundary cases, one has:
453  *
454  * v2 = v1 and R = dx = -0.5
455  * v2 = v3 and R = dx = 0.5
456  *
457  * Another special case is when the observed pixel values are perfectly
458  * symmetrical:
459  *
460  * v1 = v3 and R = dx = 0.0
461  *
462  * In all the intermediate cases the relation between R and dx depends
463  * on the shape of the fiber profile, that has been determined elsewhere.
464  * Using the accurate reconstruction of the fiber profile obtained by
465  * the * functions ifuProfile() and rebinProfile(), it can be shown
466  * that R differs from dx always less than 0.01 pixels. If the condition
467  *
468  * v1 <= v2 and v3 < v2
469  * or
470  * v1 < v2 and v3 <= v2
471  *
472  * is not fulfilled, then this function returns the value 2.0.
473  */
474 
475 static double values_to_dx(double v1, double v2, double v3)
476 {
477 
478  static double epsilon = 0.00000001;
479  double r = 2.0;
480 
481 
482  if (v1 > v2 || v3 > v2)
483  return r;
484 
485  if (2 * v2 - v1 - v3 < epsilon)
486  return r;
487 
488  r = 0.5 * (v3 - v1) / (2 * v2 - v3 - v1);
489 
490  return r;
491 
492 }
493 
494 
495 /*
496  * The following static function passes a min filter of given box
497  * size on the data buffer. The box size must be a positive odd integer.
498  */
499 
500 static float *min_filter(float *buffer, int length, int size)
501 {
502  float *minf = cpl_calloc(length, sizeof(float));
503  float min;
504  int start = size / 2;
505  int end = length - size / 2;
506  int i, j;
507 
508 
509  for (i = start; i < end; i++) {
510  min = buffer[i-start];
511  for (j = i - start + 1; j <= i + start; j++)
512  if (min > buffer[j])
513  min = buffer[j];
514  minf[i] = min;
515  }
516 
517  for (i = 0; i < start; i++)
518  minf[i] = minf[start];
519 
520  for (i = end; i < length; i++)
521  minf[i] = minf[end-1];
522 
523  return minf;
524 }
525 
526 
527 /*
528  * The following static function passes a max filter of given box
529  * size on the data buffer. The box size must be a positive odd integer.
530  */
531 
532 static float *max_filter(float *buffer, int length, int size)
533 {
534  float *maxf = cpl_calloc(length, sizeof(float));
535  float max;
536  int start = size / 2;
537  int end = length - size / 2;
538  int i, j;
539 
540 
541  for (i = start; i < end; i++) {
542  max = buffer[i-start];
543  for (j = i - start + 1; j <= i + start; j++)
544  if (max < buffer[j])
545  max = buffer[j];
546  maxf[i] = max;
547  }
548 
549  for (i = 0; i < start; i++)
550  maxf[i] = maxf[start];
551 
552  for (i = end; i < length; i++)
553  maxf[i] = maxf[end-1];
554 
555  return maxf;
556 }
557 
558 
559 /*
560  * The following static function passes a running average of given box
561  * size on the data buffer. The box size must be a positive odd integer.
562  */
563 
564 static float *smo_filter(float *buffer, int length, int size)
565 {
566  float *smof = cpl_calloc(length, sizeof(float));
567  double sum;
568  int start = size / 2;
569  int end = length - size / 2;
570  int i, j;
571 
572 
573  for (i = start; i < end; i++) {
574  sum = 0.0;
575  for (j = i - start; j <= i + start; j++)
576  sum += buffer[j];
577  smof[i] = sum / size;
578  }
579 
580  for (i = 0; i < start; i++)
581  smof[i] = smof[start];
582 
583  for (i = end; i < length; i++)
584  smof[i] = smof[end-1];
585 
586  return smof;
587 }
588 
589 /*
590  * The following two static functions are used to read and write from the
591  * global distortion table the different model components. Conventionally
592  * the table consists of 6 columns and 10 rows. Each row is just ordered
593  * storage for model coefficients, and these functions guarantee that the
594  * coefficients are read in and written out correctly, independent on their
595  * physical meaning. The first 6 table rows are a description of the IDS
596  * coefficients, followed by a row containing only the used reference
597  * wavelength. The remaining 3 are a description of the spectral curvature.
598  * The first row is a description of coefficient c0, the second of coefficient
599  * c1, etc., of the IDS. The 8th row is a description of coefficient c0,
600  * the 9th of coefficient c1, etc., of the spectral curvature. All are
601  * bivariate polynomialx on x,y mask coordinates. If the input table
602  * to the write routine is NULL, it is allocated and initialised. Also
603  * the input polynomial could be NULL, and nothing would be written to
604  * the table. If both pointers are NULL the function is basically a
605  * constructor of the global distortion table.
606  */
607 
608 static cpl_polynomial *read_global_distortion(cpl_table *global, cpl_size row)
609 {
610  cpl_polynomial *poly = NULL;
611  cpl_size p[2];
612  cpl_size degree = 2;
613  int null;
614  double coeff;
615 
616  char name[MAX_COLNAME];
617 
618 
619  for (p[0] = 0; p[0] <= degree; p[0]++) {
620  for (p[1] = 0; p[1] <= degree - p[0]; p[1]++) {
621  snprintf(name, MAX_COLNAME, "a%"CPL_SIZE_FORMAT"%"CPL_SIZE_FORMAT"", p[0], p[1]);
622  coeff = cpl_table_get_double(global, name, row, &null);
623  if (null)
624  continue;
625  if (poly == NULL)
626  poly = cpl_polynomial_new(2);
627  cpl_polynomial_set_coeff(poly, p, coeff);
628  }
629  }
630 
631  return poly;
632 }
633 
634 static cpl_table *write_global_distortion(cpl_table *global, int row,
635  cpl_polynomial *poly)
636 {
637  cpl_table *table;
638  cpl_size p[2];
639  cpl_size degree = 2;
640  int nrow = 13;
641 
642  char name[MAX_COLNAME];
643 
644 
645  if (global) {
646  table = global;
647  }
648  else {
649  table = cpl_table_new(nrow);
650  for (p[0] = 0; p[0] <= degree; p[0]++) {
651  for (p[1] = 0; p[1] <= degree - p[0]; p[1]++) {
652  snprintf(name, MAX_COLNAME, "a%"CPL_SIZE_FORMAT"%"CPL_SIZE_FORMAT"", p[0], p[1]);
653  cpl_table_new_column(table, name, CPL_TYPE_DOUBLE);
654  }
655  }
656  }
657 
658  if (poly) {
659  for (p[0] = 0; p[0] <= degree; p[0]++) {
660  for (p[1] = 0; p[1] <= degree - p[0]; p[1]++) {
661  snprintf(name, MAX_COLNAME, "a%"CPL_SIZE_FORMAT"%"CPL_SIZE_FORMAT"", p[0], p[1]);
662  cpl_table_set_double(table, name, row,
663  cpl_polynomial_get_coeff(poly, p));
664  }
665  }
666  }
667 
668  return table;
669 }
670 
671 
672 /*
673  * The following static function is performing a robust linear fit
674  * (drawn from the VIMOS library, and originally from ESO-Eclipse).
675  *
676  * ----> y = a + b * x
677  *
678  * This function return 0 on success.
679  */
680 
681 #define SEGNO(a,b) ((b) >= 0.0 ? fabs(a) : -fabs(a))
682 static int robustLinearFit(cpl_bivector *list, double *a, double *b,
683  double *abdev)
684 {
685  cpl_vector *vx;
686  cpl_vector *vy;
687  cpl_vector *va;
688 
689  double aa, bb, bcomp, b1, b2, del, abdevt, f, f1, f2, sigb, temp, d, sum;
690  double sx, sy, sxy, sxx, chisq;
691  double *arr;
692  double aa_ls, bb_ls;
693  double *x;
694  double *y;
695  int np;
696  int iter;
697  int max_iterate = 30;
698  int i;
699 
700 
701  np = cpl_bivector_get_size(list);
702  vx = cpl_bivector_get_x(list);
703  vy = cpl_bivector_get_y(list);
704  x = cpl_vector_get_data(vx);
705  y = cpl_vector_get_data(vy);
706 
707  sx = sy = sxx = sxy = 0.00;
708  for (i = 0; i < np; i++) {
709  sx += x[i];
710  sy += y[i];
711  sxy += x[i] * y[i];
712  sxx += x[i] * x[i];
713  }
714 
715  del = np * sxx - sx * sx;
716  aa_ls = aa = (sxx * sy - sx * sxy) / del;
717  bb_ls = bb = (np * sxy - sx * sy) / del;
718 
719  chisq = 0.00;
720  for (i = 0; i < np; i++) {
721  temp = y[i] - (aa+bb*x[i]);
722  temp *= temp;
723  chisq += temp;
724  }
725 
726  va = cpl_vector_new(np);
727  arr = cpl_vector_get_data(va);
728  sigb = sqrt(chisq/del);
729  b1 = bb;
730 
731  bcomp = b1;
732  sum = 0.00;
733  for (i = 0; i < np; i++) {
734  arr[i] = y[i] - bcomp * x[i];
735  }
736  aa = cpl_vector_get_median_const(va);
737  abdevt = 0.0;
738  for (i = 0; i < np; i++) {
739  d = y[i] - (bcomp * x[i] + aa);
740  abdevt += fabs(d);
741  if (y[i] != 0.0)
742  d /= fabs(y[i]);
743  if (fabs(d) > 1e-7)
744  sum += (d >= 0.0 ? x[i] : -x[i]);
745  }
746  f1 = sum;
747 
748  b2 = bb + SEGNO(3.0 * sigb, f1);
749 
750  bcomp = b2;
751  sum = 0.00;
752  for (i = 0; i < np; i++) {
753  arr[i] = y[i] - bcomp * x[i];
754  }
755  aa = cpl_vector_get_median_const(va);
756  abdevt = 0.0;
757  for (i = 0; i < np; i++) {
758  d = y[i] - (bcomp * x[i] + aa);
759  abdevt += fabs(d);
760  if (y[i] != 0.0)
761  d /= fabs(y[i]);
762  if (fabs(d) > 1e-7)
763  sum += (d >= 0.0 ? x[i] : -x[i]);
764  }
765  f2 = sum;
766 
767  if (fabs(b2-b1)<1e-7) {
768  *a = aa;
769  *b = bb;
770  *abdev = abdevt / (double)np;
771  cpl_vector_delete(va);
772  return 0;
773  }
774 
775  iter = 0;
776  while (f1*f2 > 0.0) {
777  bb = 2.0*b2-b1;
778  b1 = b2;
779  f1 = f2;
780  b2 = bb;
781 
782  bcomp = b2;
783  sum = 0.00;
784  for (i = 0; i < np; i++) {
785  arr[i] = y[i] - bcomp * x[i];
786  }
787  aa = cpl_vector_get_median_const(va);
788  abdevt = 0.0;
789  for (i = 0; i < np; i++) {
790  d = y[i] - (bcomp * x[i] + aa);
791  abdevt += fabs(d);
792  if (y[i] != 0.0)
793  d /= fabs(y[i]);
794  if (fabs(d) > 1e-7)
795  sum += (d >= 0.0 ? x[i] : -x[i]);
796  }
797  f2 = sum;
798  iter++;
799  if (iter >= max_iterate)
800  break;
801  }
802  if (iter >= max_iterate) {
803  *a = aa_ls;
804  *b = bb_ls;
805  *abdev = -1.0;
806  cpl_vector_delete(va);
807  return 1;
808  }
809 
810  sigb = 0.01 * sigb;
811  while (fabs(b2-b1) > sigb) {
812  bb = 0.5 * (b1 + b2);
813  if ((fabs(bb-b1) < 1e-7) || (fabs(bb-b2) < 1e-7))
814  break;
815  bcomp = bb;
816  sum = 0.0;
817  for (i = 0; i < np; i++) {
818  arr[i] = y[i] - bcomp * x[i];
819  }
820  aa = cpl_vector_get_median_const(va);
821  abdevt = 0.0;
822  for (i = 0; i < np; i++) {
823  d = y[i] - (bcomp * x[i] + aa);
824  abdevt += fabs(d);
825  if (y[i] != 0.0)
826  d /= fabs(y[i]);
827  if (fabs(d) > 1e-7)
828  sum += (d >= 0.0 ? x[i] : -x[i]);
829  }
830  f = sum;
831 
832  if (f*f1 >= 0.0) {
833  f1=f;
834  b1=bb;
835  }
836  else {
837  f2=f;
838  b2=bb;
839  }
840  }
841  cpl_vector_delete(va);
842  *a = aa;
843  *b = bb;
844  *abdev = abdevt / np;
845  return 0;
846 }
847 #undef SEGNO
848 
849 /*
850  * The following static function applies the Hough transform from a table
851  * of points to another table of points. Given the points p_i = (x_i,y_i)
852  * and p_j = (x_j,y_j), the point (X,Y) with X = (y_i - y_j)/(x_i - x_j)
853  * and Y = y_i - X*x_i is computed and added to the output table for each
854  * p_i, p_j pair. This means that if the input table has N points, the
855  * output table has N*(N-1)/2 points.
856  */
857 
858 /* static */
859 cpl_table *mos_hough_table(cpl_table *table, const char *x, const char *y)
860 {
861  cpl_table *output;
862  double *xdata;
863  double *ydata;
864  double *xodata;
865  double *yodata;
866  int npoints;
867  int opoints;
868  int i, j, k;
869 
870 
871  npoints = cpl_table_get_nrow(table);
872  opoints = npoints*(npoints-1)/2;
873 
874  output = cpl_table_new(opoints);
875  cpl_table_new_column(output, "m", CPL_TYPE_DOUBLE);
876  cpl_table_new_column(output, "q", CPL_TYPE_DOUBLE);
877  cpl_table_fill_column_window_double(output, "m", 0, opoints, 0.0);
878  cpl_table_fill_column_window_double(output, "q", 0, opoints, 0.0);
879 
880  xodata = cpl_table_get_data_double(output, "m");
881  yodata = cpl_table_get_data_double(output, "q");
882 
883  cpl_table_cast_column(table, x, "x", CPL_TYPE_DOUBLE);
884  cpl_table_cast_column(table, y, "y", CPL_TYPE_DOUBLE);
885 
886  xdata = cpl_table_get_data_double(table, "x");
887  ydata = cpl_table_get_data_double(table, "y");
888 
889  k = 0;
890  for (i = 0; i < npoints; i++) {
891  for (j = i+1; j < npoints; j++) {
892  xodata[k] = (ydata[i]-ydata[j])/(xdata[i]-xdata[j]);
893  yodata[k] = ydata[i] - xodata[k] * xdata[i];
894  k++;
895  }
896  }
897 
898  if (k != opoints)
899  printf("Assert k = %d, expected %d\n", k, opoints);
900 
901  cpl_table_erase_column(table, "x");
902  cpl_table_erase_column(table, "y");
903 
904  return output;
905 }
906 
907 
908 /*
909  * The following static function is performing the spectral
910  * extraction for the function mos_extract_objects()
911  */
912 
913 static void mos_extraction(cpl_image *sciwin, cpl_image *sci_var_win,
914  cpl_image *skywin,
915  cpl_image *extracted, cpl_image *sky,
916  cpl_image *error, int nobjects, int extraction,
917  double ron, double conad, int ncomb)
918 {
919 
920  cpl_vector *vprofile;
921  cpl_image *smowin;
922 
923  int i, j;
924  int specLen;
925  int numRows;
926  int index;
927  int iter;
928  int maxIter = 2; /* Not less than 2 !!! */
929  int smoothBox = 31; /* Not less than 5 !!! */
930  double nsigma = 5.0;
931 
932  double sumWeight, sum, sumSky, sumProf, sumVar, variance, weight;
933  double *profile;
934  double *buffer;
935  float *edata;
936  float *ekdata;
937  float *endata;
938  float *sdata;
939  float *kdata;
940  float *fdata;
941  float *vardata;
942 
943  double value;
944 
945 
946  specLen = cpl_image_get_size_x(sciwin);
947  numRows = cpl_image_get_size_y(sciwin);
948 
949  edata = cpl_image_get_data(extracted);
950  edata += nobjects * specLen;
951 
952  ekdata = cpl_image_get_data(sky);
953  ekdata += nobjects * specLen;
954 
955  endata = cpl_image_get_data(error);
956  endata += nobjects * specLen;
957 
958  sdata = cpl_image_get_data(sciwin);
959  kdata = cpl_image_get_data(skywin);
960  if(sci_var_win != NULL)
961  vardata = cpl_image_get_data(sci_var_win);
962 
963  /*
964  * Initial spectrum estimate
965  if (sdata[i + j * specLen] > 0.0)
966  */
967 
968  if (extraction && numRows > 5) {
969  smowin = mos_image_filter_median(sciwin, 3, 3);
970  fdata = cpl_image_get_data(smowin);
971  for (i = 0; i < specLen; i++)
972  for (j = 0, edata[i] = 0.0; j < numRows; j++)
973  edata[i] += fdata[i + j * specLen];
974  cpl_image_delete(smowin);
975  }
976  else {
977  for (i = 0; i < specLen; i++)
978  for (j = 0, edata[i] = 0.0; j < numRows; j++)
979  edata[i] += sdata[i + j * specLen];
980  }
981 
982  if (extraction) {
983 
984  profile = cpl_calloc(specLen * numRows, sizeof(double));
985  buffer = cpl_calloc(specLen, sizeof(double));
986 
987  for (iter = 0; iter < maxIter; iter++) {
988 
989  /*
990  * Normalised spatial profile
991  */
992 
993  for (i = 0; i < specLen; i++) {
994  for (j = 0; j < numRows; j++) {
995  index = i + j * specLen;
996 /* if (sdata[index] > 0.0 && edata[i] > 0.00001) */
997  if (fabs(edata[i]) > 0.00001)
998  profile[index] = sdata[index] / edata[i];
999  else
1000  profile[index] = 0.0;
1001  }
1002  }
1003 
1004  for (j = 0; j < numRows; j++) {
1005 
1006  /*
1007  * Smooth each row in the dispersion direction, and enforce positivity
1008  */
1009 
1010  for (i = 0; i < specLen - smoothBox; i++) {
1011  vprofile = cpl_vector_wrap(smoothBox, profile + i + j*specLen);
1012  value = cpl_vector_get_median_const(vprofile);
1013  cpl_vector_unwrap(vprofile);
1014  if (value < 0)
1015  value = 0.0;
1016  buffer[i + smoothBox / 2] = value;
1017  }
1018 
1019  /*
1020  * Replace the end portions (i.e., not median filtered) with a mean
1021  */
1022 
1023  vprofile = cpl_vector_wrap(smoothBox / 2, profile + j*specLen);
1024  value = cpl_vector_get_mean(vprofile);
1025  cpl_vector_unwrap(vprofile);
1026 
1027  if (value < 0)
1028  value = 0.0;
1029 
1030  for (i = 0; i < smoothBox / 2; i++)
1031  buffer[i] = value;
1032 
1033  vprofile = cpl_vector_wrap(smoothBox / 2,
1034  profile + specLen - smoothBox/2 + j*specLen);
1035  value = cpl_vector_get_mean(vprofile);
1036  cpl_vector_unwrap(vprofile);
1037 
1038  if (value < 0)
1039  value = 0.0;
1040 
1041  for (i = 0; i < smoothBox / 2; i++)
1042  buffer[i + specLen - smoothBox / 2] = value;
1043 
1044  for (i = 0; i < specLen; i++)
1045  profile[i + j * specLen] = buffer[i];
1046 
1047  }
1048 
1049  /*
1050  * Enforce normalization of spatial profile after smoothing
1051  */
1052 
1053  for (i = 0; i < specLen; i++) {
1054  for (j = 0, value = 0.0; j < numRows; j++)
1055  value += profile[i + j * specLen];
1056  if (value > 0.00001)
1057  for (j = 0; j < numRows; j++)
1058  profile[i + j * specLen] /= value;
1059  else
1060  for (j = 0; j < numRows; j++)
1061  profile[i + j * specLen] = 0.0;
1062  }
1063 
1064 
1065  /*
1066  * Optimal extraction
1067  */
1068 
1069  for (i = 0; i < specLen; i++) {
1070  sum = 0.0;
1071  sumSky = 0.0;
1072  sumWeight = 0.0;
1073  sumProf = 0.0;
1074  sumVar = 0;
1075  for (j = 0; j < numRows; j++) {
1076  index = i + j * specLen;
1077  /*
1078  if (sdata[index] > 0.0) {
1079  */
1080  //This is the theoretical estimated variance. In principle, since we
1081  //have the propagated variance, we could use that one, but I leave
1082  //this as this is the original algorithm (cgarcia)
1083  variance = ron*ron + fabs(edata[i] * profile[index] + kdata[index])
1084  / conad;
1085  variance /= ncomb; /* If input dataset is sum of ncomb images */
1086  value = sdata[index] - edata[i] * profile[index];
1087  if (fabs(value) / sqrt(variance) < nsigma) {
1088  weight = 1000000 * profile[index] / variance;
1089  sum += weight * sdata[index];
1090  sumSky += weight * kdata[index];
1091  sumWeight += weight * profile[index];
1092  sumProf += profile[index];
1093  //This is how we propagated the variance. We assume that the
1094  //weigth has no error, although in has been computed from the
1095  //profile and the theoretical variance (which also includes the data)
1096  if(sci_var_win != NULL)
1097  sumVar += weight * weight * vardata[index];
1098  }
1099  }
1100 
1101  if (sumWeight > 0.00001) {
1102  edata[i] = sum / sumWeight;
1103  ekdata[i] = sumSky / sumWeight;
1104  if(sci_var_win != NULL)
1105  endata[i] = sqrt(sumVar / sumWeight / sumWeight); //This is the error, not the variance.
1106  else
1107  endata[i] = 1000 * sqrt(sumProf / sumWeight); //This was the old formula, which is not a real error propagation
1108  }
1109  else {
1110 /*
1111  edata[i] = 0.0;
1112  ekdata[i] = 0.0;
1113  endata[i] = 0.0;
1114 */
1115  //endata[i] = sqrt(ron*ron + fabs(edata[i] + ekdata[i]) / conad);
1116  }
1117  }
1118  }
1119  cpl_free(profile);
1120  cpl_free(buffer);
1121  }
1122  else {
1123 
1124  /*
1125  * Add sky estimation for the simple aperture extraction.
1126  if (kdata[i + j * specLen] > 0.0)
1127  */
1128 
1129  for (i = 0; i < specLen; i++)
1130  for (j = 0, ekdata[i] = 0.0; j < numRows; j++)
1131  ekdata[i] += kdata[i + j * specLen];
1132 
1133  /*
1134  * Add error estimation for the simple aperture extraction.
1135  */
1136  for (i = 0; i < specLen; i++)
1137  {
1138  if(sci_var_win != NULL)
1139  {
1140  //We propagate the variance of a simple addition
1141  for (j = 0, endata[i] = 0.0; j < numRows; j++)
1142  endata[i] += vardata[i + j * specLen];
1143  endata[i] = sqrt(endata[i]); //We return the error, not the variance
1144  }
1145  else
1146  endata[i] = sqrt(ron*ron + fabs(edata[i] + ekdata[i]) / conad);
1147 
1148  }
1149  }
1150 
1151 }
1152 
1153 
1200 cpl_table *mos_global_distortion(cpl_table *slits, cpl_table *maskslits,
1201  cpl_table *ids, cpl_table *crv,
1202  double reference)
1203 {
1204  const char *func = "mos_global_distortion";
1205 
1206  const char *clab[6] = {"c0", "c1", "c2", "c3", "c4", "c5"};
1207 
1208  cpl_table *global = NULL;
1209  cpl_table *coeff;
1210  cpl_table *dummy;
1211  cpl_vector *ci;
1212  cpl_vector *xmask;
1213  cpl_vector *ymask;
1214  cpl_bivector *mask;
1215  cpl_vector *xccd;
1216  cpl_vector *yccd;
1217  cpl_bivector *ccd;
1218  cpl_polynomial *poly;
1219  double *xtop;
1220  double *ytop;
1221  double *xbottom;
1222  double *ybottom;
1223  double *mxtop;
1224  double *mytop;
1225  double *mxbottom;
1226  double *mybottom;
1227  int *position;
1228  int *length;
1229  int *slit_id;
1230  int *mslit_id;
1231  int nslits, nmaskslits, npoints;
1232  int order;
1233  int i, j;
1234  int minslit = 6; // 12;
1235 
1236 
1237  if (slits == NULL || maskslits == NULL || ids == NULL || crv == NULL) {
1238  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
1239  return NULL;
1240  }
1241 
1242  nslits = cpl_table_get_nrow(slits);
1243 
1244  if (nslits < minslit) {
1245  cpl_msg_warning(func, "Too few slits (%d < %d) for global "
1246  "distortion model determination", nslits, minslit);
1247  return NULL;
1248  }
1249 
1250  nmaskslits = cpl_table_get_nrow(maskslits);
1251 
1252  length = cpl_table_get_data_int(slits, "length");
1253  position = cpl_table_get_data_int(slits, "position");
1254  slit_id = cpl_table_get_data_int(slits, "slit_id");
1255  mslit_id = cpl_table_get_data_int(maskslits, "slit_id");
1256  xtop = cpl_table_get_data_double(slits, "xtop");
1257  ytop = cpl_table_get_data_double(slits, "ytop");
1258  xbottom = cpl_table_get_data_double(slits, "xbottom");
1259  ybottom = cpl_table_get_data_double(slits, "ybottom");
1260  mxtop = cpl_table_get_data_double(maskslits, "xtop");
1261  mytop = cpl_table_get_data_double(maskslits, "ytop");
1262  mxbottom = cpl_table_get_data_double(maskslits, "xbottom");
1263  mybottom = cpl_table_get_data_double(maskslits, "ybottom");
1264 
1265 
1266  /*
1267  * Global IDS
1268  */
1269 
1270  coeff = cpl_table_new(nslits);
1271  cpl_table_copy_structure(coeff, ids);
1272  cpl_table_new_column(coeff, "xccd", CPL_TYPE_DOUBLE);
1273  cpl_table_new_column(coeff, "yccd", CPL_TYPE_DOUBLE);
1274  cpl_table_new_column(coeff, "xmask", CPL_TYPE_DOUBLE);
1275  cpl_table_new_column(coeff, "ymask", CPL_TYPE_DOUBLE);
1276 
1277  for (i = 0; i < nslits; i++) {
1278  for (j = 0; j < nmaskslits; j++) {
1279  if (slit_id[i] == mslit_id[j]) {
1280  cpl_table_set_double(coeff, "xmask", i,
1281  (mxtop[j] + mxbottom[j]) / 2);
1282  cpl_table_set_double(coeff, "ymask", i,
1283  (mytop[j] + mybottom[j]) / 2);
1284  }
1285  }
1286  }
1287 
1288  if (cpl_table_has_invalid(coeff, "xmask")) {
1289  cpl_error_set(func, CPL_ERROR_INCOMPATIBLE_INPUT);
1290  cpl_table_delete(coeff);
1291  return NULL;
1292  }
1293 
1294  for (i = 0; i < nslits; i++) {
1295  cpl_table_set_double(coeff, "xccd", i, (xtop[i] + xbottom[i]) / 2);
1296  cpl_table_set_double(coeff, "yccd", i, (ytop[i] + ybottom[i]) / 2);
1297  }
1298 
1299  for (i = 0; i < nslits; i++) {
1300 
1301  if (length[i] == 0)
1302  continue;
1303 
1304  cpl_table_and_selected_window(ids, position[i], length[i]);
1305  dummy = cpl_table_extract_selected(ids);
1306  for (j = 0; j < 6; j++) {
1307  if (cpl_table_has_column(dummy, clab[j])) {
1308  if (length[i] - cpl_table_count_invalid(dummy, clab[j]) > 10) {
1309  cpl_table_set_double(coeff, clab[j], i,
1310  cpl_table_get_column_median(dummy, clab[j]));
1311  }
1312  }
1313  }
1314 
1315  cpl_table_delete(dummy);
1316  cpl_table_select_all(ids);
1317 
1318  }
1319 
1320  for (j = 0; j < 6; j++) {
1321  if (cpl_table_has_column(coeff, clab[j])) {
1322  cpl_table_and_selected_invalid(coeff, clab[j]);
1323 
1324  if (cpl_table_not_selected(coeff))
1325  dummy = cpl_table_extract_selected(coeff);
1326  else
1327  break;
1328 
1329  npoints = cpl_table_get_nrow(dummy);
1330 
1331  if (npoints >= 6) {
1332 
1333  if (npoints >= 12)
1334  order = 2;
1335  else
1336  order = 1;
1337 
1338  ci = cpl_vector_wrap(npoints,
1339  cpl_table_get_data_double(dummy, clab[j]));
1340  if (j) {
1341  xccd = cpl_vector_wrap(npoints,
1342  cpl_table_get_data_double(dummy, "xccd"));
1343  yccd = cpl_vector_wrap(npoints,
1344  cpl_table_get_data_double(dummy, "yccd"));
1345  ccd = cpl_bivector_wrap_vectors(xccd, yccd);
1346 
1347 /* %%% */
1348  poly = cpl_polynomial_fit_2d_create(ccd, ci, order, NULL);
1349 
1350  cpl_bivector_unwrap_vectors(ccd);
1351  cpl_vector_unwrap(xccd);
1352  cpl_vector_unwrap(yccd);
1353  cpl_vector_unwrap(ci);
1354  }
1355  else {
1356  xmask = cpl_vector_wrap(npoints,
1357  cpl_table_get_data_double(dummy, "xmask"));
1358  ymask = cpl_vector_wrap(npoints,
1359  cpl_table_get_data_double(dummy, "ymask"));
1360  mask = cpl_bivector_wrap_vectors(xmask, ymask);
1361 
1362 /* %%% */
1363  poly = cpl_polynomial_fit_2d_create(mask, ci, order, NULL);
1364 
1365  cpl_bivector_unwrap_vectors(mask);
1366  cpl_vector_unwrap(xmask);
1367  cpl_vector_unwrap(ymask);
1368  cpl_vector_unwrap(ci);
1369  }
1370  }
1371  else {
1372  cpl_size p[2] = {0, 0};
1373  poly = cpl_polynomial_new(2);
1374  cpl_polynomial_set_coeff(poly, p,
1375  cpl_table_get_column_median(dummy, clab[j]));
1376  }
1377 
1378  cpl_table_delete(dummy);
1379 
1380  global = write_global_distortion(global, j, poly);
1381 
1382  cpl_polynomial_delete(poly);
1383 
1384  cpl_table_select_all(coeff);
1385  }
1386  }
1387 
1388  cpl_table_delete(coeff);
1389 
1390 
1391  /*
1392  * Add model's reference wavelength
1393  */
1394 
1395  cpl_table_set_double(global, "a00", 6, reference);
1396 
1397 
1398  /*
1399  * Global curvature model
1400  */
1401 
1402  coeff = cpl_table_duplicate(crv);
1403  cpl_table_new_column(coeff, "xmask", CPL_TYPE_DOUBLE);
1404  cpl_table_new_column(coeff, "ymask", CPL_TYPE_DOUBLE);
1405  cpl_table_new_column(coeff, "xccd", CPL_TYPE_DOUBLE);
1406  cpl_table_new_column(coeff, "yccd", CPL_TYPE_DOUBLE);
1407  slit_id = cpl_table_get_data_int(coeff, "slit_id");
1408  npoints = cpl_table_get_nrow(coeff);
1409 
1410  for (i = 0; i < npoints; i++) {
1411  for (j = 0; j < nmaskslits; j++) {
1412  if (slit_id[i] == mslit_id[j]) {
1413  if (i%2) {
1414  cpl_table_set_double(coeff, "xmask", i, mxbottom[j]);
1415  cpl_table_set_double(coeff, "ymask", i, mybottom[j]);
1416  }
1417  else {
1418  cpl_table_set_double(coeff, "xmask", i, mxtop[j]);
1419  cpl_table_set_double(coeff, "ymask", i, mytop[j]);
1420  }
1421  }
1422  }
1423  if (i%2)
1424  {
1425  cpl_table_set_double(coeff, "xccd", i, xtop[(i-1)/2]);
1426  cpl_table_set_double(coeff, "yccd", i, ytop[(i-1)/2]);
1427  }
1428  else
1429  {
1430  cpl_table_set_double(coeff, "xccd", i, xbottom[i/2]);
1431  cpl_table_set_double(coeff, "yccd", i, ybottom[i/2]);
1432  }
1433  }
1434 
1435  if (cpl_table_has_invalid(coeff, "xmask")) {
1436  cpl_error_set(func, CPL_ERROR_INCOMPATIBLE_INPUT);
1437  cpl_table_delete(coeff);
1438  return NULL;
1439  }
1440 
1441  for (j = 0; j < 3; j++) {
1442  cpl_polynomial * poly_ccd;
1443  if (cpl_table_has_column(coeff, clab[j])) {
1444  cpl_table_and_selected_invalid(coeff, clab[j]);
1445 
1446  if (cpl_table_not_selected(coeff))
1447  dummy = cpl_table_extract_selected(coeff);
1448  else
1449  break;
1450 
1451  npoints = cpl_table_get_nrow(dummy);
1452 
1453  if (npoints >= 6) {
1454 
1455  if (npoints >= 12)
1456  order = 2;
1457  else
1458  order = 1;
1459 
1460  ci = cpl_vector_wrap(npoints,
1461  cpl_table_get_data_double(dummy, clab[j]));
1462  xmask = cpl_vector_wrap(npoints,
1463  cpl_table_get_data_double(dummy, "xmask"));
1464  ymask = cpl_vector_wrap(npoints,
1465  cpl_table_get_data_double(dummy, "ymask"));
1466  mask = cpl_bivector_wrap_vectors(xmask, ymask);
1467 
1468  poly = cpl_polynomial_fit_2d_create(mask, ci, order, NULL);
1469 
1470  xccd = cpl_vector_wrap(npoints,
1471  cpl_table_get_data_double(dummy, "xccd"));
1472  yccd = cpl_vector_wrap(npoints,
1473  cpl_table_get_data_double(dummy, "yccd"));
1474  ccd = cpl_bivector_wrap_vectors(xccd, yccd);
1475 
1476  poly_ccd = cpl_polynomial_fit_2d_create(ccd, ci, order, NULL);
1477 
1478 
1479  cpl_bivector_unwrap_vectors(mask);
1480  cpl_vector_unwrap(ci);
1481  cpl_vector_unwrap(xmask);
1482  cpl_vector_unwrap(ymask);
1483  }
1484  else {
1485  cpl_size p[2] = {0, 0};
1486  poly = cpl_polynomial_new(2);
1487  cpl_polynomial_set_coeff(poly, p,
1488  cpl_table_get_column_median(dummy, clab[j]));
1489  }
1490 
1491  cpl_table_delete(dummy);
1492 
1493  global = write_global_distortion(global, j + 7, poly);
1494  global = write_global_distortion(global, j + 10, poly_ccd);
1495 
1496  cpl_polynomial_delete(poly);
1497  cpl_polynomial_delete(poly_ccd);
1498  cpl_table_select_all(coeff);
1499  }
1500  }
1501 
1502  cpl_table_delete(coeff);
1503 
1504  return global;
1505 
1506 }
1507 
1508 
1546 cpl_table *mos_build_slit_location(cpl_table *global, cpl_table *maskslits,
1547  int ysize)
1548 {
1549  const char *func = "mos_build_slit_location";
1550 
1551  cpl_propertylist *sort_col;
1552  cpl_polynomial *ids0;
1553  cpl_polynomial *crv[3];
1554  cpl_polynomial *loc_crv;
1555  cpl_vector *point;
1556  cpl_table *slits;
1557  cpl_size nslits;
1558  int *slit_id;
1559  double *dpoint;
1560  double *xtop;
1561  double *ytop;
1562  double *xbottom;
1563  double *ybottom;
1564  double *mxtop;
1565  double *mytop;
1566  double *mxbottom;
1567  double *mybottom;
1568  cpl_size i;
1569  cpl_size j;
1570 
1571 
1572  if (global == NULL || maskslits == NULL) {
1573  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
1574  return NULL;
1575  }
1576 
1577  nslits = cpl_table_get_nrow(maskslits);
1578  slit_id = cpl_table_get_data_int(maskslits, "slit_id");
1579  mxtop = cpl_table_get_data_double(maskslits, "xtop");
1580  mytop = cpl_table_get_data_double(maskslits, "ytop");
1581  mxbottom = cpl_table_get_data_double(maskslits, "xbottom");
1582  mybottom = cpl_table_get_data_double(maskslits, "ybottom");
1583 
1584  slits = cpl_table_duplicate(maskslits);
1585 
1586  xtop = cpl_table_get_data_double(slits, "xtop");
1587  ytop = cpl_table_get_data_double(slits, "ytop");
1588  xbottom = cpl_table_get_data_double(slits, "xbottom");
1589  ybottom = cpl_table_get_data_double(slits, "ybottom");
1590 
1591  ids0 = read_global_distortion(global, 0);
1592  crv[0] = read_global_distortion(global, 7);
1593  crv[1] = read_global_distortion(global, 8);
1594  crv[2] = read_global_distortion(global, 9);
1595 
1596  loc_crv = cpl_polynomial_new(1);
1597 
1598  point = cpl_vector_new(2);
1599  dpoint = cpl_vector_get_data(point);
1600 
1601  for (i = 0; i < nslits; i++) {
1602  dpoint[0] = mxtop[i];
1603  dpoint[1] = mytop[i];
1604 
1605  xtop[i] = cpl_polynomial_eval(ids0, point);
1606 
1607  for (j = 0; j < 3; j++)
1608  if (crv[j])
1609  cpl_polynomial_set_coeff(loc_crv, &j,
1610  cpl_polynomial_eval(crv[j], point));
1611 
1612  ytop[i] = cpl_polynomial_eval_1d(loc_crv, xtop[i], NULL);
1613 
1614  dpoint[0] = mxbottom[i];
1615  dpoint[1] = mybottom[i];
1616  xbottom[i] = cpl_polynomial_eval(ids0, point);
1617 
1618  for (j = 0; j < 3; j++)
1619  if (crv[j])
1620  cpl_polynomial_set_coeff(loc_crv, &j,
1621  cpl_polynomial_eval(crv[j], point));
1622 
1623  ybottom[i] = cpl_polynomial_eval_1d(loc_crv, xbottom[i], NULL);
1624  }
1625 
1626  cpl_vector_delete(point);
1627  cpl_polynomial_delete(ids0);
1628  cpl_polynomial_delete(loc_crv);
1629  for (j = 0; j < 3; j++)
1630  cpl_polynomial_delete(crv[j]);
1631 
1632  sort_col = cpl_propertylist_new();
1633  cpl_propertylist_append_bool(sort_col, "ytop", 1);
1634  cpl_table_sort(slits, sort_col);
1635  cpl_table_sort(maskslits, sort_col);
1636  cpl_propertylist_delete(sort_col);
1637 
1638  /*
1639  * Eliminate slits which are _entirely_ outside the CCD
1640  */
1641 
1642  cpl_table_and_selected_double(slits, "ybottom", CPL_GREATER_THAN, ysize-1);
1643  cpl_table_or_selected_double(slits, "ytop", CPL_LESS_THAN, 0);
1644  cpl_table_erase_selected(slits);
1645 
1646  nslits = cpl_table_get_nrow(slits);
1647 
1648  if (nslits == 0) {
1649  cpl_msg_warning(func, "No slits found on the CCD");
1650  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
1651  cpl_table_delete(slits);
1652  return NULL;
1653  }
1654 
1655  if (nslits > 1)
1656  cpl_msg_info(func, "Slit location: %"CPL_SIZE_FORMAT" slits are entirely or partially "
1657  "contained in CCD", nslits);
1658  else
1659  cpl_msg_info(func, "Slit location: %"CPL_SIZE_FORMAT" slit is entirely or partially "
1660  "contained in CCD", nslits);
1661 
1662  return slits;
1663 
1664 }
1665 
1666 
1693 cpl_table *mos_build_curv_coeff(cpl_table *global, cpl_table *maskslits,
1694  cpl_table *slits)
1695 {
1696  const char *func = "mos_build_curv_coeff";
1697 
1698  const char *clab[6] = {"c0", "c1", "c2", "c3", "c4", "c5"};
1699  /* Max order is 5 */
1700 
1701  cpl_polynomial *crv[3];
1702  cpl_vector *point;
1703  cpl_table *polytraces;
1704  double *dpoint;
1705  double *xtop;
1706  double *ytop;
1707  double *xbottom;
1708  double *ybottom;
1709  int *slit_id;
1710  int *valid_id;
1711  int nslits, nvalid;
1712  int found;
1713  int i, j, k;
1714 
1715 
1716  if (global == NULL || slits == NULL || maskslits == NULL) {
1717  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
1718  return NULL;
1719  }
1720 
1721  nslits = cpl_table_get_nrow(maskslits);
1722  slit_id = cpl_table_get_data_int(maskslits, "slit_id");
1723  xtop = cpl_table_get_data_double(maskslits, "xtop");
1724  ytop = cpl_table_get_data_double(maskslits, "ytop");
1725  xbottom = cpl_table_get_data_double(maskslits, "xbottom");
1726  ybottom = cpl_table_get_data_double(maskslits, "ybottom");
1727 
1728  polytraces = cpl_table_new(2*nslits);
1729  cpl_table_new_column(polytraces, "slit_id", CPL_TYPE_INT);
1730  for (i = 0; i < 3; i++)
1731  cpl_table_new_column(polytraces, clab[i], CPL_TYPE_DOUBLE);
1732 
1733  crv[0] = read_global_distortion(global, 7);
1734  crv[1] = read_global_distortion(global, 8);
1735  crv[2] = read_global_distortion(global, 9);
1736 
1737  point = cpl_vector_new(2);
1738  dpoint = cpl_vector_get_data(point);
1739 
1740  for (i = 0; i < nslits; i++) {
1741  for (j = 0; j < 2; j++) { /* For top and bottom trace of each slit */
1742 
1743  cpl_table_set_int(polytraces, "slit_id", 2*i+j, slit_id[i]);
1744 
1745  if (j) {
1746  dpoint[0] = xbottom[i];
1747  dpoint[1] = ybottom[i];
1748  }
1749  else {
1750  dpoint[0] = xtop[i];
1751  dpoint[1] = ytop[i];
1752  }
1753 
1754  for (k = 0; k < 3; k++)
1755  if (crv[j])
1756  cpl_table_set_double(polytraces, clab[k], 2*i+j,
1757  cpl_polynomial_eval(crv[k], point));
1758  }
1759  }
1760 
1761  cpl_vector_delete(point);
1762  for (j = 0; j < 3; j++)
1763  cpl_polynomial_delete(crv[j]);
1764 
1765  /*
1766  * Eliminate slits which are _entirely_ outside the CCD
1767  */
1768 
1769  nvalid = cpl_table_get_nrow(slits);
1770  valid_id = cpl_table_get_data_int(slits, "slit_id");
1771  cpl_table_unselect_all(polytraces);
1772  for (i = 0; i < nslits; i++) {
1773  found = 0;
1774  for (j = 0; j < nvalid; j++) {
1775  if (slit_id[i] == valid_id[j]) {
1776  found = 1;
1777  break;
1778  }
1779  }
1780  if (!found) {
1781  cpl_table_select_row(polytraces, 2*i);
1782  cpl_table_select_row(polytraces, 2*i + 1);
1783  }
1784  }
1785  cpl_table_erase_selected(polytraces);
1786 
1787  nslits = cpl_table_get_nrow(polytraces);
1788 
1789  if (nslits == 0) {
1790  cpl_msg_warning(func, "No slits found on the CCD");
1791  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
1792  cpl_table_delete(polytraces);
1793  return NULL;
1794  }
1795 
1796  if (nslits > 2)
1797  cpl_msg_info(func, "Curvature model: %d slits are entirely or "
1798  "partially contained in CCD", nslits / 2);
1799  else
1800  cpl_msg_info(func, "Curvature model: %d slit is entirely or "
1801  "partially contained in CCD", nslits / 2);
1802 
1803  return polytraces;
1804 }
1805 
1806 
1848 cpl_table *mos_build_disp_coeff(cpl_table *global, cpl_table *slits)
1849 {
1850  const char *func = "mos_build_disp_coeff";
1851 
1852  const char *clab[6] = {"c0", "c1", "c2", "c3", "c4", "c5"};
1853 
1854  cpl_polynomial *ids[6];
1855  cpl_vector *point;
1856  cpl_table *idscoeff;
1857  double *dpoint;
1858  double *xtop;
1859  double *ytop;
1860  double *xbottom;
1861  double *ybottom;
1862  int *position;
1863  int *length;
1864  int nslits;
1865  int nrows;
1866  int order;
1867  int ylow, yhig;
1868  int i, j, k;
1869 
1870 
1871  if (global == NULL || slits == NULL) {
1872  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
1873  return NULL;
1874  }
1875 
1876  nslits = cpl_table_get_nrow(slits);
1877  position = cpl_table_get_data_int(slits, "position");
1878  length = cpl_table_get_data_int(slits, "length");
1879  xtop = cpl_table_get_data_double(slits, "xtop");
1880  ytop = cpl_table_get_data_double(slits, "ytop");
1881  xbottom = cpl_table_get_data_double(slits, "xbottom");
1882  ybottom = cpl_table_get_data_double(slits, "ybottom");
1883 
1884  for (i = 0; i < 6; i++)
1885  ids[i] = read_global_distortion(global, i);
1886 
1887  for (i = 0; i < 6; i++)
1888  if (ids[i] == NULL)
1889  break;
1890 
1891  order = i - 1;
1892 
1893  if (order < 1) {
1894  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
1895  return NULL;
1896  }
1897 
1898  nrows = 0;
1899  for (i = 0; i < nslits; i++)
1900  nrows += length[i];
1901 
1902  idscoeff = cpl_table_new(nrows);
1903 
1904  for (j = 0; j <= order; j++)
1905  cpl_table_new_column(idscoeff, clab[j], CPL_TYPE_DOUBLE);
1906 
1907  cpl_table_new_column(idscoeff, "error", CPL_TYPE_DOUBLE);
1908  cpl_table_fill_column_window_double(idscoeff, "error", 0, nrows, 0.0);
1909  cpl_table_new_column(idscoeff, "nlines", CPL_TYPE_INT);
1910  cpl_table_fill_column_window_int(idscoeff, "nlines", 0, nrows, 0);
1911 
1912  point = cpl_vector_new(2);
1913  dpoint = cpl_vector_get_data(point);
1914 
1915  for (i = 0; i < nslits; i++) {
1916 
1917  if (length[i] == 0)
1918  continue;
1919 
1920  ylow = position[i];
1921  yhig = ylow + length[i];
1922 
1923  for (j = 0; j <= order; j++) {
1924  if (j) {
1925  for (k = 0; k < length[i]; k++) {
1926  dpoint[0] = xbottom[i] + k*(xtop[i]-xbottom[i])/length[i];
1927  dpoint[1] = ybottom[i] + k*(ytop[i]-ybottom[i])/length[i];
1928  cpl_table_set_double(idscoeff, clab[j], ylow + k,
1929  cpl_polynomial_eval(ids[j], point));
1930  }
1931  }
1932  else {
1933  for (k = 0; k < length[i]; k++) {
1934  cpl_table_set_double(idscoeff, clab[0], ylow + k,
1935  xbottom[i] + k*(xtop[i]-xbottom[i])/length[i]);
1936  }
1937  }
1938  }
1939  }
1940 
1941  cpl_vector_delete(point);
1942  for (j = 0; j < 6; j++)
1943  cpl_polynomial_delete(ids[j]);
1944 
1945  return idscoeff;
1946 
1947 }
1948 
1949 
1972 cpl_image *mos_subtract_sky(cpl_image *science, cpl_table *slits,
1973  cpl_table *polytraces, double reference,
1974  double blue, double red, double dispersion)
1975 {
1976  const char *func = "mos_subtract_sky";
1977 
1978  const char *clab[6] = {"c0", "c1", "c2", "c3", "c4", "c5"};
1979  /* Max order is 5 */
1980 
1981  cpl_image *sky;
1982  cpl_bivector *list;
1983  cpl_vector *listx;
1984  cpl_vector *listy;
1985  cpl_polynomial *polytop;
1986  cpl_polynomial *polybot;
1987  cpl_polynomial *trend;
1988 
1989  int *slit_id;
1990  double *dlistx;
1991  double *dlisty;
1992  float *sdata;
1993  float *kdata;
1994  double top, bot;
1995  int itop, ibot;
1996  double coeff;
1997  double ytop, ybot;
1998  double m, q, err;
1999  int npix;
2000 
2001  int pixel_above, pixel_below, refpixel, start_pixel, end_pixel;
2002  int nx, ny;
2003  int nslits;
2004  int *length;
2005  int missing_top, missing_bot;
2006  int order;
2007  int null;
2008  int window = 50; /* Longer slits have polynomial sky model */
2009  int count;
2010  int i, j;
2011  cpl_size k;
2012 
2013 
2014  if (science == NULL || slits == NULL || polytraces == NULL) {
2015  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
2016  return NULL;
2017  }
2018 
2019  if (dispersion <= 0.0) {
2020  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
2021  return NULL;
2022  }
2023 
2024  if (red - blue < dispersion) {
2025  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
2026  return NULL;
2027  }
2028 
2029  nx = cpl_image_get_size_x(science);
2030  ny = cpl_image_get_size_y(science);
2031 
2032  sky = cpl_image_new(nx, ny, CPL_TYPE_FLOAT);
2033 
2034  sdata = cpl_image_get_data(science);
2035  kdata = cpl_image_get_data(sky);
2036 
2037  nslits = cpl_table_get_nrow(slits);
2038  order = cpl_table_get_ncol(polytraces) - 2;
2039  length = cpl_table_get_data_int(slits, "length");
2040  slit_id = cpl_table_get_data_int(slits, "slit_id");
2041 
2042  /*
2043  * The spatial resampling is performed for a certain number of
2044  * pixels above and below the position of the reference wavelength:
2045  */
2046 
2047  pixel_above = STRETCH_FACTOR * (red - reference) / dispersion;
2048  pixel_below = STRETCH_FACTOR * (reference - blue) / dispersion;
2049 
2050  for (i = 0; i < nslits; i++) {
2051 
2052  if (length[i] == 0)
2053  continue;
2054 
2055 
2056  /*
2057  * Recover from the table of spectral curvature coefficients
2058  * the curvature polynomials.
2059  */
2060 
2061  refpixel = cpl_table_get_double(slits, "xtop", i, NULL);
2062 
2063  start_pixel = refpixel - pixel_below;
2064  if (start_pixel < 0)
2065  start_pixel = 0;
2066 
2067  end_pixel = refpixel + pixel_above;
2068  if (end_pixel > nx)
2069  end_pixel = nx;
2070 
2071  missing_top = 0;
2072  polytop = cpl_polynomial_new(1);
2073  for (k = 0; k <= order; k++) {
2074  coeff = cpl_table_get_double(polytraces, clab[k], 2*i, &null);
2075  if (null) {
2076  cpl_polynomial_delete(polytop);
2077  missing_top = 1;
2078  break;
2079  }
2080  cpl_polynomial_set_coeff(polytop, &k, coeff);
2081  }
2082 
2083  missing_bot = 0;
2084  polybot = cpl_polynomial_new(1);
2085  for (k = 0; k <= order; k++) {
2086  coeff = cpl_table_get_double(polytraces, clab[k], 2*i+1, &null);
2087  if (null) {
2088  cpl_polynomial_delete(polybot);
2089  missing_bot = 1;
2090  break;
2091  }
2092  cpl_polynomial_set_coeff(polybot, &k, coeff);
2093  }
2094 
2095  if (missing_top && missing_bot) {
2096  cpl_msg_debug(func, "Slit %d was not traced: no extraction!",
2097  slit_id[i]);
2098  continue;
2099  }
2100 
2101  /*
2102  * In case just one of the two edges was not traced, the other
2103  * edge curvature model is duplicated and shifted to the other
2104  * end of the slit: better than nothing!
2105  */
2106 
2107  if (missing_top) {
2108  cpl_msg_debug(func, "Upper edge of slit %d was not traced: "
2109  "the spectral curvature of the lower edge "
2110  "is used instead.", slit_id[i]);
2111  polytop = cpl_polynomial_duplicate(polybot);
2112  ytop = cpl_table_get_double(slits, "ytop", i, NULL);
2113  ybot = cpl_table_get_double(slits, "ybottom", i, NULL);
2114  k = 0;
2115  coeff = cpl_polynomial_get_coeff(polybot, &k);
2116  coeff += ytop - ybot;
2117  cpl_polynomial_set_coeff(polytop, &k, coeff);
2118  }
2119 
2120  if (missing_bot) {
2121  cpl_msg_debug(func, "Lower edge of slit %d was not traced: "
2122  "the spectral curvature of the upper edge "
2123  "is used instead.", slit_id[i]);
2124  polybot = cpl_polynomial_duplicate(polytop);
2125  ytop = cpl_table_get_double(slits, "ytop", i, NULL);
2126  ybot = cpl_table_get_double(slits, "ybottom", i, NULL);
2127  k = 0;
2128  coeff = cpl_polynomial_get_coeff(polytop, &k);
2129  coeff -= ytop - ybot;
2130  cpl_polynomial_set_coeff(polybot, &k, coeff);
2131  }
2132 
2133 
2134  /*
2135  * Now read pixel values along spatial direction, and fit them.
2136  */
2137 
2138  for (j = start_pixel; j < end_pixel; j++) {
2139  top = cpl_polynomial_eval_1d(polytop, j, NULL);
2140  bot = cpl_polynomial_eval_1d(polybot, j, NULL);
2141  itop = floor(top + 0.5) + 1;
2142  ibot = floor(bot + 0.5);
2143  if (itop > ny)
2144  itop = ny;
2145  if (ibot < 0)
2146  ibot = 0;
2147  npix = itop - ibot;
2148  if (npix < 5)
2149  break;
2150 
2151  list = cpl_bivector_new(npix);
2152  listx = cpl_bivector_get_x(list);
2153  listy = cpl_bivector_get_y(list);
2154  dlistx = cpl_vector_get_data(listx);
2155  dlisty = cpl_vector_get_data(listy);
2156 
2157  for (k = 0; k < npix; k++) {
2158  dlistx[k] = k;
2159  dlisty[k] = sdata[j + (ibot + k)*nx];
2160  }
2161 
2162  if (robustLinearFit(list, &q, &m, &err)) {
2163  cpl_bivector_delete(list);
2164  continue;
2165  }
2166 
2167  cpl_bivector_delete(list);
2168 
2169  for (k = 0; k < npix; k++) {
2170  kdata[j + (ibot + k)*nx] = m*k + q;
2171  }
2172 
2173  if (npix > window) {
2174 
2175  /*
2176  * Polynomial iteration
2177  */
2178 
2179  err = 3*sqrt(err);
2180 
2181  count = 0;
2182  for (k = 0; k < npix; k++)
2183  if (fabs(sdata[j + (ibot + k)*nx] - m*k - q) < err)
2184  count++;
2185 
2186  if (count < 10)
2187  continue;
2188 
2189  list = cpl_bivector_new(count);
2190  listx = cpl_bivector_get_x(list);
2191  listy = cpl_bivector_get_y(list);
2192  dlistx = cpl_vector_get_data(listx);
2193  dlisty = cpl_vector_get_data(listy);
2194 
2195  count = 0;
2196  for (k = 0; k < npix; k++) {
2197  if (fabs(sdata[j + (ibot + k)*nx] - m*k - q) < err) {
2198  dlistx[count] = k;
2199  dlisty[count] = sdata[j + (ibot + k)*nx];
2200  count++;
2201  }
2202  }
2203 
2204  trend = cpl_polynomial_fit_1d_create(listx, listy, 2, &err);
2205 
2206  cpl_bivector_delete(list);
2207 
2208  err = 3*sqrt(err);
2209 
2210  count = 0;
2211  for (k = 0; k < npix; k++)
2212  if (fabs(sdata[j + (ibot + k)*nx]
2213  - cpl_polynomial_eval_1d(trend, k, NULL)) < err)
2214  count++;
2215 
2216  if (count < 10) {
2217  cpl_polynomial_delete(trend);
2218  continue;
2219  }
2220 
2221  list = cpl_bivector_new(count);
2222  listx = cpl_bivector_get_x(list);
2223  listy = cpl_bivector_get_y(list);
2224  dlistx = cpl_vector_get_data(listx);
2225  dlisty = cpl_vector_get_data(listy);
2226 
2227  count = 0;
2228  for (k = 0; k < npix; k++) {
2229  if (fabs(sdata[j + (ibot + k)*nx]
2230  - cpl_polynomial_eval_1d(trend, k, NULL)) < err) {
2231  dlistx[count] = k;
2232  dlisty[count] = sdata[j + (ibot + k)*nx];
2233  count++;
2234  }
2235  }
2236 
2237  cpl_polynomial_delete(trend);
2238 
2239  trend = cpl_polynomial_fit_1d_create(listx, listy, 3, &err);
2240 
2241  cpl_bivector_delete(list);
2242 
2243  for (k = 0; k < npix; k++) {
2244  kdata[j + (ibot + k)*nx] = cpl_polynomial_eval_1d(trend,
2245  k, NULL);
2246  }
2247 
2248  cpl_polynomial_delete(trend);
2249  }
2250  }
2251  cpl_polynomial_delete(polytop);
2252  cpl_polynomial_delete(polybot);
2253  }
2254 
2255  cpl_image_subtract(science, sky);
2256 
2257  return sky;
2258 }
2259 
2260 
2293 cpl_image *mos_normalise_flat(cpl_image *flat, cpl_image *spatial,
2294  cpl_table *slits, cpl_table *polytraces,
2295  double reference, double blue, double red,
2296  double dispersion, int sradius, int polyorder)
2297 {
2298  const char *func = "mos_normalise_flat";
2299 
2300  const char *clab[6] = {"c0", "c1", "c2", "c3", "c4", "c5"};
2301  /* Max order is 5 */
2302 
2303  cpl_image *rectified;
2304  cpl_image *smo_flat;
2305  cpl_image *exslit;
2306  cpl_vector *positions;
2307  cpl_vector *flux;
2308  cpl_vector *smo_flux;
2309  cpl_polynomial *trend;
2310  cpl_polynomial *polytop;
2311  cpl_polynomial *polybot;
2312 
2313  int *slit_id;
2314  float *p;
2315  float *data;
2316  double *fdata;
2317  double *pdata;
2318  float *sdata;
2319  float *xdata;
2320  float *wdata;
2321  double vtop, vbot, value;
2322  double top, bot;
2323  double coeff;
2324  double ytop, ybot;
2325  double ypos;
2326  double fvalue;
2327  int ivalue;
2328  int yint, yprev;
2329  int npseudo;
2330 
2331  int pixel_above, pixel_below, refpixel, start_pixel, end_pixel;
2332  int nx, ny, nsubx, nsuby;
2333  int xlow, ylow, xhig, yhig;
2334  int nslits;
2335  int *position;
2336  int *length;
2337  int missing_top, missing_bot;
2338  int order;
2339  int npoints;
2340  int uradius;
2341  int null;
2342  int i, j;
2343  cpl_size k;
2344 
2345 /* int exclude = 5; Number of excluded pixels at edges */
2346 
2347  /* For debug puposes only: cpl_image *smo_rectified; */
2348 
2349 
2350  if (flat == NULL || slits == NULL || polytraces == NULL) {
2351  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
2352  return NULL;
2353  }
2354 
2355  if (dispersion <= 0.0) {
2356  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
2357  return NULL;
2358  }
2359 
2360  if (red - blue < dispersion) {
2361  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
2362  return NULL;
2363  }
2364 
2365  rectified = mos_spatial_calibration(flat, slits, polytraces, reference,
2366  blue, red, dispersion, 0, NULL);
2367 
2368  nx = cpl_image_get_size_x(rectified);
2369  ny = cpl_image_get_size_y(rectified);
2370 
2371  smo_flat = cpl_image_new(cpl_image_get_size_x(spatial),
2372  cpl_image_get_size_y(spatial), CPL_TYPE_FLOAT);
2373  wdata = cpl_image_get_data(smo_flat);
2374 
2375  nslits = cpl_table_get_nrow(slits);
2376  order = cpl_table_get_ncol(polytraces) - 2;
2377  position = cpl_table_get_data_int(slits, "position");
2378  length = cpl_table_get_data_int(slits, "length");
2379  slit_id = cpl_table_get_data_int(slits, "slit_id");
2380 
2381  /*
2382  * The spatial resampling is performed for a certain number of
2383  * pixels above and below the position of the reference wavelength:
2384  */
2385 
2386  pixel_above = STRETCH_FACTOR * (red - reference) / dispersion;
2387  pixel_below = STRETCH_FACTOR * (reference - blue) / dispersion;
2388 
2389  xlow = 1;
2390  xhig = nx;
2391  for (i = 0; i < nslits; i++) {
2392 
2393  if (length[i] == 0)
2394  continue;
2395 
2396  /*
2397  * We DON'T write:
2398  *
2399  * ylow = position[i];
2400  * yhig = ylow + length[i];
2401  *
2402  * because the cpl_image pixels are counted from 1, and because in
2403  * cpl_image_extract() the coordinates of the last pixel are inclusive.
2404  */
2405 
2406  ylow = position[i] + 1;
2407  yhig = ylow + length[i] - 1;
2408 
2409  exslit = cpl_image_extract(rectified, xlow, ylow, xhig, yhig);
2410 
2411  if (polyorder < 0) {
2412 
2413  cpl_image_turn(exslit, -1); /* For faster memory access */
2414 
2415  nsubx = cpl_image_get_size_x(exslit);
2416  nsuby = cpl_image_get_size_y(exslit);
2417  data = cpl_image_get_data(exslit);
2418  flux = cpl_vector_new(nsubx);
2419 
2420  uradius = nsubx / 2;
2421  if (uradius > sradius)
2422  uradius = sradius;
2423 
2424  for (j = 0; j < nsuby; j++) {
2425  fdata = cpl_vector_get_data(flux);
2426  p = data;
2427  for (k = 0; k < nsubx; k++)
2428  *fdata++ = *p++;
2429  smo_flux = cpl_vector_filter_median_create(flux, uradius);
2430  fdata = cpl_vector_get_data(smo_flux);
2431  p = data;
2432  for (k = 0; k < nsubx; k++)
2433  *p++ = *fdata++;
2434  cpl_vector_delete(smo_flux);
2435  data += nsubx;
2436  }
2437 
2438  cpl_vector_delete(flux);
2439 
2440 
2441  /*
2442  * First fit fluxes along the spatial direction with a low-degree
2443  * polynomial (excluding the first and the last pixels, close to
2444  * the edges)
2445  */
2446 /*
2447  if (nsubx-2*exclude > 10) {
2448  flux = cpl_vector_new(nsubx-2*exclude);
2449  fdata = cpl_vector_get_data(flux);
2450  positions = cpl_vector_new(nsubx-2*exclude);
2451  for (j = 0; j < nsubx-2*exclude; j++)
2452  cpl_vector_set(positions, j, j+exclude);
2453 
2454  for (k = 0; k < nsuby; k++) {
2455  for (j = 0; j < nsubx-2*exclude; j++)
2456  fdata[j] = data[j+exclude];
2457  trend = cpl_polynomial_fit_1d_create(positions, flux,
2458  1, NULL);
2459  for (j = 0; j < nsubx; j++)
2460  data[j] = cpl_polynomial_eval_1d(trend, j, NULL);
2461  cpl_polynomial_delete(trend);
2462  data += nsubx;
2463  }
2464 
2465  cpl_vector_delete(flux);
2466  cpl_vector_delete(positions);
2467  }
2468 */
2469 
2470  /*
2471  * Now smooth along the dispersion direction
2472  */
2473 
2474  cpl_image_turn(exslit, 1); /* For faster memory access */
2475  nsubx = cpl_image_get_size_x(exslit);
2476  nsuby = cpl_image_get_size_y(exslit);
2477  data = cpl_image_get_data(exslit);
2478 
2479  for (j = 0; j < nsuby; j++) {
2480  flux = cpl_vector_new(nsubx);
2481  fdata = cpl_vector_get_data(flux);
2482  p = data;
2483  for (k = 0; k < nsubx; k++)
2484  *fdata++ = *p++;
2485  smo_flux = cpl_vector_filter_median_create(flux, sradius);
2486  cpl_vector_delete(flux);
2487  fdata = cpl_vector_get_data(smo_flux);
2488  p = data;
2489  for (k = 0; k < nsubx; k++)
2490  *p++ = *fdata++;
2491  cpl_vector_delete(smo_flux);
2492  data += nsubx;
2493  }
2494  }
2495  else {
2496 
2497  /*
2498  * Fit with a polynomial the flat field trend row by row.
2499  */
2500 
2501  nsubx = cpl_image_get_size_x(exslit);
2502  nsuby = cpl_image_get_size_y(exslit);
2503  data = cpl_image_get_data(exslit);
2504 
2505  for (j = 0; j < nsuby; j++) {
2506 
2507  /*
2508  * First get the size of the vectors to allocate:
2509  */
2510 
2511  npoints = 0;
2512  p = data + j*nsubx;
2513  for (k = 0; k < nsubx; k++)
2514  if (p[k] > 1.0)
2515  npoints++;
2516 
2517  if (npoints > polyorder + 1) {
2518 
2519  /*
2520  * Fill the vectors for the fitting
2521  */
2522 
2523  flux = cpl_vector_new(npoints);
2524  fdata = cpl_vector_get_data(flux);
2525  positions = cpl_vector_new(npoints);
2526  pdata = cpl_vector_get_data(positions);
2527 
2528  npoints = 0;
2529  p = data + j*nsubx;
2530  for (k = 0; k < nsubx; k++) {
2531  if (p[k] > 1.0) {
2532  fdata[npoints] = p[k];
2533  pdata[npoints] = k;
2534  npoints++;
2535  }
2536  }
2537 
2538  trend = cpl_polynomial_fit_1d_create(positions, flux,
2539  polyorder, NULL);
2540 
2541  cpl_vector_delete(flux);
2542  cpl_vector_delete(positions);
2543 
2544  if (trend) {
2545  p = data + j*nsubx;
2546  for (k = 0; k < nsubx; k++)
2547  if (p[k] > 1.0)
2548  p[k] = cpl_polynomial_eval_1d(trend, k, NULL);
2549  cpl_polynomial_delete(trend);
2550  }
2551  else {
2552  cpl_msg_warning(func, "Invalid flat field flux fit "
2553  "(ignored)");
2554  }
2555  }
2556  }
2557  }
2558 
2559 
2560  /*
2561  * Recover from the table of spectral curvature coefficients
2562  * the curvature polynomials.
2563  */
2564 
2565  refpixel = cpl_table_get_double(slits, "xtop", i, NULL);
2566 
2567  start_pixel = refpixel - pixel_below;
2568  if (start_pixel < 0)
2569  start_pixel = 0;
2570 
2571  end_pixel = refpixel + pixel_above;
2572  if (end_pixel > nx)
2573  end_pixel = nx;
2574 
2575  missing_top = 0;
2576  polytop = cpl_polynomial_new(1);
2577  for (k = 0; k <= order; k++) {
2578  coeff = cpl_table_get_double(polytraces, clab[k], 2*i, &null);
2579  if (null) {
2580  cpl_polynomial_delete(polytop);
2581  missing_top = 1;
2582  break;
2583  }
2584  cpl_polynomial_set_coeff(polytop, &k, coeff);
2585  }
2586 
2587  missing_bot = 0;
2588  polybot = cpl_polynomial_new(1);
2589  for (k = 0; k <= order; k++) {
2590  coeff = cpl_table_get_double(polytraces, clab[k], 2*i+1, &null);
2591  if (null) {
2592  cpl_polynomial_delete(polybot);
2593  missing_bot = 1;
2594  break;
2595  }
2596  cpl_polynomial_set_coeff(polybot, &k, coeff);
2597  }
2598 
2599  if (missing_top && missing_bot) {
2600  cpl_msg_debug(func, "Slit %d was not traced: no extraction!",
2601  slit_id[i]);
2602  continue;
2603  }
2604 
2605  /*
2606  * In case just one of the two edges was not traced, the other
2607  * edge curvature model is duplicated and shifted to the other
2608  * end of the slit: better than nothing!
2609  */
2610 
2611  if (missing_top) {
2612  cpl_msg_debug(func, "Upper edge of slit %d was not traced: "
2613  "the spectral curvature of the lower edge "
2614  "is used instead.", slit_id[i]);
2615  polytop = cpl_polynomial_duplicate(polybot);
2616  ytop = cpl_table_get_double(slits, "ytop", i, NULL);
2617  ybot = cpl_table_get_double(slits, "ybottom", i, NULL);
2618  k = 0;
2619  coeff = cpl_polynomial_get_coeff(polybot, &k);
2620  coeff += ytop - ybot;
2621  cpl_polynomial_set_coeff(polytop, &k, coeff);
2622  }
2623 
2624  if (missing_bot) {
2625  cpl_msg_debug(func, "Lower edge of slit %d was not traced: "
2626  "the spectral curvature of the upper edge "
2627  "is used instead.", slit_id[i]);
2628  polybot = cpl_polynomial_duplicate(polytop);
2629  ytop = cpl_table_get_double(slits, "ytop", i, NULL);
2630  ybot = cpl_table_get_double(slits, "ybottom", i, NULL);
2631  k = 0;
2632  coeff = cpl_polynomial_get_coeff(polytop, &k);
2633  coeff -= ytop - ybot;
2634  cpl_polynomial_set_coeff(polybot, &k, coeff);
2635  }
2636 
2637 
2638  /*
2639  * Now map smoothed image to CCD.
2640  * Note that the npseudo value related to this slit is equal
2641  * to the number of spatial pseudo-pixels decreased by 1
2642  * (compare with function mos_spatial_calibration()).
2643  */
2644 
2645  nx = cpl_image_get_size_x(flat);
2646  ny = cpl_image_get_size_y(flat);
2647 
2648  sdata = cpl_image_get_data(spatial);
2649  xdata = cpl_image_get_data(exslit);
2650  npseudo = cpl_image_get_size_y(exslit) - 1;
2651 
2652  /*
2653  * Write interpolated smoothed values to CCD image
2654  */
2655 
2656  for (j = start_pixel; j < end_pixel; j++) {
2657  top = cpl_polynomial_eval_1d(polytop, j, NULL);
2658  bot = cpl_polynomial_eval_1d(polybot, j, NULL);
2659  for (k = 0; k <= npseudo; k++) {
2660  ypos = top - k*(top-bot)/npseudo;
2661  yint = ypos;
2662 
2663  /*
2664  * The line:
2665  * value = sdata[j + nx*yint];
2666  * should be equivalent to:
2667  * value = npseudo*(top-yint)/(top-bot);
2668  */
2669 
2670  if (yint < 0 || yint >= ny-1) {
2671  yprev = yint;
2672  continue;
2673  }
2674 
2675  value = sdata[j + nx*yint]; /* Spatial coordinate on CCD */
2676  ivalue = value; /* Nearest spatial pixels: */
2677  fvalue = value - ivalue; /* ivalue and ivalue+1 */
2678  if (ivalue < npseudo && ivalue >= 0) {
2679  vtop = xdata[j + nx*(npseudo-ivalue)];
2680  vbot = xdata[j + nx*(npseudo-ivalue-1)];
2681  wdata[j + nx*yint] = vtop*(1-fvalue) + vbot*fvalue;
2682 
2683  if (k) {
2684 
2685  /*
2686  * This is added to recover lost pixels on
2687  * the CCD image (pixels are lost because
2688  * the CCD pixels are less than npseudo+1).
2689  */
2690 
2691  if (yprev - yint > 1) {
2692  value = sdata[j + nx*(yint+1)];
2693  ivalue = value;
2694  fvalue = value - ivalue;
2695  if (ivalue < npseudo && ivalue >= 0) {
2696  vtop = xdata[j + nx*(npseudo-ivalue)];
2697  vbot = xdata[j + nx*(npseudo-ivalue-1)];
2698  wdata[j + nx*(yint+1)] = vtop*(1-fvalue)
2699  + vbot*fvalue;
2700  }
2701  }
2702  }
2703  }
2704  yprev = yint;
2705  }
2706  }
2707  cpl_polynomial_delete(polytop);
2708  cpl_polynomial_delete(polybot);
2709  cpl_image_delete(exslit);
2710  }
2711 
2712  cpl_image_delete(rectified);
2713 
2714  cpl_image_divide(flat, smo_flat);
2715 
2716  return smo_flat;
2717 }
2718 
2719 
2744 cpl_image *mos_normalise_longflat(cpl_image *flat, int sradius, int dradius,
2745  int polyorder)
2746 {
2747  const char *func = "mos_normalise_longflat";
2748 
2749  cpl_image *smo_flat;
2750  cpl_image *profile;
2751  cpl_vector *flux;
2752  cpl_vector *smo_flux;
2753  cpl_vector *positions;
2754  cpl_polynomial *trend;
2755 
2756  float *level;
2757  float *p;
2758  float *data;
2759  double *fdata;
2760  double *pdata;
2761 
2762  int nx, ny;
2763  int npoints;
2764  int i, j;
2765 
2766 
2767  if (flat == NULL) {
2768  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
2769  return NULL;
2770  }
2771 
2772  if (sradius < 1 || dradius < 1) {
2773  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
2774  return NULL;
2775  }
2776 
2777  smo_flat = cpl_image_duplicate(flat);
2778 
2779  if (polyorder < 0) {
2780 
2781  /*
2782  * First smooth along the spatial direction
2783  */
2784 
2785  cpl_image_turn(smo_flat, -1); /* For faster memory access */
2786 
2787  nx = cpl_image_get_size_x(smo_flat);
2788  ny = cpl_image_get_size_y(smo_flat);
2789  data = cpl_image_get_data(smo_flat);
2790 
2791  for (i = 0; i < ny; i++) {
2792  flux = cpl_vector_new(nx);
2793  fdata = cpl_vector_get_data(flux);
2794  p = data;
2795  for (j = 0; j < nx; j++)
2796  *fdata++ = *p++;
2797  smo_flux = cpl_vector_filter_median_create(flux, sradius);
2798  cpl_vector_delete(flux);
2799  fdata = cpl_vector_get_data(smo_flux);
2800  p = data;
2801  for (j = 0; j < nx; j++)
2802  *p++ = *fdata++;
2803  cpl_vector_delete(smo_flux);
2804  data += nx;
2805  }
2806 
2807  /*
2808  * Second smooth along the dispersion direction
2809  */
2810 
2811  cpl_image_turn(smo_flat, 1); /* For faster memory access */
2812 
2813  nx = cpl_image_get_size_x(smo_flat);
2814  ny = cpl_image_get_size_y(smo_flat);
2815  data = cpl_image_get_data(smo_flat);
2816 
2817  for (i = 0; i < ny; i++) {
2818  flux = cpl_vector_new(nx);
2819  fdata = cpl_vector_get_data(flux);
2820  p = data;
2821  for (j = 0; j < nx; j++)
2822  *fdata++ = *p++;
2823  smo_flux = cpl_vector_filter_median_create(flux, sradius);
2824  cpl_vector_delete(flux);
2825  fdata = cpl_vector_get_data(smo_flux);
2826  p = data;
2827  for (j = 0; j < nx; j++)
2828  *p++ = *fdata++;
2829  cpl_vector_delete(smo_flux);
2830  data += nx;
2831  }
2832  }
2833  else {
2834 
2835  /*
2836  * Fit with a polynomial the flat field trend column by column.
2837  */
2838 
2839  cpl_image_turn(smo_flat, -1); /* For faster memory access */
2840 
2841  nx = cpl_image_get_size_x(smo_flat);
2842  ny = cpl_image_get_size_y(smo_flat);
2843  data = cpl_image_get_data(smo_flat);
2844 
2845  profile = cpl_image_collapse_median_create(smo_flat, 1, 0, 0);
2846  level = cpl_image_get_data(profile);
2847 
2848  for (i = 0; i < ny; i++) {
2849 
2850  /*
2851  * First get the size of the vectors to allocate:
2852  * eliminate from fit any value more than 20% away
2853  * from median level in current column.
2854  */
2855 
2856  npoints = 0;
2857  p = data + i*nx;
2858  for (j = 0; j < nx; j++)
2859  if (fabs(p[j]/level[i] - 1) < 0.20)
2860  npoints++;
2861 
2862  if (npoints > polyorder + 1) {
2863 
2864  /*
2865  * Fill the vectors for the fitting
2866  */
2867 
2868  flux = cpl_vector_new(npoints);
2869  fdata = cpl_vector_get_data(flux);
2870  positions = cpl_vector_new(npoints);
2871  pdata = cpl_vector_get_data(positions);
2872 
2873  npoints = 0;
2874  p = data + i*nx;
2875  for (j = 0; j < nx; j++) {
2876  if (fabs(p[j]/level[i] - 1) < 0.20) {
2877  fdata[npoints] = p[j];
2878  pdata[npoints] = j;
2879  npoints++;
2880  }
2881  }
2882 
2883  trend = cpl_polynomial_fit_1d_create(positions, flux,
2884  polyorder, NULL);
2885 
2886  cpl_vector_delete(flux);
2887  cpl_vector_delete(positions);
2888 
2889  if (trend) {
2890  p = data + i*nx;
2891  for (j = 0; j < nx; j++)
2892  p[j] = cpl_polynomial_eval_1d(trend, j, NULL);
2893  cpl_polynomial_delete(trend);
2894  }
2895  else {
2896  cpl_msg_warning(func,
2897  "Invalid flat field flux fit (ignored)");
2898  }
2899  }
2900  }
2901 
2902  cpl_image_delete(profile);
2903  cpl_image_turn(smo_flat, 1);
2904 
2905  }
2906 
2907  cpl_image_divide(flat, smo_flat);
2908 
2909  return smo_flat;
2910 }
2911 
2912 
2935 cpl_error_code mos_interpolate_wavecalib_slit(cpl_table *idscoeff,
2936  cpl_table *slits,
2937  int order, int global)
2938 {
2939  const char *clab[6] = {"c0", "c1", "c2", "c3", "c4", "c5"};
2940  /* Max order is 5 */
2941  int nrow = cpl_table_get_nrow(slits);
2942  int i, j;
2943 
2944 
2945  if (order < 0)
2946  return CPL_ERROR_NONE;
2947 
2948  cpl_table_new_column(idscoeff, "x", CPL_TYPE_DOUBLE);
2949  cpl_table_new_column(idscoeff, "y", CPL_TYPE_DOUBLE);
2950 
2951  for (i = 0; i < nrow; i++) {
2952  int position = cpl_table_get_int (slits, "position", i, NULL);
2953  int length = cpl_table_get_int (slits, "length", i, NULL);
2954  double xtop = cpl_table_get_double(slits, "xtop", i, NULL);
2955  double xbot = cpl_table_get_double(slits, "xbottom", i, NULL);
2956  double ytop = cpl_table_get_double(slits, "ytop", i, NULL);
2957  double ybot = cpl_table_get_double(slits, "ybottom", i, NULL);
2958  double dx = xtop - xbot;
2959  double dy = ytop - ybot;
2960  cpl_table *table = cpl_table_extract(idscoeff, position, length);
2961 
2962  if (mos_interpolate_wavecalib(table, NULL, 2, order))
2963  continue;
2964 
2965  cpl_table_erase_window(idscoeff, position, length);
2966  cpl_table_insert(idscoeff, table, position);
2967 
2968  cpl_table_delete(table);
2969 
2970  for (j = 0; j < length; j++) {
2971  cpl_table_set_double(idscoeff, "x", j + position,
2972  xbot + j*(dx/length));
2973  cpl_table_set_double(idscoeff, "y", j + position,
2974  ybot + j*(dy/length));
2975  }
2976  }
2977 
2978  if (global) {
2979 
2980  /*
2981  * Now fit a global solution
2982  */
2983 
2984  nrow = cpl_table_get_nrow(idscoeff);
2985 
2986  for (i = 0; i < 6; i++) {
2987  cpl_table *dummy;
2988  cpl_vector *x;
2989  cpl_vector *y;
2990  cpl_bivector *z;
2991  cpl_vector *c;
2992  cpl_polynomial *p;
2993  cpl_vector *point;
2994  double *dpoint;
2995  int npoints;
2996 
2997  if (!cpl_table_has_column(idscoeff, clab[i]))
2998  break;
2999 
3000  npoints = nrow - cpl_table_count_invalid(idscoeff, clab[i]);
3001  if (npoints < 18)
3002  break;
3003 
3004  dummy = cpl_table_new(nrow);
3005  cpl_table_duplicate_column(dummy, "x", idscoeff, "x");
3006  cpl_table_duplicate_column(dummy, "y", idscoeff, "y");
3007  cpl_table_duplicate_column(dummy, clab[i], idscoeff, clab[i]);
3008  cpl_table_erase_invalid(dummy);
3009 
3010  x = cpl_vector_wrap(npoints, cpl_table_get_data_double(dummy, "x"));
3011  y = cpl_vector_wrap(npoints, cpl_table_get_data_double(dummy, "y"));
3012  z = cpl_bivector_wrap_vectors(x, y);
3013  c = cpl_vector_wrap(npoints, cpl_table_get_data_double(dummy,
3014  clab[i]));
3015  p = cpl_polynomial_fit_2d_create(z, c, 2, NULL);
3016  cpl_bivector_unwrap_vectors(z);
3017  cpl_vector_unwrap(x);
3018  cpl_vector_unwrap(y);
3019  cpl_vector_unwrap(c);
3020  cpl_table_delete(dummy);
3021 
3022  point = cpl_vector_new(2);
3023  dpoint = cpl_vector_get_data(point);
3024  for (j = 0; j < nrow; j++) {
3025  dpoint[0] = cpl_table_get_double(idscoeff, "x", j, NULL);
3026  dpoint[1] = cpl_table_get_double(idscoeff, "y", j, NULL);
3027  cpl_table_set_double(idscoeff, clab[i], j,
3028  cpl_polynomial_eval(p, point));
3029  }
3030  cpl_vector_delete(point);
3031  cpl_polynomial_delete(p);
3032  }
3033  }
3034 
3035  return CPL_ERROR_NONE;
3036 }
3037 
3038 
3064 cpl_error_code mos_interpolate_wavecalib(cpl_table *idscoeff,
3065  cpl_image *wavemap, int mode,
3066  int degree)
3067 {
3068  const char *func = "mos_interpolate_wavecalib";
3069 
3070  const char *clab[6] = {"c0", "c1", "c2", "c3", "c4", "c5"};
3071  /* Max order is 5 */
3072 
3073  cpl_vector *wave;
3074  cpl_vector *positions;
3075  cpl_polynomial *trend;
3076 
3077  float *p;
3078  float *data;
3079  double *wdata;
3080  double *pdata;
3081 
3082  double c;
3083  double mse, ksigma;
3084 
3085  int order;
3086  int nrows, first_row, last_row;
3087  int nx, ny;
3088  int npoints, rpoints;
3089  int null;
3090  int i, j, k;
3091 
3092  int polyorder = 4; /* Candidate input argument */
3093 
3094 
3095  if (idscoeff == NULL)
3096  return cpl_error_set(func, CPL_ERROR_NULL_INPUT);
3097 
3098  if (mode < 0 || mode > 2)
3099  return cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
3100 
3101  if (mode == 0 || degree < 0)
3102  return CPL_ERROR_NONE;
3103 
3104  if (wavemap) {
3105 
3106  /*
3107  * Fit with a polynomial the wavelength trend column by column.
3108  */
3109 
3110  cpl_image_turn(wavemap, -1); /* For faster memory access */
3111 
3112  nx = cpl_image_get_size_x(wavemap);
3113  ny = cpl_image_get_size_y(wavemap);
3114  data = cpl_image_get_data(wavemap);
3115 
3116  for (i = 0; i < ny; i++) {
3117 
3118  /*
3119  * First get the size of the vectors to allocate:
3120  * eliminate from fit any value with "impossible" wavelength.
3121  */
3122 
3123  npoints = 0;
3124  p = data + i*nx;
3125  for (j = 0; j < nx; j++)
3126  if (p[j] > 1.0)
3127  npoints++;
3128 
3129  if (npoints > polyorder + 1) {
3130 
3131  /*
3132  * Fill the vectors for the fitting
3133  */
3134 
3135  wave = cpl_vector_new(npoints);
3136  wdata = cpl_vector_get_data(wave);
3137  positions = cpl_vector_new(npoints);
3138  pdata = cpl_vector_get_data(positions);
3139 
3140  npoints = 0;
3141  p = data + i*nx;
3142  for (j = 0; j < nx; j++) {
3143  if (p[j] > 1.0) {
3144  wdata[npoints] = p[j];
3145  pdata[npoints] = j;
3146  npoints++;
3147  }
3148  }
3149 
3150  trend = cpl_polynomial_fit_1d_create(positions, wave,
3151  polyorder, &mse);
3152 
3153  ksigma = 3*sqrt(mse);
3154 
3155  cpl_vector_delete(wave);
3156  cpl_vector_delete(positions);
3157 
3158  if (trend) {
3159 
3160  /*
3161  * Apply 3-sigma rejection
3162  */
3163 
3164  rpoints = 0;
3165  p = data + i*nx;
3166  for (j = 0; j < nx; j++)
3167  if (p[j] > 1.0)
3168  if (fabs(cpl_polynomial_eval_1d(trend, j, NULL)
3169  - p[j]) < ksigma)
3170  rpoints++;
3171 
3172  if (rpoints < npoints && rpoints > polyorder + 1) {
3173 
3174  wave = cpl_vector_new(rpoints);
3175  wdata = cpl_vector_get_data(wave);
3176  positions = cpl_vector_new(rpoints);
3177  pdata = cpl_vector_get_data(positions);
3178 
3179  npoints = 0;
3180  p = data + i*nx;
3181  for (j = 0; j < nx; j++) {
3182  if (p[j] > 1.0) {
3183  if (fabs(cpl_polynomial_eval_1d(trend,
3184  j, NULL) - p[j])
3185  < ksigma) {
3186  wdata[npoints] = p[j];
3187  pdata[npoints] = j;
3188  npoints++;
3189  }
3190  }
3191  }
3192 
3193  cpl_polynomial_delete(trend);
3194  trend = cpl_polynomial_fit_1d_create(positions, wave,
3195  polyorder, NULL);
3196 
3197  cpl_vector_delete(wave);
3198  cpl_vector_delete(positions);
3199  }
3200  }
3201 
3202  if (trend) {
3203  p = data + i*nx;
3204  if (mode == 1) {
3205  for (j = 0; j < nx; j++)
3206  if (p[j] < 1.0)
3207  p[j] = cpl_polynomial_eval_1d(trend, j, NULL);
3208  }
3209  else if (mode == 2) {
3210  for (j = 0; j < nx; j++)
3211  p[j] = cpl_polynomial_eval_1d(trend, j, NULL);
3212  }
3213  cpl_polynomial_delete(trend);
3214  }
3215  else {
3216  cpl_msg_warning(func,
3217  "Invalid wavelength field fit (ignored)");
3218  }
3219  }
3220 
3221  }
3222 
3223  cpl_image_turn(wavemap, 1);
3224 
3225  }
3226 
3227 
3228  /*
3229  * Interpolating the IDS coefficients
3230  */
3231 
3232  nrows = cpl_table_get_nrow(idscoeff);
3233 
3234  order = 0;
3235  while (order < 6 && cpl_table_has_column(idscoeff, clab[order]))
3236  ++order;
3237  --order;
3238 
3239  if (degree == 0) {
3240  for (k = 0; k <= order; k++) {
3241  double m;
3242  if (cpl_table_has_column(idscoeff, clab[k])) {
3243  m = cpl_table_get_column_median(idscoeff, clab[k]);
3244  cpl_table_fill_column_window_double(idscoeff, clab[k],
3245  0, nrows, m);
3246  }
3247  }
3248 
3249  return CPL_ERROR_NONE;
3250  }
3251 
3252  first_row = 0;
3253  while (!cpl_table_is_valid(idscoeff, clab[0], first_row))
3254  first_row++;
3255 
3256  last_row = nrows - 1;
3257  while (!cpl_table_is_valid(idscoeff, clab[0], last_row))
3258  last_row--;
3259 
3260  for (k = 0; k <= order; k++) {
3261 
3262  npoints = nrows - cpl_table_count_invalid(idscoeff, clab[k]);
3263  wave = cpl_vector_new(npoints);
3264  wdata = cpl_vector_get_data(wave);
3265  positions = cpl_vector_new(npoints);
3266  pdata = cpl_vector_get_data(positions);
3267 
3268  npoints = 0;
3269  for (i = first_row; i <= last_row; i++) {
3270  c = cpl_table_get_double(idscoeff, clab[k], i, &null);
3271  if (null == 0) {
3272  wdata[npoints] = c;
3273  pdata[npoints] = i;
3274  npoints++;
3275  }
3276  }
3277 
3278 // This doesn't seem to provide good results, I have not understood why.
3279 // Restore for robust linear fitting.
3280 //
3281  if (degree == 1) {
3282  cpl_vector *p;
3283  cpl_vector *w;
3284  cpl_bivector *list;
3285  double q, m;
3286 
3287  if (npoints > 4) {
3288  p = cpl_vector_extract(positions, 2, npoints - 2, 1);
3289  w = cpl_vector_extract(wave, 2, npoints - 2, 1);
3290  }
3291  else {
3292  p = positions;
3293  w = wave;
3294  }
3295 
3296  list = cpl_bivector_wrap_vectors(p, w);
3297 
3298  robustLinearFit(list, &q, &m, &mse);
3299  cpl_bivector_unwrap_vectors(list);
3300  for (i = first_row; i <= last_row; i++)
3301  cpl_table_set_double(idscoeff, clab[k], i, q + m*i);
3302 
3303  if (npoints > 4) {
3304  cpl_vector_delete(p);
3305  cpl_vector_delete(w);
3306  }
3307 
3308  cpl_vector_delete(wave);
3309  cpl_vector_delete(positions);
3310 
3311  continue;
3312  }
3313 
3314 // End robust linear fitting
3315 
3316  trend = cpl_polynomial_fit_1d_create(positions, wave, degree, &mse);
3317 
3318  ksigma = 3*sqrt(mse);
3319 
3320  cpl_vector_delete(wave);
3321  cpl_vector_delete(positions);
3322 
3323  /*
3324  * Iteration
3325  */
3326 
3327  if (trend) {
3328  rpoints = 0;
3329  for (i = first_row; i <= last_row; i++) {
3330  c = cpl_table_get_double(idscoeff, clab[k], i, &null);
3331  if (null == 0) {
3332  if (fabs(cpl_polynomial_eval_1d(trend, i, NULL) - c)
3333  < ksigma) {
3334  rpoints++;
3335  }
3336  }
3337  }
3338 
3339  if (rpoints > 0 && rpoints < npoints) {
3340  cpl_msg_debug(func, "%d points rejected from "
3341  "wavelength calibration fit",
3342  npoints - rpoints);
3343 
3344  wave = cpl_vector_new(rpoints);
3345  wdata = cpl_vector_get_data(wave);
3346  positions = cpl_vector_new(rpoints);
3347  pdata = cpl_vector_get_data(positions);
3348 
3349  npoints = 0;
3350  for (i = first_row; i <= last_row; i++) {
3351  c = cpl_table_get_double(idscoeff, clab[k], i, &null);
3352  if (null == 0) {
3353  if (fabs(cpl_polynomial_eval_1d(trend, i, NULL) - c)
3354  < ksigma) {
3355  wdata[npoints] = c;
3356  pdata[npoints] = i;
3357  npoints++;
3358  }
3359  }
3360  }
3361 
3362  if (npoints) {
3363  cpl_polynomial_delete(trend);
3364  trend = cpl_polynomial_fit_1d_create(positions,
3365  wave, degree, NULL);
3366  }
3367 
3368  cpl_vector_delete(wave);
3369  cpl_vector_delete(positions);
3370 
3371  }
3372  }
3373 
3374  if (trend) {
3375  for (i = first_row; i <= last_row; i++) {
3376  if (mode == 1) {
3377  if (!cpl_table_is_valid(idscoeff, clab[k], i)) {
3378  cpl_table_set_double(idscoeff, clab[k], i,
3379  cpl_polynomial_eval_1d(trend, i,
3380  NULL));
3381  }
3382  }
3383  else if (mode == 2) {
3384  cpl_table_set_double(idscoeff, clab[k], i,
3385  cpl_polynomial_eval_1d(trend, i, NULL));
3386  }
3387  }
3388  cpl_polynomial_delete(trend);
3389  }
3390  else {
3391  cpl_msg_warning(func, "Invalid IDS coefficient fit (ignored)");
3392  }
3393 
3394  }
3395 
3396  return CPL_ERROR_NONE;
3397 }
3398 
3416 cpl_error_code mos_interpolate_wavecalib_mos(cpl_table *idscoeff,
3417  int mode,
3418  int degree)
3419 {
3420  const char *func = "mos_interpolate_wavecalib";
3421 
3422  const char *clab[6] = {"c0", "c1", "c2", "c3", "c4", "c5"};
3423  /* Max order is 5 */
3424 
3425  cpl_vector *wave;
3426  cpl_vector *positions;
3427  cpl_polynomial *trend;
3428 
3429  double *wdata;
3430  double *pdata;
3431 
3432  double c;
3433  double mse, ksigma;
3434 
3435  int order;
3436  int nrows, first_row, last_row;
3437  int npoints, rpoints;
3438  int null;
3439  int i;
3440 
3441 
3442  if (idscoeff == NULL)
3443  return cpl_error_set(func, CPL_ERROR_NULL_INPUT);
3444 
3445  if (mode < 0 || mode > 2)
3446  return cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
3447 
3448  if (mode == 0 || degree < 0)
3449  return CPL_ERROR_NONE;
3450 
3451  /*
3452  * Interpolating the IDS coefficients
3453  */
3454 
3455  nrows = cpl_table_get_nrow(idscoeff);
3456 
3457  order = 0;
3458  while (order < 6 && cpl_table_has_column(idscoeff, clab[order]))
3459  ++order;
3460  --order;
3461 
3462  //For coefficients > 0 always do a median
3463 
3464  int kinit = 1;
3465  if (degree == 0)
3466  kinit = 0;
3467  for (int k = kinit; k <= order; k++) {
3468  double m;
3469  if (cpl_table_has_column(idscoeff, clab[k])) {
3470  m = cpl_table_get_column_median(idscoeff, clab[k]);
3471  cpl_table_fill_column_window_double(idscoeff, clab[k],
3472  0, nrows, m);
3473  }
3474  }
3475 
3476  if(degree > 0)
3477  {
3478  first_row = 0;
3479  while (!cpl_table_is_valid(idscoeff, clab[0], first_row))
3480  first_row++;
3481 
3482  last_row = nrows - 1;
3483  while (!cpl_table_is_valid(idscoeff, clab[0], last_row))
3484  last_row--;
3485 
3486  int korder = 0;
3487 
3488  npoints = nrows - cpl_table_count_invalid(idscoeff, clab[korder]);
3489  wave = cpl_vector_new(npoints);
3490  wdata = cpl_vector_get_data(wave);
3491  positions = cpl_vector_new(npoints);
3492  pdata = cpl_vector_get_data(positions);
3493 
3494  npoints = 0;
3495  for (i = first_row; i <= last_row; i++) {
3496  c = cpl_table_get_double(idscoeff, clab[korder], i, &null);
3497  if (null == 0) {
3498  wdata[npoints] = c;
3499  pdata[npoints] = i;
3500  npoints++;
3501  }
3502  }
3503 
3504  // This doesn't seem to provide good results, I have not understood why.
3505  // Restore for robust linear fitting.
3506  //
3507  if (degree == 1) {
3508  cpl_vector *p;
3509  cpl_vector *w;
3510  cpl_bivector *list;
3511  double q, m;
3512 
3513  if (npoints > 4) {
3514  p = cpl_vector_extract(positions, 2, npoints - 2, 1);
3515  w = cpl_vector_extract(wave, 2, npoints - 2, 1);
3516  }
3517  else {
3518  p = positions;
3519  w = wave;
3520  }
3521 
3522  list = cpl_bivector_wrap_vectors(p, w);
3523 
3524  robustLinearFit(list, &q, &m, &mse);
3525  cpl_bivector_unwrap_vectors(list);
3526  for (i = first_row; i <= last_row; i++)
3527  cpl_table_set_double(idscoeff, clab[korder], i, q + m*i);
3528 
3529  if (npoints > 4) {
3530  cpl_vector_delete(p);
3531  cpl_vector_delete(w);
3532  }
3533 
3534  cpl_vector_delete(wave);
3535  cpl_vector_delete(positions);
3536 
3537  }
3538  else
3539  {
3540 
3541  // End robust linear fitting
3542 
3543  trend = cpl_polynomial_fit_1d_create(positions, wave, degree, &mse);
3544 
3545  ksigma = 3*sqrt(mse);
3546 
3547  cpl_vector_delete(wave);
3548  cpl_vector_delete(positions);
3549 
3550  /*
3551  * Iteration
3552  */
3553 
3554  if (trend) {
3555  rpoints = 0;
3556  for (i = first_row; i <= last_row; i++) {
3557  c = cpl_table_get_double(idscoeff, clab[korder], i, &null);
3558  if (null == 0) {
3559  if (fabs(cpl_polynomial_eval_1d(trend, i, NULL) - c)
3560  < ksigma) {
3561  rpoints++;
3562  }
3563  }
3564  }
3565 
3566  if (rpoints > 0 && rpoints < npoints) {
3567  cpl_msg_debug(func, "%d points rejected from "
3568  "wavelength calibration fit",
3569  npoints - rpoints);
3570 
3571  wave = cpl_vector_new(rpoints);
3572  wdata = cpl_vector_get_data(wave);
3573  positions = cpl_vector_new(rpoints);
3574  pdata = cpl_vector_get_data(positions);
3575 
3576  npoints = 0;
3577  for (i = first_row; i <= last_row; i++) {
3578  c = cpl_table_get_double(idscoeff, clab[korder], i, &null);
3579  if (null == 0) {
3580  if (fabs(cpl_polynomial_eval_1d(trend, i, NULL) - c)
3581  < ksigma) {
3582  wdata[npoints] = c;
3583  pdata[npoints] = i;
3584  npoints++;
3585  }
3586  }
3587  }
3588 
3589  if (npoints) {
3590  cpl_polynomial_delete(trend);
3591  trend = cpl_polynomial_fit_1d_create(positions,
3592  wave, degree, NULL);
3593  }
3594 
3595  cpl_vector_delete(wave);
3596  cpl_vector_delete(positions);
3597 
3598  }
3599  }
3600 
3601  if (trend) {
3602  for (i = first_row; i <= last_row; i++) {
3603  if (mode == 1) {
3604  if (!cpl_table_is_valid(idscoeff, clab[korder], i)) {
3605  cpl_table_set_double(idscoeff, clab[korder], i,
3606  cpl_polynomial_eval_1d(trend, i,
3607  NULL));
3608  }
3609  }
3610  else if (mode == 2) {
3611  cpl_table_set_double(idscoeff, clab[korder], i,
3612  cpl_polynomial_eval_1d(trend, i, NULL));
3613  }
3614  }
3615  cpl_polynomial_delete(trend);
3616  }
3617  else {
3618  cpl_msg_warning(func, "Invalid IDS coefficient fit (ignored)");
3619  }
3620  }
3621  }
3622 
3623  return CPL_ERROR_NONE;
3624 }
3625 
3626 
3651 //TODO:Deprecate this function. Change all the recipes which still use it.
3652 //Use the new functionality to remove overscan, trimm and bias.
3653 
3654 cpl_image *mos_remove_bias(cpl_image *image, cpl_image *bias,
3655  cpl_table *overscans)
3656 {
3657  const char *func = "mos_remove_bias";
3658 
3659  cpl_image *unbiased;
3660  cpl_image *overscan;
3661  double mean_bias_level;
3662  double mean_overscans_level;
3663  int count;
3664  int nrows;
3665  int xlow, ylow, xhig, yhig;
3666  int i;
3667 
3668 
3669  if (image == NULL || overscans == NULL) {
3670  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
3671  return NULL;
3672  }
3673 
3674  nrows = cpl_table_get_nrow(overscans);
3675 
3676  if (nrows == 0) {
3677  cpl_msg_error(func, "Empty overscan table");
3678  cpl_error_set(func, CPL_ERROR_DATA_NOT_FOUND);
3679  return NULL;
3680  }
3681 
3682  if (bias) {
3683  if (nrows == 1) {
3684  unbiased = cpl_image_subtract_create(image, bias);
3685  if (unbiased == NULL) {
3686  cpl_msg_error(func, "Incompatible master bias");
3687  cpl_error_set(func, CPL_ERROR_INCOMPATIBLE_INPUT);
3688  }
3689  return unbiased;
3690  }
3691  mean_bias_level = cpl_image_get_mean(bias);
3692  }
3693  else {
3694  if (nrows == 1) {
3695  cpl_msg_error(func, "No master bias in input, and no overscan "
3696  "regions in input image: bias subtraction "
3697  "cannot be performed!");
3698  cpl_error_set(func, CPL_ERROR_DATA_NOT_FOUND);
3699  return NULL;
3700  }
3701  mean_bias_level = 0.0;
3702  }
3703 
3704  mean_overscans_level = 0.0;
3705  count = 0;
3706  for (i = 0; i < nrows; i++) {
3707  xlow = cpl_table_get_int(overscans, "xlow", i, NULL);
3708  ylow = cpl_table_get_int(overscans, "ylow", i, NULL);
3709  xhig = cpl_table_get_int(overscans, "xhig", i, NULL);
3710  yhig = cpl_table_get_int(overscans, "yhig", i, NULL);
3711 
3712  if (i == 0) {
3713  unbiased = cpl_image_extract(image, xlow+1, ylow+1, xhig, yhig);
3714  if (unbiased == NULL) {
3715  cpl_msg_error(func, "Incompatible overscan table");
3716  cpl_error_set(func, CPL_ERROR_INCOMPATIBLE_INPUT);
3717  return NULL;
3718  }
3719  if (bias) {
3720  if (cpl_image_subtract(unbiased, bias)) {
3721  cpl_msg_error(func, "Incompatible master bias");
3722  cpl_error_set(func, CPL_ERROR_INCOMPATIBLE_INPUT);
3723  cpl_image_delete(unbiased);
3724  return NULL;
3725  }
3726  }
3727  }
3728  else {
3729  overscan = cpl_image_extract(image, xlow+1, ylow+1, xhig, yhig);
3730  if (overscan == NULL) {
3731  cpl_msg_error(func, "Incompatible overscan table");
3732  cpl_error_set(func, CPL_ERROR_INCOMPATIBLE_INPUT);
3733  cpl_image_delete(unbiased);
3734  return NULL;
3735  }
3736 
3737  mean_overscans_level += cpl_image_get_median(overscan);
3738  count++;
3739 
3740 /***
3741  * Here the mean level was used: not very robust...
3742 
3743  mean_overscans_level += cpl_image_get_flux(overscan);
3744  count += cpl_image_get_size_x(overscan)
3745  * cpl_image_get_size_y(overscan);
3746 ***/
3747  cpl_image_delete(overscan);
3748  }
3749  }
3750 
3751  /*
3752  * Overscan correction
3753  */
3754 
3755  mean_overscans_level /= count;
3756 
3757  cpl_image_subtract_scalar(unbiased, mean_overscans_level - mean_bias_level);
3758 
3759  cpl_msg_info(cpl_func,
3760  "Difference between mean overscans level "
3761  "and mean bias level: %.2f",
3762  mean_overscans_level - mean_bias_level);
3763 
3764  return unbiased;
3765 
3766 }
3767 
3768 
3827 cpl_error_code mos_arc_background_1D(float *spectrum, float *back,
3828  int length, int msize, int fsize)
3829 {
3830  const char *func = "mos_arc_background_1D";
3831 
3832  float *minf;
3833  float *maxf;
3834  float *smof;
3835  int i;
3836 
3837 
3838  if (spectrum == NULL || back == NULL)
3839  return cpl_error_set(func, CPL_ERROR_NULL_INPUT);
3840 
3841  if (msize % 2 == 0)
3842  msize++;
3843 
3844  if (fsize % 2 == 0)
3845  fsize++;
3846 
3847  if (msize < 3 || fsize < msize || length < 2*fsize)
3848  return cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
3849 
3850 
3851  minf = min_filter(spectrum, length, msize);
3852  smof = smo_filter(minf, length, fsize);
3853  cpl_free(minf);
3854  maxf = max_filter(smof, length, 2*msize+1);
3855  cpl_free(smof);
3856  smof = smo_filter(maxf, length, 2*fsize+1);
3857  cpl_free(maxf);
3858  minf = min_filter(smof, length, 2*msize+1);
3859  cpl_free(smof);
3860  smof = smo_filter(minf, length, 2*fsize+1);
3861  cpl_free(minf);
3862 
3863  for (i = 0; i < length; i++)
3864  back[i] = smof[i];
3865 
3866  cpl_free(smof);
3867 
3868  return CPL_ERROR_NONE;
3869 
3870 }
3871 
3872 
3929 cpl_image *mos_arc_background(cpl_image *image, int msize, int fsize)
3930 {
3931  const char *func = "mos_arc_background";
3932 
3933  cpl_image *fimage;
3934  cpl_image *bimage;
3935  float *data;
3936  float *bdata;
3937  float *row;
3938  float *brow;
3939  int nx, ny;
3940  int i;
3941 
3942 
3943  if (image == NULL) {
3944  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
3945  return NULL;
3946  }
3947 
3948  if (msize % 2 == 0)
3949  msize++;
3950 
3951  if (fsize % 2 == 0)
3952  fsize++;
3953 
3954  nx = cpl_image_get_size_x(image);
3955  ny = cpl_image_get_size_y(image);
3956 
3957  bimage = cpl_image_new(nx, ny, CPL_TYPE_FLOAT);
3958 
3959  fimage = mos_image_filter_median(image, 3, 3);
3960 
3961  data = cpl_image_get_data_float(fimage);
3962  bdata = cpl_image_get_data_float(bimage);
3963 
3964  for (i = 0; i < ny; i++) {
3965  row = data + i * nx;
3966  brow = bdata + i * nx;
3967  if (mos_arc_background_1D(row, brow, nx, msize, fsize)) {
3968  cpl_error_set_where(func);
3969  cpl_image_delete(fimage);
3970  cpl_image_delete(bimage);
3971  return NULL;
3972  }
3973  }
3974 
3975  cpl_image_delete(fimage);
3976 
3977  return bimage;
3978 }
3979 
3980 
4001 int mos_lines_width(const float *spectrum, int length)
4002 {
4003 
4004  const char *func = "mos_lines_width";
4005 
4006  double *profile1 = cpl_calloc(length - 1, sizeof(double));
4007  double *profile2 = cpl_calloc(length - 1, sizeof(double));
4008 
4009  double norm, value, max;
4010  int radius = 20;
4011  int short_length = length - 2*radius - 1;
4012  int width;
4013  int i, j, k;
4014 
4015 
4016  /*
4017  * Derivative, and separation of positive and negative derivatives
4018  */
4019 
4020  for (j = 0, i = 1; i < length; j++, i++) {
4021  profile1[j] = profile2[j] = spectrum[i] - spectrum[j];
4022  if (profile1[j] < 0)
4023  profile1[j] = 0;
4024  if (profile2[j] > 0)
4025  profile2[j] = 0;
4026  else
4027  profile2[j] = -profile2[j];
4028  }
4029 
4030 
4031  /*
4032  * Profiles normalisation
4033  */
4034 
4035  length--;
4036 
4037  norm = 0;
4038  for (i = 0; i < length; i++)
4039  if (norm < profile1[i])
4040  norm = profile1[i];
4041 
4042  for (i = 0; i < length; i++) {
4043  profile1[i] /= norm;
4044  profile2[i] /= norm;
4045  }
4046 
4047 
4048  /*
4049  * Cross-correlation
4050  */
4051 
4052  max = -1;
4053  for (i = 0; i <= radius; i++) {
4054  value = 0;
4055  for (j = 0; j < short_length; j++) {
4056  k = radius+j;
4057  value += profile1[k] * profile2[k+i];
4058  }
4059  if (max < value) {
4060  max = value;
4061  width = i;
4062  }
4063  }
4064 
4065  cpl_free(profile1);
4066  cpl_free(profile2);
4067 
4068  if (max < 0.0) {
4069  cpl_msg_debug(func, "Cannot estimate line width");
4070  width = 1;
4071  }
4072 
4073  return width;
4074 
4075 }
4076 
4077 
4104 cpl_vector *mos_peak_candidates(const float *spectrum,
4105  int length, float level,
4106  float exp_width)
4107 {
4108 
4109  const char *func = "mos_peak_candidates";
4110 
4111  int i, j;
4112  int nint = length - 1;
4113  int n = 0;
4114  int width = 2 * ceil(exp_width / 2) + 1;
4115  int start = width / 2;
4116  int end = length - width / 2;
4117  int step;
4118  float *smo;
4119  double *data = cpl_calloc(length/2, sizeof(double));
4120 
4121 
4122  if (spectrum == NULL) {
4123  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
4124  return NULL;
4125  }
4126 
4127 
4128  /*
4129  * If lines have a flat top (as in the case of broad slit), smooth
4130  * before determining the max.
4131  */
4132 
4133  if (width > 7) {
4134  smo = cpl_calloc(length, sizeof(float));
4135  start = width / 2;
4136  end = length - width / 2;
4137  for (i = 0; i < start; i++)
4138  smo[i] = spectrum[i];
4139  for (i = start; i < end; i++) {
4140  for (j = i - start; j <= i + start; j++)
4141  smo[i] += spectrum[j];
4142  smo[i] /= width;
4143  }
4144  for (i = end; i < length; i++)
4145  smo[i] = spectrum[i];
4146  }
4147  else {
4148  smo = (float *)spectrum;
4149  }
4150 
4151  /*
4152  * Collect all relative maxima along spectrum, that are higher than the
4153  * specified level.
4154  */
4155 
4156  if (width > 20)
4157  step = width / 2;
4158  else
4159  step = 1;
4160 
4161  for (i = step; i < nint - step + 1; i += step) {
4162  if (smo[i] > level) {
4163  if (smo[i] >= smo[i-step] && smo[i] > smo[i+step]) {
4164  if (smo[i-step] != 0.0 && smo[i+step] != 0.0) {
4165  data[n] = i + step * values_to_dx(smo[i-step], smo[i], smo[i+step]);
4166  ++n;
4167  }
4168  }
4169  }
4170  }
4171 
4172  if (width > 7) {
4173  cpl_free(smo);
4174  }
4175 
4176  if (n == 0) {
4177  cpl_free(data);
4178  return NULL;
4179  }
4180 
4181  return cpl_vector_wrap(n, data);
4182 
4183 }
4184 
4185 
4207 cpl_vector *mos_refine_peaks(const float *spectrum, int length,
4208  cpl_vector *peaks, int sradius)
4209 {
4210 
4211  const char *func = "mos_refine_peaks";
4212 
4213  double *data;
4214  float pos;
4215  int npeaks;
4216  int startPos, endPos;
4217  int window = 2*sradius+1;
4218  int i, j;
4219 
4220 
4221  if (peaks == NULL || spectrum == NULL) {
4222  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
4223  return NULL;
4224  }
4225 
4226  npeaks = cpl_vector_get_size(peaks);
4227  data = cpl_vector_unwrap(peaks);
4228 
4229  for (i = 0; i < npeaks; i++) {
4230  startPos = data[i] - window/2;
4231  endPos = startPos + window;
4232  if (startPos < 0 || endPos >= length)
4233  continue;
4234 
4235  if (0 == peakPosition(spectrum + startPos, window, &pos, 1)) {
4236  pos += startPos;
4237  data[i] = pos;
4238  }
4239  }
4240 
4241  for (i = 1; i < npeaks; i++)
4242  if (data[i] - data[i-1] < 0.5)
4243  data[i-1] = -1.0;
4244 
4245  for (i = 0, j = 0; i < npeaks; i++) {
4246  if (data[i] > 0.0) {
4247  if (i != j)
4248  data[j] = data[i];
4249  j++;
4250  }
4251  }
4252 
4253  return cpl_vector_wrap(j, data);
4254 
4255 }
4256 
4257 
4258 void mos_set_multiplex(int multiplex)
4259 {
4260  mos_multiplex = multiplex;
4261 }
4262 
4316 cpl_bivector *mos_identify_peaks(cpl_vector *peaks, cpl_vector *lines,
4317  double min_disp, double max_disp,
4318  double tolerance)
4319 {
4320 
4321  int i, j, k, l;
4322  int nlint, npint;
4323  int minpos;
4324  float min;
4325  double lratio, pratio;
4326  double lo_start, lo_end, hi_start, hi_end, denom;
4327  double disp, variation, prev_variation;
4328  int max, maxpos, minl, mink;
4329  int ambiguous;
4330  int npeaks_lo, npeaks_hi;
4331  int *peak_lo;
4332  int *peak_hi;
4333  int **ident;
4334  int *nident;
4335  int *lident;
4336 
4337  double *peak;
4338  double *line;
4339  int npeaks, nlines;
4340 
4341  double *xpos;
4342  double *lambda;
4343  int *ilambda;
4344  double *tmp_xpos;
4345  double *tmp_lambda;
4346  int *tmp_ilambda;
4347  int *flag;
4348  int n = 0;
4349  int nn;
4350  int nseq = 0;
4351  int gap;
4352  int *seq_length;
4353  int found;
4354 
4355  peak = cpl_vector_get_data(peaks);
4356  npeaks = cpl_vector_get_size(peaks);
4357  line = cpl_vector_get_data(lines);
4358  nlines = cpl_vector_get_size(lines);
4359 
4360  if (npeaks < 4)
4361  return NULL;
4362 
4363  peak_lo = cpl_malloc(npeaks * sizeof(int));
4364  peak_hi = cpl_malloc(npeaks * sizeof(int));
4365  nident = cpl_calloc(npeaks, sizeof(int));
4366  lident = cpl_calloc(nlines, sizeof(int));
4367  xpos = cpl_calloc(npeaks, sizeof(double));
4368  lambda = cpl_calloc(npeaks, sizeof(double));
4369  ilambda = cpl_calloc(npeaks, sizeof(int));
4370  tmp_xpos = cpl_calloc(npeaks, sizeof(double));
4371  tmp_lambda = cpl_calloc(npeaks, sizeof(double));
4372  tmp_ilambda = cpl_calloc(npeaks, sizeof(int));
4373  flag = cpl_calloc(npeaks, sizeof(int));
4374  seq_length = cpl_calloc(npeaks, sizeof(int));
4375  ident = cpl_malloc(npeaks * sizeof(int *));
4376  for (i = 0; i < npeaks; i++)
4377  ident[i] = cpl_malloc(3 * npeaks * sizeof(int));
4378 
4379  /*
4380  * This is just the number of intervals - one less than the number
4381  * of points (catalog wavelengths, or detected peaks).
4382  */
4383 
4384  nlint = nlines - 1;
4385  npint = npeaks - 1;
4386 
4387 
4388  /*
4389  * Here the big loops on catalog lines begins.
4390  */
4391 
4392  for (i = 1; i < nlint; i++) {
4393 
4394 
4395  /*
4396  * For each catalog wavelength I take the previous and the next one,
4397  * and compute the ratio of the corresponding wavelength intervals.
4398  * This ratio will be compared to all the ratios obtained doing the
4399  * same with all the detected peaks positions.
4400  */
4401 
4402  lratio = (line[i+1] - line[i]) / (line[i] - line[i-1]);
4403 
4404 
4405  /*
4406  * Here the loop on detected peaks positions begins.
4407  */
4408 
4409  for (j = 1; j < npint; j++) {
4410 
4411  /*
4412  * Not all peaks are used for computing ratios: just the ones
4413  * that are compatible with the expected spectral dispersion
4414  * are taken into consideration. Therefore, I define the pixel
4415  * intervals before and after any peak that are compatible with
4416  * the specified dispersion interval, and select just the peaks
4417  * within such intervals. If either of the two intervals doesn't
4418  * contain any peak, then I skip the current peak and continue
4419  * with the next.
4420  */
4421 
4422  lo_start = peak[j] - (line[i] - line[i-1]) / min_disp;
4423  lo_end = peak[j] - (line[i] - line[i-1]) / max_disp;
4424  hi_start = peak[j] + (line[i+1] - line[i]) / max_disp;
4425  hi_end = peak[j] + (line[i+1] - line[i]) / min_disp;
4426 
4427  for (npeaks_lo = 0, k = 0; k < npeaks; k++) {
4428  if (peak[k] > lo_end)
4429  break;
4430  if (peak[k] > lo_start) {
4431  peak_lo[npeaks_lo] = k;
4432  ++npeaks_lo;
4433  }
4434  }
4435 
4436  if (npeaks_lo == 0)
4437  continue;
4438 
4439  for (npeaks_hi = 0, k = 0; k < npeaks; k++) {
4440  if (peak[k] > hi_end)
4441  break;
4442  if (peak[k] > hi_start) {
4443  peak_hi[npeaks_hi] = k;
4444  ++npeaks_hi;
4445  }
4446  }
4447 
4448  if (npeaks_hi == 0)
4449  continue;
4450 
4451 
4452  /*
4453  * Now I have all peaks that may help for a local identification.
4454  * peak_lo[k] is the sequence number of the k-th peak of the lower
4455  * interval; peak_hi[l] is the sequence number of the l-th peak of
4456  * the higher interval. j is, of course, the sequence number of the
4457  * current peak (second big loop).
4458  */
4459 
4460  prev_variation = 1000.0;
4461  minl = mink = 0;
4462 
4463  for (k = 0; k < npeaks_lo; k++) {
4464  denom = peak[j] - peak[peak_lo[k]];
4465  for (l = 0; l < npeaks_hi; l++) {
4466 
4467  /*
4468  * For any pair of peaks - one from the lower and the other
4469  * from the higher interval - I compute the same ratio that
4470  * was computed with the current line catalog wavelength.
4471  */
4472 
4473  pratio = (peak[peak_hi[l]] - peak[j]) / denom;
4474 
4475  /*
4476  * If the two ratios are compatible within the specified
4477  * tolerance, we have a preliminary identification. This
4478  * will be marked in the matrix ident[][], where the first
4479  * index corresponds to a peak sequence number, and the second
4480  * index is the counter of the identifications made during
4481  * this whole process. The array of counters is nident[].
4482  * If more than one interval pair fulfills the specified
4483  * tolerance, the closest to the expected ratio is selected.
4484  */
4485 
4486  variation = fabs(lratio-pratio) / pratio;
4487 
4488  if (variation < tolerance) {
4489  if (variation < prev_variation) {
4490  prev_variation = variation;
4491  minl = l;
4492  mink = k;
4493  }
4494  }
4495  }
4496  }
4497  if (prev_variation < tolerance) {
4498  ident[j][nident[j]] = i;
4499  ident[peak_hi[minl]][nident[peak_hi[minl]]] = i + 1;
4500  ident[peak_lo[mink]][nident[peak_lo[mink]]] = i - 1;
4501  ++nident[j];
4502  ++nident[peak_hi[minl]];
4503  ++nident[peak_lo[mink]];
4504  }
4505  } /* End loop on positions */
4506  } /* End loop on lines */
4507 
4508 
4509  /*
4510  * At this point I have filled the ident matrix with all my preliminary
4511  * identifications. Ambiguous identifications must be eliminated.
4512  */
4513 
4514 
4515  for (i = 0; i < npeaks; i++) {
4516 
4517 
4518  /*
4519  * I don't take into consideration peaks that were never identified.
4520  * They are likely contaminations, or emission lines that were not
4521  * listed in the input wavelength catalog.
4522  */
4523 
4524  if (nident[i] > 1) {
4525 
4526 
4527  /*
4528  * Initialise the histogram of wavelengths assigned to the i-th peak.
4529  */
4530 
4531  for (j = 0; j < nlines; j++)
4532  lident[j] = 0;
4533 
4534 
4535  /*
4536  * Count how many times each catalog wavelength was assigned
4537  * to the i-th peak.
4538  */
4539 
4540  for (j = 0; j < nident[i]; j++)
4541  ++lident[ident[i][j]];
4542 
4543 
4544  /*
4545  * What wavelength was most frequently assigned to the i-th peak?
4546  */
4547 
4548  max = 0;
4549  maxpos = 0;
4550  for (j = 0; j < nlines; j++) {
4551  if (max < lident[j]) {
4552  max = lident[j];
4553  maxpos = j;
4554  }
4555  }
4556 
4557 
4558  /*
4559  * Were there other wavelengths assigned with the same frequency?
4560  * This would be the case of an ambiguous identification. It is
4561  * safer to reject this peak...
4562  */
4563 
4564  ambiguous = 0;
4565 
4566  for (k = maxpos + 1; k < nlines; k++) {
4567  if (lident[k] == max) {
4568  ambiguous = 1;
4569  break;
4570  }
4571  }
4572 
4573  if (ambiguous)
4574  continue;
4575 
4576 
4577  /*
4578  * Otherwise, I assign to the i-th peak the wavelength that was
4579  * most often assigned to it.
4580  */
4581 
4582  tmp_xpos[n] = peak[i];
4583  tmp_lambda[n] = line[maxpos];
4584  tmp_ilambda[n] = maxpos;
4585 
4586  ++n;
4587 
4588  }
4589 
4590  }
4591 
4592 
4593  /*
4594  * Check on identified peaks. Contaminations from other spectra might
4595  * be present and should be excluded: this type of contamination
4596  * consists of peaks that have been _correctly_ identified! The non-
4597  * spectral type of light contamination should have been almost all
4598  * removed already in the previous steps, but it may still be present.
4599  * Here, the self-consistent sequences of identified peaks are
4600  * separated one from the other. At the moment, just the longest of
4601  * such sequences is selected (in other words, spectral multiplexing
4602  * is ignored).
4603  */
4604 
4605  if (n > 1) {
4606  nn = 0; /* Number of peaks in the list of sequences */
4607  nseq = 0; /* Current sequence */
4608  for (k = 0; k < n; k++) {
4609  if (flag[k] == 0) { /* Was peak k already assigned to a sequence? */
4610  flag[k] = 1;
4611  xpos[nn] = tmp_xpos[k]; /* Begin the nseq-th sequence */
4612  lambda[nn] = tmp_lambda[k];
4613  ilambda[nn] = tmp_ilambda[k];
4614  ++seq_length[nseq];
4615  ++nn;
4616 
4617  /*
4618  * Now look for all the following peaks that are compatible
4619  * with the expected spectral dispersion, and add them in
4620  * sequence to xpos. Note that missing peaks are not a problem...
4621  */
4622 
4623  i = k;
4624  while (i < n - 1) {
4625  found = 0;
4626  for (j = i + 1; j < n; j++) {
4627  if (flag[j] == 0) {
4628  disp = (tmp_lambda[j] - tmp_lambda[i])
4629  / (tmp_xpos[j] - tmp_xpos[i]);
4630  if (disp >= min_disp && disp <= max_disp) {
4631  flag[j] = 1;
4632  xpos[nn] = tmp_xpos[j];
4633  lambda[nn] = tmp_lambda[j];
4634  ilambda[nn] = tmp_ilambda[j];
4635  ++seq_length[nseq];
4636  ++nn;
4637  i = j;
4638  found = 1;
4639  break;
4640  }
4641  }
4642  }
4643  if (!found)
4644  break;
4645  }
4646 
4647  /*
4648  * Current sequence is completed: begin new sequence on the
4649  * excluded peaks, starting the loop on peaks again.
4650  */
4651 
4652  ++nseq;
4653  k = 0;
4654  }
4655  }
4656 
4657 
4658  /*
4659  * Find the longest sequence of self-consistent peaks.
4660  */
4661 
4662  maxpos = max = 0;
4663 
4664  if (mos_multiplex < 0) {
4665  for (i = 0; i < nseq; i++) {
4666  if (seq_length[i] > max) {
4667  max = seq_length[i];
4668  maxpos = i;
4669  }
4670  }
4671  }
4672  else {
4673 
4674  /*
4675  * Now consider the sequence which lays in the specified
4676  * CCD region (indicated by mos_multiplex): that is, _most_
4677  * of its lines (more than half) must be in that region...
4678  */
4679 
4680  nn = 0;
4681  found = 0;
4682 
4683  for (i = 0; i < nseq; i++) {
4684  n = seq_length[i];
4685  if (n > 5) {
4686  cpl_array *regions = cpl_array_new(n, CPL_TYPE_INT);
4687  int region;
4688 
4689  for (j = 0; j < n; j++)
4690  cpl_array_set_int(regions, j,
4691  ((int)floor(xpos[nn + j])) / mos_region_size);
4692 
4693  region = (int)cpl_array_get_median(regions);
4694  cpl_array_delete(regions);
4695 
4696  if (mos_multiplex == region) {
4697  if (found) {
4698  cpl_msg_debug(cpl_func, "More than one spectrum found in "
4699  "region %d (only the first one is extracted)",
4700  mos_multiplex);
4701  break;
4702  }
4703  found = 1;
4704  max = seq_length[i];
4705  maxpos = i;
4706  }
4707  }
4708  nn += seq_length[i];
4709  }
4710  }
4711 
4712  /*
4713  * Find where this sequence starts in the whole peak position
4714  * storage.
4715  */
4716 
4717  nn = 0;
4718  for (i = 0; i < maxpos; i++)
4719  nn += seq_length[i];
4720 
4721  /*
4722  * Move the longest sequence at the beginning of the returned lists
4723  */
4724 
4725  n = max;
4726  for (i = 0; i < n; i++, nn++) {
4727  xpos[i] = xpos[nn];
4728  lambda[i] = lambda[nn];
4729  ilambda[i] = ilambda[nn];
4730  }
4731 
4732 
4733  /*
4734  * Are some wavelengths missing? Recover them.
4735  */
4736 
4737  for (i = 1; i < n; i++) {
4738  gap = ilambda[i] - ilambda[i-1];
4739  for (j = 1; j < gap; j++) {
4740 
4741  if (j == 1) {
4742 
4743  /*
4744  * Determine the local dispersion from the current pair of peaks
4745  */
4746 
4747  disp = (lambda[i] - lambda[i-1]) / (xpos[i] - xpos[i-1]);
4748  }
4749 
4750  /*
4751  * With this, find the expected position of the missing
4752  * peak by linear interpolation.
4753  */
4754 
4755  hi_start = xpos[i-1] + (line[ilambda[i-1] + j] - lambda[i-1]) / disp;
4756 
4757  /*
4758  * Is there a peak at that position? Here a peak from the
4759  * original list is searched, that is closer than 2 pixels
4760  * to the expected position. If it is found, insert it at
4761  * the current position on the list of identified peaks,
4762  * and leave immediately the loop (taking the new position
4763  * for the following linear interpolation, in case more
4764  * than one peak is missing in the current interval).
4765  * If it is not found, stay in the loop, looking for
4766  * the following missing peaks in this interval.
4767  */
4768 
4769  found = 0;
4770  for (k = 0; k < npeaks; k++) {
4771  if (fabs(peak[k] - hi_start) < 2) {
4772  for (l = n; l > i; l--) {
4773  xpos[l] = xpos[l-1];
4774  lambda[l] = lambda[l-1];
4775  ilambda[l] = ilambda[l-1];
4776  }
4777  xpos[i] = peak[k];
4778  lambda[i] = line[ilambda[i-1] + j];
4779  ilambda[i] = ilambda[i-1] + j;
4780  ++n;
4781  found = 1;
4782  break;
4783  }
4784  }
4785  if (found)
4786  break;
4787  }
4788  }
4789 
4790 
4791  /*
4792  * Try to extrapolate forward
4793  */
4794 
4795  found = 1;
4796  while (ilambda[n-1] < nlines - 1 && found) {
4797 
4798  /*
4799  * Determine the local dispersion from the last pair of
4800  * identified peaks
4801  */
4802 
4803  if (n > 1)
4804  disp = (lambda[n-1] - lambda[n-2]) / (xpos[n-1] - xpos[n-2]);
4805  else
4806  disp = 0.0;
4807 
4808  if (disp > max_disp || disp < min_disp)
4809  break;
4810 
4811 
4812  /*
4813  * With this, find the expected position of the missing
4814  * peak by linear interpolation.
4815  */
4816 
4817  hi_start = xpos[n-1] + (line[ilambda[n-1] + 1] - lambda[n-1]) / disp;
4818 
4819  /*
4820  * Is there a peak at that position? Here a peak from the
4821  * original list is searched, that is closer than 6 pixels
4822  * to the expected position. If it is found, insert it at
4823  * the end of the list of identified peaks. If it is not
4824  * found, leave the loop.
4825  */
4826 
4827  found = 0;
4828  min = fabs(peak[0] - hi_start);
4829  minpos = 0;
4830  for (k = 1; k < npeaks; k++) {
4831  if (min > fabs(peak[k] - hi_start)) {
4832  min = fabs(peak[k] - hi_start);
4833  minpos = k;
4834  }
4835  }
4836  if (min < 6 && fabs(peak[minpos] - xpos[n-1]) > 1.0) {
4837  xpos[n] = peak[minpos];
4838  lambda[n] = line[ilambda[n-1] + 1];
4839  ilambda[n] = ilambda[n-1] + 1;
4840  ++n;
4841  found = 1;
4842  }
4843  }
4844 
4845 
4846  /*
4847  * Try to extrapolate backward
4848  */
4849 
4850  found = 1;
4851  while (ilambda[0] > 0 && found) {
4852 
4853  /*
4854  * Determine the local dispersion from the first pair of
4855  * identified peaks
4856  */
4857 
4858  disp = (lambda[1] - lambda[0]) / (xpos[1] - xpos[0]);
4859 
4860  if (disp > max_disp || disp < min_disp)
4861  break;
4862 
4863 
4864  /*
4865  * With this, find the expected position of the missing
4866  * peak by linear interpolation.
4867  */
4868 
4869  hi_start = xpos[0] - (lambda[0] - line[ilambda[0] - 1]) / disp;
4870 
4871 
4872  /*
4873  * Is there a peak at that position? Here a peak from the
4874  * original list is searched, that is closer than 6 pixels
4875  * to the expected position. If it is found, insert it at
4876  * the beginning of the list of identified peaks. If it is not
4877  * found, leave the loop.
4878  */
4879 
4880  found = 0;
4881  min = fabs(peak[0] - hi_start);
4882  minpos = 0;
4883  for (k = 1; k < npeaks; k++) {
4884  if (min > fabs(peak[k] - hi_start)) {
4885  min = fabs(peak[k] - hi_start);
4886  minpos = k;
4887  }
4888  }
4889  if (min < 6 && fabs(peak[minpos] - xpos[0]) > 1.0) {
4890  for (j = n; j > 0; j--) {
4891  xpos[j] = xpos[j-1];
4892  lambda[j] = lambda[j-1];
4893  ilambda[j] = ilambda[j-1];
4894  }
4895  xpos[0] = peak[minpos];
4896  lambda[0] = line[ilambda[0] - 1];
4897  ilambda[0] = ilambda[0] - 1;
4898  ++n;
4899  found = 1;
4900  }
4901  }
4902  }
4903 
4904 
4905  /*
4906  * At this point all peaks are processed. Free memory, and return
4907  * the result.
4908  */
4909 
4910 /************************************************+
4911  for (i = 0; i < npeaks; i++) {
4912  printf("Peak %d:\n ", i);
4913  for (j = 0; j < nident[i]; j++)
4914  printf("%.2f, ", line[ident[i][j]]);
4915  printf("\n");
4916  }
4917 
4918  printf("\n");
4919 
4920  for (i = 0; i < n; i++)
4921  printf("%.2f, %.2f\n", xpos[i], lambda[i]);
4922 +************************************************/
4923  for (i = 0; i < npeaks; i++)
4924  cpl_free(ident[i]);
4925  cpl_free(ident);
4926  cpl_free(nident);
4927  cpl_free(lident);
4928  cpl_free(ilambda);
4929  cpl_free(tmp_xpos);
4930  cpl_free(tmp_lambda);
4931  cpl_free(tmp_ilambda);
4932  cpl_free(peak_lo);
4933  cpl_free(flag);
4934  cpl_free(seq_length);
4935  cpl_free(peak_hi);
4936 
4937  if (n == 0) {
4938  cpl_free(xpos);
4939  cpl_free(lambda);
4940  return NULL;
4941  }
4942 
4943  return cpl_bivector_wrap_vectors(cpl_vector_wrap(n, xpos),
4944  cpl_vector_wrap(n, lambda));
4945 }
4946 
4947 
4965 /*
4966 double mos_eval_dds(cpl_polynomial *ids, double blue, double red,
4967  double refwave, double pixel)
4968 {
4969  double yellow;
4970  double cpixel;
4971  double tolerance = 0.02;
4972  int max_iter = 20;
4973  int iter = 0;
4974 
4975 
4976  if (cpl_polynomial_eval_1d(ids, blue-refwave, NULL) > pixel)
4977  return 0.0;
4978 
4979  if (cpl_polynomial_eval_1d(ids, red-refwave, NULL) < pixel)
4980  return 0.0;
4981 
4982  yellow = (blue + red) / 2;
4983 
4984  cpixel = cpl_polynomial_eval_1d(ids, yellow-refwave, NULL);
4985 
4986  while (fabs(cpixel - pixel) > tolerance && iter < max_iter) {
4987 
4988  if (cpixel > pixel)
4989  red = yellow;
4990  else
4991  blue = yellow;
4992 
4993  yellow = (blue + red) / 2;
4994  cpixel = cpl_polynomial_eval_1d(ids, yellow-refwave, NULL);
4995 
4996  iter++;
4997 
4998  }
4999 
5000  return yellow;
5001 
5002 }
5003 */
5004 
5005 double mos_eval_dds(cpl_polynomial *ids, double blue, double red,
5006  double refwave, double pixel)
5007 {
5008  double yellow;
5009  double coeff;
5010  cpl_size zero = 0;
5011 
5012  if (cpl_polynomial_eval_1d(ids, blue-refwave, NULL) > pixel)
5013  return 0.0;
5014 
5015  if (cpl_polynomial_eval_1d(ids, red-refwave, NULL) < pixel)
5016  return 0.0;
5017 
5018  yellow = (blue + red) / 2 - refwave;
5019 
5020  coeff = cpl_polynomial_get_coeff(ids, &zero);
5021  cpl_polynomial_set_coeff(ids, &zero, coeff - pixel);
5022 
5023  cpl_polynomial_solve_1d(ids, yellow, &yellow, 1);
5024  if (cpl_error_get_code() != CPL_ERROR_NONE) {
5025  cpl_error_reset();
5026  return 0.0;
5027  }
5028 
5029  cpl_polynomial_set_coeff(ids, &zero, coeff);
5030 
5031  return yellow + refwave;
5032 
5033 }
5034 
5060 cpl_polynomial *mos_poly_wav2pix(cpl_bivector *pixwav, int order,
5061  double reject, int minlines,
5062  int *nlines, double *err)
5063 {
5064  const char *func = "mos_poly_wav2pix";
5065 
5066  cpl_bivector *pixwav2;
5067  cpl_vector *wavel;
5068  cpl_vector *pixel;
5069  double *d_wavel;
5070  double *d_pixel;
5071  double pixpos;
5072  int fitlines;
5073  int rejection = 0;
5074  int i, j;
5075 
5076  cpl_polynomial *ids;
5077 
5078 
5079  *nlines = 0;
5080  *err = 0;
5081 
5082  if (pixwav == NULL) {
5083  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
5084  return NULL;
5085  }
5086 
5087  fitlines = cpl_bivector_get_size(pixwav);
5088 
5089  if (fitlines < minlines) {
5090  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
5091  return NULL;
5092  }
5093 
5094 
5095  /*
5096  * If outliers rejection was requested, allocate a working
5097  * vector (that can be modified as soon as outliers are removed)
5098  */
5099 
5100  if (reject > 0.0)
5101  rejection = 1;
5102 
5103  if (rejection)
5104  pixwav2 = cpl_bivector_duplicate(pixwav);
5105  else
5106  pixwav2 = pixwav;
5107 
5108 
5109  /*
5110  * The single vectors are extracted just because the fitting routine
5111  * requires it
5112  */
5113 
5114  pixel = cpl_bivector_get_x(pixwav2);
5115  wavel = cpl_bivector_get_y(pixwav2);
5116 
5117 
5118  /*
5119  * Get rid of the wrapper, in case of duplication
5120  */
5121 
5122  if (rejection)
5123  cpl_bivector_unwrap_vectors(pixwav2);
5124 
5125 
5126  /*
5127  * Here begins the iterative fit of identified lines
5128  */
5129 
5130  while (fitlines >= minlines) {
5131 
5132  ids = cpl_polynomial_fit_1d_create(wavel, pixel, order, err);
5133  *err = sqrt(*err);
5134 
5135  if (ids == NULL) {
5136  cpl_msg_debug(cpl_error_get_where(), "%s", cpl_error_get_message());
5137  cpl_msg_debug(func, "Fitting IDS");
5138  cpl_error_set_where(func);
5139  if (rejection) {
5140  cpl_vector_delete(wavel);
5141  cpl_vector_delete(pixel);
5142  }
5143  return NULL;
5144  }
5145 
5146  if (rejection) {
5147 
5148 
5149  /*
5150  * Now work directly with the vector data buffers...
5151  */
5152 
5153  d_pixel = cpl_vector_unwrap(pixel);
5154  d_wavel = cpl_vector_unwrap(wavel);
5155 
5156  for (i = 0, j = 0; i < fitlines; i++) {
5157  pixpos = cpl_polynomial_eval_1d(ids, d_wavel[i], NULL);
5158  if (fabs(pixpos - d_pixel[i]) < reject) {
5159  d_pixel[j] = d_pixel[i];
5160  d_wavel[j] = d_wavel[i];
5161  j++;
5162  }
5163  }
5164 
5165  if (j == fitlines) { /* No rejection in last iteration */
5166  cpl_free(d_wavel);
5167  cpl_free(d_pixel);
5168  *nlines = fitlines;
5169  return ids;
5170  }
5171  else { /* Some lines were rejected */
5172  fitlines = j;
5173  cpl_polynomial_delete(ids);
5174  if (fitlines >= minlines) {
5175  pixel = cpl_vector_wrap(fitlines, d_pixel);
5176  wavel = cpl_vector_wrap(fitlines, d_wavel);
5177  }
5178  else { /* Too few lines: failure */
5179  cpl_free(d_wavel);
5180  cpl_free(d_pixel);
5181  cpl_error_set(func, CPL_ERROR_CONTINUE);
5182  return NULL;
5183  }
5184  }
5185  }
5186  else {
5187  *nlines = fitlines;
5188  return ids; /* Exit at first iteration if no rejection */
5189  }
5190  }
5191 
5192  return ids; /* To avoid compiler warnings */
5193 }
5194 
5195 
5220 cpl_polynomial *mos_poly_pix2wav(cpl_bivector *pixwav, int order,
5221  double reject, int minlines,
5222  int *nlines, double *err)
5223 {
5224 
5225  cpl_bivector *wavpix;
5226  cpl_vector *wavel;
5227  cpl_vector *pixel;
5228 
5229  cpl_polynomial *dds;
5230 
5231 
5232  /*
5233  * Swap vectors in bivector, in order to reuse mos_poly_wav2pix()
5234  */
5235 
5236  pixel = cpl_bivector_get_x(pixwav);
5237  wavel = cpl_bivector_get_y(pixwav);
5238 
5239  wavpix = cpl_bivector_wrap_vectors(wavel, pixel);
5240 
5241  dds = mos_poly_wav2pix(wavpix, order, reject, minlines, nlines, err);
5242 
5243  cpl_bivector_unwrap_vectors(wavpix);
5244 
5245  return dds;
5246 
5247 }
5248 
5249 
5272 cpl_bivector *mos_find_peaks(const float *spectrum, int length,
5273  cpl_vector *lines, cpl_polynomial *ids,
5274  double refwave, int sradius)
5275 {
5276  const char *func = "mos_find_peaks";
5277 
5278  double *data;
5279  double *d_pixel;
5280  double *d_wavel;
5281  float pos;
5282  int nlines;
5283  int pixel;
5284  int i, j;
5285 
5286 
5287  if (spectrum == NULL || lines == NULL || ids == NULL) {
5288  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
5289  return NULL;
5290  }
5291 
5292  nlines = cpl_vector_get_size(lines);
5293 
5294  if (sradius < 1 || length < 2*sradius+1 || nlines < 1) {
5295  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
5296  return NULL;
5297  }
5298 
5299  d_wavel = cpl_malloc(nlines * sizeof(double));
5300  d_pixel = cpl_malloc(nlines * sizeof(double));
5301 
5302  data = cpl_vector_get_data(lines);
5303 
5304  for (i = 0, j = 0; i < nlines; i++) {
5305  pixel = cpl_polynomial_eval_1d(ids, data[i]-refwave, NULL) + 0.5;
5306  if (pixel < 0 || pixel - sradius < 0 || pixel + sradius >= length)
5307  continue;
5308  if (peakPosition(spectrum+pixel-sradius, 2*sradius+1, &pos, 1) == 0) {
5309  pos += pixel - sradius;
5310  d_pixel[j] = pos;
5311  d_wavel[j] = data[i];
5312  j++;
5313  }
5314  }
5315 
5316  if (j > 0) {
5317  return cpl_bivector_wrap_vectors(cpl_vector_wrap(j, d_pixel),
5318  cpl_vector_wrap(j, d_wavel));
5319  }
5320  else {
5321  cpl_free(d_wavel);
5322  cpl_free(d_pixel);
5323  cpl_error_set(func, CPL_ERROR_ILLEGAL_OUTPUT);
5324  return NULL;
5325  }
5326 }
5327 
5328 
5452 cpl_image *mos_wavelength_calibration_raw(const cpl_image *image,
5453  cpl_vector *lines,
5454  double dispersion, float level,
5455  int sradius, int order,
5456  double reject, double refwave,
5457  double *wavestart, double *waveend,
5458  int *nlines, double *error,
5459  cpl_table *idscoeff,
5460  cpl_image *calibration,
5461  cpl_image *residuals,
5462  cpl_table *restable,
5463  cpl_mask *refmask,
5464  cpl_table *detected_lines)
5465 {
5466 
5467  const char *func = "mos_wavelength_calibration_raw";
5468 
5469  const char *clab[6] = {"c0", "c1", "c2", "c3", "c4", "c5"};
5470  /* Max order is 5 */
5471 
5472  double tolerance = 20.0; /* Probably forever... */
5473  int step = 10; /* Compute restable every "step" rows */
5474 
5475  char name[MAX_COLNAME];
5476  cpl_image *resampled;
5477  cpl_bivector *output;
5478  cpl_bivector *new_output;
5479  cpl_vector *peaks;
5480  cpl_vector *wavel;
5481  cpl_polynomial *ids;
5482  cpl_polynomial *lin;
5483  cpl_matrix *kernel;
5484  double ids_err;
5485  double max_disp, min_disp;
5486  double *line;
5487  double firstLambda, lastLambda, lambda;
5488  double value, wave, pixe;
5489  cpl_binary *mdata;
5490  const float *sdata;
5491  float *rdata;
5492  float *idata;
5493  float *ddata;
5494  float v1, v2, vi;
5495  float fpixel;
5496  int *have_it;
5497  int pixstart, pixend;
5498  int extrapolation;
5499  int nref;
5500  int nl, nx, ny, pixel;
5501  int countLines, usedLines;
5502  int uorder;
5503  int in, first, last;
5504  int width, uradius;
5505  int i, j;
5506  int null;
5507  cpl_size k;
5508 
5509 
5510  if (dispersion == 0.0) {
5511  cpl_msg_error(func, "The expected dispersion (A/pixel) must be given");
5512  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
5513  return NULL;
5514  }
5515 
5516  if (dispersion < 0.0) {
5517  cpl_msg_error(func, "The expected dispersion must be positive");
5518  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
5519  return NULL;
5520  }
5521 
5522  max_disp = dispersion + dispersion * tolerance / 100;
5523  min_disp = dispersion - dispersion * tolerance / 100;
5524 
5525  if (order < 1) {
5526  cpl_msg_error(func, "The order of the fitting polynomial "
5527  "must be at least 1");
5528  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
5529  return NULL;
5530  }
5531 
5532  if (image == NULL || lines == NULL) {
5533  cpl_msg_error(func, "Both spectral exposure and reference line "
5534  "catalog are required in input");
5535  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
5536  return NULL;
5537  }
5538 
5539  nx = cpl_image_get_size_x(image);
5540  ny = cpl_image_get_size_y(image);
5541  sdata = cpl_image_get_data_float_const(image);
5542 
5543  nref = cpl_vector_get_size(lines);
5544  line = cpl_vector_get_data(lines);
5545 
5546  if (*wavestart < 1.0 && *waveend < 1.0) {
5547  firstLambda = line[0];
5548  lastLambda = line[nref-1];
5549  extrapolation = (lastLambda - firstLambda) / 10;
5550  firstLambda -= extrapolation;
5551  lastLambda += extrapolation;
5552  *wavestart = firstLambda;
5553  *waveend = lastLambda;
5554  }
5555  else {
5556  firstLambda = *wavestart;
5557  lastLambda = *waveend;
5558  }
5559 
5560  nl = (lastLambda - firstLambda) / dispersion;
5561  resampled = cpl_image_new(nl, ny, CPL_TYPE_FLOAT);
5562  rdata = cpl_image_get_data_float(resampled);
5563 
5564  if (calibration)
5565  idata = cpl_image_get_data_float(calibration);
5566 
5567  if (residuals)
5568  ddata = cpl_image_get_data_float(residuals);
5569 
5570  if (idscoeff)
5571  for (j = 0; j <= order; j++)
5572  cpl_table_new_column(idscoeff, clab[j], CPL_TYPE_DOUBLE);
5573 
5574  if (restable) {
5575  cpl_table_set_size(restable, nref);
5576  cpl_table_new_column(restable, "wavelength", CPL_TYPE_DOUBLE);
5577  cpl_table_copy_data_double(restable, "wavelength", line);
5578  for (i = 0; i < ny; i += step) {
5579  snprintf(name, MAX_COLNAME, "r%d", i);
5580  cpl_table_new_column(restable, name, CPL_TYPE_DOUBLE);
5581  snprintf(name, MAX_COLNAME, "d%d", i);
5582  cpl_table_new_column(restable, name, CPL_TYPE_DOUBLE);
5583  snprintf(name, MAX_COLNAME, "p%d", i);
5584  cpl_table_new_column(restable, name, CPL_TYPE_DOUBLE);
5585  }
5586  }
5587 
5588  if (detected_lines) {
5589  cpl_table_set_size(detected_lines, 0);
5590  cpl_table_new_column(detected_lines, "xpos", CPL_TYPE_DOUBLE);
5591  cpl_table_new_column(detected_lines, "ypos", CPL_TYPE_DOUBLE);
5592  cpl_table_new_column(detected_lines, "xpos_iter", CPL_TYPE_DOUBLE);
5593  cpl_table_new_column(detected_lines, "ypos_iter", CPL_TYPE_DOUBLE);
5594  cpl_table_new_column(detected_lines, "peak_flux", CPL_TYPE_DOUBLE);
5595  cpl_table_new_column(detected_lines, "wave_ident", CPL_TYPE_DOUBLE);
5596  cpl_table_new_column(detected_lines, "wave_ident_iter", CPL_TYPE_DOUBLE);
5597  cpl_table_new_column(detected_lines, "xpos_fit_rect_wavecal", CPL_TYPE_DOUBLE);
5598  cpl_table_new_column(detected_lines, "res_xpos", CPL_TYPE_DOUBLE);
5599  }
5600 
5601  /*
5602  * Here is the real thing: detecting and identifying peaks,
5603  * and then fit the transformation from wavelength to pixel
5604  * and from pixel to wavelength.
5605  */
5606 
5607  for (i = 0; i < ny; i++) {
5608  width = mos_lines_width(sdata + i*nx, nx);
5609  if (sradius > 0) {
5610  if (width > sradius) {
5611  uradius = width;
5612  }
5613  else {
5614  uradius = sradius;
5615  }
5616  }
5617  if (width < 5)
5618  width = 5;
5619  peaks = mos_peak_candidates(sdata + i*nx, nx, level, width);
5620  if (peaks) {
5621  peaks = mos_refine_peaks(sdata + i*nx, nx, peaks, width);
5622  }
5623  if (peaks) {
5624  output = mos_identify_peaks(peaks, lines, min_disp, max_disp, 0.05);
5625  if (output) {
5626  countLines = cpl_bivector_get_size(output);
5627  if (countLines < 4) {
5628  cpl_bivector_delete(output);
5629  cpl_vector_delete(peaks);
5630  if (nlines)
5631  nlines[i] = 0;
5632  if (error)
5633  error[i] = 0.0;
5634  continue;
5635  }
5636 
5637  /*
5638  * Set reference wavelength as zero point
5639  */
5640 
5641  wavel = cpl_bivector_get_y(output);
5642  cpl_vector_subtract_scalar(wavel, refwave);
5643 
5644  uorder = countLines / 2 - 1;
5645  if (uorder > order)
5646  uorder = order;
5647 
5648 /* This part is now commented out. In case the first-guess iteration
5649  * was requested, the first fit was made with a lower polynomial degree:
5650  * more robust, and still accurate enough to be used as a first-guess.
5651 
5652  if (sradius > 0 && uorder > 2)
5653  --uorder;
5654 
5655  * End of commented part */
5656 
5657  ids = mos_poly_wav2pix(output, uorder, reject,
5658  2 * (uorder + 1), &usedLines,
5659  &ids_err);
5660 
5661  if (ids == NULL) {
5662  cpl_bivector_delete(output);
5663  cpl_vector_delete(peaks);
5664  if (nlines)
5665  nlines[i] = 0;
5666  if (error)
5667  error[i] = 0.0;
5668  cpl_error_reset();
5669  continue;
5670  }
5671 
5672  if (idscoeff) {
5673 
5674  /*
5675  * Write it anyway, even in case a first-guess based
5676  * solution will be searched afterwards: in case of
5677  * failure, the "blind" solution is kept.
5678  */
5679 
5680  for (k = 0; k <= order; k++) {
5681  if (k > uorder) {
5682  cpl_table_set_double(idscoeff, clab[k], i, 0.0);
5683  }
5684  else {
5685  cpl_table_set_double(idscoeff, clab[k], i,
5686  cpl_polynomial_get_coeff(ids, &k));
5687  }
5688  }
5689  }
5690 
5691  if(detected_lines)
5692  {
5693  cpl_size newlines = cpl_vector_get_size(peaks);
5694  cpl_size oldsize = cpl_table_get_nrow(detected_lines);
5695  cpl_table_set_size(detected_lines, oldsize + newlines);
5696  for(cpl_size iline = 0; iline < newlines; ++iline)
5697  {
5698  cpl_table_set_double(detected_lines, "xpos",
5699  oldsize + iline, cpl_vector_get(peaks, iline) + 1);
5700  cpl_table_set_double(detected_lines, "ypos",
5701  oldsize + iline, (double)i + 1);
5702  cpl_table_set_double(detected_lines, "peak_flux",
5703  oldsize + iline,
5704  sdata[i*nx+(int)(cpl_vector_get(peaks, iline)+0.5)]);
5705  }
5706  }
5707 
5708  //Fill the line identification information in
5709  //the detected_lines table
5710  if(detected_lines)
5711  {
5712  cpl_size nidentlines = cpl_bivector_get_size(output);
5713  cpl_size ndetectlines = cpl_vector_get_size(peaks);
5714  cpl_size totalsize = cpl_table_get_nrow(detected_lines);
5715  for(cpl_size idline = 0; idline < nidentlines; ++idline)
5716  {
5717  for(cpl_size detline = 0; detline < ndetectlines; ++detline)
5718  {
5719  if(cpl_vector_get(peaks, detline) ==
5720  cpl_bivector_get_x_data(output)[idline])
5721  {
5722  cpl_size table_pos = totalsize - ndetectlines + detline;
5723  double wave_ident = cpl_bivector_get_y_data(output)[idline] + refwave;
5724  double xpix_fit = cpl_polynomial_eval_1d(ids,
5725  wave_ident - refwave, NULL);
5726  double xpos_det = cpl_table_get_double(detected_lines,
5727  "xpos",
5728  table_pos, &null);
5729  cpl_table_set_double(detected_lines,
5730  "wave_ident",
5731  table_pos,
5732  wave_ident);
5733  cpl_table_set_double(detected_lines,
5734  "xpos_fit_rect_wavecal",
5735  table_pos,
5736  xpix_fit + 1);
5737  cpl_table_set_double(detected_lines,
5738  "res_xpos",
5739  table_pos,
5740  xpos_det - xpix_fit - 1);
5741 
5742  }
5743  }
5744  }
5745  }
5746 
5747  if (sradius > 0) {
5748 
5749  /*
5750  * Use ids as a first-guess
5751  */
5752 
5753  new_output = mos_find_peaks(sdata + i*nx, nx, lines,
5754  ids, refwave, uradius);
5755 
5756  if (new_output) {
5757  cpl_bivector_delete(output);
5758  output = new_output;
5759  }
5760  else
5761  cpl_error_reset();
5762 
5763 
5764  cpl_polynomial_delete(ids);
5765 
5766  countLines = cpl_bivector_get_size(output);
5767 
5768  if (countLines < 4) {
5769  cpl_bivector_delete(output);
5770  cpl_vector_delete(peaks);
5771 
5772  /*
5773  * With the following code a decision is taken:
5774  * if using the first-guess gives no results,
5775  * then also the "blind" solution is rejected.
5776  */
5777 
5778  if (nlines)
5779  nlines[i] = 0;
5780  if (error)
5781  error[i] = 0.0;
5782  if (idscoeff)
5783  for (k = 0; k <= order; k++)
5784  cpl_table_set_invalid(idscoeff, clab[k], i);
5785  continue;
5786  }
5787 
5788  wavel = cpl_bivector_get_y(output);
5789  cpl_vector_subtract_scalar(wavel, refwave);
5790 
5791  uorder = countLines / 2 - 1;
5792  if (uorder > order)
5793  uorder = order;
5794 
5795  ids = mos_poly_wav2pix(output, uorder, reject,
5796  2 * (uorder + 1), &usedLines,
5797  &ids_err);
5798 
5799  if (ids == NULL) {
5800  cpl_bivector_delete(output);
5801  cpl_vector_delete(peaks);
5802 
5803  /*
5804  * With the following code a decision is taken:
5805  * if using the first-guess gives no results,
5806  * then also the "blind" solution is rejected.
5807  */
5808 
5809  if (nlines)
5810  nlines[i] = 0;
5811  if (error)
5812  error[i] = 0.0;
5813  if (idscoeff)
5814  for (k = 0; k <= order; k++)
5815  cpl_table_set_invalid(idscoeff, clab[k], i);
5816  cpl_error_reset();
5817  continue;
5818  }
5819 
5820  if (idscoeff) {
5821  for (k = 0; k <= order; k++) {
5822  if (k > uorder) {
5823  cpl_table_set_double(idscoeff, clab[k], i, 0.0);
5824  }
5825  else {
5826  cpl_table_set_double(idscoeff, clab[k], i,
5827  cpl_polynomial_get_coeff(ids, &k));
5828  }
5829  }
5830  }
5831 
5832 
5833  if(detected_lines)
5834  {
5835  cpl_size oldsize = cpl_table_get_nrow(detected_lines);
5836  cpl_size nidentlines = cpl_bivector_get_size(output);
5837  cpl_table_set_size(detected_lines, oldsize + nidentlines);
5838  for(cpl_size idline = 0; idline < nidentlines ; ++idline)
5839  {
5840  double wave_ident = cpl_bivector_get_y_data(output)[idline] + refwave;
5841  double xpix_fit = cpl_polynomial_eval_1d(ids,
5842  wave_ident - refwave, NULL);
5843  cpl_table_set_double(detected_lines, "xpos_iter",
5844  oldsize + idline, cpl_bivector_get_x_data(output)[idline] + 1);
5845  cpl_table_set_double(detected_lines, "ypos_iter",
5846  oldsize + idline, (double)i + 1);
5847  cpl_table_set_double(detected_lines, "peak_flux",
5848  oldsize + idline,
5849  sdata[i*nx+(int)(cpl_bivector_get_x_data(output)[idline]+0.5)]);
5850  cpl_table_set_double(detected_lines, "wave_ident_iter",
5851  oldsize + idline, wave_ident);
5852  cpl_table_set_double(detected_lines, "xpos_fit_rect_wavecal",
5853  oldsize + idline, xpix_fit + 1);
5854  }
5855  }
5856 
5857  } /* End of "use ids as a first-guess" */
5858 
5859  if (nlines)
5860  nlines[i] = usedLines;
5861  if (error)
5862  error[i] = ids_err / sqrt(usedLines/(uorder + 1));
5863 
5864  pixstart = cpl_polynomial_eval_1d(ids,
5865  cpl_bivector_get_y_data(output)[0], NULL);
5866  pixend = cpl_polynomial_eval_1d(ids,
5867  cpl_bivector_get_y_data(output)[countLines-1], NULL);
5868  extrapolation = (pixend - pixstart) / 5;
5869  pixstart -= extrapolation;
5870  pixend += extrapolation;
5871  if (pixstart < 0)
5872  pixstart = 0;
5873  if (pixend > nx)
5874  pixend = nx;
5875 
5876  /*
5877  * Wavelength calibrated image (if requested):
5878  */
5879 
5880  if (calibration) {
5881  for (j = pixstart; j < pixend; j++) {
5882  (idata + i*nx)[j] = mos_eval_dds(ids, firstLambda,
5883  lastLambda, refwave,
5884  j);
5885  }
5886  }
5887 
5888  /*
5889  * Resampled image:
5890  */
5891 
5892  for (j = 0; j < nl; j++) {
5893  lambda = firstLambda + j * dispersion;
5894  fpixel = cpl_polynomial_eval_1d(ids, lambda - refwave,
5895  NULL);
5896  pixel = fpixel;
5897  if (pixel >= 0 && pixel < nx-1) {
5898  v1 = (sdata + i*nx)[pixel];
5899  v2 = (sdata + i*nx)[pixel+1];
5900  vi = v1 + (v2-v1)*(fpixel-pixel);
5901  (rdata + i*nl)[j] = vi;
5902  }
5903  }
5904 
5905  /*
5906  * Residuals image
5907  */
5908 
5909  if (residuals || (restable && !(i%step))) {
5910  if (restable && !(i%step)) {
5911  lin = cpl_polynomial_new(1);
5912  for (k = 0; k < 2; k++)
5913  cpl_polynomial_set_coeff(lin, &k,
5914  cpl_polynomial_get_coeff(ids, &k));
5915  }
5916  for (j = 0; j < countLines; j++) {
5917  pixe = cpl_bivector_get_x_data(output)[j];
5918  wave = cpl_bivector_get_y_data(output)[j];
5919  value = pixe - cpl_polynomial_eval_1d(ids, wave, NULL);
5920  if (residuals) {
5921  pixel = pixe + 0.5;
5922  (ddata + i*nx)[pixel] = value;
5923  }
5924  if (restable && !(i%step)) {
5925  for (k = 0; k < nref; k++) {
5926  if (fabs(line[k] - refwave - wave) < 0.1) {
5927  snprintf(name, MAX_COLNAME, "r%d", i);
5928  cpl_table_set_double(restable, name,
5929  k, value);
5930  value = pixe
5931  - cpl_polynomial_eval_1d(lin, wave,
5932  NULL);
5933  snprintf(name, MAX_COLNAME, "d%d", i);
5934  cpl_table_set_double(restable, name,
5935  k, value);
5936  snprintf(name, MAX_COLNAME, "p%d", i);
5937  cpl_table_set_double(restable, name,
5938  k, pixe);
5939  break;
5940  }
5941  }
5942  }
5943  }
5944  if (restable && !(i%step)) {
5945  cpl_polynomial_delete(lin);
5946  }
5947  }
5948 
5949  /*
5950  * Mask at reference wavelength
5951  */
5952 
5953  if (refmask) {
5954  mdata = cpl_mask_get_data(refmask);
5955  pixel = cpl_polynomial_eval_1d(ids, 0.0, NULL) + 0.5;
5956  if (pixel - 1 >= 0 && pixel + 1 < nx) {
5957  mdata[pixel-1 + i*nx] = CPL_BINARY_1;
5958  mdata[pixel + i*nx] = CPL_BINARY_1;
5959  mdata[pixel+1 + i*nx] = CPL_BINARY_1;
5960  }
5961  }
5962 
5963  cpl_polynomial_delete(ids);
5964  cpl_bivector_delete(output);
5965  }
5966  cpl_vector_delete(peaks);
5967  }
5968  }
5969 
5970  if (refmask) {
5971  kernel = cpl_matrix_new(3, 3);
5972  cpl_matrix_set(kernel, 0, 1, 1.0);
5973  cpl_matrix_set(kernel, 1, 1, 1.0);
5974  cpl_matrix_set(kernel, 2, 1, 1.0);
5975 
5976  cpl_mask_dilation(refmask, kernel);
5977  cpl_mask_erosion(refmask, kernel);
5978  cpl_mask_erosion(refmask, kernel);
5979  cpl_mask_dilation(refmask, kernel);
5980 
5981  cpl_matrix_delete(kernel);
5982 
5983  /*
5984  * Fill possible gaps
5985  */
5986 
5987  mdata = cpl_mask_get_data(refmask);
5988  have_it = cpl_calloc(ny, sizeof(int));
5989 
5990  for (i = 0; i < ny; i++, mdata += nx) {
5991  for (j = 0; j < nx; j++) {
5992  if (mdata[j] == CPL_BINARY_1) {
5993  have_it[i] = j;
5994  break;
5995  }
5996  }
5997  }
5998 
5999  mdata = cpl_mask_get_data(refmask);
6000  in = 0;
6001  first = last = 0;
6002 
6003  for (i = 0; i < ny; i++) {
6004  if (have_it[i]) {
6005  if (!in) {
6006  in = 1;
6007  if (first) {
6008  last = i;
6009  if (abs(have_it[first] - have_it[last]) < 3) {
6010  for (j = first; j < last; j++) {
6011  mdata[have_it[first] + nx*j + 0] = CPL_BINARY_1;
6012  mdata[have_it[first] + nx*j + 1] = CPL_BINARY_1;
6013  mdata[have_it[first] + nx*j + 2] = CPL_BINARY_1;
6014  }
6015  }
6016  }
6017  }
6018  }
6019  else {
6020  if (in) {
6021  in = 0;
6022  first = i - 1;
6023  }
6024  }
6025  }
6026 
6027  cpl_free(have_it);
6028 
6029  }
6030 
6031 /*
6032  for (i = 0; i < ny; i++) {
6033  if (nlines[i] == 0) {
6034  for (k = 0; k <= order; k++) {
6035  cpl_table_set_invalid(idscoeff, clab[k], i);
6036  }
6037  }
6038  }
6039 */
6040 
6041  return resampled;
6042 }
6043 
6044 
6066 /*
6067 cpl_table *mos_locate_spectra_bis(cpl_mask *mask)
6068 {
6069  const char *func = "mos_locate_spectra_bis";
6070 
6071  cpl_apertures *slits;
6072  cpl_image *labimage;
6073  cpl_image *refimage;
6074  cpl_binary *mdata;
6075  cpl_table *slitpos;
6076  cpl_propertylist *sort_col;
6077  int nslits;
6078  int *have_it;
6079  int in, first, last;
6080  int i, j;
6081 
6082 
6083  if (mask == NULL) {
6084  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
6085  return NULL;
6086  }
6087 
6088  nx = cpl_mask_get_size_x(mask);
6089  ny = cpl_mask_get_size_y(mask);
6090 
6091  mdata = cpl_mask_get_data(refmask);
6092  have_it = cpl_calloc(ny, sizeof(int));
6093 
6094  for (i = 0; i < ny; i++, mdata += nx) {
6095  for (j = 0; j < nx; j++) {
6096  if (mdata[j] == CPL_BINARY_1) {
6097  have_it[i] = j + 1;
6098  break;
6099  }
6100  }
6101  }
6102 
6103  mdata = cpl_mask_get_data(refmask);
6104  in = 0;
6105  first = last = 0;
6106  nslits = 0;
6107 
6108  for (i = 0; i < ny; i++) {
6109  if (have_it[i]) {
6110  if (in) {
6111  if (i) {
6112  if (abs(have_it[i] - have_it[i-1]) > 3) {
6113  nslits++;
6114  }
6115  }
6116  }
6117  else {
6118  in = 1;
6119  nslits++;
6120  }
6121  }
6122  else {
6123  if (in) {
6124  in = 0;
6125  }
6126  }
6127  }
6128 }
6129 */
6130 
6131 
6153 cpl_table *mos_locate_spectra(cpl_mask *mask)
6154 {
6155  const char *func = "mos_locate_spectra";
6156 
6157  cpl_apertures *slits;
6158  cpl_image *labimage;
6159  cpl_image *refimage;
6160  cpl_table *slitpos;
6161  cpl_propertylist *sort_col;
6162  cpl_size nslits;
6163  int i;
6164 
6165 
6166  if (mask == NULL) {
6167  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
6168  return NULL;
6169  }
6170 
6171  labimage = cpl_image_labelise_mask_create(mask, &nslits);
6172 
6173  if (nslits < 1) {
6174  cpl_image_delete(labimage);
6175  cpl_error_set(func, CPL_ERROR_DATA_NOT_FOUND);
6176  return NULL;
6177  }
6178 
6179  refimage = cpl_image_new_from_mask(mask);
6180 
6181  slits = cpl_apertures_new_from_image(refimage, labimage);
6182 
6183  cpl_image_delete(labimage);
6184  cpl_image_delete(refimage);
6185 
6186  nslits = cpl_apertures_get_size(slits); /* Overwriting nslits - safer! */
6187  if (nslits < 1) {
6188  cpl_apertures_delete(slits);
6189  cpl_error_set(func, CPL_ERROR_DATA_NOT_FOUND);
6190  return NULL;
6191  }
6192 
6193  slitpos = cpl_table_new(nslits);
6194  cpl_table_new_column(slitpos, "xtop", CPL_TYPE_DOUBLE);
6195  cpl_table_new_column(slitpos, "ytop", CPL_TYPE_DOUBLE);
6196  cpl_table_new_column(slitpos, "xbottom", CPL_TYPE_DOUBLE);
6197  cpl_table_new_column(slitpos, "ybottom", CPL_TYPE_DOUBLE);
6198  cpl_table_set_column_unit(slitpos, "xtop", "pixel");
6199  cpl_table_set_column_unit(slitpos, "ytop", "pixel");
6200  cpl_table_set_column_unit(slitpos, "xbottom", "pixel");
6201  cpl_table_set_column_unit(slitpos, "ybottom", "pixel");
6202 
6203  for (i = 0; i < nslits; i++) {
6204  cpl_table_set_double(slitpos, "xtop", i,
6205  cpl_apertures_get_top_x(slits, i+1) - 1);
6206  cpl_table_set_double(slitpos, "ytop", i,
6207  cpl_apertures_get_top(slits, i+1));
6208  cpl_table_set_double(slitpos, "xbottom", i,
6209  cpl_apertures_get_bottom_x(slits, i+1) - 1);
6210  cpl_table_set_double(slitpos, "ybottom", i,
6211  cpl_apertures_get_bottom(slits, i+1));
6212  }
6213 
6214  cpl_apertures_delete(slits);
6215 
6216  sort_col = cpl_propertylist_new();
6217  cpl_propertylist_append_bool(sort_col, "ytop", 1);
6218  cpl_table_sort(slitpos, sort_col);
6219  cpl_propertylist_delete(sort_col);
6220 
6221  return slitpos;
6222 
6223 }
6224 
6225 
6241 cpl_error_code mos_validate_slits(cpl_table *slits)
6242 {
6243  const char *func = "mos_validate_slits";
6244 
6245 
6246  if (slits == NULL)
6247  return cpl_error_set(func, CPL_ERROR_NULL_INPUT);
6248 
6249  if (1 != cpl_table_has_column(slits, "xtop"))
6250  return cpl_error_set(func, CPL_ERROR_DATA_NOT_FOUND);
6251 
6252  if (1 != cpl_table_has_column(slits, "ytop"))
6253  return cpl_error_set(func, CPL_ERROR_DATA_NOT_FOUND);
6254 
6255  if (1 != cpl_table_has_column(slits, "xbottom"))
6256  return cpl_error_set(func, CPL_ERROR_DATA_NOT_FOUND);
6257 
6258  if (1 != cpl_table_has_column(slits, "ybottom"))
6259  return cpl_error_set(func, CPL_ERROR_DATA_NOT_FOUND);
6260 
6261  if (CPL_TYPE_DOUBLE != cpl_table_get_column_type(slits, "xtop"))
6262  return cpl_error_set(func, CPL_ERROR_INVALID_TYPE);
6263 
6264  if (CPL_TYPE_DOUBLE != cpl_table_get_column_type(slits, "ytop"))
6265  return cpl_error_set(func, CPL_ERROR_INVALID_TYPE);
6266 
6267  if (CPL_TYPE_DOUBLE != cpl_table_get_column_type(slits, "xbottom"))
6268  return cpl_error_set(func, CPL_ERROR_INVALID_TYPE);
6269 
6270  if (CPL_TYPE_DOUBLE != cpl_table_get_column_type(slits, "ybottom"))
6271  return cpl_error_set(func, CPL_ERROR_INVALID_TYPE);
6272 
6273  return CPL_ERROR_NONE;
6274 }
6275 
6276 
6305 cpl_error_code mos_rotate_slits(cpl_table *slits, int rotation, int nx, int ny)
6306 {
6307  const char *func = "mos_rotate_slits";
6308 
6309  cpl_error_code error;
6310  char aux_name[] = "_0";
6311  int i;
6312 
6313 
6314  rotation %= 4;
6315  if (rotation < 0)
6316  rotation += 4;
6317 
6318  if (rotation == 0)
6319  return CPL_ERROR_NONE;
6320 
6321  error = mos_validate_slits(slits);
6322  if (error)
6323  return cpl_error_set(func, error);
6324 
6325  if (rotation == 1 || rotation == 3) {
6326 
6327  /*
6328  * Swap x and y column names
6329  */
6330 
6331  for (i = 0; i < 77; i++)
6332  if (1 == cpl_table_has_column(slits, aux_name))
6333  aux_name[1]++;
6334  if (1 == cpl_table_has_column(slits, aux_name))
6335  return cpl_error_set(func, CPL_ERROR_CONTINUE);
6336  cpl_table_name_column(slits, "xtop", aux_name);
6337  cpl_table_name_column(slits, "ytop", "xtop");
6338  cpl_table_name_column(slits, aux_name, "ytop");
6339  cpl_table_name_column(slits, "xbottom", aux_name);
6340  cpl_table_name_column(slits, "ybottom", "xbottom");
6341  cpl_table_name_column(slits, aux_name, "ybottom");
6342  }
6343 
6344  if (rotation == 1 || rotation == 2) {
6345  cpl_table_multiply_scalar(slits, "xtop", -1.0);
6346  cpl_table_multiply_scalar(slits, "xbottom", -1.0);
6347  cpl_table_add_scalar(slits, "xtop", nx);
6348  cpl_table_add_scalar(slits, "xbottom", nx);
6349  }
6350 
6351  if (rotation == 3 || rotation == 2) {
6352  cpl_table_multiply_scalar(slits, "ytop", -1.0);
6353  cpl_table_multiply_scalar(slits, "ybottom", -1.0);
6354  cpl_table_add_scalar(slits, "ytop", ny);
6355  cpl_table_add_scalar(slits, "ybottom", ny);
6356  }
6357 
6358  return CPL_ERROR_NONE;
6359 }
6360 
6361 
6419 cpl_table *mos_identify_slits(cpl_table *slits, cpl_table *maskslits,
6420  cpl_table *global)
6421 {
6422  cpl_array *top_ident = NULL;;
6423  cpl_array *bot_ident = NULL;;
6424  cpl_matrix *mdata;
6425  cpl_matrix *mpattern;
6426  cpl_matrix *top_data;
6427  cpl_matrix *top_pattern;
6428  cpl_matrix *top_mdata;
6429  cpl_matrix *top_mpattern;
6430  cpl_matrix *bot_data;
6431  cpl_matrix *bot_pattern;
6432  cpl_matrix *bot_mdata;
6433  cpl_matrix *bot_mpattern;
6434  cpl_propertylist *sort_col;
6435  double *xtop;
6436  double *ytop;
6437  double *xmtop;
6438  double *ymtop;
6439  double *xbot;
6440  double *ybot;
6441  double *xmbot;
6442  double *ymbot;
6443  double top_scale, bot_scale;
6444  double angle, top_angle, bot_angle;
6445  double xmse, ymse;
6446  double xrms, top_xrms, bot_xrms;
6447  double yrms, top_yrms, bot_yrms;
6448  int nslits;
6449  int nmaskslits, use_pattern;
6450  int found_slits, found_slits_top, found_slits_bot;
6451  int i;
6452  cpl_table *positions;
6453  cpl_error_code error;
6454 
6455  cpl_vector *point;
6456  double *dpoint;
6457  cpl_vector *xpos;
6458  cpl_vector *ypos;
6459  cpl_vector *xmpos;
6460  cpl_vector *ympos;
6461  cpl_bivector *mpos;
6462  cpl_polynomial *xpoly = NULL;
6463  cpl_polynomial *ypoly = NULL;
6464  cpl_polynomial *top_xpoly = NULL;
6465  cpl_polynomial *top_ypoly = NULL;
6466  cpl_polynomial *bot_xpoly = NULL;
6467  cpl_polynomial *bot_ypoly = NULL;
6468 
6469  char *msg_multiplex = " ";
6470 
6471 
6472  error = mos_validate_slits(slits);
6473  if (error) {
6474  cpl_msg_error(cpl_func, "CCD slits table validation: %s",
6475  cpl_error_get_message());
6476  cpl_error_set(cpl_func, error);
6477  return NULL;
6478  }
6479 
6480  error = mos_validate_slits(maskslits);
6481  if (error) {
6482  cpl_msg_error(cpl_func, "Mask slits table validation: %s",
6483  cpl_error_get_message());
6484  cpl_error_set(cpl_func, error);
6485  return NULL;
6486  }
6487 
6488  if (1 != cpl_table_has_column(maskslits, "slit_id")) {
6489  cpl_msg_error(cpl_func, "Missing slits identifiers");
6490  cpl_error_set(cpl_func, CPL_ERROR_DATA_NOT_FOUND);
6491  return NULL;
6492  }
6493 
6494  if (CPL_TYPE_INT != cpl_table_get_column_type(maskslits, "slit_id")) {
6495  cpl_msg_error(cpl_func, "Wrong type used for slits identifiers");
6496  cpl_error_set(cpl_func, CPL_ERROR_INVALID_TYPE);
6497  return NULL;
6498  }
6499 
6500  nslits = cpl_table_get_nrow(slits);
6501  nmaskslits = cpl_table_get_nrow(maskslits);
6502 
6503  if (nslits == 0 || nmaskslits == 0) {
6504  cpl_msg_error(cpl_func, "Empty slits table");
6505  cpl_error_set(cpl_func, CPL_ERROR_ILLEGAL_INPUT);
6506  return NULL;
6507  }
6508 
6509  if (nslits > 100 && mos_multiplex < 0) {
6510  cpl_msg_info(cpl_func, "Many slits: using 'fast' pattern matching...");
6511  positions = mos_identify_slits_fast(slits, maskslits, global);
6512  if (positions == NULL)
6513  cpl_error_set_where(cpl_func);
6514  return positions;
6515  }
6516 
6517  /*
6518  * Guarantee that both input tables are sorted in the same way
6519  */
6520 
6521  sort_col = cpl_propertylist_new();
6522  cpl_propertylist_append_bool(sort_col, "ytop", 1);
6523  cpl_table_sort(slits, sort_col);
6524  cpl_table_sort(maskslits, sort_col);
6525  cpl_propertylist_delete(sort_col);
6526 
6527  /*
6528  * First we handle all the special cases (too few slits...)
6529  */
6530 
6531  if (nslits < 3 && nmaskslits > nslits) {
6532 
6533  /*
6534  * If there are just 1 or 2 slits on the CCD, and more on the
6535  * mask, the ambiguity cannot be solved, and an error is returned.
6536  * This is a case that must be solved with a first-guess relation
6537  * between mask and CCD.
6538  */
6539 
6540  if (nslits > 1)
6541  cpl_msg_warning(cpl_func, "Cannot match the %d found CCD slits "
6542  "with the %d mask slits: process will continue "
6543  "using the detected CCD slits positions", nslits,
6544  nmaskslits);
6545  else
6546  cpl_msg_warning(cpl_func, "Cannot match the found CCD slit with "
6547  "the %d mask slits: process will continue using "
6548  "the detected CCD slit position", nmaskslits);
6549  return NULL;
6550  }
6551 
6552  if (nmaskslits < 3 && nslits > nmaskslits) {
6553 
6554  /*
6555  * If there are less than 3 slits on the mask the ambiguity cannot
6556  * be solved, and an error is returned. This is a case that must
6557  * be solved with a first-guess relation between mask and CCD.
6558  */
6559 
6560  cpl_msg_warning(cpl_func, "Cannot match the %d found CCD slits with "
6561  "the %d mask slits: process will continue using "
6562  "the detected CCD slits positions", nslits,
6563  nmaskslits);
6564  return NULL;
6565  }
6566 
6567  /*
6568  * Pattern matching related operations begin here. Two pattern
6569  * matching will be run, one based on the "top" and another one
6570  * based on the "bottom" slit coordinates. The one with the
6571  * smallest rms in the Y coordinate will be chosen.
6572  */
6573 
6574  xtop = cpl_table_get_data_double(slits, "xtop");
6575  ytop = cpl_table_get_data_double(slits, "ytop");
6576  xmtop = cpl_table_get_data_double(maskslits, "xtop");
6577  ymtop = cpl_table_get_data_double(maskslits, "ytop");
6578 
6579  xbot = cpl_table_get_data_double(slits, "xbottom");
6580  ybot = cpl_table_get_data_double(slits, "ybottom");
6581  xmbot = cpl_table_get_data_double(maskslits, "xbottom");
6582  ymbot = cpl_table_get_data_double(maskslits, "ybottom");
6583 
6584  top_data = cpl_matrix_new(2, nslits);
6585  top_pattern = cpl_matrix_new(2, nmaskslits);
6586  bot_data = cpl_matrix_new(2, nslits);
6587  bot_pattern = cpl_matrix_new(2, nmaskslits);
6588 
6589  for (i = 0; i < nslits; i++)
6590  cpl_matrix_set(top_data, 0, i, xtop[i]);
6591 
6592  for (i = 0; i < nslits; i++)
6593  cpl_matrix_set(top_data, 1, i, ytop[i]);
6594 
6595  for (i = 0; i < nmaskslits; i++)
6596  cpl_matrix_set(top_pattern, 0, i, xmtop[i]);
6597 
6598  for (i = 0; i < nmaskslits; i++)
6599  cpl_matrix_set(top_pattern, 1, i, ymtop[i]);
6600 
6601  for (i = 0; i < nslits; i++)
6602  cpl_matrix_set(bot_data, 0, i, xbot[i]);
6603 
6604  for (i = 0; i < nslits; i++)
6605  cpl_matrix_set(bot_data, 1, i, ybot[i]);
6606 
6607  for (i = 0; i < nmaskslits; i++)
6608  cpl_matrix_set(bot_pattern, 0, i, xmbot[i]);
6609 
6610  for (i = 0; i < nmaskslits; i++)
6611  cpl_matrix_set(bot_pattern, 1, i, ymbot[i]);
6612 
6613  if (nmaskslits > nslits)
6614  use_pattern = nslits;
6615  else
6616  use_pattern = nmaskslits;
6617 
6618  top_ident = cpl_ppm_match_points(top_data, nslits, 1.0, top_pattern,
6619  use_pattern, 0.0, 0.1, 5, &top_mdata,
6620  &top_mpattern, &top_scale, &top_angle);
6621 
6622  bot_ident = cpl_ppm_match_points(bot_data, nslits, 1.0, bot_pattern,
6623  use_pattern, 0.0, 0.1, 5, &bot_mdata,
6624  &bot_mpattern, &bot_scale, &bot_angle);
6625  cpl_matrix_delete(top_data);
6626  cpl_matrix_delete(top_pattern);
6627  cpl_matrix_delete(bot_data);
6628  cpl_matrix_delete(bot_pattern);
6629 
6630  if (top_ident == NULL && bot_ident == NULL) {
6631  cpl_msg_warning(cpl_func, "Pattern matching failure: cannot match "
6632  "the %d found CCD slits with the %d mask slits: "
6633  "process will continue using the detected CCD "
6634  "slits positions", nslits, nmaskslits);
6635  return NULL;
6636  }
6637 
6638  found_slits_top = 0;
6639  found_slits_bot = 0;
6640  if (top_ident && bot_ident) {
6641  cpl_msg_info(cpl_func, "Median platescale: %f +/- %f pixel/mm",
6642  (top_scale + bot_scale) / 2, fabs(top_scale - bot_scale));
6643  cpl_msg_info(cpl_func, "Median rotation: %f +/- %f degrees",
6644  (top_angle + bot_angle) / 2, fabs(top_angle - bot_angle));
6645  if (fabs(top_angle) < fabs(bot_angle))
6646  angle = fabs(top_angle);
6647  else
6648  angle = fabs(bot_angle);
6649  found_slits_top = cpl_matrix_get_ncol(top_mdata);
6650  found_slits_bot = cpl_matrix_get_ncol(bot_mdata);
6651  }
6652  else if (top_ident) {
6653  cpl_msg_info(cpl_func, "Median platescale: %f pixel/mm", top_scale);
6654  cpl_msg_info(cpl_func, "Median rotation: %f degrees", top_angle);
6655  angle = fabs(top_angle);
6656  found_slits_top = cpl_matrix_get_ncol(top_mdata);
6657  }
6658  else {
6659  cpl_msg_info(cpl_func, "Median platescale: %f pixel/mm", bot_scale);
6660  cpl_msg_info(cpl_func, "Median rotation: %f degrees", bot_angle);
6661  angle = fabs(bot_angle);
6662  found_slits_bot = cpl_matrix_get_ncol(bot_mdata);
6663  }
6664 
6665  cpl_array_delete(top_ident);
6666  cpl_array_delete(bot_ident);
6667 
6668  if (angle > 4.0) {
6669  cpl_msg_warning(cpl_func, "Uncertain pattern matching: the rotation "
6670  "angle is expected to be around zero. This match is "
6671  "rejected: the process will continue using the %d "
6672  "detected CCD slits positions", nslits);
6673  return NULL;
6674  }
6675 
6676  found_slits = found_slits_top;
6677  if (found_slits < found_slits_bot)
6678  found_slits = found_slits_bot; /* Max value */
6679 
6680  if (found_slits < 4) {
6681  cpl_msg_warning(cpl_func,
6682  "Too few safely identified slits: %d out of %d "
6683  "candidates (%d expected). Process will continue "
6684  "using the detected CCD slits positions", found_slits,
6685  nslits, nmaskslits);
6686  return NULL;
6687  }
6688 
6689  cpl_msg_info(cpl_func, "Preliminary identified slits: %d out of %d "
6690  "candidates\n(%d expected)", found_slits, nslits,
6691  nmaskslits);
6692 
6693  if (found_slits_top < 4)
6694  found_slits_top = 0;
6695 
6696  if (found_slits_bot < 4)
6697  found_slits_bot = 0;
6698 
6699  /*
6700  * Now for each set select the points of the identified slits, and fit
6701  * two bivariate polynomials to determine a first approximate relation
6702  * between positions on the mask and positions on the CCD.
6703  */
6704 
6705  for (i = 0; i < 2; i++) {
6706  cpl_size mindeg2d[] = {0, 0};
6707  cpl_size maxdeg2d[2];
6708  cpl_vector * fitresidual;
6709  if (i) {
6710  found_slits = found_slits_top;
6711  mdata = top_mdata;
6712  mpattern = top_mpattern;
6713  }
6714  else {
6715  found_slits = found_slits_bot;
6716  mdata = bot_mdata;
6717  mpattern = bot_mpattern;
6718  }
6719 
6720  if (found_slits == 0)
6721  continue;
6722  else if (found_slits < 10)
6723  maxdeg2d[0] = maxdeg2d[1] = 1;
6724  else
6725  maxdeg2d[0] = maxdeg2d[1] = 2;
6726 
6727  xpos = cpl_vector_wrap(found_slits,
6728  cpl_matrix_get_data(mdata) );
6729  ypos = cpl_vector_wrap(found_slits,
6730  cpl_matrix_get_data(mdata) + found_slits);
6731  xmpos = cpl_vector_wrap(found_slits,
6732  cpl_matrix_get_data(mpattern) );
6733  ympos = cpl_vector_wrap(found_slits,
6734  cpl_matrix_get_data(mpattern) + found_slits);
6735  mpos = cpl_bivector_wrap_vectors(xmpos, ympos);
6736  fitresidual = cpl_vector_new(cpl_vector_get_size(xpos));
6737  xpoly = cpl_polynomial_new(2);
6738  cpl_polynomial_fit(xpoly, mpattern, NULL, xpos, NULL, CPL_FALSE, mindeg2d, maxdeg2d);
6739  cpl_vector_fill_polynomial_fit_residual(fitresidual, xpos, NULL, xpoly, mpattern, NULL);
6740  xmse = cpl_vector_product(fitresidual, fitresidual)
6741  / cpl_vector_get_size(fitresidual);
6742  ypoly = cpl_polynomial_new(2);
6743  cpl_polynomial_fit(ypoly, mpattern, NULL, ypos, NULL, CPL_FALSE, mindeg2d, maxdeg2d);
6744  cpl_vector_fill_polynomial_fit_residual(fitresidual, ypos, NULL, ypoly, mpattern, NULL);
6745  ymse = cpl_vector_product(fitresidual, fitresidual)
6746  / cpl_vector_get_size(fitresidual);
6747 
6748  cpl_bivector_unwrap_vectors(mpos);
6749  cpl_vector_unwrap(xpos);
6750  cpl_vector_unwrap(ypos);
6751  cpl_vector_unwrap(xmpos);
6752  cpl_vector_unwrap(ympos);
6753  cpl_matrix_delete(mdata);
6754  cpl_matrix_delete(mpattern);
6755  cpl_vector_delete(fitresidual);
6756 
6757  if (i) {
6758  top_xpoly = xpoly;
6759  top_ypoly = ypoly;
6760  top_xrms = sqrt(xmse*2*maxdeg2d[0]/(found_slits - 1));
6761  top_yrms = sqrt(ymse*2*maxdeg2d[0]/(found_slits - 1));
6762  }
6763  else {
6764  bot_xpoly = xpoly;
6765  bot_ypoly = ypoly;
6766  bot_xrms = sqrt(xmse*2*maxdeg2d[0]/(found_slits - 1));
6767  bot_yrms = sqrt(ymse*2*maxdeg2d[0]/(found_slits - 1));
6768  }
6769  }
6770 
6771  if (top_xpoly && bot_xpoly) {
6772  if (top_xrms < bot_xrms) { /* top X solution wins... */
6773  xrms = top_xrms;
6774  xpoly = top_xpoly;
6775  cpl_polynomial_delete(bot_xpoly);
6776  }
6777  else { /* bottom X solution wins... */
6778  xrms = bot_xrms;
6779  xpoly = bot_xpoly;
6780  cpl_polynomial_delete(top_xpoly);
6781  }
6782  }
6783  else if (top_xpoly) {
6784  xrms = top_xrms;
6785  xpoly = top_xpoly;
6786  }
6787  else {
6788  xrms = bot_xrms;
6789  xpoly = bot_xpoly;
6790  }
6791 
6792  if (top_ypoly && bot_ypoly) {
6793  if (top_yrms < bot_yrms) { /* top Y solution wins... */
6794  yrms = top_yrms;
6795  ypoly = top_ypoly;
6796  cpl_polynomial_delete(bot_ypoly);
6797  }
6798  else { /* bottom Y solution wins... */
6799  yrms = bot_yrms;
6800  ypoly = bot_ypoly;
6801  cpl_polynomial_delete(top_ypoly);
6802  }
6803  }
6804  else if (top_ypoly) {
6805  yrms = top_yrms;
6806  ypoly = top_ypoly;
6807  }
6808  else {
6809  yrms = bot_yrms;
6810  ypoly = bot_ypoly;
6811  }
6812 
6813  if (xpoly == NULL || ypoly == NULL) {
6814  cpl_msg_warning(cpl_func, "Fit failure: the accuracy of the "
6815  "identified slits positions cannot be improved.");
6816  cpl_polynomial_delete(xpoly);
6817  cpl_polynomial_delete(ypoly);
6818  cpl_error_reset();
6819  return NULL;
6820  }
6821 
6822  cpl_msg_info(cpl_func,
6823  "Fit successful: X rms = %.3g, Y rms = %.3g (pixel)",
6824  xrms, yrms);
6825 
6826  if (global) {
6827  write_global_distortion(global, 0, xpoly);
6828  write_global_distortion(global, 7, ypoly);
6829  }
6830 
6831  /*
6832  * The fit was successful: use the polynomials to obtain a new
6833  * position table with the improved positions of the slits
6834  */
6835 
6836  positions = cpl_table_duplicate(maskslits);
6837  cpl_table_duplicate_column(positions, "xmtop", positions, "xtop");
6838  cpl_table_duplicate_column(positions, "ymtop", positions, "ytop");
6839  cpl_table_duplicate_column(positions, "xmbottom", positions, "xbottom");
6840  cpl_table_duplicate_column(positions, "ymbottom", positions, "ybottom");
6841 
6842  point = cpl_vector_new(2);
6843  dpoint = cpl_vector_get_data(point);
6844 
6845  for (i = 0; i < nmaskslits; i++) {
6846  double position_x;
6847  double position_y;
6848 
6849  dpoint[0] = cpl_table_get_double(positions, "xmtop", i, NULL);
6850  dpoint[1] = cpl_table_get_double(positions, "ymtop", i, NULL);
6851  position_x = cpl_polynomial_eval(xpoly, point);
6852 // if (mos_multiplex >= 0) {
6853 // if (mos_multiplex != ((int)floor(position)) / mos_region_size) {
6854 // cpl_table_unselect_row(positions, i);
6855 // continue;
6856 // }
6857 // }
6858  cpl_table_set_double(positions, "xtop", i, position_x);
6859  position_y = cpl_polynomial_eval(ypoly, point);
6860  cpl_table_set_double(positions, "ytop", i, position_y);
6861  dpoint[0] = cpl_table_get_double(positions, "xmbottom", i, NULL);
6862  dpoint[1] = cpl_table_get_double(positions, "ymbottom", i, NULL);
6863  position_x = cpl_polynomial_eval(xpoly, point);
6864  cpl_table_set_double(positions, "xbottom", i, position_x);
6865  position_y = cpl_polynomial_eval(ypoly, point);
6866  cpl_table_set_double(positions, "ybottom", i, position_y);
6867  }
6868 
6869 // if (mos_multiplex >= 0) {
6870 // cpl_table_not_selected(positions);
6871 // cpl_table_erase_selected(positions);
6872 // nmaskslits = cpl_table_get_nrow(positions);
6873 // }
6874 
6875  cpl_vector_delete(point);
6876  cpl_polynomial_delete(xpoly);
6877  cpl_polynomial_delete(ypoly);
6878 
6879  cpl_table_erase_column(positions, "xmtop");
6880  cpl_table_erase_column(positions, "ymtop");
6881  cpl_table_erase_column(positions, "xmbottom");
6882  cpl_table_erase_column(positions, "ymbottom");
6883 
6884  if (mos_multiplex >= 0) {
6885  msg_multiplex =
6886  cpl_sprintf("in the CCD section between %d and %d pixel",
6887  mos_multiplex * mos_region_size,
6888  (mos_multiplex + 1) * mos_region_size);
6889  }
6890 
6891  if (nmaskslits > nslits)
6892  cpl_msg_info(cpl_func,
6893  "Finally identified slits: %d out of %d expected %s\n"
6894  "(%d recovered)", nmaskslits, nmaskslits, msg_multiplex,
6895  nmaskslits - nslits);
6896  else if (nmaskslits < nslits)
6897  cpl_msg_info(cpl_func,
6898  "Finally identified slits: %d out of %d expected %s\n"
6899  "(%d rejected)", nmaskslits, nmaskslits, msg_multiplex,
6900  nslits - nmaskslits);
6901  else
6902  cpl_msg_info(cpl_func,
6903  "Finally identified slits: %d out of %d expected %s",
6904  nmaskslits, nmaskslits, msg_multiplex);
6905 
6906  if (mos_multiplex >= 0) {
6907  cpl_free(msg_multiplex);
6908  }
6909 
6910  return positions;
6911 
6912 }
6913 
6914 
6915 cpl_table *mos_identify_slits_fast(cpl_table *slits, cpl_table *maskslits,
6916  cpl_table *global)
6917 {
6918  const char *func = "mos_identify_slits_fast";
6919 
6920  cpl_propertylist *sort_col;
6921  cpl_table *positions;
6922  cpl_vector *scales;
6923  cpl_vector *angles;
6924  cpl_vector *point;
6925  cpl_vector *xpos;
6926  cpl_vector *ypos;
6927  cpl_vector *xmpos;
6928  cpl_vector *ympos;
6929  cpl_bivector *mpos;
6930  cpl_polynomial *xpoly = NULL;
6931  cpl_polynomial *ypoly = NULL;
6932  cpl_error_code error;
6933  int nslits;
6934  int nmaskslits;
6935  int found_slits;
6936  int i, j, k;
6937 
6938  double dist1, dist2, dist3, dist, mindist;
6939  double scale, minscale, maxscale;
6940  double angle, minangle, maxangle;
6941  double *dscale;
6942  double *dangle;
6943  double *dpoint;
6944  double *xtop;
6945  double *ytop;
6946  double *xbottom;
6947  double *ybottom;
6948  double *xcenter;
6949  double *ycenter;
6950  double *xpseudo;
6951  double *ypseudo;
6952  int *slit_id;
6953  double *xmtop;
6954  double *ymtop;
6955  double *xmbottom;
6956  double *ymbottom;
6957  double *xmcenter;
6958  double *ymcenter;
6959  double *xmpseudo;
6960  double *ympseudo;
6961  double xmse, ymse;
6962  int *mslit_id;
6963  int *good;
6964  int minpos;
6965  int degree;
6966 
6967  double sradius = 0.01; /* Candidate input argument... */
6968  int in_sradius;
6969 
6970  double pi = 3.14159265358979323846;
6971 
6972 
6973  error = mos_validate_slits(slits);
6974  if (error) {
6975  cpl_msg_error(func, "CCD slits table validation: %s",
6976  cpl_error_get_message());
6977  cpl_error_set(func, error);
6978  return NULL;
6979  }
6980 
6981  error = mos_validate_slits(maskslits);
6982  if (error) {
6983  cpl_msg_error(func, "Mask slits table validation: %s",
6984  cpl_error_get_message());
6985  cpl_error_set(func, error);
6986  return NULL;
6987  }
6988 
6989  if (1 != cpl_table_has_column(maskslits, "slit_id")) {
6990  cpl_msg_error(func, "Missing slits identifiers");
6991  cpl_error_set(func, CPL_ERROR_DATA_NOT_FOUND);
6992  return NULL;
6993  }
6994 
6995  if (CPL_TYPE_INT != cpl_table_get_column_type(maskslits, "slit_id")) {
6996  cpl_msg_error(func, "Wrong type used for slits identifiers");
6997  cpl_error_set(func, CPL_ERROR_INVALID_TYPE);
6998  return NULL;
6999  }
7000 
7001  nslits = cpl_table_get_nrow(slits);
7002  nmaskslits = cpl_table_get_nrow(maskslits);
7003 
7004  if (nslits == 0 || nmaskslits == 0) {
7005  cpl_msg_error(func, "Empty slits table");
7006  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
7007  return NULL;
7008  }
7009 
7010 
7011  /*
7012  * Compute middle point coordinates for each slit listed in both
7013  * input tables.
7014  */
7015 
7016  if (cpl_table_has_column(slits, "xcenter"))
7017  cpl_table_erase_column(slits, "xcenter");
7018 
7019  if (cpl_table_has_column(slits, "ycenter"))
7020  cpl_table_erase_column(slits, "ycenter");
7021 
7022  if (cpl_table_has_column(maskslits, "xcenter"))
7023  cpl_table_erase_column(maskslits, "xcenter");
7024 
7025  if (cpl_table_has_column(maskslits, "ycenter"))
7026  cpl_table_erase_column(maskslits, "ycenter");
7027 
7028  cpl_table_duplicate_column(slits, "xcenter", slits, "xtop");
7029  cpl_table_add_columns(slits, "xcenter", "xbottom");
7030  cpl_table_divide_scalar(slits, "xcenter", 2.0);
7031  cpl_table_duplicate_column(slits, "ycenter", slits, "ytop");
7032  cpl_table_add_columns(slits, "ycenter", "ybottom");
7033  cpl_table_divide_scalar(slits, "ycenter", 2.0);
7034 
7035  cpl_table_duplicate_column(maskslits, "xcenter", maskslits, "xtop");
7036  cpl_table_add_columns(maskslits, "xcenter", "xbottom");
7037  cpl_table_divide_scalar(maskslits, "xcenter", 2.0);
7038  cpl_table_duplicate_column(maskslits, "ycenter", maskslits, "ytop");
7039  cpl_table_add_columns(maskslits, "ycenter", "ybottom");
7040  cpl_table_divide_scalar(maskslits, "ycenter", 2.0);
7041 
7042 
7043  /*
7044  * Guarantee that both input tables are sorted in the same way
7045  */
7046 
7047  sort_col = cpl_propertylist_new();
7048  cpl_propertylist_append_bool(sort_col, "ycenter", 1);
7049  cpl_table_sort(slits, sort_col);
7050  cpl_table_sort(maskslits, sort_col);
7051  cpl_propertylist_delete(sort_col);
7052 
7053 
7054  /*
7055  * First we handle all the special cases (too few slits...)
7056  */
7057 
7058  if (nslits < 3 && nmaskslits > nslits) {
7059 
7060  /*
7061  * If there are just 1 or 2 slits on the CCD, and more on the
7062  * mask, the ambiguity cannot be solved, and an error is returned.
7063  * This is a case that must be solved with a first-guess relation
7064  * between mask and CCD.
7065  */
7066 
7067  if (nslits > 1)
7068  cpl_msg_warning(func, "Cannot match the found CCD slit with the "
7069  "%d mask slits: process will continue using the "
7070  "detected CCD slit position", nmaskslits);
7071  else
7072  cpl_msg_warning(func, "Cannot match the %d found CCD slits with "
7073  "the %d mask slits: process will continue using "
7074  "the detected CCD slits positions", nslits,
7075  nmaskslits);
7076  return NULL;
7077  }
7078 
7079  if (nslits <= 3 && nslits == nmaskslits) {
7080 
7081  cpl_msg_warning(func, "Too few slits (%d) on mask and CCD", nslits);
7082  cpl_msg_warning(func, "Their detected positions are left unchanged");
7083 
7084  /*
7085  * If there are just up to 3 slits, both on the mask and on the CCD,
7086  * we can reasonably hope that those slits were found, and accept
7087  * that their positions on the CCD cannot be improved. We prepare
7088  * therefore an output position table containing the slits with
7089  * their original positions. We can however give an estimate of
7090  * the platescale if there is more than one slit.
7091  */
7092 
7093  positions = cpl_table_duplicate(slits);
7094  cpl_table_erase_column(slits, "xcenter");
7095  cpl_table_erase_column(slits, "ycenter");
7096  cpl_table_duplicate_column(positions, "xmtop", maskslits, "xtop");
7097  cpl_table_duplicate_column(positions, "ymtop", maskslits, "ytop");
7098  cpl_table_duplicate_column(positions, "xmbottom", maskslits, "xbottom");
7099  cpl_table_duplicate_column(positions, "ymbottom", maskslits, "ybottom");
7100  cpl_table_duplicate_column(positions, "xmcenter", maskslits, "xcenter");
7101  cpl_table_duplicate_column(positions, "ymcenter", maskslits, "ycenter");
7102  cpl_table_duplicate_column(positions, "slit_id", maskslits, "slit_id");
7103  cpl_table_erase_column(maskslits, "xcenter");
7104  cpl_table_erase_column(maskslits, "ycenter");
7105 
7106  if (nslits > 1) {
7107  xcenter = cpl_table_get_data_double(positions, "xcenter");
7108  ycenter = cpl_table_get_data_double(positions, "ycenter");
7109  xmcenter = cpl_table_get_data_double(positions, "xmcenter");
7110  ymcenter = cpl_table_get_data_double(positions, "ymcenter");
7111 
7112  dist1 = (xcenter[0] - xcenter[1])*(xcenter[0] - xcenter[1])
7113  + (ycenter[0] - ycenter[1])*(ycenter[0] - ycenter[1]);
7114  dist2 = (xmcenter[0] - xmcenter[1])*(xmcenter[0] - xmcenter[1])
7115  + (ymcenter[0] - ymcenter[1])*(ymcenter[0] - ymcenter[1]);
7116  scale = sqrt(dist1/dist2);
7117 
7118  if (nslits == 3) {
7119  dist1 = (xcenter[1] - xcenter[2])*(xcenter[1] - xcenter[2])
7120  + (ycenter[1] - ycenter[2])*(ycenter[1] - ycenter[2]);
7121  dist2 = (xmcenter[1] - xmcenter[2])*(xmcenter[1] - xmcenter[2])
7122  + (ymcenter[1] - ymcenter[2])*(ymcenter[1] - ymcenter[2]);
7123  scale += sqrt(dist1/dist2);
7124  scale /= 2;
7125  }
7126 
7127  cpl_msg_info(func, "Platescale: %f pixel/mm", scale);
7128  }
7129 
7130  return positions;
7131  }
7132 
7133  if (nmaskslits < 3 && nslits > nmaskslits) {
7134 
7135  /*
7136  * If there are less than 3 slits on the mask the ambiguity cannot
7137  * be solved, and an error is returned. This is a case that must
7138  * be solved with a first-guess relation between mask and CCD.
7139  */
7140 
7141  cpl_msg_warning(func, "Cannot match the %d found CCD slits with "
7142  "the %d mask slits: process will continue using "
7143  "the detected CCD slits positions", nslits,
7144  nmaskslits);
7145  return NULL;
7146  }
7147 
7148 
7149  /*
7150  * At this point of the program all the region of the plane
7151  * (nslits, nmaskslits) where either or both mask and CCD display
7152  * less than 3 slits are handled in some way. It would be better
7153  * to add in this place a special handling for identifying slits
7154  * in case of a very reduced number of slits (say, below 6).
7155  * It is also clear that if there are many more slits on the
7156  * mask than on the CCD, or many more on the CCD than on the
7157  * mask, something went deeply wrong with the preliminary
7158  * wavelength calibration. Such cases should be handled with
7159  * a _complete_ pattern-recognition algorithm based on the
7160  * construction of all possible triangles. For the moment,
7161  * we go directly to the limited pattern-recognition applied
7162  * below, based on triangles build only for consecutive slits.
7163  * This is reasonably safe, since the preliminary wavelength
7164  * calibration performed by mos_identify_peaks() is generally
7165  * robust.
7166  */
7167 
7168 
7169  /*
7170  * Compute (X, Y) coordinates on pseudo-plane describing the
7171  * different position ratios of successive slits, in both
7172  * input tables.
7173  */
7174 
7175  if (cpl_table_has_column(slits, "xpseudo"))
7176  cpl_table_erase_column(slits, "xpseudo");
7177 
7178  if (cpl_table_has_column(slits, "ypseudo"))
7179  cpl_table_erase_column(slits, "ypseudo");
7180 
7181  if (cpl_table_has_column(maskslits, "xpseudo"))
7182  cpl_table_erase_column(maskslits, "xpseudo");
7183 
7184  if (cpl_table_has_column(maskslits, "ypseudo"))
7185  cpl_table_erase_column(maskslits, "ypseudo");
7186 
7187  cpl_table_duplicate_column(slits, "xpseudo", slits, "xcenter");
7188  cpl_table_duplicate_column(slits, "ypseudo", slits, "ycenter");
7189 
7190  xcenter = cpl_table_get_data_double(slits, "xcenter");
7191  ycenter = cpl_table_get_data_double(slits, "ycenter");
7192  xpseudo = cpl_table_get_data_double(slits, "xpseudo");
7193  ypseudo = cpl_table_get_data_double(slits, "ypseudo");
7194 
7195  for (i = 1; i < nslits - 1; i++) {
7196  dist1 = (xcenter[i-1] - xcenter[i]) * (xcenter[i-1] - xcenter[i])
7197  + (ycenter[i-1] - ycenter[i]) * (ycenter[i-1] - ycenter[i]);
7198  dist2 = (xcenter[i-1] - xcenter[i+1]) * (xcenter[i-1] - xcenter[i+1])
7199  + (ycenter[i-1] - ycenter[i+1]) * (ycenter[i-1] - ycenter[i+1]);
7200  dist3 = (xcenter[i] - xcenter[i+1]) * (xcenter[i] - xcenter[i+1])
7201  + (ycenter[i] - ycenter[i+1]) * (ycenter[i] - ycenter[i+1]);
7202  xpseudo[i] = sqrt(dist1/dist2);
7203  ypseudo[i] = sqrt(dist3/dist2);
7204  }
7205 
7206  cpl_table_set_invalid(slits, "xpseudo", 0);
7207  cpl_table_set_invalid(slits, "xpseudo", nslits-1);
7208  cpl_table_set_invalid(slits, "ypseudo", 0);
7209  cpl_table_set_invalid(slits, "ypseudo", nslits-1);
7210 
7211  cpl_table_duplicate_column(maskslits, "xpseudo", maskslits, "xcenter");
7212  cpl_table_duplicate_column(maskslits, "ypseudo", maskslits, "ycenter");
7213 
7214  xcenter = cpl_table_get_data_double(maskslits, "xcenter");
7215  ycenter = cpl_table_get_data_double(maskslits, "ycenter");
7216  xmpseudo = cpl_table_get_data_double(maskslits, "xpseudo");
7217  ympseudo = cpl_table_get_data_double(maskslits, "ypseudo");
7218 
7219  for (i = 1; i < nmaskslits - 1; i++) {
7220  dist1 = (xcenter[i-1] - xcenter[i])*(xcenter[i-1] - xcenter[i])
7221  + (ycenter[i-1] - ycenter[i])*(ycenter[i-1] - ycenter[i]);
7222  dist2 = (xcenter[i-1] - xcenter[i+1])*(xcenter[i-1] - xcenter[i+1])
7223  + (ycenter[i-1] - ycenter[i+1])*(ycenter[i-1] - ycenter[i+1]);
7224  dist3 = (xcenter[i] - xcenter[i+1])*(xcenter[i] - xcenter[i+1])
7225  + (ycenter[i] - ycenter[i+1])*(ycenter[i] - ycenter[i+1]);
7226  xmpseudo[i] = sqrt(dist1/dist2);
7227  ympseudo[i] = sqrt(dist3/dist2);
7228  }
7229 
7230  cpl_table_set_invalid(maskslits, "xpseudo", 0);
7231  cpl_table_set_invalid(maskslits, "xpseudo", nmaskslits-1);
7232  cpl_table_set_invalid(maskslits, "ypseudo", 0);
7233  cpl_table_set_invalid(maskslits, "ypseudo", nmaskslits-1);
7234 
7235 
7236  /*
7237  * For each (X, Y) on the pseudo-plane related to the mask positions,
7238  * find the closest (X, Y) on the pseudo-plane related to the CCD
7239  * positions. If the closest point is closer than a given search
7240  * radius, a triangle has been found, and 3 slits are identified.
7241  * However, if more than one point is found within the search
7242  * radius, we have an ambiguity and this is rejected.
7243  */
7244 
7245  if (cpl_table_has_column(slits, "slit_id"))
7246  cpl_table_erase_column(slits, "slit_id");
7247  cpl_table_new_column(slits, "slit_id", CPL_TYPE_INT);
7248  slit_id = cpl_table_get_data_int(maskslits, "slit_id");
7249 
7250  for (i = 1; i < nmaskslits - 1; i++) {
7251  in_sradius = 0;
7252  mindist = (xmpseudo[i] - xpseudo[1]) * (xmpseudo[i] - xpseudo[1])
7253  + (ympseudo[i] - ypseudo[1]) * (ympseudo[i] - ypseudo[1]);
7254  minpos = 1;
7255  if (mindist < sradius*sradius)
7256  in_sradius++;
7257  for (j = 2; j < nslits - 1; j++) {
7258  dist = (xmpseudo[i] - xpseudo[j]) * (xmpseudo[i] - xpseudo[j])
7259  + (ympseudo[i] - ypseudo[j]) * (ympseudo[i] - ypseudo[j]);
7260  if (dist < sradius*sradius)
7261  in_sradius++;
7262  if (in_sradius > 1) /* More than one triangle within radius */
7263  break;
7264  if (mindist > dist) {
7265  mindist = dist;
7266  minpos = j;
7267  }
7268  }
7269 
7270  mindist = sqrt(mindist);
7271 
7272  if (mindist < sradius && in_sradius == 1) {
7273  cpl_table_set_int(slits, "slit_id", minpos-1, slit_id[i-1]);
7274  cpl_table_set_int(slits, "slit_id", minpos, slit_id[i]);
7275  cpl_table_set_int(slits, "slit_id", minpos+1, slit_id[i+1]);
7276  }
7277  }
7278 
7279 
7280  /*
7281  * At this point, the slit_id column contains invalid elements
7282  * corresponding to unidentified slits.
7283  */
7284 
7285  found_slits = nslits - cpl_table_count_invalid(slits, "slit_id");
7286 
7287  if (found_slits < 3) {
7288  cpl_msg_warning(func, "Too few preliminarily identified slits: "
7289  "%d out of %d", found_slits, nslits);
7290  if (nslits == nmaskslits) {
7291  cpl_msg_warning(func, "(this is not an error, it could be caused "
7292  "by a mask with regularly located slits)");
7293  cpl_msg_warning(func, "The detected slits positions are left "
7294  "unchanged");
7295 
7296  /*
7297  * If there are less than 3 identified slits, this is probably
7298  * a mask with regularly spaced slits (leading to an ambiguous
7299  * pattern). Only in the case all expected slits appear to have
7300  * been found on the CCD we can proceed...
7301  */
7302 
7303  cpl_table_erase_column(slits, "slit_id");
7304  cpl_table_erase_column(slits, "xpseudo");
7305  cpl_table_erase_column(slits, "ypseudo");
7306  positions = cpl_table_duplicate(slits);
7307  cpl_table_erase_column(slits, "xcenter");
7308  cpl_table_erase_column(slits, "ycenter");
7309 
7310  cpl_table_erase_column(maskslits, "xpseudo");
7311  cpl_table_erase_column(maskslits, "ypseudo");
7312  cpl_table_duplicate_column(positions, "xmtop",
7313  maskslits, "xtop");
7314  cpl_table_duplicate_column(positions, "ymtop",
7315  maskslits, "ytop");
7316  cpl_table_duplicate_column(positions, "xmbottom",
7317  maskslits, "xbottom");
7318  cpl_table_duplicate_column(positions, "ymbottom",
7319  maskslits, "ybottom");
7320  cpl_table_duplicate_column(positions, "xmcenter",
7321  maskslits, "xcenter");
7322  cpl_table_duplicate_column(positions, "ymcenter",
7323  maskslits, "ycenter");
7324  cpl_table_duplicate_column(positions, "slit_id",
7325  maskslits, "slit_id");
7326  cpl_table_erase_column(maskslits, "xcenter");
7327  cpl_table_erase_column(maskslits, "ycenter");
7328  return positions;
7329  }
7330  else {
7331  cpl_table_erase_column(slits, "slit_id");
7332  cpl_table_erase_column(slits, "xpseudo");
7333  cpl_table_erase_column(slits, "ypseudo");
7334  positions = cpl_table_duplicate(slits);
7335  cpl_table_erase_column(slits, "xcenter");
7336  cpl_table_erase_column(slits, "ycenter");
7337  cpl_msg_warning(func, "(the failure could be caused "
7338  "by a mask with regularly located slits)");
7339  return NULL;
7340  }
7341  }
7342  else {
7343  cpl_msg_info(func, "Preliminarily identified slits: %d out of %d "
7344  "candidates (%d expected)", found_slits, nslits,
7345  nmaskslits);
7346  }
7347 
7348 
7349  /*
7350  * Create a table with the coordinates of the preliminarily identified
7351  * slits, both on CCD and mask. The original order of the slits positions
7352  * is preserved.
7353  */
7354 
7355  positions = cpl_table_new(found_slits);
7356  cpl_table_new_column(positions, "slit_id", CPL_TYPE_INT);
7357  cpl_table_new_column(positions, "xtop", CPL_TYPE_DOUBLE);
7358  cpl_table_new_column(positions, "ytop", CPL_TYPE_DOUBLE);
7359  cpl_table_new_column(positions, "xbottom", CPL_TYPE_DOUBLE);
7360  cpl_table_new_column(positions, "ybottom", CPL_TYPE_DOUBLE);
7361  cpl_table_new_column(positions, "xcenter", CPL_TYPE_DOUBLE);
7362  cpl_table_new_column(positions, "ycenter", CPL_TYPE_DOUBLE);
7363  cpl_table_new_column(positions, "xmtop", CPL_TYPE_DOUBLE);
7364  cpl_table_new_column(positions, "ymtop", CPL_TYPE_DOUBLE);
7365  cpl_table_new_column(positions, "xmbottom", CPL_TYPE_DOUBLE);
7366  cpl_table_new_column(positions, "ymbottom", CPL_TYPE_DOUBLE);
7367  cpl_table_new_column(positions, "xmcenter", CPL_TYPE_DOUBLE);
7368  cpl_table_new_column(positions, "ymcenter", CPL_TYPE_DOUBLE);
7369  cpl_table_new_column(positions, "good", CPL_TYPE_INT);
7370  cpl_table_fill_column_window_int(positions, "good", 0, found_slits, 0);
7371 
7372  slit_id = cpl_table_get_data_int (slits, "slit_id");
7373  xtop = cpl_table_get_data_double(slits, "xtop");
7374  ytop = cpl_table_get_data_double(slits, "ytop");
7375  xbottom = cpl_table_get_data_double(slits, "xbottom");
7376  ybottom = cpl_table_get_data_double(slits, "ybottom");
7377  xcenter = cpl_table_get_data_double(slits, "xcenter");
7378  ycenter = cpl_table_get_data_double(slits, "ycenter");
7379 
7380  mslit_id = cpl_table_get_data_int (maskslits, "slit_id");
7381  xmtop = cpl_table_get_data_double(maskslits, "xtop");
7382  ymtop = cpl_table_get_data_double(maskslits, "ytop");
7383  xmbottom = cpl_table_get_data_double(maskslits, "xbottom");
7384  ymbottom = cpl_table_get_data_double(maskslits, "ybottom");
7385  xmcenter = cpl_table_get_data_double(maskslits, "xcenter");
7386  ymcenter = cpl_table_get_data_double(maskslits, "ycenter");
7387 
7388 
7389  /*
7390  * Transferring the valid slits information to the new table.
7391  * Note that invalid elements are coded as 0 in the internal
7392  * buffer, and this is the way they are recognised and excluded.
7393  */
7394 
7395  k = 0;
7396  cpl_table_fill_invalid_int(slits, "slit_id", 0);
7397  for (i = 0; i < nmaskslits; i++) {
7398  for (j = 0; j < nslits; j++) {
7399  if (slit_id[j] == 0)
7400  continue; /* Skip invalid slit */
7401  if (mslit_id[i] == slit_id[j]) {
7402  cpl_table_set_int (positions, "slit_id", k, slit_id[j]);
7403 
7404  cpl_table_set_double(positions, "xtop", k, xtop[j]);
7405  cpl_table_set_double(positions, "ytop", k, ytop[j]);
7406  cpl_table_set_double(positions, "xbottom", k, xbottom[j]);
7407  cpl_table_set_double(positions, "ybottom", k, ybottom[j]);
7408  cpl_table_set_double(positions, "xcenter", k, xcenter[j]);
7409  cpl_table_set_double(positions, "ycenter", k, ycenter[j]);
7410 
7411  cpl_table_set_double(positions, "xmtop", k, xmtop[i]);
7412  cpl_table_set_double(positions, "ymtop", k, ymtop[i]);
7413  cpl_table_set_double(positions, "xmbottom", k, xmbottom[i]);
7414  cpl_table_set_double(positions, "ymbottom", k, ymbottom[i]);
7415  cpl_table_set_double(positions, "xmcenter", k, xmcenter[i]);
7416  cpl_table_set_double(positions, "ymcenter", k, ymcenter[i]);
7417 
7418  k++;
7419 
7420  break;
7421  }
7422  }
7423  }
7424 
7425  found_slits = k;
7426 
7427  cpl_table_erase_column(slits, "slit_id");
7428  cpl_table_erase_column(slits, "xpseudo");
7429  cpl_table_erase_column(slits, "ypseudo");
7430  cpl_table_erase_column(slits, "xcenter");
7431  cpl_table_erase_column(slits, "ycenter");
7432  cpl_table_erase_column(maskslits, "xpseudo");
7433  cpl_table_erase_column(maskslits, "ypseudo");
7434  cpl_table_erase_column(maskslits, "xcenter");
7435  cpl_table_erase_column(maskslits, "ycenter");
7436 
7437 
7438  /*
7439  * Find the median platescale and rotation angle from the identified
7440  * slits, and then exclude slits outlaying more than 10% from the
7441  * median platescale, and more than 2 degrees from the median
7442  * rotation angle.
7443  */
7444 
7445  ytop = cpl_table_get_data_double(positions, "ytop");
7446  ybottom = cpl_table_get_data_double(positions, "ybottom");
7447  xcenter = cpl_table_get_data_double(positions, "xcenter");
7448  ycenter = cpl_table_get_data_double(positions, "ycenter");
7449  xmcenter = cpl_table_get_data_double(positions, "xmcenter");
7450  ymcenter = cpl_table_get_data_double(positions, "ymcenter");
7451 
7452  scales = cpl_vector_new(found_slits - 1);
7453  dscale = cpl_vector_get_data(scales);
7454  angles = cpl_vector_new(found_slits - 1);
7455  dangle = cpl_vector_get_data(angles);
7456 
7457  for (i = 1; i < found_slits; i++) {
7458  dist1 = (xcenter[i-1] - xcenter[i]) * (xcenter[i-1] - xcenter[i])
7459  + (ycenter[i-1] - ycenter[i]) * (ycenter[i-1] - ycenter[i]);
7460  dist2 = (xmcenter[i-1] - xmcenter[i]) * (xmcenter[i-1] - xmcenter[i])
7461  + (ymcenter[i-1] - ymcenter[i]) * (ymcenter[i-1] - ymcenter[i]);
7462  dscale[i-1] = sqrt(dist1/dist2);
7463  dangle[i-1] = atan2(ycenter[i-1] - ycenter[i],
7464  xcenter[i-1] - xcenter[i])
7465  - atan2(ymcenter[i-1] - ymcenter[i],
7466  xmcenter[i-1] - xmcenter[i]);
7467  dangle[i-1] *= 180;
7468  dangle[i-1] /= pi;
7469  }
7470 
7471  minscale = cpl_vector_get_min(scales);
7472  scale = cpl_vector_get_median_const(scales);
7473  maxscale = cpl_vector_get_max(scales);
7474 
7475  minangle = cpl_vector_get_min(angles);
7476  angle = cpl_vector_get_median_const(angles);
7477  maxangle = cpl_vector_get_max(angles);
7478 
7479  cpl_msg_info(func, "Median platescale: %f pixel/mm", scale);
7480  cpl_msg_info(func, "Minmax platescale: %f, %f pixel/mm",
7481  minscale, maxscale);
7482 
7483  cpl_msg_info(func, "Median rotation: %f degrees", angle);
7484  cpl_msg_info(func, "Minmax rotation: %f, %f degrees",
7485  minangle, maxangle);
7486 
7487  good = cpl_table_get_data_int(positions, "good");
7488 
7489  good[0] = good[found_slits - 1] = 1;
7490  for (i = 1; i < found_slits; i++) {
7491  if (fabs((dscale[i-1] - scale)/scale) < 0.10
7492  && fabs(dangle[i-1] - angle) < 2) {
7493  good[i-1]++;
7494  good[i]++;
7495  }
7496  }
7497 
7498  for (i = 0; i < found_slits; i++) {
7499  if (good[i] < 2)
7500  good[i] = 0;
7501  else
7502  good[i] = 1;
7503  }
7504 
7505 /*
7506  for (i = 1; i < found_slits; i++)
7507  if (fabs((dscale[i-1] - scale)/scale) < 0.10)
7508  good[i-1] = good[i] = 1;
7509 */
7510 
7511 /* DEBUG ************+
7512  for (i = 0; i < found_slits; i++) {
7513  if (good[i]) {
7514  if (i == found_slits - 1)
7515  printf("include slit %d, prev = %f, %f\n",
7516  i, dscale[i-1], dangle[i-1]);
7517  else if (i == 0)
7518  printf("include slit %d, next %f, %f\n",
7519  i, dscale[i], dangle[i]);
7520  else
7521  printf("include slit %d, prev = %f, %f, next %f, %f\n", i,
7522  dscale[i-1], dangle[i-1], dscale[i], dangle[i]);
7523  }
7524  else {
7525  if (i == found_slits - 1)
7526  printf("EXclude slit %d, prev = %f, %f\n",
7527  i, dscale[i-1], dangle[i-1]);
7528  else if (i == 0)
7529  printf("EXclude slit %d, next %f, %f\n",
7530  i, dscale[i], dangle[i]);
7531  else
7532  printf("EXclude slit %d, prev = %f, %f, next %f, %f\n", i,
7533  dscale[i-1], dangle[i-1], dscale[i], dangle[i]);
7534  }
7535  }
7536 +*********** DEBUG */
7537 
7538  cpl_vector_delete(scales);
7539  cpl_vector_delete(angles);
7540 
7541  cpl_table_and_selected_int(positions, "good", CPL_EQUAL_TO, 0);
7542  cpl_table_erase_selected(positions);
7543  cpl_table_erase_column(positions, "good");
7544  found_slits = cpl_table_get_nrow(positions);
7545 
7546  if (found_slits < 4) {
7547 
7548  /*
7549  * If the self-consistency check gives such a poor result,
7550  * something must have gone really wrong in the preliminary
7551  * wavelength calibration... Nothing can be done.
7552  */
7553 
7554  cpl_msg_warning(func, "Too few safely identified slits: %d out of %d "
7555  "candidates (%d expected). Process will continue "
7556  "using the detected CCD slits positions", found_slits,
7557  nslits, nmaskslits);
7558  cpl_table_delete(positions);
7559  return NULL;
7560  }
7561  else {
7562  cpl_msg_info(func, "Safely identified slits: %d out of %d "
7563  "candidates\n(%d expected)", found_slits, nslits,
7564  nmaskslits);
7565  }
7566 
7567 
7568  /*
7569  * Now select the central points of the identified slits, and
7570  * fit two bivariate polynomials to determine a first approximate
7571  * relation between positions on the mask and positions on the CCD.
7572  */
7573 
7574  xpos = cpl_vector_wrap(found_slits,
7575  cpl_table_get_data_double(positions, "xcenter"));
7576  ypos = cpl_vector_wrap(found_slits,
7577  cpl_table_get_data_double(positions, "ycenter"));
7578  xmpos = cpl_vector_wrap(found_slits,
7579  cpl_table_get_data_double(positions, "xmcenter"));
7580  ympos = cpl_vector_wrap(found_slits,
7581  cpl_table_get_data_double(positions, "ymcenter"));
7582  mpos = cpl_bivector_wrap_vectors(xmpos, ympos);
7583 
7584  if (found_slits < 10)
7585  degree = 1;
7586  else
7587  degree = 2;
7588 
7589  xpoly = cpl_polynomial_fit_2d_create(mpos, xpos, degree, &xmse);
7590  if (xpoly != NULL)
7591  ypoly = cpl_polynomial_fit_2d_create(mpos, ypos, degree, &ymse);
7592  cpl_bivector_unwrap_vectors(mpos);
7593  cpl_vector_unwrap(xpos);
7594  cpl_vector_unwrap(ypos);
7595  cpl_vector_unwrap(xmpos);
7596  cpl_vector_unwrap(ympos);
7597  if (ypoly == NULL) {
7598  if (found_slits == nmaskslits) {
7599  cpl_msg_warning(func, "Fit failure: the accuracy of the "
7600  "identified slits positions is not improved.");
7601 
7602  /*
7603  * The determination of the transformation from mask to CCD
7604  * failed, but since all slits have been already identified
7605  * this is not a problem: data can be reduced also without
7606  * such transformation. Simply the accuracy of the slits
7607  * positions cannot be improved.
7608  */
7609 
7610  } else {
7611  cpl_msg_info(func, "Fit failure: not all slits have been "
7612  "identified. Process will continue using "
7613  "the detected CCD slits positions");
7614  }
7615 
7616  cpl_polynomial_delete(xpoly);
7617  return positions;
7618  }
7619 
7620  cpl_msg_info(func, "Fit successful: X rms = %.3g, Y rms = %.3g (pixel)",
7621  sqrt(xmse), sqrt(ymse));
7622 
7623  if (global) {
7624  write_global_distortion(global, 0, xpoly);
7625  write_global_distortion(global, 7, ypoly);
7626  }
7627 
7628  /*
7629  * The fit was successful: use the polynomials to obtain a new
7630  * position table with the improved positions of the slits
7631  */
7632 
7633  cpl_table_delete(positions);
7634 
7635  positions = cpl_table_duplicate(maskslits);
7636  cpl_table_duplicate_column(positions, "xmtop", positions, "xtop");
7637  cpl_table_duplicate_column(positions, "ymtop", positions, "ytop");
7638  cpl_table_duplicate_column(positions, "xmbottom", positions, "xbottom");
7639  cpl_table_duplicate_column(positions, "ymbottom", positions, "ybottom");
7640 
7641  point = cpl_vector_new(2);
7642  dpoint = cpl_vector_get_data(point);
7643 
7644  for (i = 0; i < nmaskslits; i++) {
7645  dpoint[0] = cpl_table_get_double(positions, "xmtop", i, NULL);
7646  dpoint[1] = cpl_table_get_double(positions, "ymtop", i, NULL);
7647  cpl_table_set_double(positions, "xtop", i,
7648  cpl_polynomial_eval(xpoly, point));
7649  cpl_table_set_double(positions, "ytop", i,
7650  cpl_polynomial_eval(ypoly, point));
7651  dpoint[0] = cpl_table_get_double(positions, "xmbottom", i, NULL);
7652  dpoint[1] = cpl_table_get_double(positions, "ymbottom", i, NULL);
7653  cpl_table_set_double(positions, "xbottom", i,
7654  cpl_polynomial_eval(xpoly, point));
7655  cpl_table_set_double(positions, "ybottom", i,
7656  cpl_polynomial_eval(ypoly, point));
7657  }
7658 
7659  cpl_vector_delete(point);
7660  cpl_polynomial_delete(xpoly);
7661  cpl_polynomial_delete(ypoly);
7662 
7663  cpl_table_erase_column(positions, "xmtop");
7664  cpl_table_erase_column(positions, "ymtop");
7665  cpl_table_erase_column(positions, "xmbottom");
7666  cpl_table_erase_column(positions, "ymbottom");
7667 
7668  if (nmaskslits > nslits)
7669  cpl_msg_info(func, "Finally identified slits: %d out of %d expected\n"
7670  "(%d recovered)", nmaskslits, nmaskslits, nmaskslits - nslits);
7671  else if (nmaskslits < nslits)
7672  cpl_msg_info(func, "Finally identified slits: %d out of %d expected\n"
7673  "(%d rejected)", nmaskslits, nmaskslits, nslits - nmaskslits);
7674  else
7675  cpl_msg_info(func, "Finally identified slits: %d out of %d expected",
7676  nmaskslits, nmaskslits);
7677 
7678  return positions;
7679 }
7680 
7681 
7723 cpl_table *mos_trace_flat(cpl_image *flat, cpl_table *slits, double reference,
7724  double blue, double red, double dispersion)
7725 {
7726 
7727  const char *func = "mos_trace_flat";
7728 
7729  cpl_image *gradient;
7730  cpl_image *sgradient;
7731  float *dgradient;
7732  float level = 500; /* It was 250... */
7733  cpl_vector *row;
7734  cpl_vector *srow;
7735  cpl_vector **peaks;
7736  double *peak;
7737  int *slit_id;
7738  float *g;
7739  double *r;
7740  double *xtop;
7741  double *ytop;
7742  double *xbottom;
7743  double *ybottom;
7744  double min, dist;
7745  double sradius;
7746  double tolerance;
7747  double start_y, prev_y;
7748  int minpos;
7749  int nslits;
7750  int nrows;
7751  int step = 10;
7752  int filtbox = 15;
7753  int nx, ny, npix;
7754  int pos, ypos;
7755  int npeaks;
7756  int pixel_above, pixel_below;
7757  int i, j, k, l;
7758  char trace_id[MAX_COLNAME];
7759 
7760  cpl_table *traces;
7761 
7762 
7763  if (flat == NULL || slits == NULL) {
7764  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
7765  return NULL;
7766  }
7767 
7768  if (dispersion <= 0.0) {
7769  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
7770  return NULL;
7771  }
7772 
7773  if (red - blue < dispersion) {
7774  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
7775  return NULL;
7776  }
7777 
7778  /*
7779  * Create a dummy slit_id column if it is missing in the
7780  * input slits table
7781  */
7782 
7783  nslits = cpl_table_get_nrow(slits);
7784  if (1 != cpl_table_has_column(slits, "slit_id")) {
7785  cpl_table_new_column(slits, "slit_id", CPL_TYPE_INT);
7786  for (i = 0; i < nslits; i++)
7787  cpl_table_set_int(slits, "slit_id", i, -(i+1)); /* it was (i+1) */
7788  }
7789 
7790  slit_id = cpl_table_get_data_int(slits, "slit_id");
7791 
7792  nx = cpl_image_get_size_x(flat);
7793  ny = cpl_image_get_size_y(flat);
7794  npix = nx * ny;
7795 
7796  gradient = cpl_image_duplicate(flat);
7797  dgradient = cpl_image_get_data_float(gradient);
7798 
7799  for (i = 0; i < ny - 1; i++) {
7800  k = i * nx;
7801  for (j = 0; j < nx; j++) {
7802  l = k + j;
7803  dgradient[l] = fabs(dgradient[l] - dgradient[l + nx]);
7804  }
7805  }
7806 
7807  npix--;
7808  for (j = 0; j < nx; j++)
7809  dgradient[npix - j] = 0.0;
7810 
7811  cpl_image_turn(gradient, -1);
7812  nx = cpl_image_get_size_x(gradient);
7813  ny = cpl_image_get_size_y(gradient);
7814  sgradient = mos_image_vertical_median_filter(gradient,
7815  filtbox, 0, ny, 0, step);
7816  cpl_image_delete(gradient);
7817 
7818 
7819  /*
7820  * Remove background from processed image rows
7821  */
7822 
7823  dgradient = cpl_image_get_data_float(sgradient);
7824 
7825  for (i = 1; i <= ny; i += step) {
7826  row = cpl_vector_new_from_image_row(sgradient, i);
7827  srow = cpl_vector_filter_median_create(row, filtbox);
7828  cpl_vector_subtract(row, srow);
7829  cpl_vector_delete(srow);
7830  g = dgradient + (i-1)*nx;
7831  r = cpl_vector_get_data(row);
7832  for (j = 0; j < nx; j++)
7833  g[j] = r[j];
7834  cpl_vector_delete(row);
7835  }
7836 
7837 
7838  /*
7839  * Rotate (temporarily) the input slits table, to get coordinates
7840  * compatible with the rotated gradient image.
7841  */
7842 
7843  mos_rotate_slits(slits, 1, nx, ny);
7844  xtop = cpl_table_get_data_double(slits, "xtop");
7845  ytop = cpl_table_get_data_double(slits, "ytop");
7846  xbottom = cpl_table_get_data_double(slits, "xbottom");
7847  ybottom = cpl_table_get_data_double(slits, "ybottom");
7848 
7849 
7850  /*
7851  * Get positions of peaks candidates for each processed gradient
7852  * image row
7853  */
7854 
7855  peaks = cpl_calloc(ny, sizeof(cpl_vector *));
7856 
7857  for (i = 0; i < ny; i += step) {
7858  g = dgradient + i*nx;
7859  peaks[i] = mos_peak_candidates(g, nx, level, 1.0);
7860 
7861  /* I thought this would be required, but apparently I was wrong.
7862  * Check twice... */
7863  if (peaks[i])
7864  cpl_vector_subtract_scalar(peaks[i], 0.5);
7865 
7866  }
7867 
7868  cpl_image_delete(sgradient);
7869 
7870 
7871  /*
7872  * Tracing the flat field spectra edges, starting from the
7873  * slits positions obtained at reference wavelength. The
7874  * gradient maximum closest to each slits ends is found and
7875  * accepted only within a given search radius:
7876  */
7877 
7878  sradius = 5.0; /* Pixel */
7879 
7880  /*
7881  * The tracing proceeds along the processed gradient image rows,
7882  * above and below the start position, finding the closest peak
7883  * to the previous obtained position, accepting the new position
7884  * only if it is closer than a given tolerance:
7885  */
7886 
7887  tolerance = 0.9; /* Pixel */
7888 
7889  /*
7890  * The trace is attempted for a certain number of pixels above
7891  * and below the position of the reference wavelength:
7892  */
7893 
7894  pixel_above = STRETCH_FACTOR * (red - reference) / dispersion;
7895  pixel_below = STRETCH_FACTOR * (reference - blue) / dispersion;
7896 
7897 
7898  /*
7899  * Prepare the structure of the output table:
7900  */
7901 
7902  nrows = (ny-1)/step + 1;
7903  traces = cpl_table_new(nrows);
7904  cpl_table_new_column(traces, "x", CPL_TYPE_DOUBLE);
7905  cpl_table_set_column_unit(traces, "x", "pixel");
7906  for (i = 0, j = 0; i < ny; i += step, j++)
7907  cpl_table_set(traces, "x", j, i);
7908 
7909  for (i = 0; i < nslits; i++) {
7910 
7911  /*
7912  * Find the closest processed gradient image row
7913  */
7914 
7915  ypos = ytop[i];
7916 
7917  if (ypos < 0)
7918  ypos = 0;
7919  if (ypos >= ny)
7920  ypos = ny - 1;
7921 
7922  pos = ypos / step;
7923  pos *= step;
7924 
7925  /*
7926  * Find the peak in that row that is closest to xtop[i]
7927  */
7928 
7929  if (peaks[pos]) {
7930  peak = cpl_vector_get_data(peaks[pos]);
7931  npeaks = cpl_vector_get_size(peaks[pos]);
7932 
7933  min = fabs(peak[0] - xtop[i]);
7934  minpos = 0;
7935  for (j = 1; j < npeaks; j++) {
7936  dist = fabs(peak[j] - xtop[i]);
7937  if (min > dist) {
7938  min = dist;
7939  minpos = j;
7940  }
7941  }
7942  }
7943  else {
7944  npeaks = 0;
7945  }
7946 
7947  snprintf(trace_id, MAX_COLNAME, "t%d", slit_id[i]);
7948  cpl_table_new_column(traces, trace_id, CPL_TYPE_DOUBLE);
7949 
7950  if (min > sradius || npeaks == 0) {
7951  cpl_msg_warning(func, "Cannot find spectrum edge for "
7952  "top (or left) end of slit %d", slit_id[i]);
7953  }
7954  else {
7955 
7956  /*
7957  * Add to output table the start y position. Note that
7958  * y positions are written in coordinates of the unrotated
7959  * image, i.e., with horizontal dispersion. Currently nx
7960  * is the x size of the temporarily rotated image (for
7961  * faster memory access), but it has the sense of a y size.
7962  */
7963 
7964  cpl_table_set(traces, trace_id, pos/step, nx - peak[minpos]);
7965  start_y = peak[minpos];
7966 
7967  /*
7968  * Perform the tracing of current edge. Above:
7969  */
7970 
7971  prev_y = start_y;
7972 
7973  for (j = pos + step; j < ny; j += step) {
7974  if (j - pos > pixel_above)
7975  break;
7976  if (peaks[j]) {
7977  peak = cpl_vector_get_data(peaks[j]);
7978  npeaks = cpl_vector_get_size(peaks[j]);
7979  min = fabs(peak[0] - prev_y);
7980  minpos = 0;
7981  for (k = 1; k < npeaks; k++) {
7982  dist = fabs(peak[k] - prev_y);
7983  if (min > dist) {
7984  min = dist;
7985  minpos = k;
7986  }
7987  }
7988  if (min < tolerance) {
7989  cpl_table_set(traces, trace_id, j/step,
7990  nx - peak[minpos]);
7991  prev_y = peak[minpos];
7992  }
7993  }
7994  }
7995 
7996  /*
7997  * Perform the tracing of current edge. Below:
7998  */
7999 
8000  prev_y = start_y;
8001 
8002  for (j = pos - step; j >= 0; j -= step) {
8003  if (pos - j > pixel_below)
8004  break;
8005  if (peaks[j]) {
8006  peak = cpl_vector_get_data(peaks[j]);
8007  npeaks = cpl_vector_get_size(peaks[j]);
8008  min = fabs(peak[0] - prev_y);
8009  minpos = 0;
8010  for (k = 1; k < npeaks; k++) {
8011  dist = fabs(peak[k] - prev_y);
8012  if (min > dist) {
8013  min = dist;
8014  minpos = k;
8015  }
8016  }
8017  if (min < tolerance) {
8018  cpl_table_set(traces, trace_id, j/step,
8019  nx - peak[minpos]);
8020  prev_y = peak[minpos];
8021  }
8022  }
8023  }
8024  }
8025 
8026 
8027  /*
8028  * Find the peak in that row that is closest to xbottom[i]
8029  */
8030 
8031  if (peaks[pos]) {
8032  peak = cpl_vector_get_data(peaks[pos]);
8033  npeaks = cpl_vector_get_size(peaks[pos]);
8034 
8035  min = fabs(peak[0] - xbottom[i]);
8036  minpos = 0;
8037  for (j = 1; j < npeaks; j++) {
8038  dist = fabs(peak[j] - xbottom[i]);
8039  if (min > dist) {
8040  min = dist;
8041  minpos = j;
8042  }
8043  }
8044  }
8045  else {
8046  npeaks = 0;
8047  }
8048 
8049  snprintf(trace_id, MAX_COLNAME, "b%d", slit_id[i]);
8050  cpl_table_new_column(traces, trace_id, CPL_TYPE_DOUBLE);
8051 
8052  if (min > sradius || npeaks == 0) {
8053  cpl_msg_warning(func, "Cannot find spectrum edge for "
8054  "bottom (or right) end of slit %d", slit_id[i]);
8055  }
8056  else {
8057 
8058  cpl_table_set(traces, trace_id, pos/step, nx - peak[minpos]);
8059  start_y = peak[minpos];
8060 
8061  /*
8062  * Perform the tracing of current edge. Above:
8063  */
8064 
8065  prev_y = start_y;
8066 
8067  for (j = pos + step; j < ny; j += step) {
8068  if (j - pos > pixel_above)
8069  break;
8070  if (peaks[j]) {
8071  peak = cpl_vector_get_data(peaks[j]);
8072  npeaks = cpl_vector_get_size(peaks[j]);
8073  min = fabs(peak[0] - prev_y);
8074  minpos = 0;
8075  for (k = 1; k < npeaks; k++) {
8076  dist = fabs(peak[k] - prev_y);
8077  if (min > dist) {
8078  min = dist;
8079  minpos = k;
8080  }
8081  }
8082  if (min < tolerance) {
8083  cpl_table_set(traces, trace_id, j/step,
8084  nx - peak[minpos]);
8085  prev_y = peak[minpos];
8086  }
8087  }
8088  }
8089 
8090  /*
8091  * Perform the tracing of current edge. Below:
8092  */
8093 
8094  prev_y = start_y;
8095 
8096  for (j = pos - step; j >= 0; j -= step) {
8097  if (pos - j > pixel_below)
8098  break;
8099  if (peaks[j]) {
8100  peak = cpl_vector_get_data(peaks[j]);
8101  npeaks = cpl_vector_get_size(peaks[j]);
8102  min = fabs(peak[0] - prev_y);
8103  minpos = 0;
8104  for (k = 1; k < npeaks; k++) {
8105  dist = fabs(peak[k] - prev_y);
8106  if (min > dist) {
8107  min = dist;
8108  minpos = k;
8109  }
8110  }
8111  if (min < tolerance) {
8112  cpl_table_set(traces, trace_id, j/step,
8113  nx - peak[minpos]);
8114  prev_y = peak[minpos];
8115  }
8116  }
8117  }
8118  }
8119 
8120  } /* End of loop on slits */
8121 
8122  for (i = 0; i < ny; i += step)
8123  cpl_vector_delete(peaks[i]);
8124  cpl_free(peaks);
8125 
8126  /*
8127  * Restore original orientation of slits positions table
8128  */
8129 
8130  mos_rotate_slits(slits, -1, ny, nx);
8131 
8132  return traces;
8133 
8134 }
8135 
8136 
8157 cpl_table *mos_poly_trace(cpl_table *slits, cpl_table *traces, int order)
8158 {
8159  const char *func = "mos_poly_trace";
8160 
8161  cpl_table *polytraces;
8162  cpl_table *dummy;
8163  cpl_vector *x;
8164  cpl_vector *trace;
8165  cpl_polynomial *polytrace;
8166  char trace_id[MAX_COLNAME];
8167  char trace_res[MAX_COLNAME];
8168  char trace_mod[MAX_COLNAME];
8169  const char *clab[6] = {"c0", "c1", "c2", "c3", "c4", "c5"};
8170  /* Max order is 5 */
8171  double *xdata;
8172  int *slit_id;
8173  int nslits;
8174  int nrows;
8175  int npoints;
8176  int i, j;
8177  cpl_size k;
8178 
8179 
8180  if (traces == NULL || slits == NULL) {
8181  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
8182  return NULL;
8183  }
8184 
8185  if (order > 5) {
8186  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
8187  return NULL;
8188  }
8189 
8190  nrows = cpl_table_get_nrow(traces);
8191  xdata = cpl_table_get_data_double(traces, "x");
8192  nslits = cpl_table_get_nrow(slits);
8193  slit_id = cpl_table_get_data_int(slits, "slit_id");
8194 
8195  polytraces = cpl_table_new(2*nslits);
8196  cpl_table_new_column(polytraces, "slit_id", CPL_TYPE_INT);
8197  for (i = 0; i <= order; i++)
8198  cpl_table_new_column(polytraces, clab[i], CPL_TYPE_DOUBLE);
8199 
8200  for (i = 0; i < nslits; i++) {
8201  for (j = 0; j < 2; j++) { /* For top and bottom trace of each slit */
8202 
8203  if (j) {
8204  snprintf(trace_id, MAX_COLNAME, "b%d", slit_id[i]);
8205  snprintf(trace_res, MAX_COLNAME, "b%d_res", slit_id[i]);
8206  snprintf(trace_mod, MAX_COLNAME, "b%d_mod", slit_id[i]);
8207  }
8208  else {
8209  snprintf(trace_id, MAX_COLNAME, "t%d", slit_id[i]);
8210  snprintf(trace_res, MAX_COLNAME, "t%d_res", slit_id[i]);
8211  snprintf(trace_mod, MAX_COLNAME, "t%d_mod", slit_id[i]);
8212  }
8213 
8214  cpl_table_set_int(polytraces, "slit_id", 2*i+j, slit_id[i]);
8215 
8216  /*
8217  * The "dummy" table is just a tool for eliminating invalid
8218  * points from the vectors to be fitted.
8219  */
8220 
8221  dummy = cpl_table_new(nrows);
8222  cpl_table_duplicate_column(dummy, "x", traces, "x");
8223  cpl_table_duplicate_column(dummy, trace_id, traces, trace_id);
8224  npoints = nrows - cpl_table_count_invalid(dummy, trace_id);
8225  if (npoints < 2 * order) {
8226  cpl_table_delete(dummy);
8227  continue;
8228  }
8229  cpl_table_erase_invalid(dummy);
8230  x = cpl_vector_wrap(npoints,
8231  cpl_table_get_data_double(dummy, "x"));
8232  trace = cpl_vector_wrap(npoints,
8233  cpl_table_get_data_double(dummy, trace_id));
8234  polytrace = cpl_polynomial_fit_1d_create(x, trace, order, NULL);
8235  cpl_vector_unwrap(x);
8236  cpl_vector_unwrap(trace);
8237  cpl_table_delete(dummy);
8238 
8239  /*
8240  * Screen bad solutions. At the moment, a primitive screening
8241  * consists in excluding solutions displaying excessive
8242  * curvature (larger than 1E-5 / pixel)
8243  */
8244 
8245  k = 2;
8246  if (fabs(cpl_polynomial_get_coeff(polytrace, &k)) > 1.E-4) {
8247  cpl_polynomial_delete(polytrace);
8248  cpl_table_new_column(traces, trace_mod, CPL_TYPE_DOUBLE);
8249  cpl_table_duplicate_column(traces, trace_res, traces,
8250  trace_mod);
8251  if (j)
8252  cpl_msg_warning(func, "Exclude bad curvature solution "
8253  "for bottom (right) edge of slit %d", slit_id[i]);
8254  else
8255  cpl_msg_warning(func, "Exclude bad curvature solution "
8256  "for top (left) edge of slit %d", slit_id[i]);
8257  continue;
8258  }
8259 
8260  /*
8261  * Write polynomial coefficients to the output table,
8262  * tagged with the appropriate slit_id
8263  */
8264 
8265  for (k = 0; k <= order; k++)
8266  cpl_table_set_double(polytraces, clab[k], 2*i+j,
8267  cpl_polynomial_get_coeff(polytrace, &k));
8268 
8269  /*
8270  * Add column of residuals to input traces table
8271  */
8272 
8273  cpl_table_new_column(traces, trace_mod, CPL_TYPE_DOUBLE);
8274  cpl_table_set_column_unit(traces, trace_mod, "pixel");
8275 
8276  for (k = 0; k < nrows; k++) {
8277  cpl_table_set_double(traces, trace_mod, k,
8278  cpl_polynomial_eval_1d(polytrace, xdata[k], NULL));
8279  }
8280 
8281  cpl_polynomial_delete(polytrace);
8282 
8283  cpl_table_duplicate_column(traces, trace_res, traces, trace_mod);
8284  cpl_table_subtract_columns(traces, trace_res, trace_id);
8285  cpl_table_multiply_scalar(traces, trace_res, -1.0);
8286 
8287  }
8288  }
8289 
8290  return polytraces;
8291 
8292 }
8293 
8294 
8318 cpl_error_code mos_global_trace(cpl_table *slits, cpl_table *polytraces,
8319  int mode)
8320 {
8321  const char *func = "mos_global_trace";
8322 
8323  const char *clab[6] = {"c0", "c1", "c2", "c3", "c4", "c5"};
8324  /* Max order is 5 */
8325  cpl_table *table;
8326  cpl_vector *c0;
8327  cpl_vector *cn;
8328  cpl_bivector *list;
8329 /* alternative (not robust)
8330  cpl_polynomial *poly;
8331 */
8332 
8333  double *offset;
8334  double rms, q, m;
8335 
8336  int order, nrows, nslits;
8337  int i, j;
8338 
8339 
8340  if (polytraces == NULL) {
8341  cpl_msg_error(func, "Missing spectral curvature table");
8342  return cpl_error_set(func, CPL_ERROR_NULL_INPUT);
8343  }
8344 
8345  if (slits == NULL) {
8346  cpl_msg_error(func, "Missing slits positions table");
8347  return cpl_error_set(func, CPL_ERROR_NULL_INPUT);
8348  }
8349 
8350  nslits = cpl_table_get_nrow(slits);
8351 
8352  table = cpl_table_duplicate(polytraces);
8353  cpl_table_erase_invalid(table);
8354 
8355  nrows = cpl_table_get_nrow(table);
8356 
8357  if (nrows < 4) {
8358  cpl_msg_warning(func, "Too few successful spectral curvature tracings "
8359  "(%d): the determination of a global curvature model "
8360  "failed", nrows);
8361  return CPL_ERROR_NONE;
8362  }
8363 
8364  order = cpl_table_get_ncol(polytraces) - 2;
8365 
8366  for (i = 0; i <= order; i++) {
8367  if (!cpl_table_has_column(table, clab[i])) {
8368  cpl_msg_error(func, "Wrong spectral curvature table");
8369  return cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
8370  }
8371  }
8372 
8373 
8374  /*
8375  * Fill in advance the missing offset terms
8376  */
8377 
8378  for (i = 0; i < nslits; i++) {
8379  if (!cpl_table_is_valid(polytraces, clab[0], 2*i)) {
8380  cpl_table_set_double(polytraces, clab[0], 2*i,
8381  cpl_table_get_double(slits, "ytop", i, NULL));
8382  }
8383  if (!cpl_table_is_valid(polytraces, clab[0], 2*i+1)) {
8384  cpl_table_set_double(polytraces, clab[0], 2*i+1,
8385  cpl_table_get_double(slits, "ybottom", i, NULL));
8386  }
8387  }
8388 
8389  offset = cpl_table_get_data_double(polytraces, clab[0]);
8390 
8391 
8392  /*
8393  * Fit the global model and modify polytraces table accordingly
8394  */
8395 
8396  c0 = cpl_vector_wrap(nrows, cpl_table_get_data_double(table, clab[0]));
8397 
8398  for (i = 1; i <= order; i++) {
8399  cn = cpl_vector_wrap(nrows, cpl_table_get_data_double(table, clab[i]));
8400  list = cpl_bivector_wrap_vectors(c0, cn);
8401  robustLinearFit(list, &q, &m, &rms);
8402 /* alternative (not robust)
8403  poly = cpl_polynomial_fit_1d_create(c0, cn, 1, NULL);
8404 */
8405  for (j = 0; j < 2*nslits; j++) {
8406  if (mode == 1)
8407  if (cpl_table_is_valid(polytraces, clab[i], j))
8408  continue;
8409  cpl_table_set_double(polytraces, clab[i], j, offset[j]*m + q);
8410 /* alternative (not robust)
8411  cpl_table_set_double(polytraces, clab[i], j,
8412  cpl_polynomial_eval_1d(poly, offset[j], NULL));
8413 */
8414  }
8415  cpl_bivector_unwrap_vectors(list);
8416 /* alternative (not robust)
8417  cpl_polynomial_delete(poly);
8418 */
8419  cpl_vector_unwrap(cn);
8420  }
8421 
8422  cpl_vector_unwrap(c0);
8423  cpl_table_delete(table);
8424 
8425  return CPL_ERROR_NONE;
8426 
8427 }
8428 
8429 
8499 cpl_image *mos_spatial_calibration(cpl_image *spectra, cpl_table *slits,
8500  cpl_table *polytraces, double reference,
8501  double blue, double red, double dispersion,
8502  int flux, cpl_image *calibration)
8503 {
8504  const char *func = "mos_spatial_calibration";
8505 
8506  const char *clab[6] = {"c0", "c1", "c2", "c3", "c4", "c5"};
8507  /* Max order is 5 */
8508  cpl_polynomial *polytop;
8509  cpl_polynomial *polybot;
8510  cpl_image **exslit;
8511  cpl_image *resampled;
8512  float *data;
8513  float *sdata;
8514  float *xdata;
8515  double vtop, vbot, value;
8516  double top, bot;
8517  double coeff;
8518  double ytop, ybot;
8519  double ypos, yfra;
8520  double factor;
8521  int yint, ysize, yprev;
8522  int nslits;
8523  int npseudo;
8524  int *slit_id;
8525  int *length;
8526  int nx, ny;
8527  int pixel_above, pixel_below, refpixel, start_pixel, end_pixel;
8528  int missing_top, missing_bot;
8529  int null;
8530  int order;
8531  int i, j;
8532  cpl_size k;
8533 
8534  int create_position = 1;
8535 
8536 
8537  if (spectra == NULL || slits == NULL || polytraces == NULL) {
8538  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
8539  return NULL;
8540  }
8541 
8542  if (dispersion <= 0.0) {
8543  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
8544  return NULL;
8545  }
8546 
8547  if (red - blue < dispersion) {
8548  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
8549  return NULL;
8550  }
8551 
8552  nx = cpl_image_get_size_x(spectra);
8553  ny = cpl_image_get_size_y(spectra);
8554  sdata = cpl_image_get_data(spectra);
8555  if (calibration)
8556  data = cpl_image_get_data(calibration);
8557 
8558  if (cpl_table_has_column(slits, "position"))
8559  create_position = 0;
8560 
8561  if (create_position) {
8562  cpl_table_new_column(slits, "position", CPL_TYPE_INT);
8563  cpl_table_new_column(slits, "length", CPL_TYPE_INT);
8564  cpl_table_set_column_unit(slits, "position", "pixel");
8565  cpl_table_set_column_unit(slits, "length", "pixel");
8566  }
8567  else
8568  length = cpl_table_get_data_int(slits, "length");
8569 
8570  nslits = cpl_table_get_nrow(slits);
8571  slit_id = cpl_table_get_data_int(slits, "slit_id");
8572  order = cpl_table_get_ncol(polytraces) - 2;
8573 
8574  /*
8575  * The spatial resampling is performed for a certain number of
8576  * pixels above and below the position of the reference wavelength:
8577  */
8578 
8579  pixel_above = STRETCH_FACTOR * (red - reference) / dispersion;
8580  pixel_below = STRETCH_FACTOR * (reference - blue) / dispersion;
8581 
8582  exslit = cpl_calloc(nslits, sizeof(cpl_image *));
8583 
8584  for (i = 0; i < nslits; i++) {
8585 
8586  if (create_position == 0)
8587  if (length[i] == 0)
8588  continue;
8589 
8590  /*
8591  * Note that the x coordinate of the reference pixels on the CCD
8592  * is taken arbitrarily at the top end of each slit. This wouldn't
8593  * be entirely correct in case of curved slits, or in presence of
8594  * heavy distortions: in such cases the spatial resampling is
8595  * really performed across a wide range of wavelengths. But
8596  * the lag between top and bottom spectral curvature models
8597  * would introduce even in such cases negligible effects on
8598  * the spectral spatial resampling.
8599  */
8600 
8601  refpixel = cpl_table_get_double(slits, "xtop", i, NULL);
8602 
8603  start_pixel = refpixel - pixel_below;
8604  if (start_pixel < 0)
8605  start_pixel = 0;
8606 
8607  end_pixel = refpixel + pixel_above;
8608  if (end_pixel > nx)
8609  end_pixel = nx;
8610 
8611  /*
8612  * Recover from the table of spectral curvature coefficients
8613  * the curvature polynomials.
8614  */
8615 
8616  missing_top = 0;
8617  polytop = cpl_polynomial_new(1);
8618  for (k = 0; k <= order; k++) {
8619  coeff = cpl_table_get_double(polytraces, clab[k], 2*i, &null);
8620  if (null) {
8621  cpl_polynomial_delete(polytop);
8622  missing_top = 1;
8623  break;
8624  }
8625  cpl_polynomial_set_coeff(polytop, &k, coeff);
8626  }
8627 
8628  missing_bot = 0;
8629  polybot = cpl_polynomial_new(1);
8630  for (k = 0; k <= order; k++) {
8631  coeff = cpl_table_get_double(polytraces, clab[k], 2*i+1, &null);
8632  if (null) {
8633  cpl_polynomial_delete(polybot);
8634  missing_bot = 1;
8635  break;
8636  }
8637  cpl_polynomial_set_coeff(polybot, &k, coeff);
8638  }
8639 
8640  if (missing_top && missing_bot) {
8641  cpl_msg_warning(func, "Spatial calibration, slit %d was not "
8642  "traced: no extraction!",
8643  slit_id[i]);
8644  continue;
8645  }
8646 
8647  /*
8648  * In case just one of the two edges was not traced, the other
8649  * edge curvature model is duplicated and shifted to the other
8650  * end of the slit: better than nothing!
8651  */
8652 
8653  if (missing_top) {
8654  cpl_msg_warning(func, "Upper edge of slit %d was not traced: "
8655  "the spectral curvature of the lower edge "
8656  "is used instead.", slit_id[i]);
8657  polytop = cpl_polynomial_duplicate(polybot);
8658  ytop = cpl_table_get_double(slits, "ytop", i, NULL);
8659  ybot = cpl_table_get_double(slits, "ybottom", i, NULL);
8660  k = 0;
8661  coeff = cpl_polynomial_get_coeff(polybot, &k);
8662  coeff += ytop - ybot;
8663  cpl_polynomial_set_coeff(polytop, &k, coeff);
8664  }
8665 
8666  if (missing_bot) {
8667  cpl_msg_warning(func, "Lower edge of slit %d was not traced: "
8668  "the spectral curvature of the upper edge "
8669  "is used instead.", slit_id[i]);
8670  polybot = cpl_polynomial_duplicate(polytop);
8671  ytop = cpl_table_get_double(slits, "ytop", i, NULL);
8672  ybot = cpl_table_get_double(slits, "ybottom", i, NULL);
8673  k = 0;
8674  coeff = cpl_polynomial_get_coeff(polytop, &k);
8675  coeff -= ytop - ybot;
8676  cpl_polynomial_set_coeff(polybot, &k, coeff);
8677  }
8678 
8679  /*
8680  * Allocate image for current extracted slit
8681  */
8682 
8683  top = cpl_polynomial_eval_1d(polytop, refpixel, NULL);
8684  bot = cpl_polynomial_eval_1d(polybot, refpixel, NULL);
8685  npseudo = ceil(top-bot) + 1;
8686 
8687  if (npseudo < 1) {
8688  cpl_polynomial_delete(polytop);
8689  cpl_polynomial_delete(polybot);
8690  cpl_msg_warning(func, "Slit %d was badly traced: no extraction!",
8691  slit_id[i]);
8692  continue;
8693  }
8694 
8695  exslit[i] = cpl_image_new(nx, npseudo+1, CPL_TYPE_FLOAT);
8696  xdata = cpl_image_get_data(exslit[i]);
8697 
8698  /*
8699  * Write interpolated values to slit image.
8700  */
8701 
8702  for (j = start_pixel; j < end_pixel; j++) {
8703  top = cpl_polynomial_eval_1d(polytop, j, NULL);
8704  bot = cpl_polynomial_eval_1d(polybot, j, NULL);
8705  factor = (top-bot)/npseudo;
8706  for (k = 0; k <= npseudo; k++) {
8707  ypos = top - k*factor;
8708  yint = floor(ypos);
8709  yfra = ypos - yint;
8710  if (yint >= 0 && yint < ny-1) {
8711  vtop = sdata[j + nx*yint];
8712  vbot = sdata[j + nx*(yint+1)];
8713 
8714  //This means that the top and bottom traces are crossing,
8715  //which is physically impossible, so let's set it to 0.
8716  if(factor <= 0 )
8717  value = 0;
8718  else if(vtop == FLT_MAX || vbot == FLT_MAX)
8719  value = FLT_MAX;
8720  else
8721  {
8722  value = vtop*(1-yfra) + vbot*yfra;
8723  if (flux)
8724  value *= factor;
8725  }
8726  xdata[j + nx*(npseudo-k)] = value;
8727  if (calibration) {
8728  data[j + nx*yint] = (top-yint)/factor;
8729  if (k) {
8730 
8731  /*
8732  * This is added to recover lost pixels on
8733  * the CCD image (pixels are lost because
8734  * the CCD pixels are less than npseudo+1).
8735  */
8736 
8737  if (yprev - yint > 1) {
8738  data[j + nx*(yint+1)] = (top-yint-1)/factor;
8739  }
8740  }
8741  }
8742  }
8743  yprev = yint;
8744  }
8745  }
8746  cpl_polynomial_delete(polytop);
8747  cpl_polynomial_delete(polybot);
8748  }
8749 
8750  /*
8751  * Now all the slits images are copied to a single image
8752  */
8753 
8754  ysize = 0;
8755  for (i = 0; i < nslits; i++)
8756  if (exslit[i])
8757  ysize += cpl_image_get_size_y(exslit[i]);
8758 
8759  if(ysize == 0)
8760  return NULL;
8761 
8762  resampled = cpl_image_new(nx, ysize, CPL_TYPE_FLOAT);
8763 
8764  yint = -1;
8765  for (i = 0; i < nslits; i++) {
8766  if (exslit[i]) {
8767  yint += cpl_image_get_size_y(exslit[i]);
8768  cpl_image_copy(resampled, exslit[i], 1, ysize - yint);
8769  if (create_position) {
8770  cpl_table_set_int(slits, "position", i, ysize - yint - 1);
8771  cpl_table_set_int(slits, "length", i,
8772  cpl_image_get_size_y(exslit[i]));
8773  }
8774  cpl_image_delete(exslit[i]);
8775  }
8776  else if (create_position) {
8777  cpl_table_set_int(slits, "position", i, -1);
8778  cpl_table_set_int(slits, "length", i, 0);
8779  }
8780  }
8781 
8782 
8783  /*
8784  * Elimination of non-traced slits from slit position table: we cannot do
8785  * it because we would lose sync with polytraces and other slit-oriented
8786  * tables. COMMENTED OUT.
8787  *
8788 
8789  if (create_position) {
8790 
8791  if (cpl_table_and_selected_int(slits, "position", CPL_EQUAL_TO, -1))
8792  cpl_table_erase_selected(slits);
8793 
8794  }
8795 
8796  */
8797 
8798  cpl_free(exslit);
8799 
8800  return resampled;
8801 
8802 }
8803 
8804 
8917 cpl_image *mos_wavelength_calibration_final(cpl_image *image, cpl_table *slits,
8918  cpl_vector *lines,
8919  double dispersion, float level,
8920  int sradius, int order,
8921  double reject, double refwave,
8922  double *wavestart, double *waveend,
8923  int *nlines, double *error,
8924  cpl_table *idscoeff,
8925  cpl_image *calibration,
8926  cpl_image *residuals,
8927  cpl_table *restable,
8928  cpl_table *detected_lines)
8929 {
8930 
8931  const char *func = "mos_wavelength_calibration_final";
8932 
8933  const char *clab[6] = {"c0", "c1", "c2", "c3", "c4", "c5"};
8934  /* Max order is 5 */
8935 
8936  double tolerance = 20.0; /* Probably forever... */
8937  int step = 10; /* Compute restable every "step" rows */
8938 
8939  char name[MAX_COLNAME];
8940 
8941  cpl_image *resampled;
8942  cpl_bivector *peaks_ident;
8943  cpl_vector *wavel;
8944  cpl_vector *peaks;
8945  cpl_polynomial *ids;
8946  cpl_polynomial *lin;
8947  cpl_polynomial *fguess;
8948  cpl_table *coeff;
8949  double ids_err;
8950  double max_disp, min_disp;
8951  double *line;
8952  double firstLambda, lastLambda, lambda;
8953  double wave, pixe, value;
8954  double c;
8955  float *sdata;
8956  float *rdata;
8957  float *idata;
8958  float *ddata;
8959  float v1, v2, vi;
8960  float fpixel;
8961  int *length;
8962  int pixstart, pixend;
8963  int row_top, row_bot;
8964  int extrapolation;
8965  int nref;
8966  int nslits;
8967  int nfits;
8968  int nl, nx, ny, pixel;
8969  int countLines, usedLines;
8970  int uorder;
8971  int missing;
8972  int null;
8973  int width, uradius;
8974  int i, j, s;
8975  cpl_size k;
8976 
8977 
8978  if (dispersion == 0.0) {
8979  cpl_msg_error(func, "The expected dispersion (A/pixel) must be given");
8980  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
8981  return NULL;
8982  }
8983 
8984  if (dispersion < 0.0) {
8985  cpl_msg_error(func, "The expected dispersion must be positive");
8986  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
8987  return NULL;
8988  }
8989 
8990  if (idscoeff == NULL) {
8991  cpl_msg_error(func, "A preallocated IDS coeff table must be given");
8992  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
8993  return NULL;
8994  }
8995 
8996  max_disp = dispersion + dispersion * tolerance / 100;
8997  min_disp = dispersion - dispersion * tolerance / 100;
8998 
8999  if (order < 1) {
9000  cpl_msg_error(func, "The order of the fitting polynomial "
9001  "must be at least 1");
9002  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
9003  return NULL;
9004  }
9005 
9006  if (image == NULL || lines == NULL) {
9007  cpl_msg_error(func, "Both spectral exposure and reference line "
9008  "catalog are required in input");
9009  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
9010  return NULL;
9011  }
9012 
9013  nx = cpl_image_get_size_x(image);
9014  ny = cpl_image_get_size_y(image);
9015  sdata = cpl_image_get_data_float(image);
9016 
9017  nref = cpl_vector_get_size(lines);
9018  line = cpl_vector_get_data(lines);
9019 
9020  if (*wavestart < 1.0 && *waveend < 1.0) {
9021  firstLambda = line[0];
9022  lastLambda = line[nref-1];
9023  extrapolation = (lastLambda - firstLambda) / 10;
9024  firstLambda -= extrapolation;
9025  lastLambda += extrapolation;
9026  *wavestart = firstLambda;
9027  *waveend = lastLambda;
9028  }
9029  else {
9030  firstLambda = *wavestart;
9031  lastLambda = *waveend;
9032  }
9033 
9034  nl = (lastLambda - firstLambda) / dispersion;
9035  resampled = cpl_image_new(nl, ny, CPL_TYPE_FLOAT);
9036  rdata = cpl_image_get_data_float(resampled);
9037 
9038  /*
9039  * Allocate total output table of IDS coefficients
9040  */
9041 
9042  for (j = 0; j <= order; j++)
9043  cpl_table_new_column(idscoeff, clab[j], CPL_TYPE_DOUBLE);
9044 
9045  if (calibration)
9046  idata = cpl_image_get_data_float(calibration);
9047 
9048  if (residuals)
9049  ddata = cpl_image_get_data_float(residuals);
9050 
9051  if (restable) {
9052  cpl_table_set_size(restable, nref);
9053  cpl_table_new_column(restable, "wavelength", CPL_TYPE_DOUBLE);
9054  cpl_table_copy_data_double(restable, "wavelength", line);
9055  for (i = 0; i < ny; i += step) {
9056  snprintf(name, MAX_COLNAME, "r%d", i);
9057  cpl_table_new_column(restable, name, CPL_TYPE_DOUBLE);
9058  snprintf(name, MAX_COLNAME, "d%d", i);
9059  cpl_table_new_column(restable, name, CPL_TYPE_DOUBLE);
9060  snprintf(name, MAX_COLNAME, "p%d", i);
9061  cpl_table_new_column(restable, name, CPL_TYPE_DOUBLE);
9062  }
9063  }
9064 
9065  if (detected_lines) {
9066  cpl_table_set_size(detected_lines, 0);
9067  cpl_table_new_column(detected_lines, "slit_id", CPL_TYPE_INT);
9068  cpl_table_new_column(detected_lines, "xpos_rectified", CPL_TYPE_DOUBLE);
9069  cpl_table_new_column(detected_lines, "ypos_rectified", CPL_TYPE_DOUBLE);
9070  cpl_table_new_column(detected_lines, "xpos_rectified_iter", CPL_TYPE_DOUBLE);
9071  cpl_table_new_column(detected_lines, "ypos_rectified_iter", CPL_TYPE_DOUBLE);
9072  cpl_table_new_column(detected_lines, "peak_flux", CPL_TYPE_DOUBLE);
9073  cpl_table_new_column(detected_lines, "wave_ident", CPL_TYPE_DOUBLE);
9074  cpl_table_new_column(detected_lines, "wave_ident_iter", CPL_TYPE_DOUBLE);
9075  cpl_table_new_column(detected_lines, "xpos_fit_rect_wavecal", CPL_TYPE_DOUBLE);
9076  cpl_table_new_column(detected_lines, "res_xpos", CPL_TYPE_DOUBLE);
9077  }
9078 
9079 
9080  /*
9081  * Process all slits separately.
9082  */
9083 
9084  nslits = cpl_table_get_nrow(slits);
9085  length = cpl_table_get_data_int(slits, "length");
9086 
9087  row_top = ny;
9088  for (s = 0; s < nslits; s++) {
9089 
9090  int slit_id;
9091  slit_id = cpl_table_get_int(slits, "slit_id", s, NULL);
9092 
9093  if (length[s] == 0)
9094  continue;
9095 
9096  /*
9097  * row_top and row_bot define the boundaries of the current slit.
9098  * Here we begin (arbitrarily...) from the top slit.
9099  */
9100 
9101  row_bot = cpl_table_get_int(slits, "position", s, NULL);
9102 
9103  if (sradius > 0) {
9104 
9105  /*
9106  * If a search radius was defined, allocate the table of
9107  * the fitting polynomials coefficients. This table is
9108  * just used to generate the first-guess polynomial made
9109  * of the median coefficients of all polynomials found
9110  * for this slit.
9111  */
9112 
9113  coeff = cpl_table_new(row_top - row_bot);
9114  for (j = 0; j <= order; j++)
9115  cpl_table_new_column(coeff, clab[j], CPL_TYPE_DOUBLE);
9116  }
9117 
9118  /*
9119  * Here is the loop on all rows of the current slit. They are
9120  * wavelength calibrated one by one.
9121  */
9122 
9123  for (i = row_bot; i < row_top; i++) {
9124  width = mos_lines_width(sdata + i*nx, nx);
9125  if (width < 5)
9126  width = 5;
9127  peaks = mos_peak_candidates(sdata + i*nx, nx, level, width);
9128  if (peaks) {
9129  peaks = mos_refine_peaks(sdata + i*nx, nx, peaks, width);
9130  }
9131  if (peaks) {
9132  int keep_multiplex = mos_multiplex;
9133  mos_multiplex = -1;
9134  if(detected_lines)
9135  {
9136  cpl_size newlines = cpl_vector_get_size(peaks);
9137  cpl_size oldsize = cpl_table_get_nrow(detected_lines);
9138  cpl_table_set_size(detected_lines, oldsize + newlines);
9139  for(cpl_size iline = 0; iline < newlines; ++iline)
9140  {
9141  cpl_table_set_int(detected_lines, "slit_id",
9142  oldsize + iline, slit_id);
9143  cpl_table_set_double(detected_lines, "xpos_rectified",
9144  oldsize + iline, cpl_vector_get(peaks, iline) + 1);
9145  cpl_table_set_double(detected_lines, "ypos_rectified",
9146  oldsize + iline, (double)i + 1);
9147  cpl_table_set_double(detected_lines, "peak_flux",
9148  oldsize + iline,
9149  sdata[i*nx+(int)(cpl_vector_get(peaks, iline)+0.5)]);
9150  }
9151  }
9152  peaks_ident = mos_identify_peaks(peaks, lines,
9153  min_disp, max_disp, 0.05);
9154  mos_multiplex = keep_multiplex;
9155  if (peaks_ident) {
9156  countLines = cpl_bivector_get_size(peaks_ident);
9157  if (countLines < 4) {
9158  cpl_bivector_delete(peaks_ident);
9159  cpl_vector_delete(peaks);
9160  if (nlines)
9161  nlines[i] = 0;
9162  if (error)
9163  error[i] = 0.0;
9164  continue;
9165  }
9166 
9167  /*
9168  * Set reference wavelength as zero point
9169  */
9170 
9171  wavel = cpl_bivector_get_y(peaks_ident);
9172  cpl_vector_subtract_scalar(wavel, refwave);
9173 
9174  uorder = countLines / 2 - 1;
9175  if (uorder > order)
9176  uorder = order;
9177 
9178  ids = mos_poly_wav2pix(peaks_ident, uorder, reject,
9179  2 * (uorder + 1), &usedLines,
9180  &ids_err);
9181 
9182  if (ids == NULL) {
9183  cpl_bivector_delete(peaks_ident);
9184  cpl_vector_delete(peaks);
9185  if (nlines)
9186  nlines[i] = 0;
9187  if (error)
9188  error[i] = 0.0;
9189  cpl_error_reset();
9190  continue;
9191  }
9192 
9193  if (sradius > 0) {
9194  for (k = 0; k <= order; k++) {
9195  if (k > uorder) {
9196  cpl_table_set_double(coeff, clab[k],
9197  i - row_bot, 0.0);
9198  }
9199  else {
9200  cpl_table_set_double(coeff, clab[k],
9201  i - row_bot, cpl_polynomial_get_coeff(ids, &k));
9202  }
9203  }
9204  }
9205  /* else { */
9206  if (calibration) {
9207  pixstart = cpl_polynomial_eval_1d(ids,
9208  cpl_bivector_get_y_data(peaks_ident)[0],
9209  NULL);
9210  pixend = cpl_polynomial_eval_1d(ids,
9211  cpl_bivector_get_y_data(peaks_ident)[countLines-1],
9212  NULL);
9213  extrapolation = (pixend - pixstart) / 5;
9214  pixstart -= extrapolation;
9215  pixend += extrapolation;
9216  if (pixstart < 0)
9217  pixstart = 0;
9218  if (pixend > nx)
9219  pixend = nx;
9220 
9221  for (j = pixstart; j < pixend; j++) {
9222  (idata + i*nx)[j] = mos_eval_dds(ids,
9223  firstLambda, lastLambda, refwave, j);
9224  }
9225  }
9226 
9227  /*
9228  * Residuals image
9229  */
9230 
9231  if (residuals || (restable && !(i%step))) {
9232  if (restable && !(i%step)) {
9233  lin = cpl_polynomial_new(1);
9234  for (k = 0; k < 2; k++)
9235  cpl_polynomial_set_coeff(lin, &k,
9236  cpl_polynomial_get_coeff(ids, &k));
9237  }
9238  for (j = 0; j < countLines; j++) {
9239  pixe = cpl_bivector_get_x_data(peaks_ident)[j];
9240  wave = cpl_bivector_get_y_data(peaks_ident)[j];
9241  value = pixe
9242  - cpl_polynomial_eval_1d(ids, wave, NULL);
9243  if (residuals) {
9244  pixel = pixe + 0.5;
9245  (ddata + i*nx)[pixel] = value;
9246  }
9247  if (restable && !(i%step)) {
9248  for (k = 0; k < nref; k++) {
9249  if (fabs(line[k]-refwave-wave) < 0.1) {
9250  snprintf(name, MAX_COLNAME,
9251  "r%d", i);
9252  cpl_table_set_double(restable, name,
9253  k, value);
9254  value = pixe
9255  - cpl_polynomial_eval_1d(lin,
9256  wave, NULL);
9257  snprintf(name, MAX_COLNAME,
9258  "d%d", i);
9259  cpl_table_set_double(restable, name,
9260  k, value);
9261  snprintf(name, MAX_COLNAME,
9262  "p%d", i);
9263  cpl_table_set_double(restable, name,
9264  k, pixe);
9265  break;
9266  }
9267  }
9268  }
9269  }
9270  if (restable && !(i%step)) {
9271  cpl_polynomial_delete(lin);
9272  }
9273 /***
9274  for (j = 0; j < countLines; j++) {
9275  pixel = cpl_bivector_get_x_data(output)[j]
9276  + 0.5;
9277  (ddata + i*nx)[pixel] =
9278  cpl_bivector_get_x_data(output)[j]
9279  - cpl_polynomial_eval_1d(ids,
9280  cpl_bivector_get_y_data(output)[j],
9281  NULL);
9282  }
9283 ***/
9284  //Fill the line identification information in
9285  //the detected_lines table
9286  if(detected_lines)
9287  {
9288  cpl_size nidentlines = cpl_bivector_get_size(peaks_ident);
9289  cpl_size ndetectlines = cpl_vector_get_size(peaks);
9290  cpl_size totalsize = cpl_table_get_nrow(detected_lines);
9291  for(cpl_size idline = 0; idline < nidentlines; ++idline)
9292  {
9293  for(cpl_size detline = 0; detline < ndetectlines; ++detline)
9294  {
9295  if(cpl_vector_get(peaks, detline) ==
9296  cpl_bivector_get_x_data(peaks_ident)[idline])
9297  {
9298  cpl_size table_pos = totalsize - ndetectlines + detline;
9299  double wave_ident = cpl_bivector_get_y_data(peaks_ident)[idline] + refwave;
9300  double xpix_fit = cpl_polynomial_eval_1d(ids,
9301  wave_ident - refwave, NULL);
9302  double xpos_det = cpl_table_get_double(detected_lines,
9303  "xpos_rectified",
9304  table_pos, &null);
9305  cpl_table_set_double(detected_lines,
9306  "wave_ident",
9307  table_pos,
9308  wave_ident);
9309  cpl_table_set_double(detected_lines,
9310  "xpos_fit_rect_wavecal",
9311  table_pos,
9312  xpix_fit + 1);
9313  cpl_table_set_double(detected_lines,
9314  "res_xpos",
9315  table_pos,
9316  xpos_det - xpix_fit - 1);
9317  }
9318  }
9319  }
9320  }
9321 
9322  }
9323  /* } */
9324 
9325  /*
9326  * Write it anyway, even in case a first-guess based
9327  * solution will be searched afterwards: in case of
9328  * failure, the "blind" solution is kept.
9329  */
9330 
9331  if (nlines)
9332  nlines[i] = usedLines;
9333  if (error)
9334  error[i] = ids_err / sqrt(usedLines/(uorder + 1));
9335 
9336  for (k = 0; k <= order; k++) {
9337  if (k > uorder) {
9338  cpl_table_set_double(idscoeff, clab[k], i, 0.0);
9339  }
9340  else {
9341  cpl_table_set_double(idscoeff, clab[k], i,
9342  cpl_polynomial_get_coeff(ids, &k));
9343  }
9344  }
9345 
9346  cpl_polynomial_delete(ids);
9347  cpl_bivector_delete(peaks_ident);
9348  }
9349  cpl_vector_delete(peaks);
9350  }
9351  } /* End of loop on current slit's rows */
9352 
9353 
9354  if (sradius > 0) {
9355 
9356  /*
9357  * See whether there are valid fits at all...
9358  */
9359 
9360  nfits = row_top - row_bot - cpl_table_count_invalid(coeff, clab[0]);
9361 
9362  if (nfits) {
9363  int slope = 0;
9364 
9365  fguess = cpl_polynomial_new(1);
9366 
9367  if (mos_interpolate_wavecalib_mos(coeff, 2, 1)) {
9368 
9369  slope = 0;
9370 
9371  /*
9372  * Compute a median IDS polynomial for the current slit
9373  */
9374 
9375  for (k = 0; k <= order; k++) {
9376  c = cpl_table_get_column_median(coeff, clab[k]);
9377  cpl_polynomial_set_coeff(fguess, &k, c);
9378  }
9379  }
9380  else {
9381  slope = 1;
9382  }
9383 
9384  for (i = row_bot; i < row_top; i++) {
9385 
9386  /*
9387  * Use first-guess to find the reference lines again
9388  */
9389 
9390  width = mos_lines_width(sdata + i*nx, nx);
9391  if (width > sradius) {
9392  uradius = width;
9393  }
9394  else {
9395  uradius = sradius;
9396  }
9397 
9398  if (slope) {
9399  for (k = 0; k <= order; k++) {
9400  c = cpl_table_get_double(coeff, clab[k],
9401  i - row_bot, NULL);
9402  cpl_polynomial_set_coeff(fguess, &k, c);
9403  }
9404  }
9405 
9406  peaks_ident = mos_find_peaks(sdata + i*nx, nx, lines,
9407  fguess, refwave, uradius);
9408 
9409  if (peaks_ident == NULL) {
9410  cpl_error_reset();
9411  continue;
9412  }
9413 
9414  countLines = cpl_bivector_get_size(peaks_ident);
9415 
9416  if (countLines < 4) {
9417  cpl_bivector_delete(peaks_ident);
9418  continue;
9419  }
9420 
9421  /*
9422  * Set reference wavelength as zero point
9423  */
9424 
9425  wavel = cpl_bivector_get_y(peaks_ident);
9426  cpl_vector_subtract_scalar(wavel, refwave);
9427 
9428  uorder = countLines / 2 - 1;
9429  if (uorder > order)
9430  uorder = order;
9431 
9432  ids = mos_poly_wav2pix(peaks_ident, uorder, reject,
9433  2 * (uorder + 1), &usedLines,
9434  &ids_err);
9435 
9436  if (ids == NULL) {
9437  cpl_error_reset();
9438  cpl_bivector_delete(peaks_ident);
9439  continue;
9440  }
9441 
9442  if (nlines)
9443  nlines[i] = usedLines;
9444  if (error)
9445  error[i] = ids_err / sqrt(usedLines/(uorder + 1));
9446 
9447  if (calibration) {
9448  pixstart = cpl_polynomial_eval_1d(ids,
9449  cpl_bivector_get_y_data(peaks_ident)[0],
9450  NULL);
9451  pixend = cpl_polynomial_eval_1d(ids,
9452  cpl_bivector_get_y_data(peaks_ident)[countLines-1],
9453  NULL);
9454  extrapolation = (pixend - pixstart) / 5;
9455  pixstart -= extrapolation;
9456  pixend += extrapolation;
9457  if (pixstart < 0)
9458  pixstart = 0;
9459  if (pixend > nx)
9460  pixend = nx;
9461 
9462  for (j = pixstart; j < pixend; j++) {
9463  (idata + i*nx)[j] = mos_eval_dds(ids,
9464  firstLambda, lastLambda, refwave, j);
9465  }
9466  }
9467 
9468  /*
9469  * Residuals image
9470  */
9471 
9472  if (residuals || (restable && !(i%step))) {
9473  if (restable && !(i%step)) {
9474  lin = cpl_polynomial_new(1);
9475  for (k = 0; k < 2; k++)
9476  cpl_polynomial_set_coeff(lin, &k,
9477  cpl_polynomial_get_coeff(ids, &k));
9478  }
9479  for (j = 0; j < countLines; j++) {
9480  pixe = cpl_bivector_get_x_data(peaks_ident)[j];
9481  wave = cpl_bivector_get_y_data(peaks_ident)[j];
9482  value = pixe
9483  - cpl_polynomial_eval_1d(ids, wave, NULL);
9484  if (residuals) {
9485  pixel = pixe + 0.5;
9486  (ddata + i*nx)[pixel] = value;
9487  }
9488  if (restable && !(i%step)) {
9489  for (k = 0; k < nref; k++) {
9490  if (fabs(line[k]-refwave-wave) < 0.1) {
9491  snprintf(name, MAX_COLNAME,
9492  "r%d", i);
9493  cpl_table_set_double(restable, name,
9494  k, value);
9495  value = pixe
9496  - cpl_polynomial_eval_1d(lin,
9497  wave, NULL);
9498  snprintf(name, MAX_COLNAME,
9499  "d%d", i);
9500  cpl_table_set_double(restable, name,
9501  k, value);
9502  snprintf(name, MAX_COLNAME,
9503  "p%d", i);
9504  cpl_table_set_double(restable, name,
9505  k, pixe);
9506  break;
9507  }
9508  }
9509  }
9510  }
9511  if (restable && !(i%step)) {
9512  cpl_polynomial_delete(lin);
9513  }
9514 /***
9515  for (j = 0; j < countLines; j++) {
9516  pixel = cpl_bivector_get_x_data(output)[j]
9517  + 0.5;
9518  (ddata + i*nx)[pixel] =
9519  cpl_bivector_get_x_data(output)[j]
9520  - cpl_polynomial_eval_1d(ids,
9521  cpl_bivector_get_y_data(output)[j],
9522  NULL);
9523  }
9524 ***/
9525  }
9526 
9527  if(detected_lines)
9528  {
9529  cpl_size oldsize = cpl_table_get_nrow(detected_lines);
9530  cpl_size nidentlines = cpl_bivector_get_size(peaks_ident);
9531  cpl_table_set_size(detected_lines, oldsize + nidentlines);
9532  for(cpl_size idline = 0; idline < nidentlines ; ++idline)
9533  {
9534  double wave_ident = cpl_bivector_get_y_data(peaks_ident)[idline] + refwave;
9535  double xpix_fit = cpl_polynomial_eval_1d(ids,
9536  wave_ident - refwave, NULL);
9537  cpl_table_set_int(detected_lines, "slit_id",
9538  oldsize + idline, slit_id);
9539  cpl_table_set_double(detected_lines, "xpos_rectified_iter",
9540  oldsize + idline, cpl_bivector_get_x_data(peaks_ident)[idline] + 1);
9541  cpl_table_set_double(detected_lines, "ypos_rectified_iter",
9542  oldsize + idline, (double)i + 1);
9543  cpl_table_set_double(detected_lines, "peak_flux",
9544  oldsize + idline,
9545  sdata[i*nx+(int)(cpl_bivector_get_x_data(peaks_ident)[idline]+0.5)]);
9546  cpl_table_set_double(detected_lines, "wave_ident_iter",
9547  oldsize + idline, wave_ident);
9548  cpl_table_set_double(detected_lines, "xpos_fit_rect_wavecal",
9549  oldsize + idline, xpix_fit + 1);
9550  }
9551  }
9552 
9553 
9554  for (k = 0; k <= order; k++) {
9555  if (k > uorder) {
9556  cpl_table_set_double(idscoeff, clab[k], i, 0.0);
9557  }
9558  else {
9559  cpl_table_set_double(idscoeff, clab[k], i,
9560  cpl_polynomial_get_coeff(ids, &k));
9561  }
9562  }
9563 
9564  cpl_bivector_delete(peaks_ident);
9565  cpl_polynomial_delete(ids);
9566 
9567  } /* End of loop "use ids as a first-guess" */
9568 
9569  cpl_polynomial_delete(fguess);
9570  }
9571 
9572  cpl_table_delete(coeff);
9573 
9574  }
9575 
9576  row_top = row_bot;
9577 
9578  } /* End of loop on slits */
9579 
9580 
9581  /*
9582  * At this point the idscoeff table has been filled with all the
9583  * fits coefficients obtained for all the rows of the input image.
9584  * Now we apply these coefficients to resample the input image
9585  * at constant wavelength step.
9586  */
9587 
9588  for (i = 0; i < ny; i++) {
9589 
9590  missing = 0;
9591  ids = cpl_polynomial_new(1);
9592  for (k = 0; k <= order; k++) {
9593  c = cpl_table_get_double(idscoeff, clab[k], i, &null);
9594  if (null) {
9595  cpl_polynomial_delete(ids);
9596  missing = 1;
9597  break;
9598  }
9599  cpl_polynomial_set_coeff(ids, &k, c);
9600  }
9601  if (missing)
9602  continue;
9603 
9604  pixstart = cpl_polynomial_eval_1d(ids, firstLambda - refwave, NULL);
9605  pixend = cpl_polynomial_eval_1d(ids, lastLambda - refwave, NULL);
9606  if (pixstart < 0)
9607  pixstart = 0;
9608  if (pixend > nx)
9609  pixend = nx;
9610 
9611  /*
9612  * Resampled image:
9613  */
9614 
9615  for (j = 0; j < nl; j++) {
9616  lambda = firstLambda + j * dispersion;
9617  fpixel = cpl_polynomial_eval_1d(ids, lambda - refwave, NULL);
9618  pixel = fpixel;
9619  if (pixel >= 0 && pixel < nx-1) {
9620  v1 = (sdata + i*nx)[pixel];
9621  v2 = (sdata + i*nx)[pixel+1];
9622  vi = v1 + (v2-v1)*(fpixel-pixel);
9623  (rdata + i*nl)[j] = vi;
9624  }
9625  }
9626 
9627  cpl_polynomial_delete(ids);
9628  }
9629 
9630  return resampled;
9631 }
9632 
9633 
9660 cpl_image *mos_wavelength_calibration(cpl_image *image, double refwave,
9661  double firstLambda, double lastLambda,
9662  double dispersion, cpl_table *idscoeff,
9663  int flux)
9664 {
9665 
9666  const char *func = "mos_wavelength_calibration";
9667 
9668  const char *clab[6] = {"c0", "c1", "c2", "c3", "c4", "c5"};
9669  /* Max order is 5 */
9670 
9671  cpl_image *resampled;
9672  cpl_polynomial *ids;
9673  double pixel_per_lambda;
9674  double lambda;
9675  double c;
9676  float *sdata;
9677  float *rdata;
9678  float v0, v1, v2, v3, vi;
9679  float fpixel;
9680  int order;
9681  int pixstart, pixend;
9682  int nl, nx, ny, pixel;
9683  int missing;
9684  int null;
9685  int i, j;
9686  cpl_size k;
9687 
9688 
9689  if (dispersion <= 0.0) {
9690  cpl_msg_error(func, "The resampling step must be positive");
9691  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
9692  return NULL;
9693  }
9694 
9695  if (lastLambda - firstLambda < dispersion) {
9696  cpl_msg_error(func, "Invalid spectral range: %.2f to %.2f",
9697  firstLambda, lastLambda);
9698  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
9699  return NULL;
9700  }
9701 
9702  if (idscoeff == NULL) {
9703  cpl_msg_error(func, "An IDS coeff table must be given");
9704  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
9705  return NULL;
9706  }
9707 
9708  if (image == NULL) {
9709  cpl_msg_error(func, "A scientific spectral image must be given");
9710  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
9711  return NULL;
9712  }
9713 
9714  nx = cpl_image_get_size_x(image);
9715  ny = cpl_image_get_size_y(image);
9716  sdata = cpl_image_get_data_float(image);
9717 
9718  nl = (lastLambda - firstLambda) / dispersion;
9719  resampled = cpl_image_new(nl, ny, CPL_TYPE_FLOAT);
9720  rdata = cpl_image_get_data_float(resampled);
9721 
9722  order = 0;
9723  while (order < 6 && cpl_table_has_column(idscoeff, clab[order]))
9724  ++order;
9725  --order;
9726 
9727  for (i = 0; i < ny; i++) {
9728 
9729  missing = 0;
9730  ids = cpl_polynomial_new(1);
9731  for (k = 0; k <= order; k++) {
9732  c = cpl_table_get_double(idscoeff, clab[k], i, &null);
9733  if (null) {
9734  cpl_polynomial_delete(ids);
9735  missing = 1;
9736  break;
9737  }
9738  cpl_polynomial_set_coeff(ids, &k, c);
9739  }
9740  if (missing)
9741  continue;
9742 
9743  pixstart = cpl_polynomial_eval_1d(ids, firstLambda - refwave, NULL);
9744  pixend = cpl_polynomial_eval_1d(ids, lastLambda - refwave, NULL);
9745  if (pixstart < 0)
9746  pixstart = 0;
9747  if (pixend > nx)
9748  pixend = nx;
9749 
9750  /*
9751  * Resampled image:
9752  */
9753 
9754  for (j = 0; j < nl; j++) {
9755  lambda = firstLambda + j * dispersion;
9756  fpixel = cpl_polynomial_eval_1d(ids, lambda - refwave,
9757  &pixel_per_lambda);
9758 
9759  /*
9760  * The local dispersion is 1 / pixel_per_lambda
9761  * and this factor is used for applying the flux
9762  * conservation correction (if requested).
9763  */
9764 
9765  pixel = fpixel;
9766 
9767  // if (dispersion * pixel_per_lambda < 2.0) {
9768  if (1) { /* Old behaviour: this is safe. */
9769 
9770  /*
9771  * In this case we just sample interpolating the
9772  * signal of nearby pixels.
9773  */
9774 
9775  //the wave calibration should be a monotonically increasing
9776  //function. If the detivative is negative, we are outside of
9777  //the wavelength solution domain.
9778  if(pixel_per_lambda <= 0)
9779  vi = 0;
9780  else if (fpixel < 0)
9781  vi = 0;
9782  else if (pixel >= 1 && pixel < nx-2) {
9783  v0 = (sdata + i*nx)[pixel-1];
9784  v1 = (sdata + i*nx)[pixel];
9785  v2 = (sdata + i*nx)[pixel+1];
9786  v3 = (sdata + i*nx)[pixel+2];
9787  vi = (fpixel-pixel)*(fpixel-pixel)*(v3 - v2 - v1 + v0)
9788  + (fpixel-pixel)*(3*v2 - v3 - v1 - v0)
9789  + 2*v1;
9790  vi /= 2;
9791  if (v1 > v2) {
9792  if (vi > v1) {
9793  vi = v1;
9794  }
9795  else if (vi < v2) {
9796  vi = v2;
9797  }
9798  }
9799  else {
9800  if (vi > v2) {
9801  vi = v2;
9802  }
9803  else if (vi < v1) {
9804  vi = v1;
9805  }
9806  }
9807  if (flux)
9808  vi *= dispersion * pixel_per_lambda;
9809  }
9810  else if (pixel >= 0 && pixel < nx-1) {
9811  v1 = (sdata + i*nx)[pixel];
9812  v2 = (sdata + i*nx)[pixel+1];
9813  vi = v1 + (v2-v1)*(fpixel-pixel);
9814  if (flux)
9815  vi *= dispersion * pixel_per_lambda;
9816  }
9817  else
9818  vi = 0;
9819  (rdata + i*nl)[j] = vi;
9820  }
9821  else {
9822 
9823  /*
9824  * Here instead we integrate the pixel values in
9825  * the interval centered at the interpolation point.
9826  * This interval is long dispersion * pixel_per_lambda
9827  * of the original pixels, and is centered at fpixel.
9828  * So it starts at fpixel - dispersion * pixel_per_lambda / 2,
9829  * and it ends at fpixel + dispersion * pixel_per_lambda / 2.
9830  */
9831 
9832  double spos = fpixel - dispersion * pixel_per_lambda / 2;
9833  double epos = fpixel + dispersion * pixel_per_lambda / 2;
9834 
9835  /*
9836  * Brutal sum over all involved pixels
9837  */
9838 
9839  int spix = spos;
9840  int epix = epos + 1;
9841 
9842  if (spix < 0)
9843  spix = 0;
9844 
9845  if (epix > nx)
9846  epix = nx;
9847 
9848  vi = 0.0;
9849  for (k = spix; k < epix; k++) {
9850  if (pixel >= 0 && pixel < nx) {
9851  vi += (sdata + i*nx)[k];
9852  }
9853  }
9854 
9855  /*
9856  * Correct integrated flux by true length
9857  * of interval. This is clearly an approximation,
9858  * but it's good enough if the PSF is much larger
9859  * than the original pix.
9860  */
9861 
9862  vi *= dispersion * pixel_per_lambda / (epix - spix);
9863 
9864  /*
9865  * Flux conservation is a geometric factor that is applied
9866  * always in the same way...
9867  */
9868 
9869  if (flux)
9870  vi *= dispersion * pixel_per_lambda;
9871 
9872  (rdata + i*nl)[j] = vi;
9873  }
9874  }
9875 
9876  cpl_polynomial_delete(ids);
9877  }
9878 
9879  return resampled;
9880 }
9881 
9882 
9949 cpl_table *mos_wavelength_align(cpl_image *image, cpl_table *slits,
9950  double refwave, double firstLambda,
9951  double lastLambda, cpl_table *idscoeff,
9952  cpl_vector *skylines, int highres, int order,
9953  cpl_image *calibration, int sradius)
9954 {
9955  const char *func = "mos_wavelength_align";
9956 
9957  const char *clab[6] = {"c0", "c1", "c2", "c3", "c4", "c5"};
9958  /* Max order is 5 */
9959  double *line;
9960  double *data;
9961  double expPos, offset;
9962  double c;
9963  double lambda1, lambda2;
9964  double rms;
9965  float pos;
9966  float *sdata;
9967  float *cdata;
9968  int *idata;
9969  int startPos, endPos;
9970  int window = 2*sradius + 1;
9971  int nlines;
9972  int nslits;
9973  int npoints;
9974  int nrows;
9975  int nx, ny;
9976  int xlow, ylow, xhig, yhig;
9977  int idsorder, uorder;
9978  int *slit_id;
9979  int *position;
9980  int *length;
9981  int missing;
9982  int null;
9983  int i;
9984  cpl_size j, k;
9985 
9986  char offname[MAX_COLNAME];
9987  char name[MAX_COLNAME];
9988 
9989  cpl_polynomial *ids;
9990  cpl_polynomial *polycorr;
9991  cpl_image *exslit;
9992  cpl_image *sky;
9993  cpl_table *offsets;
9994  cpl_table *dummy;
9995  cpl_vector *wave;
9996  cpl_vector *offs;
9997 
9998 
9999  if (idscoeff == NULL) {
10000  cpl_msg_error(func, "An IDS coeff table must be given");
10001  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
10002  return NULL;
10003  }
10004 
10005  if (image == NULL) {
10006  cpl_msg_error(func, "A scientific spectral image must be given");
10007  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
10008  return NULL;
10009  }
10010 
10011  if (slits == NULL) {
10012  cpl_msg_error(func, "A slit position table must be given");
10013  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
10014  return NULL;
10015  }
10016 
10017  if (skylines) {
10018  line = cpl_vector_get_data(skylines);
10019  nlines = cpl_vector_get_size(skylines);
10020  }
10021  else {
10022  cpl_msg_warning(func, "A catalog of sky lines wavelengths was not "
10023  "given: using internal list of reference sky lines");
10024  if (highres) {
10025  line = default_lines_hi;
10026  nlines = sizeof(default_lines_hi) / sizeof(double);
10027  }
10028  else {
10029  line = default_lines_lo;
10030  nlines = sizeof(default_lines_lo) / sizeof(double);
10031  }
10032  }
10033 
10034  if (calibration)
10035  cdata = cpl_image_get_data(calibration);
10036 
10037  nx = cpl_image_get_size_x(image);
10038  ny = cpl_image_get_size_y(image);
10039 
10040  nslits = cpl_table_get_nrow(slits);
10041  slit_id = cpl_table_get_data_int(slits, "slit_id");
10042  position = cpl_table_get_data_int(slits, "position");
10043  length = cpl_table_get_data_int(slits, "length");
10044 
10045 
10046  /*
10047  * Define the output table of offsets
10048  */
10049 
10050  nrows = 0;
10051  for (i = 0; i < nlines; i++)
10052  if (line[i] > firstLambda && line[i] < lastLambda)
10053  nrows++;
10054 
10055  offsets = cpl_table_new(nrows);
10056  cpl_table_new_column(offsets, "wave", CPL_TYPE_DOUBLE);
10057  cpl_table_set_column_unit(offsets, "wave", "Angstrom");
10058 
10059  nrows = 0;
10060  for (i = 0; i < nlines; i++) {
10061  if (line[i] > firstLambda && line[i] < lastLambda) {
10062  cpl_table_set_double(offsets, "wave", nrows, line[i]);
10063  nrows++;
10064  }
10065  }
10066 
10067  /*
10068  * Here "line" is made to point to the new list of selected wavelengths
10069  */
10070 
10071  line = cpl_table_get_data_double(offsets, "wave");
10072  nlines = nrows;
10073 
10074  idsorder = 0;
10075  while (idsorder < 6 && cpl_table_has_column(idscoeff, clab[idsorder]))
10076  ++idsorder;
10077  --idsorder;
10078 
10079  xlow = 1;
10080  xhig = nx;
10081  for (i = 0; i < nslits; i++) {
10082 
10083  if (length[i] == 0)
10084  continue;
10085 
10086  snprintf(offname, MAX_COLNAME, "offset%d", slit_id[i]);
10087  cpl_table_new_column(offsets, offname, CPL_TYPE_DOUBLE);
10088 
10089  /*
10090  * Define the extraction boundaries. We DON'T write:
10091  *
10092  * ylow = position[i];
10093  * yhig = ylow + length[i];
10094  *
10095  * because the cpl_image pixels are counted from 1, and because in
10096  * cpl_image_extract() the coordinates of the last pixel are inclusive.
10097  */
10098 
10099  ylow = position[i] + 1;
10100  yhig = ylow + length[i] - 1;
10101 
10102  exslit = cpl_image_extract(image, xlow, ylow, xhig, yhig);
10103  sky = cpl_image_collapse_median_create(exslit, 0, 0, 1);
10104  sdata = cpl_image_get_data(sky);
10105 
10106  cpl_image_delete(exslit);
10107 
10108  /*
10109  * Return here to a decent way of counting pixels (i.e., starting
10110  * from 0)
10111  */
10112 
10113  ylow--;
10114 
10115  /*
10116  * Allocate a dummy table for collecting all the offsets
10117  * for all the lines: this is only needed for the computation
10118  * of the median offset for each sky line
10119  */
10120 
10121  dummy = cpl_table_new(yhig - ylow);
10122  for (j = 0; j < nlines; j++) {
10123  snprintf(name, MAX_COLNAME, "%"CPL_SIZE_FORMAT, j);
10124  cpl_table_new_column(dummy, name, CPL_TYPE_DOUBLE);
10125  }
10126 
10127  for (j = ylow; j < yhig; j++) {
10128 
10129  /*
10130  * Get the IDS polynomial for the current slit row
10131  */
10132 
10133  missing = 0;
10134  ids = cpl_polynomial_new(1);
10135  for (k = 0; k <= idsorder; k++) {
10136  c = cpl_table_get_double(idscoeff, clab[k], j, &null);
10137  if (null) {
10138  cpl_polynomial_delete(ids);
10139  missing = 1;
10140  break;
10141  }
10142  cpl_polynomial_set_coeff(ids, &k, c);
10143  }
10144  if (missing)
10145  continue;
10146 
10147  for (k = 0; k < nlines; k++) {
10148  expPos = cpl_polynomial_eval_1d(ids, line[k] - refwave, NULL);
10149  startPos = expPos - sradius;
10150  endPos = startPos + window;
10151  if (startPos < 0 || endPos >= nx)
10152  continue;
10153 
10154  if (0 == peakPosition(sdata + startPos, window, &pos, 1)) {
10155  pos += startPos;
10156  offset = pos - expPos;
10157  snprintf(name, MAX_COLNAME, "%"CPL_SIZE_FORMAT, k);
10158  cpl_table_set_double(dummy, name, j - ylow, offset);
10159  }
10160  }
10161 
10162  cpl_polynomial_delete(ids);
10163  }
10164 
10165  cpl_image_delete(sky);
10166 
10167  for (j = 0; j < nlines; j++) {
10168  snprintf(name, MAX_COLNAME, "%"CPL_SIZE_FORMAT, j);
10169  if (cpl_table_has_valid(dummy, name)) {
10170  offset = cpl_table_get_column_median(dummy, name);
10171  cpl_table_set_double(offsets, offname, j, offset);
10172  }
10173  }
10174 
10175  cpl_table_delete(dummy);
10176 
10177  }
10178 
10179 
10180  /*
10181  * In the following the input idscoeff table is modified by simply
10182  * adding the coefficients of the polynomial used to fit the sky
10183  * line residuals to the coefficients of the IDS polynomials.
10184  */
10185 
10186  for (i = 0; i < nslits; i++) {
10187 
10188  if (length[i] == 0)
10189  continue;
10190 
10191  snprintf(offname, MAX_COLNAME, "offset%d", slit_id[i]);
10192 
10193  /*
10194  * In the following, the "dummy" table is just a tool for
10195  * eliminating invalid points from the vectors to be fitted.
10196  */
10197 
10198  dummy = cpl_table_new(nlines);
10199  cpl_table_duplicate_column(dummy, "wave", offsets, "wave");
10200  cpl_table_duplicate_column(dummy, "offset", offsets, offname);
10201 
10202  npoints = nlines - cpl_table_count_invalid(dummy, "offset");
10203  if (npoints == 0) {
10204  cpl_msg_warning(func, "No sky lines alignment was possible "
10205  "for slit ID=%d: no sky line found", slit_id[i]);
10206  cpl_table_delete(dummy);
10207  continue;
10208  }
10209 
10210  uorder = order;
10211  if (npoints <= uorder) {
10212  uorder = npoints - 1;
10213  if (uorder) {
10214  cpl_msg_warning(func, "Just %d sky lines detected for slit "
10215  "ID=%d, while a polynomial order %d was "
10216  "requested. Using polynomial order %d for "
10217  "this slit!", npoints, slit_id[i], order,
10218  uorder);
10219  }
10220  else {
10221  cpl_msg_warning(func, "Just %d sky lines detected for slit "
10222  "ID=%d, while a polynomial order %d was "
10223  "requested. Computing a median offset for "
10224  "this slit!", npoints, slit_id[i], order);
10225  }
10226  }
10227 
10228  cpl_table_erase_invalid(dummy);
10229 
10230  if (uorder > 1) {
10231 
10232  /*
10233  * Model offsets with polynomial fitting
10234  */
10235 
10236  wave = cpl_vector_wrap(npoints,
10237  cpl_table_get_data_double(dummy, "wave"));
10238  offs = cpl_vector_wrap(npoints,
10239  cpl_table_get_data_double(dummy, "offset"));
10240 
10241  /*
10242  * Set reference wavelength as zero point
10243  */
10244 
10245  cpl_vector_subtract_scalar(wave, refwave);
10246 
10247  polycorr = cpl_polynomial_fit_1d_create(wave, offs, uorder, &rms);
10248 
10249  rms = sqrt(rms * (uorder + 1) / npoints);
10250 
10251  cpl_vector_unwrap(wave);
10252  cpl_vector_unwrap(offs);
10253  cpl_table_delete(dummy);
10254 
10255  /*
10256  * Now correct the coefficients of the corresponding IDS
10257  * polynomials related to this slit:
10258  */
10259 
10260  ylow = position[i];
10261  yhig = ylow + length[i];
10262 
10263  for (j = 0; j <= uorder; j++) {
10264  data = cpl_table_get_data_double(idscoeff, clab[j]);
10265  c = cpl_polynomial_get_coeff(polycorr, &j);
10266  for (k = ylow; k < yhig; k++)
10267  data[k] += c;
10268  }
10269 
10270  data = cpl_table_get_data_double(idscoeff, "error");
10271  for (k = ylow; k < yhig; k++)
10272  data[k] = sqrt(data[k]*data[k] + rms*rms);
10273 
10274  idata = cpl_table_get_data_int(idscoeff, "nlines");
10275  for (k = ylow; k < yhig; k++)
10276  idata[k] = npoints;
10277 
10278  /*
10279  * If a wavelengths map was provided, correct it to keep
10280  * into account the alignment to skylines:
10281  */
10282 
10283  if (calibration) {
10284  for (j = ylow; j < yhig; j++) {
10285  for (k = 1; k < nx; k++) {
10286  lambda1 = cdata[k - 1 + j*nx];
10287  lambda2 = cdata[k + j*nx];
10288  if (lambda1 < 1.0 || lambda2 < 1.0)
10289  continue;
10290  offset = cpl_polynomial_eval_1d(polycorr,
10291  lambda1-refwave, NULL);
10292  cdata[k - 1 + j*nx] -= offset * (lambda2-lambda1);
10293  }
10294  }
10295  }
10296 
10297  cpl_polynomial_delete(polycorr);
10298  }
10299  else if (uorder == 1) {
10300 
10301  /*
10302  * Model offsets with robust linear fitting
10303  */
10304 
10305  double q, m;
10306  cpl_bivector *list;
10307 
10308 
10309  wave = cpl_vector_wrap(npoints,
10310  cpl_table_get_data_double(dummy, "wave"));
10311  offs = cpl_vector_wrap(npoints,
10312  cpl_table_get_data_double(dummy, "offset"));
10313 
10314  list = cpl_bivector_wrap_vectors(wave, offs);
10315 
10316  /*
10317  * Set reference wavelength as zero point
10318  */
10319 
10320  cpl_vector_subtract_scalar(wave, refwave);
10321 
10322  robustLinearFit(list, &q, &m, &rms);
10323 
10324  rms = sqrt(rms * (uorder + 1) / npoints);
10325 
10326  cpl_bivector_unwrap_vectors(list);
10327  cpl_vector_unwrap(wave);
10328  cpl_vector_unwrap(offs);
10329  cpl_table_delete(dummy);
10330 
10331  /*
10332  * Now correct the coefficients of the corresponding IDS
10333  * polynomials related to this slit:
10334  */
10335 
10336  ylow = position[i];
10337  yhig = ylow + length[i];
10338 
10339  for (j = 0; j <= uorder; j++) {
10340  data = cpl_table_get_data_double(idscoeff, clab[j]);
10341  if (j)
10342  c = m;
10343  else
10344  c = q;
10345  for (k = ylow; k < yhig; k++)
10346  data[k] += c;
10347  }
10348 
10349  data = cpl_table_get_data_double(idscoeff, "error");
10350  for (k = ylow; k < yhig; k++)
10351  data[k] = sqrt(data[k]*data[k] + rms*rms);
10352 
10353  idata = cpl_table_get_data_int(idscoeff, "nlines");
10354  for (k = ylow; k < yhig; k++)
10355  idata[k] = npoints;
10356 
10357  /*
10358  * If a wavelengths map was provided, correct it to keep
10359  * into account the alignment to skylines:
10360  */
10361 
10362  if (calibration) {
10363  for (j = ylow; j < yhig; j++) {
10364  for (k = 1; k < nx; k++) {
10365  lambda1 = cdata[k - 1 + j*nx];
10366  lambda2 = cdata[k + j*nx];
10367  if (lambda1 < 1.0 || lambda2 < 1.0)
10368  continue;
10369  offset = q + m*(lambda1-refwave);
10370  cdata[k - 1 + j*nx] -= offset * (lambda2-lambda1);
10371  }
10372  }
10373  }
10374  }
10375  else {
10376 
10377  /*
10378  * Just compute median offset
10379  */
10380 
10381  offs = cpl_vector_wrap(npoints,
10382  cpl_table_get_data_double(dummy, "offset"));
10383 
10384  offset = cpl_vector_get_median_const(offs);
10385 
10386  if (npoints > 1)
10387  rms = cpl_table_get_column_stdev(dummy, "offset");
10388  else
10389  rms = 0.0;
10390 
10391  rms /= sqrt(npoints);
10392 
10393  cpl_vector_unwrap(offs);
10394  cpl_table_delete(dummy);
10395 
10396  /*
10397  * Now correct the constant term of the corresponding IDS
10398  * polynomials related to this slit:
10399  */
10400 
10401  ylow = position[i];
10402  yhig = ylow + length[i];
10403 
10404  data = cpl_table_get_data_double(idscoeff, clab[0]);
10405  for (k = ylow; k < yhig; k++)
10406  data[k] += offset;
10407 
10408  data = cpl_table_get_data_double(idscoeff, "error");
10409  for (k = ylow; k < yhig; k++)
10410  data[k] = sqrt(data[k]*data[k] + rms*rms);
10411 
10412  idata = cpl_table_get_data_int(idscoeff, "nlines");
10413  for (k = ylow; k < yhig; k++)
10414  idata[k] = npoints;
10415 
10416  /*
10417  * If a wavelengths map was provided, correct it to keep
10418  * into account the alignment to skylines. Note that
10419  * the offset must be converted from pixels to wavelengths.
10420  */
10421 
10422  if (calibration) {
10423  for (j = ylow; j < yhig; j++) {
10424  for (k = 1; k < nx; k++) {
10425  lambda1 = cdata[k - 1 + j*nx];
10426  lambda2 = cdata[k + j*nx];
10427  if (lambda1 < 1.0 || lambda2 < 1.0)
10428  continue;
10429  cdata[k - 1 + j*nx] -= offset * (lambda2-lambda1);
10430  }
10431  }
10432  }
10433  }
10434  }
10435 
10436  return offsets;
10437 
10438 }
10439 
10440 
10502 cpl_table *mos_wavelength_align_lss(cpl_image *image, double refwave,
10503  double firstLambda, double lastLambda,
10504  cpl_table *idscoeff, cpl_vector *skylines,
10505  int highres, int order,
10506  cpl_image *calibration, int sradius)
10507 {
10508  const char *func = "mos_wavelength_align_lss";
10509 
10510  const char *clab[6] = {"c0", "c1", "c2", "c3", "c4", "c5"};
10511  /* Max order is 5 */
10512  double *line;
10513  double *data;
10514  double *wdata;
10515  double *odata;
10516  double expPos, offset;
10517  double c;
10518  double lambda1, lambda2;
10519  double rms;
10520  float pos;
10521  float *sdata;
10522  float *cdata;
10523  int *idata;
10524  int startPos, endPos;
10525  int window = 2*sradius + 1;
10526  int nlines;
10527  int npoints;
10528  int nrows;
10529  int nx, ny;
10530  int idsorder, uorder;
10531  int missing;
10532  int i;
10533  cpl_size j, k;
10534 
10535  char name[MAX_COLNAME];
10536  char fname[MAX_COLNAME];
10537 
10538  cpl_polynomial *ids;
10539  cpl_polynomial *polycorr;
10540  cpl_table *offsets;
10541  cpl_table *fittable;
10542  cpl_table *dummy;
10543  cpl_vector *wave;
10544  cpl_vector *offs;
10545  cpl_vector *row;
10546 
10547 
10548  if (idscoeff == NULL) {
10549  cpl_msg_error(func, "An IDS coeff table must be given");
10550  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
10551  return NULL;
10552  }
10553 
10554  if (image == NULL) {
10555  cpl_msg_error(func, "A scientific spectral image must be given");
10556  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
10557  return NULL;
10558  }
10559 
10560  if (skylines) {
10561  line = cpl_vector_get_data(skylines);
10562  nlines = cpl_vector_get_size(skylines);
10563  }
10564  else {
10565  cpl_msg_warning(func, "A catalog of sky lines wavelengths was not "
10566  "given: using internal list of reference sky lines");
10567  if (highres) {
10568  line = default_lines_hi;
10569  nlines = sizeof(default_lines_hi) / sizeof(double);
10570  }
10571  else {
10572  line = default_lines_lo;
10573  nlines = sizeof(default_lines_lo) / sizeof(double);
10574  }
10575  }
10576 
10577  if (calibration)
10578  cdata = cpl_image_get_data(calibration);
10579 
10580  nx = cpl_image_get_size_x(image);
10581  ny = cpl_image_get_size_y(image);
10582 
10583  sdata = cpl_image_get_data(image);
10584 
10585  if (ny != cpl_table_get_nrow(idscoeff)) {
10586  cpl_error_set(func, CPL_ERROR_INCOMPATIBLE_INPUT);
10587  return NULL;
10588  }
10589 
10590 
10591  /*FIXME: This is a remnant of the adaptation of the function
10592  * mos_wavelength_align(), where an offset table was created.
10593  * I leave it here because I am in a hurry, it is just used to
10594  * hold the list of selected sky lines.
10595  *
10596  * Define table of wavelengths
10597  */
10598 
10599  nrows = 0;
10600  for (i = 0; i < nlines; i++)
10601  if (line[i] > firstLambda && line[i] < lastLambda)
10602  nrows++;
10603 
10604  offsets = cpl_table_new(nrows);
10605  cpl_table_new_column(offsets, "wave", CPL_TYPE_DOUBLE);
10606  cpl_table_set_column_unit(offsets, "wave", "Angstrom");
10607 
10608  nrows = 0;
10609  for (i = 0; i < nlines; i++) {
10610  if (line[i] > firstLambda && line[i] < lastLambda) {
10611  cpl_table_set_double(offsets, "wave", nrows, line[i]);
10612  nrows++;
10613  }
10614  }
10615 
10616  /*
10617  * Here "line" is made to point to the new list of selected wavelengths
10618  */
10619 
10620  line = cpl_table_get_data_double(offsets, "wave");
10621  nlines = nrows;
10622 
10623  idsorder = 0;
10624  while (idsorder < 6 && cpl_table_has_column(idscoeff, clab[idsorder]))
10625  ++idsorder;
10626  --idsorder;
10627 
10628 
10629  /*
10630  * Allocate a dummy table for collecting all the offsets
10631  * for all the lines
10632  */
10633 
10634  dummy = cpl_table_new(ny);
10635  for (j = 0; j < nlines; j++) {
10636  snprintf(name, MAX_COLNAME, "off_%d", (int)line[j]);
10637  snprintf(fname, MAX_COLNAME, "fit_%d", (int)line[j]);
10638  cpl_table_new_column(dummy, name, CPL_TYPE_DOUBLE);
10639  cpl_table_new_column(dummy, fname, CPL_TYPE_DOUBLE);
10640  }
10641 
10642  for (j = 0; j < ny; j++, sdata += nx) {
10643 
10644  /*
10645  * Get the IDS polynomial for the current slit row
10646  */
10647 
10648  missing = 0;
10649  ids = cpl_polynomial_new(1);
10650  for (k = 0; k <= idsorder; k++) {
10651  c = cpl_table_get_double(idscoeff, clab[k], j, &missing);
10652  if (missing) {
10653  cpl_polynomial_delete(ids);
10654  break;
10655  }
10656  cpl_polynomial_set_coeff(ids, &k, c);
10657  }
10658  if (missing)
10659  continue;
10660 
10661  for (k = 0; k < nlines; k++) {
10662  expPos = cpl_polynomial_eval_1d(ids, line[k] - refwave, NULL);
10663  startPos = expPos - sradius;
10664  endPos = startPos + window;
10665  if (startPos < 0 || endPos >= nx)
10666  continue;
10667 
10668  if (0 == peakPosition(sdata + startPos, window, &pos, 1)) {
10669  pos += startPos;
10670  offset = pos - expPos;
10671  snprintf(name, MAX_COLNAME, "off_%d", (int)line[k]);
10672  cpl_table_set_double(dummy, name, j, offset);
10673  }
10674  }
10675 
10676  cpl_polynomial_delete(ids);
10677  }
10678 
10679 
10680  /*
10681  * At this point for each sky line we model its offset along
10682  * the image rows using a robust linear fitting
10683  */
10684 
10685  for (j = 0; j < nlines; j++) {
10686  snprintf(name, MAX_COLNAME, "off_%d", (int)line[j]);
10687  snprintf(fname, MAX_COLNAME, "fit_%d", (int)line[j]);
10688  if (cpl_table_has_valid(dummy, name)) {
10689 
10690  /*
10691  * In the following, the "fittable" is just a tool for
10692  * eliminating invalid points from the vectors to be fitted.
10693  */
10694 
10695  double q, m;
10696  cpl_bivector *list;
10697 
10698  fittable = cpl_table_new(ny);
10699  cpl_table_new_column(fittable, "row", CPL_TYPE_DOUBLE);
10700  cpl_table_set_column_unit(fittable, "row", "pixel");
10701  for (k = 0; k < ny; k++)
10702  cpl_table_set_double(fittable, "row", k, k);
10703  cpl_table_duplicate_column(fittable, "offset", dummy, name);
10704  npoints = ny - cpl_table_count_invalid(fittable, "offset");
10705  cpl_table_erase_invalid(fittable);
10706  row = cpl_vector_wrap(npoints,
10707  cpl_table_get_data_double(fittable, "row"));
10708  offs = cpl_vector_wrap(npoints,
10709  cpl_table_get_data_double(fittable, "offset"));
10710  list = cpl_bivector_wrap_vectors(row, offs);
10711  robustLinearFit(list, &q, &m, &rms);
10712  cpl_bivector_unwrap_vectors(list);
10713  cpl_vector_unwrap(row);
10714  cpl_vector_unwrap(offs);
10715  cpl_table_delete(fittable);
10716  for (k = 0; k < ny; k++)
10717  cpl_table_set_double(dummy, fname, k, q + m*k);
10718  }
10719  }
10720 
10721 
10722  /*
10723  * Now each dummy table row consists of a sequence of offsets,
10724  * one for each wavelength. A table row corresponds to an image row.
10725  * We must fit a polynomial to each one of these rows, in order to
10726  * express the offsets as a function of wavelength. The obtained
10727  * polynomial coefficients are used to correct the IDS coefficients.
10728  */
10729 
10730  for (i = 0; i < ny; i++) {
10731 
10732  if (!cpl_table_is_valid(idscoeff, clab[0], i))
10733  continue;
10734 
10735  npoints = 0;
10736  for (j = 0; j < nlines; j++) {
10737  snprintf(name, MAX_COLNAME, "fit_%d", (int)line[j]);
10738  if (cpl_table_is_valid(dummy, name, i))
10739  npoints++;
10740  }
10741 
10742  if (npoints == 0)
10743  continue;
10744 
10745  uorder = order;
10746  if (npoints <= uorder)
10747  uorder = npoints - 1;
10748 
10749  if (uorder > 1) {
10750 
10751  /*
10752  * Model offsets with polynomial fitting
10753  */
10754 
10755  wave = cpl_vector_new(npoints);
10756  wdata = cpl_vector_get_data(wave);
10757  offs = cpl_vector_new(npoints);
10758  odata = cpl_vector_get_data(offs);
10759 
10760  npoints = 0;
10761  for (j = 0; j < nlines; j++) {
10762  snprintf(name, MAX_COLNAME, "fit_%d", (int)line[j]);
10763  if (cpl_table_is_valid(dummy, name, i)) {
10764  wdata[npoints] = line[j] - refwave;
10765  odata[npoints] = cpl_table_get_double(dummy, name, i, NULL);
10766  npoints++;
10767  }
10768  }
10769 
10770  polycorr = cpl_polynomial_fit_1d_create(wave, offs, uorder, &rms);
10771 
10772  rms = sqrt(rms * (uorder + 1) / npoints);
10773 
10774  cpl_vector_delete(wave);
10775  cpl_vector_delete(offs);
10776 
10777  /*
10778  * Now correct the coefficients of the corresponding IDS
10779  * polynomials related to this slit:
10780  */
10781 
10782  for (j = 0; j <= uorder; j++) {
10783  data = cpl_table_get_data_double(idscoeff, clab[j]);
10784  c = cpl_polynomial_get_coeff(polycorr, &j);
10785  data[i] += c;
10786  }
10787 
10788  data = cpl_table_get_data_double(idscoeff, "error");
10789  data[i] = sqrt(data[i]*data[i] + rms*rms);
10790 
10791  idata = cpl_table_get_data_int(idscoeff, "nlines");
10792  idata[i] = npoints;
10793 
10794  /*
10795  * If a wavelengths map was provided, correct it to keep
10796  * into account the alignment to skylines:
10797  */
10798 
10799  if (calibration) {
10800  for (k = 1; k < nx; k++) {
10801  lambda1 = cdata[k - 1 + i*nx];
10802  lambda2 = cdata[k + i*nx];
10803  if (lambda1 < 1.0 || lambda2 < 1.0)
10804  continue;
10805  offset = cpl_polynomial_eval_1d(polycorr,
10806  lambda1-refwave, NULL);
10807  cdata[k - 1 + i*nx] -= offset * (lambda2-lambda1);
10808  }
10809  }
10810 
10811  cpl_polynomial_delete(polycorr);
10812 
10813  }
10814  else if (uorder == 1) {
10815 
10816  /*
10817  * Model offsets with robust linear fitting
10818  */
10819 
10820  cpl_bivector *list;
10821  double q, m;
10822 
10823  wave = cpl_vector_new(npoints);
10824  wdata = cpl_vector_get_data(wave);
10825  offs = cpl_vector_new(npoints);
10826  odata = cpl_vector_get_data(offs);
10827 
10828  npoints = 0;
10829  for (j = 0; j < nlines; j++) {
10830  snprintf(name, MAX_COLNAME, "fit_%d", (int)line[j]);
10831  if (cpl_table_is_valid(dummy, name, i)) {
10832  wdata[npoints] = line[j] - refwave;
10833  odata[npoints] = cpl_table_get_double(dummy, name, i, NULL);
10834  npoints++;
10835  }
10836  }
10837 
10838  list = cpl_bivector_wrap_vectors(wave, offs);
10839  robustLinearFit(list, &q, &m, &rms);
10840 
10841  rms = sqrt(rms * (uorder + 1) / npoints);
10842 
10843  cpl_bivector_unwrap_vectors(list);
10844  cpl_vector_delete(wave);
10845  cpl_vector_delete(offs);
10846 
10847  /*
10848  * Now correct the coefficients of the corresponding IDS
10849  * polynomials related to this row:
10850  */
10851 
10852  for (j = 0; j <= uorder; j++) {
10853  data = cpl_table_get_data_double(idscoeff, clab[j]);
10854  if (j)
10855  c = m;
10856  else
10857  c = q;
10858  data[i] += c;
10859  }
10860 
10861  data = cpl_table_get_data_double(idscoeff, "error");
10862  data[i] = sqrt(data[i]*data[i] + rms*rms);
10863 
10864  idata = cpl_table_get_data_int(idscoeff, "nlines");
10865  idata[i] = npoints;
10866 
10867  /*
10868  * If a wavelengths map was provided, correct it to keep
10869  * into account the alignment to skylines:
10870  */
10871 
10872  if (calibration) {
10873  for (k = 1; k < nx; k++) {
10874  lambda1 = cdata[k - 1 + i*nx];
10875  lambda2 = cdata[k + i*nx];
10876  if (lambda1 < 1.0 || lambda2 < 1.0)
10877  continue;
10878  offset = q + m*(lambda1-refwave);
10879  cdata[k - 1 + i*nx] -= offset * (lambda2-lambda1);
10880  }
10881  }
10882  }
10883  else {
10884 
10885  /*
10886  * Just compute median offset
10887  */
10888 
10889  offs = cpl_vector_new(npoints);
10890  odata = cpl_vector_get_data(offs);
10891 
10892  npoints = 0;
10893  for (j = 0; j < nlines; j++) {
10894  snprintf(name, MAX_COLNAME, "fit_%d", (int)line[j]);
10895  if (cpl_table_is_valid(dummy, name, i)) {
10896  odata[npoints] = cpl_table_get_double(dummy, name, i, NULL);
10897  npoints++;
10898  }
10899  }
10900 
10901  offset = cpl_vector_get_median_const(offs);
10902 
10903  if (npoints > 1) {
10904  rms = cpl_vector_get_stdev(offs);
10905  }
10906  else if (npoints == 1) {
10907  snprintf(name, MAX_COLNAME, "off_%d", (int)line[0]);
10908  if (cpl_table_has_valid(dummy, name)) {
10909  rms = cpl_table_get_column_stdev(dummy, name);
10910  rms /= sqrt(ny - cpl_table_count_invalid(dummy, name));
10911  }
10912  else {
10913  rms = 0.0;
10914  }
10915  }
10916  else {
10917  rms = 0.0;
10918  }
10919 
10920  rms /= sqrt(npoints);
10921 
10922  cpl_vector_delete(offs);
10923 
10924  /*
10925  * Now correct the constant term of the corresponding IDS
10926  * polynomials related to this slit:
10927  */
10928 
10929  data = cpl_table_get_data_double(idscoeff, clab[0]);
10930  data[i] += offset;
10931 
10932  data = cpl_table_get_data_double(idscoeff, "error");
10933  data[i] = sqrt(data[i]*data[i] + rms*rms);
10934 
10935  idata = cpl_table_get_data_int(idscoeff, "nlines");
10936  idata[i] = npoints;
10937 
10938  /*
10939  * If a wavelengths map was provided, correct it to keep
10940  * into account the alignment to skylines. Note that
10941  * the offset must be converted from pixels to wavelengths.
10942  */
10943 
10944  if (calibration) {
10945  for (k = 1; k < nx; k++) {
10946  lambda1 = cdata[k - 1 + i*nx];
10947  lambda2 = cdata[k + i*nx];
10948  if (lambda1 < 1.0 || lambda2 < 1.0)
10949  continue;
10950  cdata[k - 1 + i*nx] -= offset * (lambda2-lambda1);
10951  }
10952  }
10953  }
10954  }
10955 
10956  missing = 1;
10957  for (j = 0; j < nlines; j++) {
10958  snprintf(name, MAX_COLNAME, "off_%d", (int)line[j]);
10959  if (cpl_table_has_valid(dummy, name)) {
10960  missing = 0;
10961  offset = cpl_table_get_column_median(dummy, name);
10962  cpl_msg_info(func, "Median offset for %.3f: %.3f pixel",
10963  line[j], offset);
10964  }
10965  else {
10966  cpl_msg_info(func,
10967  "Median offset for %.2f: not available", line[j]);
10968  }
10969  }
10970 
10971  cpl_table_delete(offsets);
10972 
10973  if (missing) {
10974  cpl_table_delete(dummy);
10975  dummy = NULL;
10976  }
10977 
10978  return dummy;
10979 
10980 }
10981 
10982 
11010 double mos_distortions_rms(cpl_image *rectified, cpl_vector *lines,
11011  double wavestart, double dispersion, int radius,
11012  int highres)
11013 {
11014 
11015  const char *func = "mos_distortions_rms";
11016 
11017  int xlen;
11018  int ylen;
11019  int numLines;
11020  int cpix, npix, nzero;
11021  int sp, ep;
11022  int i, j, k;
11023  int npeaks, allPeaks;
11024 
11025  float *profile;
11026  float peak, expectPeak, offset;
11027  double lambda;
11028 
11029  double average;
11030  double rms, oneRms;
11031 
11032  float *sdata;
11033  double *wdata;
11034 
11035 
11036  xlen = cpl_image_get_size_x(rectified);
11037  ylen = cpl_image_get_size_y(rectified);
11038  sdata = cpl_image_get_data(rectified);
11039 
11040  if (lines) {
11041  wdata = cpl_vector_get_data(lines);
11042  numLines = cpl_vector_get_size(lines);
11043  }
11044  else {
11045  cpl_msg_warning(func, "A catalog of sky lines wavelengths was not "
11046  "given: using internal list of reference sky lines");
11047  if (highres) {
11048  wdata = default_lines_hi;
11049  numLines = sizeof(default_lines_hi) / sizeof(double);
11050  }
11051  else {
11052  wdata = default_lines_lo;
11053  numLines = sizeof(default_lines_lo) / sizeof(double);
11054  }
11055  }
11056 
11057  npix = 2 * radius + 1;
11058  profile = cpl_calloc(npix, sizeof(float));
11059 
11060  rms = 0.0;
11061  allPeaks = 0;
11062 
11063  for (i = 0; i < numLines; i++) {
11064 
11065  /*
11066  * Expected peak and closest pixel to specified wavelength.
11067  */
11068 
11069  lambda = wdata[i];
11070  expectPeak = (lambda - wavestart) / dispersion;
11071  cpix = floor(expectPeak + 0.5);
11072 
11073  /*
11074  * Search interval for peak. Abort if too close to image border.
11075  */
11076 
11077  sp = cpix - radius;
11078  ep = cpix + radius;
11079 
11080  if (sp < 0 || ep > xlen)
11081  continue;
11082 
11083  average = 0.0;
11084  npeaks = 0;
11085  oneRms = 0.0;
11086 
11087  for (j = 0; j < ylen; j++) { /* For each row of each slit */
11088  nzero = 0;
11089  for (k = 0; k < npix; k++) {
11090  profile[k] = sdata[sp + k + j * xlen];
11091  if (fabs(profile[k]) < 0.0001)
11092  nzero++; /* Count number of 0 pixels (spectrum truncated) */
11093  }
11094  if (nzero > 0)
11095  continue;
11096 
11097  if (peakPosition(profile, npix, &peak, 1) == 0) {
11098  offset = (sp + peak) - expectPeak;
11099  average += offset;
11100  rms += fabs(offset);
11101  oneRms += fabs(offset);
11102  npeaks++;
11103  allPeaks++;
11104  }
11105  }
11106 
11107  if (npeaks)
11108  cpl_msg_info(func, "RMS for %.2f: %.3f pixel (%d points)",
11109  lambda, oneRms / npeaks * 1.25, npeaks);
11110  else
11111  cpl_msg_info(func, "RMS for %.2f: line not available", lambda);
11112  }
11113 
11114  cpl_free(profile);
11115 
11116  if (allPeaks < 10)
11117  return 0.0;
11118 
11119  rms /= allPeaks;
11120  rms *= 1.25; /* Factor to convert average deviation to sigma */
11121 
11122  return rms;
11123 
11124 }
11125 
11126 
11147 cpl_image *mos_map_pixel(cpl_table *idscoeff, double reference,
11148  double blue, double red, double dispersion, int trend)
11149 {
11150  const char *func = "mos_map_pixel";
11151 
11152  const char *clab[6] = {"c0", "c1", "c2", "c3", "c4", "c5"};
11153  /* Max order is 5 */
11154 
11155  cpl_polynomial *ids;
11156  cpl_image *map;
11157  float *mdata;
11158  double lambda;
11159  double c;
11160  int order;
11161  int xsize, ysize;
11162  int missing;
11163  int i, j;
11164  cpl_size k;
11165 
11166 
11167  if (idscoeff == NULL) {
11168  cpl_msg_error(func, "An IDS coeff table must be given");
11169  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
11170  return NULL;
11171  }
11172 
11173  xsize = (red - blue) / dispersion;
11174  ysize = cpl_table_get_nrow(idscoeff);
11175  map = cpl_image_new(xsize, ysize, CPL_TYPE_FLOAT);
11176  mdata = cpl_image_get_data(map);
11177 
11178  order = 0;
11179  while (order < 6 && cpl_table_has_column(idscoeff, clab[order]))
11180  ++order;
11181  --order;
11182 
11183  for (i = 0; i < ysize; i++, mdata += xsize) {
11184 
11185  missing = 0;
11186  ids = cpl_polynomial_new(1);
11187  for (k = trend; k <= order; k++) {
11188  c = cpl_table_get_double(idscoeff, clab[k], i, &missing);
11189  if (missing) {
11190  cpl_polynomial_delete(ids);
11191  break;
11192  }
11193  cpl_polynomial_set_coeff(ids, &k, c);
11194  }
11195  if (missing)
11196  continue;
11197 
11198  for (j = 0; j < xsize; j++) {
11199  lambda = blue + j*dispersion;
11200  mdata[j] = cpl_polynomial_eval_1d(ids, lambda-reference, NULL);
11201  }
11202 
11203  cpl_polynomial_delete(ids);
11204  }
11205 
11206  return map;
11207 
11208 }
11209 
11210 
11232 cpl_image *mos_map_idscoeff(cpl_table *idscoeff, int xsize, double reference,
11233  double blue, double red)
11234 {
11235  const char *func = "mos_map_idscoeff";
11236 
11237  const char *clab[6] = {"c0", "c1", "c2", "c3", "c4", "c5"};
11238  /* Max order is 5 */
11239 
11240  cpl_polynomial *ids;
11241  cpl_image *map;
11242  float *mdata;
11243  double lambda;
11244  double c;
11245  int order;
11246  int ysize;
11247  int missing;
11248  int i, j;
11249  cpl_size k;
11250 
11251 
11252  if (idscoeff == NULL) {
11253  cpl_msg_error(func, "An IDS coeff table must be given");
11254  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
11255  return NULL;
11256  }
11257 
11258  if (xsize < 1) {
11259  cpl_msg_error(func, "Invalid image size");
11260  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
11261  return NULL;
11262  }
11263 
11264  if (xsize < 20 || xsize > 5000) {
11265  cpl_msg_warning(func, "Do you really have a detector %d pixels long?",
11266  xsize);
11267  }
11268 
11269  ysize = cpl_table_get_nrow(idscoeff);
11270  map = cpl_image_new(xsize, ysize, CPL_TYPE_FLOAT);
11271  mdata = cpl_image_get_data(map);
11272 
11273  order = 0;
11274  while (order < 6 && cpl_table_has_column(idscoeff, clab[order]))
11275  ++order;
11276  --order;
11277 
11278  for (i = 0; i < ysize; i++, mdata += xsize) {
11279 
11280  missing = 0;
11281  ids = cpl_polynomial_new(1);
11282  for (k = 0; k <= order; k++) {
11283  c = cpl_table_get_double(idscoeff, clab[k], i, &missing);
11284  if (missing) {
11285  cpl_polynomial_delete(ids);
11286  break;
11287  }
11288  cpl_polynomial_set_coeff(ids, &k, c);
11289  }
11290  if (missing)
11291  continue;
11292 
11293  for (j = 0; j < xsize; j++) {
11294  lambda = mos_eval_dds(ids, blue, red, reference, j);
11295 
11296  if (lambda >= blue && lambda <= red) {
11297  mdata[j] = lambda;
11298  }
11299  }
11300 
11301  cpl_polynomial_delete(ids);
11302  }
11303 
11304  return map;
11305 
11306 }
11307 
11308 
11343 cpl_image *mos_map_wavelengths(cpl_image *spatial, cpl_image *calibration,
11344  cpl_table *slits, cpl_table *polytraces,
11345  double reference, double blue, double red,
11346  double dispersion)
11347 {
11348  const char *func = "mos_map_wavelengths";
11349 
11350  const char *clab[6] = {"c0", "c1", "c2", "c3", "c4", "c5"};
11351  /* Max order is 5 */
11352  cpl_polynomial *polytop;
11353  cpl_polynomial *polybot;
11354  cpl_image *remapped;
11355  float *data;
11356  float *wdata;
11357  float *sdata;
11358  float *xdata;
11359  double vtop, vbot, value;
11360  double top, bot;
11361  double coeff;
11362  double ytop, ybot;
11363  double ypos;
11364  double fvalue;
11365  int ivalue;
11366  int yint, ysize, yprev;
11367  int nslits;
11368  int npseudo;
11369  int *slit_id;
11370  int *position;
11371  int *length;
11372  int nx, ny;
11373  int pixel_above, pixel_below, refpixel, start_pixel, end_pixel;
11374  int missing_top, missing_bot;
11375  int null;
11376  int order;
11377  int i, j;
11378  cpl_size k;
11379 
11380 
11381  if (spatial == NULL || calibration == NULL ||
11382  slits == NULL || polytraces == NULL) {
11383  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
11384  return NULL;
11385  }
11386 
11387  if (dispersion <= 0.0) {
11388  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
11389  return NULL;
11390  }
11391 
11392  if (red - blue < dispersion) {
11393  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
11394  return NULL;
11395  }
11396 
11397  nx = cpl_image_get_size_x(spatial);
11398  ny = cpl_image_get_size_y(spatial);
11399  ysize = cpl_image_get_size_y(calibration);
11400  remapped = cpl_image_new(nx, ny, CPL_TYPE_FLOAT);
11401  data = cpl_image_get_data(remapped);
11402  sdata = cpl_image_get_data(spatial);
11403  wdata = cpl_image_get_data(calibration);
11404 
11405  nslits = cpl_table_get_nrow(slits);
11406  slit_id = cpl_table_get_data_int(slits, "slit_id");
11407  order = cpl_table_get_ncol(polytraces) - 2;
11408  position = cpl_table_get_data_int(slits, "position");
11409  length = cpl_table_get_data_int(slits, "length");
11410 
11411  /*
11412  * The spatial resampling is performed for a certain number of
11413  * pixels above and below the position of the reference wavelength:
11414  */
11415 
11416  pixel_above = STRETCH_FACTOR * (red - reference) / dispersion;
11417  pixel_below = STRETCH_FACTOR * (reference - blue) / dispersion;
11418 
11419  for (i = 0; i < nslits; i++) {
11420 
11421  if (length[i] == 0)
11422  continue;
11423 
11424  /*
11425  * Note that the x coordinate of the reference pixels on the CCD
11426  * is taken arbitrarily at the top end of each slit. This wouldn't
11427  * be entirely correct in case of curved slits, or in presence of
11428  * heavy distortions: in such cases the spatial resampling is
11429  * really performed across a wide range of wavelengths. But
11430  * the lag between top and bottom spectral curvature models
11431  * would introduce even in such cases negligible effects on
11432  * the spectral spatial resampling.
11433  */
11434 
11435  refpixel = cpl_table_get_double(slits, "xtop", i, NULL);
11436 
11437  start_pixel = refpixel - pixel_below;
11438  if (start_pixel < 0)
11439  start_pixel = 0;
11440 
11441  end_pixel = refpixel + pixel_above;
11442  if (end_pixel > nx)
11443  end_pixel = nx;
11444 
11445  /*
11446  * Recover from the table of spectral curvature coefficients
11447  * the curvature polynomials.
11448  */
11449 
11450  missing_top = 0;
11451  polytop = cpl_polynomial_new(1);
11452  for (k = 0; k <= order; k++) {
11453  coeff = cpl_table_get_double(polytraces, clab[k], 2*i, &null);
11454  if (null) {
11455  cpl_polynomial_delete(polytop);
11456  missing_top = 1;
11457  break;
11458  }
11459  cpl_polynomial_set_coeff(polytop, &k, coeff);
11460  }
11461 
11462  missing_bot = 0;
11463  polybot = cpl_polynomial_new(1);
11464  for (k = 0; k <= order; k++) {
11465  coeff = cpl_table_get_double(polytraces, clab[k], 2*i+1, &null);
11466  if (null) {
11467  cpl_polynomial_delete(polybot);
11468  missing_bot = 1;
11469  break;
11470  }
11471  cpl_polynomial_set_coeff(polybot, &k, coeff);
11472  }
11473 
11474  if (missing_top && missing_bot) {
11475  cpl_msg_debug(func, "Slit %d was not traced: no extraction!",
11476  slit_id[i]);
11477  continue;
11478  }
11479 
11480  /*
11481  * In case just one of the two edges was not traced, the other
11482  * edge curvature model is duplicated and shifted to the other
11483  * end of the slit: better than nothing!
11484  */
11485 
11486  if (missing_top) {
11487  cpl_msg_debug(func, "Upper edge of slit %d was not traced: "
11488  "the spectral curvature of the lower edge "
11489  "is used instead.", slit_id[i]);
11490  polytop = cpl_polynomial_duplicate(polybot);
11491  ytop = cpl_table_get_double(slits, "ytop", i, NULL);
11492  ybot = cpl_table_get_double(slits, "ybottom", i, NULL);
11493  k = 0;
11494  coeff = cpl_polynomial_get_coeff(polybot, &k);
11495  coeff += ytop - ybot;
11496  cpl_polynomial_set_coeff(polytop, &k, coeff);
11497  }
11498 
11499  if (missing_bot) {
11500  cpl_msg_debug(func, "Lower edge of slit %d was not traced: "
11501  "the spectral curvature of the upper edge "
11502  "is used instead.", slit_id[i]);
11503  polybot = cpl_polynomial_duplicate(polytop);
11504  ytop = cpl_table_get_double(slits, "ytop", i, NULL);
11505  ybot = cpl_table_get_double(slits, "ybottom", i, NULL);
11506  k = 0;
11507  coeff = cpl_polynomial_get_coeff(polytop, &k);
11508  coeff -= ytop - ybot;
11509  cpl_polynomial_set_coeff(polybot, &k, coeff);
11510  }
11511 
11512  /*
11513  * Point to current slit on wavelength calibration image.
11514  * Note that the npseudo value related to this slit is equal
11515  * to the number of spatial pseudo-pixels decreased by 1
11516  * (compare with function mos_spatial_calibration()).
11517  */
11518 
11519  xdata = wdata + nx*position[i];
11520  npseudo = length[i] - 1;
11521 
11522  /*
11523  * Write interpolated wavelengths to CCD image
11524  */
11525 
11526  for (j = start_pixel; j < end_pixel; j++) {
11527  top = cpl_polynomial_eval_1d(polytop, j, NULL);
11528  bot = cpl_polynomial_eval_1d(polybot, j, NULL);
11529  for (k = 0; k <= npseudo; k++) {
11530  ypos = top - k*(top-bot)/npseudo;
11531  yint = ypos;
11532 
11533  /*
11534  * The line:
11535  * value = sdata[j + nx*yint];
11536  * should be equivalent to:
11537  * value = npseudo*(top-yint)/(top-bot);
11538  */
11539 
11540  if (yint < 0 || yint >= ny-1) {
11541  yprev = yint;
11542  continue;
11543  }
11544 
11545  value = sdata[j + nx*yint]; /* Spatial coordinate on CCD */
11546  ivalue = value; /* Nearest spatial pixels: */
11547  fvalue = value - ivalue; /* ivalue and ivalue+1 */
11548  if (ivalue < npseudo && ivalue >= 0) {
11549  vtop = xdata[j + nx*(npseudo-ivalue)];
11550  vbot = xdata[j + nx*(npseudo-ivalue-1)];
11551  if (vtop < 1.0) { /* Impossible wavelength */
11552  if (vbot < 1.0) {
11553  value = 0.0;
11554  }
11555  else {
11556  value = vbot;
11557  }
11558  }
11559  else if (vbot < 1.0) {
11560  if (k)
11561  value = vtop;
11562  else
11563  value = 0.0;
11564  }
11565  else if (fabs(vbot-vtop) > 10*dispersion) {
11566  value = 0.0;
11567  }
11568  else {
11569  value = vtop*(1-fvalue) + vbot*fvalue;
11570  }
11571  data[j + nx*yint] = value;
11572 
11573  if (k) {
11574 
11575  /*
11576  * This is added to recover lost pixels on
11577  * the CCD image (pixels are lost because
11578  * the CCD pixels are less than npseudo+1).
11579  */
11580 
11581  if (yprev - yint > 1) {
11582  value = sdata[j + nx*(yint+1)];
11583  ivalue = value;
11584  fvalue = value - ivalue;
11585  if (ivalue < npseudo && ivalue >= 0) {
11586  vtop = xdata[j + nx*(npseudo-ivalue)];
11587  vbot = xdata[j + nx*(npseudo-ivalue-1)];
11588  if (vtop < 1.0) {
11589  if (vbot < 1.0) {
11590  value = data[j + nx*(yint+1)];
11591  }
11592  else {
11593  value = vbot;
11594  }
11595  }
11596  else if (vbot < 1.0) {
11597  value = vtop;
11598  }
11599  else if (fabs(vbot-vtop) > 2*dispersion) {
11600  value = vtop;
11601  }
11602  else {
11603  value = vtop*(1-fvalue) + vbot*fvalue;
11604  }
11605  data[j + nx*(yint+1)] = value;
11606  }
11607  }
11608  }
11609  }
11610  yprev = yint;
11611  }
11612  }
11613  cpl_polynomial_delete(polytop);
11614  cpl_polynomial_delete(polybot);
11615  }
11616 
11617  return remapped;
11618 }
11619 
11693 cpl_image *mos_map_spectrum(cpl_image *spectra, cpl_image *wavecalib,
11694  cpl_image *spatial, cpl_table *slits,
11695  cpl_table *polytraces, double reference,
11696  double blue, double red, double dispersion,
11697  int flux)
11698 {
11699  const char *func = "mos_map_spectrum";
11700 
11701  const char *clab[6] = {"c0", "c1", "c2", "c3", "c4", "c5"};
11702  /* Max order is 5 */
11703  cpl_polynomial *polytop;
11704  cpl_polynomial *polybot;
11705  cpl_image *remapped;
11706  cpl_image **exslit;
11707  float *data;
11708  float *wdata;
11709  float *sdata;
11710  float *xdata;
11711  double lambda00, lambda01, lambda10, lambda11, lambda;
11712  double space00, space01, space10, space11, space;
11713  double value00, value01, value10, value11, value0, value1, value;
11714  double dL, dS;
11715  double top, bot;
11716  double coeff;
11717  double ytop, ybot;
11718  double xfrac, yfrac;
11719  int yint, ysize;
11720  int itop, ibot;
11721  int shift;
11722  int L, S;
11723  int nslits;
11724  int npseudo;
11725  int *slit_id;
11726  int *position;
11727  int *length;
11728  int nx, ny;
11729  int x, y;
11730  int nlambda;
11731  int pixel_above, pixel_below, refpixel, start_pixel, end_pixel;
11732  int missing_top, missing_bot;
11733  int null;
11734  int order;
11735  int i;
11736  cpl_size k;
11737 
11738 
11739  flux += flux;
11740 
11741  if (spectra == NULL || spatial == NULL || wavecalib == NULL ||
11742  slits == NULL || polytraces == NULL) {
11743  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
11744  return NULL;
11745  }
11746 
11747  if (dispersion <= 0.0) {
11748  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
11749  return NULL;
11750  }
11751 
11752  if (red - blue < dispersion) {
11753  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
11754  return NULL;
11755  }
11756 
11757  nx = cpl_image_get_size_x(spectra);
11758  ny = cpl_image_get_size_y(spectra);
11759 
11760  if (nx != cpl_image_get_size_x(spatial) ||
11761  ny != cpl_image_get_size_y(spatial) ||
11762  nx != cpl_image_get_size_x(wavecalib) ||
11763  ny != cpl_image_get_size_y(wavecalib)) {
11764  cpl_error_set(func, CPL_ERROR_INCOMPATIBLE_INPUT);
11765  return NULL;
11766  }
11767 
11768  nlambda = STRETCH_FACTOR * (red - blue) / dispersion;
11769  pixel_above = STRETCH_FACTOR * (red - reference) / dispersion;
11770  pixel_below = STRETCH_FACTOR * (reference - blue) / dispersion;
11771 
11772  data = cpl_image_get_data(spectra);
11773  sdata = cpl_image_get_data(spatial);
11774  wdata = cpl_image_get_data(wavecalib);
11775 
11776  nslits = cpl_table_get_nrow(slits);
11777  slit_id = cpl_table_get_data_int(slits, "slit_id");
11778  order = cpl_table_get_ncol(polytraces) - 2;
11779  position = cpl_table_get_data_int(slits, "position");
11780  length = cpl_table_get_data_int(slits, "length");
11781 
11782  exslit = cpl_calloc(nslits, sizeof(cpl_image *));
11783 
11784  for (i = 0; i < nslits; i++) {
11785 
11786  if (length == 0)
11787  continue;
11788 
11789  /*
11790  * Note that the x coordinate of the reference pixels on the CCD
11791  * is taken arbitrarily at the top end of each slit. This wouldn't
11792  * be entirely correct in case of curved slits, or in presence of
11793  * heavy distortions: in such cases the spatial resampling is
11794  * really performed across a wide range of wavelengths. But
11795  * the lag between top and bottom spectral curvature models
11796  * would introduce even in such cases negligible effects on
11797  * the spectral spatial resampling.
11798  */
11799 
11800  refpixel = cpl_table_get_double(slits, "xtop", i, NULL);
11801 
11802  start_pixel = refpixel - pixel_below;
11803  if (start_pixel < 1)
11804  start_pixel = 1;
11805 
11806  end_pixel = refpixel + pixel_above;
11807  if (end_pixel > nx)
11808  end_pixel = nx;
11809 
11810  /*
11811  * Recover from the table of spectral curvature coefficients
11812  * the curvature polynomials.
11813  */
11814 
11815  missing_top = 0;
11816  polytop = cpl_polynomial_new(1);
11817  for (k = 0; k <= order; k++) {
11818  coeff = cpl_table_get_double(polytraces, clab[k], 2*i, &null);
11819  if (null) {
11820  cpl_polynomial_delete(polytop);
11821  missing_top = 1;
11822  break;
11823  }
11824  cpl_polynomial_set_coeff(polytop, &k, coeff);
11825  }
11826 
11827  missing_bot = 0;
11828  polybot = cpl_polynomial_new(1);
11829  for (k = 0; k <= order; k++) {
11830  coeff = cpl_table_get_double(polytraces, clab[k], 2*i+1, &null);
11831  if (null) {
11832  cpl_polynomial_delete(polybot);
11833  missing_bot = 1;
11834  break;
11835  }
11836  cpl_polynomial_set_coeff(polybot, &k, coeff);
11837  }
11838 
11839  if (missing_top && missing_bot) {
11840  cpl_msg_debug(func, "Slit %d was not traced: no extraction!",
11841  slit_id[i]);
11842  continue;
11843  }
11844 
11845  /*
11846  * In case just one of the two edges was not traced, the other
11847  * edge curvature model is duplicated and shifted to the other
11848  * end of the slit: better than nothing!
11849  */
11850 
11851  if (missing_top) {
11852  cpl_msg_debug(func, "Upper edge of slit %d was not traced: "
11853  "the spectral curvature of the lower edge "
11854  "is used instead.", slit_id[i]);
11855  polytop = cpl_polynomial_duplicate(polybot);
11856  ytop = cpl_table_get_double(slits, "ytop", i, NULL);
11857  ybot = cpl_table_get_double(slits, "ybottom", i, NULL);
11858  k = 0;
11859  coeff = cpl_polynomial_get_coeff(polybot, &k);
11860  coeff += ytop - ybot;
11861  cpl_polynomial_set_coeff(polytop, &k, coeff);
11862  }
11863 
11864  if (missing_bot) {
11865  cpl_msg_debug(func, "Lower edge of slit %d was not traced: "
11866  "the spectral curvature of the upper edge "
11867  "is used instead.", slit_id[i]);
11868  polybot = cpl_polynomial_duplicate(polytop);
11869  ytop = cpl_table_get_double(slits, "ytop", i, NULL);
11870  ybot = cpl_table_get_double(slits, "ybottom", i, NULL);
11871  k = 0;
11872  coeff = cpl_polynomial_get_coeff(polytop, &k);
11873  coeff -= ytop - ybot;
11874  cpl_polynomial_set_coeff(polybot, &k, coeff);
11875  }
11876 
11877  /*
11878  * Allocate image for current extracted slit
11879  */
11880 
11881  top = cpl_polynomial_eval_1d(polytop, refpixel, NULL);
11882  bot = cpl_polynomial_eval_1d(polybot, refpixel, NULL);
11883  npseudo = ceil(top-bot) + 1;
11884 
11885  if (npseudo < 1) {
11886  cpl_polynomial_delete(polytop);
11887  cpl_polynomial_delete(polybot);
11888  cpl_msg_debug(func, "Slit %d was badly traced: no extraction!",
11889  slit_id[i]);
11890  continue;
11891  }
11892 
11893  exslit[i] = cpl_image_new(nlambda, npseudo+1, CPL_TYPE_FLOAT);
11894  xdata = cpl_image_get_data(exslit[i]);
11895 
11896  /*
11897  * Write interpolated spectral values to remapped slit spectrum.
11898  */
11899 
11900  for (x = start_pixel; x < end_pixel; x++) {
11901  top = cpl_polynomial_eval_1d(polytop, x, NULL);
11902  bot = cpl_polynomial_eval_1d(polybot, x, NULL);
11903  itop = top + 1;
11904  ibot = bot;
11905  if (itop < 0)
11906  itop = 0;
11907  if (itop > ny - 1)
11908  itop = ny - 1;
11909  if (ibot < 0)
11910  ibot = 0;
11911  if (ibot > ny - 1)
11912  ibot = ny - 1;
11913  for (y = ibot; y < itop; y++) {
11914  lambda11 = wdata[x + y*nx];
11915  if (lambda11 < 1.0) /* Impossible wavelength */
11916  continue;
11917  space11 = sdata[x + y*nx];
11918  if (space11 < 0.0) /* Impossible spatial coordinate */
11919  continue;
11920  lambda01 = wdata[x - 1 + y*nx];
11921  if (lambda01 < 1.0) /* Impossible wavelength */
11922  continue;
11923  space01 = sdata[x - 1 + y*nx];
11924  if (space01 < 0.0) /* Impossible spatial coordinate */
11925  continue;
11926 
11927  shift = 0;
11928 
11929 /****+
11930  if (wdata[x + (y+1)*nx] > 1.0) {
11931  if (wdata[x + (y+1)*nx] - lambda11 > 0) {
11932  shift = -1;
11933  while (wdata[x + shift + (y+1)*nx] - lambda11 > 0)
11934  shift--;
11935  if (lambda11 - wdata[x + shift + (y+1)*nx] >
11936  wdata[x + shift + 1 + (y+1)*nx] - lambda11) {
11937  shift++;
11938  }
11939  }
11940  else {
11941  shift = 1;
11942  while (wdata[x + shift + (y+1)*nx] - lambda11 < 0)
11943  shift++;
11944  if (wdata[x + shift + (y+1)*nx] - lambda11 >
11945  lambda11 - wdata[x + shift + 1 + (y+1)*nx]) {
11946  shift--;
11947  }
11948  }
11949  }
11950 ****/
11951 
11952 /****
11953 printf("y = %d, shift = %d\n", y, shift);
11954 ****/
11955 
11956  lambda10 = wdata[x + shift + (y+1)*nx];
11957  if (lambda10 < 1.0) /* Impossible wavelength */
11958  continue;
11959  space10 = sdata[x + shift + (y+1)*nx];
11960  if (space10 < 0.0) /* Impossible spatial coordinate */
11961  continue;
11962  lambda00 = wdata[x - 1 + shift + (y+1)*nx];
11963  if (lambda00 < 1.0) /* Impossible wavelength */
11964  continue;
11965  space00 = sdata[x - 1 + shift + (y+1)*nx];
11966  if (space00 < 0.0) /* Impossible spatial coordinate */
11967  continue;
11968 
11969  /*
11970  * Find the variation in lambda and space in this
11971  * position for each CCD pixel (both quantities are
11972  * expected to be positive).
11973  */
11974 
11975  dL = lambda11 - lambda01;
11976  dS = space11 - space10;
11977 
11978  /*
11979  * Find the position (L,S) of the output pixel
11980  * (by integer truncation).
11981  */
11982 
11983  L = (lambda11 - blue)/dispersion + 0.5;
11984  S = space11 + 0.5; /* Counted from top! */
11985 
11986  if (L < 0 || L >= nlambda)
11987  continue;
11988  if (S < 0 || S > npseudo)
11989  continue;
11990 
11991  /*
11992  * Find the coordinate of pixel (L,S)
11993  */
11994 
11995  lambda = blue + L*dispersion;
11996  space = S;
11997 
11998  /*
11999  * Find the interpolation point on the CCD: it is
12000  * defined as the (positive) distance from current
12001  * CCD pixel (x,y) of the interpolation point (x',y'),
12002  * measured in CCD pixels. The interpolation point
12003  * is located between the four CCD pixels selected
12004  * above.
12005  */
12006 
12007  xfrac = (lambda11-lambda)/dL;
12008  yfrac = (space11-space)/dS;
12009 
12010 /*
12011 if (xfrac < 0.0 || xfrac > 1.0 || yfrac < 0.0 || yfrac > 1.0)
12012 printf("xyfrac = %f, %f\n", xfrac, yfrac);
12013 */
12014 
12015  /*
12016  * Get the four values to interpolate
12017  */
12018 
12019  value11 = data[x + y*nx];
12020  value01 = data[x - 1 + y*nx];
12021  value10 = data[x + shift + (y+1)*nx];
12022  value00 = data[x + shift - 1 + (y+1)*nx];
12023 
12024  /*
12025  * Interpolation
12026  */
12027 
12028  value1 = (1-xfrac)*value11 + xfrac*value01;
12029  value0 = (1-xfrac)*value10 + xfrac*value00;
12030  value = (1-yfrac)*value1 + yfrac*value0;
12031 
12032  /*
12033  * Write this value to the appropriate (L,S) coordinate
12034  * on output slit
12035  */
12036 
12037  xdata[L + nlambda*(npseudo-S)] = value;
12038 
12039  }
12040  }
12041  cpl_polynomial_delete(polytop);
12042  cpl_polynomial_delete(polybot);
12043  }
12044 
12045  /*
12046  * Now all the slits images are copied to a single image
12047  */
12048 
12049  ysize = 0;
12050  for (i = 0; i < nslits; i++)
12051  if (exslit[i])
12052  ysize += cpl_image_get_size_y(exslit[i]);
12053 
12054  remapped = cpl_image_new(nlambda, ysize, CPL_TYPE_FLOAT);
12055 
12056  yint = -1;
12057  for (i = 0; i < nslits; i++) {
12058  if (exslit[i]) {
12059  yint += cpl_image_get_size_y(exslit[i]);
12060  cpl_image_copy(remapped, exslit[i], 1, ysize - yint);
12061  cpl_image_delete(exslit[i]);
12062  cpl_table_set_int(slits, "position", i, ysize - yint - 1);
12063  }
12064  }
12065 
12066  cpl_free(exslit);
12067 
12068  return remapped;
12069 
12070 }
12071 
12072 
12105 cpl_table *mos_sky_map_super(cpl_image *spectra, cpl_image *wavemap,
12106  double dispersion, double factor, int minpoints,
12107  cpl_image *skymap)
12108 {
12109  const char *func = "mos_sky_map_super";
12110 
12111  cpl_vector **vector;
12112  cpl_vector **wvector;
12113  double firstLambda, lastLambda;
12114  double lambda, lambda1, lambda2;
12115  double value, value1, value2;
12116  double frac;
12117  float min, max;
12118  int *count;
12119  int nbin, bin;
12120  int nx, ny, npix;
12121  int first_valid, valid_bins;
12122  int i, j;
12123 
12124  cpl_table *sky;
12125  double *sky_spectrum;
12126  double *sky_wave;
12127  float *data;
12128  float *sdata;
12129  float *kdata;
12130 
12131 
12132  if (spectra == NULL || wavemap == NULL || skymap == NULL) {
12133  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
12134  return NULL;
12135  }
12136 
12137  if (dispersion <= 0.0) {
12138  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
12139  cpl_msg_error(func, "Negative dispersion: %s", cpl_error_get_message());
12140  return NULL;
12141  }
12142 
12143  nx = cpl_image_get_size_x(spectra);
12144  ny = cpl_image_get_size_y(spectra);
12145  npix = nx * ny;
12146 
12147  if (nx != cpl_image_get_size_x(wavemap) ||
12148  ny != cpl_image_get_size_y(wavemap) ||
12149  nx != cpl_image_get_size_x(skymap) ||
12150  ny != cpl_image_get_size_y(skymap)) {
12151  cpl_error_set(func, CPL_ERROR_INCOMPATIBLE_INPUT);
12152  cpl_msg_error(func, "Image sizes: %s", cpl_error_get_message());
12153  return NULL;
12154  }
12155 
12156  if (factor < 1.0) {
12157  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
12158  cpl_msg_error(func, "Undersampling (%f): %s", factor,
12159  cpl_error_get_message());
12160  return NULL;
12161  }
12162 
12163  if (minpoints < 0) {
12164  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
12165  cpl_msg_error(func, "Negative threshold: %s", cpl_error_get_message());
12166  return NULL;
12167  }
12168 
12169  dispersion /= factor;
12170 
12171 
12172  /*
12173  * Find bluest and reddest wavelengths in the whole image
12174  */
12175 
12176  data = cpl_image_get_data(wavemap);
12177 
12178  for (i = 0; i < npix; i++) {
12179  if (data[i] > 1.0) {
12180  min = max = data[i];
12181  j = i+1;
12182  break;
12183  }
12184  }
12185 
12186  for (i = j; i < npix; i++) {
12187  if (data[i] < 1.0) /* Impossible wavelength */
12188  continue;
12189  if (min > data[i])
12190  min = data[i];
12191  if (max < data[i])
12192  max = data[i];
12193  }
12194 
12195  firstLambda = min;
12196  lastLambda = max;
12197 
12198 
12199  /*
12200  * Determine length of median spectrum
12201  */
12202 
12203  nbin = (lastLambda - firstLambda) / dispersion;
12204 
12205  /*
12206  * Count how many values will be found for each spectral bin.
12207  * The ith bin has a wavelength range from firstLambda + i*dispersion
12208  * (inclusive) to firstLambda + (i+1)*dispersion (exclusive), and
12209  * it is assigned to its central wavelength.
12210  */
12211 
12212  count = cpl_calloc(nbin, sizeof(int));
12213 
12214  data = cpl_image_get_data(wavemap);
12215 
12216  for (i = 0; i < npix; i++) {
12217  if (data[i] < 1.0)
12218  continue;
12219  bin = (data[i] - firstLambda) / dispersion;
12220  if (bin < nbin) /* Safer */
12221  count[bin]++;
12222  }
12223 
12224  valid_bins = 0;
12225  for (i = 0; i < nbin; i++)
12226  if (count[i] >= minpoints)
12227  valid_bins++;
12228 
12229  if (valid_bins < nbin/3) {
12230  cpl_msg_warning(func, "Cannot determine a good global sky "
12231  "spectrum from input data");
12232  return NULL;
12233  }
12234 
12235 
12236  /*
12237  * Allocate an array of vectors with the appropriate size, to
12238  * contain a list of all the spectral pixels values. At the same
12239  * time, reset the array of counters (because we will have to
12240  * count again...).
12241  */
12242 
12243  vector = cpl_calloc(nbin, sizeof(cpl_vector *));
12244  wvector = cpl_calloc(nbin, sizeof(cpl_vector *));
12245  for (i = 0; i < nbin; i++) {
12246  if (count[i] >= minpoints) {
12247  vector[i] = cpl_vector_new(count[i]);
12248  wvector[i] = cpl_vector_new(count[i]);
12249  }
12250  count[i] = 0;
12251  }
12252 
12253 
12254  /*
12255  * Read the wavemap and the spectral images, and add the data values
12256  * to the appropriate wavelength bins
12257  */
12258 
12259  data = cpl_image_get_data(wavemap);
12260  sdata = cpl_image_get_data(spectra);
12261 
12262  for (i = 0; i < npix; i++) {
12263  if (data[i] < 1.0)
12264  continue;
12265  bin = (data[i] - firstLambda) / dispersion;
12266  if (bin < nbin) { /* Safer */
12267  if (vector[bin]) {
12268  cpl_vector_set(vector[bin], count[bin], sdata[i]);
12269  cpl_vector_set(wvector[bin], count[bin], data[i]);
12270  }
12271  count[bin]++;
12272  }
12273  }
12274 
12275 
12276  /*
12277  * Compute the median flux for each wavelength bin, and destroy
12278  * at the same time the used vectors
12279  */
12280 
12281  sky_spectrum = cpl_calloc(nbin, sizeof(double));
12282  sky_wave = cpl_calloc(nbin, sizeof(double));
12283  for (i = 0; i < nbin; i++) {
12284  if (vector[i]) {
12285  sky_spectrum[i] = cpl_vector_get_median_const(vector[i]);
12286  sky_wave[i] = cpl_vector_get_median_const(wvector[i]);
12287  cpl_vector_delete(vector[i]);
12288  cpl_vector_delete(wvector[i]);
12289  }
12290  }
12291 
12292  cpl_free(vector);
12293  cpl_free(wvector);
12294 
12295 
12296  /*
12297  * Here possible gaps in the final spectrum are filled by interpolation
12298  */
12299 
12300  for (i = 0; i < nbin; i++) {
12301  if (count[i] >= minpoints) {
12302  first_valid = i;
12303  break;
12304  }
12305  }
12306 
12307  for (i = first_valid; i < nbin; i++) {
12308  if (count[i] < minpoints) {
12309  sky_wave[i] = firstLambda + (i+0.5)*dispersion;
12310  for (j = i+1; j < nbin; j++) {
12311  if (count[j] >= minpoints) {
12312  if (sky_wave[j] - sky_wave[i-1] < 0.1) {
12313  sky_spectrum[i] = (sky_spectrum[j] + sky_spectrum[i-1])
12314  / 2;
12315  }
12316  else {
12317  frac = (sky_wave[i] - sky_wave[i-1])
12318  / (sky_wave[j] - sky_wave[i-1]);
12319  sky_spectrum[i] = frac * sky_spectrum[j]
12320  + (1 - frac) * sky_spectrum[i-1];
12321  }
12322  }
12323  }
12324  }
12325  }
12326 
12327 
12328  /*
12329  * Create the output table
12330  */
12331 
12332  sky = cpl_table_new(nbin);
12333  cpl_table_wrap_double(sky, sky_wave, "wavelength");
12334  cpl_table_wrap_double(sky, sky_spectrum, "sky");
12335  cpl_table_wrap_int(sky, count, "npoints");
12336 
12337 
12338  /*
12339  * Fill the sky map
12340  */
12341 
12342  data = cpl_image_get_data(wavemap);
12343  sdata = cpl_image_get_data(spectra);
12344  kdata = cpl_image_get_data(skymap);
12345 
12346  for (i = 0; i < npix; i++) {
12347 
12348  /*
12349  * Currently based on linear interpolation
12350  */
12351 
12352  lambda = data[i];
12353  if (lambda < 1.0)
12354  continue;
12355  bin = (lambda - firstLambda) / dispersion;
12356  lambda1 = sky_wave[bin];
12357  value1 = sky_spectrum[bin];
12358  if (lambda1 < lambda) {
12359  bin++;
12360  if (bin < nbin) {
12361  lambda2 = sky_wave[bin];
12362  value2 = sky_spectrum[bin];
12363  if (lambda2 - lambda1 < 0.1) {
12364  value = (value1 + value2) / 2;
12365  }
12366  else {
12367  frac = (lambda - lambda1) / (lambda2 - lambda1);
12368  value = frac * value2 + (1 - frac) * value1;
12369  }
12370  }
12371  else {
12372  value = value1;
12373  }
12374  }
12375  else {
12376  if (bin > 0) {
12377  bin--;
12378  lambda2 = lambda1;
12379  value2 = value1;
12380  lambda1 = sky_wave[bin];
12381  value1 = sky_spectrum[bin];
12382  if (lambda2 - lambda1 < 0.1) {
12383  value = (value1 + value2) / 2;
12384  }
12385  else {
12386  frac = (lambda - lambda1) / (lambda2 - lambda1);
12387  value = frac * value2 + (1 - frac) * value1;
12388  }
12389  }
12390  else {
12391  value = value1;
12392  }
12393  }
12394  kdata[i] = value;
12395  }
12396 
12397  if (first_valid)
12398  cpl_table_erase_window(sky, 0, first_valid);
12399 
12400  return sky;
12401 
12402 }
12403 
12404 
12438 cpl_table *mos_sky_map(cpl_image *spectra, cpl_image *wavemap,
12439  double dispersion, cpl_image *skymap)
12440 {
12441  const char *func = "mos_sky_map";
12442 
12443  cpl_vector **vector;
12444  double firstLambda, lastLambda;
12445  double lambda, lambda1, lambda2;
12446  double value, value1, value2;
12447  float min, max;
12448  int *count;
12449  int nbin, bin;
12450  int nx, ny, npix;
12451  int i, j;
12452 
12453  cpl_table *sky;
12454  double *sky_spectrum;
12455  float *data;
12456  float *sdata;
12457  float *kdata;
12458  double *wdata;
12459 
12460 
12461  if (spectra == NULL || wavemap == NULL || skymap == NULL) {
12462  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
12463  return NULL;
12464  }
12465 
12466  if (dispersion <= 0.0) {
12467  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
12468  return NULL;
12469  }
12470 
12471  nx = cpl_image_get_size_x(spectra);
12472  ny = cpl_image_get_size_y(spectra);
12473  npix = nx * ny;
12474 
12475  if (nx != cpl_image_get_size_x(wavemap) ||
12476  ny != cpl_image_get_size_y(wavemap) ||
12477  nx != cpl_image_get_size_x(skymap) ||
12478  ny != cpl_image_get_size_y(skymap)) {
12479  cpl_error_set(func, CPL_ERROR_INCOMPATIBLE_INPUT);
12480  return NULL;
12481  }
12482 
12483 
12484  /*
12485  * Find bluest and reddest wavelengths in the whole image
12486  */
12487 
12488  data = cpl_image_get_data(wavemap);
12489 
12490  for (i = 0; i < npix; i++) {
12491  if (data[i] > 1.0) {
12492  min = max = data[i];
12493  j = i+1;
12494  break;
12495  }
12496  }
12497 
12498  for (i = j; i < npix; i++) {
12499  if (data[i] < 1.0) /* Impossible wavelength */
12500  continue;
12501  if (min > data[i])
12502  min = data[i];
12503  if (max < data[i])
12504  max = data[i];
12505  }
12506 
12507  firstLambda = min;
12508  lastLambda = max;
12509 
12510 
12511  /*
12512  * Determine length of median spectrum
12513  */
12514 
12515  nbin = (lastLambda - firstLambda) / dispersion;
12516 
12517  /*
12518  * Count how many values will be found for each spectral bin.
12519  * The ith bin has a wavelength range from firstLambda + i*dispersion
12520  * (inclusive) to firstLambda + (i+1)*dispersion (exclusive), and
12521  * it is assigned to its central wavelength.
12522  */
12523 
12524  count = cpl_calloc(nbin, sizeof(int));
12525 
12526  data = cpl_image_get_data(wavemap);
12527 
12528  for (i = 0; i < npix; i++) {
12529  if (data[i] < 1.0)
12530  continue;
12531  bin = (data[i] - firstLambda) / dispersion;
12532  if (bin < nbin) /* Safer */
12533  count[bin]++;
12534  }
12535 
12536 
12537  /*
12538  * Allocate an array of vectors with the appropriate size, to
12539  * contain a list of all the spectral pixels values. At the same
12540  * time, reset the array of counters (because we will have to
12541  * count again...).
12542  */
12543 
12544  vector = cpl_calloc(nbin, sizeof(cpl_vector *));
12545  for (i = 0; i < nbin; i++) {
12546  if (count[i])
12547  vector[i] = cpl_vector_new(count[i]);
12548  else
12549  vector[i] = NULL;
12550  count[i] = 0;
12551  }
12552 
12553 
12554  /*
12555  * Read the wavemap and the spectral images, and add the data values
12556  * to the appropriate wavelength bins
12557  */
12558 
12559  data = cpl_image_get_data(wavemap);
12560  sdata = cpl_image_get_data(spectra);
12561 
12562  for (i = 0; i < npix; i++) {
12563  if (data[i] < 1.0)
12564  continue;
12565  bin = (data[i] - firstLambda) / dispersion;
12566  if (bin < nbin) { /* Safer */
12567  cpl_vector_set(vector[bin], count[bin], sdata[i]);
12568  count[bin]++;
12569  }
12570  }
12571 
12572 
12573  /*
12574  * Compute the median flux for each wavelength bin, and destroy
12575  * at the same time the used vectors
12576  */
12577 
12578  sky_spectrum = cpl_calloc(nbin, sizeof(double));
12579  for (i = 0; i < nbin; i++) {
12580  if (vector[i]) {
12581  sky_spectrum[i] = cpl_vector_get_median_const(vector[i]);
12582  cpl_vector_delete(vector[i]);
12583  }
12584  }
12585 
12586  cpl_free(vector);
12587 
12588 
12589  /*
12590  * Here possible gaps in the final spectrum should be filled
12591  * by interpolation
12592  */
12593 
12594  /* ... */
12595 
12596  /*
12597  * Create the output table
12598  */
12599 
12600  sky = cpl_table_new(nbin);
12601  cpl_table_new_column(sky, "wavelength", CPL_TYPE_DOUBLE);
12602  cpl_table_set_column_unit(sky, "wavelength", "pixel");
12603  cpl_table_wrap_double(sky, sky_spectrum, "sky");
12604  cpl_table_wrap_int(sky, count, "npoints");
12605  for (i = 0; i < nbin; i++)
12606  cpl_table_set_double(sky, "wavelength", i,
12607  firstLambda + (i+0.5)*dispersion);
12608 
12609 
12610  /*
12611  * Fill the sky map
12612  */
12613 
12614  data = cpl_image_get_data(wavemap);
12615  sdata = cpl_image_get_data(spectra);
12616  kdata = cpl_image_get_data(skymap);
12617  wdata = cpl_table_get_data_double(sky, "wavelength");
12618 
12619  for (i = 0; i < npix; i++) {
12620 
12621  /*
12622  * Currently based on linear interpolation
12623  */
12624 
12625  lambda = data[i];
12626  if (lambda < 1.0)
12627  continue;
12628  bin = (lambda - firstLambda) / dispersion;
12629  lambda1 = wdata[bin];
12630  value1 = sky_spectrum[bin];
12631  if (lambda1 < lambda) {
12632  bin++;
12633  if (bin < nbin) {
12634  lambda2 = wdata[bin];
12635  value2 = sky_spectrum[bin];
12636  value = ((lambda2 - lambda)*value1
12637  + (lambda - lambda1)*value2) / dispersion;
12638  }
12639  else {
12640  value = value1;
12641  }
12642  }
12643  else {
12644  if (bin > 0) {
12645  bin--;
12646  lambda2 = lambda1;
12647  value2 = value1;
12648  lambda1 = wdata[bin];
12649  value1 = sky_spectrum[bin];
12650  value = ((lambda2 - lambda)*value1
12651  + (lambda - lambda1)*value2)/dispersion;
12652  }
12653  else {
12654  value = value1;
12655  }
12656  }
12657  kdata[i] = value;
12658  }
12659 
12660  return sky;
12661 
12662 }
12663 
12664 
12680 cpl_image *mos_sky_local_old(cpl_image *spectra, cpl_table *slits)
12681 {
12682  const char *func = "mos_sky_local_old";
12683 
12684  cpl_image *exslit;
12685  cpl_image *sky;
12686  cpl_image *skymap;
12687  float *data;
12688  float *sdata;
12689  int nx, ny;
12690  int xlow, ylow, xhig, yhig;
12691  int nslits;
12692  int *slit_id;
12693  int *position;
12694  int *length;
12695  int i, j, k;
12696 
12697 
12698  if (spectra == NULL) {
12699  cpl_msg_error(func,
12700  "A scientific rectified spectral image must be given");
12701  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
12702  return NULL;
12703  }
12704 
12705  if (slits == NULL) {
12706  cpl_msg_error(func, "A slits position table must be given");
12707  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
12708  return NULL;
12709  }
12710 
12711  nslits = cpl_table_get_nrow(slits);
12712  slit_id = cpl_table_get_data_int(slits, "slit_id");
12713  position = cpl_table_get_data_int(slits, "position");
12714  length = cpl_table_get_data_int(slits, "length");
12715 
12716  nx = cpl_image_get_size_x(spectra);
12717  ny = cpl_image_get_size_y(spectra);
12718 
12719  skymap = cpl_image_new(nx, ny, CPL_TYPE_FLOAT);
12720 
12721  xlow = 1;
12722  xhig = nx;
12723  for (i = 0; i < nslits; i++) {
12724 
12725  if (length[i] == 0)
12726  continue;
12727 
12728  /*
12729  * Define the extraction boundaries. We DON'T write:
12730  *
12731  * ylow = position[i];
12732  * yhig = ylow + length[i];
12733  *
12734  * because the cpl_image pixels are counted from 1, and because in
12735  * cpl_image_extract() the coordinates of the last pixel are inclusive.
12736  */
12737 
12738  ylow = position[i] + 1;
12739  yhig = ylow + length[i] - 1;
12740 
12741  exslit = cpl_image_extract(spectra, xlow, ylow, xhig, yhig);
12742  sky = cpl_image_collapse_median_create(exslit, 0, 0, 1);
12743  cpl_image_delete(exslit);
12744 
12745  data = cpl_image_get_data(skymap);
12746  data += nx * position[i];
12747 
12748  for (j = 0; j < length[i]; j++) {
12749  sdata = cpl_image_get_data(sky);
12750  for (k = 0; k < nx; k++) {
12751  *data++ = *sdata++;
12752  }
12753  }
12754 
12755  cpl_image_delete(sky);
12756  }
12757 
12758  return skymap;
12759 
12760 }
12761 
12762 
12782 cpl_image *mos_sky_local(cpl_image *spectra, cpl_table *slits, int order)
12783 {
12784  const char *func = "mos_sky_local";
12785 
12786  char name[MAX_COLNAME];
12787 
12788  cpl_polynomial *fit;
12789  cpl_vector *points;
12790  cpl_vector *values;
12791  cpl_vector *keep_points;
12792  cpl_vector *keep_values;
12793  cpl_image *exslit;
12794  cpl_image *sky;
12795  cpl_image *subtracted;
12796  cpl_image *profile;
12797  cpl_image *skymap;
12798  cpl_table *objects;
12799  float *data;
12800  float *sdata;
12801  float *xdata;
12802  double *vdata;
12803  double *pdata;
12804  double median;
12805  int nx, ny;
12806  int xlow, ylow, xhig, yhig;
12807  int nslits;
12808  int *slit_id;
12809  int *position;
12810  int *length;
12811  int *is_sky;
12812  int nsky, nbad;
12813  int maxobjects;
12814  int margin = 3;
12815  int radius = 6;
12816  int i, j, k;
12817 
12818 
12819  if (spectra == NULL) {
12820  cpl_msg_error(func,
12821  "A scientific rectified spectral image must be given");
12822  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
12823  return NULL;
12824  }
12825 
12826  if (slits == NULL) {
12827  cpl_msg_error(func, "A slits position table must be given");
12828  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
12829  return NULL;
12830  }
12831 
12832  if (order < 0) {
12833  cpl_msg_error(func, "Invalid fit order");
12834  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
12835  return NULL;
12836  }
12837 
12838  nslits = cpl_table_get_nrow(slits);
12839  slit_id = cpl_table_get_data_int(slits, "slit_id");
12840  position = cpl_table_get_data_int(slits, "position");
12841  length = cpl_table_get_data_int(slits, "length");
12842 
12843  nx = cpl_image_get_size_x(spectra);
12844  ny = cpl_image_get_size_y(spectra);
12845 
12846  skymap = cpl_image_new(nx, ny, CPL_TYPE_FLOAT);
12847 
12848  xlow = 1;
12849  xhig = nx;
12850  for (i = 0; i < nslits; i++) {
12851 
12852  if (length[i] == 0)
12853  continue;
12854 
12855  /*
12856  * Define the extraction boundaries. We DON'T write:
12857  *
12858  * ylow = position[i];
12859  * yhig = ylow + length[i];
12860  *
12861  * because the cpl_image pixels are counted from 1, and because in
12862  * cpl_image_extract() the coordinates of the last pixel are inclusive.
12863  */
12864 
12865  ylow = position[i] + 1;
12866  yhig = ylow + length[i] - 1;
12867 
12868  exslit = cpl_image_extract(spectra, xlow, ylow, xhig, yhig);
12869  sky = cpl_image_collapse_median_create(exslit, 0, 0, 1);
12870  cpl_image_delete(exslit);
12871 
12872  data = cpl_image_get_data(skymap);
12873  data += nx * position[i];
12874 
12875  for (j = 0; j < length[i]; j++) {
12876  sdata = cpl_image_get_data(sky);
12877  for (k = 0; k < nx; k++) {
12878  *data++ = *sdata++;
12879  }
12880  }
12881 
12882  cpl_image_delete(sky);
12883  }
12884 
12885 
12886  /*
12887  * Preliminary sky-subtracted image
12888  */
12889 
12890  subtracted = cpl_image_duplicate(spectra);
12891  cpl_image_subtract(subtracted, skymap);
12892  cpl_image_delete(skymap);
12893 
12894 
12895  /*
12896  * Detect objects positions in all slits
12897  */
12898 
12899  objects = cpl_table_duplicate(slits);
12900  profile = mos_detect_objects(subtracted, objects, margin, radius, 0);
12901  cpl_image_delete(profile);
12902  cpl_image_delete(subtracted);
12903 
12904 
12905  /*
12906  * Flag the sky pixels. Note that maxobjects is intentionally
12907  * the max number of objects increased by one.
12908  */
12909 
12910  maxobjects = 1;
12911  snprintf(name, MAX_COLNAME, "object_%d", maxobjects);
12912  while (cpl_table_has_column(objects, name)) {
12913  maxobjects++;
12914  snprintf(name, MAX_COLNAME, "object_%d", maxobjects);
12915  }
12916 
12917  is_sky = cpl_calloc(ny, sizeof(int));
12918 
12919  for (i = 0; i < nslits; i++) {
12920 
12921  if (length[i] == 0)
12922  continue;
12923 
12924  ylow = position[i] + margin;
12925  yhig = position[i] + length[i] - margin;
12926 
12927  for (j = ylow; j < yhig; j++)
12928  is_sky[j] = 1;
12929 
12930  for (j = 1; j < maxobjects; j++) {
12931  snprintf(name, MAX_COLNAME, "object_%d", j);
12932  if (cpl_table_is_valid(objects, name, i)) {
12933  snprintf(name, MAX_COLNAME, "start_%d", j);
12934  ylow = cpl_table_get_int(objects, name, i, NULL);
12935  snprintf(name, MAX_COLNAME, "end_%d", j);
12936  yhig = cpl_table_get_int(objects, name, i, NULL);
12937  for (k = ylow; k <= yhig; k++)
12938  is_sky[k] = 0;
12939  }
12940  }
12941 
12942 
12943  /*
12944  * Eliminate isolated sky points
12945  */
12946 
12947  ylow = position[i] + margin + 1;
12948  yhig = position[i] + length[i] - margin - 1;
12949 
12950  for (j = ylow; j < yhig; j++)
12951  if (is_sky[j])
12952  if (is_sky[j-1] == 0 && is_sky[j+1] == 0)
12953  is_sky[j] = 0;
12954 
12955  }
12956 
12957 
12958  /*
12959  * Determination of the sky map
12960  */
12961 
12962  skymap = cpl_image_new(nx, ny, CPL_TYPE_FLOAT);
12963 
12964  for (i = 0; i < nslits; i++) {
12965 
12966  if (length[i] == 0)
12967  continue;
12968 
12969  ylow = position[i];
12970  yhig = ylow + length[i];
12971 
12972  nsky = 0;
12973  for (j = ylow; j < yhig; j++)
12974  if (is_sky[j])
12975  nsky++;
12976 
12977  if (nsky > order + 1) {
12978  if (order) {
12979  points = cpl_vector_new(nsky);
12980  nsky = 0;
12981  for (j = ylow; j < yhig; j++) {
12982  if (is_sky[j]) {
12983  cpl_vector_set(points, nsky, j);
12984  nsky++;
12985  }
12986  }
12987 
12988  exslit = cpl_image_extract(spectra, 1, ylow+1, nx, yhig);
12989  xdata = cpl_image_get_data(exslit);
12990  values = cpl_vector_new(nsky);
12991 
12992  for (j = 0; j < nx; j++) {
12993  nsky = 0;
12994  for (k = ylow; k < yhig; k++) {
12995  if (is_sky[k]) {
12996  cpl_vector_set(values, nsky, xdata[j+(k-ylow)*nx]);
12997  nsky++;
12998  }
12999  }
13000 
13001  /*
13002  * Eliminate obvious outliers
13003  */
13004 
13005  median = cpl_vector_get_median_const(values);
13006  vdata = cpl_vector_get_data(values);
13007  pdata = cpl_vector_get_data(points);
13008  nbad = 0;
13009  for (k = 0; k < nsky; k++) {
13010  if (fabs(vdata[k] - median) < 100) {
13011  if (nbad) {
13012  vdata[k-nbad] = vdata[k];
13013  pdata[k-nbad] = pdata[k];
13014  }
13015  }
13016  else
13017  nbad++;
13018  }
13019 
13020  if (nsky == nbad)
13021  continue;
13022 
13023  if (nbad && nsky - nbad > order + 1) {
13024  keep_values = values;
13025  keep_points = points;
13026  values = cpl_vector_wrap(nsky-nbad, vdata);
13027  points = cpl_vector_wrap(nsky-nbad, pdata);
13028  }
13029 
13030  if (nsky - nbad > order + 1) {
13031 
13032  fit = cpl_polynomial_fit_1d_create(points, values,
13033  order, NULL);
13034 
13035  if (fit) {
13036  for (k = ylow; k < yhig; k++) {
13037  xdata[j+(k-ylow)*nx] =
13038  cpl_polynomial_eval_1d(fit, k, NULL);
13039  }
13040 
13041  cpl_polynomial_delete(fit);
13042  }
13043  else
13044  cpl_error_reset();
13045  }
13046  else {
13047  for (k = 0; k < nsky; k++) {
13048  xdata[j+k*nx] = median;
13049  }
13050  }
13051 
13052  if (nbad && nsky - nbad > order + 1) {
13053  cpl_vector_unwrap(values);
13054  cpl_vector_unwrap(points);
13055  values = keep_values;
13056  points = keep_points;
13057  }
13058 
13059  if (nbad) {
13060  nsky = 0;
13061  for (k = ylow; k < yhig; k++) {
13062  if (is_sky[k]) {
13063  cpl_vector_set(points, nsky, k);
13064  nsky++;
13065  }
13066  }
13067  }
13068 
13069  }
13070 
13071  cpl_vector_delete(values);
13072  cpl_vector_delete(points);
13073 
13074  cpl_image_copy(skymap, exslit, 1, ylow+1);
13075  cpl_image_delete(exslit);
13076 
13077  }
13078  else {
13079  exslit = cpl_image_extract(spectra, 1, ylow+1, nx, yhig);
13080  xdata = cpl_image_get_data(exslit);
13081  values = cpl_vector_new(nsky);
13082 
13083  for (j = 0; j < nx; j++) {
13084  nsky = 0;
13085  for (k = ylow; k < yhig; k++) {
13086  if (is_sky[k]) {
13087  cpl_vector_set(values, nsky, xdata[j+(k-ylow)*nx]);
13088  nsky++;
13089  }
13090  }
13091 
13092  median = cpl_vector_get_median_const(values);
13093 
13094  for (k = ylow; k < yhig; k++)
13095  xdata[j+(k-ylow)*nx] = median;
13096 
13097  }
13098 
13099  cpl_vector_delete(values);
13100 
13101  cpl_image_copy(skymap, exslit, 1, ylow+1);
13102  cpl_image_delete(exslit);
13103  }
13104  }
13105  else
13106  cpl_msg_warning(func, "Too few sky points in slit %d", i + 1);
13107  }
13108 
13109  cpl_free(is_sky);
13110 
13111  return skymap;
13112 
13113 }
13114 
13115 
13137 cpl_error_code mos_clean_cosmics(cpl_image *image, float gain,
13138  float threshold, float ratio)
13139 {
13140  const char *func = "mos_clean_cosmics";
13141 
13142  cpl_image *smoothImage;
13143  cpl_table *table;
13144  cpl_matrix *kernel;
13145  int *xdata;
13146  int *ydata;
13147  float *idata;
13148  float *sdata;
13149  float sigma, sum, value, smoothValue;
13150  double noise;
13151  int count;
13152  float fMax;
13153  int iMin, iMax, jMin, jMax, iPosMax, jPosMax;
13154  int xLen;
13155  int yLen;
13156  int nPix;
13157  int first = 1; /* position of first cosmic ray candidate
13158  encountered while scanning the image */
13159  int pos, i, j, k, l, ii, jj, iii = 0, jjj = 0;
13160  int numCosmic = 0;
13161  int found, foundContiguousCandidate;
13162  int *cosmic;
13163 
13164 
13165  if (image == NULL)
13166  return cpl_error_set(func, CPL_ERROR_NULL_INPUT);
13167 
13168 
13169  /*
13170  * "cosmic" is a flags holder (initialized to zero):
13171  *
13172  * -1 = candidate for cosmic ray
13173  * 0 = not a cosmic
13174  * 1 = a cosmic ray
13175  * 2 = member of current group of contiguous candidates
13176  * 3 = examined member of current group
13177  */
13178 
13179  xLen = cpl_image_get_size_x(image);
13180  yLen = cpl_image_get_size_y(image);
13181 
13182  if (xLen < 4 || yLen < 4)
13183  return CPL_ERROR_NONE;
13184 
13185  nPix = xLen * yLen;
13186 
13187  /*
13188  * Noise estimation from negative offsets in image. Note that this
13189  * assumes that the background level (skyLevel) has already been
13190  * subtracted from the data. In this way we estimate the noise due
13191  * to detector readout and to the background signal level (before
13192  * it were removed). Theoretically this is given by
13193  *
13194  * noise = sqrt(ron^2 + skyLevel/gain)
13195  *
13196  * where ron is the read-out-noise. To this we will sum the noise
13197  * contribution due to any increase of the signal above the background
13198  * by an amount scienceLevel. Theoretically the total noise is given by
13199  *
13200  * totalNoise = sqrt(ron^2 + (skyLevel+scienceLevel)/gain)
13201  *
13202  * that is
13203  *
13204  * totalNoise = sqrt(noise^2 + scienceLevel/gain)
13205  *
13206  */
13207 
13208  idata = cpl_image_get_data(image);
13209  noise = 0.0;
13210  count = 0;
13211 
13212  for (i = 0; i < nPix; i++) {
13213  if (idata[i] < -0.00001) {
13214  noise -= idata[i];
13215  count++;
13216  }
13217  }
13218 
13219  noise /= count;
13220  noise *= 1.25; /* Factor to convert average deviation to sigma */
13221 
13222  cosmic = cpl_calloc(nPix, sizeof(int));
13223 
13224  if (threshold < 0.)
13225  threshold = 4.0;
13226  if (ratio < 0.)
13227  ratio = 2.0;
13228 
13229  kernel = cpl_matrix_new(3, 3);
13230  cpl_matrix_fill(kernel, 1.0);
13231  cpl_matrix_set(kernel, 1, 1, 0.0);
13232  smoothImage = cpl_image_filter_median(image, kernel);
13233  cpl_matrix_delete(kernel);
13234 
13235  /*
13236  * Loop on images pixels, searching for cosmic rays candidates.
13237  * Border pixels are currently excluded (they cannot contain
13238  * candidates), to avoid that the search for groups of contiguous
13239  * pixels would ever go out of image boundaries. In future we may
13240  * overcome this limit, adding an appropriate check when contiguous
13241  * pixels are searched.
13242  */
13243 
13244  sdata = cpl_image_get_data(smoothImage);
13245 
13246  for (j = 1; j < yLen - 1; j++) {
13247  for (i = 1; i < xLen - 1; i++) {
13248  value = idata[i + j * xLen];
13249  smoothValue = sdata[i + j * xLen];
13250  if (smoothValue < 1.0)
13251  smoothValue = 1.0;
13252  sigma = sqrt(noise * noise + smoothValue / gain);
13253  if (value - smoothValue >= threshold * sigma)
13254  cosmic[i + j * xLen] = -1;
13255  }
13256  }
13257 
13258  cpl_image_delete(smoothImage);
13259 
13260 
13261  /*
13262  * Search for groups of contiguous cosmic rays candidates.
13263  */
13264 
13265  do {
13266  found = 0;
13267  for (pos = first; pos < nPix; pos++) {
13268  if (cosmic[pos] == -1) {
13269  cosmic[pos] = 2; /* Candidate found. */
13270  i = pos % xLen; /* Its coordinates. */
13271  j = pos / xLen;
13272  first = pos;
13273  first++; /* ??? really necessary? */
13274  found = 1;
13275  break;
13276  }
13277  }
13278 
13279  if (found) {
13280 
13281  /*
13282  * Determine new group of contiguous cosmic rays candidates.
13283  * Initialize the working box boundaries, iMin, iMax, jMin, jMax,
13284  * and the value of the max pixel and its position, fMax, iPosMax,
13285  * jPosMax.
13286  */
13287 
13288  iMin = iMax = iPosMax = i;
13289  jMin = jMax = jPosMax = j;
13290  fMax = idata[i + j * xLen];
13291 
13292  do {
13293  foundContiguousCandidate = 0;
13294  for (l = 0; l <= 1; l++) {
13295  for (k = 0; k <= 1; k++) {
13296 
13297  /*
13298  * Looping on 4 pixels to North, East, South and West
13299  */
13300 
13301  ii = i + k - l;
13302  jj = j + k + l - 1;
13303  if (cosmic[ii + jj * xLen] == -1) {
13304  foundContiguousCandidate = 1;
13305  cosmic[ii + jj * xLen] = 2;
13306  /* Candidate belongs to current group */
13307  iii = ii; /* Keep its position */
13308  jjj = jj;
13309 
13310  /*
13311  * Upgrade search box
13312  */
13313 
13314  if (ii < iMin)
13315  iMin = ii;
13316  if (ii > iMax)
13317  iMax = ii;
13318  if (jj < jMin)
13319  jMin = jj;
13320  if (jj > jMax)
13321  jMax = jj;
13322 
13323  if (idata[ii + jj * xLen] > fMax) {
13324  fMax = idata[ii + jj * xLen];
13325  iPosMax = ii;
13326  jPosMax = jj;
13327  }
13328  }
13329  }
13330  }
13331 
13332  /*
13333  * We are done exploring the "cross". Now mark as "examined"
13334  * the current candidate (at the center of the cross):
13335  */
13336 
13337  cosmic[i + j * xLen] = 3; /* It may probably be set to 1 now */
13338 
13339  if (foundContiguousCandidate) {
13340 
13341  /*
13342  * Pass (arbitrarily) the coordinates of the LAST found
13343  * candidate
13344  */
13345 
13346  i = iii;
13347  j = jjj;
13348 
13349  /*
13350  * Skip the rest, continue loop on new candidate
13351  */
13352 
13353  continue;
13354  }
13355 
13356 
13357  /*
13358  * Look for leftovers in the (growing!) search box
13359  */
13360 
13361  for (l = jMin; l <= jMax; l++) {
13362  for (k = iMin; k <= iMax; k++) {
13363  if (cosmic[k + l * xLen] == 2) {
13364  i = k;
13365  j = l;
13366  foundContiguousCandidate = 1;
13367  break;
13368  }
13369  }
13370  if (foundContiguousCandidate)
13371  break;
13372  }
13373  } while (foundContiguousCandidate);
13374 
13375 
13376  /*
13377  * No more contiguous candidates are found. Decide now
13378  * whether the current group is a cosmic ray or not.
13379  */
13380 
13381  sum = 0.; /* Sum of 8 pixels around max position */
13382  for (l = -1; l <= 1; l++) {
13383  for (k = -1; k <= 1; k++) {
13384  if (l != 0 || k != 0) {
13385  sum += idata[iPosMax + k + (jPosMax + l) * xLen];
13386  }
13387  }
13388  }
13389 
13390  sum /= 8.;
13391  if (fMax > ratio * sum) {
13392  for (l = jMin - 1; l <= jMax + 1; l++) {
13393  for (k = iMin - 1; k <= iMax + 1; k++) {
13394  if (cosmic[k + l * xLen] == 3) {
13395  cosmic[k + l * xLen] = 1;
13396  numCosmic++;
13397  }
13398  }
13399  }
13400  }
13401  else {
13402  for (l = jMin - 1; l <= jMax + 1; l++) {
13403  for (k = iMin - 1; k <= iMax + 1; k++) {
13404  if (cosmic[k + l * xLen] != -1) {
13405  if (cosmic[k + l * xLen] == 1)
13406  numCosmic--;
13407  cosmic[k + l * xLen] = 0;
13408  }
13409  }
13410  }
13411  }
13412  }
13413  } while (found);
13414 
13415 
13416  /*
13417  * Prepare table containing cosmic rays coordinates.
13418  */
13419 
13420  table = cpl_table_new(numCosmic);
13421  cpl_table_new_column(table, "x", CPL_TYPE_INT);
13422  cpl_table_new_column(table, "y", CPL_TYPE_INT);
13423  cpl_table_set_column_unit(table, "x", "pixel");
13424  cpl_table_set_column_unit(table, "y", "pixel");
13425  xdata = cpl_table_get_data_int(table, "x");
13426  ydata = cpl_table_get_data_int(table, "y");
13427 
13428  for (pos = 0, i = 0; pos < nPix; pos++) {
13429  if (cosmic[pos] == 1) {
13430  xdata[i] = (pos % xLen);
13431  ydata[i] = (pos / xLen);
13432  i++;
13433  }
13434  }
13435 
13436  mos_clean_bad_pixels(image, table, 1);
13437 
13438  cpl_free(cosmic);
13439  cpl_table_delete(table);
13440 
13441  return CPL_ERROR_NONE;
13442 
13443 }
13444 
13445 
13446 cpl_error_code mos_clean_bad_pixels(cpl_image *image, cpl_table *table,
13447  int spectral)
13448 {
13449  const char *func = "mos_clean_cosmics";
13450 
13451  float *idata;
13452  int *isBadPix;
13453  int i, j, k, d;
13454  int xlen, ylen, totPix;
13455  int nBadPixels = 0;
13456  int sign, foundFirst;
13457  int *xValue = NULL;
13458  int *yValue = NULL;
13459  float save = 0.;
13460  double sumd;
13461  int cx, cy;
13462  int nPairs;
13463  float estimate[4];
13464  int sx[] = {0, 1, 1, 1};
13465  int sy[] = {1,-1, 0, 1};
13466  int searchHorizon = 100;
13467  int percent = 15;
13468 
13469 
13470  if (image == NULL || table == NULL)
13471  return cpl_error_set(func, CPL_ERROR_NULL_INPUT);
13472 
13473  if (1 != cpl_table_has_column(table, "x"))
13474  return cpl_error_set(func, CPL_ERROR_DATA_NOT_FOUND);
13475 
13476  if (1 != cpl_table_has_column(table, "y"))
13477  return cpl_error_set(func, CPL_ERROR_DATA_NOT_FOUND);
13478 
13479  if (CPL_TYPE_INT != cpl_table_get_column_type(table, "x"))
13480  return cpl_error_set(func, CPL_ERROR_INVALID_TYPE);
13481 
13482  if (CPL_TYPE_INT != cpl_table_get_column_type(table, "y"))
13483  return cpl_error_set(func, CPL_ERROR_INVALID_TYPE);
13484 
13485  nBadPixels = cpl_table_get_nrow(table);
13486 
13487  if (nBadPixels) {
13488  xlen = cpl_image_get_size_x(image);
13489  ylen = cpl_image_get_size_y(image);
13490  idata = cpl_image_get_data(image);
13491  totPix = xlen * ylen;
13492  if (((float) nBadPixels) / ((float) totPix) < percent/100.) {
13493  isBadPix = cpl_calloc(totPix, sizeof(int));
13494  }
13495  else {
13496  cpl_msg_warning(func, "Too many bad pixels (> %d%%): "
13497  "skip bad pixel correction", percent);
13498  return cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
13499  }
13500  }
13501  else {
13502  cpl_msg_debug(func, "No pixel values to interpolate");
13503  return CPL_ERROR_NONE;
13504  }
13505 
13506  xValue = cpl_table_get_data_int(table, "x");
13507  yValue = cpl_table_get_data_int(table, "y");
13508 
13509  for (i = 0; i < nBadPixels; i++)
13510  isBadPix[xValue[i] + yValue[i] * xlen] = 1;
13511 
13512  for (i = 0; i < nBadPixels; i++) {
13513 
13514  /*
13515  * Search for the closest good pixel along the 4 fundamental
13516  * directions (in both senses):
13517  * \ | /
13518  * \|/
13519  * --- ---
13520  * /|\
13521  * / | \
13522  *
13523  * Then collect pairs of values to interpolate linearly.
13524  */
13525 
13526  nPairs = 0;
13527  for (j = 0; j < 4; j++) {
13528 
13529  if (spectral) /* Just horizontal interpolation for spectral data */
13530  if (j != 2)
13531  continue;
13532 
13533  estimate[nPairs] = 0.; /* Pairs interpolations are stored here */
13534  sumd = 0.;
13535  foundFirst = 0;
13536  for (k = 0; k < 2; k++) {
13537  sign = 2 * k - 1;
13538  d = 0;
13539  cx = xValue[i];
13540  cy = yValue[i];
13541  do {
13542  cx += sign * sx[j];
13543  cy += sign * sy[j];
13544  if (cx < 0 || cx >= xlen || cy < 0 || cy >= ylen)
13545  break;
13546  d++;
13547  } while (isBadPix[cx + cy * xlen] && d < searchHorizon);
13548 
13549  if (cx >= 0 && cx < xlen &&
13550  cy >= 0 && cy < ylen && d < searchHorizon) {
13551 
13552  /*
13553  * In this block is cripted the linear interpolation...
13554  */
13555 
13556  save = idata[cx + cy * xlen];
13557  estimate[nPairs] += save / d;
13558  sumd += 1. / (double) d;
13559  if (k) {
13560  estimate[nPairs] /= sumd;
13561  nPairs++;
13562  }
13563  else {
13564  foundFirst = 1;
13565  }
13566  }
13567  else {
13568 
13569  /*
13570  * Image borders were crossed, incomplete pair of values
13571  */
13572 
13573  if (k) {
13574  if (foundFirst) {
13575  estimate[nPairs] = save;
13576  nPairs++;
13577  }
13578  }
13579  }
13580  }
13581  }
13582 
13583  /*
13584  * Replace pixel value of the input image, corresponding to
13585  * the current bad pixel, with the median of the estimates
13586  * resulted from the 4 linear interpolations.
13587  */
13588 
13589  if (nPairs > 2) {
13590  idata[xValue[i] + yValue[i] * xlen] =
13591  cpl_tools_get_median_float(estimate, nPairs);
13592  }
13593  else if (nPairs == 2) {
13594  idata[xValue[i] + yValue[i] * xlen] =
13595  (estimate[0] + estimate[1]) / 2.;
13596  }
13597  else if (nPairs == 1) {
13598  idata[xValue[i] + yValue[i] * xlen] = estimate[0];
13599  }
13600  else {
13601  cpl_msg_debug(func, "Cannot correct bad pixel %d,%d\n",
13602  xValue[i], yValue[i]);
13603  }
13604  }
13605 
13606  cpl_free(isBadPix);
13607 
13608  return CPL_ERROR_NONE;
13609 }
13610 
13611 
13641 cpl_image *mos_spatial_map(cpl_image *spectra, cpl_table *slits,
13642  cpl_table *polytraces, double reference,
13643  double blue, double red, double dispersion)
13644 {
13645  const char *func = "mos_spatial_map";
13646 
13647  const char *clab[6] = {"c0", "c1", "c2", "c3", "c4", "c5"};
13648  /* Max order is 5 */
13649  cpl_polynomial *polytop;
13650  cpl_polynomial *polybot;
13651  cpl_image *calibration;
13652  float *data;
13653  double top, bot;
13654  double coeff;
13655  double ytop, ybot;
13656  double ypos, yfra;
13657  double factor;
13658  int yint, yprev;
13659  int nslits;
13660  int npseudo;
13661  int *slit_id;
13662  int *length;
13663  int nx, ny;
13664  int pixel_above, pixel_below, refpixel, start_pixel, end_pixel;
13665  int missing_top, missing_bot;
13666  int null;
13667  int order;
13668  int i, j;
13669  cpl_size k;
13670 
13671 
13672  if (spectra == NULL || slits == NULL || polytraces == NULL) {
13673  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
13674  return NULL;
13675  }
13676 
13677  if (dispersion <= 0.0) {
13678  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
13679  return NULL;
13680  }
13681 
13682  if (red - blue < dispersion) {
13683  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
13684  return NULL;
13685  }
13686 
13687  nx = cpl_image_get_size_x(spectra);
13688  ny = cpl_image_get_size_y(spectra);
13689 
13690  calibration = cpl_image_new(nx, ny, CPL_TYPE_FLOAT);
13691  data = cpl_image_get_data(calibration);
13692 
13693  length = cpl_table_get_data_int(slits, "length");
13694  nslits = cpl_table_get_nrow(slits);
13695  slit_id = cpl_table_get_data_int(slits, "slit_id");
13696  order = cpl_table_get_ncol(polytraces) - 2;
13697 
13698  /*
13699  * The spatial resampling is performed for a certain number of
13700  * pixels above and below the position of the reference wavelength:
13701  */
13702 
13703  pixel_above = STRETCH_FACTOR * (red - reference) / dispersion;
13704  pixel_below = STRETCH_FACTOR * (reference - blue) / dispersion;
13705 
13706  for (i = 0; i < nslits; i++) {
13707 
13708  if (length[i] == 0)
13709  continue;
13710 
13711  /*
13712  * Note that the x coordinate of the reference pixels on the CCD
13713  * is taken arbitrarily at the top end of each slit. This wouldn't
13714  * be entirely correct in case of curved slits, or in presence of
13715  * heavy distortions: in such cases the spatial resampling is
13716  * really performed across a wide range of wavelengths. But
13717  * the lag between top and bottom spectral curvature models
13718  * would introduce even in such cases negligible effects on
13719  * the spectral spatial resampling.
13720  */
13721 
13722  refpixel = cpl_table_get_double(slits, "xtop", i, NULL);
13723 
13724  start_pixel = refpixel - pixel_below;
13725  if (start_pixel < 0)
13726  start_pixel = 0;
13727 
13728  end_pixel = refpixel + pixel_above;
13729  if (end_pixel > nx)
13730  end_pixel = nx;
13731 
13732  /*
13733  * Recover from the table of spectral curvature coefficients
13734  * the curvature polynomials.
13735  */
13736 
13737  missing_top = 0;
13738  polytop = cpl_polynomial_new(1);
13739  for (k = 0; k <= order; k++) {
13740  coeff = cpl_table_get_double(polytraces, clab[k], 2*i, &null);
13741  if (null) {
13742  cpl_polynomial_delete(polytop);
13743  missing_top = 1;
13744  break;
13745  }
13746  cpl_polynomial_set_coeff(polytop, &k, coeff);
13747  }
13748 
13749  missing_bot = 0;
13750  polybot = cpl_polynomial_new(1);
13751  for (k = 0; k <= order; k++) {
13752  coeff = cpl_table_get_double(polytraces, clab[k], 2*i+1, &null);
13753  if (null) {
13754  cpl_polynomial_delete(polybot);
13755  missing_bot = 1;
13756  break;
13757  }
13758  cpl_polynomial_set_coeff(polybot, &k, coeff);
13759  }
13760 
13761  if (missing_top && missing_bot) {
13762  cpl_msg_warning(func, "Spatial map, slit %d was not traced!",
13763  slit_id[i]);
13764  continue;
13765  }
13766 
13767  /*
13768  * In case just one of the two edges was not traced, the other
13769  * edge curvature model is duplicated and shifted to the other
13770  * end of the slit: better than nothing!
13771  */
13772 
13773  if (missing_top) {
13774  cpl_msg_warning(func, "Upper edge of slit %d was not traced: "
13775  "the spectral curvature of the lower edge "
13776  "is used instead.", slit_id[i]);
13777  polytop = cpl_polynomial_duplicate(polybot);
13778  ytop = cpl_table_get_double(slits, "ytop", i, NULL);
13779  ybot = cpl_table_get_double(slits, "ybottom", i, NULL);
13780  k = 0;
13781  coeff = cpl_polynomial_get_coeff(polybot, &k);
13782  coeff += ytop - ybot;
13783  cpl_polynomial_set_coeff(polytop, &k, coeff);
13784  }
13785 
13786  if (missing_bot) {
13787  cpl_msg_warning(func, "Lower edge of slit %d was not traced: "
13788  "the spectral curvature of the upper edge "
13789  "is used instead.", slit_id[i]);
13790  polybot = cpl_polynomial_duplicate(polytop);
13791  ytop = cpl_table_get_double(slits, "ytop", i, NULL);
13792  ybot = cpl_table_get_double(slits, "ybottom", i, NULL);
13793  k = 0;
13794  coeff = cpl_polynomial_get_coeff(polytop, &k);
13795  coeff -= ytop - ybot;
13796  cpl_polynomial_set_coeff(polybot, &k, coeff);
13797  }
13798 
13799  top = cpl_polynomial_eval_1d(polytop, refpixel, NULL);
13800  bot = cpl_polynomial_eval_1d(polybot, refpixel, NULL);
13801  npseudo = ceil(top-bot) + 1;
13802 
13803  if (npseudo < 1) {
13804  cpl_polynomial_delete(polytop);
13805  cpl_polynomial_delete(polybot);
13806  cpl_msg_warning(func, "Slit %d was badly traced: no extraction!",
13807  slit_id[i]);
13808  continue;
13809  }
13810 
13811  for (j = start_pixel; j < end_pixel; j++) {
13812  top = cpl_polynomial_eval_1d(polytop, j, NULL);
13813  bot = cpl_polynomial_eval_1d(polybot, j, NULL);
13814  factor = (top-bot)/npseudo;
13815  for (k = 0; k <= npseudo; k++) {
13816  ypos = top - k*factor;
13817  yint = ypos;
13818  yfra = ypos - yint;
13819  if (yint >= 0 && yint < ny-1) {
13820  data[j + nx*yint] = (top-yint)/factor;
13821  if (k) {
13822 
13823  /*
13824  * This is added to recover lost pixels on
13825  * the CCD image (pixels are lost because
13826  * the CCD pixels are less than npseudo+1).
13827  */
13828 
13829  if (yprev - yint > 1) {
13830  data[j + nx*(yint+1)] = (top-yint-1)/factor;
13831  }
13832  }
13833  }
13834  yprev = yint;
13835  }
13836  }
13837  cpl_polynomial_delete(polytop);
13838  cpl_polynomial_delete(polybot);
13839  }
13840 
13841  return calibration;
13842 }
13843 
13844 
13907 cpl_image *mos_detect_objects(cpl_image *image, cpl_table *slits, int margin,
13908  int maxradius, int conradius)
13909 {
13910  const char *func = "mos_detect_objects";
13911 
13912  cpl_image *profile;
13913  float *pdata;
13914  float *p;
13915 
13916  char name[MAX_COLNAME];
13917 
13918  int nslits;
13919  int npeaks;
13920  int nobjects, objpos, totobj;
13921  int maxobjects;
13922  int *position;
13923  int *length;
13924  int *reject;
13925  double *place;
13926  double *bright;
13927  double mindistance;
13928  int pos, count;
13929  int up;
13930  int low, hig;
13931  int row;
13932  int i, j, k;
13933 
13934  const int min_pixels = 10;
13935 
13936 
13937  if (cpl_error_get_code() != CPL_ERROR_NONE)
13938  return NULL;
13939 
13940  if (image == NULL || slits == NULL) {
13941  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
13942  return NULL;
13943  }
13944 
13945  if (margin < 0)
13946  margin = 0;
13947 
13948  if (maxradius < 0) {
13949  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
13950  return NULL;
13951  }
13952 
13953  if (conradius < 0) {
13954  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
13955  return NULL;
13956  }
13957 
13958  nslits = cpl_table_get_nrow(slits);
13959  position = cpl_table_get_data_int(slits, "position");
13960  length = cpl_table_get_data_int(slits, "length");
13961 
13962  profile = cpl_image_collapse_create(image, 1);
13963  cpl_image_divide_scalar(profile, cpl_image_get_size_x(image));
13964  pdata = cpl_image_get_data(profile);
13965 
13966  row = 1;
13967  maxobjects = 0;
13968  totobj = 0;
13969  for (i = 0; i < nslits; i++) {
13970 
13971  if (length[i] == 0)
13972  continue;
13973 
13974  pos = position[i] + margin;
13975  count = length[i] - 2*margin;
13976 
13977  if (count < min_pixels)
13978  continue;
13979 
13980  p = pdata + pos;
13981 
13982 
13983  /*
13984  * Count peaks candidates
13985  */
13986 
13987  npeaks = 0;
13988  if (p[0] > p[1] && p[1] > p[2] && p[2] > p[3] && p[3] > 0) {
13989  npeaks++;
13990  }
13991 
13992  up = 0;
13993  for (j = 0; j < count - 3; j++) {
13994  if (p[j] > 0) {
13995  if (p[j+1] > p[j]) {
13996  up++;
13997  }
13998  else {
13999  if (up > 2) {
14000  if (p[j+1] > p[j+2] && p[j+2] > 0) {
14001  if (p[j] > 5)
14002  npeaks++;
14003  }
14004  }
14005  else if (up > 1) {
14006  if (p[j+1] > p[j+2] && p[j+2] > p[j+3] && p[j+3] > 0) {
14007  if (p[j] > 5)
14008  npeaks++;
14009  }
14010  }
14011  up = 0;
14012  }
14013  }
14014  else {
14015  up = 0;
14016  }
14017  }
14018 
14019  if (p[count-1] > p[count-2] && p[count-2] > p[count-3]
14020  && p[count-3] > p[count-4] && p[count-4] > 0) {
14021  npeaks++;
14022  }
14023 
14024  if (npeaks == 0)
14025  continue;
14026 
14027 
14028  /*
14029  * Get candidates parameters
14030  */
14031 
14032  reject = cpl_calloc(npeaks, sizeof(int));
14033  bright = cpl_calloc(npeaks, sizeof(double));
14034  place = cpl_calloc(npeaks, sizeof(double));
14035 
14036  npeaks = 0;
14037  if (p[0] > p[1] && p[1] > p[2] && p[2] > p[3] && p[3] > 0) {
14038  bright[0] = p[0];
14039  place[0] = position[i] + margin;
14040  npeaks++;
14041  }
14042 
14043  up = 0;
14044  for (j = 0; j < count - 3; j++) {
14045  if (p[j] > 0) {
14046  if (p[j+1] > p[j]) {
14047  up++;
14048  }
14049  else {
14050  if (up > 2) {
14051  if (p[j+1] > p[j+2] && p[j+2] > 0) {
14052  if (p[j] > 5) {
14053  bright[npeaks] = p[j];
14054  place[npeaks] = position[i] + margin + j + 1
14055  + values_to_dx(p[j-1], p[j], p[j+1]);
14056  npeaks++;
14057  }
14058  }
14059  }
14060  else if (up > 1) {
14061  if (p[j+1] > p[j+2] && p[j+2] > p[j+3] && p[j+3] > 0) {
14062  if (p[j] > 5) {
14063  bright[npeaks] = p[j];
14064  place[npeaks] = position[i] + margin + j + 1
14065  + values_to_dx(p[j-1], p[j], p[j+1]);
14066  npeaks++;
14067  }
14068  }
14069  }
14070  up = 0;
14071  }
14072  }
14073  else {
14074  up = 0;
14075  }
14076  }
14077 
14078  if (p[count-1] > p[count-2] && p[count-2] > p[count-3]
14079  && p[count-3] > p[count-4] && p[count-4] > 0) {
14080  bright[npeaks] = p[count-1];
14081  place[npeaks] = position[i] + count;
14082  npeaks++;
14083  }
14084 
14085 
14086  /*
14087  * Now select the uncontaminated peaks
14088  */
14089 
14090  if (fabs(place[0] - pos) < 1.0)
14091  reject[0] = 1;
14092  if (fabs(place[npeaks-1] - pos - count) < 1.0)
14093  reject[npeaks-1] = 1;
14094  for (j = 0; j < npeaks; j++) {
14095  for (k = 0; k < npeaks; k++) {
14096  if (k == j)
14097  continue;
14098  mindistance = conradius * bright[k] / bright[j]
14099  * bright[k] / bright[j];
14100  if (fabs(place[j] - place[k]) < mindistance)
14101  reject[j] = 1;
14102  }
14103  }
14104 
14105 /* new part */
14106  for (j = 0; j < npeaks; j++) {
14107  if (reject[j])
14108  continue;
14109  if (j) {
14110  low = (place[j-1]*bright[j] + place[j]*bright[j-1])
14111  / (bright[j-1] + bright[j]) + 1;
14112  }
14113  else {
14114  low = pos;
14115  }
14116  if (j < npeaks - 1) {
14117  hig = (place[j+1]*bright[j] + place[j]*bright[j+1])
14118  / (bright[j+1] + bright[j]) + 1;
14119  }
14120  else {
14121  hig = pos + count;
14122  }
14123 
14124  if (low < pos)
14125  low = pos;
14126  if (hig > pos + count)
14127  hig = pos + count;
14128  if (place[j] - low > maxradius)
14129  low = place[j] - maxradius;
14130  if (hig - place[j] > maxradius)
14131  hig = place[j] + maxradius;
14132  if (hig == low)
14133  reject[j] = 1;
14134  }
14135 /* end new part */
14136 
14137  nobjects = npeaks;
14138  for (j = 0; j < npeaks; j++)
14139  if (reject[j])
14140  nobjects--;
14141 
14142  for (j = 0; j < nobjects; j++) {
14143  snprintf(name, MAX_COLNAME, "object_%d", j+1);
14144  if (cpl_table_has_column(slits, name))
14145  continue;
14146  cpl_table_new_column(slits, name, CPL_TYPE_DOUBLE);
14147  snprintf(name, MAX_COLNAME, "start_%d", j+1);
14148  cpl_table_new_column(slits, name, CPL_TYPE_INT);
14149  cpl_table_set_column_unit(slits, name, "pixel");
14150  snprintf(name, MAX_COLNAME, "end_%d", j+1);
14151  cpl_table_new_column(slits, name, CPL_TYPE_INT);
14152  cpl_table_set_column_unit(slits, name, "pixel");
14153  snprintf(name, MAX_COLNAME, "row_%d", j+1);
14154  cpl_table_new_column(slits, name, CPL_TYPE_INT);
14155  cpl_table_set_column_unit(slits, name, "pixel");
14156  }
14157 
14158  objpos = nobjects;
14159  for (j = 0; j < npeaks; j++) {
14160  if (reject[j])
14161  continue;
14162  if (j) {
14163  low = (place[j-1]*bright[j] + place[j]*bright[j-1])
14164  / (bright[j-1] + bright[j]) + 1;
14165  }
14166  else {
14167  low = pos;
14168  }
14169  if (j < npeaks - 1) {
14170  hig = (place[j+1]*bright[j] + place[j]*bright[j+1])
14171  / (bright[j+1] + bright[j]) + 1;
14172  }
14173  else {
14174  hig = pos + count;
14175  }
14176 
14177  if (low < pos)
14178  low = pos;
14179  if (hig > pos + count)
14180  hig = pos + count;
14181  if (place[j] - low > maxradius)
14182  low = place[j] - maxradius;
14183  if (hig - place[j] > maxradius)
14184  hig = place[j] + maxradius;
14185 
14186  snprintf(name, MAX_COLNAME, "object_%d", objpos);
14187  cpl_table_set_double(slits, name, i, place[j]);
14188  snprintf(name, MAX_COLNAME, "start_%d", objpos);
14189  cpl_table_set_int(slits, name, i, low);
14190  snprintf(name, MAX_COLNAME, "end_%d", objpos);
14191  cpl_table_set_int(slits, name, i, hig);
14192  snprintf(name, MAX_COLNAME, "row_%d", objpos);
14193  cpl_table_set_int(slits, name, i, row + objpos - 1);
14194  totobj++;
14195  objpos--;
14196  }
14197 
14198  row += nobjects;
14199 
14200  if (maxobjects < nobjects)
14201  maxobjects = nobjects;
14202 
14203  cpl_free(reject);
14204  cpl_free(bright);
14205  cpl_free(place);
14206 
14207  }
14208 
14209 /* nobjects = row - nobjects; A bug, I think... */
14210  row = cpl_table_get_nrow(slits);
14211 
14212  for (i = 0; i < row; i++) {
14213  for (j = 0; j < maxobjects; j++) {
14214  snprintf(name, MAX_COLNAME, "row_%d", j+1);
14215  if (cpl_table_is_valid(slits, name, i))
14216  cpl_table_set_int(slits, name, i, totobj -
14217  cpl_table_get_int(slits, name, i, NULL));
14218  }
14219  }
14220 
14221  for (i = 0; i < maxobjects; i++) {
14222  snprintf(name, MAX_COLNAME, "start_%d", i+1);
14223  cpl_table_fill_invalid_int(slits, name, -1);
14224  snprintf(name, MAX_COLNAME, "end_%d", i+1);
14225  cpl_table_fill_invalid_int(slits, name, -1);
14226  snprintf(name, MAX_COLNAME, "row_%d", i+1);
14227  cpl_table_fill_invalid_int(slits, name, -1);
14228  }
14229 
14230  return profile;
14231 }
14232 
14233 
14258 cpl_image **mos_extract_objects(cpl_image *science, cpl_image *science_var,
14259  cpl_image *sky,
14260  cpl_table *objects, int extraction, double ron,
14261  double gain, int ncombined)
14262 {
14263  const char *func = "mos_extract_objects";
14264 
14265  char name[MAX_COLNAME];
14266 
14267  cpl_image **output;
14268  cpl_image *extracted;
14269  cpl_image *extr_sky;
14270  cpl_image *error;
14271  cpl_image *sciwin;
14272  cpl_image *sci_var_win = NULL;
14273  cpl_image *skywin;
14274  int nslits;
14275  int nobjects;
14276  int maxobjects;
14277  int nx;
14278  int ylow, yhig;
14279  int i, j;
14280 
14281 
14282  if (science == NULL || sky == NULL) {
14283  cpl_msg_error(func, "Both scientific exposures are required in input");
14284  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
14285  return NULL;
14286  }
14287 
14288  if (objects == NULL) {
14289  cpl_msg_error(func, "An object table is required in input");
14290  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
14291  return NULL;
14292  }
14293 
14294  if (extraction < 0 || extraction > 1) {
14295  cpl_msg_error(func, "Invalid extraction mode (%d): it should be "
14296  "either 0 or 1", extraction);
14297  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
14298  return NULL;
14299  }
14300 
14301  if (ron < 0.0) {
14302  cpl_msg_error(func, "Invalid read-out-noise (%f ADU)", ron);
14303  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
14304  return NULL;
14305  }
14306 
14307  if (gain < 0.1) {
14308  cpl_msg_error(func, "Invalid gain factor (%f e-/ADU)", gain);
14309  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
14310  return NULL;
14311  }
14312 
14313  if (ncombined < 1) {
14314  cpl_msg_error(func, "Invalid number of combined frames (%d): "
14315  "it should be at least 1", ncombined);
14316  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
14317  return NULL;
14318  }
14319 
14320 
14321  /*
14322  * Count the max number of objects per slit. Note that maxobjects
14323  * is intentionally the max number of objects increased by one.
14324  */
14325 
14326  maxobjects = 1;
14327  snprintf(name, MAX_COLNAME, "object_%d", maxobjects);
14328  while (cpl_table_has_column(objects, name)) {
14329  maxobjects++;
14330  snprintf(name, MAX_COLNAME, "object_%d", maxobjects);
14331  }
14332 
14333 
14334  /*
14335  * Count objects to extract
14336  */
14337 
14338  nobjects = 0;
14339  nslits = cpl_table_get_nrow(objects);
14340 
14341  for (i = 0; i < nslits; i++) {
14342  for (j = 1; j < maxobjects; j++) {
14343  snprintf(name, MAX_COLNAME, "object_%d", j);
14344  if (cpl_table_is_valid(objects, name, i))
14345  nobjects++;
14346  }
14347  }
14348 
14349  if (nobjects == 0)
14350  return NULL;
14351 
14352  nx = cpl_image_get_size_x(science);
14353 
14354  output = cpl_calloc(3, sizeof(cpl_image *));
14355  extracted = output[0] = cpl_image_new(nx, nobjects, CPL_TYPE_FLOAT);
14356  extr_sky = output[1] = cpl_image_new(nx, nobjects, CPL_TYPE_FLOAT);
14357  error = output[2] = cpl_image_new(nx, nobjects, CPL_TYPE_FLOAT);
14358 
14359 
14360  /*
14361  * Extract objects
14362  */
14363 
14364  nobjects = 0;
14365  for (i = 0; i < nslits; i++) {
14366  for (j = 1; j < maxobjects; j++) {
14367  snprintf(name, MAX_COLNAME, "object_%d", j);
14368  if (cpl_table_is_valid(objects, name, i)) {
14369  snprintf(name, MAX_COLNAME, "start_%d", j);
14370  ylow = cpl_table_get_int(objects, name, i, NULL);
14371  snprintf(name, MAX_COLNAME, "end_%d", j);
14372  yhig = cpl_table_get_int(objects, name, i, NULL);
14373  snprintf(name, MAX_COLNAME, "row_%d", j);
14374  nobjects = cpl_table_get_int(objects, name, i, NULL);
14375  sciwin = cpl_image_extract(science, 1, ylow+1, nx, yhig);
14376  if(science_var != NULL)
14377  sci_var_win = cpl_image_extract(science_var, 1, ylow+1, nx, yhig);
14378  skywin = cpl_image_extract(sky, 1, ylow+1, nx, yhig);
14379 /*
14380  * Cleaning the cosmics locally was really NOT a good idea...
14381  * I leave it here, commented out, to never forget this mistake!
14382 
14383  if (extraction) {
14384  mos_clean_cosmics(sciwin, gain, -1., -1.);
14385  }
14386  */
14387  mos_extraction(sciwin, sci_var_win, skywin, extracted, extr_sky, error,
14388  nobjects, extraction, ron, gain, ncombined);
14389 
14390  /*
14391  * Hidden check whether the spectrum was saturated or not
14392  */
14393 
14394  {
14395  cpl_image *total = cpl_image_add_create(sciwin, skywin);
14396  float *data = cpl_image_get_data_float(total);
14397  int size = cpl_image_get_size_x(total)
14398  * cpl_image_get_size_y(total);
14399  int k;
14400  char *saturation_level = getenv("SATURATION_LEVEL");
14401  float saturation = 62000.0;
14402  char *max_saturated = getenv("MAX_SATURATED");
14403  int max_satur = 10;
14404  int saturated;
14405 
14406  if (saturation_level)
14407  saturation = atof(saturation_level);
14408 
14409  if (max_saturated)
14410  max_satur = atoi(max_saturated);
14411 
14412  saturated = 0;
14413  for (k = 0; k < size; k++) {
14414  if (data[k] > saturation) {
14415  saturated++;
14416  if (saturated > max_satur) {
14417  break;
14418  }
14419  }
14420  }
14421 
14422  if (saturated > max_satur)
14423  saturated = 1;
14424  else
14425  saturated = 0;
14426 
14427  data = cpl_image_get_data(extracted);
14428  data[nobjects * nx] = saturated;
14429  }
14430 
14431  cpl_image_delete(sciwin);
14432  cpl_image_delete(skywin);
14433  nobjects++;
14434  }
14435  }
14436  }
14437 
14438  return output;
14439 
14440 }
14441 
14442 
14465 int mos_spectral_resolution(cpl_image *image, double lambda, double startwave,
14466  double dispersion, int saturation,
14467  double *mfwhm, double *rmsfwhm,
14468  double *resolution, double *rmsres, int *nlines)
14469 {
14470  cpl_vector *vector;
14471 
14472  int i, j, n, m;
14473  int position, maxpos;
14474  int xlen, ylen;
14475  int sp, ep;
14476  int radius;
14477  int sradius = 40;
14478  int threshold = 250; /* Peak must be so many ADUs above min */
14479 
14480  int ifwhm;
14481  double fwhm;
14482  double *buffer;
14483  double min, max, halfmax;
14484  double cut = 1.5; /* To cut outliers from FWHM values (pixel) */
14485  double value, rms;
14486 
14487  float *data;
14488 
14489 
14490  *resolution = 0.0;
14491  *rmsres = 0.0;
14492  *nlines = 0;
14493 
14494  xlen = cpl_image_get_size_x(image);
14495  ylen = cpl_image_get_size_y(image);
14496  data = cpl_image_get_data(image);
14497 
14498  buffer = cpl_malloc(ylen * sizeof(double));
14499 
14500  /*
14501  * Closest pixel to specified wavelength.
14502  */
14503 
14504  position = floor((lambda - startwave) / dispersion + 0.5);
14505 
14506  sp = position - sradius;
14507  ep = position + sradius;
14508 
14509  if (sp < 0 || ep > xlen) {
14510  cpl_free(buffer);
14511  return 0;
14512  }
14513 
14514  for (i = 0, n = 0; i < ylen; i++) { /* For each row of each slit */
14515 
14516  /*
14517  * Search interval for peak. Abort if too close to image border.
14518  */
14519 
14520  radius = mos_lines_width(data + i*xlen + position - sradius,
14521  2*sradius + 1);
14522  if (radius < 5)
14523  radius = 5;
14524 
14525  sp = position - radius;
14526  ep = position + radius;
14527 
14528  if (sp < 0 || ep > xlen) {
14529  cpl_free(buffer);
14530  return 0;
14531  }
14532 
14533 
14534  /*
14535  * Determine min-max value and position.
14536  */
14537 
14538  maxpos = sp;
14539  min = max = data[sp + i * xlen];
14540  for (j = sp; j < ep; j++) {
14541  if (data[j + i * xlen] > max) {
14542  max = data[j + i * xlen];
14543  maxpos = j;
14544  }
14545  if (data[j + i * xlen] < min) {
14546  min = data[j + i * xlen];
14547  }
14548  }
14549 
14550  if (fabs(min) < 0.0000001) /* Truncated spectrum */
14551  continue;
14552 
14553  if (max - min < threshold) /* Low signal... */
14554  continue;
14555 
14556  if (max > saturation) /* Saturation */
14557  continue;
14558 
14559  /*
14560  * Determine FWHM counting pixels with value greater than
14561  * half of the max value, to the right and to the left of
14562  * the max. Linear interpolation between the pixels where
14563  * the transition happens.
14564  */
14565 
14566  halfmax = (max + min)/ 2.0;
14567 
14568  fwhm = 0.0;
14569  ifwhm = 0;
14570  for (j = maxpos; j < maxpos + radius; j++) {
14571  if (j < xlen) {
14572  if (data[j + i * xlen] < halfmax) {
14573  fwhm = ifwhm + (data[j - 1 + i * xlen] - halfmax)
14574  / (data[j - 1 + i * xlen] - data[j + i * xlen]);
14575  break;
14576  }
14577  ifwhm++;
14578  }
14579  }
14580 
14581  ifwhm = 0;
14582  for (j = maxpos; j > maxpos - radius; j--) {
14583  if (j >= 0) {
14584  if (data[j + i * xlen] < halfmax) {
14585  fwhm += ifwhm + (data[j + 1 + i * xlen] - halfmax)
14586  / (data[j + 1 + i * xlen] - data[j + i * xlen]);
14587  break;
14588  }
14589  ifwhm++;
14590  }
14591  }
14592 
14593  if (fwhm > 3.0) {
14594  buffer[n] = fwhm - 2.0;
14595  n++;
14596  }
14597 
14598  }
14599 
14600  if (n == 0) {
14601  cpl_free(buffer);
14602  return 0;
14603  }
14604 
14605  vector = cpl_vector_wrap(n, buffer);
14606  value = cpl_vector_get_median_const(vector);
14607  cpl_vector_unwrap(vector);
14608 
14609  rms = 0.0;
14610  for (i = 0, m = 0; i < n; i++) {
14611  if (fabs(buffer[i] - value) < cut) {
14612  rms += fabs(buffer[i] - value);
14613  m++;
14614  }
14615  }
14616 
14617  cpl_free(buffer);
14618 
14619  if (m < 3)
14620  return 0;
14621 
14622  rms /= m;
14623  rms *= 1.25; /* Factor to convert average deviation to sigma */
14624 
14625  value *= dispersion;
14626  rms *= dispersion;
14627 
14628  *mfwhm = value;
14629  *rmsfwhm = rms;
14630 
14631  *resolution = lambda / value;
14632  *rmsres = *resolution * rms / value;
14633 
14634  *nlines = m;
14635 
14636  return 1;
14637 }
14638 
14639 
14661 cpl_table *mos_resolution_table(cpl_image *image, double startwave,
14662  double dispersion, int saturation,
14663  cpl_vector *lines)
14664 {
14665 
14666  cpl_table *table;
14667  double *line;
14668  double fwhm;
14669  double rmsfwhm;
14670  double resolution;
14671  double rmsres;
14672  int nref;
14673  int nlines;
14674  int i;
14675 
14676 
14677  nref = cpl_vector_get_size(lines);
14678  line = cpl_vector_get_data(lines);
14679 
14680  table = cpl_table_new(nref);
14681  cpl_table_new_column(table, "wavelength", CPL_TYPE_DOUBLE);
14682  cpl_table_set_column_unit(table, "wavelength", "Angstrom");
14683  cpl_table_new_column(table, "fwhm", CPL_TYPE_DOUBLE);
14684  cpl_table_set_column_unit(table, "fwhm", "Angstrom");
14685  cpl_table_new_column(table, "fwhm_rms", CPL_TYPE_DOUBLE);
14686  cpl_table_set_column_unit(table, "fwhm_rms", "Angstrom");
14687  cpl_table_new_column(table, "resolution", CPL_TYPE_DOUBLE);
14688  cpl_table_new_column(table, "resolution_rms", CPL_TYPE_DOUBLE);
14689  cpl_table_new_column(table, "nlines", CPL_TYPE_INT);
14690 
14691  for (i = 0; i < nref; i++) {
14692  if (mos_spectral_resolution(image, line[i], startwave, dispersion,
14693  saturation, &fwhm, &rmsfwhm,
14694  &resolution, &rmsres, &nlines)) {
14695  cpl_table_set_double(table, "wavelength", i, line[i]);
14696  cpl_table_set_double(table, "fwhm", i, fwhm);
14697  cpl_table_set_double(table, "fwhm_rms", i, rmsfwhm);
14698  cpl_table_set_double(table, "resolution", i, resolution);
14699  cpl_table_set_double(table, "resolution_rms", i, rmsres);
14700  cpl_table_set_int(table, "nlines", i, nlines);
14701  }
14702  else
14703  cpl_table_set_int(table, "nlines", i, 0);
14704  }
14705 
14706  if (cpl_table_has_valid(table, "wavelength"))
14707  return table;
14708 
14709  cpl_table_delete(table);
14710 
14711  return NULL;
14712 
14713 }
14714 
14715 
14733 double mos_integrate_signal(cpl_image *image, cpl_image *wavemap,
14734  int ystart, int yend, double wstart, double wend)
14735 {
14736  const char *func = "mos_integrate_signal";
14737 
14738  double sum;
14739  float *sdata;
14740  float *wdata;
14741  int nx, ny;
14742  int x, y;
14743 
14744 
14745  if (image == NULL || wavemap == NULL) {
14746  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
14747  return 0.0;
14748  }
14749 
14750  if (ystart > yend || wstart >= wend) {
14751  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
14752  return 0.0;
14753  }
14754 
14755  nx = cpl_image_get_size_x(image);
14756  ny = cpl_image_get_size_y(image);
14757 
14758  if (!(nx == cpl_image_get_size_x(wavemap)
14759  && ny == cpl_image_get_size_y(wavemap))) {
14760  cpl_error_set(func, CPL_ERROR_INCOMPATIBLE_INPUT);
14761  return 0.0;
14762  }
14763 
14764  if (ystart < 0 || yend > ny) {
14765  cpl_error_set(func, CPL_ERROR_ACCESS_OUT_OF_RANGE);
14766  return 0.0;
14767  }
14768 
14769  sdata = cpl_image_get_data(image);
14770  wdata = cpl_image_get_data(wavemap);
14771 
14772  sdata += ystart*nx;
14773  wdata += ystart*nx;
14774 
14775  sum = 0.0;
14776  for (y = ystart; y < yend; y++) {
14777  for (x = 0; x < nx; x++) {
14778  if (wdata[x] < wstart || wdata[x] > wend)
14779  continue;
14780  sum += sdata[x];
14781  }
14782  sdata += nx;
14783  wdata += nx;
14784  }
14785 
14786  return sum;
14787 
14788 }
14789 
14790 /****************************************************************************
14791  * From this point on, the instrument dependent functions are added:
14792  * they are functions that retrieve information that is stored in
14793  * the data headers in some instrument specific way, such as the
14794  * location of overscans, the slits positions on the telescope
14795  * focal plane, the gain factor, etc.
14796  */
14797 
14798 
14821 cpl_table *mos_load_slits_fors_mxu(cpl_propertylist *header)
14822 {
14823  const char *func = "mos_load_slits_fors_mxu";
14824 
14825  cpl_table *slits;
14826  char keyname[MAX_COLNAME];
14827  const char *instrume;
14828  const char *target_name;
14829  float slit_x;
14830  float slit_y;
14831  float length;
14832 /* double arc2mm = 0.53316; */
14833  double arc2mm = 0.528;
14834  int nslits;
14835  int slit_id;
14836  int fors;
14837  int chip;
14838  int found;
14839 
14840  /*
14841  * The limits below are used to exclude from the loaded slit list
14842  * any slit that surely doesn't belong to the used chip. This is
14843  * a way to reduce the chance of ambiguous slit identification.
14844  */
14845 
14846  float low_limit1 = 10.0;
14847  float hig_limit2 = 30.0;
14848 
14849 
14850  if (cpl_error_get_code() != CPL_ERROR_NONE) {
14851  return NULL;
14852  }
14853 
14854  if (header == NULL) {
14855  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
14856  return NULL;
14857  }
14858 
14859 
14860  /*
14861  * See if this is FORS1 or FORS2;
14862  */
14863 
14864  instrume = cpl_propertylist_get_string(header, "INSTRUME");
14865 
14866  fors = 0;
14867  if (instrume[4] == '1')
14868  fors = 1;
14869  if (instrume[4] == '2')
14870  fors = 2;
14871 
14872  if (fors != 2) {
14873  cpl_msg_error(func, "Wrong instrument: %s\n"
14874  "FORS2 is expected for MXU data", instrume);
14875  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
14876  return NULL;
14877  }
14878 
14879 
14880  /*
14881  * The master and slave chips can be identified by their positions
14882  * in the chip array in the case of FORS2 data (with fors1 the chip
14883  * is always 1). chip = 2 is the master, chip = 1 is the slave.
14884  */
14885 
14886  chip = cpl_propertylist_get_int(header, "ESO DET CHIP1 Y");
14887 
14888  if (cpl_error_get_code() != CPL_ERROR_NONE) {
14889  cpl_msg_error(func, "Missing keyword ESO DET CHIP1 Y "
14890  "in FITS header");
14891  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
14892  return NULL;
14893  }
14894 
14895  if (chip != 1 && chip != 2) {
14896  cpl_msg_error(func, "Unexpected chip position in keyword "
14897  "ESO DET CHIP1 Y: %d", chip);
14898  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
14899  return NULL;
14900  }
14901 
14902 
14903  /*
14904  * Count slits in header (excluding reference slits, and the slits
14905  * that _surely_ belong to the other chip)
14906  */
14907 
14908  nslits = 0;
14909  slit_id = 0;
14910  found = 1;
14911 
14912  while (found) {
14913  slit_id++;
14914  snprintf(keyname, MAX_COLNAME, "ESO INS MOS%d YPOS", slit_id + 100);
14915  if (cpl_propertylist_has(header, keyname)) {
14916  slit_y = cpl_propertylist_get_double(header, keyname);
14917 
14918  if (chip == 1)
14919  if (slit_y < low_limit1)
14920  continue;
14921  if (chip == 2)
14922  if (slit_y > hig_limit2)
14923  continue;
14924 
14925  snprintf(keyname, MAX_COLNAME, "ESO INS TARG%d NAME",
14926  slit_id + 100);
14927  if (cpl_propertylist_has(header, keyname)) {
14928  target_name = cpl_propertylist_get_string(header, keyname);
14929  if (strncmp(target_name, "refslit", 7))
14930  nslits++;
14931  }
14932  else
14933  nslits++;
14934  }
14935  else
14936  found = 0;
14937  }
14938 
14939  if (cpl_error_get_code() != CPL_ERROR_NONE) {
14940  cpl_msg_error(func, "%s while loading slits coordinates from "
14941  "FITS header", cpl_error_get_message());
14942  cpl_error_set_where(func);
14943  return NULL;
14944  }
14945 
14946  if (nslits == 0) {
14947  cpl_msg_error(func, "No slits coordinates found in header");
14948  cpl_error_set(func, CPL_ERROR_DATA_NOT_FOUND);
14949  return NULL;
14950  }
14951 
14952  slits = cpl_table_new(nslits);
14953  cpl_table_new_column(slits, "slit_id", CPL_TYPE_INT);
14954  cpl_table_new_column(slits, "xtop", CPL_TYPE_DOUBLE);
14955  cpl_table_new_column(slits, "ytop", CPL_TYPE_DOUBLE);
14956  cpl_table_new_column(slits, "xbottom", CPL_TYPE_DOUBLE);
14957  cpl_table_new_column(slits, "ybottom", CPL_TYPE_DOUBLE);
14958  cpl_table_set_column_unit(slits, "xtop", "pixel");
14959  cpl_table_set_column_unit(slits, "ytop", "pixel");
14960  cpl_table_set_column_unit(slits, "xbottom", "pixel");
14961  cpl_table_set_column_unit(slits, "ybottom", "pixel");
14962 
14963  nslits = 0;
14964  slit_id = 0;
14965  found = 1;
14966  while (found) {
14967  slit_id++;
14968  snprintf(keyname, MAX_COLNAME, "ESO INS MOS%d YPOS", slit_id + 100);
14969  if (cpl_propertylist_has(header, keyname)) {
14970  slit_y = cpl_propertylist_get_double(header, keyname);
14971 
14972  if (chip == 1)
14973  if (slit_y < low_limit1)
14974  continue;
14975  if (chip == 2)
14976  if (slit_y > hig_limit2)
14977  continue;
14978 
14979  /*
14980  * Y-flip the slit position, to match CCD pixel coordinate
14981  * convention
14982  */
14983 
14984  slit_y = -slit_y;
14985 
14986  snprintf(keyname, MAX_COLNAME, "ESO INS MOS%d XPOS", slit_id + 100);
14987  slit_x = cpl_propertylist_get_double(header, keyname);
14988  if (cpl_error_get_code() != CPL_ERROR_NONE) {
14989  cpl_table_delete(slits);
14990  cpl_msg_error(func, "Missing keyword %s in FITS header",
14991  keyname);
14992  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
14993  return NULL;
14994  }
14995 
14996  snprintf(keyname, MAX_COLNAME, "ESO INS MOS%d LEN", slit_id + 100);
14997  length = cpl_propertylist_get_double(header, keyname);
14998  if (cpl_error_get_code() != CPL_ERROR_NONE) {
14999  cpl_table_delete(slits);
15000  cpl_msg_error(func, "Missing keyword %s in FITS header",
15001  keyname);
15002  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
15003  return NULL;
15004  }
15005 
15006  length *= arc2mm;
15007 
15008  snprintf(keyname, MAX_COLNAME, "ESO INS TARG%d NAME",
15009  slit_id + 100);
15010  if (cpl_propertylist_has(header, keyname)) {
15011  target_name = cpl_propertylist_get_string(header, keyname);
15012  if (strncmp(target_name, "refslit", 7)) {
15013  cpl_table_set_int(slits, "slit_id", nslits, slit_id);
15014  cpl_table_set(slits, "xtop", nslits, slit_x);
15015  cpl_table_set(slits, "ytop", nslits, slit_y + length/2);
15016  cpl_table_set(slits, "xbottom", nslits, slit_x);
15017  cpl_table_set(slits, "ybottom", nslits, slit_y - length/2);
15018  nslits++;
15019  }
15020  }
15021  else {
15022  cpl_table_set_int(slits, "slit_id", nslits, slit_id);
15023  cpl_table_set(slits, "xtop", nslits, slit_x);
15024  cpl_table_set(slits, "ytop", nslits, slit_y + length/2);
15025  cpl_table_set(slits, "xbottom", nslits, slit_x);
15026  cpl_table_set(slits, "ybottom", nslits, slit_y - length/2);
15027  nslits++;
15028  }
15029  }
15030  else
15031  found = 0;
15032  }
15033 
15034  return slits;
15035 }
15036 
15037 
15061 cpl_table *mos_load_slits_fors_mos(cpl_propertylist *header,
15062  int * nslits_out_det)
15063 {
15064  const char *func = "mos_load_slits_fors_mos";
15065 
15066  cpl_table *slits;
15067  char keyname[MAX_COLNAME];
15068  const char *instrume;
15069  const char *chipname;
15070  float slit_x;
15071  int first_slit, last_slit;
15072  cpl_size nslits;
15073  int slit_id;
15074  int fors;
15075  int chip;
15076  int fors_is_old;
15077 
15078  /*
15079  * The Y coordinates of the slits are fixed
15080  */
15081 
15082  float ytop[19] = { 113.9, 101.3, 89.9, 77.3, 65.9, 53.3,
15083  41.9, 29.3, 17.9, 5.3, -6.1, -18.7,
15084  -30.1, -42.7, -54.1, -66.7, -78.1, -90.7,
15085  -102.1 };
15086  float ybottom[19] = { 102.1, 90.7, 78.1, 66.7, 54.1, 42.7,
15087  30.1, 18.7, 6.1, -5.3, -17.9, -29.3,
15088  -41.9, -53.3, -65.9, -77.3, -89.9, -101.3,
15089  -113.9 };
15090 
15091 
15092  if (cpl_error_get_code() != CPL_ERROR_NONE) {
15093  return NULL;
15094  }
15095 
15096  if (header == NULL) {
15097  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
15098  return NULL;
15099  }
15100 
15101 
15102  /*
15103  * See if this is FORS1 or FORS2;
15104  */
15105 
15106  instrume = cpl_propertylist_get_string(header, "INSTRUME");
15107 
15108  fors = 0;
15109  if (instrume[4] == '1')
15110  fors = 1;
15111  if (instrume[4] == '2')
15112  fors = 2;
15113 
15114  if (fors == 0) {
15115  cpl_msg_error(func, "Wrong instrument found in FITS header: %s",
15116  instrume);
15117  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
15118  return NULL;
15119  }
15120 
15121  /* FIXME:
15122  * This is the way FORS1 data belong to the upgraded chips,
15123  * named "Marlene" and "Norma III". It's a quick solution,
15124  * there are hardcoded values here!!!
15125  */
15126 
15127  chipname = cpl_propertylist_get_string(header, "ESO DET CHIP1 ID");
15128 
15129  if (chipname[0] == 'M' || chipname[0] == 'N')
15130  fors_is_old = 0;
15131  else
15132  fors_is_old = 1;
15133 
15134  if (fors == 1 && fors_is_old) {
15135  first_slit = 1;
15136  last_slit = 19;
15137  }
15138  else {
15139 
15140  /*
15141  * The master and slave chips can be identified by their positions
15142  * in the chip array in the case of FORS2 data: chip = 2 is the
15143  * master, chip = 1 is the slave.
15144  */
15145 
15146  chip = cpl_propertylist_get_int(header, "ESO DET CHIP1 Y");
15147 
15148  if (cpl_error_get_code() != CPL_ERROR_NONE) {
15149  cpl_msg_error(func, "Missing keyword ESO DET CHIP1 Y "
15150  "in FITS header");
15151  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
15152  return NULL;
15153  }
15154 
15155  if (chip != 1 && chip != 2) {
15156  cpl_msg_error(func, "Unexpected chip position in keyword "
15157  "ESO DET CHIP1 Y: %d", chip);
15158  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
15159  return NULL;
15160  }
15161 
15162  if (chip == 1) {
15163  first_slit = 12;
15164  last_slit = 19;
15165  }
15166  else {
15167  first_slit = 1;
15168  last_slit = 11;
15169  }
15170  }
15171 
15172 
15173  /*
15174  * Count slits in header (excluding closed slits - i.e. those with
15175  * offsets greater than 115 mm - and the slits that do not belong
15176  * to this chip)
15177  */
15178 
15179  nslits = 0;
15180  slit_id = 0;
15181  for (slit_id = first_slit; slit_id <= last_slit; slit_id++) {
15182  snprintf(keyname, MAX_COLNAME, "ESO INS MOS%d POS", slit_id);
15183  if (cpl_propertylist_has(header, keyname)) {
15184  slit_x = cpl_propertylist_get_double(header, keyname);
15185  if (fabs(slit_x) < 115.0)
15186  nslits++;
15187  else
15188  (*nslits_out_det)++;
15189  }
15190  else {
15191  cpl_msg_error(func, "Missing keyword %s in FITS header", keyname);
15192  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
15193  return NULL;
15194  }
15195  }
15196 
15197  if (cpl_error_get_code() != CPL_ERROR_NONE) {
15198  cpl_msg_error(func, "%s while loading slits coordinates from "
15199  "FITS header", cpl_error_get_message());
15200  cpl_error_set_where(func);
15201  return NULL;
15202  }
15203 
15204  if (nslits == 0) {
15205  cpl_msg_error(func, "No slits coordinates found in header");
15206  cpl_error_set(func, CPL_ERROR_DATA_NOT_FOUND);
15207  return NULL;
15208  }
15209 
15210  slits = cpl_table_new(nslits);
15211  cpl_table_new_column(slits, "slit_id", CPL_TYPE_INT);
15212  cpl_table_new_column(slits, "xtop", CPL_TYPE_DOUBLE);
15213  cpl_table_new_column(slits, "ytop", CPL_TYPE_DOUBLE);
15214  cpl_table_new_column(slits, "xbottom", CPL_TYPE_DOUBLE);
15215  cpl_table_new_column(slits, "ybottom", CPL_TYPE_DOUBLE);
15216  cpl_table_set_column_unit(slits, "xtop", "pixel");
15217  cpl_table_set_column_unit(slits, "ytop", "pixel");
15218  cpl_table_set_column_unit(slits, "xbottom", "pixel");
15219  cpl_table_set_column_unit(slits, "ybottom", "pixel");
15220 
15221  nslits = 0;
15222  slit_id = 0;
15223  for (slit_id = first_slit; slit_id <= last_slit; slit_id++) {
15224  snprintf(keyname, MAX_COLNAME, "ESO INS MOS%d POS", slit_id);
15225  slit_x = cpl_propertylist_get_double(header, keyname);
15226  if (fabs(slit_x) < 115.0) {
15227  cpl_table_set_int(slits, "slit_id", nslits, slit_id);
15228  cpl_table_set(slits, "xtop", nslits, slit_x);
15229  cpl_table_set(slits, "ytop", nslits, ytop[slit_id-1]);
15230  cpl_table_set(slits, "xbottom", nslits, slit_x);
15231  cpl_table_set(slits, "ybottom", nslits, ybottom[slit_id-1]);
15232  nslits++;
15233  }
15234  }
15235 
15236  return slits;
15237 }
15238 
15239 
15263 cpl_table *mos_load_slits_fors_lss(cpl_propertylist *header)
15264 {
15265  const char *func = "mos_load_slits_fors_lss";
15266 
15267  cpl_table *slits;
15268  char *slit_name;
15269  const char *instrume;
15270  int fors;
15271  int chip;
15272  float ytop;
15273  float ybottom;
15274 
15275  if (cpl_error_get_code() != CPL_ERROR_NONE) {
15276  return NULL;
15277  }
15278 
15279  if (header == NULL) {
15280  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
15281  return NULL;
15282  }
15283 
15284 
15285  /*
15286  * See if this is FORS1 or FORS2;
15287  */
15288 
15289  instrume = cpl_propertylist_get_string(header, "INSTRUME");
15290 
15291  fors = 0;
15292  if (instrume[4] == '1')
15293  fors = 1;
15294  if (instrume[4] == '2')
15295  fors = 2;
15296 
15297  if (fors == 0) {
15298  cpl_msg_error(func, "Wrong instrument found in FITS header: %s",
15299  instrume);
15300  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
15301  return NULL;
15302  }
15303 
15304  if (fors == 1) {
15305  ytop = 109.94;
15306  ybottom = -109.94;
15307  }
15308  else {
15309 
15310  /*
15311  * The master and slave chips can be identified by their positions
15312  * in the chip array in the case of FORS2 data: chip = 2 is the
15313  * master, chip = 1 is the slave.
15314  */
15315 
15316  chip = cpl_propertylist_get_int(header, "ESO DET CHIP1 Y");
15317 
15318  if (cpl_error_get_code() != CPL_ERROR_NONE) {
15319  cpl_msg_error(func, "Missing keyword ESO DET CHIP1 Y "
15320  "in FITS header");
15321  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
15322  return NULL;
15323  }
15324 
15325  if (chip != 1 && chip != 2) {
15326  cpl_msg_error(func, "Unexpected chip position in keyword "
15327  "ESO DET CHIP1 Y: %d", chip);
15328  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
15329  return NULL;
15330  }
15331 
15332  if (chip == 1) {
15333  ytop = 30.0;
15334  ybottom = -109.94;
15335  }
15336  else {
15337  ytop = 109.94;
15338  ybottom = -20.0;
15339  }
15340  }
15341 
15342 
15343  slits = cpl_table_new(1);
15344  cpl_table_new_column(slits, "slit_id", CPL_TYPE_INT);
15345  cpl_table_new_column(slits, "xtop", CPL_TYPE_DOUBLE);
15346  cpl_table_new_column(slits, "ytop", CPL_TYPE_DOUBLE);
15347  cpl_table_new_column(slits, "xbottom", CPL_TYPE_DOUBLE);
15348  cpl_table_new_column(slits, "ybottom", CPL_TYPE_DOUBLE);
15349  cpl_table_set_column_unit(slits, "xtop", "pixel");
15350  cpl_table_set_column_unit(slits, "ytop", "pixel");
15351  cpl_table_set_column_unit(slits, "xbottom", "pixel");
15352  cpl_table_set_column_unit(slits, "ybottom", "pixel");
15353 
15354  slit_name = (char *)cpl_propertylist_get_string(header,
15355  "ESO INS SLIT NAME");
15356 
15357  cpl_table_set(slits, "ytop", 0, ytop);
15358  cpl_table_set(slits, "ybottom", 0, ybottom);
15359 
15360  if (!strncmp(slit_name, "lSlit0_3arcsec", 14)) {
15361  cpl_table_set_int(slits, "slit_id", 0, 1);
15362  cpl_table_set(slits, "xbottom", 0, -0.075);
15363  cpl_table_set(slits, "xtop", 0, 0.075);
15364  }
15365  else if (!strncmp(slit_name, "lSlit0_4arcsec", 14)) {
15366  cpl_table_set_int(slits, "slit_id", 0, 2);
15367  cpl_table_set(slits, "xbottom", 0, 5.895);
15368  cpl_table_set(slits, "xtop", 0, 6.105);
15369  }
15370  else if (!strncmp(slit_name, "lSlit0_5arcsec", 14)) {
15371  cpl_table_set_int(slits, "slit_id", 0, 3);
15372  cpl_table_set(slits, "xbottom", 0, -6.135);
15373  cpl_table_set(slits, "xtop", 0, -5.865);
15374  }
15375  else if (!strncmp(slit_name, "lSlit0_7arcsec", 14)) {
15376  cpl_table_set_int(slits, "slit_id", 0, 4);
15377  cpl_table_set(slits, "xbottom", 0, 11.815);
15378  cpl_table_set(slits, "xtop", 0, 12.185);
15379  }
15380  else if (!strncmp(slit_name, "lSlit1_0arcsec", 14)) {
15381  cpl_table_set_int(slits, "slit_id", 0, 5);
15382  cpl_table_set(slits, "xbottom", 0, -12.265);
15383  cpl_table_set(slits, "xtop", 0, -11.735);
15384  }
15385  else if (!strncmp(slit_name, "lSlit1_3arcsec", 14)) {
15386  cpl_table_set_int(slits, "slit_id", 0, 6);
15387  cpl_table_set(slits, "xbottom", 0, 17.655);
15388  cpl_table_set(slits, "xtop", 0, 18.345);
15389  }
15390  else if (!strncmp(slit_name, "lSlit1_6arcsec", 14)) {
15391  cpl_table_set_int(slits, "slit_id", 0, 7);
15392  cpl_table_set(slits, "xbottom", 0, -18.425);
15393  cpl_table_set(slits, "xtop", 0, -17.575);
15394  }
15395  else if (!strncmp(slit_name, "lSlit2_0arcsec", 14)) {
15396  cpl_table_set_int(slits, "slit_id", 0, 8);
15397  cpl_table_set(slits, "xbottom", 0, 23.475);
15398  cpl_table_set(slits, "xtop", 0, 24.525);
15399  }
15400  else if (!strncmp(slit_name, "lSlit2_5arcsec", 14)) {
15401  cpl_table_set_int(slits, "slit_id", 0, 9);
15402  cpl_table_set(slits, "xbottom", 0, -24.66);
15403  cpl_table_set(slits, "xtop", 0, -23.34);
15404  }
15405  else {
15406  cpl_msg_error(func, "Invalid slit %s in keyword ESO INS SLIT NAME",
15407  slit_name);
15408  cpl_error_set(func, CPL_ERROR_DATA_NOT_FOUND);
15409  cpl_table_delete(slits);
15410  return NULL;
15411  }
15412 
15413  return slits;
15414 }
15415 
15416 
15431 double mos_get_gain_vimos(cpl_propertylist *header)
15432 {
15433  const char *func = "mos_get_gain_vimos";
15434 
15435  double gain = -1.0;
15436 
15437 
15438  if (cpl_error_get_code() != CPL_ERROR_NONE)
15439  return gain;
15440 
15441  if (header == NULL) {
15442  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
15443  return gain;
15444  }
15445 
15446  gain = cpl_propertylist_get_double(header, "ESO DET OUT1 CONAD");
15447  if (cpl_error_get_code()) {
15448  cpl_error_set_where(func);
15449  gain = -1.0;
15450  }
15451 
15452  return gain;
15453 
15454 }
15455 
15456 
15476 cpl_table *mos_load_slits_vimos(cpl_propertylist *header)
15477 {
15478  const char *func = "mos_load_slits_vimos";
15479 
15480  cpl_table *slits;
15481  char keyname[MAX_COLNAME];
15482  float slit_x;
15483  float slit_y;
15484  float dim_x;
15485  float dim_y;
15486  int nslits;
15487  int slit_id;
15488  int curved;
15489  int i;
15490 
15491 
15492  if (cpl_error_get_code() != CPL_ERROR_NONE) {
15493  return NULL;
15494  }
15495 
15496  if (header == NULL) {
15497  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
15498  return NULL;
15499  }
15500 
15501  nslits = cpl_propertylist_get_int(header, "ESO INS SLIT NO");
15502 
15503  if (cpl_error_get_code() != CPL_ERROR_NONE) {
15504  cpl_error_set_where(func);
15505  return NULL;
15506  }
15507 
15508  slits = cpl_table_new(nslits);
15509  cpl_table_new_column(slits, "slit_id", CPL_TYPE_INT);
15510  cpl_table_new_column(slits, "xtop", CPL_TYPE_DOUBLE);
15511  cpl_table_new_column(slits, "ytop", CPL_TYPE_DOUBLE);
15512  cpl_table_new_column(slits, "xbottom", CPL_TYPE_DOUBLE);
15513  cpl_table_new_column(slits, "ybottom", CPL_TYPE_DOUBLE);
15514  cpl_table_new_column(slits, "xwidth", CPL_TYPE_DOUBLE);
15515  cpl_table_new_column(slits, "ywidth", CPL_TYPE_DOUBLE);
15516  cpl_table_new_column(slits, "curved", CPL_TYPE_INT);
15517  cpl_table_set_column_unit(slits, "xtop", "pixel");
15518  cpl_table_set_column_unit(slits, "ytop", "pixel");
15519  cpl_table_set_column_unit(slits, "xbottom", "pixel");
15520  cpl_table_set_column_unit(slits, "ybottom", "pixel");
15521  cpl_table_set_column_unit(slits, "xwidth", "mm");
15522  cpl_table_set_column_unit(slits, "ywidth", "mm");
15523 
15524  for (i = 0; i < nslits; i++) {
15525  sprintf(keyname, "ESO INS SLIT%d ID", i+1);
15526  slit_id = cpl_propertylist_get_int(header, keyname);
15527  if (cpl_error_get_code() != CPL_ERROR_NONE) {
15528  cpl_error_set_where(func);
15529  return NULL;
15530  }
15531  sprintf(keyname, "ESO INS SLIT%d X", i+1);
15532  slit_x = cpl_propertylist_get_double(header, keyname);
15533  if (cpl_error_get_code() != CPL_ERROR_NONE) {
15534  cpl_error_set_where(func);
15535  return NULL;
15536  }
15537  sprintf(keyname, "ESO INS SLIT%d Y", i+1);
15538  slit_y = cpl_propertylist_get_double(header, keyname);
15539  if (cpl_error_get_code() != CPL_ERROR_NONE) {
15540  cpl_error_set_where(func);
15541  return NULL;
15542  }
15543  sprintf(keyname, "ESO INS SLIT%d DIMX", i+1);
15544  dim_x = cpl_propertylist_get_double(header, keyname);
15545  if (cpl_error_get_code() != CPL_ERROR_NONE) {
15546  cpl_error_set_where(func);
15547  return NULL;
15548  }
15549 
15550  sprintf(keyname, "ESO INS SLIT%d BEZIER DY", i+1);
15551  if (cpl_propertylist_has(header, keyname)) {
15552  curved = 1;
15553  }
15554  else {
15555  sprintf(keyname, "ESO INS SLIT%d DIMY", i+1);
15556  curved = 0;
15557  }
15558  dim_y = cpl_propertylist_get_double(header, keyname);
15559  if (cpl_error_get_code() != CPL_ERROR_NONE) {
15560  cpl_error_set_where(func);
15561  return NULL;
15562  }
15563 
15564  cpl_table_set_int(slits, "slit_id", i, slit_id);
15565  cpl_table_set(slits, "xtop", i, slit_x - dim_x/2);
15566  cpl_table_set(slits, "ytop", i, slit_y);
15567  cpl_table_set(slits, "xbottom", i, slit_x + dim_x/2);
15568  cpl_table_set(slits, "ybottom", i, slit_y);
15569  cpl_table_set(slits, "xwidth", i, dim_x);
15570  cpl_table_set(slits, "ywidth", i, dim_y);
15571  cpl_table_set_int(slits, "curved", i, curved);
15572  }
15573 
15574  return slits;
15575 }
15576 
15577 
15587 int mos_check_multiplex(cpl_table *slits)
15588 {
15589  cpl_propertylist *sort;
15590  int nrow;
15591  int i, multiplex, xprev, xcur;
15592  double prev, cur;
15593  double tolerance = 1.0; // About spatially aligned slits (mm)
15594 
15595 
15596  /*
15597  * Create an auxiliary column containing a sort of integer
15598  * x coordinate of the slit, to guarantee that slits at the
15599  * same spatial offset are recognised immediately as in spectral
15600  * multiplexing.
15601  */
15602 
15603  sort = cpl_propertylist_new();
15604  cpl_propertylist_append_bool(sort, "xtop", 0);
15605  cpl_table_sort(slits, sort);
15606  cpl_propertylist_delete(sort);
15607 
15608  prev = cpl_table_get_double(slits, "xtop", 0, NULL);
15609  cpl_table_new_column(slits, "xind", CPL_TYPE_INT);
15610  cpl_table_set_int(slits, "xind", 0, prev); // cast to int is intentional
15611  nrow = cpl_table_get_nrow(slits);
15612  for (i = 1; i < nrow; i++) {
15613  cur = cpl_table_get_double(slits, "xtop", i, NULL);
15614  if (fabs(prev - cur) > tolerance)
15615  prev = cur;
15616  cpl_table_set_int(slits, "xind", i, prev);
15617  }
15618 
15619  /*
15620  * Now sort according to increasing (integer) x positions, and when
15621  * those are equal (multiplexed) according to the increasing y position.
15622  */
15623 
15624  sort = cpl_propertylist_new();
15625  cpl_propertylist_append_bool(sort, "xind", 0);
15626  cpl_propertylist_append_bool(sort, "ytop", 0);
15627  cpl_table_sort(slits, sort);
15628  cpl_propertylist_delete(sort);
15629 
15630  /*
15631  * Now assign to each slit its multiplex order.
15632  */
15633 
15634  multiplex = 0;
15635  cpl_table_new_column(slits, "multiplex", CPL_TYPE_INT);
15636  xprev = cpl_table_get_int(slits, "xind", 0, NULL);
15637  cpl_table_set_int(slits, "multiplex", 0, multiplex);
15638  nrow = cpl_table_get_nrow(slits);
15639  for (i = 1; i < nrow; i++) {
15640  xcur = cpl_table_get_int(slits, "xind", i, NULL);
15641  if (xcur == xprev) {
15642  multiplex++;
15643  }
15644  else {
15645  xprev = xcur;
15646  multiplex = 0;
15647  }
15648  cpl_table_set_int(slits, "multiplex", i, multiplex);
15649  }
15650 
15651  cpl_table_save(slits, NULL, NULL, "multiplex.fits", CPL_IO_DEFAULT);
15652 
15653  cpl_table_erase_column(slits, "xind");
15654 
15655  return 1 + cpl_table_get_column_max(slits, "multiplex");
15656 
15657 }
15658 
15659 
15686 cpl_table *mos_load_overscans_vimos(const cpl_propertylist *header,
15687  int check_consistency)
15688 {
15689  const char *func = "mos_load_overscans_vimos";
15690 
15691  int nx = 0;
15692  int ny = 0;
15693  int px = 0;
15694  int py = 0;
15695  int ox = 0;
15696  int oy = 0;
15697  int vx = 0;
15698  int vy = 0;
15699  int nrows;
15700  cpl_table *overscans;
15701 
15702 
15703  if (cpl_error_get_code() != CPL_ERROR_NONE) {
15704  cpl_msg_error(func, "Reset your error: %s", cpl_error_get_message());
15705  return NULL;
15706  }
15707 
15708  if (header == NULL) {
15709  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
15710  return NULL;
15711  }
15712 
15713  if (cpl_propertylist_has(header, "NAXIS1"))
15714  nx = cpl_propertylist_get_int(header, "NAXIS1");
15715  if (cpl_propertylist_has(header, "NAXIS2"))
15716  ny = cpl_propertylist_get_int(header, "NAXIS2");
15717  if (cpl_propertylist_has(header, "ESO DET OUT1 PRSCX"))
15718  px = cpl_propertylist_get_int(header, "ESO DET OUT1 PRSCX");
15719  if (cpl_propertylist_has(header, "ESO DET OUT1 PRSCY"))
15720  py = cpl_propertylist_get_int(header, "ESO DET OUT1 PRSCY");
15721  if (cpl_propertylist_has(header, "ESO DET OUT1 OVSCX"))
15722  ox = cpl_propertylist_get_int(header, "ESO DET OUT1 OVSCX");
15723  if (cpl_propertylist_has(header, "ESO DET OUT1 OVSCY"))
15724  oy = cpl_propertylist_get_int(header, "ESO DET OUT1 OVSCY");
15725  if (cpl_propertylist_has(header, "ESO DET OUT1 NX"))
15726  vx = cpl_propertylist_get_int(header, "ESO DET OUT1 NX");
15727  if (cpl_propertylist_has(header, "ESO DET OUT1 NY"))
15728  vy = cpl_propertylist_get_int(header, "ESO DET OUT1 NY");
15729 
15730  if (cpl_error_get_code() != CPL_ERROR_NONE) {
15731  cpl_msg_error(func, "Missing overscan keywords in header");
15732  cpl_error_set_where(func);
15733  return NULL;
15734  }
15735 
15736  if (px < 0 || py < 0 || ox < 0 || oy < 0) {
15737  cpl_msg_error(func, "Missing overscan keywords in header");
15738  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
15739  return NULL;
15740  }
15741 
15742  if ((px + vx + ox != nx) || (py + vy + oy != ny)) {
15743  if (check_consistency) {
15744  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
15745  return NULL;
15746  }
15747  else {
15748  cpl_msg_debug(func, "Overscans description conflicts with "
15749  "reported image sizes, "
15750  "%d + %d + %d != %d or "
15751  "%d + %d + %d != %d",
15752  px, vx, ox, nx,
15753  py, vy, oy, ny);
15754  }
15755  }
15756 
15757  nrows = 0;
15758  if (px > 0)
15759  nrows++;
15760  if (ox > 0)
15761  nrows++;
15762  if (py > 0)
15763  nrows++;
15764  if (oy > 0)
15765  nrows++;
15766 
15767  if (nrows > 2) {
15768  cpl_msg_error(func, "Unexpected overscan regions "
15769  "(both in X and Y direction)");
15770  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
15771  return NULL;
15772  }
15773 
15774 
15775  /*
15776  * A row is added for the description of the valid region of the
15777  * exposure the input header belongs to.
15778  */
15779 
15780  nrows++;
15781 
15782  overscans = cpl_table_new(nrows);
15783  cpl_table_new_column(overscans, "xlow", CPL_TYPE_INT);
15784  cpl_table_new_column(overscans, "ylow", CPL_TYPE_INT);
15785  cpl_table_new_column(overscans, "xhig", CPL_TYPE_INT);
15786  cpl_table_new_column(overscans, "yhig", CPL_TYPE_INT);
15787 
15788  nrows = 0;
15789 
15790  cpl_table_set_int(overscans, "xlow", nrows, px);
15791  cpl_table_set_int(overscans, "ylow", nrows, py);
15792  cpl_table_set_int(overscans, "xhig", nrows, nx - ox);
15793  cpl_table_set_int(overscans, "yhig", nrows, ny - oy);
15794  nrows++;
15795 
15796  if (px > 0) {
15797  cpl_table_set_int(overscans, "xlow", nrows, 0);
15798  cpl_table_set_int(overscans, "ylow", nrows, 0);
15799  cpl_table_set_int(overscans, "xhig", nrows, px);
15800  cpl_table_set_int(overscans, "yhig", nrows, ny);
15801  nrows++;
15802  }
15803 
15804  if (ox > 0) {
15805  cpl_table_set_int(overscans, "xlow", nrows, nx - ox);
15806  cpl_table_set_int(overscans, "ylow", nrows, 0);
15807  cpl_table_set_int(overscans, "xhig", nrows, nx);
15808  cpl_table_set_int(overscans, "yhig", nrows, ny);
15809  nrows++;
15810  }
15811 
15812  if (py > 0) {
15813  cpl_table_set_int(overscans, "xlow", nrows, 0);
15814  cpl_table_set_int(overscans, "ylow", nrows, 0);
15815  cpl_table_set_int(overscans, "xhig", nrows, nx);
15816  cpl_table_set_int(overscans, "yhig", nrows, py);
15817  nrows++;
15818  }
15819 
15820  if (oy > 0) {
15821  cpl_table_set_int(overscans, "xlow", nrows, 0);
15822  cpl_table_set_int(overscans, "ylow", nrows, ny - oy);
15823  cpl_table_set_int(overscans, "xhig", nrows, nx);
15824  cpl_table_set_int(overscans, "yhig", nrows, ny);
15825  nrows++;
15826  }
15827 
15828  return overscans;
15829 
15830 }
15831 
15832 
15833 cpl_table *mos_load_overscans_fors(const cpl_propertylist *header)
15834 {
15835  const char *func = "mos_load_overscans_fors";
15836 
15837  int nports;
15838  int nx = 0;
15839  int ny = 0;
15840  int px = 0;
15841  int py = 0;
15842  int ox = 0;
15843  int oy = 0;
15844  int rebin;
15845  int nrows;
15846  cpl_table *overscans;
15847 
15848 
15849  if (cpl_error_get_code() != CPL_ERROR_NONE) {
15850  cpl_msg_error(func, "Reset your error: %s", cpl_error_get_message());
15851  return NULL;
15852  }
15853 
15854  if (header == NULL) {
15855  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
15856  return NULL;
15857  }
15858 
15859  if (cpl_propertylist_has(header, "ESO DET OUTPUTS"))
15860  nports = cpl_propertylist_get_int(header, "ESO DET OUTPUTS");
15861 
15862  if (nports == 4 &&
15863  cpl_propertylist_has(header, "ESO DET OUT1 PRSCX") &&
15864  cpl_propertylist_has(header, "ESO DET WIN1 BINX")) {
15865 
15866  rebin = cpl_propertylist_get_int(header, "ESO DET WIN1 BINX");
15867 
15868  overscans = cpl_table_new(3);
15869  cpl_table_new_column(overscans, "xlow", CPL_TYPE_INT);
15870  cpl_table_new_column(overscans, "ylow", CPL_TYPE_INT);
15871  cpl_table_new_column(overscans, "xhig", CPL_TYPE_INT);
15872  cpl_table_new_column(overscans, "yhig", CPL_TYPE_INT);
15873 
15874  px = 16 / rebin;
15875  ox = 16 / rebin;
15876  nx = 2080 / rebin;
15877  ny = 2048 / rebin;
15878  nrows = 0;
15879 
15880  cpl_table_set_int(overscans, "xlow", nrows, px);
15881  cpl_table_set_int(overscans, "ylow", nrows, py);
15882  cpl_table_set_int(overscans, "xhig", nrows, nx - ox);
15883  cpl_table_set_int(overscans, "yhig", nrows, ny - oy);
15884  nrows++;
15885 
15886  cpl_table_set_int(overscans, "xlow", nrows, 0);
15887  cpl_table_set_int(overscans, "ylow", nrows, 0);
15888  cpl_table_set_int(overscans, "xhig", nrows, px);
15889  cpl_table_set_int(overscans, "yhig", nrows, ny);
15890  nrows++;
15891 
15892  cpl_table_set_int(overscans, "xlow", nrows, nx - ox);
15893  cpl_table_set_int(overscans, "ylow", nrows, 0);
15894  cpl_table_set_int(overscans, "xhig", nrows, nx);
15895  cpl_table_set_int(overscans, "yhig", nrows, ny);
15896  nrows++;
15897  }
15898  else {
15899  overscans = mos_load_overscans_vimos(header, 0);
15900  }
15901 
15902  return overscans;
15903 
15904 }
15905 
15937 #define READY 1
15938 #ifdef READY
15939 
15940 cpl_polynomial *mos_montecarlo_polyfit(cpl_table *points, cpl_table *evaluate,
15941  int samples, int order)
15942 {
15943 
15944  const char *func = "mos_montecarlo_polyfit";
15945 
15946  cpl_polynomial *p;
15947  cpl_polynomial *q;
15948  cpl_vector *listx;
15949  cpl_vector *listy;
15950  double err;
15951  double *x;
15952  double *px;
15953  double *x_eval;
15954  double *px_eval;
15955  double *sigma;
15956  double *vy;
15957  double *dy;
15958  int npoints, nevaluate;
15959  int i, j;
15960 
15961 
15962  if (points == NULL || evaluate == NULL) {
15963  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
15964  return NULL;
15965  }
15966 
15967  if (!cpl_table_has_column(points, "x")) {
15968  cpl_error_set(func, CPL_ERROR_DATA_NOT_FOUND);
15969  return NULL;
15970  }
15971 
15972  if (cpl_table_get_column_type(points, "x") != CPL_TYPE_DOUBLE) {
15973  cpl_error_set(func, CPL_ERROR_INVALID_TYPE);
15974  return NULL;
15975  }
15976 
15977  if (cpl_table_has_invalid(points, "x")) {
15978  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
15979  return NULL;
15980  }
15981 
15982  if (!cpl_table_has_column(points, "y")) {
15983  cpl_error_set(func, CPL_ERROR_DATA_NOT_FOUND);
15984  return NULL;
15985  }
15986 
15987  if (cpl_table_get_column_type(points, "y") != CPL_TYPE_DOUBLE) {
15988  cpl_error_set(func, CPL_ERROR_INVALID_TYPE);
15989  return NULL;
15990  }
15991 
15992  if (cpl_table_has_invalid(points, "y")) {
15993  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
15994  return NULL;
15995  }
15996 
15997  if (cpl_table_has_column(points, "y_err")) {
15998 
15999  if (cpl_table_get_column_type(points, "y_err") != CPL_TYPE_DOUBLE) {
16000  cpl_error_set(func, CPL_ERROR_INVALID_TYPE);
16001  return NULL;
16002  }
16003 
16004  if (cpl_table_has_invalid(points, "y_err")) {
16005  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
16006  return NULL;
16007  }
16008  }
16009 
16010  if (!cpl_table_has_column(evaluate, "x")) {
16011  cpl_error_set(func, CPL_ERROR_DATA_NOT_FOUND);
16012  return NULL;
16013  }
16014 
16015  if (cpl_table_get_column_type(evaluate, "x") != CPL_TYPE_DOUBLE) {
16016  cpl_error_set(func, CPL_ERROR_INVALID_TYPE);
16017  return NULL;
16018  }
16019 
16020  if (cpl_table_has_invalid(evaluate, "x")) {
16021  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
16022  return NULL;
16023  }
16024 
16025  if (samples < 2 || order < 0) {
16026  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
16027  return NULL;
16028  }
16029 
16030  npoints = cpl_table_get_nrow(points);
16031  listx = cpl_vector_wrap(npoints, cpl_table_get_data_double(points, "x"));
16032  listy = cpl_vector_wrap(npoints, cpl_table_get_data_double(points, "y"));
16033 
16034  p = cpl_polynomial_fit_1d_create(listx, listy, order, &err);
16035 
16036  if (!cpl_table_has_column(points, "y_err")) {
16037  err = sqrt(err);
16038  cpl_table_new_column(points, "y_err", CPL_TYPE_DOUBLE);
16039  cpl_table_fill_column_window_double(points, "y_err", 0, npoints, err);
16040  cpl_msg_info(func, "Error column not found - set to %f\n", err);
16041  }
16042 
16043  /*
16044  * Create columns containing modeled values at each x
16045  */
16046 
16047  if (cpl_table_has_column(points, "px"))
16048  cpl_table_erase_column(points, "px");
16049  cpl_table_new_column(points, "px", CPL_TYPE_DOUBLE);
16050  cpl_table_fill_column_window_double(points, "px", 0, npoints, 0);
16051  x = cpl_table_get_data_double(points, "x");
16052  px = cpl_table_get_data_double(points, "px");
16053  for (i = 0; i < npoints; i++)
16054  px[i] = cpl_polynomial_eval_1d(p, x[i], NULL);
16055 
16056  nevaluate = cpl_table_get_nrow(evaluate);
16057 
16058  if (cpl_table_has_column(evaluate, "px"))
16059  cpl_table_erase_column(evaluate, "px");
16060  cpl_table_new_column(evaluate, "px", CPL_TYPE_DOUBLE);
16061  cpl_table_fill_column_window_double(evaluate, "px", 0, nevaluate, 0);
16062  x_eval = cpl_table_get_data_double(evaluate, "x");
16063  px_eval = cpl_table_get_data_double(evaluate, "px");
16064  for (i = 0; i < nevaluate; i++)
16065  px_eval[i] = cpl_polynomial_eval_1d(p, x_eval[i], NULL);
16066 
16067  /*
16068  * Initialise column with sigma
16069  */
16070 
16071  if (cpl_table_has_column(evaluate, "sigma"))
16072  cpl_table_erase_column(evaluate, "sigma");
16073  cpl_table_new_column(evaluate, "sigma", CPL_TYPE_DOUBLE);
16074  cpl_table_fill_column_window_double(evaluate, "sigma", 0, nevaluate, 0);
16075  sigma = cpl_table_get_data_double(evaluate, "sigma");
16076 
16077  /*
16078  * Compute varied y cordinates to fit
16079  */
16080 
16081  if (cpl_table_has_column(points, "vy"))
16082  cpl_table_erase_column(points, "vy");
16083  cpl_table_new_column(points, "vy", CPL_TYPE_DOUBLE);
16084  cpl_table_fill_column_window_double(points, "vy", 0, npoints, 0);
16085  vy = cpl_table_get_data_double(points, "vy");
16086  dy = cpl_table_get_data_double(points, "y_err");
16087  cpl_vector_unwrap(listy);
16088  listy = cpl_vector_wrap(npoints, vy);
16089 
16090  for (i = 0; i < samples; i++) {
16091  for (j = 0; j < npoints; j++)
16092  vy[j] = px[j] + dy[j] * mos_randg(1);
16093  q = cpl_polynomial_fit_1d_create(listx, listy, order, NULL);
16094  for (j = 0; j < nevaluate; j++)
16095  sigma[j] += fabs(px_eval[j]
16096  - cpl_polynomial_eval_1d(q, x_eval[j], NULL));
16097  cpl_polynomial_delete(q);
16098  }
16099 
16100  /*
16101  * Factor 1.25 to convert average deviation to sigma
16102  */
16103 
16104  cpl_table_multiply_scalar(evaluate, "sigma", 1.25);
16105  cpl_table_divide_scalar(evaluate, "sigma", samples);
16106 
16107  cpl_vector_unwrap(listx);
16108  cpl_vector_unwrap(listy);
16109 
16110  return p;
16111 }
16112 
16113 #endif
16114 
16137 cpl_error_code mos_randomise_image(cpl_image *image, double ron,
16138  double gain, double bias)
16139 {
16140  float *data;
16141  int npix, i;
16142 
16143 
16144  if (image == NULL)
16145  return cpl_error_set(cpl_func, CPL_ERROR_NULL_INPUT);
16146 
16147  if (ron < 0.0 || gain <= FLT_EPSILON)
16148  return cpl_error_set(cpl_func, CPL_ERROR_ILLEGAL_INPUT);
16149 
16150  data = cpl_image_get_data_float(image);
16151  npix = cpl_image_get_size_x(image) * cpl_image_get_size_y(image);
16152  ron *= ron;
16153 
16154  for (i = 0; i < npix; i++) {
16155  if (data[i] < bias) {
16156  data[i] += sqrt(ron) * mos_randg(1);
16157  }
16158  else {
16159  data[i] += sqrt(ron + (data[i] - bias) / gain) * mos_randg(1);
16160  }
16161  }
16162 
16163  return CPL_ERROR_NONE;
16164 }
16165 
16166 
16181 cpl_error_code mos_refmask_find_gaps(cpl_mask *refmask,
16182  cpl_image *master_flat,
16183  double level)
16184 {
16185  int nx = cpl_mask_get_size_x(refmask);
16186  int ny = cpl_mask_get_size_y(refmask);
16187 
16188  int * xpos = cpl_calloc(sizeof(int), ny);
16189 
16190  cpl_image * filtered = cpl_image_duplicate(master_flat);
16191  cpl_mask * kernel = cpl_mask_new(9, 3);
16192  cpl_vector * v = cpl_vector_new(ny);
16193  cpl_vector * truev;
16194  int nvalid = 0;
16195  double * flats = cpl_vector_get_data(v);
16196 
16197  double median, stdev, delta;
16198 
16199  int i, kill;
16200 
16201  cpl_mask_not(kernel);
16202  cpl_image_filter_mask(filtered, master_flat, kernel,
16203  CPL_FILTER_MEDIAN, CPL_BORDER_COPY);
16204  cpl_mask_delete(kernel);
16205 
16206  for (i = 1; i <= ny; i++) {
16207  int j = 0;
16208 
16209  do j++;
16210  while (!cpl_mask_get(refmask, j, i) && j < nx);
16211 
16212  if (j < nx) {
16213  int rejected;
16214 
16215  xpos[i - 1] = j;
16216  flats[nvalid] = cpl_image_get(filtered, j, i, &rejected);
16217  nvalid++;
16218  }
16219  else {
16220  xpos[i - 1] = -1;
16221  }
16222  }
16223 
16224  if (nvalid == 0)
16225  return cpl_error_set(cpl_func, CPL_ERROR_DATA_NOT_FOUND);
16226 
16227  truev = cpl_vector_wrap(nvalid, flats);
16228 
16229  median = cpl_vector_get_median(truev);
16230 
16231  if (level < 0.0)
16232  stdev = cpl_vector_get_stdev(truev);
16233 
16234  cpl_vector_unwrap(truev);
16235  cpl_vector_delete(v);
16236 
16237  for (i = 1; i <= ny; i++) {
16238  if (xpos[i - 1] > 0) {
16239  int rejected;
16240  double kappa = 1.5;
16241 
16242  delta = cpl_image_get(filtered, xpos[i - 1], i, &rejected) - median;
16243 
16244  if (level < 0.0)
16245  kill = fabs(delta) > stdev * kappa;
16246  else
16247  kill = delta < level;
16248 
16249  if (kill) {
16250  int j = 0;
16251 
16252  while (cpl_mask_get(refmask, xpos[i - 1] + j, i)) {
16253  cpl_mask_set(refmask, xpos[i - 1] + j, i, CPL_BINARY_0);
16254  j++;
16255  }
16256  }
16257  }
16258  }
16259 
16260  cpl_image_delete(filtered);
16261  cpl_free(xpos);
16262 
16263  return cpl_error_get_code();
16264 }
16265 
16273 cpl_error_code mos_saturation_process(cpl_image * image)
16274 {
16275  int nx = cpl_image_get_size_x(image);
16276  int ny = cpl_image_get_size_y(image);
16277  int npix = nx * ny;
16278  float * sdata = cpl_image_get_data_float(image);
16279 
16280  int count, i, j, k;
16281 
16282  /*
16283  * This is used to avoid saturation level coded with pixel value zero
16284  * To make it more robust against random 0.0 values, check that also
16285  * next pixel along the spatial direction is 0.0.
16286  */
16287 
16288  //This could be applied only to raw images, but it is being applied
16289  //to already bias/overscan processed images, which doesn't make sense.
16290 // for (i = 0; i < npix - nx; i++)
16291 // if (sdata[i] == 0.0 && sdata[i + nx] == 0.0)
16292 // sdata[i] = 65535.0;
16293 
16294 // for (i = npix - nx; i < npix; i++)
16295 // if (sdata[i] == 0.0)
16296 // sdata[i] = 65535.0;
16297 
16298  /*
16299  * This is a dirty trick to overcome saturations (making up a false
16300  * tip on their flat tops). This should be useless with a better
16301  * peak detection algorithm.
16302  */
16303 
16304  for (i = 0; i < npix; i++) {
16305  if (sdata[i] >= 65535.0) {
16306  count = 0;
16307  for (j = i; j < npix; j++) {
16308  if (sdata[j] < 65535.0) {
16309  break;
16310  }
16311  else {
16312  count++;
16313  }
16314  }
16315  if (count < 30 && count > 2) {
16316  for (j = i; j < i + count/2; j++)
16317  sdata[j] = sdata[i] + 1000.0 * (j - i);
16318  if (count % 2 != 0) {
16319  sdata[j] = sdata[j-1] + 1000.0;
16320  j++;
16321  }
16322  for (k = j; k <= i + count; k++)
16323  sdata[k] = sdata[i] - 1000.0 * (k - i - count);
16324  i = k;
16325  }
16326  }
16327  }
16328 
16329  return cpl_error_get_code();
16330 }
16331 
16332 
16341 cpl_error_code mos_subtract_background(cpl_image * image)
16342 {
16343  /*
16344  * Create and subtract background
16345  */
16346 
16347  cpl_image * bimage = mos_arc_background(image, 15, 15);
16348  cpl_image_subtract(image, bimage);
16349  cpl_image_delete(bimage);
16350 
16351  return cpl_error_get_code();
16352 }
16353 
16354 
16371 cpl_error_code mos_object_intersect(cpl_table **slitss, cpl_table *origslits,
16372  int nscience, float tolerance)
16373 {
16374  int i, j;
16375 
16376  cpl_table *summary;
16377  int summary_nobjs = 0;
16378 
16379  int nobjs;
16380 
16381  int nmatches;
16382  int nslits = cpl_table_get_nrow(slitss[0]);
16383 
16384  int maxobjs;
16385  int k, m;
16386  int nstokes, sstokes;
16387 
16388  cpl_table **work;
16389 
16390  work = (cpl_table **)cpl_malloc(sizeof(cpl_table *) * nscience);
16391 
16392 
16393  /*
16394  * First we build a table listing the offset of each detected
16395  * object at each angle and each beam, from the bottom of each
16396  * slit spectrum, and the pair that slit spectrum belongs to.
16397  * This summary table will have as many rows as objects found
16398  * in total at all angles.
16399  */
16400 
16401  for (j = 0; j < nscience; j++) {
16402  int c_nobjs = mos_get_nobjects(slitss[j]);
16403  if (!c_nobjs)
16404  return cpl_error_set(cpl_func, CPL_ERROR_DATA_NOT_FOUND);
16405  summary_nobjs += c_nobjs;
16406  }
16407 
16408  summary = cpl_table_new(summary_nobjs);
16409 
16410  cpl_table_new_column(summary, "offset", CPL_TYPE_DOUBLE);
16411  cpl_table_new_column(summary, "pair", CPL_TYPE_INT);
16412  cpl_table_new_column(summary, "absolute", CPL_TYPE_DOUBLE);
16413  cpl_table_new_column(summary, "pos", CPL_TYPE_DOUBLE);
16414 
16415  /*
16416  * Fill the summary table with data from all objects:
16417  */
16418 
16419  nobjs = 0;
16420 
16421  /* Loop on all object tables (one for each angle) */
16422  for (j = 0; j < nscience; j++) {
16423  int c_maxobjs = mos_get_maxobjs_per_slit(slitss[j]);
16424 
16425  /* Loop on all slits found on first - i.e., ALL - object table */
16426  for (k = 0; k < nslits; k++) {
16427 
16428  /* Loop on all objects found on each object table */
16429  for (m = 0; m < c_maxobjs; m++) {
16430  int null;
16431  char *name = cpl_sprintf("object_%d", m + 1);
16432  double obj = cpl_table_get_double(slitss[j], name, k, &null);
16433  int pos;
16434  int pair;
16435 
16436  cpl_free(name);
16437 
16438  if (null)
16439  break; /* No object #m+1 in this slit - go to next slit */
16440 
16441  /*
16442  * Copy necessary object data to summary table. Note
16443  * that the absolute object position (row) in the
16444  * rectified image is made relative to the bottom
16445  * position (row) of the current slit.
16446  */
16447 
16448  pos = cpl_table_get_int(slitss[j], "position", k, &null);
16449  pair = cpl_table_get_int(slitss[j], "pair_id", k, &null);
16450  cpl_table_set(summary, "absolute", nobjs, obj);
16451  cpl_table_set(summary, "pos", nobjs, pos);
16452  cpl_table_set(summary, "offset", nobjs, obj - pos);
16453  cpl_table_set(summary, "pair", nobjs, pair);
16454 
16455  nobjs++;
16456  }
16457  }
16458  }
16459 
16460 // cpl_table_save(summary, NULL, NULL, "susu.fits", CPL_IO_DEFAULT);
16461 
16462  /*
16463  * Perform the intersection: what are the objects belonging
16464  * to the same slit (same pair ordinary + extraordinary) which
16465  * are observed at the same offset at all angles? Those are
16466  * the polarimetric objects.
16467  */
16468 
16469  nmatches = 0;
16470  maxobjs = mos_get_maxobjs_per_slit(slitss[0]);
16471 
16472  /*
16473  * We loop on the objects of the first-angle object table as
16474  * reference, and check whether those objects are present also
16475  * at *all* other angles. Note that the loop advances by pairs.
16476  * If the top (k = 0) slit spectrum is not an ordinary beam,
16477  * it is ignored. The loop advances by pairs, starting at the
16478  * first complete pair. It is implicitely assumed that the
16479  * slit spectrum on top is always from the ordinary beam, and
16480  * the spectrum below (k+1) its extraordinary match.
16481  */
16482 
16483  for (k = 0; k < nslits; k+=2) {
16484  int slitmatches = 0;
16485 
16486  if (k + 1 < nslits ) {
16487  if (cpl_table_get_int(slitss[0], "pair_id", k, NULL) !=
16488  cpl_table_get_int(slitss[0], "pair_id", k + 1, NULL)) {
16489 
16490  /*
16491  * This is not an ordinary beam - advance to next slit.
16492  */
16493 
16494  /* It will be incremented by two, so the effect is like k++ */
16495  k--;
16496 
16497  continue;
16498  }
16499  }
16500 
16501  for (m = 0; m < maxobjs; m++) {
16502  int null;
16503  char *name = cpl_sprintf("object_%d", m + 1);
16504  double obj = cpl_table_get_double(slitss[0], name, k, &null);
16505  double pos;
16506  int pair;
16507 
16508  char *name_obj = NULL;
16509  char *name_start = NULL;
16510  char *name_end = NULL;
16511  char *name_row = NULL;
16512  char *name_row_s = NULL;
16513 
16514  char *name_start_o = NULL;
16515  char *name_end_o = NULL;
16516  char *name_row_o = NULL;
16517  char *name_start_v = NULL;
16518  char *name_end_v = NULL;
16519  char *name_obj_v = NULL;
16520 
16521  int start, end;
16522  int length;
16523 
16524  int selected;
16525  int v, start_v, end_v;
16526  double min_v, obj_v;
16527 
16528 
16529  cpl_free(name);
16530 
16531  if (null)
16532  break;
16533 
16534  /*
16535  * Each object of the first object table belongs to a
16536  * slit spectrum (k). This slit spectrum has a position
16537  * in the rectified image, and it belongs to a given
16538  * ordinary + extraordinary pair.
16539  */
16540 
16541  pos = cpl_table_get_int(slitss[0], "position", k, &null);
16542  pair = cpl_table_get_int(slitss[0], "pair_id", k, &null);
16543 
16544  /*
16545  * Now from the summary table we can select all objects
16546  * which have the same offset (obj - pos) within all slit
16547  * spectra belonging to the same ordinary + extraordinary
16548  * pair (at all angles).
16549  */
16550 
16551  cpl_table_select_all(summary); /* Reset selection */
16552 
16553  cpl_table_and_selected_int(summary, "pair", CPL_EQUAL_TO, pair);
16554  cpl_table_and_selected_double(summary, "offset", CPL_LESS_THAN,
16555  obj - pos + tolerance);
16556  selected =
16557  cpl_table_and_selected_double(summary, "offset", CPL_GREATER_THAN,
16558  obj - pos - tolerance);
16559 
16560 
16561  /*
16562  * If this object were observed at all angles (nscience) and
16563  * at all beams (2), we should have selected exactly 2*nscience
16564  * objects. If not, this is not a polarimetric object, and it
16565  * is discarded from the intersection.
16566  */
16567 
16568  if (selected != nscience * 2)
16569  continue;
16570 
16571  /*
16572  * If we reach this point we have found one valid polarimetric
16573  * object, that must be inserted in the intersection object
16574  * table.
16575  */
16576 
16577  slitmatches++;
16578 
16579  /*
16580  * Names of the columns of the output table where the
16581  * object information needs to be copied. Note that a
16582  * new column is created, the "row_stokes_#", where the
16583  * row number of the extracted polarimetric signal is
16584  * also computed. For the moment this column will be
16585  * left empty - it will be filled only when all matches
16586  * are collected.
16587  */
16588 
16589  name_obj = cpl_sprintf("object_%d", slitmatches);
16590  name_start = cpl_sprintf("start_%d", slitmatches);
16591  name_end = cpl_sprintf("end_%d", slitmatches);
16592  name_row = cpl_sprintf("row_%d", slitmatches);
16593  name_row_s = cpl_sprintf("row_stokes_%d", slitmatches);
16594 
16595  /*
16596  * Names of the columns of the input table where the
16597  * object information is available.
16598  */
16599 
16600  name_start_o = cpl_sprintf("start_%d", m + 1);
16601  name_end_o = cpl_sprintf("end_%d", m + 1);
16602  name_row_o = cpl_sprintf("row_%d", m + 1);
16603 
16604  /*
16605  * If the output columns do not exist yet, create them.
16606  */
16607 
16608  if (!cpl_table_has_column(origslits, name_obj)) {
16609  cpl_table_new_column(origslits, name_obj, CPL_TYPE_DOUBLE);
16610  cpl_table_new_column(origslits, name_start, CPL_TYPE_INT);
16611  cpl_table_new_column(origslits, name_end, CPL_TYPE_INT);
16612  cpl_table_new_column(origslits, name_row, CPL_TYPE_INT);
16613  cpl_table_new_column(origslits, name_row_s, CPL_TYPE_INT);
16614  }
16615 
16616  /*
16617  * The current slit spectrum is k. The slit spectrum immediately
16618  * below (in the rectified image) is k+1. We need the length of
16619  * the spectrum below for computing the _absolute_ coordinates
16620  * of the objects in the rectified image in both beams.
16621  */
16622 
16623  length = cpl_table_get_int(origslits, "length", k + 1, &null);
16624 
16625  /* NEW:
16626  * Names of the columns of the input table where
16627  * the information of the corresponding object of
16628  * the next beam is available.
16629  */
16630 
16631  for (v = 0; v < maxobjs; v++) {
16632  char *name_v = cpl_sprintf("object_%d", v + 1);
16633  double obj_v = cpl_table_get_double(slitss[0], name_v,
16634  k + 1, &null);
16635 
16636  cpl_free(name_v);
16637 
16638  if (null)
16639  break;
16640 
16641  if (v) {
16642  if (fabs(obj - length - obj_v) < min_v) {
16643  min_v = fabs(obj - length - obj_v);
16644  cpl_free(name_start_v);
16645  cpl_free(name_end_v);
16646  cpl_free(name_obj_v);
16647  name_start_v = cpl_sprintf("start_%d", v + 1);
16648  name_end_v = cpl_sprintf("end_%d", v + 1);
16649  name_obj_v = cpl_sprintf("object_%d", v + 1);
16650  }
16651  }
16652  else {
16653  min_v = fabs(obj - length - obj_v);
16654  name_start_v = cpl_sprintf("start_%d", v + 1);
16655  name_end_v = cpl_sprintf("end_%d", v + 1);
16656  name_obj_v = cpl_sprintf("object_%d", v + 1);
16657  }
16658  }
16659 
16660  /*
16661  * Read from the first input object table (first angle)
16662  * the spatial window enclosing the object.
16663  */
16664 
16665  start = cpl_table_get_int(slitss[0], name_start_o, k, &null);
16666  end = cpl_table_get_int(slitss[0], name_end_o, k, &null);
16667 
16668  /* NEW:
16669  * Spatial window of the matching object in the next beam.
16670  */
16671 
16672  start_v = cpl_table_get_int(slitss[0], name_start_v, k + 1, &null);
16673  end_v = cpl_table_get_int(slitss[0], name_end_v, k + 1, &null);
16674  obj_v = cpl_table_get_double(slitss[0], name_obj_v, k + 1, &null);
16675 
16676  /*
16677  * Write the object coordinates in the same slit, and in the
16678  * slit below. Note that here we assume that all slits were
16679  * traced perfectly, and we compute the theoretical coords
16680  * (obj - length) within the next slit spectrum (k + 1). In
16681  * principle we should read them as well from the input
16682  * table!
16683  */
16684 
16685  cpl_table_set_double(origslits, name_obj, k, obj);
16686  cpl_table_set_double(origslits, name_obj, k + 1, obj_v);
16687  // cpl_table_set_double(origslits, name_obj, k + 1, obj - length);
16688 
16689  cpl_table_set_int(origslits, name_start, k, start);
16690  cpl_table_set_int(origslits, name_start, k + 1, start_v);
16691  // cpl_table_set_int(origslits, name_start, k + 1, start - length);
16692 
16693  cpl_table_set_int(origslits, name_end, k, end);
16694  cpl_table_set_int(origslits, name_end, k + 1, end_v);
16695  // cpl_table_set_int(origslits, name_end, k + 1, end - length);
16696 
16697  /*
16698  * "nmatches" is counting at what "reduced" image row the
16699  * extracted spectra are. Note that this is s preliminary
16700  * numbering - which is wrong: other objects may be found
16701  * in the same slit, and then the indeces would not be in
16702  * sequence. What is important is that at the end of this
16703  * loop "nmatches" would be the total number of matching
16704  * objects. The two cpl_table_set_int() calls made here
16705  * cannot be removed - they "validate" those table elements
16706  * (see ahead).
16707  */
16708 
16709  cpl_table_set_int(origslits, name_row, k, nmatches);
16710  nmatches++;
16711  cpl_table_set_int(origslits, name_row, k + 1, nmatches);
16712  nmatches++;
16713 
16714  cpl_free(name_obj);
16715  cpl_free(name_start);
16716  cpl_free(name_end);
16717  cpl_free(name_row);
16718  cpl_free(name_row_s);
16719 
16720  cpl_free(name_start_o);
16721  cpl_free(name_end_o);
16722  cpl_free(name_row_o);
16723 
16724  cpl_free(name_start_v); name_start_v = NULL;
16725  cpl_free(name_end_v); name_end_v = NULL;
16726  cpl_free(name_obj_v); name_obj_v = NULL;
16727  }
16728  }
16729 
16730  /*
16731  * The summary table has fulfilled its function. If no matching
16732  * objects are found, the function returns with an error.
16733  */
16734 
16735  cpl_table_delete(summary);
16736 
16737  if (!nmatches)
16738  return cpl_error_set(cpl_func, CPL_ERROR_DATA_NOT_FOUND);
16739 
16740  /*
16741  * Now we consider the resulting intersection object table,
16742  * listing all matches. As seen, the image row number reported
16743  * in the columns "row_#" was not really performed sequentially.
16744  * We need to renumber sequentially...
16745  * We need also to fill the "row_stokes_#" column the way the
16746  * extracted polarimetric signal will be stored in the
16747  * reduced_pol_images...
16748  */
16749 
16750  maxobjs = mos_get_maxobjs_per_slit(origslits);
16751  nstokes = nmatches / 2; /* nmatches is always an even number */
16752 
16753  for (k = 0; k < nslits; k++) {
16754  if (k % 2) { /* Extraordinary beam */
16755  nstokes = sstokes; /* Use same start value as for ordinary */
16756  }
16757  else { /* Ordinary beam */
16758  sstokes = nstokes; /* Memorise start value at ordinary beam */
16759  }
16760 
16761  for (m = 0; m < maxobjs; m++) {
16762  char *name = cpl_sprintf("row_%d", m + 1);
16763  char *namestokes = cpl_sprintf("row_stokes_%d", m + 1);
16764 
16765  if (!cpl_table_is_valid(origslits, name, k)) {
16766  cpl_free(name);
16767  cpl_free(namestokes);
16768  break;
16769  }
16770  else {
16771  nmatches--;
16772  nstokes--;
16773  cpl_table_set_int(origslits, name, k, nmatches);
16774  cpl_table_set_int(origslits, namestokes, k, nstokes);
16775  }
16776 
16777  cpl_free(name);
16778  cpl_free(namestokes);
16779  }
16780  }
16781 
16782 
16783  /*
16784  * This is done to avoid the NULL value is zero (it would invalidate
16785  * also the row_# = 0 or start_# = 0 for an object), and to enable
16786  * working directly with the column data buffers, when using this
16787  * table afterwards.
16788  */
16789 
16790  for (j = 0; j < maxobjs; j++) {
16791  char *name = cpl_sprintf("object_%d", j + 1);
16792  cpl_table_fill_invalid_double(origslits, name, -1);
16793  cpl_free(name);
16794 
16795  name = cpl_sprintf("start_%d", j + 1);
16796  cpl_table_fill_invalid_int(origslits, name, -1);
16797  cpl_free(name);
16798 
16799  name = cpl_sprintf("end_%d", j + 1);
16800  cpl_table_fill_invalid_int(origslits, name, -1);
16801  cpl_free(name);
16802 
16803  name = cpl_sprintf("row_%d", j + 1);
16804  cpl_table_fill_invalid_int(origslits, name, -1);
16805  cpl_free(name);
16806 
16807  name = cpl_sprintf("row_stokes_%d", j + 1);
16808  cpl_table_fill_invalid_int(origslits, name, -1);
16809  cpl_free(name);
16810  }
16811 
16812  /*********************************************************************
16813  * This tail has been added to propagate the selection of valid
16814  * objects also to the input slitss[] tables. Just eliminate all
16815  * this final part to suppress this behaviour.
16816  */
16817 
16818  /*
16819  * First of all, make a working copy and remove all columns related
16820  * to objects from the input object tables.
16821  */
16822 
16823  for (i = 0; i < nscience; i++) {
16824  int c_maxobjs = mos_get_maxobjs_per_slit(slitss[i]);
16825 
16826  work[i] = cpl_table_duplicate(slitss[i]);
16827 
16828  for (m = 0; m < c_maxobjs; m++) {
16829  char *object_o = cpl_sprintf("object_%d", m + 1);
16830  char *start_o = cpl_sprintf("start_%d", m + 1);
16831  char *end_o = cpl_sprintf("end_%d", m + 1);
16832  char *row_o = cpl_sprintf("row_%d", m + 1);
16833 
16834  cpl_table_erase_column(slitss[i], object_o);
16835  cpl_table_erase_column(slitss[i], start_o);
16836  cpl_table_erase_column(slitss[i], end_o);
16837  cpl_table_erase_column(slitss[i], row_o);
16838  }
16839  }
16840 
16841  /*
16842  * Now just consider all the objects in the intersection table.
16843  */
16844 
16845  for (k = 0; k < nslits; k++) {
16846  for (j = 0; j < maxobjs; j++) {
16847  double object_w, object_r;
16848  int row_w;
16849 
16850  char *object_i = cpl_sprintf("object_%d", j + 1);
16851  char *start_i = cpl_sprintf("start_%d", j + 1);
16852  char *end_i = cpl_sprintf("end_%d", j + 1);
16853  char *row_i = cpl_sprintf("row_%d", j + 1);
16854 
16855 
16856  if (!cpl_table_is_valid(origslits, object_i, k))
16857  break;
16858 
16859  /*
16860  * We have found a valid object (valid because it belongs
16861  * to the intersection). Now we look for this object in each
16862  * one of the original tables, we get its parameters, and
16863  * copy them at the right position (i.e., same position as
16864  * in intersection table). The object will be the one closest
16865  * to the object position (column object_i) in the intersection
16866  * table. Note that we examine the same row, k, in all tables.
16867  */
16868 
16869  object_w = cpl_table_get_double(origslits, object_i, k, NULL);
16870  row_w = cpl_table_get_int (origslits, row_i, k, NULL);
16871 
16872  for (i = 0; i < nscience; i++) {
16873  int c_maxobjs = mos_get_maxobjs_per_slit(work[i]);
16874  int minpos;
16875  double mindiff, diff;
16876  char *object_o;
16877  char *start_o;
16878  char *end_o;
16879  char *row_o;
16880 
16881  for (m = 0; m < c_maxobjs; m++) {
16882  object_o = cpl_sprintf("object_%d", m + 1);
16883  start_o = cpl_sprintf("start_%d", m + 1);
16884  end_o = cpl_sprintf("end_%d", m + 1);
16885  row_o = cpl_sprintf("row_%d", m + 1);
16886 
16887  if (!cpl_table_is_valid(work[i], object_o, k))
16888  break;
16889 
16890  object_r = cpl_table_get_double(work[i], object_o, k, NULL);
16891  //row_r = cpl_table_get_int (work[i], row_o, k, NULL);
16892 
16893  diff = fabs(object_w - object_r);
16894  if (m) {
16895  if (mindiff > diff) {
16896  mindiff = diff;
16897  minpos = m;
16898  }
16899  }
16900  else {
16901  mindiff = diff;
16902  minpos = 0;
16903  }
16904 
16905  cpl_free(object_o);
16906  cpl_free(start_o);
16907  cpl_free(end_o);
16908  cpl_free(row_o);
16909  }
16910 
16911  object_o = cpl_sprintf("object_%d", minpos + 1);
16912  start_o = cpl_sprintf("start_%d", minpos + 1);
16913  end_o = cpl_sprintf("end_%d", minpos + 1);
16914  row_o = cpl_sprintf("row_%d", minpos + 1);
16915 
16916  if (!cpl_table_has_column(slitss[i], object_i)) {
16917  cpl_table_new_column(slitss[i], object_i, CPL_TYPE_DOUBLE);
16918  cpl_table_new_column(slitss[i], start_i, CPL_TYPE_INT);
16919  cpl_table_new_column(slitss[i], end_i, CPL_TYPE_INT);
16920  cpl_table_new_column(slitss[i], row_i, CPL_TYPE_INT);
16921  cpl_table_fill_invalid_double(slitss[i], object_i, -1);
16922  cpl_table_fill_invalid_int (slitss[i], start_i, -1);
16923  cpl_table_fill_invalid_int (slitss[i], end_i, -1);
16924  cpl_table_fill_invalid_int (slitss[i], row_i, -1);
16925  }
16926 
16927  cpl_table_set_double(slitss[i], object_i, k,
16928  cpl_table_get_double(work[i], object_o,
16929  k, NULL));
16930  cpl_table_set_int(slitss[i], start_i , k,
16931  cpl_table_get_int(work[i], start_o, k, NULL));
16932  cpl_table_set_int(slitss[i], end_i , k,
16933  cpl_table_get_int(work[i], end_o, k, NULL));
16934  cpl_table_set_int(slitss[i], row_i , k, row_w);
16935 
16936  cpl_free(object_o);
16937  cpl_free(start_o);
16938  cpl_free(end_o);
16939  cpl_free(row_o);
16940  }
16941 
16942  cpl_free(object_i);
16943  cpl_free(start_i);
16944  cpl_free(end_i);
16945  cpl_free(row_i);
16946  }
16947  }
16948 
16949  for (i = 0; i < nscience; i++)
16950  cpl_table_delete(work[i]);
16951 
16952  cpl_free(work);
16953 
16954 
16955  return cpl_error_get_code();
16956 }
16957 
16958 
16966 int mos_get_maxobjs_per_slit(cpl_table * slits)
16967 {
16968  int maxobjs = 1;
16969 
16970  char * colname = cpl_sprintf("object_%d", maxobjs);
16971 
16972  while (cpl_table_has_column(slits, colname)) {
16973  maxobjs++;
16974  cpl_free(colname);
16975  colname = cpl_sprintf("object_%d", maxobjs);
16976  }
16977 
16978  cpl_free(colname);
16979 
16980  maxobjs--;
16981 
16982  return maxobjs;
16983 }
16984 
16992 int mos_get_nobjects(cpl_table * slits)
16993 {
16994  int nobjs = 0;
16995 
16996  int nslits = cpl_table_get_nrow(slits);
16997  int maxobjs = mos_get_maxobjs_per_slit(slits);
16998 
16999  int k, m;
17000 
17001  for (k = 0; k < nslits; k++) {
17002  for (m = 0; m < maxobjs; m++) {
17003  char * name = cpl_sprintf("object_%d", m + 1);
17004  int null = !cpl_table_is_valid(slits, name, k);
17005 
17006  cpl_free(name);
17007 
17008  if (null) break;
17009  else nobjs++;
17010  }
17011  }
17012 
17013  return nobjs;
17014 }
17015 
17023 int mos_check_slits(cpl_table *slits, float rescale)
17024 {
17025 
17026  cpl_propertylist *sort;
17027 
17028  int nslits = cpl_table_get_nrow(slits);
17029 
17030  int k, null;
17031 
17032  const float interval = 90.0 * rescale;
17033  const float offset = (90.0 - 5) * rescale;
17034 
17035 
17036  for (k = 0; k < nslits; k++) {
17037  double ytop = cpl_table_get_double(slits, "ytop", k, &null);
17038  double ybottom = cpl_table_get_double(slits, "ybottom", k, &null);
17039 
17040  double xtop = cpl_table_get_double(slits, "xtop", k, &null);
17041  double xbottom = cpl_table_get_double(slits, "xbottom", k, &null);
17042 
17043  int nmiss = (int)((ytop - ybottom) / interval + 0.5);
17044 
17045  if (nmiss > 1) {
17046  cpl_msg_warning(cpl_func,
17047  "Some slits could not be properly detected. "
17048  "There might be accountable inaccuracies.");
17049  while (nmiss > 1) {
17050  cpl_table_set_size(slits, nslits + 1);
17051 
17052  /* Fill in new slit 'cut' */
17053 
17054  /* x coordinates be the same (acceptable approximation) */
17055  cpl_table_set_double(slits, "xtop", nslits, xtop);
17056  cpl_table_set_double(slits, "xbottom", nslits, xbottom);
17057 
17058  /* Cut */
17059  if (k == 0) {
17060  cpl_table_set_double(slits, "ybottom", nslits, ybottom);
17061  cpl_table_set_double(slits, "ytop", nslits, ybottom
17062  + offset);
17063  ybottom += interval;
17064  cpl_table_set_double(slits, "ybottom", k, ybottom);
17065  } else {
17066  cpl_table_set_double(slits, "ytop", nslits, ytop);
17067  cpl_table_set_double(slits, "ybottom", nslits, ytop
17068  - offset);
17069  ytop -= interval;
17070  cpl_table_set_double(slits, "ytop", k, ytop);
17071  }
17072 
17073  nslits++; nmiss--;
17074  }
17075  }
17076  }
17077 
17078  sort = cpl_propertylist_new();
17079  cpl_propertylist_append_bool(sort, "ytop", 1);
17080  cpl_table_sort(slits, sort);
17081  cpl_propertylist_delete(sort);
17082 
17083  /*
17084  * Add here an ad hoc check on the last slit: is it too long
17085  * (by more than 10%)? Then shorten it...
17086  */
17087 
17088  k = cpl_table_get_nrow(slits) - 1;
17089 
17090  {
17091  double ytop = cpl_table_get_double(slits, "ytop", k, &null);
17092  double ybottom = cpl_table_get_double(slits, "ybottom", k, &null);
17093  double length = (ytop - ybottom) / interval;
17094 
17095  if (length > 1.1) {
17096  cpl_table_set_double(slits, "ybottom", k, ytop - offset);
17097  }
17098 
17099  }
17100 
17101  return 0;
17102 }
17103 
17126 cpl_table *mos_load_slits_fors_pmos(cpl_propertylist *header,
17127  int * nslits_out_det)
17128 {
17129  int m, null;
17130  int halfsize;
17131 
17132  cpl_propertylist * sort;
17133  cpl_table * slits;
17134 
17135  slits = mos_load_slits_fors_mos(header, nslits_out_det);
17136  halfsize = cpl_table_get_nrow(slits);
17137 
17138  cpl_table_set_size(slits, 2 * halfsize);
17139 
17140  for (m = 0; m < halfsize; m++) {
17141 
17142  double gap = 1.4;
17143 
17144  double length =
17145  cpl_table_get(slits, "ytop", m, &null) -
17146  cpl_table_get(slits, "ybottom", m, &null);
17147 
17148  if (m) {
17149  double interval =
17150  cpl_table_get(slits, "ybottom", m - 1, &null) -
17151  cpl_table_get(slits, "ytop", m, &null);
17152 
17153  gap = (interval - length) / 2;
17154  }
17155 
17156  cpl_table_set(slits, "slit_id", m + halfsize,
17157  cpl_table_get(slits, "slit_id", m, &null) - 1);
17158 
17159  cpl_table_set(slits, "xtop", m + halfsize,
17160  cpl_table_get(slits, "xtop", m, &null));
17161 
17162  cpl_table_set(slits, "xbottom", m + halfsize,
17163  cpl_table_get(slits, "xbottom", m, &null));
17164 
17165  cpl_table_set(slits, "ytop", m + halfsize,
17166  cpl_table_get(slits, "ytop", m, &null) + gap + length);
17167 
17168  cpl_table_set(slits, "ybottom", m + halfsize,
17169  cpl_table_get(slits, "ytop", m, &null) + gap);
17170  }
17171 
17172  for (m = 0; m < 2 * halfsize; m++) {
17173  cpl_table_set(slits, "ytop", m,
17174  cpl_table_get(slits, "ytop", m, &null) - 5.3);
17175 
17176  cpl_table_set(slits, "ybottom", m,
17177  cpl_table_get(slits, "ybottom", m, &null) - 5.3);
17178 
17179  }
17180 
17181  sort = cpl_propertylist_new();
17182  cpl_propertylist_append_bool(sort, "ytop", 1);
17183  cpl_table_sort(slits, sort);
17184 
17185  cpl_propertylist_delete(sort);
17186 
17187  return slits;
17188 }
17189 
17190 int * fors_get_nobjs_perslit(cpl_table * slits)
17191 {
17192  int nslits = cpl_table_get_nrow(slits);
17193  int maxobjs = mos_get_maxobjs_per_slit(slits);
17194 
17195  int * nobjs_per_slit = cpl_malloc(sizeof(int) * nslits);
17196 
17197  int k, m;
17198 
17199  for (k = 0; k < nslits; k++) {
17200  int nobjs = 0;
17201  for (m = 0; m < maxobjs; m++) {
17202  char * name = cpl_sprintf("object_%d", m + 1);
17203  int null = !cpl_table_is_valid(slits, name, k);
17204 
17205  cpl_free(name);
17206 
17207  if (null) break;
17208  else nobjs++;
17209  }
17210 
17211  nobjs_per_slit[k] = nobjs;
17212  }
17213 
17214  return nobjs_per_slit;
17215 }
17216 
17217 double fors_get_object_position(cpl_table *slits, int slit, int object)
17218 {
17219  char *name = cpl_sprintf("object_%d", object);
17220  double position;
17221 
17222  position = cpl_table_get_double(slits, name, slit, NULL)
17223  - cpl_table_get_int(slits, "position", slit, NULL);
17224 
17225  cpl_free(name);
17226 
17227  return position;
17228 }
17229 
17230 int mos_rebin_signal(cpl_image **image, int rebin)
17231 {
17232  cpl_image *rebinned;
17233 
17234 
17235  if (*image == NULL)
17236  return 1;
17237 
17238  if (rebin == 1)
17239  return 0;
17240 
17241  rebinned = cpl_image_rebin(*image, 1, 1, rebin, 1);
17242 
17243  cpl_image_delete(*image);
17244 
17245  *image = rebinned;
17246 
17247  return 0;
17248 }
17249 
17250 int mos_rebin_error(cpl_image **image, int rebin)
17251 {
17252  if (*image == NULL)
17253  return 1;
17254 
17255  if (rebin == 1)
17256  return 0;
17257 
17258  cpl_image_power(*image, 2);
17259  mos_rebin_signal(image, rebin);
17260  cpl_image_power(*image, 0.5);
17261 
17262  return 0;
17263 }
17264 
17265 /*
17266  * @brief
17267  * Map table values into a 1D image
17268  *
17269  * @param image Target image
17270  * @param start Coordinate of first pixel in image
17271  * @param step Coordinate step for one pixel in image
17272  * @param table Source table
17273  * @param xname Name of coordinate column
17274  * @param yname Name of values column
17275  *
17276  * @return 0 on success
17277  *
17278  * The values in @em yname are linearly interpolated at the @em image
17279  * pixel coordinates. The @em image must have Nx1 size.
17280  */
17281 
17282 int map_table(cpl_image *image, double start, double step,
17283  cpl_table *table, const char *xname, const char *yname)
17284 {
17285  int length = cpl_image_get_size_x(image);
17286  int nrows = cpl_table_get_nrow(table);
17287  float *data = cpl_image_get_data_float(image);
17288  float *fdata = NULL;
17289  double *xdata = NULL;
17290  double *ydata = NULL;
17291  cpl_type xtype = cpl_table_get_column_type(table, xname);
17292  cpl_type ytype = cpl_table_get_column_type(table, yname);
17293  double xzero, pos;
17294  int i, j, n;
17295 
17296 
17297  /*
17298  * Initialization of output image at 0.0 - this value is left
17299  * on non-overlapping portions.
17300  */
17301 
17302  for (i = 0; i < length; i++)
17303  data[i] = 0.0;
17304 
17305 
17306  /*
17307  * Do everything in double precision
17308  */
17309 
17310  if (xtype == CPL_TYPE_FLOAT) {
17311  fdata = cpl_table_get_data_float(table, xname);
17312  xdata = cpl_malloc(nrows * sizeof(double));
17313  for (i = 0; i < nrows; i++) {
17314  xdata[i] = fdata[i];
17315  }
17316  }
17317  else {
17318  xdata = cpl_table_get_data_double(table, xname);
17319  }
17320 
17321  if (ytype == CPL_TYPE_FLOAT) {
17322  fdata = cpl_table_get_data_float(table, yname);
17323  ydata = cpl_malloc(nrows * sizeof(double));
17324  for (i = 0; i < nrows; i++) {
17325  ydata[i] = fdata[i];
17326  }
17327  }
17328  else {
17329  ydata = cpl_table_get_data_double(table, yname);
17330  }
17331 
17332  /*
17333  * Mapping
17334  */
17335 
17336  n = 0;
17337  xzero = xdata[n];
17338 
17339  for (i = 0; i < length; i++) {
17340  pos = start + step * i;
17341  if (pos < xzero)
17342  continue;
17343  for (j = n; j < nrows; j++) {
17344  if (xdata[j] > pos) {
17345  n = j;
17346  data[i] = ydata[j-1]
17347  + (ydata[j] - ydata[j-1])
17348  * (pos - xdata[j-1]) / (xdata[j] - xdata[j-1]);
17349  break;
17350  }
17351  }
17352  }
17353 
17354  if (xtype == CPL_TYPE_FLOAT)
17355  cpl_free(xdata);
17356 
17357  if (ytype == CPL_TYPE_FLOAT)
17358  cpl_free(ydata);
17359 
17360  return 0;
17361 }
17362 
17363 
17364 /*
17365  * @brief
17366  * Fit overall trend of a Nx1 image
17367  *
17368  * @param image Values to smooth
17369  * @param order Order of fitting polynomial
17370  * @param hw Half width of smoothing window
17371  *
17372  * @return Smoothed image, or NULL on failure.
17373  *
17374  * Heavily smooth and fit data in the input Nx1 size @em image.
17375  */
17376 
17377 static cpl_image *polysmooth(cpl_image *image, int order, int hw)
17378 {
17379  int npoints;
17380  cpl_vector *x;
17381  cpl_vector *y;
17382  double *xdata;
17383  double *ydata;
17384  cpl_polynomial *poly;
17385  cpl_vector *ysmooth;
17386  cpl_image *smoothed;
17387  float *sdata;
17388  int i;
17389 
17390 
17391  npoints = cpl_image_get_size_x(image);
17392 
17393  if (2 * hw + 1 > npoints)
17394  return NULL;
17395 
17396  x = cpl_vector_new(npoints);
17397  y = cpl_vector_new(npoints);
17398  xdata = cpl_vector_get_data(x);
17399  ydata = cpl_vector_get_data(y);
17400 
17401  smoothed = cpl_image_duplicate(image);
17402  sdata = cpl_image_get_data_float(smoothed);
17403 
17404  for (i = 0; i < npoints; i++) {
17405  xdata[i] = i;
17406  ydata[i] = sdata[i];
17407  }
17408 
17409  ysmooth = cpl_vector_filter_median_create(y, hw);
17410  cpl_vector_delete(y);
17411 
17412  poly = cpl_polynomial_fit_1d_create(x, ysmooth, order, NULL);
17413  cpl_vector_delete(x);
17414  cpl_vector_delete(ysmooth);
17415 
17416  if (poly) {
17417  for (i = 0; i < npoints; i++)
17418  sdata[i] = cpl_polynomial_eval_1d(poly, i, NULL);
17419 
17420  cpl_polynomial_delete(poly);
17421  }
17422  else {
17423  cpl_image_delete(smoothed);
17424  return NULL;
17425  }
17426 
17427  return smoothed;
17428 }
17429 
17430 #undef cleanup
17431 #define cleanup \
17432 do { \
17433  cpl_image_delete(spectrum); \
17434  cpl_image_delete(flux); \
17435  cpl_image_delete(efficiency); \
17436  cpl_image_delete(smo_efficiency); \
17437  cpl_image_delete(extinction); \
17438  cpl_image_delete(response); \
17439  cpl_image_delete(smo_response); \
17440  cpl_image_delete(physical); \
17441 } while (0)
17442 
17466 cpl_table *mos_photometric_calibration(cpl_image *spectra, double startwave,
17467  double dispersion, double gain,
17468  double exptime, cpl_table *ext_table,
17469  double airmass, cpl_table *flux_table,
17470  int order)
17471 {
17472 
17473  cpl_image *spectrum = NULL; // Extracted standard star spectrum
17474  float *data;
17475  cpl_image *extinction = NULL; // Extinction binned as "spectrum"
17476  float *ext_data;
17477  cpl_image *flux = NULL; // Standard star flux binned as "spectrum"
17478  float *flux_data;
17479  cpl_image *physical = NULL; // Physical units of above
17480  float *phys_data;
17481  cpl_image *efficiency = NULL; // Raw efficiency curve
17482  float *eff_data;
17483  cpl_image *smo_efficiency = NULL; // Smoothed efficiency curve
17484  float *smo_eff_data;
17485  cpl_image *response = NULL; // Raw response curve
17486  float *res_data;
17487  cpl_image *smo_response = NULL; // Smoothed response curve
17488  float *smo_res_data;
17489  cpl_image *image;
17490  cpl_image *smo_image;
17491  cpl_table *table;
17492  float lambda;
17493  int nx, ny;
17494  int ext_count, ext_pos;
17495  int eff_count, eff_pos;
17496  int flux_count, flux_pos;
17497  int start, end;
17498  int i;
17499 
17500 
17501  if (spectra == NULL || ext_table == NULL || flux_table == NULL) {
17502  cpl_error_set(cpl_func, CPL_ERROR_NULL_INPUT);
17503  return NULL;
17504  }
17505 
17506  if (!cpl_table_has_column(ext_table, "WAVE")) {
17507  cpl_error_set_message(cpl_func, CPL_ERROR_DATA_NOT_FOUND,
17508  "Column WAVE in atmospheric extinction table");
17509  return NULL;
17510  }
17511 
17512  if (!cpl_table_has_column(ext_table, "EXTINCTION")) {
17513  cpl_error_set_message(cpl_func, CPL_ERROR_DATA_NOT_FOUND,
17514  "Column EXTINCTION in atmospheric extinction table");
17515  return NULL;
17516  }
17517 
17518  if (!cpl_table_has_column(flux_table, "WAVE")) {
17519  cpl_error_set_message(cpl_func, CPL_ERROR_DATA_NOT_FOUND,
17520  "Column WAVE in standard star flux table");
17521  return NULL;
17522  }
17523 
17524  if (!cpl_table_has_column(flux_table, "FLUX")) {
17525  cpl_error_set_message(cpl_func, CPL_ERROR_DATA_NOT_FOUND,
17526  "Column FLUX in standard star flux table");
17527  return NULL;
17528  }
17529 
17530  if (gain < 0.1) {
17531  cpl_error_set_message(cpl_func, CPL_ERROR_ILLEGAL_INPUT,
17532  "Invalid gain factor (%.2f)", gain);
17533  return NULL;
17534  }
17535 
17536  if (exptime < 0.001) {
17537  cpl_error_set_message(cpl_func, CPL_ERROR_ILLEGAL_INPUT,
17538  "Invalid exposure time (%.2f)", exptime);
17539  return NULL;
17540  }
17541 
17542  if (dispersion < 0.001) {
17543  cpl_error_set_message(cpl_func, CPL_ERROR_ILLEGAL_INPUT,
17544  "Invalid dispersion (%.2f)", dispersion);
17545  return NULL;
17546  }
17547 
17548  if (order < 2) {
17549  cpl_error_set_message(cpl_func, CPL_ERROR_ILLEGAL_INPUT,
17550  "Order of the polynomial fitting the "
17551  "instrument response must be at least 2");
17552  return NULL;
17553  }
17554 
17555  nx = cpl_image_get_size_x(spectra);
17556  ny = cpl_image_get_size_y(spectra);
17557 
17558 
17559  /*
17560  * Find brightest spectrum and duplicate it.
17561  */
17562 
17563  if (ny == 1) {
17564  spectrum = cpl_image_duplicate(spectra);
17565  }
17566  else {
17567  cpl_size x, y;
17568  cpl_image *brights = cpl_image_collapse_create(spectra, 1);
17569 
17570  cpl_image_get_maxpos(brights, &x, &y);
17571  cpl_image_delete(brights);
17572  spectrum = cpl_image_extract(spectra, 1, y, nx, y);
17573  }
17574 
17575 
17576  /*
17577  * Convert standard star spectrum in electrons per second per Angstrom.
17578  */
17579 
17580  cpl_image_multiply_scalar(spectrum, gain / exptime / dispersion);
17581 
17582 
17583  /*
17584  * Map the atmospheric extinction factors to the same lambda sampling
17585  * of the extracted spectrum.
17586  */
17587 
17588  extinction = cpl_image_duplicate(spectrum);
17589  map_table(extinction, startwave + dispersion/2, dispersion,
17590  ext_table, "WAVE", "EXTINCTION");
17591 
17592 
17593  /*
17594  * Convert from magnitudes to actual flux loss.
17595  */
17596 
17597  cpl_image_multiply_scalar(extinction, 0.4 * airmass);
17598  cpl_image_exponential(extinction, 10.);
17599 
17600 
17601  /*
17602  * Correct the scientific spectrum to airmass 0
17603  */
17604 
17605  cpl_image_multiply(spectrum, extinction);
17606 
17607 
17608  /*
17609  * Find in what pixel interval (start at "ext_pos", for "ext_count"
17610  * pixels) the atmospheric extinction is available.
17611  */
17612 
17613  ext_data = cpl_image_get_data_float(extinction);
17614 
17615  ext_count = 0;
17616  ext_pos = 0;
17617  for (i = 0; i < nx; i++) {
17618  if (ext_data[i] > 0.0) {
17619  if (ext_count == 0) {
17620  ext_pos = i;
17621  }
17622  ext_count++;
17623  }
17624  else {
17625  if (ext_count) {
17626  break;
17627  }
17628  }
17629  }
17630 
17631  cpl_image_delete(extinction); extinction = NULL;
17632 
17633 
17634  /*
17635  * Map the standard star catalog flux to the same lambda sampling
17636  * of the extracted spectrum.
17637  */
17638 
17639  flux = cpl_image_duplicate(spectrum);
17640  map_table(flux, startwave + dispersion/2, dispersion,
17641  flux_table, "WAVE", "FLUX");
17642 
17643 
17644  /*
17645  * Find in what pixel interval (start at "flux_pos", for "flux_count"
17646  * pixels) the standard star flux is available.
17647  */
17648 
17649  flux_data = cpl_image_get_data_float(flux);
17650 
17651  flux_count = 0;
17652  flux_pos = 0;
17653  for (i = 0; i < nx; i++) {
17654  if (flux_data[i] > 0.0) {
17655  if (flux_count == 0) {
17656  flux_pos = i;
17657  }
17658  flux_count++;
17659  }
17660  else {
17661  if (flux_count) {
17662  break;
17663  }
17664  }
17665  }
17666 
17667 
17668  /*
17669  * Intersection with previous selection
17670  */
17671 
17672  start = ext_pos > flux_pos ? ext_pos : flux_pos;
17673  end = (ext_pos + ext_count) < (flux_pos + flux_count) ?
17674  (ext_pos + ext_count) : (flux_pos + flux_count);
17675  flux_pos = start;
17676  flux_count = end - start;
17677 
17678 
17679  /*
17680  * Convert the flux to photons (per second per Angstrom).
17681  * std_flux is in units of erg / cm^2 / s / Angstrom. This
17682  * must be multiplied by the efficient area of the telescope,
17683  * 5.18E+5 cm^2, and divided by hv (v = frequency). With
17684  * hc = 1.98E-8 erg*Angstrom one obtains the following:
17685  */
17686 
17687  physical = cpl_image_duplicate(spectrum);
17688  phys_data = cpl_image_get_data_float(physical);
17689 
17690  for (i = 0; i < nx; i++) {
17691  lambda = startwave + dispersion * (i + 0.5);
17692  phys_data[i] = 0.0026 * lambda * flux_data[i];
17693  }
17694 
17695  efficiency = cpl_image_duplicate(spectrum);
17696  eff_data = cpl_image_get_data_float(efficiency);
17697  data = cpl_image_get_data_float(spectrum);
17698 
17699  for (i = 0; i < nx; i++) {
17700  if (phys_data[i] > 0.0)
17701  eff_data[i] = data[i] / phys_data[i];
17702  else
17703  eff_data[i] = 0.0;
17704  }
17705 
17706  cpl_image_delete(physical); physical = NULL;
17707 
17708 
17709  /*
17710  * Find interval (longer than 300 pixels) where efficiency is
17711  * greater than 1%
17712  */
17713 
17714  eff_count = 0;
17715  eff_pos = 0;
17716  for (i = 0; i < nx; i++) {
17717  if (eff_data[i] > 0.01) {
17718  if (eff_count == 0) {
17719  eff_pos = i;
17720  }
17721  eff_count++;
17722  }
17723  else {
17724  if (eff_count > 300) {
17725  break;
17726  }
17727  }
17728  }
17729 
17730 
17731  /*
17732  * Intersection with previous selection
17733  */
17734 
17735  start = eff_pos > flux_pos ? eff_pos : flux_pos;
17736  end = (eff_pos + eff_count) < (flux_pos + flux_count) ?
17737  (eff_pos + eff_count) : (flux_pos + flux_count);
17738  eff_pos = start;
17739  eff_count = end - start;
17740 
17741  if (eff_count < 1) {
17742  cpl_error_set_message(cpl_func, CPL_ERROR_INCOMPATIBLE_INPUT,
17743  "No overlap between catalog and spectrum");
17744  cleanup;
17745  return NULL;
17746  }
17747 
17748 
17749  /*
17750  * Extract only data to fit, i.e., where the efficiency is available.
17751  */
17752 
17753  image = cpl_image_extract(efficiency, eff_pos + 1, 1,
17754  eff_pos + eff_count, 1);
17755 
17756  smo_image = polysmooth(image, order, 50);
17757  cpl_image_delete(image);
17758 
17759  smo_efficiency = cpl_image_duplicate(efficiency);
17760  smo_eff_data = cpl_image_get_data_float(smo_efficiency);
17761  cpl_image_copy(smo_efficiency, smo_image, eff_pos + 1, 1);
17762 
17763  cpl_image_delete(smo_image);
17764 
17765 
17766  /*
17767  * Compute instrument response as the ratio between the catalog
17768  * spectrum and the observed spectrum (converted in physical units).
17769  * The polynomial smoothing, however, is performed on the inverse
17770  * of this ration, for obvious reasons (i.e., no divergence at zero
17771  * efficiency).
17772  */
17773 
17774  response = cpl_image_duplicate(spectrum);
17775  res_data = cpl_image_get_data_float(response);
17776 
17777  for (i = 0; i < nx; i++) {
17778  if (eff_data[i] > 0.01 && flux_data[i] > 0.0)
17779  res_data[i] = data[i] / flux_data[i];
17780  else
17781  res_data[i] = 0.0;
17782  }
17783 
17784 
17785  /*
17786  * Extract only data to fit, i.e., where the response is available.
17787  */
17788 
17789  image = cpl_image_extract(response, eff_pos + 1, 1, eff_pos + eff_count, 1);
17790 
17791  smo_image = polysmooth(image, order, 50);
17792  cpl_image_delete(image);
17793 
17794  smo_response = cpl_image_duplicate(response);
17795  smo_res_data = cpl_image_get_data_float(smo_response);
17796  cpl_image_copy(smo_response, smo_image, eff_pos + 1, 1);
17797 
17798  cpl_image_delete(smo_image);
17799 
17800  for (i = 0; i < nx; i++) {
17801  if (eff_data[i] > 0.01) {
17802  res_data[i] = 1 / res_data[i];
17803  smo_res_data[i] = 1 / smo_res_data[i];
17804  }
17805  else {
17806  res_data[i] = 0.0;
17807  smo_res_data[i] = 0.0;
17808  }
17809  }
17810 
17811 
17812  /*
17813  * Assemble the product spectrophotometric table.
17814  */
17815 
17816  table = cpl_table_new(nx);
17817 
17818  cpl_table_new_column(table, "WAVE", CPL_TYPE_FLOAT);
17819  cpl_table_set_column_unit(table, "WAVE", "Angstrom");
17820 
17821  for (i = 0; i < nx; i++)
17822  cpl_table_set_float(table, "WAVE", i, startwave + dispersion*(i+0.5));
17823 
17824  cpl_table_new_column(table, "STD_FLUX", CPL_TYPE_FLOAT);
17825  cpl_table_set_column_unit(table, "STD_FLUX",
17826  "10^(-16) erg/(cm^2 s Angstrom)");
17827  cpl_table_copy_data_float(table, "STD_FLUX", flux_data);
17828  cpl_image_delete(flux); flux = NULL;
17829 
17830  cpl_table_new_column(table, "OBS_FLUX", CPL_TYPE_FLOAT);
17831  cpl_table_set_column_unit(table, "OBS_FLUX", "electron/(s Angstrom)");
17832  cpl_table_copy_data_float(table, "OBS_FLUX", data);
17833  cpl_image_delete(spectrum); spectrum = NULL;
17834 
17835  cpl_table_new_column(table, "RAW_EFFICIENCY", CPL_TYPE_FLOAT);
17836  cpl_table_set_column_unit(table, "RAW_EFFICIENCY", "electron/photon");
17837  cpl_table_copy_data_float(table, "RAW_EFFICIENCY", eff_data);
17838  cpl_image_delete(efficiency); efficiency = NULL;
17839 
17840  cpl_table_new_column(table, "EFFICIENCY", CPL_TYPE_FLOAT);
17841  cpl_table_set_column_unit(table, "EFFICIENCY", "electron/photon");
17842  cpl_table_copy_data_float(table, "EFFICIENCY", smo_eff_data);
17843  cpl_image_delete(smo_efficiency); smo_efficiency = NULL;
17844 
17845  cpl_table_new_column(table, "RAW_RESPONSE", CPL_TYPE_FLOAT);
17846  cpl_table_set_column_unit(table, "RAW_RESPONSE",
17847  "10^(-16) erg/(cm^2 electron)");
17848  cpl_table_copy_data_float(table, "RAW_RESPONSE", res_data);
17849  cpl_image_delete(response); response = NULL;
17850 
17851  cpl_table_new_column(table, "RESPONSE", CPL_TYPE_FLOAT);
17852  cpl_table_set_column_unit(table,
17853  "RESPONSE", "10^(-16) erg/(cm^2 electron)");
17854  cpl_table_copy_data_float(table, "RESPONSE", smo_res_data);
17855  cpl_image_delete(smo_response); smo_response = NULL;
17856 
17857  cleanup;
17858 
17859  return table;
17860 }
17861 
17862 static double ksigma_vector(cpl_vector *values,
17863  double klow, double khigh, int kiter, int *good)
17864 {
17865  cpl_vector *accepted;
17866  double mean = 0.0;
17867  double sigma = 0.0;
17868  double *data = cpl_vector_get_data(values);
17869  int n = cpl_vector_get_size(values);
17870  int ngood = n;
17871  int count = 0;
17872  int i;
17873 
17874 
17875  /*
17876  * At first iteration the mean is taken as the median, and the
17877  * standard deviation relative to this value is computed.
17878  */
17879 
17880  mean = cpl_vector_get_median(values);
17881 
17882  for (i = 0; i < n; i++)
17883  sigma += (mean - data[i]) * (mean - data[i]);
17884 
17885  sigma = sqrt(sigma / (n - 1));
17886 
17887  while (kiter) {
17888  count = 0;
17889  for (i = 0; i < ngood; i++) {
17890  if (data[i]-mean < khigh*sigma && mean-data[i] < klow*sigma) {
17891  data[count] = data[i];
17892  ++count;
17893  }
17894  }
17895 
17896  if (count == 0) // This cannot happen at first iteration.
17897  break; // So we can break: we have already computed a mean.
17898 
17899  /*
17900  * The mean must be computed even if no element was rejected
17901  * (count == ngood), because at first iteration median instead
17902  * of mean was computed.
17903  */
17904 
17905  accepted = cpl_vector_wrap(count, data);
17906  mean = cpl_vector_get_mean(accepted);
17907  if (count > 1)
17908  sigma = cpl_vector_get_stdev(accepted);
17909  cpl_vector_unwrap(accepted);
17910 
17911  if (count == ngood || count == 1)
17912  break;
17913 
17914  ngood = count;
17915  --kiter;
17916  }
17917 
17918  if (good)
17919  *good = ngood;
17920 
17921  return mean;
17922 }
17923 
17924 
17943 cpl_image *mos_ksigma_stack(cpl_imagelist *imlist,
17944  double klow, double khigh, int kiter,
17945  cpl_image **good)
17946 {
17947  int ni, nx, ny, npix;
17948  cpl_image *out_ima;
17949  float *pout_ima;
17950  float *good_ima;
17951  cpl_image *image;
17952  float **data;
17953  cpl_vector *time_line;
17954  double *ptime_line;
17955  int ngood;
17956  int i, j;
17957 
17958 
17959  ni = cpl_imagelist_get_size(imlist);
17960 
17961  image = cpl_imagelist_get(imlist, 0);
17962  nx = cpl_image_get_size_x(image);
17963  ny = cpl_image_get_size_y(image);
17964  npix = nx * ny;
17965 
17966  out_ima = cpl_image_new(nx, ny, CPL_TYPE_FLOAT);
17967  pout_ima = cpl_image_get_data_float(out_ima);
17968 
17969  if (good) {
17970  *good = cpl_image_new(nx, ny, CPL_TYPE_FLOAT);
17971  good_ima = cpl_image_get_data_float(*good);
17972  }
17973 
17974  time_line = cpl_vector_new(ni);
17975  ptime_line = cpl_vector_get_data(time_line);
17976 
17977  data = cpl_calloc(sizeof(float *), ni);
17978 
17979  for (i = 0; i < ni; i++) {
17980  image = cpl_imagelist_get(imlist, i);
17981  data[i] = cpl_image_get_data_float(image);
17982  }
17983 
17984  for (i = 0; i < npix; i++) {
17985  for (j = 0; j < ni; j++) {
17986  ptime_line[j] = data[j][i];
17987  }
17988  pout_ima[i] = ksigma_vector(time_line, klow, khigh, kiter, &ngood);
17989  if (good) {
17990  good_ima[i] = ngood;
17991  }
17992  }
17993 
17994  cpl_free(data);
17995  cpl_vector_delete(time_line);
17996 
17997  return out_ima;
17998 
17999 }
18000 
18001 
18018 cpl_image *mos_apply_photometry(cpl_image *spectra, cpl_table *response,
18019  cpl_table *ext_table, double startwave,
18020  double dispersion, double gain,
18021  double exptime, double airmass)
18022 {
18023  cpl_image *extinction;
18024  cpl_image *outspectra;
18025  cpl_image *mapresponse;
18026  float *res_data;
18027  float *out_data;
18028  float *ext_data;
18029  int tlength, xlength, ylength;
18030  int i, j, k;
18031 
18032 
18033  if (spectra == NULL || ext_table == NULL || response == NULL) {
18034  cpl_error_set(cpl_func, CPL_ERROR_NULL_INPUT);
18035  return NULL;
18036  }
18037 
18038  if(cpl_table_has_column(response, "RESPONSE"))
18039  cpl_table_cast_column(response, "RESPONSE", "RESPONSE_F", CPL_TYPE_FLOAT);
18040  else if(cpl_table_has_column(response, "RESPONSE_FFSED"))
18041  cpl_table_cast_column(response, "RESPONSE_FFSED", "RESPONSE_F", CPL_TYPE_FLOAT);
18042  else
18043  return NULL;
18044 
18045  res_data = cpl_table_get_data_float(response, "RESPONSE_F");
18046 
18047  if (res_data == NULL) {
18048  cpl_error_set(cpl_func, CPL_ERROR_DATA_NOT_FOUND);
18049  return NULL;
18050  }
18051 
18052  tlength = cpl_table_get_nrow(response);
18053  xlength = cpl_image_get_size_x(spectra);
18054  ylength = cpl_image_get_size_y(spectra);
18055 
18056  if (xlength != tlength) {
18057  mapresponse = cpl_image_new(xlength, 1, CPL_TYPE_FLOAT);
18058  map_table(mapresponse, startwave + dispersion/2, dispersion,
18059  response, "WAVE", "RESPONSE_F");
18060  res_data = cpl_image_get_data_float(mapresponse);
18061  }
18062 
18063  /*
18064  * Map the atmospheric extinction factors to the same lambda sampling
18065  * of the extracted spectrum.
18066  */
18067 
18068  extinction = cpl_image_new(xlength, 1, CPL_TYPE_FLOAT);
18069  map_table(extinction, startwave + dispersion/2, dispersion,
18070  ext_table, "WAVE", "EXTINCTION");
18071 
18072 
18073  /*
18074  * Convert from magnitudes to actual flux loss.
18075  */
18076 
18077  cpl_image_multiply_scalar(extinction, 0.4 * airmass);
18078  cpl_image_exponential(extinction, 10.);
18079 
18080  outspectra = cpl_image_duplicate(spectra);
18081 
18082  ext_data = cpl_image_get_data_float(extinction);
18083  out_data = cpl_image_get_data_float(outspectra);
18084 
18085  for (k = 0, i = 0; i < ylength; i++) {
18086  for (j = 0; j < xlength; j++, k++) {
18087  out_data[k] *= ext_data[j] * res_data[j];
18088  }
18089  }
18090 
18091  cpl_image_delete(extinction);
18092  if (xlength != tlength) {
18093  cpl_image_delete(mapresponse);
18094  }
18095 
18096  cpl_image_multiply_scalar(outspectra, gain / exptime / dispersion);
18097 
18098  cpl_table_erase_column(response, "RESPONSE_F");
18099 
18100  return outspectra;
18101 }
18102 
18103 
18120 cpl_image *mos_propagate_photometry_error(cpl_image *spectra,
18121  cpl_image *errors,
18122  cpl_table *response,
18123  cpl_table *ext_table,
18124  double startwave,
18125  double dispersion, double gain,
18126  double exptime, double airmass)
18127 {
18128  cpl_image *extinction;
18129  cpl_image *outerrors;
18130  cpl_image *mapresponse;
18131  cpl_image *maperror;
18132  float *err_data;
18133  float *out_data;
18134  float *ext_data;
18135  float *res_data;
18136  float *spe_data;
18137  int tlength, xlength, ylength;
18138  int i, j, k;
18139 
18140 
18141  if (errors == NULL || ext_table == NULL || response == NULL) {
18142  cpl_error_set(cpl_func, CPL_ERROR_NULL_INPUT);
18143  return NULL;
18144  }
18145 
18146  if (!cpl_table_has_column(response, "ERROR")) {
18147  return mos_apply_photometry(errors, response, ext_table, startwave,
18148  dispersion, gain, exptime, airmass);
18149  }
18150 
18151  cpl_table_cast_column(response, "RESPONSE", "RESPONSE_F", CPL_TYPE_FLOAT);
18152  res_data = cpl_table_get_data_float(response, "RESPONSE_F");
18153 
18154  if (res_data == NULL) {
18155  cpl_error_set(cpl_func, CPL_ERROR_DATA_NOT_FOUND);
18156  return NULL;
18157  }
18158 
18159  err_data = cpl_table_get_data_float(response, "ERROR");
18160 
18161  if (err_data == NULL) {
18162  cpl_error_set(cpl_func, CPL_ERROR_DATA_NOT_FOUND);
18163  return NULL;
18164  }
18165 
18166  tlength = cpl_table_get_nrow(response);
18167  xlength = cpl_image_get_size_x(errors);
18168  ylength = cpl_image_get_size_y(errors);
18169 
18170  if (xlength != tlength) {
18171  mapresponse = cpl_image_new(xlength, 1, CPL_TYPE_FLOAT);
18172  map_table(mapresponse, startwave + dispersion/2, dispersion,
18173  response, "WAVE", "RESPONSE_F");
18174  res_data = cpl_image_get_data_float(mapresponse);
18175 
18176  maperror = cpl_image_new(xlength, 1, CPL_TYPE_FLOAT);
18177  map_table(maperror, startwave + dispersion/2, dispersion,
18178  response, "WAVE", "ERROR");
18179  err_data = cpl_image_get_data_float(maperror);
18180  }
18181 
18182  /*
18183  * Map the atmospheric extinction factors to the same lambda sampling
18184  * of the extracted spectrum.
18185  */
18186 
18187  extinction = cpl_image_new(xlength, 1, CPL_TYPE_FLOAT);
18188  map_table(extinction, startwave + dispersion/2, dispersion,
18189  ext_table, "WAVE", "EXTINCTION");
18190 
18191 
18192  /*
18193  * Convert from magnitudes to actual flux loss.
18194  */
18195 
18196  cpl_image_multiply_scalar(extinction, 0.4 * airmass);
18197  cpl_image_exponential(extinction, 10.);
18198 
18199  outerrors = cpl_image_duplicate(errors);
18200 
18201  ext_data = cpl_image_get_data_float(extinction);
18202  out_data = cpl_image_get_data_float(outerrors);
18203  spe_data = cpl_image_get_data_float(spectra);
18204 
18205  for (k = 0, i = 0; i < ylength; i++) {
18206  for (j = 0; j < xlength; j++, k++) {
18207  out_data[k] = ext_data[j] *
18208  sqrt(err_data[j] * err_data[j] * spe_data[k] * spe_data[k] +
18209  res_data[j] * res_data[j] * out_data[k] * out_data[k]);
18210  }
18211  }
18212 
18213  cpl_image_delete(extinction);
18214  if (xlength != tlength) {
18215  cpl_image_delete(maperror);
18216  }
18217 
18218  cpl_image_multiply_scalar(outerrors, gain / exptime / dispersion);
18219 
18220  cpl_table_erase_column(response, "RESPONSE_F");
18221  return outerrors;
18222 }
18223 
18224 
18300 int mos_check_polarisation(cpl_image *q_image, cpl_image *q_error,
18301  cpl_image *u_image, cpl_image *u_error,
18302  double startwave, double dispersion,
18303  double band, cpl_table *pol_sta,
18304  double ra, double dec, char *filter,
18305  int *polarisation,
18306  double *p_offset, double *p_error,
18307  double *a_offset, double *a_error)
18308 {
18309  cpl_table *standard;
18310  cpl_image *q_noise;
18311  cpl_image *q_signal;
18312  cpl_image *u_noise;
18313  cpl_image *u_signal;
18314  cpl_image *noise;
18315  double *q_ndata;
18316  double *q_sdata;
18317  double *u_ndata;
18318  double *u_sdata;
18319  double arctol = 0.5; /* Arc tolerance in degrees */
18320  double mindist;
18321  double cwave;
18322  double bwave[] = {3650., 4450., 5510., 6580., 8060};
18323  char *bands = "UBVRI";
18324  char p_label[] = {' ', 'p', '\0'};
18325  char dp_label[] = {' ', 'd', 'p', '\0'};
18326  char a_label[] = {' ', 'a', '\0'};
18327  char da_label[] = {' ', 'd', 'a', '\0'};
18328  int nbands = strlen(bands);
18329  int selected;
18330  int first, last, count, center;
18331  int nx;
18332  cpl_size col, row;
18333  int i, found, closest;
18334  int pband;
18335  int polarised;
18336  double q_obs;
18337  double q_err;
18338  double u_obs;
18339  double u_err;
18340  double p_obs;
18341  double p_err;
18342  double p_ref;
18343  double dp_ref;
18344  double a_obs;
18345  double a_err;
18346  double a_ref;
18347  double da_ref;
18348 
18349 
18350  *filter = '\0';
18351  *polarisation = 0;
18352  *p_offset = 0.0;
18353  *p_error = 0.0;
18354  *a_offset = 0.0;
18355  *a_error = 0.0;
18356 
18357  /*
18358  * Select reference standard star
18359  */
18360 
18361  cpl_table_select_all(pol_sta);
18362  cpl_table_and_selected_double(pol_sta, "RA", CPL_GREATER_THAN, ra-arctol);
18363  cpl_table_and_selected_double(pol_sta, "RA", CPL_LESS_THAN, ra+arctol);
18364  cpl_table_and_selected_double(pol_sta, "DEC", CPL_GREATER_THAN, dec-arctol);
18365  selected =
18366  cpl_table_and_selected_double(pol_sta, "DEC", CPL_LESS_THAN, dec+arctol);
18367 
18368  if (selected == 0) {
18369  cpl_msg_warning(cpl_func, "No standard star found in FOV");
18370  return 1;
18371  }
18372 
18373  if (selected > 1) {
18374  cpl_msg_warning(cpl_func,
18375  "Ambiguity: %d standard stars found in FOV", selected);
18376  return 1;
18377  }
18378 
18379  standard = cpl_table_extract_selected(pol_sta);
18380 
18381  cpl_msg_info(cpl_func, "Standard star: %s",
18382  cpl_table_get_string(standard, "name", 0));
18383 
18384  /*
18385  * Check whether the star is polarised or not
18386  */
18387 
18388  polarised = cpl_table_get_int(standard, "polarised", 0, NULL);
18389 
18390  cpl_msg_info(cpl_func, "This star is%sexpected to be polarised",
18391  polarised ? " " : " not ");
18392 
18393 
18394  /*
18395  * Determine the image row with the smallest median noise: this
18396  * row is assumed to refer to the standard star.
18397  * (note: the higher the S/N ratio of the original spectra, the
18398  * smaller the noise of the Stokes parameters Q and U).
18399  */
18400 
18401  nx = cpl_image_get_size_x(q_error);
18402 
18403  noise = cpl_image_collapse_median_create(q_error, 1, 0, 0);
18404  cpl_image_get_minpos(noise, &col, &row);
18405 
18406  cpl_image_delete(noise);
18407 
18408  if (col != 1) {
18409  cpl_table_delete(standard);
18410  cpl_msg_error(cpl_func,
18411  "Assertion failure!!! col = %"CPL_SIZE_FORMAT" (it should be 1)", col);
18412  return 1;
18413  }
18414 
18415  q_signal = cpl_image_extract(q_image, 1, row, nx, row);
18416  q_noise = cpl_image_extract(q_error, 1, row, nx, row);
18417  u_signal = cpl_image_extract(u_image, 1, row, nx, row);
18418  u_noise = cpl_image_extract(u_error, 1, row, nx, row);
18419 
18420  q_sdata = cpl_image_get_data_double(q_signal);
18421  q_ndata = cpl_image_get_data_double(q_noise);
18422  u_sdata = cpl_image_get_data_double(u_signal);
18423  u_ndata = cpl_image_get_data_double(u_noise);
18424 
18425 
18426  /*
18427  * Determine valid interval in input images (where error is positive).
18428  */
18429 
18430  first = -1;
18431  last = nx = cpl_image_get_size_x(q_signal);
18432  for (i = 0; i < nx; i++) {
18433  if (first < 0) {
18434  if (q_ndata[i] > 0.0) {
18435  first = i;
18436  }
18437  }
18438  else {
18439  if (q_ndata[i] <= 0.0) {
18440  last = i - 1;
18441  break;
18442  }
18443  }
18444  }
18445 
18446  count = last - first + 1;
18447 
18448  if (first < 0 || count < band) {
18449  cpl_table_delete(standard);
18450  cpl_image_delete(q_signal);
18451  cpl_image_delete(q_noise);
18452  cpl_image_delete(u_signal);
18453  cpl_image_delete(u_noise);
18454  cpl_msg_warning(cpl_func, "Too short spectrum (%d pixels)", count);
18455  return 1;
18456  }
18457 
18458  center = (first + last) / 2; // Center of valid spectrum
18459  cwave = startwave + dispersion * center; // Corresponding wavelength
18460 
18461 
18462  /*
18463  * Find the band UBVRI closest to the central wavelength.
18464  */
18465 
18466  found = 0;
18467  for (i = 0; i < nbands; i++) {
18468  p_label[0] = bands[i];
18469  if (cpl_table_is_valid(standard, p_label, 0)) {
18470  if (found == 0) {
18471  found = 1;
18472  mindist = fabs(bwave[i] - cwave);
18473  closest = i;
18474  }
18475  else if (mindist > fabs(bwave[i] - cwave)) {
18476  mindist = fabs(bwave[i] - cwave);
18477  closest = i;
18478  }
18479  }
18480  }
18481 
18482  if (!found) {
18483  cpl_table_delete(standard);
18484  cpl_image_delete(q_signal);
18485  cpl_image_delete(q_noise);
18486  cpl_image_delete(u_signal);
18487  cpl_image_delete(u_noise);
18488  cpl_msg_warning(cpl_func, "No reference value available");
18489  return 1;
18490  }
18491 
18492  center = (bwave[closest] - startwave) / dispersion; // Center of band (pix)
18493  cwave = bwave[closest]; // Wavelength of band
18494 
18495 
18496  /*
18497  * Check that the integration interval is entirely contained
18498  * in the valid interval, or give it up.
18499  */
18500 
18501  pband = floor(band / dispersion); // Band width in pixels
18502 
18503  if (center - pband/2 < first || center + pband/2 > last) {
18504  cpl_table_delete(standard);
18505  cpl_image_delete(q_signal);
18506  cpl_image_delete(q_noise);
18507  cpl_image_delete(u_signal);
18508  cpl_image_delete(u_noise);
18509  cpl_msg_warning(cpl_func, "No reference value available");
18510  return 1;
18511  }
18512 
18513  first = center - pband/2;
18514  last = center + pband/2;
18515 
18516  /*
18517  * Collect reference values. Note that if angle info is not available,
18518  * angle stuff is set automaticaly to zero.
18519  */
18520 
18521  p_label[0] = bands[closest];
18522  dp_label[0] = bands[closest];
18523  a_label[0] = bands[closest];
18524  da_label[0] = bands[closest];
18525 
18526  p_ref = cpl_table_get(standard, p_label, 0, NULL);
18527  dp_ref = cpl_table_get(standard, dp_label, 0, NULL);
18528  a_ref = cpl_table_get(standard, a_label, 0, NULL);
18529  da_ref = cpl_table_get(standard, da_label, 0, NULL);
18530 
18531  cpl_msg_info(cpl_func,
18532  "The expected polarisation is %.2f +- %.2f %%",
18533  p_ref, dp_ref);
18534 
18535  if (polarised) {
18536  cpl_msg_info(cpl_func,
18537  "The expected polarisation angle is %.2f +- %.2f degrees",
18538  a_ref, da_ref);
18539  }
18540 
18541  /*
18542  * Find median signal and median error.
18543  */
18544 
18545  q_obs = cpl_image_get_median_window(q_image, first, 1, last, 1);
18546  q_err = cpl_image_get_median_window(q_error, first, 1, last, 1);
18547  u_obs = cpl_image_get_median_window(u_image, first, 1, last, 1);
18548  u_err = cpl_image_get_median_window(u_error, first, 1, last, 1);
18549 
18550  /*
18551  * Measured linear polarisation and its error
18552  */
18553 
18554  p_obs = sqrt(q_obs * q_obs + u_obs * u_obs);
18555  p_err = CPL_MATH_SQRT1_2 * 0.5 * (q_err + u_err);
18556 
18557  /*
18558  * Measured polarisation angle
18559  */
18560 
18561  a_obs = 0.0;
18562  if (polarised) {
18563  if (fabs(q_obs) < 0.00001) {
18564  if (u_obs > 0.0) {
18565  a_obs = 45.0;
18566  }
18567  else {
18568  a_obs = 135.0;
18569  }
18570  }
18571  else {
18572  a_obs = 0.5 * atan(u_obs / q_obs) * 180 / CPL_MATH_PI;
18573  if (q_obs > 0.0) {
18574  if (u_obs < 0.0) {
18575  a_obs += 180.;
18576  }
18577  }
18578  else {
18579  a_obs += 90.;
18580  }
18581  }
18582  }
18583 
18584  /*
18585  * Error on polarisation angle
18586  */
18587 
18588  a_err = 0.0;
18589  if (polarised) {
18590  a_err = sqrt(q_obs*q_obs*u_err*u_err + u_obs*u_obs*q_err*q_err)
18591  / (p_obs * p_obs)
18592  * 90 / CPL_MATH_PI;
18593  }
18594 
18595  p_obs *= 100;
18596  p_err *= 100;
18597  cpl_msg_info(cpl_func,
18598  "The measured polarisation is %.2f +- %.2f %%",
18599  p_obs, p_err);
18600 
18601  if (polarised) {
18602  cpl_msg_info(cpl_func,
18603  "The measured polarisation angle is %.2f +- %.2f degrees",
18604  a_obs, a_err);
18605  }
18606 
18607  *filter = bands[closest];
18608  *polarisation = polarised;
18609 
18610  if (polarised) {
18611  *p_offset = (p_obs - p_ref) / p_ref;
18612  *p_error = sqrt(p_err * p_err + dp_ref * dp_ref) / p_ref;
18613  }
18614  else {
18615  *p_offset = p_obs - p_ref;
18616  *p_error = sqrt(p_err * p_err + dp_ref * dp_ref);
18617  }
18618 
18619  *a_offset = a_obs - a_ref;
18620  *a_error = sqrt(a_err*a_err + da_ref*da_ref);
18621 
18622  return 0;
18623 
18624 }
18625 
18626 
18658 int mos_compute_offset(cpl_table *reference, cpl_table *objects, double *offset)
18659 {
18660  cpl_array *offsets;
18661  int noffset;
18662  int nslits = cpl_table_get_nrow(reference);
18663  int *nref;
18664  int *nobj;
18665  int corr, maxcorr;
18666  int best_shift;
18667  int i, j, k;
18668 
18669  cpl_error_code status = CPL_ERROR_NONE;
18670 
18671 
18672  *offset = 0.0;
18673 
18674  if (objects == NULL)
18675  return CPL_ERROR_NULL_INPUT;
18676 
18677  if (nslits != cpl_table_get_nrow(objects))
18678  return CPL_ERROR_INCOMPATIBLE_INPUT;
18679 
18680  nref = fors_get_nobjs_perslit(reference);
18681  nobj = fors_get_nobjs_perslit(objects);
18682 
18683  noffset = 0;
18684  for (i = 0; i < nslits; i++)
18685  noffset += nobj[i];
18686 
18687  if (noffset == 0) {
18688  cpl_free(nref);
18689  cpl_free(nobj);
18690  return CPL_ERROR_DATA_NOT_FOUND;
18691  }
18692 
18693  noffset = 0;
18694  for (i = 0; i < nslits; i++)
18695  noffset += nref[i];
18696 
18697  if (noffset == 0) {
18698  cpl_free(nref);
18699  cpl_free(nobj);
18700  return CPL_ERROR_DATA_NOT_FOUND;
18701  }
18702 
18703  offsets = cpl_array_new(noffset, CPL_TYPE_DOUBLE);
18704 
18705  noffset = 0; // The real number of offsets found will be counted.
18706 
18707  for (i = 0; i < nslits; i++) {
18708  if (nref[i] > 0 && nobj[i] > 0) {
18709  double shift;
18710  int length = cpl_table_get_int(objects, "length", i, NULL);
18711  double ytop = cpl_table_get_double(objects, "xtop", i, NULL);
18712  double ybottom = cpl_table_get_double(objects, "xbottom", i, NULL);
18713  int *aref = cpl_calloc(length, sizeof(int));
18714  int *aobj = cpl_calloc(length, sizeof(int));
18715  float *pref = cpl_calloc(nref[i], sizeof(float));
18716  float *pobj = cpl_calloc(nobj[i], sizeof(float));
18717 
18718  for (j = 0; j < nref[i]; j++) {
18719  pref[j] = fors_get_object_position(reference, i, j + 1);
18720  aref[(int)pref[j]] = 1;
18721  }
18722 
18723  for (j = 0; j < nobj[i]; j++) {
18724  pobj[j] = fors_get_object_position(objects, i, j + 1);
18725  aobj[(int)pobj[j]] = 1;
18726  }
18727 
18728  /*
18729  * Do not consider objects at border
18730  */
18731 
18732  aref[0] = 0;
18733  aref[length - 1] = 0;
18734  aobj[0] = 0;
18735  aobj[length - 1] = 0;
18736 
18737 //for (j = 0; j < nref[i]; j++)
18738 //printf("references: %f, ", pref[j]);
18739 //printf("\n");
18740 //for (j = 0; j < nref[i]; j++)
18741 //printf("objects : %f, ", pobj[j]);
18742 //printf("\n");
18743 //for (j = 0; j < length; j++)
18744 //printf("%d", aref[j]);
18745 //printf("\n");
18746 //for (j = 0; j < length; j++)
18747 //printf("%d", aobj[j]);
18748 //printf("\n");
18749 
18750  /*
18751  * Object matching by correlation
18752  */
18753 
18754  maxcorr = 0;
18755  best_shift = length;
18756 
18757  for (shift = length/2, j = 0; j <= length; shift--, j++) {
18758  int rstart, ostart, count;
18759 
18760  if (shift > 0) {
18761  rstart = shift;
18762  ostart = 0;
18763  count = length - shift;
18764  }
18765  else {
18766  rstart = 0;
18767  ostart = -shift;
18768  count = length + shift;
18769  }
18770 
18771  corr = 0;
18772  for (k = 0; k < count; k++) {
18773  corr += aref[rstart + k] * aobj[ostart + k];
18774  }
18775 
18776  if (maxcorr < corr) {
18777  maxcorr = corr;
18778  best_shift = shift;
18779  }
18780  }
18781 
18782  if (best_shift == length) { // No shift found
18783 //printf("%d: No shift found\n", i);
18784  cpl_free(aref);
18785  cpl_free(aobj);
18786  cpl_free(pref);
18787  cpl_free(pobj);
18788  continue;
18789  }
18790 //printf("%d: Integer shift found = %d\n", i, best_shift);
18791 
18792  for (j = 0; j < nref[i]; j++) {
18793  for (k = 0; k < nobj[i]; k++) {
18794  if (fabs(pref[j] - pobj[k] - best_shift) < 2) {
18795  double ccd_offset = (pref[j] - pobj[k])
18796  * (ytop - ybottom)
18797  / length;
18798 
18799 //printf("%d: Match found: %f\n", i, ccd_offset);
18800  /*
18801  * The matching object is found, store the
18802  * corresponding offset
18803  */
18804 
18805  cpl_array_set(offsets, noffset, ccd_offset);
18806  noffset++;
18807  break;
18808  }
18809  }
18810  }
18811 
18812  cpl_free(aref);
18813  cpl_free(aobj);
18814  cpl_free(pref);
18815  cpl_free(pobj);
18816  }
18817 //else
18818 //printf("%d: No object found\n", i);
18819  }
18820 
18821  cpl_free(nref);
18822  cpl_free(nobj);
18823 
18824 //printf("%d offsets found in total\n", noffset);
18825  if (noffset > 0) {
18826  if (noffset % 2) {
18827  *offset = cpl_array_get_median(offsets);
18828  }
18829  else {
18830  double *a = cpl_malloc(sizeof(double) * noffset);
18831  for (i = 0; i < noffset; i++) {
18832  a[i] = cpl_array_get_double(offsets, i, NULL);
18833  }
18834  *offset = (fors_tools_get_kth_double(a, noffset, (noffset-1)/2) +
18835  fors_tools_get_kth_double(a, noffset, (noffset/2))) / 2.0;
18836  cpl_free(a);
18837  }
18838  }
18839  else
18840  status = CPL_ERROR_DATA_NOT_FOUND;
18841 //printf("Median offset: %f\n", *offset);
18842 
18843  cpl_array_delete(offsets);
18844 
18845  return status;
18846 
18847 }
18848 
18849 
18861 cpl_error_code mos_image_shift(cpl_image *image, double dx, double dy)
18862 {
18863  cpl_image *source;
18864  int nx = cpl_image_get_size_x(image);
18865  int ny = cpl_image_get_size_y(image);
18866  float *idata;
18867  float *sdata;
18868  int i, j, pos;
18869  double xpos, ypos, xfrac, yfrac;
18870  int xint, yint;
18871 
18872 
18873  if (fabs(dx) >= nx || fabs(dy) >= ny)
18874  return CPL_ERROR_ACCESS_OUT_OF_RANGE;
18875 
18876  source = cpl_image_duplicate(image);
18877  idata = cpl_image_get_data_float(image);
18878  sdata = cpl_image_get_data_float(source);
18879 
18880  /*
18881  * Shift in y
18882  */
18883 
18884  yfrac = - dy - floor(- dy);
18885  xfrac = - dx - floor(- dx);
18886 
18887  for (pos = 0, j = 0; j < ny; j++) {
18888  ypos = j - dy;
18889  yint = floor(ypos);
18890  for (i = 0; i < nx; i++) {
18891  xpos = i - dx;
18892  xint = floor(xpos);
18893  if (xint < 0 || yint < 0 || xint > nx - 2 || yint > ny - 2) {
18894  idata[pos] = 0.0;
18895  }
18896  else {
18897  idata[pos] = sdata[xint + nx*yint] * (1 - xfrac) * (1 - yfrac)
18898  + sdata[xint + 1 + nx*yint] * xfrac * (1 - yfrac)
18899  + sdata[xint + nx*(yint + 1)] * (1 - xfrac) * yfrac
18900  + sdata[xint + 1 + nx*(yint + 1)] * xfrac * yfrac;
18901  }
18902  pos++;
18903  }
18904  }
18905 
18906  cpl_image_delete(source);
18907 
18908  return CPL_ERROR_NONE;
18909 }
18910 
18922 int mos_slit_closest_to_center(cpl_table *slits, int nx, int ny)
18923 {
18924 #ifdef CPL_SIZE_FORMAT
18925  cpl_size row;
18926 #else
18927  int row;
18928 #endif
18929 
18930  cpl_table_duplicate_column(slits, "x", slits, "xtop");
18931  cpl_table_add_columns(slits, "x", "xbottom");
18932  cpl_table_divide_scalar(slits, "x", 2); // Mean x position
18933  cpl_table_subtract_scalar(slits, "x", nx/2); // Relative to CCD center
18934  cpl_table_multiply_columns(slits, "x", "x"); // Squared
18935 
18936  cpl_table_duplicate_column(slits, "y", slits, "ytop");
18937  cpl_table_add_columns(slits, "y", "ybottom");
18938  cpl_table_divide_scalar(slits, "y", 2); // Mean y position
18939  cpl_table_subtract_scalar(slits, "y", ny/2); // Relative to CCD center
18940  cpl_table_multiply_columns(slits, "y", "y"); // Squared
18941 
18942  cpl_table_add_columns(slits, "x", "y"); // Distance from center
18943  cpl_table_get_column_minpos(slits, "x", &row); // Min distance from center
18944 
18945  cpl_table_erase_column(slits, "x");
18946  cpl_table_erase_column(slits, "y");
18947 
18948  return row;
18949 }
18950 
18970 cpl_error_code mos_extract_flux(cpl_image *image, cpl_table *slits,
18971  double xwidth, double ywidth,
18972  int dx, double gain, double *o_flux, double *o_err)
18973 {
18974  int nx = cpl_image_get_size_x(image);
18975  int ny = cpl_image_get_size_y(image);
18976  int slit = mos_slit_closest_to_center(slits, nx, ny);
18977  int ytop = (int)cpl_table_get(slits, "ytop", slit, NULL);
18978  int ybottom = (int)cpl_table_get(slits, "ybottom", slit, NULL);
18979  int dy = ytop - ybottom;
18980  int xcenter = (int)((cpl_table_get(slits, "xtop", slit, NULL) +
18981  cpl_table_get(slits, "xbottom", slit, NULL)) / 2);
18982  int xleft = xcenter - dx;
18983  int xright = xcenter + dx + 1;
18984  double area = xwidth * ywidth; // squared mm
18985  int npix = (2*dx + 1) * dy;
18986  int count = 0;
18987  float *data = cpl_image_get_data_float(image);
18988  double flux = 0.0;
18989  double error = 0.0;
18990  int satur = 60000;
18991  int x, y;
18992 
18993 
18994  if (cpl_table_has_column(slits, "ywidth")) {
18995  area = cpl_table_get(slits, "xwidth", slit, NULL)
18996  * cpl_table_get(slits, "ywidth", slit, NULL);
18997  }
18998 
18999  *o_flux = 0.0;
19000  *o_err = 0.0;
19001 
19002  if (xleft < 0)
19003  xleft = 0;
19004 
19005  if (xleft > nx)
19006  xleft = nx;
19007 
19008  if (xright < 0)
19009  xright = 0;
19010 
19011  if (xright > nx)
19012  xright = nx;
19013 
19014  if (ytop < 0)
19015  ytop = 0;
19016 
19017  if (ytop > ny)
19018  ytop = ny;
19019 
19020  if (ybottom < 0)
19021  ybottom = 0;
19022 
19023  if (ybottom > ny)
19024  ybottom = ny;
19025 
19026  count = (xright - xleft) * (ytop - ybottom);
19027 
19028  if (count == 0)
19029  return CPL_ERROR_ACCESS_OUT_OF_RANGE;
19030 
19031  count = 0;
19032 
19033  for (y = ybottom; y < ytop; y++) {
19034  for (x = xleft; x < xright; x++) {
19035  double value = data[x + y * nx];
19036  if (value < satur) {
19037  flux += value;
19038  count++;
19039  }
19040  }
19041  }
19042 
19043  if (count == 0)
19044  return CPL_ERROR_DIVISION_BY_ZERO;
19045 
19046  error = sqrt(flux/gain);
19047 
19048  /*
19049  * Flux correction for lost pixels
19050  */
19051 
19052  flux *= (float)npix / count;
19053  error *= (float)npix / count;
19054 
19055  flux /= area;
19056  error /= area;
19057 
19058  *o_flux = flux;
19059  *o_err = error;
19060 
19061  return CPL_ERROR_NONE;
19062 }
19063 
19064 
19087 cpl_error_code mos_extract_flux_mapped(cpl_image *image, cpl_table *slits,
19088  double xwidth, double ywidth,
19089  double lambda, double startwave,
19090  double dispersion, int dx, double gain,
19091  double *o_flux, double *o_err)
19092 {
19093  int nx = cpl_image_get_size_x(image);
19094  int ny = cpl_image_get_size_y(image);
19095  int slit = mos_slit_closest_to_center(slits, nx, ny);
19096  int dy = (int)cpl_table_get(slits, "length", slit, NULL);
19097  int ybottom = (int)cpl_table_get(slits, "position", slit, NULL);
19098  int ytop = ybottom + dy;
19099  int xcenter = (int)floor((lambda - startwave) / dispersion + 0.5);
19100  int xleft = xcenter - dx;
19101  int xright = xcenter + dx + 1;
19102  double area = xwidth * ywidth;
19103  int npix = (2*dx + 1) * dy;
19104  int count = 0;
19105  float *data = cpl_image_get_data_float(image);
19106  double flux = 0.0;
19107  double error = 0.0;
19108  int satur = 60000;
19109  int x, y;
19110 
19111 
19112  if (cpl_table_has_column(slits, "ywidth")) {
19113  area = cpl_table_get(slits, "xwidth", slit, NULL)
19114  * cpl_table_get(slits, "ywidth", slit, NULL);
19115  }
19116 
19117  *o_flux = 0.0;
19118  *o_err = 0.0;
19119 
19120  if (xleft < 0)
19121  xleft = 0;
19122 
19123  if (xleft > nx)
19124  xleft = nx;
19125 
19126  if (xright < 0)
19127  xright = 0;
19128 
19129  if (xright > nx)
19130  xright = nx;
19131 
19132  if (ytop < 0)
19133  ytop = 0;
19134 
19135  if (ytop > ny)
19136  ytop = ny;
19137 
19138  if (ybottom < 0)
19139  ybottom = 0;
19140 
19141  if (ybottom > ny)
19142  ybottom = ny;
19143 
19144  count = (xright - xleft) * (ytop - ybottom);
19145 
19146  if (count == 0)
19147  return CPL_ERROR_ACCESS_OUT_OF_RANGE;
19148 
19149  count = 0;
19150 
19151  for (y = ybottom; y < ytop; y++) {
19152  for (x = xleft; x < xright; x++) {
19153  double value = data[x + y * nx];
19154  if (value < satur) {
19155  flux += value;
19156  count++;
19157  }
19158  }
19159  }
19160 
19161  if (count == 0)
19162  return CPL_ERROR_DIVISION_BY_ZERO;
19163 
19164  error = sqrt(flux/gain);
19165 
19166  /*
19167  * Flux correction for lost pixels
19168  */
19169 
19170  flux *= (float)npix / count;
19171  error *= (float)npix / count;
19172 
19173  flux /= area;
19174  error /= area;
19175 
19176  *o_flux = flux;
19177  *o_err = error;
19178 
19179  return CPL_ERROR_NONE;
19180 
19181 }
19182 
19183 
19197 int mos_median_in_slit(cpl_table *table, cpl_table *slits, int slit,
19198  char *label, double *mvalue)
19199 {
19200  int position = cpl_table_get_int(slits, "position", slit, NULL);
19201  int length = cpl_table_get_int(slits, "length", slit, NULL);
19202  cpl_table *tmp = cpl_table_extract(table, position, length);
19203 
19204  *mvalue = cpl_table_get_column_median(tmp, label);
19205  cpl_table_delete(tmp);
19206 
19207  if (cpl_error_get_code() != CPL_ERROR_NONE)
19208  return 1;
19209 
19210  return 0;
19211 }
19212 
19213 
19225 cpl_image *mos_image_filter_median(cpl_image *image, int nx, int ny)
19226 {
19227  cpl_mask *kernel = cpl_mask_new(nx, ny);
19228  cpl_image *filtered = cpl_image_new(cpl_image_get_size_x(image),
19229  cpl_image_get_size_y(image),
19230  cpl_image_get_type(image));
19231 
19232  cpl_mask_not(kernel);
19233  cpl_image_filter_mask(filtered, image, kernel,
19234  CPL_FILTER_MEDIAN, CPL_BORDER_FILTER);
19235  cpl_mask_delete(kernel);
19236 
19237  return filtered;
19238 }
19239 
19240 int fors_mos_is_lss_like(cpl_table *maskslits, int nslits_out_det)
19241 {
19242  int treat_as_lss = 1;
19243  double mxpos = cpl_table_get_column_median(maskslits, "xtop");
19244  double * slit_xpos = cpl_table_get_data_double(maskslits, "xtop");
19245  cpl_size nslits = cpl_table_get_nrow(maskslits);
19246 
19247  //If not all the slits are illuminated, then we cannot say that
19248  //it is lss-like (PIPE-4380)
19249  if(nslits_out_det != 0)
19250  return 0;
19251 
19252  for (cpl_size i = 0; i < nslits; i++) {
19253  if (fabs(mxpos-slit_xpos[i]) > 0.01) {
19254  treat_as_lss = 0;
19255  break;
19256  }
19257  }
19258  return treat_as_lss;
19259 }
cpl_image * mos_spatial_calibration(cpl_image *spectra, cpl_table *slits, cpl_table *polytraces, double reference, double blue, double red, double dispersion, int flux, cpl_image *calibration)
Spatial remapping of CCD spectra eliminating the spectral curvature.
Definition: moses.c:8499
cpl_table * mos_photometric_calibration(cpl_image *spectra, double startwave, double dispersion, double gain, double exptime, cpl_table *ext_table, double airmass, cpl_table *flux_table, int order)
Produce instrument response curve, with some ancillary information.
Definition: moses.c:17466
cpl_table * mos_build_disp_coeff(cpl_table *global, cpl_table *slits)
Build the IDS coefficients table from a global distortions table.
Definition: moses.c:1848
cpl_image * mos_map_pixel(cpl_table *idscoeff, double reference, double blue, double red, double dispersion, int trend)
Create a pixel map from an IDS coefficients table.
Definition: moses.c:11147
double mos_integrate_signal(cpl_image *image, cpl_image *wavemap, int ystart, int yend, double wstart, double wend)
Integrate signal from wavelength and spatial interval.
Definition: moses.c:14733
cpl_bivector * mos_identify_peaks(cpl_vector *peaks, cpl_vector *lines, double min_disp, double max_disp, double tolerance)
Identify peak candidates.
Definition: moses.c:4316
cpl_vector * mos_refine_peaks(const float *spectrum, int length, cpl_vector *peaks, int sradius)
Improve (when possible) accuracy of peaks candidates positions.
Definition: moses.c:4207
cpl_table * mos_sky_map(cpl_image *spectra, cpl_image *wavemap, double dispersion, cpl_image *skymap)
Create a CCD median sky map.
Definition: moses.c:12438
cpl_error_code mos_interpolate_wavecalib(cpl_table *idscoeff, cpl_image *wavemap, int mode, int degree)
Interpolate LSS wavelength calibration.
Definition: moses.c:3064
cpl_image * mos_wavelength_calibration_raw(const cpl_image *image, cpl_vector *lines, double dispersion, float level, int sradius, int order, double reject, double refwave, double *wavestart, double *waveend, int *nlines, double *error, cpl_table *idscoeff, cpl_image *calibration, cpl_image *residuals, cpl_table *restable, cpl_mask *refmask, cpl_table *detected_lines)
Derive wavelength calibration from a raw arc lamp or sky exposure.
Definition: moses.c:5452
double mos_distortions_rms(cpl_image *rectified, cpl_vector *lines, double wavestart, double dispersion, int radius, int highres)
Estimate the spectral distortion modeling goodness.
Definition: moses.c:11010
cpl_image * mos_propagate_photometry_error(cpl_image *spectra, cpl_image *errors, cpl_table *response, cpl_table *ext_table, double startwave, double dispersion, double gain, double exptime, double airmass)
Propagate errors from response curve and extracted spectra.
Definition: moses.c:18120
cpl_table * mos_load_slits_fors_mxu(cpl_propertylist *header)
Create slit location table from FITS header of FORS2-MXU data.
Definition: moses.c:14821
cpl_image * mos_normalise_flat(cpl_image *flat, cpl_image *spatial, cpl_table *slits, cpl_table *polytraces, double reference, double blue, double red, double dispersion, int sradius, int polyorder)
Normalise a flat field exposure.
Definition: moses.c:2293
int mos_get_nobjects(cpl_table *slits)
Get the total number of objects detected in a slits table.
Definition: moses.c:16992
cpl_error_code mos_rotate_slits(cpl_table *slits, int rotation, int nx, int ny)
Rotate a slit location table.
Definition: moses.c:6305
cpl_image * mos_map_wavelengths(cpl_image *spatial, cpl_image *calibration, cpl_table *slits, cpl_table *polytraces, double reference, double blue, double red, double dispersion)
Remapping of spatially rectified wavelengths to original CCD pixels.
Definition: moses.c:11343
cpl_image * mos_wavelength_calibration(cpl_image *image, double refwave, double firstLambda, double lastLambda, double dispersion, cpl_table *idscoeff, int flux)
Remap at constant wavelength step an image of rectified scientific spectra.
Definition: moses.c:9660
cpl_table * mos_identify_slits(cpl_table *slits, cpl_table *maskslits, cpl_table *global)
Identify slits listed in a slit location table.
Definition: moses.c:6419
cpl_table * mos_sky_map_super(cpl_image *spectra, cpl_image *wavemap, double dispersion, double factor, int minpoints, cpl_image *skymap)
Create a CCD median sky map.
Definition: moses.c:12105
cpl_image * mos_sky_local(cpl_image *spectra, cpl_table *slits, int order)
Local determination of sky.
Definition: moses.c:12782
double fors_tools_get_kth_double(double *a, int n, int k)
Same as cpl_tools_get_kth_double.
Definition: fors_utils.c:211
cpl_table * mos_load_slits_fors_mos(cpl_propertylist *header, int *nslits_out_det)
Create slit location table from FITS header of FORS1/2 MOS data.
Definition: moses.c:15061
double mos_get_gain_vimos(cpl_propertylist *header)
Return gain factor for a VIMOS exposure.
Definition: moses.c:15431
cpl_table * mos_wavelength_align(cpl_image *image, cpl_table *slits, double refwave, double firstLambda, double lastLambda, cpl_table *idscoeff, cpl_vector *skylines, int highres, int order, cpl_image *calibration, int sradius)
Modify the input wavelength solution to match reference sky lines.
Definition: moses.c:9949
cpl_error_code mos_validate_slits(cpl_table *slits)
Check validity of a slit location table.
Definition: moses.c:6241
cpl_error_code mos_subtract_background(cpl_image *image)
Subtract the background.
Definition: moses.c:16341
int mos_get_maxobjs_per_slit(cpl_table *slits)
Get the maximum possible number of objects in a slit.
Definition: moses.c:16966
cpl_error_code mos_interpolate_wavecalib_slit(cpl_table *idscoeff, cpl_table *slits, int order, int global)
Interpolate MOS wavelength calibration.
Definition: moses.c:2935
cpl_image * mos_remove_bias(cpl_image *image, cpl_image *bias, cpl_table *overscans)
Subtract the bias from a CCD exposure.
Definition: moses.c:3654
cpl_table * mos_poly_trace(cpl_table *slits, cpl_table *traces, int order)
Fit spectral traces.
Definition: moses.c:8157
cpl_image * mos_detect_objects(cpl_image *image, cpl_table *slits, int margin, int maxradius, int conradius)
Detect objects in rectified scientific frame.
Definition: moses.c:13907
cpl_image * mos_sky_local_old(cpl_image *spectra, cpl_table *slits)
Local determination of sky.
Definition: moses.c:12680
int mos_check_polarisation(cpl_image *q_image, cpl_image *q_error, cpl_image *u_image, cpl_image *u_error, double startwave, double dispersion, double band, cpl_table *pol_sta, double ra, double dec, char *filter, int *polarisation, double *p_offset, double *p_error, double *a_offset, double *a_error)
Estimate linear polarisation parameters on spectral interval.
Definition: moses.c:18300
cpl_polynomial * mos_poly_wav2pix(cpl_bivector *pixwav, int order, double reject, int minlines, int *nlines, double *err)
Fit polynomial relation from wavelengths to pixels.
Definition: moses.c:5060
cpl_error_code mos_image_shift(cpl_image *image, double dx, double dy)
Shift values in an image.
Definition: moses.c:18861
int mos_check_multiplex(cpl_table *slits)
Determining whether a VIMOS mask has spectral multplexing or not.
Definition: moses.c:15587
cpl_error_code mos_extract_flux(cpl_image *image, cpl_table *slits, double xwidth, double ywidth, int dx, double gain, double *o_flux, double *o_err)
Measure flux from spectral interval on CCD.
Definition: moses.c:18970
int mos_lines_width(const float *spectrum, int length)
Estimate lines widths (in pixel) in arc lamp spectrum.
Definition: moses.c:4001
cpl_error_code mos_global_trace(cpl_table *slits, cpl_table *polytraces, int mode)
Recompute tracing coefficients globally.
Definition: moses.c:8318
cpl_bivector * mos_find_peaks(const float *spectrum, int length, cpl_vector *lines, cpl_polynomial *ids, double refwave, int sradius)
Find the reference lines peaks using a polynomial first-guess.
Definition: moses.c:5272
cpl_vector * mos_peak_candidates(const float *spectrum, int length, float level, float exp_width)
Find positions of peaks candidates.
Definition: moses.c:4104
cpl_error_code mos_object_intersect(cpl_table **slitss, cpl_table *origslits, int nscience, float tolerance)
Intersect a number of slit tables.
Definition: moses.c:16371
cpl_table * mos_wavelength_align_lss(cpl_image *image, double refwave, double firstLambda, double lastLambda, cpl_table *idscoeff, cpl_vector *skylines, int highres, int order, cpl_image *calibration, int sradius)
Modify the input wavelength solution to match reference sky lines (LSS).
Definition: moses.c:10502
cpl_image * mos_subtract_sky(cpl_image *science, cpl_table *slits, cpl_table *polytraces, double reference, double blue, double red, double dispersion)
Subtract the sky from the scientific CCD exposure.
Definition: moses.c:1972
cpl_image * mos_map_idscoeff(cpl_table *idscoeff, int xsize, double reference, double blue, double red)
Create a wavelengths map from an IDS coefficients table.
Definition: moses.c:11232
double mos_eval_dds(cpl_polynomial *ids, double blue, double red, double refwave, double pixel)
Evaluate the wavelength of a pixel position.
Definition: moses.c:5005
cpl_table * mos_trace_flat(cpl_image *flat, cpl_table *slits, double reference, double blue, double red, double dispersion)
Trace flat field spectra.
Definition: moses.c:7723
cpl_image * mos_wavelength_calibration_final(cpl_image *image, cpl_table *slits, cpl_vector *lines, double dispersion, float level, int sradius, int order, double reject, double refwave, double *wavestart, double *waveend, int *nlines, double *error, cpl_table *idscoeff, cpl_image *calibration, cpl_image *residuals, cpl_table *restable, cpl_table *detected_lines)
Derive wavelength calibration from a rectified arc lamp or sky exposure.
Definition: moses.c:8917
int mos_check_slits(cpl_table *slits, float rescale)
Check that all slit have been detected, insert them if not.
Definition: moses.c:17023
cpl_image * mos_apply_photometry(cpl_image *spectra, cpl_table *response, cpl_table *ext_table, double startwave, double dispersion, double gain, double exptime, double airmass)
Apply response curve to extracted spectra.
Definition: moses.c:18018
cpl_error_code mos_refmask_find_gaps(cpl_mask *refmask, cpl_image *master_flat, double level)
Reconstruct the gaps required for slit location.
Definition: moses.c:16181
Definition: list.c:74
cpl_image * mos_ksigma_stack(cpl_imagelist *imlist, double klow, double khigh, int kiter, cpl_image **good)
Stack images using k-sigma clipping.
Definition: moses.c:17943
cpl_image ** mos_extract_objects(cpl_image *science, cpl_image *science_var, cpl_image *sky, cpl_table *objects, int extraction, double ron, double gain, int ncombined)
Extract detected objects from rectified scientific frame.
Definition: moses.c:14258
cpl_error_code mos_clean_cosmics(cpl_image *image, float gain, float threshold, float ratio)
Remove cosmic rays from sky-subtracted CCD spectral exposure.
Definition: moses.c:13137
cpl_table * mos_global_distortion(cpl_table *slits, cpl_table *maskslits, cpl_table *ids, cpl_table *crv, double reference)
Determine all global distortions models.
Definition: moses.c:1200
int mos_spectral_resolution(cpl_image *image, double lambda, double startwave, double dispersion, int saturation, double *mfwhm, double *rmsfwhm, double *resolution, double *rmsres, int *nlines)
Compute mean spectral resolution at a given arc lamp line.
Definition: moses.c:14465
cpl_error_code mos_arc_background_1D(float *spectrum, float *back, int length, int msize, int fsize)
Background determination on 1D emission line spectrum (arc)
Definition: moses.c:3827
cpl_table * mos_resolution_table(cpl_image *image, double startwave, double dispersion, int saturation, cpl_vector *lines)
Compute mean spectral resolution at a given arc lamp line.
Definition: moses.c:14661
cpl_table * mos_load_slits_fors_pmos(cpl_propertylist *header, int *nslits_out_det)
Create PMOS slit location table from FITS header of FORS1/2 MOS data.
Definition: moses.c:17126
cpl_table * mos_load_slits_vimos(cpl_propertylist *header)
Create slit location table from FITS header of VIMOS data.
Definition: moses.c:15476
cpl_error_code mos_randomise_image(cpl_image *image, double ron, double gain, double bias)
Randomise image.
Definition: moses.c:16137
cpl_table * mos_build_curv_coeff(cpl_table *global, cpl_table *maskslits, cpl_table *slits)
Build the curvature coefficients table from a global distortions table.
Definition: moses.c:1693
cpl_table * mos_locate_spectra(cpl_mask *mask)
Find the location of detected spectra on the CCD.
Definition: moses.c:6153
cpl_table * mos_load_slits_fors_lss(cpl_propertylist *header)
Create slit location table from FITS header of FORS1/2 LSS data.
Definition: moses.c:15263
cpl_polynomial * mos_poly_pix2wav(cpl_bivector *pixwav, int order, double reject, int minlines, int *nlines, double *err)
Fit polynomial relation from pixels to wavelengths.
Definition: moses.c:5220
cpl_error_code mos_interpolate_wavecalib_mos(cpl_table *idscoeff, int mode, int degree)
Interpolate wavelength calibration for a single MOS slit.
Definition: moses.c:3416
cpl_image * mos_arc_background(cpl_image *image, int msize, int fsize)
Background determination on emission line spectrum (arc)
Definition: moses.c:3929
cpl_error_code mos_saturation_process(cpl_image *image)
Process saturation.
Definition: moses.c:16273
cpl_image * mos_map_spectrum(cpl_image *spectra, cpl_image *wavecalib, cpl_image *spatial, cpl_table *slits, cpl_table *polytraces, double reference, double blue, double red, double dispersion, int flux)
Remapping of slit spectra into a grid of lambda-space coordinates.
Definition: moses.c:11693
int mos_slit_closest_to_center(cpl_table *slits, int nx, int ny)
Return slit closest to CCD center.
Definition: moses.c:18922
cpl_image * mos_image_filter_median(cpl_image *image, int nx, int ny)
Convenience function for standard median filtering.
Definition: moses.c:19225
cpl_image * mos_spatial_map(cpl_image *spectra, cpl_table *slits, cpl_table *polytraces, double reference, double blue, double red, double dispersion)
Create coordinate map from spectral curvature table.
Definition: moses.c:13641
cpl_error_code mos_extract_flux_mapped(cpl_image *image, cpl_table *slits, double xwidth, double ywidth, double lambda, double startwave, double dispersion, int dx, double gain, double *o_flux, double *o_err)
Measure flux from spectral interval on remapped frame.
Definition: moses.c:19087
int mos_compute_offset(cpl_table *reference, cpl_table *objects, double *offset)
Estimate offset between two object tables.
Definition: moses.c:18658
cpl_image * mos_normalise_longflat(cpl_image *flat, int sradius, int dradius, int polyorder)
Normalise a long slit flat field exposure.
Definition: moses.c:2744
cpl_table * mos_load_overscans_vimos(const cpl_propertylist *header, int check_consistency)
Get the overscan positions from FITS header of VIMOS data.
Definition: moses.c:15686
int mos_median_in_slit(cpl_table *table, cpl_table *slits, int slit, char *label, double *mvalue)
Compute median from a table column section corresponding to a slit.
Definition: moses.c:19197
cpl_table * mos_build_slit_location(cpl_table *global, cpl_table *maskslits, int ysize)
Build the slit location table from a global distortions table.
Definition: moses.c:1546