FORS Pipeline Reference Manual  4.12.5
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 = 10;
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 *skywin,
914  cpl_image *extracted, cpl_image *sky,
915  cpl_image *error, int nobjects, int extraction,
916  double ron, double conad, int ncomb)
917 {
918 
919  cpl_vector *vprofile;
920  cpl_image *smowin;
921 
922  int i, j;
923  int specLen;
924  int numRows;
925  int index;
926  int iter;
927  int maxIter = 2; /* Not less than 2 !!! */
928  int smoothBox = 31; /* Not less than 5 !!! */
929  double nsigma = 5.0;
930 
931  double sumWeight, sum, sumSky, sumProf, variance, weight;
932  double *profile;
933  double *buffer;
934  float *edata;
935  float *ekdata;
936  float *endata;
937  float *sdata;
938  float *kdata;
939  float *fdata;
940 
941  double value;
942 
943 
944  specLen = cpl_image_get_size_x(sciwin);
945  numRows = cpl_image_get_size_y(sciwin);
946 
947  edata = cpl_image_get_data(extracted);
948  edata += nobjects * specLen;
949 
950  ekdata = cpl_image_get_data(sky);
951  ekdata += nobjects * specLen;
952 
953  endata = cpl_image_get_data(error);
954  endata += nobjects * specLen;
955 
956  sdata = cpl_image_get_data(sciwin);
957  kdata = cpl_image_get_data(skywin);
958 
959 
960  /*
961  * Initial spectrum estimate
962  if (sdata[i + j * specLen] > 0.0)
963  */
964 
965  if (extraction && numRows > 5) {
966  smowin = mos_image_filter_median(sciwin, 3, 3);
967  fdata = cpl_image_get_data(smowin);
968  for (i = 0; i < specLen; i++)
969  for (j = 0, edata[i] = 0.0; j < numRows; j++)
970  edata[i] += fdata[i + j * specLen];
971  cpl_image_delete(smowin);
972  }
973  else {
974  for (i = 0; i < specLen; i++)
975  for (j = 0, edata[i] = 0.0; j < numRows; j++)
976  edata[i] += sdata[i + j * specLen];
977  }
978 
979  if (extraction) {
980 
981  profile = cpl_calloc(specLen * numRows, sizeof(double));
982  buffer = cpl_calloc(specLen, sizeof(double));
983 
984  for (iter = 0; iter < maxIter; iter++) {
985 
986  /*
987  * Normalised spatial profile
988  */
989 
990  for (i = 0; i < specLen; i++) {
991  for (j = 0; j < numRows; j++) {
992  index = i + j * specLen;
993 /* if (sdata[index] > 0.0 && edata[i] > 0.00001) */
994  if (fabs(edata[i]) > 0.00001)
995  profile[index] = sdata[index] / edata[i];
996  else
997  profile[index] = 0.0;
998  }
999  }
1000 
1001  for (j = 0; j < numRows; j++) {
1002 
1003  /*
1004  * Smooth each row in the dispersion direction, and enforce positivity
1005  */
1006 
1007  for (i = 0; i < specLen - smoothBox; i++) {
1008  vprofile = cpl_vector_wrap(smoothBox, profile + i + j*specLen);
1009  value = cpl_vector_get_median_const(vprofile);
1010  cpl_vector_unwrap(vprofile);
1011  if (value < 0)
1012  value = 0.0;
1013  buffer[i + smoothBox / 2] = value;
1014  }
1015 
1016  /*
1017  * Replace the end portions (i.e., not median filtered) with a mean
1018  */
1019 
1020  vprofile = cpl_vector_wrap(smoothBox / 2, profile + j*specLen);
1021  value = cpl_vector_get_mean(vprofile);
1022  cpl_vector_unwrap(vprofile);
1023 
1024  if (value < 0)
1025  value = 0.0;
1026 
1027  for (i = 0; i < smoothBox / 2; i++)
1028  buffer[i] = value;
1029 
1030  vprofile = cpl_vector_wrap(smoothBox / 2,
1031  profile + specLen - smoothBox/2 + j*specLen);
1032  value = cpl_vector_get_mean(vprofile);
1033  cpl_vector_unwrap(vprofile);
1034 
1035  if (value < 0)
1036  value = 0.0;
1037 
1038  for (i = 0; i < smoothBox / 2; i++)
1039  buffer[i + specLen - smoothBox / 2] = value;
1040 
1041  for (i = 0; i < specLen; i++)
1042  profile[i + j * specLen] = buffer[i];
1043 
1044  }
1045 
1046  /*
1047  * Enforce normalization of spatial profile after smoothing
1048  */
1049 
1050  for (i = 0; i < specLen; i++) {
1051  for (j = 0, value = 0.0; j < numRows; j++)
1052  value += profile[i + j * specLen];
1053  if (value > 0.00001)
1054  for (j = 0; j < numRows; j++)
1055  profile[i + j * specLen] /= value;
1056  else
1057  for (j = 0; j < numRows; j++)
1058  profile[i + j * specLen] = 0.0;
1059  }
1060 
1061 
1062  /*
1063  * Optimal extraction
1064  */
1065 
1066  for (i = 0; i < specLen; i++) {
1067  sum = 0.0;
1068  sumSky = 0.0;
1069  sumWeight = 0.0;
1070  sumProf = 0.0;
1071  for (j = 0; j < numRows; j++) {
1072  index = i + j * specLen;
1073 /*
1074 if (sdata[index] > 0.0) {
1075 */
1076  variance = ron*ron + fabs(edata[i] * profile[index] + kdata[index])
1077  / conad;
1078  variance /= ncomb; /* If input dataset is sum of ncomb images */
1079  value = sdata[index] - edata[i] * profile[index];
1080  if (fabs(value) / sqrt(variance) < nsigma) {
1081  weight = 1000000 * profile[index] / variance;
1082  sum += weight * sdata[index];
1083  sumSky += weight * kdata[index];
1084  sumWeight += weight * profile[index];
1085  sumProf += profile[index];
1086  }
1087 /*
1088 }
1089 */
1090  }
1091 
1092  if (sumWeight > 0.00001) {
1093  edata[i] = sum / sumWeight;
1094  ekdata[i] = sumSky / sumWeight;
1095  endata[i] = 1000 * sqrt(sumProf / sumWeight);
1096  }
1097  else {
1098 /*
1099  edata[i] = 0.0;
1100  ekdata[i] = 0.0;
1101  endata[i] = 0.0;
1102 */
1103  endata[i] = sqrt(ron*ron + fabs(edata[i] + ekdata[i]) / conad);
1104  }
1105  }
1106  }
1107  cpl_free(profile);
1108  cpl_free(buffer);
1109  }
1110  else {
1111 
1112  /*
1113  * Add sky estimation for the simple aperture extraction.
1114  if (kdata[i + j * specLen] > 0.0)
1115  */
1116 
1117  for (i = 0; i < specLen; i++)
1118  for (j = 0, ekdata[i] = 0.0; j < numRows; j++)
1119  ekdata[i] += kdata[i + j * specLen];
1120 
1121  /*
1122  * Add error estimation for the simple aperture extraction.
1123  */
1124 
1125  for (i = 0; i < specLen; i++)
1126  endata[i] = sqrt(ron*ron + fabs(edata[i] + ekdata[i]) / conad);
1127 
1128  }
1129 
1130 }
1131 
1132 
1179 cpl_table *mos_global_distortion(cpl_table *slits, cpl_table *maskslits,
1180  cpl_table *ids, cpl_table *crv,
1181  double reference)
1182 {
1183  const char *func = "mos_global_distortion";
1184 
1185  const char *clab[6] = {"c0", "c1", "c2", "c3", "c4", "c5"};
1186 
1187  cpl_table *global = NULL;
1188  cpl_table *coeff;
1189  cpl_table *dummy;
1190  cpl_vector *ci;
1191  cpl_vector *xmask;
1192  cpl_vector *ymask;
1193  cpl_bivector *mask;
1194  cpl_vector *xccd;
1195  cpl_vector *yccd;
1196  cpl_bivector *ccd;
1197  cpl_polynomial *poly;
1198  double *xtop;
1199  double *ytop;
1200  double *xbottom;
1201  double *ybottom;
1202  double *mxtop;
1203  double *mytop;
1204  double *mxbottom;
1205  double *mybottom;
1206  int *position;
1207  int *length;
1208  int *slit_id;
1209  int *mslit_id;
1210  int nslits, nmaskslits, npoints;
1211  int order;
1212  int i, j;
1213  int minslit = 6; // 12;
1214 
1215 
1216 /* *+
1217 printf("error1: %s\n", cpl_error_get_message());
1218 +* */
1219  if (slits == NULL || maskslits == NULL || ids == NULL || crv == NULL) {
1220  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
1221  return NULL;
1222  }
1223 /* *+
1224 printf("error1a: %s\n", cpl_error_get_message());
1225 +* */
1226 
1227  nslits = cpl_table_get_nrow(slits);
1228 /* *+
1229 printf("error1b: %s\n", cpl_error_get_message());
1230 +* */
1231 
1232  if (nslits < minslit) {
1233  cpl_msg_warning(func, "Too few slits (%d < %d) for global "
1234  "distortion model determination", nslits, minslit);
1235  return NULL;
1236  }
1237 /* *+
1238 printf("error1c: %s\n", cpl_error_get_message());
1239 +* */
1240 
1241  nmaskslits = cpl_table_get_nrow(maskslits);
1242 
1243  length = cpl_table_get_data_int(slits, "length");
1244  position = cpl_table_get_data_int(slits, "position");
1245  slit_id = cpl_table_get_data_int(slits, "slit_id");
1246  mslit_id = cpl_table_get_data_int(maskslits, "slit_id");
1247  xtop = cpl_table_get_data_double(slits, "xtop");
1248  ytop = cpl_table_get_data_double(slits, "ytop");
1249  xbottom = cpl_table_get_data_double(slits, "xbottom");
1250  ybottom = cpl_table_get_data_double(slits, "ybottom");
1251  mxtop = cpl_table_get_data_double(maskslits, "xtop");
1252  mytop = cpl_table_get_data_double(maskslits, "ytop");
1253  mxbottom = cpl_table_get_data_double(maskslits, "xbottom");
1254  mybottom = cpl_table_get_data_double(maskslits, "ybottom");
1255 
1256 
1257  /*
1258  * Global IDS
1259  */
1260 
1261  coeff = cpl_table_new(nslits);
1262  cpl_table_copy_structure(coeff, ids);
1263  cpl_table_new_column(coeff, "xccd", CPL_TYPE_DOUBLE);
1264  cpl_table_new_column(coeff, "yccd", CPL_TYPE_DOUBLE);
1265  cpl_table_new_column(coeff, "xmask", CPL_TYPE_DOUBLE);
1266  cpl_table_new_column(coeff, "ymask", CPL_TYPE_DOUBLE);
1267 
1268 /* *+
1269 printf("error2: %s\n", cpl_error_get_message());
1270 +* */
1271  for (i = 0; i < nslits; i++) {
1272  for (j = 0; j < nmaskslits; j++) {
1273  if (slit_id[i] == mslit_id[j]) {
1274  cpl_table_set_double(coeff, "xmask", i,
1275  (mxtop[j] + mxbottom[j]) / 2);
1276  cpl_table_set_double(coeff, "ymask", i,
1277  (mytop[j] + mybottom[j]) / 2);
1278  }
1279  }
1280  }
1281 
1282  if (cpl_table_has_invalid(coeff, "xmask")) {
1283  cpl_error_set(func, CPL_ERROR_INCOMPATIBLE_INPUT);
1284  cpl_table_delete(coeff);
1285  return NULL;
1286  }
1287 
1288  for (i = 0; i < nslits; i++) {
1289  cpl_table_set_double(coeff, "xccd", i, (xtop[i] + xbottom[i]) / 2);
1290  cpl_table_set_double(coeff, "yccd", i, (ytop[i] + ybottom[i]) / 2);
1291  }
1292 
1293 /* *+
1294 printf("error3: %s\n", cpl_error_get_message());
1295 +* */
1296  for (i = 0; i < nslits; i++) {
1297 
1298  if (length[i] == 0)
1299  continue;
1300 
1301  cpl_table_and_selected_window(ids, position[i], length[i]);
1302  dummy = cpl_table_extract_selected(ids);
1303  for (j = 0; j < 6; j++) {
1304  if (cpl_table_has_column(dummy, clab[j])) {
1305  if (length[i] - cpl_table_count_invalid(dummy, clab[j]) > 10) {
1306  cpl_table_set_double(coeff, clab[j], i,
1307  cpl_table_get_column_median(dummy, clab[j]));
1308  }
1309  }
1310  }
1311 
1312  cpl_table_delete(dummy);
1313  cpl_table_select_all(ids);
1314 
1315  }
1316 
1317 /* *+
1318 printf("error4: %s\n", cpl_error_get_message());
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 /* *+
1389 printf("error5: %s\n", cpl_error_get_message());
1390 +* */
1391  cpl_table_delete(coeff);
1392 /* *+
1393 printf("error6: %s\n", cpl_error_get_message());
1394 +* */
1395 
1396 
1397  /*
1398  * Add model's reference wavelength
1399  */
1400 
1401  cpl_table_set_double(global, "a00", 6, reference);
1402 
1403 
1404  /*
1405  * Global curvature model
1406  */
1407 
1408  coeff = cpl_table_duplicate(crv);
1409  cpl_table_new_column(coeff, "xmask", CPL_TYPE_DOUBLE);
1410  cpl_table_new_column(coeff, "ymask", CPL_TYPE_DOUBLE);
1411  slit_id = cpl_table_get_data_int(coeff, "slit_id");
1412  npoints = cpl_table_get_nrow(coeff);
1413 
1414 /* *+
1415 printf("error7: %s\n", cpl_error_get_message());
1416 +* */
1417  for (i = 0; i < npoints; i++) {
1418  for (j = 0; j < nmaskslits; j++) {
1419  if (slit_id[i] == mslit_id[j]) {
1420  if (i%2) {
1421  cpl_table_set_double(coeff, "xmask", i, mxbottom[j]);
1422  cpl_table_set_double(coeff, "ymask", i, mybottom[j]);
1423  }
1424  else {
1425  cpl_table_set_double(coeff, "xmask", i, mxtop[j]);
1426  cpl_table_set_double(coeff, "ymask", i, mytop[j]);
1427  }
1428  }
1429  }
1430  }
1431 
1432 /* *+
1433 printf("error8: %s\n", cpl_error_get_message());
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 /* *+
1442 printf("error9: %s\n", cpl_error_get_message());
1443 +* */
1444  for (j = 0; j < 3; j++) {
1445  if (cpl_table_has_column(coeff, clab[j])) {
1446  cpl_table_and_selected_invalid(coeff, clab[j]);
1447 
1448  if (cpl_table_not_selected(coeff))
1449  dummy = cpl_table_extract_selected(coeff);
1450  else
1451  break;
1452 
1453  npoints = cpl_table_get_nrow(dummy);
1454 
1455  if (npoints >= 6) {
1456 
1457  if (npoints >= 12)
1458  order = 2;
1459  else
1460  order = 1;
1461 
1462  ci = cpl_vector_wrap(npoints,
1463  cpl_table_get_data_double(dummy, clab[j]));
1464  xmask = cpl_vector_wrap(npoints,
1465  cpl_table_get_data_double(dummy, "xmask"));
1466  ymask = cpl_vector_wrap(npoints,
1467  cpl_table_get_data_double(dummy, "ymask"));
1468  mask = cpl_bivector_wrap_vectors(xmask, ymask);
1469 
1470  poly = cpl_polynomial_fit_2d_create(mask, ci, order, NULL);
1471 
1472  cpl_bivector_unwrap_vectors(mask);
1473  cpl_vector_unwrap(ci);
1474  cpl_vector_unwrap(xmask);
1475  cpl_vector_unwrap(ymask);
1476  }
1477  else {
1478  cpl_size p[2] = {0, 0};
1479  poly = cpl_polynomial_new(2);
1480  cpl_polynomial_set_coeff(poly, p,
1481  cpl_table_get_column_median(dummy, clab[j]));
1482  }
1483 
1484  cpl_table_delete(dummy);
1485 
1486  global = write_global_distortion(global, j + 7, poly);
1487 
1488  cpl_polynomial_delete(poly);
1489  cpl_table_select_all(coeff);
1490  }
1491  }
1492 
1493 /* *+
1494 printf("error10: %s\n", cpl_error_get_message());
1495 +* */
1496  cpl_table_delete(coeff);
1497 /* *+
1498 printf("error11: %s\n", cpl_error_get_message());
1499 +* */
1500 
1501  return global;
1502 
1503 }
1504 
1505 
1543 cpl_table *mos_build_slit_location(cpl_table *global, cpl_table *maskslits,
1544  int ysize)
1545 {
1546  const char *func = "mos_build_slit_location";
1547 
1548  cpl_propertylist *sort_col;
1549  cpl_polynomial *ids0;
1550  cpl_polynomial *crv[3];
1551  cpl_polynomial *loc_crv;
1552  cpl_vector *point;
1553  cpl_table *slits;
1554  cpl_size nslits;
1555  int *slit_id;
1556  double *dpoint;
1557  double *xtop;
1558  double *ytop;
1559  double *xbottom;
1560  double *ybottom;
1561  double *mxtop;
1562  double *mytop;
1563  double *mxbottom;
1564  double *mybottom;
1565  cpl_size i;
1566  cpl_size j;
1567 
1568 
1569  if (global == NULL || maskslits == NULL) {
1570  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
1571  return NULL;
1572  }
1573 
1574  nslits = cpl_table_get_nrow(maskslits);
1575  slit_id = cpl_table_get_data_int(maskslits, "slit_id");
1576  mxtop = cpl_table_get_data_double(maskslits, "xtop");
1577  mytop = cpl_table_get_data_double(maskslits, "ytop");
1578  mxbottom = cpl_table_get_data_double(maskslits, "xbottom");
1579  mybottom = cpl_table_get_data_double(maskslits, "ybottom");
1580 
1581  slits = cpl_table_duplicate(maskslits);
1582 
1583  xtop = cpl_table_get_data_double(slits, "xtop");
1584  ytop = cpl_table_get_data_double(slits, "ytop");
1585  xbottom = cpl_table_get_data_double(slits, "xbottom");
1586  ybottom = cpl_table_get_data_double(slits, "ybottom");
1587 
1588  ids0 = read_global_distortion(global, 0);
1589  crv[0] = read_global_distortion(global, 7);
1590  crv[1] = read_global_distortion(global, 8);
1591  crv[2] = read_global_distortion(global, 9);
1592 
1593  loc_crv = cpl_polynomial_new(1);
1594 
1595  point = cpl_vector_new(2);
1596  dpoint = cpl_vector_get_data(point);
1597 
1598  for (i = 0; i < nslits; i++) {
1599  dpoint[0] = mxtop[i];
1600  dpoint[1] = mytop[i];
1601 
1602  xtop[i] = cpl_polynomial_eval(ids0, point);
1603 
1604  for (j = 0; j < 3; j++)
1605  if (crv[j])
1606  cpl_polynomial_set_coeff(loc_crv, &j,
1607  cpl_polynomial_eval(crv[j], point));
1608 
1609  ytop[i] = cpl_polynomial_eval_1d(loc_crv, xtop[i], NULL);
1610 
1611  dpoint[0] = mxbottom[i];
1612  dpoint[1] = mybottom[i];
1613  xbottom[i] = cpl_polynomial_eval(ids0, point);
1614 
1615  for (j = 0; j < 3; j++)
1616  if (crv[j])
1617  cpl_polynomial_set_coeff(loc_crv, &j,
1618  cpl_polynomial_eval(crv[j], point));
1619 
1620  ybottom[i] = cpl_polynomial_eval_1d(loc_crv, xbottom[i], NULL);
1621  }
1622 
1623  cpl_vector_delete(point);
1624  cpl_polynomial_delete(ids0);
1625  cpl_polynomial_delete(loc_crv);
1626  for (j = 0; j < 3; j++)
1627  cpl_polynomial_delete(crv[j]);
1628 
1629  sort_col = cpl_propertylist_new();
1630  cpl_propertylist_append_bool(sort_col, "ytop", 1);
1631  cpl_table_sort(slits, sort_col);
1632  cpl_table_sort(maskslits, sort_col);
1633  cpl_propertylist_delete(sort_col);
1634 
1635  /*
1636  * Eliminate slits which are _entirely_ outside the CCD
1637  */
1638 
1639  cpl_table_and_selected_double(slits, "ybottom", CPL_GREATER_THAN, ysize-1);
1640  cpl_table_or_selected_double(slits, "ytop", CPL_LESS_THAN, 0);
1641  cpl_table_erase_selected(slits);
1642 
1643  nslits = cpl_table_get_nrow(slits);
1644 
1645  if (nslits == 0) {
1646  cpl_msg_warning(func, "No slits found on the CCD");
1647  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
1648  cpl_table_delete(slits);
1649  return NULL;
1650  }
1651 
1652  if (nslits > 1)
1653  cpl_msg_info(func, "Slit location: %"CPL_SIZE_FORMAT" slits are entirely or partially "
1654  "contained in CCD", nslits);
1655  else
1656  cpl_msg_info(func, "Slit location: %"CPL_SIZE_FORMAT" slit is entirely or partially "
1657  "contained in CCD", nslits);
1658 
1659  return slits;
1660 
1661 }
1662 
1663 
1690 cpl_table *mos_build_curv_coeff(cpl_table *global, cpl_table *maskslits,
1691  cpl_table *slits)
1692 {
1693  const char *func = "mos_build_curv_coeff";
1694 
1695  const char *clab[6] = {"c0", "c1", "c2", "c3", "c4", "c5"};
1696  /* Max order is 5 */
1697 
1698  cpl_polynomial *crv[3];
1699  cpl_vector *point;
1700  cpl_table *polytraces;
1701  double *dpoint;
1702  double *xtop;
1703  double *ytop;
1704  double *xbottom;
1705  double *ybottom;
1706  int *slit_id;
1707  int *valid_id;
1708  int nslits, nvalid;
1709  int found;
1710  int i, j, k;
1711 
1712 
1713  if (global == NULL || slits == NULL || maskslits == NULL) {
1714  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
1715  return NULL;
1716  }
1717 
1718  nslits = cpl_table_get_nrow(maskslits);
1719  slit_id = cpl_table_get_data_int(maskslits, "slit_id");
1720  xtop = cpl_table_get_data_double(maskslits, "xtop");
1721  ytop = cpl_table_get_data_double(maskslits, "ytop");
1722  xbottom = cpl_table_get_data_double(maskslits, "xbottom");
1723  ybottom = cpl_table_get_data_double(maskslits, "ybottom");
1724 
1725  polytraces = cpl_table_new(2*nslits);
1726  cpl_table_new_column(polytraces, "slit_id", CPL_TYPE_INT);
1727  for (i = 0; i < 3; i++)
1728  cpl_table_new_column(polytraces, clab[i], CPL_TYPE_DOUBLE);
1729 
1730  crv[0] = read_global_distortion(global, 7);
1731  crv[1] = read_global_distortion(global, 8);
1732  crv[2] = read_global_distortion(global, 9);
1733 
1734  point = cpl_vector_new(2);
1735  dpoint = cpl_vector_get_data(point);
1736 
1737  for (i = 0; i < nslits; i++) {
1738  for (j = 0; j < 2; j++) { /* For top and bottom trace of each slit */
1739 
1740  cpl_table_set_int(polytraces, "slit_id", 2*i+j, slit_id[i]);
1741 
1742  if (j) {
1743  dpoint[0] = xbottom[i];
1744  dpoint[1] = ybottom[i];
1745  }
1746  else {
1747  dpoint[0] = xtop[i];
1748  dpoint[1] = ytop[i];
1749  }
1750 
1751  for (k = 0; k < 3; k++)
1752  if (crv[j])
1753  cpl_table_set_double(polytraces, clab[k], 2*i+j,
1754  cpl_polynomial_eval(crv[k], point));
1755  }
1756  }
1757 
1758  cpl_vector_delete(point);
1759  for (j = 0; j < 3; j++)
1760  cpl_polynomial_delete(crv[j]);
1761 
1762  /*
1763  * Eliminate slits which are _entirely_ outside the CCD
1764  */
1765 
1766  nvalid = cpl_table_get_nrow(slits);
1767  valid_id = cpl_table_get_data_int(slits, "slit_id");
1768  cpl_table_unselect_all(polytraces);
1769  for (i = 0; i < nslits; i++) {
1770  found = 0;
1771  for (j = 0; j < nvalid; j++) {
1772  if (slit_id[i] == valid_id[j]) {
1773  found = 1;
1774  break;
1775  }
1776  }
1777  if (!found) {
1778  cpl_table_select_row(polytraces, 2*i);
1779  cpl_table_select_row(polytraces, 2*i + 1);
1780  }
1781  }
1782  cpl_table_erase_selected(polytraces);
1783 
1784  nslits = cpl_table_get_nrow(polytraces);
1785 
1786  if (nslits == 0) {
1787  cpl_msg_warning(func, "No slits found on the CCD");
1788  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
1789  cpl_table_delete(polytraces);
1790  return NULL;
1791  }
1792 
1793  if (nslits > 2)
1794  cpl_msg_info(func, "Curvature model: %d slits are entirely or "
1795  "partially contained in CCD", nslits / 2);
1796  else
1797  cpl_msg_info(func, "Curvature model: %d slit is entirely or "
1798  "partially contained in CCD", nslits / 2);
1799 
1800  return polytraces;
1801 }
1802 
1803 
1845 cpl_table *mos_build_disp_coeff(cpl_table *global, cpl_table *slits)
1846 {
1847  const char *func = "mos_build_disp_coeff";
1848 
1849  const char *clab[6] = {"c0", "c1", "c2", "c3", "c4", "c5"};
1850 
1851  cpl_polynomial *ids[6];
1852  cpl_vector *point;
1853  cpl_table *idscoeff;
1854  double *dpoint;
1855  double *xtop;
1856  double *ytop;
1857  double *xbottom;
1858  double *ybottom;
1859  int *position;
1860  int *length;
1861  int nslits;
1862  int nrows;
1863  int order;
1864  int ylow, yhig;
1865  int i, j, k;
1866 
1867 
1868  if (global == NULL || slits == NULL) {
1869  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
1870  return NULL;
1871  }
1872 
1873  nslits = cpl_table_get_nrow(slits);
1874  position = cpl_table_get_data_int(slits, "position");
1875  length = cpl_table_get_data_int(slits, "length");
1876  xtop = cpl_table_get_data_double(slits, "xtop");
1877  ytop = cpl_table_get_data_double(slits, "ytop");
1878  xbottom = cpl_table_get_data_double(slits, "xbottom");
1879  ybottom = cpl_table_get_data_double(slits, "ybottom");
1880 
1881  for (i = 0; i < 6; i++)
1882  ids[i] = read_global_distortion(global, i);
1883 
1884  for (i = 0; i < 6; i++)
1885  if (ids[i] == NULL)
1886  break;
1887 
1888  order = i - 1;
1889 
1890  if (order < 1) {
1891  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
1892  return NULL;
1893  }
1894 
1895  nrows = 0;
1896  for (i = 0; i < nslits; i++)
1897  nrows += length[i];
1898 
1899  idscoeff = cpl_table_new(nrows);
1900 
1901  for (j = 0; j <= order; j++)
1902  cpl_table_new_column(idscoeff, clab[j], CPL_TYPE_DOUBLE);
1903 
1904  cpl_table_new_column(idscoeff, "error", CPL_TYPE_DOUBLE);
1905  cpl_table_fill_column_window_double(idscoeff, "error", 0, nrows, 0.0);
1906  cpl_table_new_column(idscoeff, "nlines", CPL_TYPE_INT);
1907  cpl_table_fill_column_window_int(idscoeff, "nlines", 0, nrows, 0);
1908 
1909  point = cpl_vector_new(2);
1910  dpoint = cpl_vector_get_data(point);
1911 
1912  for (i = 0; i < nslits; i++) {
1913 
1914  if (length[i] == 0)
1915  continue;
1916 
1917  ylow = position[i];
1918  yhig = ylow + length[i];
1919 
1920  for (j = 0; j <= order; j++) {
1921  if (j) {
1922  for (k = 0; k < length[i]; k++) {
1923  dpoint[0] = xbottom[i] + k*(xtop[i]-xbottom[i])/length[i];
1924  dpoint[1] = ybottom[i] + k*(ytop[i]-ybottom[i])/length[i];
1925  cpl_table_set_double(idscoeff, clab[j], ylow + k,
1926  cpl_polynomial_eval(ids[j], point));
1927  }
1928  }
1929  else {
1930  for (k = 0; k < length[i]; k++) {
1931  cpl_table_set_double(idscoeff, clab[0], ylow + k,
1932  xbottom[i] + k*(xtop[i]-xbottom[i])/length[i]);
1933  }
1934  }
1935  }
1936  }
1937 
1938  cpl_vector_delete(point);
1939  for (j = 0; j < 6; j++)
1940  cpl_polynomial_delete(ids[j]);
1941 
1942  return idscoeff;
1943 
1944 }
1945 
1946 
1969 cpl_image *mos_subtract_sky(cpl_image *science, cpl_table *slits,
1970  cpl_table *polytraces, double reference,
1971  double blue, double red, double dispersion)
1972 {
1973  const char *func = "mos_subtract_sky";
1974 
1975  const char *clab[6] = {"c0", "c1", "c2", "c3", "c4", "c5"};
1976  /* Max order is 5 */
1977 
1978  cpl_image *sky;
1979  cpl_bivector *list;
1980  cpl_vector *listx;
1981  cpl_vector *listy;
1982  cpl_polynomial *polytop;
1983  cpl_polynomial *polybot;
1984  cpl_polynomial *trend;
1985 
1986  int *slit_id;
1987  double *dlistx;
1988  double *dlisty;
1989  float *sdata;
1990  float *kdata;
1991  double top, bot;
1992  int itop, ibot;
1993  double coeff;
1994  double ytop, ybot;
1995  double m, q, err;
1996  int npix;
1997 
1998  int pixel_above, pixel_below, refpixel, start_pixel, end_pixel;
1999  int nx, ny;
2000  int nslits;
2001  int *length;
2002  int missing_top, missing_bot;
2003  int order;
2004  int null;
2005  int window = 50; /* Longer slits have polynomial sky model */
2006  int count;
2007  int i, j;
2008  cpl_size k;
2009 
2010 
2011  if (science == NULL || slits == NULL || polytraces == NULL) {
2012  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
2013  return NULL;
2014  }
2015 
2016  if (dispersion <= 0.0) {
2017  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
2018  return NULL;
2019  }
2020 
2021  if (red - blue < dispersion) {
2022  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
2023  return NULL;
2024  }
2025 
2026  nx = cpl_image_get_size_x(science);
2027  ny = cpl_image_get_size_y(science);
2028 
2029  sky = cpl_image_new(nx, ny, CPL_TYPE_FLOAT);
2030 
2031  sdata = cpl_image_get_data(science);
2032  kdata = cpl_image_get_data(sky);
2033 
2034  nslits = cpl_table_get_nrow(slits);
2035  order = cpl_table_get_ncol(polytraces) - 2;
2036  length = cpl_table_get_data_int(slits, "length");
2037  slit_id = cpl_table_get_data_int(slits, "slit_id");
2038 
2039  /*
2040  * The spatial resampling is performed for a certain number of
2041  * pixels above and below the position of the reference wavelength:
2042  */
2043 
2044  pixel_above = STRETCH_FACTOR * (red - reference) / dispersion;
2045  pixel_below = STRETCH_FACTOR * (reference - blue) / dispersion;
2046 
2047  for (i = 0; i < nslits; i++) {
2048 
2049  if (length[i] == 0)
2050  continue;
2051 
2052 
2053  /*
2054  * Recover from the table of spectral curvature coefficients
2055  * the curvature polynomials.
2056  */
2057 
2058  refpixel = cpl_table_get_double(slits, "xtop", i, NULL);
2059 
2060  start_pixel = refpixel - pixel_below;
2061  if (start_pixel < 0)
2062  start_pixel = 0;
2063 
2064  end_pixel = refpixel + pixel_above;
2065  if (end_pixel > nx)
2066  end_pixel = nx;
2067 
2068  missing_top = 0;
2069  polytop = cpl_polynomial_new(1);
2070  for (k = 0; k <= order; k++) {
2071  coeff = cpl_table_get_double(polytraces, clab[k], 2*i, &null);
2072  if (null) {
2073  cpl_polynomial_delete(polytop);
2074  missing_top = 1;
2075  break;
2076  }
2077  cpl_polynomial_set_coeff(polytop, &k, coeff);
2078  }
2079 
2080  missing_bot = 0;
2081  polybot = cpl_polynomial_new(1);
2082  for (k = 0; k <= order; k++) {
2083  coeff = cpl_table_get_double(polytraces, clab[k], 2*i+1, &null);
2084  if (null) {
2085  cpl_polynomial_delete(polybot);
2086  missing_bot = 1;
2087  break;
2088  }
2089  cpl_polynomial_set_coeff(polybot, &k, coeff);
2090  }
2091 
2092  if (missing_top && missing_bot) {
2093  cpl_msg_debug(func, "Slit %d was not traced: no extraction!",
2094  slit_id[i]);
2095  continue;
2096  }
2097 
2098  /*
2099  * In case just one of the two edges was not traced, the other
2100  * edge curvature model is duplicated and shifted to the other
2101  * end of the slit: better than nothing!
2102  */
2103 
2104  if (missing_top) {
2105  cpl_msg_debug(func, "Upper edge of slit %d was not traced: "
2106  "the spectral curvature of the lower edge "
2107  "is used instead.", slit_id[i]);
2108  polytop = cpl_polynomial_duplicate(polybot);
2109  ytop = cpl_table_get_double(slits, "ytop", i, NULL);
2110  ybot = cpl_table_get_double(slits, "ybottom", i, NULL);
2111  k = 0;
2112  coeff = cpl_polynomial_get_coeff(polybot, &k);
2113  coeff += ytop - ybot;
2114  cpl_polynomial_set_coeff(polytop, &k, coeff);
2115  }
2116 
2117  if (missing_bot) {
2118  cpl_msg_debug(func, "Lower edge of slit %d was not traced: "
2119  "the spectral curvature of the upper edge "
2120  "is used instead.", slit_id[i]);
2121  polybot = cpl_polynomial_duplicate(polytop);
2122  ytop = cpl_table_get_double(slits, "ytop", i, NULL);
2123  ybot = cpl_table_get_double(slits, "ybottom", i, NULL);
2124  k = 0;
2125  coeff = cpl_polynomial_get_coeff(polytop, &k);
2126  coeff -= ytop - ybot;
2127  cpl_polynomial_set_coeff(polybot, &k, coeff);
2128  }
2129 
2130 
2131  /*
2132  * Now read pixel values along spatial direction, and fit them.
2133  */
2134 
2135  for (j = start_pixel; j < end_pixel; j++) {
2136  top = cpl_polynomial_eval_1d(polytop, j, NULL);
2137  bot = cpl_polynomial_eval_1d(polybot, j, NULL);
2138  itop = floor(top + 0.5) + 1;
2139  ibot = floor(bot + 0.5);
2140  if (itop > ny)
2141  itop = ny;
2142  if (ibot < 0)
2143  ibot = 0;
2144  npix = itop - ibot;
2145  if (npix < 5)
2146  break;
2147 
2148  list = cpl_bivector_new(npix);
2149  listx = cpl_bivector_get_x(list);
2150  listy = cpl_bivector_get_y(list);
2151  dlistx = cpl_vector_get_data(listx);
2152  dlisty = cpl_vector_get_data(listy);
2153 
2154  for (k = 0; k < npix; k++) {
2155  dlistx[k] = k;
2156  dlisty[k] = sdata[j + (ibot + k)*nx];
2157  }
2158 
2159  if (robustLinearFit(list, &q, &m, &err)) {
2160  cpl_bivector_delete(list);
2161  continue;
2162  }
2163 
2164  cpl_bivector_delete(list);
2165 
2166  for (k = 0; k < npix; k++) {
2167  kdata[j + (ibot + k)*nx] = m*k + q;
2168  }
2169 
2170  if (npix > window) {
2171 
2172  /*
2173  * Polynomial iteration
2174  */
2175 
2176  err = 3*sqrt(err);
2177 
2178  count = 0;
2179  for (k = 0; k < npix; k++)
2180  if (fabs(sdata[j + (ibot + k)*nx] - m*k - q) < err)
2181  count++;
2182 
2183  if (count < 10)
2184  continue;
2185 
2186  list = cpl_bivector_new(count);
2187  listx = cpl_bivector_get_x(list);
2188  listy = cpl_bivector_get_y(list);
2189  dlistx = cpl_vector_get_data(listx);
2190  dlisty = cpl_vector_get_data(listy);
2191 
2192  count = 0;
2193  for (k = 0; k < npix; k++) {
2194  if (fabs(sdata[j + (ibot + k)*nx] - m*k - q) < err) {
2195  dlistx[count] = k;
2196  dlisty[count] = sdata[j + (ibot + k)*nx];
2197  count++;
2198  }
2199  }
2200 
2201  trend = cpl_polynomial_fit_1d_create(listx, listy, 2, &err);
2202 
2203  cpl_bivector_delete(list);
2204 
2205  err = 3*sqrt(err);
2206 
2207  count = 0;
2208  for (k = 0; k < npix; k++)
2209  if (fabs(sdata[j + (ibot + k)*nx]
2210  - cpl_polynomial_eval_1d(trend, k, NULL)) < err)
2211  count++;
2212 
2213  if (count < 10) {
2214  cpl_polynomial_delete(trend);
2215  continue;
2216  }
2217 
2218  list = cpl_bivector_new(count);
2219  listx = cpl_bivector_get_x(list);
2220  listy = cpl_bivector_get_y(list);
2221  dlistx = cpl_vector_get_data(listx);
2222  dlisty = cpl_vector_get_data(listy);
2223 
2224  count = 0;
2225  for (k = 0; k < npix; k++) {
2226  if (fabs(sdata[j + (ibot + k)*nx]
2227  - cpl_polynomial_eval_1d(trend, k, NULL)) < err) {
2228  dlistx[count] = k;
2229  dlisty[count] = sdata[j + (ibot + k)*nx];
2230  count++;
2231  }
2232  }
2233 
2234  cpl_polynomial_delete(trend);
2235 
2236  trend = cpl_polynomial_fit_1d_create(listx, listy, 3, &err);
2237 
2238  cpl_bivector_delete(list);
2239 
2240  for (k = 0; k < npix; k++) {
2241  kdata[j + (ibot + k)*nx] = cpl_polynomial_eval_1d(trend,
2242  k, NULL);
2243  }
2244 
2245  cpl_polynomial_delete(trend);
2246  }
2247  }
2248  cpl_polynomial_delete(polytop);
2249  cpl_polynomial_delete(polybot);
2250  }
2251 
2252  cpl_image_subtract(science, sky);
2253 
2254  return sky;
2255 }
2256 
2257 
2290 cpl_image *mos_normalise_flat(cpl_image *flat, cpl_image *spatial,
2291  cpl_table *slits, cpl_table *polytraces,
2292  double reference, double blue, double red,
2293  double dispersion, int sradius, int polyorder)
2294 {
2295  const char *func = "mos_normalise_flat";
2296 
2297  const char *clab[6] = {"c0", "c1", "c2", "c3", "c4", "c5"};
2298  /* Max order is 5 */
2299 
2300  cpl_image *rectified;
2301  cpl_image *smo_flat;
2302  cpl_image *exslit;
2303  cpl_vector *positions;
2304  cpl_vector *flux;
2305  cpl_vector *smo_flux;
2306  cpl_polynomial *trend;
2307  cpl_polynomial *polytop;
2308  cpl_polynomial *polybot;
2309 
2310  int *slit_id;
2311  float *p;
2312  float *data;
2313  double *fdata;
2314  double *pdata;
2315  float *sdata;
2316  float *xdata;
2317  float *wdata;
2318  double vtop, vbot, value;
2319  double top, bot;
2320  double coeff;
2321  double ytop, ybot;
2322  double ypos;
2323  double fvalue;
2324  int ivalue;
2325  int yint, yprev;
2326  int npseudo;
2327 
2328  int pixel_above, pixel_below, refpixel, start_pixel, end_pixel;
2329  int nx, ny, nsubx, nsuby;
2330  int xlow, ylow, xhig, yhig;
2331  int nslits;
2332  int *position;
2333  int *length;
2334  int missing_top, missing_bot;
2335  int order;
2336  int npoints;
2337  int uradius;
2338  int null;
2339  int i, j;
2340  cpl_size k;
2341 
2342 /* int exclude = 5; Number of excluded pixels at edges */
2343 
2344  /* For debug puposes only: cpl_image *smo_rectified; */
2345 
2346 
2347  if (flat == NULL || slits == NULL || polytraces == NULL) {
2348  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
2349  return NULL;
2350  }
2351 
2352  if (dispersion <= 0.0) {
2353  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
2354  return NULL;
2355  }
2356 
2357  if (red - blue < dispersion) {
2358  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
2359  return NULL;
2360  }
2361 
2362  rectified = mos_spatial_calibration(flat, slits, polytraces, reference,
2363  blue, red, dispersion, 0, NULL);
2364 
2365  nx = cpl_image_get_size_x(rectified);
2366  ny = cpl_image_get_size_y(rectified);
2367 
2368  smo_flat = cpl_image_new(cpl_image_get_size_x(spatial),
2369  cpl_image_get_size_y(spatial), CPL_TYPE_FLOAT);
2370  wdata = cpl_image_get_data(smo_flat);
2371 
2372  nslits = cpl_table_get_nrow(slits);
2373  order = cpl_table_get_ncol(polytraces) - 2;
2374  position = cpl_table_get_data_int(slits, "position");
2375  length = cpl_table_get_data_int(slits, "length");
2376  slit_id = cpl_table_get_data_int(slits, "slit_id");
2377 
2378  /*
2379  * The spatial resampling is performed for a certain number of
2380  * pixels above and below the position of the reference wavelength:
2381  */
2382 
2383  pixel_above = STRETCH_FACTOR * (red - reference) / dispersion;
2384  pixel_below = STRETCH_FACTOR * (reference - blue) / dispersion;
2385 
2386  xlow = 1;
2387  xhig = nx;
2388  for (i = 0; i < nslits; i++) {
2389 
2390  if (length[i] == 0)
2391  continue;
2392 
2393  /*
2394  * We DON'T write:
2395  *
2396  * ylow = position[i];
2397  * yhig = ylow + length[i];
2398  *
2399  * because the cpl_image pixels are counted from 1, and because in
2400  * cpl_image_extract() the coordinates of the last pixel are inclusive.
2401  */
2402 
2403  ylow = position[i] + 1;
2404  yhig = ylow + length[i] - 1;
2405 
2406  exslit = cpl_image_extract(rectified, xlow, ylow, xhig, yhig);
2407 
2408  if (polyorder < 0) {
2409 
2410  cpl_image_turn(exslit, -1); /* For faster memory access */
2411 
2412  nsubx = cpl_image_get_size_x(exslit);
2413  nsuby = cpl_image_get_size_y(exslit);
2414  data = cpl_image_get_data(exslit);
2415  flux = cpl_vector_new(nsubx);
2416 
2417  uradius = nsubx / 2;
2418  if (uradius > sradius)
2419  uradius = sradius;
2420 
2421  for (j = 0; j < nsuby; j++) {
2422  fdata = cpl_vector_get_data(flux);
2423  p = data;
2424  for (k = 0; k < nsubx; k++)
2425  *fdata++ = *p++;
2426  smo_flux = cpl_vector_filter_median_create(flux, uradius);
2427  fdata = cpl_vector_get_data(smo_flux);
2428  p = data;
2429  for (k = 0; k < nsubx; k++)
2430  *p++ = *fdata++;
2431  cpl_vector_delete(smo_flux);
2432  data += nsubx;
2433  }
2434 
2435  cpl_vector_delete(flux);
2436 
2437 
2438  /*
2439  * First fit fluxes along the spatial direction with a low-degree
2440  * polynomial (excluding the first and the last pixels, close to
2441  * the edges)
2442  */
2443 /*
2444  if (nsubx-2*exclude > 10) {
2445  flux = cpl_vector_new(nsubx-2*exclude);
2446  fdata = cpl_vector_get_data(flux);
2447  positions = cpl_vector_new(nsubx-2*exclude);
2448  for (j = 0; j < nsubx-2*exclude; j++)
2449  cpl_vector_set(positions, j, j+exclude);
2450 
2451  for (k = 0; k < nsuby; k++) {
2452  for (j = 0; j < nsubx-2*exclude; j++)
2453  fdata[j] = data[j+exclude];
2454  trend = cpl_polynomial_fit_1d_create(positions, flux,
2455  1, NULL);
2456  for (j = 0; j < nsubx; j++)
2457  data[j] = cpl_polynomial_eval_1d(trend, j, NULL);
2458  cpl_polynomial_delete(trend);
2459  data += nsubx;
2460  }
2461 
2462  cpl_vector_delete(flux);
2463  cpl_vector_delete(positions);
2464  }
2465 */
2466 
2467  /*
2468  * Now smooth along the dispersion direction
2469  */
2470 
2471  cpl_image_turn(exslit, 1); /* For faster memory access */
2472  nsubx = cpl_image_get_size_x(exslit);
2473  nsuby = cpl_image_get_size_y(exslit);
2474  data = cpl_image_get_data(exslit);
2475 
2476  for (j = 0; j < nsuby; j++) {
2477  flux = cpl_vector_new(nsubx);
2478  fdata = cpl_vector_get_data(flux);
2479  p = data;
2480  for (k = 0; k < nsubx; k++)
2481  *fdata++ = *p++;
2482  smo_flux = cpl_vector_filter_median_create(flux, sradius);
2483  cpl_vector_delete(flux);
2484  fdata = cpl_vector_get_data(smo_flux);
2485  p = data;
2486  for (k = 0; k < nsubx; k++)
2487  *p++ = *fdata++;
2488  cpl_vector_delete(smo_flux);
2489  data += nsubx;
2490  }
2491  }
2492  else {
2493 
2494  /*
2495  * Fit with a polynomial the flat field trend row by row.
2496  */
2497 
2498  nsubx = cpl_image_get_size_x(exslit);
2499  nsuby = cpl_image_get_size_y(exslit);
2500  data = cpl_image_get_data(exslit);
2501 
2502  for (j = 0; j < nsuby; j++) {
2503 
2504  /*
2505  * First get the size of the vectors to allocate:
2506  */
2507 
2508  npoints = 0;
2509  p = data + j*nsubx;
2510  for (k = 0; k < nsubx; k++)
2511  if (p[k] > 1.0)
2512  npoints++;
2513 
2514  if (npoints > polyorder + 1) {
2515 
2516  /*
2517  * Fill the vectors for the fitting
2518  */
2519 
2520  flux = cpl_vector_new(npoints);
2521  fdata = cpl_vector_get_data(flux);
2522  positions = cpl_vector_new(npoints);
2523  pdata = cpl_vector_get_data(positions);
2524 
2525  npoints = 0;
2526  p = data + j*nsubx;
2527  for (k = 0; k < nsubx; k++) {
2528  if (p[k] > 1.0) {
2529  fdata[npoints] = p[k];
2530  pdata[npoints] = k;
2531  npoints++;
2532  }
2533  }
2534 
2535  trend = cpl_polynomial_fit_1d_create(positions, flux,
2536  polyorder, NULL);
2537 
2538  cpl_vector_delete(flux);
2539  cpl_vector_delete(positions);
2540 
2541  if (trend) {
2542  p = data + j*nsubx;
2543  for (k = 0; k < nsubx; k++)
2544  if (p[k] > 1.0)
2545  p[k] = cpl_polynomial_eval_1d(trend, k, NULL);
2546  cpl_polynomial_delete(trend);
2547  }
2548  else {
2549  cpl_msg_warning(func, "Invalid flat field flux fit "
2550  "(ignored)");
2551  }
2552  }
2553  }
2554  }
2555 
2556 
2557  /*
2558  * Recover from the table of spectral curvature coefficients
2559  * the curvature polynomials.
2560  */
2561 
2562  refpixel = cpl_table_get_double(slits, "xtop", i, NULL);
2563 
2564  start_pixel = refpixel - pixel_below;
2565  if (start_pixel < 0)
2566  start_pixel = 0;
2567 
2568  end_pixel = refpixel + pixel_above;
2569  if (end_pixel > nx)
2570  end_pixel = nx;
2571 
2572  missing_top = 0;
2573  polytop = cpl_polynomial_new(1);
2574  for (k = 0; k <= order; k++) {
2575  coeff = cpl_table_get_double(polytraces, clab[k], 2*i, &null);
2576  if (null) {
2577  cpl_polynomial_delete(polytop);
2578  missing_top = 1;
2579  break;
2580  }
2581  cpl_polynomial_set_coeff(polytop, &k, coeff);
2582  }
2583 
2584  missing_bot = 0;
2585  polybot = cpl_polynomial_new(1);
2586  for (k = 0; k <= order; k++) {
2587  coeff = cpl_table_get_double(polytraces, clab[k], 2*i+1, &null);
2588  if (null) {
2589  cpl_polynomial_delete(polybot);
2590  missing_bot = 1;
2591  break;
2592  }
2593  cpl_polynomial_set_coeff(polybot, &k, coeff);
2594  }
2595 
2596  if (missing_top && missing_bot) {
2597  cpl_msg_debug(func, "Slit %d was not traced: no extraction!",
2598  slit_id[i]);
2599  continue;
2600  }
2601 
2602  /*
2603  * In case just one of the two edges was not traced, the other
2604  * edge curvature model is duplicated and shifted to the other
2605  * end of the slit: better than nothing!
2606  */
2607 
2608  if (missing_top) {
2609  cpl_msg_debug(func, "Upper edge of slit %d was not traced: "
2610  "the spectral curvature of the lower edge "
2611  "is used instead.", slit_id[i]);
2612  polytop = cpl_polynomial_duplicate(polybot);
2613  ytop = cpl_table_get_double(slits, "ytop", i, NULL);
2614  ybot = cpl_table_get_double(slits, "ybottom", i, NULL);
2615  k = 0;
2616  coeff = cpl_polynomial_get_coeff(polybot, &k);
2617  coeff += ytop - ybot;
2618  cpl_polynomial_set_coeff(polytop, &k, coeff);
2619  }
2620 
2621  if (missing_bot) {
2622  cpl_msg_debug(func, "Lower edge of slit %d was not traced: "
2623  "the spectral curvature of the upper edge "
2624  "is used instead.", slit_id[i]);
2625  polybot = cpl_polynomial_duplicate(polytop);
2626  ytop = cpl_table_get_double(slits, "ytop", i, NULL);
2627  ybot = cpl_table_get_double(slits, "ybottom", i, NULL);
2628  k = 0;
2629  coeff = cpl_polynomial_get_coeff(polytop, &k);
2630  coeff -= ytop - ybot;
2631  cpl_polynomial_set_coeff(polybot, &k, coeff);
2632  }
2633 
2634 
2635  /*
2636  * Now map smoothed image to CCD.
2637  * Note that the npseudo value related to this slit is equal
2638  * to the number of spatial pseudo-pixels decreased by 1
2639  * (compare with function mos_spatial_calibration()).
2640  */
2641 
2642  nx = cpl_image_get_size_x(flat);
2643  ny = cpl_image_get_size_y(flat);
2644 
2645  sdata = cpl_image_get_data(spatial);
2646  xdata = cpl_image_get_data(exslit);
2647  npseudo = cpl_image_get_size_y(exslit) - 1;
2648 
2649  /*
2650  * Write interpolated smoothed values to CCD image
2651  */
2652 
2653  for (j = start_pixel; j < end_pixel; j++) {
2654  top = cpl_polynomial_eval_1d(polytop, j, NULL);
2655  bot = cpl_polynomial_eval_1d(polybot, j, NULL);
2656  for (k = 0; k <= npseudo; k++) {
2657  ypos = top - k*(top-bot)/npseudo;
2658  yint = ypos;
2659 
2660  /*
2661  * The line:
2662  * value = sdata[j + nx*yint];
2663  * should be equivalent to:
2664  * value = npseudo*(top-yint)/(top-bot);
2665  */
2666 
2667  if (yint < 0 || yint >= ny-1) {
2668  yprev = yint;
2669  continue;
2670  }
2671 
2672  value = sdata[j + nx*yint]; /* Spatial coordinate on CCD */
2673  ivalue = value; /* Nearest spatial pixels: */
2674  fvalue = value - ivalue; /* ivalue and ivalue+1 */
2675  if (ivalue < npseudo && ivalue >= 0) {
2676  vtop = xdata[j + nx*(npseudo-ivalue)];
2677  vbot = xdata[j + nx*(npseudo-ivalue-1)];
2678  wdata[j + nx*yint] = vtop*(1-fvalue) + vbot*fvalue;
2679 
2680  if (k) {
2681 
2682  /*
2683  * This is added to recover lost pixels on
2684  * the CCD image (pixels are lost because
2685  * the CCD pixels are less than npseudo+1).
2686  */
2687 
2688  if (yprev - yint > 1) {
2689  value = sdata[j + nx*(yint+1)];
2690  ivalue = value;
2691  fvalue = value - ivalue;
2692  if (ivalue < npseudo && ivalue >= 0) {
2693  vtop = xdata[j + nx*(npseudo-ivalue)];
2694  vbot = xdata[j + nx*(npseudo-ivalue-1)];
2695  wdata[j + nx*(yint+1)] = vtop*(1-fvalue)
2696  + vbot*fvalue;
2697  }
2698  }
2699  }
2700  }
2701  yprev = yint;
2702  }
2703  }
2704  cpl_polynomial_delete(polytop);
2705  cpl_polynomial_delete(polybot);
2706  cpl_image_delete(exslit);
2707  }
2708 
2709  cpl_image_delete(rectified);
2710 
2711  cpl_image_divide(flat, smo_flat);
2712 
2713  return smo_flat;
2714 }
2715 
2716 
2741 cpl_image *mos_normalise_longflat(cpl_image *flat, int sradius, int dradius,
2742  int polyorder)
2743 {
2744  const char *func = "mos_normalise_longflat";
2745 
2746  cpl_image *smo_flat;
2747  cpl_image *profile;
2748  cpl_vector *flux;
2749  cpl_vector *smo_flux;
2750  cpl_vector *positions;
2751  cpl_polynomial *trend;
2752 
2753  float *level;
2754  float *p;
2755  float *data;
2756  double *fdata;
2757  double *pdata;
2758 
2759  int nx, ny;
2760  int npoints;
2761  int i, j;
2762 
2763 
2764  if (flat == NULL) {
2765  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
2766  return NULL;
2767  }
2768 
2769  if (sradius < 1 || dradius < 1) {
2770  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
2771  return NULL;
2772  }
2773 
2774  smo_flat = cpl_image_duplicate(flat);
2775 
2776  if (polyorder < 0) {
2777 
2778  /*
2779  * First smooth along the spatial direction
2780  */
2781 
2782  cpl_image_turn(smo_flat, -1); /* For faster memory access */
2783 
2784  nx = cpl_image_get_size_x(smo_flat);
2785  ny = cpl_image_get_size_y(smo_flat);
2786  data = cpl_image_get_data(smo_flat);
2787 
2788  for (i = 0; i < ny; i++) {
2789  flux = cpl_vector_new(nx);
2790  fdata = cpl_vector_get_data(flux);
2791  p = data;
2792  for (j = 0; j < nx; j++)
2793  *fdata++ = *p++;
2794  smo_flux = cpl_vector_filter_median_create(flux, sradius);
2795  cpl_vector_delete(flux);
2796  fdata = cpl_vector_get_data(smo_flux);
2797  p = data;
2798  for (j = 0; j < nx; j++)
2799  *p++ = *fdata++;
2800  cpl_vector_delete(smo_flux);
2801  data += nx;
2802  }
2803 
2804  /*
2805  * Second smooth along the dispersion direction
2806  */
2807 
2808  cpl_image_turn(smo_flat, 1); /* For faster memory access */
2809 
2810  nx = cpl_image_get_size_x(smo_flat);
2811  ny = cpl_image_get_size_y(smo_flat);
2812  data = cpl_image_get_data(smo_flat);
2813 
2814  for (i = 0; i < ny; i++) {
2815  flux = cpl_vector_new(nx);
2816  fdata = cpl_vector_get_data(flux);
2817  p = data;
2818  for (j = 0; j < nx; j++)
2819  *fdata++ = *p++;
2820  smo_flux = cpl_vector_filter_median_create(flux, sradius);
2821  cpl_vector_delete(flux);
2822  fdata = cpl_vector_get_data(smo_flux);
2823  p = data;
2824  for (j = 0; j < nx; j++)
2825  *p++ = *fdata++;
2826  cpl_vector_delete(smo_flux);
2827  data += nx;
2828  }
2829  }
2830  else {
2831 
2832  /*
2833  * Fit with a polynomial the flat field trend column by column.
2834  */
2835 
2836  cpl_image_turn(smo_flat, -1); /* For faster memory access */
2837 
2838  nx = cpl_image_get_size_x(smo_flat);
2839  ny = cpl_image_get_size_y(smo_flat);
2840  data = cpl_image_get_data(smo_flat);
2841 
2842  profile = cpl_image_collapse_median_create(smo_flat, 1, 0, 0);
2843  level = cpl_image_get_data(profile);
2844 
2845  for (i = 0; i < ny; i++) {
2846 
2847  /*
2848  * First get the size of the vectors to allocate:
2849  * eliminate from fit any value more than 20% away
2850  * from median level in current column.
2851  */
2852 
2853  npoints = 0;
2854  p = data + i*nx;
2855  for (j = 0; j < nx; j++)
2856  if (fabs(p[j]/level[i] - 1) < 0.20)
2857  npoints++;
2858 
2859  if (npoints > polyorder + 1) {
2860 
2861  /*
2862  * Fill the vectors for the fitting
2863  */
2864 
2865  flux = cpl_vector_new(npoints);
2866  fdata = cpl_vector_get_data(flux);
2867  positions = cpl_vector_new(npoints);
2868  pdata = cpl_vector_get_data(positions);
2869 
2870  npoints = 0;
2871  p = data + i*nx;
2872  for (j = 0; j < nx; j++) {
2873  if (fabs(p[j]/level[i] - 1) < 0.20) {
2874  fdata[npoints] = p[j];
2875  pdata[npoints] = j;
2876  npoints++;
2877  }
2878  }
2879 
2880  trend = cpl_polynomial_fit_1d_create(positions, flux,
2881  polyorder, NULL);
2882 
2883  cpl_vector_delete(flux);
2884  cpl_vector_delete(positions);
2885 
2886  if (trend) {
2887  p = data + i*nx;
2888  for (j = 0; j < nx; j++)
2889  p[j] = cpl_polynomial_eval_1d(trend, j, NULL);
2890  cpl_polynomial_delete(trend);
2891  }
2892  else {
2893  cpl_msg_warning(func,
2894  "Invalid flat field flux fit (ignored)");
2895  }
2896  }
2897  }
2898 
2899  cpl_image_delete(profile);
2900  cpl_image_turn(smo_flat, 1);
2901 
2902  }
2903 
2904  cpl_image_divide(flat, smo_flat);
2905 
2906  return smo_flat;
2907 }
2908 
2909 
2932 cpl_error_code mos_interpolate_wavecalib_slit(cpl_table *idscoeff,
2933  cpl_table *slits,
2934  int order, int global)
2935 {
2936  const char *clab[6] = {"c0", "c1", "c2", "c3", "c4", "c5"};
2937  /* Max order is 5 */
2938  int nrow = cpl_table_get_nrow(slits);
2939  int i, j;
2940 
2941 
2942  if (order < 0)
2943  return CPL_ERROR_NONE;
2944 
2945  cpl_table_new_column(idscoeff, "x", CPL_TYPE_DOUBLE);
2946  cpl_table_new_column(idscoeff, "y", CPL_TYPE_DOUBLE);
2947 
2948  for (i = 0; i < nrow; i++) {
2949  int position = cpl_table_get_int (slits, "position", i, NULL);
2950  int length = cpl_table_get_int (slits, "length", i, NULL);
2951  double xtop = cpl_table_get_double(slits, "xtop", i, NULL);
2952  double xbot = cpl_table_get_double(slits, "xbottom", i, NULL);
2953  double ytop = cpl_table_get_double(slits, "ytop", i, NULL);
2954  double ybot = cpl_table_get_double(slits, "ybottom", i, NULL);
2955  double dx = xtop - xbot;
2956  double dy = ytop - ybot;
2957  cpl_table *table = cpl_table_extract(idscoeff, position, length);
2958 
2959  if (mos_interpolate_wavecalib(table, NULL, 2, order))
2960  continue;
2961 
2962  cpl_table_erase_window(idscoeff, position, length);
2963  cpl_table_insert(idscoeff, table, position);
2964 
2965  cpl_table_delete(table);
2966 
2967  for (j = 0; j < length; j++) {
2968  cpl_table_set_double(idscoeff, "x", j + position,
2969  xbot + j*(dx/length));
2970  cpl_table_set_double(idscoeff, "y", j + position,
2971  ybot + j*(dy/length));
2972  }
2973  }
2974 
2975  if (global) {
2976 
2977  /*
2978  * Now fit a global solution
2979  */
2980 
2981  nrow = cpl_table_get_nrow(idscoeff);
2982 
2983  for (i = 0; i < 6; i++) {
2984  cpl_table *dummy;
2985  cpl_vector *x;
2986  cpl_vector *y;
2987  cpl_bivector *z;
2988  cpl_vector *c;
2989  cpl_polynomial *p;
2990  cpl_vector *point;
2991  double *dpoint;
2992  int npoints;
2993 
2994  if (!cpl_table_has_column(idscoeff, clab[i]))
2995  break;
2996 
2997  npoints = nrow - cpl_table_count_invalid(idscoeff, clab[i]);
2998  if (npoints < 18)
2999  break;
3000 
3001  dummy = cpl_table_new(nrow);
3002  cpl_table_duplicate_column(dummy, "x", idscoeff, "x");
3003  cpl_table_duplicate_column(dummy, "y", idscoeff, "y");
3004  cpl_table_duplicate_column(dummy, clab[i], idscoeff, clab[i]);
3005  cpl_table_erase_invalid(dummy);
3006 
3007  x = cpl_vector_wrap(npoints, cpl_table_get_data_double(dummy, "x"));
3008  y = cpl_vector_wrap(npoints, cpl_table_get_data_double(dummy, "y"));
3009  z = cpl_bivector_wrap_vectors(x, y);
3010  c = cpl_vector_wrap(npoints, cpl_table_get_data_double(dummy,
3011  clab[i]));
3012  p = cpl_polynomial_fit_2d_create(z, c, 2, NULL);
3013  cpl_bivector_unwrap_vectors(z);
3014  cpl_vector_unwrap(x);
3015  cpl_vector_unwrap(y);
3016  cpl_vector_unwrap(c);
3017  cpl_table_delete(dummy);
3018 
3019  point = cpl_vector_new(2);
3020  dpoint = cpl_vector_get_data(point);
3021  for (j = 0; j < nrow; j++) {
3022  dpoint[0] = cpl_table_get_double(idscoeff, "x", j, NULL);
3023  dpoint[1] = cpl_table_get_double(idscoeff, "y", j, NULL);
3024  cpl_table_set_double(idscoeff, clab[i], j,
3025  cpl_polynomial_eval(p, point));
3026  }
3027  cpl_vector_delete(point);
3028  cpl_polynomial_delete(p);
3029  }
3030  }
3031 
3032  return CPL_ERROR_NONE;
3033 }
3034 
3035 
3061 cpl_error_code mos_interpolate_wavecalib(cpl_table *idscoeff,
3062  cpl_image *wavemap, int mode,
3063  int degree)
3064 {
3065  const char *func = "mos_interpolate_wavecalib";
3066 
3067  const char *clab[6] = {"c0", "c1", "c2", "c3", "c4", "c5"};
3068  /* Max order is 5 */
3069 
3070  cpl_vector *wave;
3071  cpl_vector *positions;
3072  cpl_polynomial *trend;
3073 
3074  float *p;
3075  float *data;
3076  double *wdata;
3077  double *pdata;
3078 
3079  double c;
3080  double mse, ksigma;
3081 
3082  int order;
3083  int nrows, first_row, last_row;
3084  int nx, ny;
3085  int npoints, rpoints;
3086  int null;
3087  int i, j, k;
3088 
3089  int polyorder = 4; /* Candidate input argument */
3090 
3091 
3092  if (idscoeff == NULL)
3093  return cpl_error_set(func, CPL_ERROR_NULL_INPUT);
3094 
3095  if (mode < 0 || mode > 2)
3096  return cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
3097 
3098  if (mode == 0 || degree < 0)
3099  return CPL_ERROR_NONE;
3100 
3101  if (wavemap) {
3102 
3103  /*
3104  * Fit with a polynomial the wavelength trend column by column.
3105  */
3106 
3107  cpl_image_turn(wavemap, -1); /* For faster memory access */
3108 
3109  nx = cpl_image_get_size_x(wavemap);
3110  ny = cpl_image_get_size_y(wavemap);
3111  data = cpl_image_get_data(wavemap);
3112 
3113  for (i = 0; i < ny; i++) {
3114 
3115  /*
3116  * First get the size of the vectors to allocate:
3117  * eliminate from fit any value with "impossible" wavelength.
3118  */
3119 
3120  npoints = 0;
3121  p = data + i*nx;
3122  for (j = 0; j < nx; j++)
3123  if (p[j] > 1.0)
3124  npoints++;
3125 
3126  if (npoints > polyorder + 1) {
3127 
3128  /*
3129  * Fill the vectors for the fitting
3130  */
3131 
3132  wave = cpl_vector_new(npoints);
3133  wdata = cpl_vector_get_data(wave);
3134  positions = cpl_vector_new(npoints);
3135  pdata = cpl_vector_get_data(positions);
3136 
3137  npoints = 0;
3138  p = data + i*nx;
3139  for (j = 0; j < nx; j++) {
3140  if (p[j] > 1.0) {
3141  wdata[npoints] = p[j];
3142  pdata[npoints] = j;
3143  npoints++;
3144  }
3145  }
3146 
3147  trend = cpl_polynomial_fit_1d_create(positions, wave,
3148  polyorder, &mse);
3149 
3150  ksigma = 3*sqrt(mse);
3151 
3152  cpl_vector_delete(wave);
3153  cpl_vector_delete(positions);
3154 
3155  if (trend) {
3156 
3157  /*
3158  * Apply 3-sigma rejection
3159  */
3160 
3161  rpoints = 0;
3162  p = data + i*nx;
3163  for (j = 0; j < nx; j++)
3164  if (p[j] > 1.0)
3165  if (fabs(cpl_polynomial_eval_1d(trend, j, NULL)
3166  - p[j]) < ksigma)
3167  rpoints++;
3168 
3169  if (rpoints < npoints && rpoints > polyorder + 1) {
3170 
3171  wave = cpl_vector_new(rpoints);
3172  wdata = cpl_vector_get_data(wave);
3173  positions = cpl_vector_new(rpoints);
3174  pdata = cpl_vector_get_data(positions);
3175 
3176  npoints = 0;
3177  p = data + i*nx;
3178  for (j = 0; j < nx; j++) {
3179  if (p[j] > 1.0) {
3180  if (fabs(cpl_polynomial_eval_1d(trend,
3181  j, NULL) - p[j])
3182  < ksigma) {
3183  wdata[npoints] = p[j];
3184  pdata[npoints] = j;
3185  npoints++;
3186  }
3187  }
3188  }
3189 
3190  cpl_polynomial_delete(trend);
3191  trend = cpl_polynomial_fit_1d_create(positions, wave,
3192  polyorder, NULL);
3193 
3194  cpl_vector_delete(wave);
3195  cpl_vector_delete(positions);
3196  }
3197  }
3198 
3199  if (trend) {
3200  p = data + i*nx;
3201  if (mode == 1) {
3202  for (j = 0; j < nx; j++)
3203  if (p[j] < 1.0)
3204  p[j] = cpl_polynomial_eval_1d(trend, j, NULL);
3205  }
3206  else if (mode == 2) {
3207  for (j = 0; j < nx; j++)
3208  p[j] = cpl_polynomial_eval_1d(trend, j, NULL);
3209  }
3210  cpl_polynomial_delete(trend);
3211  }
3212  else {
3213  cpl_msg_warning(func,
3214  "Invalid wavelength field fit (ignored)");
3215  }
3216  }
3217 
3218  }
3219 
3220  cpl_image_turn(wavemap, 1);
3221 
3222  }
3223 
3224 
3225  /*
3226  * Interpolating the IDS coefficients
3227  */
3228 
3229  nrows = cpl_table_get_nrow(idscoeff);
3230 
3231  order = 0;
3232  while (order < 6 && cpl_table_has_column(idscoeff, clab[order]))
3233  ++order;
3234  --order;
3235 
3236  if (degree == 0) {
3237  for (k = 0; k <= order; k++) {
3238  double m;
3239  if (cpl_table_has_column(idscoeff, clab[k])) {
3240  m = cpl_table_get_column_median(idscoeff, clab[k]);
3241  cpl_table_fill_column_window_double(idscoeff, clab[k],
3242  0, nrows, m);
3243  }
3244  }
3245 
3246  return CPL_ERROR_NONE;
3247  }
3248 
3249  first_row = 0;
3250  while (!cpl_table_is_valid(idscoeff, clab[0], first_row))
3251  first_row++;
3252 
3253  last_row = nrows - 1;
3254  while (!cpl_table_is_valid(idscoeff, clab[0], last_row))
3255  last_row--;
3256 
3257  for (k = 0; k <= order; k++) {
3258 
3259  npoints = nrows - cpl_table_count_invalid(idscoeff, clab[k]);
3260  wave = cpl_vector_new(npoints);
3261  wdata = cpl_vector_get_data(wave);
3262  positions = cpl_vector_new(npoints);
3263  pdata = cpl_vector_get_data(positions);
3264 
3265  npoints = 0;
3266  for (i = first_row; i <= last_row; i++) {
3267  c = cpl_table_get_double(idscoeff, clab[k], i, &null);
3268  if (null == 0) {
3269  wdata[npoints] = c;
3270  pdata[npoints] = i;
3271  npoints++;
3272  }
3273  }
3274 
3275 // This doesn't seem to provide good results, I have not understood why.
3276 // Restore for robust linear fitting.
3277 //
3278  if (degree == 1) {
3279  cpl_vector *p;
3280  cpl_vector *w;
3281  cpl_bivector *list;
3282  double q, m;
3283 
3284  if (npoints > 4) {
3285  p = cpl_vector_extract(positions, 2, npoints - 2, 1);
3286  w = cpl_vector_extract(wave, 2, npoints - 2, 1);
3287  }
3288  else {
3289  p = positions;
3290  w = wave;
3291  }
3292 
3293  list = cpl_bivector_wrap_vectors(p, w);
3294 
3295  robustLinearFit(list, &q, &m, &mse);
3296  cpl_bivector_unwrap_vectors(list);
3297  for (i = first_row; i <= last_row; i++)
3298  cpl_table_set_double(idscoeff, clab[k], i, q + m*i);
3299 
3300  if (npoints > 4) {
3301  cpl_vector_delete(p);
3302  cpl_vector_delete(w);
3303  }
3304 
3305  continue;
3306  }
3307 
3308 // End robust linear fitting
3309 
3310  trend = cpl_polynomial_fit_1d_create(positions, wave, degree, &mse);
3311 
3312  ksigma = 3*sqrt(mse);
3313 
3314  cpl_vector_delete(wave);
3315  cpl_vector_delete(positions);
3316 
3317  /*
3318  * Iteration
3319  */
3320 
3321  if (trend) {
3322  rpoints = 0;
3323  for (i = first_row; i <= last_row; i++) {
3324  c = cpl_table_get_double(idscoeff, clab[k], i, &null);
3325  if (null == 0) {
3326  if (fabs(cpl_polynomial_eval_1d(trend, i, NULL) - c)
3327  < ksigma) {
3328  rpoints++;
3329  }
3330  }
3331  }
3332 
3333  if (rpoints > 0 && rpoints < npoints) {
3334  cpl_msg_debug(func, "%d points rejected from "
3335  "wavelength calibration fit",
3336  npoints - rpoints);
3337 
3338  wave = cpl_vector_new(rpoints);
3339  wdata = cpl_vector_get_data(wave);
3340  positions = cpl_vector_new(rpoints);
3341  pdata = cpl_vector_get_data(positions);
3342 
3343  npoints = 0;
3344  for (i = first_row; i <= last_row; i++) {
3345  c = cpl_table_get_double(idscoeff, clab[k], i, &null);
3346  if (null == 0) {
3347  if (fabs(cpl_polynomial_eval_1d(trend, i, NULL) - c)
3348  < ksigma) {
3349  wdata[npoints] = c;
3350  pdata[npoints] = i;
3351  npoints++;
3352  }
3353  }
3354  }
3355 
3356  if (npoints) {
3357  cpl_polynomial_delete(trend);
3358  trend = cpl_polynomial_fit_1d_create(positions,
3359  wave, degree, NULL);
3360  }
3361 
3362  cpl_vector_delete(wave);
3363  cpl_vector_delete(positions);
3364 
3365  }
3366  }
3367 
3368  if (trend) {
3369  for (i = first_row; i <= last_row; i++) {
3370  if (mode == 1) {
3371  if (!cpl_table_is_valid(idscoeff, clab[k], i)) {
3372  cpl_table_set_double(idscoeff, clab[k], i,
3373  cpl_polynomial_eval_1d(trend, i,
3374  NULL));
3375  }
3376  }
3377  else if (mode == 2) {
3378  cpl_table_set_double(idscoeff, clab[k], i,
3379  cpl_polynomial_eval_1d(trend, i, NULL));
3380  }
3381  }
3382  cpl_polynomial_delete(trend);
3383  }
3384  else {
3385  cpl_msg_warning(func, "Invalid IDS coefficient fit (ignored)");
3386  }
3387 
3388  }
3389 
3390  return CPL_ERROR_NONE;
3391 }
3392 
3393 
3394 
3419 //TODO:Deprecate this function. Change all the recipes which still use it.
3420 //Use the new functionality to remove overscan, trimm and bias.
3421 
3422 cpl_image *mos_remove_bias(cpl_image *image, cpl_image *bias,
3423  cpl_table *overscans)
3424 {
3425  const char *func = "mos_remove_bias";
3426 
3427  cpl_image *unbiased;
3428  cpl_image *overscan;
3429  double mean_bias_level;
3430  double mean_overscans_level;
3431  int count;
3432  int nrows;
3433  int xlow, ylow, xhig, yhig;
3434  int i;
3435 
3436 
3437  if (image == NULL || overscans == NULL) {
3438  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
3439  return NULL;
3440  }
3441 
3442  nrows = cpl_table_get_nrow(overscans);
3443 
3444  if (nrows == 0) {
3445  cpl_msg_error(func, "Empty overscan table");
3446  cpl_error_set(func, CPL_ERROR_DATA_NOT_FOUND);
3447  return NULL;
3448  }
3449 
3450  if (bias) {
3451  if (nrows == 1) {
3452  unbiased = cpl_image_subtract_create(image, bias);
3453  if (unbiased == NULL) {
3454  cpl_msg_error(func, "Incompatible master bias");
3455  cpl_error_set(func, CPL_ERROR_INCOMPATIBLE_INPUT);
3456  }
3457  return unbiased;
3458  }
3459  mean_bias_level = cpl_image_get_mean(bias);
3460  }
3461  else {
3462  if (nrows == 1) {
3463  cpl_msg_error(func, "No master bias in input, and no overscan "
3464  "regions in input image: bias subtraction "
3465  "cannot be performed!");
3466  cpl_error_set(func, CPL_ERROR_DATA_NOT_FOUND);
3467  return NULL;
3468  }
3469  mean_bias_level = 0.0;
3470  }
3471 
3472  mean_overscans_level = 0.0;
3473  count = 0;
3474  for (i = 0; i < nrows; i++) {
3475  xlow = cpl_table_get_int(overscans, "xlow", i, NULL);
3476  ylow = cpl_table_get_int(overscans, "ylow", i, NULL);
3477  xhig = cpl_table_get_int(overscans, "xhig", i, NULL);
3478  yhig = cpl_table_get_int(overscans, "yhig", i, NULL);
3479 
3480  if (i == 0) {
3481  unbiased = cpl_image_extract(image, xlow+1, ylow+1, xhig, yhig);
3482  if (unbiased == NULL) {
3483  cpl_msg_error(func, "Incompatible overscan table");
3484  cpl_error_set(func, CPL_ERROR_INCOMPATIBLE_INPUT);
3485  return NULL;
3486  }
3487  if (bias) {
3488  if (cpl_image_subtract(unbiased, bias)) {
3489  cpl_msg_error(func, "Incompatible master bias");
3490  cpl_error_set(func, CPL_ERROR_INCOMPATIBLE_INPUT);
3491  cpl_image_delete(unbiased);
3492  return NULL;
3493  }
3494  }
3495  }
3496  else {
3497  overscan = cpl_image_extract(image, xlow+1, ylow+1, xhig, yhig);
3498  if (overscan == NULL) {
3499  cpl_msg_error(func, "Incompatible overscan table");
3500  cpl_error_set(func, CPL_ERROR_INCOMPATIBLE_INPUT);
3501  cpl_image_delete(unbiased);
3502  return NULL;
3503  }
3504 
3505  mean_overscans_level += cpl_image_get_median(overscan);
3506  count++;
3507 
3508 /***
3509  * Here the mean level was used: not very robust...
3510 
3511  mean_overscans_level += cpl_image_get_flux(overscan);
3512  count += cpl_image_get_size_x(overscan)
3513  * cpl_image_get_size_y(overscan);
3514 ***/
3515  cpl_image_delete(overscan);
3516  }
3517  }
3518 
3519  /*
3520  * Overscan correction
3521  */
3522 
3523  mean_overscans_level /= count;
3524 
3525  cpl_image_subtract_scalar(unbiased, mean_overscans_level - mean_bias_level);
3526 
3527  cpl_msg_info(cpl_func,
3528  "Difference between mean overscans level "
3529  "and mean bias level: %.2f",
3530  mean_overscans_level - mean_bias_level);
3531 
3532  return unbiased;
3533 
3534 }
3535 
3536 
3595 cpl_error_code mos_arc_background_1D(float *spectrum, float *back,
3596  int length, int msize, int fsize)
3597 {
3598  const char *func = "mos_arc_background_1D";
3599 
3600  float *minf;
3601  float *maxf;
3602  float *smof;
3603  int i;
3604 
3605 
3606  if (spectrum == NULL || back == NULL)
3607  return cpl_error_set(func, CPL_ERROR_NULL_INPUT);
3608 
3609  if (msize % 2 == 0)
3610  msize++;
3611 
3612  if (fsize % 2 == 0)
3613  fsize++;
3614 
3615  if (msize < 3 || fsize < msize || length < 2*fsize)
3616  return cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
3617 
3618 
3619  minf = min_filter(spectrum, length, msize);
3620  smof = smo_filter(minf, length, fsize);
3621  cpl_free(minf);
3622  maxf = max_filter(smof, length, 2*msize+1);
3623  cpl_free(smof);
3624  smof = smo_filter(maxf, length, 2*fsize+1);
3625  cpl_free(maxf);
3626  minf = min_filter(smof, length, 2*msize+1);
3627  cpl_free(smof);
3628  smof = smo_filter(minf, length, 2*fsize+1);
3629  cpl_free(minf);
3630 
3631  for (i = 0; i < length; i++)
3632  back[i] = smof[i];
3633 
3634  cpl_free(smof);
3635 
3636  return CPL_ERROR_NONE;
3637 
3638 }
3639 
3640 
3697 cpl_image *mos_arc_background(cpl_image *image, int msize, int fsize)
3698 {
3699  const char *func = "mos_arc_background";
3700 
3701  cpl_image *fimage;
3702  cpl_image *bimage;
3703  cpl_matrix *kernel;
3704  float *data;
3705  float *bdata;
3706  float *row;
3707  float *brow;
3708  int nx, ny;
3709  int i;
3710 
3711 
3712  if (image == NULL) {
3713  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
3714  return NULL;
3715  }
3716 
3717  if (msize % 2 == 0)
3718  msize++;
3719 
3720  if (fsize % 2 == 0)
3721  fsize++;
3722 
3723  nx = cpl_image_get_size_x(image);
3724  ny = cpl_image_get_size_y(image);
3725 
3726  bimage = cpl_image_new(nx, ny, CPL_TYPE_FLOAT);
3727 
3728  fimage = mos_image_filter_median(image, 3, 3);
3729 
3730  data = cpl_image_get_data_float(fimage);
3731  bdata = cpl_image_get_data_float(bimage);
3732 
3733  for (i = 0; i < ny; i++) {
3734  row = data + i * nx;
3735  brow = bdata + i * nx;
3736  if (mos_arc_background_1D(row, brow, nx, msize, fsize)) {
3737  cpl_error_set_where(func);
3738  cpl_image_delete(fimage);
3739  cpl_image_delete(bimage);
3740  return NULL;
3741  }
3742  }
3743 
3744  cpl_image_delete(fimage);
3745 
3746  return bimage;
3747 }
3748 
3749 
3770 int mos_lines_width(const float *spectrum, int length)
3771 {
3772 
3773  const char *func = "mos_lines_width";
3774 
3775  double *profile1 = cpl_calloc(length - 1, sizeof(double));
3776  double *profile2 = cpl_calloc(length - 1, sizeof(double));
3777 
3778  double norm, value, max;
3779  int radius = 20;
3780  int short_length = length - 2*radius - 1;
3781  int width;
3782  int i, j, k;
3783 
3784 
3785  /*
3786  * Derivative, and separation of positive and negative derivatives
3787  */
3788 
3789  for (j = 0, i = 1; i < length; j++, i++) {
3790  profile1[j] = profile2[j] = spectrum[i] - spectrum[j];
3791  if (profile1[j] < 0)
3792  profile1[j] = 0;
3793  if (profile2[j] > 0)
3794  profile2[j] = 0;
3795  else
3796  profile2[j] = -profile2[j];
3797  }
3798 
3799 
3800  /*
3801  * Profiles normalisation
3802  */
3803 
3804  length--;
3805 
3806  norm = 0;
3807  for (i = 0; i < length; i++)
3808  if (norm < profile1[i])
3809  norm = profile1[i];
3810 
3811  for (i = 0; i < length; i++) {
3812  profile1[i] /= norm;
3813  profile2[i] /= norm;
3814  }
3815 
3816 
3817  /*
3818  * Cross-correlation
3819  */
3820 
3821  max = -1;
3822  for (i = 0; i <= radius; i++) {
3823  value = 0;
3824  for (j = 0; j < short_length; j++) {
3825  k = radius+j;
3826  value += profile1[k] * profile2[k+i];
3827  }
3828  if (max < value) {
3829  max = value;
3830  width = i;
3831  }
3832  }
3833 
3834  cpl_free(profile1);
3835  cpl_free(profile2);
3836 
3837  if (max < 0.0) {
3838  cpl_msg_debug(func, "Cannot estimate line width");
3839  width = 1;
3840  }
3841 
3842  return width;
3843 
3844 }
3845 
3846 
3873 cpl_vector *mos_peak_candidates(const float *spectrum,
3874  int length, float level,
3875  float exp_width)
3876 {
3877 
3878  const char *func = "mos_peak_candidates";
3879 
3880  int i, j;
3881  int nint = length - 1;
3882  int n = 0;
3883  int width = 2 * ceil(exp_width / 2) + 1;
3884  int start = width / 2;
3885  int end = length - width / 2;
3886  int step;
3887  float *smo;
3888  double *data = cpl_calloc(length/2, sizeof(double));
3889 
3890 
3891  if (spectrum == NULL) {
3892  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
3893  return NULL;
3894  }
3895 
3896 
3897  /*
3898  * If lines have a flat top (as in the case of broad slit), smooth
3899  * before determining the max.
3900  */
3901 
3902  if (width > 7) {
3903  smo = cpl_calloc(length, sizeof(float));
3904  start = width / 2;
3905  end = length - width / 2;
3906  for (i = 0; i < start; i++)
3907  smo[i] = spectrum[i];
3908  for (i = start; i < end; i++) {
3909  for (j = i - start; j <= i + start; j++)
3910  smo[i] += spectrum[j];
3911  smo[i] /= width;
3912  }
3913  for (i = end; i < length; i++)
3914  smo[i] = spectrum[i];
3915  }
3916  else {
3917  smo = (float *)spectrum;
3918  }
3919 
3920  /*
3921  * Collect all relative maxima along spectrum, that are higher than the
3922  * specified level.
3923  */
3924 
3925  if (width > 20)
3926  step = width / 2;
3927  else
3928  step = 1;
3929 
3930  for (i = step; i < nint - step + 1; i += step) {
3931  if (smo[i] > level) {
3932  if (smo[i] >= smo[i-step] && smo[i] > smo[i+step]) {
3933  if (smo[i-step] != 0.0 && smo[i+step] != 0.0) {
3934  data[n] = i + step * values_to_dx(smo[i-step], smo[i], smo[i+step]);
3935  ++n;
3936  }
3937  }
3938  }
3939  }
3940 
3941  if (width > 7) {
3942  cpl_free(smo);
3943  }
3944 
3945  if (n == 0) {
3946  cpl_free(data);
3947  return NULL;
3948  }
3949 
3950  return cpl_vector_wrap(n, data);
3951 
3952 }
3953 
3954 
3976 cpl_vector *mos_refine_peaks(const float *spectrum, int length,
3977  cpl_vector *peaks, int sradius)
3978 {
3979 
3980  const char *func = "mos_refine_peaks";
3981 
3982  double *data;
3983  float pos;
3984  int npeaks;
3985  int startPos, endPos;
3986  int window = 2*sradius+1;
3987  int i, j;
3988 
3989 
3990  if (peaks == NULL || spectrum == NULL) {
3991  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
3992  return NULL;
3993  }
3994 
3995  npeaks = cpl_vector_get_size(peaks);
3996  data = cpl_vector_unwrap(peaks);
3997 
3998  for (i = 0; i < npeaks; i++) {
3999  startPos = data[i] - window/2;
4000  endPos = startPos + window;
4001  if (startPos < 0 || endPos >= length)
4002  continue;
4003 
4004  if (0 == peakPosition(spectrum + startPos, window, &pos, 1)) {
4005  pos += startPos;
4006  data[i] = pos;
4007  }
4008  }
4009 
4010  for (i = 1; i < npeaks; i++)
4011  if (data[i] - data[i-1] < 0.5)
4012  data[i-1] = -1.0;
4013 
4014  for (i = 0, j = 0; i < npeaks; i++) {
4015  if (data[i] > 0.0) {
4016  if (i != j)
4017  data[j] = data[i];
4018  j++;
4019  }
4020  }
4021 
4022  return cpl_vector_wrap(j, data);
4023 
4024 }
4025 
4026 
4027 void mos_set_multiplex(int multiplex)
4028 {
4029  mos_multiplex = multiplex;
4030 }
4031 
4085 cpl_bivector *mos_identify_peaks(cpl_vector *peaks, cpl_vector *lines,
4086  double min_disp, double max_disp,
4087  double tolerance)
4088 {
4089 
4090  int i, j, k, l;
4091  int nlint, npint;
4092  int minpos;
4093  float min;
4094  double lratio, pratio;
4095  double lo_start, lo_end, hi_start, hi_end, denom;
4096  double disp, variation, prev_variation;
4097  int max, maxpos, minl, mink;
4098  int ambiguous;
4099  int npeaks_lo, npeaks_hi;
4100  int *peak_lo;
4101  int *peak_hi;
4102  int **ident;
4103  int *nident;
4104  int *lident;
4105 
4106  double *peak;
4107  double *line;
4108  int npeaks, nlines;
4109 
4110  double *xpos;
4111  double *lambda;
4112  int *ilambda;
4113  double *tmp_xpos;
4114  double *tmp_lambda;
4115  int *tmp_ilambda;
4116  int *flag;
4117  int n = 0;
4118  int nn;
4119  int nseq = 0;
4120  int gap;
4121  int *seq_length;
4122  int found;
4123 
4124  peak = cpl_vector_get_data(peaks);
4125  npeaks = cpl_vector_get_size(peaks);
4126  line = cpl_vector_get_data(lines);
4127  nlines = cpl_vector_get_size(lines);
4128 
4129  if (npeaks < 4)
4130  return NULL;
4131 
4132  peak_lo = cpl_malloc(npeaks * sizeof(int));
4133  peak_hi = cpl_malloc(npeaks * sizeof(int));
4134  nident = cpl_calloc(npeaks, sizeof(int));
4135  lident = cpl_calloc(nlines, sizeof(int));
4136  xpos = cpl_calloc(npeaks, sizeof(double));
4137  lambda = cpl_calloc(npeaks, sizeof(double));
4138  ilambda = cpl_calloc(npeaks, sizeof(int));
4139  tmp_xpos = cpl_calloc(npeaks, sizeof(double));
4140  tmp_lambda = cpl_calloc(npeaks, sizeof(double));
4141  tmp_ilambda = cpl_calloc(npeaks, sizeof(int));
4142  flag = cpl_calloc(npeaks, sizeof(int));
4143  seq_length = cpl_calloc(npeaks, sizeof(int));
4144  ident = cpl_malloc(npeaks * sizeof(int *));
4145  for (i = 0; i < npeaks; i++)
4146  ident[i] = cpl_malloc(3 * npeaks * sizeof(int));
4147 
4148  /*
4149  * This is just the number of intervals - one less than the number
4150  * of points (catalog wavelengths, or detected peaks).
4151  */
4152 
4153  nlint = nlines - 1;
4154  npint = npeaks - 1;
4155 
4156 
4157  /*
4158  * Here the big loops on catalog lines begins.
4159  */
4160 
4161  for (i = 1; i < nlint; i++) {
4162 
4163 
4164  /*
4165  * For each catalog wavelength I take the previous and the next one,
4166  * and compute the ratio of the corresponding wavelength intervals.
4167  * This ratio will be compared to all the ratios obtained doing the
4168  * same with all the detected peaks positions.
4169  */
4170 
4171  lratio = (line[i+1] - line[i]) / (line[i] - line[i-1]);
4172 
4173 
4174  /*
4175  * Here the loop on detected peaks positions begins.
4176  */
4177 
4178  for (j = 1; j < npint; j++) {
4179 
4180  /*
4181  * Not all peaks are used for computing ratios: just the ones
4182  * that are compatible with the expected spectral dispersion
4183  * are taken into consideration. Therefore, I define the pixel
4184  * intervals before and after any peak that are compatible with
4185  * the specified dispersion interval, and select just the peaks
4186  * within such intervals. If either of the two intervals doesn't
4187  * contain any peak, then I skip the current peak and continue
4188  * with the next.
4189  */
4190 
4191  lo_start = peak[j] - (line[i] - line[i-1]) / min_disp;
4192  lo_end = peak[j] - (line[i] - line[i-1]) / max_disp;
4193  hi_start = peak[j] + (line[i+1] - line[i]) / max_disp;
4194  hi_end = peak[j] + (line[i+1] - line[i]) / min_disp;
4195 
4196  for (npeaks_lo = 0, k = 0; k < npeaks; k++) {
4197  if (peak[k] > lo_end)
4198  break;
4199  if (peak[k] > lo_start) {
4200  peak_lo[npeaks_lo] = k;
4201  ++npeaks_lo;
4202  }
4203  }
4204 
4205  if (npeaks_lo == 0)
4206  continue;
4207 
4208  for (npeaks_hi = 0, k = 0; k < npeaks; k++) {
4209  if (peak[k] > hi_end)
4210  break;
4211  if (peak[k] > hi_start) {
4212  peak_hi[npeaks_hi] = k;
4213  ++npeaks_hi;
4214  }
4215  }
4216 
4217  if (npeaks_hi == 0)
4218  continue;
4219 
4220 
4221  /*
4222  * Now I have all peaks that may help for a local identification.
4223  * peak_lo[k] is the sequence number of the k-th peak of the lower
4224  * interval; peak_hi[l] is the sequence number of the l-th peak of
4225  * the higher interval. j is, of course, the sequence number of the
4226  * current peak (second big loop).
4227  */
4228 
4229  prev_variation = 1000.0;
4230  minl = mink = 0;
4231 
4232  for (k = 0; k < npeaks_lo; k++) {
4233  denom = peak[j] - peak[peak_lo[k]];
4234  for (l = 0; l < npeaks_hi; l++) {
4235 
4236  /*
4237  * For any pair of peaks - one from the lower and the other
4238  * from the higher interval - I compute the same ratio that
4239  * was computed with the current line catalog wavelength.
4240  */
4241 
4242  pratio = (peak[peak_hi[l]] - peak[j]) / denom;
4243 
4244  /*
4245  * If the two ratios are compatible within the specified
4246  * tolerance, we have a preliminary identification. This
4247  * will be marked in the matrix ident[][], where the first
4248  * index corresponds to a peak sequence number, and the second
4249  * index is the counter of the identifications made during
4250  * this whole process. The array of counters is nident[].
4251  * If more than one interval pair fulfills the specified
4252  * tolerance, the closest to the expected ratio is selected.
4253  */
4254 
4255  variation = fabs(lratio-pratio) / pratio;
4256 
4257  if (variation < tolerance) {
4258  if (variation < prev_variation) {
4259  prev_variation = variation;
4260  minl = l;
4261  mink = k;
4262  }
4263  }
4264  }
4265  }
4266  if (prev_variation < tolerance) {
4267  ident[j][nident[j]] = i;
4268  ident[peak_hi[minl]][nident[peak_hi[minl]]] = i + 1;
4269  ident[peak_lo[mink]][nident[peak_lo[mink]]] = i - 1;
4270  ++nident[j];
4271  ++nident[peak_hi[minl]];
4272  ++nident[peak_lo[mink]];
4273  }
4274  } /* End loop on positions */
4275  } /* End loop on lines */
4276 
4277 
4278  /*
4279  * At this point I have filled the ident matrix with all my preliminary
4280  * identifications. Ambiguous identifications must be eliminated.
4281  */
4282 
4283 
4284  for (i = 0; i < npeaks; i++) {
4285 
4286 
4287  /*
4288  * I don't take into consideration peaks that were never identified.
4289  * They are likely contaminations, or emission lines that were not
4290  * listed in the input wavelength catalog.
4291  */
4292 
4293  if (nident[i] > 1) {
4294 
4295 
4296  /*
4297  * Initialise the histogram of wavelengths assigned to the i-th peak.
4298  */
4299 
4300  for (j = 0; j < nlines; j++)
4301  lident[j] = 0;
4302 
4303 
4304  /*
4305  * Count how many times each catalog wavelength was assigned
4306  * to the i-th peak.
4307  */
4308 
4309  for (j = 0; j < nident[i]; j++)
4310  ++lident[ident[i][j]];
4311 
4312 
4313  /*
4314  * What wavelength was most frequently assigned to the i-th peak?
4315  */
4316 
4317  max = 0;
4318  maxpos = 0;
4319  for (j = 0; j < nlines; j++) {
4320  if (max < lident[j]) {
4321  max = lident[j];
4322  maxpos = j;
4323  }
4324  }
4325 
4326 
4327  /*
4328  * Were there other wavelengths assigned with the same frequency?
4329  * This would be the case of an ambiguous identification. It is
4330  * safer to reject this peak...
4331  */
4332 
4333  ambiguous = 0;
4334 
4335  for (k = maxpos + 1; k < nlines; k++) {
4336  if (lident[k] == max) {
4337  ambiguous = 1;
4338  break;
4339  }
4340  }
4341 
4342  if (ambiguous)
4343  continue;
4344 
4345 
4346  /*
4347  * Otherwise, I assign to the i-th peak the wavelength that was
4348  * most often assigned to it.
4349  */
4350 
4351  tmp_xpos[n] = peak[i];
4352  tmp_lambda[n] = line[maxpos];
4353  tmp_ilambda[n] = maxpos;
4354 
4355  ++n;
4356 
4357  }
4358 
4359  }
4360 
4361 
4362  /*
4363  * Check on identified peaks. Contaminations from other spectra might
4364  * be present and should be excluded: this type of contamination
4365  * consists of peaks that have been _correctly_ identified! The non-
4366  * spectral type of light contamination should have been almost all
4367  * removed already in the previous steps, but it may still be present.
4368  * Here, the self-consistent sequences of identified peaks are
4369  * separated one from the other. At the moment, just the longest of
4370  * such sequences is selected (in other words, spectral multiplexing
4371  * is ignored).
4372  */
4373 
4374  if (n > 1) {
4375  nn = 0; /* Number of peaks in the list of sequences */
4376  nseq = 0; /* Current sequence */
4377  for (k = 0; k < n; k++) {
4378  if (flag[k] == 0) { /* Was peak k already assigned to a sequence? */
4379  flag[k] = 1;
4380  xpos[nn] = tmp_xpos[k]; /* Begin the nseq-th sequence */
4381  lambda[nn] = tmp_lambda[k];
4382  ilambda[nn] = tmp_ilambda[k];
4383  ++seq_length[nseq];
4384  ++nn;
4385 
4386  /*
4387  * Now look for all the following peaks that are compatible
4388  * with the expected spectral dispersion, and add them in
4389  * sequence to xpos. Note that missing peaks are not a problem...
4390  */
4391 
4392  i = k;
4393  while (i < n - 1) {
4394  found = 0;
4395  for (j = i + 1; j < n; j++) {
4396  if (flag[j] == 0) {
4397  disp = (tmp_lambda[j] - tmp_lambda[i])
4398  / (tmp_xpos[j] - tmp_xpos[i]);
4399  if (disp >= min_disp && disp <= max_disp) {
4400  flag[j] = 1;
4401  xpos[nn] = tmp_xpos[j];
4402  lambda[nn] = tmp_lambda[j];
4403  ilambda[nn] = tmp_ilambda[j];
4404  ++seq_length[nseq];
4405  ++nn;
4406  i = j;
4407  found = 1;
4408  break;
4409  }
4410  }
4411  }
4412  if (!found)
4413  break;
4414  }
4415 
4416  /*
4417  * Current sequence is completed: begin new sequence on the
4418  * excluded peaks, starting the loop on peaks again.
4419  */
4420 
4421  ++nseq;
4422  k = 0;
4423  }
4424  }
4425 
4426 
4427  /*
4428  * Find the longest sequence of self-consistent peaks.
4429  */
4430 
4431  maxpos = max = 0;
4432 
4433  if (mos_multiplex < 0) {
4434  for (i = 0; i < nseq; i++) {
4435  if (seq_length[i] > max) {
4436  max = seq_length[i];
4437  maxpos = i;
4438  }
4439  }
4440  }
4441  else {
4442 
4443  /*
4444  * Now consider the sequence which lays in the specified
4445  * CCD region (indicated by mos_multiplex): that is, _most_
4446  * of its lines (more than half) must be in that region...
4447  */
4448 
4449  nn = 0;
4450  found = 0;
4451 
4452  for (i = 0; i < nseq; i++) {
4453  n = seq_length[i];
4454  if (n > 5) {
4455  cpl_array *regions = cpl_array_new(n, CPL_TYPE_INT);
4456  int region;
4457 
4458  for (j = 0; j < n; j++)
4459  cpl_array_set_int(regions, j,
4460  ((int)floor(xpos[nn + j])) / mos_region_size);
4461 
4462  region = (int)cpl_array_get_median(regions);
4463  cpl_array_delete(regions);
4464 
4465  if (mos_multiplex == region) {
4466  if (found) {
4467  cpl_msg_debug(cpl_func, "More than one spectrum found in "
4468  "region %d (only the first one is extracted)",
4469  mos_multiplex);
4470  break;
4471  }
4472  found = 1;
4473  max = seq_length[i];
4474  maxpos = i;
4475  }
4476  }
4477  nn += seq_length[i];
4478  }
4479  }
4480 
4481  /*
4482  * Find where this sequence starts in the whole peak position
4483  * storage.
4484  */
4485 
4486  nn = 0;
4487  for (i = 0; i < maxpos; i++)
4488  nn += seq_length[i];
4489 
4490  /*
4491  * Move the longest sequence at the beginning of the returned lists
4492  */
4493 
4494  n = max;
4495  for (i = 0; i < n; i++, nn++) {
4496  xpos[i] = xpos[nn];
4497  lambda[i] = lambda[nn];
4498  ilambda[i] = ilambda[nn];
4499  }
4500 
4501 
4502  /*
4503  * Are some wavelengths missing? Recover them.
4504  */
4505 
4506  for (i = 1; i < n; i++) {
4507  gap = ilambda[i] - ilambda[i-1];
4508  for (j = 1; j < gap; j++) {
4509 
4510  if (j == 1) {
4511 
4512  /*
4513  * Determine the local dispersion from the current pair of peaks
4514  */
4515 
4516  disp = (lambda[i] - lambda[i-1]) / (xpos[i] - xpos[i-1]);
4517  }
4518 
4519  /*
4520  * With this, find the expected position of the missing
4521  * peak by linear interpolation.
4522  */
4523 
4524  hi_start = xpos[i-1] + (line[ilambda[i-1] + j] - lambda[i-1]) / disp;
4525 
4526  /*
4527  * Is there a peak at that position? Here a peak from the
4528  * original list is searched, that is closer than 2 pixels
4529  * to the expected position. If it is found, insert it at
4530  * the current position on the list of identified peaks,
4531  * and leave immediately the loop (taking the new position
4532  * for the following linear interpolation, in case more
4533  * than one peak is missing in the current interval).
4534  * If it is not found, stay in the loop, looking for
4535  * the following missing peaks in this interval.
4536  */
4537 
4538  found = 0;
4539  for (k = 0; k < npeaks; k++) {
4540  if (fabs(peak[k] - hi_start) < 2) {
4541  for (l = n; l > i; l--) {
4542  xpos[l] = xpos[l-1];
4543  lambda[l] = lambda[l-1];
4544  ilambda[l] = ilambda[l-1];
4545  }
4546  xpos[i] = peak[k];
4547  lambda[i] = line[ilambda[i-1] + j];
4548  ilambda[i] = ilambda[i-1] + j;
4549  ++n;
4550  found = 1;
4551  break;
4552  }
4553  }
4554  if (found)
4555  break;
4556  }
4557  }
4558 
4559 
4560  /*
4561  * Try to extrapolate forward
4562  */
4563 
4564  found = 1;
4565  while (ilambda[n-1] < nlines - 1 && found) {
4566 
4567  /*
4568  * Determine the local dispersion from the last pair of
4569  * identified peaks
4570  */
4571 
4572  if (n > 1)
4573  disp = (lambda[n-1] - lambda[n-2]) / (xpos[n-1] - xpos[n-2]);
4574  else
4575  disp = 0.0;
4576 
4577  if (disp > max_disp || disp < min_disp)
4578  break;
4579 
4580 
4581  /*
4582  * With this, find the expected position of the missing
4583  * peak by linear interpolation.
4584  */
4585 
4586  hi_start = xpos[n-1] + (line[ilambda[n-1] + 1] - lambda[n-1]) / disp;
4587 
4588  /*
4589  * Is there a peak at that position? Here a peak from the
4590  * original list is searched, that is closer than 6 pixels
4591  * to the expected position. If it is found, insert it at
4592  * the end of the list of identified peaks. If it is not
4593  * found, leave the loop.
4594  */
4595 
4596  found = 0;
4597  min = fabs(peak[0] - hi_start);
4598  minpos = 0;
4599  for (k = 1; k < npeaks; k++) {
4600  if (min > fabs(peak[k] - hi_start)) {
4601  min = fabs(peak[k] - hi_start);
4602  minpos = k;
4603  }
4604  }
4605  if (min < 6 && fabs(peak[minpos] - xpos[n-1]) > 1.0) {
4606  xpos[n] = peak[minpos];
4607  lambda[n] = line[ilambda[n-1] + 1];
4608  ilambda[n] = ilambda[n-1] + 1;
4609  ++n;
4610  found = 1;
4611  }
4612  }
4613 
4614 
4615  /*
4616  * Try to extrapolate backward
4617  */
4618 
4619  found = 1;
4620  while (ilambda[0] > 0 && found) {
4621 
4622  /*
4623  * Determine the local dispersion from the first pair of
4624  * identified peaks
4625  */
4626 
4627  disp = (lambda[1] - lambda[0]) / (xpos[1] - xpos[0]);
4628 
4629  if (disp > max_disp || disp < min_disp)
4630  break;
4631 
4632 
4633  /*
4634  * With this, find the expected position of the missing
4635  * peak by linear interpolation.
4636  */
4637 
4638  hi_start = xpos[0] - (lambda[0] - line[ilambda[0] - 1]) / disp;
4639 
4640 
4641  /*
4642  * Is there a peak at that position? Here a peak from the
4643  * original list is searched, that is closer than 6 pixels
4644  * to the expected position. If it is found, insert it at
4645  * the beginning of the list of identified peaks. If it is not
4646  * found, leave the loop.
4647  */
4648 
4649  found = 0;
4650  min = fabs(peak[0] - hi_start);
4651  minpos = 0;
4652  for (k = 1; k < npeaks; k++) {
4653  if (min > fabs(peak[k] - hi_start)) {
4654  min = fabs(peak[k] - hi_start);
4655  minpos = k;
4656  }
4657  }
4658  if (min < 6 && fabs(peak[minpos] - xpos[0]) > 1.0) {
4659  for (j = n; j > 0; j--) {
4660  xpos[j] = xpos[j-1];
4661  lambda[j] = lambda[j-1];
4662  ilambda[j] = ilambda[j-1];
4663  }
4664  xpos[0] = peak[minpos];
4665  lambda[0] = line[ilambda[0] - 1];
4666  ilambda[0] = ilambda[0] - 1;
4667  ++n;
4668  found = 1;
4669  }
4670  }
4671  }
4672 
4673 
4674  /*
4675  * At this point all peaks are processed. Free memory, and return
4676  * the result.
4677  */
4678 
4679 /************************************************+
4680  for (i = 0; i < npeaks; i++) {
4681  printf("Peak %d:\n ", i);
4682  for (j = 0; j < nident[i]; j++)
4683  printf("%.2f, ", line[ident[i][j]]);
4684  printf("\n");
4685  }
4686 
4687  printf("\n");
4688 
4689  for (i = 0; i < n; i++)
4690  printf("%.2f, %.2f\n", xpos[i], lambda[i]);
4691 +************************************************/
4692  for (i = 0; i < npeaks; i++)
4693  cpl_free(ident[i]);
4694  cpl_free(ident);
4695  cpl_free(nident);
4696  cpl_free(lident);
4697  cpl_free(ilambda);
4698  cpl_free(tmp_xpos);
4699  cpl_free(tmp_lambda);
4700  cpl_free(tmp_ilambda);
4701  cpl_free(peak_lo);
4702  cpl_free(flag);
4703  cpl_free(seq_length);
4704  cpl_free(peak_hi);
4705 
4706  if (n == 0) {
4707  cpl_free(xpos);
4708  cpl_free(lambda);
4709  return NULL;
4710  }
4711 
4712  return cpl_bivector_wrap_vectors(cpl_vector_wrap(n, xpos),
4713  cpl_vector_wrap(n, lambda));
4714 }
4715 
4716 
4734 /*
4735 double mos_eval_dds(cpl_polynomial *ids, double blue, double red,
4736  double refwave, double pixel)
4737 {
4738  double yellow;
4739  double cpixel;
4740  double tolerance = 0.02;
4741  int max_iter = 20;
4742  int iter = 0;
4743 
4744 
4745  if (cpl_polynomial_eval_1d(ids, blue-refwave, NULL) > pixel)
4746  return 0.0;
4747 
4748  if (cpl_polynomial_eval_1d(ids, red-refwave, NULL) < pixel)
4749  return 0.0;
4750 
4751  yellow = (blue + red) / 2;
4752 
4753  cpixel = cpl_polynomial_eval_1d(ids, yellow-refwave, NULL);
4754 
4755  while (fabs(cpixel - pixel) > tolerance && iter < max_iter) {
4756 
4757  if (cpixel > pixel)
4758  red = yellow;
4759  else
4760  blue = yellow;
4761 
4762  yellow = (blue + red) / 2;
4763  cpixel = cpl_polynomial_eval_1d(ids, yellow-refwave, NULL);
4764 
4765  iter++;
4766 
4767  }
4768 
4769  return yellow;
4770 
4771 }
4772 */
4773 
4774 double mos_eval_dds(cpl_polynomial *ids, double blue, double red,
4775  double refwave, double pixel)
4776 {
4777  double yellow;
4778  double coeff;
4779  cpl_size zero = 0;
4780 
4781  if (cpl_polynomial_eval_1d(ids, blue-refwave, NULL) > pixel)
4782  return 0.0;
4783 
4784  if (cpl_polynomial_eval_1d(ids, red-refwave, NULL) < pixel)
4785  return 0.0;
4786 
4787  yellow = (blue + red) / 2 - refwave;
4788 
4789  coeff = cpl_polynomial_get_coeff(ids, &zero);
4790  cpl_polynomial_set_coeff(ids, &zero, coeff - pixel);
4791 
4792  cpl_polynomial_solve_1d(ids, yellow, &yellow, 1);
4793  if (cpl_error_get_code() != CPL_ERROR_NONE) {
4794  cpl_error_reset();
4795  return 0.0;
4796  }
4797 
4798  cpl_polynomial_set_coeff(ids, &zero, coeff);
4799 
4800  return yellow + refwave;
4801 
4802 }
4803 
4829 cpl_polynomial *mos_poly_wav2pix(cpl_bivector *pixwav, int order,
4830  double reject, int minlines,
4831  int *nlines, double *err)
4832 {
4833  const char *func = "mos_poly_wav2pix";
4834 
4835  cpl_bivector *pixwav2;
4836  cpl_vector *wavel;
4837  cpl_vector *pixel;
4838  double *d_wavel;
4839  double *d_pixel;
4840  double pixpos;
4841  int fitlines;
4842  int rejection = 0;
4843  int i, j;
4844 
4845  cpl_polynomial *ids;
4846 
4847 
4848  *nlines = 0;
4849  *err = 0;
4850 
4851  if (pixwav == NULL) {
4852  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
4853  return NULL;
4854  }
4855 
4856  fitlines = cpl_bivector_get_size(pixwav);
4857 
4858  if (fitlines < minlines) {
4859  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
4860  return NULL;
4861  }
4862 
4863 
4864  /*
4865  * If outliers rejection was requested, allocate a working
4866  * vector (that can be modified as soon as outliers are removed)
4867  */
4868 
4869  if (reject > 0.0)
4870  rejection = 1;
4871 
4872  if (rejection)
4873  pixwav2 = cpl_bivector_duplicate(pixwav);
4874  else
4875  pixwav2 = pixwav;
4876 
4877 
4878  /*
4879  * The single vectors are extracted just because the fitting routine
4880  * requires it
4881  */
4882 
4883  pixel = cpl_bivector_get_x(pixwav2);
4884  wavel = cpl_bivector_get_y(pixwav2);
4885 
4886 
4887  /*
4888  * Get rid of the wrapper, in case of duplication
4889  */
4890 
4891  if (rejection)
4892  cpl_bivector_unwrap_vectors(pixwav2);
4893 
4894 
4895  /*
4896  * Here begins the iterative fit of identified lines
4897  */
4898 
4899  while (fitlines >= minlines) {
4900 
4901  ids = cpl_polynomial_fit_1d_create(wavel, pixel, order, err);
4902  *err = sqrt(*err);
4903 
4904  if (ids == NULL) {
4905  cpl_msg_debug(cpl_error_get_where(), "%s", cpl_error_get_message());
4906  cpl_msg_debug(func, "Fitting IDS");
4907  cpl_error_set_where(func);
4908  if (rejection) {
4909  cpl_vector_delete(wavel);
4910  cpl_vector_delete(pixel);
4911  }
4912  return NULL;
4913  }
4914 
4915  if (rejection) {
4916 
4917 
4918  /*
4919  * Now work directly with the vector data buffers...
4920  */
4921 
4922  d_pixel = cpl_vector_unwrap(pixel);
4923  d_wavel = cpl_vector_unwrap(wavel);
4924 
4925  for (i = 0, j = 0; i < fitlines; i++) {
4926  pixpos = cpl_polynomial_eval_1d(ids, d_wavel[i], NULL);
4927  if (fabs(pixpos - d_pixel[i]) < reject) {
4928  d_pixel[j] = d_pixel[i];
4929  d_wavel[j] = d_wavel[i];
4930  j++;
4931  }
4932  }
4933 
4934  if (j == fitlines) { /* No rejection in last iteration */
4935  cpl_free(d_wavel);
4936  cpl_free(d_pixel);
4937  *nlines = fitlines;
4938  return ids;
4939  }
4940  else { /* Some lines were rejected */
4941  fitlines = j;
4942  cpl_polynomial_delete(ids);
4943  if (fitlines >= minlines) {
4944  pixel = cpl_vector_wrap(fitlines, d_pixel);
4945  wavel = cpl_vector_wrap(fitlines, d_wavel);
4946  }
4947  else { /* Too few lines: failure */
4948  cpl_free(d_wavel);
4949  cpl_free(d_pixel);
4950  cpl_error_set(func, CPL_ERROR_CONTINUE);
4951  return NULL;
4952  }
4953  }
4954  }
4955  else {
4956  *nlines = fitlines;
4957  return ids; /* Exit at first iteration if no rejection */
4958  }
4959  }
4960 
4961  return ids; /* To avoid compiler warnings */
4962 }
4963 
4964 
4989 cpl_polynomial *mos_poly_pix2wav(cpl_bivector *pixwav, int order,
4990  double reject, int minlines,
4991  int *nlines, double *err)
4992 {
4993 
4994  cpl_bivector *wavpix;
4995  cpl_vector *wavel;
4996  cpl_vector *pixel;
4997 
4998  cpl_polynomial *dds;
4999 
5000 
5001  /*
5002  * Swap vectors in bivector, in order to reuse mos_poly_wav2pix()
5003  */
5004 
5005  pixel = cpl_bivector_get_x(pixwav);
5006  wavel = cpl_bivector_get_y(pixwav);
5007 
5008  wavpix = cpl_bivector_wrap_vectors(wavel, pixel);
5009 
5010  dds = mos_poly_wav2pix(wavpix, order, reject, minlines, nlines, err);
5011 
5012  cpl_bivector_unwrap_vectors(wavpix);
5013 
5014  return dds;
5015 
5016 }
5017 
5018 
5041 cpl_bivector *mos_find_peaks(const float *spectrum, int length,
5042  cpl_vector *lines, cpl_polynomial *ids,
5043  double refwave, int sradius)
5044 {
5045  const char *func = "mos_find_peaks";
5046 
5047  double *data;
5048  double *d_pixel;
5049  double *d_wavel;
5050  float pos;
5051  int nlines;
5052  int pixel;
5053  int i, j;
5054 
5055 
5056  if (spectrum == NULL || lines == NULL || ids == NULL) {
5057  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
5058  return NULL;
5059  }
5060 
5061  nlines = cpl_vector_get_size(lines);
5062 
5063  if (sradius < 1 || length < 2*sradius+1 || nlines < 1) {
5064  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
5065  return NULL;
5066  }
5067 
5068  d_wavel = cpl_malloc(nlines * sizeof(double));
5069  d_pixel = cpl_malloc(nlines * sizeof(double));
5070 
5071  data = cpl_vector_get_data(lines);
5072 
5073  for (i = 0, j = 0; i < nlines; i++) {
5074  pixel = cpl_polynomial_eval_1d(ids, data[i]-refwave, NULL) + 0.5;
5075  if (pixel < 0 || pixel - sradius < 0 || pixel + sradius >= length)
5076  continue;
5077  if (peakPosition(spectrum+pixel-sradius, 2*sradius+1, &pos, 1) == 0) {
5078  pos += pixel - sradius;
5079  d_pixel[j] = pos;
5080  d_wavel[j] = data[i];
5081  j++;
5082  }
5083  }
5084 
5085  if (j > 0) {
5086  return cpl_bivector_wrap_vectors(cpl_vector_wrap(j, d_pixel),
5087  cpl_vector_wrap(j, d_wavel));
5088  }
5089  else {
5090  cpl_free(d_wavel);
5091  cpl_free(d_pixel);
5092  cpl_error_set(func, CPL_ERROR_ILLEGAL_OUTPUT);
5093  return NULL;
5094  }
5095 }
5096 
5097 
5221 cpl_image *mos_wavelength_calibration_raw(const cpl_image *image,
5222  cpl_vector *lines,
5223  double dispersion, float level,
5224  int sradius, int order,
5225  double reject, double refwave,
5226  double *wavestart, double *waveend,
5227  int *nlines, double *error,
5228  cpl_table *idscoeff,
5229  cpl_image *calibration,
5230  cpl_image *residuals,
5231  cpl_table *restable,
5232  cpl_mask *refmask,
5233  cpl_table *detected_lines)
5234 {
5235 
5236  const char *func = "mos_wavelength_calibration_raw";
5237 
5238  const char *clab[6] = {"c0", "c1", "c2", "c3", "c4", "c5"};
5239  /* Max order is 5 */
5240 
5241  double tolerance = 20.0; /* Probably forever... */
5242  int step = 10; /* Compute restable every "step" rows */
5243 
5244  char name[MAX_COLNAME];
5245  cpl_image *resampled;
5246  cpl_bivector *output;
5247  cpl_bivector *new_output;
5248  cpl_vector *peaks;
5249  cpl_vector *wavel;
5250  cpl_polynomial *ids;
5251  cpl_polynomial *lin;
5252  cpl_matrix *kernel;
5253  double ids_err;
5254  double max_disp, min_disp;
5255  double *line;
5256  double firstLambda, lastLambda, lambda;
5257  double value, wave, pixe;
5258  cpl_binary *mdata;
5259  const float *sdata;
5260  float *rdata;
5261  float *idata;
5262  float *ddata;
5263  float v1, v2, vi;
5264  float fpixel;
5265  int *have_it;
5266  int pixstart, pixend;
5267  int extrapolation;
5268  int nref;
5269  int nl, nx, ny, pixel;
5270  int countLines, usedLines;
5271  int uorder;
5272  int in, first, last;
5273  int width, uradius;
5274  int i, j;
5275  int null;
5276  cpl_size k;
5277 
5278 
5279  if (dispersion == 0.0) {
5280  cpl_msg_error(func, "The expected dispersion (A/pixel) must be given");
5281  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
5282  return NULL;
5283  }
5284 
5285  if (dispersion < 0.0) {
5286  cpl_msg_error(func, "The expected dispersion must be positive");
5287  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
5288  return NULL;
5289  }
5290 
5291  max_disp = dispersion + dispersion * tolerance / 100;
5292  min_disp = dispersion - dispersion * tolerance / 100;
5293 
5294  if (order < 1) {
5295  cpl_msg_error(func, "The order of the fitting polynomial "
5296  "must be at least 1");
5297  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
5298  return NULL;
5299  }
5300 
5301  if (image == NULL || lines == NULL) {
5302  cpl_msg_error(func, "Both spectral exposure and reference line "
5303  "catalog are required in input");
5304  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
5305  return NULL;
5306  }
5307 
5308  nx = cpl_image_get_size_x(image);
5309  ny = cpl_image_get_size_y(image);
5310  sdata = cpl_image_get_data_float_const(image);
5311 
5312  nref = cpl_vector_get_size(lines);
5313  line = cpl_vector_get_data(lines);
5314 
5315  if (*wavestart < 1.0 && *waveend < 1.0) {
5316  firstLambda = line[0];
5317  lastLambda = line[nref-1];
5318  extrapolation = (lastLambda - firstLambda) / 10;
5319  firstLambda -= extrapolation;
5320  lastLambda += extrapolation;
5321  *wavestart = firstLambda;
5322  *waveend = lastLambda;
5323  }
5324  else {
5325  firstLambda = *wavestart;
5326  lastLambda = *waveend;
5327  }
5328 
5329  nl = (lastLambda - firstLambda) / dispersion;
5330  resampled = cpl_image_new(nl, ny, CPL_TYPE_FLOAT);
5331  rdata = cpl_image_get_data_float(resampled);
5332 
5333  if (calibration)
5334  idata = cpl_image_get_data_float(calibration);
5335 
5336  if (residuals)
5337  ddata = cpl_image_get_data_float(residuals);
5338 
5339  if (idscoeff)
5340  for (j = 0; j <= order; j++)
5341  cpl_table_new_column(idscoeff, clab[j], CPL_TYPE_DOUBLE);
5342 
5343  if (restable) {
5344  cpl_table_set_size(restable, nref);
5345  cpl_table_new_column(restable, "wavelength", CPL_TYPE_DOUBLE);
5346  cpl_table_copy_data_double(restable, "wavelength", line);
5347  for (i = 0; i < ny; i += step) {
5348  snprintf(name, MAX_COLNAME, "r%d", i);
5349  cpl_table_new_column(restable, name, CPL_TYPE_DOUBLE);
5350  snprintf(name, MAX_COLNAME, "d%d", i);
5351  cpl_table_new_column(restable, name, CPL_TYPE_DOUBLE);
5352  snprintf(name, MAX_COLNAME, "p%d", i);
5353  cpl_table_new_column(restable, name, CPL_TYPE_DOUBLE);
5354  }
5355  }
5356 
5357  if (detected_lines) {
5358  cpl_table_set_size(detected_lines, 0);
5359  cpl_table_new_column(detected_lines, "xpos", CPL_TYPE_DOUBLE);
5360  cpl_table_new_column(detected_lines, "ypos", CPL_TYPE_DOUBLE);
5361  cpl_table_new_column(detected_lines, "xpos_iter", CPL_TYPE_DOUBLE);
5362  cpl_table_new_column(detected_lines, "ypos_iter", CPL_TYPE_DOUBLE);
5363  cpl_table_new_column(detected_lines, "peak_flux", CPL_TYPE_DOUBLE);
5364  cpl_table_new_column(detected_lines, "wave_ident", CPL_TYPE_DOUBLE);
5365  cpl_table_new_column(detected_lines, "wave_ident_iter", CPL_TYPE_DOUBLE);
5366  cpl_table_new_column(detected_lines, "xpos_fit_rect_wavecal", CPL_TYPE_DOUBLE);
5367  cpl_table_new_column(detected_lines, "res_xpos", CPL_TYPE_DOUBLE);
5368  }
5369 
5370  /*
5371  * Here is the real thing: detecting and identifying peaks,
5372  * and then fit the transformation from wavelength to pixel
5373  * and from pixel to wavelength.
5374  */
5375 
5376  for (i = 0; i < ny; i++) {
5377  width = mos_lines_width(sdata + i*nx, nx);
5378  if (sradius > 0) {
5379  if (width > sradius) {
5380  uradius = width;
5381  }
5382  else {
5383  uradius = sradius;
5384  }
5385  }
5386  if (width < 5)
5387  width = 5;
5388  peaks = mos_peak_candidates(sdata + i*nx, nx, level, width);
5389  if (peaks) {
5390  peaks = mos_refine_peaks(sdata + i*nx, nx, peaks, width);
5391  }
5392  if (peaks) {
5393  output = mos_identify_peaks(peaks, lines, min_disp, max_disp, 0.05);
5394  if (output) {
5395  countLines = cpl_bivector_get_size(output);
5396  if (countLines < 4) {
5397  cpl_bivector_delete(output);
5398  cpl_vector_delete(peaks);
5399  if (nlines)
5400  nlines[i] = 0;
5401  if (error)
5402  error[i] = 0.0;
5403  continue;
5404  }
5405 
5406  /*
5407  * Set reference wavelength as zero point
5408  */
5409 
5410  wavel = cpl_bivector_get_y(output);
5411  cpl_vector_subtract_scalar(wavel, refwave);
5412 
5413  uorder = countLines / 2 - 1;
5414  if (uorder > order)
5415  uorder = order;
5416 
5417 /* This part is now commented out. In case the first-guess iteration
5418  * was requested, the first fit was made with a lower polynomial degree:
5419  * more robust, and still accurate enough to be used as a first-guess.
5420 
5421  if (sradius > 0 && uorder > 2)
5422  --uorder;
5423 
5424  * End of commented part */
5425 
5426  ids = mos_poly_wav2pix(output, uorder, reject,
5427  2 * (uorder + 1), &usedLines,
5428  &ids_err);
5429 
5430  if (ids == NULL) {
5431  cpl_bivector_delete(output);
5432  cpl_vector_delete(peaks);
5433  if (nlines)
5434  nlines[i] = 0;
5435  if (error)
5436  error[i] = 0.0;
5437  cpl_error_reset();
5438  continue;
5439  }
5440 
5441  if (idscoeff) {
5442 
5443  /*
5444  * Write it anyway, even in case a first-guess based
5445  * solution will be searched afterwards: in case of
5446  * failure, the "blind" solution is kept.
5447  */
5448 
5449  for (k = 0; k <= order; k++) {
5450  if (k > uorder) {
5451  cpl_table_set_double(idscoeff, clab[k], i, 0.0);
5452  }
5453  else {
5454  cpl_table_set_double(idscoeff, clab[k], i,
5455  cpl_polynomial_get_coeff(ids, &k));
5456  }
5457  }
5458  }
5459 
5460  if(detected_lines)
5461  {
5462  cpl_size newlines = cpl_vector_get_size(peaks);
5463  cpl_size oldsize = cpl_table_get_nrow(detected_lines);
5464  cpl_table_set_size(detected_lines, oldsize + newlines);
5465  for(cpl_size iline = 0; iline < newlines; ++iline)
5466  {
5467  cpl_table_set_double(detected_lines, "xpos",
5468  oldsize + iline, cpl_vector_get(peaks, iline) + 1);
5469  cpl_table_set_double(detected_lines, "ypos",
5470  oldsize + iline, (double)i + 1);
5471  cpl_table_set_double(detected_lines, "peak_flux",
5472  oldsize + iline,
5473  sdata[i*nx+(int)(cpl_vector_get(peaks, iline)+0.5)]);
5474  }
5475  }
5476 
5477  //Fill the line identification information in
5478  //the detected_lines table
5479  if(detected_lines)
5480  {
5481  cpl_size nidentlines = cpl_bivector_get_size(output);
5482  cpl_size ndetectlines = cpl_vector_get_size(peaks);
5483  cpl_size totalsize = cpl_table_get_nrow(detected_lines);
5484  for(cpl_size idline = 0; idline < nidentlines; ++idline)
5485  {
5486  for(cpl_size detline = 0; detline < ndetectlines; ++detline)
5487  {
5488  if(cpl_vector_get(peaks, detline) ==
5489  cpl_bivector_get_x_data(output)[idline])
5490  {
5491  cpl_size table_pos = totalsize - ndetectlines + detline;
5492  double wave_ident = cpl_bivector_get_y_data(output)[idline] + refwave;
5493  double xpix_fit = cpl_polynomial_eval_1d(ids,
5494  wave_ident - refwave, NULL);
5495  double xpos_det = cpl_table_get_double(detected_lines,
5496  "xpos",
5497  table_pos, &null);
5498  cpl_table_set_double(detected_lines,
5499  "wave_ident",
5500  table_pos,
5501  wave_ident);
5502  cpl_table_set_double(detected_lines,
5503  "xpos_fit_rect_wavecal",
5504  table_pos,
5505  xpix_fit + 1);
5506  cpl_table_set_double(detected_lines,
5507  "res_xpos",
5508  table_pos,
5509  xpos_det - xpix_fit - 1);
5510 
5511  }
5512  }
5513  }
5514  }
5515 
5516  if (sradius > 0) {
5517 
5518  /*
5519  * Use ids as a first-guess
5520  */
5521 
5522  new_output = mos_find_peaks(sdata + i*nx, nx, lines,
5523  ids, refwave, uradius);
5524 
5525  if (new_output) {
5526  cpl_bivector_delete(output);
5527  output = new_output;
5528  }
5529  else
5530  cpl_error_reset();
5531 
5532 
5533  cpl_polynomial_delete(ids);
5534 
5535  countLines = cpl_bivector_get_size(output);
5536 
5537  if (countLines < 4) {
5538  cpl_bivector_delete(output);
5539  cpl_vector_delete(peaks);
5540 
5541  /*
5542  * With the following code a decision is taken:
5543  * if using the first-guess gives no results,
5544  * then also the "blind" solution is rejected.
5545  */
5546 
5547  if (nlines)
5548  nlines[i] = 0;
5549  if (error)
5550  error[i] = 0.0;
5551  if (idscoeff)
5552  for (k = 0; k <= order; k++)
5553  cpl_table_set_invalid(idscoeff, clab[k], i);
5554  continue;
5555  }
5556 
5557  wavel = cpl_bivector_get_y(output);
5558  cpl_vector_subtract_scalar(wavel, refwave);
5559 
5560  uorder = countLines / 2 - 1;
5561  if (uorder > order)
5562  uorder = order;
5563 
5564  ids = mos_poly_wav2pix(output, uorder, reject,
5565  2 * (uorder + 1), &usedLines,
5566  &ids_err);
5567 
5568  if (ids == NULL) {
5569  cpl_bivector_delete(output);
5570  cpl_vector_delete(peaks);
5571 
5572  /*
5573  * With the following code a decision is taken:
5574  * if using the first-guess gives no results,
5575  * then also the "blind" solution is rejected.
5576  */
5577 
5578  if (nlines)
5579  nlines[i] = 0;
5580  if (error)
5581  error[i] = 0.0;
5582  if (idscoeff)
5583  for (k = 0; k <= order; k++)
5584  cpl_table_set_invalid(idscoeff, clab[k], i);
5585  cpl_error_reset();
5586  continue;
5587  }
5588 
5589  if (idscoeff) {
5590  for (k = 0; k <= order; k++) {
5591  if (k > uorder) {
5592  cpl_table_set_double(idscoeff, clab[k], i, 0.0);
5593  }
5594  else {
5595  cpl_table_set_double(idscoeff, clab[k], i,
5596  cpl_polynomial_get_coeff(ids, &k));
5597  }
5598  }
5599  }
5600 
5601 
5602  if(detected_lines)
5603  {
5604  cpl_size oldsize = cpl_table_get_nrow(detected_lines);
5605  cpl_size nidentlines = cpl_bivector_get_size(output);
5606  cpl_table_set_size(detected_lines, oldsize + nidentlines);
5607  for(cpl_size idline = 0; idline < nidentlines ; ++idline)
5608  {
5609  double wave_ident = cpl_bivector_get_y_data(output)[idline] + refwave;
5610  double xpix_fit = cpl_polynomial_eval_1d(ids,
5611  wave_ident - refwave, NULL);
5612  cpl_table_set_double(detected_lines, "xpos_iter",
5613  oldsize + idline, cpl_bivector_get_x_data(output)[idline] + 1);
5614  cpl_table_set_double(detected_lines, "ypos_iter",
5615  oldsize + idline, (double)i + 1);
5616  cpl_table_set_double(detected_lines, "peak_flux",
5617  oldsize + idline,
5618  sdata[i*nx+(int)(cpl_bivector_get_x_data(output)[idline]+0.5)]);
5619  cpl_table_set_double(detected_lines, "wave_ident_iter",
5620  oldsize + idline, wave_ident);
5621  cpl_table_set_double(detected_lines, "xpos_fit_rect_wavecal",
5622  oldsize + idline, xpix_fit + 1);
5623  }
5624  }
5625 
5626  } /* End of "use ids as a first-guess" */
5627 
5628  if (nlines)
5629  nlines[i] = usedLines;
5630  if (error)
5631  error[i] = ids_err / sqrt(usedLines/(uorder + 1));
5632 
5633  pixstart = cpl_polynomial_eval_1d(ids,
5634  cpl_bivector_get_y_data(output)[0], NULL);
5635  pixend = cpl_polynomial_eval_1d(ids,
5636  cpl_bivector_get_y_data(output)[countLines-1], NULL);
5637  extrapolation = (pixend - pixstart) / 5;
5638  pixstart -= extrapolation;
5639  pixend += extrapolation;
5640  if (pixstart < 0)
5641  pixstart = 0;
5642  if (pixend > nx)
5643  pixend = nx;
5644 
5645  /*
5646  * Wavelength calibrated image (if requested):
5647  */
5648 
5649  if (calibration) {
5650  for (j = pixstart; j < pixend; j++) {
5651  (idata + i*nx)[j] = mos_eval_dds(ids, firstLambda,
5652  lastLambda, refwave,
5653  j);
5654  }
5655  }
5656 
5657  /*
5658  * Resampled image:
5659  */
5660 
5661  for (j = 0; j < nl; j++) {
5662  lambda = firstLambda + j * dispersion;
5663  fpixel = cpl_polynomial_eval_1d(ids, lambda - refwave,
5664  NULL);
5665  pixel = fpixel;
5666  if (pixel >= 0 && pixel < nx-1) {
5667  v1 = (sdata + i*nx)[pixel];
5668  v2 = (sdata + i*nx)[pixel+1];
5669  vi = v1 + (v2-v1)*(fpixel-pixel);
5670  (rdata + i*nl)[j] = vi;
5671  }
5672  }
5673 
5674  /*
5675  * Residuals image
5676  */
5677 
5678  if (residuals || (restable && !(i%step))) {
5679  if (restable && !(i%step)) {
5680  lin = cpl_polynomial_new(1);
5681  for (k = 0; k < 2; k++)
5682  cpl_polynomial_set_coeff(lin, &k,
5683  cpl_polynomial_get_coeff(ids, &k));
5684  }
5685  for (j = 0; j < countLines; j++) {
5686  pixe = cpl_bivector_get_x_data(output)[j];
5687  wave = cpl_bivector_get_y_data(output)[j];
5688  value = pixe - cpl_polynomial_eval_1d(ids, wave, NULL);
5689  if (residuals) {
5690  pixel = pixe + 0.5;
5691  (ddata + i*nx)[pixel] = value;
5692  }
5693  if (restable && !(i%step)) {
5694  for (k = 0; k < nref; k++) {
5695  if (fabs(line[k] - refwave - wave) < 0.1) {
5696  snprintf(name, MAX_COLNAME, "r%d", i);
5697  cpl_table_set_double(restable, name,
5698  k, value);
5699  value = pixe
5700  - cpl_polynomial_eval_1d(lin, wave,
5701  NULL);
5702  snprintf(name, MAX_COLNAME, "d%d", i);
5703  cpl_table_set_double(restable, name,
5704  k, value);
5705  snprintf(name, MAX_COLNAME, "p%d", i);
5706  cpl_table_set_double(restable, name,
5707  k, pixe);
5708  break;
5709  }
5710  }
5711  }
5712  }
5713  if (restable && !(i%step)) {
5714  cpl_polynomial_delete(lin);
5715  }
5716  }
5717 
5718  /*
5719  * Mask at reference wavelength
5720  */
5721 
5722  if (refmask) {
5723  mdata = cpl_mask_get_data(refmask);
5724  pixel = cpl_polynomial_eval_1d(ids, 0.0, NULL) + 0.5;
5725  if (pixel - 1 >= 0 && pixel + 1 < nx) {
5726  mdata[pixel-1 + i*nx] = CPL_BINARY_1;
5727  mdata[pixel + i*nx] = CPL_BINARY_1;
5728  mdata[pixel+1 + i*nx] = CPL_BINARY_1;
5729  }
5730  }
5731 
5732  cpl_polynomial_delete(ids);
5733  cpl_bivector_delete(output);
5734  }
5735  cpl_vector_delete(peaks);
5736  }
5737  }
5738 
5739  if (refmask) {
5740  kernel = cpl_matrix_new(3, 3);
5741  cpl_matrix_set(kernel, 0, 1, 1.0);
5742  cpl_matrix_set(kernel, 1, 1, 1.0);
5743  cpl_matrix_set(kernel, 2, 1, 1.0);
5744 
5745  cpl_mask_dilation(refmask, kernel);
5746  cpl_mask_erosion(refmask, kernel);
5747  cpl_mask_erosion(refmask, kernel);
5748  cpl_mask_dilation(refmask, kernel);
5749 
5750  cpl_matrix_delete(kernel);
5751 
5752  /*
5753  * Fill possible gaps
5754  */
5755 
5756  mdata = cpl_mask_get_data(refmask);
5757  have_it = cpl_calloc(ny, sizeof(int));
5758 
5759  for (i = 0; i < ny; i++, mdata += nx) {
5760  for (j = 0; j < nx; j++) {
5761  if (mdata[j] == CPL_BINARY_1) {
5762  have_it[i] = j;
5763  break;
5764  }
5765  }
5766  }
5767 
5768  mdata = cpl_mask_get_data(refmask);
5769  in = 0;
5770  first = last = 0;
5771 
5772  for (i = 0; i < ny; i++) {
5773  if (have_it[i]) {
5774  if (!in) {
5775  in = 1;
5776  if (first) {
5777  last = i;
5778  if (abs(have_it[first] - have_it[last]) < 3) {
5779  for (j = first; j < last; j++) {
5780  mdata[have_it[first] + nx*j + 0] = CPL_BINARY_1;
5781  mdata[have_it[first] + nx*j + 1] = CPL_BINARY_1;
5782  mdata[have_it[first] + nx*j + 2] = CPL_BINARY_1;
5783  }
5784  }
5785  }
5786  }
5787  }
5788  else {
5789  if (in) {
5790  in = 0;
5791  first = i - 1;
5792  }
5793  }
5794  }
5795 
5796  cpl_free(have_it);
5797 
5798  }
5799 
5800 /*
5801  for (i = 0; i < ny; i++) {
5802  if (nlines[i] == 0) {
5803  for (k = 0; k <= order; k++) {
5804  cpl_table_set_invalid(idscoeff, clab[k], i);
5805  }
5806  }
5807  }
5808 */
5809 
5810  return resampled;
5811 }
5812 
5813 
5835 /*
5836 cpl_table *mos_locate_spectra_bis(cpl_mask *mask)
5837 {
5838  const char *func = "mos_locate_spectra_bis";
5839 
5840  cpl_apertures *slits;
5841  cpl_image *labimage;
5842  cpl_image *refimage;
5843  cpl_binary *mdata;
5844  cpl_table *slitpos;
5845  cpl_propertylist *sort_col;
5846  int nslits;
5847  int *have_it;
5848  int in, first, last;
5849  int i, j;
5850 
5851 
5852  if (mask == NULL) {
5853  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
5854  return NULL;
5855  }
5856 
5857  nx = cpl_mask_get_size_x(mask);
5858  ny = cpl_mask_get_size_y(mask);
5859 
5860  mdata = cpl_mask_get_data(refmask);
5861  have_it = cpl_calloc(ny, sizeof(int));
5862 
5863  for (i = 0; i < ny; i++, mdata += nx) {
5864  for (j = 0; j < nx; j++) {
5865  if (mdata[j] == CPL_BINARY_1) {
5866  have_it[i] = j + 1;
5867  break;
5868  }
5869  }
5870  }
5871 
5872  mdata = cpl_mask_get_data(refmask);
5873  in = 0;
5874  first = last = 0;
5875  nslits = 0;
5876 
5877  for (i = 0; i < ny; i++) {
5878  if (have_it[i]) {
5879  if (in) {
5880  if (i) {
5881  if (abs(have_it[i] - have_it[i-1]) > 3) {
5882  nslits++;
5883  }
5884  }
5885  }
5886  else {
5887  in = 1;
5888  nslits++;
5889  }
5890  }
5891  else {
5892  if (in) {
5893  in = 0;
5894  }
5895  }
5896  }
5897 }
5898 */
5899 
5900 
5922 cpl_table *mos_locate_spectra(cpl_mask *mask)
5923 {
5924  const char *func = "mos_locate_spectra";
5925 
5926  cpl_apertures *slits;
5927  cpl_image *labimage;
5928  cpl_image *refimage;
5929  cpl_table *slitpos;
5930  cpl_propertylist *sort_col;
5931  cpl_size nslits;
5932  int i;
5933 
5934 
5935  if (mask == NULL) {
5936  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
5937  return NULL;
5938  }
5939 
5940  labimage = cpl_image_labelise_mask_create(mask, &nslits);
5941 
5942  if (nslits < 1) {
5943  cpl_image_delete(labimage);
5944  cpl_error_set(func, CPL_ERROR_DATA_NOT_FOUND);
5945  return NULL;
5946  }
5947 
5948  refimage = cpl_image_new_from_mask(mask);
5949 
5950  slits = cpl_apertures_new_from_image(refimage, labimage);
5951 
5952  cpl_image_delete(labimage);
5953  cpl_image_delete(refimage);
5954 
5955  nslits = cpl_apertures_get_size(slits); /* Overwriting nslits - safer! */
5956  if (nslits < 1) {
5957  cpl_apertures_delete(slits);
5958  cpl_error_set(func, CPL_ERROR_DATA_NOT_FOUND);
5959  return NULL;
5960  }
5961 
5962  slitpos = cpl_table_new(nslits);
5963  cpl_table_new_column(slitpos, "xtop", CPL_TYPE_DOUBLE);
5964  cpl_table_new_column(slitpos, "ytop", CPL_TYPE_DOUBLE);
5965  cpl_table_new_column(slitpos, "xbottom", CPL_TYPE_DOUBLE);
5966  cpl_table_new_column(slitpos, "ybottom", CPL_TYPE_DOUBLE);
5967  cpl_table_set_column_unit(slitpos, "xtop", "pixel");
5968  cpl_table_set_column_unit(slitpos, "ytop", "pixel");
5969  cpl_table_set_column_unit(slitpos, "xbottom", "pixel");
5970  cpl_table_set_column_unit(slitpos, "ybottom", "pixel");
5971 
5972  for (i = 0; i < nslits; i++) {
5973  cpl_table_set_double(slitpos, "xtop", i,
5974  cpl_apertures_get_top_x(slits, i+1) - 1);
5975  cpl_table_set_double(slitpos, "ytop", i,
5976  cpl_apertures_get_top(slits, i+1));
5977  cpl_table_set_double(slitpos, "xbottom", i,
5978  cpl_apertures_get_bottom_x(slits, i+1) - 1);
5979  cpl_table_set_double(slitpos, "ybottom", i,
5980  cpl_apertures_get_bottom(slits, i+1));
5981  }
5982 
5983  cpl_apertures_delete(slits);
5984 
5985  sort_col = cpl_propertylist_new();
5986  cpl_propertylist_append_bool(sort_col, "ytop", 1);
5987  cpl_table_sort(slitpos, sort_col);
5988  cpl_propertylist_delete(sort_col);
5989 
5990  return slitpos;
5991 
5992 }
5993 
5994 
6010 cpl_error_code mos_validate_slits(cpl_table *slits)
6011 {
6012  const char *func = "mos_validate_slits";
6013 
6014 
6015  if (slits == NULL)
6016  return cpl_error_set(func, CPL_ERROR_NULL_INPUT);
6017 
6018  if (1 != cpl_table_has_column(slits, "xtop"))
6019  return cpl_error_set(func, CPL_ERROR_DATA_NOT_FOUND);
6020 
6021  if (1 != cpl_table_has_column(slits, "ytop"))
6022  return cpl_error_set(func, CPL_ERROR_DATA_NOT_FOUND);
6023 
6024  if (1 != cpl_table_has_column(slits, "xbottom"))
6025  return cpl_error_set(func, CPL_ERROR_DATA_NOT_FOUND);
6026 
6027  if (1 != cpl_table_has_column(slits, "ybottom"))
6028  return cpl_error_set(func, CPL_ERROR_DATA_NOT_FOUND);
6029 
6030  if (CPL_TYPE_DOUBLE != cpl_table_get_column_type(slits, "xtop"))
6031  return cpl_error_set(func, CPL_ERROR_INVALID_TYPE);
6032 
6033  if (CPL_TYPE_DOUBLE != cpl_table_get_column_type(slits, "ytop"))
6034  return cpl_error_set(func, CPL_ERROR_INVALID_TYPE);
6035 
6036  if (CPL_TYPE_DOUBLE != cpl_table_get_column_type(slits, "xbottom"))
6037  return cpl_error_set(func, CPL_ERROR_INVALID_TYPE);
6038 
6039  if (CPL_TYPE_DOUBLE != cpl_table_get_column_type(slits, "ybottom"))
6040  return cpl_error_set(func, CPL_ERROR_INVALID_TYPE);
6041 
6042  return CPL_ERROR_NONE;
6043 }
6044 
6045 
6074 cpl_error_code mos_rotate_slits(cpl_table *slits, int rotation, int nx, int ny)
6075 {
6076  const char *func = "mos_rotate_slits";
6077 
6078  cpl_error_code error;
6079  char aux_name[] = "_0";
6080  int i;
6081 
6082 
6083  rotation %= 4;
6084  if (rotation < 0)
6085  rotation += 4;
6086 
6087  if (rotation == 0)
6088  return CPL_ERROR_NONE;
6089 
6090  error = mos_validate_slits(slits);
6091  if (error)
6092  return cpl_error_set(func, error);
6093 
6094  if (rotation == 1 || rotation == 3) {
6095 
6096  /*
6097  * Swap x and y column names
6098  */
6099 
6100  for (i = 0; i < 77; i++)
6101  if (1 == cpl_table_has_column(slits, aux_name))
6102  aux_name[1]++;
6103  if (1 == cpl_table_has_column(slits, aux_name))
6104  return cpl_error_set(func, CPL_ERROR_CONTINUE);
6105  cpl_table_name_column(slits, "xtop", aux_name);
6106  cpl_table_name_column(slits, "ytop", "xtop");
6107  cpl_table_name_column(slits, aux_name, "ytop");
6108  cpl_table_name_column(slits, "xbottom", aux_name);
6109  cpl_table_name_column(slits, "ybottom", "xbottom");
6110  cpl_table_name_column(slits, aux_name, "ybottom");
6111  }
6112 
6113  if (rotation == 1 || rotation == 2) {
6114  cpl_table_multiply_scalar(slits, "xtop", -1.0);
6115  cpl_table_multiply_scalar(slits, "xbottom", -1.0);
6116  cpl_table_add_scalar(slits, "xtop", nx);
6117  cpl_table_add_scalar(slits, "xbottom", nx);
6118  }
6119 
6120  if (rotation == 3 || rotation == 2) {
6121  cpl_table_multiply_scalar(slits, "ytop", -1.0);
6122  cpl_table_multiply_scalar(slits, "ybottom", -1.0);
6123  cpl_table_add_scalar(slits, "ytop", ny);
6124  cpl_table_add_scalar(slits, "ybottom", ny);
6125  }
6126 
6127  return CPL_ERROR_NONE;
6128 }
6129 
6130 
6188 cpl_table *mos_identify_slits(cpl_table *slits, cpl_table *maskslits,
6189  cpl_table *global)
6190 {
6191  cpl_array *top_ident = NULL;;
6192  cpl_array *bot_ident = NULL;;
6193  cpl_matrix *mdata;
6194  cpl_matrix *mpattern;
6195  cpl_matrix *top_data;
6196  cpl_matrix *top_pattern;
6197  cpl_matrix *top_mdata;
6198  cpl_matrix *top_mpattern;
6199  cpl_matrix *bot_data;
6200  cpl_matrix *bot_pattern;
6201  cpl_matrix *bot_mdata;
6202  cpl_matrix *bot_mpattern;
6203  cpl_propertylist *sort_col;
6204  double *xtop;
6205  double *ytop;
6206  double *xmtop;
6207  double *ymtop;
6208  double *xbot;
6209  double *ybot;
6210  double *xmbot;
6211  double *ymbot;
6212  double top_scale, bot_scale;
6213  double angle, top_angle, bot_angle;
6214  double xmse, ymse;
6215  double xrms, top_xrms, bot_xrms;
6216  double yrms, top_yrms, bot_yrms;
6217  int nslits;
6218  int nmaskslits, use_pattern;
6219  int found_slits, found_slits_top, found_slits_bot;
6220  int i;
6221  cpl_table *positions;
6222  cpl_error_code error;
6223 
6224  cpl_vector *point;
6225  double *dpoint;
6226  cpl_vector *xpos;
6227  cpl_vector *ypos;
6228  cpl_vector *xmpos;
6229  cpl_vector *ympos;
6230  cpl_bivector *mpos;
6231  cpl_polynomial *xpoly = NULL;
6232  cpl_polynomial *ypoly = NULL;
6233  cpl_polynomial *top_xpoly = NULL;
6234  cpl_polynomial *top_ypoly = NULL;
6235  cpl_polynomial *bot_xpoly = NULL;
6236  cpl_polynomial *bot_ypoly = NULL;
6237 
6238  char *msg_multiplex = " ";
6239 
6240 
6241  error = mos_validate_slits(slits);
6242  if (error) {
6243  cpl_msg_error(cpl_func, "CCD slits table validation: %s",
6244  cpl_error_get_message());
6245  cpl_error_set(cpl_func, error);
6246  return NULL;
6247  }
6248 
6249  error = mos_validate_slits(maskslits);
6250  if (error) {
6251  cpl_msg_error(cpl_func, "Mask slits table validation: %s",
6252  cpl_error_get_message());
6253  cpl_error_set(cpl_func, error);
6254  return NULL;
6255  }
6256 
6257  if (1 != cpl_table_has_column(maskslits, "slit_id")) {
6258  cpl_msg_error(cpl_func, "Missing slits identifiers");
6259  cpl_error_set(cpl_func, CPL_ERROR_DATA_NOT_FOUND);
6260  return NULL;
6261  }
6262 
6263  if (CPL_TYPE_INT != cpl_table_get_column_type(maskslits, "slit_id")) {
6264  cpl_msg_error(cpl_func, "Wrong type used for slits identifiers");
6265  cpl_error_set(cpl_func, CPL_ERROR_INVALID_TYPE);
6266  return NULL;
6267  }
6268 
6269  nslits = cpl_table_get_nrow(slits);
6270  nmaskslits = cpl_table_get_nrow(maskslits);
6271 
6272  if (nslits == 0 || nmaskslits == 0) {
6273  cpl_msg_error(cpl_func, "Empty slits table");
6274  cpl_error_set(cpl_func, CPL_ERROR_ILLEGAL_INPUT);
6275  return NULL;
6276  }
6277 
6278  if (nslits > 25 && mos_multiplex < 0) {
6279  cpl_msg_info(cpl_func, "Many slits: using 'fast' pattern matching...");
6280  positions = mos_identify_slits_fast(slits, maskslits, global);
6281  if (positions == NULL)
6282  cpl_error_set_where(cpl_func);
6283  return positions;
6284  }
6285 
6286  /*
6287  * Guarantee that both input tables are sorted in the same way
6288  */
6289 
6290  sort_col = cpl_propertylist_new();
6291  cpl_propertylist_append_bool(sort_col, "ytop", 1);
6292  cpl_table_sort(slits, sort_col);
6293  cpl_table_sort(maskslits, sort_col);
6294  cpl_propertylist_delete(sort_col);
6295 
6296  /*
6297  * First we handle all the special cases (too few slits...)
6298  */
6299 
6300  if (nslits < 3 && nmaskslits > nslits) {
6301 
6302  /*
6303  * If there are just 1 or 2 slits on the CCD, and more on the
6304  * mask, the ambiguity cannot be solved, and an error is returned.
6305  * This is a case that must be solved with a first-guess relation
6306  * between mask and CCD.
6307  */
6308 
6309  if (nslits > 1)
6310  cpl_msg_warning(cpl_func, "Cannot match the %d found CCD slits "
6311  "with the %d mask slits: process will continue "
6312  "using the detected CCD slits positions", nslits,
6313  nmaskslits);
6314  else
6315  cpl_msg_warning(cpl_func, "Cannot match the found CCD slit with "
6316  "the %d mask slits: process will continue using "
6317  "the detected CCD slit position", nmaskslits);
6318  return NULL;
6319  }
6320 
6321  if (nmaskslits < 3 && nslits > nmaskslits) {
6322 
6323  /*
6324  * If there are less than 3 slits on the mask the ambiguity cannot
6325  * be solved, and an error is returned. This is a case that must
6326  * be solved with a first-guess relation between mask and CCD.
6327  */
6328 
6329  cpl_msg_warning(cpl_func, "Cannot match the %d found CCD slits with "
6330  "the %d mask slits: process will continue using "
6331  "the detected CCD slits positions", nslits,
6332  nmaskslits);
6333  return NULL;
6334  }
6335 
6336  /*
6337  * Pattern matching related operations begin here. Two pattern
6338  * matching will be run, one based on the "top" and another one
6339  * based on the "bottom" slit coordinates. The one with the
6340  * smallest rms in the Y coordinate will be chosen.
6341  */
6342 
6343  xtop = cpl_table_get_data_double(slits, "xtop");
6344  ytop = cpl_table_get_data_double(slits, "ytop");
6345  xmtop = cpl_table_get_data_double(maskslits, "xtop");
6346  ymtop = cpl_table_get_data_double(maskslits, "ytop");
6347 
6348  xbot = cpl_table_get_data_double(slits, "xbottom");
6349  ybot = cpl_table_get_data_double(slits, "ybottom");
6350  xmbot = cpl_table_get_data_double(maskslits, "xbottom");
6351  ymbot = cpl_table_get_data_double(maskslits, "ybottom");
6352 
6353  top_data = cpl_matrix_new(2, nslits);
6354  top_pattern = cpl_matrix_new(2, nmaskslits);
6355  bot_data = cpl_matrix_new(2, nslits);
6356  bot_pattern = cpl_matrix_new(2, nmaskslits);
6357 
6358  for (i = 0; i < nslits; i++)
6359  cpl_matrix_set(top_data, 0, i, xtop[i]);
6360 
6361  for (i = 0; i < nslits; i++)
6362  cpl_matrix_set(top_data, 1, i, ytop[i]);
6363 
6364  for (i = 0; i < nmaskslits; i++)
6365  cpl_matrix_set(top_pattern, 0, i, xmtop[i]);
6366 
6367  for (i = 0; i < nmaskslits; i++)
6368  cpl_matrix_set(top_pattern, 1, i, ymtop[i]);
6369 
6370  for (i = 0; i < nslits; i++)
6371  cpl_matrix_set(bot_data, 0, i, xbot[i]);
6372 
6373  for (i = 0; i < nslits; i++)
6374  cpl_matrix_set(bot_data, 1, i, ybot[i]);
6375 
6376  for (i = 0; i < nmaskslits; i++)
6377  cpl_matrix_set(bot_pattern, 0, i, xmbot[i]);
6378 
6379  for (i = 0; i < nmaskslits; i++)
6380  cpl_matrix_set(bot_pattern, 1, i, ymbot[i]);
6381 
6382  if (nmaskslits > nslits)
6383  use_pattern = nslits;
6384  else
6385  use_pattern = nmaskslits;
6386 
6387  top_ident = cpl_ppm_match_points(top_data, nslits, 1.0, top_pattern,
6388  use_pattern, 0.0, 0.1, 5, &top_mdata,
6389  &top_mpattern, &top_scale, &top_angle);
6390 
6391  bot_ident = cpl_ppm_match_points(bot_data, nslits, 1.0, bot_pattern,
6392  use_pattern, 0.0, 0.1, 5, &bot_mdata,
6393  &bot_mpattern, &bot_scale, &bot_angle);
6394 
6395  if (top_ident == NULL && bot_ident == NULL) {
6396  cpl_msg_warning(cpl_func, "Pattern matching failure: cannot match "
6397  "the %d found CCD slits with the %d mask slits: "
6398  "process will continue using the detected CCD "
6399  "slits positions", nslits, nmaskslits);
6400  return NULL;
6401  }
6402 
6403  found_slits_top = 0;
6404  found_slits_bot = 0;
6405  if (top_ident && bot_ident) {
6406  cpl_msg_info(cpl_func, "Median platescale: %f +/- %f pixel/mm",
6407  (top_scale + bot_scale) / 2, fabs(top_scale - bot_scale));
6408  cpl_msg_info(cpl_func, "Median rotation: %f +/- %f degrees",
6409  (top_angle + bot_angle) / 2, fabs(top_angle - bot_angle));
6410  if (fabs(top_angle) < fabs(bot_angle))
6411  angle = fabs(top_angle);
6412  else
6413  angle = fabs(bot_angle);
6414  found_slits_top = cpl_matrix_get_ncol(top_mdata);
6415  found_slits_bot = cpl_matrix_get_ncol(bot_mdata);
6416  }
6417  else if (top_ident) {
6418  cpl_msg_info(cpl_func, "Median platescale: %f pixel/mm", top_scale);
6419  cpl_msg_info(cpl_func, "Median rotation: %f degrees", top_angle);
6420  angle = fabs(top_angle);
6421  found_slits_top = cpl_matrix_get_ncol(top_mdata);
6422  }
6423  else {
6424  cpl_msg_info(cpl_func, "Median platescale: %f pixel/mm", bot_scale);
6425  cpl_msg_info(cpl_func, "Median rotation: %f degrees", bot_angle);
6426  angle = fabs(bot_angle);
6427  found_slits_bot = cpl_matrix_get_ncol(bot_mdata);
6428  }
6429 
6430  cpl_array_delete(top_ident);
6431  cpl_array_delete(bot_ident);
6432 
6433  if (angle > 4.0) {
6434  cpl_msg_warning(cpl_func, "Uncertain pattern matching: the rotation "
6435  "angle is expected to be around zero. This match is "
6436  "rejected: the process will continue using the %d "
6437  "detected CCD slits positions", nslits);
6438  return NULL;
6439  }
6440 
6441  found_slits = found_slits_top;
6442  if (found_slits < found_slits_bot)
6443  found_slits = found_slits_bot; /* Max value */
6444 
6445  if (found_slits < 4) {
6446  cpl_msg_warning(cpl_func,
6447  "Too few safely identified slits: %d out of %d "
6448  "candidates (%d expected). Process will continue "
6449  "using the detected CCD slits positions", found_slits,
6450  nslits, nmaskslits);
6451  return NULL;
6452  }
6453 
6454  cpl_msg_info(cpl_func, "Preliminary identified slits: %d out of %d "
6455  "candidates\n(%d expected)", found_slits, nslits,
6456  nmaskslits);
6457 
6458  if (found_slits_top < 4)
6459  found_slits_top = 0;
6460 
6461  if (found_slits_bot < 4)
6462  found_slits_bot = 0;
6463 
6464  /*
6465  * Now for each set select the points of the identified slits, and fit
6466  * two bivariate polynomials to determine a first approximate relation
6467  * between positions on the mask and positions on the CCD.
6468  */
6469 
6470  for (i = 0; i < 2; i++) {
6471  cpl_size mindeg2d[] = {0, 0};
6472  cpl_size maxdeg2d[2];
6473  cpl_vector * fitresidual;
6474  if (i) {
6475  found_slits = found_slits_top;
6476  mdata = top_mdata;
6477  mpattern = top_mpattern;
6478  }
6479  else {
6480  found_slits = found_slits_bot;
6481  mdata = bot_mdata;
6482  mpattern = bot_mpattern;
6483  }
6484 
6485  if (found_slits == 0)
6486  continue;
6487  else if (found_slits < 10)
6488  maxdeg2d[0] = maxdeg2d[1] = 1;
6489  else
6490  maxdeg2d[0] = maxdeg2d[1] = 2;
6491 
6492  xpos = cpl_vector_wrap(found_slits,
6493  cpl_matrix_get_data(mdata) );
6494  ypos = cpl_vector_wrap(found_slits,
6495  cpl_matrix_get_data(mdata) + found_slits);
6496  xmpos = cpl_vector_wrap(found_slits,
6497  cpl_matrix_get_data(mpattern) );
6498  ympos = cpl_vector_wrap(found_slits,
6499  cpl_matrix_get_data(mpattern) + found_slits);
6500  mpos = cpl_bivector_wrap_vectors(xmpos, ympos);
6501  fitresidual = cpl_vector_new(cpl_vector_get_size(xpos));
6502  xpoly = cpl_polynomial_new(2);
6503  cpl_polynomial_fit(xpoly, mpattern, NULL, xpos, NULL, CPL_FALSE, mindeg2d, maxdeg2d);
6504  cpl_vector_fill_polynomial_fit_residual(fitresidual, xpos, NULL, xpoly, mpattern, NULL);
6505  xmse = cpl_vector_product(fitresidual, fitresidual)
6506  / cpl_vector_get_size(fitresidual);
6507  ypoly = cpl_polynomial_new(2);
6508  cpl_polynomial_fit(ypoly, mpattern, NULL, ypos, NULL, CPL_FALSE, mindeg2d, maxdeg2d);
6509  cpl_vector_fill_polynomial_fit_residual(fitresidual, ypos, NULL, ypoly, mpattern, NULL);
6510  ymse = cpl_vector_product(fitresidual, fitresidual)
6511  / cpl_vector_get_size(fitresidual);
6512 
6513  cpl_bivector_unwrap_vectors(mpos);
6514  cpl_vector_unwrap(xpos);
6515  cpl_vector_unwrap(ypos);
6516  cpl_vector_unwrap(xmpos);
6517  cpl_vector_unwrap(ympos);
6518  cpl_matrix_delete(mdata);
6519  cpl_matrix_delete(mpattern);
6520  cpl_vector_delete(fitresidual);
6521 
6522  if (i) {
6523  top_xpoly = xpoly;
6524  top_ypoly = ypoly;
6525  top_xrms = sqrt(xmse*2*maxdeg2d[0]/(found_slits - 1));
6526  top_yrms = sqrt(ymse*2*maxdeg2d[0]/(found_slits - 1));
6527  }
6528  else {
6529  bot_xpoly = xpoly;
6530  bot_ypoly = ypoly;
6531  bot_xrms = sqrt(xmse*2*maxdeg2d[0]/(found_slits - 1));
6532  bot_yrms = sqrt(ymse*2*maxdeg2d[0]/(found_slits - 1));
6533  }
6534  }
6535 
6536  if (top_xpoly && bot_xpoly) {
6537  if (top_xrms < bot_xrms) { /* top X solution wins... */
6538  xrms = top_xrms;
6539  xpoly = top_xpoly;
6540  cpl_polynomial_delete(bot_xpoly);
6541  }
6542  else { /* bottom X solution wins... */
6543  xrms = bot_xrms;
6544  xpoly = bot_xpoly;
6545  cpl_polynomial_delete(top_xpoly);
6546  }
6547  }
6548  else if (top_xpoly) {
6549  xrms = top_xrms;
6550  xpoly = top_xpoly;
6551  }
6552  else {
6553  xrms = bot_xrms;
6554  xpoly = bot_xpoly;
6555  }
6556 
6557  if (top_ypoly && bot_ypoly) {
6558  if (top_yrms < bot_yrms) { /* top Y solution wins... */
6559  yrms = top_yrms;
6560  ypoly = top_ypoly;
6561  cpl_polynomial_delete(bot_ypoly);
6562  }
6563  else { /* bottom Y solution wins... */
6564  yrms = bot_yrms;
6565  ypoly = bot_ypoly;
6566  cpl_polynomial_delete(top_ypoly);
6567  }
6568  }
6569  else if (top_ypoly) {
6570  yrms = top_yrms;
6571  ypoly = top_ypoly;
6572  }
6573  else {
6574  yrms = bot_yrms;
6575  ypoly = bot_ypoly;
6576  }
6577 
6578  if (xpoly == NULL || ypoly == NULL) {
6579  cpl_msg_warning(cpl_func, "Fit failure: the accuracy of the "
6580  "identified slits positions cannot be improved.");
6581  cpl_polynomial_delete(xpoly);
6582  cpl_polynomial_delete(ypoly);
6583  cpl_error_reset();
6584  return NULL;
6585  }
6586 
6587  cpl_msg_info(cpl_func,
6588  "Fit successful: X rms = %.3g, Y rms = %.3g (pixel)",
6589  xrms, yrms);
6590 
6591  if (global) {
6592  write_global_distortion(global, 0, xpoly);
6593  write_global_distortion(global, 7, ypoly);
6594  }
6595 
6596  /*
6597  * The fit was successful: use the polynomials to obtain a new
6598  * position table with the improved positions of the slits
6599  */
6600 
6601  positions = cpl_table_duplicate(maskslits);
6602  cpl_table_duplicate_column(positions, "xmtop", positions, "xtop");
6603  cpl_table_duplicate_column(positions, "ymtop", positions, "ytop");
6604  cpl_table_duplicate_column(positions, "xmbottom", positions, "xbottom");
6605  cpl_table_duplicate_column(positions, "ymbottom", positions, "ybottom");
6606 
6607  point = cpl_vector_new(2);
6608  dpoint = cpl_vector_get_data(point);
6609 
6610  for (i = 0; i < nmaskslits; i++) {
6611  double position_x;
6612  double position_y;
6613 
6614  dpoint[0] = cpl_table_get_double(positions, "xmtop", i, NULL);
6615  dpoint[1] = cpl_table_get_double(positions, "ymtop", i, NULL);
6616  position_x = cpl_polynomial_eval(xpoly, point);
6617 // if (mos_multiplex >= 0) {
6618 // if (mos_multiplex != ((int)floor(position)) / mos_region_size) {
6619 // cpl_table_unselect_row(positions, i);
6620 // continue;
6621 // }
6622 // }
6623  cpl_table_set_double(positions, "xtop", i, position_x);
6624  position_y = cpl_polynomial_eval(ypoly, point);
6625  cpl_table_set_double(positions, "ytop", i, position_y);
6626  dpoint[0] = cpl_table_get_double(positions, "xmbottom", i, NULL);
6627  dpoint[1] = cpl_table_get_double(positions, "ymbottom", i, NULL);
6628  position_x = cpl_polynomial_eval(xpoly, point);
6629  cpl_table_set_double(positions, "xbottom", i, position_x);
6630  position_y = cpl_polynomial_eval(ypoly, point);
6631  cpl_table_set_double(positions, "ybottom", i, position_y);
6632  }
6633 
6634 // if (mos_multiplex >= 0) {
6635 // cpl_table_not_selected(positions);
6636 // cpl_table_erase_selected(positions);
6637 // nmaskslits = cpl_table_get_nrow(positions);
6638 // }
6639 
6640  cpl_vector_delete(point);
6641  cpl_polynomial_delete(xpoly);
6642  cpl_polynomial_delete(ypoly);
6643 
6644  cpl_table_erase_column(positions, "xmtop");
6645  cpl_table_erase_column(positions, "ymtop");
6646  cpl_table_erase_column(positions, "xmbottom");
6647  cpl_table_erase_column(positions, "ymbottom");
6648 
6649  if (mos_multiplex >= 0) {
6650  msg_multiplex =
6651  cpl_sprintf("in the CCD section between %d and %d pixel",
6652  mos_multiplex * mos_region_size,
6653  (mos_multiplex + 1) * mos_region_size);
6654  }
6655 
6656  if (nmaskslits > nslits)
6657  cpl_msg_info(cpl_func,
6658  "Finally identified slits: %d out of %d expected %s\n"
6659  "(%d recovered)", nmaskslits, nmaskslits, msg_multiplex,
6660  nmaskslits - nslits);
6661  else if (nmaskslits < nslits)
6662  cpl_msg_info(cpl_func,
6663  "Finally identified slits: %d out of %d expected %s\n"
6664  "(%d rejected)", nmaskslits, nmaskslits, msg_multiplex,
6665  nslits - nmaskslits);
6666  else
6667  cpl_msg_info(cpl_func,
6668  "Finally identified slits: %d out of %d expected %s",
6669  nmaskslits, nmaskslits, msg_multiplex);
6670 
6671  if (mos_multiplex >= 0) {
6672  cpl_free(msg_multiplex);
6673  }
6674 
6675  return positions;
6676 
6677 }
6678 
6679 
6680 cpl_table *mos_identify_slits_fast(cpl_table *slits, cpl_table *maskslits,
6681  cpl_table *global)
6682 {
6683  const char *func = "mos_identify_slits_fast";
6684 
6685  cpl_propertylist *sort_col;
6686  cpl_table *positions;
6687  cpl_vector *scales;
6688  cpl_vector *angles;
6689  cpl_vector *point;
6690  cpl_vector *xpos;
6691  cpl_vector *ypos;
6692  cpl_vector *xmpos;
6693  cpl_vector *ympos;
6694  cpl_bivector *mpos;
6695  cpl_polynomial *xpoly = NULL;
6696  cpl_polynomial *ypoly = NULL;
6697  cpl_error_code error;
6698  int nslits;
6699  int nmaskslits;
6700  int found_slits;
6701  int i, j, k;
6702 
6703  double dist1, dist2, dist3, dist, mindist;
6704  double scale, minscale, maxscale;
6705  double angle, minangle, maxangle;
6706  double *dscale;
6707  double *dangle;
6708  double *dpoint;
6709  double *xtop;
6710  double *ytop;
6711  double *xbottom;
6712  double *ybottom;
6713  double *xcenter;
6714  double *ycenter;
6715  double *xpseudo;
6716  double *ypseudo;
6717  int *slit_id;
6718  double *xmtop;
6719  double *ymtop;
6720  double *xmbottom;
6721  double *ymbottom;
6722  double *xmcenter;
6723  double *ymcenter;
6724  double *xmpseudo;
6725  double *ympseudo;
6726  double xmse, ymse;
6727  int *mslit_id;
6728  int *good;
6729  int minpos;
6730  int degree;
6731 
6732  double sradius = 0.01; /* Candidate input argument... */
6733  int in_sradius;
6734 
6735  double pi = 3.14159265358979323846;
6736 
6737 
6738  error = mos_validate_slits(slits);
6739  if (error) {
6740  cpl_msg_error(func, "CCD slits table validation: %s",
6741  cpl_error_get_message());
6742  cpl_error_set(func, error);
6743  return NULL;
6744  }
6745 
6746  error = mos_validate_slits(maskslits);
6747  if (error) {
6748  cpl_msg_error(func, "Mask slits table validation: %s",
6749  cpl_error_get_message());
6750  cpl_error_set(func, error);
6751  return NULL;
6752  }
6753 
6754  if (1 != cpl_table_has_column(maskslits, "slit_id")) {
6755  cpl_msg_error(func, "Missing slits identifiers");
6756  cpl_error_set(func, CPL_ERROR_DATA_NOT_FOUND);
6757  return NULL;
6758  }
6759 
6760  if (CPL_TYPE_INT != cpl_table_get_column_type(maskslits, "slit_id")) {
6761  cpl_msg_error(func, "Wrong type used for slits identifiers");
6762  cpl_error_set(func, CPL_ERROR_INVALID_TYPE);
6763  return NULL;
6764  }
6765 
6766  nslits = cpl_table_get_nrow(slits);
6767  nmaskslits = cpl_table_get_nrow(maskslits);
6768 
6769  if (nslits == 0 || nmaskslits == 0) {
6770  cpl_msg_error(func, "Empty slits table");
6771  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
6772  return NULL;
6773  }
6774 
6775 
6776  /*
6777  * Compute middle point coordinates for each slit listed in both
6778  * input tables.
6779  */
6780 
6781  if (cpl_table_has_column(slits, "xcenter"))
6782  cpl_table_erase_column(slits, "xcenter");
6783 
6784  if (cpl_table_has_column(slits, "ycenter"))
6785  cpl_table_erase_column(slits, "ycenter");
6786 
6787  if (cpl_table_has_column(maskslits, "xcenter"))
6788  cpl_table_erase_column(maskslits, "xcenter");
6789 
6790  if (cpl_table_has_column(maskslits, "ycenter"))
6791  cpl_table_erase_column(maskslits, "ycenter");
6792 
6793  cpl_table_duplicate_column(slits, "xcenter", slits, "xtop");
6794  cpl_table_add_columns(slits, "xcenter", "xbottom");
6795  cpl_table_divide_scalar(slits, "xcenter", 2.0);
6796  cpl_table_duplicate_column(slits, "ycenter", slits, "ytop");
6797  cpl_table_add_columns(slits, "ycenter", "ybottom");
6798  cpl_table_divide_scalar(slits, "ycenter", 2.0);
6799 
6800  cpl_table_duplicate_column(maskslits, "xcenter", maskslits, "xtop");
6801  cpl_table_add_columns(maskslits, "xcenter", "xbottom");
6802  cpl_table_divide_scalar(maskslits, "xcenter", 2.0);
6803  cpl_table_duplicate_column(maskslits, "ycenter", maskslits, "ytop");
6804  cpl_table_add_columns(maskslits, "ycenter", "ybottom");
6805  cpl_table_divide_scalar(maskslits, "ycenter", 2.0);
6806 
6807 
6808  /*
6809  * Guarantee that both input tables are sorted in the same way
6810  */
6811 
6812  sort_col = cpl_propertylist_new();
6813  cpl_propertylist_append_bool(sort_col, "ycenter", 1);
6814  cpl_table_sort(slits, sort_col);
6815  cpl_table_sort(maskslits, sort_col);
6816  cpl_propertylist_delete(sort_col);
6817 
6818 
6819  /*
6820  * First we handle all the special cases (too few slits...)
6821  */
6822 
6823  if (nslits < 3 && nmaskslits > nslits) {
6824 
6825  /*
6826  * If there are just 1 or 2 slits on the CCD, and more on the
6827  * mask, the ambiguity cannot be solved, and an error is returned.
6828  * This is a case that must be solved with a first-guess relation
6829  * between mask and CCD.
6830  */
6831 
6832  if (nslits > 1)
6833  cpl_msg_warning(func, "Cannot match the found CCD slit with the "
6834  "%d mask slits: process will continue using the "
6835  "detected CCD slit position", nmaskslits);
6836  else
6837  cpl_msg_warning(func, "Cannot match the %d found CCD slits with "
6838  "the %d mask slits: process will continue using "
6839  "the detected CCD slits positions", nslits,
6840  nmaskslits);
6841  return NULL;
6842  }
6843 
6844  if (nslits <= 3 && nslits == nmaskslits) {
6845 
6846  cpl_msg_warning(func, "Too few slits (%d) on mask and CCD", nslits);
6847  cpl_msg_warning(func, "Their detected positions are left unchanged");
6848 
6849  /*
6850  * If there are just up to 3 slits, both on the mask and on the CCD,
6851  * we can reasonably hope that those slits were found, and accept
6852  * that their positions on the CCD cannot be improved. We prepare
6853  * therefore an output position table containing the slits with
6854  * their original positions. We can however give an estimate of
6855  * the platescale if there is more than one slit.
6856  */
6857 
6858  positions = cpl_table_duplicate(slits);
6859  cpl_table_erase_column(slits, "xcenter");
6860  cpl_table_erase_column(slits, "ycenter");
6861  cpl_table_duplicate_column(positions, "xmtop", maskslits, "xtop");
6862  cpl_table_duplicate_column(positions, "ymtop", maskslits, "ytop");
6863  cpl_table_duplicate_column(positions, "xmbottom", maskslits, "xbottom");
6864  cpl_table_duplicate_column(positions, "ymbottom", maskslits, "ybottom");
6865  cpl_table_duplicate_column(positions, "xmcenter", maskslits, "xcenter");
6866  cpl_table_duplicate_column(positions, "ymcenter", maskslits, "ycenter");
6867  cpl_table_duplicate_column(positions, "slit_id", maskslits, "slit_id");
6868  cpl_table_erase_column(maskslits, "xcenter");
6869  cpl_table_erase_column(maskslits, "ycenter");
6870 
6871  if (nslits > 1) {
6872  xcenter = cpl_table_get_data_double(positions, "xcenter");
6873  ycenter = cpl_table_get_data_double(positions, "ycenter");
6874  xmcenter = cpl_table_get_data_double(positions, "xmcenter");
6875  ymcenter = cpl_table_get_data_double(positions, "ymcenter");
6876 
6877  dist1 = (xcenter[0] - xcenter[1])*(xcenter[0] - xcenter[1])
6878  + (ycenter[0] - ycenter[1])*(ycenter[0] - ycenter[1]);
6879  dist2 = (xmcenter[0] - xmcenter[1])*(xmcenter[0] - xmcenter[1])
6880  + (ymcenter[0] - ymcenter[1])*(ymcenter[0] - ymcenter[1]);
6881  scale = sqrt(dist1/dist2);
6882 
6883  if (nslits == 3) {
6884  dist1 = (xcenter[1] - xcenter[2])*(xcenter[1] - xcenter[2])
6885  + (ycenter[1] - ycenter[2])*(ycenter[1] - ycenter[2]);
6886  dist2 = (xmcenter[1] - xmcenter[2])*(xmcenter[1] - xmcenter[2])
6887  + (ymcenter[1] - ymcenter[2])*(ymcenter[1] - ymcenter[2]);
6888  scale += sqrt(dist1/dist2);
6889  scale /= 2;
6890  }
6891 
6892  cpl_msg_info(func, "Platescale: %f pixel/mm", scale);
6893  }
6894 
6895  return positions;
6896  }
6897 
6898  if (nmaskslits < 3 && nslits > nmaskslits) {
6899 
6900  /*
6901  * If there are less than 3 slits on the mask the ambiguity cannot
6902  * be solved, and an error is returned. This is a case that must
6903  * be solved with a first-guess relation between mask and CCD.
6904  */
6905 
6906  cpl_msg_warning(func, "Cannot match the %d found CCD slits with "
6907  "the %d mask slits: process will continue using "
6908  "the detected CCD slits positions", nslits,
6909  nmaskslits);
6910  return NULL;
6911  }
6912 
6913 
6914  /*
6915  * At this point of the program all the region of the plane
6916  * (nslits, nmaskslits) where either or both mask and CCD display
6917  * less than 3 slits are handled in some way. It would be better
6918  * to add in this place a special handling for identifying slits
6919  * in case of a very reduced number of slits (say, below 6).
6920  * It is also clear that if there are many more slits on the
6921  * mask than on the CCD, or many more on the CCD than on the
6922  * mask, something went deeply wrong with the preliminary
6923  * wavelength calibration. Such cases should be handled with
6924  * a _complete_ pattern-recognition algorithm based on the
6925  * construction of all possible triangles. For the moment,
6926  * we go directly to the limited pattern-recognition applied
6927  * below, based on triangles build only for consecutive slits.
6928  * This is reasonably safe, since the preliminary wavelength
6929  * calibration performed by mos_identify_peaks() is generally
6930  * robust.
6931  */
6932 
6933 
6934  /*
6935  * Compute (X, Y) coordinates on pseudo-plane describing the
6936  * different position ratios of successive slits, in both
6937  * input tables.
6938  */
6939 
6940  if (cpl_table_has_column(slits, "xpseudo"))
6941  cpl_table_erase_column(slits, "xpseudo");
6942 
6943  if (cpl_table_has_column(slits, "ypseudo"))
6944  cpl_table_erase_column(slits, "ypseudo");
6945 
6946  if (cpl_table_has_column(maskslits, "xpseudo"))
6947  cpl_table_erase_column(maskslits, "xpseudo");
6948 
6949  if (cpl_table_has_column(maskslits, "ypseudo"))
6950  cpl_table_erase_column(maskslits, "ypseudo");
6951 
6952  cpl_table_duplicate_column(slits, "xpseudo", slits, "xcenter");
6953  cpl_table_duplicate_column(slits, "ypseudo", slits, "ycenter");
6954 
6955  xcenter = cpl_table_get_data_double(slits, "xcenter");
6956  ycenter = cpl_table_get_data_double(slits, "ycenter");
6957  xpseudo = cpl_table_get_data_double(slits, "xpseudo");
6958  ypseudo = cpl_table_get_data_double(slits, "ypseudo");
6959 
6960  for (i = 1; i < nslits - 1; i++) {
6961  dist1 = (xcenter[i-1] - xcenter[i]) * (xcenter[i-1] - xcenter[i])
6962  + (ycenter[i-1] - ycenter[i]) * (ycenter[i-1] - ycenter[i]);
6963  dist2 = (xcenter[i-1] - xcenter[i+1]) * (xcenter[i-1] - xcenter[i+1])
6964  + (ycenter[i-1] - ycenter[i+1]) * (ycenter[i-1] - ycenter[i+1]);
6965  dist3 = (xcenter[i] - xcenter[i+1]) * (xcenter[i] - xcenter[i+1])
6966  + (ycenter[i] - ycenter[i+1]) * (ycenter[i] - ycenter[i+1]);
6967  xpseudo[i] = sqrt(dist1/dist2);
6968  ypseudo[i] = sqrt(dist3/dist2);
6969  }
6970 
6971  cpl_table_set_invalid(slits, "xpseudo", 0);
6972  cpl_table_set_invalid(slits, "xpseudo", nslits-1);
6973  cpl_table_set_invalid(slits, "ypseudo", 0);
6974  cpl_table_set_invalid(slits, "ypseudo", nslits-1);
6975 
6976  cpl_table_duplicate_column(maskslits, "xpseudo", maskslits, "xcenter");
6977  cpl_table_duplicate_column(maskslits, "ypseudo", maskslits, "ycenter");
6978 
6979  xcenter = cpl_table_get_data_double(maskslits, "xcenter");
6980  ycenter = cpl_table_get_data_double(maskslits, "ycenter");
6981  xmpseudo = cpl_table_get_data_double(maskslits, "xpseudo");
6982  ympseudo = cpl_table_get_data_double(maskslits, "ypseudo");
6983 
6984  for (i = 1; i < nmaskslits - 1; i++) {
6985  dist1 = (xcenter[i-1] - xcenter[i])*(xcenter[i-1] - xcenter[i])
6986  + (ycenter[i-1] - ycenter[i])*(ycenter[i-1] - ycenter[i]);
6987  dist2 = (xcenter[i-1] - xcenter[i+1])*(xcenter[i-1] - xcenter[i+1])
6988  + (ycenter[i-1] - ycenter[i+1])*(ycenter[i-1] - ycenter[i+1]);
6989  dist3 = (xcenter[i] - xcenter[i+1])*(xcenter[i] - xcenter[i+1])
6990  + (ycenter[i] - ycenter[i+1])*(ycenter[i] - ycenter[i+1]);
6991  xmpseudo[i] = sqrt(dist1/dist2);
6992  ympseudo[i] = sqrt(dist3/dist2);
6993  }
6994 
6995  cpl_table_set_invalid(maskslits, "xpseudo", 0);
6996  cpl_table_set_invalid(maskslits, "xpseudo", nmaskslits-1);
6997  cpl_table_set_invalid(maskslits, "ypseudo", 0);
6998  cpl_table_set_invalid(maskslits, "ypseudo", nmaskslits-1);
6999 
7000 
7001  /*
7002  * For each (X, Y) on the pseudo-plane related to the mask positions,
7003  * find the closest (X, Y) on the pseudo-plane related to the CCD
7004  * positions. If the closest point is closer than a given search
7005  * radius, a triangle has been found, and 3 slits are identified.
7006  * However, if more than one point is found within the search
7007  * radius, we have an ambiguity and this is rejected.
7008  */
7009 
7010  if (cpl_table_has_column(slits, "slit_id"))
7011  cpl_table_erase_column(slits, "slit_id");
7012  cpl_table_new_column(slits, "slit_id", CPL_TYPE_INT);
7013  slit_id = cpl_table_get_data_int(maskslits, "slit_id");
7014 
7015  for (i = 1; i < nmaskslits - 1; i++) {
7016  in_sradius = 0;
7017  mindist = (xmpseudo[i] - xpseudo[1]) * (xmpseudo[i] - xpseudo[1])
7018  + (ympseudo[i] - ypseudo[1]) * (ympseudo[i] - ypseudo[1]);
7019  minpos = 1;
7020  if (mindist < sradius*sradius)
7021  in_sradius++;
7022  for (j = 2; j < nslits - 1; j++) {
7023  dist = (xmpseudo[i] - xpseudo[j]) * (xmpseudo[i] - xpseudo[j])
7024  + (ympseudo[i] - ypseudo[j]) * (ympseudo[i] - ypseudo[j]);
7025  if (dist < sradius*sradius)
7026  in_sradius++;
7027  if (in_sradius > 1) /* More than one triangle within radius */
7028  break;
7029  if (mindist > dist) {
7030  mindist = dist;
7031  minpos = j;
7032  }
7033  }
7034 
7035  mindist = sqrt(mindist);
7036 
7037  if (mindist < sradius && in_sradius == 1) {
7038  cpl_table_set_int(slits, "slit_id", minpos-1, slit_id[i-1]);
7039  cpl_table_set_int(slits, "slit_id", minpos, slit_id[i]);
7040  cpl_table_set_int(slits, "slit_id", minpos+1, slit_id[i+1]);
7041  }
7042  }
7043 
7044 
7045  /*
7046  * At this point, the slit_id column contains invalid elements
7047  * corresponding to unidentified slits.
7048  */
7049 
7050  found_slits = nslits - cpl_table_count_invalid(slits, "slit_id");
7051 
7052  if (found_slits < 3) {
7053  cpl_msg_warning(func, "Too few preliminarily identified slits: "
7054  "%d out of %d", found_slits, nslits);
7055  if (nslits == nmaskslits) {
7056  cpl_msg_warning(func, "(this is not an error, it could be caused "
7057  "by a mask with regularly located slits)");
7058  cpl_msg_warning(func, "The detected slits positions are left "
7059  "unchanged");
7060 
7061  /*
7062  * If there are less than 3 identified slits, this is probably
7063  * a mask with regularly spaced slits (leading to an ambiguous
7064  * pattern). Only in the case all expected slits appear to have
7065  * been found on the CCD we can proceed...
7066  */
7067 
7068  cpl_table_erase_column(slits, "slit_id");
7069  cpl_table_erase_column(slits, "xpseudo");
7070  cpl_table_erase_column(slits, "ypseudo");
7071  positions = cpl_table_duplicate(slits);
7072  cpl_table_erase_column(slits, "xcenter");
7073  cpl_table_erase_column(slits, "ycenter");
7074 
7075  cpl_table_erase_column(maskslits, "xpseudo");
7076  cpl_table_erase_column(maskslits, "ypseudo");
7077  cpl_table_duplicate_column(positions, "xmtop",
7078  maskslits, "xtop");
7079  cpl_table_duplicate_column(positions, "ymtop",
7080  maskslits, "ytop");
7081  cpl_table_duplicate_column(positions, "xmbottom",
7082  maskslits, "xbottom");
7083  cpl_table_duplicate_column(positions, "ymbottom",
7084  maskslits, "ybottom");
7085  cpl_table_duplicate_column(positions, "xmcenter",
7086  maskslits, "xcenter");
7087  cpl_table_duplicate_column(positions, "ymcenter",
7088  maskslits, "ycenter");
7089  cpl_table_duplicate_column(positions, "slit_id",
7090  maskslits, "slit_id");
7091  cpl_table_erase_column(maskslits, "xcenter");
7092  cpl_table_erase_column(maskslits, "ycenter");
7093  return positions;
7094  }
7095  else {
7096  cpl_table_erase_column(slits, "slit_id");
7097  cpl_table_erase_column(slits, "xpseudo");
7098  cpl_table_erase_column(slits, "ypseudo");
7099  positions = cpl_table_duplicate(slits);
7100  cpl_table_erase_column(slits, "xcenter");
7101  cpl_table_erase_column(slits, "ycenter");
7102  cpl_msg_warning(func, "(the failure could be caused "
7103  "by a mask with regularly located slits)");
7104  return NULL;
7105  }
7106  }
7107  else {
7108  cpl_msg_info(func, "Preliminarily identified slits: %d out of %d "
7109  "candidates (%d expected)", found_slits, nslits,
7110  nmaskslits);
7111  }
7112 
7113 
7114  /*
7115  * Create a table with the coordinates of the preliminarily identified
7116  * slits, both on CCD and mask. The original order of the slits positions
7117  * is preserved.
7118  */
7119 
7120  positions = cpl_table_new(found_slits);
7121  cpl_table_new_column(positions, "slit_id", CPL_TYPE_INT);
7122  cpl_table_new_column(positions, "xtop", CPL_TYPE_DOUBLE);
7123  cpl_table_new_column(positions, "ytop", CPL_TYPE_DOUBLE);
7124  cpl_table_new_column(positions, "xbottom", CPL_TYPE_DOUBLE);
7125  cpl_table_new_column(positions, "ybottom", CPL_TYPE_DOUBLE);
7126  cpl_table_new_column(positions, "xcenter", CPL_TYPE_DOUBLE);
7127  cpl_table_new_column(positions, "ycenter", CPL_TYPE_DOUBLE);
7128  cpl_table_new_column(positions, "xmtop", CPL_TYPE_DOUBLE);
7129  cpl_table_new_column(positions, "ymtop", CPL_TYPE_DOUBLE);
7130  cpl_table_new_column(positions, "xmbottom", CPL_TYPE_DOUBLE);
7131  cpl_table_new_column(positions, "ymbottom", CPL_TYPE_DOUBLE);
7132  cpl_table_new_column(positions, "xmcenter", CPL_TYPE_DOUBLE);
7133  cpl_table_new_column(positions, "ymcenter", CPL_TYPE_DOUBLE);
7134  cpl_table_new_column(positions, "good", CPL_TYPE_INT);
7135  cpl_table_fill_column_window_int(positions, "good", 0, found_slits, 0);
7136 
7137  slit_id = cpl_table_get_data_int (slits, "slit_id");
7138  xtop = cpl_table_get_data_double(slits, "xtop");
7139  ytop = cpl_table_get_data_double(slits, "ytop");
7140  xbottom = cpl_table_get_data_double(slits, "xbottom");
7141  ybottom = cpl_table_get_data_double(slits, "ybottom");
7142  xcenter = cpl_table_get_data_double(slits, "xcenter");
7143  ycenter = cpl_table_get_data_double(slits, "ycenter");
7144 
7145  mslit_id = cpl_table_get_data_int (maskslits, "slit_id");
7146  xmtop = cpl_table_get_data_double(maskslits, "xtop");
7147  ymtop = cpl_table_get_data_double(maskslits, "ytop");
7148  xmbottom = cpl_table_get_data_double(maskslits, "xbottom");
7149  ymbottom = cpl_table_get_data_double(maskslits, "ybottom");
7150  xmcenter = cpl_table_get_data_double(maskslits, "xcenter");
7151  ymcenter = cpl_table_get_data_double(maskslits, "ycenter");
7152 
7153 
7154  /*
7155  * Transferring the valid slits information to the new table.
7156  * Note that invalid elements are coded as 0 in the internal
7157  * buffer, and this is the way they are recognised and excluded.
7158  */
7159 
7160  k = 0;
7161  cpl_table_fill_invalid_int(slits, "slit_id", 0);
7162  for (i = 0; i < nmaskslits; i++) {
7163  for (j = 0; j < nslits; j++) {
7164  if (slit_id[j] == 0)
7165  continue; /* Skip invalid slit */
7166  if (mslit_id[i] == slit_id[j]) {
7167  cpl_table_set_int (positions, "slit_id", k, slit_id[j]);
7168 
7169  cpl_table_set_double(positions, "xtop", k, xtop[j]);
7170  cpl_table_set_double(positions, "ytop", k, ytop[j]);
7171  cpl_table_set_double(positions, "xbottom", k, xbottom[j]);
7172  cpl_table_set_double(positions, "ybottom", k, ybottom[j]);
7173  cpl_table_set_double(positions, "xcenter", k, xcenter[j]);
7174  cpl_table_set_double(positions, "ycenter", k, ycenter[j]);
7175 
7176  cpl_table_set_double(positions, "xmtop", k, xmtop[i]);
7177  cpl_table_set_double(positions, "ymtop", k, ymtop[i]);
7178  cpl_table_set_double(positions, "xmbottom", k, xmbottom[i]);
7179  cpl_table_set_double(positions, "ymbottom", k, ymbottom[i]);
7180  cpl_table_set_double(positions, "xmcenter", k, xmcenter[i]);
7181  cpl_table_set_double(positions, "ymcenter", k, ymcenter[i]);
7182 
7183  k++;
7184 
7185  break;
7186  }
7187  }
7188  }
7189 
7190  found_slits = k;
7191 
7192  cpl_table_erase_column(slits, "slit_id");
7193  cpl_table_erase_column(slits, "xpseudo");
7194  cpl_table_erase_column(slits, "ypseudo");
7195  cpl_table_erase_column(slits, "xcenter");
7196  cpl_table_erase_column(slits, "ycenter");
7197  cpl_table_erase_column(maskslits, "xpseudo");
7198  cpl_table_erase_column(maskslits, "ypseudo");
7199  cpl_table_erase_column(maskslits, "xcenter");
7200  cpl_table_erase_column(maskslits, "ycenter");
7201 
7202 
7203  /*
7204  * Find the median platescale and rotation angle from the identified
7205  * slits, and then exclude slits outlaying more than 10% from the
7206  * median platescale, and more than 2 degrees from the median
7207  * rotation angle.
7208  */
7209 
7210  ytop = cpl_table_get_data_double(positions, "ytop");
7211  ybottom = cpl_table_get_data_double(positions, "ybottom");
7212  xcenter = cpl_table_get_data_double(positions, "xcenter");
7213  ycenter = cpl_table_get_data_double(positions, "ycenter");
7214  xmcenter = cpl_table_get_data_double(positions, "xmcenter");
7215  ymcenter = cpl_table_get_data_double(positions, "ymcenter");
7216 
7217  scales = cpl_vector_new(found_slits - 1);
7218  dscale = cpl_vector_get_data(scales);
7219  angles = cpl_vector_new(found_slits - 1);
7220  dangle = cpl_vector_get_data(angles);
7221 
7222  for (i = 1; i < found_slits; i++) {
7223  dist1 = (xcenter[i-1] - xcenter[i]) * (xcenter[i-1] - xcenter[i])
7224  + (ycenter[i-1] - ycenter[i]) * (ycenter[i-1] - ycenter[i]);
7225  dist2 = (xmcenter[i-1] - xmcenter[i]) * (xmcenter[i-1] - xmcenter[i])
7226  + (ymcenter[i-1] - ymcenter[i]) * (ymcenter[i-1] - ymcenter[i]);
7227  dscale[i-1] = sqrt(dist1/dist2);
7228  dangle[i-1] = atan2(ycenter[i-1] - ycenter[i],
7229  xcenter[i-1] - xcenter[i])
7230  - atan2(ymcenter[i-1] - ymcenter[i],
7231  xmcenter[i-1] - xmcenter[i]);
7232  dangle[i-1] *= 180;
7233  dangle[i-1] /= pi;
7234  }
7235 
7236  minscale = cpl_vector_get_min(scales);
7237  scale = cpl_vector_get_median_const(scales);
7238  maxscale = cpl_vector_get_max(scales);
7239 
7240  minangle = cpl_vector_get_min(angles);
7241  angle = cpl_vector_get_median_const(angles);
7242  maxangle = cpl_vector_get_max(angles);
7243 
7244  cpl_msg_info(func, "Median platescale: %f pixel/mm", scale);
7245  cpl_msg_info(func, "Minmax platescale: %f, %f pixel/mm",
7246  minscale, maxscale);
7247 
7248  cpl_msg_info(func, "Median rotation: %f degrees", angle);
7249  cpl_msg_info(func, "Minmax rotation: %f, %f degrees",
7250  minangle, maxangle);
7251 
7252  good = cpl_table_get_data_int(positions, "good");
7253 
7254  good[0] = good[found_slits - 1] = 1;
7255  for (i = 1; i < found_slits; i++) {
7256  if (fabs((dscale[i-1] - scale)/scale) < 0.10
7257  && fabs(dangle[i-1] - angle) < 2) {
7258  good[i-1]++;
7259  good[i]++;
7260  }
7261  }
7262 
7263  for (i = 0; i < found_slits; i++) {
7264  if (good[i] < 2)
7265  good[i] = 0;
7266  else
7267  good[i] = 1;
7268  }
7269 
7270 /*
7271  for (i = 1; i < found_slits; i++)
7272  if (fabs((dscale[i-1] - scale)/scale) < 0.10)
7273  good[i-1] = good[i] = 1;
7274 */
7275 
7276 /* DEBUG ************+
7277  for (i = 0; i < found_slits; i++) {
7278  if (good[i]) {
7279  if (i == found_slits - 1)
7280  printf("include slit %d, prev = %f, %f\n",
7281  i, dscale[i-1], dangle[i-1]);
7282  else if (i == 0)
7283  printf("include slit %d, next %f, %f\n",
7284  i, dscale[i], dangle[i]);
7285  else
7286  printf("include slit %d, prev = %f, %f, next %f, %f\n", i,
7287  dscale[i-1], dangle[i-1], dscale[i], dangle[i]);
7288  }
7289  else {
7290  if (i == found_slits - 1)
7291  printf("EXclude slit %d, prev = %f, %f\n",
7292  i, dscale[i-1], dangle[i-1]);
7293  else if (i == 0)
7294  printf("EXclude slit %d, next %f, %f\n",
7295  i, dscale[i], dangle[i]);
7296  else
7297  printf("EXclude slit %d, prev = %f, %f, next %f, %f\n", i,
7298  dscale[i-1], dangle[i-1], dscale[i], dangle[i]);
7299  }
7300  }
7301 +*********** DEBUG */
7302 
7303  cpl_vector_delete(scales);
7304  cpl_vector_delete(angles);
7305 
7306  cpl_table_and_selected_int(positions, "good", CPL_EQUAL_TO, 0);
7307  cpl_table_erase_selected(positions);
7308  cpl_table_erase_column(positions, "good");
7309  found_slits = cpl_table_get_nrow(positions);
7310 
7311  if (found_slits < 4) {
7312 
7313  /*
7314  * If the self-consistency check gives such a poor result,
7315  * something must have gone really wrong in the preliminary
7316  * wavelength calibration... Nothing can be done.
7317  */
7318 
7319  cpl_msg_warning(func, "Too few safely identified slits: %d out of %d "
7320  "candidates (%d expected). Process will continue "
7321  "using the detected CCD slits positions", found_slits,
7322  nslits, nmaskslits);
7323  cpl_table_delete(positions);
7324  return NULL;
7325  }
7326  else {
7327  cpl_msg_info(func, "Safely identified slits: %d out of %d "
7328  "candidates\n(%d expected)", found_slits, nslits,
7329  nmaskslits);
7330  }
7331 
7332 
7333  /*
7334  * Now select the central points of the identified slits, and
7335  * fit two bivariate polynomials to determine a first approximate
7336  * relation between positions on the mask and positions on the CCD.
7337  */
7338 
7339  xpos = cpl_vector_wrap(found_slits,
7340  cpl_table_get_data_double(positions, "xcenter"));
7341  ypos = cpl_vector_wrap(found_slits,
7342  cpl_table_get_data_double(positions, "ycenter"));
7343  xmpos = cpl_vector_wrap(found_slits,
7344  cpl_table_get_data_double(positions, "xmcenter"));
7345  ympos = cpl_vector_wrap(found_slits,
7346  cpl_table_get_data_double(positions, "ymcenter"));
7347  mpos = cpl_bivector_wrap_vectors(xmpos, ympos);
7348 
7349  if (found_slits < 10)
7350  degree = 1;
7351  else
7352  degree = 2;
7353 
7354  xpoly = cpl_polynomial_fit_2d_create(mpos, xpos, degree, &xmse);
7355  if (xpoly != NULL)
7356  ypoly = cpl_polynomial_fit_2d_create(mpos, ypos, degree, &ymse);
7357  cpl_bivector_unwrap_vectors(mpos);
7358  cpl_vector_unwrap(xpos);
7359  cpl_vector_unwrap(ypos);
7360  cpl_vector_unwrap(xmpos);
7361  cpl_vector_unwrap(ympos);
7362  if (ypoly == NULL) {
7363  if (found_slits == nmaskslits) {
7364  cpl_msg_warning(func, "Fit failure: the accuracy of the "
7365  "identified slits positions is not improved.");
7366 
7367  /*
7368  * The determination of the transformation from mask to CCD
7369  * failed, but since all slits have been already identified
7370  * this is not a problem: data can be reduced also without
7371  * such transformation. Simply the accuracy of the slits
7372  * positions cannot be improved.
7373  */
7374 
7375  } else {
7376  cpl_msg_info(func, "Fit failure: not all slits have been "
7377  "identified. Process will continue using "
7378  "the detected CCD slits positions");
7379  }
7380 
7381  cpl_polynomial_delete(xpoly);
7382  return positions;
7383  }
7384 
7385  cpl_msg_info(func, "Fit successful: X rms = %.3g, Y rms = %.3g (pixel)",
7386  sqrt(xmse), sqrt(ymse));
7387 
7388  if (global) {
7389  write_global_distortion(global, 0, xpoly);
7390  write_global_distortion(global, 7, ypoly);
7391  }
7392 
7393  /*
7394  * The fit was successful: use the polynomials to obtain a new
7395  * position table with the improved positions of the slits
7396  */
7397 
7398  cpl_table_delete(positions);
7399 
7400  positions = cpl_table_duplicate(maskslits);
7401  cpl_table_duplicate_column(positions, "xmtop", positions, "xtop");
7402  cpl_table_duplicate_column(positions, "ymtop", positions, "ytop");
7403  cpl_table_duplicate_column(positions, "xmbottom", positions, "xbottom");
7404  cpl_table_duplicate_column(positions, "ymbottom", positions, "ybottom");
7405 
7406  point = cpl_vector_new(2);
7407  dpoint = cpl_vector_get_data(point);
7408 
7409  for (i = 0; i < nmaskslits; i++) {
7410  dpoint[0] = cpl_table_get_double(positions, "xmtop", i, NULL);
7411  dpoint[1] = cpl_table_get_double(positions, "ymtop", i, NULL);
7412  cpl_table_set_double(positions, "xtop", i,
7413  cpl_polynomial_eval(xpoly, point));
7414  cpl_table_set_double(positions, "ytop", i,
7415  cpl_polynomial_eval(ypoly, point));
7416  dpoint[0] = cpl_table_get_double(positions, "xmbottom", i, NULL);
7417  dpoint[1] = cpl_table_get_double(positions, "ymbottom", i, NULL);
7418  cpl_table_set_double(positions, "xbottom", i,
7419  cpl_polynomial_eval(xpoly, point));
7420  cpl_table_set_double(positions, "ybottom", i,
7421  cpl_polynomial_eval(ypoly, point));
7422  }
7423 
7424  cpl_vector_delete(point);
7425  cpl_polynomial_delete(xpoly);
7426  cpl_polynomial_delete(ypoly);
7427 
7428  cpl_table_erase_column(positions, "xmtop");
7429  cpl_table_erase_column(positions, "ymtop");
7430  cpl_table_erase_column(positions, "xmbottom");
7431  cpl_table_erase_column(positions, "ymbottom");
7432 
7433  if (nmaskslits > nslits)
7434  cpl_msg_info(func, "Finally identified slits: %d out of %d expected\n"
7435  "(%d recovered)", nmaskslits, nmaskslits, nmaskslits - nslits);
7436  else if (nmaskslits < nslits)
7437  cpl_msg_info(func, "Finally identified slits: %d out of %d expected\n"
7438  "(%d rejected)", nmaskslits, nmaskslits, nslits - nmaskslits);
7439  else
7440  cpl_msg_info(func, "Finally identified slits: %d out of %d expected",
7441  nmaskslits, nmaskslits);
7442 
7443  return positions;
7444 }
7445 
7446 
7488 cpl_table *mos_trace_flat(cpl_image *flat, cpl_table *slits, double reference,
7489  double blue, double red, double dispersion)
7490 {
7491 
7492  const char *func = "mos_trace_flat";
7493 
7494  cpl_image *gradient;
7495  cpl_image *sgradient;
7496  float *dgradient;
7497  float level = 500; /* It was 250... */
7498  cpl_vector *row;
7499  cpl_vector *srow;
7500  cpl_vector **peaks;
7501  double *peak;
7502  int *slit_id;
7503  float *g;
7504  double *r;
7505  double *xtop;
7506  double *ytop;
7507  double *xbottom;
7508  double *ybottom;
7509  double min, dist;
7510  double sradius;
7511  double tolerance;
7512  double start_y, prev_y;
7513  int minpos;
7514  int nslits;
7515  int nrows;
7516  int step = 10;
7517  int filtbox = 15;
7518  int nx, ny, npix;
7519  int pos, ypos;
7520  int npeaks;
7521  int pixel_above, pixel_below;
7522  int i, j, k, l;
7523  char trace_id[MAX_COLNAME];
7524 
7525  cpl_table *traces;
7526 
7527 
7528  if (flat == NULL || slits == NULL) {
7529  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
7530  return NULL;
7531  }
7532 
7533  if (dispersion <= 0.0) {
7534  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
7535  return NULL;
7536  }
7537 
7538  if (red - blue < dispersion) {
7539  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
7540  return NULL;
7541  }
7542 
7543  /*
7544  * Create a dummy slit_id column if it is missing in the
7545  * input slits table
7546  */
7547 
7548  nslits = cpl_table_get_nrow(slits);
7549  if (1 != cpl_table_has_column(slits, "slit_id")) {
7550  cpl_table_new_column(slits, "slit_id", CPL_TYPE_INT);
7551  for (i = 0; i < nslits; i++)
7552  cpl_table_set_int(slits, "slit_id", i, -(i+1)); /* it was (i+1) */
7553  }
7554 
7555  slit_id = cpl_table_get_data_int(slits, "slit_id");
7556 
7557  nx = cpl_image_get_size_x(flat);
7558  ny = cpl_image_get_size_y(flat);
7559  npix = nx * ny;
7560 
7561  gradient = cpl_image_duplicate(flat);
7562  dgradient = cpl_image_get_data_float(gradient);
7563 
7564  for (i = 0; i < ny - 1; i++) {
7565  k = i * nx;
7566  for (j = 0; j < nx; j++) {
7567  l = k + j;
7568  dgradient[l] = fabs(dgradient[l] - dgradient[l + nx]);
7569  }
7570  }
7571 
7572  npix--;
7573  for (j = 0; j < nx; j++)
7574  dgradient[npix - j] = 0.0;
7575 
7576  cpl_image_turn(gradient, -1);
7577  nx = cpl_image_get_size_x(gradient);
7578  ny = cpl_image_get_size_y(gradient);
7579  sgradient = mos_image_vertical_median_filter(gradient,
7580  filtbox, 0, ny, 0, step);
7581  cpl_image_delete(gradient);
7582 
7583 
7584  /*
7585  * Remove background from processed image rows
7586  */
7587 
7588  dgradient = cpl_image_get_data_float(sgradient);
7589 
7590  for (i = 1; i <= ny; i += step) {
7591  row = cpl_vector_new_from_image_row(sgradient, i);
7592  srow = cpl_vector_filter_median_create(row, filtbox);
7593  cpl_vector_subtract(row, srow);
7594  cpl_vector_delete(srow);
7595  g = dgradient + (i-1)*nx;
7596  r = cpl_vector_get_data(row);
7597  for (j = 0; j < nx; j++)
7598  g[j] = r[j];
7599  cpl_vector_delete(row);
7600  }
7601 
7602 
7603  /*
7604  * Rotate (temporarily) the input slits table, to get coordinates
7605  * compatible with the rotated gradient image.
7606  */
7607 
7608  mos_rotate_slits(slits, 1, nx, ny);
7609  xtop = cpl_table_get_data_double(slits, "xtop");
7610  ytop = cpl_table_get_data_double(slits, "ytop");
7611  xbottom = cpl_table_get_data_double(slits, "xbottom");
7612  ybottom = cpl_table_get_data_double(slits, "ybottom");
7613 
7614 
7615  /*
7616  * Get positions of peaks candidates for each processed gradient
7617  * image row
7618  */
7619 
7620  peaks = cpl_calloc(ny, sizeof(cpl_vector *));
7621 
7622  for (i = 0; i < ny; i += step) {
7623  g = dgradient + i*nx;
7624  peaks[i] = mos_peak_candidates(g, nx, level, 1.0);
7625 
7626  /* I thought this would be required, but apparently I was wrong.
7627  * Check twice... */
7628  if (peaks[i])
7629  cpl_vector_subtract_scalar(peaks[i], 0.5);
7630 
7631  }
7632 
7633  cpl_image_delete(sgradient);
7634 
7635 
7636  /*
7637  * Tracing the flat field spectra edges, starting from the
7638  * slits positions obtained at reference wavelength. The
7639  * gradient maximum closest to each slits ends is found and
7640  * accepted only within a given search radius:
7641  */
7642 
7643  sradius = 5.0; /* Pixel */
7644 
7645  /*
7646  * The tracing proceeds along the processed gradient image rows,
7647  * above and below the start position, finding the closest peak
7648  * to the previous obtained position, accepting the new position
7649  * only if it is closer than a given tolerance:
7650  */
7651 
7652  tolerance = 0.9; /* Pixel */
7653 
7654  /*
7655  * The trace is attempted for a certain number of pixels above
7656  * and below the position of the reference wavelength:
7657  */
7658 
7659  pixel_above = STRETCH_FACTOR * (red - reference) / dispersion;
7660  pixel_below = STRETCH_FACTOR * (reference - blue) / dispersion;
7661 
7662 
7663  /*
7664  * Prepare the structure of the output table:
7665  */
7666 
7667  nrows = (ny-1)/step + 1;
7668  traces = cpl_table_new(nrows);
7669  cpl_table_new_column(traces, "x", CPL_TYPE_DOUBLE);
7670  cpl_table_set_column_unit(traces, "x", "pixel");
7671  for (i = 0, j = 0; i < ny; i += step, j++)
7672  cpl_table_set(traces, "x", j, i);
7673 
7674  for (i = 0; i < nslits; i++) {
7675 
7676  /*
7677  * Find the closest processed gradient image row
7678  */
7679 
7680  ypos = ytop[i];
7681 
7682  if (ypos < 0)
7683  ypos = 0;
7684  if (ypos >= ny)
7685  ypos = ny - 1;
7686 
7687  pos = ypos / step;
7688  pos *= step;
7689 
7690  /*
7691  * Find the peak in that row that is closest to xtop[i]
7692  */
7693 
7694  if (peaks[pos]) {
7695  peak = cpl_vector_get_data(peaks[pos]);
7696  npeaks = cpl_vector_get_size(peaks[pos]);
7697 
7698  min = fabs(peak[0] - xtop[i]);
7699  minpos = 0;
7700  for (j = 1; j < npeaks; j++) {
7701  dist = fabs(peak[j] - xtop[i]);
7702  if (min > dist) {
7703  min = dist;
7704  minpos = j;
7705  }
7706  }
7707  }
7708  else {
7709  npeaks = 0;
7710  }
7711 
7712  snprintf(trace_id, MAX_COLNAME, "t%d", slit_id[i]);
7713  cpl_table_new_column(traces, trace_id, CPL_TYPE_DOUBLE);
7714 
7715  if (min > sradius || npeaks == 0) {
7716  cpl_msg_warning(func, "Cannot find spectrum edge for "
7717  "top (or left) end of slit %d", slit_id[i]);
7718  }
7719  else {
7720 
7721  /*
7722  * Add to output table the start y position. Note that
7723  * y positions are written in coordinates of the unrotated
7724  * image, i.e., with horizontal dispersion. Currently nx
7725  * is the x size of the temporarily rotated image (for
7726  * faster memory access), but it has the sense of a y size.
7727  */
7728 
7729  cpl_table_set(traces, trace_id, pos/step, nx - peak[minpos]);
7730  start_y = peak[minpos];
7731 
7732  /*
7733  * Perform the tracing of current edge. Above:
7734  */
7735 
7736  prev_y = start_y;
7737 
7738  for (j = pos + step; j < ny; j += step) {
7739  if (j - pos > pixel_above)
7740  break;
7741  if (peaks[j]) {
7742  peak = cpl_vector_get_data(peaks[j]);
7743  npeaks = cpl_vector_get_size(peaks[j]);
7744  min = fabs(peak[0] - prev_y);
7745  minpos = 0;
7746  for (k = 1; k < npeaks; k++) {
7747  dist = fabs(peak[k] - prev_y);
7748  if (min > dist) {
7749  min = dist;
7750  minpos = k;
7751  }
7752  }
7753  if (min < tolerance) {
7754  cpl_table_set(traces, trace_id, j/step,
7755  nx - peak[minpos]);
7756  prev_y = peak[minpos];
7757  }
7758  }
7759  }
7760 
7761  /*
7762  * Perform the tracing of current edge. Below:
7763  */
7764 
7765  prev_y = start_y;
7766 
7767  for (j = pos - step; j >= 0; j -= step) {
7768  if (pos - j > pixel_below)
7769  break;
7770  if (peaks[j]) {
7771  peak = cpl_vector_get_data(peaks[j]);
7772  npeaks = cpl_vector_get_size(peaks[j]);
7773  min = fabs(peak[0] - prev_y);
7774  minpos = 0;
7775  for (k = 1; k < npeaks; k++) {
7776  dist = fabs(peak[k] - prev_y);
7777  if (min > dist) {
7778  min = dist;
7779  minpos = k;
7780  }
7781  }
7782  if (min < tolerance) {
7783  cpl_table_set(traces, trace_id, j/step,
7784  nx - peak[minpos]);
7785  prev_y = peak[minpos];
7786  }
7787  }
7788  }
7789  }
7790 
7791 
7792  /*
7793  * Find the peak in that row that is closest to xbottom[i]
7794  */
7795 
7796  if (peaks[pos]) {
7797  peak = cpl_vector_get_data(peaks[pos]);
7798  npeaks = cpl_vector_get_size(peaks[pos]);
7799 
7800  min = fabs(peak[0] - xbottom[i]);
7801  minpos = 0;
7802  for (j = 1; j < npeaks; j++) {
7803  dist = fabs(peak[j] - xbottom[i]);
7804  if (min > dist) {
7805  min = dist;
7806  minpos = j;
7807  }
7808  }
7809  }
7810  else {
7811  npeaks = 0;
7812  }
7813 
7814  snprintf(trace_id, MAX_COLNAME, "b%d", slit_id[i]);
7815  cpl_table_new_column(traces, trace_id, CPL_TYPE_DOUBLE);
7816 
7817  if (min > sradius || npeaks == 0) {
7818  cpl_msg_warning(func, "Cannot find spectrum edge for "
7819  "bottom (or right) end of slit %d", slit_id[i]);
7820  }
7821  else {
7822 
7823  cpl_table_set(traces, trace_id, pos/step, nx - peak[minpos]);
7824  start_y = peak[minpos];
7825 
7826  /*
7827  * Perform the tracing of current edge. Above:
7828  */
7829 
7830  prev_y = start_y;
7831 
7832  for (j = pos + step; j < ny; j += step) {
7833  if (j - pos > pixel_above)
7834  break;
7835  if (peaks[j]) {
7836  peak = cpl_vector_get_data(peaks[j]);
7837  npeaks = cpl_vector_get_size(peaks[j]);
7838  min = fabs(peak[0] - prev_y);
7839  minpos = 0;
7840  for (k = 1; k < npeaks; k++) {
7841  dist = fabs(peak[k] - prev_y);
7842  if (min > dist) {
7843  min = dist;
7844  minpos = k;
7845  }
7846  }
7847  if (min < tolerance) {
7848  cpl_table_set(traces, trace_id, j/step,
7849  nx - peak[minpos]);
7850  prev_y = peak[minpos];
7851  }
7852  }
7853  }
7854 
7855  /*
7856  * Perform the tracing of current edge. Below:
7857  */
7858 
7859  prev_y = start_y;
7860 
7861  for (j = pos - step; j >= 0; j -= step) {
7862  if (pos - j > pixel_below)
7863  break;
7864  if (peaks[j]) {
7865  peak = cpl_vector_get_data(peaks[j]);
7866  npeaks = cpl_vector_get_size(peaks[j]);
7867  min = fabs(peak[0] - prev_y);
7868  minpos = 0;
7869  for (k = 1; k < npeaks; k++) {
7870  dist = fabs(peak[k] - prev_y);
7871  if (min > dist) {
7872  min = dist;
7873  minpos = k;
7874  }
7875  }
7876  if (min < tolerance) {
7877  cpl_table_set(traces, trace_id, j/step,
7878  nx - peak[minpos]);
7879  prev_y = peak[minpos];
7880  }
7881  }
7882  }
7883  }
7884 
7885  } /* End of loop on slits */
7886 
7887  for (i = 0; i < ny; i += step)
7888  cpl_vector_delete(peaks[i]);
7889  cpl_free(peaks);
7890 
7891  /*
7892  * Restore original orientation of slits positions table
7893  */
7894 
7895  mos_rotate_slits(slits, -1, ny, nx);
7896 
7897  return traces;
7898 
7899 }
7900 
7901 
7922 cpl_table *mos_poly_trace(cpl_table *slits, cpl_table *traces, int order)
7923 {
7924  const char *func = "mos_poly_trace";
7925 
7926  cpl_table *polytraces;
7927  cpl_table *dummy;
7928  cpl_vector *x;
7929  cpl_vector *trace;
7930  cpl_polynomial *polytrace;
7931  char trace_id[MAX_COLNAME];
7932  char trace_res[MAX_COLNAME];
7933  char trace_mod[MAX_COLNAME];
7934  const char *clab[6] = {"c0", "c1", "c2", "c3", "c4", "c5"};
7935  /* Max order is 5 */
7936  double *xdata;
7937  int *slit_id;
7938  int nslits;
7939  int nrows;
7940  int npoints;
7941  int i, j;
7942  cpl_size k;
7943 
7944 
7945  if (traces == NULL || slits == NULL) {
7946  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
7947  return NULL;
7948  }
7949 
7950  if (order > 5) {
7951  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
7952  return NULL;
7953  }
7954 
7955  nrows = cpl_table_get_nrow(traces);
7956  xdata = cpl_table_get_data_double(traces, "x");
7957  nslits = cpl_table_get_nrow(slits);
7958  slit_id = cpl_table_get_data_int(slits, "slit_id");
7959 
7960  polytraces = cpl_table_new(2*nslits);
7961  cpl_table_new_column(polytraces, "slit_id", CPL_TYPE_INT);
7962  for (i = 0; i <= order; i++)
7963  cpl_table_new_column(polytraces, clab[i], CPL_TYPE_DOUBLE);
7964 
7965  for (i = 0; i < nslits; i++) {
7966  for (j = 0; j < 2; j++) { /* For top and bottom trace of each slit */
7967 
7968  if (j) {
7969  snprintf(trace_id, MAX_COLNAME, "b%d", slit_id[i]);
7970  snprintf(trace_res, MAX_COLNAME, "b%d_res", slit_id[i]);
7971  snprintf(trace_mod, MAX_COLNAME, "b%d_mod", slit_id[i]);
7972  }
7973  else {
7974  snprintf(trace_id, MAX_COLNAME, "t%d", slit_id[i]);
7975  snprintf(trace_res, MAX_COLNAME, "t%d_res", slit_id[i]);
7976  snprintf(trace_mod, MAX_COLNAME, "t%d_mod", slit_id[i]);
7977  }
7978 
7979  cpl_table_set_int(polytraces, "slit_id", 2*i+j, slit_id[i]);
7980 
7981  /*
7982  * The "dummy" table is just a tool for eliminating invalid
7983  * points from the vectors to be fitted.
7984  */
7985 
7986  dummy = cpl_table_new(nrows);
7987  cpl_table_duplicate_column(dummy, "x", traces, "x");
7988  cpl_table_duplicate_column(dummy, trace_id, traces, trace_id);
7989  npoints = nrows - cpl_table_count_invalid(dummy, trace_id);
7990  if (npoints < 2 * order) {
7991  cpl_table_delete(dummy);
7992  continue;
7993  }
7994  cpl_table_erase_invalid(dummy);
7995  x = cpl_vector_wrap(npoints,
7996  cpl_table_get_data_double(dummy, "x"));
7997  trace = cpl_vector_wrap(npoints,
7998  cpl_table_get_data_double(dummy, trace_id));
7999  polytrace = cpl_polynomial_fit_1d_create(x, trace, order, NULL);
8000  cpl_vector_unwrap(x);
8001  cpl_vector_unwrap(trace);
8002  cpl_table_delete(dummy);
8003 
8004  /*
8005  * Screen bad solutions. At the moment, a primitive screening
8006  * consists in excluding solutions displaying excessive
8007  * curvature (larger than 1E-5 / pixel)
8008  */
8009 
8010  k = 2;
8011  if (fabs(cpl_polynomial_get_coeff(polytrace, &k)) > 1.E-4) {
8012  cpl_polynomial_delete(polytrace);
8013  cpl_table_new_column(traces, trace_mod, CPL_TYPE_DOUBLE);
8014  cpl_table_duplicate_column(traces, trace_res, traces,
8015  trace_mod);
8016  if (j)
8017  cpl_msg_warning(func, "Exclude bad curvature solution "
8018  "for bottom (right) edge of slit %d", slit_id[i]);
8019  else
8020  cpl_msg_warning(func, "Exclude bad curvature solution "
8021  "for top (left) edge of slit %d", slit_id[i]);
8022  continue;
8023  }
8024 
8025  /*
8026  * Write polynomial coefficients to the output table,
8027  * tagged with the appropriate slit_id
8028  */
8029 
8030  for (k = 0; k <= order; k++)
8031  cpl_table_set_double(polytraces, clab[k], 2*i+j,
8032  cpl_polynomial_get_coeff(polytrace, &k));
8033 
8034  /*
8035  * Add column of residuals to input traces table
8036  */
8037 
8038  cpl_table_new_column(traces, trace_mod, CPL_TYPE_DOUBLE);
8039  cpl_table_set_column_unit(traces, trace_mod, "pixel");
8040 
8041  for (k = 0; k < nrows; k++) {
8042  cpl_table_set_double(traces, trace_mod, k,
8043  cpl_polynomial_eval_1d(polytrace, xdata[k], NULL));
8044  }
8045 
8046  cpl_polynomial_delete(polytrace);
8047 
8048  cpl_table_duplicate_column(traces, trace_res, traces, trace_mod);
8049  cpl_table_subtract_columns(traces, trace_res, trace_id);
8050  cpl_table_multiply_scalar(traces, trace_res, -1.0);
8051 
8052  }
8053  }
8054 
8055  return polytraces;
8056 
8057 }
8058 
8059 
8083 cpl_error_code mos_global_trace(cpl_table *slits, cpl_table *polytraces,
8084  int mode)
8085 {
8086  const char *func = "mos_global_trace";
8087 
8088  const char *clab[6] = {"c0", "c1", "c2", "c3", "c4", "c5"};
8089  /* Max order is 5 */
8090  cpl_table *table;
8091  cpl_vector *c0;
8092  cpl_vector *cn;
8093  cpl_bivector *list;
8094 /* alternative (not robust)
8095  cpl_polynomial *poly;
8096 */
8097 
8098  double *offset;
8099  double rms, q, m;
8100 
8101  int order, nrows, nslits;
8102  int i, j;
8103 
8104 
8105  if (polytraces == NULL) {
8106  cpl_msg_error(func, "Missing spectral curvature table");
8107  return cpl_error_set(func, CPL_ERROR_NULL_INPUT);
8108  }
8109 
8110  if (slits == NULL) {
8111  cpl_msg_error(func, "Missing slits positions table");
8112  return cpl_error_set(func, CPL_ERROR_NULL_INPUT);
8113  }
8114 
8115  nslits = cpl_table_get_nrow(slits);
8116 
8117  table = cpl_table_duplicate(polytraces);
8118  cpl_table_erase_invalid(table);
8119 
8120  nrows = cpl_table_get_nrow(table);
8121 
8122  if (nrows < 4) {
8123  cpl_msg_warning(func, "Too few successful spectral curvature tracings "
8124  "(%d): the determination of a global curvature model "
8125  "failed", nrows);
8126  return CPL_ERROR_NONE;
8127  }
8128 
8129  order = cpl_table_get_ncol(polytraces) - 2;
8130 
8131  for (i = 0; i <= order; i++) {
8132  if (!cpl_table_has_column(table, clab[i])) {
8133  cpl_msg_error(func, "Wrong spectral curvature table");
8134  return cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
8135  }
8136  }
8137 
8138 
8139  /*
8140  * Fill in advance the missing offset terms
8141  */
8142 
8143  for (i = 0; i < nslits; i++) {
8144  if (!cpl_table_is_valid(polytraces, clab[0], 2*i)) {
8145  cpl_table_set_double(polytraces, clab[0], 2*i,
8146  cpl_table_get_double(slits, "ytop", i, NULL));
8147  }
8148  if (!cpl_table_is_valid(polytraces, clab[0], 2*i+1)) {
8149  cpl_table_set_double(polytraces, clab[0], 2*i+1,
8150  cpl_table_get_double(slits, "ybottom", i, NULL));
8151  }
8152  }
8153 
8154  offset = cpl_table_get_data_double(polytraces, clab[0]);
8155 
8156 
8157  /*
8158  * Fit the global model and modify polytraces table accordingly
8159  */
8160 
8161  c0 = cpl_vector_wrap(nrows, cpl_table_get_data_double(table, clab[0]));
8162 
8163  for (i = 1; i <= order; i++) {
8164  cn = cpl_vector_wrap(nrows, cpl_table_get_data_double(table, clab[i]));
8165  list = cpl_bivector_wrap_vectors(c0, cn);
8166  robustLinearFit(list, &q, &m, &rms);
8167 /* alternative (not robust)
8168  poly = cpl_polynomial_fit_1d_create(c0, cn, 1, NULL);
8169 */
8170  for (j = 0; j < 2*nslits; j++) {
8171  if (mode == 1)
8172  if (cpl_table_is_valid(polytraces, clab[i], j))
8173  continue;
8174  cpl_table_set_double(polytraces, clab[i], j, offset[j]*m + q);
8175 /* alternative (not robust)
8176  cpl_table_set_double(polytraces, clab[i], j,
8177  cpl_polynomial_eval_1d(poly, offset[j], NULL));
8178 */
8179  }
8180  cpl_bivector_unwrap_vectors(list);
8181 /* alternative (not robust)
8182  cpl_polynomial_delete(poly);
8183 */
8184  cpl_vector_unwrap(cn);
8185  }
8186 
8187  cpl_vector_unwrap(c0);
8188  cpl_table_delete(table);
8189 
8190  return CPL_ERROR_NONE;
8191 
8192 }
8193 
8194 
8264 cpl_image *mos_spatial_calibration(cpl_image *spectra, cpl_table *slits,
8265  cpl_table *polytraces, double reference,
8266  double blue, double red, double dispersion,
8267  int flux, cpl_image *calibration)
8268 {
8269  const char *func = "mos_spatial_calibration";
8270 
8271  const char *clab[6] = {"c0", "c1", "c2", "c3", "c4", "c5"};
8272  /* Max order is 5 */
8273  cpl_polynomial *polytop;
8274  cpl_polynomial *polybot;
8275  cpl_image **exslit;
8276  cpl_image *resampled;
8277  float *data;
8278  float *sdata;
8279  float *xdata;
8280  double vtop, vbot, value;
8281  double top, bot;
8282  double coeff;
8283  double ytop, ybot;
8284  double ypos, yfra;
8285  double factor;
8286  int yint, ysize, yprev;
8287  int nslits;
8288  int npseudo;
8289  int *slit_id;
8290  int *length;
8291  int nx, ny;
8292  int pixel_above, pixel_below, refpixel, start_pixel, end_pixel;
8293  int missing_top, missing_bot;
8294  int null;
8295  int order;
8296  int i, j;
8297  cpl_size k;
8298 
8299  int create_position = 1;
8300 
8301 
8302  if (spectra == NULL || slits == NULL || polytraces == NULL) {
8303  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
8304  return NULL;
8305  }
8306 
8307  if (dispersion <= 0.0) {
8308  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
8309  return NULL;
8310  }
8311 
8312  if (red - blue < dispersion) {
8313  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
8314  return NULL;
8315  }
8316 
8317  nx = cpl_image_get_size_x(spectra);
8318  ny = cpl_image_get_size_y(spectra);
8319  sdata = cpl_image_get_data(spectra);
8320  if (calibration)
8321  data = cpl_image_get_data(calibration);
8322 
8323  if (cpl_table_has_column(slits, "position"))
8324  create_position = 0;
8325 
8326  if (create_position) {
8327  cpl_table_new_column(slits, "position", CPL_TYPE_INT);
8328  cpl_table_new_column(slits, "length", CPL_TYPE_INT);
8329  cpl_table_set_column_unit(slits, "position", "pixel");
8330  cpl_table_set_column_unit(slits, "length", "pixel");
8331  }
8332  else
8333  length = cpl_table_get_data_int(slits, "length");
8334 
8335  nslits = cpl_table_get_nrow(slits);
8336  slit_id = cpl_table_get_data_int(slits, "slit_id");
8337  order = cpl_table_get_ncol(polytraces) - 2;
8338 
8339  /*
8340  * The spatial resampling is performed for a certain number of
8341  * pixels above and below the position of the reference wavelength:
8342  */
8343 
8344  pixel_above = STRETCH_FACTOR * (red - reference) / dispersion;
8345  pixel_below = STRETCH_FACTOR * (reference - blue) / dispersion;
8346 
8347  exslit = cpl_calloc(nslits, sizeof(cpl_image *));
8348 
8349  for (i = 0; i < nslits; i++) {
8350 
8351  if (create_position == 0)
8352  if (length[i] == 0)
8353  continue;
8354 
8355  /*
8356  * Note that the x coordinate of the reference pixels on the CCD
8357  * is taken arbitrarily at the top end of each slit. This wouldn't
8358  * be entirely correct in case of curved slits, or in presence of
8359  * heavy distortions: in such cases the spatial resampling is
8360  * really performed across a wide range of wavelengths. But
8361  * the lag between top and bottom spectral curvature models
8362  * would introduce even in such cases negligible effects on
8363  * the spectral spatial resampling.
8364  */
8365 
8366  refpixel = cpl_table_get_double(slits, "xtop", i, NULL);
8367 
8368  start_pixel = refpixel - pixel_below;
8369  if (start_pixel < 0)
8370  start_pixel = 0;
8371 
8372  end_pixel = refpixel + pixel_above;
8373  if (end_pixel > nx)
8374  end_pixel = nx;
8375 
8376  /*
8377  * Recover from the table of spectral curvature coefficients
8378  * the curvature polynomials.
8379  */
8380 
8381  missing_top = 0;
8382  polytop = cpl_polynomial_new(1);
8383  for (k = 0; k <= order; k++) {
8384  coeff = cpl_table_get_double(polytraces, clab[k], 2*i, &null);
8385  if (null) {
8386  cpl_polynomial_delete(polytop);
8387  missing_top = 1;
8388  break;
8389  }
8390  cpl_polynomial_set_coeff(polytop, &k, coeff);
8391  }
8392 
8393  missing_bot = 0;
8394  polybot = cpl_polynomial_new(1);
8395  for (k = 0; k <= order; k++) {
8396  coeff = cpl_table_get_double(polytraces, clab[k], 2*i+1, &null);
8397  if (null) {
8398  cpl_polynomial_delete(polybot);
8399  missing_bot = 1;
8400  break;
8401  }
8402  cpl_polynomial_set_coeff(polybot, &k, coeff);
8403  }
8404 
8405  if (missing_top && missing_bot) {
8406  cpl_msg_warning(func, "Spatial calibration, slit %d was not "
8407  "traced: no extraction!",
8408  slit_id[i]);
8409  continue;
8410  }
8411 
8412  /*
8413  * In case just one of the two edges was not traced, the other
8414  * edge curvature model is duplicated and shifted to the other
8415  * end of the slit: better than nothing!
8416  */
8417 
8418  if (missing_top) {
8419  cpl_msg_warning(func, "Upper edge of slit %d was not traced: "
8420  "the spectral curvature of the lower edge "
8421  "is used instead.", slit_id[i]);
8422  polytop = cpl_polynomial_duplicate(polybot);
8423  ytop = cpl_table_get_double(slits, "ytop", i, NULL);
8424  ybot = cpl_table_get_double(slits, "ybottom", i, NULL);
8425  k = 0;
8426  coeff = cpl_polynomial_get_coeff(polybot, &k);
8427  coeff += ytop - ybot;
8428  cpl_polynomial_set_coeff(polytop, &k, coeff);
8429  }
8430 
8431  if (missing_bot) {
8432  cpl_msg_warning(func, "Lower edge of slit %d was not traced: "
8433  "the spectral curvature of the upper edge "
8434  "is used instead.", slit_id[i]);
8435  polybot = cpl_polynomial_duplicate(polytop);
8436  ytop = cpl_table_get_double(slits, "ytop", i, NULL);
8437  ybot = cpl_table_get_double(slits, "ybottom", i, NULL);
8438  k = 0;
8439  coeff = cpl_polynomial_get_coeff(polytop, &k);
8440  coeff -= ytop - ybot;
8441  cpl_polynomial_set_coeff(polybot, &k, coeff);
8442  }
8443 
8444  /*
8445  * Allocate image for current extracted slit
8446  */
8447 
8448  top = cpl_polynomial_eval_1d(polytop, refpixel, NULL);
8449  bot = cpl_polynomial_eval_1d(polybot, refpixel, NULL);
8450  npseudo = ceil(top-bot) + 1;
8451 
8452  if (npseudo < 1) {
8453  cpl_polynomial_delete(polytop);
8454  cpl_polynomial_delete(polybot);
8455  cpl_msg_warning(func, "Slit %d was badly traced: no extraction!",
8456  slit_id[i]);
8457  continue;
8458  }
8459 
8460  exslit[i] = cpl_image_new(nx, npseudo+1, CPL_TYPE_FLOAT);
8461  xdata = cpl_image_get_data(exslit[i]);
8462 
8463  /*
8464  * Write interpolated values to slit image.
8465  */
8466 
8467  for (j = start_pixel; j < end_pixel; j++) {
8468  top = cpl_polynomial_eval_1d(polytop, j, NULL);
8469  bot = cpl_polynomial_eval_1d(polybot, j, NULL);
8470  factor = (top-bot)/npseudo;
8471  for (k = 0; k <= npseudo; k++) {
8472  ypos = top - k*factor;
8473  yint = ypos;
8474  yfra = ypos - yint;
8475  if (yint >= 0 && yint < ny-1) {
8476  vtop = sdata[j + nx*yint];
8477  vbot = sdata[j + nx*(yint+1)];
8478  value = vtop*(1-yfra) + vbot*yfra;
8479  if (flux)
8480  value *= factor;
8481  xdata[j + nx*(npseudo-k)] = value;
8482  if (calibration) {
8483  data[j + nx*yint] = (top-yint)/factor;
8484  if (k) {
8485 
8486  /*
8487  * This is added to recover lost pixels on
8488  * the CCD image (pixels are lost because
8489  * the CCD pixels are less than npseudo+1).
8490  */
8491 
8492  if (yprev - yint > 1) {
8493  data[j + nx*(yint+1)] = (top-yint-1)/factor;
8494  }
8495  }
8496  }
8497  }
8498  yprev = yint;
8499  }
8500  }
8501  cpl_polynomial_delete(polytop);
8502  cpl_polynomial_delete(polybot);
8503  }
8504 
8505  /*
8506  * Now all the slits images are copied to a single image
8507  */
8508 
8509  ysize = 0;
8510  for (i = 0; i < nslits; i++)
8511  if (exslit[i])
8512  ysize += cpl_image_get_size_y(exslit[i]);
8513 
8514  resampled = cpl_image_new(nx, ysize, CPL_TYPE_FLOAT);
8515 
8516  yint = -1;
8517  for (i = 0; i < nslits; i++) {
8518  if (exslit[i]) {
8519  yint += cpl_image_get_size_y(exslit[i]);
8520  cpl_image_copy(resampled, exslit[i], 1, ysize - yint);
8521  if (create_position) {
8522  cpl_table_set_int(slits, "position", i, ysize - yint - 1);
8523  cpl_table_set_int(slits, "length", i,
8524  cpl_image_get_size_y(exslit[i]));
8525  }
8526  cpl_image_delete(exslit[i]);
8527  }
8528  else if (create_position) {
8529  cpl_table_set_int(slits, "position", i, -1);
8530  cpl_table_set_int(slits, "length", i, 0);
8531  }
8532  }
8533 
8534 
8535  /*
8536  * Elimination of non-traced slits from slit position table: we cannot do
8537  * it because we would lose sync with polytraces and other slit-oriented
8538  * tables. COMMENTED OUT.
8539  *
8540 
8541  if (create_position) {
8542 
8543  if (cpl_table_and_selected_int(slits, "position", CPL_EQUAL_TO, -1))
8544  cpl_table_erase_selected(slits);
8545 
8546  }
8547 
8548  */
8549 
8550  cpl_free(exslit);
8551 
8552  return resampled;
8553 
8554 }
8555 
8556 
8669 cpl_image *mos_wavelength_calibration_final(cpl_image *image, cpl_table *slits,
8670  cpl_vector *lines,
8671  double dispersion, float level,
8672  int sradius, int order,
8673  double reject, double refwave,
8674  double *wavestart, double *waveend,
8675  int *nlines, double *error,
8676  cpl_table *idscoeff,
8677  cpl_image *calibration,
8678  cpl_image *residuals,
8679  cpl_table *restable,
8680  cpl_table *detected_lines)
8681 {
8682 
8683  const char *func = "mos_wavelength_calibration_final";
8684 
8685  const char *clab[6] = {"c0", "c1", "c2", "c3", "c4", "c5"};
8686  /* Max order is 5 */
8687 
8688  double tolerance = 20.0; /* Probably forever... */
8689  int step = 10; /* Compute restable every "step" rows */
8690 
8691  char name[MAX_COLNAME];
8692 
8693  cpl_image *resampled;
8694  cpl_bivector *peaks_ident;
8695  cpl_vector *wavel;
8696  cpl_vector *peaks;
8697  cpl_polynomial *ids;
8698  cpl_polynomial *lin;
8699  cpl_polynomial *fguess;
8700  cpl_table *coeff;
8701  double ids_err;
8702  double max_disp, min_disp;
8703  double *line;
8704  double firstLambda, lastLambda, lambda;
8705  double wave, pixe, value;
8706  double c;
8707  float *sdata;
8708  float *rdata;
8709  float *idata;
8710  float *ddata;
8711  float v1, v2, vi;
8712  float fpixel;
8713  int *length;
8714  int pixstart, pixend;
8715  int row_top, row_bot;
8716  int extrapolation;
8717  int nref;
8718  int nslits;
8719  int nfits;
8720  int nl, nx, ny, pixel;
8721  int countLines, usedLines;
8722  int uorder;
8723  int missing;
8724  int null;
8725  int width, uradius;
8726  int i, j, s;
8727  cpl_size k;
8728 
8729 
8730  if (dispersion == 0.0) {
8731  cpl_msg_error(func, "The expected dispersion (A/pixel) must be given");
8732  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
8733  return NULL;
8734  }
8735 
8736  if (dispersion < 0.0) {
8737  cpl_msg_error(func, "The expected dispersion must be positive");
8738  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
8739  return NULL;
8740  }
8741 
8742  if (idscoeff == NULL) {
8743  cpl_msg_error(func, "A preallocated IDS coeff table must be given");
8744  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
8745  return NULL;
8746  }
8747 
8748  max_disp = dispersion + dispersion * tolerance / 100;
8749  min_disp = dispersion - dispersion * tolerance / 100;
8750 
8751  if (order < 1) {
8752  cpl_msg_error(func, "The order of the fitting polynomial "
8753  "must be at least 1");
8754  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
8755  return NULL;
8756  }
8757 
8758  if (image == NULL || lines == NULL) {
8759  cpl_msg_error(func, "Both spectral exposure and reference line "
8760  "catalog are required in input");
8761  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
8762  return NULL;
8763  }
8764 
8765  nx = cpl_image_get_size_x(image);
8766  ny = cpl_image_get_size_y(image);
8767  sdata = cpl_image_get_data_float(image);
8768 
8769  nref = cpl_vector_get_size(lines);
8770  line = cpl_vector_get_data(lines);
8771 
8772  if (*wavestart < 1.0 && *waveend < 1.0) {
8773  firstLambda = line[0];
8774  lastLambda = line[nref-1];
8775  extrapolation = (lastLambda - firstLambda) / 10;
8776  firstLambda -= extrapolation;
8777  lastLambda += extrapolation;
8778  *wavestart = firstLambda;
8779  *waveend = lastLambda;
8780  }
8781  else {
8782  firstLambda = *wavestart;
8783  lastLambda = *waveend;
8784  }
8785 
8786  nl = (lastLambda - firstLambda) / dispersion;
8787  resampled = cpl_image_new(nl, ny, CPL_TYPE_FLOAT);
8788  rdata = cpl_image_get_data_float(resampled);
8789 
8790  /*
8791  * Allocate total output table of IDS coefficients
8792  */
8793 
8794  for (j = 0; j <= order; j++)
8795  cpl_table_new_column(idscoeff, clab[j], CPL_TYPE_DOUBLE);
8796 
8797  if (calibration)
8798  idata = cpl_image_get_data_float(calibration);
8799 
8800  if (residuals)
8801  ddata = cpl_image_get_data_float(residuals);
8802 
8803  if (restable) {
8804  cpl_table_set_size(restable, nref);
8805  cpl_table_new_column(restable, "wavelength", CPL_TYPE_DOUBLE);
8806  cpl_table_copy_data_double(restable, "wavelength", line);
8807  for (i = 0; i < ny; i += step) {
8808  snprintf(name, MAX_COLNAME, "r%d", i);
8809  cpl_table_new_column(restable, name, CPL_TYPE_DOUBLE);
8810  snprintf(name, MAX_COLNAME, "d%d", i);
8811  cpl_table_new_column(restable, name, CPL_TYPE_DOUBLE);
8812  snprintf(name, MAX_COLNAME, "p%d", i);
8813  cpl_table_new_column(restable, name, CPL_TYPE_DOUBLE);
8814  }
8815  }
8816 
8817  if (detected_lines) {
8818  cpl_table_set_size(detected_lines, 0);
8819  cpl_table_new_column(detected_lines, "slit_id", CPL_TYPE_INT);
8820  cpl_table_new_column(detected_lines, "xpos_rectified", CPL_TYPE_DOUBLE);
8821  cpl_table_new_column(detected_lines, "ypos_rectified", CPL_TYPE_DOUBLE);
8822  cpl_table_new_column(detected_lines, "xpos_rectified_iter", CPL_TYPE_DOUBLE);
8823  cpl_table_new_column(detected_lines, "ypos_rectified_iter", CPL_TYPE_DOUBLE);
8824  cpl_table_new_column(detected_lines, "peak_flux", CPL_TYPE_DOUBLE);
8825  cpl_table_new_column(detected_lines, "wave_ident", CPL_TYPE_DOUBLE);
8826  cpl_table_new_column(detected_lines, "wave_ident_iter", CPL_TYPE_DOUBLE);
8827  cpl_table_new_column(detected_lines, "xpos_fit_rect_wavecal", CPL_TYPE_DOUBLE);
8828  cpl_table_new_column(detected_lines, "res_xpos", CPL_TYPE_DOUBLE);
8829  }
8830 
8831 
8832  /*
8833  * Process all slits separately.
8834  */
8835 
8836  nslits = cpl_table_get_nrow(slits);
8837  length = cpl_table_get_data_int(slits, "length");
8838 
8839  row_top = ny;
8840  for (s = 0; s < nslits; s++) {
8841 
8842  int slit_id;
8843  slit_id = cpl_table_get_int(slits, "slit_id", s, NULL);
8844 
8845  if (length[s] == 0)
8846  continue;
8847 
8848  /*
8849  * row_top and row_bot define the boundaries of the current slit.
8850  * Here we begin (arbitrarily...) from the top slit.
8851  */
8852 
8853  row_bot = cpl_table_get_int(slits, "position", s, NULL);
8854 
8855  if (sradius > 0) {
8856 
8857  /*
8858  * If a search radius was defined, allocate the table of
8859  * the fitting polynomials coefficients. This table is
8860  * just used to generate the first-guess polynomial made
8861  * of the median coefficients of all polynomials found
8862  * for this slit.
8863  */
8864 
8865  coeff = cpl_table_new(row_top - row_bot);
8866  for (j = 0; j <= order; j++)
8867  cpl_table_new_column(coeff, clab[j], CPL_TYPE_DOUBLE);
8868  }
8869 
8870  /*
8871  * Here is the loop on all rows of the current slit. They are
8872  * wavelength calibrated one by one.
8873  */
8874 
8875  for (i = row_bot; i < row_top; i++) {
8876  width = mos_lines_width(sdata + i*nx, nx);
8877  if (width < 5)
8878  width = 5;
8879  peaks = mos_peak_candidates(sdata + i*nx, nx, level, width);
8880  if (peaks) {
8881  peaks = mos_refine_peaks(sdata + i*nx, nx, peaks, width);
8882  }
8883  if (peaks) {
8884  int keep_multiplex = mos_multiplex;
8885  mos_multiplex = -1;
8886  if(detected_lines)
8887  {
8888  cpl_size newlines = cpl_vector_get_size(peaks);
8889  cpl_size oldsize = cpl_table_get_nrow(detected_lines);
8890  cpl_table_set_size(detected_lines, oldsize + newlines);
8891  for(cpl_size iline = 0; iline < newlines; ++iline)
8892  {
8893  cpl_table_set_int(detected_lines, "slit_id",
8894  oldsize + iline, slit_id);
8895  cpl_table_set_double(detected_lines, "xpos_rectified",
8896  oldsize + iline, cpl_vector_get(peaks, iline) + 1);
8897  cpl_table_set_double(detected_lines, "ypos_rectified",
8898  oldsize + iline, (double)i + 1);
8899  cpl_table_set_double(detected_lines, "peak_flux",
8900  oldsize + iline,
8901  sdata[i*nx+(int)(cpl_vector_get(peaks, iline)+0.5)]);
8902  }
8903  }
8904  peaks_ident = mos_identify_peaks(peaks, lines,
8905  min_disp, max_disp, 0.05);
8906  mos_multiplex = keep_multiplex;
8907  if (peaks_ident) {
8908  countLines = cpl_bivector_get_size(peaks_ident);
8909  if (countLines < 4) {
8910  cpl_bivector_delete(peaks_ident);
8911  cpl_vector_delete(peaks);
8912  if (nlines)
8913  nlines[i] = 0;
8914  if (error)
8915  error[i] = 0.0;
8916  continue;
8917  }
8918 
8919  /*
8920  * Set reference wavelength as zero point
8921  */
8922 
8923  wavel = cpl_bivector_get_y(peaks_ident);
8924  cpl_vector_subtract_scalar(wavel, refwave);
8925 
8926  uorder = countLines / 2 - 1;
8927  if (uorder > order)
8928  uorder = order;
8929 
8930  ids = mos_poly_wav2pix(peaks_ident, uorder, reject,
8931  2 * (uorder + 1), &usedLines,
8932  &ids_err);
8933 
8934  if (ids == NULL) {
8935  cpl_bivector_delete(peaks_ident);
8936  cpl_vector_delete(peaks);
8937  if (nlines)
8938  nlines[i] = 0;
8939  if (error)
8940  error[i] = 0.0;
8941  cpl_error_reset();
8942  continue;
8943  }
8944 
8945  if (sradius > 0) {
8946  for (k = 0; k <= order; k++) {
8947  if (k > uorder) {
8948  cpl_table_set_double(coeff, clab[k],
8949  i - row_bot, 0.0);
8950  }
8951  else {
8952  cpl_table_set_double(coeff, clab[k],
8953  i - row_bot, cpl_polynomial_get_coeff(ids, &k));
8954  }
8955  }
8956  }
8957  /* else { */
8958  if (calibration) {
8959  pixstart = cpl_polynomial_eval_1d(ids,
8960  cpl_bivector_get_y_data(peaks_ident)[0],
8961  NULL);
8962  pixend = cpl_polynomial_eval_1d(ids,
8963  cpl_bivector_get_y_data(peaks_ident)[countLines-1],
8964  NULL);
8965  extrapolation = (pixend - pixstart) / 5;
8966  pixstart -= extrapolation;
8967  pixend += extrapolation;
8968  if (pixstart < 0)
8969  pixstart = 0;
8970  if (pixend > nx)
8971  pixend = nx;
8972 
8973  for (j = pixstart; j < pixend; j++) {
8974  (idata + i*nx)[j] = mos_eval_dds(ids,
8975  firstLambda, lastLambda, refwave, j);
8976  }
8977  }
8978 
8979  /*
8980  * Residuals image
8981  */
8982 
8983  if (residuals || (restable && !(i%step))) {
8984  if (restable && !(i%step)) {
8985  lin = cpl_polynomial_new(1);
8986  for (k = 0; k < 2; k++)
8987  cpl_polynomial_set_coeff(lin, &k,
8988  cpl_polynomial_get_coeff(ids, &k));
8989  }
8990  for (j = 0; j < countLines; j++) {
8991  pixe = cpl_bivector_get_x_data(peaks_ident)[j];
8992  wave = cpl_bivector_get_y_data(peaks_ident)[j];
8993  value = pixe
8994  - cpl_polynomial_eval_1d(ids, wave, NULL);
8995  if (residuals) {
8996  pixel = pixe + 0.5;
8997  (ddata + i*nx)[pixel] = value;
8998  }
8999  if (restable && !(i%step)) {
9000  for (k = 0; k < nref; k++) {
9001  if (fabs(line[k]-refwave-wave) < 0.1) {
9002  snprintf(name, MAX_COLNAME,
9003  "r%d", i);
9004  cpl_table_set_double(restable, name,
9005  k, value);
9006  value = pixe
9007  - cpl_polynomial_eval_1d(lin,
9008  wave, NULL);
9009  snprintf(name, MAX_COLNAME,
9010  "d%d", i);
9011  cpl_table_set_double(restable, name,
9012  k, value);
9013  snprintf(name, MAX_COLNAME,
9014  "p%d", i);
9015  cpl_table_set_double(restable, name,
9016  k, pixe);
9017  break;
9018  }
9019  }
9020  }
9021  }
9022  if (restable && !(i%step)) {
9023  cpl_polynomial_delete(lin);
9024  }
9025 /***
9026  for (j = 0; j < countLines; j++) {
9027  pixel = cpl_bivector_get_x_data(output)[j]
9028  + 0.5;
9029  (ddata + i*nx)[pixel] =
9030  cpl_bivector_get_x_data(output)[j]
9031  - cpl_polynomial_eval_1d(ids,
9032  cpl_bivector_get_y_data(output)[j],
9033  NULL);
9034  }
9035 ***/
9036  //Fill the line identification information in
9037  //the detected_lines table
9038  if(detected_lines)
9039  {
9040  cpl_size nidentlines = cpl_bivector_get_size(peaks_ident);
9041  cpl_size ndetectlines = cpl_vector_get_size(peaks);
9042  cpl_size totalsize = cpl_table_get_nrow(detected_lines);
9043  for(cpl_size idline = 0; idline < nidentlines; ++idline)
9044  {
9045  for(cpl_size detline = 0; detline < ndetectlines; ++detline)
9046  {
9047  if(cpl_vector_get(peaks, detline) ==
9048  cpl_bivector_get_x_data(peaks_ident)[idline])
9049  {
9050  cpl_size table_pos = totalsize - ndetectlines + detline;
9051  double wave_ident = cpl_bivector_get_y_data(peaks_ident)[idline] + refwave;
9052  double xpix_fit = cpl_polynomial_eval_1d(ids,
9053  wave_ident - refwave, NULL);
9054  double xpos_det = cpl_table_get_double(detected_lines,
9055  "xpos_rectified",
9056  table_pos, &null);
9057  cpl_table_set_double(detected_lines,
9058  "wave_ident",
9059  table_pos,
9060  wave_ident);
9061  cpl_table_set_double(detected_lines,
9062  "xpos_fit_rect_wavecal",
9063  table_pos,
9064  xpix_fit + 1);
9065  cpl_table_set_double(detected_lines,
9066  "res_xpos",
9067  table_pos,
9068  xpos_det - xpix_fit - 1);
9069  }
9070  }
9071  }
9072  }
9073 
9074  }
9075  /* } */
9076 
9077  /*
9078  * Write it anyway, even in case a first-guess based
9079  * solution will be searched afterwards: in case of
9080  * failure, the "blind" solution is kept.
9081  */
9082 
9083  if (nlines)
9084  nlines[i] = usedLines;
9085  if (error)
9086  error[i] = ids_err / sqrt(usedLines/(uorder + 1));
9087 
9088  for (k = 0; k <= order; k++) {
9089  if (k > uorder) {
9090  cpl_table_set_double(idscoeff, clab[k], i, 0.0);
9091  }
9092  else {
9093  cpl_table_set_double(idscoeff, clab[k], i,
9094  cpl_polynomial_get_coeff(ids, &k));
9095  }
9096  }
9097 
9098  cpl_polynomial_delete(ids);
9099  cpl_bivector_delete(peaks_ident);
9100  }
9101  cpl_vector_delete(peaks);
9102  }
9103  } /* End of loop on current slit's rows */
9104 
9105 
9106  if (sradius > 0) {
9107 
9108  /*
9109  * See whether there are valid fits at all...
9110  */
9111 
9112  nfits = row_top - row_bot - cpl_table_count_invalid(coeff, clab[0]);
9113 
9114  if (nfits) {
9115  int slope = 0;
9116 
9117  fguess = cpl_polynomial_new(1);
9118 
9119  if (mos_interpolate_wavecalib(coeff, NULL, 2, 1)) {
9120 
9121  slope = 0;
9122 
9123  /*
9124  * Compute a median IDS polynomial for the current slit
9125  */
9126 
9127  for (k = 0; k <= order; k++) {
9128  c = cpl_table_get_column_median(coeff, clab[k]);
9129  cpl_polynomial_set_coeff(fguess, &k, c);
9130  }
9131  }
9132  else {
9133  slope = 1;
9134  }
9135 
9136  for (i = row_bot; i < row_top; i++) {
9137 
9138  /*
9139  * Use first-guess to find the reference lines again
9140  */
9141 
9142  width = mos_lines_width(sdata + i*nx, nx);
9143  if (width > sradius) {
9144  uradius = width;
9145  }
9146  else {
9147  uradius = sradius;
9148  }
9149 
9150  if (slope) {
9151  for (k = 0; k <= order; k++) {
9152  c = cpl_table_get_double(coeff, clab[k],
9153  i - row_bot, NULL);
9154  cpl_polynomial_set_coeff(fguess, &k, c);
9155  }
9156  }
9157 
9158  peaks_ident = mos_find_peaks(sdata + i*nx, nx, lines,
9159  fguess, refwave, uradius);
9160 
9161  if (peaks_ident == NULL) {
9162  cpl_error_reset();
9163  continue;
9164  }
9165 
9166  countLines = cpl_bivector_get_size(peaks_ident);
9167 
9168  if (countLines < 4) {
9169  cpl_bivector_delete(peaks_ident);
9170  continue;
9171  }
9172 
9173  /*
9174  * Set reference wavelength as zero point
9175  */
9176 
9177  wavel = cpl_bivector_get_y(peaks_ident);
9178  cpl_vector_subtract_scalar(wavel, refwave);
9179 
9180  uorder = countLines / 2 - 1;
9181  if (uorder > order)
9182  uorder = order;
9183 
9184  ids = mos_poly_wav2pix(peaks_ident, uorder, reject,
9185  2 * (uorder + 1), &usedLines,
9186  &ids_err);
9187 
9188  if (ids == NULL) {
9189  cpl_error_reset();
9190  cpl_bivector_delete(peaks_ident);
9191  continue;
9192  }
9193 
9194  if (nlines)
9195  nlines[i] = usedLines;
9196  if (error)
9197  error[i] = ids_err / sqrt(usedLines/(uorder + 1));
9198 
9199  if (calibration) {
9200  pixstart = cpl_polynomial_eval_1d(ids,
9201  cpl_bivector_get_y_data(peaks_ident)[0],
9202  NULL);
9203  pixend = cpl_polynomial_eval_1d(ids,
9204  cpl_bivector_get_y_data(peaks_ident)[countLines-1],
9205  NULL);
9206  extrapolation = (pixend - pixstart) / 5;
9207  pixstart -= extrapolation;
9208  pixend += extrapolation;
9209  if (pixstart < 0)
9210  pixstart = 0;
9211  if (pixend > nx)
9212  pixend = nx;
9213 
9214  for (j = pixstart; j < pixend; j++) {
9215  (idata + i*nx)[j] = mos_eval_dds(ids,
9216  firstLambda, lastLambda, refwave, j);
9217  }
9218  }
9219 
9220  /*
9221  * Residuals image
9222  */
9223 
9224  if (residuals || (restable && !(i%step))) {
9225  if (restable && !(i%step)) {
9226  lin = cpl_polynomial_new(1);
9227  for (k = 0; k < 2; k++)
9228  cpl_polynomial_set_coeff(lin, &k,
9229  cpl_polynomial_get_coeff(ids, &k));
9230  }
9231  for (j = 0; j < countLines; j++) {
9232  pixe = cpl_bivector_get_x_data(peaks_ident)[j];
9233  wave = cpl_bivector_get_y_data(peaks_ident)[j];
9234  value = pixe
9235  - cpl_polynomial_eval_1d(ids, wave, NULL);
9236  if (residuals) {
9237  pixel = pixe + 0.5;
9238  (ddata + i*nx)[pixel] = value;
9239  }
9240  if (restable && !(i%step)) {
9241  for (k = 0; k < nref; k++) {
9242  if (fabs(line[k]-refwave-wave) < 0.1) {
9243  snprintf(name, MAX_COLNAME,
9244  "r%d", i);
9245  cpl_table_set_double(restable, name,
9246  k, value);
9247  value = pixe
9248  - cpl_polynomial_eval_1d(lin,
9249  wave, NULL);
9250  snprintf(name, MAX_COLNAME,
9251  "d%d", i);
9252  cpl_table_set_double(restable, name,
9253  k, value);
9254  snprintf(name, MAX_COLNAME,
9255  "p%d", i);
9256  cpl_table_set_double(restable, name,
9257  k, pixe);
9258  break;
9259  }
9260  }
9261  }
9262  }
9263  if (restable && !(i%step)) {
9264  cpl_polynomial_delete(lin);
9265  }
9266 /***
9267  for (j = 0; j < countLines; j++) {
9268  pixel = cpl_bivector_get_x_data(output)[j]
9269  + 0.5;
9270  (ddata + i*nx)[pixel] =
9271  cpl_bivector_get_x_data(output)[j]
9272  - cpl_polynomial_eval_1d(ids,
9273  cpl_bivector_get_y_data(output)[j],
9274  NULL);
9275  }
9276 ***/
9277  }
9278 
9279  if(detected_lines)
9280  {
9281  cpl_size oldsize = cpl_table_get_nrow(detected_lines);
9282  cpl_size nidentlines = cpl_bivector_get_size(peaks_ident);
9283  cpl_table_set_size(detected_lines, oldsize + nidentlines);
9284  for(cpl_size idline = 0; idline < nidentlines ; ++idline)
9285  {
9286  double wave_ident = cpl_bivector_get_y_data(peaks_ident)[idline] + refwave;
9287  double xpix_fit = cpl_polynomial_eval_1d(ids,
9288  wave_ident - refwave, NULL);
9289  cpl_table_set_int(detected_lines, "slit_id",
9290  oldsize + idline, slit_id);
9291  cpl_table_set_double(detected_lines, "xpos_rectified_iter",
9292  oldsize + idline, cpl_bivector_get_x_data(peaks_ident)[idline] + 1);
9293  cpl_table_set_double(detected_lines, "ypos_rectified_iter",
9294  oldsize + idline, (double)i + 1);
9295  cpl_table_set_double(detected_lines, "peak_flux",
9296  oldsize + idline,
9297  sdata[i*nx+(int)(cpl_bivector_get_x_data(peaks_ident)[idline]+0.5)]);
9298  cpl_table_set_double(detected_lines, "wave_ident_iter",
9299  oldsize + idline, wave_ident);
9300  cpl_table_set_double(detected_lines, "xpos_fit_rect_wavecal",
9301  oldsize + idline, xpix_fit + 1);
9302  }
9303  }
9304 
9305 
9306  for (k = 0; k <= order; k++) {
9307  if (k > uorder) {
9308  cpl_table_set_double(idscoeff, clab[k], i, 0.0);
9309  }
9310  else {
9311  cpl_table_set_double(idscoeff, clab[k], i,
9312  cpl_polynomial_get_coeff(ids, &k));
9313  }
9314  }
9315 
9316  cpl_bivector_delete(peaks_ident);
9317  cpl_polynomial_delete(ids);
9318 
9319  } /* End of loop "use ids as a first-guess" */
9320 
9321  cpl_polynomial_delete(fguess);
9322  }
9323 
9324  cpl_table_delete(coeff);
9325 
9326  }
9327 
9328  row_top = row_bot;
9329 
9330  } /* End of loop on slits */
9331 
9332 
9333  /*
9334  * At this point the idscoeff table has been filled with all the
9335  * fits coefficients obtained for all the rows of the input image.
9336  * Now we apply these coefficients to resample the input image
9337  * at constant wavelength step.
9338  */
9339 
9340  for (i = 0; i < ny; i++) {
9341 
9342  missing = 0;
9343  ids = cpl_polynomial_new(1);
9344  for (k = 0; k <= order; k++) {
9345  c = cpl_table_get_double(idscoeff, clab[k], i, &null);
9346  if (null) {
9347  cpl_polynomial_delete(ids);
9348  missing = 1;
9349  break;
9350  }
9351  cpl_polynomial_set_coeff(ids, &k, c);
9352  }
9353  if (missing)
9354  continue;
9355 
9356  pixstart = cpl_polynomial_eval_1d(ids, firstLambda - refwave, NULL);
9357  pixend = cpl_polynomial_eval_1d(ids, lastLambda - refwave, NULL);
9358  if (pixstart < 0)
9359  pixstart = 0;
9360  if (pixend > nx)
9361  pixend = nx;
9362 
9363  /*
9364  * Resampled image:
9365  */
9366 
9367  for (j = 0; j < nl; j++) {
9368  lambda = firstLambda + j * dispersion;
9369  fpixel = cpl_polynomial_eval_1d(ids, lambda - refwave, NULL);
9370  pixel = fpixel;
9371  if (pixel >= 0 && pixel < nx-1) {
9372  v1 = (sdata + i*nx)[pixel];
9373  v2 = (sdata + i*nx)[pixel+1];
9374  vi = v1 + (v2-v1)*(fpixel-pixel);
9375  (rdata + i*nl)[j] = vi;
9376  }
9377  }
9378 
9379  cpl_polynomial_delete(ids);
9380  }
9381 
9382  return resampled;
9383 }
9384 
9385 
9412 cpl_image *mos_wavelength_calibration(cpl_image *image, double refwave,
9413  double firstLambda, double lastLambda,
9414  double dispersion, cpl_table *idscoeff,
9415  int flux)
9416 {
9417 
9418  const char *func = "mos_wavelength_calibration";
9419 
9420  const char *clab[6] = {"c0", "c1", "c2", "c3", "c4", "c5"};
9421  /* Max order is 5 */
9422 
9423  cpl_image *resampled;
9424  cpl_polynomial *ids;
9425  double pixel_per_lambda;
9426  double lambda;
9427  double c;
9428  float *sdata;
9429  float *rdata;
9430  float v0, v1, v2, v3, vi;
9431  float fpixel;
9432  int order;
9433  int pixstart, pixend;
9434  int nl, nx, ny, pixel;
9435  int missing;
9436  int null;
9437  int i, j;
9438  cpl_size k;
9439 
9440 
9441  if (dispersion <= 0.0) {
9442  cpl_msg_error(func, "The resampling step must be positive");
9443  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
9444  return NULL;
9445  }
9446 
9447  if (lastLambda - firstLambda < dispersion) {
9448  cpl_msg_error(func, "Invalid spectral range: %.2f to %.2f",
9449  firstLambda, lastLambda);
9450  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
9451  return NULL;
9452  }
9453 
9454  if (idscoeff == NULL) {
9455  cpl_msg_error(func, "An IDS coeff table must be given");
9456  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
9457  return NULL;
9458  }
9459 
9460  if (image == NULL) {
9461  cpl_msg_error(func, "A scientific spectral image must be given");
9462  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
9463  return NULL;
9464  }
9465 
9466  nx = cpl_image_get_size_x(image);
9467  ny = cpl_image_get_size_y(image);
9468  sdata = cpl_image_get_data_float(image);
9469 
9470  nl = (lastLambda - firstLambda) / dispersion;
9471  resampled = cpl_image_new(nl, ny, CPL_TYPE_FLOAT);
9472  rdata = cpl_image_get_data_float(resampled);
9473 
9474  order = 0;
9475  while (order < 6 && cpl_table_has_column(idscoeff, clab[order]))
9476  ++order;
9477  --order;
9478 
9479  for (i = 0; i < ny; i++) {
9480 
9481  missing = 0;
9482  ids = cpl_polynomial_new(1);
9483  for (k = 0; k <= order; k++) {
9484  c = cpl_table_get_double(idscoeff, clab[k], i, &null);
9485  if (null) {
9486  cpl_polynomial_delete(ids);
9487  missing = 1;
9488  break;
9489  }
9490  cpl_polynomial_set_coeff(ids, &k, c);
9491  }
9492  if (missing)
9493  continue;
9494 
9495  pixstart = cpl_polynomial_eval_1d(ids, firstLambda - refwave, NULL);
9496  pixend = cpl_polynomial_eval_1d(ids, lastLambda - refwave, NULL);
9497  if (pixstart < 0)
9498  pixstart = 0;
9499  if (pixend > nx)
9500  pixend = nx;
9501 
9502  /*
9503  * Resampled image:
9504  */
9505 
9506  for (j = 0; j < nl; j++) {
9507  lambda = firstLambda + j * dispersion;
9508  fpixel = cpl_polynomial_eval_1d(ids, lambda - refwave,
9509  &pixel_per_lambda);
9510 
9511  /*
9512  * The local dispersion is 1 / pixel_per_lambda
9513  * and this factor is used for applying the flux
9514  * conservation correction (if requested).
9515  */
9516 
9517  pixel = fpixel;
9518 
9519  // if (dispersion * pixel_per_lambda < 2.0) {
9520  if (1) { /* Old behaviour: this is safe. */
9521 
9522  /*
9523  * In this case we just sample interpolating the
9524  * signal of nearby pixels.
9525  */
9526 
9527  if (pixel >= 1 && pixel < nx-2) {
9528  v0 = (sdata + i*nx)[pixel-1];
9529  v1 = (sdata + i*nx)[pixel];
9530  v2 = (sdata + i*nx)[pixel+1];
9531  v3 = (sdata + i*nx)[pixel+2];
9532  vi = (fpixel-pixel)*(fpixel-pixel)*(v3 - v2 - v1 + v0)
9533  + (fpixel-pixel)*(3*v2 - v3 - v1 - v0)
9534  + 2*v1;
9535  vi /= 2;
9536  if (v1 > v2) {
9537  if (vi > v1) {
9538  vi = v1;
9539  }
9540  else if (vi < v2) {
9541  vi = v2;
9542  }
9543  }
9544  else {
9545  if (vi > v2) {
9546  vi = v2;
9547  }
9548  else if (vi < v1) {
9549  vi = v1;
9550  }
9551  }
9552  if (flux)
9553  vi *= dispersion * pixel_per_lambda;
9554  (rdata + i*nl)[j] = vi;
9555  }
9556  else if (pixel >= 0 && pixel < nx-1) {
9557  v1 = (sdata + i*nx)[pixel];
9558  v2 = (sdata + i*nx)[pixel+1];
9559  vi = v1 + (v2-v1)*(fpixel-pixel);
9560  if (flux)
9561  vi *= dispersion * pixel_per_lambda;
9562  (rdata + i*nl)[j] = vi;
9563  }
9564  }
9565  else {
9566 
9567  /*
9568  * Here instead we integrate the pixel values in
9569  * the interval centered at the interpolation point.
9570  * This interval is long dispersion * pixel_per_lambda
9571  * of the original pixels, and is centered at fpixel.
9572  * So it starts at fpixel - dispersion * pixel_per_lambda / 2,
9573  * and it ends at fpixel + dispersion * pixel_per_lambda / 2.
9574  */
9575 
9576  double spos = fpixel - dispersion * pixel_per_lambda / 2;
9577  double epos = fpixel + dispersion * pixel_per_lambda / 2;
9578 
9579  /*
9580  * Brutal sum over all involved pixels
9581  */
9582 
9583  int spix = spos;
9584  int epix = epos + 1;
9585 
9586  if (spix < 0)
9587  spix = 0;
9588 
9589  if (epix > nx)
9590  epix = nx;
9591 
9592  vi = 0.0;
9593  for (k = spix; k < epix; k++) {
9594  if (pixel >= 0 && pixel < nx) {
9595  vi += (sdata + i*nx)[k];
9596  }
9597  }
9598 
9599  /*
9600  * Correct integrated flux by true length
9601  * of interval. This is clearly an approximation,
9602  * but it's good enough if the PSF is much larger
9603  * than the original pix.
9604  */
9605 
9606  vi *= dispersion * pixel_per_lambda / (epix - spix);
9607 
9608  /*
9609  * Flux conservation is a geometric factor that is applied
9610  * always in the same way...
9611  */
9612 
9613  if (flux)
9614  vi *= dispersion * pixel_per_lambda;
9615 
9616  (rdata + i*nl)[j] = vi;
9617  }
9618  }
9619 
9620  cpl_polynomial_delete(ids);
9621  }
9622 
9623  return resampled;
9624 }
9625 
9626 
9693 cpl_table *mos_wavelength_align(cpl_image *image, cpl_table *slits,
9694  double refwave, double firstLambda,
9695  double lastLambda, cpl_table *idscoeff,
9696  cpl_vector *skylines, int highres, int order,
9697  cpl_image *calibration, int sradius)
9698 {
9699  const char *func = "mos_wavelength_align";
9700 
9701  const char *clab[6] = {"c0", "c1", "c2", "c3", "c4", "c5"};
9702  /* Max order is 5 */
9703  double *line;
9704  double *data;
9705  double expPos, offset;
9706  double c;
9707  double lambda1, lambda2;
9708  double rms;
9709  float pos;
9710  float *sdata;
9711  float *cdata;
9712  int *idata;
9713  int startPos, endPos;
9714  int window = 2*sradius + 1;
9715  int nlines;
9716  int nslits;
9717  int npoints;
9718  int nrows;
9719  int nx, ny;
9720  int xlow, ylow, xhig, yhig;
9721  int idsorder, uorder;
9722  int *slit_id;
9723  int *position;
9724  int *length;
9725  int missing;
9726  int null;
9727  int i;
9728  cpl_size j, k;
9729 
9730  char offname[MAX_COLNAME];
9731  char name[MAX_COLNAME];
9732 
9733  cpl_polynomial *ids;
9734  cpl_polynomial *polycorr;
9735  cpl_image *exslit;
9736  cpl_image *sky;
9737  cpl_table *offsets;
9738  cpl_table *dummy;
9739  cpl_vector *wave;
9740  cpl_vector *offs;
9741 
9742 
9743  if (idscoeff == NULL) {
9744  cpl_msg_error(func, "An IDS coeff table must be given");
9745  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
9746  return NULL;
9747  }
9748 
9749  if (image == NULL) {
9750  cpl_msg_error(func, "A scientific spectral image must be given");
9751  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
9752  return NULL;
9753  }
9754 
9755  if (slits == NULL) {
9756  cpl_msg_error(func, "A slit position table must be given");
9757  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
9758  return NULL;
9759  }
9760 
9761  if (skylines) {
9762  line = cpl_vector_get_data(skylines);
9763  nlines = cpl_vector_get_size(skylines);
9764  }
9765  else {
9766  cpl_msg_warning(func, "A catalog of sky lines wavelengths was not "
9767  "given: using internal list of reference sky lines");
9768  if (highres) {
9769  line = default_lines_hi;
9770  nlines = sizeof(default_lines_hi) / sizeof(double);
9771  }
9772  else {
9773  line = default_lines_lo;
9774  nlines = sizeof(default_lines_lo) / sizeof(double);
9775  }
9776  }
9777 
9778  if (calibration)
9779  cdata = cpl_image_get_data(calibration);
9780 
9781  nx = cpl_image_get_size_x(image);
9782  ny = cpl_image_get_size_y(image);
9783 
9784  nslits = cpl_table_get_nrow(slits);
9785  slit_id = cpl_table_get_data_int(slits, "slit_id");
9786  position = cpl_table_get_data_int(slits, "position");
9787  length = cpl_table_get_data_int(slits, "length");
9788 
9789 
9790  /*
9791  * Define the output table of offsets
9792  */
9793 
9794  nrows = 0;
9795  for (i = 0; i < nlines; i++)
9796  if (line[i] > firstLambda && line[i] < lastLambda)
9797  nrows++;
9798 
9799  offsets = cpl_table_new(nrows);
9800  cpl_table_new_column(offsets, "wave", CPL_TYPE_DOUBLE);
9801  cpl_table_set_column_unit(offsets, "wave", "Angstrom");
9802 
9803  nrows = 0;
9804  for (i = 0; i < nlines; i++) {
9805  if (line[i] > firstLambda && line[i] < lastLambda) {
9806  cpl_table_set_double(offsets, "wave", nrows, line[i]);
9807  nrows++;
9808  }
9809  }
9810 
9811  /*
9812  * Here "line" is made to point to the new list of selected wavelengths
9813  */
9814 
9815  line = cpl_table_get_data_double(offsets, "wave");
9816  nlines = nrows;
9817 
9818  idsorder = 0;
9819  while (idsorder < 6 && cpl_table_has_column(idscoeff, clab[idsorder]))
9820  ++idsorder;
9821  --idsorder;
9822 
9823  xlow = 1;
9824  xhig = nx;
9825  for (i = 0; i < nslits; i++) {
9826 
9827  if (length[i] == 0)
9828  continue;
9829 
9830  snprintf(offname, MAX_COLNAME, "offset%d", slit_id[i]);
9831  cpl_table_new_column(offsets, offname, CPL_TYPE_DOUBLE);
9832 
9833  /*
9834  * Define the extraction boundaries. We DON'T write:
9835  *
9836  * ylow = position[i];
9837  * yhig = ylow + length[i];
9838  *
9839  * because the cpl_image pixels are counted from 1, and because in
9840  * cpl_image_extract() the coordinates of the last pixel are inclusive.
9841  */
9842 
9843  ylow = position[i] + 1;
9844  yhig = ylow + length[i] - 1;
9845 
9846  exslit = cpl_image_extract(image, xlow, ylow, xhig, yhig);
9847  sky = cpl_image_collapse_median_create(exslit, 0, 0, 1);
9848  sdata = cpl_image_get_data(sky);
9849 
9850  cpl_image_delete(exslit);
9851 
9852  /*
9853  * Return here to a decent way of counting pixels (i.e., starting
9854  * from 0)
9855  */
9856 
9857  ylow--;
9858 
9859  /*
9860  * Allocate a dummy table for collecting all the offsets
9861  * for all the lines: this is only needed for the computation
9862  * of the median offset for each sky line
9863  */
9864 
9865  dummy = cpl_table_new(yhig - ylow);
9866  for (j = 0; j < nlines; j++) {
9867  snprintf(name, MAX_COLNAME, "%"CPL_SIZE_FORMAT, j);
9868  cpl_table_new_column(dummy, name, CPL_TYPE_DOUBLE);
9869  }
9870 
9871  for (j = ylow; j < yhig; j++) {
9872 
9873  /*
9874  * Get the IDS polynomial for the current slit row
9875  */
9876 
9877  missing = 0;
9878  ids = cpl_polynomial_new(1);
9879  for (k = 0; k <= idsorder; k++) {
9880  c = cpl_table_get_double(idscoeff, clab[k], j, &null);
9881  if (null) {
9882  cpl_polynomial_delete(ids);
9883  missing = 1;
9884  break;
9885  }
9886  cpl_polynomial_set_coeff(ids, &k, c);
9887  }
9888  if (missing)
9889  continue;
9890 
9891  for (k = 0; k < nlines; k++) {
9892  expPos = cpl_polynomial_eval_1d(ids, line[k] - refwave, NULL);
9893  startPos = expPos - sradius;
9894  endPos = startPos + window;
9895  if (startPos < 0 || endPos >= nx)
9896  continue;
9897 
9898  if (0 == peakPosition(sdata + startPos, window, &pos, 1)) {
9899  pos += startPos;
9900  offset = pos - expPos;
9901  snprintf(name, MAX_COLNAME, "%"CPL_SIZE_FORMAT, k);
9902  cpl_table_set_double(dummy, name, j - ylow, offset);
9903  }
9904  }
9905 
9906  cpl_polynomial_delete(ids);
9907  }
9908 
9909  cpl_image_delete(sky);
9910 
9911  for (j = 0; j < nlines; j++) {
9912  snprintf(name, MAX_COLNAME, "%"CPL_SIZE_FORMAT, j);
9913  if (cpl_table_has_valid(dummy, name)) {
9914  offset = cpl_table_get_column_median(dummy, name);
9915  cpl_table_set_double(offsets, offname, j, offset);
9916  }
9917  }
9918 
9919  cpl_table_delete(dummy);
9920 
9921  }
9922 
9923 
9924  /*
9925  * In the following the input idscoeff table is modified by simply
9926  * adding the coefficients of the polynomial used to fit the sky
9927  * line residuals to the coefficients of the IDS polynomials.
9928  */
9929 
9930  for (i = 0; i < nslits; i++) {
9931 
9932  if (length[i] == 0)
9933  continue;
9934 
9935  snprintf(offname, MAX_COLNAME, "offset%d", slit_id[i]);
9936 
9937  /*
9938  * In the following, the "dummy" table is just a tool for
9939  * eliminating invalid points from the vectors to be fitted.
9940  */
9941 
9942  dummy = cpl_table_new(nlines);
9943  cpl_table_duplicate_column(dummy, "wave", offsets, "wave");
9944  cpl_table_duplicate_column(dummy, "offset", offsets, offname);
9945 
9946  npoints = nlines - cpl_table_count_invalid(dummy, "offset");
9947  if (npoints == 0) {
9948  cpl_msg_warning(func, "No sky lines alignment was possible "
9949  "for slit ID=%d: no sky line found", slit_id[i]);
9950  cpl_table_delete(dummy);
9951  continue;
9952  }
9953 
9954  uorder = order;
9955  if (npoints <= uorder) {
9956  uorder = npoints - 1;
9957  if (uorder) {
9958  cpl_msg_warning(func, "Just %d sky lines detected for slit "
9959  "ID=%d, while a polynomial order %d was "
9960  "requested. Using polynomial order %d for "
9961  "this slit!", npoints, slit_id[i], order,
9962  uorder);
9963  }
9964  else {
9965  cpl_msg_warning(func, "Just %d sky lines detected for slit "
9966  "ID=%d, while a polynomial order %d was "
9967  "requested. Computing a median offset for "
9968  "this slit!", npoints, slit_id[i], order);
9969  }
9970  }
9971 
9972  cpl_table_erase_invalid(dummy);
9973 
9974  if (uorder > 1) {
9975 
9976  /*
9977  * Model offsets with polynomial fitting
9978  */
9979 
9980  wave = cpl_vector_wrap(npoints,
9981  cpl_table_get_data_double(dummy, "wave"));
9982  offs = cpl_vector_wrap(npoints,
9983  cpl_table_get_data_double(dummy, "offset"));
9984 
9985  /*
9986  * Set reference wavelength as zero point
9987  */
9988 
9989  cpl_vector_subtract_scalar(wave, refwave);
9990 
9991  polycorr = cpl_polynomial_fit_1d_create(wave, offs, uorder, &rms);
9992 
9993  rms = sqrt(rms * (uorder + 1) / npoints);
9994 
9995  cpl_vector_unwrap(wave);
9996  cpl_vector_unwrap(offs);
9997  cpl_table_delete(dummy);
9998 
9999  /*
10000  * Now correct the coefficients of the corresponding IDS
10001  * polynomials related to this slit:
10002  */
10003 
10004  ylow = position[i];
10005  yhig = ylow + length[i];
10006 
10007  for (j = 0; j <= uorder; j++) {
10008  data = cpl_table_get_data_double(idscoeff, clab[j]);
10009  c = cpl_polynomial_get_coeff(polycorr, &j);
10010  for (k = ylow; k < yhig; k++)
10011  data[k] += c;
10012  }
10013 
10014  data = cpl_table_get_data_double(idscoeff, "error");
10015  for (k = ylow; k < yhig; k++)
10016  data[k] = sqrt(data[k]*data[k] + rms*rms);
10017 
10018  idata = cpl_table_get_data_int(idscoeff, "nlines");
10019  for (k = ylow; k < yhig; k++)
10020  idata[k] = npoints;
10021 
10022  /*
10023  * If a wavelengths map was provided, correct it to keep
10024  * into account the alignment to skylines:
10025  */
10026 
10027  if (calibration) {
10028  for (j = ylow; j < yhig; j++) {
10029  for (k = 1; k < nx; k++) {
10030  lambda1 = cdata[k - 1 + j*nx];
10031  lambda2 = cdata[k + j*nx];
10032  if (lambda1 < 1.0 || lambda2 < 1.0)
10033  continue;
10034  offset = cpl_polynomial_eval_1d(polycorr,
10035  lambda1-refwave, NULL);
10036  cdata[k - 1 + j*nx] -= offset * (lambda2-lambda1);
10037  }
10038  }
10039  }
10040 
10041  cpl_polynomial_delete(polycorr);
10042  }
10043  else if (uorder == 1) {
10044 
10045  /*
10046  * Model offsets with robust linear fitting
10047  */
10048 
10049  double q, m;
10050  cpl_bivector *list;
10051 
10052 
10053  wave = cpl_vector_wrap(npoints,
10054  cpl_table_get_data_double(dummy, "wave"));
10055  offs = cpl_vector_wrap(npoints,
10056  cpl_table_get_data_double(dummy, "offset"));
10057 
10058  list = cpl_bivector_wrap_vectors(wave, offs);
10059 
10060  /*
10061  * Set reference wavelength as zero point
10062  */
10063 
10064  cpl_vector_subtract_scalar(wave, refwave);
10065 
10066  robustLinearFit(list, &q, &m, &rms);
10067 
10068  rms = sqrt(rms * (uorder + 1) / npoints);
10069 
10070  cpl_bivector_unwrap_vectors(list);
10071  cpl_vector_unwrap(wave);
10072  cpl_vector_unwrap(offs);
10073  cpl_table_delete(dummy);
10074 
10075  /*
10076  * Now correct the coefficients of the corresponding IDS
10077  * polynomials related to this slit:
10078  */
10079 
10080  ylow = position[i];
10081  yhig = ylow + length[i];
10082 
10083  for (j = 0; j <= uorder; j++) {
10084  data = cpl_table_get_data_double(idscoeff, clab[j]);
10085  if (j)
10086  c = m;
10087  else
10088  c = q;
10089  for (k = ylow; k < yhig; k++)
10090  data[k] += c;
10091  }
10092 
10093  data = cpl_table_get_data_double(idscoeff, "error");
10094  for (k = ylow; k < yhig; k++)
10095  data[k] = sqrt(data[k]*data[k] + rms*rms);
10096 
10097  idata = cpl_table_get_data_int(idscoeff, "nlines");
10098  for (k = ylow; k < yhig; k++)
10099  idata[k] = npoints;
10100 
10101  /*
10102  * If a wavelengths map was provided, correct it to keep
10103  * into account the alignment to skylines:
10104  */
10105 
10106  if (calibration) {
10107  for (j = ylow; j < yhig; j++) {
10108  for (k = 1; k < nx; k++) {
10109  lambda1 = cdata[k - 1 + j*nx];
10110  lambda2 = cdata[k + j*nx];
10111  if (lambda1 < 1.0 || lambda2 < 1.0)
10112  continue;
10113  offset = q + m*(lambda1-refwave);
10114  cdata[k - 1 + j*nx] -= offset * (lambda2-lambda1);
10115  }
10116  }
10117  }
10118  }
10119  else {
10120 
10121  /*
10122  * Just compute median offset
10123  */
10124 
10125  offs = cpl_vector_wrap(npoints,
10126  cpl_table_get_data_double(dummy, "offset"));
10127 
10128  offset = cpl_vector_get_median_const(offs);
10129 
10130  if (npoints > 1)
10131  rms = cpl_table_get_column_stdev(dummy, "offset");
10132  else
10133  rms = 0.0;
10134 
10135  rms /= sqrt(npoints);
10136 
10137  cpl_vector_unwrap(offs);
10138  cpl_table_delete(dummy);
10139 
10140  /*
10141  * Now correct the constant term of the corresponding IDS
10142  * polynomials related to this slit:
10143  */
10144 
10145  ylow = position[i];
10146  yhig = ylow + length[i];
10147 
10148  data = cpl_table_get_data_double(idscoeff, clab[0]);
10149  for (k = ylow; k < yhig; k++)
10150  data[k] += offset;
10151 
10152  data = cpl_table_get_data_double(idscoeff, "error");
10153  for (k = ylow; k < yhig; k++)
10154  data[k] = sqrt(data[k]*data[k] + rms*rms);
10155 
10156  idata = cpl_table_get_data_int(idscoeff, "nlines");
10157  for (k = ylow; k < yhig; k++)
10158  idata[k] = npoints;
10159 
10160  /*
10161  * If a wavelengths map was provided, correct it to keep
10162  * into account the alignment to skylines. Note that
10163  * the offset must be converted from pixels to wavelengths.
10164  */
10165 
10166  if (calibration) {
10167  for (j = ylow; j < yhig; j++) {
10168  for (k = 1; k < nx; k++) {
10169  lambda1 = cdata[k - 1 + j*nx];
10170  lambda2 = cdata[k + j*nx];
10171  if (lambda1 < 1.0 || lambda2 < 1.0)
10172  continue;
10173  cdata[k - 1 + j*nx] -= offset * (lambda2-lambda1);
10174  }
10175  }
10176  }
10177  }
10178  }
10179 
10180  return offsets;
10181 
10182 }
10183 
10184 
10246 cpl_table *mos_wavelength_align_lss(cpl_image *image, double refwave,
10247  double firstLambda, double lastLambda,
10248  cpl_table *idscoeff, cpl_vector *skylines,
10249  int highres, int order,
10250  cpl_image *calibration, int sradius)
10251 {
10252  const char *func = "mos_wavelength_align_lss";
10253 
10254  const char *clab[6] = {"c0", "c1", "c2", "c3", "c4", "c5"};
10255  /* Max order is 5 */
10256  double *line;
10257  double *data;
10258  double *wdata;
10259  double *odata;
10260  double expPos, offset;
10261  double c;
10262  double lambda1, lambda2;
10263  double rms;
10264  float pos;
10265  float *sdata;
10266  float *cdata;
10267  int *idata;
10268  int startPos, endPos;
10269  int window = 2*sradius + 1;
10270  int nlines;
10271  int npoints;
10272  int nrows;
10273  int nx, ny;
10274  int idsorder, uorder;
10275  int missing;
10276  int i;
10277  cpl_size j, k;
10278 
10279  char name[MAX_COLNAME];
10280  char fname[MAX_COLNAME];
10281 
10282  cpl_polynomial *ids;
10283  cpl_polynomial *polycorr;
10284  cpl_table *offsets;
10285  cpl_table *fittable;
10286  cpl_table *dummy;
10287  cpl_vector *wave;
10288  cpl_vector *offs;
10289  cpl_vector *row;
10290 
10291 
10292  if (idscoeff == NULL) {
10293  cpl_msg_error(func, "An IDS coeff table must be given");
10294  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
10295  return NULL;
10296  }
10297 
10298  if (image == NULL) {
10299  cpl_msg_error(func, "A scientific spectral image must be given");
10300  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
10301  return NULL;
10302  }
10303 
10304  if (skylines) {
10305  line = cpl_vector_get_data(skylines);
10306  nlines = cpl_vector_get_size(skylines);
10307  }
10308  else {
10309  cpl_msg_warning(func, "A catalog of sky lines wavelengths was not "
10310  "given: using internal list of reference sky lines");
10311  if (highres) {
10312  line = default_lines_hi;
10313  nlines = sizeof(default_lines_hi) / sizeof(double);
10314  }
10315  else {
10316  line = default_lines_lo;
10317  nlines = sizeof(default_lines_lo) / sizeof(double);
10318  }
10319  }
10320 
10321  if (calibration)
10322  cdata = cpl_image_get_data(calibration);
10323 
10324  nx = cpl_image_get_size_x(image);
10325  ny = cpl_image_get_size_y(image);
10326 
10327  sdata = cpl_image_get_data(image);
10328 
10329  if (ny != cpl_table_get_nrow(idscoeff)) {
10330  cpl_error_set(func, CPL_ERROR_INCOMPATIBLE_INPUT);
10331  return NULL;
10332  }
10333 
10334 
10335  /*FIXME: This is a remnant of the adaptation of the function
10336  * mos_wavelength_align(), where an offset table was created.
10337  * I leave it here because I am in a hurry, it is just used to
10338  * hold the list of selected sky lines.
10339  *
10340  * Define table of wavelengths
10341  */
10342 
10343  nrows = 0;
10344  for (i = 0; i < nlines; i++)
10345  if (line[i] > firstLambda && line[i] < lastLambda)
10346  nrows++;
10347 
10348  offsets = cpl_table_new(nrows);
10349  cpl_table_new_column(offsets, "wave", CPL_TYPE_DOUBLE);
10350  cpl_table_set_column_unit(offsets, "wave", "Angstrom");
10351 
10352  nrows = 0;
10353  for (i = 0; i < nlines; i++) {
10354  if (line[i] > firstLambda && line[i] < lastLambda) {
10355  cpl_table_set_double(offsets, "wave", nrows, line[i]);
10356  nrows++;
10357  }
10358  }
10359 
10360  /*
10361  * Here "line" is made to point to the new list of selected wavelengths
10362  */
10363 
10364  line = cpl_table_get_data_double(offsets, "wave");
10365  nlines = nrows;
10366 
10367  idsorder = 0;
10368  while (idsorder < 6 && cpl_table_has_column(idscoeff, clab[idsorder]))
10369  ++idsorder;
10370  --idsorder;
10371 
10372 
10373  /*
10374  * Allocate a dummy table for collecting all the offsets
10375  * for all the lines
10376  */
10377 
10378  dummy = cpl_table_new(ny);
10379  for (j = 0; j < nlines; j++) {
10380  snprintf(name, MAX_COLNAME, "off_%d", (int)line[j]);
10381  snprintf(fname, MAX_COLNAME, "fit_%d", (int)line[j]);
10382  cpl_table_new_column(dummy, name, CPL_TYPE_DOUBLE);
10383  cpl_table_new_column(dummy, fname, CPL_TYPE_DOUBLE);
10384  }
10385 
10386  for (j = 0; j < ny; j++, sdata += nx) {
10387 
10388  /*
10389  * Get the IDS polynomial for the current slit row
10390  */
10391 
10392  missing = 0;
10393  ids = cpl_polynomial_new(1);
10394  for (k = 0; k <= idsorder; k++) {
10395  c = cpl_table_get_double(idscoeff, clab[k], j, &missing);
10396  if (missing) {
10397  cpl_polynomial_delete(ids);
10398  break;
10399  }
10400  cpl_polynomial_set_coeff(ids, &k, c);
10401  }
10402  if (missing)
10403  continue;
10404 
10405  for (k = 0; k < nlines; k++) {
10406  expPos = cpl_polynomial_eval_1d(ids, line[k] - refwave, NULL);
10407  startPos = expPos - sradius;
10408  endPos = startPos + window;
10409  if (startPos < 0 || endPos >= nx)
10410  continue;
10411 
10412  if (0 == peakPosition(sdata + startPos, window, &pos, 1)) {
10413  pos += startPos;
10414  offset = pos - expPos;
10415  snprintf(name, MAX_COLNAME, "off_%d", (int)line[k]);
10416  cpl_table_set_double(dummy, name, j, offset);
10417  }
10418  }
10419 
10420  cpl_polynomial_delete(ids);
10421  }
10422 
10423 
10424  /*
10425  * At this point for each sky line we model its offset along
10426  * the image rows using a robust linear fitting
10427  */
10428 
10429  for (j = 0; j < nlines; j++) {
10430  snprintf(name, MAX_COLNAME, "off_%d", (int)line[j]);
10431  snprintf(fname, MAX_COLNAME, "fit_%d", (int)line[j]);
10432  if (cpl_table_has_valid(dummy, name)) {
10433 
10434  /*
10435  * In the following, the "fittable" is just a tool for
10436  * eliminating invalid points from the vectors to be fitted.
10437  */
10438 
10439  double q, m;
10440  cpl_bivector *list;
10441 
10442  fittable = cpl_table_new(ny);
10443  cpl_table_new_column(fittable, "row", CPL_TYPE_DOUBLE);
10444  cpl_table_set_column_unit(fittable, "row", "pixel");
10445  for (k = 0; k < ny; k++)
10446  cpl_table_set_double(fittable, "row", k, k);
10447  cpl_table_duplicate_column(fittable, "offset", dummy, name);
10448  npoints = ny - cpl_table_count_invalid(fittable, "offset");
10449  cpl_table_erase_invalid(fittable);
10450  row = cpl_vector_wrap(npoints,
10451  cpl_table_get_data_double(fittable, "row"));
10452  offs = cpl_vector_wrap(npoints,
10453  cpl_table_get_data_double(fittable, "offset"));
10454  list = cpl_bivector_wrap_vectors(row, offs);
10455  robustLinearFit(list, &q, &m, &rms);
10456  cpl_bivector_unwrap_vectors(list);
10457  cpl_vector_unwrap(row);
10458  cpl_vector_unwrap(offs);
10459  cpl_table_delete(fittable);
10460  for (k = 0; k < ny; k++)
10461  cpl_table_set_double(dummy, fname, k, q + m*k);
10462  }
10463  }
10464 
10465 
10466  /*
10467  * Now each dummy table row consists of a sequence of offsets,
10468  * one for each wavelength. A table row corresponds to an image row.
10469  * We must fit a polynomial to each one of these rows, in order to
10470  * express the offsets as a function of wavelength. The obtained
10471  * polynomial coefficients are used to correct the IDS coefficients.
10472  */
10473 
10474  for (i = 0; i < ny; i++) {
10475 
10476  if (!cpl_table_is_valid(idscoeff, clab[0], i))
10477  continue;
10478 
10479  npoints = 0;
10480  for (j = 0; j < nlines; j++) {
10481  snprintf(name, MAX_COLNAME, "fit_%d", (int)line[j]);
10482  if (cpl_table_is_valid(dummy, name, i))
10483  npoints++;
10484  }
10485 
10486  if (npoints == 0)
10487  continue;
10488 
10489  uorder = order;
10490  if (npoints <= uorder)
10491  uorder = npoints - 1;
10492 
10493  if (uorder > 1) {
10494 
10495  /*
10496  * Model offsets with polynomial fitting
10497  */
10498 
10499  wave = cpl_vector_new(npoints);
10500  wdata = cpl_vector_get_data(wave);
10501  offs = cpl_vector_new(npoints);
10502  odata = cpl_vector_get_data(offs);
10503 
10504  npoints = 0;
10505  for (j = 0; j < nlines; j++) {
10506  snprintf(name, MAX_COLNAME, "fit_%d", (int)line[j]);
10507  if (cpl_table_is_valid(dummy, name, i)) {
10508  wdata[npoints] = line[j] - refwave;
10509  odata[npoints] = cpl_table_get_double(dummy, name, i, NULL);
10510  npoints++;
10511  }
10512  }
10513 
10514  polycorr = cpl_polynomial_fit_1d_create(wave, offs, uorder, &rms);
10515 
10516  rms = sqrt(rms * (uorder + 1) / npoints);
10517 
10518  cpl_vector_delete(wave);
10519  cpl_vector_delete(offs);
10520 
10521  /*
10522  * Now correct the coefficients of the corresponding IDS
10523  * polynomials related to this slit:
10524  */
10525 
10526  for (j = 0; j <= uorder; j++) {
10527  data = cpl_table_get_data_double(idscoeff, clab[j]);
10528  c = cpl_polynomial_get_coeff(polycorr, &j);
10529  data[i] += c;
10530  }
10531 
10532  data = cpl_table_get_data_double(idscoeff, "error");
10533  data[i] = sqrt(data[i]*data[i] + rms*rms);
10534 
10535  idata = cpl_table_get_data_int(idscoeff, "nlines");
10536  idata[i] = npoints;
10537 
10538  /*
10539  * If a wavelengths map was provided, correct it to keep
10540  * into account the alignment to skylines:
10541  */
10542 
10543  if (calibration) {
10544  for (k = 1; k < nx; k++) {
10545  lambda1 = cdata[k - 1 + i*nx];
10546  lambda2 = cdata[k + i*nx];
10547  if (lambda1 < 1.0 || lambda2 < 1.0)
10548  continue;
10549  offset = cpl_polynomial_eval_1d(polycorr,
10550  lambda1-refwave, NULL);
10551  cdata[k - 1 + i*nx] -= offset * (lambda2-lambda1);
10552  }
10553  }
10554 
10555  cpl_polynomial_delete(polycorr);
10556 
10557  }
10558  else if (uorder == 1) {
10559 
10560  /*
10561  * Model offsets with robust linear fitting
10562  */
10563 
10564  cpl_bivector *list;
10565  double q, m;
10566 
10567  wave = cpl_vector_new(npoints);
10568  wdata = cpl_vector_get_data(wave);
10569  offs = cpl_vector_new(npoints);
10570  odata = cpl_vector_get_data(offs);
10571 
10572  npoints = 0;
10573  for (j = 0; j < nlines; j++) {
10574  snprintf(name, MAX_COLNAME, "fit_%d", (int)line[j]);
10575  if (cpl_table_is_valid(dummy, name, i)) {
10576  wdata[npoints] = line[j] - refwave;
10577  odata[npoints] = cpl_table_get_double(dummy, name, i, NULL);
10578  npoints++;
10579  }
10580  }
10581 
10582  list = cpl_bivector_wrap_vectors(wave, offs);
10583  robustLinearFit(list, &q, &m, &rms);
10584 
10585  rms = sqrt(rms * (uorder + 1) / npoints);
10586 
10587  cpl_bivector_unwrap_vectors(list);
10588  cpl_vector_delete(wave);
10589  cpl_vector_delete(offs);
10590 
10591  /*
10592  * Now correct the coefficients of the corresponding IDS
10593  * polynomials related to this row:
10594  */
10595 
10596  for (j = 0; j <= uorder; j++) {
10597  data = cpl_table_get_data_double(idscoeff, clab[j]);
10598  if (j)
10599  c = m;
10600  else
10601  c = q;
10602  data[i] += c;
10603  }
10604 
10605  data = cpl_table_get_data_double(idscoeff, "error");
10606  data[i] = sqrt(data[i]*data[i] + rms*rms);
10607 
10608  idata = cpl_table_get_data_int(idscoeff, "nlines");
10609  idata[i] = npoints;
10610 
10611  /*
10612  * If a wavelengths map was provided, correct it to keep
10613  * into account the alignment to skylines:
10614  */
10615 
10616  if (calibration) {
10617  for (k = 1; k < nx; k++) {
10618  lambda1 = cdata[k - 1 + i*nx];
10619  lambda2 = cdata[k + i*nx];
10620  if (lambda1 < 1.0 || lambda2 < 1.0)
10621  continue;
10622  offset = q + m*(lambda1-refwave);
10623  cdata[k - 1 + i*nx] -= offset * (lambda2-lambda1);
10624  }
10625  }
10626  }
10627  else {
10628 
10629  /*
10630  * Just compute median offset
10631  */
10632 
10633  offs = cpl_vector_new(npoints);
10634  odata = cpl_vector_get_data(offs);
10635 
10636  npoints = 0;
10637  for (j = 0; j < nlines; j++) {
10638  snprintf(name, MAX_COLNAME, "fit_%d", (int)line[j]);
10639  if (cpl_table_is_valid(dummy, name, i)) {
10640  odata[npoints] = cpl_table_get_double(dummy, name, i, NULL);
10641  npoints++;
10642  }
10643  }
10644 
10645  offset = cpl_vector_get_median_const(offs);
10646 
10647  if (npoints > 1) {
10648  rms = cpl_vector_get_stdev(offs);
10649  }
10650  else if (npoints == 1) {
10651  snprintf(name, MAX_COLNAME, "off_%d", (int)line[0]);
10652  if (cpl_table_has_valid(dummy, name)) {
10653  rms = cpl_table_get_column_stdev(dummy, name);
10654  rms /= sqrt(ny - cpl_table_count_invalid(dummy, name));
10655  }
10656  else {
10657  rms = 0.0;
10658  }
10659  }
10660  else {
10661  rms = 0.0;
10662  }
10663 
10664  rms /= sqrt(npoints);
10665 
10666  cpl_vector_delete(offs);
10667 
10668  /*
10669  * Now correct the constant term of the corresponding IDS
10670  * polynomials related to this slit:
10671  */
10672 
10673  data = cpl_table_get_data_double(idscoeff, clab[0]);
10674  data[i] += offset;
10675 
10676  data = cpl_table_get_data_double(idscoeff, "error");
10677  data[i] = sqrt(data[i]*data[i] + rms*rms);
10678 
10679  idata = cpl_table_get_data_int(idscoeff, "nlines");
10680  idata[i] = npoints;
10681 
10682  /*
10683  * If a wavelengths map was provided, correct it to keep
10684  * into account the alignment to skylines. Note that
10685  * the offset must be converted from pixels to wavelengths.
10686  */
10687 
10688  if (calibration) {
10689  for (k = 1; k < nx; k++) {
10690  lambda1 = cdata[k - 1 + i*nx];
10691  lambda2 = cdata[k + i*nx];
10692  if (lambda1 < 1.0 || lambda2 < 1.0)
10693  continue;
10694  cdata[k - 1 + i*nx] -= offset * (lambda2-lambda1);
10695  }
10696  }
10697  }
10698  }
10699 
10700  missing = 1;
10701  for (j = 0; j < nlines; j++) {
10702  snprintf(name, MAX_COLNAME, "off_%d", (int)line[j]);
10703  if (cpl_table_has_valid(dummy, name)) {
10704  missing = 0;
10705  offset = cpl_table_get_column_median(dummy, name);
10706  cpl_msg_info(func, "Median offset for %.3f: %.3f pixel",
10707  line[j], offset);
10708  }
10709  else {
10710  cpl_msg_info(func,
10711  "Median offset for %.2f: not available", line[j]);
10712  }
10713  }
10714 
10715  cpl_table_delete(offsets);
10716 
10717  if (missing) {
10718  cpl_table_delete(dummy);
10719  dummy = NULL;
10720  }
10721 
10722  return dummy;
10723 
10724 }
10725 
10726 
10754 double mos_distortions_rms(cpl_image *rectified, cpl_vector *lines,
10755  double wavestart, double dispersion, int radius,
10756  int highres)
10757 {
10758 
10759  const char *func = "mos_distortions_rms";
10760 
10761  int xlen;
10762  int ylen;
10763  int numLines;
10764  int cpix, npix, nzero;
10765  int sp, ep;
10766  int i, j, k;
10767  int npeaks, allPeaks;
10768 
10769  float *profile;
10770  float peak, expectPeak, offset;
10771  double lambda;
10772 
10773  double average;
10774  double rms, oneRms;
10775 
10776  float *sdata;
10777  double *wdata;
10778 
10779 
10780  xlen = cpl_image_get_size_x(rectified);
10781  ylen = cpl_image_get_size_y(rectified);
10782  sdata = cpl_image_get_data(rectified);
10783 
10784  if (lines) {
10785  wdata = cpl_vector_get_data(lines);
10786  numLines = cpl_vector_get_size(lines);
10787  }
10788  else {
10789  cpl_msg_warning(func, "A catalog of sky lines wavelengths was not "
10790  "given: using internal list of reference sky lines");
10791  if (highres) {
10792  wdata = default_lines_hi;
10793  numLines = sizeof(default_lines_hi) / sizeof(double);
10794  }
10795  else {
10796  wdata = default_lines_lo;
10797  numLines = sizeof(default_lines_lo) / sizeof(double);
10798  }
10799  }
10800 
10801  npix = 2 * radius + 1;
10802  profile = cpl_calloc(npix, sizeof(float));
10803 
10804  rms = 0.0;
10805  allPeaks = 0;
10806 
10807  for (i = 0; i < numLines; i++) {
10808 
10809  /*
10810  * Expected peak and closest pixel to specified wavelength.
10811  */
10812 
10813  lambda = wdata[i];
10814  expectPeak = (lambda - wavestart) / dispersion;
10815  cpix = floor(expectPeak + 0.5);
10816 
10817  /*
10818  * Search interval for peak. Abort if too close to image border.
10819  */
10820 
10821  sp = cpix - radius;
10822  ep = cpix + radius;
10823 
10824  if (sp < 0 || ep > xlen)
10825  continue;
10826 
10827  average = 0.0;
10828  npeaks = 0;
10829  oneRms = 0.0;
10830 
10831  for (j = 0; j < ylen; j++) { /* For each row of each slit */
10832  nzero = 0;
10833  for (k = 0; k < npix; k++) {
10834  profile[k] = sdata[sp + k + j * xlen];
10835  if (fabs(profile[k]) < 0.0001)
10836  nzero++; /* Count number of 0 pixels (spectrum truncated) */
10837  }
10838  if (nzero > 0)
10839  continue;
10840 
10841  if (peakPosition(profile, npix, &peak, 1) == 0) {
10842  offset = (sp + peak) - expectPeak;
10843  average += offset;
10844  rms += fabs(offset);
10845  oneRms += fabs(offset);
10846  npeaks++;
10847  allPeaks++;
10848  }
10849  }
10850 
10851  if (npeaks)
10852  cpl_msg_info(func, "RMS for %.2f: %.3f pixel (%d points)",
10853  lambda, oneRms / npeaks * 1.25, npeaks);
10854  else
10855  cpl_msg_info(func, "RMS for %.2f: line not available", lambda);
10856  }
10857 
10858  cpl_free(profile);
10859 
10860  if (allPeaks < 10)
10861  return 0.0;
10862 
10863  rms /= allPeaks;
10864  rms *= 1.25; /* Factor to convert average deviation to sigma */
10865 
10866  return rms;
10867 
10868 }
10869 
10870 
10891 cpl_image *mos_map_pixel(cpl_table *idscoeff, double reference,
10892  double blue, double red, double dispersion, int trend)
10893 {
10894  const char *func = "mos_map_pixel";
10895 
10896  const char *clab[6] = {"c0", "c1", "c2", "c3", "c4", "c5"};
10897  /* Max order is 5 */
10898 
10899  cpl_polynomial *ids;
10900  cpl_image *map;
10901  float *mdata;
10902  double lambda;
10903  double c;
10904  int order;
10905  int xsize, ysize;
10906  int missing;
10907  int i, j;
10908  cpl_size k;
10909 
10910 
10911  if (idscoeff == NULL) {
10912  cpl_msg_error(func, "An IDS coeff table must be given");
10913  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
10914  return NULL;
10915  }
10916 
10917  xsize = (red - blue) / dispersion;
10918  ysize = cpl_table_get_nrow(idscoeff);
10919  map = cpl_image_new(xsize, ysize, CPL_TYPE_FLOAT);
10920  mdata = cpl_image_get_data(map);
10921 
10922  order = 0;
10923  while (order < 6 && cpl_table_has_column(idscoeff, clab[order]))
10924  ++order;
10925  --order;
10926 
10927  for (i = 0; i < ysize; i++, mdata += xsize) {
10928 
10929  missing = 0;
10930  ids = cpl_polynomial_new(1);
10931  for (k = trend; k <= order; k++) {
10932  c = cpl_table_get_double(idscoeff, clab[k], i, &missing);
10933  if (missing) {
10934  cpl_polynomial_delete(ids);
10935  break;
10936  }
10937  cpl_polynomial_set_coeff(ids, &k, c);
10938  }
10939  if (missing)
10940  continue;
10941 
10942  for (j = 0; j < xsize; j++) {
10943  lambda = blue + j*dispersion;
10944  mdata[j] = cpl_polynomial_eval_1d(ids, lambda-reference, NULL);
10945  }
10946 
10947  cpl_polynomial_delete(ids);
10948  }
10949 
10950  return map;
10951 
10952 }
10953 
10954 
10976 cpl_image *mos_map_idscoeff(cpl_table *idscoeff, int xsize, double reference,
10977  double blue, double red)
10978 {
10979  const char *func = "mos_map_idscoeff";
10980 
10981  const char *clab[6] = {"c0", "c1", "c2", "c3", "c4", "c5"};
10982  /* Max order is 5 */
10983 
10984  cpl_polynomial *ids;
10985  cpl_image *map;
10986  float *mdata;
10987  double lambda;
10988  double c;
10989  int order;
10990  int ysize;
10991  int missing;
10992  int i, j;
10993  cpl_size k;
10994 
10995 
10996  if (idscoeff == NULL) {
10997  cpl_msg_error(func, "An IDS coeff table must be given");
10998  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
10999  return NULL;
11000  }
11001 
11002  if (xsize < 1) {
11003  cpl_msg_error(func, "Invalid image size");
11004  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
11005  return NULL;
11006  }
11007 
11008  if (xsize < 20 || xsize > 5000) {
11009  cpl_msg_warning(func, "Do you really have a detector %d pixels long?",
11010  xsize);
11011  }
11012 
11013  ysize = cpl_table_get_nrow(idscoeff);
11014  map = cpl_image_new(xsize, ysize, CPL_TYPE_FLOAT);
11015  mdata = cpl_image_get_data(map);
11016 
11017  order = 0;
11018  while (order < 6 && cpl_table_has_column(idscoeff, clab[order]))
11019  ++order;
11020  --order;
11021 
11022  for (i = 0; i < ysize; i++, mdata += xsize) {
11023 
11024  missing = 0;
11025  ids = cpl_polynomial_new(1);
11026  for (k = 0; k <= order; k++) {
11027  c = cpl_table_get_double(idscoeff, clab[k], i, &missing);
11028  if (missing) {
11029  cpl_polynomial_delete(ids);
11030  break;
11031  }
11032  cpl_polynomial_set_coeff(ids, &k, c);
11033  }
11034  if (missing)
11035  continue;
11036 
11037  for (j = 0; j < xsize; j++) {
11038  lambda = mos_eval_dds(ids, blue, red, reference, j);
11039 
11040  if (lambda >= blue && lambda <= red) {
11041  mdata[j] = lambda;
11042  }
11043  }
11044 
11045  cpl_polynomial_delete(ids);
11046  }
11047 
11048  return map;
11049 
11050 }
11051 
11052 
11087 cpl_image *mos_map_wavelengths(cpl_image *spatial, cpl_image *calibration,
11088  cpl_table *slits, cpl_table *polytraces,
11089  double reference, double blue, double red,
11090  double dispersion)
11091 {
11092  const char *func = "mos_map_wavelengths";
11093 
11094  const char *clab[6] = {"c0", "c1", "c2", "c3", "c4", "c5"};
11095  /* Max order is 5 */
11096  cpl_polynomial *polytop;
11097  cpl_polynomial *polybot;
11098  cpl_image *remapped;
11099  float *data;
11100  float *wdata;
11101  float *sdata;
11102  float *xdata;
11103  double vtop, vbot, value;
11104  double top, bot;
11105  double coeff;
11106  double ytop, ybot;
11107  double ypos;
11108  double fvalue;
11109  int ivalue;
11110  int yint, ysize, yprev;
11111  int nslits;
11112  int npseudo;
11113  int *slit_id;
11114  int *position;
11115  int *length;
11116  int nx, ny;
11117  int pixel_above, pixel_below, refpixel, start_pixel, end_pixel;
11118  int missing_top, missing_bot;
11119  int null;
11120  int order;
11121  int i, j;
11122  cpl_size k;
11123 
11124 
11125  if (spatial == NULL || calibration == NULL ||
11126  slits == NULL || polytraces == NULL) {
11127  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
11128  return NULL;
11129  }
11130 
11131  if (dispersion <= 0.0) {
11132  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
11133  return NULL;
11134  }
11135 
11136  if (red - blue < dispersion) {
11137  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
11138  return NULL;
11139  }
11140 
11141  nx = cpl_image_get_size_x(spatial);
11142  ny = cpl_image_get_size_y(spatial);
11143  ysize = cpl_image_get_size_y(calibration);
11144  remapped = cpl_image_new(nx, ny, CPL_TYPE_FLOAT);
11145  data = cpl_image_get_data(remapped);
11146  sdata = cpl_image_get_data(spatial);
11147  wdata = cpl_image_get_data(calibration);
11148 
11149  nslits = cpl_table_get_nrow(slits);
11150  slit_id = cpl_table_get_data_int(slits, "slit_id");
11151  order = cpl_table_get_ncol(polytraces) - 2;
11152  position = cpl_table_get_data_int(slits, "position");
11153  length = cpl_table_get_data_int(slits, "length");
11154 
11155  /*
11156  * The spatial resampling is performed for a certain number of
11157  * pixels above and below the position of the reference wavelength:
11158  */
11159 
11160  pixel_above = STRETCH_FACTOR * (red - reference) / dispersion;
11161  pixel_below = STRETCH_FACTOR * (reference - blue) / dispersion;
11162 
11163  for (i = 0; i < nslits; i++) {
11164 
11165  if (length[i] == 0)
11166  continue;
11167 
11168  /*
11169  * Note that the x coordinate of the reference pixels on the CCD
11170  * is taken arbitrarily at the top end of each slit. This wouldn't
11171  * be entirely correct in case of curved slits, or in presence of
11172  * heavy distortions: in such cases the spatial resampling is
11173  * really performed across a wide range of wavelengths. But
11174  * the lag between top and bottom spectral curvature models
11175  * would introduce even in such cases negligible effects on
11176  * the spectral spatial resampling.
11177  */
11178 
11179  refpixel = cpl_table_get_double(slits, "xtop", i, NULL);
11180 
11181  start_pixel = refpixel - pixel_below;
11182  if (start_pixel < 0)
11183  start_pixel = 0;
11184 
11185  end_pixel = refpixel + pixel_above;
11186  if (end_pixel > nx)
11187  end_pixel = nx;
11188 
11189  /*
11190  * Recover from the table of spectral curvature coefficients
11191  * the curvature polynomials.
11192  */
11193 
11194  missing_top = 0;
11195  polytop = cpl_polynomial_new(1);
11196  for (k = 0; k <= order; k++) {
11197  coeff = cpl_table_get_double(polytraces, clab[k], 2*i, &null);
11198  if (null) {
11199  cpl_polynomial_delete(polytop);
11200  missing_top = 1;
11201  break;
11202  }
11203  cpl_polynomial_set_coeff(polytop, &k, coeff);
11204  }
11205 
11206  missing_bot = 0;
11207  polybot = cpl_polynomial_new(1);
11208  for (k = 0; k <= order; k++) {
11209  coeff = cpl_table_get_double(polytraces, clab[k], 2*i+1, &null);
11210  if (null) {
11211  cpl_polynomial_delete(polybot);
11212  missing_bot = 1;
11213  break;
11214  }
11215  cpl_polynomial_set_coeff(polybot, &k, coeff);
11216  }
11217 
11218  if (missing_top && missing_bot) {
11219  cpl_msg_debug(func, "Slit %d was not traced: no extraction!",
11220  slit_id[i]);
11221  continue;
11222  }
11223 
11224  /*
11225  * In case just one of the two edges was not traced, the other
11226  * edge curvature model is duplicated and shifted to the other
11227  * end of the slit: better than nothing!
11228  */
11229 
11230  if (missing_top) {
11231  cpl_msg_debug(func, "Upper edge of slit %d was not traced: "
11232  "the spectral curvature of the lower edge "
11233  "is used instead.", slit_id[i]);
11234  polytop = cpl_polynomial_duplicate(polybot);
11235  ytop = cpl_table_get_double(slits, "ytop", i, NULL);
11236  ybot = cpl_table_get_double(slits, "ybottom", i, NULL);
11237  k = 0;
11238  coeff = cpl_polynomial_get_coeff(polybot, &k);
11239  coeff += ytop - ybot;
11240  cpl_polynomial_set_coeff(polytop, &k, coeff);
11241  }
11242 
11243  if (missing_bot) {
11244  cpl_msg_debug(func, "Lower edge of slit %d was not traced: "
11245  "the spectral curvature of the upper edge "
11246  "is used instead.", slit_id[i]);
11247  polybot = cpl_polynomial_duplicate(polytop);
11248  ytop = cpl_table_get_double(slits, "ytop", i, NULL);
11249  ybot = cpl_table_get_double(slits, "ybottom", i, NULL);
11250  k = 0;
11251  coeff = cpl_polynomial_get_coeff(polytop, &k);
11252  coeff -= ytop - ybot;
11253  cpl_polynomial_set_coeff(polybot, &k, coeff);
11254  }
11255 
11256  /*
11257  * Point to current slit on wavelength calibration image.
11258  * Note that the npseudo value related to this slit is equal
11259  * to the number of spatial pseudo-pixels decreased by 1
11260  * (compare with function mos_spatial_calibration()).
11261  */
11262 
11263  xdata = wdata + nx*position[i];
11264  npseudo = length[i] - 1;
11265 
11266  /*
11267  * Write interpolated wavelengths to CCD image
11268  */
11269 
11270  for (j = start_pixel; j < end_pixel; j++) {
11271  top = cpl_polynomial_eval_1d(polytop, j, NULL);
11272  bot = cpl_polynomial_eval_1d(polybot, j, NULL);
11273  for (k = 0; k <= npseudo; k++) {
11274  ypos = top - k*(top-bot)/npseudo;
11275  yint = ypos;
11276 
11277  /*
11278  * The line:
11279  * value = sdata[j + nx*yint];
11280  * should be equivalent to:
11281  * value = npseudo*(top-yint)/(top-bot);
11282  */
11283 
11284  if (yint < 0 || yint >= ny-1) {
11285  yprev = yint;
11286  continue;
11287  }
11288 
11289  value = sdata[j + nx*yint]; /* Spatial coordinate on CCD */
11290  ivalue = value; /* Nearest spatial pixels: */
11291  fvalue = value - ivalue; /* ivalue and ivalue+1 */
11292  if (ivalue < npseudo && ivalue >= 0) {
11293  vtop = xdata[j + nx*(npseudo-ivalue)];
11294  vbot = xdata[j + nx*(npseudo-ivalue-1)];
11295  if (vtop < 1.0) { /* Impossible wavelength */
11296  if (vbot < 1.0) {
11297  value = 0.0;
11298  }
11299  else {
11300  value = vbot;
11301  }
11302  }
11303  else if (vbot < 1.0) {
11304  if (k)
11305  value = vtop;
11306  else
11307  value = 0.0;
11308  }
11309  else if (fabs(vbot-vtop) > 10*dispersion) {
11310  value = 0.0;
11311  }
11312  else {
11313  value = vtop*(1-fvalue) + vbot*fvalue;
11314  }
11315  data[j + nx*yint] = value;
11316 
11317  if (k) {
11318 
11319  /*
11320  * This is added to recover lost pixels on
11321  * the CCD image (pixels are lost because
11322  * the CCD pixels are less than npseudo+1).
11323  */
11324 
11325  if (yprev - yint > 1) {
11326  value = sdata[j + nx*(yint+1)];
11327  ivalue = value;
11328  fvalue = value - ivalue;
11329  if (ivalue < npseudo && ivalue >= 0) {
11330  vtop = xdata[j + nx*(npseudo-ivalue)];
11331  vbot = xdata[j + nx*(npseudo-ivalue-1)];
11332  if (vtop < 1.0) {
11333  if (vbot < 1.0) {
11334  value = data[j + nx*(yint+1)];
11335  }
11336  else {
11337  value = vbot;
11338  }
11339  }
11340  else if (vbot < 1.0) {
11341  value = vtop;
11342  }
11343  else if (fabs(vbot-vtop) > 2*dispersion) {
11344  value = vtop;
11345  }
11346  else {
11347  value = vtop*(1-fvalue) + vbot*fvalue;
11348  }
11349  data[j + nx*(yint+1)] = value;
11350  }
11351  }
11352  }
11353  }
11354  yprev = yint;
11355  }
11356  }
11357  cpl_polynomial_delete(polytop);
11358  cpl_polynomial_delete(polybot);
11359  }
11360 
11361  return remapped;
11362 }
11363 
11437 cpl_image *mos_map_spectrum(cpl_image *spectra, cpl_image *wavecalib,
11438  cpl_image *spatial, cpl_table *slits,
11439  cpl_table *polytraces, double reference,
11440  double blue, double red, double dispersion,
11441  int flux)
11442 {
11443  const char *func = "mos_map_spectrum";
11444 
11445  const char *clab[6] = {"c0", "c1", "c2", "c3", "c4", "c5"};
11446  /* Max order is 5 */
11447  cpl_polynomial *polytop;
11448  cpl_polynomial *polybot;
11449  cpl_image *remapped;
11450  cpl_image **exslit;
11451  float *data;
11452  float *wdata;
11453  float *sdata;
11454  float *xdata;
11455  double lambda00, lambda01, lambda10, lambda11, lambda;
11456  double space00, space01, space10, space11, space;
11457  double value00, value01, value10, value11, value0, value1, value;
11458  double dL, dS;
11459  double top, bot;
11460  double coeff;
11461  double ytop, ybot;
11462  double xfrac, yfrac;
11463  int yint, ysize;
11464  int itop, ibot;
11465  int shift;
11466  int L, S;
11467  int nslits;
11468  int npseudo;
11469  int *slit_id;
11470  int *position;
11471  int *length;
11472  int nx, ny;
11473  int x, y;
11474  int nlambda;
11475  int pixel_above, pixel_below, refpixel, start_pixel, end_pixel;
11476  int missing_top, missing_bot;
11477  int null;
11478  int order;
11479  int i;
11480  cpl_size k;
11481 
11482 
11483  flux += flux;
11484 
11485  if (spectra == NULL || spatial == NULL || wavecalib == NULL ||
11486  slits == NULL || polytraces == NULL) {
11487  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
11488  return NULL;
11489  }
11490 
11491  if (dispersion <= 0.0) {
11492  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
11493  return NULL;
11494  }
11495 
11496  if (red - blue < dispersion) {
11497  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
11498  return NULL;
11499  }
11500 
11501  nx = cpl_image_get_size_x(spectra);
11502  ny = cpl_image_get_size_y(spectra);
11503 
11504  if (nx != cpl_image_get_size_x(spatial) ||
11505  ny != cpl_image_get_size_y(spatial) ||
11506  nx != cpl_image_get_size_x(wavecalib) ||
11507  ny != cpl_image_get_size_y(wavecalib)) {
11508  cpl_error_set(func, CPL_ERROR_INCOMPATIBLE_INPUT);
11509  return NULL;
11510  }
11511 
11512  nlambda = STRETCH_FACTOR * (red - blue) / dispersion;
11513  pixel_above = STRETCH_FACTOR * (red - reference) / dispersion;
11514  pixel_below = STRETCH_FACTOR * (reference - blue) / dispersion;
11515 
11516  data = cpl_image_get_data(spectra);
11517  sdata = cpl_image_get_data(spatial);
11518  wdata = cpl_image_get_data(wavecalib);
11519 
11520  nslits = cpl_table_get_nrow(slits);
11521  slit_id = cpl_table_get_data_int(slits, "slit_id");
11522  order = cpl_table_get_ncol(polytraces) - 2;
11523  position = cpl_table_get_data_int(slits, "position");
11524  length = cpl_table_get_data_int(slits, "length");
11525 
11526  exslit = cpl_calloc(nslits, sizeof(cpl_image *));
11527 
11528  for (i = 0; i < nslits; i++) {
11529 
11530  if (length == 0)
11531  continue;
11532 
11533  /*
11534  * Note that the x coordinate of the reference pixels on the CCD
11535  * is taken arbitrarily at the top end of each slit. This wouldn't
11536  * be entirely correct in case of curved slits, or in presence of
11537  * heavy distortions: in such cases the spatial resampling is
11538  * really performed across a wide range of wavelengths. But
11539  * the lag between top and bottom spectral curvature models
11540  * would introduce even in such cases negligible effects on
11541  * the spectral spatial resampling.
11542  */
11543 
11544  refpixel = cpl_table_get_double(slits, "xtop", i, NULL);
11545 
11546  start_pixel = refpixel - pixel_below;
11547  if (start_pixel < 1)
11548  start_pixel = 1;
11549 
11550  end_pixel = refpixel + pixel_above;
11551  if (end_pixel > nx)
11552  end_pixel = nx;
11553 
11554  /*
11555  * Recover from the table of spectral curvature coefficients
11556  * the curvature polynomials.
11557  */
11558 
11559  missing_top = 0;
11560  polytop = cpl_polynomial_new(1);
11561  for (k = 0; k <= order; k++) {
11562  coeff = cpl_table_get_double(polytraces, clab[k], 2*i, &null);
11563  if (null) {
11564  cpl_polynomial_delete(polytop);
11565  missing_top = 1;
11566  break;
11567  }
11568  cpl_polynomial_set_coeff(polytop, &k, coeff);
11569  }
11570 
11571  missing_bot = 0;
11572  polybot = cpl_polynomial_new(1);
11573  for (k = 0; k <= order; k++) {
11574  coeff = cpl_table_get_double(polytraces, clab[k], 2*i+1, &null);
11575  if (null) {
11576  cpl_polynomial_delete(polybot);
11577  missing_bot = 1;
11578  break;
11579  }
11580  cpl_polynomial_set_coeff(polybot, &k, coeff);
11581  }
11582 
11583  if (missing_top && missing_bot) {
11584  cpl_msg_debug(func, "Slit %d was not traced: no extraction!",
11585  slit_id[i]);
11586  continue;
11587  }
11588 
11589  /*
11590  * In case just one of the two edges was not traced, the other
11591  * edge curvature model is duplicated and shifted to the other
11592  * end of the slit: better than nothing!
11593  */
11594 
11595  if (missing_top) {
11596  cpl_msg_debug(func, "Upper edge of slit %d was not traced: "
11597  "the spectral curvature of the lower edge "
11598  "is used instead.", slit_id[i]);
11599  polytop = cpl_polynomial_duplicate(polybot);
11600  ytop = cpl_table_get_double(slits, "ytop", i, NULL);
11601  ybot = cpl_table_get_double(slits, "ybottom", i, NULL);
11602  k = 0;
11603  coeff = cpl_polynomial_get_coeff(polybot, &k);
11604  coeff += ytop - ybot;
11605  cpl_polynomial_set_coeff(polytop, &k, coeff);
11606  }
11607 
11608  if (missing_bot) {
11609  cpl_msg_debug(func, "Lower edge of slit %d was not traced: "
11610  "the spectral curvature of the upper edge "
11611  "is used instead.", slit_id[i]);
11612  polybot = cpl_polynomial_duplicate(polytop);
11613  ytop = cpl_table_get_double(slits, "ytop", i, NULL);
11614  ybot = cpl_table_get_double(slits, "ybottom", i, NULL);
11615  k = 0;
11616  coeff = cpl_polynomial_get_coeff(polytop, &k);
11617  coeff -= ytop - ybot;
11618  cpl_polynomial_set_coeff(polybot, &k, coeff);
11619  }
11620 
11621  /*
11622  * Allocate image for current extracted slit
11623  */
11624 
11625  top = cpl_polynomial_eval_1d(polytop, refpixel, NULL);
11626  bot = cpl_polynomial_eval_1d(polybot, refpixel, NULL);
11627  npseudo = ceil(top-bot) + 1;
11628 
11629  if (npseudo < 1) {
11630  cpl_polynomial_delete(polytop);
11631  cpl_polynomial_delete(polybot);
11632  cpl_msg_debug(func, "Slit %d was badly traced: no extraction!",
11633  slit_id[i]);
11634  continue;
11635  }
11636 
11637  exslit[i] = cpl_image_new(nlambda, npseudo+1, CPL_TYPE_FLOAT);
11638  xdata = cpl_image_get_data(exslit[i]);
11639 
11640  /*
11641  * Write interpolated spectral values to remapped slit spectrum.
11642  */
11643 
11644  for (x = start_pixel; x < end_pixel; x++) {
11645  top = cpl_polynomial_eval_1d(polytop, x, NULL);
11646  bot = cpl_polynomial_eval_1d(polybot, x, NULL);
11647  itop = top + 1;
11648  ibot = bot;
11649  if (itop < 0)
11650  itop = 0;
11651  if (itop > ny - 1)
11652  itop = ny - 1;
11653  if (ibot < 0)
11654  ibot = 0;
11655  if (ibot > ny - 1)
11656  ibot = ny - 1;
11657  for (y = ibot; y < itop; y++) {
11658  lambda11 = wdata[x + y*nx];
11659  if (lambda11 < 1.0) /* Impossible wavelength */
11660  continue;
11661  space11 = sdata[x + y*nx];
11662  if (space11 < 0.0) /* Impossible spatial coordinate */
11663  continue;
11664  lambda01 = wdata[x - 1 + y*nx];
11665  if (lambda01 < 1.0) /* Impossible wavelength */
11666  continue;
11667  space01 = sdata[x - 1 + y*nx];
11668  if (space01 < 0.0) /* Impossible spatial coordinate */
11669  continue;
11670 
11671  shift = 0;
11672 
11673 /****+
11674  if (wdata[x + (y+1)*nx] > 1.0) {
11675  if (wdata[x + (y+1)*nx] - lambda11 > 0) {
11676  shift = -1;
11677  while (wdata[x + shift + (y+1)*nx] - lambda11 > 0)
11678  shift--;
11679  if (lambda11 - wdata[x + shift + (y+1)*nx] >
11680  wdata[x + shift + 1 + (y+1)*nx] - lambda11) {
11681  shift++;
11682  }
11683  }
11684  else {
11685  shift = 1;
11686  while (wdata[x + shift + (y+1)*nx] - lambda11 < 0)
11687  shift++;
11688  if (wdata[x + shift + (y+1)*nx] - lambda11 >
11689  lambda11 - wdata[x + shift + 1 + (y+1)*nx]) {
11690  shift--;
11691  }
11692  }
11693  }
11694 ****/
11695 
11696 /****
11697 printf("y = %d, shift = %d\n", y, shift);
11698 ****/
11699 
11700  lambda10 = wdata[x + shift + (y+1)*nx];
11701  if (lambda10 < 1.0) /* Impossible wavelength */
11702  continue;
11703  space10 = sdata[x + shift + (y+1)*nx];
11704  if (space10 < 0.0) /* Impossible spatial coordinate */
11705  continue;
11706  lambda00 = wdata[x - 1 + shift + (y+1)*nx];
11707  if (lambda00 < 1.0) /* Impossible wavelength */
11708  continue;
11709  space00 = sdata[x - 1 + shift + (y+1)*nx];
11710  if (space00 < 0.0) /* Impossible spatial coordinate */
11711  continue;
11712 
11713  /*
11714  * Find the variation in lambda and space in this
11715  * position for each CCD pixel (both quantities are
11716  * expected to be positive).
11717  */
11718 
11719  dL = lambda11 - lambda01;
11720  dS = space11 - space10;
11721 
11722  /*
11723  * Find the position (L,S) of the output pixel
11724  * (by integer truncation).
11725  */
11726 
11727  L = (lambda11 - blue)/dispersion + 0.5;
11728  S = space11 + 0.5; /* Counted from top! */
11729 
11730  if (L < 0 || L >= nlambda)
11731  continue;
11732  if (S < 0 || S > npseudo)
11733  continue;
11734 
11735  /*
11736  * Find the coordinate of pixel (L,S)
11737  */
11738 
11739  lambda = blue + L*dispersion;
11740  space = S;
11741 
11742  /*
11743  * Find the interpolation point on the CCD: it is
11744  * defined as the (positive) distance from current
11745  * CCD pixel (x,y) of the interpolation point (x',y'),
11746  * measured in CCD pixels. The interpolation point
11747  * is located between the four CCD pixels selected
11748  * above.
11749  */
11750 
11751  xfrac = (lambda11-lambda)/dL;
11752  yfrac = (space11-space)/dS;
11753 
11754 /*
11755 if (xfrac < 0.0 || xfrac > 1.0 || yfrac < 0.0 || yfrac > 1.0)
11756 printf("xyfrac = %f, %f\n", xfrac, yfrac);
11757 */
11758 
11759  /*
11760  * Get the four values to interpolate
11761  */
11762 
11763  value11 = data[x + y*nx];
11764  value01 = data[x - 1 + y*nx];
11765  value10 = data[x + shift + (y+1)*nx];
11766  value00 = data[x + shift - 1 + (y+1)*nx];
11767 
11768  /*
11769  * Interpolation
11770  */
11771 
11772  value1 = (1-xfrac)*value11 + xfrac*value01;
11773  value0 = (1-xfrac)*value10 + xfrac*value00;
11774  value = (1-yfrac)*value1 + yfrac*value0;
11775 
11776  /*
11777  * Write this value to the appropriate (L,S) coordinate
11778  * on output slit
11779  */
11780 
11781  xdata[L + nlambda*(npseudo-S)] = value;
11782 
11783  }
11784  }
11785  cpl_polynomial_delete(polytop);
11786  cpl_polynomial_delete(polybot);
11787  }
11788 
11789  /*
11790  * Now all the slits images are copied to a single image
11791  */
11792 
11793  ysize = 0;
11794  for (i = 0; i < nslits; i++)
11795  if (exslit[i])
11796  ysize += cpl_image_get_size_y(exslit[i]);
11797 
11798  remapped = cpl_image_new(nlambda, ysize, CPL_TYPE_FLOAT);
11799 
11800  yint = -1;
11801  for (i = 0; i < nslits; i++) {
11802  if (exslit[i]) {
11803  yint += cpl_image_get_size_y(exslit[i]);
11804  cpl_image_copy(remapped, exslit[i], 1, ysize - yint);
11805  cpl_image_delete(exslit[i]);
11806  cpl_table_set_int(slits, "position", i, ysize - yint - 1);
11807  }
11808  }
11809 
11810  cpl_free(exslit);
11811 
11812  return remapped;
11813 
11814 }
11815 
11816 
11849 cpl_table *mos_sky_map_super(cpl_image *spectra, cpl_image *wavemap,
11850  double dispersion, double factor, int minpoints,
11851  cpl_image *skymap)
11852 {
11853  const char *func = "mos_sky_map_super";
11854 
11855  cpl_vector **vector;
11856  cpl_vector **wvector;
11857  double firstLambda, lastLambda;
11858  double lambda, lambda1, lambda2;
11859  double value, value1, value2;
11860  double frac;
11861  float min, max;
11862  int *count;
11863  int nbin, bin;
11864  int nx, ny, npix;
11865  int first_valid, valid_bins;
11866  int i, j;
11867 
11868  cpl_table *sky;
11869  double *sky_spectrum;
11870  double *sky_wave;
11871  float *data;
11872  float *sdata;
11873  float *kdata;
11874 
11875 
11876  if (spectra == NULL || wavemap == NULL || skymap == NULL) {
11877  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
11878  return NULL;
11879  }
11880 
11881  if (dispersion <= 0.0) {
11882  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
11883  cpl_msg_error(func, "Negative dispersion: %s", cpl_error_get_message());
11884  return NULL;
11885  }
11886 
11887  nx = cpl_image_get_size_x(spectra);
11888  ny = cpl_image_get_size_y(spectra);
11889  npix = nx * ny;
11890 
11891  if (nx != cpl_image_get_size_x(wavemap) ||
11892  ny != cpl_image_get_size_y(wavemap) ||
11893  nx != cpl_image_get_size_x(skymap) ||
11894  ny != cpl_image_get_size_y(skymap)) {
11895  cpl_error_set(func, CPL_ERROR_INCOMPATIBLE_INPUT);
11896  cpl_msg_error(func, "Image sizes: %s", cpl_error_get_message());
11897  return NULL;
11898  }
11899 
11900  if (factor < 1.0) {
11901  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
11902  cpl_msg_error(func, "Undersampling (%f): %s", factor,
11903  cpl_error_get_message());
11904  return NULL;
11905  }
11906 
11907  if (minpoints < 0) {
11908  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
11909  cpl_msg_error(func, "Negative threshold: %s", cpl_error_get_message());
11910  return NULL;
11911  }
11912 
11913  dispersion /= factor;
11914 
11915 
11916  /*
11917  * Find bluest and reddest wavelengths in the whole image
11918  */
11919 
11920  data = cpl_image_get_data(wavemap);
11921 
11922  for (i = 0; i < npix; i++) {
11923  if (data[i] > 1.0) {
11924  min = max = data[i];
11925  j = i+1;
11926  break;
11927  }
11928  }
11929 
11930  for (i = j; i < npix; i++) {
11931  if (data[i] < 1.0) /* Impossible wavelength */
11932  continue;
11933  if (min > data[i])
11934  min = data[i];
11935  if (max < data[i])
11936  max = data[i];
11937  }
11938 
11939  firstLambda = min;
11940  lastLambda = max;
11941 
11942 
11943  /*
11944  * Determine length of median spectrum
11945  */
11946 
11947  nbin = (lastLambda - firstLambda) / dispersion;
11948 
11949  /*
11950  * Count how many values will be found for each spectral bin.
11951  * The ith bin has a wavelength range from firstLambda + i*dispersion
11952  * (inclusive) to firstLambda + (i+1)*dispersion (exclusive), and
11953  * it is assigned to its central wavelength.
11954  */
11955 
11956  count = cpl_calloc(nbin, sizeof(int));
11957 
11958  data = cpl_image_get_data(wavemap);
11959 
11960  for (i = 0; i < npix; i++) {
11961  if (data[i] < 1.0)
11962  continue;
11963  bin = (data[i] - firstLambda) / dispersion;
11964  if (bin < nbin) /* Safer */
11965  count[bin]++;
11966  }
11967 
11968  valid_bins = 0;
11969  for (i = 0; i < nbin; i++)
11970  if (count[i] >= minpoints)
11971  valid_bins++;
11972 
11973  if (valid_bins < nbin/3) {
11974  cpl_msg_warning(func, "Cannot determine a good global sky "
11975  "spectrum from input data");
11976  return NULL;
11977  }
11978 
11979 
11980  /*
11981  * Allocate an array of vectors with the appropriate size, to
11982  * contain a list of all the spectral pixels values. At the same
11983  * time, reset the array of counters (because we will have to
11984  * count again...).
11985  */
11986 
11987  vector = cpl_calloc(nbin, sizeof(cpl_vector *));
11988  wvector = cpl_calloc(nbin, sizeof(cpl_vector *));
11989  for (i = 0; i < nbin; i++) {
11990  if (count[i] >= minpoints) {
11991  vector[i] = cpl_vector_new(count[i]);
11992  wvector[i] = cpl_vector_new(count[i]);
11993  }
11994  count[i] = 0;
11995  }
11996 
11997 
11998  /*
11999  * Read the wavemap and the spectral images, and add the data values
12000  * to the appropriate wavelength bins
12001  */
12002 
12003  data = cpl_image_get_data(wavemap);
12004  sdata = cpl_image_get_data(spectra);
12005 
12006  for (i = 0; i < npix; i++) {
12007  if (data[i] < 1.0)
12008  continue;
12009  bin = (data[i] - firstLambda) / dispersion;
12010  if (bin < nbin) { /* Safer */
12011  if (vector[bin]) {
12012  cpl_vector_set(vector[bin], count[bin], sdata[i]);
12013  cpl_vector_set(wvector[bin], count[bin], data[i]);
12014  }
12015  count[bin]++;
12016  }
12017  }
12018 
12019 
12020  /*
12021  * Compute the median flux for each wavelength bin, and destroy
12022  * at the same time the used vectors
12023  */
12024 
12025  sky_spectrum = cpl_calloc(nbin, sizeof(double));
12026  sky_wave = cpl_calloc(nbin, sizeof(double));
12027  for (i = 0; i < nbin; i++) {
12028  if (vector[i]) {
12029  sky_spectrum[i] = cpl_vector_get_median_const(vector[i]);
12030  sky_wave[i] = cpl_vector_get_median_const(wvector[i]);
12031  cpl_vector_delete(vector[i]);
12032  cpl_vector_delete(wvector[i]);
12033  }
12034  }
12035 
12036  cpl_free(vector);
12037  cpl_free(wvector);
12038 
12039 
12040  /*
12041  * Here possible gaps in the final spectrum are filled by interpolation
12042  */
12043 
12044  for (i = 0; i < nbin; i++) {
12045  if (count[i] >= minpoints) {
12046  first_valid = i;
12047  break;
12048  }
12049  }
12050 
12051  for (i = first_valid; i < nbin; i++) {
12052  if (count[i] < minpoints) {
12053  sky_wave[i] = firstLambda + (i+0.5)*dispersion;
12054  for (j = i+1; j < nbin; j++) {
12055  if (count[j] >= minpoints) {
12056  if (sky_wave[j] - sky_wave[i-1] < 0.1) {
12057  sky_spectrum[i] = (sky_spectrum[j] + sky_spectrum[i-1])
12058  / 2;
12059  }
12060  else {
12061  frac = (sky_wave[i] - sky_wave[i-1])
12062  / (sky_wave[j] - sky_wave[i-1]);
12063  sky_spectrum[i] = frac * sky_spectrum[j]
12064  + (1 - frac) * sky_spectrum[i-1];
12065  }
12066  }
12067  }
12068  }
12069  }
12070 
12071 
12072  /*
12073  * Create the output table
12074  */
12075 
12076  sky = cpl_table_new(nbin);
12077  cpl_table_wrap_double(sky, sky_wave, "wavelength");
12078  cpl_table_wrap_double(sky, sky_spectrum, "sky");
12079  cpl_table_wrap_int(sky, count, "npoints");
12080 
12081 
12082  /*
12083  * Fill the sky map
12084  */
12085 
12086  data = cpl_image_get_data(wavemap);
12087  sdata = cpl_image_get_data(spectra);
12088  kdata = cpl_image_get_data(skymap);
12089 
12090  for (i = 0; i < npix; i++) {
12091 
12092  /*
12093  * Currently based on linear interpolation
12094  */
12095 
12096  lambda = data[i];
12097  if (lambda < 1.0)
12098  continue;
12099  bin = (lambda - firstLambda) / dispersion;
12100  lambda1 = sky_wave[bin];
12101  value1 = sky_spectrum[bin];
12102  if (lambda1 < lambda) {
12103  bin++;
12104  if (bin < nbin) {
12105  lambda2 = sky_wave[bin];
12106  value2 = sky_spectrum[bin];
12107  if (lambda2 - lambda1 < 0.1) {
12108  value = (value1 + value2) / 2;
12109  }
12110  else {
12111  frac = (lambda - lambda1) / (lambda2 - lambda1);
12112  value = frac * value2 + (1 - frac) * value1;
12113  }
12114  }
12115  else {
12116  value = value1;
12117  }
12118  }
12119  else {
12120  if (bin > 0) {
12121  bin--;
12122  lambda2 = lambda1;
12123  value2 = value1;
12124  lambda1 = sky_wave[bin];
12125  value1 = sky_spectrum[bin];
12126  if (lambda2 - lambda1 < 0.1) {
12127  value = (value1 + value2) / 2;
12128  }
12129  else {
12130  frac = (lambda - lambda1) / (lambda2 - lambda1);
12131  value = frac * value2 + (1 - frac) * value1;
12132  }
12133  }
12134  else {
12135  value = value1;
12136  }
12137  }
12138  kdata[i] = value;
12139  }
12140 
12141  if (first_valid)
12142  cpl_table_erase_window(sky, 0, first_valid);
12143 
12144  return sky;
12145 
12146 }
12147 
12148 
12182 cpl_table *mos_sky_map(cpl_image *spectra, cpl_image *wavemap,
12183  double dispersion, cpl_image *skymap)
12184 {
12185  const char *func = "mos_sky_map";
12186 
12187  cpl_vector **vector;
12188  double firstLambda, lastLambda;
12189  double lambda, lambda1, lambda2;
12190  double value, value1, value2;
12191  float min, max;
12192  int *count;
12193  int nbin, bin;
12194  int nx, ny, npix;
12195  int i, j;
12196 
12197  cpl_table *sky;
12198  double *sky_spectrum;
12199  float *data;
12200  float *sdata;
12201  float *kdata;
12202  double *wdata;
12203 
12204 
12205  if (spectra == NULL || wavemap == NULL || skymap == NULL) {
12206  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
12207  return NULL;
12208  }
12209 
12210  if (dispersion <= 0.0) {
12211  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
12212  return NULL;
12213  }
12214 
12215  nx = cpl_image_get_size_x(spectra);
12216  ny = cpl_image_get_size_y(spectra);
12217  npix = nx * ny;
12218 
12219  if (nx != cpl_image_get_size_x(wavemap) ||
12220  ny != cpl_image_get_size_y(wavemap) ||
12221  nx != cpl_image_get_size_x(skymap) ||
12222  ny != cpl_image_get_size_y(skymap)) {
12223  cpl_error_set(func, CPL_ERROR_INCOMPATIBLE_INPUT);
12224  return NULL;
12225  }
12226 
12227 
12228  /*
12229  * Find bluest and reddest wavelengths in the whole image
12230  */
12231 
12232  data = cpl_image_get_data(wavemap);
12233 
12234  for (i = 0; i < npix; i++) {
12235  if (data[i] > 1.0) {
12236  min = max = data[i];
12237  j = i+1;
12238  break;
12239  }
12240  }
12241 
12242  for (i = j; i < npix; i++) {
12243  if (data[i] < 1.0) /* Impossible wavelength */
12244  continue;
12245  if (min > data[i])
12246  min = data[i];
12247  if (max < data[i])
12248  max = data[i];
12249  }
12250 
12251  firstLambda = min;
12252  lastLambda = max;
12253 
12254 
12255  /*
12256  * Determine length of median spectrum
12257  */
12258 
12259  nbin = (lastLambda - firstLambda) / dispersion;
12260 
12261  /*
12262  * Count how many values will be found for each spectral bin.
12263  * The ith bin has a wavelength range from firstLambda + i*dispersion
12264  * (inclusive) to firstLambda + (i+1)*dispersion (exclusive), and
12265  * it is assigned to its central wavelength.
12266  */
12267 
12268  count = cpl_calloc(nbin, sizeof(int));
12269 
12270  data = cpl_image_get_data(wavemap);
12271 
12272  for (i = 0; i < npix; i++) {
12273  if (data[i] < 1.0)
12274  continue;
12275  bin = (data[i] - firstLambda) / dispersion;
12276  if (bin < nbin) /* Safer */
12277  count[bin]++;
12278  }
12279 
12280 
12281  /*
12282  * Allocate an array of vectors with the appropriate size, to
12283  * contain a list of all the spectral pixels values. At the same
12284  * time, reset the array of counters (because we will have to
12285  * count again...).
12286  */
12287 
12288  vector = cpl_calloc(nbin, sizeof(cpl_vector *));
12289  for (i = 0; i < nbin; i++) {
12290  if (count[i])
12291  vector[i] = cpl_vector_new(count[i]);
12292  else
12293  vector[i] = NULL;
12294  count[i] = 0;
12295  }
12296 
12297 
12298  /*
12299  * Read the wavemap and the spectral images, and add the data values
12300  * to the appropriate wavelength bins
12301  */
12302 
12303  data = cpl_image_get_data(wavemap);
12304  sdata = cpl_image_get_data(spectra);
12305 
12306  for (i = 0; i < npix; i++) {
12307  if (data[i] < 1.0)
12308  continue;
12309  bin = (data[i] - firstLambda) / dispersion;
12310  if (bin < nbin) { /* Safer */
12311  cpl_vector_set(vector[bin], count[bin], sdata[i]);
12312  count[bin]++;
12313  }
12314  }
12315 
12316 
12317  /*
12318  * Compute the median flux for each wavelength bin, and destroy
12319  * at the same time the used vectors
12320  */
12321 
12322  sky_spectrum = cpl_calloc(nbin, sizeof(double));
12323  for (i = 0; i < nbin; i++) {
12324  if (vector[i]) {
12325  sky_spectrum[i] = cpl_vector_get_median_const(vector[i]);
12326  cpl_vector_delete(vector[i]);
12327  }
12328  }
12329 
12330  cpl_free(vector);
12331 
12332 
12333  /*
12334  * Here possible gaps in the final spectrum should be filled
12335  * by interpolation
12336  */
12337 
12338  /* ... */
12339 
12340  /*
12341  * Create the output table
12342  */
12343 
12344  sky = cpl_table_new(nbin);
12345  cpl_table_new_column(sky, "wavelength", CPL_TYPE_DOUBLE);
12346  cpl_table_set_column_unit(sky, "wavelength", "pixel");
12347  cpl_table_wrap_double(sky, sky_spectrum, "sky");
12348  cpl_table_wrap_int(sky, count, "npoints");
12349  for (i = 0; i < nbin; i++)
12350  cpl_table_set_double(sky, "wavelength", i,
12351  firstLambda + (i+0.5)*dispersion);
12352 
12353 
12354  /*
12355  * Fill the sky map
12356  */
12357 
12358  data = cpl_image_get_data(wavemap);
12359  sdata = cpl_image_get_data(spectra);
12360  kdata = cpl_image_get_data(skymap);
12361  wdata = cpl_table_get_data_double(sky, "wavelength");
12362 
12363  for (i = 0; i < npix; i++) {
12364 
12365  /*
12366  * Currently based on linear interpolation
12367  */
12368 
12369  lambda = data[i];
12370  if (lambda < 1.0)
12371  continue;
12372  bin = (lambda - firstLambda) / dispersion;
12373  lambda1 = wdata[bin];
12374  value1 = sky_spectrum[bin];
12375  if (lambda1 < lambda) {
12376  bin++;
12377  if (bin < nbin) {
12378  lambda2 = wdata[bin];
12379  value2 = sky_spectrum[bin];
12380  value = ((lambda2 - lambda)*value1
12381  + (lambda - lambda1)*value2) / dispersion;
12382  }
12383  else {
12384  value = value1;
12385  }
12386  }
12387  else {
12388  if (bin > 0) {
12389  bin--;
12390  lambda2 = lambda1;
12391  value2 = value1;
12392  lambda1 = wdata[bin];
12393  value1 = sky_spectrum[bin];
12394  value = ((lambda2 - lambda)*value1
12395  + (lambda - lambda1)*value2)/dispersion;
12396  }
12397  else {
12398  value = value1;
12399  }
12400  }
12401  kdata[i] = value;
12402  }
12403 
12404  return sky;
12405 
12406 }
12407 
12408 
12424 cpl_image *mos_sky_local_old(cpl_image *spectra, cpl_table *slits)
12425 {
12426  const char *func = "mos_sky_local_old";
12427 
12428  cpl_image *exslit;
12429  cpl_image *sky;
12430  cpl_image *skymap;
12431  float *data;
12432  float *sdata;
12433  int nx, ny;
12434  int xlow, ylow, xhig, yhig;
12435  int nslits;
12436  int *slit_id;
12437  int *position;
12438  int *length;
12439  int i, j, k;
12440 
12441 
12442  if (spectra == NULL) {
12443  cpl_msg_error(func,
12444  "A scientific rectified spectral image must be given");
12445  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
12446  return NULL;
12447  }
12448 
12449  if (slits == NULL) {
12450  cpl_msg_error(func, "A slits position table must be given");
12451  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
12452  return NULL;
12453  }
12454 
12455  nslits = cpl_table_get_nrow(slits);
12456  slit_id = cpl_table_get_data_int(slits, "slit_id");
12457  position = cpl_table_get_data_int(slits, "position");
12458  length = cpl_table_get_data_int(slits, "length");
12459 
12460  nx = cpl_image_get_size_x(spectra);
12461  ny = cpl_image_get_size_y(spectra);
12462 
12463  skymap = cpl_image_new(nx, ny, CPL_TYPE_FLOAT);
12464 
12465  xlow = 1;
12466  xhig = nx;
12467  for (i = 0; i < nslits; i++) {
12468 
12469  if (length[i] == 0)
12470  continue;
12471 
12472  /*
12473  * Define the extraction boundaries. We DON'T write:
12474  *
12475  * ylow = position[i];
12476  * yhig = ylow + length[i];
12477  *
12478  * because the cpl_image pixels are counted from 1, and because in
12479  * cpl_image_extract() the coordinates of the last pixel are inclusive.
12480  */
12481 
12482  ylow = position[i] + 1;
12483  yhig = ylow + length[i] - 1;
12484 
12485  exslit = cpl_image_extract(spectra, xlow, ylow, xhig, yhig);
12486  sky = cpl_image_collapse_median_create(exslit, 0, 0, 1);
12487  cpl_image_delete(exslit);
12488 
12489  data = cpl_image_get_data(skymap);
12490  data += nx * position[i];
12491 
12492  for (j = 0; j < length[i]; j++) {
12493  sdata = cpl_image_get_data(sky);
12494  for (k = 0; k < nx; k++) {
12495  *data++ = *sdata++;
12496  }
12497  }
12498 
12499  cpl_image_delete(sky);
12500  }
12501 
12502  return skymap;
12503 
12504 }
12505 
12506 
12526 cpl_image *mos_sky_local(cpl_image *spectra, cpl_table *slits, int order)
12527 {
12528  const char *func = "mos_sky_local";
12529 
12530  char name[MAX_COLNAME];
12531 
12532  cpl_polynomial *fit;
12533  cpl_vector *points;
12534  cpl_vector *values;
12535  cpl_vector *keep_points;
12536  cpl_vector *keep_values;
12537  cpl_image *exslit;
12538  cpl_image *sky;
12539  cpl_image *subtracted;
12540  cpl_image *profile;
12541  cpl_image *skymap;
12542  cpl_table *objects;
12543  float *data;
12544  float *sdata;
12545  float *xdata;
12546  double *vdata;
12547  double *pdata;
12548  double median;
12549  int nx, ny;
12550  int xlow, ylow, xhig, yhig;
12551  int nslits;
12552  int *slit_id;
12553  int *position;
12554  int *length;
12555  int *is_sky;
12556  int nsky, nbad;
12557  int maxobjects;
12558  int margin = 3;
12559  int radius = 6;
12560  int i, j, k;
12561 
12562 
12563  if (spectra == NULL) {
12564  cpl_msg_error(func,
12565  "A scientific rectified spectral image must be given");
12566  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
12567  return NULL;
12568  }
12569 
12570  if (slits == NULL) {
12571  cpl_msg_error(func, "A slits position table must be given");
12572  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
12573  return NULL;
12574  }
12575 
12576  if (order < 0) {
12577  cpl_msg_error(func, "Invalid fit order");
12578  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
12579  return NULL;
12580  }
12581 
12582  nslits = cpl_table_get_nrow(slits);
12583  slit_id = cpl_table_get_data_int(slits, "slit_id");
12584  position = cpl_table_get_data_int(slits, "position");
12585  length = cpl_table_get_data_int(slits, "length");
12586 
12587  nx = cpl_image_get_size_x(spectra);
12588  ny = cpl_image_get_size_y(spectra);
12589 
12590  skymap = cpl_image_new(nx, ny, CPL_TYPE_FLOAT);
12591 
12592  xlow = 1;
12593  xhig = nx;
12594  for (i = 0; i < nslits; i++) {
12595 
12596  if (length[i] == 0)
12597  continue;
12598 
12599  /*
12600  * Define the extraction boundaries. We DON'T write:
12601  *
12602  * ylow = position[i];
12603  * yhig = ylow + length[i];
12604  *
12605  * because the cpl_image pixels are counted from 1, and because in
12606  * cpl_image_extract() the coordinates of the last pixel are inclusive.
12607  */
12608 
12609  ylow = position[i] + 1;
12610  yhig = ylow + length[i] - 1;
12611 
12612  exslit = cpl_image_extract(spectra, xlow, ylow, xhig, yhig);
12613  sky = cpl_image_collapse_median_create(exslit, 0, 0, 1);
12614  cpl_image_delete(exslit);
12615 
12616  data = cpl_image_get_data(skymap);
12617  data += nx * position[i];
12618 
12619  for (j = 0; j < length[i]; j++) {
12620  sdata = cpl_image_get_data(sky);
12621  for (k = 0; k < nx; k++) {
12622  *data++ = *sdata++;
12623  }
12624  }
12625 
12626  cpl_image_delete(sky);
12627  }
12628 
12629 
12630  /*
12631  * Preliminary sky-subtracted image
12632  */
12633 
12634  subtracted = cpl_image_duplicate(spectra);
12635  cpl_image_subtract(subtracted, skymap);
12636  cpl_image_delete(skymap);
12637 
12638 
12639  /*
12640  * Detect objects positions in all slits
12641  */
12642 
12643  objects = cpl_table_duplicate(slits);
12644  profile = mos_detect_objects(subtracted, objects, margin, radius, 0);
12645  cpl_image_delete(profile);
12646  cpl_image_delete(subtracted);
12647 
12648 
12649  /*
12650  * Flag the sky pixels. Note that maxobjects is intentionally
12651  * the max number of objects increased by one.
12652  */
12653 
12654  maxobjects = 1;
12655  snprintf(name, MAX_COLNAME, "object_%d", maxobjects);
12656  while (cpl_table_has_column(objects, name)) {
12657  maxobjects++;
12658  snprintf(name, MAX_COLNAME, "object_%d", maxobjects);
12659  }
12660 
12661  is_sky = cpl_calloc(ny, sizeof(int));
12662 
12663  for (i = 0; i < nslits; i++) {
12664 
12665  if (length[i] == 0)
12666  continue;
12667 
12668  ylow = position[i] + margin;
12669  yhig = position[i] + length[i] - margin;
12670 
12671  for (j = ylow; j < yhig; j++)
12672  is_sky[j] = 1;
12673 
12674  for (j = 1; j < maxobjects; j++) {
12675  snprintf(name, MAX_COLNAME, "object_%d", j);
12676  if (cpl_table_is_valid(objects, name, i)) {
12677  snprintf(name, MAX_COLNAME, "start_%d", j);
12678  ylow = cpl_table_get_int(objects, name, i, NULL);
12679  snprintf(name, MAX_COLNAME, "end_%d", j);
12680  yhig = cpl_table_get_int(objects, name, i, NULL);
12681  for (k = ylow; k <= yhig; k++)
12682  is_sky[k] = 0;
12683  }
12684  }
12685 
12686 
12687  /*
12688  * Eliminate isolated sky points
12689  */
12690 
12691  ylow = position[i] + margin + 1;
12692  yhig = position[i] + length[i] - margin - 1;
12693 
12694  for (j = ylow; j < yhig; j++)
12695  if (is_sky[j])
12696  if (is_sky[j-1] == 0 && is_sky[j+1] == 0)
12697  is_sky[j] = 0;
12698 
12699  }
12700 
12701 
12702  /*
12703  * Determination of the sky map
12704  */
12705 
12706  skymap = cpl_image_new(nx, ny, CPL_TYPE_FLOAT);
12707 
12708  for (i = 0; i < nslits; i++) {
12709 
12710  if (length[i] == 0)
12711  continue;
12712 
12713  ylow = position[i];
12714  yhig = ylow + length[i];
12715 
12716  nsky = 0;
12717  for (j = ylow; j < yhig; j++)
12718  if (is_sky[j])
12719  nsky++;
12720 
12721  if (nsky > order + 1) {
12722  if (order) {
12723  points = cpl_vector_new(nsky);
12724  nsky = 0;
12725  for (j = ylow; j < yhig; j++) {
12726  if (is_sky[j]) {
12727  cpl_vector_set(points, nsky, j);
12728  nsky++;
12729  }
12730  }
12731 
12732  exslit = cpl_image_extract(spectra, 1, ylow+1, nx, yhig);
12733  xdata = cpl_image_get_data(exslit);
12734  values = cpl_vector_new(nsky);
12735 
12736  for (j = 0; j < nx; j++) {
12737  nsky = 0;
12738  for (k = ylow; k < yhig; k++) {
12739  if (is_sky[k]) {
12740  cpl_vector_set(values, nsky, xdata[j+(k-ylow)*nx]);
12741  nsky++;
12742  }
12743  }
12744 
12745  /*
12746  * Eliminate obvious outliers
12747  */
12748 
12749  median = cpl_vector_get_median_const(values);
12750  vdata = cpl_vector_get_data(values);
12751  pdata = cpl_vector_get_data(points);
12752  nbad = 0;
12753  for (k = 0; k < nsky; k++) {
12754  if (fabs(vdata[k] - median) < 100) {
12755  if (nbad) {
12756  vdata[k-nbad] = vdata[k];
12757  pdata[k-nbad] = pdata[k];
12758  }
12759  }
12760  else
12761  nbad++;
12762  }
12763 
12764  if (nsky == nbad)
12765  continue;
12766 
12767  if (nbad && nsky - nbad > order + 1) {
12768  keep_values = values;
12769  keep_points = points;
12770  values = cpl_vector_wrap(nsky-nbad, vdata);
12771  points = cpl_vector_wrap(nsky-nbad, pdata);
12772  }
12773 
12774  if (nsky - nbad > order + 1) {
12775 
12776  fit = cpl_polynomial_fit_1d_create(points, values,
12777  order, NULL);
12778 
12779  if (fit) {
12780  for (k = ylow; k < yhig; k++) {
12781  xdata[j+(k-ylow)*nx] =
12782  cpl_polynomial_eval_1d(fit, k, NULL);
12783  }
12784 
12785  cpl_polynomial_delete(fit);
12786  }
12787  else
12788  cpl_error_reset();
12789  }
12790  else {
12791  for (k = 0; k < nsky; k++) {
12792  xdata[j+k*nx] = median;
12793  }
12794  }
12795 
12796  if (nbad && nsky - nbad > order + 1) {
12797  cpl_vector_unwrap(values);
12798  cpl_vector_unwrap(points);
12799  values = keep_values;
12800  points = keep_points;
12801  }
12802 
12803  if (nbad) {
12804  nsky = 0;
12805  for (k = ylow; k < yhig; k++) {
12806  if (is_sky[k]) {
12807  cpl_vector_set(points, nsky, k);
12808  nsky++;
12809  }
12810  }
12811  }
12812 
12813  }
12814 
12815  cpl_vector_delete(values);
12816  cpl_vector_delete(points);
12817 
12818  cpl_image_copy(skymap, exslit, 1, ylow+1);
12819  cpl_image_delete(exslit);
12820 
12821  }
12822  else {
12823  exslit = cpl_image_extract(spectra, 1, ylow+1, nx, yhig);
12824  xdata = cpl_image_get_data(exslit);
12825  values = cpl_vector_new(nsky);
12826 
12827  for (j = 0; j < nx; j++) {
12828  nsky = 0;
12829  for (k = ylow; k < yhig; k++) {
12830  if (is_sky[k]) {
12831  cpl_vector_set(values, nsky, xdata[j+(k-ylow)*nx]);
12832  nsky++;
12833  }
12834  }
12835 
12836  median = cpl_vector_get_median_const(values);
12837 
12838  for (k = ylow; k < yhig; k++)
12839  xdata[j+(k-ylow)*nx] = median;
12840 
12841  }
12842 
12843  cpl_vector_delete(values);
12844 
12845  cpl_image_copy(skymap, exslit, 1, ylow+1);
12846  cpl_image_delete(exslit);
12847  }
12848  }
12849  else
12850  cpl_msg_warning(func, "Too few sky points in slit %d", i + 1);
12851  }
12852 
12853  cpl_free(is_sky);
12854 
12855  return skymap;
12856 
12857 }
12858 
12859 
12881 cpl_error_code mos_clean_cosmics(cpl_image *image, float gain,
12882  float threshold, float ratio)
12883 {
12884  const char *func = "mos_clean_cosmics";
12885 
12886  cpl_image *smoothImage;
12887  cpl_table *table;
12888  cpl_matrix *kernel;
12889  int *xdata;
12890  int *ydata;
12891  float *idata;
12892  float *sdata;
12893  float sigma, sum, value, smoothValue;
12894  double noise;
12895  int count;
12896  float fMax;
12897  int iMin, iMax, jMin, jMax, iPosMax, jPosMax;
12898  int xLen;
12899  int yLen;
12900  int nPix;
12901  int first = 1; /* position of first cosmic ray candidate
12902  encountered while scanning the image */
12903  int pos, i, j, k, l, ii, jj, iii = 0, jjj = 0;
12904  int numCosmic = 0;
12905  int found, foundContiguousCandidate;
12906  int *cosmic;
12907 
12908 
12909  if (image == NULL)
12910  return cpl_error_set(func, CPL_ERROR_NULL_INPUT);
12911 
12912 
12913  /*
12914  * "cosmic" is a flags holder (initialized to zero):
12915  *
12916  * -1 = candidate for cosmic ray
12917  * 0 = not a cosmic
12918  * 1 = a cosmic ray
12919  * 2 = member of current group of contiguous candidates
12920  * 3 = examined member of current group
12921  */
12922 
12923  xLen = cpl_image_get_size_x(image);
12924  yLen = cpl_image_get_size_y(image);
12925 
12926  if (xLen < 4 || yLen < 4)
12927  return CPL_ERROR_NONE;
12928 
12929  nPix = xLen * yLen;
12930 
12931  /*
12932  * Noise estimation from negative offsets in image. Note that this
12933  * assumes that the background level (skyLevel) has already been
12934  * subtracted from the data. In this way we estimate the noise due
12935  * to detector readout and to the background signal level (before
12936  * it were removed). Theoretically this is given by
12937  *
12938  * noise = sqrt(ron^2 + skyLevel/gain)
12939  *
12940  * where ron is the read-out-noise. To this we will sum the noise
12941  * contribution due to any increase of the signal above the background
12942  * by an amount scienceLevel. Theoretically the total noise is given by
12943  *
12944  * totalNoise = sqrt(ron^2 + (skyLevel+scienceLevel)/gain)
12945  *
12946  * that is
12947  *
12948  * totalNoise = sqrt(noise^2 + scienceLevel/gain)
12949  *
12950  */
12951 
12952  idata = cpl_image_get_data(image);
12953  noise = 0.0;
12954  count = 0;
12955 
12956  for (i = 0; i < nPix; i++) {
12957  if (idata[i] < -0.00001) {
12958  noise -= idata[i];
12959  count++;
12960  }
12961  }
12962 
12963  noise /= count;
12964  noise *= 1.25; /* Factor to convert average deviation to sigma */
12965 
12966  cosmic = cpl_calloc(nPix, sizeof(int));
12967 
12968  if (threshold < 0.)
12969  threshold = 4.0;
12970  if (ratio < 0.)
12971  ratio = 2.0;
12972 
12973  kernel = cpl_matrix_new(3, 3);
12974  cpl_matrix_fill(kernel, 1.0);
12975  cpl_matrix_set(kernel, 1, 1, 0.0);
12976  smoothImage = cpl_image_filter_median(image, kernel);
12977  cpl_matrix_delete(kernel);
12978 
12979  /*
12980  * Loop on images pixels, searching for cosmic rays candidates.
12981  * Border pixels are currently excluded (they cannot contain
12982  * candidates), to avoid that the search for groups of contiguous
12983  * pixels would ever go out of image boundaries. In future we may
12984  * overcome this limit, adding an appropriate check when contiguous
12985  * pixels are searched.
12986  */
12987 
12988  sdata = cpl_image_get_data(smoothImage);
12989 
12990  for (j = 1; j < yLen - 1; j++) {
12991  for (i = 1; i < xLen - 1; i++) {
12992  value = idata[i + j * xLen];
12993  smoothValue = sdata[i + j * xLen];
12994  if (smoothValue < 1.0)
12995  smoothValue = 1.0;
12996  sigma = sqrt(noise * noise + smoothValue / gain);
12997  if (value - smoothValue >= threshold * sigma)
12998  cosmic[i + j * xLen] = -1;
12999  }
13000  }
13001 
13002  cpl_image_delete(smoothImage);
13003 
13004 
13005  /*
13006  * Search for groups of contiguous cosmic rays candidates.
13007  */
13008 
13009  do {
13010  found = 0;
13011  for (pos = first; pos < nPix; pos++) {
13012  if (cosmic[pos] == -1) {
13013  cosmic[pos] = 2; /* Candidate found. */
13014  i = pos % xLen; /* Its coordinates. */
13015  j = pos / xLen;
13016  first = pos;
13017  first++; /* ??? really necessary? */
13018  found = 1;
13019  break;
13020  }
13021  }
13022 
13023  if (found) {
13024 
13025  /*
13026  * Determine new group of contiguous cosmic rays candidates.
13027  * Initialize the working box boundaries, iMin, iMax, jMin, jMax,
13028  * and the value of the max pixel and its position, fMax, iPosMax,
13029  * jPosMax.
13030  */
13031 
13032  iMin = iMax = iPosMax = i;
13033  jMin = jMax = jPosMax = j;
13034  fMax = idata[i + j * xLen];
13035 
13036  do {
13037  foundContiguousCandidate = 0;
13038  for (l = 0; l <= 1; l++) {
13039  for (k = 0; k <= 1; k++) {
13040 
13041  /*
13042  * Looping on 4 pixels to North, East, South and West
13043  */
13044 
13045  ii = i + k - l;
13046  jj = j + k + l - 1;
13047  if (cosmic[ii + jj * xLen] == -1) {
13048  foundContiguousCandidate = 1;
13049  cosmic[ii + jj * xLen] = 2;
13050  /* Candidate belongs to current group */
13051  iii = ii; /* Keep its position */
13052  jjj = jj;
13053 
13054  /*
13055  * Upgrade search box
13056  */
13057 
13058  if (ii < iMin)
13059  iMin = ii;
13060  if (ii > iMax)
13061  iMax = ii;
13062  if (jj < jMin)
13063  jMin = jj;
13064  if (jj > jMax)
13065  jMax = jj;
13066 
13067  if (idata[ii + jj * xLen] > fMax) {
13068  fMax = idata[ii + jj * xLen];
13069  iPosMax = ii;
13070  jPosMax = jj;
13071  }
13072  }
13073  }
13074  }
13075 
13076  /*
13077  * We are done exploring the "cross". Now mark as "examined"
13078  * the current candidate (at the center of the cross):
13079  */
13080 
13081  cosmic[i + j * xLen] = 3; /* It may probably be set to 1 now */
13082 
13083  if (foundContiguousCandidate) {
13084 
13085  /*
13086  * Pass (arbitrarily) the coordinates of the LAST found
13087  * candidate
13088  */
13089 
13090  i = iii;
13091  j = jjj;
13092 
13093  /*
13094  * Skip the rest, continue loop on new candidate
13095  */
13096 
13097  continue;
13098  }
13099 
13100 
13101  /*
13102  * Look for leftovers in the (growing!) search box
13103  */
13104 
13105  for (l = jMin; l <= jMax; l++) {
13106  for (k = iMin; k <= iMax; k++) {
13107  if (cosmic[k + l * xLen] == 2) {
13108  i = k;
13109  j = l;
13110  foundContiguousCandidate = 1;
13111  break;
13112  }
13113  }
13114  if (foundContiguousCandidate)
13115  break;
13116  }
13117  } while (foundContiguousCandidate);
13118 
13119 
13120  /*
13121  * No more contiguous candidates are found. Decide now
13122  * whether the current group is a cosmic ray or not.
13123  */
13124 
13125  sum = 0.; /* Sum of 8 pixels around max position */
13126  for (l = -1; l <= 1; l++) {
13127  for (k = -1; k <= 1; k++) {
13128  if (l != 0 || k != 0) {
13129  sum += idata[iPosMax + k + (jPosMax + l) * xLen];
13130  }
13131  }
13132  }
13133 
13134  sum /= 8.;
13135  if (fMax > ratio * sum) {
13136  for (l = jMin - 1; l <= jMax + 1; l++) {
13137  for (k = iMin - 1; k <= iMax + 1; k++) {
13138  if (cosmic[k + l * xLen] == 3) {
13139  cosmic[k + l * xLen] = 1;
13140  numCosmic++;
13141  }
13142  }
13143  }
13144  }
13145  else {
13146  for (l = jMin - 1; l <= jMax + 1; l++) {
13147  for (k = iMin - 1; k <= iMax + 1; k++) {
13148  if (cosmic[k + l * xLen] != -1) {
13149  if (cosmic[k + l * xLen] == 1)
13150  numCosmic--;
13151  cosmic[k + l * xLen] = 0;
13152  }
13153  }
13154  }
13155  }
13156  }
13157  } while (found);
13158 
13159 
13160  /*
13161  * Prepare table containing cosmic rays coordinates.
13162  */
13163 
13164  table = cpl_table_new(numCosmic);
13165  cpl_table_new_column(table, "x", CPL_TYPE_INT);
13166  cpl_table_new_column(table, "y", CPL_TYPE_INT);
13167  cpl_table_set_column_unit(table, "x", "pixel");
13168  cpl_table_set_column_unit(table, "y", "pixel");
13169  xdata = cpl_table_get_data_int(table, "x");
13170  ydata = cpl_table_get_data_int(table, "y");
13171 
13172  for (pos = 0, i = 0; pos < nPix; pos++) {
13173  if (cosmic[pos] == 1) {
13174  xdata[i] = (pos % xLen);
13175  ydata[i] = (pos / xLen);
13176  i++;
13177  }
13178  }
13179 
13180  mos_clean_bad_pixels(image, table, 1);
13181 
13182  cpl_free(cosmic);
13183  cpl_table_delete(table);
13184 
13185  return CPL_ERROR_NONE;
13186 
13187 }
13188 
13189 
13190 cpl_error_code mos_clean_bad_pixels(cpl_image *image, cpl_table *table,
13191  int spectral)
13192 {
13193  const char *func = "mos_clean_cosmics";
13194 
13195  float *idata;
13196  int *isBadPix;
13197  int i, j, k, d;
13198  int xlen, ylen, totPix;
13199  int nBadPixels = 0;
13200  int sign, foundFirst;
13201  int *xValue = NULL;
13202  int *yValue = NULL;
13203  float save = 0.;
13204  double sumd;
13205  int cx, cy;
13206  int nPairs;
13207  float estimate[4];
13208  int sx[] = {0, 1, 1, 1};
13209  int sy[] = {1,-1, 0, 1};
13210  int searchHorizon = 100;
13211  int percent = 15;
13212 
13213 
13214  if (image == NULL || table == NULL)
13215  return cpl_error_set(func, CPL_ERROR_NULL_INPUT);
13216 
13217  if (1 != cpl_table_has_column(table, "x"))
13218  return cpl_error_set(func, CPL_ERROR_DATA_NOT_FOUND);
13219 
13220  if (1 != cpl_table_has_column(table, "y"))
13221  return cpl_error_set(func, CPL_ERROR_DATA_NOT_FOUND);
13222 
13223  if (CPL_TYPE_INT != cpl_table_get_column_type(table, "x"))
13224  return cpl_error_set(func, CPL_ERROR_INVALID_TYPE);
13225 
13226  if (CPL_TYPE_INT != cpl_table_get_column_type(table, "y"))
13227  return cpl_error_set(func, CPL_ERROR_INVALID_TYPE);
13228 
13229  nBadPixels = cpl_table_get_nrow(table);
13230 
13231  if (nBadPixels) {
13232  xlen = cpl_image_get_size_x(image);
13233  ylen = cpl_image_get_size_y(image);
13234  idata = cpl_image_get_data(image);
13235  totPix = xlen * ylen;
13236  if (((float) nBadPixels) / ((float) totPix) < percent/100.) {
13237  isBadPix = cpl_calloc(totPix, sizeof(int));
13238  }
13239  else {
13240  cpl_msg_warning(func, "Too many bad pixels (> %d%%): "
13241  "skip bad pixel correction", percent);
13242  return cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
13243  }
13244  }
13245  else {
13246  cpl_msg_debug(func, "No pixel values to interpolate");
13247  return CPL_ERROR_NONE;
13248  }
13249 
13250  xValue = cpl_table_get_data_int(table, "x");
13251  yValue = cpl_table_get_data_int(table, "y");
13252 
13253  for (i = 0; i < nBadPixels; i++)
13254  isBadPix[xValue[i] + yValue[i] * xlen] = 1;
13255 
13256  for (i = 0; i < nBadPixels; i++) {
13257 
13258  /*
13259  * Search for the closest good pixel along the 4 fundamental
13260  * directions (in both senses):
13261  * \ | /
13262  * \|/
13263  * --- ---
13264  * /|\
13265  * / | \
13266  *
13267  * Then collect pairs of values to interpolate linearly.
13268  */
13269 
13270  nPairs = 0;
13271  for (j = 0; j < 4; j++) {
13272 
13273  if (spectral) /* Just horizontal interpolation for spectral data */
13274  if (j != 2)
13275  continue;
13276 
13277  estimate[nPairs] = 0.; /* Pairs interpolations are stored here */
13278  sumd = 0.;
13279  foundFirst = 0;
13280  for (k = 0; k < 2; k++) {
13281  sign = 2 * k - 1;
13282  d = 0;
13283  cx = xValue[i];
13284  cy = yValue[i];
13285  do {
13286  cx += sign * sx[j];
13287  cy += sign * sy[j];
13288  if (cx < 0 || cx >= xlen || cy < 0 || cy >= ylen)
13289  break;
13290  d++;
13291  } while (isBadPix[cx + cy * xlen] && d < searchHorizon);
13292 
13293  if (cx >= 0 && cx < xlen &&
13294  cy >= 0 && cy < ylen && d < searchHorizon) {
13295 
13296  /*
13297  * In this block is cripted the linear interpolation...
13298  */
13299 
13300  save = idata[cx + cy * xlen];
13301  estimate[nPairs] += save / d;
13302  sumd += 1. / (double) d;
13303  if (k) {
13304  estimate[nPairs] /= sumd;
13305  nPairs++;
13306  }
13307  else {
13308  foundFirst = 1;
13309  }
13310  }
13311  else {
13312 
13313  /*
13314  * Image borders were crossed, incomplete pair of values
13315  */
13316 
13317  if (k) {
13318  if (foundFirst) {
13319  estimate[nPairs] = save;
13320  nPairs++;
13321  }
13322  }
13323  }
13324  }
13325  }
13326 
13327  /*
13328  * Replace pixel value of the input image, corresponding to
13329  * the current bad pixel, with the median of the estimates
13330  * resulted from the 4 linear interpolations.
13331  */
13332 
13333  if (nPairs > 2) {
13334  idata[xValue[i] + yValue[i] * xlen] =
13335  cpl_tools_get_median_float(estimate, nPairs);
13336  }
13337  else if (nPairs == 2) {
13338  idata[xValue[i] + yValue[i] * xlen] =
13339  (estimate[0] + estimate[1]) / 2.;
13340  }
13341  else if (nPairs == 1) {
13342  idata[xValue[i] + yValue[i] * xlen] = estimate[0];
13343  }
13344  else {
13345  cpl_msg_debug(func, "Cannot correct bad pixel %d,%d\n",
13346  xValue[i], yValue[i]);
13347  }
13348  }
13349 
13350  cpl_free(isBadPix);
13351 
13352  return CPL_ERROR_NONE;
13353 }
13354 
13355 
13385 cpl_image *mos_spatial_map(cpl_image *spectra, cpl_table *slits,
13386  cpl_table *polytraces, double reference,
13387  double blue, double red, double dispersion)
13388 {
13389  const char *func = "mos_spatial_map";
13390 
13391  const char *clab[6] = {"c0", "c1", "c2", "c3", "c4", "c5"};
13392  /* Max order is 5 */
13393  cpl_polynomial *polytop;
13394  cpl_polynomial *polybot;
13395  cpl_image *calibration;
13396  float *data;
13397  double top, bot;
13398  double coeff;
13399  double ytop, ybot;
13400  double ypos, yfra;
13401  double factor;
13402  int yint, yprev;
13403  int nslits;
13404  int npseudo;
13405  int *slit_id;
13406  int *length;
13407  int nx, ny;
13408  int pixel_above, pixel_below, refpixel, start_pixel, end_pixel;
13409  int missing_top, missing_bot;
13410  int null;
13411  int order;
13412  int i, j;
13413  cpl_size k;
13414 
13415 
13416  if (spectra == NULL || slits == NULL || polytraces == NULL) {
13417  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
13418  return NULL;
13419  }
13420 
13421  if (dispersion <= 0.0) {
13422  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
13423  return NULL;
13424  }
13425 
13426  if (red - blue < dispersion) {
13427  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
13428  return NULL;
13429  }
13430 
13431  nx = cpl_image_get_size_x(spectra);
13432  ny = cpl_image_get_size_y(spectra);
13433 
13434  calibration = cpl_image_new(nx, ny, CPL_TYPE_FLOAT);
13435  data = cpl_image_get_data(calibration);
13436 
13437  length = cpl_table_get_data_int(slits, "length");
13438  nslits = cpl_table_get_nrow(slits);
13439  slit_id = cpl_table_get_data_int(slits, "slit_id");
13440  order = cpl_table_get_ncol(polytraces) - 2;
13441 
13442  /*
13443  * The spatial resampling is performed for a certain number of
13444  * pixels above and below the position of the reference wavelength:
13445  */
13446 
13447  pixel_above = STRETCH_FACTOR * (red - reference) / dispersion;
13448  pixel_below = STRETCH_FACTOR * (reference - blue) / dispersion;
13449 
13450  for (i = 0; i < nslits; i++) {
13451 
13452  if (length[i] == 0)
13453  continue;
13454 
13455  /*
13456  * Note that the x coordinate of the reference pixels on the CCD
13457  * is taken arbitrarily at the top end of each slit. This wouldn't
13458  * be entirely correct in case of curved slits, or in presence of
13459  * heavy distortions: in such cases the spatial resampling is
13460  * really performed across a wide range of wavelengths. But
13461  * the lag between top and bottom spectral curvature models
13462  * would introduce even in such cases negligible effects on
13463  * the spectral spatial resampling.
13464  */
13465 
13466  refpixel = cpl_table_get_double(slits, "xtop", i, NULL);
13467 
13468  start_pixel = refpixel - pixel_below;
13469  if (start_pixel < 0)
13470  start_pixel = 0;
13471 
13472  end_pixel = refpixel + pixel_above;
13473  if (end_pixel > nx)
13474  end_pixel = nx;
13475 
13476  /*
13477  * Recover from the table of spectral curvature coefficients
13478  * the curvature polynomials.
13479  */
13480 
13481  missing_top = 0;
13482  polytop = cpl_polynomial_new(1);
13483  for (k = 0; k <= order; k++) {
13484  coeff = cpl_table_get_double(polytraces, clab[k], 2*i, &null);
13485  if (null) {
13486  cpl_polynomial_delete(polytop);
13487  missing_top = 1;
13488  break;
13489  }
13490  cpl_polynomial_set_coeff(polytop, &k, coeff);
13491  }
13492 
13493  missing_bot = 0;
13494  polybot = cpl_polynomial_new(1);
13495  for (k = 0; k <= order; k++) {
13496  coeff = cpl_table_get_double(polytraces, clab[k], 2*i+1, &null);
13497  if (null) {
13498  cpl_polynomial_delete(polybot);
13499  missing_bot = 1;
13500  break;
13501  }
13502  cpl_polynomial_set_coeff(polybot, &k, coeff);
13503  }
13504 
13505  if (missing_top && missing_bot) {
13506  cpl_msg_warning(func, "Spatial map, slit %d was not traced!",
13507  slit_id[i]);
13508  continue;
13509  }
13510 
13511  /*
13512  * In case just one of the two edges was not traced, the other
13513  * edge curvature model is duplicated and shifted to the other
13514  * end of the slit: better than nothing!
13515  */
13516 
13517  if (missing_top) {
13518  cpl_msg_warning(func, "Upper edge of slit %d was not traced: "
13519  "the spectral curvature of the lower edge "
13520  "is used instead.", slit_id[i]);
13521  polytop = cpl_polynomial_duplicate(polybot);
13522  ytop = cpl_table_get_double(slits, "ytop", i, NULL);
13523  ybot = cpl_table_get_double(slits, "ybottom", i, NULL);
13524  k = 0;
13525  coeff = cpl_polynomial_get_coeff(polybot, &k);
13526  coeff += ytop - ybot;
13527  cpl_polynomial_set_coeff(polytop, &k, coeff);
13528  }
13529 
13530  if (missing_bot) {
13531  cpl_msg_warning(func, "Lower edge of slit %d was not traced: "
13532  "the spectral curvature of the upper edge "
13533  "is used instead.", slit_id[i]);
13534  polybot = cpl_polynomial_duplicate(polytop);
13535  ytop = cpl_table_get_double(slits, "ytop", i, NULL);
13536  ybot = cpl_table_get_double(slits, "ybottom", i, NULL);
13537  k = 0;
13538  coeff = cpl_polynomial_get_coeff(polytop, &k);
13539  coeff -= ytop - ybot;
13540  cpl_polynomial_set_coeff(polybot, &k, coeff);
13541  }
13542 
13543  top = cpl_polynomial_eval_1d(polytop, refpixel, NULL);
13544  bot = cpl_polynomial_eval_1d(polybot, refpixel, NULL);
13545  npseudo = ceil(top-bot) + 1;
13546 
13547  if (npseudo < 1) {
13548  cpl_polynomial_delete(polytop);
13549  cpl_polynomial_delete(polybot);
13550  cpl_msg_warning(func, "Slit %d was badly traced: no extraction!",
13551  slit_id[i]);
13552  continue;
13553  }
13554 
13555  for (j = start_pixel; j < end_pixel; j++) {
13556  top = cpl_polynomial_eval_1d(polytop, j, NULL);
13557  bot = cpl_polynomial_eval_1d(polybot, j, NULL);
13558  factor = (top-bot)/npseudo;
13559  for (k = 0; k <= npseudo; k++) {
13560  ypos = top - k*factor;
13561  yint = ypos;
13562  yfra = ypos - yint;
13563  if (yint >= 0 && yint < ny-1) {
13564  data[j + nx*yint] = (top-yint)/factor;
13565  if (k) {
13566 
13567  /*
13568  * This is added to recover lost pixels on
13569  * the CCD image (pixels are lost because
13570  * the CCD pixels are less than npseudo+1).
13571  */
13572 
13573  if (yprev - yint > 1) {
13574  data[j + nx*(yint+1)] = (top-yint-1)/factor;
13575  }
13576  }
13577  }
13578  yprev = yint;
13579  }
13580  }
13581  cpl_polynomial_delete(polytop);
13582  cpl_polynomial_delete(polybot);
13583  }
13584 
13585  return calibration;
13586 }
13587 
13588 
13651 cpl_image *mos_detect_objects(cpl_image *image, cpl_table *slits, int margin,
13652  int maxradius, int conradius)
13653 {
13654  const char *func = "mos_detect_objects";
13655 
13656  cpl_image *profile;
13657  float *pdata;
13658  float *p;
13659 
13660  char name[MAX_COLNAME];
13661 
13662  int nslits;
13663  int npeaks;
13664  int nobjects, objpos, totobj;
13665  int maxobjects;
13666  int *position;
13667  int *length;
13668  int *reject;
13669  double *place;
13670  double *bright;
13671  double mindistance;
13672  int pos, count;
13673  int up;
13674  int low, hig;
13675  int row;
13676  int i, j, k;
13677 
13678  const int min_pixels = 10;
13679 
13680 
13681  if (cpl_error_get_code() != CPL_ERROR_NONE)
13682  return NULL;
13683 
13684  if (image == NULL || slits == NULL) {
13685  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
13686  return NULL;
13687  }
13688 
13689  if (margin < 0)
13690  margin = 0;
13691 
13692  if (maxradius < 0) {
13693  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
13694  return NULL;
13695  }
13696 
13697  if (conradius < 0) {
13698  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
13699  return NULL;
13700  }
13701 
13702  nslits = cpl_table_get_nrow(slits);
13703  position = cpl_table_get_data_int(slits, "position");
13704  length = cpl_table_get_data_int(slits, "length");
13705 
13706  profile = cpl_image_collapse_create(image, 1);
13707  cpl_image_divide_scalar(profile, cpl_image_get_size_x(image));
13708  pdata = cpl_image_get_data(profile);
13709 
13710  row = 1;
13711  maxobjects = 0;
13712  totobj = 0;
13713  for (i = 0; i < nslits; i++) {
13714 
13715  if (length[i] == 0)
13716  continue;
13717 
13718  pos = position[i] + margin;
13719  count = length[i] - 2*margin;
13720 
13721  if (count < min_pixels)
13722  continue;
13723 
13724  p = pdata + pos;
13725 
13726 
13727  /*
13728  * Count peaks candidates
13729  */
13730 
13731  npeaks = 0;
13732  if (p[0] > p[1] && p[1] > p[2] && p[2] > p[3] && p[3] > 0) {
13733  npeaks++;
13734  }
13735 
13736  up = 0;
13737  for (j = 0; j < count - 3; j++) {
13738  if (p[j] > 0) {
13739  if (p[j+1] > p[j]) {
13740  up++;
13741  }
13742  else {
13743  if (up > 2) {
13744  if (p[j+1] > p[j+2] && p[j+2] > 0) {
13745  if (p[j] > 5)
13746  npeaks++;
13747  }
13748  }
13749  else if (up > 1) {
13750  if (p[j+1] > p[j+2] && p[j+2] > p[j+3] && p[j+3] > 0) {
13751  if (p[j] > 5)
13752  npeaks++;
13753  }
13754  }
13755  up = 0;
13756  }
13757  }
13758  else {
13759  up = 0;
13760  }
13761  }
13762 
13763  if (p[count-1] > p[count-2] && p[count-2] > p[count-3]
13764  && p[count-3] > p[count-4] && p[count-4] > 0) {
13765  npeaks++;
13766  }
13767 
13768  if (npeaks == 0)
13769  continue;
13770 
13771 
13772  /*
13773  * Get candidates parameters
13774  */
13775 
13776  reject = cpl_calloc(npeaks, sizeof(int));
13777  bright = cpl_calloc(npeaks, sizeof(double));
13778  place = cpl_calloc(npeaks, sizeof(double));
13779 
13780  npeaks = 0;
13781  if (p[0] > p[1] && p[1] > p[2] && p[2] > p[3] && p[3] > 0) {
13782  bright[0] = p[0];
13783  place[0] = position[i] + margin;
13784  npeaks++;
13785  }
13786 
13787  up = 0;
13788  for (j = 0; j < count - 3; j++) {
13789  if (p[j] > 0) {
13790  if (p[j+1] > p[j]) {
13791  up++;
13792  }
13793  else {
13794  if (up > 2) {
13795  if (p[j+1] > p[j+2] && p[j+2] > 0) {
13796  if (p[j] > 5) {
13797  bright[npeaks] = p[j];
13798  place[npeaks] = position[i] + margin + j + 1
13799  + values_to_dx(p[j-1], p[j], p[j+1]);
13800  npeaks++;
13801  }
13802  }
13803  }
13804  else if (up > 1) {
13805  if (p[j+1] > p[j+2] && p[j+2] > p[j+3] && p[j+3] > 0) {
13806  if (p[j] > 5) {
13807  bright[npeaks] = p[j];
13808  place[npeaks] = position[i] + margin + j + 1
13809  + values_to_dx(p[j-1], p[j], p[j+1]);
13810  npeaks++;
13811  }
13812  }
13813  }
13814  up = 0;
13815  }
13816  }
13817  else {
13818  up = 0;
13819  }
13820  }
13821 
13822  if (p[count-1] > p[count-2] && p[count-2] > p[count-3]
13823  && p[count-3] > p[count-4] && p[count-4] > 0) {
13824  bright[npeaks] = p[count-1];
13825  place[npeaks] = position[i] + count;
13826  npeaks++;
13827  }
13828 
13829 
13830  /*
13831  * Now select the uncontaminated peaks
13832  */
13833 
13834  if (fabs(place[0] - pos) < 1.0)
13835  reject[0] = 1;
13836  if (fabs(place[npeaks-1] - pos - count) < 1.0)
13837  reject[npeaks-1] = 1;
13838  for (j = 0; j < npeaks; j++) {
13839  for (k = 0; k < npeaks; k++) {
13840  if (k == j)
13841  continue;
13842  mindistance = conradius * bright[k] / bright[j]
13843  * bright[k] / bright[j];
13844  if (fabs(place[j] - place[k]) < mindistance)
13845  reject[j] = 1;
13846  }
13847  }
13848 
13849 /* new part */
13850  for (j = 0; j < npeaks; j++) {
13851  if (reject[j])
13852  continue;
13853  if (j) {
13854  low = (place[j-1]*bright[j] + place[j]*bright[j-1])
13855  / (bright[j-1] + bright[j]) + 1;
13856  }
13857  else {
13858  low = pos;
13859  }
13860  if (j < npeaks - 1) {
13861  hig = (place[j+1]*bright[j] + place[j]*bright[j+1])
13862  / (bright[j+1] + bright[j]) + 1;
13863  }
13864  else {
13865  hig = pos + count;
13866  }
13867 
13868  if (low < pos)
13869  low = pos;
13870  if (hig > pos + count)
13871  hig = pos + count;
13872  if (place[j] - low > maxradius)
13873  low = place[j] - maxradius;
13874  if (hig - place[j] > maxradius)
13875  hig = place[j] + maxradius;
13876  if (hig == low)
13877  reject[j] = 1;
13878  }
13879 /* end new part */
13880 
13881  nobjects = npeaks;
13882  for (j = 0; j < npeaks; j++)
13883  if (reject[j])
13884  nobjects--;
13885 
13886  for (j = 0; j < nobjects; j++) {
13887  snprintf(name, MAX_COLNAME, "object_%d", j+1);
13888  if (cpl_table_has_column(slits, name))
13889  continue;
13890  cpl_table_new_column(slits, name, CPL_TYPE_DOUBLE);
13891  snprintf(name, MAX_COLNAME, "start_%d", j+1);
13892  cpl_table_new_column(slits, name, CPL_TYPE_INT);
13893  cpl_table_set_column_unit(slits, name, "pixel");
13894  snprintf(name, MAX_COLNAME, "end_%d", j+1);
13895  cpl_table_new_column(slits, name, CPL_TYPE_INT);
13896  cpl_table_set_column_unit(slits, name, "pixel");
13897  snprintf(name, MAX_COLNAME, "row_%d", j+1);
13898  cpl_table_new_column(slits, name, CPL_TYPE_INT);
13899  cpl_table_set_column_unit(slits, name, "pixel");
13900  }
13901 
13902  objpos = nobjects;
13903  for (j = 0; j < npeaks; j++) {
13904  if (reject[j])
13905  continue;
13906  if (j) {
13907  low = (place[j-1]*bright[j] + place[j]*bright[j-1])
13908  / (bright[j-1] + bright[j]) + 1;
13909  }
13910  else {
13911  low = pos;
13912  }
13913  if (j < npeaks - 1) {
13914  hig = (place[j+1]*bright[j] + place[j]*bright[j+1])
13915  / (bright[j+1] + bright[j]) + 1;
13916  }
13917  else {
13918  hig = pos + count;
13919  }
13920 
13921  if (low < pos)
13922  low = pos;
13923  if (hig > pos + count)
13924  hig = pos + count;
13925  if (place[j] - low > maxradius)
13926  low = place[j] - maxradius;
13927  if (hig - place[j] > maxradius)
13928  hig = place[j] + maxradius;
13929 
13930  snprintf(name, MAX_COLNAME, "object_%d", objpos);
13931  cpl_table_set_double(slits, name, i, place[j]);
13932  snprintf(name, MAX_COLNAME, "start_%d", objpos);
13933  cpl_table_set_int(slits, name, i, low);
13934  snprintf(name, MAX_COLNAME, "end_%d", objpos);
13935  cpl_table_set_int(slits, name, i, hig);
13936  snprintf(name, MAX_COLNAME, "row_%d", objpos);
13937  cpl_table_set_int(slits, name, i, row + objpos - 1);
13938  totobj++;
13939  objpos--;
13940  }
13941 
13942  row += nobjects;
13943 
13944  if (maxobjects < nobjects)
13945  maxobjects = nobjects;
13946 
13947  cpl_free(reject);
13948  cpl_free(bright);
13949  cpl_free(place);
13950 
13951  }
13952 
13953 /* nobjects = row - nobjects; A bug, I think... */
13954  row = cpl_table_get_nrow(slits);
13955 
13956  for (i = 0; i < row; i++) {
13957  for (j = 0; j < maxobjects; j++) {
13958  snprintf(name, MAX_COLNAME, "row_%d", j+1);
13959  if (cpl_table_is_valid(slits, name, i))
13960  cpl_table_set_int(slits, name, i, totobj -
13961  cpl_table_get_int(slits, name, i, NULL));
13962  }
13963  }
13964 
13965  for (i = 0; i < maxobjects; i++) {
13966  snprintf(name, MAX_COLNAME, "start_%d", i+1);
13967  cpl_table_fill_invalid_int(slits, name, -1);
13968  snprintf(name, MAX_COLNAME, "end_%d", i+1);
13969  cpl_table_fill_invalid_int(slits, name, -1);
13970  snprintf(name, MAX_COLNAME, "row_%d", i+1);
13971  cpl_table_fill_invalid_int(slits, name, -1);
13972  }
13973 
13974  return profile;
13975 }
13976 
13977 
14002 cpl_image **mos_extract_objects(cpl_image *science, cpl_image *sky,
14003  cpl_table *objects, int extraction, double ron,
14004  double gain, int ncombined)
14005 {
14006  const char *func = "mos_extract_objects";
14007 
14008  char name[MAX_COLNAME];
14009 
14010  cpl_image **output;
14011  cpl_image *extracted;
14012  cpl_image *extr_sky;
14013  cpl_image *error;
14014  cpl_image *sciwin;
14015  cpl_image *skywin;
14016  int nslits;
14017  int nobjects;
14018  int maxobjects;
14019  int nx;
14020  int ylow, yhig;
14021  int i, j;
14022 
14023 
14024  if (science == NULL || sky == NULL) {
14025  cpl_msg_error(func, "Both scientific exposures are required in input");
14026  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
14027  return NULL;
14028  }
14029 
14030  if (objects == NULL) {
14031  cpl_msg_error(func, "An object table is required in input");
14032  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
14033  return NULL;
14034  }
14035 
14036  if (extraction < 0 || extraction > 1) {
14037  cpl_msg_error(func, "Invalid extraction mode (%d): it should be "
14038  "either 0 or 1", extraction);
14039  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
14040  return NULL;
14041  }
14042 
14043  if (ron < 0.0) {
14044  cpl_msg_error(func, "Invalid read-out-noise (%f ADU)", ron);
14045  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
14046  return NULL;
14047  }
14048 
14049  if (gain < 0.1) {
14050  cpl_msg_error(func, "Invalid gain factor (%f e-/ADU)", gain);
14051  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
14052  return NULL;
14053  }
14054 
14055  if (ncombined < 1) {
14056  cpl_msg_error(func, "Invalid number of combined frames (%d): "
14057  "it should be at least 1", ncombined);
14058  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
14059  return NULL;
14060  }
14061 
14062 
14063  /*
14064  * Count the max number of objects per slit. Note that maxobjects
14065  * is intentionally the max number of objects increased by one.
14066  */
14067 
14068  maxobjects = 1;
14069  snprintf(name, MAX_COLNAME, "object_%d", maxobjects);
14070  while (cpl_table_has_column(objects, name)) {
14071  maxobjects++;
14072  snprintf(name, MAX_COLNAME, "object_%d", maxobjects);
14073  }
14074 
14075 
14076  /*
14077  * Count objects to extract
14078  */
14079 
14080  nobjects = 0;
14081  nslits = cpl_table_get_nrow(objects);
14082 
14083  for (i = 0; i < nslits; i++) {
14084  for (j = 1; j < maxobjects; j++) {
14085  snprintf(name, MAX_COLNAME, "object_%d", j);
14086  if (cpl_table_is_valid(objects, name, i))
14087  nobjects++;
14088  }
14089  }
14090 
14091  if (nobjects == 0)
14092  return NULL;
14093 
14094  nx = cpl_image_get_size_x(science);
14095 
14096  output = cpl_calloc(3, sizeof(cpl_image *));
14097  extracted = output[0] = cpl_image_new(nx, nobjects, CPL_TYPE_FLOAT);
14098  extr_sky = output[1] = cpl_image_new(nx, nobjects, CPL_TYPE_FLOAT);
14099  error = output[2] = cpl_image_new(nx, nobjects, CPL_TYPE_FLOAT);
14100 
14101 
14102  /*
14103  * Extract objects
14104  */
14105 
14106  nobjects = 0;
14107  for (i = 0; i < nslits; i++) {
14108  for (j = 1; j < maxobjects; j++) {
14109  snprintf(name, MAX_COLNAME, "object_%d", j);
14110  if (cpl_table_is_valid(objects, name, i)) {
14111  snprintf(name, MAX_COLNAME, "start_%d", j);
14112  ylow = cpl_table_get_int(objects, name, i, NULL);
14113  snprintf(name, MAX_COLNAME, "end_%d", j);
14114  yhig = cpl_table_get_int(objects, name, i, NULL);
14115  snprintf(name, MAX_COLNAME, "row_%d", j);
14116  nobjects = cpl_table_get_int(objects, name, i, NULL);
14117  sciwin = cpl_image_extract(science, 1, ylow+1, nx, yhig);
14118  skywin = cpl_image_extract(sky, 1, ylow+1, nx, yhig);
14119 /*
14120  * Cleaning the cosmics locally was really NOT a good idea...
14121  * I leave it here, commented out, to never forget this mistake!
14122 
14123  if (extraction) {
14124  mos_clean_cosmics(sciwin, gain, -1., -1.);
14125  }
14126  */
14127  mos_extraction(sciwin, skywin, extracted, extr_sky, error,
14128  nobjects, extraction, ron, gain, ncombined);
14129 
14130  /*
14131  * Hidden check whether the spectrum was saturated or not
14132  */
14133 
14134  {
14135  cpl_image *total = cpl_image_add_create(sciwin, skywin);
14136  float *data = cpl_image_get_data_float(total);
14137  int size = cpl_image_get_size_x(total)
14138  * cpl_image_get_size_y(total);
14139  int k;
14140  char *saturation_level = getenv("SATURATION_LEVEL");
14141  float saturation = 62000.0;
14142  char *max_saturated = getenv("MAX_SATURATED");
14143  int max_satur = 10;
14144  int saturated;
14145 
14146  if (saturation_level)
14147  saturation = atof(saturation_level);
14148 
14149  if (max_saturated)
14150  max_satur = atoi(max_saturated);
14151 
14152  saturated = 0;
14153  for (k = 0; k < size; k++) {
14154  if (data[k] > saturation) {
14155  saturated++;
14156  if (saturated > max_satur) {
14157  break;
14158  }
14159  }
14160  }
14161 
14162  if (saturated > max_satur)
14163  saturated = 1;
14164  else
14165  saturated = 0;
14166 
14167  data = cpl_image_get_data(extracted);
14168  data[nobjects * nx] = saturated;
14169  }
14170 
14171  cpl_image_delete(sciwin);
14172  cpl_image_delete(skywin);
14173  nobjects++;
14174  }
14175  }
14176  }
14177 
14178  return output;
14179 
14180 }
14181 
14182 
14205 int mos_spectral_resolution(cpl_image *image, double lambda, double startwave,
14206  double dispersion, int saturation,
14207  double *mfwhm, double *rmsfwhm,
14208  double *resolution, double *rmsres, int *nlines)
14209 {
14210  cpl_vector *vector;
14211 
14212  int i, j, n, m;
14213  int position, maxpos;
14214  int xlen, ylen;
14215  int sp, ep;
14216  int radius;
14217  int sradius = 40;
14218  int threshold = 250; /* Peak must be so many ADUs above min */
14219 
14220  int ifwhm;
14221  double fwhm;
14222  double *buffer;
14223  double min, max, halfmax;
14224  double cut = 1.5; /* To cut outliers from FWHM values (pixel) */
14225  double value, rms;
14226 
14227  float *data;
14228 
14229 
14230  *resolution = 0.0;
14231  *rmsres = 0.0;
14232  *nlines = 0;
14233 
14234  xlen = cpl_image_get_size_x(image);
14235  ylen = cpl_image_get_size_y(image);
14236  data = cpl_image_get_data(image);
14237 
14238  buffer = cpl_malloc(ylen * sizeof(double));
14239 
14240  /*
14241  * Closest pixel to specified wavelength.
14242  */
14243 
14244  position = floor((lambda - startwave) / dispersion + 0.5);
14245 
14246  sp = position - sradius;
14247  ep = position + sradius;
14248 
14249  if (sp < 0 || ep > xlen) {
14250  cpl_free(buffer);
14251  return 0;
14252  }
14253 
14254  for (i = 0, n = 0; i < ylen; i++) { /* For each row of each slit */
14255 
14256  /*
14257  * Search interval for peak. Abort if too close to image border.
14258  */
14259 
14260  radius = mos_lines_width(data + i*xlen + position - sradius,
14261  2*sradius + 1);
14262  if (radius < 5)
14263  radius = 5;
14264 
14265  sp = position - radius;
14266  ep = position + radius;
14267 
14268  if (sp < 0 || ep > xlen) {
14269  cpl_free(buffer);
14270  return 0;
14271  }
14272 
14273 
14274  /*
14275  * Determine min-max value and position.
14276  */
14277 
14278  maxpos = sp;
14279  min = max = data[sp + i * xlen];
14280  for (j = sp; j < ep; j++) {
14281  if (data[j + i * xlen] > max) {
14282  max = data[j + i * xlen];
14283  maxpos = j;
14284  }
14285  if (data[j + i * xlen] < min) {
14286  min = data[j + i * xlen];
14287  }
14288  }
14289 
14290  if (fabs(min) < 0.0000001) /* Truncated spectrum */
14291  continue;
14292 
14293  if (max - min < threshold) /* Low signal... */
14294  continue;
14295 
14296  if (max > saturation) /* Saturation */
14297  continue;
14298 
14299  /*
14300  * Determine FWHM counting pixels with value greater than
14301  * half of the max value, to the right and to the left of
14302  * the max. Linear interpolation between the pixels where
14303  * the transition happens.
14304  */
14305 
14306  halfmax = (max + min)/ 2.0;
14307 
14308  fwhm = 0.0;
14309  ifwhm = 0;
14310  for (j = maxpos; j < maxpos + radius; j++) {
14311  if (j < xlen) {
14312  if (data[j + i * xlen] < halfmax) {
14313  fwhm = ifwhm + (data[j - 1 + i * xlen] - halfmax)
14314  / (data[j - 1 + i * xlen] - data[j + i * xlen]);
14315  break;
14316  }
14317  ifwhm++;
14318  }
14319  }
14320 
14321  ifwhm = 0;
14322  for (j = maxpos; j > maxpos - radius; j--) {
14323  if (j >= 0) {
14324  if (data[j + i * xlen] < halfmax) {
14325  fwhm += ifwhm + (data[j + 1 + i * xlen] - halfmax)
14326  / (data[j + 1 + i * xlen] - data[j + i * xlen]);
14327  break;
14328  }
14329  ifwhm++;
14330  }
14331  }
14332 
14333  if (fwhm > 3.0) {
14334  buffer[n] = fwhm - 2.0;
14335  n++;
14336  }
14337 
14338  }
14339 
14340  if (n == 0) {
14341  cpl_free(buffer);
14342  return 0;
14343  }
14344 
14345  vector = cpl_vector_wrap(n, buffer);
14346  value = cpl_vector_get_median_const(vector);
14347  cpl_vector_unwrap(vector);
14348 
14349  rms = 0.0;
14350  for (i = 0, m = 0; i < n; i++) {
14351  if (fabs(buffer[i] - value) < cut) {
14352  rms += fabs(buffer[i] - value);
14353  m++;
14354  }
14355  }
14356 
14357  cpl_free(buffer);
14358 
14359  if (m < 3)
14360  return 0;
14361 
14362  rms /= m;
14363  rms *= 1.25; /* Factor to convert average deviation to sigma */
14364 
14365  value *= dispersion;
14366  rms *= dispersion;
14367 
14368  *mfwhm = value;
14369  *rmsfwhm = rms;
14370 
14371  *resolution = lambda / value;
14372  *rmsres = *resolution * rms / value;
14373 
14374  *nlines = m;
14375 
14376  return 1;
14377 }
14378 
14379 
14401 cpl_table *mos_resolution_table(cpl_image *image, double startwave,
14402  double dispersion, int saturation,
14403  cpl_vector *lines)
14404 {
14405 
14406  cpl_table *table;
14407  double *line;
14408  double fwhm;
14409  double rmsfwhm;
14410  double resolution;
14411  double rmsres;
14412  int nref;
14413  int nlines;
14414  int i;
14415 
14416 
14417  nref = cpl_vector_get_size(lines);
14418  line = cpl_vector_get_data(lines);
14419 
14420  table = cpl_table_new(nref);
14421  cpl_table_new_column(table, "wavelength", CPL_TYPE_DOUBLE);
14422  cpl_table_set_column_unit(table, "wavelength", "Angstrom");
14423  cpl_table_new_column(table, "fwhm", CPL_TYPE_DOUBLE);
14424  cpl_table_set_column_unit(table, "fwhm", "Angstrom");
14425  cpl_table_new_column(table, "fwhm_rms", CPL_TYPE_DOUBLE);
14426  cpl_table_set_column_unit(table, "fwhm_rms", "Angstrom");
14427  cpl_table_new_column(table, "resolution", CPL_TYPE_DOUBLE);
14428  cpl_table_new_column(table, "resolution_rms", CPL_TYPE_DOUBLE);
14429  cpl_table_new_column(table, "nlines", CPL_TYPE_INT);
14430 
14431  for (i = 0; i < nref; i++) {
14432  if (mos_spectral_resolution(image, line[i], startwave, dispersion,
14433  saturation, &fwhm, &rmsfwhm,
14434  &resolution, &rmsres, &nlines)) {
14435  cpl_table_set_double(table, "wavelength", i, line[i]);
14436  cpl_table_set_double(table, "fwhm", i, fwhm);
14437  cpl_table_set_double(table, "fwhm_rms", i, rmsfwhm);
14438  cpl_table_set_double(table, "resolution", i, resolution);
14439  cpl_table_set_double(table, "resolution_rms", i, rmsres);
14440  cpl_table_set_int(table, "nlines", i, nlines);
14441  }
14442  else
14443  cpl_table_set_int(table, "nlines", i, 0);
14444  }
14445 
14446  if (cpl_table_has_valid(table, "wavelength"))
14447  return table;
14448 
14449  cpl_table_delete(table);
14450 
14451  return NULL;
14452 
14453 }
14454 
14455 
14473 double mos_integrate_signal(cpl_image *image, cpl_image *wavemap,
14474  int ystart, int yend, double wstart, double wend)
14475 {
14476  const char *func = "mos_integrate_signal";
14477 
14478  double sum;
14479  float *sdata;
14480  float *wdata;
14481  int nx, ny;
14482  int x, y;
14483 
14484 
14485  if (image == NULL || wavemap == NULL) {
14486  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
14487  return 0.0;
14488  }
14489 
14490  if (ystart > yend || wstart >= wend) {
14491  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
14492  return 0.0;
14493  }
14494 
14495  nx = cpl_image_get_size_x(image);
14496  ny = cpl_image_get_size_y(image);
14497 
14498  if (!(nx == cpl_image_get_size_x(wavemap)
14499  && ny == cpl_image_get_size_y(wavemap))) {
14500  cpl_error_set(func, CPL_ERROR_INCOMPATIBLE_INPUT);
14501  return 0.0;
14502  }
14503 
14504  if (ystart < 0 || yend > ny) {
14505  cpl_error_set(func, CPL_ERROR_ACCESS_OUT_OF_RANGE);
14506  return 0.0;
14507  }
14508 
14509  sdata = cpl_image_get_data(image);
14510  wdata = cpl_image_get_data(wavemap);
14511 
14512  sdata += ystart*nx;
14513  wdata += ystart*nx;
14514 
14515  sum = 0.0;
14516  for (y = ystart; y < yend; y++) {
14517  for (x = 0; x < nx; x++) {
14518  if (wdata[x] < wstart || wdata[x] > wend)
14519  continue;
14520  sum += sdata[x];
14521  }
14522  sdata += nx;
14523  wdata += nx;
14524  }
14525 
14526  return sum;
14527 
14528 }
14529 
14530 /****************************************************************************
14531  * From this point on, the instrument dependent functions are added:
14532  * they are functions that retrieve information that is stored in
14533  * the data headers in some instrument specific way, such as the
14534  * location of overscans, the slits positions on the telescope
14535  * focal plane, the gain factor, etc.
14536  */
14537 
14538 
14561 cpl_table *mos_load_slits_fors_mxu(cpl_propertylist *header)
14562 {
14563  const char *func = "mos_load_slits_fors_mxu";
14564 
14565  cpl_table *slits;
14566  char keyname[MAX_COLNAME];
14567  const char *instrume;
14568  const char *target_name;
14569  float slit_x;
14570  float slit_y;
14571  float length;
14572 /* double arc2mm = 0.53316; */
14573  double arc2mm = 0.528;
14574  int nslits;
14575  int slit_id;
14576  int fors;
14577  int chip;
14578  int found;
14579 
14580  /*
14581  * The limits below are used to exclude from the loaded slit list
14582  * any slit that surely doesn't belong to the used chip. This is
14583  * a way to reduce the chance of ambiguous slit identification.
14584  */
14585 
14586  float low_limit1 = 10.0;
14587  float hig_limit2 = 30.0;
14588 
14589 
14590  if (cpl_error_get_code() != CPL_ERROR_NONE) {
14591  return NULL;
14592  }
14593 
14594  if (header == NULL) {
14595  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
14596  return NULL;
14597  }
14598 
14599 
14600  /*
14601  * See if this is FORS1 or FORS2;
14602  */
14603 
14604  instrume = cpl_propertylist_get_string(header, "INSTRUME");
14605 
14606  fors = 0;
14607  if (instrume[4] == '1')
14608  fors = 1;
14609  if (instrume[4] == '2')
14610  fors = 2;
14611 
14612  if (fors != 2) {
14613  cpl_msg_error(func, "Wrong instrument: %s\n"
14614  "FORS2 is expected for MXU data", instrume);
14615  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
14616  return NULL;
14617  }
14618 
14619 
14620  /*
14621  * The master and slave chips can be identified by their positions
14622  * in the chip array in the case of FORS2 data (with fors1 the chip
14623  * is always 1). chip = 2 is the master, chip = 1 is the slave.
14624  */
14625 
14626  chip = cpl_propertylist_get_int(header, "ESO DET CHIP1 Y");
14627 
14628  if (cpl_error_get_code() != CPL_ERROR_NONE) {
14629  cpl_msg_error(func, "Missing keyword ESO DET CHIP1 Y "
14630  "in FITS header");
14631  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
14632  return NULL;
14633  }
14634 
14635  if (chip != 1 && chip != 2) {
14636  cpl_msg_error(func, "Unexpected chip position in keyword "
14637  "ESO DET CHIP1 Y: %d", chip);
14638  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
14639  return NULL;
14640  }
14641 
14642 
14643  /*
14644  * Count slits in header (excluding reference slits, and the slits
14645  * that _surely_ belong to the other chip)
14646  */
14647 
14648  nslits = 0;
14649  slit_id = 0;
14650  found = 1;
14651 
14652  while (found) {
14653  slit_id++;
14654  snprintf(keyname, MAX_COLNAME, "ESO INS MOS%d YPOS", slit_id + 100);
14655  if (cpl_propertylist_has(header, keyname)) {
14656  slit_y = cpl_propertylist_get_double(header, keyname);
14657 
14658  if (chip == 1)
14659  if (slit_y < low_limit1)
14660  continue;
14661  if (chip == 2)
14662  if (slit_y > hig_limit2)
14663  continue;
14664 
14665  snprintf(keyname, MAX_COLNAME, "ESO INS TARG%d NAME",
14666  slit_id + 100);
14667  if (cpl_propertylist_has(header, keyname)) {
14668  target_name = cpl_propertylist_get_string(header, keyname);
14669  if (strncmp(target_name, "refslit", 7))
14670  nslits++;
14671  }
14672  else
14673  nslits++;
14674  }
14675  else
14676  found = 0;
14677  }
14678 
14679  if (cpl_error_get_code() != CPL_ERROR_NONE) {
14680  cpl_msg_error(func, "%s while loading slits coordinates from "
14681  "FITS header", cpl_error_get_message());
14682  cpl_error_set_where(func);
14683  return NULL;
14684  }
14685 
14686  if (nslits == 0) {
14687  cpl_msg_error(func, "No slits coordinates found in header");
14688  cpl_error_set(func, CPL_ERROR_DATA_NOT_FOUND);
14689  return NULL;
14690  }
14691 
14692  slits = cpl_table_new(nslits);
14693  cpl_table_new_column(slits, "slit_id", CPL_TYPE_INT);
14694  cpl_table_new_column(slits, "xtop", CPL_TYPE_DOUBLE);
14695  cpl_table_new_column(slits, "ytop", CPL_TYPE_DOUBLE);
14696  cpl_table_new_column(slits, "xbottom", CPL_TYPE_DOUBLE);
14697  cpl_table_new_column(slits, "ybottom", CPL_TYPE_DOUBLE);
14698  cpl_table_set_column_unit(slits, "xtop", "pixel");
14699  cpl_table_set_column_unit(slits, "ytop", "pixel");
14700  cpl_table_set_column_unit(slits, "xbottom", "pixel");
14701  cpl_table_set_column_unit(slits, "ybottom", "pixel");
14702 
14703  nslits = 0;
14704  slit_id = 0;
14705  found = 1;
14706  while (found) {
14707  slit_id++;
14708  snprintf(keyname, MAX_COLNAME, "ESO INS MOS%d YPOS", slit_id + 100);
14709  if (cpl_propertylist_has(header, keyname)) {
14710  slit_y = cpl_propertylist_get_double(header, keyname);
14711 
14712  if (chip == 1)
14713  if (slit_y < low_limit1)
14714  continue;
14715  if (chip == 2)
14716  if (slit_y > hig_limit2)
14717  continue;
14718 
14719  /*
14720  * Y-flip the slit position, to match CCD pixel coordinate
14721  * convention
14722  */
14723 
14724  slit_y = -slit_y;
14725 
14726  snprintf(keyname, MAX_COLNAME, "ESO INS MOS%d XPOS", slit_id + 100);
14727  slit_x = cpl_propertylist_get_double(header, keyname);
14728  if (cpl_error_get_code() != CPL_ERROR_NONE) {
14729  cpl_table_delete(slits);
14730  cpl_msg_error(func, "Missing keyword %s in FITS header",
14731  keyname);
14732  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
14733  return NULL;
14734  }
14735 
14736  snprintf(keyname, MAX_COLNAME, "ESO INS MOS%d LEN", slit_id + 100);
14737  length = cpl_propertylist_get_double(header, keyname);
14738  if (cpl_error_get_code() != CPL_ERROR_NONE) {
14739  cpl_table_delete(slits);
14740  cpl_msg_error(func, "Missing keyword %s in FITS header",
14741  keyname);
14742  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
14743  return NULL;
14744  }
14745 
14746  length *= arc2mm;
14747 
14748  snprintf(keyname, MAX_COLNAME, "ESO INS TARG%d NAME",
14749  slit_id + 100);
14750  if (cpl_propertylist_has(header, keyname)) {
14751  target_name = cpl_propertylist_get_string(header, keyname);
14752  if (strncmp(target_name, "refslit", 7)) {
14753  cpl_table_set_int(slits, "slit_id", nslits, slit_id);
14754  cpl_table_set(slits, "xtop", nslits, slit_x);
14755  cpl_table_set(slits, "ytop", nslits, slit_y + length/2);
14756  cpl_table_set(slits, "xbottom", nslits, slit_x);
14757  cpl_table_set(slits, "ybottom", nslits, slit_y - length/2);
14758  nslits++;
14759  }
14760  }
14761  else {
14762  cpl_table_set_int(slits, "slit_id", nslits, slit_id);
14763  cpl_table_set(slits, "xtop", nslits, slit_x);
14764  cpl_table_set(slits, "ytop", nslits, slit_y + length/2);
14765  cpl_table_set(slits, "xbottom", nslits, slit_x);
14766  cpl_table_set(slits, "ybottom", nslits, slit_y - length/2);
14767  nslits++;
14768  }
14769  }
14770  else
14771  found = 0;
14772  }
14773 
14774  return slits;
14775 }
14776 
14777 
14801 cpl_table *mos_load_slits_fors_mos(cpl_propertylist *header,
14802  int * nslits_out_det)
14803 {
14804  const char *func = "mos_load_slits_fors_mos";
14805 
14806  cpl_table *slits;
14807  char keyname[MAX_COLNAME];
14808  const char *instrume;
14809  const char *chipname;
14810  float slit_x;
14811  int first_slit, last_slit;
14812  cpl_size nslits;
14813  int slit_id;
14814  int fors;
14815  int chip;
14816  int fors_is_old;
14817 
14818  /*
14819  * The Y coordinates of the slits are fixed
14820  */
14821 
14822  float ytop[19] = { 113.9, 101.3, 89.9, 77.3, 65.9, 53.3,
14823  41.9, 29.3, 17.9, 5.3, -6.1, -18.7,
14824  -30.1, -42.7, -54.1, -66.7, -78.1, -90.7,
14825  -102.1 };
14826  float ybottom[19] = { 102.1, 90.7, 78.1, 66.7, 54.1, 42.7,
14827  30.1, 18.7, 6.1, -5.3, -17.9, -29.3,
14828  -41.9, -53.3, -65.9, -77.3, -89.9, -101.3,
14829  -113.9 };
14830 
14831 
14832  if (cpl_error_get_code() != CPL_ERROR_NONE) {
14833  return NULL;
14834  }
14835 
14836  if (header == NULL) {
14837  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
14838  return NULL;
14839  }
14840 
14841 
14842  /*
14843  * See if this is FORS1 or FORS2;
14844  */
14845 
14846  instrume = cpl_propertylist_get_string(header, "INSTRUME");
14847 
14848  fors = 0;
14849  if (instrume[4] == '1')
14850  fors = 1;
14851  if (instrume[4] == '2')
14852  fors = 2;
14853 
14854  if (fors == 0) {
14855  cpl_msg_error(func, "Wrong instrument found in FITS header: %s",
14856  instrume);
14857  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
14858  return NULL;
14859  }
14860 
14861  /* FIXME:
14862  * This is the way FORS1 data belong to the upgraded chips,
14863  * named "Marlene" and "Norma III". It's a quick solution,
14864  * there are hardcoded values here!!!
14865  */
14866 
14867  chipname = cpl_propertylist_get_string(header, "ESO DET CHIP1 ID");
14868 
14869  if (chipname[0] == 'M' || chipname[0] == 'N')
14870  fors_is_old = 0;
14871  else
14872  fors_is_old = 1;
14873 
14874  if (fors == 1 && fors_is_old) {
14875  first_slit = 1;
14876  last_slit = 19;
14877  }
14878  else {
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: chip = 2 is the
14883  * 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  if (chip == 1) {
14903  first_slit = 12;
14904  last_slit = 19;
14905  }
14906  else {
14907  first_slit = 1;
14908  last_slit = 11;
14909  }
14910  }
14911 
14912 
14913  /*
14914  * Count slits in header (excluding closed slits - i.e. those with
14915  * offsets greater than 115 mm - and the slits that do not belong
14916  * to this chip)
14917  */
14918 
14919  nslits = 0;
14920  slit_id = 0;
14921  for (slit_id = first_slit; slit_id <= last_slit; slit_id++) {
14922  snprintf(keyname, MAX_COLNAME, "ESO INS MOS%d POS", slit_id);
14923  if (cpl_propertylist_has(header, keyname)) {
14924  slit_x = cpl_propertylist_get_double(header, keyname);
14925  if (fabs(slit_x) < 115.0)
14926  nslits++;
14927  else
14928  (*nslits_out_det)++;
14929  }
14930  else {
14931  cpl_msg_error(func, "Missing keyword %s in FITS header", keyname);
14932  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
14933  return NULL;
14934  }
14935  }
14936 
14937  if (cpl_error_get_code() != CPL_ERROR_NONE) {
14938  cpl_msg_error(func, "%s while loading slits coordinates from "
14939  "FITS header", cpl_error_get_message());
14940  cpl_error_set_where(func);
14941  return NULL;
14942  }
14943 
14944  if (nslits == 0) {
14945  cpl_msg_error(func, "No slits coordinates found in header");
14946  cpl_error_set(func, CPL_ERROR_DATA_NOT_FOUND);
14947  return NULL;
14948  }
14949 
14950  slits = cpl_table_new(nslits);
14951  cpl_table_new_column(slits, "slit_id", CPL_TYPE_INT);
14952  cpl_table_new_column(slits, "xtop", CPL_TYPE_DOUBLE);
14953  cpl_table_new_column(slits, "ytop", CPL_TYPE_DOUBLE);
14954  cpl_table_new_column(slits, "xbottom", CPL_TYPE_DOUBLE);
14955  cpl_table_new_column(slits, "ybottom", CPL_TYPE_DOUBLE);
14956  cpl_table_set_column_unit(slits, "xtop", "pixel");
14957  cpl_table_set_column_unit(slits, "ytop", "pixel");
14958  cpl_table_set_column_unit(slits, "xbottom", "pixel");
14959  cpl_table_set_column_unit(slits, "ybottom", "pixel");
14960 
14961  nslits = 0;
14962  slit_id = 0;
14963  for (slit_id = first_slit; slit_id <= last_slit; slit_id++) {
14964  snprintf(keyname, MAX_COLNAME, "ESO INS MOS%d POS", slit_id);
14965  slit_x = cpl_propertylist_get_double(header, keyname);
14966  if (fabs(slit_x) < 115.0) {
14967  cpl_table_set_int(slits, "slit_id", nslits, slit_id);
14968  cpl_table_set(slits, "xtop", nslits, slit_x);
14969  cpl_table_set(slits, "ytop", nslits, ytop[slit_id-1]);
14970  cpl_table_set(slits, "xbottom", nslits, slit_x);
14971  cpl_table_set(slits, "ybottom", nslits, ybottom[slit_id-1]);
14972  nslits++;
14973  }
14974  }
14975 
14976  return slits;
14977 }
14978 
14979 
15003 cpl_table *mos_load_slits_fors_lss(cpl_propertylist *header)
15004 {
15005  const char *func = "mos_load_slits_fors_lss";
15006 
15007  cpl_table *slits;
15008  char *slit_name;
15009  const char *instrume;
15010  int fors;
15011  int chip;
15012  float ytop;
15013  float ybottom;
15014 
15015  if (cpl_error_get_code() != CPL_ERROR_NONE) {
15016  return NULL;
15017  }
15018 
15019  if (header == NULL) {
15020  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
15021  return NULL;
15022  }
15023 
15024 
15025  /*
15026  * See if this is FORS1 or FORS2;
15027  */
15028 
15029  instrume = cpl_propertylist_get_string(header, "INSTRUME");
15030 
15031  fors = 0;
15032  if (instrume[4] == '1')
15033  fors = 1;
15034  if (instrume[4] == '2')
15035  fors = 2;
15036 
15037  if (fors == 0) {
15038  cpl_msg_error(func, "Wrong instrument found in FITS header: %s",
15039  instrume);
15040  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
15041  return NULL;
15042  }
15043 
15044  if (fors == 1) {
15045  ytop = 109.94;
15046  ybottom = -109.94;
15047  }
15048  else {
15049 
15050  /*
15051  * The master and slave chips can be identified by their positions
15052  * in the chip array in the case of FORS2 data: chip = 2 is the
15053  * master, chip = 1 is the slave.
15054  */
15055 
15056  chip = cpl_propertylist_get_int(header, "ESO DET CHIP1 Y");
15057 
15058  if (cpl_error_get_code() != CPL_ERROR_NONE) {
15059  cpl_msg_error(func, "Missing keyword ESO DET CHIP1 Y "
15060  "in FITS header");
15061  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
15062  return NULL;
15063  }
15064 
15065  if (chip != 1 && chip != 2) {
15066  cpl_msg_error(func, "Unexpected chip position in keyword "
15067  "ESO DET CHIP1 Y: %d", chip);
15068  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
15069  return NULL;
15070  }
15071 
15072  if (chip == 1) {
15073  ytop = 30.0;
15074  ybottom = -109.94;
15075  }
15076  else {
15077  ytop = 109.94;
15078  ybottom = -20.0;
15079  }
15080  }
15081 
15082 
15083  slits = cpl_table_new(1);
15084  cpl_table_new_column(slits, "slit_id", CPL_TYPE_INT);
15085  cpl_table_new_column(slits, "xtop", CPL_TYPE_DOUBLE);
15086  cpl_table_new_column(slits, "ytop", CPL_TYPE_DOUBLE);
15087  cpl_table_new_column(slits, "xbottom", CPL_TYPE_DOUBLE);
15088  cpl_table_new_column(slits, "ybottom", CPL_TYPE_DOUBLE);
15089  cpl_table_set_column_unit(slits, "xtop", "pixel");
15090  cpl_table_set_column_unit(slits, "ytop", "pixel");
15091  cpl_table_set_column_unit(slits, "xbottom", "pixel");
15092  cpl_table_set_column_unit(slits, "ybottom", "pixel");
15093 
15094  slit_name = (char *)cpl_propertylist_get_string(header,
15095  "ESO INS SLIT NAME");
15096 
15097  cpl_table_set(slits, "ytop", 0, ytop);
15098  cpl_table_set(slits, "ybottom", 0, ybottom);
15099 
15100  if (!strncmp(slit_name, "lSlit0_3arcsec", 14)) {
15101  cpl_table_set_int(slits, "slit_id", 0, 1);
15102  cpl_table_set(slits, "xbottom", 0, -0.075);
15103  cpl_table_set(slits, "xtop", 0, 0.075);
15104  }
15105  else if (!strncmp(slit_name, "lSlit0_4arcsec", 14)) {
15106  cpl_table_set_int(slits, "slit_id", 0, 2);
15107  cpl_table_set(slits, "xbottom", 0, 5.895);
15108  cpl_table_set(slits, "xtop", 0, 6.105);
15109  }
15110  else if (!strncmp(slit_name, "lSlit0_5arcsec", 14)) {
15111  cpl_table_set_int(slits, "slit_id", 0, 3);
15112  cpl_table_set(slits, "xbottom", 0, -6.135);
15113  cpl_table_set(slits, "xtop", 0, -5.865);
15114  }
15115  else if (!strncmp(slit_name, "lSlit0_7arcsec", 14)) {
15116  cpl_table_set_int(slits, "slit_id", 0, 4);
15117  cpl_table_set(slits, "xbottom", 0, 11.815);
15118  cpl_table_set(slits, "xtop", 0, 12.185);
15119  }
15120  else if (!strncmp(slit_name, "lSlit1_0arcsec", 14)) {
15121  cpl_table_set_int(slits, "slit_id", 0, 5);
15122  cpl_table_set(slits, "xbottom", 0, -12.265);
15123  cpl_table_set(slits, "xtop", 0, -11.735);
15124  }
15125  else if (!strncmp(slit_name, "lSlit1_3arcsec", 14)) {
15126  cpl_table_set_int(slits, "slit_id", 0, 6);
15127  cpl_table_set(slits, "xbottom", 0, 17.655);
15128  cpl_table_set(slits, "xtop", 0, 18.345);
15129  }
15130  else if (!strncmp(slit_name, "lSlit1_6arcsec", 14)) {
15131  cpl_table_set_int(slits, "slit_id", 0, 7);
15132  cpl_table_set(slits, "xbottom", 0, -18.425);
15133  cpl_table_set(slits, "xtop", 0, -17.575);
15134  }
15135  else if (!strncmp(slit_name, "lSlit2_0arcsec", 14)) {
15136  cpl_table_set_int(slits, "slit_id", 0, 8);
15137  cpl_table_set(slits, "xbottom", 0, 23.475);
15138  cpl_table_set(slits, "xtop", 0, 24.525);
15139  }
15140  else if (!strncmp(slit_name, "lSlit2_5arcsec", 14)) {
15141  cpl_table_set_int(slits, "slit_id", 0, 9);
15142  cpl_table_set(slits, "xbottom", 0, -24.66);
15143  cpl_table_set(slits, "xtop", 0, -23.34);
15144  }
15145  else {
15146  cpl_msg_error(func, "Invalid slit %s in keyword ESO INS SLIT NAME",
15147  slit_name);
15148  cpl_error_set(func, CPL_ERROR_DATA_NOT_FOUND);
15149  cpl_table_delete(slits);
15150  return NULL;
15151  }
15152 
15153  return slits;
15154 }
15155 
15156 
15171 double mos_get_gain_vimos(cpl_propertylist *header)
15172 {
15173  const char *func = "mos_get_gain_vimos";
15174 
15175  double gain = -1.0;
15176 
15177 
15178  if (cpl_error_get_code() != CPL_ERROR_NONE)
15179  return gain;
15180 
15181  if (header == NULL) {
15182  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
15183  return gain;
15184  }
15185 
15186  gain = cpl_propertylist_get_double(header, "ESO DET OUT1 CONAD");
15187  if (cpl_error_get_code()) {
15188  cpl_error_set_where(func);
15189  gain = -1.0;
15190  }
15191 
15192  return gain;
15193 
15194 }
15195 
15196 
15216 cpl_table *mos_load_slits_vimos(cpl_propertylist *header)
15217 {
15218  const char *func = "mos_load_slits_vimos";
15219 
15220  cpl_table *slits;
15221  char keyname[MAX_COLNAME];
15222  float slit_x;
15223  float slit_y;
15224  float dim_x;
15225  float dim_y;
15226  int nslits;
15227  int slit_id;
15228  int curved;
15229  int i;
15230 
15231 
15232  if (cpl_error_get_code() != CPL_ERROR_NONE) {
15233  return NULL;
15234  }
15235 
15236  if (header == NULL) {
15237  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
15238  return NULL;
15239  }
15240 
15241  nslits = cpl_propertylist_get_int(header, "ESO INS SLIT NO");
15242 
15243  if (cpl_error_get_code() != CPL_ERROR_NONE) {
15244  cpl_error_set_where(func);
15245  return NULL;
15246  }
15247 
15248  slits = cpl_table_new(nslits);
15249  cpl_table_new_column(slits, "slit_id", CPL_TYPE_INT);
15250  cpl_table_new_column(slits, "xtop", CPL_TYPE_DOUBLE);
15251  cpl_table_new_column(slits, "ytop", CPL_TYPE_DOUBLE);
15252  cpl_table_new_column(slits, "xbottom", CPL_TYPE_DOUBLE);
15253  cpl_table_new_column(slits, "ybottom", CPL_TYPE_DOUBLE);
15254  cpl_table_new_column(slits, "xwidth", CPL_TYPE_DOUBLE);
15255  cpl_table_new_column(slits, "ywidth", CPL_TYPE_DOUBLE);
15256  cpl_table_new_column(slits, "curved", CPL_TYPE_INT);
15257  cpl_table_set_column_unit(slits, "xtop", "pixel");
15258  cpl_table_set_column_unit(slits, "ytop", "pixel");
15259  cpl_table_set_column_unit(slits, "xbottom", "pixel");
15260  cpl_table_set_column_unit(slits, "ybottom", "pixel");
15261  cpl_table_set_column_unit(slits, "xwidth", "mm");
15262  cpl_table_set_column_unit(slits, "ywidth", "mm");
15263 
15264  for (i = 0; i < nslits; i++) {
15265  sprintf(keyname, "ESO INS SLIT%d ID", i+1);
15266  slit_id = cpl_propertylist_get_int(header, keyname);
15267  if (cpl_error_get_code() != CPL_ERROR_NONE) {
15268  cpl_error_set_where(func);
15269  return NULL;
15270  }
15271  sprintf(keyname, "ESO INS SLIT%d X", i+1);
15272  slit_x = cpl_propertylist_get_double(header, keyname);
15273  if (cpl_error_get_code() != CPL_ERROR_NONE) {
15274  cpl_error_set_where(func);
15275  return NULL;
15276  }
15277  sprintf(keyname, "ESO INS SLIT%d Y", i+1);
15278  slit_y = cpl_propertylist_get_double(header, keyname);
15279  if (cpl_error_get_code() != CPL_ERROR_NONE) {
15280  cpl_error_set_where(func);
15281  return NULL;
15282  }
15283  sprintf(keyname, "ESO INS SLIT%d DIMX", i+1);
15284  dim_x = cpl_propertylist_get_double(header, keyname);
15285  if (cpl_error_get_code() != CPL_ERROR_NONE) {
15286  cpl_error_set_where(func);
15287  return NULL;
15288  }
15289 
15290  sprintf(keyname, "ESO INS SLIT%d BEZIER DY", i+1);
15291  if (cpl_propertylist_has(header, keyname)) {
15292  curved = 1;
15293  }
15294  else {
15295  sprintf(keyname, "ESO INS SLIT%d DIMY", i+1);
15296  curved = 0;
15297  }
15298  dim_y = cpl_propertylist_get_double(header, keyname);
15299  if (cpl_error_get_code() != CPL_ERROR_NONE) {
15300  cpl_error_set_where(func);
15301  return NULL;
15302  }
15303 
15304  cpl_table_set_int(slits, "slit_id", i, slit_id);
15305  cpl_table_set(slits, "xtop", i, slit_x - dim_x/2);
15306  cpl_table_set(slits, "ytop", i, slit_y);
15307  cpl_table_set(slits, "xbottom", i, slit_x + dim_x/2);
15308  cpl_table_set(slits, "ybottom", i, slit_y);
15309  cpl_table_set(slits, "xwidth", i, dim_x);
15310  cpl_table_set(slits, "ywidth", i, dim_y);
15311  cpl_table_set_int(slits, "curved", i, curved);
15312  }
15313 
15314  return slits;
15315 }
15316 
15317 
15327 int mos_check_multiplex(cpl_table *slits)
15328 {
15329  cpl_propertylist *sort;
15330  int nrow;
15331  int i, multiplex, xprev, xcur;
15332  double prev, cur;
15333  double tolerance = 1.0; // About spatially aligned slits (mm)
15334 
15335 
15336  /*
15337  * Create an auxiliary column containing a sort of integer
15338  * x coordinate of the slit, to guarantee that slits at the
15339  * same spatial offset are recognised immediately as in spectral
15340  * multiplexing.
15341  */
15342 
15343  sort = cpl_propertylist_new();
15344  cpl_propertylist_append_bool(sort, "xtop", 0);
15345  cpl_table_sort(slits, sort);
15346  cpl_propertylist_delete(sort);
15347 
15348  prev = cpl_table_get_double(slits, "xtop", 0, NULL);
15349  cpl_table_new_column(slits, "xind", CPL_TYPE_INT);
15350  cpl_table_set_int(slits, "xind", 0, prev); // cast to int is intentional
15351  nrow = cpl_table_get_nrow(slits);
15352  for (i = 1; i < nrow; i++) {
15353  cur = cpl_table_get_double(slits, "xtop", i, NULL);
15354  if (fabs(prev - cur) > tolerance)
15355  prev = cur;
15356  cpl_table_set_int(slits, "xind", i, prev);
15357  }
15358 
15359  /*
15360  * Now sort according to increasing (integer) x positions, and when
15361  * those are equal (multiplexed) according to the increasing y position.
15362  */
15363 
15364  sort = cpl_propertylist_new();
15365  cpl_propertylist_append_bool(sort, "xind", 0);
15366  cpl_propertylist_append_bool(sort, "ytop", 0);
15367  cpl_table_sort(slits, sort);
15368  cpl_propertylist_delete(sort);
15369 
15370  /*
15371  * Now assign to each slit its multiplex order.
15372  */
15373 
15374  multiplex = 0;
15375  cpl_table_new_column(slits, "multiplex", CPL_TYPE_INT);
15376  xprev = cpl_table_get_int(slits, "xind", 0, NULL);
15377  cpl_table_set_int(slits, "multiplex", 0, multiplex);
15378  nrow = cpl_table_get_nrow(slits);
15379  for (i = 1; i < nrow; i++) {
15380  xcur = cpl_table_get_int(slits, "xind", i, NULL);
15381  if (xcur == xprev) {
15382  multiplex++;
15383  }
15384  else {
15385  xprev = xcur;
15386  multiplex = 0;
15387  }
15388  cpl_table_set_int(slits, "multiplex", i, multiplex);
15389  }
15390 
15391  cpl_table_save(slits, NULL, NULL, "multiplex.fits", CPL_IO_DEFAULT);
15392 
15393  cpl_table_erase_column(slits, "xind");
15394 
15395  return 1 + cpl_table_get_column_max(slits, "multiplex");
15396 
15397 }
15398 
15399 
15426 cpl_table *mos_load_overscans_vimos(const cpl_propertylist *header,
15427  int check_consistency)
15428 {
15429  const char *func = "mos_load_overscans_vimos";
15430 
15431  int nx = 0;
15432  int ny = 0;
15433  int px = 0;
15434  int py = 0;
15435  int ox = 0;
15436  int oy = 0;
15437  int vx = 0;
15438  int vy = 0;
15439  int nrows;
15440  cpl_table *overscans;
15441 
15442 
15443  if (cpl_error_get_code() != CPL_ERROR_NONE) {
15444  cpl_msg_error(func, "Reset your error: %s", cpl_error_get_message());
15445  return NULL;
15446  }
15447 
15448  if (header == NULL) {
15449  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
15450  return NULL;
15451  }
15452 
15453  if (cpl_propertylist_has(header, "NAXIS1"))
15454  nx = cpl_propertylist_get_int(header, "NAXIS1");
15455  if (cpl_propertylist_has(header, "NAXIS2"))
15456  ny = cpl_propertylist_get_int(header, "NAXIS2");
15457  if (cpl_propertylist_has(header, "ESO DET OUT1 PRSCX"))
15458  px = cpl_propertylist_get_int(header, "ESO DET OUT1 PRSCX");
15459  if (cpl_propertylist_has(header, "ESO DET OUT1 PRSCY"))
15460  py = cpl_propertylist_get_int(header, "ESO DET OUT1 PRSCY");
15461  if (cpl_propertylist_has(header, "ESO DET OUT1 OVSCX"))
15462  ox = cpl_propertylist_get_int(header, "ESO DET OUT1 OVSCX");
15463  if (cpl_propertylist_has(header, "ESO DET OUT1 OVSCY"))
15464  oy = cpl_propertylist_get_int(header, "ESO DET OUT1 OVSCY");
15465  if (cpl_propertylist_has(header, "ESO DET OUT1 NX"))
15466  vx = cpl_propertylist_get_int(header, "ESO DET OUT1 NX");
15467  if (cpl_propertylist_has(header, "ESO DET OUT1 NY"))
15468  vy = cpl_propertylist_get_int(header, "ESO DET OUT1 NY");
15469 
15470  if (cpl_error_get_code() != CPL_ERROR_NONE) {
15471  cpl_msg_error(func, "Missing overscan keywords in header");
15472  cpl_error_set_where(func);
15473  return NULL;
15474  }
15475 
15476  if (px < 0 || py < 0 || ox < 0 || oy < 0) {
15477  cpl_msg_error(func, "Missing overscan keywords in header");
15478  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
15479  return NULL;
15480  }
15481 
15482  if ((px + vx + ox != nx) || (py + vy + oy != ny)) {
15483  if (check_consistency) {
15484  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
15485  return NULL;
15486  }
15487  else {
15488  cpl_msg_debug(func, "Overscans description conflicts with "
15489  "reported image sizes, "
15490  "%d + %d + %d != %d or "
15491  "%d + %d + %d != %d",
15492  px, vx, ox, nx,
15493  py, vy, oy, ny);
15494  }
15495  }
15496 
15497  nrows = 0;
15498  if (px > 0)
15499  nrows++;
15500  if (ox > 0)
15501  nrows++;
15502  if (py > 0)
15503  nrows++;
15504  if (oy > 0)
15505  nrows++;
15506 
15507  if (nrows > 2) {
15508  cpl_msg_error(func, "Unexpected overscan regions "
15509  "(both in X and Y direction)");
15510  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
15511  return NULL;
15512  }
15513 
15514 
15515  /*
15516  * A row is added for the description of the valid region of the
15517  * exposure the input header belongs to.
15518  */
15519 
15520  nrows++;
15521 
15522  overscans = cpl_table_new(nrows);
15523  cpl_table_new_column(overscans, "xlow", CPL_TYPE_INT);
15524  cpl_table_new_column(overscans, "ylow", CPL_TYPE_INT);
15525  cpl_table_new_column(overscans, "xhig", CPL_TYPE_INT);
15526  cpl_table_new_column(overscans, "yhig", CPL_TYPE_INT);
15527 
15528  nrows = 0;
15529 
15530  cpl_table_set_int(overscans, "xlow", nrows, px);
15531  cpl_table_set_int(overscans, "ylow", nrows, py);
15532  cpl_table_set_int(overscans, "xhig", nrows, nx - ox);
15533  cpl_table_set_int(overscans, "yhig", nrows, ny - oy);
15534  nrows++;
15535 
15536  if (px > 0) {
15537  cpl_table_set_int(overscans, "xlow", nrows, 0);
15538  cpl_table_set_int(overscans, "ylow", nrows, 0);
15539  cpl_table_set_int(overscans, "xhig", nrows, px);
15540  cpl_table_set_int(overscans, "yhig", nrows, ny);
15541  nrows++;
15542  }
15543 
15544  if (ox > 0) {
15545  cpl_table_set_int(overscans, "xlow", nrows, nx - ox);
15546  cpl_table_set_int(overscans, "ylow", nrows, 0);
15547  cpl_table_set_int(overscans, "xhig", nrows, nx);
15548  cpl_table_set_int(overscans, "yhig", nrows, ny);
15549  nrows++;
15550  }
15551 
15552  if (py > 0) {
15553  cpl_table_set_int(overscans, "xlow", nrows, 0);
15554  cpl_table_set_int(overscans, "ylow", nrows, 0);
15555  cpl_table_set_int(overscans, "xhig", nrows, nx);
15556  cpl_table_set_int(overscans, "yhig", nrows, py);
15557  nrows++;
15558  }
15559 
15560  if (oy > 0) {
15561  cpl_table_set_int(overscans, "xlow", nrows, 0);
15562  cpl_table_set_int(overscans, "ylow", nrows, ny - oy);
15563  cpl_table_set_int(overscans, "xhig", nrows, nx);
15564  cpl_table_set_int(overscans, "yhig", nrows, ny);
15565  nrows++;
15566  }
15567 
15568  return overscans;
15569 
15570 }
15571 
15572 
15573 cpl_table *mos_load_overscans_fors(const cpl_propertylist *header)
15574 {
15575  const char *func = "mos_load_overscans_fors";
15576 
15577  int nports;
15578  int nx = 0;
15579  int ny = 0;
15580  int px = 0;
15581  int py = 0;
15582  int ox = 0;
15583  int oy = 0;
15584  int rebin;
15585  int nrows;
15586  cpl_table *overscans;
15587 
15588 
15589  if (cpl_error_get_code() != CPL_ERROR_NONE) {
15590  cpl_msg_error(func, "Reset your error: %s", cpl_error_get_message());
15591  return NULL;
15592  }
15593 
15594  if (header == NULL) {
15595  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
15596  return NULL;
15597  }
15598 
15599  if (cpl_propertylist_has(header, "ESO DET OUTPUTS"))
15600  nports = cpl_propertylist_get_int(header, "ESO DET OUTPUTS");
15601 
15602  if (nports == 4 &&
15603  cpl_propertylist_has(header, "ESO DET OUT1 PRSCX") &&
15604  cpl_propertylist_has(header, "ESO DET WIN1 BINX")) {
15605 
15606  rebin = cpl_propertylist_get_int(header, "ESO DET WIN1 BINX");
15607 
15608  overscans = cpl_table_new(3);
15609  cpl_table_new_column(overscans, "xlow", CPL_TYPE_INT);
15610  cpl_table_new_column(overscans, "ylow", CPL_TYPE_INT);
15611  cpl_table_new_column(overscans, "xhig", CPL_TYPE_INT);
15612  cpl_table_new_column(overscans, "yhig", CPL_TYPE_INT);
15613 
15614  px = 16 / rebin;
15615  ox = 16 / rebin;
15616  nx = 2080 / rebin;
15617  ny = 2048 / rebin;
15618  nrows = 0;
15619 
15620  cpl_table_set_int(overscans, "xlow", nrows, px);
15621  cpl_table_set_int(overscans, "ylow", nrows, py);
15622  cpl_table_set_int(overscans, "xhig", nrows, nx - ox);
15623  cpl_table_set_int(overscans, "yhig", nrows, ny - oy);
15624  nrows++;
15625 
15626  cpl_table_set_int(overscans, "xlow", nrows, 0);
15627  cpl_table_set_int(overscans, "ylow", nrows, 0);
15628  cpl_table_set_int(overscans, "xhig", nrows, px);
15629  cpl_table_set_int(overscans, "yhig", nrows, ny);
15630  nrows++;
15631 
15632  cpl_table_set_int(overscans, "xlow", nrows, nx - ox);
15633  cpl_table_set_int(overscans, "ylow", nrows, 0);
15634  cpl_table_set_int(overscans, "xhig", nrows, nx);
15635  cpl_table_set_int(overscans, "yhig", nrows, ny);
15636  nrows++;
15637  }
15638  else {
15639  overscans = mos_load_overscans_vimos(header, 0);
15640  }
15641 
15642  return overscans;
15643 
15644 }
15645 
15677 #define READY 1
15678 #ifdef READY
15679 
15680 cpl_polynomial *mos_montecarlo_polyfit(cpl_table *points, cpl_table *evaluate,
15681  int samples, int order)
15682 {
15683 
15684  const char *func = "mos_montecarlo_polyfit";
15685 
15686  cpl_polynomial *p;
15687  cpl_polynomial *q;
15688  cpl_vector *listx;
15689  cpl_vector *listy;
15690  double err;
15691  double *x;
15692  double *px;
15693  double *x_eval;
15694  double *px_eval;
15695  double *sigma;
15696  double *vy;
15697  double *dy;
15698  int npoints, nevaluate;
15699  int i, j;
15700 
15701 
15702  if (points == NULL || evaluate == NULL) {
15703  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
15704  return NULL;
15705  }
15706 
15707  if (!cpl_table_has_column(points, "x")) {
15708  cpl_error_set(func, CPL_ERROR_DATA_NOT_FOUND);
15709  return NULL;
15710  }
15711 
15712  if (cpl_table_get_column_type(points, "x") != CPL_TYPE_DOUBLE) {
15713  cpl_error_set(func, CPL_ERROR_INVALID_TYPE);
15714  return NULL;
15715  }
15716 
15717  if (cpl_table_has_invalid(points, "x")) {
15718  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
15719  return NULL;
15720  }
15721 
15722  if (!cpl_table_has_column(points, "y")) {
15723  cpl_error_set(func, CPL_ERROR_DATA_NOT_FOUND);
15724  return NULL;
15725  }
15726 
15727  if (cpl_table_get_column_type(points, "y") != CPL_TYPE_DOUBLE) {
15728  cpl_error_set(func, CPL_ERROR_INVALID_TYPE);
15729  return NULL;
15730  }
15731 
15732  if (cpl_table_has_invalid(points, "y")) {
15733  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
15734  return NULL;
15735  }
15736 
15737  if (cpl_table_has_column(points, "y_err")) {
15738 
15739  if (cpl_table_get_column_type(points, "y_err") != CPL_TYPE_DOUBLE) {
15740  cpl_error_set(func, CPL_ERROR_INVALID_TYPE);
15741  return NULL;
15742  }
15743 
15744  if (cpl_table_has_invalid(points, "y_err")) {
15745  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
15746  return NULL;
15747  }
15748  }
15749 
15750  if (!cpl_table_has_column(evaluate, "x")) {
15751  cpl_error_set(func, CPL_ERROR_DATA_NOT_FOUND);
15752  return NULL;
15753  }
15754 
15755  if (cpl_table_get_column_type(evaluate, "x") != CPL_TYPE_DOUBLE) {
15756  cpl_error_set(func, CPL_ERROR_INVALID_TYPE);
15757  return NULL;
15758  }
15759 
15760  if (cpl_table_has_invalid(evaluate, "x")) {
15761  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
15762  return NULL;
15763  }
15764 
15765  if (samples < 2 || order < 0) {
15766  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
15767  return NULL;
15768  }
15769 
15770  npoints = cpl_table_get_nrow(points);
15771  listx = cpl_vector_wrap(npoints, cpl_table_get_data_double(points, "x"));
15772  listy = cpl_vector_wrap(npoints, cpl_table_get_data_double(points, "y"));
15773 
15774  p = cpl_polynomial_fit_1d_create(listx, listy, order, &err);
15775 
15776  if (!cpl_table_has_column(points, "y_err")) {
15777  err = sqrt(err);
15778  cpl_table_new_column(points, "y_err", CPL_TYPE_DOUBLE);
15779  cpl_table_fill_column_window_double(points, "y_err", 0, npoints, err);
15780  cpl_msg_info(func, "Error column not found - set to %f\n", err);
15781  }
15782 
15783  /*
15784  * Create columns containing modeled values at each x
15785  */
15786 
15787  if (cpl_table_has_column(points, "px"))
15788  cpl_table_erase_column(points, "px");
15789  cpl_table_new_column(points, "px", CPL_TYPE_DOUBLE);
15790  cpl_table_fill_column_window_double(points, "px", 0, npoints, 0);
15791  x = cpl_table_get_data_double(points, "x");
15792  px = cpl_table_get_data_double(points, "px");
15793  for (i = 0; i < npoints; i++)
15794  px[i] = cpl_polynomial_eval_1d(p, x[i], NULL);
15795 
15796  nevaluate = cpl_table_get_nrow(evaluate);
15797 
15798  if (cpl_table_has_column(evaluate, "px"))
15799  cpl_table_erase_column(evaluate, "px");
15800  cpl_table_new_column(evaluate, "px", CPL_TYPE_DOUBLE);
15801  cpl_table_fill_column_window_double(evaluate, "px", 0, nevaluate, 0);
15802  x_eval = cpl_table_get_data_double(evaluate, "x");
15803  px_eval = cpl_table_get_data_double(evaluate, "px");
15804  for (i = 0; i < nevaluate; i++)
15805  px_eval[i] = cpl_polynomial_eval_1d(p, x_eval[i], NULL);
15806 
15807  /*
15808  * Initialise column with sigma
15809  */
15810 
15811  if (cpl_table_has_column(evaluate, "sigma"))
15812  cpl_table_erase_column(evaluate, "sigma");
15813  cpl_table_new_column(evaluate, "sigma", CPL_TYPE_DOUBLE);
15814  cpl_table_fill_column_window_double(evaluate, "sigma", 0, nevaluate, 0);
15815  sigma = cpl_table_get_data_double(evaluate, "sigma");
15816 
15817  /*
15818  * Compute varied y cordinates to fit
15819  */
15820 
15821  if (cpl_table_has_column(points, "vy"))
15822  cpl_table_erase_column(points, "vy");
15823  cpl_table_new_column(points, "vy", CPL_TYPE_DOUBLE);
15824  cpl_table_fill_column_window_double(points, "vy", 0, npoints, 0);
15825  vy = cpl_table_get_data_double(points, "vy");
15826  dy = cpl_table_get_data_double(points, "y_err");
15827  cpl_vector_unwrap(listy);
15828  listy = cpl_vector_wrap(npoints, vy);
15829 
15830  for (i = 0; i < samples; i++) {
15831  for (j = 0; j < npoints; j++)
15832  vy[j] = px[j] + dy[j] * mos_randg(1);
15833  q = cpl_polynomial_fit_1d_create(listx, listy, order, NULL);
15834  for (j = 0; j < nevaluate; j++)
15835  sigma[j] += fabs(px_eval[j]
15836  - cpl_polynomial_eval_1d(q, x_eval[j], NULL));
15837  cpl_polynomial_delete(q);
15838  }
15839 
15840  /*
15841  * Factor 1.25 to convert average deviation to sigma
15842  */
15843 
15844  cpl_table_multiply_scalar(evaluate, "sigma", 1.25);
15845  cpl_table_divide_scalar(evaluate, "sigma", samples);
15846 
15847  cpl_vector_unwrap(listx);
15848  cpl_vector_unwrap(listy);
15849 
15850  return p;
15851 }
15852 
15853 #endif
15854 
15877 cpl_error_code mos_randomise_image(cpl_image *image, double ron,
15878  double gain, double bias)
15879 {
15880  float *data;
15881  int npix, i;
15882 
15883 
15884  if (image == NULL)
15885  return cpl_error_set(cpl_func, CPL_ERROR_NULL_INPUT);
15886 
15887  if (ron < 0.0 || gain <= FLT_EPSILON)
15888  return cpl_error_set(cpl_func, CPL_ERROR_ILLEGAL_INPUT);
15889 
15890  data = cpl_image_get_data_float(image);
15891  npix = cpl_image_get_size_x(image) * cpl_image_get_size_y(image);
15892  ron *= ron;
15893 
15894  for (i = 0; i < npix; i++) {
15895  if (data[i] < bias) {
15896  data[i] += sqrt(ron) * mos_randg(1);
15897  }
15898  else {
15899  data[i] += sqrt(ron + (data[i] - bias) / gain) * mos_randg(1);
15900  }
15901  }
15902 
15903  return CPL_ERROR_NONE;
15904 }
15905 
15906 
15921 cpl_error_code mos_refmask_find_gaps(cpl_mask *refmask,
15922  cpl_image *master_flat,
15923  double level)
15924 {
15925  int nx = cpl_mask_get_size_x(refmask);
15926  int ny = cpl_mask_get_size_y(refmask);
15927 
15928  int * xpos = cpl_calloc(sizeof(int), ny);
15929 
15930  cpl_image * filtered = cpl_image_duplicate(master_flat);
15931  cpl_mask * kernel = cpl_mask_new(9, 3);
15932  cpl_vector * v = cpl_vector_new(ny);
15933  cpl_vector * truev;
15934  int nvalid = 0;
15935  double * flats = cpl_vector_get_data(v);
15936 
15937  double median, stdev, delta;
15938 
15939  int i, kill;
15940 
15941  cpl_mask_not(kernel);
15942  cpl_image_filter_mask(filtered, master_flat, kernel,
15943  CPL_FILTER_MEDIAN, CPL_BORDER_COPY);
15944  cpl_mask_delete(kernel);
15945 
15946  for (i = 1; i <= ny; i++) {
15947  int j = 0;
15948 
15949  do j++;
15950  while (!cpl_mask_get(refmask, j, i) && j < nx);
15951 
15952  if (j < nx) {
15953  int rejected;
15954 
15955  xpos[i - 1] = j;
15956  flats[nvalid] = cpl_image_get(filtered, j, i, &rejected);
15957  nvalid++;
15958  }
15959  else {
15960  xpos[i - 1] = -1;
15961  }
15962  }
15963 
15964  if (nvalid == 0)
15965  return cpl_error_set(cpl_func, CPL_ERROR_DATA_NOT_FOUND);
15966 
15967  truev = cpl_vector_wrap(nvalid, flats);
15968 
15969  median = cpl_vector_get_median(truev);
15970 
15971  if (level < 0.0)
15972  stdev = cpl_vector_get_stdev(truev);
15973 
15974  cpl_vector_unwrap(truev);
15975  cpl_vector_delete(v);
15976 
15977  for (i = 1; i <= ny; i++) {
15978  if (xpos[i - 1] > 0) {
15979  int rejected;
15980  double kappa = 1.5;
15981 
15982  delta = cpl_image_get(filtered, xpos[i - 1], i, &rejected) - median;
15983 
15984  if (level < 0.0)
15985  kill = fabs(delta) > stdev * kappa;
15986  else
15987  kill = delta < level;
15988 
15989  if (kill) {
15990  int j = 0;
15991 
15992  while (cpl_mask_get(refmask, xpos[i - 1] + j, i)) {
15993  cpl_mask_set(refmask, xpos[i - 1] + j, i, CPL_BINARY_0);
15994  j++;
15995  }
15996  }
15997  }
15998  }
15999 
16000  cpl_image_delete(filtered);
16001  cpl_free(xpos);
16002 
16003  return cpl_error_get_code();
16004 }
16005 
16013 cpl_error_code mos_saturation_process(cpl_image * image)
16014 {
16015  int nx = cpl_image_get_size_x(image);
16016  int ny = cpl_image_get_size_y(image);
16017  int npix = nx * ny;
16018  float * sdata = cpl_image_get_data_float(image);
16019 
16020  int count, i, j, k;
16021 
16022  /*
16023  * This is used to avoid saturation level coded with pixel value zero
16024  * To make it more robust against random 0.0 values, check that also
16025  * next pixel along the spatial direction is 0.0.
16026  */
16027 
16028  for (i = 0; i < npix - nx; i++)
16029  if (sdata[i] == 0.0 && sdata[i + nx] == 0.0)
16030  sdata[i] = 65535.0;
16031 
16032  for (i = npix - nx; i < npix; i++)
16033  if (sdata[i] == 0.0)
16034  sdata[i] = 65535.0;
16035 
16036  /*
16037  * This is a dirty trick to overcome saturations (making up a false
16038  * tip on their flat tops). This should be useless with a better
16039  * peak detection algorithm.
16040  */
16041 
16042  for (i = 0; i < npix; i++) {
16043  if (sdata[i] >= 65535.0) {
16044  count = 0;
16045  for (j = i; j < npix; j++) {
16046  if (sdata[j] < 65535.0) {
16047  break;
16048  }
16049  else {
16050  count++;
16051  }
16052  }
16053  if (count < 30 && count > 2) {
16054  for (j = i; j < i + count/2; j++)
16055  sdata[j] = sdata[i] + 1000.0 * (j - i);
16056  if (count % 2 != 0) {
16057  sdata[j] = sdata[j-1] + 1000.0;
16058  j++;
16059  }
16060  for (k = j; k <= i + count; k++)
16061  sdata[k] = sdata[i] - 1000.0 * (k - i - count);
16062  i = k;
16063  }
16064  }
16065  }
16066 
16067  return cpl_error_get_code();
16068 }
16069 
16070 
16079 cpl_error_code mos_subtract_background(cpl_image * image)
16080 {
16081  /*
16082  * Create and subtract background
16083  */
16084 
16085  cpl_image * bimage = mos_arc_background(image, 15, 15);
16086  cpl_image_subtract(image, bimage);
16087  cpl_image_delete(bimage);
16088 
16089  return cpl_error_get_code();
16090 }
16091 
16092 
16109 cpl_error_code mos_object_intersect(cpl_table **slitss, cpl_table *origslits,
16110  int nscience, float tolerance)
16111 {
16112  int i, j;
16113 
16114  cpl_table *summary;
16115  int summary_nobjs = 0;
16116 
16117  int nobjs;
16118 
16119  int nmatches;
16120  int nslits = cpl_table_get_nrow(slitss[0]);
16121 
16122  int maxobjs;
16123  int k, m;
16124  int nstokes, sstokes;
16125 
16126  cpl_table **work;
16127 
16128  work = (cpl_table **)cpl_malloc(sizeof(cpl_table *) * nscience);
16129 
16130 
16131  /*
16132  * First we build a table listing the offset of each detected
16133  * object at each angle and each beam, from the bottom of each
16134  * slit spectrum, and the pair that slit spectrum belongs to.
16135  * This summary table will have as many rows as objects found
16136  * in total at all angles.
16137  */
16138 
16139  for (j = 0; j < nscience; j++) {
16140  int c_nobjs = mos_get_nobjects(slitss[j]);
16141  if (!c_nobjs)
16142  return cpl_error_set(cpl_func, CPL_ERROR_DATA_NOT_FOUND);
16143  summary_nobjs += c_nobjs;
16144  }
16145 
16146  summary = cpl_table_new(summary_nobjs);
16147 
16148  cpl_table_new_column(summary, "offset", CPL_TYPE_DOUBLE);
16149  cpl_table_new_column(summary, "pair", CPL_TYPE_INT);
16150  cpl_table_new_column(summary, "absolute", CPL_TYPE_DOUBLE);
16151  cpl_table_new_column(summary, "pos", CPL_TYPE_DOUBLE);
16152 
16153  /*
16154  * Fill the summary table with data from all objects:
16155  */
16156 
16157  nobjs = 0;
16158 
16159  /* Loop on all object tables (one for each angle) */
16160  for (j = 0; j < nscience; j++) {
16161  int c_maxobjs = mos_get_maxobjs_per_slit(slitss[j]);
16162 
16163  /* Loop on all slits found on first - i.e., ALL - object table */
16164  for (k = 0; k < nslits; k++) {
16165 
16166  /* Loop on all objects found on each object table */
16167  for (m = 0; m < c_maxobjs; m++) {
16168  int null;
16169  char *name = cpl_sprintf("object_%d", m + 1);
16170  double obj = cpl_table_get_double(slitss[j], name, k, &null);
16171  int pos;
16172  int pair;
16173 
16174  cpl_free(name);
16175 
16176  if (null)
16177  break; /* No object #m+1 in this slit - go to next slit */
16178 
16179  /*
16180  * Copy necessary object data to summary table. Note
16181  * that the absolute object position (row) in the
16182  * rectified image is made relative to the bottom
16183  * position (row) of the current slit.
16184  */
16185 
16186  pos = cpl_table_get_int(slitss[j], "position", k, &null);
16187  pair = cpl_table_get_int(slitss[j], "pair_id", k, &null);
16188  cpl_table_set(summary, "absolute", nobjs, obj);
16189  cpl_table_set(summary, "pos", nobjs, pos);
16190  cpl_table_set(summary, "offset", nobjs, obj - pos);
16191  cpl_table_set(summary, "pair", nobjs, pair);
16192 
16193  nobjs++;
16194  }
16195  }
16196  }
16197 
16198 // cpl_table_save(summary, NULL, NULL, "susu.fits", CPL_IO_DEFAULT);
16199 
16200  /*
16201  * Perform the intersection: what are the objects belonging
16202  * to the same slit (same pair ordinary + extraordinary) which
16203  * are observed at the same offset at all angles? Those are
16204  * the polarimetric objects.
16205  */
16206 
16207  nmatches = 0;
16208  maxobjs = mos_get_maxobjs_per_slit(slitss[0]);
16209 
16210  /*
16211  * We loop on the objects of the first-angle object table as
16212  * reference, and check whether those objects are present also
16213  * at *all* other angles. Note that the loop advances by pairs.
16214  * If the top (k = 0) slit spectrum is not an ordinary beam,
16215  * it is ignored. The loop advances by pairs, starting at the
16216  * first complete pair. It is implicitely assumed that the
16217  * slit spectrum on top is always from the ordinary beam, and
16218  * the spectrum below (k+1) its extraordinary match.
16219  */
16220 
16221  for (k = 0; k < nslits; k+=2) {
16222  int slitmatches = 0;
16223 
16224  if (k + 1 < nslits ) {
16225  if (cpl_table_get_int(slitss[0], "pair_id", k, NULL) !=
16226  cpl_table_get_int(slitss[0], "pair_id", k + 1, NULL)) {
16227 
16228  /*
16229  * This is not an ordinary beam - advance to next slit.
16230  */
16231 
16232  /* It will be incremented by two, so the effect is like k++ */
16233  k--;
16234 
16235  continue;
16236  }
16237  }
16238 
16239  for (m = 0; m < maxobjs; m++) {
16240  int null;
16241  char *name = cpl_sprintf("object_%d", m + 1);
16242  double obj = cpl_table_get_double(slitss[0], name, k, &null);
16243  double pos;
16244  int pair;
16245 
16246  char *name_obj = NULL;
16247  char *name_start = NULL;
16248  char *name_end = NULL;
16249  char *name_row = NULL;
16250  char *name_row_s = NULL;
16251 
16252  char *name_start_o = NULL;
16253  char *name_end_o = NULL;
16254  char *name_row_o = NULL;
16255  char *name_start_v = NULL;
16256  char *name_end_v = NULL;
16257  char *name_obj_v = NULL;
16258 
16259  int start, end;
16260  int length;
16261 
16262  int selected;
16263  int v, start_v, end_v;
16264  double min_v, obj_v;
16265 
16266 
16267  cpl_free(name);
16268 
16269  if (null)
16270  break;
16271 
16272  /*
16273  * Each object of the first object table belongs to a
16274  * slit spectrum (k). This slit spectrum has a position
16275  * in the rectified image, and it belongs to a given
16276  * ordinary + extraordinary pair.
16277  */
16278 
16279  pos = cpl_table_get_int(slitss[0], "position", k, &null);
16280  pair = cpl_table_get_int(slitss[0], "pair_id", k, &null);
16281 
16282  /*
16283  * Now from the summary table we can select all objects
16284  * which have the same offset (obj - pos) within all slit
16285  * spectra belonging to the same ordinary + extraordinary
16286  * pair (at all angles).
16287  */
16288 
16289  cpl_table_select_all(summary); /* Reset selection */
16290 
16291  cpl_table_and_selected_int(summary, "pair", CPL_EQUAL_TO, pair);
16292  cpl_table_and_selected_double(summary, "offset", CPL_LESS_THAN,
16293  obj - pos + tolerance);
16294  selected =
16295  cpl_table_and_selected_double(summary, "offset", CPL_GREATER_THAN,
16296  obj - pos - tolerance);
16297 
16298 
16299  /*
16300  * If this object were observed at all angles (nscience) and
16301  * at all beams (2), we should have selected exactly 2*nscience
16302  * objects. If not, this is not a polarimetric object, and it
16303  * is discarded from the intersection.
16304  */
16305 
16306  if (selected != nscience * 2)
16307  continue;
16308 
16309  /*
16310  * If we reach this point we have found one valid polarimetric
16311  * object, that must be inserted in the intersection object
16312  * table.
16313  */
16314 
16315  slitmatches++;
16316 
16317  /*
16318  * Names of the columns of the output table where the
16319  * object information needs to be copied. Note that a
16320  * new column is created, the "row_stokes_#", where the
16321  * row number of the extracted polarimetric signal is
16322  * also computed. For the moment this column will be
16323  * left empty - it will be filled only when all matches
16324  * are collected.
16325  */
16326 
16327  name_obj = cpl_sprintf("object_%d", slitmatches);
16328  name_start = cpl_sprintf("start_%d", slitmatches);
16329  name_end = cpl_sprintf("end_%d", slitmatches);
16330  name_row = cpl_sprintf("row_%d", slitmatches);
16331  name_row_s = cpl_sprintf("row_stokes_%d", slitmatches);
16332 
16333  /*
16334  * Names of the columns of the input table where the
16335  * object information is available.
16336  */
16337 
16338  name_start_o = cpl_sprintf("start_%d", m + 1);
16339  name_end_o = cpl_sprintf("end_%d", m + 1);
16340  name_row_o = cpl_sprintf("row_%d", m + 1);
16341 
16342  /*
16343  * If the output columns do not exist yet, create them.
16344  */
16345 
16346  if (!cpl_table_has_column(origslits, name_obj)) {
16347  cpl_table_new_column(origslits, name_obj, CPL_TYPE_DOUBLE);
16348  cpl_table_new_column(origslits, name_start, CPL_TYPE_INT);
16349  cpl_table_new_column(origslits, name_end, CPL_TYPE_INT);
16350  cpl_table_new_column(origslits, name_row, CPL_TYPE_INT);
16351  cpl_table_new_column(origslits, name_row_s, CPL_TYPE_INT);
16352  }
16353 
16354  /*
16355  * The current slit spectrum is k. The slit spectrum immediately
16356  * below (in the rectified image) is k+1. We need the length of
16357  * the spectrum below for computing the _absolute_ coordinates
16358  * of the objects in the rectified image in both beams.
16359  */
16360 
16361  length = cpl_table_get_int(origslits, "length", k + 1, &null);
16362 
16363  /* NEW:
16364  * Names of the columns of the input table where
16365  * the information of the corresponding object of
16366  * the next beam is available.
16367  */
16368 
16369  for (v = 0; v < maxobjs; v++) {
16370  char *name_v = cpl_sprintf("object_%d", v + 1);
16371  double obj_v = cpl_table_get_double(slitss[0], name_v,
16372  k + 1, &null);
16373 
16374  cpl_free(name_v);
16375 
16376  if (null)
16377  break;
16378 
16379  if (v) {
16380  if (fabs(obj - length - obj_v) < min_v) {
16381  min_v = fabs(obj - length - obj_v);
16382  cpl_free(name_start_v);
16383  cpl_free(name_end_v);
16384  cpl_free(name_obj_v);
16385  name_start_v = cpl_sprintf("start_%d", v + 1);
16386  name_end_v = cpl_sprintf("end_%d", v + 1);
16387  name_obj_v = cpl_sprintf("object_%d", v + 1);
16388  }
16389  }
16390  else {
16391  min_v = fabs(obj - length - obj_v);
16392  name_start_v = cpl_sprintf("start_%d", v + 1);
16393  name_end_v = cpl_sprintf("end_%d", v + 1);
16394  name_obj_v = cpl_sprintf("object_%d", v + 1);
16395  }
16396  }
16397 
16398  /*
16399  * Read from the first input object table (first angle)
16400  * the spatial window enclosing the object.
16401  */
16402 
16403  start = cpl_table_get_int(slitss[0], name_start_o, k, &null);
16404  end = cpl_table_get_int(slitss[0], name_end_o, k, &null);
16405 
16406  /* NEW:
16407  * Spatial window of the matching object in the next beam.
16408  */
16409 
16410  start_v = cpl_table_get_int(slitss[0], name_start_v, k + 1, &null);
16411  end_v = cpl_table_get_int(slitss[0], name_end_v, k + 1, &null);
16412  obj_v = cpl_table_get_double(slitss[0], name_obj_v, k + 1, &null);
16413 
16414  /*
16415  * Write the object coordinates in the same slit, and in the
16416  * slit below. Note that here we assume that all slits were
16417  * traced perfectly, and we compute the theoretical coords
16418  * (obj - length) within the next slit spectrum (k + 1). In
16419  * principle we should read them as well from the input
16420  * table!
16421  */
16422 
16423  cpl_table_set_double(origslits, name_obj, k, obj);
16424  cpl_table_set_double(origslits, name_obj, k + 1, obj_v);
16425  // cpl_table_set_double(origslits, name_obj, k + 1, obj - length);
16426 
16427  cpl_table_set_int(origslits, name_start, k, start);
16428  cpl_table_set_int(origslits, name_start, k + 1, start_v);
16429  // cpl_table_set_int(origslits, name_start, k + 1, start - length);
16430 
16431  cpl_table_set_int(origslits, name_end, k, end);
16432  cpl_table_set_int(origslits, name_end, k + 1, end_v);
16433  // cpl_table_set_int(origslits, name_end, k + 1, end - length);
16434 
16435  /*
16436  * "nmatches" is counting at what "reduced" image row the
16437  * extracted spectra are. Note that this is s preliminary
16438  * numbering - which is wrong: other objects may be found
16439  * in the same slit, and then the indeces would not be in
16440  * sequence. What is important is that at the end of this
16441  * loop "nmatches" would be the total number of matching
16442  * objects. The two cpl_table_set_int() calls made here
16443  * cannot be removed - they "validate" those table elements
16444  * (see ahead).
16445  */
16446 
16447  cpl_table_set_int(origslits, name_row, k, nmatches);
16448  nmatches++;
16449  cpl_table_set_int(origslits, name_row, k + 1, nmatches);
16450  nmatches++;
16451 
16452  cpl_free(name_obj);
16453  cpl_free(name_start);
16454  cpl_free(name_end);
16455  cpl_free(name_row);
16456  cpl_free(name_row_s);
16457 
16458  cpl_free(name_start_o);
16459  cpl_free(name_end_o);
16460  cpl_free(name_row_o);
16461 
16462  cpl_free(name_start_v); name_start_v = NULL;
16463  cpl_free(name_end_v); name_end_v = NULL;
16464  cpl_free(name_obj_v); name_obj_v = NULL;
16465  }
16466  }
16467 
16468  /*
16469  * The summary table has fulfilled its function. If no matching
16470  * objects are found, the function returns with an error.
16471  */
16472 
16473  cpl_table_delete(summary);
16474 
16475  if (!nmatches)
16476  return cpl_error_set(cpl_func, CPL_ERROR_DATA_NOT_FOUND);
16477 
16478  /*
16479  * Now we consider the resulting intersection object table,
16480  * listing all matches. As seen, the image row number reported
16481  * in the columns "row_#" was not really performed sequentially.
16482  * We need to renumber sequentially...
16483  * We need also to fill the "row_stokes_#" column the way the
16484  * extracted polarimetric signal will be stored in the
16485  * reduced_pol_images...
16486  */
16487 
16488  maxobjs = mos_get_maxobjs_per_slit(origslits);
16489  nstokes = nmatches / 2; /* nmatches is always an even number */
16490 
16491  for (k = 0; k < nslits; k++) {
16492  if (k % 2) { /* Extraordinary beam */
16493  nstokes = sstokes; /* Use same start value as for ordinary */
16494  }
16495  else { /* Ordinary beam */
16496  sstokes = nstokes; /* Memorise start value at ordinary beam */
16497  }
16498 
16499  for (m = 0; m < maxobjs; m++) {
16500  char *name = cpl_sprintf("row_%d", m + 1);
16501  char *namestokes = cpl_sprintf("row_stokes_%d", m + 1);
16502 
16503  if (!cpl_table_is_valid(origslits, name, k)) {
16504  cpl_free(name);
16505  cpl_free(namestokes);
16506  break;
16507  }
16508  else {
16509  nmatches--;
16510  nstokes--;
16511  cpl_table_set_int(origslits, name, k, nmatches);
16512  cpl_table_set_int(origslits, namestokes, k, nstokes);
16513  }
16514 
16515  cpl_free(name);
16516  cpl_free(namestokes);
16517  }
16518  }
16519 
16520 
16521  /*
16522  * This is done to avoid the NULL value is zero (it would invalidate
16523  * also the row_# = 0 or start_# = 0 for an object), and to enable
16524  * working directly with the column data buffers, when using this
16525  * table afterwards.
16526  */
16527 
16528  for (j = 0; j < maxobjs; j++) {
16529  char *name = cpl_sprintf("object_%d", j + 1);
16530  cpl_table_fill_invalid_double(origslits, name, -1);
16531  cpl_free(name);
16532 
16533  name = cpl_sprintf("start_%d", j + 1);
16534  cpl_table_fill_invalid_int(origslits, name, -1);
16535  cpl_free(name);
16536 
16537  name = cpl_sprintf("end_%d", j + 1);
16538  cpl_table_fill_invalid_int(origslits, name, -1);
16539  cpl_free(name);
16540 
16541  name = cpl_sprintf("row_%d", j + 1);
16542  cpl_table_fill_invalid_int(origslits, name, -1);
16543  cpl_free(name);
16544 
16545  name = cpl_sprintf("row_stokes_%d", j + 1);
16546  cpl_table_fill_invalid_int(origslits, name, -1);
16547  cpl_free(name);
16548  }
16549 
16550  /*********************************************************************
16551  * This tail has been added to propagate the selection of valid
16552  * objects also to the input slitss[] tables. Just eliminate all
16553  * this final part to suppress this behaviour.
16554  */
16555 
16556  /*
16557  * First of all, make a working copy and remove all columns related
16558  * to objects from the input object tables.
16559  */
16560 
16561  for (i = 0; i < nscience; i++) {
16562  int c_maxobjs = mos_get_maxobjs_per_slit(slitss[i]);
16563 
16564  work[i] = cpl_table_duplicate(slitss[i]);
16565 
16566  for (m = 0; m < c_maxobjs; m++) {
16567  char *object_o = cpl_sprintf("object_%d", m + 1);
16568  char *start_o = cpl_sprintf("start_%d", m + 1);
16569  char *end_o = cpl_sprintf("end_%d", m + 1);
16570  char *row_o = cpl_sprintf("row_%d", m + 1);
16571 
16572  cpl_table_erase_column(slitss[i], object_o);
16573  cpl_table_erase_column(slitss[i], start_o);
16574  cpl_table_erase_column(slitss[i], end_o);
16575  cpl_table_erase_column(slitss[i], row_o);
16576  }
16577  }
16578 
16579  /*
16580  * Now just consider all the objects in the intersection table.
16581  */
16582 
16583  for (k = 0; k < nslits; k++) {
16584  for (j = 0; j < maxobjs; j++) {
16585  double object_w, object_r;
16586  int row_w;
16587 
16588  char *object_i = cpl_sprintf("object_%d", j + 1);
16589  char *start_i = cpl_sprintf("start_%d", j + 1);
16590  char *end_i = cpl_sprintf("end_%d", j + 1);
16591  char *row_i = cpl_sprintf("row_%d", j + 1);
16592 
16593 
16594  if (!cpl_table_is_valid(origslits, object_i, k))
16595  break;
16596 
16597  /*
16598  * We have found a valid object (valid because it belongs
16599  * to the intersection). Now we look for this object in each
16600  * one of the original tables, we get its parameters, and
16601  * copy them at the right position (i.e., same position as
16602  * in intersection table). The object will be the one closest
16603  * to the object position (column object_i) in the intersection
16604  * table. Note that we examine the same row, k, in all tables.
16605  */
16606 
16607  object_w = cpl_table_get_double(origslits, object_i, k, NULL);
16608  row_w = cpl_table_get_int (origslits, row_i, k, NULL);
16609 
16610  for (i = 0; i < nscience; i++) {
16611  int c_maxobjs = mos_get_maxobjs_per_slit(work[i]);
16612  int minpos;
16613  double mindiff, diff;
16614  char *object_o;
16615  char *start_o;
16616  char *end_o;
16617  char *row_o;
16618 
16619  for (m = 0; m < c_maxobjs; m++) {
16620  object_o = cpl_sprintf("object_%d", m + 1);
16621  start_o = cpl_sprintf("start_%d", m + 1);
16622  end_o = cpl_sprintf("end_%d", m + 1);
16623  row_o = cpl_sprintf("row_%d", m + 1);
16624 
16625  if (!cpl_table_is_valid(work[i], object_o, k))
16626  break;
16627 
16628  object_r = cpl_table_get_double(work[i], object_o, k, NULL);
16629  //row_r = cpl_table_get_int (work[i], row_o, k, NULL);
16630 
16631  diff = fabs(object_w - object_r);
16632  if (m) {
16633  if (mindiff > diff) {
16634  mindiff = diff;
16635  minpos = m;
16636  }
16637  }
16638  else {
16639  mindiff = diff;
16640  minpos = 0;
16641  }
16642 
16643  cpl_free(object_o);
16644  cpl_free(start_o);
16645  cpl_free(end_o);
16646  cpl_free(row_o);
16647  }
16648 
16649  object_o = cpl_sprintf("object_%d", minpos + 1);
16650  start_o = cpl_sprintf("start_%d", minpos + 1);
16651  end_o = cpl_sprintf("end_%d", minpos + 1);
16652  row_o = cpl_sprintf("row_%d", minpos + 1);
16653 
16654  if (!cpl_table_has_column(slitss[i], object_i)) {
16655  cpl_table_new_column(slitss[i], object_i, CPL_TYPE_DOUBLE);
16656  cpl_table_new_column(slitss[i], start_i, CPL_TYPE_INT);
16657  cpl_table_new_column(slitss[i], end_i, CPL_TYPE_INT);
16658  cpl_table_new_column(slitss[i], row_i, CPL_TYPE_INT);
16659  cpl_table_fill_invalid_double(slitss[i], object_i, -1);
16660  cpl_table_fill_invalid_int (slitss[i], start_i, -1);
16661  cpl_table_fill_invalid_int (slitss[i], end_i, -1);
16662  cpl_table_fill_invalid_int (slitss[i], row_i, -1);
16663  }
16664 
16665  cpl_table_set_double(slitss[i], object_i, k,
16666  cpl_table_get_double(work[i], object_o,
16667  k, NULL));
16668  cpl_table_set_int(slitss[i], start_i , k,
16669  cpl_table_get_int(work[i], start_o, k, NULL));
16670  cpl_table_set_int(slitss[i], end_i , k,
16671  cpl_table_get_int(work[i], end_o, k, NULL));
16672  cpl_table_set_int(slitss[i], row_i , k, row_w);
16673 
16674  cpl_free(object_o);
16675  cpl_free(start_o);
16676  cpl_free(end_o);
16677  cpl_free(row_o);
16678  }
16679 
16680  cpl_free(object_i);
16681  cpl_free(start_i);
16682  cpl_free(end_i);
16683  cpl_free(row_i);
16684  }
16685  }
16686 
16687  for (i = 0; i < nscience; i++)
16688  cpl_table_delete(work[i]);
16689 
16690  cpl_free(work);
16691 
16692 
16693  return cpl_error_get_code();
16694 }
16695 
16696 
16704 int mos_get_maxobjs_per_slit(cpl_table * slits)
16705 {
16706  int maxobjs = 1;
16707 
16708  char * colname = cpl_sprintf("object_%d", maxobjs);
16709 
16710  while (cpl_table_has_column(slits, colname)) {
16711  maxobjs++;
16712  cpl_free(colname);
16713  colname = cpl_sprintf("object_%d", maxobjs);
16714  }
16715 
16716  cpl_free(colname);
16717 
16718  maxobjs--;
16719 
16720  return maxobjs;
16721 }
16722 
16730 int mos_get_nobjects(cpl_table * slits)
16731 {
16732  int nobjs = 0;
16733 
16734  int nslits = cpl_table_get_nrow(slits);
16735  int maxobjs = mos_get_maxobjs_per_slit(slits);
16736 
16737  int k, m;
16738 
16739  for (k = 0; k < nslits; k++) {
16740  for (m = 0; m < maxobjs; m++) {
16741  char * name = cpl_sprintf("object_%d", m + 1);
16742  int null = !cpl_table_is_valid(slits, name, k);
16743 
16744  cpl_free(name);
16745 
16746  if (null) break;
16747  else nobjs++;
16748  }
16749  }
16750 
16751  return nobjs;
16752 }
16753 
16761 int mos_check_slits(cpl_table *slits, float rescale)
16762 {
16763 
16764  cpl_propertylist *sort;
16765 
16766  int nslits = cpl_table_get_nrow(slits);
16767 
16768  int k, null;
16769 
16770  const float interval = 90.0 * rescale;
16771  const float offset = (90.0 - 5) * rescale;
16772 
16773 
16774  for (k = 0; k < nslits; k++) {
16775  double ytop = cpl_table_get_double(slits, "ytop", k, &null);
16776  double ybottom = cpl_table_get_double(slits, "ybottom", k, &null);
16777 
16778  double xtop = cpl_table_get_double(slits, "xtop", k, &null);
16779  double xbottom = cpl_table_get_double(slits, "xbottom", k, &null);
16780 
16781  int nmiss = (int)((ytop - ybottom) / interval + 0.5);
16782 
16783  if (nmiss > 1) {
16784  cpl_msg_warning(cpl_func,
16785  "Some slits could not be properly detected. "
16786  "There might be accountable inaccuracies.");
16787  while (nmiss > 1) {
16788  cpl_table_set_size(slits, nslits + 1);
16789 
16790  /* Fill in new slit 'cut' */
16791 
16792  /* x coordinates be the same (acceptable approximation) */
16793  cpl_table_set_double(slits, "xtop", nslits, xtop);
16794  cpl_table_set_double(slits, "xbottom", nslits, xbottom);
16795 
16796  /* Cut */
16797  if (k == 0) {
16798  cpl_table_set_double(slits, "ybottom", nslits, ybottom);
16799  cpl_table_set_double(slits, "ytop", nslits, ybottom
16800  + offset);
16801  ybottom += interval;
16802  cpl_table_set_double(slits, "ybottom", k, ybottom);
16803  } else {
16804  cpl_table_set_double(slits, "ytop", nslits, ytop);
16805  cpl_table_set_double(slits, "ybottom", nslits, ytop
16806  - offset);
16807  ytop -= interval;
16808  cpl_table_set_double(slits, "ytop", k, ytop);
16809  }
16810 
16811  nslits++; nmiss--;
16812  }
16813  }
16814  }
16815 
16816  sort = cpl_propertylist_new();
16817  cpl_propertylist_append_bool(sort, "ytop", 1);
16818  cpl_table_sort(slits, sort);
16819  cpl_propertylist_delete(sort);
16820 
16821  /*
16822  * Add here an ad hoc check on the last slit: is it too long
16823  * (by more than 10%)? Then shorten it...
16824  */
16825 
16826  k = cpl_table_get_nrow(slits) - 1;
16827 
16828  {
16829  double ytop = cpl_table_get_double(slits, "ytop", k, &null);
16830  double ybottom = cpl_table_get_double(slits, "ybottom", k, &null);
16831  double length = (ytop - ybottom) / interval;
16832 
16833  if (length > 1.1) {
16834  cpl_table_set_double(slits, "ybottom", k, ytop - offset);
16835  }
16836 
16837  }
16838 
16839  return 0;
16840 }
16841 
16864 cpl_table *mos_load_slits_fors_pmos(cpl_propertylist *header,
16865  int * nslits_out_det)
16866 {
16867  int m, null;
16868  int halfsize;
16869 
16870  cpl_propertylist * sort;
16871  cpl_table * slits;
16872 
16873  slits = mos_load_slits_fors_mos(header, nslits_out_det);
16874  halfsize = cpl_table_get_nrow(slits);
16875 
16876  cpl_table_set_size(slits, 2 * halfsize);
16877 
16878  for (m = 0; m < halfsize; m++) {
16879 
16880  double gap = 1.4;
16881 
16882  double length =
16883  cpl_table_get(slits, "ytop", m, &null) -
16884  cpl_table_get(slits, "ybottom", m, &null);
16885 
16886  if (m) {
16887  double interval =
16888  cpl_table_get(slits, "ybottom", m - 1, &null) -
16889  cpl_table_get(slits, "ytop", m, &null);
16890 
16891  gap = (interval - length) / 2;
16892  }
16893 
16894  cpl_table_set(slits, "slit_id", m + halfsize,
16895  cpl_table_get(slits, "slit_id", m, &null) - 1);
16896 
16897  cpl_table_set(slits, "xtop", m + halfsize,
16898  cpl_table_get(slits, "xtop", m, &null));
16899 
16900  cpl_table_set(slits, "xbottom", m + halfsize,
16901  cpl_table_get(slits, "xbottom", m, &null));
16902 
16903  cpl_table_set(slits, "ytop", m + halfsize,
16904  cpl_table_get(slits, "ytop", m, &null) + gap + length);
16905 
16906  cpl_table_set(slits, "ybottom", m + halfsize,
16907  cpl_table_get(slits, "ytop", m, &null) + gap);
16908  }
16909 
16910  for (m = 0; m < 2 * halfsize; m++) {
16911  cpl_table_set(slits, "ytop", m,
16912  cpl_table_get(slits, "ytop", m, &null) - 5.3);
16913 
16914  cpl_table_set(slits, "ybottom", m,
16915  cpl_table_get(slits, "ybottom", m, &null) - 5.3);
16916 
16917  }
16918 
16919  sort = cpl_propertylist_new();
16920  cpl_propertylist_append_bool(sort, "ytop", 1);
16921  cpl_table_sort(slits, sort);
16922 
16923  cpl_propertylist_delete(sort);
16924 
16925  return slits;
16926 }
16927 
16928 int * fors_get_nobjs_perslit(cpl_table * slits)
16929 {
16930  int nslits = cpl_table_get_nrow(slits);
16931  int maxobjs = mos_get_maxobjs_per_slit(slits);
16932 
16933  int * nobjs_per_slit = cpl_malloc(sizeof(int) * nslits);
16934 
16935  int k, m;
16936 
16937  for (k = 0; k < nslits; k++) {
16938  int nobjs = 0;
16939  for (m = 0; m < maxobjs; m++) {
16940  char * name = cpl_sprintf("object_%d", m + 1);
16941  int null = !cpl_table_is_valid(slits, name, k);
16942 
16943  cpl_free(name);
16944 
16945  if (null) break;
16946  else nobjs++;
16947  }
16948 
16949  nobjs_per_slit[k] = nobjs;
16950  }
16951 
16952  return nobjs_per_slit;
16953 }
16954 
16955 double fors_get_object_position(cpl_table *slits, int slit, int object)
16956 {
16957  char *name = cpl_sprintf("object_%d", object);
16958  double position;
16959 
16960  position = cpl_table_get_double(slits, name, slit, NULL)
16961  - cpl_table_get_int(slits, "position", slit, NULL);
16962 
16963  cpl_free(name);
16964 
16965  return position;
16966 }
16967 
16968 int mos_rebin_signal(cpl_image **image, int rebin)
16969 {
16970  cpl_image *rebinned;
16971 
16972 
16973  if (*image == NULL)
16974  return 1;
16975 
16976  if (rebin == 1)
16977  return 0;
16978 
16979  rebinned = cpl_image_rebin(*image, 1, 1, rebin, 1);
16980 
16981  cpl_image_delete(*image);
16982 
16983  *image = rebinned;
16984 
16985  return 0;
16986 }
16987 
16988 int mos_rebin_error(cpl_image **image, int rebin)
16989 {
16990  if (*image == NULL)
16991  return 1;
16992 
16993  if (rebin == 1)
16994  return 0;
16995 
16996  cpl_image_power(*image, 2);
16997  mos_rebin_signal(image, rebin);
16998  cpl_image_power(*image, 0.5);
16999 
17000  return 0;
17001 }
17002 
17003 /*
17004  * @brief
17005  * Map table values into a 1D image
17006  *
17007  * @param image Target image
17008  * @param start Coordinate of first pixel in image
17009  * @param step Coordinate step for one pixel in image
17010  * @param table Source table
17011  * @param xname Name of coordinate column
17012  * @param yname Name of values column
17013  *
17014  * @return 0 on success
17015  *
17016  * The values in @em yname are linearly interpolated at the @em image
17017  * pixel coordinates. The @em image must have Nx1 size.
17018  */
17019 
17020 int map_table(cpl_image *image, double start, double step,
17021  cpl_table *table, char *xname, char *yname)
17022 {
17023  int length = cpl_image_get_size_x(image);
17024  int nrows = cpl_table_get_nrow(table);
17025  float *data = cpl_image_get_data_float(image);
17026  float *fdata = NULL;
17027  double *xdata = NULL;
17028  double *ydata = NULL;
17029  cpl_type xtype = cpl_table_get_column_type(table, xname);
17030  cpl_type ytype = cpl_table_get_column_type(table, yname);
17031  double xzero, pos;
17032  int i, j, n;
17033 
17034 
17035  /*
17036  * Initialization of output image at 0.0 - this value is left
17037  * on non-overlapping portions.
17038  */
17039 
17040  for (i = 0; i < length; i++)
17041  data[i] = 0.0;
17042 
17043 
17044  /*
17045  * Do everything in double precision
17046  */
17047 
17048  if (xtype == CPL_TYPE_FLOAT) {
17049  fdata = cpl_table_get_data_float(table, xname);
17050  xdata = cpl_malloc(nrows * sizeof(double));
17051  for (i = 0; i < nrows; i++) {
17052  xdata[i] = fdata[i];
17053  }
17054  }
17055  else {
17056  xdata = cpl_table_get_data_double(table, xname);
17057  }
17058 
17059  if (ytype == CPL_TYPE_FLOAT) {
17060  fdata = cpl_table_get_data_float(table, yname);
17061  ydata = cpl_malloc(nrows * sizeof(double));
17062  for (i = 0; i < nrows; i++) {
17063  ydata[i] = fdata[i];
17064  }
17065  }
17066  else {
17067  ydata = cpl_table_get_data_double(table, yname);
17068  }
17069 
17070  /*
17071  * Mapping
17072  */
17073 
17074  n = 0;
17075  xzero = xdata[n];
17076 
17077  for (i = 0; i < length; i++) {
17078  pos = start + step * i;
17079  if (pos < xzero)
17080  continue;
17081  for (j = n; j < nrows; j++) {
17082  if (xdata[j] > pos) {
17083  n = j;
17084  data[i] = ydata[j-1]
17085  + (ydata[j] - ydata[j-1])
17086  * (pos - xdata[j-1]) / (xdata[j] - xdata[j-1]);
17087  break;
17088  }
17089  }
17090  }
17091 
17092  if (xtype == CPL_TYPE_FLOAT)
17093  cpl_free(xdata);
17094 
17095  if (ytype == CPL_TYPE_FLOAT)
17096  cpl_free(ydata);
17097 
17098  return 0;
17099 }
17100 
17101 
17102 /*
17103  * @brief
17104  * Fit overall trend of a Nx1 image
17105  *
17106  * @param image Values to smooth
17107  * @param order Order of fitting polynomial
17108  * @param hw Half width of smoothing window
17109  *
17110  * @return Smoothed image, or NULL on failure.
17111  *
17112  * Heavily smooth and fit data in the input Nx1 size @em image.
17113  */
17114 
17115 static cpl_image *polysmooth(cpl_image *image, int order, int hw)
17116 {
17117  int npoints;
17118  cpl_vector *x;
17119  cpl_vector *y;
17120  double *xdata;
17121  double *ydata;
17122  cpl_polynomial *poly;
17123  cpl_vector *ysmooth;
17124  cpl_image *smoothed;
17125  float *sdata;
17126  int i;
17127 
17128 
17129  npoints = cpl_image_get_size_x(image);
17130 
17131  if (2 * hw + 1 > npoints)
17132  return NULL;
17133 
17134  x = cpl_vector_new(npoints);
17135  y = cpl_vector_new(npoints);
17136  xdata = cpl_vector_get_data(x);
17137  ydata = cpl_vector_get_data(y);
17138 
17139  smoothed = cpl_image_duplicate(image);
17140  sdata = cpl_image_get_data_float(smoothed);
17141 
17142  for (i = 0; i < npoints; i++) {
17143  xdata[i] = i;
17144  ydata[i] = sdata[i];
17145  }
17146 
17147  ysmooth = cpl_vector_filter_median_create(y, hw);
17148  cpl_vector_delete(y);
17149 
17150  poly = cpl_polynomial_fit_1d_create(x, ysmooth, order, NULL);
17151  cpl_vector_delete(x);
17152  cpl_vector_delete(ysmooth);
17153 
17154  if (poly) {
17155  for (i = 0; i < npoints; i++)
17156  sdata[i] = cpl_polynomial_eval_1d(poly, i, NULL);
17157 
17158  cpl_polynomial_delete(poly);
17159  }
17160  else {
17161  cpl_image_delete(smoothed);
17162  return NULL;
17163  }
17164 
17165  return smoothed;
17166 }
17167 
17168 #undef cleanup
17169 #define cleanup \
17170 do { \
17171  cpl_image_delete(spectrum); \
17172  cpl_image_delete(flux); \
17173  cpl_image_delete(efficiency); \
17174  cpl_image_delete(smo_efficiency); \
17175  cpl_image_delete(extinction); \
17176  cpl_image_delete(response); \
17177  cpl_image_delete(smo_response); \
17178  cpl_image_delete(physical); \
17179 } while (0)
17180 
17204 cpl_table *mos_photometric_calibration(cpl_image *spectra, double startwave,
17205  double dispersion, double gain,
17206  double exptime, cpl_table *ext_table,
17207  double airmass, cpl_table *flux_table,
17208  int order)
17209 {
17210 
17211  cpl_image *spectrum = NULL; // Extracted standard star spectrum
17212  float *data;
17213  cpl_image *extinction = NULL; // Extinction binned as "spectrum"
17214  float *ext_data;
17215  cpl_image *flux = NULL; // Standard star flux binned as "spectrum"
17216  float *flux_data;
17217  cpl_image *physical = NULL; // Physical units of above
17218  float *phys_data;
17219  cpl_image *efficiency = NULL; // Raw efficiency curve
17220  float *eff_data;
17221  cpl_image *smo_efficiency = NULL; // Smoothed efficiency curve
17222  float *smo_eff_data;
17223  cpl_image *response = NULL; // Raw response curve
17224  float *res_data;
17225  cpl_image *smo_response = NULL; // Smoothed response curve
17226  float *smo_res_data;
17227  cpl_image *image;
17228  cpl_image *smo_image;
17229  cpl_table *table;
17230  float lambda;
17231  int nx, ny;
17232  int ext_count, ext_pos;
17233  int eff_count, eff_pos;
17234  int flux_count, flux_pos;
17235  int start, end;
17236  int i;
17237 
17238 
17239  if (spectra == NULL || ext_table == NULL || flux_table == NULL) {
17240  cpl_error_set(cpl_func, CPL_ERROR_NULL_INPUT);
17241  return NULL;
17242  }
17243 
17244  if (!cpl_table_has_column(ext_table, "WAVE")) {
17245  cpl_error_set_message(cpl_func, CPL_ERROR_DATA_NOT_FOUND,
17246  "Column WAVE in atmospheric extinction table");
17247  return NULL;
17248  }
17249 
17250  if (!cpl_table_has_column(ext_table, "EXTINCTION")) {
17251  cpl_error_set_message(cpl_func, CPL_ERROR_DATA_NOT_FOUND,
17252  "Column EXTINCTION in atmospheric extinction table");
17253  return NULL;
17254  }
17255 
17256  if (!cpl_table_has_column(flux_table, "WAVE")) {
17257  cpl_error_set_message(cpl_func, CPL_ERROR_DATA_NOT_FOUND,
17258  "Column WAVE in standard star flux table");
17259  return NULL;
17260  }
17261 
17262  if (!cpl_table_has_column(flux_table, "FLUX")) {
17263  cpl_error_set_message(cpl_func, CPL_ERROR_DATA_NOT_FOUND,
17264  "Column FLUX in standard star flux table");
17265  return NULL;
17266  }
17267 
17268  if (gain < 0.1) {
17269  cpl_error_set_message(cpl_func, CPL_ERROR_ILLEGAL_INPUT,
17270  "Invalid gain factor (%.2f)", gain);
17271  return NULL;
17272  }
17273 
17274  if (exptime < 0.001) {
17275  cpl_error_set_message(cpl_func, CPL_ERROR_ILLEGAL_INPUT,
17276  "Invalid exposure time (%.2f)", exptime);
17277  return NULL;
17278  }
17279 
17280  if (dispersion < 0.001) {
17281  cpl_error_set_message(cpl_func, CPL_ERROR_ILLEGAL_INPUT,
17282  "Invalid dispersion (%.2f)", dispersion);
17283  return NULL;
17284  }
17285 
17286  if (order < 2) {
17287  cpl_error_set_message(cpl_func, CPL_ERROR_ILLEGAL_INPUT,
17288  "Order of the polynomial fitting the "
17289  "instrument response must be at least 2");
17290  return NULL;
17291  }
17292 
17293  nx = cpl_image_get_size_x(spectra);
17294  ny = cpl_image_get_size_y(spectra);
17295 
17296 
17297  /*
17298  * Find brightest spectrum and duplicate it.
17299  */
17300 
17301  if (ny == 1) {
17302  spectrum = cpl_image_duplicate(spectra);
17303  }
17304  else {
17305  cpl_size x, y;
17306  cpl_image *brights = cpl_image_collapse_create(spectra, 1);
17307 
17308  cpl_image_get_maxpos(brights, &x, &y);
17309  cpl_image_delete(brights);
17310  spectrum = cpl_image_extract(spectra, 1, y, nx, y);
17311  }
17312 
17313 
17314  /*
17315  * Convert standard star spectrum in electrons per second per Angstrom.
17316  */
17317 
17318  cpl_image_multiply_scalar(spectrum, gain / exptime / dispersion);
17319 
17320 
17321  /*
17322  * Map the atmospheric extinction factors to the same lambda sampling
17323  * of the extracted spectrum.
17324  */
17325 
17326  extinction = cpl_image_duplicate(spectrum);
17327  map_table(extinction, startwave + dispersion/2, dispersion,
17328  ext_table, "WAVE", "EXTINCTION");
17329 
17330 
17331  /*
17332  * Convert from magnitudes to actual flux loss.
17333  */
17334 
17335  cpl_image_multiply_scalar(extinction, 0.4 * airmass);
17336  cpl_image_exponential(extinction, 10.);
17337 
17338 
17339  /*
17340  * Correct the scientific spectrum to airmass 0
17341  */
17342 
17343  cpl_image_multiply(spectrum, extinction);
17344 
17345 
17346  /*
17347  * Find in what pixel interval (start at "ext_pos", for "ext_count"
17348  * pixels) the atmospheric extinction is available.
17349  */
17350 
17351  ext_data = cpl_image_get_data_float(extinction);
17352 
17353  ext_count = 0;
17354  ext_pos = 0;
17355  for (i = 0; i < nx; i++) {
17356  if (ext_data[i] > 0.0) {
17357  if (ext_count == 0) {
17358  ext_pos = i;
17359  }
17360  ext_count++;
17361  }
17362  else {
17363  if (ext_count) {
17364  break;
17365  }
17366  }
17367  }
17368 
17369  cpl_image_delete(extinction); extinction = NULL;
17370 
17371 
17372  /*
17373  * Map the standard star catalog flux to the same lambda sampling
17374  * of the extracted spectrum.
17375  */
17376 
17377  flux = cpl_image_duplicate(spectrum);
17378  map_table(flux, startwave + dispersion/2, dispersion,
17379  flux_table, "WAVE", "FLUX");
17380 
17381 
17382  /*
17383  * Find in what pixel interval (start at "flux_pos", for "flux_count"
17384  * pixels) the standard star flux is available.
17385  */
17386 
17387  flux_data = cpl_image_get_data_float(flux);
17388 
17389  flux_count = 0;
17390  flux_pos = 0;
17391  for (i = 0; i < nx; i++) {
17392  if (flux_data[i] > 0.0) {
17393  if (flux_count == 0) {
17394  flux_pos = i;
17395  }
17396  flux_count++;
17397  }
17398  else {
17399  if (flux_count) {
17400  break;
17401  }
17402  }
17403  }
17404 
17405 
17406  /*
17407  * Intersection with previous selection
17408  */
17409 
17410  start = ext_pos > flux_pos ? ext_pos : flux_pos;
17411  end = (ext_pos + ext_count) < (flux_pos + flux_count) ?
17412  (ext_pos + ext_count) : (flux_pos + flux_count);
17413  flux_pos = start;
17414  flux_count = end - start;
17415 
17416 
17417  /*
17418  * Convert the flux to photons (per second per Angstrom).
17419  * std_flux is in units of erg / cm^2 / s / Angstrom. This
17420  * must be multiplied by the efficient area of the telescope,
17421  * 5.18E+5 cm^2, and divided by hv (v = frequency). With
17422  * hc = 1.98E-8 erg*Angstrom one obtains the following:
17423  */
17424 
17425  physical = cpl_image_duplicate(spectrum);
17426  phys_data = cpl_image_get_data_float(physical);
17427 
17428  for (i = 0; i < nx; i++) {
17429  lambda = startwave + dispersion * (i + 0.5);
17430  phys_data[i] = 0.0026 * lambda * flux_data[i];
17431  }
17432 
17433  efficiency = cpl_image_duplicate(spectrum);
17434  eff_data = cpl_image_get_data_float(efficiency);
17435  data = cpl_image_get_data_float(spectrum);
17436 
17437  for (i = 0; i < nx; i++) {
17438  if (phys_data[i] > 0.0)
17439  eff_data[i] = data[i] / phys_data[i];
17440  else
17441  eff_data[i] = 0.0;
17442  }
17443 
17444  cpl_image_delete(physical); physical = NULL;
17445 
17446 
17447  /*
17448  * Find interval (longer than 300 pixels) where efficiency is
17449  * greater than 1%
17450  */
17451 
17452  eff_count = 0;
17453  eff_pos = 0;
17454  for (i = 0; i < nx; i++) {
17455  if (eff_data[i] > 0.01) {
17456  if (eff_count == 0) {
17457  eff_pos = i;
17458  }
17459  eff_count++;
17460  }
17461  else {
17462  if (eff_count > 300) {
17463  break;
17464  }
17465  }
17466  }
17467 
17468 
17469  /*
17470  * Intersection with previous selection
17471  */
17472 
17473  start = eff_pos > flux_pos ? eff_pos : flux_pos;
17474  end = (eff_pos + eff_count) < (flux_pos + flux_count) ?
17475  (eff_pos + eff_count) : (flux_pos + flux_count);
17476  eff_pos = start;
17477  eff_count = end - start;
17478 
17479  if (eff_count < 1) {
17480  cpl_error_set_message(cpl_func, CPL_ERROR_INCOMPATIBLE_INPUT,
17481  "No overlap between catalog and spectrum");
17482  cleanup;
17483  return NULL;
17484  }
17485 
17486 
17487  /*
17488  * Extract only data to fit, i.e., where the efficiency is available.
17489  */
17490 
17491  image = cpl_image_extract(efficiency, eff_pos + 1, 1,
17492  eff_pos + eff_count, 1);
17493 
17494  smo_image = polysmooth(image, order, 50);
17495  cpl_image_delete(image);
17496 
17497  smo_efficiency = cpl_image_duplicate(efficiency);
17498  smo_eff_data = cpl_image_get_data_float(smo_efficiency);
17499  cpl_image_copy(smo_efficiency, smo_image, eff_pos + 1, 1);
17500 
17501  cpl_image_delete(smo_image);
17502 
17503 
17504  /*
17505  * Compute instrument response as the ratio between the catalog
17506  * spectrum and the observed spectrum (converted in physical units).
17507  * The polynomial smoothing, however, is performed on the inverse
17508  * of this ration, for obvious reasons (i.e., no divergence at zero
17509  * efficiency).
17510  */
17511 
17512  response = cpl_image_duplicate(spectrum);
17513  res_data = cpl_image_get_data_float(response);
17514 
17515  for (i = 0; i < nx; i++) {
17516  if (eff_data[i] > 0.01 && flux_data[i] > 0.0)
17517  res_data[i] = data[i] / flux_data[i];
17518  else
17519  res_data[i] = 0.0;
17520  }
17521 
17522 
17523  /*
17524  * Extract only data to fit, i.e., where the response is available.
17525  */
17526 
17527  image = cpl_image_extract(response, eff_pos + 1, 1, eff_pos + eff_count, 1);
17528 
17529  smo_image = polysmooth(image, order, 50);
17530  cpl_image_delete(image);
17531 
17532  smo_response = cpl_image_duplicate(response);
17533  smo_res_data = cpl_image_get_data_float(smo_response);
17534  cpl_image_copy(smo_response, smo_image, eff_pos + 1, 1);
17535 
17536  cpl_image_delete(smo_image);
17537 
17538  for (i = 0; i < nx; i++) {
17539  if (eff_data[i] > 0.01) {
17540  res_data[i] = 1 / res_data[i];
17541  smo_res_data[i] = 1 / smo_res_data[i];
17542  }
17543  else {
17544  res_data[i] = 0.0;
17545  smo_res_data[i] = 0.0;
17546  }
17547  }
17548 
17549 
17550  /*
17551  * Assemble the product spectrophotometric table.
17552  */
17553 
17554  table = cpl_table_new(nx);
17555 
17556  cpl_table_new_column(table, "WAVE", CPL_TYPE_FLOAT);
17557  cpl_table_set_column_unit(table, "WAVE", "Angstrom");
17558 
17559  for (i = 0; i < nx; i++)
17560  cpl_table_set_float(table, "WAVE", i, startwave + dispersion*(i+0.5));
17561 
17562  cpl_table_new_column(table, "STD_FLUX", CPL_TYPE_FLOAT);
17563  cpl_table_set_column_unit(table, "STD_FLUX",
17564  "10^(-16) erg/(cm^2 s Angstrom)");
17565  cpl_table_copy_data_float(table, "STD_FLUX", flux_data);
17566  cpl_image_delete(flux); flux = NULL;
17567 
17568  cpl_table_new_column(table, "OBS_FLUX", CPL_TYPE_FLOAT);
17569  cpl_table_set_column_unit(table, "OBS_FLUX", "electron/(s Angstrom)");
17570  cpl_table_copy_data_float(table, "OBS_FLUX", data);
17571  cpl_image_delete(spectrum); spectrum = NULL;
17572 
17573  cpl_table_new_column(table, "RAW_EFFICIENCY", CPL_TYPE_FLOAT);
17574  cpl_table_set_column_unit(table, "RAW_EFFICIENCY", "electron/photon");
17575  cpl_table_copy_data_float(table, "RAW_EFFICIENCY", eff_data);
17576  cpl_image_delete(efficiency); efficiency = NULL;
17577 
17578  cpl_table_new_column(table, "EFFICIENCY", CPL_TYPE_FLOAT);
17579  cpl_table_set_column_unit(table, "EFFICIENCY", "electron/photon");
17580  cpl_table_copy_data_float(table, "EFFICIENCY", smo_eff_data);
17581  cpl_image_delete(smo_efficiency); smo_efficiency = NULL;
17582 
17583  cpl_table_new_column(table, "RAW_RESPONSE", CPL_TYPE_FLOAT);
17584  cpl_table_set_column_unit(table, "RAW_RESPONSE",
17585  "10^(-16) erg/(cm^2 electron)");
17586  cpl_table_copy_data_float(table, "RAW_RESPONSE", res_data);
17587  cpl_image_delete(response); response = NULL;
17588 
17589  cpl_table_new_column(table, "RESPONSE", CPL_TYPE_FLOAT);
17590  cpl_table_set_column_unit(table,
17591  "RESPONSE", "10^(-16) erg/(cm^2 electron)");
17592  cpl_table_copy_data_float(table, "RESPONSE", smo_res_data);
17593  cpl_image_delete(smo_response); smo_response = NULL;
17594 
17595  cleanup;
17596 
17597  return table;
17598 }
17599 
17600 static double ksigma_vector(cpl_vector *values,
17601  double klow, double khigh, int kiter, int *good)
17602 {
17603  cpl_vector *accepted;
17604  double mean = 0.0;
17605  double sigma = 0.0;
17606  double *data = cpl_vector_get_data(values);
17607  int n = cpl_vector_get_size(values);
17608  int ngood = n;
17609  int count = 0;
17610  int i;
17611 
17612 
17613  /*
17614  * At first iteration the mean is taken as the median, and the
17615  * standard deviation relative to this value is computed.
17616  */
17617 
17618  mean = cpl_vector_get_median(values);
17619 
17620  for (i = 0; i < n; i++)
17621  sigma += (mean - data[i]) * (mean - data[i]);
17622 
17623  sigma = sqrt(sigma / (n - 1));
17624 
17625  while (kiter) {
17626  count = 0;
17627  for (i = 0; i < ngood; i++) {
17628  if (data[i]-mean < khigh*sigma && mean-data[i] < klow*sigma) {
17629  data[count] = data[i];
17630  ++count;
17631  }
17632  }
17633 
17634  if (count == 0) // This cannot happen at first iteration.
17635  break; // So we can break: we have already computed a mean.
17636 
17637  /*
17638  * The mean must be computed even if no element was rejected
17639  * (count == ngood), because at first iteration median instead
17640  * of mean was computed.
17641  */
17642 
17643  accepted = cpl_vector_wrap(count, data);
17644  mean = cpl_vector_get_mean(accepted);
17645  if (count > 1)
17646  sigma = cpl_vector_get_stdev(accepted);
17647  cpl_vector_unwrap(accepted);
17648 
17649  if (count == ngood || count == 1)
17650  break;
17651 
17652  ngood = count;
17653  --kiter;
17654  }
17655 
17656  if (good)
17657  *good = ngood;
17658 
17659  return mean;
17660 }
17661 
17662 
17681 cpl_image *mos_ksigma_stack(cpl_imagelist *imlist,
17682  double klow, double khigh, int kiter,
17683  cpl_image **good)
17684 {
17685  int ni, nx, ny, npix;
17686  cpl_image *out_ima;
17687  float *pout_ima;
17688  float *good_ima;
17689  cpl_image *image;
17690  float **data;
17691  cpl_vector *time_line;
17692  double *ptime_line;
17693  int ngood;
17694  int i, j;
17695 
17696 
17697  ni = cpl_imagelist_get_size(imlist);
17698 
17699  image = cpl_imagelist_get(imlist, 0);
17700  nx = cpl_image_get_size_x(image);
17701  ny = cpl_image_get_size_y(image);
17702  npix = nx * ny;
17703 
17704  out_ima = cpl_image_new(nx, ny, CPL_TYPE_FLOAT);
17705  pout_ima = cpl_image_get_data_float(out_ima);
17706 
17707  if (good) {
17708  *good = cpl_image_new(nx, ny, CPL_TYPE_FLOAT);
17709  good_ima = cpl_image_get_data_float(*good);
17710  }
17711 
17712  time_line = cpl_vector_new(ni);
17713  ptime_line = cpl_vector_get_data(time_line);
17714 
17715  data = cpl_calloc(sizeof(float *), ni);
17716 
17717  for (i = 0; i < ni; i++) {
17718  image = cpl_imagelist_get(imlist, i);
17719  data[i] = cpl_image_get_data_float(image);
17720  }
17721 
17722  for (i = 0; i < npix; i++) {
17723  for (j = 0; j < ni; j++) {
17724  ptime_line[j] = data[j][i];
17725  }
17726  pout_ima[i] = ksigma_vector(time_line, klow, khigh, kiter, &ngood);
17727  if (good) {
17728  good_ima[i] = ngood;
17729  }
17730  }
17731 
17732  cpl_free(data);
17733  cpl_vector_delete(time_line);
17734 
17735  return out_ima;
17736 
17737 }
17738 
17739 
17756 cpl_image *mos_apply_photometry(cpl_image *spectra, cpl_table *response,
17757  cpl_table *ext_table, double startwave,
17758  double dispersion, double gain,
17759  double exptime, double airmass)
17760 {
17761  cpl_image *extinction;
17762  cpl_image *outspectra;
17763  cpl_image *mapresponse;
17764  float *res_data;
17765  float *out_data;
17766  float *ext_data;
17767  int tlength, xlength, ylength;
17768  int i, j, k;
17769 
17770 
17771  if (spectra == NULL || ext_table == NULL || response == NULL) {
17772  cpl_error_set(cpl_func, CPL_ERROR_NULL_INPUT);
17773  return NULL;
17774  }
17775 
17776  res_data = cpl_table_get_data_float(response, "RESPONSE");
17777 
17778  if (res_data == NULL) {
17779  cpl_error_set(cpl_func, CPL_ERROR_DATA_NOT_FOUND);
17780  return NULL;
17781  }
17782 
17783  tlength = cpl_table_get_nrow(response);
17784  xlength = cpl_image_get_size_x(spectra);
17785  ylength = cpl_image_get_size_y(spectra);
17786 
17787  if (xlength != tlength) {
17788  mapresponse = cpl_image_new(xlength, 1, CPL_TYPE_FLOAT);
17789  map_table(mapresponse, startwave + dispersion/2, dispersion,
17790  response, "WAVE", "RESPONSE");
17791  res_data = cpl_image_get_data_float(mapresponse);
17792  }
17793 
17794  /*
17795  * Map the atmospheric extinction factors to the same lambda sampling
17796  * of the extracted spectrum.
17797  */
17798 
17799  extinction = cpl_image_new(xlength, 1, CPL_TYPE_FLOAT);
17800  map_table(extinction, startwave + dispersion/2, dispersion,
17801  ext_table, "WAVE", "EXTINCTION");
17802 
17803 
17804  /*
17805  * Convert from magnitudes to actual flux loss.
17806  */
17807 
17808  cpl_image_multiply_scalar(extinction, 0.4 * airmass);
17809  cpl_image_exponential(extinction, 10.);
17810 
17811  outspectra = cpl_image_duplicate(spectra);
17812 
17813  ext_data = cpl_image_get_data_float(extinction);
17814  out_data = cpl_image_get_data_float(outspectra);
17815 
17816  for (k = 0, i = 0; i < ylength; i++) {
17817  for (j = 0; j < xlength; j++, k++) {
17818  out_data[k] *= ext_data[j] * res_data[j];
17819  }
17820  }
17821 
17822  cpl_image_delete(extinction);
17823  if (xlength != tlength) {
17824  cpl_image_delete(mapresponse);
17825  }
17826 
17827  cpl_image_multiply_scalar(outspectra, gain / exptime / dispersion);
17828 
17829  return outspectra;
17830 }
17831 
17832 
17849 cpl_image *mos_propagate_photometry_error(cpl_image *spectra,
17850  cpl_image *errors,
17851  cpl_table *response,
17852  cpl_table *ext_table,
17853  double startwave,
17854  double dispersion, double gain,
17855  double exptime, double airmass)
17856 {
17857  cpl_image *extinction;
17858  cpl_image *outerrors;
17859  cpl_image *mapresponse;
17860  cpl_image *maperror;
17861  float *err_data;
17862  float *out_data;
17863  float *ext_data;
17864  float *res_data;
17865  float *spe_data;
17866  int tlength, xlength, ylength;
17867  int i, j, k;
17868 
17869 
17870  if (errors == NULL || ext_table == NULL || response == NULL) {
17871  cpl_error_set(cpl_func, CPL_ERROR_NULL_INPUT);
17872  return NULL;
17873  }
17874 
17875  if (!cpl_table_has_column(response, "ERROR")) {
17876  return mos_apply_photometry(errors, response, ext_table, startwave,
17877  dispersion, gain, exptime, airmass);
17878  }
17879 
17880  res_data = cpl_table_get_data_float(response, "RESPONSE");
17881 
17882  if (res_data == NULL) {
17883  cpl_error_set(cpl_func, CPL_ERROR_DATA_NOT_FOUND);
17884  return NULL;
17885  }
17886 
17887  err_data = cpl_table_get_data_float(response, "ERROR");
17888 
17889  if (err_data == NULL) {
17890  cpl_error_set(cpl_func, CPL_ERROR_DATA_NOT_FOUND);
17891  return NULL;
17892  }
17893 
17894  tlength = cpl_table_get_nrow(response);
17895  xlength = cpl_image_get_size_x(errors);
17896  ylength = cpl_image_get_size_y(errors);
17897 
17898  if (xlength != tlength) {
17899  mapresponse = cpl_image_new(xlength, 1, CPL_TYPE_FLOAT);
17900  map_table(mapresponse, startwave + dispersion/2, dispersion,
17901  response, "WAVE", "RESPONSE");
17902  res_data = cpl_image_get_data_float(mapresponse);
17903 
17904  maperror = cpl_image_new(xlength, 1, CPL_TYPE_FLOAT);
17905  map_table(maperror, startwave + dispersion/2, dispersion,
17906  response, "WAVE", "ERROR");
17907  err_data = cpl_image_get_data_float(maperror);
17908  }
17909 
17910  /*
17911  * Map the atmospheric extinction factors to the same lambda sampling
17912  * of the extracted spectrum.
17913  */
17914 
17915  extinction = cpl_image_new(xlength, 1, CPL_TYPE_FLOAT);
17916  map_table(extinction, startwave + dispersion/2, dispersion,
17917  ext_table, "WAVE", "EXTINCTION");
17918 
17919 
17920  /*
17921  * Convert from magnitudes to actual flux loss.
17922  */
17923 
17924  cpl_image_multiply_scalar(extinction, 0.4 * airmass);
17925  cpl_image_exponential(extinction, 10.);
17926 
17927  outerrors = cpl_image_duplicate(errors);
17928 
17929  ext_data = cpl_image_get_data_float(extinction);
17930  out_data = cpl_image_get_data_float(outerrors);
17931  spe_data = cpl_image_get_data_float(spectra);
17932 
17933  for (k = 0, i = 0; i < ylength; i++) {
17934  for (j = 0; j < xlength; j++, k++) {
17935  out_data[k] = ext_data[j] *
17936  sqrt(err_data[j] * err_data[j] * spe_data[k] * spe_data[k] +
17937  res_data[j] * res_data[j] * out_data[k] * out_data[k]);
17938  }
17939  }
17940 
17941  cpl_image_delete(extinction);
17942  if (xlength != tlength) {
17943  cpl_image_delete(maperror);
17944  }
17945 
17946  cpl_image_multiply_scalar(outerrors, gain / exptime / dispersion);
17947 
17948  return outerrors;
17949 }
17950 
17951 
18027 int mos_check_polarisation(cpl_image *q_image, cpl_image *q_error,
18028  cpl_image *u_image, cpl_image *u_error,
18029  double startwave, double dispersion,
18030  double band, cpl_table *pol_sta,
18031  double ra, double dec, char *filter,
18032  int *polarisation,
18033  double *p_offset, double *p_error,
18034  double *a_offset, double *a_error)
18035 {
18036  cpl_table *standard;
18037  cpl_image *q_noise;
18038  cpl_image *q_signal;
18039  cpl_image *u_noise;
18040  cpl_image *u_signal;
18041  cpl_image *noise;
18042  double *q_ndata;
18043  double *q_sdata;
18044  double *u_ndata;
18045  double *u_sdata;
18046  double arctol = 0.5; /* Arc tolerance in degrees */
18047  double mindist;
18048  double cwave;
18049  double bwave[] = {3650., 4450., 5510., 6580., 8060};
18050  char *bands = "UBVRI";
18051  char p_label[] = {' ', 'p', '\0'};
18052  char dp_label[] = {' ', 'd', 'p', '\0'};
18053  char a_label[] = {' ', 'a', '\0'};
18054  char da_label[] = {' ', 'd', 'a', '\0'};
18055  int nbands = strlen(bands);
18056  int selected;
18057  int first, last, count, center;
18058  int nx;
18059  cpl_size col, row;
18060  int i, found, closest;
18061  int pband;
18062  int polarised;
18063  double q_obs;
18064  double q_err;
18065  double u_obs;
18066  double u_err;
18067  double p_obs;
18068  double p_err;
18069  double p_ref;
18070  double dp_ref;
18071  double a_obs;
18072  double a_err;
18073  double a_ref;
18074  double da_ref;
18075 
18076 
18077  *filter = '\0';
18078  *polarisation = 0;
18079  *p_offset = 0.0;
18080  *p_error = 0.0;
18081  *a_offset = 0.0;
18082  *a_error = 0.0;
18083 
18084  /*
18085  * Select reference standard star
18086  */
18087 
18088  cpl_table_select_all(pol_sta);
18089  cpl_table_and_selected_double(pol_sta, "RA", CPL_GREATER_THAN, ra-arctol);
18090  cpl_table_and_selected_double(pol_sta, "RA", CPL_LESS_THAN, ra+arctol);
18091  cpl_table_and_selected_double(pol_sta, "DEC", CPL_GREATER_THAN, dec-arctol);
18092  selected =
18093  cpl_table_and_selected_double(pol_sta, "DEC", CPL_LESS_THAN, dec+arctol);
18094 
18095  if (selected == 0) {
18096  cpl_msg_warning(cpl_func, "No standard star found in FOV");
18097  return 1;
18098  }
18099 
18100  if (selected > 1) {
18101  cpl_msg_warning(cpl_func,
18102  "Ambiguity: %d standard stars found in FOV", selected);
18103  return 1;
18104  }
18105 
18106  standard = cpl_table_extract_selected(pol_sta);
18107 
18108  cpl_msg_info(cpl_func, "Standard star: %s",
18109  cpl_table_get_string(standard, "name", 0));
18110 
18111  /*
18112  * Check whether the star is polarised or not
18113  */
18114 
18115  polarised = cpl_table_get_int(standard, "polarised", 0, NULL);
18116 
18117  cpl_msg_info(cpl_func, "This star is%sexpected to be polarised",
18118  polarised ? " " : " not ");
18119 
18120 
18121  /*
18122  * Determine the image row with the smallest median noise: this
18123  * row is assumed to refer to the standard star.
18124  * (note: the higher the S/N ratio of the original spectra, the
18125  * smaller the noise of the Stokes parameters Q and U).
18126  */
18127 
18128  nx = cpl_image_get_size_x(q_error);
18129 
18130  noise = cpl_image_collapse_median_create(q_error, 1, 0, 0);
18131  cpl_image_get_minpos(noise, &col, &row);
18132 
18133  cpl_image_delete(noise);
18134 
18135  if (col != 1) {
18136  cpl_table_delete(standard);
18137  cpl_msg_error(cpl_func,
18138  "Assertion failure!!! col = %"CPL_SIZE_FORMAT" (it should be 1)", col);
18139  return 1;
18140  }
18141 
18142  q_signal = cpl_image_extract(q_image, 1, row, nx, row);
18143  q_noise = cpl_image_extract(q_error, 1, row, nx, row);
18144  u_signal = cpl_image_extract(u_image, 1, row, nx, row);
18145  u_noise = cpl_image_extract(u_error, 1, row, nx, row);
18146 
18147  q_sdata = cpl_image_get_data_double(q_signal);
18148  q_ndata = cpl_image_get_data_double(q_noise);
18149  u_sdata = cpl_image_get_data_double(u_signal);
18150  u_ndata = cpl_image_get_data_double(u_noise);
18151 
18152 
18153  /*
18154  * Determine valid interval in input images (where error is positive).
18155  */
18156 
18157  first = -1;
18158  last = nx = cpl_image_get_size_x(q_signal);
18159  for (i = 0; i < nx; i++) {
18160  if (first < 0) {
18161  if (q_ndata[i] > 0.0) {
18162  first = i;
18163  }
18164  }
18165  else {
18166  if (q_ndata[i] <= 0.0) {
18167  last = i - 1;
18168  break;
18169  }
18170  }
18171  }
18172 
18173  count = last - first + 1;
18174 
18175  if (first < 0 || count < band) {
18176  cpl_table_delete(standard);
18177  cpl_image_delete(q_signal);
18178  cpl_image_delete(q_noise);
18179  cpl_image_delete(u_signal);
18180  cpl_image_delete(u_noise);
18181  cpl_msg_warning(cpl_func, "Too short spectrum (%d pixels)", count);
18182  return 1;
18183  }
18184 
18185  center = (first + last) / 2; // Center of valid spectrum
18186  cwave = startwave + dispersion * center; // Corresponding wavelength
18187 
18188 
18189  /*
18190  * Find the band UBVRI closest to the central wavelength.
18191  */
18192 
18193  found = 0;
18194  for (i = 0; i < nbands; i++) {
18195  p_label[0] = bands[i];
18196  if (cpl_table_is_valid(standard, p_label, 0)) {
18197  if (found == 0) {
18198  found = 1;
18199  mindist = fabs(bwave[i] - cwave);
18200  closest = i;
18201  }
18202  else if (mindist > fabs(bwave[i] - cwave)) {
18203  mindist = fabs(bwave[i] - cwave);
18204  closest = i;
18205  }
18206  }
18207  }
18208 
18209  if (!found) {
18210  cpl_table_delete(standard);
18211  cpl_image_delete(q_signal);
18212  cpl_image_delete(q_noise);
18213  cpl_image_delete(u_signal);
18214  cpl_image_delete(u_noise);
18215  cpl_msg_warning(cpl_func, "No reference value available");
18216  return 1;
18217  }
18218 
18219  center = (bwave[closest] - startwave) / dispersion; // Center of band (pix)
18220  cwave = bwave[closest]; // Wavelength of band
18221 
18222 
18223  /*
18224  * Check that the integration interval is entirely contained
18225  * in the valid interval, or give it up.
18226  */
18227 
18228  pband = floor(band / dispersion); // Band width in pixels
18229 
18230  if (center - pband/2 < first || center + pband/2 > last) {
18231  cpl_table_delete(standard);
18232  cpl_image_delete(q_signal);
18233  cpl_image_delete(q_noise);
18234  cpl_image_delete(u_signal);
18235  cpl_image_delete(u_noise);
18236  cpl_msg_warning(cpl_func, "No reference value available");
18237  return 1;
18238  }
18239 
18240  first = center - pband/2;
18241  last = center + pband/2;
18242 
18243  /*
18244  * Collect reference values. Note that if angle info is not available,
18245  * angle stuff is set automaticaly to zero.
18246  */
18247 
18248  p_label[0] = bands[closest];
18249  dp_label[0] = bands[closest];
18250  a_label[0] = bands[closest];
18251  da_label[0] = bands[closest];
18252 
18253  p_ref = cpl_table_get(standard, p_label, 0, NULL);
18254  dp_ref = cpl_table_get(standard, dp_label, 0, NULL);
18255  a_ref = cpl_table_get(standard, a_label, 0, NULL);
18256  da_ref = cpl_table_get(standard, da_label, 0, NULL);
18257 
18258  cpl_msg_info(cpl_func,
18259  "The expected polarisation is %.2f +- %.2f %%",
18260  p_ref, dp_ref);
18261 
18262  if (polarised) {
18263  cpl_msg_info(cpl_func,
18264  "The expected polarisation angle is %.2f +- %.2f degrees",
18265  a_ref, da_ref);
18266  }
18267 
18268  /*
18269  * Find median signal and median error.
18270  */
18271 
18272  q_obs = cpl_image_get_median_window(q_image, first, 1, last, 1);
18273  q_err = cpl_image_get_median_window(q_error, first, 1, last, 1);
18274  u_obs = cpl_image_get_median_window(u_image, first, 1, last, 1);
18275  u_err = cpl_image_get_median_window(u_error, first, 1, last, 1);
18276 
18277  /*
18278  * Measured linear polarisation and its error
18279  */
18280 
18281  p_obs = sqrt(q_obs * q_obs + u_obs * u_obs);
18282  p_err = CPL_MATH_SQRT1_2 * 0.5 * (q_err + u_err);
18283 
18284  /*
18285  * Measured polarisation angle
18286  */
18287 
18288  a_obs = 0.0;
18289  if (polarised) {
18290  if (fabs(q_obs) < 0.00001) {
18291  if (u_obs > 0.0) {
18292  a_obs = 45.0;
18293  }
18294  else {
18295  a_obs = 135.0;
18296  }
18297  }
18298  else {
18299  a_obs = 0.5 * atan(u_obs / q_obs) * 180 / CPL_MATH_PI;
18300  if (q_obs > 0.0) {
18301  if (u_obs < 0.0) {
18302  a_obs += 180.;
18303  }
18304  }
18305  else {
18306  a_obs += 90.;
18307  }
18308  }
18309  }
18310 
18311  /*
18312  * Error on polarisation angle
18313  */
18314 
18315  a_err = 0.0;
18316  if (polarised) {
18317  a_err = sqrt(q_obs*q_obs*u_err*u_err + u_obs*u_obs*q_err*q_err)
18318  / (p_obs * p_obs)
18319  * 90 / CPL_MATH_PI;
18320  }
18321 
18322  p_obs *= 100;
18323  p_err *= 100;
18324  cpl_msg_info(cpl_func,
18325  "The measured polarisation is %.2f +- %.2f %%",
18326  p_obs, p_err);
18327 
18328  if (polarised) {
18329  cpl_msg_info(cpl_func,
18330  "The measured polarisation angle is %.2f +- %.2f degrees",
18331  a_obs, a_err);
18332  }
18333 
18334  *filter = bands[closest];
18335  *polarisation = polarised;
18336 
18337  if (polarised) {
18338  *p_offset = (p_obs - p_ref) / p_ref;
18339  *p_error = sqrt(p_err * p_err + dp_ref * dp_ref) / p_ref;
18340  }
18341  else {
18342  *p_offset = p_obs - p_ref;
18343  *p_error = sqrt(p_err * p_err + dp_ref * dp_ref);
18344  }
18345 
18346  *a_offset = a_obs - a_ref;
18347  *a_error = sqrt(a_err*a_err + da_ref*da_ref);
18348 
18349  return 0;
18350 
18351 }
18352 
18353 
18385 int mos_compute_offset(cpl_table *reference, cpl_table *objects, double *offset)
18386 {
18387  cpl_array *offsets;
18388  int noffset;
18389  int nslits = cpl_table_get_nrow(reference);
18390  int *nref;
18391  int *nobj;
18392  int corr, maxcorr;
18393  int best_shift;
18394  int i, j, k;
18395 
18396  cpl_error_code status = CPL_ERROR_NONE;
18397 
18398 
18399  *offset = 0.0;
18400 
18401  if (nslits != cpl_table_get_nrow(objects))
18402  return CPL_ERROR_INCOMPATIBLE_INPUT;
18403 
18404  nref = fors_get_nobjs_perslit(reference);
18405  nobj = fors_get_nobjs_perslit(objects);
18406 
18407  noffset = 0;
18408  for (i = 0; i < nslits; i++)
18409  noffset += nobj[i];
18410 
18411  if (noffset == 0) {
18412  cpl_free(nref);
18413  cpl_free(nobj);
18414  return CPL_ERROR_DATA_NOT_FOUND;
18415  }
18416 
18417  noffset = 0;
18418  for (i = 0; i < nslits; i++)
18419  noffset += nref[i];
18420 
18421  if (noffset == 0) {
18422  cpl_free(nref);
18423  cpl_free(nobj);
18424  return CPL_ERROR_DATA_NOT_FOUND;
18425  }
18426 
18427  offsets = cpl_array_new(noffset, CPL_TYPE_DOUBLE);
18428 
18429  noffset = 0; // The real number of offsets found will be counted.
18430 
18431  for (i = 0; i < nslits; i++) {
18432  if (nref[i] > 0 && nobj[i] > 0) {
18433  double shift;
18434  int length = cpl_table_get_int(objects, "length", i, NULL);
18435  double ytop = cpl_table_get_double(objects, "xtop", i, NULL);
18436  double ybottom = cpl_table_get_double(objects, "xbottom", i, NULL);
18437  int *aref = cpl_calloc(length, sizeof(int));
18438  int *aobj = cpl_calloc(length, sizeof(int));
18439  float *pref = cpl_calloc(nref[i], sizeof(float));
18440  float *pobj = cpl_calloc(nobj[i], sizeof(float));
18441 
18442  for (j = 0; j < nref[i]; j++) {
18443  pref[j] = fors_get_object_position(reference, i, j + 1);
18444  aref[(int)pref[j]] = 1;
18445  }
18446 
18447  for (j = 0; j < nobj[i]; j++) {
18448  pobj[j] = fors_get_object_position(objects, i, j + 1);
18449  aobj[(int)pobj[j]] = 1;
18450  }
18451 
18452  /*
18453  * Do not consider objects at border
18454  */
18455 
18456  aref[0] = 0;
18457  aref[length - 1] = 0;
18458  aobj[0] = 0;
18459  aobj[length - 1] = 0;
18460 
18461 //for (j = 0; j < nref[i]; j++)
18462 //printf("references: %f, ", pref[j]);
18463 //printf("\n");
18464 //for (j = 0; j < nref[i]; j++)
18465 //printf("objects : %f, ", pobj[j]);
18466 //printf("\n");
18467 //for (j = 0; j < length; j++)
18468 //printf("%d", aref[j]);
18469 //printf("\n");
18470 //for (j = 0; j < length; j++)
18471 //printf("%d", aobj[j]);
18472 //printf("\n");
18473 
18474  /*
18475  * Object matching by correlation
18476  */
18477 
18478  maxcorr = 0;
18479  best_shift = length;
18480 
18481  for (shift = length/2, j = 0; j <= length; shift--, j++) {
18482  int rstart, ostart, count;
18483 
18484  if (shift > 0) {
18485  rstart = shift;
18486  ostart = 0;
18487  count = length - shift;
18488  }
18489  else {
18490  rstart = 0;
18491  ostart = -shift;
18492  count = length + shift;
18493  }
18494 
18495  corr = 0;
18496  for (k = 0; k < count; k++) {
18497  corr += aref[rstart + k] * aobj[ostart + k];
18498  }
18499 
18500  if (maxcorr < corr) {
18501  maxcorr = corr;
18502  best_shift = shift;
18503  }
18504  }
18505 
18506  if (best_shift == length) { // No shift found
18507 //printf("%d: No shift found\n", i);
18508  cpl_free(aref);
18509  cpl_free(aobj);
18510  cpl_free(pref);
18511  cpl_free(pobj);
18512  continue;
18513  }
18514 //printf("%d: Integer shift found = %d\n", i, best_shift);
18515 
18516  for (j = 0; j < nref[i]; j++) {
18517  for (k = 0; k < nobj[i]; k++) {
18518  if (fabs(pref[j] - pobj[k] - best_shift) < 2) {
18519  double ccd_offset = (pref[j] - pobj[k])
18520  * (ytop - ybottom)
18521  / length;
18522 
18523 //printf("%d: Match found: %f\n", i, ccd_offset);
18524  /*
18525  * The matching object is found, store the
18526  * corresponding offset
18527  */
18528 
18529  cpl_array_set(offsets, noffset, ccd_offset);
18530  noffset++;
18531  break;
18532  }
18533  }
18534  }
18535 
18536  cpl_free(aref);
18537  cpl_free(aobj);
18538  cpl_free(pref);
18539  cpl_free(pobj);
18540  }
18541 //else
18542 //printf("%d: No object found\n", i);
18543  }
18544 
18545  cpl_free(nref);
18546  cpl_free(nobj);
18547 
18548 //printf("%d offsets found in total\n", noffset);
18549  if (noffset > 0) {
18550  if (noffset % 2) {
18551  *offset = cpl_array_get_median(offsets);
18552  }
18553  else {
18554  double *a = cpl_malloc(sizeof(double) * noffset);
18555  for (i = 0; i < noffset; i++) {
18556  a[i] = cpl_array_get_double(offsets, i, NULL);
18557  }
18558  *offset = (fors_tools_get_kth_double(a, noffset, (noffset-1)/2) +
18559  fors_tools_get_kth_double(a, noffset, (noffset/2))) / 2.0;
18560  cpl_free(a);
18561  }
18562  }
18563  else
18564  status = CPL_ERROR_DATA_NOT_FOUND;
18565 //printf("Median offset: %f\n", *offset);
18566 
18567  cpl_array_delete(offsets);
18568 
18569  return status;
18570 
18571 }
18572 
18573 
18585 cpl_error_code mos_image_shift(cpl_image *image, double dx, double dy)
18586 {
18587  cpl_image *source;
18588  int nx = cpl_image_get_size_x(image);
18589  int ny = cpl_image_get_size_y(image);
18590  float *idata;
18591  float *sdata;
18592  int i, j, pos;
18593  double xpos, ypos, xfrac, yfrac;
18594  int xint, yint;
18595 
18596 
18597  if (fabs(dx) >= nx || fabs(dy) >= ny)
18598  return CPL_ERROR_ACCESS_OUT_OF_RANGE;
18599 
18600  source = cpl_image_duplicate(image);
18601  idata = cpl_image_get_data_float(image);
18602  sdata = cpl_image_get_data_float(source);
18603 
18604  /*
18605  * Shift in y
18606  */
18607 
18608  yfrac = - dy - floor(- dy);
18609  xfrac = - dx - floor(- dx);
18610 
18611  for (pos = 0, j = 0; j < ny; j++) {
18612  ypos = j - dy;
18613  yint = floor(ypos);
18614  for (i = 0; i < nx; i++) {
18615  xpos = i - dx;
18616  xint = floor(xpos);
18617  if (xint < 0 || yint < 0 || xint > nx - 2 || yint > ny - 2) {
18618  idata[pos] = 0.0;
18619  }
18620  else {
18621  idata[pos] = sdata[xint + nx*yint] * (1 - xfrac) * (1 - yfrac)
18622  + sdata[xint + 1 + nx*yint] * xfrac * (1 - yfrac)
18623  + sdata[xint + nx*(yint + 1)] * (1 - xfrac) * yfrac
18624  + sdata[xint + 1 + nx*(yint + 1)] * xfrac * yfrac;
18625  }
18626  pos++;
18627  }
18628  }
18629 
18630  cpl_image_delete(source);
18631 
18632  return CPL_ERROR_NONE;
18633 }
18634 
18646 int mos_slit_closest_to_center(cpl_table *slits, int nx, int ny)
18647 {
18648 #ifdef CPL_SIZE_FORMAT
18649  cpl_size row;
18650 #else
18651  int row;
18652 #endif
18653 
18654  cpl_table_duplicate_column(slits, "x", slits, "xtop");
18655  cpl_table_add_columns(slits, "x", "xbottom");
18656  cpl_table_divide_scalar(slits, "x", 2); // Mean x position
18657  cpl_table_subtract_scalar(slits, "x", nx/2); // Relative to CCD center
18658  cpl_table_multiply_columns(slits, "x", "x"); // Squared
18659 
18660  cpl_table_duplicate_column(slits, "y", slits, "ytop");
18661  cpl_table_add_columns(slits, "y", "ybottom");
18662  cpl_table_divide_scalar(slits, "y", 2); // Mean y position
18663  cpl_table_subtract_scalar(slits, "y", ny/2); // Relative to CCD center
18664  cpl_table_multiply_columns(slits, "y", "y"); // Squared
18665 
18666  cpl_table_add_columns(slits, "x", "y"); // Distance from center
18667  cpl_table_get_column_minpos(slits, "x", &row); // Min distance from center
18668 
18669  cpl_table_erase_column(slits, "x");
18670  cpl_table_erase_column(slits, "y");
18671 
18672  return row;
18673 }
18674 
18694 cpl_error_code mos_extract_flux(cpl_image *image, cpl_table *slits,
18695  double xwidth, double ywidth,
18696  int dx, double gain, double *o_flux, double *o_err)
18697 {
18698  int nx = cpl_image_get_size_x(image);
18699  int ny = cpl_image_get_size_y(image);
18700  int slit = mos_slit_closest_to_center(slits, nx, ny);
18701  int ytop = (int)cpl_table_get(slits, "ytop", slit, NULL);
18702  int ybottom = (int)cpl_table_get(slits, "ybottom", slit, NULL);
18703  int dy = ytop - ybottom;
18704  int xcenter = (int)((cpl_table_get(slits, "xtop", slit, NULL) +
18705  cpl_table_get(slits, "xbottom", slit, NULL)) / 2);
18706  int xleft = xcenter - dx;
18707  int xright = xcenter + dx + 1;
18708  double area = xwidth * ywidth; // squared mm
18709  int npix = (2*dx + 1) * dy;
18710  int count = 0;
18711  float *data = cpl_image_get_data_float(image);
18712  double flux = 0.0;
18713  double error = 0.0;
18714  int satur = 60000;
18715  int x, y;
18716 
18717 
18718  if (cpl_table_has_column(slits, "ywidth")) {
18719  area = cpl_table_get(slits, "xwidth", slit, NULL)
18720  * cpl_table_get(slits, "ywidth", slit, NULL);
18721  }
18722 
18723  *o_flux = 0.0;
18724  *o_err = 0.0;
18725 
18726  if (xleft < 0)
18727  xleft = 0;
18728 
18729  if (xleft > nx)
18730  xleft = nx;
18731 
18732  if (xright < 0)
18733  xright = 0;
18734 
18735  if (xright > nx)
18736  xright = nx;
18737 
18738  if (ytop < 0)
18739  ytop = 0;
18740 
18741  if (ytop > ny)
18742  ytop = ny;
18743 
18744  if (ybottom < 0)
18745  ybottom = 0;
18746 
18747  if (ybottom > ny)
18748  ybottom = ny;
18749 
18750  count = (xright - xleft) * (ytop - ybottom);
18751 
18752  if (count == 0)
18753  return CPL_ERROR_ACCESS_OUT_OF_RANGE;
18754 
18755  count = 0;
18756 
18757  for (y = ybottom; y < ytop; y++) {
18758  for (x = xleft; x < xright; x++) {
18759  double value = data[x + y * nx];
18760  if (value < satur) {
18761  flux += value;
18762  count++;
18763  }
18764  }
18765  }
18766 
18767  if (count == 0)
18768  return CPL_ERROR_DIVISION_BY_ZERO;
18769 
18770  error = sqrt(flux/gain);
18771 
18772  /*
18773  * Flux correction for lost pixels
18774  */
18775 
18776  flux *= (float)npix / count;
18777  error *= (float)npix / count;
18778 
18779  flux /= area;
18780  error /= area;
18781 
18782  *o_flux = flux;
18783  *o_err = error;
18784 
18785  return CPL_ERROR_NONE;
18786 }
18787 
18788 
18811 cpl_error_code mos_extract_flux_mapped(cpl_image *image, cpl_table *slits,
18812  double xwidth, double ywidth,
18813  double lambda, double startwave,
18814  double dispersion, int dx, double gain,
18815  double *o_flux, double *o_err)
18816 {
18817  int nx = cpl_image_get_size_x(image);
18818  int ny = cpl_image_get_size_y(image);
18819  int slit = mos_slit_closest_to_center(slits, nx, ny);
18820  int dy = (int)cpl_table_get(slits, "length", slit, NULL);
18821  int ybottom = (int)cpl_table_get(slits, "position", slit, NULL);
18822  int ytop = ybottom + dy;
18823  int xcenter = (int)floor((lambda - startwave) / dispersion + 0.5);
18824  int xleft = xcenter - dx;
18825  int xright = xcenter + dx + 1;
18826  double area = xwidth * ywidth;
18827  int npix = (2*dx + 1) * dy;
18828  int count = 0;
18829  float *data = cpl_image_get_data_float(image);
18830  double flux = 0.0;
18831  double error = 0.0;
18832  int satur = 60000;
18833  int x, y;
18834 
18835 
18836  if (cpl_table_has_column(slits, "ywidth")) {
18837  area = cpl_table_get(slits, "xwidth", slit, NULL)
18838  * cpl_table_get(slits, "ywidth", slit, NULL);
18839  }
18840 
18841  *o_flux = 0.0;
18842  *o_err = 0.0;
18843 
18844  if (xleft < 0)
18845  xleft = 0;
18846 
18847  if (xleft > nx)
18848  xleft = nx;
18849 
18850  if (xright < 0)
18851  xright = 0;
18852 
18853  if (xright > nx)
18854  xright = nx;
18855 
18856  if (ytop < 0)
18857  ytop = 0;
18858 
18859  if (ytop > ny)
18860  ytop = ny;
18861 
18862  if (ybottom < 0)
18863  ybottom = 0;
18864 
18865  if (ybottom > ny)
18866  ybottom = ny;
18867 
18868  count = (xright - xleft) * (ytop - ybottom);
18869 
18870  if (count == 0)
18871  return CPL_ERROR_ACCESS_OUT_OF_RANGE;
18872 
18873  count = 0;
18874 
18875  for (y = ybottom; y < ytop; y++) {
18876  for (x = xleft; x < xright; x++) {
18877  double value = data[x + y * nx];
18878  if (value < satur) {
18879  flux += value;
18880  count++;
18881  }
18882  }
18883  }
18884 
18885  if (count == 0)
18886  return CPL_ERROR_DIVISION_BY_ZERO;
18887 
18888  error = sqrt(flux/gain);
18889 
18890  /*
18891  * Flux correction for lost pixels
18892  */
18893 
18894  flux *= (float)npix / count;
18895  error *= (float)npix / count;
18896 
18897  flux /= area;
18898  error /= area;
18899 
18900  *o_flux = flux;
18901  *o_err = error;
18902 
18903  return CPL_ERROR_NONE;
18904 
18905 }
18906 
18907 
18921 int mos_median_in_slit(cpl_table *table, cpl_table *slits, int slit,
18922  char *label, double *mvalue)
18923 {
18924  int position = cpl_table_get_int(slits, "position", slit, NULL);
18925  int length = cpl_table_get_int(slits, "length", slit, NULL);
18926  cpl_table *tmp = cpl_table_extract(table, position, length);
18927 
18928  *mvalue = cpl_table_get_column_median(tmp, label);
18929  cpl_table_delete(tmp);
18930 
18931  if (cpl_error_get_code() != CPL_ERROR_NONE)
18932  return 1;
18933 
18934  return 0;
18935 }
18936 
18937 
18949 cpl_image *mos_image_filter_median(cpl_image *image, int nx, int ny)
18950 {
18951  cpl_mask *kernel = cpl_mask_new(nx, ny);
18952  cpl_image *filtered = cpl_image_new(cpl_image_get_size_x(image),
18953  cpl_image_get_size_y(image),
18954  cpl_image_get_type(image));
18955 
18956  cpl_mask_not(kernel);
18957  cpl_image_filter_mask(filtered, image, kernel,
18958  CPL_FILTER_MEDIAN, CPL_BORDER_FILTER);
18959  cpl_mask_delete(kernel);
18960 
18961  return filtered;
18962 }
18963 
18964 int fors_mos_is_lss_like(cpl_table *maskslits, int nslits_out_det)
18965 {
18966  int treat_as_lss = 1;
18967  double mxpos = cpl_table_get_column_median(maskslits, "xtop");
18968  double * slit_xpos = cpl_table_get_data_double(maskslits, "xtop");
18969  cpl_size nslits = cpl_table_get_nrow(maskslits);
18970 
18971  //If not all the slits are illuminated, then we cannot say that
18972  //it is lss-like (PIPE-4380)
18973  if(nslits_out_det != 0)
18974  return 0;
18975 
18976  cpl_msg_warning(cpl_func, "Number of slits %"CPL_SIZE_FORMAT, nslits);
18977  for (cpl_size i = 0; i < nslits; i++) {
18978  if (fabs(mxpos-slit_xpos[i]) > 0.01) {
18979  treat_as_lss = 0;
18980  break;
18981  }
18982  }
18983  return treat_as_lss;
18984 }
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:8264
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:17204
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:1845
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:10891
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:14473
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:4085
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:3976
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:12182
cpl_error_code mos_interpolate_wavecalib(cpl_table *idscoeff, cpl_image *wavemap, int mode, int degree)
Interpolate LSS wavelength calibration.
Definition: moses.c:3061
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:5221
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:10754
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:17849
cpl_table * mos_load_slits_fors_mxu(cpl_propertylist *header)
Create slit location table from FITS header of FORS2-MXU data.
Definition: moses.c:14561
cpl_image ** mos_extract_objects(cpl_image *science, cpl_image *sky, cpl_table *objects, int extraction, double ron, double gain, int ncombined)
Extract detected objects from rectified scientific frame.
Definition: moses.c:14002
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:2290
int mos_get_nobjects(cpl_table *slits)
Get the total number of objects detected in a slits table.
Definition: moses.c:16730
cpl_error_code mos_rotate_slits(cpl_table *slits, int rotation, int nx, int ny)
Rotate a slit location table.
Definition: moses.c:6074
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:11087
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:9412
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:6188
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:11849
cpl_image * mos_sky_local(cpl_image *spectra, cpl_table *slits, int order)
Local determination of sky.
Definition: moses.c:12526
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:14801
double mos_get_gain_vimos(cpl_propertylist *header)
Return gain factor for a VIMOS exposure.
Definition: moses.c:15171
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:9693
cpl_error_code mos_validate_slits(cpl_table *slits)
Check validity of a slit location table.
Definition: moses.c:6010
cpl_error_code mos_subtract_background(cpl_image *image)
Subtract the background.
Definition: moses.c:16079
int mos_get_maxobjs_per_slit(cpl_table *slits)
Get the maximum possible number of objects in a slit.
Definition: moses.c:16704
cpl_error_code mos_interpolate_wavecalib_slit(cpl_table *idscoeff, cpl_table *slits, int order, int global)
Interpolate MOS wavelength calibration.
Definition: moses.c:2932
cpl_image * mos_remove_bias(cpl_image *image, cpl_image *bias, cpl_table *overscans)
Subtract the bias from a CCD exposure.
Definition: moses.c:3422
cpl_table * mos_poly_trace(cpl_table *slits, cpl_table *traces, int order)
Fit spectral traces.
Definition: moses.c:7922
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:13651
cpl_image * mos_sky_local_old(cpl_image *spectra, cpl_table *slits)
Local determination of sky.
Definition: moses.c:12424
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:18027
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:4829
cpl_error_code mos_image_shift(cpl_image *image, double dx, double dy)
Shift values in an image.
Definition: moses.c:18585
int mos_check_multiplex(cpl_table *slits)
Determining whether a VIMOS mask has spectral multplexing or not.
Definition: moses.c:15327
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:18694
int mos_lines_width(const float *spectrum, int length)
Estimate lines widths (in pixel) in arc lamp spectrum.
Definition: moses.c:3770
cpl_error_code mos_global_trace(cpl_table *slits, cpl_table *polytraces, int mode)
Recompute tracing coefficients globally.
Definition: moses.c:8083
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:5041
cpl_vector * mos_peak_candidates(const float *spectrum, int length, float level, float exp_width)
Find positions of peaks candidates.
Definition: moses.c:3873
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:16109
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:10246
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:1969
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:10976
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:4774
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:7488
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:8669
int mos_check_slits(cpl_table *slits, float rescale)
Check that all slit have been detected, insert them if not.
Definition: moses.c:16761
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:17756
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:15921
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:17681
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:12881
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:1179
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:14205
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:3595
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:14401
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:16864
cpl_table * mos_load_slits_vimos(cpl_propertylist *header)
Create slit location table from FITS header of VIMOS data.
Definition: moses.c:15216
cpl_error_code mos_randomise_image(cpl_image *image, double ron, double gain, double bias)
Randomise image.
Definition: moses.c:15877
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:1690
cpl_table * mos_locate_spectra(cpl_mask *mask)
Find the location of detected spectra on the CCD.
Definition: moses.c:5922
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:15003
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:4989
cpl_image * mos_arc_background(cpl_image *image, int msize, int fsize)
Background determination on emission line spectrum (arc)
Definition: moses.c:3697
cpl_error_code mos_saturation_process(cpl_image *image)
Process saturation.
Definition: moses.c:16013
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:11437
int mos_slit_closest_to_center(cpl_table *slits, int nx, int ny)
Return slit closest to CCD center.
Definition: moses.c:18646
cpl_image * mos_image_filter_median(cpl_image *image, int nx, int ny)
Convenience function for standard median filtering.
Definition: moses.c:18949
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:13385
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:18811
int mos_compute_offset(cpl_table *reference, cpl_table *objects, double *offset)
Estimate offset between two object tables.
Definition: moses.c:18385
cpl_image * mos_normalise_longflat(cpl_image *flat, int sradius, int dradius, int polyorder)
Normalise a long slit flat field exposure.
Definition: moses.c:2741
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:15426
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:18921
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:1543