FORS Pipeline Reference Manual  5.0.9
moses.c
1 /* $Id: moses.c,v 1.116 2013-10-15 09:27:38 cgarcia Exp $
2  *
3  * This file is part of the MOSES library
4  * Copyright (C) 2002-2010 European Southern Observatory
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
19  */
20 
21 /*
22  * $Author: cgarcia $
23  * $Date: 2013-10-15 09:27:38 $
24  * $Revision: 1.116 $
25  * $Name: not supported by cvs2svn $
26  */
27 
28 #ifdef HAVE_CONFIG_H
29 #include <config.h>
30 #endif
31 
32 #include <stdio.h>
33 #include <stdlib.h>
34 #include <string.h>
35 #include <unistd.h>
36 #include <math.h>
37 #include <time.h>
38 
39 #include <fors_utils.h>
40 #include <moses.h>
41 
42 /* Prototypes */
43 static cpl_polynomial *read_global_distortion(cpl_table *global, cpl_size row);
44 
45 
46 /* Cheating: moving here the cpl_tools_get_median_float() prototype,
47  * even if cpl_tool.h is not public. It should be removed as soon as
48  * an image median filtering with generic kernel will be implemented
49  * in the CPL, or as soon as this module will be moved into the CPL. */
50 /* FIXME: This has to be removed!! */
51 
52 float cpl_tools_get_median_float(float *, cpl_size);
53 
54 #define MAX_COLNAME (80)
55 #define STRETCH_FACTOR (1.20)
56 
57 // Related to mos_identify_peaks(), used in multiplex mode
58 
59 static int mos_multiplex = -1;
60 static int mos_region_size = 800;
61 
62 static double default_lines_hi[] = { /* Default sky line catalog */
63  5577.338, /* for high res data */
64  5889.953,
65  5895.923,
66  5915.301,
67  5932.862,
68  5953.420,
69  6257.961,
70  6287.434,
71  6300.304,
72  6306.869,
73  6363.780,
74  6498.729,
75  6533.044,
76  6553.617,
77  6841.945,
78  6863.955,
79  6870.994,
80  6889.288,
81  6900.833,
82  6912.623,
83  6923.220,
84  6939.521,
85  6969.930,
86  7003.858,
87  7244.907,
88  7276.405,
89  7284.439,
90  7316.282,
91  7329.148,
92  7340.885,
93  7358.659,
94  7571.746,
95  7750.640,
96  7759.996,
97  7794.112,
98  7808.467,
99  7821.503,
100  7841.266,
101  7913.708,
102  7949.204,
103  7964.650,
104  7993.332,
105  8014.059,
106  8310.719,
107  8344.602,
108  8382.392,
109  8399.170,
110  8415.231,
111  8430.174,
112  8452.250,
113  8493.389,
114  8791.186,
115  8827.096,
116  8885.850,
117  8903.114,
118  8943.395,
119  8988.366
120  };
121 
122 static double default_lines_lo[] = { /* Default sky line catalog */
123  5577.338, /* for low res data */
124  6300.304,
125  6863.955,
126  7571.746,
127  7964.650,
128  7993.332
129  };
130 
131 
141 /*
142  * The following macros and function for finding the k-th smallest
143  * value on a float array will be accessible from cpl_tools once
144  * this module will be moved into the CPL.
145  */
146 
147 /****
148 
149 #define PIX_SWAP(a,b) { register float t=(a);(a)=(b);(b)=t; }
150 
151 static float kthSmallest(float a[], int n, int k)
152 {
153  register int i,j,l,m;
154  register float x;
155 
156  l = 0;
157  m = n-1;
158  while (l < m) {
159  x = a[k];
160  i = l;
161  j = m;
162  do {
163  while (a[i] < x) {
164  i++;
165  }
166  while (x < a[j]) {
167  j--;
168  }
169  if (i <= j) {
170  PIX_SWAP(a[i],a[j]);
171  i++;
172  j--;
173  }
174  } while (i <= j);
175 
176  if (j < k) {
177  l = i;
178  }
179  if (k < i) {
180  m = j;
181  }
182 
183  }
184  return(a[k]);
185 }
186 
187 #define medianWirth(a, n) kthSmallest(a, n, (((n)&1) ? ((n)/2) : (((n)/2)-1)))
188 
189 ****/
190 
191 /*
192  * Return random number with gaussian distribution (mean = 0, variance = 1)
193  * (Box-Mueller method). The mos_randg() argument is either true or false,
194  * indicating whether to "seed" or not the sequence of generated random
195  * numbers. The "seeding" is performed just at the first mos_randg(1) call,
196  * and at further calls the input argument is ignored. This function
197  * generates two random numbers at each call, returning the first one
198  * at odd calls, and the second one at even calls.
199  */
200 
201 static void mos_seed(void)
202 {
203  srand((unsigned int)time((time_t *)0));
204 }
205 
206 static double mos_randg(int seme)
207 {
208  static int doit = 1;
209  static int gotit = 1;
210  double x1, x2, w, y1;
211  static double y2;
212 
213  if (gotit && seme) {
214  mos_seed();
215  gotit = 0;
216  }
217 
218  if (doit) {
219  doit = 0;
220  do {
221  x1 = 2.0 * (double)rand() / RAND_MAX - 1.0;
222  x2 = 2.0 * (double)rand() / RAND_MAX - 1.0;
223  w = x1 * x1 + x2 * x2;
224  } while (w >= 1.0 || w == 0.0);
225 
226  w = sqrt( (-2.0 * log(w)) / w);
227 
228  y1 = x1 * w;
229  y2 = x2 * w;
230  return y1;
231  }
232 
233  doit = 1;
234  return y2;
235 }
236 
237 /*
238  * This function contained a dependency on the VIMOS library
239  * (function medianPixelvalue()): it should be removed as soon as an
240  * image median filtering with generic kernel will be implemented
241  * in the CPL. Currently it has been solved by a direct call to
242  * a cpl_tool function.
243  */
244 
245 static cpl_image *mos_image_vertical_median_filter(cpl_image *ima_in,
246  int filtsizey, int refrow,
247  int above, int below, int step)
248 {
249 
250  const char *func = "mos_image_general_median_filter";
251 
252  cpl_image *filt_img = NULL;
253  int col, row;
254  float *buf = NULL;
255  float *data;
256  float *fdata;
257  int upright_y, loleft_y;
258  int j;
259  int yIsEven = !(filtsizey - (filtsizey/2)*2);
260  int f2y;
261  int nx = cpl_image_get_size_x(ima_in);
262  int ny = cpl_image_get_size_y(ima_in);
263  int firstRow;
264 
265 
266  if (yIsEven) filtsizey++;
267 
268  if (ny <= filtsizey) {
269  cpl_msg_error(func,
270  "Median filter size: %d, image size: %d", filtsizey, ny);
271  return NULL;
272  }
273 
274  f2y = filtsizey / 2;
275 
276  filt_img = cpl_image_duplicate(ima_in);
277  buf = cpl_malloc(filtsizey * sizeof(float));
278  data = cpl_image_get_data(ima_in);
279  fdata = cpl_image_get_data(filt_img);
280 
281  firstRow = refrow - step * (below / step);
282  if (firstRow < f2y)
283  firstRow += step;
284 
285  for (col = 0; col < nx; col++) {
286  for (row = firstRow; row < refrow + above; row += step) {
287  if (row >= ny - f2y)
288  break;
289  loleft_y = row - f2y;
290  upright_y = row + f2y + 1;
291  for (j = loleft_y; j < upright_y; j++)
292  buf[j - loleft_y] = data[col + j * nx];
293 
294  fdata[col + row * nx] = cpl_tools_get_median_float(buf, filtsizey);
295  }
296  }
297 
298  cpl_free(buf);
299 
300  return filt_img;
301 
302 }
303 
304 
305 /*
306  * The following static function is used to find an accurate position
307  * of a peak within a short interval (however at least 5 pixels long).
308  * The basic idea is to find the baricenter of all the pixel values
309  * that pass a threshold level between the median value and the maximum
310  * value within the examined interval (in case such levels are equal,
311  * the input is considered flat and no position is returned). At least
312  * minPoints must pass this threshold, or no position is computed. To
313  * evaluate the significance of the computed baricenter, the variance
314  * of the contributing positions (relative to the found baricenter) is
315  * also evaluated, and compared with the expected variance for a uniform
316  * distribution of positions. If the observed variance is greater than
317  * 80% of the variance of the uniform distribution, the found position
318  * is rejected.
319  */
320 
321 static int peakPosition(const float *data, int size, float *position,
322  int minPoints)
323 {
324  int i;
325  int count = 0;
326  float *copy;
327  float max, median, level, pos, variance, uniformVariance;
328  double sum, weights;
329 
330 
331  if (data == NULL)
332  return 1;
333 
334  if (size < 5) /* Hardcoded, I know... */
335  return 1;
336 
337 
338  /*
339  * Find median level
340  */
341 
342  copy = (float *) cpl_malloc(size*sizeof(float));
343  for (i = 0; i < size; i++)
344  copy[i] = data[i];
345  median = cpl_tools_get_median_float(copy, size);
346  cpl_free(copy);
347 
348 
349  /*
350  * Find max
351  */
352 
353  max = data[0];
354  for (i = 1; i < size; i++)
355  if (data[i] > max)
356  max = data[i];
357 
358 
359  /*
360  * If the max equals the median we have a flat input, therefore
361  * no peak is found.
362  */
363 
364  if (max-median < 0.00001)
365  return 1;
366 
367 
368  /*
369  * Discrimination level: only pixels with values above this
370  * level are considered in baricenter calculation.
371  */
372 
373  level = (max + median) / 2;
374 
375 
376  /*
377  * Of the values above this level compute the baricenter and
378  * then the variance of the positions used. Note that the weights
379  * are taken as the difference between the pixels values and
380  * the median level (supposedly the background).
381  */
382 
383  count = 0;
384  for (i = 0, sum = 0., weights = 0.; i < size; i++) {
385  if (data[i] > level) {
386  count++;
387  weights += (data[i] - median);
388  sum += i * (data[i] - median);
389  }
390  }
391 
392 
393  /*
394  * If too few values are above threshold, refuse the position
395  * as insignificant
396  */
397 
398  if (count < minPoints)
399  return 1;
400 
401  pos = sum / weights;
402  for (i = 0, sum = 0., weights = 0.; i < size; i++) {
403  if (data[i] > level) {
404  weights++;
405  sum += (i - pos) * (i - pos);
406  }
407  }
408  variance = sqrt(sum / weights);
409 
410 
411  /*
412  * The "uniform variance" is the variance that should be obtained
413  * in the case of uniform distribution of the points positions in
414  * the selected interval. If the real variance is comparable with
415  * this value, the peak is considered not found.
416  */
417 
418  uniformVariance = sqrt(size*size/3 - pos*size + pos*pos);
419 
420  if (variance > 0.8 * uniformVariance)
421  return 1;
422 
423  *position = pos + 0.5;
424 
425  return 0;
426 }
427 
428 
429 /*
430  * The following static function determines the quantity dx to be
431  * added to the position of the highest pixel of a fiber profile,
432  * to get the true position of the profile maximum. All is needed
433  * is the maximum observed value v2 in the profile, and the observed
434  * values v1 and v3 of the previous and the next pixels in the profile.
435  *
436  * The following ratio is defined:
437  *
438  * R = 0.5 (v3 - v1) / (2*v2 - v3 - v1)
439  *
440  * This is a conventional ratio that wouldn't diverge for any set of
441  * pixel values, and that would not depend on the presence of background
442  * (with the assumption that the background level is the same for the
443  * three pixels). R has also been chosen in such a way that its value
444  * is already quite close to the real dx. It should be noted that the
445  * following condition should be fulfilled:
446  *
447  * v1 <= v2 and v3 < v2
448  * or
449  * v1 < v2 and v3 <= v2
450  *
451  * This implies that dx varies between -0.5 and 0.5 pixels. In such
452  * boundary cases, one has:
453  *
454  * v2 = v1 and R = dx = -0.5
455  * v2 = v3 and R = dx = 0.5
456  *
457  * Another special case is when the observed pixel values are perfectly
458  * symmetrical:
459  *
460  * v1 = v3 and R = dx = 0.0
461  *
462  * In all the intermediate cases the relation between R and dx depends
463  * on the shape of the fiber profile, that has been determined elsewhere.
464  * Using the accurate reconstruction of the fiber profile obtained by
465  * the * functions ifuProfile() and rebinProfile(), it can be shown
466  * that R differs from dx always less than 0.01 pixels. If the condition
467  *
468  * v1 <= v2 and v3 < v2
469  * or
470  * v1 < v2 and v3 <= v2
471  *
472  * is not fulfilled, then this function returns the value 2.0.
473  */
474 
475 static double values_to_dx(double v1, double v2, double v3)
476 {
477 
478  static double epsilon = 0.00000001;
479  double r = 2.0;
480 
481 
482  if (v1 > v2 || v3 > v2)
483  return r;
484 
485  if (2 * v2 - v1 - v3 < epsilon)
486  return r;
487 
488  r = 0.5 * (v3 - v1) / (2 * v2 - v3 - v1);
489 
490  return r;
491 
492 }
493 
494 
495 /*
496  * The following static function passes a min filter of given box
497  * size on the data buffer. The box size must be a positive odd integer.
498  */
499 
500 static float *min_filter(float *buffer, int length, int size)
501 {
502  float *minf = cpl_calloc(length, sizeof(float));
503  float min;
504  int start = size / 2;
505  int end = length - size / 2;
506  int i, j;
507 
508 
509  for (i = start; i < end; i++) {
510  min = buffer[i-start];
511  for (j = i - start + 1; j <= i + start; j++)
512  if (min > buffer[j])
513  min = buffer[j];
514  minf[i] = min;
515  }
516 
517  for (i = 0; i < start; i++)
518  minf[i] = minf[start];
519 
520  for (i = end; i < length; i++)
521  minf[i] = minf[end-1];
522 
523  return minf;
524 }
525 
526 
527 /*
528  * The following static function passes a max filter of given box
529  * size on the data buffer. The box size must be a positive odd integer.
530  */
531 
532 static float *max_filter(float *buffer, int length, int size)
533 {
534  float *maxf = cpl_calloc(length, sizeof(float));
535  float max;
536  int start = size / 2;
537  int end = length - size / 2;
538  int i, j;
539 
540 
541  for (i = start; i < end; i++) {
542  max = buffer[i-start];
543  for (j = i - start + 1; j <= i + start; j++)
544  if (max < buffer[j])
545  max = buffer[j];
546  maxf[i] = max;
547  }
548 
549  for (i = 0; i < start; i++)
550  maxf[i] = maxf[start];
551 
552  for (i = end; i < length; i++)
553  maxf[i] = maxf[end-1];
554 
555  return maxf;
556 }
557 
558 
559 /*
560  * The following static function passes a running average of given box
561  * size on the data buffer. The box size must be a positive odd integer.
562  */
563 
564 static float *smo_filter(float *buffer, int length, int size)
565 {
566  float *smof = cpl_calloc(length, sizeof(float));
567  double sum;
568  int start = size / 2;
569  int end = length - size / 2;
570  int i, j;
571 
572 
573  for (i = start; i < end; i++) {
574  sum = 0.0;
575  for (j = i - start; j <= i + start; j++)
576  sum += buffer[j];
577  smof[i] = sum / size;
578  }
579 
580  for (i = 0; i < start; i++)
581  smof[i] = smof[start];
582 
583  for (i = end; i < length; i++)
584  smof[i] = smof[end-1];
585 
586  return smof;
587 }
588 
589 /*
590  * The following two static functions are used to read and write from the
591  * global distortion table the different model components. Conventionally
592  * the table consists of 6 columns and 10 rows. Each row is just ordered
593  * storage for model coefficients, and these functions guarantee that the
594  * coefficients are read in and written out correctly, independent on their
595  * physical meaning. The first 6 table rows are a description of the IDS
596  * coefficients, followed by a row containing only the used reference
597  * wavelength. The remaining 3 are a description of the spectral curvature.
598  * The first row is a description of coefficient c0, the second of coefficient
599  * c1, etc., of the IDS. The 8th row is a description of coefficient c0,
600  * the 9th of coefficient c1, etc., of the spectral curvature. All are
601  * bivariate polynomialx on x,y mask coordinates. If the input table
602  * to the write routine is NULL, it is allocated and initialised. Also
603  * the input polynomial could be NULL, and nothing would be written to
604  * the table. If both pointers are NULL the function is basically a
605  * constructor of the global distortion table.
606  */
607 
608 static cpl_polynomial *read_global_distortion(cpl_table *global, cpl_size row)
609 {
610  cpl_polynomial *poly = NULL;
611  cpl_size p[2];
612  cpl_size degree = 2;
613  int null;
614  double coeff;
615 
616  char name[MAX_COLNAME];
617 
618 
619  for (p[0] = 0; p[0] <= degree; p[0]++) {
620  for (p[1] = 0; p[1] <= degree - p[0]; p[1]++) {
621  snprintf(name, MAX_COLNAME, "a%"CPL_SIZE_FORMAT"%"CPL_SIZE_FORMAT"", p[0], p[1]);
622  coeff = cpl_table_get_double(global, name, row, &null);
623  if (null)
624  continue;
625  if (poly == NULL)
626  poly = cpl_polynomial_new(2);
627  cpl_polynomial_set_coeff(poly, p, coeff);
628  }
629  }
630 
631  return poly;
632 }
633 
634 static cpl_table *write_global_distortion(cpl_table *global, int row,
635  cpl_polynomial *poly)
636 {
637  cpl_table *table;
638  cpl_size p[2];
639  cpl_size degree = 2;
640  int nrow = 13;
641 
642  char name[MAX_COLNAME];
643 
644 
645  if (global) {
646  table = global;
647  }
648  else {
649  table = cpl_table_new(nrow);
650  for (p[0] = 0; p[0] <= degree; p[0]++) {
651  for (p[1] = 0; p[1] <= degree - p[0]; p[1]++) {
652  snprintf(name, MAX_COLNAME, "a%"CPL_SIZE_FORMAT"%"CPL_SIZE_FORMAT"", p[0], p[1]);
653  cpl_table_new_column(table, name, CPL_TYPE_DOUBLE);
654  }
655  }
656  }
657 
658  if (poly) {
659  for (p[0] = 0; p[0] <= degree; p[0]++) {
660  for (p[1] = 0; p[1] <= degree - p[0]; p[1]++) {
661  snprintf(name, MAX_COLNAME, "a%"CPL_SIZE_FORMAT"%"CPL_SIZE_FORMAT"", p[0], p[1]);
662  cpl_table_set_double(table, name, row,
663  cpl_polynomial_get_coeff(poly, p));
664  }
665  }
666  }
667 
668  return table;
669 }
670 
671 
672 /*
673  * The following static function is performing a robust linear fit
674  * (drawn from the VIMOS library, and originally from ESO-Eclipse).
675  *
676  * ----> y = a + b * x
677  *
678  * This function return 0 on success.
679  */
680 
681 #define SEGNO(a,b) ((b) >= 0.0 ? fabs(a) : -fabs(a))
682 static int robustLinearFit(cpl_bivector *list, double *a, double *b,
683  double *abdev)
684 {
685  cpl_vector *vx;
686  cpl_vector *vy;
687  cpl_vector *va;
688 
689  double aa, bb, bcomp, b1, b2, del, abdevt, f, f1, f2, sigb, temp, d, sum;
690  double sx, sy, sxy, sxx, chisq;
691  double *arr;
692  double aa_ls, bb_ls;
693  double *x;
694  double *y;
695  int np;
696  int iter;
697  int max_iterate = 30;
698  int i;
699 
700 
701  np = cpl_bivector_get_size(list);
702  vx = cpl_bivector_get_x(list);
703  vy = cpl_bivector_get_y(list);
704  x = cpl_vector_get_data(vx);
705  y = cpl_vector_get_data(vy);
706 
707  sx = sy = sxx = sxy = 0.00;
708  for (i = 0; i < np; i++) {
709  sx += x[i];
710  sy += y[i];
711  sxy += x[i] * y[i];
712  sxx += x[i] * x[i];
713  }
714 
715  del = np * sxx - sx * sx;
716  aa_ls = aa = (sxx * sy - sx * sxy) / del;
717  bb_ls = bb = (np * sxy - sx * sy) / del;
718 
719  chisq = 0.00;
720  for (i = 0; i < np; i++) {
721  temp = y[i] - (aa+bb*x[i]);
722  temp *= temp;
723  chisq += temp;
724  }
725 
726  va = cpl_vector_new(np);
727  arr = cpl_vector_get_data(va);
728  sigb = sqrt(chisq/del);
729  b1 = bb;
730 
731  bcomp = b1;
732  sum = 0.00;
733  for (i = 0; i < np; i++) {
734  arr[i] = y[i] - bcomp * x[i];
735  }
736  aa = cpl_vector_get_median_const(va);
737  abdevt = 0.0;
738  for (i = 0; i < np; i++) {
739  d = y[i] - (bcomp * x[i] + aa);
740  abdevt += fabs(d);
741  if (y[i] != 0.0)
742  d /= fabs(y[i]);
743  if (fabs(d) > 1e-7)
744  sum += (d >= 0.0 ? x[i] : -x[i]);
745  }
746  f1 = sum;
747 
748  b2 = bb + SEGNO(3.0 * sigb, f1);
749 
750  bcomp = b2;
751  sum = 0.00;
752  for (i = 0; i < np; i++) {
753  arr[i] = y[i] - bcomp * x[i];
754  }
755  aa = cpl_vector_get_median_const(va);
756  abdevt = 0.0;
757  for (i = 0; i < np; i++) {
758  d = y[i] - (bcomp * x[i] + aa);
759  abdevt += fabs(d);
760  if (y[i] != 0.0)
761  d /= fabs(y[i]);
762  if (fabs(d) > 1e-7)
763  sum += (d >= 0.0 ? x[i] : -x[i]);
764  }
765  f2 = sum;
766 
767  if (fabs(b2-b1)<1e-7) {
768  *a = aa;
769  *b = bb;
770  *abdev = abdevt / (double)np;
771  cpl_vector_delete(va);
772  return 0;
773  }
774 
775  iter = 0;
776  while (f1*f2 > 0.0) {
777  bb = 2.0*b2-b1;
778  b1 = b2;
779  f1 = f2;
780  b2 = bb;
781 
782  bcomp = b2;
783  sum = 0.00;
784  for (i = 0; i < np; i++) {
785  arr[i] = y[i] - bcomp * x[i];
786  }
787  aa = cpl_vector_get_median_const(va);
788  abdevt = 0.0;
789  for (i = 0; i < np; i++) {
790  d = y[i] - (bcomp * x[i] + aa);
791  abdevt += fabs(d);
792  if (y[i] != 0.0)
793  d /= fabs(y[i]);
794  if (fabs(d) > 1e-7)
795  sum += (d >= 0.0 ? x[i] : -x[i]);
796  }
797  f2 = sum;
798  iter++;
799  if (iter >= max_iterate)
800  break;
801  }
802  if (iter >= max_iterate) {
803  *a = aa_ls;
804  *b = bb_ls;
805  *abdev = -1.0;
806  cpl_vector_delete(va);
807  return 1;
808  }
809 
810  sigb = 0.01 * sigb;
811  while (fabs(b2-b1) > sigb) {
812  bb = 0.5 * (b1 + b2);
813  if ((fabs(bb-b1) < 1e-7) || (fabs(bb-b2) < 1e-7))
814  break;
815  bcomp = bb;
816  sum = 0.0;
817  for (i = 0; i < np; i++) {
818  arr[i] = y[i] - bcomp * x[i];
819  }
820  aa = cpl_vector_get_median_const(va);
821  abdevt = 0.0;
822  for (i = 0; i < np; i++) {
823  d = y[i] - (bcomp * x[i] + aa);
824  abdevt += fabs(d);
825  if (y[i] != 0.0)
826  d /= fabs(y[i]);
827  if (fabs(d) > 1e-7)
828  sum += (d >= 0.0 ? x[i] : -x[i]);
829  }
830  f = sum;
831 
832  if (f*f1 >= 0.0) {
833  f1=f;
834  b1=bb;
835  }
836  else {
837  f2=f;
838  b2=bb;
839  }
840  }
841  cpl_vector_delete(va);
842  *a = aa;
843  *b = bb;
844  *abdev = abdevt / np;
845  return 0;
846 }
847 #undef SEGNO
848 
849 /*
850  * The following static function applies the Hough transform from a table
851  * of points to another table of points. Given the points p_i = (x_i,y_i)
852  * and p_j = (x_j,y_j), the point (X,Y) with X = (y_i - y_j)/(x_i - x_j)
853  * and Y = y_i - X*x_i is computed and added to the output table for each
854  * p_i, p_j pair. This means that if the input table has N points, the
855  * output table has N*(N-1)/2 points.
856  */
857 
858 /* static */
859 cpl_table *mos_hough_table(cpl_table *table, const char *x, const char *y)
860 {
861  cpl_table *output;
862  double *xdata;
863  double *ydata;
864  double *xodata;
865  double *yodata;
866  int npoints;
867  int opoints;
868  int i, j, k;
869 
870 
871  npoints = cpl_table_get_nrow(table);
872  opoints = npoints*(npoints-1)/2;
873 
874  output = cpl_table_new(opoints);
875  cpl_table_new_column(output, "m", CPL_TYPE_DOUBLE);
876  cpl_table_new_column(output, "q", CPL_TYPE_DOUBLE);
877  cpl_table_fill_column_window_double(output, "m", 0, opoints, 0.0);
878  cpl_table_fill_column_window_double(output, "q", 0, opoints, 0.0);
879 
880  xodata = cpl_table_get_data_double(output, "m");
881  yodata = cpl_table_get_data_double(output, "q");
882 
883  cpl_table_cast_column(table, x, "x", CPL_TYPE_DOUBLE);
884  cpl_table_cast_column(table, y, "y", CPL_TYPE_DOUBLE);
885 
886  xdata = cpl_table_get_data_double(table, "x");
887  ydata = cpl_table_get_data_double(table, "y");
888 
889  k = 0;
890  for (i = 0; i < npoints; i++) {
891  for (j = i+1; j < npoints; j++) {
892  xodata[k] = (ydata[i]-ydata[j])/(xdata[i]-xdata[j]);
893  yodata[k] = ydata[i] - xodata[k] * xdata[i];
894  k++;
895  }
896  }
897 
898  if (k != opoints)
899  printf("Assert k = %d, expected %d\n", k, opoints);
900 
901  cpl_table_erase_column(table, "x");
902  cpl_table_erase_column(table, "y");
903 
904  return output;
905 }
906 
907 
908 /*
909  * The following static function is performing the spectral
910  * extraction for the function mos_extract_objects()
911  */
912 
913 static void mos_extraction(cpl_image *sciwin, cpl_image *sci_var_win,
914  cpl_image *skywin,
915  cpl_image *extracted, cpl_image *sky,
916  cpl_image *error, int nobjects, int extraction,
917  double ron, double conad, int ncomb)
918 {
919 
920  cpl_vector *vprofile;
921  cpl_image *smowin;
922 
923  int i, j;
924  int specLen;
925  int numRows;
926  int index;
927  int iter;
928  int maxIter = 2; /* Not less than 2 !!! */
929  int smoothBox = 31; /* Not less than 5 !!! */
930  double nsigma = 5.0;
931 
932  double sumWeight, sum, sumSky, sumProf, sumVar, variance, weight;
933  double *profile;
934  double *buffer;
935  float *edata;
936  float *ekdata;
937  float *endata;
938  float *sdata;
939  float *kdata;
940  float *fdata;
941  float *vardata;
942 
943  double value;
944 
945 
946  specLen = cpl_image_get_size_x(sciwin);
947  numRows = cpl_image_get_size_y(sciwin);
948 
949  edata = cpl_image_get_data(extracted);
950  edata += nobjects * specLen;
951 
952  ekdata = cpl_image_get_data(sky);
953  ekdata += nobjects * specLen;
954 
955  endata = cpl_image_get_data(error);
956  endata += nobjects * specLen;
957 
958  sdata = cpl_image_get_data(sciwin);
959  kdata = cpl_image_get_data(skywin);
960  if(sci_var_win != NULL)
961  vardata = cpl_image_get_data(sci_var_win);
962 
963  /*
964  * Initial spectrum estimate
965  if (sdata[i + j * specLen] > 0.0)
966  */
967 
968  if (extraction && numRows > 5) {
969  smowin = mos_image_filter_median(sciwin, 3, 3);
970  fdata = cpl_image_get_data(smowin);
971  for (i = 0; i < specLen; i++)
972  for (j = 0, edata[i] = 0.0; j < numRows; j++)
973  edata[i] += fdata[i + j * specLen];
974  cpl_image_delete(smowin);
975  }
976  else {
977  for (i = 0; i < specLen; i++)
978  for (j = 0, edata[i] = 0.0; j < numRows; j++)
979  edata[i] += sdata[i + j * specLen];
980  }
981 
982  if (extraction) {
983 
984  profile = cpl_calloc(specLen * numRows, sizeof(double));
985  buffer = cpl_calloc(specLen, sizeof(double));
986 
987  for (iter = 0; iter < maxIter; iter++) {
988 
989  /*
990  * Normalised spatial profile
991  */
992 
993  for (i = 0; i < specLen; i++) {
994  for (j = 0; j < numRows; j++) {
995  index = i + j * specLen;
996 /* if (sdata[index] > 0.0 && edata[i] > 0.00001) */
997  if (fabs(edata[i]) > 0.00001)
998  profile[index] = sdata[index] / edata[i];
999  else
1000  profile[index] = 0.0;
1001  }
1002  }
1003 
1004  for (j = 0; j < numRows; j++) {
1005 
1006  /*
1007  * Smooth each row in the dispersion direction, and enforce positivity
1008  */
1009 
1010  for (i = 0; i < specLen - smoothBox; i++) {
1011  vprofile = cpl_vector_wrap(smoothBox, profile + i + j*specLen);
1012  value = cpl_vector_get_median_const(vprofile);
1013  cpl_vector_unwrap(vprofile);
1014  if (value < 0)
1015  value = 0.0;
1016  buffer[i + smoothBox / 2] = value;
1017  }
1018 
1019  /*
1020  * Replace the end portions (i.e., not median filtered) with a mean
1021  */
1022 
1023  vprofile = cpl_vector_wrap(smoothBox / 2, profile + j*specLen);
1024  value = cpl_vector_get_mean(vprofile);
1025  cpl_vector_unwrap(vprofile);
1026 
1027  if (value < 0)
1028  value = 0.0;
1029 
1030  for (i = 0; i < smoothBox / 2; i++)
1031  buffer[i] = value;
1032 
1033  vprofile = cpl_vector_wrap(smoothBox / 2,
1034  profile + specLen - smoothBox/2 + j*specLen);
1035  value = cpl_vector_get_mean(vprofile);
1036  cpl_vector_unwrap(vprofile);
1037 
1038  if (value < 0)
1039  value = 0.0;
1040 
1041  for (i = 0; i < smoothBox / 2; i++)
1042  buffer[i + specLen - smoothBox / 2] = value;
1043 
1044  for (i = 0; i < specLen; i++)
1045  profile[i + j * specLen] = buffer[i];
1046 
1047  }
1048 
1049  /*
1050  * Enforce normalization of spatial profile after smoothing
1051  */
1052 
1053  for (i = 0; i < specLen; i++) {
1054  for (j = 0, value = 0.0; j < numRows; j++)
1055  value += profile[i + j * specLen];
1056  if (value > 0.00001)
1057  for (j = 0; j < numRows; j++)
1058  profile[i + j * specLen] /= value;
1059  else
1060  for (j = 0; j < numRows; j++)
1061  profile[i + j * specLen] = 0.0;
1062  }
1063 
1064 
1065  /*
1066  * Optimal extraction
1067  */
1068 
1069  for (i = 0; i < specLen; i++) {
1070  sum = 0.0;
1071  sumSky = 0.0;
1072  sumWeight = 0.0;
1073  sumProf = 0.0;
1074  sumVar = 0;
1075  for (j = 0; j < numRows; j++) {
1076  index = i + j * specLen;
1077  /*
1078  if (sdata[index] > 0.0) {
1079  */
1080  //This is the theoretical estimated variance. In principle, since we
1081  //have the propagated variance, we could use that one, but I leave
1082  //this as this is the original algorithm (cgarcia)
1083  variance = ron*ron + fabs(edata[i] * profile[index] + kdata[index])
1084  / conad;
1085  variance /= ncomb; /* If input dataset is sum of ncomb images */
1086  value = sdata[index] - edata[i] * profile[index];
1087  if (fabs(value) / sqrt(variance) < nsigma) {
1088  weight = 1000000 * profile[index] / variance;
1089  sum += weight * sdata[index];
1090  sumSky += weight * kdata[index];
1091  sumWeight += weight * profile[index];
1092  sumProf += profile[index];
1093  //This is how we propagated the variance. We assume that the
1094  //weigth has no error, although in has been computed from the
1095  //profile and the theoretical variance (which also includes the data)
1096  if(sci_var_win != NULL)
1097  sumVar += weight * weight * vardata[index];
1098  }
1099  }
1100 
1101  if (sumWeight > 0.00001) {
1102  edata[i] = sum / sumWeight;
1103  ekdata[i] = sumSky / sumWeight;
1104  if(sci_var_win != NULL)
1105  endata[i] = sqrt(sumVar / sumWeight / sumWeight); //This is the error, not the variance.
1106  else
1107  endata[i] = 1000 * sqrt(sumProf / sumWeight); //This was the old formula, which is not a real error propagation
1108  }
1109  else {
1110 /*
1111  edata[i] = 0.0;
1112  ekdata[i] = 0.0;
1113  endata[i] = 0.0;
1114 */
1115  //endata[i] = sqrt(ron*ron + fabs(edata[i] + ekdata[i]) / conad);
1116  }
1117  }
1118  }
1119  cpl_free(profile);
1120  cpl_free(buffer);
1121  }
1122  else {
1123 
1124  /*
1125  * Add sky estimation for the simple aperture extraction.
1126  if (kdata[i + j * specLen] > 0.0)
1127  */
1128 
1129  for (i = 0; i < specLen; i++)
1130  for (j = 0, ekdata[i] = 0.0; j < numRows; j++)
1131  ekdata[i] += kdata[i + j * specLen];
1132 
1133  /*
1134  * Add error estimation for the simple aperture extraction.
1135  */
1136  for (i = 0; i < specLen; i++)
1137  {
1138  if(sci_var_win != NULL)
1139  {
1140  //We propagate the variance of a simple addition
1141  for (j = 0, endata[i] = 0.0; j < numRows; j++)
1142  endata[i] += vardata[i + j * specLen];
1143  endata[i] = sqrt(endata[i]); //We return the error, not the variance
1144  }
1145  else
1146  endata[i] = sqrt(ron*ron + fabs(edata[i] + ekdata[i]) / conad);
1147 
1148  }
1149  }
1150 
1151 }
1152 
1153 
1200 cpl_table *mos_global_distortion(cpl_table *slits, cpl_table *maskslits,
1201  cpl_table *ids, cpl_table *crv,
1202  double reference)
1203 {
1204  const char *func = "mos_global_distortion";
1205 
1206  const char *clab[6] = {"c0", "c1", "c2", "c3", "c4", "c5"};
1207 
1208  cpl_table *global = NULL;
1209  cpl_table *coeff;
1210  cpl_table *dummy;
1211  cpl_vector *ci;
1212  cpl_vector *xmask;
1213  cpl_vector *ymask;
1214  cpl_bivector *mask;
1215  cpl_vector *xccd;
1216  cpl_vector *yccd;
1217  cpl_bivector *ccd;
1218  cpl_polynomial *poly;
1219  double *xtop;
1220  double *ytop;
1221  double *xbottom;
1222  double *ybottom;
1223  double *mxtop;
1224  double *mytop;
1225  double *mxbottom;
1226  double *mybottom;
1227  int *position;
1228  int *length;
1229  int *slit_id;
1230  int *mslit_id;
1231  int nslits, nmaskslits, npoints;
1232  int order;
1233  int i, j;
1234  int minslit = 6; // 12;
1235 
1236 
1237 /* *+
1238 printf("error1: %s\n", cpl_error_get_message());
1239 +* */
1240  if (slits == NULL || maskslits == NULL || ids == NULL || crv == NULL) {
1241  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
1242  return NULL;
1243  }
1244 /* *+
1245 printf("error1a: %s\n", cpl_error_get_message());
1246 +* */
1247 
1248  nslits = cpl_table_get_nrow(slits);
1249 /* *+
1250 printf("error1b: %s\n", cpl_error_get_message());
1251 +* */
1252 
1253  if (nslits < minslit) {
1254  cpl_msg_warning(func, "Too few slits (%d < %d) for global "
1255  "distortion model determination", nslits, minslit);
1256  return NULL;
1257  }
1258 /* *+
1259 printf("error1c: %s\n", cpl_error_get_message());
1260 +* */
1261 
1262  nmaskslits = cpl_table_get_nrow(maskslits);
1263 
1264  length = cpl_table_get_data_int(slits, "length");
1265  position = cpl_table_get_data_int(slits, "position");
1266  slit_id = cpl_table_get_data_int(slits, "slit_id");
1267  mslit_id = cpl_table_get_data_int(maskslits, "slit_id");
1268  xtop = cpl_table_get_data_double(slits, "xtop");
1269  ytop = cpl_table_get_data_double(slits, "ytop");
1270  xbottom = cpl_table_get_data_double(slits, "xbottom");
1271  ybottom = cpl_table_get_data_double(slits, "ybottom");
1272  mxtop = cpl_table_get_data_double(maskslits, "xtop");
1273  mytop = cpl_table_get_data_double(maskslits, "ytop");
1274  mxbottom = cpl_table_get_data_double(maskslits, "xbottom");
1275  mybottom = cpl_table_get_data_double(maskslits, "ybottom");
1276 
1277 
1278  /*
1279  * Global IDS
1280  */
1281 
1282  coeff = cpl_table_new(nslits);
1283  cpl_table_copy_structure(coeff, ids);
1284  cpl_table_new_column(coeff, "xccd", CPL_TYPE_DOUBLE);
1285  cpl_table_new_column(coeff, "yccd", CPL_TYPE_DOUBLE);
1286  cpl_table_new_column(coeff, "xmask", CPL_TYPE_DOUBLE);
1287  cpl_table_new_column(coeff, "ymask", CPL_TYPE_DOUBLE);
1288 
1289 /* *+
1290 printf("error2: %s\n", cpl_error_get_message());
1291 +* */
1292  for (i = 0; i < nslits; i++) {
1293  for (j = 0; j < nmaskslits; j++) {
1294  if (slit_id[i] == mslit_id[j]) {
1295  cpl_table_set_double(coeff, "xmask", i,
1296  (mxtop[j] + mxbottom[j]) / 2);
1297  cpl_table_set_double(coeff, "ymask", i,
1298  (mytop[j] + mybottom[j]) / 2);
1299  }
1300  }
1301  }
1302 
1303  if (cpl_table_has_invalid(coeff, "xmask")) {
1304  cpl_error_set(func, CPL_ERROR_INCOMPATIBLE_INPUT);
1305  cpl_table_delete(coeff);
1306  return NULL;
1307  }
1308 
1309  for (i = 0; i < nslits; i++) {
1310  cpl_table_set_double(coeff, "xccd", i, (xtop[i] + xbottom[i]) / 2);
1311  cpl_table_set_double(coeff, "yccd", i, (ytop[i] + ybottom[i]) / 2);
1312  }
1313 
1314 /* *+
1315 printf("error3: %s\n", cpl_error_get_message());
1316 +* */
1317  for (i = 0; i < nslits; i++) {
1318 
1319  if (length[i] == 0)
1320  continue;
1321 
1322  cpl_table_and_selected_window(ids, position[i], length[i]);
1323  dummy = cpl_table_extract_selected(ids);
1324  for (j = 0; j < 6; j++) {
1325  if (cpl_table_has_column(dummy, clab[j])) {
1326  if (length[i] - cpl_table_count_invalid(dummy, clab[j]) > 10) {
1327  cpl_table_set_double(coeff, clab[j], i,
1328  cpl_table_get_column_median(dummy, clab[j]));
1329  }
1330  }
1331  }
1332 
1333  cpl_table_delete(dummy);
1334  cpl_table_select_all(ids);
1335 
1336  }
1337 
1338 /* *+
1339 printf("error4: %s\n", cpl_error_get_message());
1340 +* */
1341  for (j = 0; j < 6; j++) {
1342  if (cpl_table_has_column(coeff, clab[j])) {
1343  cpl_table_and_selected_invalid(coeff, clab[j]);
1344 
1345  if (cpl_table_not_selected(coeff))
1346  dummy = cpl_table_extract_selected(coeff);
1347  else
1348  break;
1349 
1350  npoints = cpl_table_get_nrow(dummy);
1351 
1352  if (npoints >= 6) {
1353 
1354  if (npoints >= 12)
1355  order = 2;
1356  else
1357  order = 1;
1358 
1359  ci = cpl_vector_wrap(npoints,
1360  cpl_table_get_data_double(dummy, clab[j]));
1361  if (j) {
1362  xccd = cpl_vector_wrap(npoints,
1363  cpl_table_get_data_double(dummy, "xccd"));
1364  yccd = cpl_vector_wrap(npoints,
1365  cpl_table_get_data_double(dummy, "yccd"));
1366  ccd = cpl_bivector_wrap_vectors(xccd, yccd);
1367 
1368 /* %%% */
1369  poly = cpl_polynomial_fit_2d_create(ccd, ci, order, NULL);
1370 
1371  cpl_bivector_unwrap_vectors(ccd);
1372  cpl_vector_unwrap(xccd);
1373  cpl_vector_unwrap(yccd);
1374  cpl_vector_unwrap(ci);
1375  }
1376  else {
1377  xmask = cpl_vector_wrap(npoints,
1378  cpl_table_get_data_double(dummy, "xmask"));
1379  ymask = cpl_vector_wrap(npoints,
1380  cpl_table_get_data_double(dummy, "ymask"));
1381  mask = cpl_bivector_wrap_vectors(xmask, ymask);
1382 
1383 /* %%% */
1384  poly = cpl_polynomial_fit_2d_create(mask, ci, order, NULL);
1385 
1386  cpl_bivector_unwrap_vectors(mask);
1387  cpl_vector_unwrap(xmask);
1388  cpl_vector_unwrap(ymask);
1389  cpl_vector_unwrap(ci);
1390  }
1391  }
1392  else {
1393  cpl_size p[2] = {0, 0};
1394  poly = cpl_polynomial_new(2);
1395  cpl_polynomial_set_coeff(poly, p,
1396  cpl_table_get_column_median(dummy, clab[j]));
1397  }
1398 
1399  cpl_table_delete(dummy);
1400 
1401  global = write_global_distortion(global, j, poly);
1402 
1403  cpl_polynomial_delete(poly);
1404 
1405  cpl_table_select_all(coeff);
1406  }
1407  }
1408 
1409 /* *+
1410 printf("error5: %s\n", cpl_error_get_message());
1411 +* */
1412  cpl_table_delete(coeff);
1413 /* *+
1414 printf("error6: %s\n", cpl_error_get_message());
1415 +* */
1416 
1417 
1418  /*
1419  * Add model's reference wavelength
1420  */
1421 
1422  cpl_table_set_double(global, "a00", 6, reference);
1423 
1424 
1425  /*
1426  * Global curvature model
1427  */
1428 
1429  coeff = cpl_table_duplicate(crv);
1430  cpl_table_new_column(coeff, "xmask", CPL_TYPE_DOUBLE);
1431  cpl_table_new_column(coeff, "ymask", CPL_TYPE_DOUBLE);
1432  cpl_table_new_column(coeff, "xccd", CPL_TYPE_DOUBLE);
1433  cpl_table_new_column(coeff, "yccd", CPL_TYPE_DOUBLE);
1434  slit_id = cpl_table_get_data_int(coeff, "slit_id");
1435  npoints = cpl_table_get_nrow(coeff);
1436 
1437 /* *+
1438 printf("error7: %s\n", cpl_error_get_message());
1439 +* */
1440  for (i = 0; i < npoints; i++) {
1441  for (j = 0; j < nmaskslits; j++) {
1442  if (slit_id[i] == mslit_id[j]) {
1443  if (i%2) {
1444  cpl_table_set_double(coeff, "xmask", i, mxbottom[j]);
1445  cpl_table_set_double(coeff, "ymask", i, mybottom[j]);
1446  }
1447  else {
1448  cpl_table_set_double(coeff, "xmask", i, mxtop[j]);
1449  cpl_table_set_double(coeff, "ymask", i, mytop[j]);
1450  }
1451  }
1452  }
1453  if (i%2)
1454  {
1455  cpl_table_set_double(coeff, "xccd", i, xtop[(i-1)/2]);
1456  cpl_table_set_double(coeff, "yccd", i, ytop[(i-1)/2]);
1457  }
1458  else
1459  {
1460  cpl_table_set_double(coeff, "xccd", i, xbottom[i/2]);
1461  cpl_table_set_double(coeff, "yccd", i, ybottom[i/2]);
1462  }
1463  }
1464 
1465 /* *+
1466 printf("error8: %s\n", cpl_error_get_message());
1467 +* */
1468  if (cpl_table_has_invalid(coeff, "xmask")) {
1469  cpl_error_set(func, CPL_ERROR_INCOMPATIBLE_INPUT);
1470  cpl_table_delete(coeff);
1471  return NULL;
1472  }
1473 
1474 
1475 /* *+
1476 printf("error9: %s\n", cpl_error_get_message());
1477 +* */
1478  for (j = 0; j < 3; j++) {
1479  cpl_polynomial * poly_ccd;
1480  if (cpl_table_has_column(coeff, clab[j])) {
1481  cpl_table_and_selected_invalid(coeff, clab[j]);
1482 
1483  if (cpl_table_not_selected(coeff))
1484  dummy = cpl_table_extract_selected(coeff);
1485  else
1486  break;
1487 
1488  npoints = cpl_table_get_nrow(dummy);
1489 
1490  if (npoints >= 6) {
1491 
1492  if (npoints >= 12)
1493  order = 2;
1494  else
1495  order = 1;
1496 
1497  ci = cpl_vector_wrap(npoints,
1498  cpl_table_get_data_double(dummy, clab[j]));
1499  xmask = cpl_vector_wrap(npoints,
1500  cpl_table_get_data_double(dummy, "xmask"));
1501  ymask = cpl_vector_wrap(npoints,
1502  cpl_table_get_data_double(dummy, "ymask"));
1503  mask = cpl_bivector_wrap_vectors(xmask, ymask);
1504 
1505  poly = cpl_polynomial_fit_2d_create(mask, ci, order, NULL);
1506 
1507  xccd = cpl_vector_wrap(npoints,
1508  cpl_table_get_data_double(dummy, "xccd"));
1509  yccd = cpl_vector_wrap(npoints,
1510  cpl_table_get_data_double(dummy, "yccd"));
1511  ccd = cpl_bivector_wrap_vectors(xccd, yccd);
1512 
1513  poly_ccd = cpl_polynomial_fit_2d_create(ccd, ci, order, NULL);
1514 
1515 
1516  cpl_bivector_unwrap_vectors(mask);
1517  cpl_vector_unwrap(ci);
1518  cpl_vector_unwrap(xmask);
1519  cpl_vector_unwrap(ymask);
1520  }
1521  else {
1522  cpl_size p[2] = {0, 0};
1523  poly = cpl_polynomial_new(2);
1524  cpl_polynomial_set_coeff(poly, p,
1525  cpl_table_get_column_median(dummy, clab[j]));
1526  }
1527 
1528  cpl_table_delete(dummy);
1529 
1530  global = write_global_distortion(global, j + 7, poly);
1531  global = write_global_distortion(global, j + 10, poly_ccd);
1532 
1533  cpl_polynomial_delete(poly);
1534  cpl_polynomial_delete(poly_ccd);
1535  cpl_table_select_all(coeff);
1536  }
1537  }
1538 
1539  cpl_table_delete(coeff);
1540 /* *+
1541 printf("error11: %s\n", cpl_error_get_message());
1542 +* */
1543 
1544  return global;
1545 
1546 }
1547 
1548 
1586 cpl_table *mos_build_slit_location(cpl_table *global, cpl_table *maskslits,
1587  int ysize)
1588 {
1589  const char *func = "mos_build_slit_location";
1590 
1591  cpl_propertylist *sort_col;
1592  cpl_polynomial *ids0;
1593  cpl_polynomial *crv[3];
1594  cpl_polynomial *loc_crv;
1595  cpl_vector *point;
1596  cpl_table *slits;
1597  cpl_size nslits;
1598  int *slit_id;
1599  double *dpoint;
1600  double *xtop;
1601  double *ytop;
1602  double *xbottom;
1603  double *ybottom;
1604  double *mxtop;
1605  double *mytop;
1606  double *mxbottom;
1607  double *mybottom;
1608  cpl_size i;
1609  cpl_size j;
1610 
1611 
1612  if (global == NULL || maskslits == NULL) {
1613  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
1614  return NULL;
1615  }
1616 
1617  nslits = cpl_table_get_nrow(maskslits);
1618  slit_id = cpl_table_get_data_int(maskslits, "slit_id");
1619  mxtop = cpl_table_get_data_double(maskslits, "xtop");
1620  mytop = cpl_table_get_data_double(maskslits, "ytop");
1621  mxbottom = cpl_table_get_data_double(maskslits, "xbottom");
1622  mybottom = cpl_table_get_data_double(maskslits, "ybottom");
1623 
1624  slits = cpl_table_duplicate(maskslits);
1625 
1626  xtop = cpl_table_get_data_double(slits, "xtop");
1627  ytop = cpl_table_get_data_double(slits, "ytop");
1628  xbottom = cpl_table_get_data_double(slits, "xbottom");
1629  ybottom = cpl_table_get_data_double(slits, "ybottom");
1630 
1631  ids0 = read_global_distortion(global, 0);
1632  crv[0] = read_global_distortion(global, 7);
1633  crv[1] = read_global_distortion(global, 8);
1634  crv[2] = read_global_distortion(global, 9);
1635 
1636  loc_crv = cpl_polynomial_new(1);
1637 
1638  point = cpl_vector_new(2);
1639  dpoint = cpl_vector_get_data(point);
1640 
1641  for (i = 0; i < nslits; i++) {
1642  dpoint[0] = mxtop[i];
1643  dpoint[1] = mytop[i];
1644 
1645  xtop[i] = cpl_polynomial_eval(ids0, point);
1646 
1647  for (j = 0; j < 3; j++)
1648  if (crv[j])
1649  cpl_polynomial_set_coeff(loc_crv, &j,
1650  cpl_polynomial_eval(crv[j], point));
1651 
1652  ytop[i] = cpl_polynomial_eval_1d(loc_crv, xtop[i], NULL);
1653 
1654  dpoint[0] = mxbottom[i];
1655  dpoint[1] = mybottom[i];
1656  xbottom[i] = cpl_polynomial_eval(ids0, point);
1657 
1658  for (j = 0; j < 3; j++)
1659  if (crv[j])
1660  cpl_polynomial_set_coeff(loc_crv, &j,
1661  cpl_polynomial_eval(crv[j], point));
1662 
1663  ybottom[i] = cpl_polynomial_eval_1d(loc_crv, xbottom[i], NULL);
1664  }
1665 
1666  cpl_vector_delete(point);
1667  cpl_polynomial_delete(ids0);
1668  cpl_polynomial_delete(loc_crv);
1669  for (j = 0; j < 3; j++)
1670  cpl_polynomial_delete(crv[j]);
1671 
1672  sort_col = cpl_propertylist_new();
1673  cpl_propertylist_append_bool(sort_col, "ytop", 1);
1674  cpl_table_sort(slits, sort_col);
1675  cpl_table_sort(maskslits, sort_col);
1676  cpl_propertylist_delete(sort_col);
1677 
1678  /*
1679  * Eliminate slits which are _entirely_ outside the CCD
1680  */
1681 
1682  cpl_table_and_selected_double(slits, "ybottom", CPL_GREATER_THAN, ysize-1);
1683  cpl_table_or_selected_double(slits, "ytop", CPL_LESS_THAN, 0);
1684  cpl_table_erase_selected(slits);
1685 
1686  nslits = cpl_table_get_nrow(slits);
1687 
1688  if (nslits == 0) {
1689  cpl_msg_warning(func, "No slits found on the CCD");
1690  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
1691  cpl_table_delete(slits);
1692  return NULL;
1693  }
1694 
1695  if (nslits > 1)
1696  cpl_msg_info(func, "Slit location: %"CPL_SIZE_FORMAT" slits are entirely or partially "
1697  "contained in CCD", nslits);
1698  else
1699  cpl_msg_info(func, "Slit location: %"CPL_SIZE_FORMAT" slit is entirely or partially "
1700  "contained in CCD", nslits);
1701 
1702  return slits;
1703 
1704 }
1705 
1706 
1733 cpl_table *mos_build_curv_coeff(cpl_table *global, cpl_table *maskslits,
1734  cpl_table *slits)
1735 {
1736  const char *func = "mos_build_curv_coeff";
1737 
1738  const char *clab[6] = {"c0", "c1", "c2", "c3", "c4", "c5"};
1739  /* Max order is 5 */
1740 
1741  cpl_polynomial *crv[3];
1742  cpl_vector *point;
1743  cpl_table *polytraces;
1744  double *dpoint;
1745  double *xtop;
1746  double *ytop;
1747  double *xbottom;
1748  double *ybottom;
1749  int *slit_id;
1750  int *valid_id;
1751  int nslits, nvalid;
1752  int found;
1753  int i, j, k;
1754 
1755 
1756  if (global == NULL || slits == NULL || maskslits == NULL) {
1757  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
1758  return NULL;
1759  }
1760 
1761  nslits = cpl_table_get_nrow(maskslits);
1762  slit_id = cpl_table_get_data_int(maskslits, "slit_id");
1763  xtop = cpl_table_get_data_double(maskslits, "xtop");
1764  ytop = cpl_table_get_data_double(maskslits, "ytop");
1765  xbottom = cpl_table_get_data_double(maskslits, "xbottom");
1766  ybottom = cpl_table_get_data_double(maskslits, "ybottom");
1767 
1768  polytraces = cpl_table_new(2*nslits);
1769  cpl_table_new_column(polytraces, "slit_id", CPL_TYPE_INT);
1770  for (i = 0; i < 3; i++)
1771  cpl_table_new_column(polytraces, clab[i], CPL_TYPE_DOUBLE);
1772 
1773  crv[0] = read_global_distortion(global, 7);
1774  crv[1] = read_global_distortion(global, 8);
1775  crv[2] = read_global_distortion(global, 9);
1776 
1777  point = cpl_vector_new(2);
1778  dpoint = cpl_vector_get_data(point);
1779 
1780  for (i = 0; i < nslits; i++) {
1781  for (j = 0; j < 2; j++) { /* For top and bottom trace of each slit */
1782 
1783  cpl_table_set_int(polytraces, "slit_id", 2*i+j, slit_id[i]);
1784 
1785  if (j) {
1786  dpoint[0] = xbottom[i];
1787  dpoint[1] = ybottom[i];
1788  }
1789  else {
1790  dpoint[0] = xtop[i];
1791  dpoint[1] = ytop[i];
1792  }
1793 
1794  for (k = 0; k < 3; k++)
1795  if (crv[j])
1796  cpl_table_set_double(polytraces, clab[k], 2*i+j,
1797  cpl_polynomial_eval(crv[k], point));
1798  }
1799  }
1800 
1801  cpl_vector_delete(point);
1802  for (j = 0; j < 3; j++)
1803  cpl_polynomial_delete(crv[j]);
1804 
1805  /*
1806  * Eliminate slits which are _entirely_ outside the CCD
1807  */
1808 
1809  nvalid = cpl_table_get_nrow(slits);
1810  valid_id = cpl_table_get_data_int(slits, "slit_id");
1811  cpl_table_unselect_all(polytraces);
1812  for (i = 0; i < nslits; i++) {
1813  found = 0;
1814  for (j = 0; j < nvalid; j++) {
1815  if (slit_id[i] == valid_id[j]) {
1816  found = 1;
1817  break;
1818  }
1819  }
1820  if (!found) {
1821  cpl_table_select_row(polytraces, 2*i);
1822  cpl_table_select_row(polytraces, 2*i + 1);
1823  }
1824  }
1825  cpl_table_erase_selected(polytraces);
1826 
1827  nslits = cpl_table_get_nrow(polytraces);
1828 
1829  if (nslits == 0) {
1830  cpl_msg_warning(func, "No slits found on the CCD");
1831  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
1832  cpl_table_delete(polytraces);
1833  return NULL;
1834  }
1835 
1836  if (nslits > 2)
1837  cpl_msg_info(func, "Curvature model: %d slits are entirely or "
1838  "partially contained in CCD", nslits / 2);
1839  else
1840  cpl_msg_info(func, "Curvature model: %d slit is entirely or "
1841  "partially contained in CCD", nslits / 2);
1842 
1843  return polytraces;
1844 }
1845 
1846 
1888 cpl_table *mos_build_disp_coeff(cpl_table *global, cpl_table *slits)
1889 {
1890  const char *func = "mos_build_disp_coeff";
1891 
1892  const char *clab[6] = {"c0", "c1", "c2", "c3", "c4", "c5"};
1893 
1894  cpl_polynomial *ids[6];
1895  cpl_vector *point;
1896  cpl_table *idscoeff;
1897  double *dpoint;
1898  double *xtop;
1899  double *ytop;
1900  double *xbottom;
1901  double *ybottom;
1902  int *position;
1903  int *length;
1904  int nslits;
1905  int nrows;
1906  int order;
1907  int ylow, yhig;
1908  int i, j, k;
1909 
1910 
1911  if (global == NULL || slits == NULL) {
1912  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
1913  return NULL;
1914  }
1915 
1916  nslits = cpl_table_get_nrow(slits);
1917  position = cpl_table_get_data_int(slits, "position");
1918  length = cpl_table_get_data_int(slits, "length");
1919  xtop = cpl_table_get_data_double(slits, "xtop");
1920  ytop = cpl_table_get_data_double(slits, "ytop");
1921  xbottom = cpl_table_get_data_double(slits, "xbottom");
1922  ybottom = cpl_table_get_data_double(slits, "ybottom");
1923 
1924  for (i = 0; i < 6; i++)
1925  ids[i] = read_global_distortion(global, i);
1926 
1927  for (i = 0; i < 6; i++)
1928  if (ids[i] == NULL)
1929  break;
1930 
1931  order = i - 1;
1932 
1933  if (order < 1) {
1934  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
1935  return NULL;
1936  }
1937 
1938  nrows = 0;
1939  for (i = 0; i < nslits; i++)
1940  nrows += length[i];
1941 
1942  idscoeff = cpl_table_new(nrows);
1943 
1944  for (j = 0; j <= order; j++)
1945  cpl_table_new_column(idscoeff, clab[j], CPL_TYPE_DOUBLE);
1946 
1947  cpl_table_new_column(idscoeff, "error", CPL_TYPE_DOUBLE);
1948  cpl_table_fill_column_window_double(idscoeff, "error", 0, nrows, 0.0);
1949  cpl_table_new_column(idscoeff, "nlines", CPL_TYPE_INT);
1950  cpl_table_fill_column_window_int(idscoeff, "nlines", 0, nrows, 0);
1951 
1952  point = cpl_vector_new(2);
1953  dpoint = cpl_vector_get_data(point);
1954 
1955  for (i = 0; i < nslits; i++) {
1956 
1957  if (length[i] == 0)
1958  continue;
1959 
1960  ylow = position[i];
1961  yhig = ylow + length[i];
1962 
1963  for (j = 0; j <= order; j++) {
1964  if (j) {
1965  for (k = 0; k < length[i]; k++) {
1966  dpoint[0] = xbottom[i] + k*(xtop[i]-xbottom[i])/length[i];
1967  dpoint[1] = ybottom[i] + k*(ytop[i]-ybottom[i])/length[i];
1968  cpl_table_set_double(idscoeff, clab[j], ylow + k,
1969  cpl_polynomial_eval(ids[j], point));
1970  }
1971  }
1972  else {
1973  for (k = 0; k < length[i]; k++) {
1974  cpl_table_set_double(idscoeff, clab[0], ylow + k,
1975  xbottom[i] + k*(xtop[i]-xbottom[i])/length[i]);
1976  }
1977  }
1978  }
1979  }
1980 
1981  cpl_vector_delete(point);
1982  for (j = 0; j < 6; j++)
1983  cpl_polynomial_delete(ids[j]);
1984 
1985  return idscoeff;
1986 
1987 }
1988 
1989 
2012 cpl_image *mos_subtract_sky(cpl_image *science, cpl_table *slits,
2013  cpl_table *polytraces, double reference,
2014  double blue, double red, double dispersion)
2015 {
2016  const char *func = "mos_subtract_sky";
2017 
2018  const char *clab[6] = {"c0", "c1", "c2", "c3", "c4", "c5"};
2019  /* Max order is 5 */
2020 
2021  cpl_image *sky;
2022  cpl_bivector *list;
2023  cpl_vector *listx;
2024  cpl_vector *listy;
2025  cpl_polynomial *polytop;
2026  cpl_polynomial *polybot;
2027  cpl_polynomial *trend;
2028 
2029  int *slit_id;
2030  double *dlistx;
2031  double *dlisty;
2032  float *sdata;
2033  float *kdata;
2034  double top, bot;
2035  int itop, ibot;
2036  double coeff;
2037  double ytop, ybot;
2038  double m, q, err;
2039  int npix;
2040 
2041  int pixel_above, pixel_below, refpixel, start_pixel, end_pixel;
2042  int nx, ny;
2043  int nslits;
2044  int *length;
2045  int missing_top, missing_bot;
2046  int order;
2047  int null;
2048  int window = 50; /* Longer slits have polynomial sky model */
2049  int count;
2050  int i, j;
2051  cpl_size k;
2052 
2053 
2054  if (science == NULL || slits == NULL || polytraces == NULL) {
2055  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
2056  return NULL;
2057  }
2058 
2059  if (dispersion <= 0.0) {
2060  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
2061  return NULL;
2062  }
2063 
2064  if (red - blue < dispersion) {
2065  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
2066  return NULL;
2067  }
2068 
2069  nx = cpl_image_get_size_x(science);
2070  ny = cpl_image_get_size_y(science);
2071 
2072  sky = cpl_image_new(nx, ny, CPL_TYPE_FLOAT);
2073 
2074  sdata = cpl_image_get_data(science);
2075  kdata = cpl_image_get_data(sky);
2076 
2077  nslits = cpl_table_get_nrow(slits);
2078  order = cpl_table_get_ncol(polytraces) - 2;
2079  length = cpl_table_get_data_int(slits, "length");
2080  slit_id = cpl_table_get_data_int(slits, "slit_id");
2081 
2082  /*
2083  * The spatial resampling is performed for a certain number of
2084  * pixels above and below the position of the reference wavelength:
2085  */
2086 
2087  pixel_above = STRETCH_FACTOR * (red - reference) / dispersion;
2088  pixel_below = STRETCH_FACTOR * (reference - blue) / dispersion;
2089 
2090  for (i = 0; i < nslits; i++) {
2091 
2092  if (length[i] == 0)
2093  continue;
2094 
2095 
2096  /*
2097  * Recover from the table of spectral curvature coefficients
2098  * the curvature polynomials.
2099  */
2100 
2101  refpixel = cpl_table_get_double(slits, "xtop", i, NULL);
2102 
2103  start_pixel = refpixel - pixel_below;
2104  if (start_pixel < 0)
2105  start_pixel = 0;
2106 
2107  end_pixel = refpixel + pixel_above;
2108  if (end_pixel > nx)
2109  end_pixel = nx;
2110 
2111  missing_top = 0;
2112  polytop = cpl_polynomial_new(1);
2113  for (k = 0; k <= order; k++) {
2114  coeff = cpl_table_get_double(polytraces, clab[k], 2*i, &null);
2115  if (null) {
2116  cpl_polynomial_delete(polytop);
2117  missing_top = 1;
2118  break;
2119  }
2120  cpl_polynomial_set_coeff(polytop, &k, coeff);
2121  }
2122 
2123  missing_bot = 0;
2124  polybot = cpl_polynomial_new(1);
2125  for (k = 0; k <= order; k++) {
2126  coeff = cpl_table_get_double(polytraces, clab[k], 2*i+1, &null);
2127  if (null) {
2128  cpl_polynomial_delete(polybot);
2129  missing_bot = 1;
2130  break;
2131  }
2132  cpl_polynomial_set_coeff(polybot, &k, coeff);
2133  }
2134 
2135  if (missing_top && missing_bot) {
2136  cpl_msg_debug(func, "Slit %d was not traced: no extraction!",
2137  slit_id[i]);
2138  continue;
2139  }
2140 
2141  /*
2142  * In case just one of the two edges was not traced, the other
2143  * edge curvature model is duplicated and shifted to the other
2144  * end of the slit: better than nothing!
2145  */
2146 
2147  if (missing_top) {
2148  cpl_msg_debug(func, "Upper edge of slit %d was not traced: "
2149  "the spectral curvature of the lower edge "
2150  "is used instead.", slit_id[i]);
2151  polytop = cpl_polynomial_duplicate(polybot);
2152  ytop = cpl_table_get_double(slits, "ytop", i, NULL);
2153  ybot = cpl_table_get_double(slits, "ybottom", i, NULL);
2154  k = 0;
2155  coeff = cpl_polynomial_get_coeff(polybot, &k);
2156  coeff += ytop - ybot;
2157  cpl_polynomial_set_coeff(polytop, &k, coeff);
2158  }
2159 
2160  if (missing_bot) {
2161  cpl_msg_debug(func, "Lower edge of slit %d was not traced: "
2162  "the spectral curvature of the upper edge "
2163  "is used instead.", slit_id[i]);
2164  polybot = cpl_polynomial_duplicate(polytop);
2165  ytop = cpl_table_get_double(slits, "ytop", i, NULL);
2166  ybot = cpl_table_get_double(slits, "ybottom", i, NULL);
2167  k = 0;
2168  coeff = cpl_polynomial_get_coeff(polytop, &k);
2169  coeff -= ytop - ybot;
2170  cpl_polynomial_set_coeff(polybot, &k, coeff);
2171  }
2172 
2173 
2174  /*
2175  * Now read pixel values along spatial direction, and fit them.
2176  */
2177 
2178  for (j = start_pixel; j < end_pixel; j++) {
2179  top = cpl_polynomial_eval_1d(polytop, j, NULL);
2180  bot = cpl_polynomial_eval_1d(polybot, j, NULL);
2181  itop = floor(top + 0.5) + 1;
2182  ibot = floor(bot + 0.5);
2183  if (itop > ny)
2184  itop = ny;
2185  if (ibot < 0)
2186  ibot = 0;
2187  npix = itop - ibot;
2188  if (npix < 5)
2189  break;
2190 
2191  list = cpl_bivector_new(npix);
2192  listx = cpl_bivector_get_x(list);
2193  listy = cpl_bivector_get_y(list);
2194  dlistx = cpl_vector_get_data(listx);
2195  dlisty = cpl_vector_get_data(listy);
2196 
2197  for (k = 0; k < npix; k++) {
2198  dlistx[k] = k;
2199  dlisty[k] = sdata[j + (ibot + k)*nx];
2200  }
2201 
2202  if (robustLinearFit(list, &q, &m, &err)) {
2203  cpl_bivector_delete(list);
2204  continue;
2205  }
2206 
2207  cpl_bivector_delete(list);
2208 
2209  for (k = 0; k < npix; k++) {
2210  kdata[j + (ibot + k)*nx] = m*k + q;
2211  }
2212 
2213  if (npix > window) {
2214 
2215  /*
2216  * Polynomial iteration
2217  */
2218 
2219  err = 3*sqrt(err);
2220 
2221  count = 0;
2222  for (k = 0; k < npix; k++)
2223  if (fabs(sdata[j + (ibot + k)*nx] - m*k - q) < err)
2224  count++;
2225 
2226  if (count < 10)
2227  continue;
2228 
2229  list = cpl_bivector_new(count);
2230  listx = cpl_bivector_get_x(list);
2231  listy = cpl_bivector_get_y(list);
2232  dlistx = cpl_vector_get_data(listx);
2233  dlisty = cpl_vector_get_data(listy);
2234 
2235  count = 0;
2236  for (k = 0; k < npix; k++) {
2237  if (fabs(sdata[j + (ibot + k)*nx] - m*k - q) < err) {
2238  dlistx[count] = k;
2239  dlisty[count] = sdata[j + (ibot + k)*nx];
2240  count++;
2241  }
2242  }
2243 
2244  trend = cpl_polynomial_fit_1d_create(listx, listy, 2, &err);
2245 
2246  cpl_bivector_delete(list);
2247 
2248  err = 3*sqrt(err);
2249 
2250  count = 0;
2251  for (k = 0; k < npix; k++)
2252  if (fabs(sdata[j + (ibot + k)*nx]
2253  - cpl_polynomial_eval_1d(trend, k, NULL)) < err)
2254  count++;
2255 
2256  if (count < 10) {
2257  cpl_polynomial_delete(trend);
2258  continue;
2259  }
2260 
2261  list = cpl_bivector_new(count);
2262  listx = cpl_bivector_get_x(list);
2263  listy = cpl_bivector_get_y(list);
2264  dlistx = cpl_vector_get_data(listx);
2265  dlisty = cpl_vector_get_data(listy);
2266 
2267  count = 0;
2268  for (k = 0; k < npix; k++) {
2269  if (fabs(sdata[j + (ibot + k)*nx]
2270  - cpl_polynomial_eval_1d(trend, k, NULL)) < err) {
2271  dlistx[count] = k;
2272  dlisty[count] = sdata[j + (ibot + k)*nx];
2273  count++;
2274  }
2275  }
2276 
2277  cpl_polynomial_delete(trend);
2278 
2279  trend = cpl_polynomial_fit_1d_create(listx, listy, 3, &err);
2280 
2281  cpl_bivector_delete(list);
2282 
2283  for (k = 0; k < npix; k++) {
2284  kdata[j + (ibot + k)*nx] = cpl_polynomial_eval_1d(trend,
2285  k, NULL);
2286  }
2287 
2288  cpl_polynomial_delete(trend);
2289  }
2290  }
2291  cpl_polynomial_delete(polytop);
2292  cpl_polynomial_delete(polybot);
2293  }
2294 
2295  cpl_image_subtract(science, sky);
2296 
2297  return sky;
2298 }
2299 
2300 
2333 cpl_image *mos_normalise_flat(cpl_image *flat, cpl_image *spatial,
2334  cpl_table *slits, cpl_table *polytraces,
2335  double reference, double blue, double red,
2336  double dispersion, int sradius, int polyorder)
2337 {
2338  const char *func = "mos_normalise_flat";
2339 
2340  const char *clab[6] = {"c0", "c1", "c2", "c3", "c4", "c5"};
2341  /* Max order is 5 */
2342 
2343  cpl_image *rectified;
2344  cpl_image *smo_flat;
2345  cpl_image *exslit;
2346  cpl_vector *positions;
2347  cpl_vector *flux;
2348  cpl_vector *smo_flux;
2349  cpl_polynomial *trend;
2350  cpl_polynomial *polytop;
2351  cpl_polynomial *polybot;
2352 
2353  int *slit_id;
2354  float *p;
2355  float *data;
2356  double *fdata;
2357  double *pdata;
2358  float *sdata;
2359  float *xdata;
2360  float *wdata;
2361  double vtop, vbot, value;
2362  double top, bot;
2363  double coeff;
2364  double ytop, ybot;
2365  double ypos;
2366  double fvalue;
2367  int ivalue;
2368  int yint, yprev;
2369  int npseudo;
2370 
2371  int pixel_above, pixel_below, refpixel, start_pixel, end_pixel;
2372  int nx, ny, nsubx, nsuby;
2373  int xlow, ylow, xhig, yhig;
2374  int nslits;
2375  int *position;
2376  int *length;
2377  int missing_top, missing_bot;
2378  int order;
2379  int npoints;
2380  int uradius;
2381  int null;
2382  int i, j;
2383  cpl_size k;
2384 
2385 /* int exclude = 5; Number of excluded pixels at edges */
2386 
2387  /* For debug puposes only: cpl_image *smo_rectified; */
2388 
2389 
2390  if (flat == NULL || slits == NULL || polytraces == NULL) {
2391  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
2392  return NULL;
2393  }
2394 
2395  if (dispersion <= 0.0) {
2396  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
2397  return NULL;
2398  }
2399 
2400  if (red - blue < dispersion) {
2401  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
2402  return NULL;
2403  }
2404 
2405  rectified = mos_spatial_calibration(flat, slits, polytraces, reference,
2406  blue, red, dispersion, 0, NULL);
2407 
2408  nx = cpl_image_get_size_x(rectified);
2409  ny = cpl_image_get_size_y(rectified);
2410 
2411  smo_flat = cpl_image_new(cpl_image_get_size_x(spatial),
2412  cpl_image_get_size_y(spatial), CPL_TYPE_FLOAT);
2413  wdata = cpl_image_get_data(smo_flat);
2414 
2415  nslits = cpl_table_get_nrow(slits);
2416  order = cpl_table_get_ncol(polytraces) - 2;
2417  position = cpl_table_get_data_int(slits, "position");
2418  length = cpl_table_get_data_int(slits, "length");
2419  slit_id = cpl_table_get_data_int(slits, "slit_id");
2420 
2421  /*
2422  * The spatial resampling is performed for a certain number of
2423  * pixels above and below the position of the reference wavelength:
2424  */
2425 
2426  pixel_above = STRETCH_FACTOR * (red - reference) / dispersion;
2427  pixel_below = STRETCH_FACTOR * (reference - blue) / dispersion;
2428 
2429  xlow = 1;
2430  xhig = nx;
2431  for (i = 0; i < nslits; i++) {
2432 
2433  if (length[i] == 0)
2434  continue;
2435 
2436  /*
2437  * We DON'T write:
2438  *
2439  * ylow = position[i];
2440  * yhig = ylow + length[i];
2441  *
2442  * because the cpl_image pixels are counted from 1, and because in
2443  * cpl_image_extract() the coordinates of the last pixel are inclusive.
2444  */
2445 
2446  ylow = position[i] + 1;
2447  yhig = ylow + length[i] - 1;
2448 
2449  exslit = cpl_image_extract(rectified, xlow, ylow, xhig, yhig);
2450 
2451  if (polyorder < 0) {
2452 
2453  cpl_image_turn(exslit, -1); /* For faster memory access */
2454 
2455  nsubx = cpl_image_get_size_x(exslit);
2456  nsuby = cpl_image_get_size_y(exslit);
2457  data = cpl_image_get_data(exslit);
2458  flux = cpl_vector_new(nsubx);
2459 
2460  uradius = nsubx / 2;
2461  if (uradius > sradius)
2462  uradius = sradius;
2463 
2464  for (j = 0; j < nsuby; j++) {
2465  fdata = cpl_vector_get_data(flux);
2466  p = data;
2467  for (k = 0; k < nsubx; k++)
2468  *fdata++ = *p++;
2469  smo_flux = cpl_vector_filter_median_create(flux, uradius);
2470  fdata = cpl_vector_get_data(smo_flux);
2471  p = data;
2472  for (k = 0; k < nsubx; k++)
2473  *p++ = *fdata++;
2474  cpl_vector_delete(smo_flux);
2475  data += nsubx;
2476  }
2477 
2478  cpl_vector_delete(flux);
2479 
2480 
2481  /*
2482  * First fit fluxes along the spatial direction with a low-degree
2483  * polynomial (excluding the first and the last pixels, close to
2484  * the edges)
2485  */
2486 /*
2487  if (nsubx-2*exclude > 10) {
2488  flux = cpl_vector_new(nsubx-2*exclude);
2489  fdata = cpl_vector_get_data(flux);
2490  positions = cpl_vector_new(nsubx-2*exclude);
2491  for (j = 0; j < nsubx-2*exclude; j++)
2492  cpl_vector_set(positions, j, j+exclude);
2493 
2494  for (k = 0; k < nsuby; k++) {
2495  for (j = 0; j < nsubx-2*exclude; j++)
2496  fdata[j] = data[j+exclude];
2497  trend = cpl_polynomial_fit_1d_create(positions, flux,
2498  1, NULL);
2499  for (j = 0; j < nsubx; j++)
2500  data[j] = cpl_polynomial_eval_1d(trend, j, NULL);
2501  cpl_polynomial_delete(trend);
2502  data += nsubx;
2503  }
2504 
2505  cpl_vector_delete(flux);
2506  cpl_vector_delete(positions);
2507  }
2508 */
2509 
2510  /*
2511  * Now smooth along the dispersion direction
2512  */
2513 
2514  cpl_image_turn(exslit, 1); /* For faster memory access */
2515  nsubx = cpl_image_get_size_x(exslit);
2516  nsuby = cpl_image_get_size_y(exslit);
2517  data = cpl_image_get_data(exslit);
2518 
2519  for (j = 0; j < nsuby; j++) {
2520  flux = cpl_vector_new(nsubx);
2521  fdata = cpl_vector_get_data(flux);
2522  p = data;
2523  for (k = 0; k < nsubx; k++)
2524  *fdata++ = *p++;
2525  smo_flux = cpl_vector_filter_median_create(flux, sradius);
2526  cpl_vector_delete(flux);
2527  fdata = cpl_vector_get_data(smo_flux);
2528  p = data;
2529  for (k = 0; k < nsubx; k++)
2530  *p++ = *fdata++;
2531  cpl_vector_delete(smo_flux);
2532  data += nsubx;
2533  }
2534  }
2535  else {
2536 
2537  /*
2538  * Fit with a polynomial the flat field trend row by row.
2539  */
2540 
2541  nsubx = cpl_image_get_size_x(exslit);
2542  nsuby = cpl_image_get_size_y(exslit);
2543  data = cpl_image_get_data(exslit);
2544 
2545  for (j = 0; j < nsuby; j++) {
2546 
2547  /*
2548  * First get the size of the vectors to allocate:
2549  */
2550 
2551  npoints = 0;
2552  p = data + j*nsubx;
2553  for (k = 0; k < nsubx; k++)
2554  if (p[k] > 1.0)
2555  npoints++;
2556 
2557  if (npoints > polyorder + 1) {
2558 
2559  /*
2560  * Fill the vectors for the fitting
2561  */
2562 
2563  flux = cpl_vector_new(npoints);
2564  fdata = cpl_vector_get_data(flux);
2565  positions = cpl_vector_new(npoints);
2566  pdata = cpl_vector_get_data(positions);
2567 
2568  npoints = 0;
2569  p = data + j*nsubx;
2570  for (k = 0; k < nsubx; k++) {
2571  if (p[k] > 1.0) {
2572  fdata[npoints] = p[k];
2573  pdata[npoints] = k;
2574  npoints++;
2575  }
2576  }
2577 
2578  trend = cpl_polynomial_fit_1d_create(positions, flux,
2579  polyorder, NULL);
2580 
2581  cpl_vector_delete(flux);
2582  cpl_vector_delete(positions);
2583 
2584  if (trend) {
2585  p = data + j*nsubx;
2586  for (k = 0; k < nsubx; k++)
2587  if (p[k] > 1.0)
2588  p[k] = cpl_polynomial_eval_1d(trend, k, NULL);
2589  cpl_polynomial_delete(trend);
2590  }
2591  else {
2592  cpl_msg_warning(func, "Invalid flat field flux fit "
2593  "(ignored)");
2594  }
2595  }
2596  }
2597  }
2598 
2599 
2600  /*
2601  * Recover from the table of spectral curvature coefficients
2602  * the curvature polynomials.
2603  */
2604 
2605  refpixel = cpl_table_get_double(slits, "xtop", i, NULL);
2606 
2607  start_pixel = refpixel - pixel_below;
2608  if (start_pixel < 0)
2609  start_pixel = 0;
2610 
2611  end_pixel = refpixel + pixel_above;
2612  if (end_pixel > nx)
2613  end_pixel = nx;
2614 
2615  missing_top = 0;
2616  polytop = cpl_polynomial_new(1);
2617  for (k = 0; k <= order; k++) {
2618  coeff = cpl_table_get_double(polytraces, clab[k], 2*i, &null);
2619  if (null) {
2620  cpl_polynomial_delete(polytop);
2621  missing_top = 1;
2622  break;
2623  }
2624  cpl_polynomial_set_coeff(polytop, &k, coeff);
2625  }
2626 
2627  missing_bot = 0;
2628  polybot = cpl_polynomial_new(1);
2629  for (k = 0; k <= order; k++) {
2630  coeff = cpl_table_get_double(polytraces, clab[k], 2*i+1, &null);
2631  if (null) {
2632  cpl_polynomial_delete(polybot);
2633  missing_bot = 1;
2634  break;
2635  }
2636  cpl_polynomial_set_coeff(polybot, &k, coeff);
2637  }
2638 
2639  if (missing_top && missing_bot) {
2640  cpl_msg_debug(func, "Slit %d was not traced: no extraction!",
2641  slit_id[i]);
2642  continue;
2643  }
2644 
2645  /*
2646  * In case just one of the two edges was not traced, the other
2647  * edge curvature model is duplicated and shifted to the other
2648  * end of the slit: better than nothing!
2649  */
2650 
2651  if (missing_top) {
2652  cpl_msg_debug(func, "Upper edge of slit %d was not traced: "
2653  "the spectral curvature of the lower edge "
2654  "is used instead.", slit_id[i]);
2655  polytop = cpl_polynomial_duplicate(polybot);
2656  ytop = cpl_table_get_double(slits, "ytop", i, NULL);
2657  ybot = cpl_table_get_double(slits, "ybottom", i, NULL);
2658  k = 0;
2659  coeff = cpl_polynomial_get_coeff(polybot, &k);
2660  coeff += ytop - ybot;
2661  cpl_polynomial_set_coeff(polytop, &k, coeff);
2662  }
2663 
2664  if (missing_bot) {
2665  cpl_msg_debug(func, "Lower edge of slit %d was not traced: "
2666  "the spectral curvature of the upper edge "
2667  "is used instead.", slit_id[i]);
2668  polybot = cpl_polynomial_duplicate(polytop);
2669  ytop = cpl_table_get_double(slits, "ytop", i, NULL);
2670  ybot = cpl_table_get_double(slits, "ybottom", i, NULL);
2671  k = 0;
2672  coeff = cpl_polynomial_get_coeff(polytop, &k);
2673  coeff -= ytop - ybot;
2674  cpl_polynomial_set_coeff(polybot, &k, coeff);
2675  }
2676 
2677 
2678  /*
2679  * Now map smoothed image to CCD.
2680  * Note that the npseudo value related to this slit is equal
2681  * to the number of spatial pseudo-pixels decreased by 1
2682  * (compare with function mos_spatial_calibration()).
2683  */
2684 
2685  nx = cpl_image_get_size_x(flat);
2686  ny = cpl_image_get_size_y(flat);
2687 
2688  sdata = cpl_image_get_data(spatial);
2689  xdata = cpl_image_get_data(exslit);
2690  npseudo = cpl_image_get_size_y(exslit) - 1;
2691 
2692  /*
2693  * Write interpolated smoothed values to CCD image
2694  */
2695 
2696  for (j = start_pixel; j < end_pixel; j++) {
2697  top = cpl_polynomial_eval_1d(polytop, j, NULL);
2698  bot = cpl_polynomial_eval_1d(polybot, j, NULL);
2699  for (k = 0; k <= npseudo; k++) {
2700  ypos = top - k*(top-bot)/npseudo;
2701  yint = ypos;
2702 
2703  /*
2704  * The line:
2705  * value = sdata[j + nx*yint];
2706  * should be equivalent to:
2707  * value = npseudo*(top-yint)/(top-bot);
2708  */
2709 
2710  if (yint < 0 || yint >= ny-1) {
2711  yprev = yint;
2712  continue;
2713  }
2714 
2715  value = sdata[j + nx*yint]; /* Spatial coordinate on CCD */
2716  ivalue = value; /* Nearest spatial pixels: */
2717  fvalue = value - ivalue; /* ivalue and ivalue+1 */
2718  if (ivalue < npseudo && ivalue >= 0) {
2719  vtop = xdata[j + nx*(npseudo-ivalue)];
2720  vbot = xdata[j + nx*(npseudo-ivalue-1)];
2721  wdata[j + nx*yint] = vtop*(1-fvalue) + vbot*fvalue;
2722 
2723  if (k) {
2724 
2725  /*
2726  * This is added to recover lost pixels on
2727  * the CCD image (pixels are lost because
2728  * the CCD pixels are less than npseudo+1).
2729  */
2730 
2731  if (yprev - yint > 1) {
2732  value = sdata[j + nx*(yint+1)];
2733  ivalue = value;
2734  fvalue = value - ivalue;
2735  if (ivalue < npseudo && ivalue >= 0) {
2736  vtop = xdata[j + nx*(npseudo-ivalue)];
2737  vbot = xdata[j + nx*(npseudo-ivalue-1)];
2738  wdata[j + nx*(yint+1)] = vtop*(1-fvalue)
2739  + vbot*fvalue;
2740  }
2741  }
2742  }
2743  }
2744  yprev = yint;
2745  }
2746  }
2747  cpl_polynomial_delete(polytop);
2748  cpl_polynomial_delete(polybot);
2749  cpl_image_delete(exslit);
2750  }
2751 
2752  cpl_image_delete(rectified);
2753 
2754  cpl_image_divide(flat, smo_flat);
2755 
2756  return smo_flat;
2757 }
2758 
2759 
2784 cpl_image *mos_normalise_longflat(cpl_image *flat, int sradius, int dradius,
2785  int polyorder)
2786 {
2787  const char *func = "mos_normalise_longflat";
2788 
2789  cpl_image *smo_flat;
2790  cpl_image *profile;
2791  cpl_vector *flux;
2792  cpl_vector *smo_flux;
2793  cpl_vector *positions;
2794  cpl_polynomial *trend;
2795 
2796  float *level;
2797  float *p;
2798  float *data;
2799  double *fdata;
2800  double *pdata;
2801 
2802  int nx, ny;
2803  int npoints;
2804  int i, j;
2805 
2806 
2807  if (flat == NULL) {
2808  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
2809  return NULL;
2810  }
2811 
2812  if (sradius < 1 || dradius < 1) {
2813  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
2814  return NULL;
2815  }
2816 
2817  smo_flat = cpl_image_duplicate(flat);
2818 
2819  if (polyorder < 0) {
2820 
2821  /*
2822  * First smooth along the spatial direction
2823  */
2824 
2825  cpl_image_turn(smo_flat, -1); /* For faster memory access */
2826 
2827  nx = cpl_image_get_size_x(smo_flat);
2828  ny = cpl_image_get_size_y(smo_flat);
2829  data = cpl_image_get_data(smo_flat);
2830 
2831  for (i = 0; i < ny; i++) {
2832  flux = cpl_vector_new(nx);
2833  fdata = cpl_vector_get_data(flux);
2834  p = data;
2835  for (j = 0; j < nx; j++)
2836  *fdata++ = *p++;
2837  smo_flux = cpl_vector_filter_median_create(flux, sradius);
2838  cpl_vector_delete(flux);
2839  fdata = cpl_vector_get_data(smo_flux);
2840  p = data;
2841  for (j = 0; j < nx; j++)
2842  *p++ = *fdata++;
2843  cpl_vector_delete(smo_flux);
2844  data += nx;
2845  }
2846 
2847  /*
2848  * Second smooth along the dispersion direction
2849  */
2850 
2851  cpl_image_turn(smo_flat, 1); /* For faster memory access */
2852 
2853  nx = cpl_image_get_size_x(smo_flat);
2854  ny = cpl_image_get_size_y(smo_flat);
2855  data = cpl_image_get_data(smo_flat);
2856 
2857  for (i = 0; i < ny; i++) {
2858  flux = cpl_vector_new(nx);
2859  fdata = cpl_vector_get_data(flux);
2860  p = data;
2861  for (j = 0; j < nx; j++)
2862  *fdata++ = *p++;
2863  smo_flux = cpl_vector_filter_median_create(flux, sradius);
2864  cpl_vector_delete(flux);
2865  fdata = cpl_vector_get_data(smo_flux);
2866  p = data;
2867  for (j = 0; j < nx; j++)
2868  *p++ = *fdata++;
2869  cpl_vector_delete(smo_flux);
2870  data += nx;
2871  }
2872  }
2873  else {
2874 
2875  /*
2876  * Fit with a polynomial the flat field trend column by column.
2877  */
2878 
2879  cpl_image_turn(smo_flat, -1); /* For faster memory access */
2880 
2881  nx = cpl_image_get_size_x(smo_flat);
2882  ny = cpl_image_get_size_y(smo_flat);
2883  data = cpl_image_get_data(smo_flat);
2884 
2885  profile = cpl_image_collapse_median_create(smo_flat, 1, 0, 0);
2886  level = cpl_image_get_data(profile);
2887 
2888  for (i = 0; i < ny; i++) {
2889 
2890  /*
2891  * First get the size of the vectors to allocate:
2892  * eliminate from fit any value more than 20% away
2893  * from median level in current column.
2894  */
2895 
2896  npoints = 0;
2897  p = data + i*nx;
2898  for (j = 0; j < nx; j++)
2899  if (fabs(p[j]/level[i] - 1) < 0.20)
2900  npoints++;
2901 
2902  if (npoints > polyorder + 1) {
2903 
2904  /*
2905  * Fill the vectors for the fitting
2906  */
2907 
2908  flux = cpl_vector_new(npoints);
2909  fdata = cpl_vector_get_data(flux);
2910  positions = cpl_vector_new(npoints);
2911  pdata = cpl_vector_get_data(positions);
2912 
2913  npoints = 0;
2914  p = data + i*nx;
2915  for (j = 0; j < nx; j++) {
2916  if (fabs(p[j]/level[i] - 1) < 0.20) {
2917  fdata[npoints] = p[j];
2918  pdata[npoints] = j;
2919  npoints++;
2920  }
2921  }
2922 
2923  trend = cpl_polynomial_fit_1d_create(positions, flux,
2924  polyorder, NULL);
2925 
2926  cpl_vector_delete(flux);
2927  cpl_vector_delete(positions);
2928 
2929  if (trend) {
2930  p = data + i*nx;
2931  for (j = 0; j < nx; j++)
2932  p[j] = cpl_polynomial_eval_1d(trend, j, NULL);
2933  cpl_polynomial_delete(trend);
2934  }
2935  else {
2936  cpl_msg_warning(func,
2937  "Invalid flat field flux fit (ignored)");
2938  }
2939  }
2940  }
2941 
2942  cpl_image_delete(profile);
2943  cpl_image_turn(smo_flat, 1);
2944 
2945  }
2946 
2947  cpl_image_divide(flat, smo_flat);
2948 
2949  return smo_flat;
2950 }
2951 
2952 
2975 cpl_error_code mos_interpolate_wavecalib_slit(cpl_table *idscoeff,
2976  cpl_table *slits,
2977  int order, int global)
2978 {
2979  const char *clab[6] = {"c0", "c1", "c2", "c3", "c4", "c5"};
2980  /* Max order is 5 */
2981  int nrow = cpl_table_get_nrow(slits);
2982  int i, j;
2983 
2984 
2985  if (order < 0)
2986  return CPL_ERROR_NONE;
2987 
2988  cpl_table_new_column(idscoeff, "x", CPL_TYPE_DOUBLE);
2989  cpl_table_new_column(idscoeff, "y", CPL_TYPE_DOUBLE);
2990 
2991  for (i = 0; i < nrow; i++) {
2992  int position = cpl_table_get_int (slits, "position", i, NULL);
2993  int length = cpl_table_get_int (slits, "length", i, NULL);
2994  double xtop = cpl_table_get_double(slits, "xtop", i, NULL);
2995  double xbot = cpl_table_get_double(slits, "xbottom", i, NULL);
2996  double ytop = cpl_table_get_double(slits, "ytop", i, NULL);
2997  double ybot = cpl_table_get_double(slits, "ybottom", i, NULL);
2998  double dx = xtop - xbot;
2999  double dy = ytop - ybot;
3000  cpl_table *table = cpl_table_extract(idscoeff, position, length);
3001 
3002  if (mos_interpolate_wavecalib(table, NULL, 2, order))
3003  continue;
3004 
3005  cpl_table_erase_window(idscoeff, position, length);
3006  cpl_table_insert(idscoeff, table, position);
3007 
3008  cpl_table_delete(table);
3009 
3010  for (j = 0; j < length; j++) {
3011  cpl_table_set_double(idscoeff, "x", j + position,
3012  xbot + j*(dx/length));
3013  cpl_table_set_double(idscoeff, "y", j + position,
3014  ybot + j*(dy/length));
3015  }
3016  }
3017 
3018  if (global) {
3019 
3020  /*
3021  * Now fit a global solution
3022  */
3023 
3024  nrow = cpl_table_get_nrow(idscoeff);
3025 
3026  for (i = 0; i < 6; i++) {
3027  cpl_table *dummy;
3028  cpl_vector *x;
3029  cpl_vector *y;
3030  cpl_bivector *z;
3031  cpl_vector *c;
3032  cpl_polynomial *p;
3033  cpl_vector *point;
3034  double *dpoint;
3035  int npoints;
3036 
3037  if (!cpl_table_has_column(idscoeff, clab[i]))
3038  break;
3039 
3040  npoints = nrow - cpl_table_count_invalid(idscoeff, clab[i]);
3041  if (npoints < 18)
3042  break;
3043 
3044  dummy = cpl_table_new(nrow);
3045  cpl_table_duplicate_column(dummy, "x", idscoeff, "x");
3046  cpl_table_duplicate_column(dummy, "y", idscoeff, "y");
3047  cpl_table_duplicate_column(dummy, clab[i], idscoeff, clab[i]);
3048  cpl_table_erase_invalid(dummy);
3049 
3050  x = cpl_vector_wrap(npoints, cpl_table_get_data_double(dummy, "x"));
3051  y = cpl_vector_wrap(npoints, cpl_table_get_data_double(dummy, "y"));
3052  z = cpl_bivector_wrap_vectors(x, y);
3053  c = cpl_vector_wrap(npoints, cpl_table_get_data_double(dummy,
3054  clab[i]));
3055  p = cpl_polynomial_fit_2d_create(z, c, 2, NULL);
3056  cpl_bivector_unwrap_vectors(z);
3057  cpl_vector_unwrap(x);
3058  cpl_vector_unwrap(y);
3059  cpl_vector_unwrap(c);
3060  cpl_table_delete(dummy);
3061 
3062  point = cpl_vector_new(2);
3063  dpoint = cpl_vector_get_data(point);
3064  for (j = 0; j < nrow; j++) {
3065  dpoint[0] = cpl_table_get_double(idscoeff, "x", j, NULL);
3066  dpoint[1] = cpl_table_get_double(idscoeff, "y", j, NULL);
3067  cpl_table_set_double(idscoeff, clab[i], j,
3068  cpl_polynomial_eval(p, point));
3069  }
3070  cpl_vector_delete(point);
3071  cpl_polynomial_delete(p);
3072  }
3073  }
3074 
3075  return CPL_ERROR_NONE;
3076 }
3077 
3078 
3104 cpl_error_code mos_interpolate_wavecalib(cpl_table *idscoeff,
3105  cpl_image *wavemap, int mode,
3106  int degree)
3107 {
3108  const char *func = "mos_interpolate_wavecalib";
3109 
3110  const char *clab[6] = {"c0", "c1", "c2", "c3", "c4", "c5"};
3111  /* Max order is 5 */
3112 
3113  cpl_vector *wave;
3114  cpl_vector *positions;
3115  cpl_polynomial *trend;
3116 
3117  float *p;
3118  float *data;
3119  double *wdata;
3120  double *pdata;
3121 
3122  double c;
3123  double mse, ksigma;
3124 
3125  int order;
3126  int nrows, first_row, last_row;
3127  int nx, ny;
3128  int npoints, rpoints;
3129  int null;
3130  int i, j, k;
3131 
3132  int polyorder = 4; /* Candidate input argument */
3133 
3134 
3135  if (idscoeff == NULL)
3136  return cpl_error_set(func, CPL_ERROR_NULL_INPUT);
3137 
3138  if (mode < 0 || mode > 2)
3139  return cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
3140 
3141  if (mode == 0 || degree < 0)
3142  return CPL_ERROR_NONE;
3143 
3144  if (wavemap) {
3145 
3146  /*
3147  * Fit with a polynomial the wavelength trend column by column.
3148  */
3149 
3150  cpl_image_turn(wavemap, -1); /* For faster memory access */
3151 
3152  nx = cpl_image_get_size_x(wavemap);
3153  ny = cpl_image_get_size_y(wavemap);
3154  data = cpl_image_get_data(wavemap);
3155 
3156  for (i = 0; i < ny; i++) {
3157 
3158  /*
3159  * First get the size of the vectors to allocate:
3160  * eliminate from fit any value with "impossible" wavelength.
3161  */
3162 
3163  npoints = 0;
3164  p = data + i*nx;
3165  for (j = 0; j < nx; j++)
3166  if (p[j] > 1.0)
3167  npoints++;
3168 
3169  if (npoints > polyorder + 1) {
3170 
3171  /*
3172  * Fill the vectors for the fitting
3173  */
3174 
3175  wave = cpl_vector_new(npoints);
3176  wdata = cpl_vector_get_data(wave);
3177  positions = cpl_vector_new(npoints);
3178  pdata = cpl_vector_get_data(positions);
3179 
3180  npoints = 0;
3181  p = data + i*nx;
3182  for (j = 0; j < nx; j++) {
3183  if (p[j] > 1.0) {
3184  wdata[npoints] = p[j];
3185  pdata[npoints] = j;
3186  npoints++;
3187  }
3188  }
3189 
3190  trend = cpl_polynomial_fit_1d_create(positions, wave,
3191  polyorder, &mse);
3192 
3193  ksigma = 3*sqrt(mse);
3194 
3195  cpl_vector_delete(wave);
3196  cpl_vector_delete(positions);
3197 
3198  if (trend) {
3199 
3200  /*
3201  * Apply 3-sigma rejection
3202  */
3203 
3204  rpoints = 0;
3205  p = data + i*nx;
3206  for (j = 0; j < nx; j++)
3207  if (p[j] > 1.0)
3208  if (fabs(cpl_polynomial_eval_1d(trend, j, NULL)
3209  - p[j]) < ksigma)
3210  rpoints++;
3211 
3212  if (rpoints < npoints && rpoints > polyorder + 1) {
3213 
3214  wave = cpl_vector_new(rpoints);
3215  wdata = cpl_vector_get_data(wave);
3216  positions = cpl_vector_new(rpoints);
3217  pdata = cpl_vector_get_data(positions);
3218 
3219  npoints = 0;
3220  p = data + i*nx;
3221  for (j = 0; j < nx; j++) {
3222  if (p[j] > 1.0) {
3223  if (fabs(cpl_polynomial_eval_1d(trend,
3224  j, NULL) - p[j])
3225  < ksigma) {
3226  wdata[npoints] = p[j];
3227  pdata[npoints] = j;
3228  npoints++;
3229  }
3230  }
3231  }
3232 
3233  cpl_polynomial_delete(trend);
3234  trend = cpl_polynomial_fit_1d_create(positions, wave,
3235  polyorder, NULL);
3236 
3237  cpl_vector_delete(wave);
3238  cpl_vector_delete(positions);
3239  }
3240  }
3241 
3242  if (trend) {
3243  p = data + i*nx;
3244  if (mode == 1) {
3245  for (j = 0; j < nx; j++)
3246  if (p[j] < 1.0)
3247  p[j] = cpl_polynomial_eval_1d(trend, j, NULL);
3248  }
3249  else if (mode == 2) {
3250  for (j = 0; j < nx; j++)
3251  p[j] = cpl_polynomial_eval_1d(trend, j, NULL);
3252  }
3253  cpl_polynomial_delete(trend);
3254  }
3255  else {
3256  cpl_msg_warning(func,
3257  "Invalid wavelength field fit (ignored)");
3258  }
3259  }
3260 
3261  }
3262 
3263  cpl_image_turn(wavemap, 1);
3264 
3265  }
3266 
3267 
3268  /*
3269  * Interpolating the IDS coefficients
3270  */
3271 
3272  nrows = cpl_table_get_nrow(idscoeff);
3273 
3274  order = 0;
3275  while (order < 6 && cpl_table_has_column(idscoeff, clab[order]))
3276  ++order;
3277  --order;
3278 
3279  if (degree == 0) {
3280  for (k = 0; k <= order; k++) {
3281  double m;
3282  if (cpl_table_has_column(idscoeff, clab[k])) {
3283  m = cpl_table_get_column_median(idscoeff, clab[k]);
3284  cpl_table_fill_column_window_double(idscoeff, clab[k],
3285  0, nrows, m);
3286  }
3287  }
3288 
3289  return CPL_ERROR_NONE;
3290  }
3291 
3292  first_row = 0;
3293  while (!cpl_table_is_valid(idscoeff, clab[0], first_row))
3294  first_row++;
3295 
3296  last_row = nrows - 1;
3297  while (!cpl_table_is_valid(idscoeff, clab[0], last_row))
3298  last_row--;
3299 
3300  for (k = 0; k <= order; k++) {
3301 
3302  npoints = nrows - cpl_table_count_invalid(idscoeff, clab[k]);
3303  wave = cpl_vector_new(npoints);
3304  wdata = cpl_vector_get_data(wave);
3305  positions = cpl_vector_new(npoints);
3306  pdata = cpl_vector_get_data(positions);
3307 
3308  npoints = 0;
3309  for (i = first_row; i <= last_row; i++) {
3310  c = cpl_table_get_double(idscoeff, clab[k], i, &null);
3311  if (null == 0) {
3312  wdata[npoints] = c;
3313  pdata[npoints] = i;
3314  npoints++;
3315  }
3316  }
3317 
3318 // This doesn't seem to provide good results, I have not understood why.
3319 // Restore for robust linear fitting.
3320 //
3321  if (degree == 1) {
3322  cpl_vector *p;
3323  cpl_vector *w;
3324  cpl_bivector *list;
3325  double q, m;
3326 
3327  if (npoints > 4) {
3328  p = cpl_vector_extract(positions, 2, npoints - 2, 1);
3329  w = cpl_vector_extract(wave, 2, npoints - 2, 1);
3330  }
3331  else {
3332  p = positions;
3333  w = wave;
3334  }
3335 
3336  list = cpl_bivector_wrap_vectors(p, w);
3337 
3338  robustLinearFit(list, &q, &m, &mse);
3339  cpl_bivector_unwrap_vectors(list);
3340  for (i = first_row; i <= last_row; i++)
3341  cpl_table_set_double(idscoeff, clab[k], i, q + m*i);
3342 
3343  if (npoints > 4) {
3344  cpl_vector_delete(p);
3345  cpl_vector_delete(w);
3346  }
3347 
3348  cpl_vector_delete(wave);
3349  cpl_vector_delete(positions);
3350 
3351  continue;
3352  }
3353 
3354 // End robust linear fitting
3355 
3356  trend = cpl_polynomial_fit_1d_create(positions, wave, degree, &mse);
3357 
3358  ksigma = 3*sqrt(mse);
3359 
3360  cpl_vector_delete(wave);
3361  cpl_vector_delete(positions);
3362 
3363  /*
3364  * Iteration
3365  */
3366 
3367  if (trend) {
3368  rpoints = 0;
3369  for (i = first_row; i <= last_row; i++) {
3370  c = cpl_table_get_double(idscoeff, clab[k], i, &null);
3371  if (null == 0) {
3372  if (fabs(cpl_polynomial_eval_1d(trend, i, NULL) - c)
3373  < ksigma) {
3374  rpoints++;
3375  }
3376  }
3377  }
3378 
3379  if (rpoints > 0 && rpoints < npoints) {
3380  cpl_msg_debug(func, "%d points rejected from "
3381  "wavelength calibration fit",
3382  npoints - rpoints);
3383 
3384  wave = cpl_vector_new(rpoints);
3385  wdata = cpl_vector_get_data(wave);
3386  positions = cpl_vector_new(rpoints);
3387  pdata = cpl_vector_get_data(positions);
3388 
3389  npoints = 0;
3390  for (i = first_row; i <= last_row; i++) {
3391  c = cpl_table_get_double(idscoeff, clab[k], i, &null);
3392  if (null == 0) {
3393  if (fabs(cpl_polynomial_eval_1d(trend, i, NULL) - c)
3394  < ksigma) {
3395  wdata[npoints] = c;
3396  pdata[npoints] = i;
3397  npoints++;
3398  }
3399  }
3400  }
3401 
3402  if (npoints) {
3403  cpl_polynomial_delete(trend);
3404  trend = cpl_polynomial_fit_1d_create(positions,
3405  wave, degree, NULL);
3406  }
3407 
3408  cpl_vector_delete(wave);
3409  cpl_vector_delete(positions);
3410 
3411  }
3412  }
3413 
3414  if (trend) {
3415  for (i = first_row; i <= last_row; i++) {
3416  if (mode == 1) {
3417  if (!cpl_table_is_valid(idscoeff, clab[k], i)) {
3418  cpl_table_set_double(idscoeff, clab[k], i,
3419  cpl_polynomial_eval_1d(trend, i,
3420  NULL));
3421  }
3422  }
3423  else if (mode == 2) {
3424  cpl_table_set_double(idscoeff, clab[k], i,
3425  cpl_polynomial_eval_1d(trend, i, NULL));
3426  }
3427  }
3428  cpl_polynomial_delete(trend);
3429  }
3430  else {
3431  cpl_msg_warning(func, "Invalid IDS coefficient fit (ignored)");
3432  }
3433 
3434  }
3435 
3436  return CPL_ERROR_NONE;
3437 }
3438 
3456 cpl_error_code mos_interpolate_wavecalib_mos(cpl_table *idscoeff,
3457  int mode,
3458  int degree)
3459 {
3460  const char *func = "mos_interpolate_wavecalib";
3461 
3462  const char *clab[6] = {"c0", "c1", "c2", "c3", "c4", "c5"};
3463  /* Max order is 5 */
3464 
3465  cpl_vector *wave;
3466  cpl_vector *positions;
3467  cpl_polynomial *trend;
3468 
3469  double *wdata;
3470  double *pdata;
3471 
3472  double c;
3473  double mse, ksigma;
3474 
3475  int order;
3476  int nrows, first_row, last_row;
3477  int npoints, rpoints;
3478  int null;
3479  int i;
3480 
3481 
3482  if (idscoeff == NULL)
3483  return cpl_error_set(func, CPL_ERROR_NULL_INPUT);
3484 
3485  if (mode < 0 || mode > 2)
3486  return cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
3487 
3488  if (mode == 0 || degree < 0)
3489  return CPL_ERROR_NONE;
3490 
3491  /*
3492  * Interpolating the IDS coefficients
3493  */
3494 
3495  nrows = cpl_table_get_nrow(idscoeff);
3496 
3497  order = 0;
3498  while (order < 6 && cpl_table_has_column(idscoeff, clab[order]))
3499  ++order;
3500  --order;
3501 
3502  //For coefficients > 0 always do a median
3503 
3504  int kinit = 1;
3505  if (degree == 0)
3506  kinit = 0;
3507  for (int k = kinit; k <= order; k++) {
3508  double m;
3509  if (cpl_table_has_column(idscoeff, clab[k])) {
3510  m = cpl_table_get_column_median(idscoeff, clab[k]);
3511  cpl_table_fill_column_window_double(idscoeff, clab[k],
3512  0, nrows, m);
3513  }
3514  }
3515 
3516  if(degree > 0)
3517  {
3518  first_row = 0;
3519  while (!cpl_table_is_valid(idscoeff, clab[0], first_row))
3520  first_row++;
3521 
3522  last_row = nrows - 1;
3523  while (!cpl_table_is_valid(idscoeff, clab[0], last_row))
3524  last_row--;
3525 
3526  int korder = 0;
3527 
3528  npoints = nrows - cpl_table_count_invalid(idscoeff, clab[korder]);
3529  wave = cpl_vector_new(npoints);
3530  wdata = cpl_vector_get_data(wave);
3531  positions = cpl_vector_new(npoints);
3532  pdata = cpl_vector_get_data(positions);
3533 
3534  npoints = 0;
3535  for (i = first_row; i <= last_row; i++) {
3536  c = cpl_table_get_double(idscoeff, clab[korder], i, &null);
3537  if (null == 0) {
3538  wdata[npoints] = c;
3539  pdata[npoints] = i;
3540  npoints++;
3541  }
3542  }
3543 
3544  // This doesn't seem to provide good results, I have not understood why.
3545  // Restore for robust linear fitting.
3546  //
3547  if (degree == 1) {
3548  cpl_vector *p;
3549  cpl_vector *w;
3550  cpl_bivector *list;
3551  double q, m;
3552 
3553  if (npoints > 4) {
3554  p = cpl_vector_extract(positions, 2, npoints - 2, 1);
3555  w = cpl_vector_extract(wave, 2, npoints - 2, 1);
3556  }
3557  else {
3558  p = positions;
3559  w = wave;
3560  }
3561 
3562  list = cpl_bivector_wrap_vectors(p, w);
3563 
3564  robustLinearFit(list, &q, &m, &mse);
3565  cpl_bivector_unwrap_vectors(list);
3566  for (i = first_row; i <= last_row; i++)
3567  cpl_table_set_double(idscoeff, clab[korder], i, q + m*i);
3568 
3569  if (npoints > 4) {
3570  cpl_vector_delete(p);
3571  cpl_vector_delete(w);
3572  }
3573 
3574  cpl_vector_delete(wave);
3575  cpl_vector_delete(positions);
3576 
3577  }
3578  else
3579  {
3580 
3581  // End robust linear fitting
3582 
3583  trend = cpl_polynomial_fit_1d_create(positions, wave, degree, &mse);
3584 
3585  ksigma = 3*sqrt(mse);
3586 
3587  cpl_vector_delete(wave);
3588  cpl_vector_delete(positions);
3589 
3590  /*
3591  * Iteration
3592  */
3593 
3594  if (trend) {
3595  rpoints = 0;
3596  for (i = first_row; i <= last_row; i++) {
3597  c = cpl_table_get_double(idscoeff, clab[korder], i, &null);
3598  if (null == 0) {
3599  if (fabs(cpl_polynomial_eval_1d(trend, i, NULL) - c)
3600  < ksigma) {
3601  rpoints++;
3602  }
3603  }
3604  }
3605 
3606  if (rpoints > 0 && rpoints < npoints) {
3607  cpl_msg_debug(func, "%d points rejected from "
3608  "wavelength calibration fit",
3609  npoints - rpoints);
3610 
3611  wave = cpl_vector_new(rpoints);
3612  wdata = cpl_vector_get_data(wave);
3613  positions = cpl_vector_new(rpoints);
3614  pdata = cpl_vector_get_data(positions);
3615 
3616  npoints = 0;
3617  for (i = first_row; i <= last_row; i++) {
3618  c = cpl_table_get_double(idscoeff, clab[korder], i, &null);
3619  if (null == 0) {
3620  if (fabs(cpl_polynomial_eval_1d(trend, i, NULL) - c)
3621  < ksigma) {
3622  wdata[npoints] = c;
3623  pdata[npoints] = i;
3624  npoints++;
3625  }
3626  }
3627  }
3628 
3629  if (npoints) {
3630  cpl_polynomial_delete(trend);
3631  trend = cpl_polynomial_fit_1d_create(positions,
3632  wave, degree, NULL);
3633  }
3634 
3635  cpl_vector_delete(wave);
3636  cpl_vector_delete(positions);
3637 
3638  }
3639  }
3640 
3641  if (trend) {
3642  for (i = first_row; i <= last_row; i++) {
3643  if (mode == 1) {
3644  if (!cpl_table_is_valid(idscoeff, clab[korder], i)) {
3645  cpl_table_set_double(idscoeff, clab[korder], i,
3646  cpl_polynomial_eval_1d(trend, i,
3647  NULL));
3648  }
3649  }
3650  else if (mode == 2) {
3651  cpl_table_set_double(idscoeff, clab[korder], i,
3652  cpl_polynomial_eval_1d(trend, i, NULL));
3653  }
3654  }
3655  cpl_polynomial_delete(trend);
3656  }
3657  else {
3658  cpl_msg_warning(func, "Invalid IDS coefficient fit (ignored)");
3659  }
3660  }
3661  }
3662 
3663  return CPL_ERROR_NONE;
3664 }
3665 
3666 
3691 //TODO:Deprecate this function. Change all the recipes which still use it.
3692 //Use the new functionality to remove overscan, trimm and bias.
3693 
3694 cpl_image *mos_remove_bias(cpl_image *image, cpl_image *bias,
3695  cpl_table *overscans)
3696 {
3697  const char *func = "mos_remove_bias";
3698 
3699  cpl_image *unbiased;
3700  cpl_image *overscan;
3701  double mean_bias_level;
3702  double mean_overscans_level;
3703  int count;
3704  int nrows;
3705  int xlow, ylow, xhig, yhig;
3706  int i;
3707 
3708 
3709  if (image == NULL || overscans == NULL) {
3710  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
3711  return NULL;
3712  }
3713 
3714  nrows = cpl_table_get_nrow(overscans);
3715 
3716  if (nrows == 0) {
3717  cpl_msg_error(func, "Empty overscan table");
3718  cpl_error_set(func, CPL_ERROR_DATA_NOT_FOUND);
3719  return NULL;
3720  }
3721 
3722  if (bias) {
3723  if (nrows == 1) {
3724  unbiased = cpl_image_subtract_create(image, bias);
3725  if (unbiased == NULL) {
3726  cpl_msg_error(func, "Incompatible master bias");
3727  cpl_error_set(func, CPL_ERROR_INCOMPATIBLE_INPUT);
3728  }
3729  return unbiased;
3730  }
3731  mean_bias_level = cpl_image_get_mean(bias);
3732  }
3733  else {
3734  if (nrows == 1) {
3735  cpl_msg_error(func, "No master bias in input, and no overscan "
3736  "regions in input image: bias subtraction "
3737  "cannot be performed!");
3738  cpl_error_set(func, CPL_ERROR_DATA_NOT_FOUND);
3739  return NULL;
3740  }
3741  mean_bias_level = 0.0;
3742  }
3743 
3744  mean_overscans_level = 0.0;
3745  count = 0;
3746  for (i = 0; i < nrows; i++) {
3747  xlow = cpl_table_get_int(overscans, "xlow", i, NULL);
3748  ylow = cpl_table_get_int(overscans, "ylow", i, NULL);
3749  xhig = cpl_table_get_int(overscans, "xhig", i, NULL);
3750  yhig = cpl_table_get_int(overscans, "yhig", i, NULL);
3751 
3752  if (i == 0) {
3753  unbiased = cpl_image_extract(image, xlow+1, ylow+1, xhig, yhig);
3754  if (unbiased == NULL) {
3755  cpl_msg_error(func, "Incompatible overscan table");
3756  cpl_error_set(func, CPL_ERROR_INCOMPATIBLE_INPUT);
3757  return NULL;
3758  }
3759  if (bias) {
3760  if (cpl_image_subtract(unbiased, bias)) {
3761  cpl_msg_error(func, "Incompatible master bias");
3762  cpl_error_set(func, CPL_ERROR_INCOMPATIBLE_INPUT);
3763  cpl_image_delete(unbiased);
3764  return NULL;
3765  }
3766  }
3767  }
3768  else {
3769  overscan = cpl_image_extract(image, xlow+1, ylow+1, xhig, yhig);
3770  if (overscan == NULL) {
3771  cpl_msg_error(func, "Incompatible overscan table");
3772  cpl_error_set(func, CPL_ERROR_INCOMPATIBLE_INPUT);
3773  cpl_image_delete(unbiased);
3774  return NULL;
3775  }
3776 
3777  mean_overscans_level += cpl_image_get_median(overscan);
3778  count++;
3779 
3780 /***
3781  * Here the mean level was used: not very robust...
3782 
3783  mean_overscans_level += cpl_image_get_flux(overscan);
3784  count += cpl_image_get_size_x(overscan)
3785  * cpl_image_get_size_y(overscan);
3786 ***/
3787  cpl_image_delete(overscan);
3788  }
3789  }
3790 
3791  /*
3792  * Overscan correction
3793  */
3794 
3795  mean_overscans_level /= count;
3796 
3797  cpl_image_subtract_scalar(unbiased, mean_overscans_level - mean_bias_level);
3798 
3799  cpl_msg_info(cpl_func,
3800  "Difference between mean overscans level "
3801  "and mean bias level: %.2f",
3802  mean_overscans_level - mean_bias_level);
3803 
3804  return unbiased;
3805 
3806 }
3807 
3808 
3867 cpl_error_code mos_arc_background_1D(float *spectrum, float *back,
3868  int length, int msize, int fsize)
3869 {
3870  const char *func = "mos_arc_background_1D";
3871 
3872  float *minf;
3873  float *maxf;
3874  float *smof;
3875  int i;
3876 
3877 
3878  if (spectrum == NULL || back == NULL)
3879  return cpl_error_set(func, CPL_ERROR_NULL_INPUT);
3880 
3881  if (msize % 2 == 0)
3882  msize++;
3883 
3884  if (fsize % 2 == 0)
3885  fsize++;
3886 
3887  if (msize < 3 || fsize < msize || length < 2*fsize)
3888  return cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
3889 
3890 
3891  minf = min_filter(spectrum, length, msize);
3892  smof = smo_filter(minf, length, fsize);
3893  cpl_free(minf);
3894  maxf = max_filter(smof, length, 2*msize+1);
3895  cpl_free(smof);
3896  smof = smo_filter(maxf, length, 2*fsize+1);
3897  cpl_free(maxf);
3898  minf = min_filter(smof, length, 2*msize+1);
3899  cpl_free(smof);
3900  smof = smo_filter(minf, length, 2*fsize+1);
3901  cpl_free(minf);
3902 
3903  for (i = 0; i < length; i++)
3904  back[i] = smof[i];
3905 
3906  cpl_free(smof);
3907 
3908  return CPL_ERROR_NONE;
3909 
3910 }
3911 
3912 
3969 cpl_image *mos_arc_background(cpl_image *image, int msize, int fsize)
3970 {
3971  const char *func = "mos_arc_background";
3972 
3973  cpl_image *fimage;
3974  cpl_image *bimage;
3975  float *data;
3976  float *bdata;
3977  float *row;
3978  float *brow;
3979  int nx, ny;
3980  int i;
3981 
3982 
3983  if (image == NULL) {
3984  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
3985  return NULL;
3986  }
3987 
3988  if (msize % 2 == 0)
3989  msize++;
3990 
3991  if (fsize % 2 == 0)
3992  fsize++;
3993 
3994  nx = cpl_image_get_size_x(image);
3995  ny = cpl_image_get_size_y(image);
3996 
3997  bimage = cpl_image_new(nx, ny, CPL_TYPE_FLOAT);
3998 
3999  fimage = mos_image_filter_median(image, 3, 3);
4000 
4001  data = cpl_image_get_data_float(fimage);
4002  bdata = cpl_image_get_data_float(bimage);
4003 
4004  for (i = 0; i < ny; i++) {
4005  row = data + i * nx;
4006  brow = bdata + i * nx;
4007  if (mos_arc_background_1D(row, brow, nx, msize, fsize)) {
4008  cpl_error_set_where(func);
4009  cpl_image_delete(fimage);
4010  cpl_image_delete(bimage);
4011  return NULL;
4012  }
4013  }
4014 
4015  cpl_image_delete(fimage);
4016 
4017  return bimage;
4018 }
4019 
4020 
4041 int mos_lines_width(const float *spectrum, int length)
4042 {
4043 
4044  const char *func = "mos_lines_width";
4045 
4046  double *profile1 = cpl_calloc(length - 1, sizeof(double));
4047  double *profile2 = cpl_calloc(length - 1, sizeof(double));
4048 
4049  double norm, value, max;
4050  int radius = 20;
4051  int short_length = length - 2*radius - 1;
4052  int width;
4053  int i, j, k;
4054 
4055 
4056  /*
4057  * Derivative, and separation of positive and negative derivatives
4058  */
4059 
4060  for (j = 0, i = 1; i < length; j++, i++) {
4061  profile1[j] = profile2[j] = spectrum[i] - spectrum[j];
4062  if (profile1[j] < 0)
4063  profile1[j] = 0;
4064  if (profile2[j] > 0)
4065  profile2[j] = 0;
4066  else
4067  profile2[j] = -profile2[j];
4068  }
4069 
4070 
4071  /*
4072  * Profiles normalisation
4073  */
4074 
4075  length--;
4076 
4077  norm = 0;
4078  for (i = 0; i < length; i++)
4079  if (norm < profile1[i])
4080  norm = profile1[i];
4081 
4082  for (i = 0; i < length; i++) {
4083  profile1[i] /= norm;
4084  profile2[i] /= norm;
4085  }
4086 
4087 
4088  /*
4089  * Cross-correlation
4090  */
4091 
4092  max = -1;
4093  for (i = 0; i <= radius; i++) {
4094  value = 0;
4095  for (j = 0; j < short_length; j++) {
4096  k = radius+j;
4097  value += profile1[k] * profile2[k+i];
4098  }
4099  if (max < value) {
4100  max = value;
4101  width = i;
4102  }
4103  }
4104 
4105  cpl_free(profile1);
4106  cpl_free(profile2);
4107 
4108  if (max < 0.0) {
4109  cpl_msg_debug(func, "Cannot estimate line width");
4110  width = 1;
4111  }
4112 
4113  return width;
4114 
4115 }
4116 
4117 
4144 cpl_vector *mos_peak_candidates(const float *spectrum,
4145  int length, float level,
4146  float exp_width)
4147 {
4148 
4149  const char *func = "mos_peak_candidates";
4150 
4151  int i, j;
4152  int nint = length - 1;
4153  int n = 0;
4154  int width = 2 * ceil(exp_width / 2) + 1;
4155  int start = width / 2;
4156  int end = length - width / 2;
4157  int step;
4158  float *smo;
4159  double *data = cpl_calloc(length/2, sizeof(double));
4160 
4161 
4162  if (spectrum == NULL) {
4163  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
4164  return NULL;
4165  }
4166 
4167 
4168  /*
4169  * If lines have a flat top (as in the case of broad slit), smooth
4170  * before determining the max.
4171  */
4172 
4173  if (width > 7) {
4174  smo = cpl_calloc(length, sizeof(float));
4175  start = width / 2;
4176  end = length - width / 2;
4177  for (i = 0; i < start; i++)
4178  smo[i] = spectrum[i];
4179  for (i = start; i < end; i++) {
4180  for (j = i - start; j <= i + start; j++)
4181  smo[i] += spectrum[j];
4182  smo[i] /= width;
4183  }
4184  for (i = end; i < length; i++)
4185  smo[i] = spectrum[i];
4186  }
4187  else {
4188  smo = (float *)spectrum;
4189  }
4190 
4191  /*
4192  * Collect all relative maxima along spectrum, that are higher than the
4193  * specified level.
4194  */
4195 
4196  if (width > 20)
4197  step = width / 2;
4198  else
4199  step = 1;
4200 
4201  for (i = step; i < nint - step + 1; i += step) {
4202  if (smo[i] > level) {
4203  if (smo[i] >= smo[i-step] && smo[i] > smo[i+step]) {
4204  if (smo[i-step] != 0.0 && smo[i+step] != 0.0) {
4205  data[n] = i + step * values_to_dx(smo[i-step], smo[i], smo[i+step]);
4206  ++n;
4207  }
4208  }
4209  }
4210  }
4211 
4212  if (width > 7) {
4213  cpl_free(smo);
4214  }
4215 
4216  if (n == 0) {
4217  cpl_free(data);
4218  return NULL;
4219  }
4220 
4221  return cpl_vector_wrap(n, data);
4222 
4223 }
4224 
4225 
4247 cpl_vector *mos_refine_peaks(const float *spectrum, int length,
4248  cpl_vector *peaks, int sradius)
4249 {
4250 
4251  const char *func = "mos_refine_peaks";
4252 
4253  double *data;
4254  float pos;
4255  int npeaks;
4256  int startPos, endPos;
4257  int window = 2*sradius+1;
4258  int i, j;
4259 
4260 
4261  if (peaks == NULL || spectrum == NULL) {
4262  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
4263  return NULL;
4264  }
4265 
4266  npeaks = cpl_vector_get_size(peaks);
4267  data = cpl_vector_unwrap(peaks);
4268 
4269  for (i = 0; i < npeaks; i++) {
4270  startPos = data[i] - window/2;
4271  endPos = startPos + window;
4272  if (startPos < 0 || endPos >= length)
4273  continue;
4274 
4275  if (0 == peakPosition(spectrum + startPos, window, &pos, 1)) {
4276  pos += startPos;
4277  data[i] = pos;
4278  }
4279  }
4280 
4281  for (i = 1; i < npeaks; i++)
4282  if (data[i] - data[i-1] < 0.5)
4283  data[i-1] = -1.0;
4284 
4285  for (i = 0, j = 0; i < npeaks; i++) {
4286  if (data[i] > 0.0) {
4287  if (i != j)
4288  data[j] = data[i];
4289  j++;
4290  }
4291  }
4292 
4293  return cpl_vector_wrap(j, data);
4294 
4295 }
4296 
4297 
4298 void mos_set_multiplex(int multiplex)
4299 {
4300  mos_multiplex = multiplex;
4301 }
4302 
4356 cpl_bivector *mos_identify_peaks(cpl_vector *peaks, cpl_vector *lines,
4357  double min_disp, double max_disp,
4358  double tolerance)
4359 {
4360 
4361  int i, j, k, l;
4362  int nlint, npint;
4363  int minpos;
4364  float min;
4365  double lratio, pratio;
4366  double lo_start, lo_end, hi_start, hi_end, denom;
4367  double disp, variation, prev_variation;
4368  int max, maxpos, minl, mink;
4369  int ambiguous;
4370  int npeaks_lo, npeaks_hi;
4371  int *peak_lo;
4372  int *peak_hi;
4373  int **ident;
4374  int *nident;
4375  int *lident;
4376 
4377  double *peak;
4378  double *line;
4379  int npeaks, nlines;
4380 
4381  double *xpos;
4382  double *lambda;
4383  int *ilambda;
4384  double *tmp_xpos;
4385  double *tmp_lambda;
4386  int *tmp_ilambda;
4387  int *flag;
4388  int n = 0;
4389  int nn;
4390  int nseq = 0;
4391  int gap;
4392  int *seq_length;
4393  int found;
4394 
4395  peak = cpl_vector_get_data(peaks);
4396  npeaks = cpl_vector_get_size(peaks);
4397  line = cpl_vector_get_data(lines);
4398  nlines = cpl_vector_get_size(lines);
4399 
4400  if (npeaks < 4)
4401  return NULL;
4402 
4403  peak_lo = cpl_malloc(npeaks * sizeof(int));
4404  peak_hi = cpl_malloc(npeaks * sizeof(int));
4405  nident = cpl_calloc(npeaks, sizeof(int));
4406  lident = cpl_calloc(nlines, sizeof(int));
4407  xpos = cpl_calloc(npeaks, sizeof(double));
4408  lambda = cpl_calloc(npeaks, sizeof(double));
4409  ilambda = cpl_calloc(npeaks, sizeof(int));
4410  tmp_xpos = cpl_calloc(npeaks, sizeof(double));
4411  tmp_lambda = cpl_calloc(npeaks, sizeof(double));
4412  tmp_ilambda = cpl_calloc(npeaks, sizeof(int));
4413  flag = cpl_calloc(npeaks, sizeof(int));
4414  seq_length = cpl_calloc(npeaks, sizeof(int));
4415  ident = cpl_malloc(npeaks * sizeof(int *));
4416  for (i = 0; i < npeaks; i++)
4417  ident[i] = cpl_malloc(3 * npeaks * sizeof(int));
4418 
4419  /*
4420  * This is just the number of intervals - one less than the number
4421  * of points (catalog wavelengths, or detected peaks).
4422  */
4423 
4424  nlint = nlines - 1;
4425  npint = npeaks - 1;
4426 
4427 
4428  /*
4429  * Here the big loops on catalog lines begins.
4430  */
4431 
4432  for (i = 1; i < nlint; i++) {
4433 
4434 
4435  /*
4436  * For each catalog wavelength I take the previous and the next one,
4437  * and compute the ratio of the corresponding wavelength intervals.
4438  * This ratio will be compared to all the ratios obtained doing the
4439  * same with all the detected peaks positions.
4440  */
4441 
4442  lratio = (line[i+1] - line[i]) / (line[i] - line[i-1]);
4443 
4444 
4445  /*
4446  * Here the loop on detected peaks positions begins.
4447  */
4448 
4449  for (j = 1; j < npint; j++) {
4450 
4451  /*
4452  * Not all peaks are used for computing ratios: just the ones
4453  * that are compatible with the expected spectral dispersion
4454  * are taken into consideration. Therefore, I define the pixel
4455  * intervals before and after any peak that are compatible with
4456  * the specified dispersion interval, and select just the peaks
4457  * within such intervals. If either of the two intervals doesn't
4458  * contain any peak, then I skip the current peak and continue
4459  * with the next.
4460  */
4461 
4462  lo_start = peak[j] - (line[i] - line[i-1]) / min_disp;
4463  lo_end = peak[j] - (line[i] - line[i-1]) / max_disp;
4464  hi_start = peak[j] + (line[i+1] - line[i]) / max_disp;
4465  hi_end = peak[j] + (line[i+1] - line[i]) / min_disp;
4466 
4467  for (npeaks_lo = 0, k = 0; k < npeaks; k++) {
4468  if (peak[k] > lo_end)
4469  break;
4470  if (peak[k] > lo_start) {
4471  peak_lo[npeaks_lo] = k;
4472  ++npeaks_lo;
4473  }
4474  }
4475 
4476  if (npeaks_lo == 0)
4477  continue;
4478 
4479  for (npeaks_hi = 0, k = 0; k < npeaks; k++) {
4480  if (peak[k] > hi_end)
4481  break;
4482  if (peak[k] > hi_start) {
4483  peak_hi[npeaks_hi] = k;
4484  ++npeaks_hi;
4485  }
4486  }
4487 
4488  if (npeaks_hi == 0)
4489  continue;
4490 
4491 
4492  /*
4493  * Now I have all peaks that may help for a local identification.
4494  * peak_lo[k] is the sequence number of the k-th peak of the lower
4495  * interval; peak_hi[l] is the sequence number of the l-th peak of
4496  * the higher interval. j is, of course, the sequence number of the
4497  * current peak (second big loop).
4498  */
4499 
4500  prev_variation = 1000.0;
4501  minl = mink = 0;
4502 
4503  for (k = 0; k < npeaks_lo; k++) {
4504  denom = peak[j] - peak[peak_lo[k]];
4505  for (l = 0; l < npeaks_hi; l++) {
4506 
4507  /*
4508  * For any pair of peaks - one from the lower and the other
4509  * from the higher interval - I compute the same ratio that
4510  * was computed with the current line catalog wavelength.
4511  */
4512 
4513  pratio = (peak[peak_hi[l]] - peak[j]) / denom;
4514 
4515  /*
4516  * If the two ratios are compatible within the specified
4517  * tolerance, we have a preliminary identification. This
4518  * will be marked in the matrix ident[][], where the first
4519  * index corresponds to a peak sequence number, and the second
4520  * index is the counter of the identifications made during
4521  * this whole process. The array of counters is nident[].
4522  * If more than one interval pair fulfills the specified
4523  * tolerance, the closest to the expected ratio is selected.
4524  */
4525 
4526  variation = fabs(lratio-pratio) / pratio;
4527 
4528  if (variation < tolerance) {
4529  if (variation < prev_variation) {
4530  prev_variation = variation;
4531  minl = l;
4532  mink = k;
4533  }
4534  }
4535  }
4536  }
4537  if (prev_variation < tolerance) {
4538  ident[j][nident[j]] = i;
4539  ident[peak_hi[minl]][nident[peak_hi[minl]]] = i + 1;
4540  ident[peak_lo[mink]][nident[peak_lo[mink]]] = i - 1;
4541  ++nident[j];
4542  ++nident[peak_hi[minl]];
4543  ++nident[peak_lo[mink]];
4544  }
4545  } /* End loop on positions */
4546  } /* End loop on lines */
4547 
4548 
4549  /*
4550  * At this point I have filled the ident matrix with all my preliminary
4551  * identifications. Ambiguous identifications must be eliminated.
4552  */
4553 
4554 
4555  for (i = 0; i < npeaks; i++) {
4556 
4557 
4558  /*
4559  * I don't take into consideration peaks that were never identified.
4560  * They are likely contaminations, or emission lines that were not
4561  * listed in the input wavelength catalog.
4562  */
4563 
4564  if (nident[i] > 1) {
4565 
4566 
4567  /*
4568  * Initialise the histogram of wavelengths assigned to the i-th peak.
4569  */
4570 
4571  for (j = 0; j < nlines; j++)
4572  lident[j] = 0;
4573 
4574 
4575  /*
4576  * Count how many times each catalog wavelength was assigned
4577  * to the i-th peak.
4578  */
4579 
4580  for (j = 0; j < nident[i]; j++)
4581  ++lident[ident[i][j]];
4582 
4583 
4584  /*
4585  * What wavelength was most frequently assigned to the i-th peak?
4586  */
4587 
4588  max = 0;
4589  maxpos = 0;
4590  for (j = 0; j < nlines; j++) {
4591  if (max < lident[j]) {
4592  max = lident[j];
4593  maxpos = j;
4594  }
4595  }
4596 
4597 
4598  /*
4599  * Were there other wavelengths assigned with the same frequency?
4600  * This would be the case of an ambiguous identification. It is
4601  * safer to reject this peak...
4602  */
4603 
4604  ambiguous = 0;
4605 
4606  for (k = maxpos + 1; k < nlines; k++) {
4607  if (lident[k] == max) {
4608  ambiguous = 1;
4609  break;
4610  }
4611  }
4612 
4613  if (ambiguous)
4614  continue;
4615 
4616 
4617  /*
4618  * Otherwise, I assign to the i-th peak the wavelength that was
4619  * most often assigned to it.
4620  */
4621 
4622  tmp_xpos[n] = peak[i];
4623  tmp_lambda[n] = line[maxpos];
4624  tmp_ilambda[n] = maxpos;
4625 
4626  ++n;
4627 
4628  }
4629 
4630  }
4631 
4632 
4633  /*
4634  * Check on identified peaks. Contaminations from other spectra might
4635  * be present and should be excluded: this type of contamination
4636  * consists of peaks that have been _correctly_ identified! The non-
4637  * spectral type of light contamination should have been almost all
4638  * removed already in the previous steps, but it may still be present.
4639  * Here, the self-consistent sequences of identified peaks are
4640  * separated one from the other. At the moment, just the longest of
4641  * such sequences is selected (in other words, spectral multiplexing
4642  * is ignored).
4643  */
4644 
4645  if (n > 1) {
4646  nn = 0; /* Number of peaks in the list of sequences */
4647  nseq = 0; /* Current sequence */
4648  for (k = 0; k < n; k++) {
4649  if (flag[k] == 0) { /* Was peak k already assigned to a sequence? */
4650  flag[k] = 1;
4651  xpos[nn] = tmp_xpos[k]; /* Begin the nseq-th sequence */
4652  lambda[nn] = tmp_lambda[k];
4653  ilambda[nn] = tmp_ilambda[k];
4654  ++seq_length[nseq];
4655  ++nn;
4656 
4657  /*
4658  * Now look for all the following peaks that are compatible
4659  * with the expected spectral dispersion, and add them in
4660  * sequence to xpos. Note that missing peaks are not a problem...
4661  */
4662 
4663  i = k;
4664  while (i < n - 1) {
4665  found = 0;
4666  for (j = i + 1; j < n; j++) {
4667  if (flag[j] == 0) {
4668  disp = (tmp_lambda[j] - tmp_lambda[i])
4669  / (tmp_xpos[j] - tmp_xpos[i]);
4670  if (disp >= min_disp && disp <= max_disp) {
4671  flag[j] = 1;
4672  xpos[nn] = tmp_xpos[j];
4673  lambda[nn] = tmp_lambda[j];
4674  ilambda[nn] = tmp_ilambda[j];
4675  ++seq_length[nseq];
4676  ++nn;
4677  i = j;
4678  found = 1;
4679  break;
4680  }
4681  }
4682  }
4683  if (!found)
4684  break;
4685  }
4686 
4687  /*
4688  * Current sequence is completed: begin new sequence on the
4689  * excluded peaks, starting the loop on peaks again.
4690  */
4691 
4692  ++nseq;
4693  k = 0;
4694  }
4695  }
4696 
4697 
4698  /*
4699  * Find the longest sequence of self-consistent peaks.
4700  */
4701 
4702  maxpos = max = 0;
4703 
4704  if (mos_multiplex < 0) {
4705  for (i = 0; i < nseq; i++) {
4706  if (seq_length[i] > max) {
4707  max = seq_length[i];
4708  maxpos = i;
4709  }
4710  }
4711  }
4712  else {
4713 
4714  /*
4715  * Now consider the sequence which lays in the specified
4716  * CCD region (indicated by mos_multiplex): that is, _most_
4717  * of its lines (more than half) must be in that region...
4718  */
4719 
4720  nn = 0;
4721  found = 0;
4722 
4723  for (i = 0; i < nseq; i++) {
4724  n = seq_length[i];
4725  if (n > 5) {
4726  cpl_array *regions = cpl_array_new(n, CPL_TYPE_INT);
4727  int region;
4728 
4729  for (j = 0; j < n; j++)
4730  cpl_array_set_int(regions, j,
4731  ((int)floor(xpos[nn + j])) / mos_region_size);
4732 
4733  region = (int)cpl_array_get_median(regions);
4734  cpl_array_delete(regions);
4735 
4736  if (mos_multiplex == region) {
4737  if (found) {
4738  cpl_msg_debug(cpl_func, "More than one spectrum found in "
4739  "region %d (only the first one is extracted)",
4740  mos_multiplex);
4741  break;
4742  }
4743  found = 1;
4744  max = seq_length[i];
4745  maxpos = i;
4746  }
4747  }
4748  nn += seq_length[i];
4749  }
4750  }
4751 
4752  /*
4753  * Find where this sequence starts in the whole peak position
4754  * storage.
4755  */
4756 
4757  nn = 0;
4758  for (i = 0; i < maxpos; i++)
4759  nn += seq_length[i];
4760 
4761  /*
4762  * Move the longest sequence at the beginning of the returned lists
4763  */
4764 
4765  n = max;
4766  for (i = 0; i < n; i++, nn++) {
4767  xpos[i] = xpos[nn];
4768  lambda[i] = lambda[nn];
4769  ilambda[i] = ilambda[nn];
4770  }
4771 
4772 
4773  /*
4774  * Are some wavelengths missing? Recover them.
4775  */
4776 
4777  for (i = 1; i < n; i++) {
4778  gap = ilambda[i] - ilambda[i-1];
4779  for (j = 1; j < gap; j++) {
4780 
4781  if (j == 1) {
4782 
4783  /*
4784  * Determine the local dispersion from the current pair of peaks
4785  */
4786 
4787  disp = (lambda[i] - lambda[i-1]) / (xpos[i] - xpos[i-1]);
4788  }
4789 
4790  /*
4791  * With this, find the expected position of the missing
4792  * peak by linear interpolation.
4793  */
4794 
4795  hi_start = xpos[i-1] + (line[ilambda[i-1] + j] - lambda[i-1]) / disp;
4796 
4797  /*
4798  * Is there a peak at that position? Here a peak from the
4799  * original list is searched, that is closer than 2 pixels
4800  * to the expected position. If it is found, insert it at
4801  * the current position on the list of identified peaks,
4802  * and leave immediately the loop (taking the new position
4803  * for the following linear interpolation, in case more
4804  * than one peak is missing in the current interval).
4805  * If it is not found, stay in the loop, looking for
4806  * the following missing peaks in this interval.
4807  */
4808 
4809  found = 0;
4810  for (k = 0; k < npeaks; k++) {
4811  if (fabs(peak[k] - hi_start) < 2) {
4812  for (l = n; l > i; l--) {
4813  xpos[l] = xpos[l-1];
4814  lambda[l] = lambda[l-1];
4815  ilambda[l] = ilambda[l-1];
4816  }
4817  xpos[i] = peak[k];
4818  lambda[i] = line[ilambda[i-1] + j];
4819  ilambda[i] = ilambda[i-1] + j;
4820  ++n;
4821  found = 1;
4822  break;
4823  }
4824  }
4825  if (found)
4826  break;
4827  }
4828  }
4829 
4830 
4831  /*
4832  * Try to extrapolate forward
4833  */
4834 
4835  found = 1;
4836  while (ilambda[n-1] < nlines - 1 && found) {
4837 
4838  /*
4839  * Determine the local dispersion from the last pair of
4840  * identified peaks
4841  */
4842 
4843  if (n > 1)
4844  disp = (lambda[n-1] - lambda[n-2]) / (xpos[n-1] - xpos[n-2]);
4845  else
4846  disp = 0.0;
4847 
4848  if (disp > max_disp || disp < min_disp)
4849  break;
4850 
4851 
4852  /*
4853  * With this, find the expected position of the missing
4854  * peak by linear interpolation.
4855  */
4856 
4857  hi_start = xpos[n-1] + (line[ilambda[n-1] + 1] - lambda[n-1]) / disp;
4858 
4859  /*
4860  * Is there a peak at that position? Here a peak from the
4861  * original list is searched, that is closer than 6 pixels
4862  * to the expected position. If it is found, insert it at
4863  * the end of the list of identified peaks. If it is not
4864  * found, leave the loop.
4865  */
4866 
4867  found = 0;
4868  min = fabs(peak[0] - hi_start);
4869  minpos = 0;
4870  for (k = 1; k < npeaks; k++) {
4871  if (min > fabs(peak[k] - hi_start)) {
4872  min = fabs(peak[k] - hi_start);
4873  minpos = k;
4874  }
4875  }
4876  if (min < 6 && fabs(peak[minpos] - xpos[n-1]) > 1.0) {
4877  xpos[n] = peak[minpos];
4878  lambda[n] = line[ilambda[n-1] + 1];
4879  ilambda[n] = ilambda[n-1] + 1;
4880  ++n;
4881  found = 1;
4882  }
4883  }
4884 
4885 
4886  /*
4887  * Try to extrapolate backward
4888  */
4889 
4890  found = 1;
4891  while (ilambda[0] > 0 && found) {
4892 
4893  /*
4894  * Determine the local dispersion from the first pair of
4895  * identified peaks
4896  */
4897 
4898  disp = (lambda[1] - lambda[0]) / (xpos[1] - xpos[0]);
4899 
4900  if (disp > max_disp || disp < min_disp)
4901  break;
4902 
4903 
4904  /*
4905  * With this, find the expected position of the missing
4906  * peak by linear interpolation.
4907  */
4908 
4909  hi_start = xpos[0] - (lambda[0] - line[ilambda[0] - 1]) / disp;
4910 
4911 
4912  /*
4913  * Is there a peak at that position? Here a peak from the
4914  * original list is searched, that is closer than 6 pixels
4915  * to the expected position. If it is found, insert it at
4916  * the beginning of the list of identified peaks. If it is not
4917  * found, leave the loop.
4918  */
4919 
4920  found = 0;
4921  min = fabs(peak[0] - hi_start);
4922  minpos = 0;
4923  for (k = 1; k < npeaks; k++) {
4924  if (min > fabs(peak[k] - hi_start)) {
4925  min = fabs(peak[k] - hi_start);
4926  minpos = k;
4927  }
4928  }
4929  if (min < 6 && fabs(peak[minpos] - xpos[0]) > 1.0) {
4930  for (j = n; j > 0; j--) {
4931  xpos[j] = xpos[j-1];
4932  lambda[j] = lambda[j-1];
4933  ilambda[j] = ilambda[j-1];
4934  }
4935  xpos[0] = peak[minpos];
4936  lambda[0] = line[ilambda[0] - 1];
4937  ilambda[0] = ilambda[0] - 1;
4938  ++n;
4939  found = 1;
4940  }
4941  }
4942  }
4943 
4944 
4945  /*
4946  * At this point all peaks are processed. Free memory, and return
4947  * the result.
4948  */
4949 
4950 /************************************************+
4951  for (i = 0; i < npeaks; i++) {
4952  printf("Peak %d:\n ", i);
4953  for (j = 0; j < nident[i]; j++)
4954  printf("%.2f, ", line[ident[i][j]]);
4955  printf("\n");
4956  }
4957 
4958  printf("\n");
4959 
4960  for (i = 0; i < n; i++)
4961  printf("%.2f, %.2f\n", xpos[i], lambda[i]);
4962 +************************************************/
4963  for (i = 0; i < npeaks; i++)
4964  cpl_free(ident[i]);
4965  cpl_free(ident);
4966  cpl_free(nident);
4967  cpl_free(lident);
4968  cpl_free(ilambda);
4969  cpl_free(tmp_xpos);
4970  cpl_free(tmp_lambda);
4971  cpl_free(tmp_ilambda);
4972  cpl_free(peak_lo);
4973  cpl_free(flag);
4974  cpl_free(seq_length);
4975  cpl_free(peak_hi);
4976 
4977  if (n == 0) {
4978  cpl_free(xpos);
4979  cpl_free(lambda);
4980  return NULL;
4981  }
4982 
4983  return cpl_bivector_wrap_vectors(cpl_vector_wrap(n, xpos),
4984  cpl_vector_wrap(n, lambda));
4985 }
4986 
4987 
5005 /*
5006 double mos_eval_dds(cpl_polynomial *ids, double blue, double red,
5007  double refwave, double pixel)
5008 {
5009  double yellow;
5010  double cpixel;
5011  double tolerance = 0.02;
5012  int max_iter = 20;
5013  int iter = 0;
5014 
5015 
5016  if (cpl_polynomial_eval_1d(ids, blue-refwave, NULL) > pixel)
5017  return 0.0;
5018 
5019  if (cpl_polynomial_eval_1d(ids, red-refwave, NULL) < pixel)
5020  return 0.0;
5021 
5022  yellow = (blue + red) / 2;
5023 
5024  cpixel = cpl_polynomial_eval_1d(ids, yellow-refwave, NULL);
5025 
5026  while (fabs(cpixel - pixel) > tolerance && iter < max_iter) {
5027 
5028  if (cpixel > pixel)
5029  red = yellow;
5030  else
5031  blue = yellow;
5032 
5033  yellow = (blue + red) / 2;
5034  cpixel = cpl_polynomial_eval_1d(ids, yellow-refwave, NULL);
5035 
5036  iter++;
5037 
5038  }
5039 
5040  return yellow;
5041 
5042 }
5043 */
5044 
5045 double mos_eval_dds(cpl_polynomial *ids, double blue, double red,
5046  double refwave, double pixel)
5047 {
5048  double yellow;
5049  double coeff;
5050  cpl_size zero = 0;
5051 
5052  if (cpl_polynomial_eval_1d(ids, blue-refwave, NULL) > pixel)
5053  return 0.0;
5054 
5055  if (cpl_polynomial_eval_1d(ids, red-refwave, NULL) < pixel)
5056  return 0.0;
5057 
5058  yellow = (blue + red) / 2 - refwave;
5059 
5060  coeff = cpl_polynomial_get_coeff(ids, &zero);
5061  cpl_polynomial_set_coeff(ids, &zero, coeff - pixel);
5062 
5063  cpl_polynomial_solve_1d(ids, yellow, &yellow, 1);
5064  if (cpl_error_get_code() != CPL_ERROR_NONE) {
5065  cpl_error_reset();
5066  return 0.0;
5067  }
5068 
5069  cpl_polynomial_set_coeff(ids, &zero, coeff);
5070 
5071  return yellow + refwave;
5072 
5073 }
5074 
5100 cpl_polynomial *mos_poly_wav2pix(cpl_bivector *pixwav, int order,
5101  double reject, int minlines,
5102  int *nlines, double *err)
5103 {
5104  const char *func = "mos_poly_wav2pix";
5105 
5106  cpl_bivector *pixwav2;
5107  cpl_vector *wavel;
5108  cpl_vector *pixel;
5109  double *d_wavel;
5110  double *d_pixel;
5111  double pixpos;
5112  int fitlines;
5113  int rejection = 0;
5114  int i, j;
5115 
5116  cpl_polynomial *ids;
5117 
5118 
5119  *nlines = 0;
5120  *err = 0;
5121 
5122  if (pixwav == NULL) {
5123  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
5124  return NULL;
5125  }
5126 
5127  fitlines = cpl_bivector_get_size(pixwav);
5128 
5129  if (fitlines < minlines) {
5130  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
5131  return NULL;
5132  }
5133 
5134 
5135  /*
5136  * If outliers rejection was requested, allocate a working
5137  * vector (that can be modified as soon as outliers are removed)
5138  */
5139 
5140  if (reject > 0.0)
5141  rejection = 1;
5142 
5143  if (rejection)
5144  pixwav2 = cpl_bivector_duplicate(pixwav);
5145  else
5146  pixwav2 = pixwav;
5147 
5148 
5149  /*
5150  * The single vectors are extracted just because the fitting routine
5151  * requires it
5152  */
5153 
5154  pixel = cpl_bivector_get_x(pixwav2);
5155  wavel = cpl_bivector_get_y(pixwav2);
5156 
5157 
5158  /*
5159  * Get rid of the wrapper, in case of duplication
5160  */
5161 
5162  if (rejection)
5163  cpl_bivector_unwrap_vectors(pixwav2);
5164 
5165 
5166  /*
5167  * Here begins the iterative fit of identified lines
5168  */
5169 
5170  while (fitlines >= minlines) {
5171 
5172  ids = cpl_polynomial_fit_1d_create(wavel, pixel, order, err);
5173  *err = sqrt(*err);
5174 
5175  if (ids == NULL) {
5176  cpl_msg_debug(cpl_error_get_where(), "%s", cpl_error_get_message());
5177  cpl_msg_debug(func, "Fitting IDS");
5178  cpl_error_set_where(func);
5179  if (rejection) {
5180  cpl_vector_delete(wavel);
5181  cpl_vector_delete(pixel);
5182  }
5183  return NULL;
5184  }
5185 
5186  if (rejection) {
5187 
5188 
5189  /*
5190  * Now work directly with the vector data buffers...
5191  */
5192 
5193  d_pixel = cpl_vector_unwrap(pixel);
5194  d_wavel = cpl_vector_unwrap(wavel);
5195 
5196  for (i = 0, j = 0; i < fitlines; i++) {
5197  pixpos = cpl_polynomial_eval_1d(ids, d_wavel[i], NULL);
5198  if (fabs(pixpos - d_pixel[i]) < reject) {
5199  d_pixel[j] = d_pixel[i];
5200  d_wavel[j] = d_wavel[i];
5201  j++;
5202  }
5203  }
5204 
5205  if (j == fitlines) { /* No rejection in last iteration */
5206  cpl_free(d_wavel);
5207  cpl_free(d_pixel);
5208  *nlines = fitlines;
5209  return ids;
5210  }
5211  else { /* Some lines were rejected */
5212  fitlines = j;
5213  cpl_polynomial_delete(ids);
5214  if (fitlines >= minlines) {
5215  pixel = cpl_vector_wrap(fitlines, d_pixel);
5216  wavel = cpl_vector_wrap(fitlines, d_wavel);
5217  }
5218  else { /* Too few lines: failure */
5219  cpl_free(d_wavel);
5220  cpl_free(d_pixel);
5221  cpl_error_set(func, CPL_ERROR_CONTINUE);
5222  return NULL;
5223  }
5224  }
5225  }
5226  else {
5227  *nlines = fitlines;
5228  return ids; /* Exit at first iteration if no rejection */
5229  }
5230  }
5231 
5232  return ids; /* To avoid compiler warnings */
5233 }
5234 
5235 
5260 cpl_polynomial *mos_poly_pix2wav(cpl_bivector *pixwav, int order,
5261  double reject, int minlines,
5262  int *nlines, double *err)
5263 {
5264 
5265  cpl_bivector *wavpix;
5266  cpl_vector *wavel;
5267  cpl_vector *pixel;
5268 
5269  cpl_polynomial *dds;
5270 
5271 
5272  /*
5273  * Swap vectors in bivector, in order to reuse mos_poly_wav2pix()
5274  */
5275 
5276  pixel = cpl_bivector_get_x(pixwav);
5277  wavel = cpl_bivector_get_y(pixwav);
5278 
5279  wavpix = cpl_bivector_wrap_vectors(wavel, pixel);
5280 
5281  dds = mos_poly_wav2pix(wavpix, order, reject, minlines, nlines, err);
5282 
5283  cpl_bivector_unwrap_vectors(wavpix);
5284 
5285  return dds;
5286 
5287 }
5288 
5289 
5312 cpl_bivector *mos_find_peaks(const float *spectrum, int length,
5313  cpl_vector *lines, cpl_polynomial *ids,
5314  double refwave, int sradius)
5315 {
5316  const char *func = "mos_find_peaks";
5317 
5318  double *data;
5319  double *d_pixel;
5320  double *d_wavel;
5321  float pos;
5322  int nlines;
5323  int pixel;
5324  int i, j;
5325 
5326 
5327  if (spectrum == NULL || lines == NULL || ids == NULL) {
5328  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
5329  return NULL;
5330  }
5331 
5332  nlines = cpl_vector_get_size(lines);
5333 
5334  if (sradius < 1 || length < 2*sradius+1 || nlines < 1) {
5335  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
5336  return NULL;
5337  }
5338 
5339  d_wavel = cpl_malloc(nlines * sizeof(double));
5340  d_pixel = cpl_malloc(nlines * sizeof(double));
5341 
5342  data = cpl_vector_get_data(lines);
5343 
5344  for (i = 0, j = 0; i < nlines; i++) {
5345  pixel = cpl_polynomial_eval_1d(ids, data[i]-refwave, NULL) + 0.5;
5346  if (pixel < 0 || pixel - sradius < 0 || pixel + sradius >= length)
5347  continue;
5348  if (peakPosition(spectrum+pixel-sradius, 2*sradius+1, &pos, 1) == 0) {
5349  pos += pixel - sradius;
5350  d_pixel[j] = pos;
5351  d_wavel[j] = data[i];
5352  j++;
5353  }
5354  }
5355 
5356  if (j > 0) {
5357  return cpl_bivector_wrap_vectors(cpl_vector_wrap(j, d_pixel),
5358  cpl_vector_wrap(j, d_wavel));
5359  }
5360  else {
5361  cpl_free(d_wavel);
5362  cpl_free(d_pixel);
5363  cpl_error_set(func, CPL_ERROR_ILLEGAL_OUTPUT);
5364  return NULL;
5365  }
5366 }
5367 
5368 
5492 cpl_image *mos_wavelength_calibration_raw(const cpl_image *image,
5493  cpl_vector *lines,
5494  double dispersion, float level,
5495  int sradius, int order,
5496  double reject, double refwave,
5497  double *wavestart, double *waveend,
5498  int *nlines, double *error,
5499  cpl_table *idscoeff,
5500  cpl_image *calibration,
5501  cpl_image *residuals,
5502  cpl_table *restable,
5503  cpl_mask *refmask,
5504  cpl_table *detected_lines)
5505 {
5506 
5507  const char *func = "mos_wavelength_calibration_raw";
5508 
5509  const char *clab[6] = {"c0", "c1", "c2", "c3", "c4", "c5"};
5510  /* Max order is 5 */
5511 
5512  double tolerance = 20.0; /* Probably forever... */
5513  int step = 10; /* Compute restable every "step" rows */
5514 
5515  char name[MAX_COLNAME];
5516  cpl_image *resampled;
5517  cpl_bivector *output;
5518  cpl_bivector *new_output;
5519  cpl_vector *peaks;
5520  cpl_vector *wavel;
5521  cpl_polynomial *ids;
5522  cpl_polynomial *lin;
5523  cpl_matrix *kernel;
5524  double ids_err;
5525  double max_disp, min_disp;
5526  double *line;
5527  double firstLambda, lastLambda, lambda;
5528  double value, wave, pixe;
5529  cpl_binary *mdata;
5530  const float *sdata;
5531  float *rdata;
5532  float *idata;
5533  float *ddata;
5534  float v1, v2, vi;
5535  float fpixel;
5536  int *have_it;
5537  int pixstart, pixend;
5538  int extrapolation;
5539  int nref;
5540  int nl, nx, ny, pixel;
5541  int countLines, usedLines;
5542  int uorder;
5543  int in, first, last;
5544  int width, uradius;
5545  int i, j;
5546  int null;
5547  cpl_size k;
5548 
5549 
5550  if (dispersion == 0.0) {
5551  cpl_msg_error(func, "The expected dispersion (A/pixel) must be given");
5552  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
5553  return NULL;
5554  }
5555 
5556  if (dispersion < 0.0) {
5557  cpl_msg_error(func, "The expected dispersion must be positive");
5558  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
5559  return NULL;
5560  }
5561 
5562  max_disp = dispersion + dispersion * tolerance / 100;
5563  min_disp = dispersion - dispersion * tolerance / 100;
5564 
5565  if (order < 1) {
5566  cpl_msg_error(func, "The order of the fitting polynomial "
5567  "must be at least 1");
5568  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
5569  return NULL;
5570  }
5571 
5572  if (image == NULL || lines == NULL) {
5573  cpl_msg_error(func, "Both spectral exposure and reference line "
5574  "catalog are required in input");
5575  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
5576  return NULL;
5577  }
5578 
5579  nx = cpl_image_get_size_x(image);
5580  ny = cpl_image_get_size_y(image);
5581  sdata = cpl_image_get_data_float_const(image);
5582 
5583  nref = cpl_vector_get_size(lines);
5584  line = cpl_vector_get_data(lines);
5585 
5586  if (*wavestart < 1.0 && *waveend < 1.0) {
5587  firstLambda = line[0];
5588  lastLambda = line[nref-1];
5589  extrapolation = (lastLambda - firstLambda) / 10;
5590  firstLambda -= extrapolation;
5591  lastLambda += extrapolation;
5592  *wavestart = firstLambda;
5593  *waveend = lastLambda;
5594  }
5595  else {
5596  firstLambda = *wavestart;
5597  lastLambda = *waveend;
5598  }
5599 
5600  nl = (lastLambda - firstLambda) / dispersion;
5601  resampled = cpl_image_new(nl, ny, CPL_TYPE_FLOAT);
5602  rdata = cpl_image_get_data_float(resampled);
5603 
5604  if (calibration)
5605  idata = cpl_image_get_data_float(calibration);
5606 
5607  if (residuals)
5608  ddata = cpl_image_get_data_float(residuals);
5609 
5610  if (idscoeff)
5611  for (j = 0; j <= order; j++)
5612  cpl_table_new_column(idscoeff, clab[j], CPL_TYPE_DOUBLE);
5613 
5614  if (restable) {
5615  cpl_table_set_size(restable, nref);
5616  cpl_table_new_column(restable, "wavelength", CPL_TYPE_DOUBLE);
5617  cpl_table_copy_data_double(restable, "wavelength", line);
5618  for (i = 0; i < ny; i += step) {
5619  snprintf(name, MAX_COLNAME, "r%d", i);
5620  cpl_table_new_column(restable, name, CPL_TYPE_DOUBLE);
5621  snprintf(name, MAX_COLNAME, "d%d", i);
5622  cpl_table_new_column(restable, name, CPL_TYPE_DOUBLE);
5623  snprintf(name, MAX_COLNAME, "p%d", i);
5624  cpl_table_new_column(restable, name, CPL_TYPE_DOUBLE);
5625  }
5626  }
5627 
5628  if (detected_lines) {
5629  cpl_table_set_size(detected_lines, 0);
5630  cpl_table_new_column(detected_lines, "xpos", CPL_TYPE_DOUBLE);
5631  cpl_table_new_column(detected_lines, "ypos", CPL_TYPE_DOUBLE);
5632  cpl_table_new_column(detected_lines, "xpos_iter", CPL_TYPE_DOUBLE);
5633  cpl_table_new_column(detected_lines, "ypos_iter", CPL_TYPE_DOUBLE);
5634  cpl_table_new_column(detected_lines, "peak_flux", CPL_TYPE_DOUBLE);
5635  cpl_table_new_column(detected_lines, "wave_ident", CPL_TYPE_DOUBLE);
5636  cpl_table_new_column(detected_lines, "wave_ident_iter", CPL_TYPE_DOUBLE);
5637  cpl_table_new_column(detected_lines, "xpos_fit_rect_wavecal", CPL_TYPE_DOUBLE);
5638  cpl_table_new_column(detected_lines, "res_xpos", CPL_TYPE_DOUBLE);
5639  }
5640 
5641  /*
5642  * Here is the real thing: detecting and identifying peaks,
5643  * and then fit the transformation from wavelength to pixel
5644  * and from pixel to wavelength.
5645  */
5646 
5647  for (i = 0; i < ny; i++) {
5648  width = mos_lines_width(sdata + i*nx, nx);
5649  if (sradius > 0) {
5650  if (width > sradius) {
5651  uradius = width;
5652  }
5653  else {
5654  uradius = sradius;
5655  }
5656  }
5657  if (width < 5)
5658  width = 5;
5659  peaks = mos_peak_candidates(sdata + i*nx, nx, level, width);
5660  if (peaks) {
5661  peaks = mos_refine_peaks(sdata + i*nx, nx, peaks, width);
5662  }
5663  if (peaks) {
5664  output = mos_identify_peaks(peaks, lines, min_disp, max_disp, 0.05);
5665  if (output) {
5666  countLines = cpl_bivector_get_size(output);
5667  if (countLines < 4) {
5668  cpl_bivector_delete(output);
5669  cpl_vector_delete(peaks);
5670  if (nlines)
5671  nlines[i] = 0;
5672  if (error)
5673  error[i] = 0.0;
5674  continue;
5675  }
5676 
5677  /*
5678  * Set reference wavelength as zero point
5679  */
5680 
5681  wavel = cpl_bivector_get_y(output);
5682  cpl_vector_subtract_scalar(wavel, refwave);
5683 
5684  uorder = countLines / 2 - 1;
5685  if (uorder > order)
5686  uorder = order;
5687 
5688 /* This part is now commented out. In case the first-guess iteration
5689  * was requested, the first fit was made with a lower polynomial degree:
5690  * more robust, and still accurate enough to be used as a first-guess.
5691 
5692  if (sradius > 0 && uorder > 2)
5693  --uorder;
5694 
5695  * End of commented part */
5696 
5697  ids = mos_poly_wav2pix(output, uorder, reject,
5698  2 * (uorder + 1), &usedLines,
5699  &ids_err);
5700 
5701  if (ids == NULL) {
5702  cpl_bivector_delete(output);
5703  cpl_vector_delete(peaks);
5704  if (nlines)
5705  nlines[i] = 0;
5706  if (error)
5707  error[i] = 0.0;
5708  cpl_error_reset();
5709  continue;
5710  }
5711 
5712  if (idscoeff) {
5713 
5714  /*
5715  * Write it anyway, even in case a first-guess based
5716  * solution will be searched afterwards: in case of
5717  * failure, the "blind" solution is kept.
5718  */
5719 
5720  for (k = 0; k <= order; k++) {
5721  if (k > uorder) {
5722  cpl_table_set_double(idscoeff, clab[k], i, 0.0);
5723  }
5724  else {
5725  cpl_table_set_double(idscoeff, clab[k], i,
5726  cpl_polynomial_get_coeff(ids, &k));
5727  }
5728  }
5729  }
5730 
5731  if(detected_lines)
5732  {
5733  cpl_size newlines = cpl_vector_get_size(peaks);
5734  cpl_size oldsize = cpl_table_get_nrow(detected_lines);
5735  cpl_table_set_size(detected_lines, oldsize + newlines);
5736  for(cpl_size iline = 0; iline < newlines; ++iline)
5737  {
5738  cpl_table_set_double(detected_lines, "xpos",
5739  oldsize + iline, cpl_vector_get(peaks, iline) + 1);
5740  cpl_table_set_double(detected_lines, "ypos",
5741  oldsize + iline, (double)i + 1);
5742  cpl_table_set_double(detected_lines, "peak_flux",
5743  oldsize + iline,
5744  sdata[i*nx+(int)(cpl_vector_get(peaks, iline)+0.5)]);
5745  }
5746  }
5747 
5748  //Fill the line identification information in
5749  //the detected_lines table
5750  if(detected_lines)
5751  {
5752  cpl_size nidentlines = cpl_bivector_get_size(output);
5753  cpl_size ndetectlines = cpl_vector_get_size(peaks);
5754  cpl_size totalsize = cpl_table_get_nrow(detected_lines);
5755  for(cpl_size idline = 0; idline < nidentlines; ++idline)
5756  {
5757  for(cpl_size detline = 0; detline < ndetectlines; ++detline)
5758  {
5759  if(cpl_vector_get(peaks, detline) ==
5760  cpl_bivector_get_x_data(output)[idline])
5761  {
5762  cpl_size table_pos = totalsize - ndetectlines + detline;
5763  double wave_ident = cpl_bivector_get_y_data(output)[idline] + refwave;
5764  double xpix_fit = cpl_polynomial_eval_1d(ids,
5765  wave_ident - refwave, NULL);
5766  double xpos_det = cpl_table_get_double(detected_lines,
5767  "xpos",
5768  table_pos, &null);
5769  cpl_table_set_double(detected_lines,
5770  "wave_ident",
5771  table_pos,
5772  wave_ident);
5773  cpl_table_set_double(detected_lines,
5774  "xpos_fit_rect_wavecal",
5775  table_pos,
5776  xpix_fit + 1);
5777  cpl_table_set_double(detected_lines,
5778  "res_xpos",
5779  table_pos,
5780  xpos_det - xpix_fit - 1);
5781 
5782  }
5783  }
5784  }
5785  }
5786 
5787  if (sradius > 0) {
5788 
5789  /*
5790  * Use ids as a first-guess
5791  */
5792 
5793  new_output = mos_find_peaks(sdata + i*nx, nx, lines,
5794  ids, refwave, uradius);
5795 
5796  if (new_output) {
5797  cpl_bivector_delete(output);
5798  output = new_output;
5799  }
5800  else
5801  cpl_error_reset();
5802 
5803 
5804  cpl_polynomial_delete(ids);
5805 
5806  countLines = cpl_bivector_get_size(output);
5807 
5808  if (countLines < 4) {
5809  cpl_bivector_delete(output);
5810  cpl_vector_delete(peaks);
5811 
5812  /*
5813  * With the following code a decision is taken:
5814  * if using the first-guess gives no results,
5815  * then also the "blind" solution is rejected.
5816  */
5817 
5818  if (nlines)
5819  nlines[i] = 0;
5820  if (error)
5821  error[i] = 0.0;
5822  if (idscoeff)
5823  for (k = 0; k <= order; k++)
5824  cpl_table_set_invalid(idscoeff, clab[k], i);
5825  continue;
5826  }
5827 
5828  wavel = cpl_bivector_get_y(output);
5829  cpl_vector_subtract_scalar(wavel, refwave);
5830 
5831  uorder = countLines / 2 - 1;
5832  if (uorder > order)
5833  uorder = order;
5834 
5835  ids = mos_poly_wav2pix(output, uorder, reject,
5836  2 * (uorder + 1), &usedLines,
5837  &ids_err);
5838 
5839  if (ids == NULL) {
5840  cpl_bivector_delete(output);
5841  cpl_vector_delete(peaks);
5842 
5843  /*
5844  * With the following code a decision is taken:
5845  * if using the first-guess gives no results,
5846  * then also the "blind" solution is rejected.
5847  */
5848 
5849  if (nlines)
5850  nlines[i] = 0;
5851  if (error)
5852  error[i] = 0.0;
5853  if (idscoeff)
5854  for (k = 0; k <= order; k++)
5855  cpl_table_set_invalid(idscoeff, clab[k], i);
5856  cpl_error_reset();
5857  continue;
5858  }
5859 
5860  if (idscoeff) {
5861  for (k = 0; k <= order; k++) {
5862  if (k > uorder) {
5863  cpl_table_set_double(idscoeff, clab[k], i, 0.0);
5864  }
5865  else {
5866  cpl_table_set_double(idscoeff, clab[k], i,
5867  cpl_polynomial_get_coeff(ids, &k));
5868  }
5869  }
5870  }
5871 
5872 
5873  if(detected_lines)
5874  {
5875  cpl_size oldsize = cpl_table_get_nrow(detected_lines);
5876  cpl_size nidentlines = cpl_bivector_get_size(output);
5877  cpl_table_set_size(detected_lines, oldsize + nidentlines);
5878  for(cpl_size idline = 0; idline < nidentlines ; ++idline)
5879  {
5880  double wave_ident = cpl_bivector_get_y_data(output)[idline] + refwave;
5881  double xpix_fit = cpl_polynomial_eval_1d(ids,
5882  wave_ident - refwave, NULL);
5883  cpl_table_set_double(detected_lines, "xpos_iter",
5884  oldsize + idline, cpl_bivector_get_x_data(output)[idline] + 1);
5885  cpl_table_set_double(detected_lines, "ypos_iter",
5886  oldsize + idline, (double)i + 1);
5887  cpl_table_set_double(detected_lines, "peak_flux",
5888  oldsize + idline,
5889  sdata[i*nx+(int)(cpl_bivector_get_x_data(output)[idline]+0.5)]);
5890  cpl_table_set_double(detected_lines, "wave_ident_iter",
5891  oldsize + idline, wave_ident);
5892  cpl_table_set_double(detected_lines, "xpos_fit_rect_wavecal",
5893  oldsize + idline, xpix_fit + 1);
5894  }
5895  }
5896 
5897  } /* End of "use ids as a first-guess" */
5898 
5899  if (nlines)
5900  nlines[i] = usedLines;
5901  if (error)
5902  error[i] = ids_err / sqrt(usedLines/(uorder + 1));
5903 
5904  pixstart = cpl_polynomial_eval_1d(ids,
5905  cpl_bivector_get_y_data(output)[0], NULL);
5906  pixend = cpl_polynomial_eval_1d(ids,
5907  cpl_bivector_get_y_data(output)[countLines-1], NULL);
5908  extrapolation = (pixend - pixstart) / 5;
5909  pixstart -= extrapolation;
5910  pixend += extrapolation;
5911  if (pixstart < 0)
5912  pixstart = 0;
5913  if (pixend > nx)
5914  pixend = nx;
5915 
5916  /*
5917  * Wavelength calibrated image (if requested):
5918  */
5919 
5920  if (calibration) {
5921  for (j = pixstart; j < pixend; j++) {
5922  (idata + i*nx)[j] = mos_eval_dds(ids, firstLambda,
5923  lastLambda, refwave,
5924  j);
5925  }
5926  }
5927 
5928  /*
5929  * Resampled image:
5930  */
5931 
5932  for (j = 0; j < nl; j++) {
5933  lambda = firstLambda + j * dispersion;
5934  fpixel = cpl_polynomial_eval_1d(ids, lambda - refwave,
5935  NULL);
5936  pixel = fpixel;
5937  if (pixel >= 0 && pixel < nx-1) {
5938  v1 = (sdata + i*nx)[pixel];
5939  v2 = (sdata + i*nx)[pixel+1];
5940  vi = v1 + (v2-v1)*(fpixel-pixel);
5941  (rdata + i*nl)[j] = vi;
5942  }
5943  }
5944 
5945  /*
5946  * Residuals image
5947  */
5948 
5949  if (residuals || (restable && !(i%step))) {
5950  if (restable && !(i%step)) {
5951  lin = cpl_polynomial_new(1);
5952  for (k = 0; k < 2; k++)
5953  cpl_polynomial_set_coeff(lin, &k,
5954  cpl_polynomial_get_coeff(ids, &k));
5955  }
5956  for (j = 0; j < countLines; j++) {
5957  pixe = cpl_bivector_get_x_data(output)[j];
5958  wave = cpl_bivector_get_y_data(output)[j];
5959  value = pixe - cpl_polynomial_eval_1d(ids, wave, NULL);
5960  if (residuals) {
5961  pixel = pixe + 0.5;
5962  (ddata + i*nx)[pixel] = value;
5963  }
5964  if (restable && !(i%step)) {
5965  for (k = 0; k < nref; k++) {
5966  if (fabs(line[k] - refwave - wave) < 0.1) {
5967  snprintf(name, MAX_COLNAME, "r%d", i);
5968  cpl_table_set_double(restable, name,
5969  k, value);
5970  value = pixe
5971  - cpl_polynomial_eval_1d(lin, wave,
5972  NULL);
5973  snprintf(name, MAX_COLNAME, "d%d", i);
5974  cpl_table_set_double(restable, name,
5975  k, value);
5976  snprintf(name, MAX_COLNAME, "p%d", i);
5977  cpl_table_set_double(restable, name,
5978  k, pixe);
5979  break;
5980  }
5981  }
5982  }
5983  }
5984  if (restable && !(i%step)) {
5985  cpl_polynomial_delete(lin);
5986  }
5987  }
5988 
5989  /*
5990  * Mask at reference wavelength
5991  */
5992 
5993  if (refmask) {
5994  mdata = cpl_mask_get_data(refmask);
5995  pixel = cpl_polynomial_eval_1d(ids, 0.0, NULL) + 0.5;
5996  if (pixel - 1 >= 0 && pixel + 1 < nx) {
5997  mdata[pixel-1 + i*nx] = CPL_BINARY_1;
5998  mdata[pixel + i*nx] = CPL_BINARY_1;
5999  mdata[pixel+1 + i*nx] = CPL_BINARY_1;
6000  }
6001  }
6002 
6003  cpl_polynomial_delete(ids);
6004  cpl_bivector_delete(output);
6005  }
6006  cpl_vector_delete(peaks);
6007  }
6008  }
6009 
6010  if (refmask) {
6011  kernel = cpl_matrix_new(3, 3);
6012  cpl_matrix_set(kernel, 0, 1, 1.0);
6013  cpl_matrix_set(kernel, 1, 1, 1.0);
6014  cpl_matrix_set(kernel, 2, 1, 1.0);
6015 
6016  cpl_mask_dilation(refmask, kernel);
6017  cpl_mask_erosion(refmask, kernel);
6018  cpl_mask_erosion(refmask, kernel);
6019  cpl_mask_dilation(refmask, kernel);
6020 
6021  cpl_matrix_delete(kernel);
6022 
6023  /*
6024  * Fill possible gaps
6025  */
6026 
6027  mdata = cpl_mask_get_data(refmask);
6028  have_it = cpl_calloc(ny, sizeof(int));
6029 
6030  for (i = 0; i < ny; i++, mdata += nx) {
6031  for (j = 0; j < nx; j++) {
6032  if (mdata[j] == CPL_BINARY_1) {
6033  have_it[i] = j;
6034  break;
6035  }
6036  }
6037  }
6038 
6039  mdata = cpl_mask_get_data(refmask);
6040  in = 0;
6041  first = last = 0;
6042 
6043  for (i = 0; i < ny; i++) {
6044  if (have_it[i]) {
6045  if (!in) {
6046  in = 1;
6047  if (first) {
6048  last = i;
6049  if (abs(have_it[first] - have_it[last]) < 3) {
6050  for (j = first; j < last; j++) {
6051  mdata[have_it[first] + nx*j + 0] = CPL_BINARY_1;
6052  mdata[have_it[first] + nx*j + 1] = CPL_BINARY_1;
6053  mdata[have_it[first] + nx*j + 2] = CPL_BINARY_1;
6054  }
6055  }
6056  }
6057  }
6058  }
6059  else {
6060  if (in) {
6061  in = 0;
6062  first = i - 1;
6063  }
6064  }
6065  }
6066 
6067  cpl_free(have_it);
6068 
6069  }
6070 
6071 /*
6072  for (i = 0; i < ny; i++) {
6073  if (nlines[i] == 0) {
6074  for (k = 0; k <= order; k++) {
6075  cpl_table_set_invalid(idscoeff, clab[k], i);
6076  }
6077  }
6078  }
6079 */
6080 
6081  return resampled;
6082 }
6083 
6084 
6106 /*
6107 cpl_table *mos_locate_spectra_bis(cpl_mask *mask)
6108 {
6109  const char *func = "mos_locate_spectra_bis";
6110 
6111  cpl_apertures *slits;
6112  cpl_image *labimage;
6113  cpl_image *refimage;
6114  cpl_binary *mdata;
6115  cpl_table *slitpos;
6116  cpl_propertylist *sort_col;
6117  int nslits;
6118  int *have_it;
6119  int in, first, last;
6120  int i, j;
6121 
6122 
6123  if (mask == NULL) {
6124  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
6125  return NULL;
6126  }
6127 
6128  nx = cpl_mask_get_size_x(mask);
6129  ny = cpl_mask_get_size_y(mask);
6130 
6131  mdata = cpl_mask_get_data(refmask);
6132  have_it = cpl_calloc(ny, sizeof(int));
6133 
6134  for (i = 0; i < ny; i++, mdata += nx) {
6135  for (j = 0; j < nx; j++) {
6136  if (mdata[j] == CPL_BINARY_1) {
6137  have_it[i] = j + 1;
6138  break;
6139  }
6140  }
6141  }
6142 
6143  mdata = cpl_mask_get_data(refmask);
6144  in = 0;
6145  first = last = 0;
6146  nslits = 0;
6147 
6148  for (i = 0; i < ny; i++) {
6149  if (have_it[i]) {
6150  if (in) {
6151  if (i) {
6152  if (abs(have_it[i] - have_it[i-1]) > 3) {
6153  nslits++;
6154  }
6155  }
6156  }
6157  else {
6158  in = 1;
6159  nslits++;
6160  }
6161  }
6162  else {
6163  if (in) {
6164  in = 0;
6165  }
6166  }
6167  }
6168 }
6169 */
6170 
6171 
6193 cpl_table *mos_locate_spectra(cpl_mask *mask)
6194 {
6195  const char *func = "mos_locate_spectra";
6196 
6197  cpl_apertures *slits;
6198  cpl_image *labimage;
6199  cpl_image *refimage;
6200  cpl_table *slitpos;
6201  cpl_propertylist *sort_col;
6202  cpl_size nslits;
6203  int i;
6204 
6205 
6206  if (mask == NULL) {
6207  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
6208  return NULL;
6209  }
6210 
6211  labimage = cpl_image_labelise_mask_create(mask, &nslits);
6212 
6213  if (nslits < 1) {
6214  cpl_image_delete(labimage);
6215  cpl_error_set(func, CPL_ERROR_DATA_NOT_FOUND);
6216  return NULL;
6217  }
6218 
6219  refimage = cpl_image_new_from_mask(mask);
6220 
6221  slits = cpl_apertures_new_from_image(refimage, labimage);
6222 
6223  cpl_image_delete(labimage);
6224  cpl_image_delete(refimage);
6225 
6226  nslits = cpl_apertures_get_size(slits); /* Overwriting nslits - safer! */
6227  if (nslits < 1) {
6228  cpl_apertures_delete(slits);
6229  cpl_error_set(func, CPL_ERROR_DATA_NOT_FOUND);
6230  return NULL;
6231  }
6232 
6233  slitpos = cpl_table_new(nslits);
6234  cpl_table_new_column(slitpos, "xtop", CPL_TYPE_DOUBLE);
6235  cpl_table_new_column(slitpos, "ytop", CPL_TYPE_DOUBLE);
6236  cpl_table_new_column(slitpos, "xbottom", CPL_TYPE_DOUBLE);
6237  cpl_table_new_column(slitpos, "ybottom", CPL_TYPE_DOUBLE);
6238  cpl_table_set_column_unit(slitpos, "xtop", "pixel");
6239  cpl_table_set_column_unit(slitpos, "ytop", "pixel");
6240  cpl_table_set_column_unit(slitpos, "xbottom", "pixel");
6241  cpl_table_set_column_unit(slitpos, "ybottom", "pixel");
6242 
6243  for (i = 0; i < nslits; i++) {
6244  cpl_table_set_double(slitpos, "xtop", i,
6245  cpl_apertures_get_top_x(slits, i+1) - 1);
6246  cpl_table_set_double(slitpos, "ytop", i,
6247  cpl_apertures_get_top(slits, i+1));
6248  cpl_table_set_double(slitpos, "xbottom", i,
6249  cpl_apertures_get_bottom_x(slits, i+1) - 1);
6250  cpl_table_set_double(slitpos, "ybottom", i,
6251  cpl_apertures_get_bottom(slits, i+1));
6252  }
6253 
6254  cpl_apertures_delete(slits);
6255 
6256  sort_col = cpl_propertylist_new();
6257  cpl_propertylist_append_bool(sort_col, "ytop", 1);
6258  cpl_table_sort(slitpos, sort_col);
6259  cpl_propertylist_delete(sort_col);
6260 
6261  return slitpos;
6262 
6263 }
6264 
6265 
6281 cpl_error_code mos_validate_slits(cpl_table *slits)
6282 {
6283  const char *func = "mos_validate_slits";
6284 
6285 
6286  if (slits == NULL)
6287  return cpl_error_set(func, CPL_ERROR_NULL_INPUT);
6288 
6289  if (1 != cpl_table_has_column(slits, "xtop"))
6290  return cpl_error_set(func, CPL_ERROR_DATA_NOT_FOUND);
6291 
6292  if (1 != cpl_table_has_column(slits, "ytop"))
6293  return cpl_error_set(func, CPL_ERROR_DATA_NOT_FOUND);
6294 
6295  if (1 != cpl_table_has_column(slits, "xbottom"))
6296  return cpl_error_set(func, CPL_ERROR_DATA_NOT_FOUND);
6297 
6298  if (1 != cpl_table_has_column(slits, "ybottom"))
6299  return cpl_error_set(func, CPL_ERROR_DATA_NOT_FOUND);
6300 
6301  if (CPL_TYPE_DOUBLE != cpl_table_get_column_type(slits, "xtop"))
6302  return cpl_error_set(func, CPL_ERROR_INVALID_TYPE);
6303 
6304  if (CPL_TYPE_DOUBLE != cpl_table_get_column_type(slits, "ytop"))
6305  return cpl_error_set(func, CPL_ERROR_INVALID_TYPE);
6306 
6307  if (CPL_TYPE_DOUBLE != cpl_table_get_column_type(slits, "xbottom"))
6308  return cpl_error_set(func, CPL_ERROR_INVALID_TYPE);
6309 
6310  if (CPL_TYPE_DOUBLE != cpl_table_get_column_type(slits, "ybottom"))
6311  return cpl_error_set(func, CPL_ERROR_INVALID_TYPE);
6312 
6313  return CPL_ERROR_NONE;
6314 }
6315 
6316 
6345 cpl_error_code mos_rotate_slits(cpl_table *slits, int rotation, int nx, int ny)
6346 {
6347  const char *func = "mos_rotate_slits";
6348 
6349  cpl_error_code error;
6350  char aux_name[] = "_0";
6351  int i;
6352 
6353 
6354  rotation %= 4;
6355  if (rotation < 0)
6356  rotation += 4;
6357 
6358  if (rotation == 0)
6359  return CPL_ERROR_NONE;
6360 
6361  error = mos_validate_slits(slits);
6362  if (error)
6363  return cpl_error_set(func, error);
6364 
6365  if (rotation == 1 || rotation == 3) {
6366 
6367  /*
6368  * Swap x and y column names
6369  */
6370 
6371  for (i = 0; i < 77; i++)
6372  if (1 == cpl_table_has_column(slits, aux_name))
6373  aux_name[1]++;
6374  if (1 == cpl_table_has_column(slits, aux_name))
6375  return cpl_error_set(func, CPL_ERROR_CONTINUE);
6376  cpl_table_name_column(slits, "xtop", aux_name);
6377  cpl_table_name_column(slits, "ytop", "xtop");
6378  cpl_table_name_column(slits, aux_name, "ytop");
6379  cpl_table_name_column(slits, "xbottom", aux_name);
6380  cpl_table_name_column(slits, "ybottom", "xbottom");
6381  cpl_table_name_column(slits, aux_name, "ybottom");
6382  }
6383 
6384  if (rotation == 1 || rotation == 2) {
6385  cpl_table_multiply_scalar(slits, "xtop", -1.0);
6386  cpl_table_multiply_scalar(slits, "xbottom", -1.0);
6387  cpl_table_add_scalar(slits, "xtop", nx);
6388  cpl_table_add_scalar(slits, "xbottom", nx);
6389  }
6390 
6391  if (rotation == 3 || rotation == 2) {
6392  cpl_table_multiply_scalar(slits, "ytop", -1.0);
6393  cpl_table_multiply_scalar(slits, "ybottom", -1.0);
6394  cpl_table_add_scalar(slits, "ytop", ny);
6395  cpl_table_add_scalar(slits, "ybottom", ny);
6396  }
6397 
6398  return CPL_ERROR_NONE;
6399 }
6400 
6401 
6459 cpl_table *mos_identify_slits(cpl_table *slits, cpl_table *maskslits,
6460  cpl_table *global)
6461 {
6462  cpl_array *top_ident = NULL;;
6463  cpl_array *bot_ident = NULL;;
6464  cpl_matrix *mdata;
6465  cpl_matrix *mpattern;
6466  cpl_matrix *top_data;
6467  cpl_matrix *top_pattern;
6468  cpl_matrix *top_mdata;
6469  cpl_matrix *top_mpattern;
6470  cpl_matrix *bot_data;
6471  cpl_matrix *bot_pattern;
6472  cpl_matrix *bot_mdata;
6473  cpl_matrix *bot_mpattern;
6474  cpl_propertylist *sort_col;
6475  double *xtop;
6476  double *ytop;
6477  double *xmtop;
6478  double *ymtop;
6479  double *xbot;
6480  double *ybot;
6481  double *xmbot;
6482  double *ymbot;
6483  double top_scale, bot_scale;
6484  double angle, top_angle, bot_angle;
6485  double xmse, ymse;
6486  double xrms, top_xrms, bot_xrms;
6487  double yrms, top_yrms, bot_yrms;
6488  int nslits;
6489  int nmaskslits, use_pattern;
6490  int found_slits, found_slits_top, found_slits_bot;
6491  int i;
6492  cpl_table *positions;
6493  cpl_error_code error;
6494 
6495  cpl_vector *point;
6496  double *dpoint;
6497  cpl_vector *xpos;
6498  cpl_vector *ypos;
6499  cpl_vector *xmpos;
6500  cpl_vector *ympos;
6501  cpl_bivector *mpos;
6502  cpl_polynomial *xpoly = NULL;
6503  cpl_polynomial *ypoly = NULL;
6504  cpl_polynomial *top_xpoly = NULL;
6505  cpl_polynomial *top_ypoly = NULL;
6506  cpl_polynomial *bot_xpoly = NULL;
6507  cpl_polynomial *bot_ypoly = NULL;
6508 
6509  char *msg_multiplex = " ";
6510 
6511 
6512  error = mos_validate_slits(slits);
6513  if (error) {
6514  cpl_msg_error(cpl_func, "CCD slits table validation: %s",
6515  cpl_error_get_message());
6516  cpl_error_set(cpl_func, error);
6517  return NULL;
6518  }
6519 
6520  error = mos_validate_slits(maskslits);
6521  if (error) {
6522  cpl_msg_error(cpl_func, "Mask slits table validation: %s",
6523  cpl_error_get_message());
6524  cpl_error_set(cpl_func, error);
6525  return NULL;
6526  }
6527 
6528  if (1 != cpl_table_has_column(maskslits, "slit_id")) {
6529  cpl_msg_error(cpl_func, "Missing slits identifiers");
6530  cpl_error_set(cpl_func, CPL_ERROR_DATA_NOT_FOUND);
6531  return NULL;
6532  }
6533 
6534  if (CPL_TYPE_INT != cpl_table_get_column_type(maskslits, "slit_id")) {
6535  cpl_msg_error(cpl_func, "Wrong type used for slits identifiers");
6536  cpl_error_set(cpl_func, CPL_ERROR_INVALID_TYPE);
6537  return NULL;
6538  }
6539 
6540  nslits = cpl_table_get_nrow(slits);
6541  nmaskslits = cpl_table_get_nrow(maskslits);
6542 
6543  if (nslits == 0 || nmaskslits == 0) {
6544  cpl_msg_error(cpl_func, "Empty slits table");
6545  cpl_error_set(cpl_func, CPL_ERROR_ILLEGAL_INPUT);
6546  return NULL;
6547  }
6548 
6549  if (nslits > 25 && mos_multiplex < 0) {
6550  cpl_msg_info(cpl_func, "Many slits: using 'fast' pattern matching...");
6551  positions = mos_identify_slits_fast(slits, maskslits, global);
6552  if (positions == NULL)
6553  cpl_error_set_where(cpl_func);
6554  return positions;
6555  }
6556 
6557  /*
6558  * Guarantee that both input tables are sorted in the same way
6559  */
6560 
6561  sort_col = cpl_propertylist_new();
6562  cpl_propertylist_append_bool(sort_col, "ytop", 1);
6563  cpl_table_sort(slits, sort_col);
6564  cpl_table_sort(maskslits, sort_col);
6565  cpl_propertylist_delete(sort_col);
6566 
6567  /*
6568  * First we handle all the special cases (too few slits...)
6569  */
6570 
6571  if (nslits < 3 && nmaskslits > nslits) {
6572 
6573  /*
6574  * If there are just 1 or 2 slits on the CCD, and more on the
6575  * mask, the ambiguity cannot be solved, and an error is returned.
6576  * This is a case that must be solved with a first-guess relation
6577  * between mask and CCD.
6578  */
6579 
6580  if (nslits > 1)
6581  cpl_msg_warning(cpl_func, "Cannot match the %d found CCD slits "
6582  "with the %d mask slits: process will continue "
6583  "using the detected CCD slits positions", nslits,
6584  nmaskslits);
6585  else
6586  cpl_msg_warning(cpl_func, "Cannot match the found CCD slit with "
6587  "the %d mask slits: process will continue using "
6588  "the detected CCD slit position", nmaskslits);
6589  return NULL;
6590  }
6591 
6592  if (nmaskslits < 3 && nslits > nmaskslits) {
6593 
6594  /*
6595  * If there are less than 3 slits on the mask the ambiguity cannot
6596  * be solved, and an error is returned. This is a case that must
6597  * be solved with a first-guess relation between mask and CCD.
6598  */
6599 
6600  cpl_msg_warning(cpl_func, "Cannot match the %d found CCD slits with "
6601  "the %d mask slits: process will continue using "
6602  "the detected CCD slits positions", nslits,
6603  nmaskslits);
6604  return NULL;
6605  }
6606 
6607  /*
6608  * Pattern matching related operations begin here. Two pattern
6609  * matching will be run, one based on the "top" and another one
6610  * based on the "bottom" slit coordinates. The one with the
6611  * smallest rms in the Y coordinate will be chosen.
6612  */
6613 
6614  xtop = cpl_table_get_data_double(slits, "xtop");
6615  ytop = cpl_table_get_data_double(slits, "ytop");
6616  xmtop = cpl_table_get_data_double(maskslits, "xtop");
6617  ymtop = cpl_table_get_data_double(maskslits, "ytop");
6618 
6619  xbot = cpl_table_get_data_double(slits, "xbottom");
6620  ybot = cpl_table_get_data_double(slits, "ybottom");
6621  xmbot = cpl_table_get_data_double(maskslits, "xbottom");
6622  ymbot = cpl_table_get_data_double(maskslits, "ybottom");
6623 
6624  top_data = cpl_matrix_new(2, nslits);
6625  top_pattern = cpl_matrix_new(2, nmaskslits);
6626  bot_data = cpl_matrix_new(2, nslits);
6627  bot_pattern = cpl_matrix_new(2, nmaskslits);
6628 
6629  for (i = 0; i < nslits; i++)
6630  cpl_matrix_set(top_data, 0, i, xtop[i]);
6631 
6632  for (i = 0; i < nslits; i++)
6633  cpl_matrix_set(top_data, 1, i, ytop[i]);
6634 
6635  for (i = 0; i < nmaskslits; i++)
6636  cpl_matrix_set(top_pattern, 0, i, xmtop[i]);
6637 
6638  for (i = 0; i < nmaskslits; i++)
6639  cpl_matrix_set(top_pattern, 1, i, ymtop[i]);
6640 
6641  for (i = 0; i < nslits; i++)
6642  cpl_matrix_set(bot_data, 0, i, xbot[i]);
6643 
6644  for (i = 0; i < nslits; i++)
6645  cpl_matrix_set(bot_data, 1, i, ybot[i]);
6646 
6647  for (i = 0; i < nmaskslits; i++)
6648  cpl_matrix_set(bot_pattern, 0, i, xmbot[i]);
6649 
6650  for (i = 0; i < nmaskslits; i++)
6651  cpl_matrix_set(bot_pattern, 1, i, ymbot[i]);
6652 
6653  if (nmaskslits > nslits)
6654  use_pattern = nslits;
6655  else
6656  use_pattern = nmaskslits;
6657 
6658  top_ident = cpl_ppm_match_points(top_data, nslits, 1.0, top_pattern,
6659  use_pattern, 0.0, 0.1, 5, &top_mdata,
6660  &top_mpattern, &top_scale, &top_angle);
6661 
6662  bot_ident = cpl_ppm_match_points(bot_data, nslits, 1.0, bot_pattern,
6663  use_pattern, 0.0, 0.1, 5, &bot_mdata,
6664  &bot_mpattern, &bot_scale, &bot_angle);
6665  cpl_matrix_delete(top_data);
6666  cpl_matrix_delete(top_pattern);
6667  cpl_matrix_delete(bot_data);
6668  cpl_matrix_delete(bot_pattern);
6669 
6670  if (top_ident == NULL && bot_ident == NULL) {
6671  cpl_msg_warning(cpl_func, "Pattern matching failure: cannot match "
6672  "the %d found CCD slits with the %d mask slits: "
6673  "process will continue using the detected CCD "
6674  "slits positions", nslits, nmaskslits);
6675  return NULL;
6676  }
6677 
6678  found_slits_top = 0;
6679  found_slits_bot = 0;
6680  if (top_ident && bot_ident) {
6681  cpl_msg_info(cpl_func, "Median platescale: %f +/- %f pixel/mm",
6682  (top_scale + bot_scale) / 2, fabs(top_scale - bot_scale));
6683  cpl_msg_info(cpl_func, "Median rotation: %f +/- %f degrees",
6684  (top_angle + bot_angle) / 2, fabs(top_angle - bot_angle));
6685  if (fabs(top_angle) < fabs(bot_angle))
6686  angle = fabs(top_angle);
6687  else
6688  angle = fabs(bot_angle);
6689  found_slits_top = cpl_matrix_get_ncol(top_mdata);
6690  found_slits_bot = cpl_matrix_get_ncol(bot_mdata);
6691  }
6692  else if (top_ident) {
6693  cpl_msg_info(cpl_func, "Median platescale: %f pixel/mm", top_scale);
6694  cpl_msg_info(cpl_func, "Median rotation: %f degrees", top_angle);
6695  angle = fabs(top_angle);
6696  found_slits_top = cpl_matrix_get_ncol(top_mdata);
6697  }
6698  else {
6699  cpl_msg_info(cpl_func, "Median platescale: %f pixel/mm", bot_scale);
6700  cpl_msg_info(cpl_func, "Median rotation: %f degrees", bot_angle);
6701  angle = fabs(bot_angle);
6702  found_slits_bot = cpl_matrix_get_ncol(bot_mdata);
6703  }
6704 
6705  cpl_array_delete(top_ident);
6706  cpl_array_delete(bot_ident);
6707 
6708  if (angle > 4.0) {
6709  cpl_msg_warning(cpl_func, "Uncertain pattern matching: the rotation "
6710  "angle is expected to be around zero. This match is "
6711  "rejected: the process will continue using the %d "
6712  "detected CCD slits positions", nslits);
6713  return NULL;
6714  }
6715 
6716  found_slits = found_slits_top;
6717  if (found_slits < found_slits_bot)
6718  found_slits = found_slits_bot; /* Max value */
6719 
6720  if (found_slits < 4) {
6721  cpl_msg_warning(cpl_func,
6722  "Too few safely identified slits: %d out of %d "
6723  "candidates (%d expected). Process will continue "
6724  "using the detected CCD slits positions", found_slits,
6725  nslits, nmaskslits);
6726  return NULL;
6727  }
6728 
6729  cpl_msg_info(cpl_func, "Preliminary identified slits: %d out of %d "
6730  "candidates\n(%d expected)", found_slits, nslits,
6731  nmaskslits);
6732 
6733  if (found_slits_top < 4)
6734  found_slits_top = 0;
6735 
6736  if (found_slits_bot < 4)
6737  found_slits_bot = 0;
6738 
6739  /*
6740  * Now for each set select the points of the identified slits, and fit
6741  * two bivariate polynomials to determine a first approximate relation
6742  * between positions on the mask and positions on the CCD.
6743  */
6744 
6745  for (i = 0; i < 2; i++) {
6746  cpl_size mindeg2d[] = {0, 0};
6747  cpl_size maxdeg2d[2];
6748  cpl_vector * fitresidual;
6749  if (i) {
6750  found_slits = found_slits_top;
6751  mdata = top_mdata;
6752  mpattern = top_mpattern;
6753  }
6754  else {
6755  found_slits = found_slits_bot;
6756  mdata = bot_mdata;
6757  mpattern = bot_mpattern;
6758  }
6759 
6760  if (found_slits == 0)
6761  continue;
6762  else if (found_slits < 10)
6763  maxdeg2d[0] = maxdeg2d[1] = 1;
6764  else
6765  maxdeg2d[0] = maxdeg2d[1] = 2;
6766 
6767  xpos = cpl_vector_wrap(found_slits,
6768  cpl_matrix_get_data(mdata) );
6769  ypos = cpl_vector_wrap(found_slits,
6770  cpl_matrix_get_data(mdata) + found_slits);
6771  xmpos = cpl_vector_wrap(found_slits,
6772  cpl_matrix_get_data(mpattern) );
6773  ympos = cpl_vector_wrap(found_slits,
6774  cpl_matrix_get_data(mpattern) + found_slits);
6775  mpos = cpl_bivector_wrap_vectors(xmpos, ympos);
6776  fitresidual = cpl_vector_new(cpl_vector_get_size(xpos));
6777  xpoly = cpl_polynomial_new(2);
6778  cpl_polynomial_fit(xpoly, mpattern, NULL, xpos, NULL, CPL_FALSE, mindeg2d, maxdeg2d);
6779  cpl_vector_fill_polynomial_fit_residual(fitresidual, xpos, NULL, xpoly, mpattern, NULL);
6780  xmse = cpl_vector_product(fitresidual, fitresidual)
6781  / cpl_vector_get_size(fitresidual);
6782  ypoly = cpl_polynomial_new(2);
6783  cpl_polynomial_fit(ypoly, mpattern, NULL, ypos, NULL, CPL_FALSE, mindeg2d, maxdeg2d);
6784  cpl_vector_fill_polynomial_fit_residual(fitresidual, ypos, NULL, ypoly, mpattern, NULL);
6785  ymse = cpl_vector_product(fitresidual, fitresidual)
6786  / cpl_vector_get_size(fitresidual);
6787 
6788  cpl_bivector_unwrap_vectors(mpos);
6789  cpl_vector_unwrap(xpos);
6790  cpl_vector_unwrap(ypos);
6791  cpl_vector_unwrap(xmpos);
6792  cpl_vector_unwrap(ympos);
6793  cpl_matrix_delete(mdata);
6794  cpl_matrix_delete(mpattern);
6795  cpl_vector_delete(fitresidual);
6796 
6797  if (i) {
6798  top_xpoly = xpoly;
6799  top_ypoly = ypoly;
6800  top_xrms = sqrt(xmse*2*maxdeg2d[0]/(found_slits - 1));
6801  top_yrms = sqrt(ymse*2*maxdeg2d[0]/(found_slits - 1));
6802  }
6803  else {
6804  bot_xpoly = xpoly;
6805  bot_ypoly = ypoly;
6806  bot_xrms = sqrt(xmse*2*maxdeg2d[0]/(found_slits - 1));
6807  bot_yrms = sqrt(ymse*2*maxdeg2d[0]/(found_slits - 1));
6808  }
6809  }
6810 
6811  if (top_xpoly && bot_xpoly) {
6812  if (top_xrms < bot_xrms) { /* top X solution wins... */
6813  xrms = top_xrms;
6814  xpoly = top_xpoly;
6815  cpl_polynomial_delete(bot_xpoly);
6816  }
6817  else { /* bottom X solution wins... */
6818  xrms = bot_xrms;
6819  xpoly = bot_xpoly;
6820  cpl_polynomial_delete(top_xpoly);
6821  }
6822  }
6823  else if (top_xpoly) {
6824  xrms = top_xrms;
6825  xpoly = top_xpoly;
6826  }
6827  else {
6828  xrms = bot_xrms;
6829  xpoly = bot_xpoly;
6830  }
6831 
6832  if (top_ypoly && bot_ypoly) {
6833  if (top_yrms < bot_yrms) { /* top Y solution wins... */
6834  yrms = top_yrms;
6835  ypoly = top_ypoly;
6836  cpl_polynomial_delete(bot_ypoly);
6837  }
6838  else { /* bottom Y solution wins... */
6839  yrms = bot_yrms;
6840  ypoly = bot_ypoly;
6841  cpl_polynomial_delete(top_ypoly);
6842  }
6843  }
6844  else if (top_ypoly) {
6845  yrms = top_yrms;
6846  ypoly = top_ypoly;
6847  }
6848  else {
6849  yrms = bot_yrms;
6850  ypoly = bot_ypoly;
6851  }
6852 
6853  if (xpoly == NULL || ypoly == NULL) {
6854  cpl_msg_warning(cpl_func, "Fit failure: the accuracy of the "
6855  "identified slits positions cannot be improved.");
6856  cpl_polynomial_delete(xpoly);
6857  cpl_polynomial_delete(ypoly);
6858  cpl_error_reset();
6859  return NULL;
6860  }
6861 
6862  cpl_msg_info(cpl_func,
6863  "Fit successful: X rms = %.3g, Y rms = %.3g (pixel)",
6864  xrms, yrms);
6865 
6866  if (global) {
6867  write_global_distortion(global, 0, xpoly);
6868  write_global_distortion(global, 7, ypoly);
6869  }
6870 
6871  /*
6872  * The fit was successful: use the polynomials to obtain a new
6873  * position table with the improved positions of the slits
6874  */
6875 
6876  positions = cpl_table_duplicate(maskslits);
6877  cpl_table_duplicate_column(positions, "xmtop", positions, "xtop");
6878  cpl_table_duplicate_column(positions, "ymtop", positions, "ytop");
6879  cpl_table_duplicate_column(positions, "xmbottom", positions, "xbottom");
6880  cpl_table_duplicate_column(positions, "ymbottom", positions, "ybottom");
6881 
6882  point = cpl_vector_new(2);
6883  dpoint = cpl_vector_get_data(point);
6884 
6885  for (i = 0; i < nmaskslits; i++) {
6886  double position_x;
6887  double position_y;
6888 
6889  dpoint[0] = cpl_table_get_double(positions, "xmtop", i, NULL);
6890  dpoint[1] = cpl_table_get_double(positions, "ymtop", i, NULL);
6891  position_x = cpl_polynomial_eval(xpoly, point);
6892 // if (mos_multiplex >= 0) {
6893 // if (mos_multiplex != ((int)floor(position)) / mos_region_size) {
6894 // cpl_table_unselect_row(positions, i);
6895 // continue;
6896 // }
6897 // }
6898  cpl_table_set_double(positions, "xtop", i, position_x);
6899  position_y = cpl_polynomial_eval(ypoly, point);
6900  cpl_table_set_double(positions, "ytop", i, position_y);
6901  dpoint[0] = cpl_table_get_double(positions, "xmbottom", i, NULL);
6902  dpoint[1] = cpl_table_get_double(positions, "ymbottom", i, NULL);
6903  position_x = cpl_polynomial_eval(xpoly, point);
6904  cpl_table_set_double(positions, "xbottom", i, position_x);
6905  position_y = cpl_polynomial_eval(ypoly, point);
6906  cpl_table_set_double(positions, "ybottom", i, position_y);
6907  }
6908 
6909 // if (mos_multiplex >= 0) {
6910 // cpl_table_not_selected(positions);
6911 // cpl_table_erase_selected(positions);
6912 // nmaskslits = cpl_table_get_nrow(positions);
6913 // }
6914 
6915  cpl_vector_delete(point);
6916  cpl_polynomial_delete(xpoly);
6917  cpl_polynomial_delete(ypoly);
6918 
6919  cpl_table_erase_column(positions, "xmtop");
6920  cpl_table_erase_column(positions, "ymtop");
6921  cpl_table_erase_column(positions, "xmbottom");
6922  cpl_table_erase_column(positions, "ymbottom");
6923 
6924  if (mos_multiplex >= 0) {
6925  msg_multiplex =
6926  cpl_sprintf("in the CCD section between %d and %d pixel",
6927  mos_multiplex * mos_region_size,
6928  (mos_multiplex + 1) * mos_region_size);
6929  }
6930 
6931  if (nmaskslits > nslits)
6932  cpl_msg_info(cpl_func,
6933  "Finally identified slits: %d out of %d expected %s\n"
6934  "(%d recovered)", nmaskslits, nmaskslits, msg_multiplex,
6935  nmaskslits - nslits);
6936  else if (nmaskslits < nslits)
6937  cpl_msg_info(cpl_func,
6938  "Finally identified slits: %d out of %d expected %s\n"
6939  "(%d rejected)", nmaskslits, nmaskslits, msg_multiplex,
6940  nslits - nmaskslits);
6941  else
6942  cpl_msg_info(cpl_func,
6943  "Finally identified slits: %d out of %d expected %s",
6944  nmaskslits, nmaskslits, msg_multiplex);
6945 
6946  if (mos_multiplex >= 0) {
6947  cpl_free(msg_multiplex);
6948  }
6949 
6950  return positions;
6951 
6952 }
6953 
6954 
6955 cpl_table *mos_identify_slits_fast(cpl_table *slits, cpl_table *maskslits,
6956  cpl_table *global)
6957 {
6958  const char *func = "mos_identify_slits_fast";
6959 
6960  cpl_propertylist *sort_col;
6961  cpl_table *positions;
6962  cpl_vector *scales;
6963  cpl_vector *angles;
6964  cpl_vector *point;
6965  cpl_vector *xpos;
6966  cpl_vector *ypos;
6967  cpl_vector *xmpos;
6968  cpl_vector *ympos;
6969  cpl_bivector *mpos;
6970  cpl_polynomial *xpoly = NULL;
6971  cpl_polynomial *ypoly = NULL;
6972  cpl_error_code error;
6973  int nslits;
6974  int nmaskslits;
6975  int found_slits;
6976  int i, j, k;
6977 
6978  double dist1, dist2, dist3, dist, mindist;
6979  double scale, minscale, maxscale;
6980  double angle, minangle, maxangle;
6981  double *dscale;
6982  double *dangle;
6983  double *dpoint;
6984  double *xtop;
6985  double *ytop;
6986  double *xbottom;
6987  double *ybottom;
6988  double *xcenter;
6989  double *ycenter;
6990  double *xpseudo;
6991  double *ypseudo;
6992  int *slit_id;
6993  double *xmtop;
6994  double *ymtop;
6995  double *xmbottom;
6996  double *ymbottom;
6997  double *xmcenter;
6998  double *ymcenter;
6999  double *xmpseudo;
7000  double *ympseudo;
7001  double xmse, ymse;
7002  int *mslit_id;
7003  int *good;
7004  int minpos;
7005  int degree;
7006 
7007  double sradius = 0.01; /* Candidate input argument... */
7008  int in_sradius;
7009 
7010  double pi = 3.14159265358979323846;
7011 
7012 
7013  error = mos_validate_slits(slits);
7014  if (error) {
7015  cpl_msg_error(func, "CCD slits table validation: %s",
7016  cpl_error_get_message());
7017  cpl_error_set(func, error);
7018  return NULL;
7019  }
7020 
7021  error = mos_validate_slits(maskslits);
7022  if (error) {
7023  cpl_msg_error(func, "Mask slits table validation: %s",
7024  cpl_error_get_message());
7025  cpl_error_set(func, error);
7026  return NULL;
7027  }
7028 
7029  if (1 != cpl_table_has_column(maskslits, "slit_id")) {
7030  cpl_msg_error(func, "Missing slits identifiers");
7031  cpl_error_set(func, CPL_ERROR_DATA_NOT_FOUND);
7032  return NULL;
7033  }
7034 
7035  if (CPL_TYPE_INT != cpl_table_get_column_type(maskslits, "slit_id")) {
7036  cpl_msg_error(func, "Wrong type used for slits identifiers");
7037  cpl_error_set(func, CPL_ERROR_INVALID_TYPE);
7038  return NULL;
7039  }
7040 
7041  nslits = cpl_table_get_nrow(slits);
7042  nmaskslits = cpl_table_get_nrow(maskslits);
7043 
7044  if (nslits == 0 || nmaskslits == 0) {
7045  cpl_msg_error(func, "Empty slits table");
7046  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
7047  return NULL;
7048  }
7049 
7050 
7051  /*
7052  * Compute middle point coordinates for each slit listed in both
7053  * input tables.
7054  */
7055 
7056  if (cpl_table_has_column(slits, "xcenter"))
7057  cpl_table_erase_column(slits, "xcenter");
7058 
7059  if (cpl_table_has_column(slits, "ycenter"))
7060  cpl_table_erase_column(slits, "ycenter");
7061 
7062  if (cpl_table_has_column(maskslits, "xcenter"))
7063  cpl_table_erase_column(maskslits, "xcenter");
7064 
7065  if (cpl_table_has_column(maskslits, "ycenter"))
7066  cpl_table_erase_column(maskslits, "ycenter");
7067 
7068  cpl_table_duplicate_column(slits, "xcenter", slits, "xtop");
7069  cpl_table_add_columns(slits, "xcenter", "xbottom");
7070  cpl_table_divide_scalar(slits, "xcenter", 2.0);
7071  cpl_table_duplicate_column(slits, "ycenter", slits, "ytop");
7072  cpl_table_add_columns(slits, "ycenter", "ybottom");
7073  cpl_table_divide_scalar(slits, "ycenter", 2.0);
7074 
7075  cpl_table_duplicate_column(maskslits, "xcenter", maskslits, "xtop");
7076  cpl_table_add_columns(maskslits, "xcenter", "xbottom");
7077  cpl_table_divide_scalar(maskslits, "xcenter", 2.0);
7078  cpl_table_duplicate_column(maskslits, "ycenter", maskslits, "ytop");
7079  cpl_table_add_columns(maskslits, "ycenter", "ybottom");
7080  cpl_table_divide_scalar(maskslits, "ycenter", 2.0);
7081 
7082 
7083  /*
7084  * Guarantee that both input tables are sorted in the same way
7085  */
7086 
7087  sort_col = cpl_propertylist_new();
7088  cpl_propertylist_append_bool(sort_col, "ycenter", 1);
7089  cpl_table_sort(slits, sort_col);
7090  cpl_table_sort(maskslits, sort_col);
7091  cpl_propertylist_delete(sort_col);
7092 
7093 
7094  /*
7095  * First we handle all the special cases (too few slits...)
7096  */
7097 
7098  if (nslits < 3 && nmaskslits > nslits) {
7099 
7100  /*
7101  * If there are just 1 or 2 slits on the CCD, and more on the
7102  * mask, the ambiguity cannot be solved, and an error is returned.
7103  * This is a case that must be solved with a first-guess relation
7104  * between mask and CCD.
7105  */
7106 
7107  if (nslits > 1)
7108  cpl_msg_warning(func, "Cannot match the found CCD slit with the "
7109  "%d mask slits: process will continue using the "
7110  "detected CCD slit position", nmaskslits);
7111  else
7112  cpl_msg_warning(func, "Cannot match the %d found CCD slits with "
7113  "the %d mask slits: process will continue using "
7114  "the detected CCD slits positions", nslits,
7115  nmaskslits);
7116  return NULL;
7117  }
7118 
7119  if (nslits <= 3 && nslits == nmaskslits) {
7120 
7121  cpl_msg_warning(func, "Too few slits (%d) on mask and CCD", nslits);
7122  cpl_msg_warning(func, "Their detected positions are left unchanged");
7123 
7124  /*
7125  * If there are just up to 3 slits, both on the mask and on the CCD,
7126  * we can reasonably hope that those slits were found, and accept
7127  * that their positions on the CCD cannot be improved. We prepare
7128  * therefore an output position table containing the slits with
7129  * their original positions. We can however give an estimate of
7130  * the platescale if there is more than one slit.
7131  */
7132 
7133  positions = cpl_table_duplicate(slits);
7134  cpl_table_erase_column(slits, "xcenter");
7135  cpl_table_erase_column(slits, "ycenter");
7136  cpl_table_duplicate_column(positions, "xmtop", maskslits, "xtop");
7137  cpl_table_duplicate_column(positions, "ymtop", maskslits, "ytop");
7138  cpl_table_duplicate_column(positions, "xmbottom", maskslits, "xbottom");
7139  cpl_table_duplicate_column(positions, "ymbottom", maskslits, "ybottom");
7140  cpl_table_duplicate_column(positions, "xmcenter", maskslits, "xcenter");
7141  cpl_table_duplicate_column(positions, "ymcenter", maskslits, "ycenter");
7142  cpl_table_duplicate_column(positions, "slit_id", maskslits, "slit_id");
7143  cpl_table_erase_column(maskslits, "xcenter");
7144  cpl_table_erase_column(maskslits, "ycenter");
7145 
7146  if (nslits > 1) {
7147  xcenter = cpl_table_get_data_double(positions, "xcenter");
7148  ycenter = cpl_table_get_data_double(positions, "ycenter");
7149  xmcenter = cpl_table_get_data_double(positions, "xmcenter");
7150  ymcenter = cpl_table_get_data_double(positions, "ymcenter");
7151 
7152  dist1 = (xcenter[0] - xcenter[1])*(xcenter[0] - xcenter[1])
7153  + (ycenter[0] - ycenter[1])*(ycenter[0] - ycenter[1]);
7154  dist2 = (xmcenter[0] - xmcenter[1])*(xmcenter[0] - xmcenter[1])
7155  + (ymcenter[0] - ymcenter[1])*(ymcenter[0] - ymcenter[1]);
7156  scale = sqrt(dist1/dist2);
7157 
7158  if (nslits == 3) {
7159  dist1 = (xcenter[1] - xcenter[2])*(xcenter[1] - xcenter[2])
7160  + (ycenter[1] - ycenter[2])*(ycenter[1] - ycenter[2]);
7161  dist2 = (xmcenter[1] - xmcenter[2])*(xmcenter[1] - xmcenter[2])
7162  + (ymcenter[1] - ymcenter[2])*(ymcenter[1] - ymcenter[2]);
7163  scale += sqrt(dist1/dist2);
7164  scale /= 2;
7165  }
7166 
7167  cpl_msg_info(func, "Platescale: %f pixel/mm", scale);
7168  }
7169 
7170  return positions;
7171  }
7172 
7173  if (nmaskslits < 3 && nslits > nmaskslits) {
7174 
7175  /*
7176  * If there are less than 3 slits on the mask the ambiguity cannot
7177  * be solved, and an error is returned. This is a case that must
7178  * be solved with a first-guess relation between mask and CCD.
7179  */
7180 
7181  cpl_msg_warning(func, "Cannot match the %d found CCD slits with "
7182  "the %d mask slits: process will continue using "
7183  "the detected CCD slits positions", nslits,
7184  nmaskslits);
7185  return NULL;
7186  }
7187 
7188 
7189  /*
7190  * At this point of the program all the region of the plane
7191  * (nslits, nmaskslits) where either or both mask and CCD display
7192  * less than 3 slits are handled in some way. It would be better
7193  * to add in this place a special handling for identifying slits
7194  * in case of a very reduced number of slits (say, below 6).
7195  * It is also clear that if there are many more slits on the
7196  * mask than on the CCD, or many more on the CCD than on the
7197  * mask, something went deeply wrong with the preliminary
7198  * wavelength calibration. Such cases should be handled with
7199  * a _complete_ pattern-recognition algorithm based on the
7200  * construction of all possible triangles. For the moment,
7201  * we go directly to the limited pattern-recognition applied
7202  * below, based on triangles build only for consecutive slits.
7203  * This is reasonably safe, since the preliminary wavelength
7204  * calibration performed by mos_identify_peaks() is generally
7205  * robust.
7206  */
7207 
7208 
7209  /*
7210  * Compute (X, Y) coordinates on pseudo-plane describing the
7211  * different position ratios of successive slits, in both
7212  * input tables.
7213  */
7214 
7215  if (cpl_table_has_column(slits, "xpseudo"))
7216  cpl_table_erase_column(slits, "xpseudo");
7217 
7218  if (cpl_table_has_column(slits, "ypseudo"))
7219  cpl_table_erase_column(slits, "ypseudo");
7220 
7221  if (cpl_table_has_column(maskslits, "xpseudo"))
7222  cpl_table_erase_column(maskslits, "xpseudo");
7223 
7224  if (cpl_table_has_column(maskslits, "ypseudo"))
7225  cpl_table_erase_column(maskslits, "ypseudo");
7226 
7227  cpl_table_duplicate_column(slits, "xpseudo", slits, "xcenter");
7228  cpl_table_duplicate_column(slits, "ypseudo", slits, "ycenter");
7229 
7230  xcenter = cpl_table_get_data_double(slits, "xcenter");
7231  ycenter = cpl_table_get_data_double(slits, "ycenter");
7232  xpseudo = cpl_table_get_data_double(slits, "xpseudo");
7233  ypseudo = cpl_table_get_data_double(slits, "ypseudo");
7234 
7235  for (i = 1; i < nslits - 1; i++) {
7236  dist1 = (xcenter[i-1] - xcenter[i]) * (xcenter[i-1] - xcenter[i])
7237  + (ycenter[i-1] - ycenter[i]) * (ycenter[i-1] - ycenter[i]);
7238  dist2 = (xcenter[i-1] - xcenter[i+1]) * (xcenter[i-1] - xcenter[i+1])
7239  + (ycenter[i-1] - ycenter[i+1]) * (ycenter[i-1] - ycenter[i+1]);
7240  dist3 = (xcenter[i] - xcenter[i+1]) * (xcenter[i] - xcenter[i+1])
7241  + (ycenter[i] - ycenter[i+1]) * (ycenter[i] - ycenter[i+1]);
7242  xpseudo[i] = sqrt(dist1/dist2);
7243  ypseudo[i] = sqrt(dist3/dist2);
7244  }
7245 
7246  cpl_table_set_invalid(slits, "xpseudo", 0);
7247  cpl_table_set_invalid(slits, "xpseudo", nslits-1);
7248  cpl_table_set_invalid(slits, "ypseudo", 0);
7249  cpl_table_set_invalid(slits, "ypseudo", nslits-1);
7250 
7251  cpl_table_duplicate_column(maskslits, "xpseudo", maskslits, "xcenter");
7252  cpl_table_duplicate_column(maskslits, "ypseudo", maskslits, "ycenter");
7253 
7254  xcenter = cpl_table_get_data_double(maskslits, "xcenter");
7255  ycenter = cpl_table_get_data_double(maskslits, "ycenter");
7256  xmpseudo = cpl_table_get_data_double(maskslits, "xpseudo");
7257  ympseudo = cpl_table_get_data_double(maskslits, "ypseudo");
7258 
7259  for (i = 1; i < nmaskslits - 1; i++) {
7260  dist1 = (xcenter[i-1] - xcenter[i])*(xcenter[i-1] - xcenter[i])
7261  + (ycenter[i-1] - ycenter[i])*(ycenter[i-1] - ycenter[i]);
7262  dist2 = (xcenter[i-1] - xcenter[i+1])*(xcenter[i-1] - xcenter[i+1])
7263  + (ycenter[i-1] - ycenter[i+1])*(ycenter[i-1] - ycenter[i+1]);
7264  dist3 = (xcenter[i] - xcenter[i+1])*(xcenter[i] - xcenter[i+1])
7265  + (ycenter[i] - ycenter[i+1])*(ycenter[i] - ycenter[i+1]);
7266  xmpseudo[i] = sqrt(dist1/dist2);
7267  ympseudo[i] = sqrt(dist3/dist2);
7268  }
7269 
7270  cpl_table_set_invalid(maskslits, "xpseudo", 0);
7271  cpl_table_set_invalid(maskslits, "xpseudo", nmaskslits-1);
7272  cpl_table_set_invalid(maskslits, "ypseudo", 0);
7273  cpl_table_set_invalid(maskslits, "ypseudo", nmaskslits-1);
7274 
7275 
7276  /*
7277  * For each (X, Y) on the pseudo-plane related to the mask positions,
7278  * find the closest (X, Y) on the pseudo-plane related to the CCD
7279  * positions. If the closest point is closer than a given search
7280  * radius, a triangle has been found, and 3 slits are identified.
7281  * However, if more than one point is found within the search
7282  * radius, we have an ambiguity and this is rejected.
7283  */
7284 
7285  if (cpl_table_has_column(slits, "slit_id"))
7286  cpl_table_erase_column(slits, "slit_id");
7287  cpl_table_new_column(slits, "slit_id", CPL_TYPE_INT);
7288  slit_id = cpl_table_get_data_int(maskslits, "slit_id");
7289 
7290  for (i = 1; i < nmaskslits - 1; i++) {
7291  in_sradius = 0;
7292  mindist = (xmpseudo[i] - xpseudo[1]) * (xmpseudo[i] - xpseudo[1])
7293  + (ympseudo[i] - ypseudo[1]) * (ympseudo[i] - ypseudo[1]);
7294  minpos = 1;
7295  if (mindist < sradius*sradius)
7296  in_sradius++;
7297  for (j = 2; j < nslits - 1; j++) {
7298  dist = (xmpseudo[i] - xpseudo[j]) * (xmpseudo[i] - xpseudo[j])
7299  + (ympseudo[i] - ypseudo[j]) * (ympseudo[i] - ypseudo[j]);
7300  if (dist < sradius*sradius)
7301  in_sradius++;
7302  if (in_sradius > 1) /* More than one triangle within radius */
7303  break;
7304  if (mindist > dist) {
7305  mindist = dist;
7306  minpos = j;
7307  }
7308  }
7309 
7310  mindist = sqrt(mindist);
7311 
7312  if (mindist < sradius && in_sradius == 1) {
7313  cpl_table_set_int(slits, "slit_id", minpos-1, slit_id[i-1]);
7314  cpl_table_set_int(slits, "slit_id", minpos, slit_id[i]);
7315  cpl_table_set_int(slits, "slit_id", minpos+1, slit_id[i+1]);
7316  }
7317  }
7318 
7319 
7320  /*
7321  * At this point, the slit_id column contains invalid elements
7322  * corresponding to unidentified slits.
7323  */
7324 
7325  found_slits = nslits - cpl_table_count_invalid(slits, "slit_id");
7326 
7327  if (found_slits < 3) {
7328  cpl_msg_warning(func, "Too few preliminarily identified slits: "
7329  "%d out of %d", found_slits, nslits);
7330  if (nslits == nmaskslits) {
7331  cpl_msg_warning(func, "(this is not an error, it could be caused "
7332  "by a mask with regularly located slits)");
7333  cpl_msg_warning(func, "The detected slits positions are left "
7334  "unchanged");
7335 
7336  /*
7337  * If there are less than 3 identified slits, this is probably
7338  * a mask with regularly spaced slits (leading to an ambiguous
7339  * pattern). Only in the case all expected slits appear to have
7340  * been found on the CCD we can proceed...
7341  */
7342 
7343  cpl_table_erase_column(slits, "slit_id");
7344  cpl_table_erase_column(slits, "xpseudo");
7345  cpl_table_erase_column(slits, "ypseudo");
7346  positions = cpl_table_duplicate(slits);
7347  cpl_table_erase_column(slits, "xcenter");
7348  cpl_table_erase_column(slits, "ycenter");
7349 
7350  cpl_table_erase_column(maskslits, "xpseudo");
7351  cpl_table_erase_column(maskslits, "ypseudo");
7352  cpl_table_duplicate_column(positions, "xmtop",
7353  maskslits, "xtop");
7354  cpl_table_duplicate_column(positions, "ymtop",
7355  maskslits, "ytop");
7356  cpl_table_duplicate_column(positions, "xmbottom",
7357  maskslits, "xbottom");
7358  cpl_table_duplicate_column(positions, "ymbottom",
7359  maskslits, "ybottom");
7360  cpl_table_duplicate_column(positions, "xmcenter",
7361  maskslits, "xcenter");
7362  cpl_table_duplicate_column(positions, "ymcenter",
7363  maskslits, "ycenter");
7364  cpl_table_duplicate_column(positions, "slit_id",
7365  maskslits, "slit_id");
7366  cpl_table_erase_column(maskslits, "xcenter");
7367  cpl_table_erase_column(maskslits, "ycenter");
7368  return positions;
7369  }
7370  else {
7371  cpl_table_erase_column(slits, "slit_id");
7372  cpl_table_erase_column(slits, "xpseudo");
7373  cpl_table_erase_column(slits, "ypseudo");
7374  positions = cpl_table_duplicate(slits);
7375  cpl_table_erase_column(slits, "xcenter");
7376  cpl_table_erase_column(slits, "ycenter");
7377  cpl_msg_warning(func, "(the failure could be caused "
7378  "by a mask with regularly located slits)");
7379  return NULL;
7380  }
7381  }
7382  else {
7383  cpl_msg_info(func, "Preliminarily identified slits: %d out of %d "
7384  "candidates (%d expected)", found_slits, nslits,
7385  nmaskslits);
7386  }
7387 
7388 
7389  /*
7390  * Create a table with the coordinates of the preliminarily identified
7391  * slits, both on CCD and mask. The original order of the slits positions
7392  * is preserved.
7393  */
7394 
7395  positions = cpl_table_new(found_slits);
7396  cpl_table_new_column(positions, "slit_id", CPL_TYPE_INT);
7397  cpl_table_new_column(positions, "xtop", CPL_TYPE_DOUBLE);
7398  cpl_table_new_column(positions, "ytop", CPL_TYPE_DOUBLE);
7399  cpl_table_new_column(positions, "xbottom", CPL_TYPE_DOUBLE);
7400  cpl_table_new_column(positions, "ybottom", CPL_TYPE_DOUBLE);
7401  cpl_table_new_column(positions, "xcenter", CPL_TYPE_DOUBLE);
7402  cpl_table_new_column(positions, "ycenter", CPL_TYPE_DOUBLE);
7403  cpl_table_new_column(positions, "xmtop", CPL_TYPE_DOUBLE);
7404  cpl_table_new_column(positions, "ymtop", CPL_TYPE_DOUBLE);
7405  cpl_table_new_column(positions, "xmbottom", CPL_TYPE_DOUBLE);
7406  cpl_table_new_column(positions, "ymbottom", CPL_TYPE_DOUBLE);
7407  cpl_table_new_column(positions, "xmcenter", CPL_TYPE_DOUBLE);
7408  cpl_table_new_column(positions, "ymcenter", CPL_TYPE_DOUBLE);
7409  cpl_table_new_column(positions, "good", CPL_TYPE_INT);
7410  cpl_table_fill_column_window_int(positions, "good", 0, found_slits, 0);
7411 
7412  slit_id = cpl_table_get_data_int (slits, "slit_id");
7413  xtop = cpl_table_get_data_double(slits, "xtop");
7414  ytop = cpl_table_get_data_double(slits, "ytop");
7415  xbottom = cpl_table_get_data_double(slits, "xbottom");
7416  ybottom = cpl_table_get_data_double(slits, "ybottom");
7417  xcenter = cpl_table_get_data_double(slits, "xcenter");
7418  ycenter = cpl_table_get_data_double(slits, "ycenter");
7419 
7420  mslit_id = cpl_table_get_data_int (maskslits, "slit_id");
7421  xmtop = cpl_table_get_data_double(maskslits, "xtop");
7422  ymtop = cpl_table_get_data_double(maskslits, "ytop");
7423  xmbottom = cpl_table_get_data_double(maskslits, "xbottom");
7424  ymbottom = cpl_table_get_data_double(maskslits, "ybottom");
7425  xmcenter = cpl_table_get_data_double(maskslits, "xcenter");
7426  ymcenter = cpl_table_get_data_double(maskslits, "ycenter");
7427 
7428 
7429  /*
7430  * Transferring the valid slits information to the new table.
7431  * Note that invalid elements are coded as 0 in the internal
7432  * buffer, and this is the way they are recognised and excluded.
7433  */
7434 
7435  k = 0;
7436  cpl_table_fill_invalid_int(slits, "slit_id", 0);
7437  for (i = 0; i < nmaskslits; i++) {
7438  for (j = 0; j < nslits; j++) {
7439  if (slit_id[j] == 0)
7440  continue; /* Skip invalid slit */
7441  if (mslit_id[i] == slit_id[j]) {
7442  cpl_table_set_int (positions, "slit_id", k, slit_id[j]);
7443 
7444  cpl_table_set_double(positions, "xtop", k, xtop[j]);
7445  cpl_table_set_double(positions, "ytop", k, ytop[j]);
7446  cpl_table_set_double(positions, "xbottom", k, xbottom[j]);
7447  cpl_table_set_double(positions, "ybottom", k, ybottom[j]);
7448  cpl_table_set_double(positions, "xcenter", k, xcenter[j]);
7449  cpl_table_set_double(positions, "ycenter", k, ycenter[j]);
7450 
7451  cpl_table_set_double(positions, "xmtop", k, xmtop[i]);
7452  cpl_table_set_double(positions, "ymtop", k, ymtop[i]);
7453  cpl_table_set_double(positions, "xmbottom", k, xmbottom[i]);
7454  cpl_table_set_double(positions, "ymbottom", k, ymbottom[i]);
7455  cpl_table_set_double(positions, "xmcenter", k, xmcenter[i]);
7456  cpl_table_set_double(positions, "ymcenter", k, ymcenter[i]);
7457 
7458  k++;
7459 
7460  break;
7461  }
7462  }
7463  }
7464 
7465  found_slits = k;
7466 
7467  cpl_table_erase_column(slits, "slit_id");
7468  cpl_table_erase_column(slits, "xpseudo");
7469  cpl_table_erase_column(slits, "ypseudo");
7470  cpl_table_erase_column(slits, "xcenter");
7471  cpl_table_erase_column(slits, "ycenter");
7472  cpl_table_erase_column(maskslits, "xpseudo");
7473  cpl_table_erase_column(maskslits, "ypseudo");
7474  cpl_table_erase_column(maskslits, "xcenter");
7475  cpl_table_erase_column(maskslits, "ycenter");
7476 
7477 
7478  /*
7479  * Find the median platescale and rotation angle from the identified
7480  * slits, and then exclude slits outlaying more than 10% from the
7481  * median platescale, and more than 2 degrees from the median
7482  * rotation angle.
7483  */
7484 
7485  ytop = cpl_table_get_data_double(positions, "ytop");
7486  ybottom = cpl_table_get_data_double(positions, "ybottom");
7487  xcenter = cpl_table_get_data_double(positions, "xcenter");
7488  ycenter = cpl_table_get_data_double(positions, "ycenter");
7489  xmcenter = cpl_table_get_data_double(positions, "xmcenter");
7490  ymcenter = cpl_table_get_data_double(positions, "ymcenter");
7491 
7492  scales = cpl_vector_new(found_slits - 1);
7493  dscale = cpl_vector_get_data(scales);
7494  angles = cpl_vector_new(found_slits - 1);
7495  dangle = cpl_vector_get_data(angles);
7496 
7497  for (i = 1; i < found_slits; i++) {
7498  dist1 = (xcenter[i-1] - xcenter[i]) * (xcenter[i-1] - xcenter[i])
7499  + (ycenter[i-1] - ycenter[i]) * (ycenter[i-1] - ycenter[i]);
7500  dist2 = (xmcenter[i-1] - xmcenter[i]) * (xmcenter[i-1] - xmcenter[i])
7501  + (ymcenter[i-1] - ymcenter[i]) * (ymcenter[i-1] - ymcenter[i]);
7502  dscale[i-1] = sqrt(dist1/dist2);
7503  dangle[i-1] = atan2(ycenter[i-1] - ycenter[i],
7504  xcenter[i-1] - xcenter[i])
7505  - atan2(ymcenter[i-1] - ymcenter[i],
7506  xmcenter[i-1] - xmcenter[i]);
7507  dangle[i-1] *= 180;
7508  dangle[i-1] /= pi;
7509  }
7510 
7511  minscale = cpl_vector_get_min(scales);
7512  scale = cpl_vector_get_median_const(scales);
7513  maxscale = cpl_vector_get_max(scales);
7514 
7515  minangle = cpl_vector_get_min(angles);
7516  angle = cpl_vector_get_median_const(angles);
7517  maxangle = cpl_vector_get_max(angles);
7518 
7519  cpl_msg_info(func, "Median platescale: %f pixel/mm", scale);
7520  cpl_msg_info(func, "Minmax platescale: %f, %f pixel/mm",
7521  minscale, maxscale);
7522 
7523  cpl_msg_info(func, "Median rotation: %f degrees", angle);
7524  cpl_msg_info(func, "Minmax rotation: %f, %f degrees",
7525  minangle, maxangle);
7526 
7527  good = cpl_table_get_data_int(positions, "good");
7528 
7529  good[0] = good[found_slits - 1] = 1;
7530  for (i = 1; i < found_slits; i++) {
7531  if (fabs((dscale[i-1] - scale)/scale) < 0.10
7532  && fabs(dangle[i-1] - angle) < 2) {
7533  good[i-1]++;
7534  good[i]++;
7535  }
7536  }
7537 
7538  for (i = 0; i < found_slits; i++) {
7539  if (good[i] < 2)
7540  good[i] = 0;
7541  else
7542  good[i] = 1;
7543  }
7544 
7545 /*
7546  for (i = 1; i < found_slits; i++)
7547  if (fabs((dscale[i-1] - scale)/scale) < 0.10)
7548  good[i-1] = good[i] = 1;
7549 */
7550 
7551 /* DEBUG ************+
7552  for (i = 0; i < found_slits; i++) {
7553  if (good[i]) {
7554  if (i == found_slits - 1)
7555  printf("include slit %d, prev = %f, %f\n",
7556  i, dscale[i-1], dangle[i-1]);
7557  else if (i == 0)
7558  printf("include slit %d, next %f, %f\n",
7559  i, dscale[i], dangle[i]);
7560  else
7561  printf("include slit %d, prev = %f, %f, next %f, %f\n", i,
7562  dscale[i-1], dangle[i-1], dscale[i], dangle[i]);
7563  }
7564  else {
7565  if (i == found_slits - 1)
7566  printf("EXclude slit %d, prev = %f, %f\n",
7567  i, dscale[i-1], dangle[i-1]);
7568  else if (i == 0)
7569  printf("EXclude slit %d, next %f, %f\n",
7570  i, dscale[i], dangle[i]);
7571  else
7572  printf("EXclude slit %d, prev = %f, %f, next %f, %f\n", i,
7573  dscale[i-1], dangle[i-1], dscale[i], dangle[i]);
7574  }
7575  }
7576 +*********** DEBUG */
7577 
7578  cpl_vector_delete(scales);
7579  cpl_vector_delete(angles);
7580 
7581  cpl_table_and_selected_int(positions, "good", CPL_EQUAL_TO, 0);
7582  cpl_table_erase_selected(positions);
7583  cpl_table_erase_column(positions, "good");
7584  found_slits = cpl_table_get_nrow(positions);
7585 
7586  if (found_slits < 4) {
7587 
7588  /*
7589  * If the self-consistency check gives such a poor result,
7590  * something must have gone really wrong in the preliminary
7591  * wavelength calibration... Nothing can be done.
7592  */
7593 
7594  cpl_msg_warning(func, "Too few safely identified slits: %d out of %d "
7595  "candidates (%d expected). Process will continue "
7596  "using the detected CCD slits positions", found_slits,
7597  nslits, nmaskslits);
7598  cpl_table_delete(positions);
7599  return NULL;
7600  }
7601  else {
7602  cpl_msg_info(func, "Safely identified slits: %d out of %d "
7603  "candidates\n(%d expected)", found_slits, nslits,
7604  nmaskslits);
7605  }
7606 
7607 
7608  /*
7609  * Now select the central points of the identified slits, and
7610  * fit two bivariate polynomials to determine a first approximate
7611  * relation between positions on the mask and positions on the CCD.
7612  */
7613 
7614  xpos = cpl_vector_wrap(found_slits,
7615  cpl_table_get_data_double(positions, "xcenter"));
7616  ypos = cpl_vector_wrap(found_slits,
7617  cpl_table_get_data_double(positions, "ycenter"));
7618  xmpos = cpl_vector_wrap(found_slits,
7619  cpl_table_get_data_double(positions, "xmcenter"));
7620  ympos = cpl_vector_wrap(found_slits,
7621  cpl_table_get_data_double(positions, "ymcenter"));
7622  mpos = cpl_bivector_wrap_vectors(xmpos, ympos);
7623 
7624  if (found_slits < 10)
7625  degree = 1;
7626  else
7627  degree = 2;
7628 
7629  xpoly = cpl_polynomial_fit_2d_create(mpos, xpos, degree, &xmse);
7630  if (xpoly != NULL)
7631  ypoly = cpl_polynomial_fit_2d_create(mpos, ypos, degree, &ymse);
7632  cpl_bivector_unwrap_vectors(mpos);
7633  cpl_vector_unwrap(xpos);
7634  cpl_vector_unwrap(ypos);
7635  cpl_vector_unwrap(xmpos);
7636  cpl_vector_unwrap(ympos);
7637  if (ypoly == NULL) {
7638  if (found_slits == nmaskslits) {
7639  cpl_msg_warning(func, "Fit failure: the accuracy of the "
7640  "identified slits positions is not improved.");
7641 
7642  /*
7643  * The determination of the transformation from mask to CCD
7644  * failed, but since all slits have been already identified
7645  * this is not a problem: data can be reduced also without
7646  * such transformation. Simply the accuracy of the slits
7647  * positions cannot be improved.
7648  */
7649 
7650  } else {
7651  cpl_msg_info(func, "Fit failure: not all slits have been "
7652  "identified. Process will continue using "
7653  "the detected CCD slits positions");
7654  }
7655 
7656  cpl_polynomial_delete(xpoly);
7657  return positions;
7658  }
7659 
7660  cpl_msg_info(func, "Fit successful: X rms = %.3g, Y rms = %.3g (pixel)",
7661  sqrt(xmse), sqrt(ymse));
7662 
7663  if (global) {
7664  write_global_distortion(global, 0, xpoly);
7665  write_global_distortion(global, 7, ypoly);
7666  }
7667 
7668  /*
7669  * The fit was successful: use the polynomials to obtain a new
7670  * position table with the improved positions of the slits
7671  */
7672 
7673  cpl_table_delete(positions);
7674 
7675  positions = cpl_table_duplicate(maskslits);
7676  cpl_table_duplicate_column(positions, "xmtop", positions, "xtop");
7677  cpl_table_duplicate_column(positions, "ymtop", positions, "ytop");
7678  cpl_table_duplicate_column(positions, "xmbottom", positions, "xbottom");
7679  cpl_table_duplicate_column(positions, "ymbottom", positions, "ybottom");
7680 
7681  point = cpl_vector_new(2);
7682  dpoint = cpl_vector_get_data(point);
7683 
7684  for (i = 0; i < nmaskslits; i++) {
7685  dpoint[0] = cpl_table_get_double(positions, "xmtop", i, NULL);
7686  dpoint[1] = cpl_table_get_double(positions, "ymtop", i, NULL);
7687  cpl_table_set_double(positions, "xtop", i,
7688  cpl_polynomial_eval(xpoly, point));
7689  cpl_table_set_double(positions, "ytop", i,
7690  cpl_polynomial_eval(ypoly, point));
7691  dpoint[0] = cpl_table_get_double(positions, "xmbottom", i, NULL);
7692  dpoint[1] = cpl_table_get_double(positions, "ymbottom", i, NULL);
7693  cpl_table_set_double(positions, "xbottom", i,
7694  cpl_polynomial_eval(xpoly, point));
7695  cpl_table_set_double(positions, "ybottom", i,
7696  cpl_polynomial_eval(ypoly, point));
7697  }
7698 
7699  cpl_vector_delete(point);
7700  cpl_polynomial_delete(xpoly);
7701  cpl_polynomial_delete(ypoly);
7702 
7703  cpl_table_erase_column(positions, "xmtop");
7704  cpl_table_erase_column(positions, "ymtop");
7705  cpl_table_erase_column(positions, "xmbottom");
7706  cpl_table_erase_column(positions, "ymbottom");
7707 
7708  if (nmaskslits > nslits)
7709  cpl_msg_info(func, "Finally identified slits: %d out of %d expected\n"
7710  "(%d recovered)", nmaskslits, nmaskslits, nmaskslits - nslits);
7711  else if (nmaskslits < nslits)
7712  cpl_msg_info(func, "Finally identified slits: %d out of %d expected\n"
7713  "(%d rejected)", nmaskslits, nmaskslits, nslits - nmaskslits);
7714  else
7715  cpl_msg_info(func, "Finally identified slits: %d out of %d expected",
7716  nmaskslits, nmaskslits);
7717 
7718  return positions;
7719 }
7720 
7721 
7763 cpl_table *mos_trace_flat(cpl_image *flat, cpl_table *slits, double reference,
7764  double blue, double red, double dispersion)
7765 {
7766 
7767  const char *func = "mos_trace_flat";
7768 
7769  cpl_image *gradient;
7770  cpl_image *sgradient;
7771  float *dgradient;
7772  float level = 500; /* It was 250... */
7773  cpl_vector *row;
7774  cpl_vector *srow;
7775  cpl_vector **peaks;
7776  double *peak;
7777  int *slit_id;
7778  float *g;
7779  double *r;
7780  double *xtop;
7781  double *ytop;
7782  double *xbottom;
7783  double *ybottom;
7784  double min, dist;
7785  double sradius;
7786  double tolerance;
7787  double start_y, prev_y;
7788  int minpos;
7789  int nslits;
7790  int nrows;
7791  int step = 10;
7792  int filtbox = 15;
7793  int nx, ny, npix;
7794  int pos, ypos;
7795  int npeaks;
7796  int pixel_above, pixel_below;
7797  int i, j, k, l;
7798  char trace_id[MAX_COLNAME];
7799 
7800  cpl_table *traces;
7801 
7802 
7803  if (flat == NULL || slits == NULL) {
7804  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
7805  return NULL;
7806  }
7807 
7808  if (dispersion <= 0.0) {
7809  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
7810  return NULL;
7811  }
7812 
7813  if (red - blue < dispersion) {
7814  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
7815  return NULL;
7816  }
7817 
7818  /*
7819  * Create a dummy slit_id column if it is missing in the
7820  * input slits table
7821  */
7822 
7823  nslits = cpl_table_get_nrow(slits);
7824  if (1 != cpl_table_has_column(slits, "slit_id")) {
7825  cpl_table_new_column(slits, "slit_id", CPL_TYPE_INT);
7826  for (i = 0; i < nslits; i++)
7827  cpl_table_set_int(slits, "slit_id", i, -(i+1)); /* it was (i+1) */
7828  }
7829 
7830  slit_id = cpl_table_get_data_int(slits, "slit_id");
7831 
7832  nx = cpl_image_get_size_x(flat);
7833  ny = cpl_image_get_size_y(flat);
7834  npix = nx * ny;
7835 
7836  gradient = cpl_image_duplicate(flat);
7837  dgradient = cpl_image_get_data_float(gradient);
7838 
7839  for (i = 0; i < ny - 1; i++) {
7840  k = i * nx;
7841  for (j = 0; j < nx; j++) {
7842  l = k + j;
7843  dgradient[l] = fabs(dgradient[l] - dgradient[l + nx]);
7844  }
7845  }
7846 
7847  npix--;
7848  for (j = 0; j < nx; j++)
7849  dgradient[npix - j] = 0.0;
7850 
7851  cpl_image_turn(gradient, -1);
7852  nx = cpl_image_get_size_x(gradient);
7853  ny = cpl_image_get_size_y(gradient);
7854  sgradient = mos_image_vertical_median_filter(gradient,
7855  filtbox, 0, ny, 0, step);
7856  cpl_image_delete(gradient);
7857 
7858 
7859  /*
7860  * Remove background from processed image rows
7861  */
7862 
7863  dgradient = cpl_image_get_data_float(sgradient);
7864 
7865  for (i = 1; i <= ny; i += step) {
7866  row = cpl_vector_new_from_image_row(sgradient, i);
7867  srow = cpl_vector_filter_median_create(row, filtbox);
7868  cpl_vector_subtract(row, srow);
7869  cpl_vector_delete(srow);
7870  g = dgradient + (i-1)*nx;
7871  r = cpl_vector_get_data(row);
7872  for (j = 0; j < nx; j++)
7873  g[j] = r[j];
7874  cpl_vector_delete(row);
7875  }
7876 
7877 
7878  /*
7879  * Rotate (temporarily) the input slits table, to get coordinates
7880  * compatible with the rotated gradient image.
7881  */
7882 
7883  mos_rotate_slits(slits, 1, nx, ny);
7884  xtop = cpl_table_get_data_double(slits, "xtop");
7885  ytop = cpl_table_get_data_double(slits, "ytop");
7886  xbottom = cpl_table_get_data_double(slits, "xbottom");
7887  ybottom = cpl_table_get_data_double(slits, "ybottom");
7888 
7889 
7890  /*
7891  * Get positions of peaks candidates for each processed gradient
7892  * image row
7893  */
7894 
7895  peaks = cpl_calloc(ny, sizeof(cpl_vector *));
7896 
7897  for (i = 0; i < ny; i += step) {
7898  g = dgradient + i*nx;
7899  peaks[i] = mos_peak_candidates(g, nx, level, 1.0);
7900 
7901  /* I thought this would be required, but apparently I was wrong.
7902  * Check twice... */
7903  if (peaks[i])
7904  cpl_vector_subtract_scalar(peaks[i], 0.5);
7905 
7906  }
7907 
7908  cpl_image_delete(sgradient);
7909 
7910 
7911  /*
7912  * Tracing the flat field spectra edges, starting from the
7913  * slits positions obtained at reference wavelength. The
7914  * gradient maximum closest to each slits ends is found and
7915  * accepted only within a given search radius:
7916  */
7917 
7918  sradius = 5.0; /* Pixel */
7919 
7920  /*
7921  * The tracing proceeds along the processed gradient image rows,
7922  * above and below the start position, finding the closest peak
7923  * to the previous obtained position, accepting the new position
7924  * only if it is closer than a given tolerance:
7925  */
7926 
7927  tolerance = 0.9; /* Pixel */
7928 
7929  /*
7930  * The trace is attempted for a certain number of pixels above
7931  * and below the position of the reference wavelength:
7932  */
7933 
7934  pixel_above = STRETCH_FACTOR * (red - reference) / dispersion;
7935  pixel_below = STRETCH_FACTOR * (reference - blue) / dispersion;
7936 
7937 
7938  /*
7939  * Prepare the structure of the output table:
7940  */
7941 
7942  nrows = (ny-1)/step + 1;
7943  traces = cpl_table_new(nrows);
7944  cpl_table_new_column(traces, "x", CPL_TYPE_DOUBLE);
7945  cpl_table_set_column_unit(traces, "x", "pixel");
7946  for (i = 0, j = 0; i < ny; i += step, j++)
7947  cpl_table_set(traces, "x", j, i);
7948 
7949  for (i = 0; i < nslits; i++) {
7950 
7951  /*
7952  * Find the closest processed gradient image row
7953  */
7954 
7955  ypos = ytop[i];
7956 
7957  if (ypos < 0)
7958  ypos = 0;
7959  if (ypos >= ny)
7960  ypos = ny - 1;
7961 
7962  pos = ypos / step;
7963  pos *= step;
7964 
7965  /*
7966  * Find the peak in that row that is closest to xtop[i]
7967  */
7968 
7969  if (peaks[pos]) {
7970  peak = cpl_vector_get_data(peaks[pos]);
7971  npeaks = cpl_vector_get_size(peaks[pos]);
7972 
7973  min = fabs(peak[0] - xtop[i]);
7974  minpos = 0;
7975  for (j = 1; j < npeaks; j++) {
7976  dist = fabs(peak[j] - xtop[i]);
7977  if (min > dist) {
7978  min = dist;
7979  minpos = j;
7980  }
7981  }
7982  }
7983  else {
7984  npeaks = 0;
7985  }
7986 
7987  snprintf(trace_id, MAX_COLNAME, "t%d", slit_id[i]);
7988  cpl_table_new_column(traces, trace_id, CPL_TYPE_DOUBLE);
7989 
7990  if (min > sradius || npeaks == 0) {
7991  cpl_msg_warning(func, "Cannot find spectrum edge for "
7992  "top (or left) end of slit %d", slit_id[i]);
7993  }
7994  else {
7995 
7996  /*
7997  * Add to output table the start y position. Note that
7998  * y positions are written in coordinates of the unrotated
7999  * image, i.e., with horizontal dispersion. Currently nx
8000  * is the x size of the temporarily rotated image (for
8001  * faster memory access), but it has the sense of a y size.
8002  */
8003 
8004  cpl_table_set(traces, trace_id, pos/step, nx - peak[minpos]);
8005  start_y = peak[minpos];
8006 
8007  /*
8008  * Perform the tracing of current edge. Above:
8009  */
8010 
8011  prev_y = start_y;
8012 
8013  for (j = pos + step; j < ny; j += step) {
8014  if (j - pos > pixel_above)
8015  break;
8016  if (peaks[j]) {
8017  peak = cpl_vector_get_data(peaks[j]);
8018  npeaks = cpl_vector_get_size(peaks[j]);
8019  min = fabs(peak[0] - prev_y);
8020  minpos = 0;
8021  for (k = 1; k < npeaks; k++) {
8022  dist = fabs(peak[k] - prev_y);
8023  if (min > dist) {
8024  min = dist;
8025  minpos = k;
8026  }
8027  }
8028  if (min < tolerance) {
8029  cpl_table_set(traces, trace_id, j/step,
8030  nx - peak[minpos]);
8031  prev_y = peak[minpos];
8032  }
8033  }
8034  }
8035 
8036  /*
8037  * Perform the tracing of current edge. Below:
8038  */
8039 
8040  prev_y = start_y;
8041 
8042  for (j = pos - step; j >= 0; j -= step) {
8043  if (pos - j > pixel_below)
8044  break;
8045  if (peaks[j]) {
8046  peak = cpl_vector_get_data(peaks[j]);
8047  npeaks = cpl_vector_get_size(peaks[j]);
8048  min = fabs(peak[0] - prev_y);
8049  minpos = 0;
8050  for (k = 1; k < npeaks; k++) {
8051  dist = fabs(peak[k] - prev_y);
8052  if (min > dist) {
8053  min = dist;
8054  minpos = k;
8055  }
8056  }
8057  if (min < tolerance) {
8058  cpl_table_set(traces, trace_id, j/step,
8059  nx - peak[minpos]);
8060  prev_y = peak[minpos];
8061  }
8062  }
8063  }
8064  }
8065 
8066 
8067  /*
8068  * Find the peak in that row that is closest to xbottom[i]
8069  */
8070 
8071  if (peaks[pos]) {
8072  peak = cpl_vector_get_data(peaks[pos]);
8073  npeaks = cpl_vector_get_size(peaks[pos]);
8074 
8075  min = fabs(peak[0] - xbottom[i]);
8076  minpos = 0;
8077  for (j = 1; j < npeaks; j++) {
8078  dist = fabs(peak[j] - xbottom[i]);
8079  if (min > dist) {
8080  min = dist;
8081  minpos = j;
8082  }
8083  }
8084  }
8085  else {
8086  npeaks = 0;
8087  }
8088 
8089  snprintf(trace_id, MAX_COLNAME, "b%d", slit_id[i]);
8090  cpl_table_new_column(traces, trace_id, CPL_TYPE_DOUBLE);
8091 
8092  if (min > sradius || npeaks == 0) {
8093  cpl_msg_warning(func, "Cannot find spectrum edge for "
8094  "bottom (or right) end of slit %d", slit_id[i]);
8095  }
8096  else {
8097 
8098  cpl_table_set(traces, trace_id, pos/step, nx - peak[minpos]);
8099  start_y = peak[minpos];
8100 
8101  /*
8102  * Perform the tracing of current edge. Above:
8103  */
8104 
8105  prev_y = start_y;
8106 
8107  for (j = pos + step; j < ny; j += step) {
8108  if (j - pos > pixel_above)
8109  break;
8110  if (peaks[j]) {
8111  peak = cpl_vector_get_data(peaks[j]);
8112  npeaks = cpl_vector_get_size(peaks[j]);
8113  min = fabs(peak[0] - prev_y);
8114  minpos = 0;
8115  for (k = 1; k < npeaks; k++) {
8116  dist = fabs(peak[k] - prev_y);
8117  if (min > dist) {
8118  min = dist;
8119  minpos = k;
8120  }
8121  }
8122  if (min < tolerance) {
8123  cpl_table_set(traces, trace_id, j/step,
8124  nx - peak[minpos]);
8125  prev_y = peak[minpos];
8126  }
8127  }
8128  }
8129 
8130  /*
8131  * Perform the tracing of current edge. Below:
8132  */
8133 
8134  prev_y = start_y;
8135 
8136  for (j = pos - step; j >= 0; j -= step) {
8137  if (pos - j > pixel_below)
8138  break;
8139  if (peaks[j]) {
8140  peak = cpl_vector_get_data(peaks[j]);
8141  npeaks = cpl_vector_get_size(peaks[j]);
8142  min = fabs(peak[0] - prev_y);
8143  minpos = 0;
8144  for (k = 1; k < npeaks; k++) {
8145  dist = fabs(peak[k] - prev_y);
8146  if (min > dist) {
8147  min = dist;
8148  minpos = k;
8149  }
8150  }
8151  if (min < tolerance) {
8152  cpl_table_set(traces, trace_id, j/step,
8153  nx - peak[minpos]);
8154  prev_y = peak[minpos];
8155  }
8156  }
8157  }
8158  }
8159 
8160  } /* End of loop on slits */
8161 
8162  for (i = 0; i < ny; i += step)
8163  cpl_vector_delete(peaks[i]);
8164  cpl_free(peaks);
8165 
8166  /*
8167  * Restore original orientation of slits positions table
8168  */
8169 
8170  mos_rotate_slits(slits, -1, ny, nx);
8171 
8172  return traces;
8173 
8174 }
8175 
8176 
8197 cpl_table *mos_poly_trace(cpl_table *slits, cpl_table *traces, int order)
8198 {
8199  const char *func = "mos_poly_trace";
8200 
8201  cpl_table *polytraces;
8202  cpl_table *dummy;
8203  cpl_vector *x;
8204  cpl_vector *trace;
8205  cpl_polynomial *polytrace;
8206  char trace_id[MAX_COLNAME];
8207  char trace_res[MAX_COLNAME];
8208  char trace_mod[MAX_COLNAME];
8209  const char *clab[6] = {"c0", "c1", "c2", "c3", "c4", "c5"};
8210  /* Max order is 5 */
8211  double *xdata;
8212  int *slit_id;
8213  int nslits;
8214  int nrows;
8215  int npoints;
8216  int i, j;
8217  cpl_size k;
8218 
8219 
8220  if (traces == NULL || slits == NULL) {
8221  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
8222  return NULL;
8223  }
8224 
8225  if (order > 5) {
8226  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
8227  return NULL;
8228  }
8229 
8230  nrows = cpl_table_get_nrow(traces);
8231  xdata = cpl_table_get_data_double(traces, "x");
8232  nslits = cpl_table_get_nrow(slits);
8233  slit_id = cpl_table_get_data_int(slits, "slit_id");
8234 
8235  polytraces = cpl_table_new(2*nslits);
8236  cpl_table_new_column(polytraces, "slit_id", CPL_TYPE_INT);
8237  for (i = 0; i <= order; i++)
8238  cpl_table_new_column(polytraces, clab[i], CPL_TYPE_DOUBLE);
8239 
8240  for (i = 0; i < nslits; i++) {
8241  for (j = 0; j < 2; j++) { /* For top and bottom trace of each slit */
8242 
8243  if (j) {
8244  snprintf(trace_id, MAX_COLNAME, "b%d", slit_id[i]);
8245  snprintf(trace_res, MAX_COLNAME, "b%d_res", slit_id[i]);
8246  snprintf(trace_mod, MAX_COLNAME, "b%d_mod", slit_id[i]);
8247  }
8248  else {
8249  snprintf(trace_id, MAX_COLNAME, "t%d", slit_id[i]);
8250  snprintf(trace_res, MAX_COLNAME, "t%d_res", slit_id[i]);
8251  snprintf(trace_mod, MAX_COLNAME, "t%d_mod", slit_id[i]);
8252  }
8253 
8254  cpl_table_set_int(polytraces, "slit_id", 2*i+j, slit_id[i]);
8255 
8256  /*
8257  * The "dummy" table is just a tool for eliminating invalid
8258  * points from the vectors to be fitted.
8259  */
8260 
8261  dummy = cpl_table_new(nrows);
8262  cpl_table_duplicate_column(dummy, "x", traces, "x");
8263  cpl_table_duplicate_column(dummy, trace_id, traces, trace_id);
8264  npoints = nrows - cpl_table_count_invalid(dummy, trace_id);
8265  if (npoints < 2 * order) {
8266  cpl_table_delete(dummy);
8267  continue;
8268  }
8269  cpl_table_erase_invalid(dummy);
8270  x = cpl_vector_wrap(npoints,
8271  cpl_table_get_data_double(dummy, "x"));
8272  trace = cpl_vector_wrap(npoints,
8273  cpl_table_get_data_double(dummy, trace_id));
8274  polytrace = cpl_polynomial_fit_1d_create(x, trace, order, NULL);
8275  cpl_vector_unwrap(x);
8276  cpl_vector_unwrap(trace);
8277  cpl_table_delete(dummy);
8278 
8279  /*
8280  * Screen bad solutions. At the moment, a primitive screening
8281  * consists in excluding solutions displaying excessive
8282  * curvature (larger than 1E-5 / pixel)
8283  */
8284 
8285  k = 2;
8286  if (fabs(cpl_polynomial_get_coeff(polytrace, &k)) > 1.E-4) {
8287  cpl_polynomial_delete(polytrace);
8288  cpl_table_new_column(traces, trace_mod, CPL_TYPE_DOUBLE);
8289  cpl_table_duplicate_column(traces, trace_res, traces,
8290  trace_mod);
8291  if (j)
8292  cpl_msg_warning(func, "Exclude bad curvature solution "
8293  "for bottom (right) edge of slit %d", slit_id[i]);
8294  else
8295  cpl_msg_warning(func, "Exclude bad curvature solution "
8296  "for top (left) edge of slit %d", slit_id[i]);
8297  continue;
8298  }
8299 
8300  /*
8301  * Write polynomial coefficients to the output table,
8302  * tagged with the appropriate slit_id
8303  */
8304 
8305  for (k = 0; k <= order; k++)
8306  cpl_table_set_double(polytraces, clab[k], 2*i+j,
8307  cpl_polynomial_get_coeff(polytrace, &k));
8308 
8309  /*
8310  * Add column of residuals to input traces table
8311  */
8312 
8313  cpl_table_new_column(traces, trace_mod, CPL_TYPE_DOUBLE);
8314  cpl_table_set_column_unit(traces, trace_mod, "pixel");
8315 
8316  for (k = 0; k < nrows; k++) {
8317  cpl_table_set_double(traces, trace_mod, k,
8318  cpl_polynomial_eval_1d(polytrace, xdata[k], NULL));
8319  }
8320 
8321  cpl_polynomial_delete(polytrace);
8322 
8323  cpl_table_duplicate_column(traces, trace_res, traces, trace_mod);
8324  cpl_table_subtract_columns(traces, trace_res, trace_id);
8325  cpl_table_multiply_scalar(traces, trace_res, -1.0);
8326 
8327  }
8328  }
8329 
8330  return polytraces;
8331 
8332 }
8333 
8334 
8358 cpl_error_code mos_global_trace(cpl_table *slits, cpl_table *polytraces,
8359  int mode)
8360 {
8361  const char *func = "mos_global_trace";
8362 
8363  const char *clab[6] = {"c0", "c1", "c2", "c3", "c4", "c5"};
8364  /* Max order is 5 */
8365  cpl_table *table;
8366  cpl_vector *c0;
8367  cpl_vector *cn;
8368  cpl_bivector *list;
8369 /* alternative (not robust)
8370  cpl_polynomial *poly;
8371 */
8372 
8373  double *offset;
8374  double rms, q, m;
8375 
8376  int order, nrows, nslits;
8377  int i, j;
8378 
8379 
8380  if (polytraces == NULL) {
8381  cpl_msg_error(func, "Missing spectral curvature table");
8382  return cpl_error_set(func, CPL_ERROR_NULL_INPUT);
8383  }
8384 
8385  if (slits == NULL) {
8386  cpl_msg_error(func, "Missing slits positions table");
8387  return cpl_error_set(func, CPL_ERROR_NULL_INPUT);
8388  }
8389 
8390  nslits = cpl_table_get_nrow(slits);
8391 
8392  table = cpl_table_duplicate(polytraces);
8393  cpl_table_erase_invalid(table);
8394 
8395  nrows = cpl_table_get_nrow(table);
8396 
8397  if (nrows < 4) {
8398  cpl_msg_warning(func, "Too few successful spectral curvature tracings "
8399  "(%d): the determination of a global curvature model "
8400  "failed", nrows);
8401  return CPL_ERROR_NONE;
8402  }
8403 
8404  order = cpl_table_get_ncol(polytraces) - 2;
8405 
8406  for (i = 0; i <= order; i++) {
8407  if (!cpl_table_has_column(table, clab[i])) {
8408  cpl_msg_error(func, "Wrong spectral curvature table");
8409  return cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
8410  }
8411  }
8412 
8413 
8414  /*
8415  * Fill in advance the missing offset terms
8416  */
8417 
8418  for (i = 0; i < nslits; i++) {
8419  if (!cpl_table_is_valid(polytraces, clab[0], 2*i)) {
8420  cpl_table_set_double(polytraces, clab[0], 2*i,
8421  cpl_table_get_double(slits, "ytop", i, NULL));
8422  }
8423  if (!cpl_table_is_valid(polytraces, clab[0], 2*i+1)) {
8424  cpl_table_set_double(polytraces, clab[0], 2*i+1,
8425  cpl_table_get_double(slits, "ybottom", i, NULL));
8426  }
8427  }
8428 
8429  offset = cpl_table_get_data_double(polytraces, clab[0]);
8430 
8431 
8432  /*
8433  * Fit the global model and modify polytraces table accordingly
8434  */
8435 
8436  c0 = cpl_vector_wrap(nrows, cpl_table_get_data_double(table, clab[0]));
8437 
8438  for (i = 1; i <= order; i++) {
8439  cn = cpl_vector_wrap(nrows, cpl_table_get_data_double(table, clab[i]));
8440  list = cpl_bivector_wrap_vectors(c0, cn);
8441  robustLinearFit(list, &q, &m, &rms);
8442 /* alternative (not robust)
8443  poly = cpl_polynomial_fit_1d_create(c0, cn, 1, NULL);
8444 */
8445  for (j = 0; j < 2*nslits; j++) {
8446  if (mode == 1)
8447  if (cpl_table_is_valid(polytraces, clab[i], j))
8448  continue;
8449  cpl_table_set_double(polytraces, clab[i], j, offset[j]*m + q);
8450 /* alternative (not robust)
8451  cpl_table_set_double(polytraces, clab[i], j,
8452  cpl_polynomial_eval_1d(poly, offset[j], NULL));
8453 */
8454  }
8455  cpl_bivector_unwrap_vectors(list);
8456 /* alternative (not robust)
8457  cpl_polynomial_delete(poly);
8458 */
8459  cpl_vector_unwrap(cn);
8460  }
8461 
8462  cpl_vector_unwrap(c0);
8463  cpl_table_delete(table);
8464 
8465  return CPL_ERROR_NONE;
8466 
8467 }
8468 
8469 
8539 cpl_image *mos_spatial_calibration(cpl_image *spectra, cpl_table *slits,
8540  cpl_table *polytraces, double reference,
8541  double blue, double red, double dispersion,
8542  int flux, cpl_image *calibration)
8543 {
8544  const char *func = "mos_spatial_calibration";
8545 
8546  const char *clab[6] = {"c0", "c1", "c2", "c3", "c4", "c5"};
8547  /* Max order is 5 */
8548  cpl_polynomial *polytop;
8549  cpl_polynomial *polybot;
8550  cpl_image **exslit;
8551  cpl_image *resampled;
8552  float *data;
8553  float *sdata;
8554  float *xdata;
8555  double vtop, vbot, value;
8556  double top, bot;
8557  double coeff;
8558  double ytop, ybot;
8559  double ypos, yfra;
8560  double factor;
8561  int yint, ysize, yprev;
8562  int nslits;
8563  int npseudo;
8564  int *slit_id;
8565  int *length;
8566  int nx, ny;
8567  int pixel_above, pixel_below, refpixel, start_pixel, end_pixel;
8568  int missing_top, missing_bot;
8569  int null;
8570  int order;
8571  int i, j;
8572  cpl_size k;
8573 
8574  int create_position = 1;
8575 
8576 
8577  if (spectra == NULL || slits == NULL || polytraces == NULL) {
8578  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
8579  return NULL;
8580  }
8581 
8582  if (dispersion <= 0.0) {
8583  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
8584  return NULL;
8585  }
8586 
8587  if (red - blue < dispersion) {
8588  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
8589  return NULL;
8590  }
8591 
8592  nx = cpl_image_get_size_x(spectra);
8593  ny = cpl_image_get_size_y(spectra);
8594  sdata = cpl_image_get_data(spectra);
8595  if (calibration)
8596  data = cpl_image_get_data(calibration);
8597 
8598  if (cpl_table_has_column(slits, "position"))
8599  create_position = 0;
8600 
8601  if (create_position) {
8602  cpl_table_new_column(slits, "position", CPL_TYPE_INT);
8603  cpl_table_new_column(slits, "length", CPL_TYPE_INT);
8604  cpl_table_set_column_unit(slits, "position", "pixel");
8605  cpl_table_set_column_unit(slits, "length", "pixel");
8606  }
8607  else
8608  length = cpl_table_get_data_int(slits, "length");
8609 
8610  nslits = cpl_table_get_nrow(slits);
8611  slit_id = cpl_table_get_data_int(slits, "slit_id");
8612  order = cpl_table_get_ncol(polytraces) - 2;
8613 
8614  /*
8615  * The spatial resampling is performed for a certain number of
8616  * pixels above and below the position of the reference wavelength:
8617  */
8618 
8619  pixel_above = STRETCH_FACTOR * (red - reference) / dispersion;
8620  pixel_below = STRETCH_FACTOR * (reference - blue) / dispersion;
8621 
8622  exslit = cpl_calloc(nslits, sizeof(cpl_image *));
8623 
8624  for (i = 0; i < nslits; i++) {
8625 
8626  if (create_position == 0)
8627  if (length[i] == 0)
8628  continue;
8629 
8630  /*
8631  * Note that the x coordinate of the reference pixels on the CCD
8632  * is taken arbitrarily at the top end of each slit. This wouldn't
8633  * be entirely correct in case of curved slits, or in presence of
8634  * heavy distortions: in such cases the spatial resampling is
8635  * really performed across a wide range of wavelengths. But
8636  * the lag between top and bottom spectral curvature models
8637  * would introduce even in such cases negligible effects on
8638  * the spectral spatial resampling.
8639  */
8640 
8641  refpixel = cpl_table_get_double(slits, "xtop", i, NULL);
8642 
8643  start_pixel = refpixel - pixel_below;
8644  if (start_pixel < 0)
8645  start_pixel = 0;
8646 
8647  end_pixel = refpixel + pixel_above;
8648  if (end_pixel > nx)
8649  end_pixel = nx;
8650 
8651  /*
8652  * Recover from the table of spectral curvature coefficients
8653  * the curvature polynomials.
8654  */
8655 
8656  missing_top = 0;
8657  polytop = cpl_polynomial_new(1);
8658  for (k = 0; k <= order; k++) {
8659  coeff = cpl_table_get_double(polytraces, clab[k], 2*i, &null);
8660  if (null) {
8661  cpl_polynomial_delete(polytop);
8662  missing_top = 1;
8663  break;
8664  }
8665  cpl_polynomial_set_coeff(polytop, &k, coeff);
8666  }
8667 
8668  missing_bot = 0;
8669  polybot = cpl_polynomial_new(1);
8670  for (k = 0; k <= order; k++) {
8671  coeff = cpl_table_get_double(polytraces, clab[k], 2*i+1, &null);
8672  if (null) {
8673  cpl_polynomial_delete(polybot);
8674  missing_bot = 1;
8675  break;
8676  }
8677  cpl_polynomial_set_coeff(polybot, &k, coeff);
8678  }
8679 
8680  if (missing_top && missing_bot) {
8681  cpl_msg_warning(func, "Spatial calibration, slit %d was not "
8682  "traced: no extraction!",
8683  slit_id[i]);
8684  continue;
8685  }
8686 
8687  /*
8688  * In case just one of the two edges was not traced, the other
8689  * edge curvature model is duplicated and shifted to the other
8690  * end of the slit: better than nothing!
8691  */
8692 
8693  if (missing_top) {
8694  cpl_msg_warning(func, "Upper edge of slit %d was not traced: "
8695  "the spectral curvature of the lower edge "
8696  "is used instead.", slit_id[i]);
8697  polytop = cpl_polynomial_duplicate(polybot);
8698  ytop = cpl_table_get_double(slits, "ytop", i, NULL);
8699  ybot = cpl_table_get_double(slits, "ybottom", i, NULL);
8700  k = 0;
8701  coeff = cpl_polynomial_get_coeff(polybot, &k);
8702  coeff += ytop - ybot;
8703  cpl_polynomial_set_coeff(polytop, &k, coeff);
8704  }
8705 
8706  if (missing_bot) {
8707  cpl_msg_warning(func, "Lower edge of slit %d was not traced: "
8708  "the spectral curvature of the upper edge "
8709  "is used instead.", slit_id[i]);
8710  polybot = cpl_polynomial_duplicate(polytop);
8711  ytop = cpl_table_get_double(slits, "ytop", i, NULL);
8712  ybot = cpl_table_get_double(slits, "ybottom", i, NULL);
8713  k = 0;
8714  coeff = cpl_polynomial_get_coeff(polytop, &k);
8715  coeff -= ytop - ybot;
8716  cpl_polynomial_set_coeff(polybot, &k, coeff);
8717  }
8718 
8719  /*
8720  * Allocate image for current extracted slit
8721  */
8722 
8723  top = cpl_polynomial_eval_1d(polytop, refpixel, NULL);
8724  bot = cpl_polynomial_eval_1d(polybot, refpixel, NULL);
8725  npseudo = ceil(top-bot) + 1;
8726 
8727  if (npseudo < 1) {
8728  cpl_polynomial_delete(polytop);
8729  cpl_polynomial_delete(polybot);
8730  cpl_msg_warning(func, "Slit %d was badly traced: no extraction!",
8731  slit_id[i]);
8732  continue;
8733  }
8734 
8735  exslit[i] = cpl_image_new(nx, npseudo+1, CPL_TYPE_FLOAT);
8736  xdata = cpl_image_get_data(exslit[i]);
8737 
8738  /*
8739  * Write interpolated values to slit image.
8740  */
8741 
8742  for (j = start_pixel; j < end_pixel; j++) {
8743  top = cpl_polynomial_eval_1d(polytop, j, NULL);
8744  bot = cpl_polynomial_eval_1d(polybot, j, NULL);
8745  factor = (top-bot)/npseudo;
8746  for (k = 0; k <= npseudo; k++) {
8747  ypos = top - k*factor;
8748  yint = floor(ypos);
8749  yfra = ypos - yint;
8750  if (yint >= 0 && yint < ny-1) {
8751  vtop = sdata[j + nx*yint];
8752  vbot = sdata[j + nx*(yint+1)];
8753 
8754  //This means that the top and bottom traces are crossing,
8755  //which is physically impossible, so let's set it to 0.
8756  if(factor <= 0 )
8757  value = 0;
8758  else if(vtop == FLT_MAX || vbot == FLT_MAX)
8759  value = FLT_MAX;
8760  else
8761  {
8762  value = vtop*(1-yfra) + vbot*yfra;
8763  if (flux)
8764  value *= factor;
8765  }
8766  xdata[j + nx*(npseudo-k)] = value;
8767  if (calibration) {
8768  data[j + nx*yint] = (top-yint)/factor;
8769  if (k) {
8770 
8771  /*
8772  * This is added to recover lost pixels on
8773  * the CCD image (pixels are lost because
8774  * the CCD pixels are less than npseudo+1).
8775  */
8776 
8777  if (yprev - yint > 1) {
8778  data[j + nx*(yint+1)] = (top-yint-1)/factor;
8779  }
8780  }
8781  }
8782  }
8783  yprev = yint;
8784  }
8785  }
8786  cpl_polynomial_delete(polytop);
8787  cpl_polynomial_delete(polybot);
8788  }
8789 
8790  /*
8791  * Now all the slits images are copied to a single image
8792  */
8793 
8794  ysize = 0;
8795  for (i = 0; i < nslits; i++)
8796  if (exslit[i])
8797  ysize += cpl_image_get_size_y(exslit[i]);
8798 
8799  resampled = cpl_image_new(nx, ysize, CPL_TYPE_FLOAT);
8800 
8801  yint = -1;
8802  for (i = 0; i < nslits; i++) {
8803  if (exslit[i]) {
8804  yint += cpl_image_get_size_y(exslit[i]);
8805  cpl_image_copy(resampled, exslit[i], 1, ysize - yint);
8806  if (create_position) {
8807  cpl_table_set_int(slits, "position", i, ysize - yint - 1);
8808  cpl_table_set_int(slits, "length", i,
8809  cpl_image_get_size_y(exslit[i]));
8810  }
8811  cpl_image_delete(exslit[i]);
8812  }
8813  else if (create_position) {
8814  cpl_table_set_int(slits, "position", i, -1);
8815  cpl_table_set_int(slits, "length", i, 0);
8816  }
8817  }
8818 
8819 
8820  /*
8821  * Elimination of non-traced slits from slit position table: we cannot do
8822  * it because we would lose sync with polytraces and other slit-oriented
8823  * tables. COMMENTED OUT.
8824  *
8825 
8826  if (create_position) {
8827 
8828  if (cpl_table_and_selected_int(slits, "position", CPL_EQUAL_TO, -1))
8829  cpl_table_erase_selected(slits);
8830 
8831  }
8832 
8833  */
8834 
8835  cpl_free(exslit);
8836 
8837  return resampled;
8838 
8839 }
8840 
8841 
8954 cpl_image *mos_wavelength_calibration_final(cpl_image *image, cpl_table *slits,
8955  cpl_vector *lines,
8956  double dispersion, float level,
8957  int sradius, int order,
8958  double reject, double refwave,
8959  double *wavestart, double *waveend,
8960  int *nlines, double *error,
8961  cpl_table *idscoeff,
8962  cpl_image *calibration,
8963  cpl_image *residuals,
8964  cpl_table *restable,
8965  cpl_table *detected_lines)
8966 {
8967 
8968  const char *func = "mos_wavelength_calibration_final";
8969 
8970  const char *clab[6] = {"c0", "c1", "c2", "c3", "c4", "c5"};
8971  /* Max order is 5 */
8972 
8973  double tolerance = 20.0; /* Probably forever... */
8974  int step = 10; /* Compute restable every "step" rows */
8975 
8976  char name[MAX_COLNAME];
8977 
8978  cpl_image *resampled;
8979  cpl_bivector *peaks_ident;
8980  cpl_vector *wavel;
8981  cpl_vector *peaks;
8982  cpl_polynomial *ids;
8983  cpl_polynomial *lin;
8984  cpl_polynomial *fguess;
8985  cpl_table *coeff;
8986  double ids_err;
8987  double max_disp, min_disp;
8988  double *line;
8989  double firstLambda, lastLambda, lambda;
8990  double wave, pixe, value;
8991  double c;
8992  float *sdata;
8993  float *rdata;
8994  float *idata;
8995  float *ddata;
8996  float v1, v2, vi;
8997  float fpixel;
8998  int *length;
8999  int pixstart, pixend;
9000  int row_top, row_bot;
9001  int extrapolation;
9002  int nref;
9003  int nslits;
9004  int nfits;
9005  int nl, nx, ny, pixel;
9006  int countLines, usedLines;
9007  int uorder;
9008  int missing;
9009  int null;
9010  int width, uradius;
9011  int i, j, s;
9012  cpl_size k;
9013 
9014 
9015  if (dispersion == 0.0) {
9016  cpl_msg_error(func, "The expected dispersion (A/pixel) must be given");
9017  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
9018  return NULL;
9019  }
9020 
9021  if (dispersion < 0.0) {
9022  cpl_msg_error(func, "The expected dispersion must be positive");
9023  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
9024  return NULL;
9025  }
9026 
9027  if (idscoeff == NULL) {
9028  cpl_msg_error(func, "A preallocated IDS coeff table must be given");
9029  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
9030  return NULL;
9031  }
9032 
9033  max_disp = dispersion + dispersion * tolerance / 100;
9034  min_disp = dispersion - dispersion * tolerance / 100;
9035 
9036  if (order < 1) {
9037  cpl_msg_error(func, "The order of the fitting polynomial "
9038  "must be at least 1");
9039  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
9040  return NULL;
9041  }
9042 
9043  if (image == NULL || lines == NULL) {
9044  cpl_msg_error(func, "Both spectral exposure and reference line "
9045  "catalog are required in input");
9046  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
9047  return NULL;
9048  }
9049 
9050  nx = cpl_image_get_size_x(image);
9051  ny = cpl_image_get_size_y(image);
9052  sdata = cpl_image_get_data_float(image);
9053 
9054  nref = cpl_vector_get_size(lines);
9055  line = cpl_vector_get_data(lines);
9056 
9057  if (*wavestart < 1.0 && *waveend < 1.0) {
9058  firstLambda = line[0];
9059  lastLambda = line[nref-1];
9060  extrapolation = (lastLambda - firstLambda) / 10;
9061  firstLambda -= extrapolation;
9062  lastLambda += extrapolation;
9063  *wavestart = firstLambda;
9064  *waveend = lastLambda;
9065  }
9066  else {
9067  firstLambda = *wavestart;
9068  lastLambda = *waveend;
9069  }
9070 
9071  nl = (lastLambda - firstLambda) / dispersion;
9072  resampled = cpl_image_new(nl, ny, CPL_TYPE_FLOAT);
9073  rdata = cpl_image_get_data_float(resampled);
9074 
9075  /*
9076  * Allocate total output table of IDS coefficients
9077  */
9078 
9079  for (j = 0; j <= order; j++)
9080  cpl_table_new_column(idscoeff, clab[j], CPL_TYPE_DOUBLE);
9081 
9082  if (calibration)
9083  idata = cpl_image_get_data_float(calibration);
9084 
9085  if (residuals)
9086  ddata = cpl_image_get_data_float(residuals);
9087 
9088  if (restable) {
9089  cpl_table_set_size(restable, nref);
9090  cpl_table_new_column(restable, "wavelength", CPL_TYPE_DOUBLE);
9091  cpl_table_copy_data_double(restable, "wavelength", line);
9092  for (i = 0; i < ny; i += step) {
9093  snprintf(name, MAX_COLNAME, "r%d", i);
9094  cpl_table_new_column(restable, name, CPL_TYPE_DOUBLE);
9095  snprintf(name, MAX_COLNAME, "d%d", i);
9096  cpl_table_new_column(restable, name, CPL_TYPE_DOUBLE);
9097  snprintf(name, MAX_COLNAME, "p%d", i);
9098  cpl_table_new_column(restable, name, CPL_TYPE_DOUBLE);
9099  }
9100  }
9101 
9102  if (detected_lines) {
9103  cpl_table_set_size(detected_lines, 0);
9104  cpl_table_new_column(detected_lines, "slit_id", CPL_TYPE_INT);
9105  cpl_table_new_column(detected_lines, "xpos_rectified", CPL_TYPE_DOUBLE);
9106  cpl_table_new_column(detected_lines, "ypos_rectified", CPL_TYPE_DOUBLE);
9107  cpl_table_new_column(detected_lines, "xpos_rectified_iter", CPL_TYPE_DOUBLE);
9108  cpl_table_new_column(detected_lines, "ypos_rectified_iter", CPL_TYPE_DOUBLE);
9109  cpl_table_new_column(detected_lines, "peak_flux", CPL_TYPE_DOUBLE);
9110  cpl_table_new_column(detected_lines, "wave_ident", CPL_TYPE_DOUBLE);
9111  cpl_table_new_column(detected_lines, "wave_ident_iter", CPL_TYPE_DOUBLE);
9112  cpl_table_new_column(detected_lines, "xpos_fit_rect_wavecal", CPL_TYPE_DOUBLE);
9113  cpl_table_new_column(detected_lines, "res_xpos", CPL_TYPE_DOUBLE);
9114  }
9115 
9116 
9117  /*
9118  * Process all slits separately.
9119  */
9120 
9121  nslits = cpl_table_get_nrow(slits);
9122  length = cpl_table_get_data_int(slits, "length");
9123 
9124  row_top = ny;
9125  for (s = 0; s < nslits; s++) {
9126 
9127  int slit_id;
9128  slit_id = cpl_table_get_int(slits, "slit_id", s, NULL);
9129 
9130  if (length[s] == 0)
9131  continue;
9132 
9133  /*
9134  * row_top and row_bot define the boundaries of the current slit.
9135  * Here we begin (arbitrarily...) from the top slit.
9136  */
9137 
9138  row_bot = cpl_table_get_int(slits, "position", s, NULL);
9139 
9140  if (sradius > 0) {
9141 
9142  /*
9143  * If a search radius was defined, allocate the table of
9144  * the fitting polynomials coefficients. This table is
9145  * just used to generate the first-guess polynomial made
9146  * of the median coefficients of all polynomials found
9147  * for this slit.
9148  */
9149 
9150  coeff = cpl_table_new(row_top - row_bot);
9151  for (j = 0; j <= order; j++)
9152  cpl_table_new_column(coeff, clab[j], CPL_TYPE_DOUBLE);
9153  }
9154 
9155  /*
9156  * Here is the loop on all rows of the current slit. They are
9157  * wavelength calibrated one by one.
9158  */
9159 
9160  for (i = row_bot; i < row_top; i++) {
9161  width = mos_lines_width(sdata + i*nx, nx);
9162  if (width < 5)
9163  width = 5;
9164  peaks = mos_peak_candidates(sdata + i*nx, nx, level, width);
9165  if (peaks) {
9166  peaks = mos_refine_peaks(sdata + i*nx, nx, peaks, width);
9167  }
9168  if (peaks) {
9169  int keep_multiplex = mos_multiplex;
9170  mos_multiplex = -1;
9171  if(detected_lines)
9172  {
9173  cpl_size newlines = cpl_vector_get_size(peaks);
9174  cpl_size oldsize = cpl_table_get_nrow(detected_lines);
9175  cpl_table_set_size(detected_lines, oldsize + newlines);
9176  for(cpl_size iline = 0; iline < newlines; ++iline)
9177  {
9178  cpl_table_set_int(detected_lines, "slit_id",
9179  oldsize + iline, slit_id);
9180  cpl_table_set_double(detected_lines, "xpos_rectified",
9181  oldsize + iline, cpl_vector_get(peaks, iline) + 1);
9182  cpl_table_set_double(detected_lines, "ypos_rectified",
9183  oldsize + iline, (double)i + 1);
9184  cpl_table_set_double(detected_lines, "peak_flux",
9185  oldsize + iline,
9186  sdata[i*nx+(int)(cpl_vector_get(peaks, iline)+0.5)]);
9187  }
9188  }
9189  peaks_ident = mos_identify_peaks(peaks, lines,
9190  min_disp, max_disp, 0.05);
9191  mos_multiplex = keep_multiplex;
9192  if (peaks_ident) {
9193  countLines = cpl_bivector_get_size(peaks_ident);
9194  if (countLines < 4) {
9195  cpl_bivector_delete(peaks_ident);
9196  cpl_vector_delete(peaks);
9197  if (nlines)
9198  nlines[i] = 0;
9199  if (error)
9200  error[i] = 0.0;
9201  continue;
9202  }
9203 
9204  /*
9205  * Set reference wavelength as zero point
9206  */
9207 
9208  wavel = cpl_bivector_get_y(peaks_ident);
9209  cpl_vector_subtract_scalar(wavel, refwave);
9210 
9211  uorder = countLines / 2 - 1;
9212  if (uorder > order)
9213  uorder = order;
9214 
9215  ids = mos_poly_wav2pix(peaks_ident, uorder, reject,
9216  2 * (uorder + 1), &usedLines,
9217  &ids_err);
9218 
9219  if (ids == NULL) {
9220  cpl_bivector_delete(peaks_ident);
9221  cpl_vector_delete(peaks);
9222  if (nlines)
9223  nlines[i] = 0;
9224  if (error)
9225  error[i] = 0.0;
9226  cpl_error_reset();
9227  continue;
9228  }
9229 
9230  if (sradius > 0) {
9231  for (k = 0; k <= order; k++) {
9232  if (k > uorder) {
9233  cpl_table_set_double(coeff, clab[k],
9234  i - row_bot, 0.0);
9235  }
9236  else {
9237  cpl_table_set_double(coeff, clab[k],
9238  i - row_bot, cpl_polynomial_get_coeff(ids, &k));
9239  }
9240  }
9241  }
9242  /* else { */
9243  if (calibration) {
9244  pixstart = cpl_polynomial_eval_1d(ids,
9245  cpl_bivector_get_y_data(peaks_ident)[0],
9246  NULL);
9247  pixend = cpl_polynomial_eval_1d(ids,
9248  cpl_bivector_get_y_data(peaks_ident)[countLines-1],
9249  NULL);
9250  extrapolation = (pixend - pixstart) / 5;
9251  pixstart -= extrapolation;
9252  pixend += extrapolation;
9253  if (pixstart < 0)
9254  pixstart = 0;
9255  if (pixend > nx)
9256  pixend = nx;
9257 
9258  for (j = pixstart; j < pixend; j++) {
9259  (idata + i*nx)[j] = mos_eval_dds(ids,
9260  firstLambda, lastLambda, refwave, j);
9261  }
9262  }
9263 
9264  /*
9265  * Residuals image
9266  */
9267 
9268  if (residuals || (restable && !(i%step))) {
9269  if (restable && !(i%step)) {
9270  lin = cpl_polynomial_new(1);
9271  for (k = 0; k < 2; k++)
9272  cpl_polynomial_set_coeff(lin, &k,
9273  cpl_polynomial_get_coeff(ids, &k));
9274  }
9275  for (j = 0; j < countLines; j++) {
9276  pixe = cpl_bivector_get_x_data(peaks_ident)[j];
9277  wave = cpl_bivector_get_y_data(peaks_ident)[j];
9278  value = pixe
9279  - cpl_polynomial_eval_1d(ids, wave, NULL);
9280  if (residuals) {
9281  pixel = pixe + 0.5;
9282  (ddata + i*nx)[pixel] = value;
9283  }
9284  if (restable && !(i%step)) {
9285  for (k = 0; k < nref; k++) {
9286  if (fabs(line[k]-refwave-wave) < 0.1) {
9287  snprintf(name, MAX_COLNAME,
9288  "r%d", i);
9289  cpl_table_set_double(restable, name,
9290  k, value);
9291  value = pixe
9292  - cpl_polynomial_eval_1d(lin,
9293  wave, NULL);
9294  snprintf(name, MAX_COLNAME,
9295  "d%d", i);
9296  cpl_table_set_double(restable, name,
9297  k, value);
9298  snprintf(name, MAX_COLNAME,
9299  "p%d", i);
9300  cpl_table_set_double(restable, name,
9301  k, pixe);
9302  break;
9303  }
9304  }
9305  }
9306  }
9307  if (restable && !(i%step)) {
9308  cpl_polynomial_delete(lin);
9309  }
9310 /***
9311  for (j = 0; j < countLines; j++) {
9312  pixel = cpl_bivector_get_x_data(output)[j]
9313  + 0.5;
9314  (ddata + i*nx)[pixel] =
9315  cpl_bivector_get_x_data(output)[j]
9316  - cpl_polynomial_eval_1d(ids,
9317  cpl_bivector_get_y_data(output)[j],
9318  NULL);
9319  }
9320 ***/
9321  //Fill the line identification information in
9322  //the detected_lines table
9323  if(detected_lines)
9324  {
9325  cpl_size nidentlines = cpl_bivector_get_size(peaks_ident);
9326  cpl_size ndetectlines = cpl_vector_get_size(peaks);
9327  cpl_size totalsize = cpl_table_get_nrow(detected_lines);
9328  for(cpl_size idline = 0; idline < nidentlines; ++idline)
9329  {
9330  for(cpl_size detline = 0; detline < ndetectlines; ++detline)
9331  {
9332  if(cpl_vector_get(peaks, detline) ==
9333  cpl_bivector_get_x_data(peaks_ident)[idline])
9334  {
9335  cpl_size table_pos = totalsize - ndetectlines + detline;
9336  double wave_ident = cpl_bivector_get_y_data(peaks_ident)[idline] + refwave;
9337  double xpix_fit = cpl_polynomial_eval_1d(ids,
9338  wave_ident - refwave, NULL);
9339  double xpos_det = cpl_table_get_double(detected_lines,
9340  "xpos_rectified",
9341  table_pos, &null);
9342  cpl_table_set_double(detected_lines,
9343  "wave_ident",
9344  table_pos,
9345  wave_ident);
9346  cpl_table_set_double(detected_lines,
9347  "xpos_fit_rect_wavecal",
9348  table_pos,
9349  xpix_fit + 1);
9350  cpl_table_set_double(detected_lines,
9351  "res_xpos",
9352  table_pos,
9353  xpos_det - xpix_fit - 1);
9354  }
9355  }
9356  }
9357  }
9358 
9359  }
9360  /* } */
9361 
9362  /*
9363  * Write it anyway, even in case a first-guess based
9364  * solution will be searched afterwards: in case of
9365  * failure, the "blind" solution is kept.
9366  */
9367 
9368  if (nlines)
9369  nlines[i] = usedLines;
9370  if (error)
9371  error[i] = ids_err / sqrt(usedLines/(uorder + 1));
9372 
9373  for (k = 0; k <= order; k++) {
9374  if (k > uorder) {
9375  cpl_table_set_double(idscoeff, clab[k], i, 0.0);
9376  }
9377  else {
9378  cpl_table_set_double(idscoeff, clab[k], i,
9379  cpl_polynomial_get_coeff(ids, &k));
9380  }
9381  }
9382 
9383  cpl_polynomial_delete(ids);
9384  cpl_bivector_delete(peaks_ident);
9385  }
9386  cpl_vector_delete(peaks);
9387  }
9388  } /* End of loop on current slit's rows */
9389 
9390 
9391  if (sradius > 0) {
9392 
9393  /*
9394  * See whether there are valid fits at all...
9395  */
9396 
9397  nfits = row_top - row_bot - cpl_table_count_invalid(coeff, clab[0]);
9398 
9399  if (nfits) {
9400  int slope = 0;
9401 
9402  fguess = cpl_polynomial_new(1);
9403 
9404  if (mos_interpolate_wavecalib_mos(coeff, 2, 1)) {
9405 
9406  slope = 0;
9407 
9408  /*
9409  * Compute a median IDS polynomial for the current slit
9410  */
9411 
9412  for (k = 0; k <= order; k++) {
9413  c = cpl_table_get_column_median(coeff, clab[k]);
9414  cpl_polynomial_set_coeff(fguess, &k, c);
9415  }
9416  }
9417  else {
9418  slope = 1;
9419  }
9420 
9421  for (i = row_bot; i < row_top; i++) {
9422 
9423  /*
9424  * Use first-guess to find the reference lines again
9425  */
9426 
9427  width = mos_lines_width(sdata + i*nx, nx);
9428  if (width > sradius) {
9429  uradius = width;
9430  }
9431  else {
9432  uradius = sradius;
9433  }
9434 
9435  if (slope) {
9436  for (k = 0; k <= order; k++) {
9437  c = cpl_table_get_double(coeff, clab[k],
9438  i - row_bot, NULL);
9439  cpl_polynomial_set_coeff(fguess, &k, c);
9440  }
9441  }
9442 
9443  peaks_ident = mos_find_peaks(sdata + i*nx, nx, lines,
9444  fguess, refwave, uradius);
9445 
9446  if (peaks_ident == NULL) {
9447  cpl_error_reset();
9448  continue;
9449  }
9450 
9451  countLines = cpl_bivector_get_size(peaks_ident);
9452 
9453  if (countLines < 4) {
9454  cpl_bivector_delete(peaks_ident);
9455  continue;
9456  }
9457 
9458  /*
9459  * Set reference wavelength as zero point
9460  */
9461 
9462  wavel = cpl_bivector_get_y(peaks_ident);
9463  cpl_vector_subtract_scalar(wavel, refwave);
9464 
9465  uorder = countLines / 2 - 1;
9466  if (uorder > order)
9467  uorder = order;
9468 
9469  ids = mos_poly_wav2pix(peaks_ident, uorder, reject,
9470  2 * (uorder + 1), &usedLines,
9471  &ids_err);
9472 
9473  if (ids == NULL) {
9474  cpl_error_reset();
9475  cpl_bivector_delete(peaks_ident);
9476  continue;
9477  }
9478 
9479  if (nlines)
9480  nlines[i] = usedLines;
9481  if (error)
9482  error[i] = ids_err / sqrt(usedLines/(uorder + 1));
9483 
9484  if (calibration) {
9485  pixstart = cpl_polynomial_eval_1d(ids,
9486  cpl_bivector_get_y_data(peaks_ident)[0],
9487  NULL);
9488  pixend = cpl_polynomial_eval_1d(ids,
9489  cpl_bivector_get_y_data(peaks_ident)[countLines-1],
9490  NULL);
9491  extrapolation = (pixend - pixstart) / 5;
9492  pixstart -= extrapolation;
9493  pixend += extrapolation;
9494  if (pixstart < 0)
9495  pixstart = 0;
9496  if (pixend > nx)
9497  pixend = nx;
9498 
9499  for (j = pixstart; j < pixend; j++) {
9500  (idata + i*nx)[j] = mos_eval_dds(ids,
9501  firstLambda, lastLambda, refwave, j);
9502  }
9503  }
9504 
9505  /*
9506  * Residuals image
9507  */
9508 
9509  if (residuals || (restable && !(i%step))) {
9510  if (restable && !(i%step)) {
9511  lin = cpl_polynomial_new(1);
9512  for (k = 0; k < 2; k++)
9513  cpl_polynomial_set_coeff(lin, &k,
9514  cpl_polynomial_get_coeff(ids, &k));
9515  }
9516  for (j = 0; j < countLines; j++) {
9517  pixe = cpl_bivector_get_x_data(peaks_ident)[j];
9518  wave = cpl_bivector_get_y_data(peaks_ident)[j];
9519  value = pixe
9520  - cpl_polynomial_eval_1d(ids, wave, NULL);
9521  if (residuals) {
9522  pixel = pixe + 0.5;
9523  (ddata + i*nx)[pixel] = value;
9524  }
9525  if (restable && !(i%step)) {
9526  for (k = 0; k < nref; k++) {
9527  if (fabs(line[k]-refwave-wave) < 0.1) {
9528  snprintf(name, MAX_COLNAME,
9529  "r%d", i);
9530  cpl_table_set_double(restable, name,
9531  k, value);
9532  value = pixe
9533  - cpl_polynomial_eval_1d(lin,
9534  wave, NULL);
9535  snprintf(name, MAX_COLNAME,
9536  "d%d", i);
9537  cpl_table_set_double(restable, name,
9538  k, value);
9539  snprintf(name, MAX_COLNAME,
9540  "p%d", i);
9541  cpl_table_set_double(restable, name,
9542  k, pixe);
9543  break;
9544  }
9545  }
9546  }
9547  }
9548  if (restable && !(i%step)) {
9549  cpl_polynomial_delete(lin);
9550  }
9551 /***
9552  for (j = 0; j < countLines; j++) {
9553  pixel = cpl_bivector_get_x_data(output)[j]
9554  + 0.5;
9555  (ddata + i*nx)[pixel] =
9556  cpl_bivector_get_x_data(output)[j]
9557  - cpl_polynomial_eval_1d(ids,
9558  cpl_bivector_get_y_data(output)[j],
9559  NULL);
9560  }
9561 ***/
9562  }
9563 
9564  if(detected_lines)
9565  {
9566  cpl_size oldsize = cpl_table_get_nrow(detected_lines);
9567  cpl_size nidentlines = cpl_bivector_get_size(peaks_ident);
9568  cpl_table_set_size(detected_lines, oldsize + nidentlines);
9569  for(cpl_size idline = 0; idline < nidentlines ; ++idline)
9570  {
9571  double wave_ident = cpl_bivector_get_y_data(peaks_ident)[idline] + refwave;
9572  double xpix_fit = cpl_polynomial_eval_1d(ids,
9573  wave_ident - refwave, NULL);
9574  cpl_table_set_int(detected_lines, "slit_id",
9575  oldsize + idline, slit_id);
9576  cpl_table_set_double(detected_lines, "xpos_rectified_iter",
9577  oldsize + idline, cpl_bivector_get_x_data(peaks_ident)[idline] + 1);
9578  cpl_table_set_double(detected_lines, "ypos_rectified_iter",
9579  oldsize + idline, (double)i + 1);
9580  cpl_table_set_double(detected_lines, "peak_flux",
9581  oldsize + idline,
9582  sdata[i*nx+(int)(cpl_bivector_get_x_data(peaks_ident)[idline]+0.5)]);
9583  cpl_table_set_double(detected_lines, "wave_ident_iter",
9584  oldsize + idline, wave_ident);
9585  cpl_table_set_double(detected_lines, "xpos_fit_rect_wavecal",
9586  oldsize + idline, xpix_fit + 1);
9587  }
9588  }
9589 
9590 
9591  for (k = 0; k <= order; k++) {
9592  if (k > uorder) {
9593  cpl_table_set_double(idscoeff, clab[k], i, 0.0);
9594  }
9595  else {
9596  cpl_table_set_double(idscoeff, clab[k], i,
9597  cpl_polynomial_get_coeff(ids, &k));
9598  }
9599  }
9600 
9601  cpl_bivector_delete(peaks_ident);
9602  cpl_polynomial_delete(ids);
9603 
9604  } /* End of loop "use ids as a first-guess" */
9605 
9606  cpl_polynomial_delete(fguess);
9607  }
9608 
9609  cpl_table_delete(coeff);
9610 
9611  }
9612 
9613  row_top = row_bot;
9614 
9615  } /* End of loop on slits */
9616 
9617 
9618  /*
9619  * At this point the idscoeff table has been filled with all the
9620  * fits coefficients obtained for all the rows of the input image.
9621  * Now we apply these coefficients to resample the input image
9622  * at constant wavelength step.
9623  */
9624 
9625  for (i = 0; i < ny; i++) {
9626 
9627  missing = 0;
9628  ids = cpl_polynomial_new(1);
9629  for (k = 0; k <= order; k++) {
9630  c = cpl_table_get_double(idscoeff, clab[k], i, &null);
9631  if (null) {
9632  cpl_polynomial_delete(ids);
9633  missing = 1;
9634  break;
9635  }
9636  cpl_polynomial_set_coeff(ids, &k, c);
9637  }
9638  if (missing)
9639  continue;
9640 
9641  pixstart = cpl_polynomial_eval_1d(ids, firstLambda - refwave, NULL);
9642  pixend = cpl_polynomial_eval_1d(ids, lastLambda - refwave, NULL);
9643  if (pixstart < 0)
9644  pixstart = 0;
9645  if (pixend > nx)
9646  pixend = nx;
9647 
9648  /*
9649  * Resampled image:
9650  */
9651 
9652  for (j = 0; j < nl; j++) {
9653  lambda = firstLambda + j * dispersion;
9654  fpixel = cpl_polynomial_eval_1d(ids, lambda - refwave, NULL);
9655  pixel = fpixel;
9656  if (pixel >= 0 && pixel < nx-1) {
9657  v1 = (sdata + i*nx)[pixel];
9658  v2 = (sdata + i*nx)[pixel+1];
9659  vi = v1 + (v2-v1)*(fpixel-pixel);
9660  (rdata + i*nl)[j] = vi;
9661  }
9662  }
9663 
9664  cpl_polynomial_delete(ids);
9665  }
9666 
9667  return resampled;
9668 }
9669 
9670 
9697 cpl_image *mos_wavelength_calibration(cpl_image *image, double refwave,
9698  double firstLambda, double lastLambda,
9699  double dispersion, cpl_table *idscoeff,
9700  int flux)
9701 {
9702 
9703  const char *func = "mos_wavelength_calibration";
9704 
9705  const char *clab[6] = {"c0", "c1", "c2", "c3", "c4", "c5"};
9706  /* Max order is 5 */
9707 
9708  cpl_image *resampled;
9709  cpl_polynomial *ids;
9710  double pixel_per_lambda;
9711  double lambda;
9712  double c;
9713  float *sdata;
9714  float *rdata;
9715  float v0, v1, v2, v3, vi;
9716  float fpixel;
9717  int order;
9718  int pixstart, pixend;
9719  int nl, nx, ny, pixel;
9720  int missing;
9721  int null;
9722  int i, j;
9723  cpl_size k;
9724 
9725 
9726  if (dispersion <= 0.0) {
9727  cpl_msg_error(func, "The resampling step must be positive");
9728  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
9729  return NULL;
9730  }
9731 
9732  if (lastLambda - firstLambda < dispersion) {
9733  cpl_msg_error(func, "Invalid spectral range: %.2f to %.2f",
9734  firstLambda, lastLambda);
9735  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
9736  return NULL;
9737  }
9738 
9739  if (idscoeff == NULL) {
9740  cpl_msg_error(func, "An IDS coeff table must be given");
9741  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
9742  return NULL;
9743  }
9744 
9745  if (image == NULL) {
9746  cpl_msg_error(func, "A scientific spectral image must be given");
9747  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
9748  return NULL;
9749  }
9750 
9751  nx = cpl_image_get_size_x(image);
9752  ny = cpl_image_get_size_y(image);
9753  sdata = cpl_image_get_data_float(image);
9754 
9755  nl = (lastLambda - firstLambda) / dispersion;
9756  resampled = cpl_image_new(nl, ny, CPL_TYPE_FLOAT);
9757  rdata = cpl_image_get_data_float(resampled);
9758 
9759  order = 0;
9760  while (order < 6 && cpl_table_has_column(idscoeff, clab[order]))
9761  ++order;
9762  --order;
9763 
9764  for (i = 0; i < ny; i++) {
9765 
9766  missing = 0;
9767  ids = cpl_polynomial_new(1);
9768  for (k = 0; k <= order; k++) {
9769  c = cpl_table_get_double(idscoeff, clab[k], i, &null);
9770  if (null) {
9771  cpl_polynomial_delete(ids);
9772  missing = 1;
9773  break;
9774  }
9775  cpl_polynomial_set_coeff(ids, &k, c);
9776  }
9777  if (missing)
9778  continue;
9779 
9780  pixstart = cpl_polynomial_eval_1d(ids, firstLambda - refwave, NULL);
9781  pixend = cpl_polynomial_eval_1d(ids, lastLambda - refwave, NULL);
9782  if (pixstart < 0)
9783  pixstart = 0;
9784  if (pixend > nx)
9785  pixend = nx;
9786 
9787  /*
9788  * Resampled image:
9789  */
9790 
9791  for (j = 0; j < nl; j++) {
9792  lambda = firstLambda + j * dispersion;
9793  fpixel = cpl_polynomial_eval_1d(ids, lambda - refwave,
9794  &pixel_per_lambda);
9795 
9796  /*
9797  * The local dispersion is 1 / pixel_per_lambda
9798  * and this factor is used for applying the flux
9799  * conservation correction (if requested).
9800  */
9801 
9802  pixel = fpixel;
9803 
9804  // if (dispersion * pixel_per_lambda < 2.0) {
9805  if (1) { /* Old behaviour: this is safe. */
9806 
9807  /*
9808  * In this case we just sample interpolating the
9809  * signal of nearby pixels.
9810  */
9811 
9812  //the wave calibration should be a monotonically increasing
9813  //function. If the detivative is negative, we are outside of
9814  //the wavelength solution domain.
9815  if(pixel_per_lambda <= 0)
9816  vi = 0;
9817  else if (fpixel < 0)
9818  vi = 0;
9819  else if (pixel >= 1 && pixel < nx-2) {
9820  v0 = (sdata + i*nx)[pixel-1];
9821  v1 = (sdata + i*nx)[pixel];
9822  v2 = (sdata + i*nx)[pixel+1];
9823  v3 = (sdata + i*nx)[pixel+2];
9824  vi = (fpixel-pixel)*(fpixel-pixel)*(v3 - v2 - v1 + v0)
9825  + (fpixel-pixel)*(3*v2 - v3 - v1 - v0)
9826  + 2*v1;
9827  vi /= 2;
9828  if (v1 > v2) {
9829  if (vi > v1) {
9830  vi = v1;
9831  }
9832  else if (vi < v2) {
9833  vi = v2;
9834  }
9835  }
9836  else {
9837  if (vi > v2) {
9838  vi = v2;
9839  }
9840  else if (vi < v1) {
9841  vi = v1;
9842  }
9843  }
9844  if (flux)
9845  vi *= dispersion * pixel_per_lambda;
9846  }
9847  else if (pixel >= 0 && pixel < nx-1) {
9848  v1 = (sdata + i*nx)[pixel];
9849  v2 = (sdata + i*nx)[pixel+1];
9850  vi = v1 + (v2-v1)*(fpixel-pixel);
9851  if (flux)
9852  vi *= dispersion * pixel_per_lambda;
9853  }
9854  else
9855  vi = 0;
9856  (rdata + i*nl)[j] = vi;
9857  }
9858  else {
9859 
9860  /*
9861  * Here instead we integrate the pixel values in
9862  * the interval centered at the interpolation point.
9863  * This interval is long dispersion * pixel_per_lambda
9864  * of the original pixels, and is centered at fpixel.
9865  * So it starts at fpixel - dispersion * pixel_per_lambda / 2,
9866  * and it ends at fpixel + dispersion * pixel_per_lambda / 2.
9867  */
9868 
9869  double spos = fpixel - dispersion * pixel_per_lambda / 2;
9870  double epos = fpixel + dispersion * pixel_per_lambda / 2;
9871 
9872  /*
9873  * Brutal sum over all involved pixels
9874  */
9875 
9876  int spix = spos;
9877  int epix = epos + 1;
9878 
9879  if (spix < 0)
9880  spix = 0;
9881 
9882  if (epix > nx)
9883  epix = nx;
9884 
9885  vi = 0.0;
9886  for (k = spix; k < epix; k++) {
9887  if (pixel >= 0 && pixel < nx) {
9888  vi += (sdata + i*nx)[k];
9889  }
9890  }
9891 
9892  /*
9893  * Correct integrated flux by true length
9894  * of interval. This is clearly an approximation,
9895  * but it's good enough if the PSF is much larger
9896  * than the original pix.
9897  */
9898 
9899  vi *= dispersion * pixel_per_lambda / (epix - spix);
9900 
9901  /*
9902  * Flux conservation is a geometric factor that is applied
9903  * always in the same way...
9904  */
9905 
9906  if (flux)
9907  vi *= dispersion * pixel_per_lambda;
9908 
9909  (rdata + i*nl)[j] = vi;
9910  }
9911  }
9912 
9913  cpl_polynomial_delete(ids);
9914  }
9915 
9916  return resampled;
9917 }
9918 
9919 
9986 cpl_table *mos_wavelength_align(cpl_image *image, cpl_table *slits,
9987  double refwave, double firstLambda,
9988  double lastLambda, cpl_table *idscoeff,
9989  cpl_vector *skylines, int highres, int order,
9990  cpl_image *calibration, int sradius)
9991 {
9992  const char *func = "mos_wavelength_align";
9993 
9994  const char *clab[6] = {"c0", "c1", "c2", "c3", "c4", "c5"};
9995  /* Max order is 5 */
9996  double *line;
9997  double *data;
9998  double expPos, offset;
9999  double c;
10000  double lambda1, lambda2;
10001  double rms;
10002  float pos;
10003  float *sdata;
10004  float *cdata;
10005  int *idata;
10006  int startPos, endPos;
10007  int window = 2*sradius + 1;
10008  int nlines;
10009  int nslits;
10010  int npoints;
10011  int nrows;
10012  int nx, ny;
10013  int xlow, ylow, xhig, yhig;
10014  int idsorder, uorder;
10015  int *slit_id;
10016  int *position;
10017  int *length;
10018  int missing;
10019  int null;
10020  int i;
10021  cpl_size j, k;
10022 
10023  char offname[MAX_COLNAME];
10024  char name[MAX_COLNAME];
10025 
10026  cpl_polynomial *ids;
10027  cpl_polynomial *polycorr;
10028  cpl_image *exslit;
10029  cpl_image *sky;
10030  cpl_table *offsets;
10031  cpl_table *dummy;
10032  cpl_vector *wave;
10033  cpl_vector *offs;
10034 
10035 
10036  if (idscoeff == NULL) {
10037  cpl_msg_error(func, "An IDS coeff table must be given");
10038  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
10039  return NULL;
10040  }
10041 
10042  if (image == NULL) {
10043  cpl_msg_error(func, "A scientific spectral image must be given");
10044  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
10045  return NULL;
10046  }
10047 
10048  if (slits == NULL) {
10049  cpl_msg_error(func, "A slit position table must be given");
10050  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
10051  return NULL;
10052  }
10053 
10054  if (skylines) {
10055  line = cpl_vector_get_data(skylines);
10056  nlines = cpl_vector_get_size(skylines);
10057  }
10058  else {
10059  cpl_msg_warning(func, "A catalog of sky lines wavelengths was not "
10060  "given: using internal list of reference sky lines");
10061  if (highres) {
10062  line = default_lines_hi;
10063  nlines = sizeof(default_lines_hi) / sizeof(double);
10064  }
10065  else {
10066  line = default_lines_lo;
10067  nlines = sizeof(default_lines_lo) / sizeof(double);
10068  }
10069  }
10070 
10071  if (calibration)
10072  cdata = cpl_image_get_data(calibration);
10073 
10074  nx = cpl_image_get_size_x(image);
10075  ny = cpl_image_get_size_y(image);
10076 
10077  nslits = cpl_table_get_nrow(slits);
10078  slit_id = cpl_table_get_data_int(slits, "slit_id");
10079  position = cpl_table_get_data_int(slits, "position");
10080  length = cpl_table_get_data_int(slits, "length");
10081 
10082 
10083  /*
10084  * Define the output table of offsets
10085  */
10086 
10087  nrows = 0;
10088  for (i = 0; i < nlines; i++)
10089  if (line[i] > firstLambda && line[i] < lastLambda)
10090  nrows++;
10091 
10092  offsets = cpl_table_new(nrows);
10093  cpl_table_new_column(offsets, "wave", CPL_TYPE_DOUBLE);
10094  cpl_table_set_column_unit(offsets, "wave", "Angstrom");
10095 
10096  nrows = 0;
10097  for (i = 0; i < nlines; i++) {
10098  if (line[i] > firstLambda && line[i] < lastLambda) {
10099  cpl_table_set_double(offsets, "wave", nrows, line[i]);
10100  nrows++;
10101  }
10102  }
10103 
10104  /*
10105  * Here "line" is made to point to the new list of selected wavelengths
10106  */
10107 
10108  line = cpl_table_get_data_double(offsets, "wave");
10109  nlines = nrows;
10110 
10111  idsorder = 0;
10112  while (idsorder < 6 && cpl_table_has_column(idscoeff, clab[idsorder]))
10113  ++idsorder;
10114  --idsorder;
10115 
10116  xlow = 1;
10117  xhig = nx;
10118  for (i = 0; i < nslits; i++) {
10119 
10120  if (length[i] == 0)
10121  continue;
10122 
10123  snprintf(offname, MAX_COLNAME, "offset%d", slit_id[i]);
10124  cpl_table_new_column(offsets, offname, CPL_TYPE_DOUBLE);
10125 
10126  /*
10127  * Define the extraction boundaries. We DON'T write:
10128  *
10129  * ylow = position[i];
10130  * yhig = ylow + length[i];
10131  *
10132  * because the cpl_image pixels are counted from 1, and because in
10133  * cpl_image_extract() the coordinates of the last pixel are inclusive.
10134  */
10135 
10136  ylow = position[i] + 1;
10137  yhig = ylow + length[i] - 1;
10138 
10139  exslit = cpl_image_extract(image, xlow, ylow, xhig, yhig);
10140  sky = cpl_image_collapse_median_create(exslit, 0, 0, 1);
10141  sdata = cpl_image_get_data(sky);
10142 
10143  cpl_image_delete(exslit);
10144 
10145  /*
10146  * Return here to a decent way of counting pixels (i.e., starting
10147  * from 0)
10148  */
10149 
10150  ylow--;
10151 
10152  /*
10153  * Allocate a dummy table for collecting all the offsets
10154  * for all the lines: this is only needed for the computation
10155  * of the median offset for each sky line
10156  */
10157 
10158  dummy = cpl_table_new(yhig - ylow);
10159  for (j = 0; j < nlines; j++) {
10160  snprintf(name, MAX_COLNAME, "%"CPL_SIZE_FORMAT, j);
10161  cpl_table_new_column(dummy, name, CPL_TYPE_DOUBLE);
10162  }
10163 
10164  for (j = ylow; j < yhig; j++) {
10165 
10166  /*
10167  * Get the IDS polynomial for the current slit row
10168  */
10169 
10170  missing = 0;
10171  ids = cpl_polynomial_new(1);
10172  for (k = 0; k <= idsorder; k++) {
10173  c = cpl_table_get_double(idscoeff, clab[k], j, &null);
10174  if (null) {
10175  cpl_polynomial_delete(ids);
10176  missing = 1;
10177  break;
10178  }
10179  cpl_polynomial_set_coeff(ids, &k, c);
10180  }
10181  if (missing)
10182  continue;
10183 
10184  for (k = 0; k < nlines; k++) {
10185  expPos = cpl_polynomial_eval_1d(ids, line[k] - refwave, NULL);
10186  startPos = expPos - sradius;
10187  endPos = startPos + window;
10188  if (startPos < 0 || endPos >= nx)
10189  continue;
10190 
10191  if (0 == peakPosition(sdata + startPos, window, &pos, 1)) {
10192  pos += startPos;
10193  offset = pos - expPos;
10194  snprintf(name, MAX_COLNAME, "%"CPL_SIZE_FORMAT, k);
10195  cpl_table_set_double(dummy, name, j - ylow, offset);
10196  }
10197  }
10198 
10199  cpl_polynomial_delete(ids);
10200  }
10201 
10202  cpl_image_delete(sky);
10203 
10204  for (j = 0; j < nlines; j++) {
10205  snprintf(name, MAX_COLNAME, "%"CPL_SIZE_FORMAT, j);
10206  if (cpl_table_has_valid(dummy, name)) {
10207  offset = cpl_table_get_column_median(dummy, name);
10208  cpl_table_set_double(offsets, offname, j, offset);
10209  }
10210  }
10211 
10212  cpl_table_delete(dummy);
10213 
10214  }
10215 
10216 
10217  /*
10218  * In the following the input idscoeff table is modified by simply
10219  * adding the coefficients of the polynomial used to fit the sky
10220  * line residuals to the coefficients of the IDS polynomials.
10221  */
10222 
10223  for (i = 0; i < nslits; i++) {
10224 
10225  if (length[i] == 0)
10226  continue;
10227 
10228  snprintf(offname, MAX_COLNAME, "offset%d", slit_id[i]);
10229 
10230  /*
10231  * In the following, the "dummy" table is just a tool for
10232  * eliminating invalid points from the vectors to be fitted.
10233  */
10234 
10235  dummy = cpl_table_new(nlines);
10236  cpl_table_duplicate_column(dummy, "wave", offsets, "wave");
10237  cpl_table_duplicate_column(dummy, "offset", offsets, offname);
10238 
10239  npoints = nlines - cpl_table_count_invalid(dummy, "offset");
10240  if (npoints == 0) {
10241  cpl_msg_warning(func, "No sky lines alignment was possible "
10242  "for slit ID=%d: no sky line found", slit_id[i]);
10243  cpl_table_delete(dummy);
10244  continue;
10245  }
10246 
10247  uorder = order;
10248  if (npoints <= uorder) {
10249  uorder = npoints - 1;
10250  if (uorder) {
10251  cpl_msg_warning(func, "Just %d sky lines detected for slit "
10252  "ID=%d, while a polynomial order %d was "
10253  "requested. Using polynomial order %d for "
10254  "this slit!", npoints, slit_id[i], order,
10255  uorder);
10256  }
10257  else {
10258  cpl_msg_warning(func, "Just %d sky lines detected for slit "
10259  "ID=%d, while a polynomial order %d was "
10260  "requested. Computing a median offset for "
10261  "this slit!", npoints, slit_id[i], order);
10262  }
10263  }
10264 
10265  cpl_table_erase_invalid(dummy);
10266 
10267  if (uorder > 1) {
10268 
10269  /*
10270  * Model offsets with polynomial fitting
10271  */
10272 
10273  wave = cpl_vector_wrap(npoints,
10274  cpl_table_get_data_double(dummy, "wave"));
10275  offs = cpl_vector_wrap(npoints,
10276  cpl_table_get_data_double(dummy, "offset"));
10277 
10278  /*
10279  * Set reference wavelength as zero point
10280  */
10281 
10282  cpl_vector_subtract_scalar(wave, refwave);
10283 
10284  polycorr = cpl_polynomial_fit_1d_create(wave, offs, uorder, &rms);
10285 
10286  rms = sqrt(rms * (uorder + 1) / npoints);
10287 
10288  cpl_vector_unwrap(wave);
10289  cpl_vector_unwrap(offs);
10290  cpl_table_delete(dummy);
10291 
10292  /*
10293  * Now correct the coefficients of the corresponding IDS
10294  * polynomials related to this slit:
10295  */
10296 
10297  ylow = position[i];
10298  yhig = ylow + length[i];
10299 
10300  for (j = 0; j <= uorder; j++) {
10301  data = cpl_table_get_data_double(idscoeff, clab[j]);
10302  c = cpl_polynomial_get_coeff(polycorr, &j);
10303  for (k = ylow; k < yhig; k++)
10304  data[k] += c;
10305  }
10306 
10307  data = cpl_table_get_data_double(idscoeff, "error");
10308  for (k = ylow; k < yhig; k++)
10309  data[k] = sqrt(data[k]*data[k] + rms*rms);
10310 
10311  idata = cpl_table_get_data_int(idscoeff, "nlines");
10312  for (k = ylow; k < yhig; k++)
10313  idata[k] = npoints;
10314 
10315  /*
10316  * If a wavelengths map was provided, correct it to keep
10317  * into account the alignment to skylines:
10318  */
10319 
10320  if (calibration) {
10321  for (j = ylow; j < yhig; j++) {
10322  for (k = 1; k < nx; k++) {
10323  lambda1 = cdata[k - 1 + j*nx];
10324  lambda2 = cdata[k + j*nx];
10325  if (lambda1 < 1.0 || lambda2 < 1.0)
10326  continue;
10327  offset = cpl_polynomial_eval_1d(polycorr,
10328  lambda1-refwave, NULL);
10329  cdata[k - 1 + j*nx] -= offset * (lambda2-lambda1);
10330  }
10331  }
10332  }
10333 
10334  cpl_polynomial_delete(polycorr);
10335  }
10336  else if (uorder == 1) {
10337 
10338  /*
10339  * Model offsets with robust linear fitting
10340  */
10341 
10342  double q, m;
10343  cpl_bivector *list;
10344 
10345 
10346  wave = cpl_vector_wrap(npoints,
10347  cpl_table_get_data_double(dummy, "wave"));
10348  offs = cpl_vector_wrap(npoints,
10349  cpl_table_get_data_double(dummy, "offset"));
10350 
10351  list = cpl_bivector_wrap_vectors(wave, offs);
10352 
10353  /*
10354  * Set reference wavelength as zero point
10355  */
10356 
10357  cpl_vector_subtract_scalar(wave, refwave);
10358 
10359  robustLinearFit(list, &q, &m, &rms);
10360 
10361  rms = sqrt(rms * (uorder + 1) / npoints);
10362 
10363  cpl_bivector_unwrap_vectors(list);
10364  cpl_vector_unwrap(wave);
10365  cpl_vector_unwrap(offs);
10366  cpl_table_delete(dummy);
10367 
10368  /*
10369  * Now correct the coefficients of the corresponding IDS
10370  * polynomials related to this slit:
10371  */
10372 
10373  ylow = position[i];
10374  yhig = ylow + length[i];
10375 
10376  for (j = 0; j <= uorder; j++) {
10377  data = cpl_table_get_data_double(idscoeff, clab[j]);
10378  if (j)
10379  c = m;
10380  else
10381  c = q;
10382  for (k = ylow; k < yhig; k++)
10383  data[k] += c;
10384  }
10385 
10386  data = cpl_table_get_data_double(idscoeff, "error");
10387  for (k = ylow; k < yhig; k++)
10388  data[k] = sqrt(data[k]*data[k] + rms*rms);
10389 
10390  idata = cpl_table_get_data_int(idscoeff, "nlines");
10391  for (k = ylow; k < yhig; k++)
10392  idata[k] = npoints;
10393 
10394  /*
10395  * If a wavelengths map was provided, correct it to keep
10396  * into account the alignment to skylines:
10397  */
10398 
10399  if (calibration) {
10400  for (j = ylow; j < yhig; j++) {
10401  for (k = 1; k < nx; k++) {
10402  lambda1 = cdata[k - 1 + j*nx];
10403  lambda2 = cdata[k + j*nx];
10404  if (lambda1 < 1.0 || lambda2 < 1.0)
10405  continue;
10406  offset = q + m*(lambda1-refwave);
10407  cdata[k - 1 + j*nx] -= offset * (lambda2-lambda1);
10408  }
10409  }
10410  }
10411  }
10412  else {
10413 
10414  /*
10415  * Just compute median offset
10416  */
10417 
10418  offs = cpl_vector_wrap(npoints,
10419  cpl_table_get_data_double(dummy, "offset"));
10420 
10421  offset = cpl_vector_get_median_const(offs);
10422 
10423  if (npoints > 1)
10424  rms = cpl_table_get_column_stdev(dummy, "offset");
10425  else
10426  rms = 0.0;
10427 
10428  rms /= sqrt(npoints);
10429 
10430  cpl_vector_unwrap(offs);
10431  cpl_table_delete(dummy);
10432 
10433  /*
10434  * Now correct the constant term of the corresponding IDS
10435  * polynomials related to this slit:
10436  */
10437 
10438  ylow = position[i];
10439  yhig = ylow + length[i];
10440 
10441  data = cpl_table_get_data_double(idscoeff, clab[0]);
10442  for (k = ylow; k < yhig; k++)
10443  data[k] += offset;
10444 
10445  data = cpl_table_get_data_double(idscoeff, "error");
10446  for (k = ylow; k < yhig; k++)
10447  data[k] = sqrt(data[k]*data[k] + rms*rms);
10448 
10449  idata = cpl_table_get_data_int(idscoeff, "nlines");
10450  for (k = ylow; k < yhig; k++)
10451  idata[k] = npoints;
10452 
10453  /*
10454  * If a wavelengths map was provided, correct it to keep
10455  * into account the alignment to skylines. Note that
10456  * the offset must be converted from pixels to wavelengths.
10457  */
10458 
10459  if (calibration) {
10460  for (j = ylow; j < yhig; j++) {
10461  for (k = 1; k < nx; k++) {
10462  lambda1 = cdata[k - 1 + j*nx];
10463  lambda2 = cdata[k + j*nx];
10464  if (lambda1 < 1.0 || lambda2 < 1.0)
10465  continue;
10466  cdata[k - 1 + j*nx] -= offset * (lambda2-lambda1);
10467  }
10468  }
10469  }
10470  }
10471  }
10472 
10473  return offsets;
10474 
10475 }
10476 
10477 
10539 cpl_table *mos_wavelength_align_lss(cpl_image *image, double refwave,
10540  double firstLambda, double lastLambda,
10541  cpl_table *idscoeff, cpl_vector *skylines,
10542  int highres, int order,
10543  cpl_image *calibration, int sradius)
10544 {
10545  const char *func = "mos_wavelength_align_lss";
10546 
10547  const char *clab[6] = {"c0", "c1", "c2", "c3", "c4", "c5"};
10548  /* Max order is 5 */
10549  double *line;
10550  double *data;
10551  double *wdata;
10552  double *odata;
10553  double expPos, offset;
10554  double c;
10555  double lambda1, lambda2;
10556  double rms;
10557  float pos;
10558  float *sdata;
10559  float *cdata;
10560  int *idata;
10561  int startPos, endPos;
10562  int window = 2*sradius + 1;
10563  int nlines;
10564  int npoints;
10565  int nrows;
10566  int nx, ny;
10567  int idsorder, uorder;
10568  int missing;
10569  int i;
10570  cpl_size j, k;
10571 
10572  char name[MAX_COLNAME];
10573  char fname[MAX_COLNAME];
10574 
10575  cpl_polynomial *ids;
10576  cpl_polynomial *polycorr;
10577  cpl_table *offsets;
10578  cpl_table *fittable;
10579  cpl_table *dummy;
10580  cpl_vector *wave;
10581  cpl_vector *offs;
10582  cpl_vector *row;
10583 
10584 
10585  if (idscoeff == NULL) {
10586  cpl_msg_error(func, "An IDS coeff table must be given");
10587  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
10588  return NULL;
10589  }
10590 
10591  if (image == NULL) {
10592  cpl_msg_error(func, "A scientific spectral image must be given");
10593  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
10594  return NULL;
10595  }
10596 
10597  if (skylines) {
10598  line = cpl_vector_get_data(skylines);
10599  nlines = cpl_vector_get_size(skylines);
10600  }
10601  else {
10602  cpl_msg_warning(func, "A catalog of sky lines wavelengths was not "
10603  "given: using internal list of reference sky lines");
10604  if (highres) {
10605  line = default_lines_hi;
10606  nlines = sizeof(default_lines_hi) / sizeof(double);
10607  }
10608  else {
10609  line = default_lines_lo;
10610  nlines = sizeof(default_lines_lo) / sizeof(double);
10611  }
10612  }
10613 
10614  if (calibration)
10615  cdata = cpl_image_get_data(calibration);
10616 
10617  nx = cpl_image_get_size_x(image);
10618  ny = cpl_image_get_size_y(image);
10619 
10620  sdata = cpl_image_get_data(image);
10621 
10622  if (ny != cpl_table_get_nrow(idscoeff)) {
10623  cpl_error_set(func, CPL_ERROR_INCOMPATIBLE_INPUT);
10624  return NULL;
10625  }
10626 
10627 
10628  /*FIXME: This is a remnant of the adaptation of the function
10629  * mos_wavelength_align(), where an offset table was created.
10630  * I leave it here because I am in a hurry, it is just used to
10631  * hold the list of selected sky lines.
10632  *
10633  * Define table of wavelengths
10634  */
10635 
10636  nrows = 0;
10637  for (i = 0; i < nlines; i++)
10638  if (line[i] > firstLambda && line[i] < lastLambda)
10639  nrows++;
10640 
10641  offsets = cpl_table_new(nrows);
10642  cpl_table_new_column(offsets, "wave", CPL_TYPE_DOUBLE);
10643  cpl_table_set_column_unit(offsets, "wave", "Angstrom");
10644 
10645  nrows = 0;
10646  for (i = 0; i < nlines; i++) {
10647  if (line[i] > firstLambda && line[i] < lastLambda) {
10648  cpl_table_set_double(offsets, "wave", nrows, line[i]);
10649  nrows++;
10650  }
10651  }
10652 
10653  /*
10654  * Here "line" is made to point to the new list of selected wavelengths
10655  */
10656 
10657  line = cpl_table_get_data_double(offsets, "wave");
10658  nlines = nrows;
10659 
10660  idsorder = 0;
10661  while (idsorder < 6 && cpl_table_has_column(idscoeff, clab[idsorder]))
10662  ++idsorder;
10663  --idsorder;
10664 
10665 
10666  /*
10667  * Allocate a dummy table for collecting all the offsets
10668  * for all the lines
10669  */
10670 
10671  dummy = cpl_table_new(ny);
10672  for (j = 0; j < nlines; j++) {
10673  snprintf(name, MAX_COLNAME, "off_%d", (int)line[j]);
10674  snprintf(fname, MAX_COLNAME, "fit_%d", (int)line[j]);
10675  cpl_table_new_column(dummy, name, CPL_TYPE_DOUBLE);
10676  cpl_table_new_column(dummy, fname, CPL_TYPE_DOUBLE);
10677  }
10678 
10679  for (j = 0; j < ny; j++, sdata += nx) {
10680 
10681  /*
10682  * Get the IDS polynomial for the current slit row
10683  */
10684 
10685  missing = 0;
10686  ids = cpl_polynomial_new(1);
10687  for (k = 0; k <= idsorder; k++) {
10688  c = cpl_table_get_double(idscoeff, clab[k], j, &missing);
10689  if (missing) {
10690  cpl_polynomial_delete(ids);
10691  break;
10692  }
10693  cpl_polynomial_set_coeff(ids, &k, c);
10694  }
10695  if (missing)
10696  continue;
10697 
10698  for (k = 0; k < nlines; k++) {
10699  expPos = cpl_polynomial_eval_1d(ids, line[k] - refwave, NULL);
10700  startPos = expPos - sradius;
10701  endPos = startPos + window;
10702  if (startPos < 0 || endPos >= nx)
10703  continue;
10704 
10705  if (0 == peakPosition(sdata + startPos, window, &pos, 1)) {
10706  pos += startPos;
10707  offset = pos - expPos;
10708  snprintf(name, MAX_COLNAME, "off_%d", (int)line[k]);
10709  cpl_table_set_double(dummy, name, j, offset);
10710  }
10711  }
10712 
10713  cpl_polynomial_delete(ids);
10714  }
10715 
10716 
10717  /*
10718  * At this point for each sky line we model its offset along
10719  * the image rows using a robust linear fitting
10720  */
10721 
10722  for (j = 0; j < nlines; j++) {
10723  snprintf(name, MAX_COLNAME, "off_%d", (int)line[j]);
10724  snprintf(fname, MAX_COLNAME, "fit_%d", (int)line[j]);
10725  if (cpl_table_has_valid(dummy, name)) {
10726 
10727  /*
10728  * In the following, the "fittable" is just a tool for
10729  * eliminating invalid points from the vectors to be fitted.
10730  */
10731 
10732  double q, m;
10733  cpl_bivector *list;
10734 
10735  fittable = cpl_table_new(ny);
10736  cpl_table_new_column(fittable, "row", CPL_TYPE_DOUBLE);
10737  cpl_table_set_column_unit(fittable, "row", "pixel");
10738  for (k = 0; k < ny; k++)
10739  cpl_table_set_double(fittable, "row", k, k);
10740  cpl_table_duplicate_column(fittable, "offset", dummy, name);
10741  npoints = ny - cpl_table_count_invalid(fittable, "offset");
10742  cpl_table_erase_invalid(fittable);
10743  row = cpl_vector_wrap(npoints,
10744  cpl_table_get_data_double(fittable, "row"));
10745  offs = cpl_vector_wrap(npoints,
10746  cpl_table_get_data_double(fittable, "offset"));
10747  list = cpl_bivector_wrap_vectors(row, offs);
10748  robustLinearFit(list, &q, &m, &rms);
10749  cpl_bivector_unwrap_vectors(list);
10750  cpl_vector_unwrap(row);
10751  cpl_vector_unwrap(offs);
10752  cpl_table_delete(fittable);
10753  for (k = 0; k < ny; k++)
10754  cpl_table_set_double(dummy, fname, k, q + m*k);
10755  }
10756  }
10757 
10758 
10759  /*
10760  * Now each dummy table row consists of a sequence of offsets,
10761  * one for each wavelength. A table row corresponds to an image row.
10762  * We must fit a polynomial to each one of these rows, in order to
10763  * express the offsets as a function of wavelength. The obtained
10764  * polynomial coefficients are used to correct the IDS coefficients.
10765  */
10766 
10767  for (i = 0; i < ny; i++) {
10768 
10769  if (!cpl_table_is_valid(idscoeff, clab[0], i))
10770  continue;
10771 
10772  npoints = 0;
10773  for (j = 0; j < nlines; j++) {
10774  snprintf(name, MAX_COLNAME, "fit_%d", (int)line[j]);
10775  if (cpl_table_is_valid(dummy, name, i))
10776  npoints++;
10777  }
10778 
10779  if (npoints == 0)
10780  continue;
10781 
10782  uorder = order;
10783  if (npoints <= uorder)
10784  uorder = npoints - 1;
10785 
10786  if (uorder > 1) {
10787 
10788  /*
10789  * Model offsets with polynomial fitting
10790  */
10791 
10792  wave = cpl_vector_new(npoints);
10793  wdata = cpl_vector_get_data(wave);
10794  offs = cpl_vector_new(npoints);
10795  odata = cpl_vector_get_data(offs);
10796 
10797  npoints = 0;
10798  for (j = 0; j < nlines; j++) {
10799  snprintf(name, MAX_COLNAME, "fit_%d", (int)line[j]);
10800  if (cpl_table_is_valid(dummy, name, i)) {
10801  wdata[npoints] = line[j] - refwave;
10802  odata[npoints] = cpl_table_get_double(dummy, name, i, NULL);
10803  npoints++;
10804  }
10805  }
10806 
10807  polycorr = cpl_polynomial_fit_1d_create(wave, offs, uorder, &rms);
10808 
10809  rms = sqrt(rms * (uorder + 1) / npoints);
10810 
10811  cpl_vector_delete(wave);
10812  cpl_vector_delete(offs);
10813 
10814  /*
10815  * Now correct the coefficients of the corresponding IDS
10816  * polynomials related to this slit:
10817  */
10818 
10819  for (j = 0; j <= uorder; j++) {
10820  data = cpl_table_get_data_double(idscoeff, clab[j]);
10821  c = cpl_polynomial_get_coeff(polycorr, &j);
10822  data[i] += c;
10823  }
10824 
10825  data = cpl_table_get_data_double(idscoeff, "error");
10826  data[i] = sqrt(data[i]*data[i] + rms*rms);
10827 
10828  idata = cpl_table_get_data_int(idscoeff, "nlines");
10829  idata[i] = npoints;
10830 
10831  /*
10832  * If a wavelengths map was provided, correct it to keep
10833  * into account the alignment to skylines:
10834  */
10835 
10836  if (calibration) {
10837  for (k = 1; k < nx; k++) {
10838  lambda1 = cdata[k - 1 + i*nx];
10839  lambda2 = cdata[k + i*nx];
10840  if (lambda1 < 1.0 || lambda2 < 1.0)
10841  continue;
10842  offset = cpl_polynomial_eval_1d(polycorr,
10843  lambda1-refwave, NULL);
10844  cdata[k - 1 + i*nx] -= offset * (lambda2-lambda1);
10845  }
10846  }
10847 
10848  cpl_polynomial_delete(polycorr);
10849 
10850  }
10851  else if (uorder == 1) {
10852 
10853  /*
10854  * Model offsets with robust linear fitting
10855  */
10856 
10857  cpl_bivector *list;
10858  double q, m;
10859 
10860  wave = cpl_vector_new(npoints);
10861  wdata = cpl_vector_get_data(wave);
10862  offs = cpl_vector_new(npoints);
10863  odata = cpl_vector_get_data(offs);
10864 
10865  npoints = 0;
10866  for (j = 0; j < nlines; j++) {
10867  snprintf(name, MAX_COLNAME, "fit_%d", (int)line[j]);
10868  if (cpl_table_is_valid(dummy, name, i)) {
10869  wdata[npoints] = line[j] - refwave;
10870  odata[npoints] = cpl_table_get_double(dummy, name, i, NULL);
10871  npoints++;
10872  }
10873  }
10874 
10875  list = cpl_bivector_wrap_vectors(wave, offs);
10876  robustLinearFit(list, &q, &m, &rms);
10877 
10878  rms = sqrt(rms * (uorder + 1) / npoints);
10879 
10880  cpl_bivector_unwrap_vectors(list);
10881  cpl_vector_delete(wave);
10882  cpl_vector_delete(offs);
10883 
10884  /*
10885  * Now correct the coefficients of the corresponding IDS
10886  * polynomials related to this row:
10887  */
10888 
10889  for (j = 0; j <= uorder; j++) {
10890  data = cpl_table_get_data_double(idscoeff, clab[j]);
10891  if (j)
10892  c = m;
10893  else
10894  c = q;
10895  data[i] += c;
10896  }
10897 
10898  data = cpl_table_get_data_double(idscoeff, "error");
10899  data[i] = sqrt(data[i]*data[i] + rms*rms);
10900 
10901  idata = cpl_table_get_data_int(idscoeff, "nlines");
10902  idata[i] = npoints;
10903 
10904  /*
10905  * If a wavelengths map was provided, correct it to keep
10906  * into account the alignment to skylines:
10907  */
10908 
10909  if (calibration) {
10910  for (k = 1; k < nx; k++) {
10911  lambda1 = cdata[k - 1 + i*nx];
10912  lambda2 = cdata[k + i*nx];
10913  if (lambda1 < 1.0 || lambda2 < 1.0)
10914  continue;
10915  offset = q + m*(lambda1-refwave);
10916  cdata[k - 1 + i*nx] -= offset * (lambda2-lambda1);
10917  }
10918  }
10919  }
10920  else {
10921 
10922  /*
10923  * Just compute median offset
10924  */
10925 
10926  offs = cpl_vector_new(npoints);
10927  odata = cpl_vector_get_data(offs);
10928 
10929  npoints = 0;
10930  for (j = 0; j < nlines; j++) {
10931  snprintf(name, MAX_COLNAME, "fit_%d", (int)line[j]);
10932  if (cpl_table_is_valid(dummy, name, i)) {
10933  odata[npoints] = cpl_table_get_double(dummy, name, i, NULL);
10934  npoints++;
10935  }
10936  }
10937 
10938  offset = cpl_vector_get_median_const(offs);
10939 
10940  if (npoints > 1) {
10941  rms = cpl_vector_get_stdev(offs);
10942  }
10943  else if (npoints == 1) {
10944  snprintf(name, MAX_COLNAME, "off_%d", (int)line[0]);
10945  if (cpl_table_has_valid(dummy, name)) {
10946  rms = cpl_table_get_column_stdev(dummy, name);
10947  rms /= sqrt(ny - cpl_table_count_invalid(dummy, name));
10948  }
10949  else {
10950  rms = 0.0;
10951  }
10952  }
10953  else {
10954  rms = 0.0;
10955  }
10956 
10957  rms /= sqrt(npoints);
10958 
10959  cpl_vector_delete(offs);
10960 
10961  /*
10962  * Now correct the constant term of the corresponding IDS
10963  * polynomials related to this slit:
10964  */
10965 
10966  data = cpl_table_get_data_double(idscoeff, clab[0]);
10967  data[i] += offset;
10968 
10969  data = cpl_table_get_data_double(idscoeff, "error");
10970  data[i] = sqrt(data[i]*data[i] + rms*rms);
10971 
10972  idata = cpl_table_get_data_int(idscoeff, "nlines");
10973  idata[i] = npoints;
10974 
10975  /*
10976  * If a wavelengths map was provided, correct it to keep
10977  * into account the alignment to skylines. Note that
10978  * the offset must be converted from pixels to wavelengths.
10979  */
10980 
10981  if (calibration) {
10982  for (k = 1; k < nx; k++) {
10983  lambda1 = cdata[k - 1 + i*nx];
10984  lambda2 = cdata[k + i*nx];
10985  if (lambda1 < 1.0 || lambda2 < 1.0)
10986  continue;
10987  cdata[k - 1 + i*nx] -= offset * (lambda2-lambda1);
10988  }
10989  }
10990  }
10991  }
10992 
10993  missing = 1;
10994  for (j = 0; j < nlines; j++) {
10995  snprintf(name, MAX_COLNAME, "off_%d", (int)line[j]);
10996  if (cpl_table_has_valid(dummy, name)) {
10997  missing = 0;
10998  offset = cpl_table_get_column_median(dummy, name);
10999  cpl_msg_info(func, "Median offset for %.3f: %.3f pixel",
11000  line[j], offset);
11001  }
11002  else {
11003  cpl_msg_info(func,
11004  "Median offset for %.2f: not available", line[j]);
11005  }
11006  }
11007 
11008  cpl_table_delete(offsets);
11009 
11010  if (missing) {
11011  cpl_table_delete(dummy);
11012  dummy = NULL;
11013  }
11014 
11015  return dummy;
11016 
11017 }
11018 
11019 
11047 double mos_distortions_rms(cpl_image *rectified, cpl_vector *lines,
11048  double wavestart, double dispersion, int radius,
11049  int highres)
11050 {
11051 
11052  const char *func = "mos_distortions_rms";
11053 
11054  int xlen;
11055  int ylen;
11056  int numLines;
11057  int cpix, npix, nzero;
11058  int sp, ep;
11059  int i, j, k;
11060  int npeaks, allPeaks;
11061 
11062  float *profile;
11063  float peak, expectPeak, offset;
11064  double lambda;
11065 
11066  double average;
11067  double rms, oneRms;
11068 
11069  float *sdata;
11070  double *wdata;
11071 
11072 
11073  xlen = cpl_image_get_size_x(rectified);
11074  ylen = cpl_image_get_size_y(rectified);
11075  sdata = cpl_image_get_data(rectified);
11076 
11077  if (lines) {
11078  wdata = cpl_vector_get_data(lines);
11079  numLines = cpl_vector_get_size(lines);
11080  }
11081  else {
11082  cpl_msg_warning(func, "A catalog of sky lines wavelengths was not "
11083  "given: using internal list of reference sky lines");
11084  if (highres) {
11085  wdata = default_lines_hi;
11086  numLines = sizeof(default_lines_hi) / sizeof(double);
11087  }
11088  else {
11089  wdata = default_lines_lo;
11090  numLines = sizeof(default_lines_lo) / sizeof(double);
11091  }
11092  }
11093 
11094  npix = 2 * radius + 1;
11095  profile = cpl_calloc(npix, sizeof(float));
11096 
11097  rms = 0.0;
11098  allPeaks = 0;
11099 
11100  for (i = 0; i < numLines; i++) {
11101 
11102  /*
11103  * Expected peak and closest pixel to specified wavelength.
11104  */
11105 
11106  lambda = wdata[i];
11107  expectPeak = (lambda - wavestart) / dispersion;
11108  cpix = floor(expectPeak + 0.5);
11109 
11110  /*
11111  * Search interval for peak. Abort if too close to image border.
11112  */
11113 
11114  sp = cpix - radius;
11115  ep = cpix + radius;
11116 
11117  if (sp < 0 || ep > xlen)
11118  continue;
11119 
11120  average = 0.0;
11121  npeaks = 0;
11122  oneRms = 0.0;
11123 
11124  for (j = 0; j < ylen; j++) { /* For each row of each slit */
11125  nzero = 0;
11126  for (k = 0; k < npix; k++) {
11127  profile[k] = sdata[sp + k + j * xlen];
11128  if (fabs(profile[k]) < 0.0001)
11129  nzero++; /* Count number of 0 pixels (spectrum truncated) */
11130  }
11131  if (nzero > 0)
11132  continue;
11133 
11134  if (peakPosition(profile, npix, &peak, 1) == 0) {
11135  offset = (sp + peak) - expectPeak;
11136  average += offset;
11137  rms += fabs(offset);
11138  oneRms += fabs(offset);
11139  npeaks++;
11140  allPeaks++;
11141  }
11142  }
11143 
11144  if (npeaks)
11145  cpl_msg_info(func, "RMS for %.2f: %.3f pixel (%d points)",
11146  lambda, oneRms / npeaks * 1.25, npeaks);
11147  else
11148  cpl_msg_info(func, "RMS for %.2f: line not available", lambda);
11149  }
11150 
11151  cpl_free(profile);
11152 
11153  if (allPeaks < 10)
11154  return 0.0;
11155 
11156  rms /= allPeaks;
11157  rms *= 1.25; /* Factor to convert average deviation to sigma */
11158 
11159  return rms;
11160 
11161 }
11162 
11163 
11184 cpl_image *mos_map_pixel(cpl_table *idscoeff, double reference,
11185  double blue, double red, double dispersion, int trend)
11186 {
11187  const char *func = "mos_map_pixel";
11188 
11189  const char *clab[6] = {"c0", "c1", "c2", "c3", "c4", "c5"};
11190  /* Max order is 5 */
11191 
11192  cpl_polynomial *ids;
11193  cpl_image *map;
11194  float *mdata;
11195  double lambda;
11196  double c;
11197  int order;
11198  int xsize, ysize;
11199  int missing;
11200  int i, j;
11201  cpl_size k;
11202 
11203 
11204  if (idscoeff == NULL) {
11205  cpl_msg_error(func, "An IDS coeff table must be given");
11206  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
11207  return NULL;
11208  }
11209 
11210  xsize = (red - blue) / dispersion;
11211  ysize = cpl_table_get_nrow(idscoeff);
11212  map = cpl_image_new(xsize, ysize, CPL_TYPE_FLOAT);
11213  mdata = cpl_image_get_data(map);
11214 
11215  order = 0;
11216  while (order < 6 && cpl_table_has_column(idscoeff, clab[order]))
11217  ++order;
11218  --order;
11219 
11220  for (i = 0; i < ysize; i++, mdata += xsize) {
11221 
11222  missing = 0;
11223  ids = cpl_polynomial_new(1);
11224  for (k = trend; k <= order; k++) {
11225  c = cpl_table_get_double(idscoeff, clab[k], i, &missing);
11226  if (missing) {
11227  cpl_polynomial_delete(ids);
11228  break;
11229  }
11230  cpl_polynomial_set_coeff(ids, &k, c);
11231  }
11232  if (missing)
11233  continue;
11234 
11235  for (j = 0; j < xsize; j++) {
11236  lambda = blue + j*dispersion;
11237  mdata[j] = cpl_polynomial_eval_1d(ids, lambda-reference, NULL);
11238  }
11239 
11240  cpl_polynomial_delete(ids);
11241  }
11242 
11243  return map;
11244 
11245 }
11246 
11247 
11269 cpl_image *mos_map_idscoeff(cpl_table *idscoeff, int xsize, double reference,
11270  double blue, double red)
11271 {
11272  const char *func = "mos_map_idscoeff";
11273 
11274  const char *clab[6] = {"c0", "c1", "c2", "c3", "c4", "c5"};
11275  /* Max order is 5 */
11276 
11277  cpl_polynomial *ids;
11278  cpl_image *map;
11279  float *mdata;
11280  double lambda;
11281  double c;
11282  int order;
11283  int ysize;
11284  int missing;
11285  int i, j;
11286  cpl_size k;
11287 
11288 
11289  if (idscoeff == NULL) {
11290  cpl_msg_error(func, "An IDS coeff table must be given");
11291  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
11292  return NULL;
11293  }
11294 
11295  if (xsize < 1) {
11296  cpl_msg_error(func, "Invalid image size");
11297  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
11298  return NULL;
11299  }
11300 
11301  if (xsize < 20 || xsize > 5000) {
11302  cpl_msg_warning(func, "Do you really have a detector %d pixels long?",
11303  xsize);
11304  }
11305 
11306  ysize = cpl_table_get_nrow(idscoeff);
11307  map = cpl_image_new(xsize, ysize, CPL_TYPE_FLOAT);
11308  mdata = cpl_image_get_data(map);
11309 
11310  order = 0;
11311  while (order < 6 && cpl_table_has_column(idscoeff, clab[order]))
11312  ++order;
11313  --order;
11314 
11315  for (i = 0; i < ysize; i++, mdata += xsize) {
11316 
11317  missing = 0;
11318  ids = cpl_polynomial_new(1);
11319  for (k = 0; k <= order; k++) {
11320  c = cpl_table_get_double(idscoeff, clab[k], i, &missing);
11321  if (missing) {
11322  cpl_polynomial_delete(ids);
11323  break;
11324  }
11325  cpl_polynomial_set_coeff(ids, &k, c);
11326  }
11327  if (missing)
11328  continue;
11329 
11330  for (j = 0; j < xsize; j++) {
11331  lambda = mos_eval_dds(ids, blue, red, reference, j);
11332 
11333  if (lambda >= blue && lambda <= red) {
11334  mdata[j] = lambda;
11335  }
11336  }
11337 
11338  cpl_polynomial_delete(ids);
11339  }
11340 
11341  return map;
11342 
11343 }
11344 
11345 
11380 cpl_image *mos_map_wavelengths(cpl_image *spatial, cpl_image *calibration,
11381  cpl_table *slits, cpl_table *polytraces,
11382  double reference, double blue, double red,
11383  double dispersion)
11384 {
11385  const char *func = "mos_map_wavelengths";
11386 
11387  const char *clab[6] = {"c0", "c1", "c2", "c3", "c4", "c5"};
11388  /* Max order is 5 */
11389  cpl_polynomial *polytop;
11390  cpl_polynomial *polybot;
11391  cpl_image *remapped;
11392  float *data;
11393  float *wdata;
11394  float *sdata;
11395  float *xdata;
11396  double vtop, vbot, value;
11397  double top, bot;
11398  double coeff;
11399  double ytop, ybot;
11400  double ypos;
11401  double fvalue;
11402  int ivalue;
11403  int yint, ysize, yprev;
11404  int nslits;
11405  int npseudo;
11406  int *slit_id;
11407  int *position;
11408  int *length;
11409  int nx, ny;
11410  int pixel_above, pixel_below, refpixel, start_pixel, end_pixel;
11411  int missing_top, missing_bot;
11412  int null;
11413  int order;
11414  int i, j;
11415  cpl_size k;
11416 
11417 
11418  if (spatial == NULL || calibration == NULL ||
11419  slits == NULL || polytraces == NULL) {
11420  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
11421  return NULL;
11422  }
11423 
11424  if (dispersion <= 0.0) {
11425  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
11426  return NULL;
11427  }
11428 
11429  if (red - blue < dispersion) {
11430  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
11431  return NULL;
11432  }
11433 
11434  nx = cpl_image_get_size_x(spatial);
11435  ny = cpl_image_get_size_y(spatial);
11436  ysize = cpl_image_get_size_y(calibration);
11437  remapped = cpl_image_new(nx, ny, CPL_TYPE_FLOAT);
11438  data = cpl_image_get_data(remapped);
11439  sdata = cpl_image_get_data(spatial);
11440  wdata = cpl_image_get_data(calibration);
11441 
11442  nslits = cpl_table_get_nrow(slits);
11443  slit_id = cpl_table_get_data_int(slits, "slit_id");
11444  order = cpl_table_get_ncol(polytraces) - 2;
11445  position = cpl_table_get_data_int(slits, "position");
11446  length = cpl_table_get_data_int(slits, "length");
11447 
11448  /*
11449  * The spatial resampling is performed for a certain number of
11450  * pixels above and below the position of the reference wavelength:
11451  */
11452 
11453  pixel_above = STRETCH_FACTOR * (red - reference) / dispersion;
11454  pixel_below = STRETCH_FACTOR * (reference - blue) / dispersion;
11455 
11456  for (i = 0; i < nslits; i++) {
11457 
11458  if (length[i] == 0)
11459  continue;
11460 
11461  /*
11462  * Note that the x coordinate of the reference pixels on the CCD
11463  * is taken arbitrarily at the top end of each slit. This wouldn't
11464  * be entirely correct in case of curved slits, or in presence of
11465  * heavy distortions: in such cases the spatial resampling is
11466  * really performed across a wide range of wavelengths. But
11467  * the lag between top and bottom spectral curvature models
11468  * would introduce even in such cases negligible effects on
11469  * the spectral spatial resampling.
11470  */
11471 
11472  refpixel = cpl_table_get_double(slits, "xtop", i, NULL);
11473 
11474  start_pixel = refpixel - pixel_below;
11475  if (start_pixel < 0)
11476  start_pixel = 0;
11477 
11478  end_pixel = refpixel + pixel_above;
11479  if (end_pixel > nx)
11480  end_pixel = nx;
11481 
11482  /*
11483  * Recover from the table of spectral curvature coefficients
11484  * the curvature polynomials.
11485  */
11486 
11487  missing_top = 0;
11488  polytop = cpl_polynomial_new(1);
11489  for (k = 0; k <= order; k++) {
11490  coeff = cpl_table_get_double(polytraces, clab[k], 2*i, &null);
11491  if (null) {
11492  cpl_polynomial_delete(polytop);
11493  missing_top = 1;
11494  break;
11495  }
11496  cpl_polynomial_set_coeff(polytop, &k, coeff);
11497  }
11498 
11499  missing_bot = 0;
11500  polybot = cpl_polynomial_new(1);
11501  for (k = 0; k <= order; k++) {
11502  coeff = cpl_table_get_double(polytraces, clab[k], 2*i+1, &null);
11503  if (null) {
11504  cpl_polynomial_delete(polybot);
11505  missing_bot = 1;
11506  break;
11507  }
11508  cpl_polynomial_set_coeff(polybot, &k, coeff);
11509  }
11510 
11511  if (missing_top && missing_bot) {
11512  cpl_msg_debug(func, "Slit %d was not traced: no extraction!",
11513  slit_id[i]);
11514  continue;
11515  }
11516 
11517  /*
11518  * In case just one of the two edges was not traced, the other
11519  * edge curvature model is duplicated and shifted to the other
11520  * end of the slit: better than nothing!
11521  */
11522 
11523  if (missing_top) {
11524  cpl_msg_debug(func, "Upper edge of slit %d was not traced: "
11525  "the spectral curvature of the lower edge "
11526  "is used instead.", slit_id[i]);
11527  polytop = cpl_polynomial_duplicate(polybot);
11528  ytop = cpl_table_get_double(slits, "ytop", i, NULL);
11529  ybot = cpl_table_get_double(slits, "ybottom", i, NULL);
11530  k = 0;
11531  coeff = cpl_polynomial_get_coeff(polybot, &k);
11532  coeff += ytop - ybot;
11533  cpl_polynomial_set_coeff(polytop, &k, coeff);
11534  }
11535 
11536  if (missing_bot) {
11537  cpl_msg_debug(func, "Lower edge of slit %d was not traced: "
11538  "the spectral curvature of the upper edge "
11539  "is used instead.", slit_id[i]);
11540  polybot = cpl_polynomial_duplicate(polytop);
11541  ytop = cpl_table_get_double(slits, "ytop", i, NULL);
11542  ybot = cpl_table_get_double(slits, "ybottom", i, NULL);
11543  k = 0;
11544  coeff = cpl_polynomial_get_coeff(polytop, &k);
11545  coeff -= ytop - ybot;
11546  cpl_polynomial_set_coeff(polybot, &k, coeff);
11547  }
11548 
11549  /*
11550  * Point to current slit on wavelength calibration image.
11551  * Note that the npseudo value related to this slit is equal
11552  * to the number of spatial pseudo-pixels decreased by 1
11553  * (compare with function mos_spatial_calibration()).
11554  */
11555 
11556  xdata = wdata + nx*position[i];
11557  npseudo = length[i] - 1;
11558 
11559  /*
11560  * Write interpolated wavelengths to CCD image
11561  */
11562 
11563  for (j = start_pixel; j < end_pixel; j++) {
11564  top = cpl_polynomial_eval_1d(polytop, j, NULL);
11565  bot = cpl_polynomial_eval_1d(polybot, j, NULL);
11566  for (k = 0; k <= npseudo; k++) {
11567  ypos = top - k*(top-bot)/npseudo;
11568  yint = ypos;
11569 
11570  /*
11571  * The line:
11572  * value = sdata[j + nx*yint];
11573  * should be equivalent to:
11574  * value = npseudo*(top-yint)/(top-bot);
11575  */
11576 
11577  if (yint < 0 || yint >= ny-1) {
11578  yprev = yint;
11579  continue;
11580  }
11581 
11582  value = sdata[j + nx*yint]; /* Spatial coordinate on CCD */
11583  ivalue = value; /* Nearest spatial pixels: */
11584  fvalue = value - ivalue; /* ivalue and ivalue+1 */
11585  if (ivalue < npseudo && ivalue >= 0) {
11586  vtop = xdata[j + nx*(npseudo-ivalue)];
11587  vbot = xdata[j + nx*(npseudo-ivalue-1)];
11588  if (vtop < 1.0) { /* Impossible wavelength */
11589  if (vbot < 1.0) {
11590  value = 0.0;
11591  }
11592  else {
11593  value = vbot;
11594  }
11595  }
11596  else if (vbot < 1.0) {
11597  if (k)
11598  value = vtop;
11599  else
11600  value = 0.0;
11601  }
11602  else if (fabs(vbot-vtop) > 10*dispersion) {
11603  value = 0.0;
11604  }
11605  else {
11606  value = vtop*(1-fvalue) + vbot*fvalue;
11607  }
11608  data[j + nx*yint] = value;
11609 
11610  if (k) {
11611 
11612  /*
11613  * This is added to recover lost pixels on
11614  * the CCD image (pixels are lost because
11615  * the CCD pixels are less than npseudo+1).
11616  */
11617 
11618  if (yprev - yint > 1) {
11619  value = sdata[j + nx*(yint+1)];
11620  ivalue = value;
11621  fvalue = value - ivalue;
11622  if (ivalue < npseudo && ivalue >= 0) {
11623  vtop = xdata[j + nx*(npseudo-ivalue)];
11624  vbot = xdata[j + nx*(npseudo-ivalue-1)];
11625  if (vtop < 1.0) {
11626  if (vbot < 1.0) {
11627  value = data[j + nx*(yint+1)];
11628  }
11629  else {
11630  value = vbot;
11631  }
11632  }
11633  else if (vbot < 1.0) {
11634  value = vtop;
11635  }
11636  else if (fabs(vbot-vtop) > 2*dispersion) {
11637  value = vtop;
11638  }
11639  else {
11640  value = vtop*(1-fvalue) + vbot*fvalue;
11641  }
11642  data[j + nx*(yint+1)] = value;
11643  }
11644  }
11645  }
11646  }
11647  yprev = yint;
11648  }
11649  }
11650  cpl_polynomial_delete(polytop);
11651  cpl_polynomial_delete(polybot);
11652  }
11653 
11654  return remapped;
11655 }
11656 
11730 cpl_image *mos_map_spectrum(cpl_image *spectra, cpl_image *wavecalib,
11731  cpl_image *spatial, cpl_table *slits,
11732  cpl_table *polytraces, double reference,
11733  double blue, double red, double dispersion,
11734  int flux)
11735 {
11736  const char *func = "mos_map_spectrum";
11737 
11738  const char *clab[6] = {"c0", "c1", "c2", "c3", "c4", "c5"};
11739  /* Max order is 5 */
11740  cpl_polynomial *polytop;
11741  cpl_polynomial *polybot;
11742  cpl_image *remapped;
11743  cpl_image **exslit;
11744  float *data;
11745  float *wdata;
11746  float *sdata;
11747  float *xdata;
11748  double lambda00, lambda01, lambda10, lambda11, lambda;
11749  double space00, space01, space10, space11, space;
11750  double value00, value01, value10, value11, value0, value1, value;
11751  double dL, dS;
11752  double top, bot;
11753  double coeff;
11754  double ytop, ybot;
11755  double xfrac, yfrac;
11756  int yint, ysize;
11757  int itop, ibot;
11758  int shift;
11759  int L, S;
11760  int nslits;
11761  int npseudo;
11762  int *slit_id;
11763  int *position;
11764  int *length;
11765  int nx, ny;
11766  int x, y;
11767  int nlambda;
11768  int pixel_above, pixel_below, refpixel, start_pixel, end_pixel;
11769  int missing_top, missing_bot;
11770  int null;
11771  int order;
11772  int i;
11773  cpl_size k;
11774 
11775 
11776  flux += flux;
11777 
11778  if (spectra == NULL || spatial == NULL || wavecalib == NULL ||
11779  slits == NULL || polytraces == NULL) {
11780  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
11781  return NULL;
11782  }
11783 
11784  if (dispersion <= 0.0) {
11785  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
11786  return NULL;
11787  }
11788 
11789  if (red - blue < dispersion) {
11790  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
11791  return NULL;
11792  }
11793 
11794  nx = cpl_image_get_size_x(spectra);
11795  ny = cpl_image_get_size_y(spectra);
11796 
11797  if (nx != cpl_image_get_size_x(spatial) ||
11798  ny != cpl_image_get_size_y(spatial) ||
11799  nx != cpl_image_get_size_x(wavecalib) ||
11800  ny != cpl_image_get_size_y(wavecalib)) {
11801  cpl_error_set(func, CPL_ERROR_INCOMPATIBLE_INPUT);
11802  return NULL;
11803  }
11804 
11805  nlambda = STRETCH_FACTOR * (red - blue) / dispersion;
11806  pixel_above = STRETCH_FACTOR * (red - reference) / dispersion;
11807  pixel_below = STRETCH_FACTOR * (reference - blue) / dispersion;
11808 
11809  data = cpl_image_get_data(spectra);
11810  sdata = cpl_image_get_data(spatial);
11811  wdata = cpl_image_get_data(wavecalib);
11812 
11813  nslits = cpl_table_get_nrow(slits);
11814  slit_id = cpl_table_get_data_int(slits, "slit_id");
11815  order = cpl_table_get_ncol(polytraces) - 2;
11816  position = cpl_table_get_data_int(slits, "position");
11817  length = cpl_table_get_data_int(slits, "length");
11818 
11819  exslit = cpl_calloc(nslits, sizeof(cpl_image *));
11820 
11821  for (i = 0; i < nslits; i++) {
11822 
11823  if (length == 0)
11824  continue;
11825 
11826  /*
11827  * Note that the x coordinate of the reference pixels on the CCD
11828  * is taken arbitrarily at the top end of each slit. This wouldn't
11829  * be entirely correct in case of curved slits, or in presence of
11830  * heavy distortions: in such cases the spatial resampling is
11831  * really performed across a wide range of wavelengths. But
11832  * the lag between top and bottom spectral curvature models
11833  * would introduce even in such cases negligible effects on
11834  * the spectral spatial resampling.
11835  */
11836 
11837  refpixel = cpl_table_get_double(slits, "xtop", i, NULL);
11838 
11839  start_pixel = refpixel - pixel_below;
11840  if (start_pixel < 1)
11841  start_pixel = 1;
11842 
11843  end_pixel = refpixel + pixel_above;
11844  if (end_pixel > nx)
11845  end_pixel = nx;
11846 
11847  /*
11848  * Recover from the table of spectral curvature coefficients
11849  * the curvature polynomials.
11850  */
11851 
11852  missing_top = 0;
11853  polytop = cpl_polynomial_new(1);
11854  for (k = 0; k <= order; k++) {
11855  coeff = cpl_table_get_double(polytraces, clab[k], 2*i, &null);
11856  if (null) {
11857  cpl_polynomial_delete(polytop);
11858  missing_top = 1;
11859  break;
11860  }
11861  cpl_polynomial_set_coeff(polytop, &k, coeff);
11862  }
11863 
11864  missing_bot = 0;
11865  polybot = cpl_polynomial_new(1);
11866  for (k = 0; k <= order; k++) {
11867  coeff = cpl_table_get_double(polytraces, clab[k], 2*i+1, &null);
11868  if (null) {
11869  cpl_polynomial_delete(polybot);
11870  missing_bot = 1;
11871  break;
11872  }
11873  cpl_polynomial_set_coeff(polybot, &k, coeff);
11874  }
11875 
11876  if (missing_top && missing_bot) {
11877  cpl_msg_debug(func, "Slit %d was not traced: no extraction!",
11878  slit_id[i]);
11879  continue;
11880  }
11881 
11882  /*
11883  * In case just one of the two edges was not traced, the other
11884  * edge curvature model is duplicated and shifted to the other
11885  * end of the slit: better than nothing!
11886  */
11887 
11888  if (missing_top) {
11889  cpl_msg_debug(func, "Upper edge of slit %d was not traced: "
11890  "the spectral curvature of the lower edge "
11891  "is used instead.", slit_id[i]);
11892  polytop = cpl_polynomial_duplicate(polybot);
11893  ytop = cpl_table_get_double(slits, "ytop", i, NULL);
11894  ybot = cpl_table_get_double(slits, "ybottom", i, NULL);
11895  k = 0;
11896  coeff = cpl_polynomial_get_coeff(polybot, &k);
11897  coeff += ytop - ybot;
11898  cpl_polynomial_set_coeff(polytop, &k, coeff);
11899  }
11900 
11901  if (missing_bot) {
11902  cpl_msg_debug(func, "Lower edge of slit %d was not traced: "
11903  "the spectral curvature of the upper edge "
11904  "is used instead.", slit_id[i]);
11905  polybot = cpl_polynomial_duplicate(polytop);
11906  ytop = cpl_table_get_double(slits, "ytop", i, NULL);
11907  ybot = cpl_table_get_double(slits, "ybottom", i, NULL);
11908  k = 0;
11909  coeff = cpl_polynomial_get_coeff(polytop, &k);
11910  coeff -= ytop - ybot;
11911  cpl_polynomial_set_coeff(polybot, &k, coeff);
11912  }
11913 
11914  /*
11915  * Allocate image for current extracted slit
11916  */
11917 
11918  top = cpl_polynomial_eval_1d(polytop, refpixel, NULL);
11919  bot = cpl_polynomial_eval_1d(polybot, refpixel, NULL);
11920  npseudo = ceil(top-bot) + 1;
11921 
11922  if (npseudo < 1) {
11923  cpl_polynomial_delete(polytop);
11924  cpl_polynomial_delete(polybot);
11925  cpl_msg_debug(func, "Slit %d was badly traced: no extraction!",
11926  slit_id[i]);
11927  continue;
11928  }
11929 
11930  exslit[i] = cpl_image_new(nlambda, npseudo+1, CPL_TYPE_FLOAT);
11931  xdata = cpl_image_get_data(exslit[i]);
11932 
11933  /*
11934  * Write interpolated spectral values to remapped slit spectrum.
11935  */
11936 
11937  for (x = start_pixel; x < end_pixel; x++) {
11938  top = cpl_polynomial_eval_1d(polytop, x, NULL);
11939  bot = cpl_polynomial_eval_1d(polybot, x, NULL);
11940  itop = top + 1;
11941  ibot = bot;
11942  if (itop < 0)
11943  itop = 0;
11944  if (itop > ny - 1)
11945  itop = ny - 1;
11946  if (ibot < 0)
11947  ibot = 0;
11948  if (ibot > ny - 1)
11949  ibot = ny - 1;
11950  for (y = ibot; y < itop; y++) {
11951  lambda11 = wdata[x + y*nx];
11952  if (lambda11 < 1.0) /* Impossible wavelength */
11953  continue;
11954  space11 = sdata[x + y*nx];
11955  if (space11 < 0.0) /* Impossible spatial coordinate */
11956  continue;
11957  lambda01 = wdata[x - 1 + y*nx];
11958  if (lambda01 < 1.0) /* Impossible wavelength */
11959  continue;
11960  space01 = sdata[x - 1 + y*nx];
11961  if (space01 < 0.0) /* Impossible spatial coordinate */
11962  continue;
11963 
11964  shift = 0;
11965 
11966 /****+
11967  if (wdata[x + (y+1)*nx] > 1.0) {
11968  if (wdata[x + (y+1)*nx] - lambda11 > 0) {
11969  shift = -1;
11970  while (wdata[x + shift + (y+1)*nx] - lambda11 > 0)
11971  shift--;
11972  if (lambda11 - wdata[x + shift + (y+1)*nx] >
11973  wdata[x + shift + 1 + (y+1)*nx] - lambda11) {
11974  shift++;
11975  }
11976  }
11977  else {
11978  shift = 1;
11979  while (wdata[x + shift + (y+1)*nx] - lambda11 < 0)
11980  shift++;
11981  if (wdata[x + shift + (y+1)*nx] - lambda11 >
11982  lambda11 - wdata[x + shift + 1 + (y+1)*nx]) {
11983  shift--;
11984  }
11985  }
11986  }
11987 ****/
11988 
11989 /****
11990 printf("y = %d, shift = %d\n", y, shift);
11991 ****/
11992 
11993  lambda10 = wdata[x + shift + (y+1)*nx];
11994  if (lambda10 < 1.0) /* Impossible wavelength */
11995  continue;
11996  space10 = sdata[x + shift + (y+1)*nx];
11997  if (space10 < 0.0) /* Impossible spatial coordinate */
11998  continue;
11999  lambda00 = wdata[x - 1 + shift + (y+1)*nx];
12000  if (lambda00 < 1.0) /* Impossible wavelength */
12001  continue;
12002  space00 = sdata[x - 1 + shift + (y+1)*nx];
12003  if (space00 < 0.0) /* Impossible spatial coordinate */
12004  continue;
12005 
12006  /*
12007  * Find the variation in lambda and space in this
12008  * position for each CCD pixel (both quantities are
12009  * expected to be positive).
12010  */
12011 
12012  dL = lambda11 - lambda01;
12013  dS = space11 - space10;
12014 
12015  /*
12016  * Find the position (L,S) of the output pixel
12017  * (by integer truncation).
12018  */
12019 
12020  L = (lambda11 - blue)/dispersion + 0.5;
12021  S = space11 + 0.5; /* Counted from top! */
12022 
12023  if (L < 0 || L >= nlambda)
12024  continue;
12025  if (S < 0 || S > npseudo)
12026  continue;
12027 
12028  /*
12029  * Find the coordinate of pixel (L,S)
12030  */
12031 
12032  lambda = blue + L*dispersion;
12033  space = S;
12034 
12035  /*
12036  * Find the interpolation point on the CCD: it is
12037  * defined as the (positive) distance from current
12038  * CCD pixel (x,y) of the interpolation point (x',y'),
12039  * measured in CCD pixels. The interpolation point
12040  * is located between the four CCD pixels selected
12041  * above.
12042  */
12043 
12044  xfrac = (lambda11-lambda)/dL;
12045  yfrac = (space11-space)/dS;
12046 
12047 /*
12048 if (xfrac < 0.0 || xfrac > 1.0 || yfrac < 0.0 || yfrac > 1.0)
12049 printf("xyfrac = %f, %f\n", xfrac, yfrac);
12050 */
12051 
12052  /*
12053  * Get the four values to interpolate
12054  */
12055 
12056  value11 = data[x + y*nx];
12057  value01 = data[x - 1 + y*nx];
12058  value10 = data[x + shift + (y+1)*nx];
12059  value00 = data[x + shift - 1 + (y+1)*nx];
12060 
12061  /*
12062  * Interpolation
12063  */
12064 
12065  value1 = (1-xfrac)*value11 + xfrac*value01;
12066  value0 = (1-xfrac)*value10 + xfrac*value00;
12067  value = (1-yfrac)*value1 + yfrac*value0;
12068 
12069  /*
12070  * Write this value to the appropriate (L,S) coordinate
12071  * on output slit
12072  */
12073 
12074  xdata[L + nlambda*(npseudo-S)] = value;
12075 
12076  }
12077  }
12078  cpl_polynomial_delete(polytop);
12079  cpl_polynomial_delete(polybot);
12080  }
12081 
12082  /*
12083  * Now all the slits images are copied to a single image
12084  */
12085 
12086  ysize = 0;
12087  for (i = 0; i < nslits; i++)
12088  if (exslit[i])
12089  ysize += cpl_image_get_size_y(exslit[i]);
12090 
12091  remapped = cpl_image_new(nlambda, ysize, CPL_TYPE_FLOAT);
12092 
12093  yint = -1;
12094  for (i = 0; i < nslits; i++) {
12095  if (exslit[i]) {
12096  yint += cpl_image_get_size_y(exslit[i]);
12097  cpl_image_copy(remapped, exslit[i], 1, ysize - yint);
12098  cpl_image_delete(exslit[i]);
12099  cpl_table_set_int(slits, "position", i, ysize - yint - 1);
12100  }
12101  }
12102 
12103  cpl_free(exslit);
12104 
12105  return remapped;
12106 
12107 }
12108 
12109 
12142 cpl_table *mos_sky_map_super(cpl_image *spectra, cpl_image *wavemap,
12143  double dispersion, double factor, int minpoints,
12144  cpl_image *skymap)
12145 {
12146  const char *func = "mos_sky_map_super";
12147 
12148  cpl_vector **vector;
12149  cpl_vector **wvector;
12150  double firstLambda, lastLambda;
12151  double lambda, lambda1, lambda2;
12152  double value, value1, value2;
12153  double frac;
12154  float min, max;
12155  int *count;
12156  int nbin, bin;
12157  int nx, ny, npix;
12158  int first_valid, valid_bins;
12159  int i, j;
12160 
12161  cpl_table *sky;
12162  double *sky_spectrum;
12163  double *sky_wave;
12164  float *data;
12165  float *sdata;
12166  float *kdata;
12167 
12168 
12169  if (spectra == NULL || wavemap == NULL || skymap == NULL) {
12170  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
12171  return NULL;
12172  }
12173 
12174  if (dispersion <= 0.0) {
12175  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
12176  cpl_msg_error(func, "Negative dispersion: %s", cpl_error_get_message());
12177  return NULL;
12178  }
12179 
12180  nx = cpl_image_get_size_x(spectra);
12181  ny = cpl_image_get_size_y(spectra);
12182  npix = nx * ny;
12183 
12184  if (nx != cpl_image_get_size_x(wavemap) ||
12185  ny != cpl_image_get_size_y(wavemap) ||
12186  nx != cpl_image_get_size_x(skymap) ||
12187  ny != cpl_image_get_size_y(skymap)) {
12188  cpl_error_set(func, CPL_ERROR_INCOMPATIBLE_INPUT);
12189  cpl_msg_error(func, "Image sizes: %s", cpl_error_get_message());
12190  return NULL;
12191  }
12192 
12193  if (factor < 1.0) {
12194  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
12195  cpl_msg_error(func, "Undersampling (%f): %s", factor,
12196  cpl_error_get_message());
12197  return NULL;
12198  }
12199 
12200  if (minpoints < 0) {
12201  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
12202  cpl_msg_error(func, "Negative threshold: %s", cpl_error_get_message());
12203  return NULL;
12204  }
12205 
12206  dispersion /= factor;
12207 
12208 
12209  /*
12210  * Find bluest and reddest wavelengths in the whole image
12211  */
12212 
12213  data = cpl_image_get_data(wavemap);
12214 
12215  for (i = 0; i < npix; i++) {
12216  if (data[i] > 1.0) {
12217  min = max = data[i];
12218  j = i+1;
12219  break;
12220  }
12221  }
12222 
12223  for (i = j; i < npix; i++) {
12224  if (data[i] < 1.0) /* Impossible wavelength */
12225  continue;
12226  if (min > data[i])
12227  min = data[i];
12228  if (max < data[i])
12229  max = data[i];
12230  }
12231 
12232  firstLambda = min;
12233  lastLambda = max;
12234 
12235 
12236  /*
12237  * Determine length of median spectrum
12238  */
12239 
12240  nbin = (lastLambda - firstLambda) / dispersion;
12241 
12242  /*
12243  * Count how many values will be found for each spectral bin.
12244  * The ith bin has a wavelength range from firstLambda + i*dispersion
12245  * (inclusive) to firstLambda + (i+1)*dispersion (exclusive), and
12246  * it is assigned to its central wavelength.
12247  */
12248 
12249  count = cpl_calloc(nbin, sizeof(int));
12250 
12251  data = cpl_image_get_data(wavemap);
12252 
12253  for (i = 0; i < npix; i++) {
12254  if (data[i] < 1.0)
12255  continue;
12256  bin = (data[i] - firstLambda) / dispersion;
12257  if (bin < nbin) /* Safer */
12258  count[bin]++;
12259  }
12260 
12261  valid_bins = 0;
12262  for (i = 0; i < nbin; i++)
12263  if (count[i] >= minpoints)
12264  valid_bins++;
12265 
12266  if (valid_bins < nbin/3) {
12267  cpl_msg_warning(func, "Cannot determine a good global sky "
12268  "spectrum from input data");
12269  return NULL;
12270  }
12271 
12272 
12273  /*
12274  * Allocate an array of vectors with the appropriate size, to
12275  * contain a list of all the spectral pixels values. At the same
12276  * time, reset the array of counters (because we will have to
12277  * count again...).
12278  */
12279 
12280  vector = cpl_calloc(nbin, sizeof(cpl_vector *));
12281  wvector = cpl_calloc(nbin, sizeof(cpl_vector *));
12282  for (i = 0; i < nbin; i++) {
12283  if (count[i] >= minpoints) {
12284  vector[i] = cpl_vector_new(count[i]);
12285  wvector[i] = cpl_vector_new(count[i]);
12286  }
12287  count[i] = 0;
12288  }
12289 
12290 
12291  /*
12292  * Read the wavemap and the spectral images, and add the data values
12293  * to the appropriate wavelength bins
12294  */
12295 
12296  data = cpl_image_get_data(wavemap);
12297  sdata = cpl_image_get_data(spectra);
12298 
12299  for (i = 0; i < npix; i++) {
12300  if (data[i] < 1.0)
12301  continue;
12302  bin = (data[i] - firstLambda) / dispersion;
12303  if (bin < nbin) { /* Safer */
12304  if (vector[bin]) {
12305  cpl_vector_set(vector[bin], count[bin], sdata[i]);
12306  cpl_vector_set(wvector[bin], count[bin], data[i]);
12307  }
12308  count[bin]++;
12309  }
12310  }
12311 
12312 
12313  /*
12314  * Compute the median flux for each wavelength bin, and destroy
12315  * at the same time the used vectors
12316  */
12317 
12318  sky_spectrum = cpl_calloc(nbin, sizeof(double));
12319  sky_wave = cpl_calloc(nbin, sizeof(double));
12320  for (i = 0; i < nbin; i++) {
12321  if (vector[i]) {
12322  sky_spectrum[i] = cpl_vector_get_median_const(vector[i]);
12323  sky_wave[i] = cpl_vector_get_median_const(wvector[i]);
12324  cpl_vector_delete(vector[i]);
12325  cpl_vector_delete(wvector[i]);
12326  }
12327  }
12328 
12329  cpl_free(vector);
12330  cpl_free(wvector);
12331 
12332 
12333  /*
12334  * Here possible gaps in the final spectrum are filled by interpolation
12335  */
12336 
12337  for (i = 0; i < nbin; i++) {
12338  if (count[i] >= minpoints) {
12339  first_valid = i;
12340  break;
12341  }
12342  }
12343 
12344  for (i = first_valid; i < nbin; i++) {
12345  if (count[i] < minpoints) {
12346  sky_wave[i] = firstLambda + (i+0.5)*dispersion;
12347  for (j = i+1; j < nbin; j++) {
12348  if (count[j] >= minpoints) {
12349  if (sky_wave[j] - sky_wave[i-1] < 0.1) {
12350  sky_spectrum[i] = (sky_spectrum[j] + sky_spectrum[i-1])
12351  / 2;
12352  }
12353  else {
12354  frac = (sky_wave[i] - sky_wave[i-1])
12355  / (sky_wave[j] - sky_wave[i-1]);
12356  sky_spectrum[i] = frac * sky_spectrum[j]
12357  + (1 - frac) * sky_spectrum[i-1];
12358  }
12359  }
12360  }
12361  }
12362  }
12363 
12364 
12365  /*
12366  * Create the output table
12367  */
12368 
12369  sky = cpl_table_new(nbin);
12370  cpl_table_wrap_double(sky, sky_wave, "wavelength");
12371  cpl_table_wrap_double(sky, sky_spectrum, "sky");
12372  cpl_table_wrap_int(sky, count, "npoints");
12373 
12374 
12375  /*
12376  * Fill the sky map
12377  */
12378 
12379  data = cpl_image_get_data(wavemap);
12380  sdata = cpl_image_get_data(spectra);
12381  kdata = cpl_image_get_data(skymap);
12382 
12383  for (i = 0; i < npix; i++) {
12384 
12385  /*
12386  * Currently based on linear interpolation
12387  */
12388 
12389  lambda = data[i];
12390  if (lambda < 1.0)
12391  continue;
12392  bin = (lambda - firstLambda) / dispersion;
12393  lambda1 = sky_wave[bin];
12394  value1 = sky_spectrum[bin];
12395  if (lambda1 < lambda) {
12396  bin++;
12397  if (bin < nbin) {
12398  lambda2 = sky_wave[bin];
12399  value2 = sky_spectrum[bin];
12400  if (lambda2 - lambda1 < 0.1) {
12401  value = (value1 + value2) / 2;
12402  }
12403  else {
12404  frac = (lambda - lambda1) / (lambda2 - lambda1);
12405  value = frac * value2 + (1 - frac) * value1;
12406  }
12407  }
12408  else {
12409  value = value1;
12410  }
12411  }
12412  else {
12413  if (bin > 0) {
12414  bin--;
12415  lambda2 = lambda1;
12416  value2 = value1;
12417  lambda1 = sky_wave[bin];
12418  value1 = sky_spectrum[bin];
12419  if (lambda2 - lambda1 < 0.1) {
12420  value = (value1 + value2) / 2;
12421  }
12422  else {
12423  frac = (lambda - lambda1) / (lambda2 - lambda1);
12424  value = frac * value2 + (1 - frac) * value1;
12425  }
12426  }
12427  else {
12428  value = value1;
12429  }
12430  }
12431  kdata[i] = value;
12432  }
12433 
12434  if (first_valid)
12435  cpl_table_erase_window(sky, 0, first_valid);
12436 
12437  return sky;
12438 
12439 }
12440 
12441 
12475 cpl_table *mos_sky_map(cpl_image *spectra, cpl_image *wavemap,
12476  double dispersion, cpl_image *skymap)
12477 {
12478  const char *func = "mos_sky_map";
12479 
12480  cpl_vector **vector;
12481  double firstLambda, lastLambda;
12482  double lambda, lambda1, lambda2;
12483  double value, value1, value2;
12484  float min, max;
12485  int *count;
12486  int nbin, bin;
12487  int nx, ny, npix;
12488  int i, j;
12489 
12490  cpl_table *sky;
12491  double *sky_spectrum;
12492  float *data;
12493  float *sdata;
12494  float *kdata;
12495  double *wdata;
12496 
12497 
12498  if (spectra == NULL || wavemap == NULL || skymap == NULL) {
12499  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
12500  return NULL;
12501  }
12502 
12503  if (dispersion <= 0.0) {
12504  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
12505  return NULL;
12506  }
12507 
12508  nx = cpl_image_get_size_x(spectra);
12509  ny = cpl_image_get_size_y(spectra);
12510  npix = nx * ny;
12511 
12512  if (nx != cpl_image_get_size_x(wavemap) ||
12513  ny != cpl_image_get_size_y(wavemap) ||
12514  nx != cpl_image_get_size_x(skymap) ||
12515  ny != cpl_image_get_size_y(skymap)) {
12516  cpl_error_set(func, CPL_ERROR_INCOMPATIBLE_INPUT);
12517  return NULL;
12518  }
12519 
12520 
12521  /*
12522  * Find bluest and reddest wavelengths in the whole image
12523  */
12524 
12525  data = cpl_image_get_data(wavemap);
12526 
12527  for (i = 0; i < npix; i++) {
12528  if (data[i] > 1.0) {
12529  min = max = data[i];
12530  j = i+1;
12531  break;
12532  }
12533  }
12534 
12535  for (i = j; i < npix; i++) {
12536  if (data[i] < 1.0) /* Impossible wavelength */
12537  continue;
12538  if (min > data[i])
12539  min = data[i];
12540  if (max < data[i])
12541  max = data[i];
12542  }
12543 
12544  firstLambda = min;
12545  lastLambda = max;
12546 
12547 
12548  /*
12549  * Determine length of median spectrum
12550  */
12551 
12552  nbin = (lastLambda - firstLambda) / dispersion;
12553 
12554  /*
12555  * Count how many values will be found for each spectral bin.
12556  * The ith bin has a wavelength range from firstLambda + i*dispersion
12557  * (inclusive) to firstLambda + (i+1)*dispersion (exclusive), and
12558  * it is assigned to its central wavelength.
12559  */
12560 
12561  count = cpl_calloc(nbin, sizeof(int));
12562 
12563  data = cpl_image_get_data(wavemap);
12564 
12565  for (i = 0; i < npix; i++) {
12566  if (data[i] < 1.0)
12567  continue;
12568  bin = (data[i] - firstLambda) / dispersion;
12569  if (bin < nbin) /* Safer */
12570  count[bin]++;
12571  }
12572 
12573 
12574  /*
12575  * Allocate an array of vectors with the appropriate size, to
12576  * contain a list of all the spectral pixels values. At the same
12577  * time, reset the array of counters (because we will have to
12578  * count again...).
12579  */
12580 
12581  vector = cpl_calloc(nbin, sizeof(cpl_vector *));
12582  for (i = 0; i < nbin; i++) {
12583  if (count[i])
12584  vector[i] = cpl_vector_new(count[i]);
12585  else
12586  vector[i] = NULL;
12587  count[i] = 0;
12588  }
12589 
12590 
12591  /*
12592  * Read the wavemap and the spectral images, and add the data values
12593  * to the appropriate wavelength bins
12594  */
12595 
12596  data = cpl_image_get_data(wavemap);
12597  sdata = cpl_image_get_data(spectra);
12598 
12599  for (i = 0; i < npix; i++) {
12600  if (data[i] < 1.0)
12601  continue;
12602  bin = (data[i] - firstLambda) / dispersion;
12603  if (bin < nbin) { /* Safer */
12604  cpl_vector_set(vector[bin], count[bin], sdata[i]);
12605  count[bin]++;
12606  }
12607  }
12608 
12609 
12610  /*
12611  * Compute the median flux for each wavelength bin, and destroy
12612  * at the same time the used vectors
12613  */
12614 
12615  sky_spectrum = cpl_calloc(nbin, sizeof(double));
12616  for (i = 0; i < nbin; i++) {
12617  if (vector[i]) {
12618  sky_spectrum[i] = cpl_vector_get_median_const(vector[i]);
12619  cpl_vector_delete(vector[i]);
12620  }
12621  }
12622 
12623  cpl_free(vector);
12624 
12625 
12626  /*
12627  * Here possible gaps in the final spectrum should be filled
12628  * by interpolation
12629  */
12630 
12631  /* ... */
12632 
12633  /*
12634  * Create the output table
12635  */
12636 
12637  sky = cpl_table_new(nbin);
12638  cpl_table_new_column(sky, "wavelength", CPL_TYPE_DOUBLE);
12639  cpl_table_set_column_unit(sky, "wavelength", "pixel");
12640  cpl_table_wrap_double(sky, sky_spectrum, "sky");
12641  cpl_table_wrap_int(sky, count, "npoints");
12642  for (i = 0; i < nbin; i++)
12643  cpl_table_set_double(sky, "wavelength", i,
12644  firstLambda + (i+0.5)*dispersion);
12645 
12646 
12647  /*
12648  * Fill the sky map
12649  */
12650 
12651  data = cpl_image_get_data(wavemap);
12652  sdata = cpl_image_get_data(spectra);
12653  kdata = cpl_image_get_data(skymap);
12654  wdata = cpl_table_get_data_double(sky, "wavelength");
12655 
12656  for (i = 0; i < npix; i++) {
12657 
12658  /*
12659  * Currently based on linear interpolation
12660  */
12661 
12662  lambda = data[i];
12663  if (lambda < 1.0)
12664  continue;
12665  bin = (lambda - firstLambda) / dispersion;
12666  lambda1 = wdata[bin];
12667  value1 = sky_spectrum[bin];
12668  if (lambda1 < lambda) {
12669  bin++;
12670  if (bin < nbin) {
12671  lambda2 = wdata[bin];
12672  value2 = sky_spectrum[bin];
12673  value = ((lambda2 - lambda)*value1
12674  + (lambda - lambda1)*value2) / dispersion;
12675  }
12676  else {
12677  value = value1;
12678  }
12679  }
12680  else {
12681  if (bin > 0) {
12682  bin--;
12683  lambda2 = lambda1;
12684  value2 = value1;
12685  lambda1 = wdata[bin];
12686  value1 = sky_spectrum[bin];
12687  value = ((lambda2 - lambda)*value1
12688  + (lambda - lambda1)*value2)/dispersion;
12689  }
12690  else {
12691  value = value1;
12692  }
12693  }
12694  kdata[i] = value;
12695  }
12696 
12697  return sky;
12698 
12699 }
12700 
12701 
12717 cpl_image *mos_sky_local_old(cpl_image *spectra, cpl_table *slits)
12718 {
12719  const char *func = "mos_sky_local_old";
12720 
12721  cpl_image *exslit;
12722  cpl_image *sky;
12723  cpl_image *skymap;
12724  float *data;
12725  float *sdata;
12726  int nx, ny;
12727  int xlow, ylow, xhig, yhig;
12728  int nslits;
12729  int *slit_id;
12730  int *position;
12731  int *length;
12732  int i, j, k;
12733 
12734 
12735  if (spectra == NULL) {
12736  cpl_msg_error(func,
12737  "A scientific rectified spectral image must be given");
12738  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
12739  return NULL;
12740  }
12741 
12742  if (slits == NULL) {
12743  cpl_msg_error(func, "A slits position table must be given");
12744  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
12745  return NULL;
12746  }
12747 
12748  nslits = cpl_table_get_nrow(slits);
12749  slit_id = cpl_table_get_data_int(slits, "slit_id");
12750  position = cpl_table_get_data_int(slits, "position");
12751  length = cpl_table_get_data_int(slits, "length");
12752 
12753  nx = cpl_image_get_size_x(spectra);
12754  ny = cpl_image_get_size_y(spectra);
12755 
12756  skymap = cpl_image_new(nx, ny, CPL_TYPE_FLOAT);
12757 
12758  xlow = 1;
12759  xhig = nx;
12760  for (i = 0; i < nslits; i++) {
12761 
12762  if (length[i] == 0)
12763  continue;
12764 
12765  /*
12766  * Define the extraction boundaries. We DON'T write:
12767  *
12768  * ylow = position[i];
12769  * yhig = ylow + length[i];
12770  *
12771  * because the cpl_image pixels are counted from 1, and because in
12772  * cpl_image_extract() the coordinates of the last pixel are inclusive.
12773  */
12774 
12775  ylow = position[i] + 1;
12776  yhig = ylow + length[i] - 1;
12777 
12778  exslit = cpl_image_extract(spectra, xlow, ylow, xhig, yhig);
12779  sky = cpl_image_collapse_median_create(exslit, 0, 0, 1);
12780  cpl_image_delete(exslit);
12781 
12782  data = cpl_image_get_data(skymap);
12783  data += nx * position[i];
12784 
12785  for (j = 0; j < length[i]; j++) {
12786  sdata = cpl_image_get_data(sky);
12787  for (k = 0; k < nx; k++) {
12788  *data++ = *sdata++;
12789  }
12790  }
12791 
12792  cpl_image_delete(sky);
12793  }
12794 
12795  return skymap;
12796 
12797 }
12798 
12799 
12819 cpl_image *mos_sky_local(cpl_image *spectra, cpl_table *slits, int order)
12820 {
12821  const char *func = "mos_sky_local";
12822 
12823  char name[MAX_COLNAME];
12824 
12825  cpl_polynomial *fit;
12826  cpl_vector *points;
12827  cpl_vector *values;
12828  cpl_vector *keep_points;
12829  cpl_vector *keep_values;
12830  cpl_image *exslit;
12831  cpl_image *sky;
12832  cpl_image *subtracted;
12833  cpl_image *profile;
12834  cpl_image *skymap;
12835  cpl_table *objects;
12836  float *data;
12837  float *sdata;
12838  float *xdata;
12839  double *vdata;
12840  double *pdata;
12841  double median;
12842  int nx, ny;
12843  int xlow, ylow, xhig, yhig;
12844  int nslits;
12845  int *slit_id;
12846  int *position;
12847  int *length;
12848  int *is_sky;
12849  int nsky, nbad;
12850  int maxobjects;
12851  int margin = 3;
12852  int radius = 6;
12853  int i, j, k;
12854 
12855 
12856  if (spectra == NULL) {
12857  cpl_msg_error(func,
12858  "A scientific rectified spectral image must be given");
12859  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
12860  return NULL;
12861  }
12862 
12863  if (slits == NULL) {
12864  cpl_msg_error(func, "A slits position table must be given");
12865  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
12866  return NULL;
12867  }
12868 
12869  if (order < 0) {
12870  cpl_msg_error(func, "Invalid fit order");
12871  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
12872  return NULL;
12873  }
12874 
12875  nslits = cpl_table_get_nrow(slits);
12876  slit_id = cpl_table_get_data_int(slits, "slit_id");
12877  position = cpl_table_get_data_int(slits, "position");
12878  length = cpl_table_get_data_int(slits, "length");
12879 
12880  nx = cpl_image_get_size_x(spectra);
12881  ny = cpl_image_get_size_y(spectra);
12882 
12883  skymap = cpl_image_new(nx, ny, CPL_TYPE_FLOAT);
12884 
12885  xlow = 1;
12886  xhig = nx;
12887  for (i = 0; i < nslits; i++) {
12888 
12889  if (length[i] == 0)
12890  continue;
12891 
12892  /*
12893  * Define the extraction boundaries. We DON'T write:
12894  *
12895  * ylow = position[i];
12896  * yhig = ylow + length[i];
12897  *
12898  * because the cpl_image pixels are counted from 1, and because in
12899  * cpl_image_extract() the coordinates of the last pixel are inclusive.
12900  */
12901 
12902  ylow = position[i] + 1;
12903  yhig = ylow + length[i] - 1;
12904 
12905  exslit = cpl_image_extract(spectra, xlow, ylow, xhig, yhig);
12906  sky = cpl_image_collapse_median_create(exslit, 0, 0, 1);
12907  cpl_image_delete(exslit);
12908 
12909  data = cpl_image_get_data(skymap);
12910  data += nx * position[i];
12911 
12912  for (j = 0; j < length[i]; j++) {
12913  sdata = cpl_image_get_data(sky);
12914  for (k = 0; k < nx; k++) {
12915  *data++ = *sdata++;
12916  }
12917  }
12918 
12919  cpl_image_delete(sky);
12920  }
12921 
12922 
12923  /*
12924  * Preliminary sky-subtracted image
12925  */
12926 
12927  subtracted = cpl_image_duplicate(spectra);
12928  cpl_image_subtract(subtracted, skymap);
12929  cpl_image_delete(skymap);
12930 
12931 
12932  /*
12933  * Detect objects positions in all slits
12934  */
12935 
12936  objects = cpl_table_duplicate(slits);
12937  profile = mos_detect_objects(subtracted, objects, margin, radius, 0);
12938  cpl_image_delete(profile);
12939  cpl_image_delete(subtracted);
12940 
12941 
12942  /*
12943  * Flag the sky pixels. Note that maxobjects is intentionally
12944  * the max number of objects increased by one.
12945  */
12946 
12947  maxobjects = 1;
12948  snprintf(name, MAX_COLNAME, "object_%d", maxobjects);
12949  while (cpl_table_has_column(objects, name)) {
12950  maxobjects++;
12951  snprintf(name, MAX_COLNAME, "object_%d", maxobjects);
12952  }
12953 
12954  is_sky = cpl_calloc(ny, sizeof(int));
12955 
12956  for (i = 0; i < nslits; i++) {
12957 
12958  if (length[i] == 0)
12959  continue;
12960 
12961  ylow = position[i] + margin;
12962  yhig = position[i] + length[i] - margin;
12963 
12964  for (j = ylow; j < yhig; j++)
12965  is_sky[j] = 1;
12966 
12967  for (j = 1; j < maxobjects; j++) {
12968  snprintf(name, MAX_COLNAME, "object_%d", j);
12969  if (cpl_table_is_valid(objects, name, i)) {
12970  snprintf(name, MAX_COLNAME, "start_%d", j);
12971  ylow = cpl_table_get_int(objects, name, i, NULL);
12972  snprintf(name, MAX_COLNAME, "end_%d", j);
12973  yhig = cpl_table_get_int(objects, name, i, NULL);
12974  for (k = ylow; k <= yhig; k++)
12975  is_sky[k] = 0;
12976  }
12977  }
12978 
12979 
12980  /*
12981  * Eliminate isolated sky points
12982  */
12983 
12984  ylow = position[i] + margin + 1;
12985  yhig = position[i] + length[i] - margin - 1;
12986 
12987  for (j = ylow; j < yhig; j++)
12988  if (is_sky[j])
12989  if (is_sky[j-1] == 0 && is_sky[j+1] == 0)
12990  is_sky[j] = 0;
12991 
12992  }
12993 
12994 
12995  /*
12996  * Determination of the sky map
12997  */
12998 
12999  skymap = cpl_image_new(nx, ny, CPL_TYPE_FLOAT);
13000 
13001  for (i = 0; i < nslits; i++) {
13002 
13003  if (length[i] == 0)
13004  continue;
13005 
13006  ylow = position[i];
13007  yhig = ylow + length[i];
13008 
13009  nsky = 0;
13010  for (j = ylow; j < yhig; j++)
13011  if (is_sky[j])
13012  nsky++;
13013 
13014  if (nsky > order + 1) {
13015  if (order) {
13016  points = cpl_vector_new(nsky);
13017  nsky = 0;
13018  for (j = ylow; j < yhig; j++) {
13019  if (is_sky[j]) {
13020  cpl_vector_set(points, nsky, j);
13021  nsky++;
13022  }
13023  }
13024 
13025  exslit = cpl_image_extract(spectra, 1, ylow+1, nx, yhig);
13026  xdata = cpl_image_get_data(exslit);
13027  values = cpl_vector_new(nsky);
13028 
13029  for (j = 0; j < nx; j++) {
13030  nsky = 0;
13031  for (k = ylow; k < yhig; k++) {
13032  if (is_sky[k]) {
13033  cpl_vector_set(values, nsky, xdata[j+(k-ylow)*nx]);
13034  nsky++;
13035  }
13036  }
13037 
13038  /*
13039  * Eliminate obvious outliers
13040  */
13041 
13042  median = cpl_vector_get_median_const(values);
13043  vdata = cpl_vector_get_data(values);
13044  pdata = cpl_vector_get_data(points);
13045  nbad = 0;
13046  for (k = 0; k < nsky; k++) {
13047  if (fabs(vdata[k] - median) < 100) {
13048  if (nbad) {
13049  vdata[k-nbad] = vdata[k];
13050  pdata[k-nbad] = pdata[k];
13051  }
13052  }
13053  else
13054  nbad++;
13055  }
13056 
13057  if (nsky == nbad)
13058  continue;
13059 
13060  if (nbad && nsky - nbad > order + 1) {
13061  keep_values = values;
13062  keep_points = points;
13063  values = cpl_vector_wrap(nsky-nbad, vdata);
13064  points = cpl_vector_wrap(nsky-nbad, pdata);
13065  }
13066 
13067  if (nsky - nbad > order + 1) {
13068 
13069  fit = cpl_polynomial_fit_1d_create(points, values,
13070  order, NULL);
13071 
13072  if (fit) {
13073  for (k = ylow; k < yhig; k++) {
13074  xdata[j+(k-ylow)*nx] =
13075  cpl_polynomial_eval_1d(fit, k, NULL);
13076  }
13077 
13078  cpl_polynomial_delete(fit);
13079  }
13080  else
13081  cpl_error_reset();
13082  }
13083  else {
13084  for (k = 0; k < nsky; k++) {
13085  xdata[j+k*nx] = median;
13086  }
13087  }
13088 
13089  if (nbad && nsky - nbad > order + 1) {
13090  cpl_vector_unwrap(values);
13091  cpl_vector_unwrap(points);
13092  values = keep_values;
13093  points = keep_points;
13094  }
13095 
13096  if (nbad) {
13097  nsky = 0;
13098  for (k = ylow; k < yhig; k++) {
13099  if (is_sky[k]) {
13100  cpl_vector_set(points, nsky, k);
13101  nsky++;
13102  }
13103  }
13104  }
13105 
13106  }
13107 
13108  cpl_vector_delete(values);
13109  cpl_vector_delete(points);
13110 
13111  cpl_image_copy(skymap, exslit, 1, ylow+1);
13112  cpl_image_delete(exslit);
13113 
13114  }
13115  else {
13116  exslit = cpl_image_extract(spectra, 1, ylow+1, nx, yhig);
13117  xdata = cpl_image_get_data(exslit);
13118  values = cpl_vector_new(nsky);
13119 
13120  for (j = 0; j < nx; j++) {
13121  nsky = 0;
13122  for (k = ylow; k < yhig; k++) {
13123  if (is_sky[k]) {
13124  cpl_vector_set(values, nsky, xdata[j+(k-ylow)*nx]);
13125  nsky++;
13126  }
13127  }
13128 
13129  median = cpl_vector_get_median_const(values);
13130 
13131  for (k = ylow; k < yhig; k++)
13132  xdata[j+(k-ylow)*nx] = median;
13133 
13134  }
13135 
13136  cpl_vector_delete(values);
13137 
13138  cpl_image_copy(skymap, exslit, 1, ylow+1);
13139  cpl_image_delete(exslit);
13140  }
13141  }
13142  else
13143  cpl_msg_warning(func, "Too few sky points in slit %d", i + 1);
13144  }
13145 
13146  cpl_free(is_sky);
13147 
13148  return skymap;
13149 
13150 }
13151 
13152 
13174 cpl_error_code mos_clean_cosmics(cpl_image *image, float gain,
13175  float threshold, float ratio)
13176 {
13177  const char *func = "mos_clean_cosmics";
13178 
13179  cpl_image *smoothImage;
13180  cpl_table *table;
13181  cpl_matrix *kernel;
13182  int *xdata;
13183  int *ydata;
13184  float *idata;
13185  float *sdata;
13186  float sigma, sum, value, smoothValue;
13187  double noise;
13188  int count;
13189  float fMax;
13190  int iMin, iMax, jMin, jMax, iPosMax, jPosMax;
13191  int xLen;
13192  int yLen;
13193  int nPix;
13194  int first = 1; /* position of first cosmic ray candidate
13195  encountered while scanning the image */
13196  int pos, i, j, k, l, ii, jj, iii = 0, jjj = 0;
13197  int numCosmic = 0;
13198  int found, foundContiguousCandidate;
13199  int *cosmic;
13200 
13201 
13202  if (image == NULL)
13203  return cpl_error_set(func, CPL_ERROR_NULL_INPUT);
13204 
13205 
13206  /*
13207  * "cosmic" is a flags holder (initialized to zero):
13208  *
13209  * -1 = candidate for cosmic ray
13210  * 0 = not a cosmic
13211  * 1 = a cosmic ray
13212  * 2 = member of current group of contiguous candidates
13213  * 3 = examined member of current group
13214  */
13215 
13216  xLen = cpl_image_get_size_x(image);
13217  yLen = cpl_image_get_size_y(image);
13218 
13219  if (xLen < 4 || yLen < 4)
13220  return CPL_ERROR_NONE;
13221 
13222  nPix = xLen * yLen;
13223 
13224  /*
13225  * Noise estimation from negative offsets in image. Note that this
13226  * assumes that the background level (skyLevel) has already been
13227  * subtracted from the data. In this way we estimate the noise due
13228  * to detector readout and to the background signal level (before
13229  * it were removed). Theoretically this is given by
13230  *
13231  * noise = sqrt(ron^2 + skyLevel/gain)
13232  *
13233  * where ron is the read-out-noise. To this we will sum the noise
13234  * contribution due to any increase of the signal above the background
13235  * by an amount scienceLevel. Theoretically the total noise is given by
13236  *
13237  * totalNoise = sqrt(ron^2 + (skyLevel+scienceLevel)/gain)
13238  *
13239  * that is
13240  *
13241  * totalNoise = sqrt(noise^2 + scienceLevel/gain)
13242  *
13243  */
13244 
13245  idata = cpl_image_get_data(image);
13246  noise = 0.0;
13247  count = 0;
13248 
13249  for (i = 0; i < nPix; i++) {
13250  if (idata[i] < -0.00001) {
13251  noise -= idata[i];
13252  count++;
13253  }
13254  }
13255 
13256  noise /= count;
13257  noise *= 1.25; /* Factor to convert average deviation to sigma */
13258 
13259  cosmic = cpl_calloc(nPix, sizeof(int));
13260 
13261  if (threshold < 0.)
13262  threshold = 4.0;
13263  if (ratio < 0.)
13264  ratio = 2.0;
13265 
13266  kernel = cpl_matrix_new(3, 3);
13267  cpl_matrix_fill(kernel, 1.0);
13268  cpl_matrix_set(kernel, 1, 1, 0.0);
13269  smoothImage = cpl_image_filter_median(image, kernel);
13270  cpl_matrix_delete(kernel);
13271 
13272  /*
13273  * Loop on images pixels, searching for cosmic rays candidates.
13274  * Border pixels are currently excluded (they cannot contain
13275  * candidates), to avoid that the search for groups of contiguous
13276  * pixels would ever go out of image boundaries. In future we may
13277  * overcome this limit, adding an appropriate check when contiguous
13278  * pixels are searched.
13279  */
13280 
13281  sdata = cpl_image_get_data(smoothImage);
13282 
13283  for (j = 1; j < yLen - 1; j++) {
13284  for (i = 1; i < xLen - 1; i++) {
13285  value = idata[i + j * xLen];
13286  smoothValue = sdata[i + j * xLen];
13287  if (smoothValue < 1.0)
13288  smoothValue = 1.0;
13289  sigma = sqrt(noise * noise + smoothValue / gain);
13290  if (value - smoothValue >= threshold * sigma)
13291  cosmic[i + j * xLen] = -1;
13292  }
13293  }
13294 
13295  cpl_image_delete(smoothImage);
13296 
13297 
13298  /*
13299  * Search for groups of contiguous cosmic rays candidates.
13300  */
13301 
13302  do {
13303  found = 0;
13304  for (pos = first; pos < nPix; pos++) {
13305  if (cosmic[pos] == -1) {
13306  cosmic[pos] = 2; /* Candidate found. */
13307  i = pos % xLen; /* Its coordinates. */
13308  j = pos / xLen;
13309  first = pos;
13310  first++; /* ??? really necessary? */
13311  found = 1;
13312  break;
13313  }
13314  }
13315 
13316  if (found) {
13317 
13318  /*
13319  * Determine new group of contiguous cosmic rays candidates.
13320  * Initialize the working box boundaries, iMin, iMax, jMin, jMax,
13321  * and the value of the max pixel and its position, fMax, iPosMax,
13322  * jPosMax.
13323  */
13324 
13325  iMin = iMax = iPosMax = i;
13326  jMin = jMax = jPosMax = j;
13327  fMax = idata[i + j * xLen];
13328 
13329  do {
13330  foundContiguousCandidate = 0;
13331  for (l = 0; l <= 1; l++) {
13332  for (k = 0; k <= 1; k++) {
13333 
13334  /*
13335  * Looping on 4 pixels to North, East, South and West
13336  */
13337 
13338  ii = i + k - l;
13339  jj = j + k + l - 1;
13340  if (cosmic[ii + jj * xLen] == -1) {
13341  foundContiguousCandidate = 1;
13342  cosmic[ii + jj * xLen] = 2;
13343  /* Candidate belongs to current group */
13344  iii = ii; /* Keep its position */
13345  jjj = jj;
13346 
13347  /*
13348  * Upgrade search box
13349  */
13350 
13351  if (ii < iMin)
13352  iMin = ii;
13353  if (ii > iMax)
13354  iMax = ii;
13355  if (jj < jMin)
13356  jMin = jj;
13357  if (jj > jMax)
13358  jMax = jj;
13359 
13360  if (idata[ii + jj * xLen] > fMax) {
13361  fMax = idata[ii + jj * xLen];
13362  iPosMax = ii;
13363  jPosMax = jj;
13364  }
13365  }
13366  }
13367  }
13368 
13369  /*
13370  * We are done exploring the "cross". Now mark as "examined"
13371  * the current candidate (at the center of the cross):
13372  */
13373 
13374  cosmic[i + j * xLen] = 3; /* It may probably be set to 1 now */
13375 
13376  if (foundContiguousCandidate) {
13377 
13378  /*
13379  * Pass (arbitrarily) the coordinates of the LAST found
13380  * candidate
13381  */
13382 
13383  i = iii;
13384  j = jjj;
13385 
13386  /*
13387  * Skip the rest, continue loop on new candidate
13388  */
13389 
13390  continue;
13391  }
13392 
13393 
13394  /*
13395  * Look for leftovers in the (growing!) search box
13396  */
13397 
13398  for (l = jMin; l <= jMax; l++) {
13399  for (k = iMin; k <= iMax; k++) {
13400  if (cosmic[k + l * xLen] == 2) {
13401  i = k;
13402  j = l;
13403  foundContiguousCandidate = 1;
13404  break;
13405  }
13406  }
13407  if (foundContiguousCandidate)
13408  break;
13409  }
13410  } while (foundContiguousCandidate);
13411 
13412 
13413  /*
13414  * No more contiguous candidates are found. Decide now
13415  * whether the current group is a cosmic ray or not.
13416  */
13417 
13418  sum = 0.; /* Sum of 8 pixels around max position */
13419  for (l = -1; l <= 1; l++) {
13420  for (k = -1; k <= 1; k++) {
13421  if (l != 0 || k != 0) {
13422  sum += idata[iPosMax + k + (jPosMax + l) * xLen];
13423  }
13424  }
13425  }
13426 
13427  sum /= 8.;
13428  if (fMax > ratio * sum) {
13429  for (l = jMin - 1; l <= jMax + 1; l++) {
13430  for (k = iMin - 1; k <= iMax + 1; k++) {
13431  if (cosmic[k + l * xLen] == 3) {
13432  cosmic[k + l * xLen] = 1;
13433  numCosmic++;
13434  }
13435  }
13436  }
13437  }
13438  else {
13439  for (l = jMin - 1; l <= jMax + 1; l++) {
13440  for (k = iMin - 1; k <= iMax + 1; k++) {
13441  if (cosmic[k + l * xLen] != -1) {
13442  if (cosmic[k + l * xLen] == 1)
13443  numCosmic--;
13444  cosmic[k + l * xLen] = 0;
13445  }
13446  }
13447  }
13448  }
13449  }
13450  } while (found);
13451 
13452 
13453  /*
13454  * Prepare table containing cosmic rays coordinates.
13455  */
13456 
13457  table = cpl_table_new(numCosmic);
13458  cpl_table_new_column(table, "x", CPL_TYPE_INT);
13459  cpl_table_new_column(table, "y", CPL_TYPE_INT);
13460  cpl_table_set_column_unit(table, "x", "pixel");
13461  cpl_table_set_column_unit(table, "y", "pixel");
13462  xdata = cpl_table_get_data_int(table, "x");
13463  ydata = cpl_table_get_data_int(table, "y");
13464 
13465  for (pos = 0, i = 0; pos < nPix; pos++) {
13466  if (cosmic[pos] == 1) {
13467  xdata[i] = (pos % xLen);
13468  ydata[i] = (pos / xLen);
13469  i++;
13470  }
13471  }
13472 
13473  mos_clean_bad_pixels(image, table, 1);
13474 
13475  cpl_free(cosmic);
13476  cpl_table_delete(table);
13477 
13478  return CPL_ERROR_NONE;
13479 
13480 }
13481 
13482 
13483 cpl_error_code mos_clean_bad_pixels(cpl_image *image, cpl_table *table,
13484  int spectral)
13485 {
13486  const char *func = "mos_clean_cosmics";
13487 
13488  float *idata;
13489  int *isBadPix;
13490  int i, j, k, d;
13491  int xlen, ylen, totPix;
13492  int nBadPixels = 0;
13493  int sign, foundFirst;
13494  int *xValue = NULL;
13495  int *yValue = NULL;
13496  float save = 0.;
13497  double sumd;
13498  int cx, cy;
13499  int nPairs;
13500  float estimate[4];
13501  int sx[] = {0, 1, 1, 1};
13502  int sy[] = {1,-1, 0, 1};
13503  int searchHorizon = 100;
13504  int percent = 15;
13505 
13506 
13507  if (image == NULL || table == NULL)
13508  return cpl_error_set(func, CPL_ERROR_NULL_INPUT);
13509 
13510  if (1 != cpl_table_has_column(table, "x"))
13511  return cpl_error_set(func, CPL_ERROR_DATA_NOT_FOUND);
13512 
13513  if (1 != cpl_table_has_column(table, "y"))
13514  return cpl_error_set(func, CPL_ERROR_DATA_NOT_FOUND);
13515 
13516  if (CPL_TYPE_INT != cpl_table_get_column_type(table, "x"))
13517  return cpl_error_set(func, CPL_ERROR_INVALID_TYPE);
13518 
13519  if (CPL_TYPE_INT != cpl_table_get_column_type(table, "y"))
13520  return cpl_error_set(func, CPL_ERROR_INVALID_TYPE);
13521 
13522  nBadPixels = cpl_table_get_nrow(table);
13523 
13524  if (nBadPixels) {
13525  xlen = cpl_image_get_size_x(image);
13526  ylen = cpl_image_get_size_y(image);
13527  idata = cpl_image_get_data(image);
13528  totPix = xlen * ylen;
13529  if (((float) nBadPixels) / ((float) totPix) < percent/100.) {
13530  isBadPix = cpl_calloc(totPix, sizeof(int));
13531  }
13532  else {
13533  cpl_msg_warning(func, "Too many bad pixels (> %d%%): "
13534  "skip bad pixel correction", percent);
13535  return cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
13536  }
13537  }
13538  else {
13539  cpl_msg_debug(func, "No pixel values to interpolate");
13540  return CPL_ERROR_NONE;
13541  }
13542 
13543  xValue = cpl_table_get_data_int(table, "x");
13544  yValue = cpl_table_get_data_int(table, "y");
13545 
13546  for (i = 0; i < nBadPixels; i++)
13547  isBadPix[xValue[i] + yValue[i] * xlen] = 1;
13548 
13549  for (i = 0; i < nBadPixels; i++) {
13550 
13551  /*
13552  * Search for the closest good pixel along the 4 fundamental
13553  * directions (in both senses):
13554  * \ | /
13555  * \|/
13556  * --- ---
13557  * /|\
13558  * / | \
13559  *
13560  * Then collect pairs of values to interpolate linearly.
13561  */
13562 
13563  nPairs = 0;
13564  for (j = 0; j < 4; j++) {
13565 
13566  if (spectral) /* Just horizontal interpolation for spectral data */
13567  if (j != 2)
13568  continue;
13569 
13570  estimate[nPairs] = 0.; /* Pairs interpolations are stored here */
13571  sumd = 0.;
13572  foundFirst = 0;
13573  for (k = 0; k < 2; k++) {
13574  sign = 2 * k - 1;
13575  d = 0;
13576  cx = xValue[i];
13577  cy = yValue[i];
13578  do {
13579  cx += sign * sx[j];
13580  cy += sign * sy[j];
13581  if (cx < 0 || cx >= xlen || cy < 0 || cy >= ylen)
13582  break;
13583  d++;
13584  } while (isBadPix[cx + cy * xlen] && d < searchHorizon);
13585 
13586  if (cx >= 0 && cx < xlen &&
13587  cy >= 0 && cy < ylen && d < searchHorizon) {
13588 
13589  /*
13590  * In this block is cripted the linear interpolation...
13591  */
13592 
13593  save = idata[cx + cy * xlen];
13594  estimate[nPairs] += save / d;
13595  sumd += 1. / (double) d;
13596  if (k) {
13597  estimate[nPairs] /= sumd;
13598  nPairs++;
13599  }
13600  else {
13601  foundFirst = 1;
13602  }
13603  }
13604  else {
13605 
13606  /*
13607  * Image borders were crossed, incomplete pair of values
13608  */
13609 
13610  if (k) {
13611  if (foundFirst) {
13612  estimate[nPairs] = save;
13613  nPairs++;
13614  }
13615  }
13616  }
13617  }
13618  }
13619 
13620  /*
13621  * Replace pixel value of the input image, corresponding to
13622  * the current bad pixel, with the median of the estimates
13623  * resulted from the 4 linear interpolations.
13624  */
13625 
13626  if (nPairs > 2) {
13627  idata[xValue[i] + yValue[i] * xlen] =
13628  cpl_tools_get_median_float(estimate, nPairs);
13629  }
13630  else if (nPairs == 2) {
13631  idata[xValue[i] + yValue[i] * xlen] =
13632  (estimate[0] + estimate[1]) / 2.;
13633  }
13634  else if (nPairs == 1) {
13635  idata[xValue[i] + yValue[i] * xlen] = estimate[0];
13636  }
13637  else {
13638  cpl_msg_debug(func, "Cannot correct bad pixel %d,%d\n",
13639  xValue[i], yValue[i]);
13640  }
13641  }
13642 
13643  cpl_free(isBadPix);
13644 
13645  return CPL_ERROR_NONE;
13646 }
13647 
13648 
13678 cpl_image *mos_spatial_map(cpl_image *spectra, cpl_table *slits,
13679  cpl_table *polytraces, double reference,
13680  double blue, double red, double dispersion)
13681 {
13682  const char *func = "mos_spatial_map";
13683 
13684  const char *clab[6] = {"c0", "c1", "c2", "c3", "c4", "c5"};
13685  /* Max order is 5 */
13686  cpl_polynomial *polytop;
13687  cpl_polynomial *polybot;
13688  cpl_image *calibration;
13689  float *data;
13690  double top, bot;
13691  double coeff;
13692  double ytop, ybot;
13693  double ypos, yfra;
13694  double factor;
13695  int yint, yprev;
13696  int nslits;
13697  int npseudo;
13698  int *slit_id;
13699  int *length;
13700  int nx, ny;
13701  int pixel_above, pixel_below, refpixel, start_pixel, end_pixel;
13702  int missing_top, missing_bot;
13703  int null;
13704  int order;
13705  int i, j;
13706  cpl_size k;
13707 
13708 
13709  if (spectra == NULL || slits == NULL || polytraces == NULL) {
13710  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
13711  return NULL;
13712  }
13713 
13714  if (dispersion <= 0.0) {
13715  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
13716  return NULL;
13717  }
13718 
13719  if (red - blue < dispersion) {
13720  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
13721  return NULL;
13722  }
13723 
13724  nx = cpl_image_get_size_x(spectra);
13725  ny = cpl_image_get_size_y(spectra);
13726 
13727  calibration = cpl_image_new(nx, ny, CPL_TYPE_FLOAT);
13728  data = cpl_image_get_data(calibration);
13729 
13730  length = cpl_table_get_data_int(slits, "length");
13731  nslits = cpl_table_get_nrow(slits);
13732  slit_id = cpl_table_get_data_int(slits, "slit_id");
13733  order = cpl_table_get_ncol(polytraces) - 2;
13734 
13735  /*
13736  * The spatial resampling is performed for a certain number of
13737  * pixels above and below the position of the reference wavelength:
13738  */
13739 
13740  pixel_above = STRETCH_FACTOR * (red - reference) / dispersion;
13741  pixel_below = STRETCH_FACTOR * (reference - blue) / dispersion;
13742 
13743  for (i = 0; i < nslits; i++) {
13744 
13745  if (length[i] == 0)
13746  continue;
13747 
13748  /*
13749  * Note that the x coordinate of the reference pixels on the CCD
13750  * is taken arbitrarily at the top end of each slit. This wouldn't
13751  * be entirely correct in case of curved slits, or in presence of
13752  * heavy distortions: in such cases the spatial resampling is
13753  * really performed across a wide range of wavelengths. But
13754  * the lag between top and bottom spectral curvature models
13755  * would introduce even in such cases negligible effects on
13756  * the spectral spatial resampling.
13757  */
13758 
13759  refpixel = cpl_table_get_double(slits, "xtop", i, NULL);
13760 
13761  start_pixel = refpixel - pixel_below;
13762  if (start_pixel < 0)
13763  start_pixel = 0;
13764 
13765  end_pixel = refpixel + pixel_above;
13766  if (end_pixel > nx)
13767  end_pixel = nx;
13768 
13769  /*
13770  * Recover from the table of spectral curvature coefficients
13771  * the curvature polynomials.
13772  */
13773 
13774  missing_top = 0;
13775  polytop = cpl_polynomial_new(1);
13776  for (k = 0; k <= order; k++) {
13777  coeff = cpl_table_get_double(polytraces, clab[k], 2*i, &null);
13778  if (null) {
13779  cpl_polynomial_delete(polytop);
13780  missing_top = 1;
13781  break;
13782  }
13783  cpl_polynomial_set_coeff(polytop, &k, coeff);
13784  }
13785 
13786  missing_bot = 0;
13787  polybot = cpl_polynomial_new(1);
13788  for (k = 0; k <= order; k++) {
13789  coeff = cpl_table_get_double(polytraces, clab[k], 2*i+1, &null);
13790  if (null) {
13791  cpl_polynomial_delete(polybot);
13792  missing_bot = 1;
13793  break;
13794  }
13795  cpl_polynomial_set_coeff(polybot, &k, coeff);
13796  }
13797 
13798  if (missing_top && missing_bot) {
13799  cpl_msg_warning(func, "Spatial map, slit %d was not traced!",
13800  slit_id[i]);
13801  continue;
13802  }
13803 
13804  /*
13805  * In case just one of the two edges was not traced, the other
13806  * edge curvature model is duplicated and shifted to the other
13807  * end of the slit: better than nothing!
13808  */
13809 
13810  if (missing_top) {
13811  cpl_msg_warning(func, "Upper edge of slit %d was not traced: "
13812  "the spectral curvature of the lower edge "
13813  "is used instead.", slit_id[i]);
13814  polytop = cpl_polynomial_duplicate(polybot);
13815  ytop = cpl_table_get_double(slits, "ytop", i, NULL);
13816  ybot = cpl_table_get_double(slits, "ybottom", i, NULL);
13817  k = 0;
13818  coeff = cpl_polynomial_get_coeff(polybot, &k);
13819  coeff += ytop - ybot;
13820  cpl_polynomial_set_coeff(polytop, &k, coeff);
13821  }
13822 
13823  if (missing_bot) {
13824  cpl_msg_warning(func, "Lower edge of slit %d was not traced: "
13825  "the spectral curvature of the upper edge "
13826  "is used instead.", slit_id[i]);
13827  polybot = cpl_polynomial_duplicate(polytop);
13828  ytop = cpl_table_get_double(slits, "ytop", i, NULL);
13829  ybot = cpl_table_get_double(slits, "ybottom", i, NULL);
13830  k = 0;
13831  coeff = cpl_polynomial_get_coeff(polytop, &k);
13832  coeff -= ytop - ybot;
13833  cpl_polynomial_set_coeff(polybot, &k, coeff);
13834  }
13835 
13836  top = cpl_polynomial_eval_1d(polytop, refpixel, NULL);
13837  bot = cpl_polynomial_eval_1d(polybot, refpixel, NULL);
13838  npseudo = ceil(top-bot) + 1;
13839 
13840  if (npseudo < 1) {
13841  cpl_polynomial_delete(polytop);
13842  cpl_polynomial_delete(polybot);
13843  cpl_msg_warning(func, "Slit %d was badly traced: no extraction!",
13844  slit_id[i]);
13845  continue;
13846  }
13847 
13848  for (j = start_pixel; j < end_pixel; j++) {
13849  top = cpl_polynomial_eval_1d(polytop, j, NULL);
13850  bot = cpl_polynomial_eval_1d(polybot, j, NULL);
13851  factor = (top-bot)/npseudo;
13852  for (k = 0; k <= npseudo; k++) {
13853  ypos = top - k*factor;
13854  yint = ypos;
13855  yfra = ypos - yint;
13856  if (yint >= 0 && yint < ny-1) {
13857  data[j + nx*yint] = (top-yint)/factor;
13858  if (k) {
13859 
13860  /*
13861  * This is added to recover lost pixels on
13862  * the CCD image (pixels are lost because
13863  * the CCD pixels are less than npseudo+1).
13864  */
13865 
13866  if (yprev - yint > 1) {
13867  data[j + nx*(yint+1)] = (top-yint-1)/factor;
13868  }
13869  }
13870  }
13871  yprev = yint;
13872  }
13873  }
13874  cpl_polynomial_delete(polytop);
13875  cpl_polynomial_delete(polybot);
13876  }
13877 
13878  return calibration;
13879 }
13880 
13881 
13944 cpl_image *mos_detect_objects(cpl_image *image, cpl_table *slits, int margin,
13945  int maxradius, int conradius)
13946 {
13947  const char *func = "mos_detect_objects";
13948 
13949  cpl_image *profile;
13950  float *pdata;
13951  float *p;
13952 
13953  char name[MAX_COLNAME];
13954 
13955  int nslits;
13956  int npeaks;
13957  int nobjects, objpos, totobj;
13958  int maxobjects;
13959  int *position;
13960  int *length;
13961  int *reject;
13962  double *place;
13963  double *bright;
13964  double mindistance;
13965  int pos, count;
13966  int up;
13967  int low, hig;
13968  int row;
13969  int i, j, k;
13970 
13971  const int min_pixels = 10;
13972 
13973 
13974  if (cpl_error_get_code() != CPL_ERROR_NONE)
13975  return NULL;
13976 
13977  if (image == NULL || slits == NULL) {
13978  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
13979  return NULL;
13980  }
13981 
13982  if (margin < 0)
13983  margin = 0;
13984 
13985  if (maxradius < 0) {
13986  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
13987  return NULL;
13988  }
13989 
13990  if (conradius < 0) {
13991  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
13992  return NULL;
13993  }
13994 
13995  nslits = cpl_table_get_nrow(slits);
13996  position = cpl_table_get_data_int(slits, "position");
13997  length = cpl_table_get_data_int(slits, "length");
13998 
13999  profile = cpl_image_collapse_create(image, 1);
14000  cpl_image_divide_scalar(profile, cpl_image_get_size_x(image));
14001  pdata = cpl_image_get_data(profile);
14002 
14003  row = 1;
14004  maxobjects = 0;
14005  totobj = 0;
14006  for (i = 0; i < nslits; i++) {
14007 
14008  if (length[i] == 0)
14009  continue;
14010 
14011  pos = position[i] + margin;
14012  count = length[i] - 2*margin;
14013 
14014  if (count < min_pixels)
14015  continue;
14016 
14017  p = pdata + pos;
14018 
14019 
14020  /*
14021  * Count peaks candidates
14022  */
14023 
14024  npeaks = 0;
14025  if (p[0] > p[1] && p[1] > p[2] && p[2] > p[3] && p[3] > 0) {
14026  npeaks++;
14027  }
14028 
14029  up = 0;
14030  for (j = 0; j < count - 3; j++) {
14031  if (p[j] > 0) {
14032  if (p[j+1] > p[j]) {
14033  up++;
14034  }
14035  else {
14036  if (up > 2) {
14037  if (p[j+1] > p[j+2] && p[j+2] > 0) {
14038  if (p[j] > 5)
14039  npeaks++;
14040  }
14041  }
14042  else if (up > 1) {
14043  if (p[j+1] > p[j+2] && p[j+2] > p[j+3] && p[j+3] > 0) {
14044  if (p[j] > 5)
14045  npeaks++;
14046  }
14047  }
14048  up = 0;
14049  }
14050  }
14051  else {
14052  up = 0;
14053  }
14054  }
14055 
14056  if (p[count-1] > p[count-2] && p[count-2] > p[count-3]
14057  && p[count-3] > p[count-4] && p[count-4] > 0) {
14058  npeaks++;
14059  }
14060 
14061  if (npeaks == 0)
14062  continue;
14063 
14064 
14065  /*
14066  * Get candidates parameters
14067  */
14068 
14069  reject = cpl_calloc(npeaks, sizeof(int));
14070  bright = cpl_calloc(npeaks, sizeof(double));
14071  place = cpl_calloc(npeaks, sizeof(double));
14072 
14073  npeaks = 0;
14074  if (p[0] > p[1] && p[1] > p[2] && p[2] > p[3] && p[3] > 0) {
14075  bright[0] = p[0];
14076  place[0] = position[i] + margin;
14077  npeaks++;
14078  }
14079 
14080  up = 0;
14081  for (j = 0; j < count - 3; j++) {
14082  if (p[j] > 0) {
14083  if (p[j+1] > p[j]) {
14084  up++;
14085  }
14086  else {
14087  if (up > 2) {
14088  if (p[j+1] > p[j+2] && p[j+2] > 0) {
14089  if (p[j] > 5) {
14090  bright[npeaks] = p[j];
14091  place[npeaks] = position[i] + margin + j + 1
14092  + values_to_dx(p[j-1], p[j], p[j+1]);
14093  npeaks++;
14094  }
14095  }
14096  }
14097  else if (up > 1) {
14098  if (p[j+1] > p[j+2] && p[j+2] > p[j+3] && p[j+3] > 0) {
14099  if (p[j] > 5) {
14100  bright[npeaks] = p[j];
14101  place[npeaks] = position[i] + margin + j + 1
14102  + values_to_dx(p[j-1], p[j], p[j+1]);
14103  npeaks++;
14104  }
14105  }
14106  }
14107  up = 0;
14108  }
14109  }
14110  else {
14111  up = 0;
14112  }
14113  }
14114 
14115  if (p[count-1] > p[count-2] && p[count-2] > p[count-3]
14116  && p[count-3] > p[count-4] && p[count-4] > 0) {
14117  bright[npeaks] = p[count-1];
14118  place[npeaks] = position[i] + count;
14119  npeaks++;
14120  }
14121 
14122 
14123  /*
14124  * Now select the uncontaminated peaks
14125  */
14126 
14127  if (fabs(place[0] - pos) < 1.0)
14128  reject[0] = 1;
14129  if (fabs(place[npeaks-1] - pos - count) < 1.0)
14130  reject[npeaks-1] = 1;
14131  for (j = 0; j < npeaks; j++) {
14132  for (k = 0; k < npeaks; k++) {
14133  if (k == j)
14134  continue;
14135  mindistance = conradius * bright[k] / bright[j]
14136  * bright[k] / bright[j];
14137  if (fabs(place[j] - place[k]) < mindistance)
14138  reject[j] = 1;
14139  }
14140  }
14141 
14142 /* new part */
14143  for (j = 0; j < npeaks; j++) {
14144  if (reject[j])
14145  continue;
14146  if (j) {
14147  low = (place[j-1]*bright[j] + place[j]*bright[j-1])
14148  / (bright[j-1] + bright[j]) + 1;
14149  }
14150  else {
14151  low = pos;
14152  }
14153  if (j < npeaks - 1) {
14154  hig = (place[j+1]*bright[j] + place[j]*bright[j+1])
14155  / (bright[j+1] + bright[j]) + 1;
14156  }
14157  else {
14158  hig = pos + count;
14159  }
14160 
14161  if (low < pos)
14162  low = pos;
14163  if (hig > pos + count)
14164  hig = pos + count;
14165  if (place[j] - low > maxradius)
14166  low = place[j] - maxradius;
14167  if (hig - place[j] > maxradius)
14168  hig = place[j] + maxradius;
14169  if (hig == low)
14170  reject[j] = 1;
14171  }
14172 /* end new part */
14173 
14174  nobjects = npeaks;
14175  for (j = 0; j < npeaks; j++)
14176  if (reject[j])
14177  nobjects--;
14178 
14179  for (j = 0; j < nobjects; j++) {
14180  snprintf(name, MAX_COLNAME, "object_%d", j+1);
14181  if (cpl_table_has_column(slits, name))
14182  continue;
14183  cpl_table_new_column(slits, name, CPL_TYPE_DOUBLE);
14184  snprintf(name, MAX_COLNAME, "start_%d", j+1);
14185  cpl_table_new_column(slits, name, CPL_TYPE_INT);
14186  cpl_table_set_column_unit(slits, name, "pixel");
14187  snprintf(name, MAX_COLNAME, "end_%d", j+1);
14188  cpl_table_new_column(slits, name, CPL_TYPE_INT);
14189  cpl_table_set_column_unit(slits, name, "pixel");
14190  snprintf(name, MAX_COLNAME, "row_%d", j+1);
14191  cpl_table_new_column(slits, name, CPL_TYPE_INT);
14192  cpl_table_set_column_unit(slits, name, "pixel");
14193  }
14194 
14195  objpos = nobjects;
14196  for (j = 0; j < npeaks; j++) {
14197  if (reject[j])
14198  continue;
14199  if (j) {
14200  low = (place[j-1]*bright[j] + place[j]*bright[j-1])
14201  / (bright[j-1] + bright[j]) + 1;
14202  }
14203  else {
14204  low = pos;
14205  }
14206  if (j < npeaks - 1) {
14207  hig = (place[j+1]*bright[j] + place[j]*bright[j+1])
14208  / (bright[j+1] + bright[j]) + 1;
14209  }
14210  else {
14211  hig = pos + count;
14212  }
14213 
14214  if (low < pos)
14215  low = pos;
14216  if (hig > pos + count)
14217  hig = pos + count;
14218  if (place[j] - low > maxradius)
14219  low = place[j] - maxradius;
14220  if (hig - place[j] > maxradius)
14221  hig = place[j] + maxradius;
14222 
14223  snprintf(name, MAX_COLNAME, "object_%d", objpos);
14224  cpl_table_set_double(slits, name, i, place[j]);
14225  snprintf(name, MAX_COLNAME, "start_%d", objpos);
14226  cpl_table_set_int(slits, name, i, low);
14227  snprintf(name, MAX_COLNAME, "end_%d", objpos);
14228  cpl_table_set_int(slits, name, i, hig);
14229  snprintf(name, MAX_COLNAME, "row_%d", objpos);
14230  cpl_table_set_int(slits, name, i, row + objpos - 1);
14231  totobj++;
14232  objpos--;
14233  }
14234 
14235  row += nobjects;
14236 
14237  if (maxobjects < nobjects)
14238  maxobjects = nobjects;
14239 
14240  cpl_free(reject);
14241  cpl_free(bright);
14242  cpl_free(place);
14243 
14244  }
14245 
14246 /* nobjects = row - nobjects; A bug, I think... */
14247  row = cpl_table_get_nrow(slits);
14248 
14249  for (i = 0; i < row; i++) {
14250  for (j = 0; j < maxobjects; j++) {
14251  snprintf(name, MAX_COLNAME, "row_%d", j+1);
14252  if (cpl_table_is_valid(slits, name, i))
14253  cpl_table_set_int(slits, name, i, totobj -
14254  cpl_table_get_int(slits, name, i, NULL));
14255  }
14256  }
14257 
14258  for (i = 0; i < maxobjects; i++) {
14259  snprintf(name, MAX_COLNAME, "start_%d", i+1);
14260  cpl_table_fill_invalid_int(slits, name, -1);
14261  snprintf(name, MAX_COLNAME, "end_%d", i+1);
14262  cpl_table_fill_invalid_int(slits, name, -1);
14263  snprintf(name, MAX_COLNAME, "row_%d", i+1);
14264  cpl_table_fill_invalid_int(slits, name, -1);
14265  }
14266 
14267  return profile;
14268 }
14269 
14270 
14295 cpl_image **mos_extract_objects(cpl_image *science, cpl_image *science_var,
14296  cpl_image *sky,
14297  cpl_table *objects, int extraction, double ron,
14298  double gain, int ncombined)
14299 {
14300  const char *func = "mos_extract_objects";
14301 
14302  char name[MAX_COLNAME];
14303 
14304  cpl_image **output;
14305  cpl_image *extracted;
14306  cpl_image *extr_sky;
14307  cpl_image *error;
14308  cpl_image *sciwin;
14309  cpl_image *sci_var_win = NULL;
14310  cpl_image *skywin;
14311  int nslits;
14312  int nobjects;
14313  int maxobjects;
14314  int nx;
14315  int ylow, yhig;
14316  int i, j;
14317 
14318 
14319  if (science == NULL || sky == NULL) {
14320  cpl_msg_error(func, "Both scientific exposures are required in input");
14321  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
14322  return NULL;
14323  }
14324 
14325  if (objects == NULL) {
14326  cpl_msg_error(func, "An object table is required in input");
14327  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
14328  return NULL;
14329  }
14330 
14331  if (extraction < 0 || extraction > 1) {
14332  cpl_msg_error(func, "Invalid extraction mode (%d): it should be "
14333  "either 0 or 1", extraction);
14334  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
14335  return NULL;
14336  }
14337 
14338  if (ron < 0.0) {
14339  cpl_msg_error(func, "Invalid read-out-noise (%f ADU)", ron);
14340  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
14341  return NULL;
14342  }
14343 
14344  if (gain < 0.1) {
14345  cpl_msg_error(func, "Invalid gain factor (%f e-/ADU)", gain);
14346  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
14347  return NULL;
14348  }
14349 
14350  if (ncombined < 1) {
14351  cpl_msg_error(func, "Invalid number of combined frames (%d): "
14352  "it should be at least 1", ncombined);
14353  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
14354  return NULL;
14355  }
14356 
14357 
14358  /*
14359  * Count the max number of objects per slit. Note that maxobjects
14360  * is intentionally the max number of objects increased by one.
14361  */
14362 
14363  maxobjects = 1;
14364  snprintf(name, MAX_COLNAME, "object_%d", maxobjects);
14365  while (cpl_table_has_column(objects, name)) {
14366  maxobjects++;
14367  snprintf(name, MAX_COLNAME, "object_%d", maxobjects);
14368  }
14369 
14370 
14371  /*
14372  * Count objects to extract
14373  */
14374 
14375  nobjects = 0;
14376  nslits = cpl_table_get_nrow(objects);
14377 
14378  for (i = 0; i < nslits; i++) {
14379  for (j = 1; j < maxobjects; j++) {
14380  snprintf(name, MAX_COLNAME, "object_%d", j);
14381  if (cpl_table_is_valid(objects, name, i))
14382  nobjects++;
14383  }
14384  }
14385 
14386  if (nobjects == 0)
14387  return NULL;
14388 
14389  nx = cpl_image_get_size_x(science);
14390 
14391  output = cpl_calloc(3, sizeof(cpl_image *));
14392  extracted = output[0] = cpl_image_new(nx, nobjects, CPL_TYPE_FLOAT);
14393  extr_sky = output[1] = cpl_image_new(nx, nobjects, CPL_TYPE_FLOAT);
14394  error = output[2] = cpl_image_new(nx, nobjects, CPL_TYPE_FLOAT);
14395 
14396 
14397  /*
14398  * Extract objects
14399  */
14400 
14401  nobjects = 0;
14402  for (i = 0; i < nslits; i++) {
14403  for (j = 1; j < maxobjects; j++) {
14404  snprintf(name, MAX_COLNAME, "object_%d", j);
14405  if (cpl_table_is_valid(objects, name, i)) {
14406  snprintf(name, MAX_COLNAME, "start_%d", j);
14407  ylow = cpl_table_get_int(objects, name, i, NULL);
14408  snprintf(name, MAX_COLNAME, "end_%d", j);
14409  yhig = cpl_table_get_int(objects, name, i, NULL);
14410  snprintf(name, MAX_COLNAME, "row_%d", j);
14411  nobjects = cpl_table_get_int(objects, name, i, NULL);
14412  sciwin = cpl_image_extract(science, 1, ylow+1, nx, yhig);
14413  if(science_var != NULL)
14414  sci_var_win = cpl_image_extract(science_var, 1, ylow+1, nx, yhig);
14415  skywin = cpl_image_extract(sky, 1, ylow+1, nx, yhig);
14416 /*
14417  * Cleaning the cosmics locally was really NOT a good idea...
14418  * I leave it here, commented out, to never forget this mistake!
14419 
14420  if (extraction) {
14421  mos_clean_cosmics(sciwin, gain, -1., -1.);
14422  }
14423  */
14424  mos_extraction(sciwin, sci_var_win, skywin, extracted, extr_sky, error,
14425  nobjects, extraction, ron, gain, ncombined);
14426 
14427  /*
14428  * Hidden check whether the spectrum was saturated or not
14429  */
14430 
14431  {
14432  cpl_image *total = cpl_image_add_create(sciwin, skywin);
14433  float *data = cpl_image_get_data_float(total);
14434  int size = cpl_image_get_size_x(total)
14435  * cpl_image_get_size_y(total);
14436  int k;
14437  char *saturation_level = getenv("SATURATION_LEVEL");
14438  float saturation = 62000.0;
14439  char *max_saturated = getenv("MAX_SATURATED");
14440  int max_satur = 10;
14441  int saturated;
14442 
14443  if (saturation_level)
14444  saturation = atof(saturation_level);
14445 
14446  if (max_saturated)
14447  max_satur = atoi(max_saturated);
14448 
14449  saturated = 0;
14450  for (k = 0; k < size; k++) {
14451  if (data[k] > saturation) {
14452  saturated++;
14453  if (saturated > max_satur) {
14454  break;
14455  }
14456  }
14457  }
14458 
14459  if (saturated > max_satur)
14460  saturated = 1;
14461  else
14462  saturated = 0;
14463 
14464  data = cpl_image_get_data(extracted);
14465  data[nobjects * nx] = saturated;
14466  }
14467 
14468  cpl_image_delete(sciwin);
14469  cpl_image_delete(skywin);
14470  nobjects++;
14471  }
14472  }
14473  }
14474 
14475  return output;
14476 
14477 }
14478 
14479 
14502 int mos_spectral_resolution(cpl_image *image, double lambda, double startwave,
14503  double dispersion, int saturation,
14504  double *mfwhm, double *rmsfwhm,
14505  double *resolution, double *rmsres, int *nlines)
14506 {
14507  cpl_vector *vector;
14508 
14509  int i, j, n, m;
14510  int position, maxpos;
14511  int xlen, ylen;
14512  int sp, ep;
14513  int radius;
14514  int sradius = 40;
14515  int threshold = 250; /* Peak must be so many ADUs above min */
14516 
14517  int ifwhm;
14518  double fwhm;
14519  double *buffer;
14520  double min, max, halfmax;
14521  double cut = 1.5; /* To cut outliers from FWHM values (pixel) */
14522  double value, rms;
14523 
14524  float *data;
14525 
14526 
14527  *resolution = 0.0;
14528  *rmsres = 0.0;
14529  *nlines = 0;
14530 
14531  xlen = cpl_image_get_size_x(image);
14532  ylen = cpl_image_get_size_y(image);
14533  data = cpl_image_get_data(image);
14534 
14535  buffer = cpl_malloc(ylen * sizeof(double));
14536 
14537  /*
14538  * Closest pixel to specified wavelength.
14539  */
14540 
14541  position = floor((lambda - startwave) / dispersion + 0.5);
14542 
14543  sp = position - sradius;
14544  ep = position + sradius;
14545 
14546  if (sp < 0 || ep > xlen) {
14547  cpl_free(buffer);
14548  return 0;
14549  }
14550 
14551  for (i = 0, n = 0; i < ylen; i++) { /* For each row of each slit */
14552 
14553  /*
14554  * Search interval for peak. Abort if too close to image border.
14555  */
14556 
14557  radius = mos_lines_width(data + i*xlen + position - sradius,
14558  2*sradius + 1);
14559  if (radius < 5)
14560  radius = 5;
14561 
14562  sp = position - radius;
14563  ep = position + radius;
14564 
14565  if (sp < 0 || ep > xlen) {
14566  cpl_free(buffer);
14567  return 0;
14568  }
14569 
14570 
14571  /*
14572  * Determine min-max value and position.
14573  */
14574 
14575  maxpos = sp;
14576  min = max = data[sp + i * xlen];
14577  for (j = sp; j < ep; j++) {
14578  if (data[j + i * xlen] > max) {
14579  max = data[j + i * xlen];
14580  maxpos = j;
14581  }
14582  if (data[j + i * xlen] < min) {
14583  min = data[j + i * xlen];
14584  }
14585  }
14586 
14587  if (fabs(min) < 0.0000001) /* Truncated spectrum */
14588  continue;
14589 
14590  if (max - min < threshold) /* Low signal... */
14591  continue;
14592 
14593  if (max > saturation) /* Saturation */
14594  continue;
14595 
14596  /*
14597  * Determine FWHM counting pixels with value greater than
14598  * half of the max value, to the right and to the left of
14599  * the max. Linear interpolation between the pixels where
14600  * the transition happens.
14601  */
14602 
14603  halfmax = (max + min)/ 2.0;
14604 
14605  fwhm = 0.0;
14606  ifwhm = 0;
14607  for (j = maxpos; j < maxpos + radius; j++) {
14608  if (j < xlen) {
14609  if (data[j + i * xlen] < halfmax) {
14610  fwhm = ifwhm + (data[j - 1 + i * xlen] - halfmax)
14611  / (data[j - 1 + i * xlen] - data[j + i * xlen]);
14612  break;
14613  }
14614  ifwhm++;
14615  }
14616  }
14617 
14618  ifwhm = 0;
14619  for (j = maxpos; j > maxpos - radius; j--) {
14620  if (j >= 0) {
14621  if (data[j + i * xlen] < halfmax) {
14622  fwhm += ifwhm + (data[j + 1 + i * xlen] - halfmax)
14623  / (data[j + 1 + i * xlen] - data[j + i * xlen]);
14624  break;
14625  }
14626  ifwhm++;
14627  }
14628  }
14629 
14630  if (fwhm > 3.0) {
14631  buffer[n] = fwhm - 2.0;
14632  n++;
14633  }
14634 
14635  }
14636 
14637  if (n == 0) {
14638  cpl_free(buffer);
14639  return 0;
14640  }
14641 
14642  vector = cpl_vector_wrap(n, buffer);
14643  value = cpl_vector_get_median_const(vector);
14644  cpl_vector_unwrap(vector);
14645 
14646  rms = 0.0;
14647  for (i = 0, m = 0; i < n; i++) {
14648  if (fabs(buffer[i] - value) < cut) {
14649  rms += fabs(buffer[i] - value);
14650  m++;
14651  }
14652  }
14653 
14654  cpl_free(buffer);
14655 
14656  if (m < 3)
14657  return 0;
14658 
14659  rms /= m;
14660  rms *= 1.25; /* Factor to convert average deviation to sigma */
14661 
14662  value *= dispersion;
14663  rms *= dispersion;
14664 
14665  *mfwhm = value;
14666  *rmsfwhm = rms;
14667 
14668  *resolution = lambda / value;
14669  *rmsres = *resolution * rms / value;
14670 
14671  *nlines = m;
14672 
14673  return 1;
14674 }
14675 
14676 
14698 cpl_table *mos_resolution_table(cpl_image *image, double startwave,
14699  double dispersion, int saturation,
14700  cpl_vector *lines)
14701 {
14702 
14703  cpl_table *table;
14704  double *line;
14705  double fwhm;
14706  double rmsfwhm;
14707  double resolution;
14708  double rmsres;
14709  int nref;
14710  int nlines;
14711  int i;
14712 
14713 
14714  nref = cpl_vector_get_size(lines);
14715  line = cpl_vector_get_data(lines);
14716 
14717  table = cpl_table_new(nref);
14718  cpl_table_new_column(table, "wavelength", CPL_TYPE_DOUBLE);
14719  cpl_table_set_column_unit(table, "wavelength", "Angstrom");
14720  cpl_table_new_column(table, "fwhm", CPL_TYPE_DOUBLE);
14721  cpl_table_set_column_unit(table, "fwhm", "Angstrom");
14722  cpl_table_new_column(table, "fwhm_rms", CPL_TYPE_DOUBLE);
14723  cpl_table_set_column_unit(table, "fwhm_rms", "Angstrom");
14724  cpl_table_new_column(table, "resolution", CPL_TYPE_DOUBLE);
14725  cpl_table_new_column(table, "resolution_rms", CPL_TYPE_DOUBLE);
14726  cpl_table_new_column(table, "nlines", CPL_TYPE_INT);
14727 
14728  for (i = 0; i < nref; i++) {
14729  if (mos_spectral_resolution(image, line[i], startwave, dispersion,
14730  saturation, &fwhm, &rmsfwhm,
14731  &resolution, &rmsres, &nlines)) {
14732  cpl_table_set_double(table, "wavelength", i, line[i]);
14733  cpl_table_set_double(table, "fwhm", i, fwhm);
14734  cpl_table_set_double(table, "fwhm_rms", i, rmsfwhm);
14735  cpl_table_set_double(table, "resolution", i, resolution);
14736  cpl_table_set_double(table, "resolution_rms", i, rmsres);
14737  cpl_table_set_int(table, "nlines", i, nlines);
14738  }
14739  else
14740  cpl_table_set_int(table, "nlines", i, 0);
14741  }
14742 
14743  if (cpl_table_has_valid(table, "wavelength"))
14744  return table;
14745 
14746  cpl_table_delete(table);
14747 
14748  return NULL;
14749 
14750 }
14751 
14752 
14770 double mos_integrate_signal(cpl_image *image, cpl_image *wavemap,
14771  int ystart, int yend, double wstart, double wend)
14772 {
14773  const char *func = "mos_integrate_signal";
14774 
14775  double sum;
14776  float *sdata;
14777  float *wdata;
14778  int nx, ny;
14779  int x, y;
14780 
14781 
14782  if (image == NULL || wavemap == NULL) {
14783  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
14784  return 0.0;
14785  }
14786 
14787  if (ystart > yend || wstart >= wend) {
14788  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
14789  return 0.0;
14790  }
14791 
14792  nx = cpl_image_get_size_x(image);
14793  ny = cpl_image_get_size_y(image);
14794 
14795  if (!(nx == cpl_image_get_size_x(wavemap)
14796  && ny == cpl_image_get_size_y(wavemap))) {
14797  cpl_error_set(func, CPL_ERROR_INCOMPATIBLE_INPUT);
14798  return 0.0;
14799  }
14800 
14801  if (ystart < 0 || yend > ny) {
14802  cpl_error_set(func, CPL_ERROR_ACCESS_OUT_OF_RANGE);
14803  return 0.0;
14804  }
14805 
14806  sdata = cpl_image_get_data(image);
14807  wdata = cpl_image_get_data(wavemap);
14808 
14809  sdata += ystart*nx;
14810  wdata += ystart*nx;
14811 
14812  sum = 0.0;
14813  for (y = ystart; y < yend; y++) {
14814  for (x = 0; x < nx; x++) {
14815  if (wdata[x] < wstart || wdata[x] > wend)
14816  continue;
14817  sum += sdata[x];
14818  }
14819  sdata += nx;
14820  wdata += nx;
14821  }
14822 
14823  return sum;
14824 
14825 }
14826 
14827 /****************************************************************************
14828  * From this point on, the instrument dependent functions are added:
14829  * they are functions that retrieve information that is stored in
14830  * the data headers in some instrument specific way, such as the
14831  * location of overscans, the slits positions on the telescope
14832  * focal plane, the gain factor, etc.
14833  */
14834 
14835 
14858 cpl_table *mos_load_slits_fors_mxu(cpl_propertylist *header)
14859 {
14860  const char *func = "mos_load_slits_fors_mxu";
14861 
14862  cpl_table *slits;
14863  char keyname[MAX_COLNAME];
14864  const char *instrume;
14865  const char *target_name;
14866  float slit_x;
14867  float slit_y;
14868  float length;
14869 /* double arc2mm = 0.53316; */
14870  double arc2mm = 0.528;
14871  int nslits;
14872  int slit_id;
14873  int fors;
14874  int chip;
14875  int found;
14876 
14877  /*
14878  * The limits below are used to exclude from the loaded slit list
14879  * any slit that surely doesn't belong to the used chip. This is
14880  * a way to reduce the chance of ambiguous slit identification.
14881  */
14882 
14883  float low_limit1 = 10.0;
14884  float hig_limit2 = 30.0;
14885 
14886 
14887  if (cpl_error_get_code() != CPL_ERROR_NONE) {
14888  return NULL;
14889  }
14890 
14891  if (header == NULL) {
14892  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
14893  return NULL;
14894  }
14895 
14896 
14897  /*
14898  * See if this is FORS1 or FORS2;
14899  */
14900 
14901  instrume = cpl_propertylist_get_string(header, "INSTRUME");
14902 
14903  fors = 0;
14904  if (instrume[4] == '1')
14905  fors = 1;
14906  if (instrume[4] == '2')
14907  fors = 2;
14908 
14909  if (fors != 2) {
14910  cpl_msg_error(func, "Wrong instrument: %s\n"
14911  "FORS2 is expected for MXU data", instrume);
14912  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
14913  return NULL;
14914  }
14915 
14916 
14917  /*
14918  * The master and slave chips can be identified by their positions
14919  * in the chip array in the case of FORS2 data (with fors1 the chip
14920  * is always 1). chip = 2 is the master, chip = 1 is the slave.
14921  */
14922 
14923  chip = cpl_propertylist_get_int(header, "ESO DET CHIP1 Y");
14924 
14925  if (cpl_error_get_code() != CPL_ERROR_NONE) {
14926  cpl_msg_error(func, "Missing keyword ESO DET CHIP1 Y "
14927  "in FITS header");
14928  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
14929  return NULL;
14930  }
14931 
14932  if (chip != 1 && chip != 2) {
14933  cpl_msg_error(func, "Unexpected chip position in keyword "
14934  "ESO DET CHIP1 Y: %d", chip);
14935  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
14936  return NULL;
14937  }
14938 
14939 
14940  /*
14941  * Count slits in header (excluding reference slits, and the slits
14942  * that _surely_ belong to the other chip)
14943  */
14944 
14945  nslits = 0;
14946  slit_id = 0;
14947  found = 1;
14948 
14949  while (found) {
14950  slit_id++;
14951  snprintf(keyname, MAX_COLNAME, "ESO INS MOS%d YPOS", slit_id + 100);
14952  if (cpl_propertylist_has(header, keyname)) {
14953  slit_y = cpl_propertylist_get_double(header, keyname);
14954 
14955  if (chip == 1)
14956  if (slit_y < low_limit1)
14957  continue;
14958  if (chip == 2)
14959  if (slit_y > hig_limit2)
14960  continue;
14961 
14962  snprintf(keyname, MAX_COLNAME, "ESO INS TARG%d NAME",
14963  slit_id + 100);
14964  if (cpl_propertylist_has(header, keyname)) {
14965  target_name = cpl_propertylist_get_string(header, keyname);
14966  if (strncmp(target_name, "refslit", 7))
14967  nslits++;
14968  }
14969  else
14970  nslits++;
14971  }
14972  else
14973  found = 0;
14974  }
14975 
14976  if (cpl_error_get_code() != CPL_ERROR_NONE) {
14977  cpl_msg_error(func, "%s while loading slits coordinates from "
14978  "FITS header", cpl_error_get_message());
14979  cpl_error_set_where(func);
14980  return NULL;
14981  }
14982 
14983  if (nslits == 0) {
14984  cpl_msg_error(func, "No slits coordinates found in header");
14985  cpl_error_set(func, CPL_ERROR_DATA_NOT_FOUND);
14986  return NULL;
14987  }
14988 
14989  slits = cpl_table_new(nslits);
14990  cpl_table_new_column(slits, "slit_id", CPL_TYPE_INT);
14991  cpl_table_new_column(slits, "xtop", CPL_TYPE_DOUBLE);
14992  cpl_table_new_column(slits, "ytop", CPL_TYPE_DOUBLE);
14993  cpl_table_new_column(slits, "xbottom", CPL_TYPE_DOUBLE);
14994  cpl_table_new_column(slits, "ybottom", CPL_TYPE_DOUBLE);
14995  cpl_table_set_column_unit(slits, "xtop", "pixel");
14996  cpl_table_set_column_unit(slits, "ytop", "pixel");
14997  cpl_table_set_column_unit(slits, "xbottom", "pixel");
14998  cpl_table_set_column_unit(slits, "ybottom", "pixel");
14999 
15000  nslits = 0;
15001  slit_id = 0;
15002  found = 1;
15003  while (found) {
15004  slit_id++;
15005  snprintf(keyname, MAX_COLNAME, "ESO INS MOS%d YPOS", slit_id + 100);
15006  if (cpl_propertylist_has(header, keyname)) {
15007  slit_y = cpl_propertylist_get_double(header, keyname);
15008 
15009  if (chip == 1)
15010  if (slit_y < low_limit1)
15011  continue;
15012  if (chip == 2)
15013  if (slit_y > hig_limit2)
15014  continue;
15015 
15016  /*
15017  * Y-flip the slit position, to match CCD pixel coordinate
15018  * convention
15019  */
15020 
15021  slit_y = -slit_y;
15022 
15023  snprintf(keyname, MAX_COLNAME, "ESO INS MOS%d XPOS", slit_id + 100);
15024  slit_x = cpl_propertylist_get_double(header, keyname);
15025  if (cpl_error_get_code() != CPL_ERROR_NONE) {
15026  cpl_table_delete(slits);
15027  cpl_msg_error(func, "Missing keyword %s in FITS header",
15028  keyname);
15029  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
15030  return NULL;
15031  }
15032 
15033  snprintf(keyname, MAX_COLNAME, "ESO INS MOS%d LEN", slit_id + 100);
15034  length = cpl_propertylist_get_double(header, keyname);
15035  if (cpl_error_get_code() != CPL_ERROR_NONE) {
15036  cpl_table_delete(slits);
15037  cpl_msg_error(func, "Missing keyword %s in FITS header",
15038  keyname);
15039  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
15040  return NULL;
15041  }
15042 
15043  length *= arc2mm;
15044 
15045  snprintf(keyname, MAX_COLNAME, "ESO INS TARG%d NAME",
15046  slit_id + 100);
15047  if (cpl_propertylist_has(header, keyname)) {
15048  target_name = cpl_propertylist_get_string(header, keyname);
15049  if (strncmp(target_name, "refslit", 7)) {
15050  cpl_table_set_int(slits, "slit_id", nslits, slit_id);
15051  cpl_table_set(slits, "xtop", nslits, slit_x);
15052  cpl_table_set(slits, "ytop", nslits, slit_y + length/2);
15053  cpl_table_set(slits, "xbottom", nslits, slit_x);
15054  cpl_table_set(slits, "ybottom", nslits, slit_y - length/2);
15055  nslits++;
15056  }
15057  }
15058  else {
15059  cpl_table_set_int(slits, "slit_id", nslits, slit_id);
15060  cpl_table_set(slits, "xtop", nslits, slit_x);
15061  cpl_table_set(slits, "ytop", nslits, slit_y + length/2);
15062  cpl_table_set(slits, "xbottom", nslits, slit_x);
15063  cpl_table_set(slits, "ybottom", nslits, slit_y - length/2);
15064  nslits++;
15065  }
15066  }
15067  else
15068  found = 0;
15069  }
15070 
15071  return slits;
15072 }
15073 
15074 
15098 cpl_table *mos_load_slits_fors_mos(cpl_propertylist *header,
15099  int * nslits_out_det)
15100 {
15101  const char *func = "mos_load_slits_fors_mos";
15102 
15103  cpl_table *slits;
15104  char keyname[MAX_COLNAME];
15105  const char *instrume;
15106  const char *chipname;
15107  float slit_x;
15108  int first_slit, last_slit;
15109  cpl_size nslits;
15110  int slit_id;
15111  int fors;
15112  int chip;
15113  int fors_is_old;
15114 
15115  /*
15116  * The Y coordinates of the slits are fixed
15117  */
15118 
15119  float ytop[19] = { 113.9, 101.3, 89.9, 77.3, 65.9, 53.3,
15120  41.9, 29.3, 17.9, 5.3, -6.1, -18.7,
15121  -30.1, -42.7, -54.1, -66.7, -78.1, -90.7,
15122  -102.1 };
15123  float ybottom[19] = { 102.1, 90.7, 78.1, 66.7, 54.1, 42.7,
15124  30.1, 18.7, 6.1, -5.3, -17.9, -29.3,
15125  -41.9, -53.3, -65.9, -77.3, -89.9, -101.3,
15126  -113.9 };
15127 
15128 
15129  if (cpl_error_get_code() != CPL_ERROR_NONE) {
15130  return NULL;
15131  }
15132 
15133  if (header == NULL) {
15134  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
15135  return NULL;
15136  }
15137 
15138 
15139  /*
15140  * See if this is FORS1 or FORS2;
15141  */
15142 
15143  instrume = cpl_propertylist_get_string(header, "INSTRUME");
15144 
15145  fors = 0;
15146  if (instrume[4] == '1')
15147  fors = 1;
15148  if (instrume[4] == '2')
15149  fors = 2;
15150 
15151  if (fors == 0) {
15152  cpl_msg_error(func, "Wrong instrument found in FITS header: %s",
15153  instrume);
15154  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
15155  return NULL;
15156  }
15157 
15158  /* FIXME:
15159  * This is the way FORS1 data belong to the upgraded chips,
15160  * named "Marlene" and "Norma III". It's a quick solution,
15161  * there are hardcoded values here!!!
15162  */
15163 
15164  chipname = cpl_propertylist_get_string(header, "ESO DET CHIP1 ID");
15165 
15166  if (chipname[0] == 'M' || chipname[0] == 'N')
15167  fors_is_old = 0;
15168  else
15169  fors_is_old = 1;
15170 
15171  if (fors == 1 && fors_is_old) {
15172  first_slit = 1;
15173  last_slit = 19;
15174  }
15175  else {
15176 
15177  /*
15178  * The master and slave chips can be identified by their positions
15179  * in the chip array in the case of FORS2 data: chip = 2 is the
15180  * master, chip = 1 is the slave.
15181  */
15182 
15183  chip = cpl_propertylist_get_int(header, "ESO DET CHIP1 Y");
15184 
15185  if (cpl_error_get_code() != CPL_ERROR_NONE) {
15186  cpl_msg_error(func, "Missing keyword ESO DET CHIP1 Y "
15187  "in FITS header");
15188  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
15189  return NULL;
15190  }
15191 
15192  if (chip != 1 && chip != 2) {
15193  cpl_msg_error(func, "Unexpected chip position in keyword "
15194  "ESO DET CHIP1 Y: %d", chip);
15195  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
15196  return NULL;
15197  }
15198 
15199  if (chip == 1) {
15200  first_slit = 12;
15201  last_slit = 19;
15202  }
15203  else {
15204  first_slit = 1;
15205  last_slit = 11;
15206  }
15207  }
15208 
15209 
15210  /*
15211  * Count slits in header (excluding closed slits - i.e. those with
15212  * offsets greater than 115 mm - and the slits that do not belong
15213  * to this chip)
15214  */
15215 
15216  nslits = 0;
15217  slit_id = 0;
15218  for (slit_id = first_slit; slit_id <= last_slit; slit_id++) {
15219  snprintf(keyname, MAX_COLNAME, "ESO INS MOS%d POS", slit_id);
15220  if (cpl_propertylist_has(header, keyname)) {
15221  slit_x = cpl_propertylist_get_double(header, keyname);
15222  if (fabs(slit_x) < 115.0)
15223  nslits++;
15224  else
15225  (*nslits_out_det)++;
15226  }
15227  else {
15228  cpl_msg_error(func, "Missing keyword %s in FITS header", keyname);
15229  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
15230  return NULL;
15231  }
15232  }
15233 
15234  if (cpl_error_get_code() != CPL_ERROR_NONE) {
15235  cpl_msg_error(func, "%s while loading slits coordinates from "
15236  "FITS header", cpl_error_get_message());
15237  cpl_error_set_where(func);
15238  return NULL;
15239  }
15240 
15241  if (nslits == 0) {
15242  cpl_msg_error(func, "No slits coordinates found in header");
15243  cpl_error_set(func, CPL_ERROR_DATA_NOT_FOUND);
15244  return NULL;
15245  }
15246 
15247  slits = cpl_table_new(nslits);
15248  cpl_table_new_column(slits, "slit_id", CPL_TYPE_INT);
15249  cpl_table_new_column(slits, "xtop", CPL_TYPE_DOUBLE);
15250  cpl_table_new_column(slits, "ytop", CPL_TYPE_DOUBLE);
15251  cpl_table_new_column(slits, "xbottom", CPL_TYPE_DOUBLE);
15252  cpl_table_new_column(slits, "ybottom", CPL_TYPE_DOUBLE);
15253  cpl_table_set_column_unit(slits, "xtop", "pixel");
15254  cpl_table_set_column_unit(slits, "ytop", "pixel");
15255  cpl_table_set_column_unit(slits, "xbottom", "pixel");
15256  cpl_table_set_column_unit(slits, "ybottom", "pixel");
15257 
15258  nslits = 0;
15259  slit_id = 0;
15260  for (slit_id = first_slit; slit_id <= last_slit; slit_id++) {
15261  snprintf(keyname, MAX_COLNAME, "ESO INS MOS%d POS", slit_id);
15262  slit_x = cpl_propertylist_get_double(header, keyname);
15263  if (fabs(slit_x) < 115.0) {
15264  cpl_table_set_int(slits, "slit_id", nslits, slit_id);
15265  cpl_table_set(slits, "xtop", nslits, slit_x);
15266  cpl_table_set(slits, "ytop", nslits, ytop[slit_id-1]);
15267  cpl_table_set(slits, "xbottom", nslits, slit_x);
15268  cpl_table_set(slits, "ybottom", nslits, ybottom[slit_id-1]);
15269  nslits++;
15270  }
15271  }
15272 
15273  return slits;
15274 }
15275 
15276 
15300 cpl_table *mos_load_slits_fors_lss(cpl_propertylist *header)
15301 {
15302  const char *func = "mos_load_slits_fors_lss";
15303 
15304  cpl_table *slits;
15305  char *slit_name;
15306  const char *instrume;
15307  int fors;
15308  int chip;
15309  float ytop;
15310  float ybottom;
15311 
15312  if (cpl_error_get_code() != CPL_ERROR_NONE) {
15313  return NULL;
15314  }
15315 
15316  if (header == NULL) {
15317  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
15318  return NULL;
15319  }
15320 
15321 
15322  /*
15323  * See if this is FORS1 or FORS2;
15324  */
15325 
15326  instrume = cpl_propertylist_get_string(header, "INSTRUME");
15327 
15328  fors = 0;
15329  if (instrume[4] == '1')
15330  fors = 1;
15331  if (instrume[4] == '2')
15332  fors = 2;
15333 
15334  if (fors == 0) {
15335  cpl_msg_error(func, "Wrong instrument found in FITS header: %s",
15336  instrume);
15337  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
15338  return NULL;
15339  }
15340 
15341  if (fors == 1) {
15342  ytop = 109.94;
15343  ybottom = -109.94;
15344  }
15345  else {
15346 
15347  /*
15348  * The master and slave chips can be identified by their positions
15349  * in the chip array in the case of FORS2 data: chip = 2 is the
15350  * master, chip = 1 is the slave.
15351  */
15352 
15353  chip = cpl_propertylist_get_int(header, "ESO DET CHIP1 Y");
15354 
15355  if (cpl_error_get_code() != CPL_ERROR_NONE) {
15356  cpl_msg_error(func, "Missing keyword ESO DET CHIP1 Y "
15357  "in FITS header");
15358  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
15359  return NULL;
15360  }
15361 
15362  if (chip != 1 && chip != 2) {
15363  cpl_msg_error(func, "Unexpected chip position in keyword "
15364  "ESO DET CHIP1 Y: %d", chip);
15365  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
15366  return NULL;
15367  }
15368 
15369  if (chip == 1) {
15370  ytop = 30.0;
15371  ybottom = -109.94;
15372  }
15373  else {
15374  ytop = 109.94;
15375  ybottom = -20.0;
15376  }
15377  }
15378 
15379 
15380  slits = cpl_table_new(1);
15381  cpl_table_new_column(slits, "slit_id", CPL_TYPE_INT);
15382  cpl_table_new_column(slits, "xtop", CPL_TYPE_DOUBLE);
15383  cpl_table_new_column(slits, "ytop", CPL_TYPE_DOUBLE);
15384  cpl_table_new_column(slits, "xbottom", CPL_TYPE_DOUBLE);
15385  cpl_table_new_column(slits, "ybottom", CPL_TYPE_DOUBLE);
15386  cpl_table_set_column_unit(slits, "xtop", "pixel");
15387  cpl_table_set_column_unit(slits, "ytop", "pixel");
15388  cpl_table_set_column_unit(slits, "xbottom", "pixel");
15389  cpl_table_set_column_unit(slits, "ybottom", "pixel");
15390 
15391  slit_name = (char *)cpl_propertylist_get_string(header,
15392  "ESO INS SLIT NAME");
15393 
15394  cpl_table_set(slits, "ytop", 0, ytop);
15395  cpl_table_set(slits, "ybottom", 0, ybottom);
15396 
15397  if (!strncmp(slit_name, "lSlit0_3arcsec", 14)) {
15398  cpl_table_set_int(slits, "slit_id", 0, 1);
15399  cpl_table_set(slits, "xbottom", 0, -0.075);
15400  cpl_table_set(slits, "xtop", 0, 0.075);
15401  }
15402  else if (!strncmp(slit_name, "lSlit0_4arcsec", 14)) {
15403  cpl_table_set_int(slits, "slit_id", 0, 2);
15404  cpl_table_set(slits, "xbottom", 0, 5.895);
15405  cpl_table_set(slits, "xtop", 0, 6.105);
15406  }
15407  else if (!strncmp(slit_name, "lSlit0_5arcsec", 14)) {
15408  cpl_table_set_int(slits, "slit_id", 0, 3);
15409  cpl_table_set(slits, "xbottom", 0, -6.135);
15410  cpl_table_set(slits, "xtop", 0, -5.865);
15411  }
15412  else if (!strncmp(slit_name, "lSlit0_7arcsec", 14)) {
15413  cpl_table_set_int(slits, "slit_id", 0, 4);
15414  cpl_table_set(slits, "xbottom", 0, 11.815);
15415  cpl_table_set(slits, "xtop", 0, 12.185);
15416  }
15417  else if (!strncmp(slit_name, "lSlit1_0arcsec", 14)) {
15418  cpl_table_set_int(slits, "slit_id", 0, 5);
15419  cpl_table_set(slits, "xbottom", 0, -12.265);
15420  cpl_table_set(slits, "xtop", 0, -11.735);
15421  }
15422  else if (!strncmp(slit_name, "lSlit1_3arcsec", 14)) {
15423  cpl_table_set_int(slits, "slit_id", 0, 6);
15424  cpl_table_set(slits, "xbottom", 0, 17.655);
15425  cpl_table_set(slits, "xtop", 0, 18.345);
15426  }
15427  else if (!strncmp(slit_name, "lSlit1_6arcsec", 14)) {
15428  cpl_table_set_int(slits, "slit_id", 0, 7);
15429  cpl_table_set(slits, "xbottom", 0, -18.425);
15430  cpl_table_set(slits, "xtop", 0, -17.575);
15431  }
15432  else if (!strncmp(slit_name, "lSlit2_0arcsec", 14)) {
15433  cpl_table_set_int(slits, "slit_id", 0, 8);
15434  cpl_table_set(slits, "xbottom", 0, 23.475);
15435  cpl_table_set(slits, "xtop", 0, 24.525);
15436  }
15437  else if (!strncmp(slit_name, "lSlit2_5arcsec", 14)) {
15438  cpl_table_set_int(slits, "slit_id", 0, 9);
15439  cpl_table_set(slits, "xbottom", 0, -24.66);
15440  cpl_table_set(slits, "xtop", 0, -23.34);
15441  }
15442  else {
15443  cpl_msg_error(func, "Invalid slit %s in keyword ESO INS SLIT NAME",
15444  slit_name);
15445  cpl_error_set(func, CPL_ERROR_DATA_NOT_FOUND);
15446  cpl_table_delete(slits);
15447  return NULL;
15448  }
15449 
15450  return slits;
15451 }
15452 
15453 
15468 double mos_get_gain_vimos(cpl_propertylist *header)
15469 {
15470  const char *func = "mos_get_gain_vimos";
15471 
15472  double gain = -1.0;
15473 
15474 
15475  if (cpl_error_get_code() != CPL_ERROR_NONE)
15476  return gain;
15477 
15478  if (header == NULL) {
15479  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
15480  return gain;
15481  }
15482 
15483  gain = cpl_propertylist_get_double(header, "ESO DET OUT1 CONAD");
15484  if (cpl_error_get_code()) {
15485  cpl_error_set_where(func);
15486  gain = -1.0;
15487  }
15488 
15489  return gain;
15490 
15491 }
15492 
15493 
15513 cpl_table *mos_load_slits_vimos(cpl_propertylist *header)
15514 {
15515  const char *func = "mos_load_slits_vimos";
15516 
15517  cpl_table *slits;
15518  char keyname[MAX_COLNAME];
15519  float slit_x;
15520  float slit_y;
15521  float dim_x;
15522  float dim_y;
15523  int nslits;
15524  int slit_id;
15525  int curved;
15526  int i;
15527 
15528 
15529  if (cpl_error_get_code() != CPL_ERROR_NONE) {
15530  return NULL;
15531  }
15532 
15533  if (header == NULL) {
15534  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
15535  return NULL;
15536  }
15537 
15538  nslits = cpl_propertylist_get_int(header, "ESO INS SLIT NO");
15539 
15540  if (cpl_error_get_code() != CPL_ERROR_NONE) {
15541  cpl_error_set_where(func);
15542  return NULL;
15543  }
15544 
15545  slits = cpl_table_new(nslits);
15546  cpl_table_new_column(slits, "slit_id", CPL_TYPE_INT);
15547  cpl_table_new_column(slits, "xtop", CPL_TYPE_DOUBLE);
15548  cpl_table_new_column(slits, "ytop", CPL_TYPE_DOUBLE);
15549  cpl_table_new_column(slits, "xbottom", CPL_TYPE_DOUBLE);
15550  cpl_table_new_column(slits, "ybottom", CPL_TYPE_DOUBLE);
15551  cpl_table_new_column(slits, "xwidth", CPL_TYPE_DOUBLE);
15552  cpl_table_new_column(slits, "ywidth", CPL_TYPE_DOUBLE);
15553  cpl_table_new_column(slits, "curved", CPL_TYPE_INT);
15554  cpl_table_set_column_unit(slits, "xtop", "pixel");
15555  cpl_table_set_column_unit(slits, "ytop", "pixel");
15556  cpl_table_set_column_unit(slits, "xbottom", "pixel");
15557  cpl_table_set_column_unit(slits, "ybottom", "pixel");
15558  cpl_table_set_column_unit(slits, "xwidth", "mm");
15559  cpl_table_set_column_unit(slits, "ywidth", "mm");
15560 
15561  for (i = 0; i < nslits; i++) {
15562  sprintf(keyname, "ESO INS SLIT%d ID", i+1);
15563  slit_id = cpl_propertylist_get_int(header, keyname);
15564  if (cpl_error_get_code() != CPL_ERROR_NONE) {
15565  cpl_error_set_where(func);
15566  return NULL;
15567  }
15568  sprintf(keyname, "ESO INS SLIT%d X", i+1);
15569  slit_x = cpl_propertylist_get_double(header, keyname);
15570  if (cpl_error_get_code() != CPL_ERROR_NONE) {
15571  cpl_error_set_where(func);
15572  return NULL;
15573  }
15574  sprintf(keyname, "ESO INS SLIT%d Y", i+1);
15575  slit_y = cpl_propertylist_get_double(header, keyname);
15576  if (cpl_error_get_code() != CPL_ERROR_NONE) {
15577  cpl_error_set_where(func);
15578  return NULL;
15579  }
15580  sprintf(keyname, "ESO INS SLIT%d DIMX", i+1);
15581  dim_x = cpl_propertylist_get_double(header, keyname);
15582  if (cpl_error_get_code() != CPL_ERROR_NONE) {
15583  cpl_error_set_where(func);
15584  return NULL;
15585  }
15586 
15587  sprintf(keyname, "ESO INS SLIT%d BEZIER DY", i+1);
15588  if (cpl_propertylist_has(header, keyname)) {
15589  curved = 1;
15590  }
15591  else {
15592  sprintf(keyname, "ESO INS SLIT%d DIMY", i+1);
15593  curved = 0;
15594  }
15595  dim_y = cpl_propertylist_get_double(header, keyname);
15596  if (cpl_error_get_code() != CPL_ERROR_NONE) {
15597  cpl_error_set_where(func);
15598  return NULL;
15599  }
15600 
15601  cpl_table_set_int(slits, "slit_id", i, slit_id);
15602  cpl_table_set(slits, "xtop", i, slit_x - dim_x/2);
15603  cpl_table_set(slits, "ytop", i, slit_y);
15604  cpl_table_set(slits, "xbottom", i, slit_x + dim_x/2);
15605  cpl_table_set(slits, "ybottom", i, slit_y);
15606  cpl_table_set(slits, "xwidth", i, dim_x);
15607  cpl_table_set(slits, "ywidth", i, dim_y);
15608  cpl_table_set_int(slits, "curved", i, curved);
15609  }
15610 
15611  return slits;
15612 }
15613 
15614 
15624 int mos_check_multiplex(cpl_table *slits)
15625 {
15626  cpl_propertylist *sort;
15627  int nrow;
15628  int i, multiplex, xprev, xcur;
15629  double prev, cur;
15630  double tolerance = 1.0; // About spatially aligned slits (mm)
15631 
15632 
15633  /*
15634  * Create an auxiliary column containing a sort of integer
15635  * x coordinate of the slit, to guarantee that slits at the
15636  * same spatial offset are recognised immediately as in spectral
15637  * multiplexing.
15638  */
15639 
15640  sort = cpl_propertylist_new();
15641  cpl_propertylist_append_bool(sort, "xtop", 0);
15642  cpl_table_sort(slits, sort);
15643  cpl_propertylist_delete(sort);
15644 
15645  prev = cpl_table_get_double(slits, "xtop", 0, NULL);
15646  cpl_table_new_column(slits, "xind", CPL_TYPE_INT);
15647  cpl_table_set_int(slits, "xind", 0, prev); // cast to int is intentional
15648  nrow = cpl_table_get_nrow(slits);
15649  for (i = 1; i < nrow; i++) {
15650  cur = cpl_table_get_double(slits, "xtop", i, NULL);
15651  if (fabs(prev - cur) > tolerance)
15652  prev = cur;
15653  cpl_table_set_int(slits, "xind", i, prev);
15654  }
15655 
15656  /*
15657  * Now sort according to increasing (integer) x positions, and when
15658  * those are equal (multiplexed) according to the increasing y position.
15659  */
15660 
15661  sort = cpl_propertylist_new();
15662  cpl_propertylist_append_bool(sort, "xind", 0);
15663  cpl_propertylist_append_bool(sort, "ytop", 0);
15664  cpl_table_sort(slits, sort);
15665  cpl_propertylist_delete(sort);
15666 
15667  /*
15668  * Now assign to each slit its multiplex order.
15669  */
15670 
15671  multiplex = 0;
15672  cpl_table_new_column(slits, "multiplex", CPL_TYPE_INT);
15673  xprev = cpl_table_get_int(slits, "xind", 0, NULL);
15674  cpl_table_set_int(slits, "multiplex", 0, multiplex);
15675  nrow = cpl_table_get_nrow(slits);
15676  for (i = 1; i < nrow; i++) {
15677  xcur = cpl_table_get_int(slits, "xind", i, NULL);
15678  if (xcur == xprev) {
15679  multiplex++;
15680  }
15681  else {
15682  xprev = xcur;
15683  multiplex = 0;
15684  }
15685  cpl_table_set_int(slits, "multiplex", i, multiplex);
15686  }
15687 
15688  cpl_table_save(slits, NULL, NULL, "multiplex.fits", CPL_IO_DEFAULT);
15689 
15690  cpl_table_erase_column(slits, "xind");
15691 
15692  return 1 + cpl_table_get_column_max(slits, "multiplex");
15693 
15694 }
15695 
15696 
15723 cpl_table *mos_load_overscans_vimos(const cpl_propertylist *header,
15724  int check_consistency)
15725 {
15726  const char *func = "mos_load_overscans_vimos";
15727 
15728  int nx = 0;
15729  int ny = 0;
15730  int px = 0;
15731  int py = 0;
15732  int ox = 0;
15733  int oy = 0;
15734  int vx = 0;
15735  int vy = 0;
15736  int nrows;
15737  cpl_table *overscans;
15738 
15739 
15740  if (cpl_error_get_code() != CPL_ERROR_NONE) {
15741  cpl_msg_error(func, "Reset your error: %s", cpl_error_get_message());
15742  return NULL;
15743  }
15744 
15745  if (header == NULL) {
15746  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
15747  return NULL;
15748  }
15749 
15750  if (cpl_propertylist_has(header, "NAXIS1"))
15751  nx = cpl_propertylist_get_int(header, "NAXIS1");
15752  if (cpl_propertylist_has(header, "NAXIS2"))
15753  ny = cpl_propertylist_get_int(header, "NAXIS2");
15754  if (cpl_propertylist_has(header, "ESO DET OUT1 PRSCX"))
15755  px = cpl_propertylist_get_int(header, "ESO DET OUT1 PRSCX");
15756  if (cpl_propertylist_has(header, "ESO DET OUT1 PRSCY"))
15757  py = cpl_propertylist_get_int(header, "ESO DET OUT1 PRSCY");
15758  if (cpl_propertylist_has(header, "ESO DET OUT1 OVSCX"))
15759  ox = cpl_propertylist_get_int(header, "ESO DET OUT1 OVSCX");
15760  if (cpl_propertylist_has(header, "ESO DET OUT1 OVSCY"))
15761  oy = cpl_propertylist_get_int(header, "ESO DET OUT1 OVSCY");
15762  if (cpl_propertylist_has(header, "ESO DET OUT1 NX"))
15763  vx = cpl_propertylist_get_int(header, "ESO DET OUT1 NX");
15764  if (cpl_propertylist_has(header, "ESO DET OUT1 NY"))
15765  vy = cpl_propertylist_get_int(header, "ESO DET OUT1 NY");
15766 
15767  if (cpl_error_get_code() != CPL_ERROR_NONE) {
15768  cpl_msg_error(func, "Missing overscan keywords in header");
15769  cpl_error_set_where(func);
15770  return NULL;
15771  }
15772 
15773  if (px < 0 || py < 0 || ox < 0 || oy < 0) {
15774  cpl_msg_error(func, "Missing overscan keywords in header");
15775  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
15776  return NULL;
15777  }
15778 
15779  if ((px + vx + ox != nx) || (py + vy + oy != ny)) {
15780  if (check_consistency) {
15781  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
15782  return NULL;
15783  }
15784  else {
15785  cpl_msg_debug(func, "Overscans description conflicts with "
15786  "reported image sizes, "
15787  "%d + %d + %d != %d or "
15788  "%d + %d + %d != %d",
15789  px, vx, ox, nx,
15790  py, vy, oy, ny);
15791  }
15792  }
15793 
15794  nrows = 0;
15795  if (px > 0)
15796  nrows++;
15797  if (ox > 0)
15798  nrows++;
15799  if (py > 0)
15800  nrows++;
15801  if (oy > 0)
15802  nrows++;
15803 
15804  if (nrows > 2) {
15805  cpl_msg_error(func, "Unexpected overscan regions "
15806  "(both in X and Y direction)");
15807  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
15808  return NULL;
15809  }
15810 
15811 
15812  /*
15813  * A row is added for the description of the valid region of the
15814  * exposure the input header belongs to.
15815  */
15816 
15817  nrows++;
15818 
15819  overscans = cpl_table_new(nrows);
15820  cpl_table_new_column(overscans, "xlow", CPL_TYPE_INT);
15821  cpl_table_new_column(overscans, "ylow", CPL_TYPE_INT);
15822  cpl_table_new_column(overscans, "xhig", CPL_TYPE_INT);
15823  cpl_table_new_column(overscans, "yhig", CPL_TYPE_INT);
15824 
15825  nrows = 0;
15826 
15827  cpl_table_set_int(overscans, "xlow", nrows, px);
15828  cpl_table_set_int(overscans, "ylow", nrows, py);
15829  cpl_table_set_int(overscans, "xhig", nrows, nx - ox);
15830  cpl_table_set_int(overscans, "yhig", nrows, ny - oy);
15831  nrows++;
15832 
15833  if (px > 0) {
15834  cpl_table_set_int(overscans, "xlow", nrows, 0);
15835  cpl_table_set_int(overscans, "ylow", nrows, 0);
15836  cpl_table_set_int(overscans, "xhig", nrows, px);
15837  cpl_table_set_int(overscans, "yhig", nrows, ny);
15838  nrows++;
15839  }
15840 
15841  if (ox > 0) {
15842  cpl_table_set_int(overscans, "xlow", nrows, nx - ox);
15843  cpl_table_set_int(overscans, "ylow", nrows, 0);
15844  cpl_table_set_int(overscans, "xhig", nrows, nx);
15845  cpl_table_set_int(overscans, "yhig", nrows, ny);
15846  nrows++;
15847  }
15848 
15849  if (py > 0) {
15850  cpl_table_set_int(overscans, "xlow", nrows, 0);
15851  cpl_table_set_int(overscans, "ylow", nrows, 0);
15852  cpl_table_set_int(overscans, "xhig", nrows, nx);
15853  cpl_table_set_int(overscans, "yhig", nrows, py);
15854  nrows++;
15855  }
15856 
15857  if (oy > 0) {
15858  cpl_table_set_int(overscans, "xlow", nrows, 0);
15859  cpl_table_set_int(overscans, "ylow", nrows, ny - oy);
15860  cpl_table_set_int(overscans, "xhig", nrows, nx);
15861  cpl_table_set_int(overscans, "yhig", nrows, ny);
15862  nrows++;
15863  }
15864 
15865  return overscans;
15866 
15867 }
15868 
15869 
15870 cpl_table *mos_load_overscans_fors(const cpl_propertylist *header)
15871 {
15872  const char *func = "mos_load_overscans_fors";
15873 
15874  int nports;
15875  int nx = 0;
15876  int ny = 0;
15877  int px = 0;
15878  int py = 0;
15879  int ox = 0;
15880  int oy = 0;
15881  int rebin;
15882  int nrows;
15883  cpl_table *overscans;
15884 
15885 
15886  if (cpl_error_get_code() != CPL_ERROR_NONE) {
15887  cpl_msg_error(func, "Reset your error: %s", cpl_error_get_message());
15888  return NULL;
15889  }
15890 
15891  if (header == NULL) {
15892  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
15893  return NULL;
15894  }
15895 
15896  if (cpl_propertylist_has(header, "ESO DET OUTPUTS"))
15897  nports = cpl_propertylist_get_int(header, "ESO DET OUTPUTS");
15898 
15899  if (nports == 4 &&
15900  cpl_propertylist_has(header, "ESO DET OUT1 PRSCX") &&
15901  cpl_propertylist_has(header, "ESO DET WIN1 BINX")) {
15902 
15903  rebin = cpl_propertylist_get_int(header, "ESO DET WIN1 BINX");
15904 
15905  overscans = cpl_table_new(3);
15906  cpl_table_new_column(overscans, "xlow", CPL_TYPE_INT);
15907  cpl_table_new_column(overscans, "ylow", CPL_TYPE_INT);
15908  cpl_table_new_column(overscans, "xhig", CPL_TYPE_INT);
15909  cpl_table_new_column(overscans, "yhig", CPL_TYPE_INT);
15910 
15911  px = 16 / rebin;
15912  ox = 16 / rebin;
15913  nx = 2080 / rebin;
15914  ny = 2048 / rebin;
15915  nrows = 0;
15916 
15917  cpl_table_set_int(overscans, "xlow", nrows, px);
15918  cpl_table_set_int(overscans, "ylow", nrows, py);
15919  cpl_table_set_int(overscans, "xhig", nrows, nx - ox);
15920  cpl_table_set_int(overscans, "yhig", nrows, ny - oy);
15921  nrows++;
15922 
15923  cpl_table_set_int(overscans, "xlow", nrows, 0);
15924  cpl_table_set_int(overscans, "ylow", nrows, 0);
15925  cpl_table_set_int(overscans, "xhig", nrows, px);
15926  cpl_table_set_int(overscans, "yhig", nrows, ny);
15927  nrows++;
15928 
15929  cpl_table_set_int(overscans, "xlow", nrows, nx - ox);
15930  cpl_table_set_int(overscans, "ylow", nrows, 0);
15931  cpl_table_set_int(overscans, "xhig", nrows, nx);
15932  cpl_table_set_int(overscans, "yhig", nrows, ny);
15933  nrows++;
15934  }
15935  else {
15936  overscans = mos_load_overscans_vimos(header, 0);
15937  }
15938 
15939  return overscans;
15940 
15941 }
15942 
15974 #define READY 1
15975 #ifdef READY
15976 
15977 cpl_polynomial *mos_montecarlo_polyfit(cpl_table *points, cpl_table *evaluate,
15978  int samples, int order)
15979 {
15980 
15981  const char *func = "mos_montecarlo_polyfit";
15982 
15983  cpl_polynomial *p;
15984  cpl_polynomial *q;
15985  cpl_vector *listx;
15986  cpl_vector *listy;
15987  double err;
15988  double *x;
15989  double *px;
15990  double *x_eval;
15991  double *px_eval;
15992  double *sigma;
15993  double *vy;
15994  double *dy;
15995  int npoints, nevaluate;
15996  int i, j;
15997 
15998 
15999  if (points == NULL || evaluate == NULL) {
16000  cpl_error_set(func, CPL_ERROR_NULL_INPUT);
16001  return NULL;
16002  }
16003 
16004  if (!cpl_table_has_column(points, "x")) {
16005  cpl_error_set(func, CPL_ERROR_DATA_NOT_FOUND);
16006  return NULL;
16007  }
16008 
16009  if (cpl_table_get_column_type(points, "x") != CPL_TYPE_DOUBLE) {
16010  cpl_error_set(func, CPL_ERROR_INVALID_TYPE);
16011  return NULL;
16012  }
16013 
16014  if (cpl_table_has_invalid(points, "x")) {
16015  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
16016  return NULL;
16017  }
16018 
16019  if (!cpl_table_has_column(points, "y")) {
16020  cpl_error_set(func, CPL_ERROR_DATA_NOT_FOUND);
16021  return NULL;
16022  }
16023 
16024  if (cpl_table_get_column_type(points, "y") != CPL_TYPE_DOUBLE) {
16025  cpl_error_set(func, CPL_ERROR_INVALID_TYPE);
16026  return NULL;
16027  }
16028 
16029  if (cpl_table_has_invalid(points, "y")) {
16030  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
16031  return NULL;
16032  }
16033 
16034  if (cpl_table_has_column(points, "y_err")) {
16035 
16036  if (cpl_table_get_column_type(points, "y_err") != CPL_TYPE_DOUBLE) {
16037  cpl_error_set(func, CPL_ERROR_INVALID_TYPE);
16038  return NULL;
16039  }
16040 
16041  if (cpl_table_has_invalid(points, "y_err")) {
16042  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
16043  return NULL;
16044  }
16045  }
16046 
16047  if (!cpl_table_has_column(evaluate, "x")) {
16048  cpl_error_set(func, CPL_ERROR_DATA_NOT_FOUND);
16049  return NULL;
16050  }
16051 
16052  if (cpl_table_get_column_type(evaluate, "x") != CPL_TYPE_DOUBLE) {
16053  cpl_error_set(func, CPL_ERROR_INVALID_TYPE);
16054  return NULL;
16055  }
16056 
16057  if (cpl_table_has_invalid(evaluate, "x")) {
16058  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
16059  return NULL;
16060  }
16061 
16062  if (samples < 2 || order < 0) {
16063  cpl_error_set(func, CPL_ERROR_ILLEGAL_INPUT);
16064  return NULL;
16065  }
16066 
16067  npoints = cpl_table_get_nrow(points);
16068  listx = cpl_vector_wrap(npoints, cpl_table_get_data_double(points, "x"));
16069  listy = cpl_vector_wrap(npoints, cpl_table_get_data_double(points, "y"));
16070 
16071  p = cpl_polynomial_fit_1d_create(listx, listy, order, &err);
16072 
16073  if (!cpl_table_has_column(points, "y_err")) {
16074  err = sqrt(err);
16075  cpl_table_new_column(points, "y_err", CPL_TYPE_DOUBLE);
16076  cpl_table_fill_column_window_double(points, "y_err", 0, npoints, err);
16077  cpl_msg_info(func, "Error column not found - set to %f\n", err);
16078  }
16079 
16080  /*
16081  * Create columns containing modeled values at each x
16082  */
16083 
16084  if (cpl_table_has_column(points, "px"))
16085  cpl_table_erase_column(points, "px");
16086  cpl_table_new_column(points, "px", CPL_TYPE_DOUBLE);
16087  cpl_table_fill_column_window_double(points, "px", 0, npoints, 0);
16088  x = cpl_table_get_data_double(points, "x");
16089  px = cpl_table_get_data_double(points, "px");
16090  for (i = 0; i < npoints; i++)
16091  px[i] = cpl_polynomial_eval_1d(p, x[i], NULL);
16092 
16093  nevaluate = cpl_table_get_nrow(evaluate);
16094 
16095  if (cpl_table_has_column(evaluate, "px"))
16096  cpl_table_erase_column(evaluate, "px");
16097  cpl_table_new_column(evaluate, "px", CPL_TYPE_DOUBLE);
16098  cpl_table_fill_column_window_double(evaluate, "px", 0, nevaluate, 0);
16099  x_eval = cpl_table_get_data_double(evaluate, "x");
16100  px_eval = cpl_table_get_data_double(evaluate, "px");
16101  for (i = 0; i < nevaluate; i++)
16102  px_eval[i] = cpl_polynomial_eval_1d(p, x_eval[i], NULL);
16103 
16104  /*
16105  * Initialise column with sigma
16106  */
16107 
16108  if (cpl_table_has_column(evaluate, "sigma"))
16109  cpl_table_erase_column(evaluate, "sigma");
16110  cpl_table_new_column(evaluate, "sigma", CPL_TYPE_DOUBLE);
16111  cpl_table_fill_column_window_double(evaluate, "sigma", 0, nevaluate, 0);
16112  sigma = cpl_table_get_data_double(evaluate, "sigma");
16113 
16114  /*
16115  * Compute varied y cordinates to fit
16116  */
16117 
16118  if (cpl_table_has_column(points, "vy"))
16119  cpl_table_erase_column(points, "vy");
16120  cpl_table_new_column(points, "vy", CPL_TYPE_DOUBLE);
16121  cpl_table_fill_column_window_double(points, "vy", 0, npoints, 0);
16122  vy = cpl_table_get_data_double(points, "vy");
16123  dy = cpl_table_get_data_double(points, "y_err");
16124  cpl_vector_unwrap(listy);
16125  listy = cpl_vector_wrap(npoints, vy);
16126 
16127  for (i = 0; i < samples; i++) {
16128  for (j = 0; j < npoints; j++)
16129  vy[j] = px[j] + dy[j] * mos_randg(1);
16130  q = cpl_polynomial_fit_1d_create(listx, listy, order, NULL);
16131  for (j = 0; j < nevaluate; j++)
16132  sigma[j] += fabs(px_eval[j]
16133  - cpl_polynomial_eval_1d(q, x_eval[j], NULL));
16134  cpl_polynomial_delete(q);
16135  }
16136 
16137  /*
16138  * Factor 1.25 to convert average deviation to sigma
16139  */
16140 
16141  cpl_table_multiply_scalar(evaluate, "sigma", 1.25);
16142  cpl_table_divide_scalar(evaluate, "sigma", samples);
16143 
16144  cpl_vector_unwrap(listx);
16145  cpl_vector_unwrap(listy);
16146 
16147  return p;
16148 }
16149 
16150 #endif
16151 
16174 cpl_error_code mos_randomise_image(cpl_image *image, double ron,
16175  double gain, double bias)
16176 {
16177  float *data;
16178  int npix, i;
16179 
16180 
16181  if (image == NULL)
16182  return cpl_error_set(cpl_func, CPL_ERROR_NULL_INPUT);
16183 
16184  if (ron < 0.0 || gain <= FLT_EPSILON)
16185  return cpl_error_set(cpl_func, CPL_ERROR_ILLEGAL_INPUT);
16186 
16187  data = cpl_image_get_data_float(image);
16188  npix = cpl_image_get_size_x(image) * cpl_image_get_size_y(image);
16189  ron *= ron;
16190 
16191  for (i = 0; i < npix; i++) {
16192  if (data[i] < bias) {
16193  data[i] += sqrt(ron) * mos_randg(1);
16194  }
16195  else {
16196  data[i] += sqrt(ron + (data[i] - bias) / gain) * mos_randg(1);
16197  }
16198  }
16199 
16200  return CPL_ERROR_NONE;
16201 }
16202 
16203 
16218 cpl_error_code mos_refmask_find_gaps(cpl_mask *refmask,
16219  cpl_image *master_flat,
16220  double level)
16221 {
16222  int nx = cpl_mask_get_size_x(refmask);
16223  int ny = cpl_mask_get_size_y(refmask);
16224 
16225  int * xpos = cpl_calloc(sizeof(int), ny);
16226 
16227  cpl_image * filtered = cpl_image_duplicate(master_flat);
16228  cpl_mask * kernel = cpl_mask_new(9, 3);
16229  cpl_vector * v = cpl_vector_new(ny);
16230  cpl_vector * truev;
16231  int nvalid = 0;
16232  double * flats = cpl_vector_get_data(v);
16233 
16234  double median, stdev, delta;
16235 
16236  int i, kill;
16237 
16238  cpl_mask_not(kernel);
16239  cpl_image_filter_mask(filtered, master_flat, kernel,
16240  CPL_FILTER_MEDIAN, CPL_BORDER_COPY);
16241  cpl_mask_delete(kernel);
16242 
16243  for (i = 1; i <= ny; i++) {
16244  int j = 0;
16245 
16246  do j++;
16247  while (!cpl_mask_get(refmask, j, i) && j < nx);
16248 
16249  if (j < nx) {
16250  int rejected;
16251 
16252  xpos[i - 1] = j;
16253  flats[nvalid] = cpl_image_get(filtered, j, i, &rejected);
16254  nvalid++;
16255  }
16256  else {
16257  xpos[i - 1] = -1;
16258  }
16259  }
16260 
16261  if (nvalid == 0)
16262  return cpl_error_set(cpl_func, CPL_ERROR_DATA_NOT_FOUND);
16263 
16264  truev = cpl_vector_wrap(nvalid, flats);
16265 
16266  median = cpl_vector_get_median(truev);
16267 
16268  if (level < 0.0)
16269  stdev = cpl_vector_get_stdev(truev);
16270 
16271  cpl_vector_unwrap(truev);
16272  cpl_vector_delete(v);
16273 
16274  for (i = 1; i <= ny; i++) {
16275  if (xpos[i - 1] > 0) {
16276  int rejected;
16277  double kappa = 1.5;
16278 
16279  delta = cpl_image_get(filtered, xpos[i - 1], i, &rejected) - median;
16280 
16281  if (level < 0.0)
16282  kill = fabs(delta) > stdev * kappa;
16283  else
16284  kill = delta < level;
16285 
16286  if (kill) {
16287  int j = 0;
16288 
16289  while (cpl_mask_get(refmask, xpos[i - 1] + j, i)) {
16290  cpl_mask_set(refmask, xpos[i - 1] + j, i, CPL_BINARY_0);
16291  j++;
16292  }
16293  }
16294  }
16295  }
16296 
16297  cpl_image_delete(filtered);
16298  cpl_free(xpos);
16299 
16300  return cpl_error_get_code();
16301 }
16302 
16310 cpl_error_code mos_saturation_process(cpl_image * image)
16311 {
16312  int nx = cpl_image_get_size_x(image);
16313  int ny = cpl_image_get_size_y(image);
16314  int npix = nx * ny;
16315  float * sdata = cpl_image_get_data_float(image);
16316 
16317  int count, i, j, k;
16318 
16319  /*
16320  * This is used to avoid saturation level coded with pixel value zero
16321  * To make it more robust against random 0.0 values, check that also
16322  * next pixel along the spatial direction is 0.0.
16323  */
16324 
16325  //This could be applied only to raw images, but it is being applied
16326  //to already bias/overscan processed images, which doesn't make sense.
16327 // for (i = 0; i < npix - nx; i++)
16328 // if (sdata[i] == 0.0 && sdata[i + nx] == 0.0)
16329 // sdata[i] = 65535.0;
16330 
16331 // for (i = npix - nx; i < npix; i++)
16332 // if (sdata[i] == 0.0)
16333 // sdata[i] = 65535.0;
16334 
16335  /*
16336  * This is a dirty trick to overcome saturations (making up a false
16337  * tip on their flat tops). This should be useless with a better
16338  * peak detection algorithm.
16339  */
16340 
16341  for (i = 0; i < npix; i++) {
16342  if (sdata[i] >= 65535.0) {
16343  count = 0;
16344  for (j = i; j < npix; j++) {
16345  if (sdata[j] < 65535.0) {
16346  break;
16347  }
16348  else {
16349  count++;
16350  }
16351  }
16352  if (count < 30 && count > 2) {
16353  for (j = i; j < i + count/2; j++)
16354  sdata[j] = sdata[i] + 1000.0 * (j - i);
16355  if (count % 2 != 0) {
16356  sdata[j] = sdata[j-1] + 1000.0;
16357  j++;
16358  }
16359  for (k = j; k <= i + count; k++)
16360  sdata[k] = sdata[i] - 1000.0 * (k - i - count);
16361  i = k;
16362  }
16363  }
16364  }
16365 
16366  return cpl_error_get_code();
16367 }
16368 
16369 
16378 cpl_error_code mos_subtract_background(cpl_image * image)
16379 {
16380  /*
16381  * Create and subtract background
16382  */
16383 
16384  cpl_image * bimage = mos_arc_background(image, 15, 15);
16385  cpl_image_subtract(image, bimage);
16386  cpl_image_delete(bimage);
16387 
16388  return cpl_error_get_code();
16389 }
16390 
16391 
16408 cpl_error_code mos_object_intersect(cpl_table **slitss, cpl_table *origslits,
16409  int nscience, float tolerance)
16410 {
16411  int i, j;
16412 
16413  cpl_table *summary;
16414  int summary_nobjs = 0;
16415 
16416  int nobjs;
16417 
16418  int nmatches;
16419  int nslits = cpl_table_get_nrow(slitss[0]);
16420 
16421  int maxobjs;
16422  int k, m;
16423  int nstokes, sstokes;
16424 
16425  cpl_table **work;
16426 
16427  work = (cpl_table **)cpl_malloc(sizeof(cpl_table *) * nscience);
16428 
16429 
16430  /*
16431  * First we build a table listing the offset of each detected
16432  * object at each angle and each beam, from the bottom of each
16433  * slit spectrum, and the pair that slit spectrum belongs to.
16434  * This summary table will have as many rows as objects found
16435  * in total at all angles.
16436  */
16437 
16438  for (j = 0; j < nscience; j++) {
16439  int c_nobjs = mos_get_nobjects(slitss[j]);
16440  if (!c_nobjs)
16441  return cpl_error_set(cpl_func, CPL_ERROR_DATA_NOT_FOUND);
16442  summary_nobjs += c_nobjs;
16443  }
16444 
16445  summary = cpl_table_new(summary_nobjs);
16446 
16447  cpl_table_new_column(summary, "offset", CPL_TYPE_DOUBLE);
16448  cpl_table_new_column(summary, "pair", CPL_TYPE_INT);
16449  cpl_table_new_column(summary, "absolute", CPL_TYPE_DOUBLE);
16450  cpl_table_new_column(summary, "pos", CPL_TYPE_DOUBLE);
16451 
16452  /*
16453  * Fill the summary table with data from all objects:
16454  */
16455 
16456  nobjs = 0;
16457 
16458  /* Loop on all object tables (one for each angle) */
16459  for (j = 0; j < nscience; j++) {
16460  int c_maxobjs = mos_get_maxobjs_per_slit(slitss[j]);
16461 
16462  /* Loop on all slits found on first - i.e., ALL - object table */
16463  for (k = 0; k < nslits; k++) {
16464 
16465  /* Loop on all objects found on each object table */
16466  for (m = 0; m < c_maxobjs; m++) {
16467  int null;
16468  char *name = cpl_sprintf("object_%d", m + 1);
16469  double obj = cpl_table_get_double(slitss[j], name, k, &null);
16470  int pos;
16471  int pair;
16472 
16473  cpl_free(name);
16474 
16475  if (null)
16476  break; /* No object #m+1 in this slit - go to next slit */
16477 
16478  /*
16479  * Copy necessary object data to summary table. Note
16480  * that the absolute object position (row) in the
16481  * rectified image is made relative to the bottom
16482  * position (row) of the current slit.
16483  */
16484 
16485  pos = cpl_table_get_int(slitss[j], "position", k, &null);
16486  pair = cpl_table_get_int(slitss[j], "pair_id", k, &null);
16487  cpl_table_set(summary, "absolute", nobjs, obj);
16488  cpl_table_set(summary, "pos", nobjs, pos);
16489  cpl_table_set(summary, "offset", nobjs, obj - pos);
16490  cpl_table_set(summary, "pair", nobjs, pair);
16491 
16492  nobjs++;
16493  }
16494  }
16495  }
16496 
16497 // cpl_table_save(summary, NULL, NULL, "susu.fits", CPL_IO_DEFAULT);
16498 
16499  /*
16500  * Perform the intersection: what are the objects belonging
16501  * to the same slit (same pair ordinary + extraordinary) which
16502  * are observed at the same offset at all angles? Those are
16503  * the polarimetric objects.
16504  */
16505 
16506  nmatches = 0;
16507  maxobjs = mos_get_maxobjs_per_slit(slitss[0]);
16508 
16509  /*
16510  * We loop on the objects of the first-angle object table as
16511  * reference, and check whether those objects are present also
16512  * at *all* other angles. Note that the loop advances by pairs.
16513  * If the top (k = 0) slit spectrum is not an ordinary beam,
16514  * it is ignored. The loop advances by pairs, starting at the
16515  * first complete pair. It is implicitely assumed that the
16516  * slit spectrum on top is always from the ordinary beam, and
16517  * the spectrum below (k+1) its extraordinary match.
16518  */
16519 
16520  for (k = 0; k < nslits; k+=2) {
16521  int slitmatches = 0;
16522 
16523  if (k + 1 < nslits ) {
16524  if (cpl_table_get_int(slitss[0], "pair_id", k, NULL) !=
16525  cpl_table_get_int(slitss[0], "pair_id", k + 1, NULL)) {
16526 
16527  /*
16528  * This is not an ordinary beam - advance to next slit.
16529  */
16530 
16531  /* It will be incremented by two, so the effect is like k++ */
16532  k--;
16533 
16534  continue;
16535  }
16536  }
16537 
16538  for (m = 0; m < maxobjs; m++) {
16539  int null;
16540  char *name = cpl_sprintf("object_%d", m + 1);
16541  double obj = cpl_table_get_double(slitss[0], name, k, &null);
16542  double pos;
16543  int pair;
16544 
16545  char *name_obj = NULL;
16546  char *name_start = NULL;
16547  char *name_end = NULL;
16548  char *name_row = NULL;
16549  char *name_row_s = NULL;
16550 
16551  char *name_start_o = NULL;
16552  char *name_end_o = NULL;
16553  char *name_row_o = NULL;
16554  char *name_start_v = NULL;
16555  char *name_end_v = NULL;
16556  char *name_obj_v = NULL;
16557 
16558  int start, end;
16559  int length;
16560 
16561  int selected;
16562  int v, start_v, end_v;
16563  double min_v, obj_v;
16564 
16565 
16566  cpl_free(name);
16567 
16568  if (null)
16569  break;
16570 
16571  /*
16572  * Each object of the first object table belongs to a
16573  * slit spectrum (k). This slit spectrum has a position
16574  * in the rectified image, and it belongs to a given
16575  * ordinary + extraordinary pair.
16576  */
16577 
16578  pos = cpl_table_get_int(slitss[0], "position", k, &null);
16579  pair = cpl_table_get_int(slitss[0], "pair_id", k, &null);
16580 
16581  /*
16582  * Now from the summary table we can select all objects
16583  * which have the same offset (obj - pos) within all slit
16584  * spectra belonging to the same ordinary + extraordinary
16585  * pair (at all angles).
16586  */
16587 
16588  cpl_table_select_all(summary); /* Reset selection */
16589 
16590  cpl_table_and_selected_int(summary, "pair", CPL_EQUAL_TO, pair);
16591  cpl_table_and_selected_double(summary, "offset", CPL_LESS_THAN,
16592  obj - pos + tolerance);
16593  selected =
16594  cpl_table_and_selected_double(summary, "offset", CPL_GREATER_THAN,
16595  obj - pos - tolerance);
16596 
16597 
16598  /*
16599  * If this object were observed at all angles (nscience) and
16600  * at all beams (2), we should have selected exactly 2*nscience
16601  * objects. If not, this is not a polarimetric object, and it
16602  * is discarded from the intersection.
16603  */
16604 
16605  if (selected != nscience * 2)
16606  continue;
16607 
16608  /*
16609  * If we reach this point we have found one valid polarimetric
16610  * object, that must be inserted in the intersection object
16611  * table.
16612  */
16613 
16614  slitmatches++;
16615 
16616  /*
16617  * Names of the columns of the output table where the
16618  * object information needs to be copied. Note that a
16619  * new column is created, the "row_stokes_#", where the
16620  * row number of the extracted polarimetric signal is
16621  * also computed. For the moment this column will be
16622  * left empty - it will be filled only when all matches
16623  * are collected.
16624  */
16625 
16626  name_obj = cpl_sprintf("object_%d", slitmatches);
16627  name_start = cpl_sprintf("start_%d", slitmatches);
16628  name_end = cpl_sprintf("end_%d", slitmatches);
16629  name_row = cpl_sprintf("row_%d", slitmatches);
16630  name_row_s = cpl_sprintf("row_stokes_%d", slitmatches);
16631 
16632  /*
16633  * Names of the columns of the input table where the
16634  * object information is available.
16635  */
16636 
16637  name_start_o = cpl_sprintf("start_%d", m + 1);
16638  name_end_o = cpl_sprintf("end_%d", m + 1);
16639  name_row_o = cpl_sprintf("row_%d", m + 1);
16640 
16641  /*
16642  * If the output columns do not exist yet, create them.
16643  */
16644 
16645  if (!cpl_table_has_column(origslits, name_obj)) {
16646  cpl_table_new_column(origslits, name_obj, CPL_TYPE_DOUBLE);
16647  cpl_table_new_column(origslits, name_start, CPL_TYPE_INT);
16648  cpl_table_new_column(origslits, name_end, CPL_TYPE_INT);
16649  cpl_table_new_column(origslits, name_row, CPL_TYPE_INT);
16650  cpl_table_new_column(origslits, name_row_s, CPL_TYPE_INT);
16651  }
16652 
16653  /*
16654  * The current slit spectrum is k. The slit spectrum immediately
16655  * below (in the rectified image) is k+1. We need the length of
16656  * the spectrum below for computing the _absolute_ coordinates
16657  * of the objects in the rectified image in both beams.
16658  */
16659 
16660  length = cpl_table_get_int(origslits, "length", k + 1, &null);
16661 
16662  /* NEW:
16663  * Names of the columns of the input table where
16664  * the information of the corresponding object of
16665  * the next beam is available.
16666  */
16667 
16668  for (v = 0; v < maxobjs; v++) {
16669  char *name_v = cpl_sprintf("object_%d", v + 1);
16670  double obj_v = cpl_table_get_double(slitss[0], name_v,
16671  k + 1, &null);
16672 
16673  cpl_free(name_v);
16674 
16675  if (null)
16676  break;
16677 
16678  if (v) {
16679  if (fabs(obj - length - obj_v) < min_v) {
16680  min_v = fabs(obj - length - obj_v);
16681  cpl_free(name_start_v);
16682  cpl_free(name_end_v);
16683  cpl_free(name_obj_v);
16684  name_start_v = cpl_sprintf("start_%d", v + 1);
16685  name_end_v = cpl_sprintf("end_%d", v + 1);
16686  name_obj_v = cpl_sprintf("object_%d", v + 1);
16687  }
16688  }
16689  else {
16690  min_v = fabs(obj - length - obj_v);
16691  name_start_v = cpl_sprintf("start_%d", v + 1);
16692  name_end_v = cpl_sprintf("end_%d", v + 1);
16693  name_obj_v = cpl_sprintf("object_%d", v + 1);
16694  }
16695  }
16696 
16697  /*
16698  * Read from the first input object table (first angle)
16699  * the spatial window enclosing the object.
16700  */
16701 
16702  start = cpl_table_get_int(slitss[0], name_start_o, k, &null);
16703  end = cpl_table_get_int(slitss[0], name_end_o, k, &null);
16704 
16705  /* NEW:
16706  * Spatial window of the matching object in the next beam.
16707  */
16708 
16709  start_v = cpl_table_get_int(slitss[0], name_start_v, k + 1, &null);
16710  end_v = cpl_table_get_int(slitss[0], name_end_v, k + 1, &null);
16711  obj_v = cpl_table_get_double(slitss[0], name_obj_v, k + 1, &null);
16712 
16713  /*
16714  * Write the object coordinates in the same slit, and in the
16715  * slit below. Note that here we assume that all slits were
16716  * traced perfectly, and we compute the theoretical coords
16717  * (obj - length) within the next slit spectrum (k + 1). In
16718  * principle we should read them as well from the input
16719  * table!
16720  */
16721 
16722  cpl_table_set_double(origslits, name_obj, k, obj);
16723  cpl_table_set_double(origslits, name_obj, k + 1, obj_v);
16724  // cpl_table_set_double(origslits, name_obj, k + 1, obj - length);
16725 
16726  cpl_table_set_int(origslits, name_start, k, start);
16727  cpl_table_set_int(origslits, name_start, k + 1, start_v);
16728  // cpl_table_set_int(origslits, name_start, k + 1, start - length);
16729 
16730  cpl_table_set_int(origslits, name_end, k, end);
16731  cpl_table_set_int(origslits, name_end, k + 1, end_v);
16732  // cpl_table_set_int(origslits, name_end, k + 1, end - length);
16733 
16734  /*
16735  * "nmatches" is counting at what "reduced" image row the
16736  * extracted spectra are. Note that this is s preliminary
16737  * numbering - which is wrong: other objects may be found
16738  * in the same slit, and then the indeces would not be in
16739  * sequence. What is important is that at the end of this
16740  * loop "nmatches" would be the total number of matching
16741  * objects. The two cpl_table_set_int() calls made here
16742  * cannot be removed - they "validate" those table elements
16743  * (see ahead).
16744  */
16745 
16746  cpl_table_set_int(origslits, name_row, k, nmatches);
16747  nmatches++;
16748  cpl_table_set_int(origslits, name_row, k + 1, nmatches);
16749  nmatches++;
16750 
16751  cpl_free(name_obj);
16752  cpl_free(name_start);
16753  cpl_free(name_end);
16754  cpl_free(name_row);
16755  cpl_free(name_row_s);
16756 
16757  cpl_free(name_start_o);
16758  cpl_free(name_end_o);
16759  cpl_free(name_row_o);
16760 
16761  cpl_free(name_start_v); name_start_v = NULL;
16762  cpl_free(name_end_v); name_end_v = NULL;
16763  cpl_free(name_obj_v); name_obj_v = NULL;
16764  }
16765  }
16766 
16767  /*
16768  * The summary table has fulfilled its function. If no matching
16769  * objects are found, the function returns with an error.
16770  */
16771 
16772  cpl_table_delete(summary);
16773 
16774  if (!nmatches)
16775  return cpl_error_set(cpl_func, CPL_ERROR_DATA_NOT_FOUND);
16776 
16777  /*
16778  * Now we consider the resulting intersection object table,
16779  * listing all matches. As seen, the image row number reported
16780  * in the columns "row_#" was not really performed sequentially.
16781  * We need to renumber sequentially...
16782  * We need also to fill the "row_stokes_#" column the way the
16783  * extracted polarimetric signal will be stored in the
16784  * reduced_pol_images...
16785  */
16786 
16787  maxobjs = mos_get_maxobjs_per_slit(origslits);
16788  nstokes = nmatches / 2; /* nmatches is always an even number */
16789 
16790  for (k = 0; k < nslits; k++) {
16791  if (k % 2) { /* Extraordinary beam */
16792  nstokes = sstokes; /* Use same start value as for ordinary */
16793  }
16794  else { /* Ordinary beam */
16795  sstokes = nstokes; /* Memorise start value at ordinary beam */
16796  }
16797 
16798  for (m = 0; m < maxobjs; m++) {
16799  char *name = cpl_sprintf("row_%d", m + 1);
16800  char *namestokes = cpl_sprintf("row_stokes_%d", m + 1);
16801 
16802  if (!cpl_table_is_valid(origslits, name, k)) {
16803  cpl_free(name);
16804  cpl_free(namestokes);
16805  break;
16806  }
16807  else {
16808  nmatches--;
16809  nstokes--;
16810  cpl_table_set_int(origslits, name, k, nmatches);
16811  cpl_table_set_int(origslits, namestokes, k, nstokes);
16812  }
16813 
16814  cpl_free(name);
16815  cpl_free(namestokes);
16816  }
16817  }
16818 
16819 
16820  /*
16821  * This is done to avoid the NULL value is zero (it would invalidate
16822  * also the row_# = 0 or start_# = 0 for an object), and to enable
16823  * working directly with the column data buffers, when using this
16824  * table afterwards.
16825  */
16826 
16827  for (j = 0; j < maxobjs; j++) {
16828  char *name = cpl_sprintf("object_%d", j + 1);
16829  cpl_table_fill_invalid_double(origslits, name, -1);
16830  cpl_free(name);
16831 
16832  name = cpl_sprintf("start_%d", j + 1);
16833  cpl_table_fill_invalid_int(origslits, name, -1);
16834  cpl_free(name);
16835 
16836  name = cpl_sprintf("end_%d", j + 1);
16837  cpl_table_fill_invalid_int(origslits, name, -1);
16838  cpl_free(name);
16839 
16840  name = cpl_sprintf("row_%d", j + 1);
16841  cpl_table_fill_invalid_int(origslits, name, -1);
16842  cpl_free(name);
16843 
16844  name = cpl_sprintf("row_stokes_%d", j + 1);
16845  cpl_table_fill_invalid_int(origslits, name, -1);
16846  cpl_free(name);
16847  }
16848 
16849  /*********************************************************************
16850  * This tail has been added to propagate the selection of valid
16851  * objects also to the input slitss[] tables. Just eliminate all
16852  * this final part to suppress this behaviour.
16853  */
16854 
16855  /*
16856  * First of all, make a working copy and remove all columns related
16857  * to objects from the input object tables.
16858  */
16859 
16860  for (i = 0; i < nscience; i++) {
16861  int c_maxobjs = mos_get_maxobjs_per_slit(slitss[i]);
16862 
16863  work[i] = cpl_table_duplicate(slitss[i]);
16864 
16865  for (m = 0; m < c_maxobjs; m++) {
16866  char *object_o = cpl_sprintf("object_%d", m + 1);
16867  char *start_o = cpl_sprintf("start_%d", m + 1);
16868  char *end_o = cpl_sprintf("end_%d", m + 1);
16869  char *row_o = cpl_sprintf("row_%d", m + 1);
16870 
16871  cpl_table_erase_column(slitss[i], object_o);
16872  cpl_table_erase_column(slitss[i], start_o);
16873  cpl_table_erase_column(slitss[i], end_o);
16874  cpl_table_erase_column(slitss[i], row_o);
16875  }
16876  }
16877 
16878  /*
16879  * Now just consider all the objects in the intersection table.
16880  */
16881 
16882  for (k = 0; k < nslits; k++) {
16883  for (j = 0; j < maxobjs; j++) {
16884  double object_w, object_r;
16885  int row_w;
16886 
16887  char *object_i = cpl_sprintf("object_%d", j + 1);
16888  char *start_i = cpl_sprintf("start_%d", j + 1);
16889  char *end_i = cpl_sprintf("end_%d", j + 1);
16890  char *row_i = cpl_sprintf("row_%d", j + 1);
16891 
16892 
16893  if (!cpl_table_is_valid(origslits, object_i, k))
16894  break;
16895 
16896  /*
16897  * We have found a valid object (valid because it belongs
16898  * to the intersection). Now we look for this object in each
16899  * one of the original tables, we get its parameters, and
16900  * copy them at the right position (i.e., same position as
16901  * in intersection table). The object will be the one closest
16902  * to the object position (column object_i) in the intersection
16903  * table. Note that we examine the same row, k, in all tables.
16904  */
16905 
16906  object_w = cpl_table_get_double(origslits, object_i, k, NULL);
16907  row_w = cpl_table_get_int (origslits, row_i, k, NULL);
16908 
16909  for (i = 0; i < nscience; i++) {
16910  int c_maxobjs = mos_get_maxobjs_per_slit(work[i]);
16911  int minpos;
16912  double mindiff, diff;
16913  char *object_o;
16914  char *start_o;
16915  char *end_o;
16916  char *row_o;
16917 
16918  for (m = 0; m < c_maxobjs; m++) {
16919  object_o = cpl_sprintf("object_%d", m + 1);
16920  start_o = cpl_sprintf("start_%d", m + 1);
16921  end_o = cpl_sprintf("end_%d", m + 1);
16922  row_o = cpl_sprintf("row_%d", m + 1);
16923 
16924  if (!cpl_table_is_valid(work[i], object_o, k))
16925  break;
16926 
16927  object_r = cpl_table_get_double(work[i], object_o, k, NULL);
16928  //row_r = cpl_table_get_int (work[i], row_o, k, NULL);
16929 
16930  diff = fabs(object_w - object_r);
16931  if (m) {
16932  if (mindiff > diff) {
16933  mindiff = diff;
16934  minpos = m;
16935  }
16936  }
16937  else {
16938  mindiff = diff;
16939  minpos = 0;
16940  }
16941 
16942  cpl_free(object_o);
16943  cpl_free(start_o);
16944  cpl_free(end_o);
16945  cpl_free(row_o);
16946  }
16947 
16948  object_o = cpl_sprintf("object_%d", minpos + 1);
16949  start_o = cpl_sprintf("start_%d", minpos + 1);
16950  end_o = cpl_sprintf("end_%d", minpos + 1);
16951  row_o = cpl_sprintf("row_%d", minpos + 1);
16952 
16953  if (!cpl_table_has_column(slitss[i], object_i)) {
16954  cpl_table_new_column(slitss[i], object_i, CPL_TYPE_DOUBLE);
16955  cpl_table_new_column(slitss[i], start_i, CPL_TYPE_INT);
16956  cpl_table_new_column(slitss[i], end_i, CPL_TYPE_INT);
16957  cpl_table_new_column(slitss[i], row_i, CPL_TYPE_INT);
16958  cpl_table_fill_invalid_double(slitss[i], object_i, -1);
16959  cpl_table_fill_invalid_int (slitss[i], start_i, -1);
16960  cpl_table_fill_invalid_int (slitss[i], end_i, -1);
16961  cpl_table_fill_invalid_int (slitss[i], row_i, -1);
16962  }
16963 
16964  cpl_table_set_double(slitss[i], object_i, k,
16965  cpl_table_get_double(work[i], object_o,
16966  k, NULL));
16967  cpl_table_set_int(slitss[i], start_i , k,
16968  cpl_table_get_int(work[i], start_o, k, NULL));
16969  cpl_table_set_int(slitss[i], end_i , k,
16970  cpl_table_get_int(work[i], end_o, k, NULL));
16971  cpl_table_set_int(slitss[i], row_i , k, row_w);
16972 
16973  cpl_free(object_o);
16974  cpl_free(start_o);
16975  cpl_free(end_o);
16976  cpl_free(row_o);
16977  }
16978 
16979  cpl_free(object_i);
16980  cpl_free(start_i);
16981  cpl_free(end_i);
16982  cpl_free(row_i);
16983  }
16984  }
16985 
16986  for (i = 0; i < nscience; i++)
16987  cpl_table_delete(work[i]);
16988 
16989  cpl_free(work);
16990 
16991 
16992  return cpl_error_get_code();
16993 }
16994 
16995 
17003 int mos_get_maxobjs_per_slit(cpl_table * slits)
17004 {
17005  int maxobjs = 1;
17006 
17007  char * colname = cpl_sprintf("object_%d", maxobjs);
17008 
17009  while (cpl_table_has_column(slits, colname)) {
17010  maxobjs++;
17011  cpl_free(colname);
17012  colname = cpl_sprintf("object_%d", maxobjs);
17013  }
17014 
17015  cpl_free(colname);
17016 
17017  maxobjs--;
17018 
17019  return maxobjs;
17020 }
17021 
17029 int mos_get_nobjects(cpl_table * slits)
17030 {
17031  int nobjs = 0;
17032 
17033  int nslits = cpl_table_get_nrow(slits);
17034  int maxobjs = mos_get_maxobjs_per_slit(slits);
17035 
17036  int k, m;
17037 
17038  for (k = 0; k < nslits; k++) {
17039  for (m = 0; m < maxobjs; m++) {
17040  char * name = cpl_sprintf("object_%d", m + 1);
17041  int null = !cpl_table_is_valid(slits, name, k);
17042 
17043  cpl_free(name);
17044 
17045  if (null) break;
17046  else nobjs++;
17047  }
17048  }
17049 
17050  return nobjs;
17051 }
17052 
17060 int mos_check_slits(cpl_table *slits, float rescale)
17061 {
17062 
17063  cpl_propertylist *sort;
17064 
17065  int nslits = cpl_table_get_nrow(slits);
17066 
17067  int k, null;
17068 
17069  const float interval = 90.0 * rescale;
17070  const float offset = (90.0 - 5) * rescale;
17071 
17072 
17073  for (k = 0; k < nslits; k++) {
17074  double ytop = cpl_table_get_double(slits, "ytop", k, &null);
17075  double ybottom = cpl_table_get_double(slits, "ybottom", k, &null);
17076 
17077  double xtop = cpl_table_get_double(slits, "xtop", k, &null);
17078  double xbottom = cpl_table_get_double(slits, "xbottom", k, &null);
17079 
17080  int nmiss = (int)((ytop - ybottom) / interval + 0.5);
17081 
17082  if (nmiss > 1) {
17083  cpl_msg_warning(cpl_func,
17084  "Some slits could not be properly detected. "
17085  "There might be accountable inaccuracies.");
17086  while (nmiss > 1) {
17087  cpl_table_set_size(slits, nslits + 1);
17088 
17089  /* Fill in new slit 'cut' */
17090 
17091  /* x coordinates be the same (acceptable approximation) */
17092  cpl_table_set_double(slits, "xtop", nslits, xtop);
17093  cpl_table_set_double(slits, "xbottom", nslits, xbottom);
17094 
17095  /* Cut */
17096  if (k == 0) {
17097  cpl_table_set_double(slits, "ybottom", nslits, ybottom);
17098  cpl_table_set_double(slits, "ytop", nslits, ybottom
17099  + offset);
17100  ybottom += interval;
17101  cpl_table_set_double(slits, "ybottom", k, ybottom);
17102  } else {
17103  cpl_table_set_double(slits, "ytop", nslits, ytop);
17104  cpl_table_set_double(slits, "ybottom", nslits, ytop
17105  - offset);
17106  ytop -= interval;
17107  cpl_table_set_double(slits, "ytop", k, ytop);
17108  }
17109 
17110  nslits++; nmiss--;
17111  }
17112  }
17113  }
17114 
17115  sort = cpl_propertylist_new();
17116  cpl_propertylist_append_bool(sort, "ytop", 1);
17117  cpl_table_sort(slits, sort);
17118  cpl_propertylist_delete(sort);
17119 
17120  /*
17121  * Add here an ad hoc check on the last slit: is it too long
17122  * (by more than 10%)? Then shorten it...
17123  */
17124 
17125  k = cpl_table_get_nrow(slits) - 1;
17126 
17127  {
17128  double ytop = cpl_table_get_double(slits, "ytop", k, &null);
17129  double ybottom = cpl_table_get_double(slits, "ybottom", k, &null);
17130  double length = (ytop - ybottom) / interval;
17131 
17132  if (length > 1.1) {
17133  cpl_table_set_double(slits, "ybottom", k, ytop - offset);
17134  }
17135 
17136  }
17137 
17138  return 0;
17139 }
17140 
17163 cpl_table *mos_load_slits_fors_pmos(cpl_propertylist *header,
17164  int * nslits_out_det)
17165 {
17166  int m, null;
17167  int halfsize;
17168 
17169  cpl_propertylist * sort;
17170  cpl_table * slits;
17171 
17172  slits = mos_load_slits_fors_mos(header, nslits_out_det);
17173  halfsize = cpl_table_get_nrow(slits);
17174 
17175  cpl_table_set_size(slits, 2 * halfsize);
17176 
17177  for (m = 0; m < halfsize; m++) {
17178 
17179  double gap = 1.4;
17180 
17181  double length =
17182  cpl_table_get(slits, "ytop", m, &null) -
17183  cpl_table_get(slits, "ybottom", m, &null);
17184 
17185  if (m) {
17186  double interval =
17187  cpl_table_get(slits, "ybottom", m - 1, &null) -
17188  cpl_table_get(slits, "ytop", m, &null);
17189 
17190  gap = (interval - length) / 2;
17191  }
17192 
17193  cpl_table_set(slits, "slit_id", m + halfsize,
17194  cpl_table_get(slits, "slit_id", m, &null) - 1);
17195 
17196  cpl_table_set(slits, "xtop", m + halfsize,
17197  cpl_table_get(slits, "xtop", m, &null));
17198 
17199  cpl_table_set(slits, "xbottom", m + halfsize,
17200  cpl_table_get(slits, "xbottom", m, &null));
17201 
17202  cpl_table_set(slits, "ytop", m + halfsize,
17203  cpl_table_get(slits, "ytop", m, &null) + gap + length);
17204 
17205  cpl_table_set(slits, "ybottom", m + halfsize,
17206  cpl_table_get(slits, "ytop", m, &null) + gap);
17207  }
17208 
17209  for (m = 0; m < 2 * halfsize; m++) {
17210  cpl_table_set(slits, "ytop", m,
17211  cpl_table_get(slits, "ytop", m, &null) - 5.3);
17212 
17213  cpl_table_set(slits, "ybottom", m,
17214  cpl_table_get(slits, "ybottom", m, &null) - 5.3);
17215 
17216  }
17217 
17218  sort = cpl_propertylist_new();
17219  cpl_propertylist_append_bool(sort, "ytop", 1);
17220  cpl_table_sort(slits, sort);
17221 
17222  cpl_propertylist_delete(sort);
17223 
17224  return slits;
17225 }
17226 
17227 int * fors_get_nobjs_perslit(cpl_table * slits)
17228 {
17229  int nslits = cpl_table_get_nrow(slits);
17230  int maxobjs = mos_get_maxobjs_per_slit(slits);
17231 
17232  int * nobjs_per_slit = cpl_malloc(sizeof(int) * nslits);
17233 
17234  int k, m;
17235 
17236  for (k = 0; k < nslits; k++) {
17237  int nobjs = 0;
17238  for (m = 0; m < maxobjs; m++) {
17239  char * name = cpl_sprintf("object_%d", m + 1);
17240  int null = !cpl_table_is_valid(slits, name, k);
17241 
17242  cpl_free(name);
17243 
17244  if (null) break;
17245  else nobjs++;
17246  }
17247 
17248  nobjs_per_slit[k] = nobjs;
17249  }
17250 
17251  return nobjs_per_slit;
17252 }
17253 
17254 double fors_get_object_position(cpl_table *slits, int slit, int object)
17255 {
17256  char *name = cpl_sprintf("object_%d", object);
17257  double position;
17258 
17259  position = cpl_table_get_double(slits, name, slit, NULL)
17260  - cpl_table_get_int(slits, "position", slit, NULL);
17261 
17262  cpl_free(name);
17263 
17264  return position;
17265 }
17266 
17267 int mos_rebin_signal(cpl_image **image, int rebin)
17268 {
17269  cpl_image *rebinned;
17270 
17271 
17272  if (*image == NULL)
17273  return 1;
17274 
17275  if (rebin == 1)
17276  return 0;
17277 
17278  rebinned = cpl_image_rebin(*image, 1, 1, rebin, 1);
17279 
17280  cpl_image_delete(*image);
17281 
17282  *image = rebinned;
17283 
17284  return 0;
17285 }
17286 
17287 int mos_rebin_error(cpl_image **image, int rebin)
17288 {
17289  if (*image == NULL)
17290  return 1;
17291 
17292  if (rebin == 1)
17293  return 0;
17294 
17295  cpl_image_power(*image, 2);
17296  mos_rebin_signal(image, rebin);
17297  cpl_image_power(*image, 0.5);
17298 
17299  return 0;
17300 }
17301 
17302 /*
17303  * @brief
17304  * Map table values into a 1D image
17305  *
17306  * @param image Target image
17307  * @param start Coordinate of first pixel in image
17308  * @param step Coordinate step for one pixel in image
17309  * @param table Source table
17310  * @param xname Name of coordinate column
17311  * @param yname Name of values column
17312  *
17313  * @return 0 on success
17314  *
17315  * The values in @em yname are linearly interpolated at the @em image
17316  * pixel coordinates. The @em image must have Nx1 size.
17317  */
17318 
17319 int map_table(cpl_image *image, double start, double step,
17320  cpl_table *table, const char *xname, const char *yname)
17321 {
17322  int length = cpl_image_get_size_x(image);
17323  int nrows = cpl_table_get_nrow(table);
17324  float *data = cpl_image_get_data_float(image);
17325  float *fdata = NULL;
17326  double *xdata = NULL;
17327  double *ydata = NULL;
17328  cpl_type xtype = cpl_table_get_column_type(table, xname);
17329  cpl_type ytype = cpl_table_get_column_type(table, yname);
17330  double xzero, pos;
17331  int i, j, n;
17332 
17333 
17334  /*
17335  * Initialization of output image at 0.0 - this value is left
17336  * on non-overlapping portions.
17337  */
17338 
17339  for (i = 0; i < length; i++)
17340  data[i] = 0.0;
17341 
17342 
17343  /*
17344  * Do everything in double precision
17345  */
17346 
17347  if (xtype == CPL_TYPE_FLOAT) {
17348  fdata = cpl_table_get_data_float(table, xname);
17349  xdata = cpl_malloc(nrows * sizeof(double));
17350  for (i = 0; i < nrows; i++) {
17351  xdata[i] = fdata[i];
17352  }
17353  }
17354  else {
17355  xdata = cpl_table_get_data_double(table, xname);
17356  }
17357 
17358  if (ytype == CPL_TYPE_FLOAT) {
17359  fdata = cpl_table_get_data_float(table, yname);
17360  ydata = cpl_malloc(nrows * sizeof(double));
17361  for (i = 0; i < nrows; i++) {
17362  ydata[i] = fdata[i];
17363  }
17364  }
17365  else {
17366  ydata = cpl_table_get_data_double(table, yname);
17367  }
17368 
17369  /*
17370  * Mapping
17371  */
17372 
17373  n = 0;
17374  xzero = xdata[n];
17375 
17376  for (i = 0; i < length; i++) {
17377  pos = start + step * i;
17378  if (pos < xzero)
17379  continue;
17380  for (j = n; j < nrows; j++) {
17381  if (xdata[j] > pos) {
17382  n = j;
17383  data[i] = ydata[j-1]
17384  + (ydata[j] - ydata[j-1])
17385  * (pos - xdata[j-1]) / (xdata[j] - xdata[j-1]);
17386  break;
17387  }
17388  }
17389  }
17390 
17391  if (xtype == CPL_TYPE_FLOAT)
17392  cpl_free(xdata);
17393 
17394  if (ytype == CPL_TYPE_FLOAT)
17395  cpl_free(ydata);
17396 
17397  return 0;
17398 }
17399 
17400 
17401 /*
17402  * @brief
17403  * Fit overall trend of a Nx1 image
17404  *
17405  * @param image Values to smooth
17406  * @param order Order of fitting polynomial
17407  * @param hw Half width of smoothing window
17408  *
17409  * @return Smoothed image, or NULL on failure.
17410  *
17411  * Heavily smooth and fit data in the input Nx1 size @em image.
17412  */
17413 
17414 static cpl_image *polysmooth(cpl_image *image, int order, int hw)
17415 {
17416  int npoints;
17417  cpl_vector *x;
17418  cpl_vector *y;
17419  double *xdata;
17420  double *ydata;
17421  cpl_polynomial *poly;
17422  cpl_vector *ysmooth;
17423  cpl_image *smoothed;
17424  float *sdata;
17425  int i;
17426 
17427 
17428  npoints = cpl_image_get_size_x(image);
17429 
17430  if (2 * hw + 1 > npoints)
17431  return NULL;
17432 
17433  x = cpl_vector_new(npoints);
17434  y = cpl_vector_new(npoints);
17435  xdata = cpl_vector_get_data(x);
17436  ydata = cpl_vector_get_data(y);
17437 
17438  smoothed = cpl_image_duplicate(image);
17439  sdata = cpl_image_get_data_float(smoothed);
17440 
17441  for (i = 0; i < npoints; i++) {
17442  xdata[i] = i;
17443  ydata[i] = sdata[i];
17444  }
17445 
17446  ysmooth = cpl_vector_filter_median_create(y, hw);
17447  cpl_vector_delete(y);
17448 
17449  poly = cpl_polynomial_fit_1d_create(x, ysmooth, order, NULL);
17450  cpl_vector_delete(x);
17451  cpl_vector_delete(ysmooth);
17452 
17453  if (poly) {
17454  for (i = 0; i < npoints; i++)
17455  sdata[i] = cpl_polynomial_eval_1d(poly, i, NULL);
17456 
17457  cpl_polynomial_delete(poly);
17458  }
17459  else {
17460  cpl_image_delete(smoothed);
17461  return NULL;
17462  }
17463 
17464  return smoothed;
17465 }
17466 
17467 #undef cleanup
17468 #define cleanup \
17469 do { \
17470  cpl_image_delete(spectrum); \
17471  cpl_image_delete(flux); \
17472  cpl_image_delete(efficiency); \
17473  cpl_image_delete(smo_efficiency); \
17474  cpl_image_delete(extinction); \
17475  cpl_image_delete(response); \
17476  cpl_image_delete(smo_response); \
17477  cpl_image_delete(physical); \
17478 } while (0)
17479 
17503 cpl_table *mos_photometric_calibration(cpl_image *spectra, double startwave,
17504  double dispersion, double gain,
17505  double exptime, cpl_table *ext_table,
17506  double airmass, cpl_table *flux_table,
17507  int order)
17508 {
17509 
17510  cpl_image *spectrum = NULL; // Extracted standard star spectrum
17511  float *data;
17512  cpl_image *extinction = NULL; // Extinction binned as "spectrum"
17513  float *ext_data;
17514  cpl_image *flux = NULL; // Standard star flux binned as "spectrum"
17515  float *flux_data;
17516  cpl_image *physical = NULL; // Physical units of above
17517  float *phys_data;
17518  cpl_image *efficiency = NULL; // Raw efficiency curve
17519  float *eff_data;
17520  cpl_image *smo_efficiency = NULL; // Smoothed efficiency curve
17521  float *smo_eff_data;
17522  cpl_image *response = NULL; // Raw response curve
17523  float *res_data;
17524  cpl_image *smo_response = NULL; // Smoothed response curve
17525  float *smo_res_data;
17526  cpl_image *image;
17527  cpl_image *smo_image;
17528  cpl_table *table;
17529  float lambda;
17530  int nx, ny;
17531  int ext_count, ext_pos;
17532  int eff_count, eff_pos;
17533  int flux_count, flux_pos;
17534  int start, end;
17535  int i;
17536 
17537 
17538  if (spectra == NULL || ext_table == NULL || flux_table == NULL) {
17539  cpl_error_set(cpl_func, CPL_ERROR_NULL_INPUT);
17540  return NULL;
17541  }
17542 
17543  if (!cpl_table_has_column(ext_table, "WAVE")) {
17544  cpl_error_set_message(cpl_func, CPL_ERROR_DATA_NOT_FOUND,
17545  "Column WAVE in atmospheric extinction table");
17546  return NULL;
17547  }
17548 
17549  if (!cpl_table_has_column(ext_table, "EXTINCTION")) {
17550  cpl_error_set_message(cpl_func, CPL_ERROR_DATA_NOT_FOUND,
17551  "Column EXTINCTION in atmospheric extinction table");
17552  return NULL;
17553  }
17554 
17555  if (!cpl_table_has_column(flux_table, "WAVE")) {
17556  cpl_error_set_message(cpl_func, CPL_ERROR_DATA_NOT_FOUND,
17557  "Column WAVE in standard star flux table");
17558  return NULL;
17559  }
17560 
17561  if (!cpl_table_has_column(flux_table, "FLUX")) {
17562  cpl_error_set_message(cpl_func, CPL_ERROR_DATA_NOT_FOUND,
17563  "Column FLUX in standard star flux table");
17564  return NULL;
17565  }
17566 
17567  if (gain < 0.1) {
17568  cpl_error_set_message(cpl_func, CPL_ERROR_ILLEGAL_INPUT,
17569  "Invalid gain factor (%.2f)", gain);
17570  return NULL;
17571  }
17572 
17573  if (exptime < 0.001) {
17574  cpl_error_set_message(cpl_func, CPL_ERROR_ILLEGAL_INPUT,
17575  "Invalid exposure time (%.2f)", exptime);
17576  return NULL;
17577  }
17578 
17579  if (dispersion < 0.001) {
17580  cpl_error_set_message(cpl_func, CPL_ERROR_ILLEGAL_INPUT,
17581  "Invalid dispersion (%.2f)", dispersion);
17582  return NULL;
17583  }
17584 
17585  if (order < 2) {
17586  cpl_error_set_message(cpl_func, CPL_ERROR_ILLEGAL_INPUT,
17587  "Order of the polynomial fitting the "
17588  "instrument response must be at least 2");
17589  return NULL;
17590  }
17591 
17592  nx = cpl_image_get_size_x(spectra);
17593  ny = cpl_image_get_size_y(spectra);
17594 
17595 
17596  /*
17597  * Find brightest spectrum and duplicate it.
17598  */
17599 
17600  if (ny == 1) {
17601  spectrum = cpl_image_duplicate(spectra);
17602  }
17603  else {
17604  cpl_size x, y;
17605  cpl_image *brights = cpl_image_collapse_create(spectra, 1);
17606 
17607  cpl_image_get_maxpos(brights, &x, &y);
17608  cpl_image_delete(brights);
17609  spectrum = cpl_image_extract(spectra, 1, y, nx, y);
17610  }
17611 
17612 
17613  /*
17614  * Convert standard star spectrum in electrons per second per Angstrom.
17615  */
17616 
17617  cpl_image_multiply_scalar(spectrum, gain / exptime / dispersion);
17618 
17619 
17620  /*
17621  * Map the atmospheric extinction factors to the same lambda sampling
17622  * of the extracted spectrum.
17623  */
17624 
17625  extinction = cpl_image_duplicate(spectrum);
17626  map_table(extinction, startwave + dispersion/2, dispersion,
17627  ext_table, "WAVE", "EXTINCTION");
17628 
17629 
17630  /*
17631  * Convert from magnitudes to actual flux loss.
17632  */
17633 
17634  cpl_image_multiply_scalar(extinction, 0.4 * airmass);
17635  cpl_image_exponential(extinction, 10.);
17636 
17637 
17638  /*
17639  * Correct the scientific spectrum to airmass 0
17640  */
17641 
17642  cpl_image_multiply(spectrum, extinction);
17643 
17644 
17645  /*
17646  * Find in what pixel interval (start at "ext_pos", for "ext_count"
17647  * pixels) the atmospheric extinction is available.
17648  */
17649 
17650  ext_data = cpl_image_get_data_float(extinction);
17651 
17652  ext_count = 0;
17653  ext_pos = 0;
17654  for (i = 0; i < nx; i++) {
17655  if (ext_data[i] > 0.0) {
17656  if (ext_count == 0) {
17657  ext_pos = i;
17658  }
17659  ext_count++;
17660  }
17661  else {
17662  if (ext_count) {
17663  break;
17664  }
17665  }
17666  }
17667 
17668  cpl_image_delete(extinction); extinction = NULL;
17669 
17670 
17671  /*
17672  * Map the standard star catalog flux to the same lambda sampling
17673  * of the extracted spectrum.
17674  */
17675 
17676  flux = cpl_image_duplicate(spectrum);
17677  map_table(flux, startwave + dispersion/2, dispersion,
17678  flux_table, "WAVE", "FLUX");
17679 
17680 
17681  /*
17682  * Find in what pixel interval (start at "flux_pos", for "flux_count"
17683  * pixels) the standard star flux is available.
17684  */
17685 
17686  flux_data = cpl_image_get_data_float(flux);
17687 
17688  flux_count = 0;
17689  flux_pos = 0;
17690  for (i = 0; i < nx; i++) {
17691  if (flux_data[i] > 0.0) {
17692  if (flux_count == 0) {
17693  flux_pos = i;
17694  }
17695  flux_count++;
17696  }
17697  else {
17698  if (flux_count) {
17699  break;
17700  }
17701  }
17702  }
17703 
17704 
17705  /*
17706  * Intersection with previous selection
17707  */
17708 
17709  start = ext_pos > flux_pos ? ext_pos : flux_pos;
17710  end = (ext_pos + ext_count) < (flux_pos + flux_count) ?
17711  (ext_pos + ext_count) : (flux_pos + flux_count);
17712  flux_pos = start;
17713  flux_count = end - start;
17714 
17715 
17716  /*
17717  * Convert the flux to photons (per second per Angstrom).
17718  * std_flux is in units of erg / cm^2 / s / Angstrom. This
17719  * must be multiplied by the efficient area of the telescope,
17720  * 5.18E+5 cm^2, and divided by hv (v = frequency). With
17721  * hc = 1.98E-8 erg*Angstrom one obtains the following:
17722  */
17723 
17724  physical = cpl_image_duplicate(spectrum);
17725  phys_data = cpl_image_get_data_float(physical);
17726 
17727  for (i = 0; i < nx; i++) {
17728  lambda = startwave + dispersion * (i + 0.5);
17729  phys_data[i] = 0.0026 * lambda * flux_data[i];
17730  }
17731 
17732  efficiency = cpl_image_duplicate(spectrum);
17733  eff_data = cpl_image_get_data_float(efficiency);
17734  data = cpl_image_get_data_float(spectrum);
17735 
17736  for (i = 0; i < nx; i++) {
17737  if (phys_data[i] > 0.0)
17738  eff_data[i] = data[i] / phys_data[i];
17739  else
17740  eff_data[i] = 0.0;
17741  }
17742 
17743  cpl_image_delete(physical); physical = NULL;
17744 
17745 
17746  /*
17747  * Find interval (longer than 300 pixels) where efficiency is
17748  * greater than 1%
17749  */
17750 
17751  eff_count = 0;
17752  eff_pos = 0;
17753  for (i = 0; i < nx; i++) {
17754  if (eff_data[i] > 0.01) {
17755  if (eff_count == 0) {
17756  eff_pos = i;
17757  }
17758  eff_count++;
17759  }
17760  else {
17761  if (eff_count > 300) {
17762  break;
17763  }
17764  }
17765  }
17766 
17767 
17768  /*
17769  * Intersection with previous selection
17770  */
17771 
17772  start = eff_pos > flux_pos ? eff_pos : flux_pos;
17773  end = (eff_pos + eff_count) < (flux_pos + flux_count) ?
17774  (eff_pos + eff_count) : (flux_pos + flux_count);
17775  eff_pos = start;
17776  eff_count = end - start;
17777 
17778  if (eff_count < 1) {
17779  cpl_error_set_message(cpl_func, CPL_ERROR_INCOMPATIBLE_INPUT,
17780  "No overlap between catalog and spectrum");
17781  cleanup;
17782  return NULL;
17783  }
17784 
17785 
17786  /*
17787  * Extract only data to fit, i.e., where the efficiency is available.
17788  */
17789 
17790  image = cpl_image_extract(efficiency, eff_pos + 1, 1,
17791  eff_pos + eff_count, 1);
17792 
17793  smo_image = polysmooth(image, order, 50);
17794  cpl_image_delete(image);
17795 
17796  smo_efficiency = cpl_image_duplicate(efficiency);
17797  smo_eff_data = cpl_image_get_data_float(smo_efficiency);
17798  cpl_image_copy(smo_efficiency, smo_image, eff_pos + 1, 1);
17799 
17800  cpl_image_delete(smo_image);
17801 
17802 
17803  /*
17804  * Compute instrument response as the ratio between the catalog
17805  * spectrum and the observed spectrum (converted in physical units).
17806  * The polynomial smoothing, however, is performed on the inverse
17807  * of this ration, for obvious reasons (i.e., no divergence at zero
17808  * efficiency).
17809  */
17810 
17811  response = cpl_image_duplicate(spectrum);
17812  res_data = cpl_image_get_data_float(response);
17813 
17814  for (i = 0; i < nx; i++) {
17815  if (eff_data[i] > 0.01 && flux_data[i] > 0.0)
17816  res_data[i] = data[i] / flux_data[i];
17817  else
17818  res_data[i] = 0.0;
17819  }
17820 
17821 
17822  /*
17823  * Extract only data to fit, i.e., where the response is available.
17824  */
17825 
17826  image = cpl_image_extract(response, eff_pos + 1, 1, eff_pos + eff_count, 1);
17827 
17828  smo_image = polysmooth(image, order, 50);
17829  cpl_image_delete(image);
17830 
17831  smo_response = cpl_image_duplicate(response);
17832  smo_res_data = cpl_image_get_data_float(smo_response);
17833  cpl_image_copy(smo_response, smo_image, eff_pos + 1, 1);
17834 
17835  cpl_image_delete(smo_image);
17836 
17837  for (i = 0; i < nx; i++) {
17838  if (eff_data[i] > 0.01) {
17839  res_data[i] = 1 / res_data[i];
17840  smo_res_data[i] = 1 / smo_res_data[i];
17841  }
17842  else {
17843  res_data[i] = 0.0;
17844  smo_res_data[i] = 0.0;
17845  }
17846  }
17847 
17848 
17849  /*
17850  * Assemble the product spectrophotometric table.
17851  */
17852 
17853  table = cpl_table_new(nx);
17854 
17855  cpl_table_new_column(table, "WAVE", CPL_TYPE_FLOAT);
17856  cpl_table_set_column_unit(table, "WAVE", "Angstrom");
17857 
17858  for (i = 0; i < nx; i++)
17859  cpl_table_set_float(table, "WAVE", i, startwave + dispersion*(i+0.5));
17860 
17861  cpl_table_new_column(table, "STD_FLUX", CPL_TYPE_FLOAT);
17862  cpl_table_set_column_unit(table, "STD_FLUX",
17863  "10^(-16) erg/(cm^2 s Angstrom)");
17864  cpl_table_copy_data_float(table, "STD_FLUX", flux_data);
17865  cpl_image_delete(flux); flux = NULL;
17866 
17867  cpl_table_new_column(table, "OBS_FLUX", CPL_TYPE_FLOAT);
17868  cpl_table_set_column_unit(table, "OBS_FLUX", "electron/(s Angstrom)");
17869  cpl_table_copy_data_float(table, "OBS_FLUX", data);
17870  cpl_image_delete(spectrum); spectrum = NULL;
17871 
17872  cpl_table_new_column(table, "RAW_EFFICIENCY", CPL_TYPE_FLOAT);
17873  cpl_table_set_column_unit(table, "RAW_EFFICIENCY", "electron/photon");
17874  cpl_table_copy_data_float(table, "RAW_EFFICIENCY", eff_data);
17875  cpl_image_delete(efficiency); efficiency = NULL;
17876 
17877  cpl_table_new_column(table, "EFFICIENCY", CPL_TYPE_FLOAT);
17878  cpl_table_set_column_unit(table, "EFFICIENCY", "electron/photon");
17879  cpl_table_copy_data_float(table, "EFFICIENCY", smo_eff_data);
17880  cpl_image_delete(smo_efficiency); smo_efficiency = NULL;
17881 
17882  cpl_table_new_column(table, "RAW_RESPONSE", CPL_TYPE_FLOAT);
17883  cpl_table_set_column_unit(table, "RAW_RESPONSE",
17884  "10^(-16) erg/(cm^2 electron)");
17885  cpl_table_copy_data_float(table, "RAW_RESPONSE", res_data);
17886  cpl_image_delete(response); response = NULL;
17887 
17888  cpl_table_new_column(table, "RESPONSE", CPL_TYPE_FLOAT);
17889  cpl_table_set_column_unit(table,
17890  "RESPONSE", "10^(-16) erg/(cm^2 electron)");
17891  cpl_table_copy_data_float(table, "RESPONSE", smo_res_data);
17892  cpl_image_delete(smo_response); smo_response = NULL;
17893 
17894  cleanup;
17895 
17896  return table;
17897 }
17898 
17899 static double ksigma_vector(cpl_vector *values,
17900  double klow, double khigh, int kiter, int *good)
17901 {
17902  cpl_vector *accepted;
17903  double mean = 0.0;
17904  double sigma = 0.0;
17905  double *data = cpl_vector_get_data(values);
17906  int n = cpl_vector_get_size(values);
17907  int ngood = n;
17908  int count = 0;
17909  int i;
17910 
17911 
17912  /*
17913  * At first iteration the mean is taken as the median, and the
17914  * standard deviation relative to this value is computed.
17915  */
17916 
17917  mean = cpl_vector_get_median(values);
17918 
17919  for (i = 0; i < n; i++)
17920  sigma += (mean - data[i]) * (mean - data[i]);
17921 
17922  sigma = sqrt(sigma / (n - 1));
17923 
17924  while (kiter) {
17925  count = 0;
17926  for (i = 0; i < ngood; i++) {
17927  if (data[i]-mean < khigh*sigma && mean-data[i] < klow*sigma) {
17928  data[count] = data[i];
17929  ++count;
17930  }
17931  }
17932 
17933  if (count == 0) // This cannot happen at first iteration.
17934  break; // So we can break: we have already computed a mean.
17935 
17936  /*
17937  * The mean must be computed even if no element was rejected
17938  * (count == ngood), because at first iteration median instead
17939  * of mean was computed.
17940  */
17941 
17942  accepted = cpl_vector_wrap(count, data);
17943  mean = cpl_vector_get_mean(accepted);
17944  if (count > 1)
17945  sigma = cpl_vector_get_stdev(accepted);
17946  cpl_vector_unwrap(accepted);
17947 
17948  if (count == ngood || count == 1)
17949  break;
17950 
17951  ngood = count;
17952  --kiter;
17953  }
17954 
17955  if (good)
17956  *good = ngood;
17957 
17958  return mean;
17959 }
17960 
17961 
17980 cpl_image *mos_ksigma_stack(cpl_imagelist *imlist,
17981  double klow, double khigh, int kiter,
17982  cpl_image **good)
17983 {
17984  int ni, nx, ny, npix;
17985  cpl_image *out_ima;
17986  float *pout_ima;
17987  float *good_ima;
17988  cpl_image *image;
17989  float **data;
17990  cpl_vector *time_line;
17991  double *ptime_line;
17992  int ngood;
17993  int i, j;
17994 
17995 
17996  ni = cpl_imagelist_get_size(imlist);
17997 
17998  image = cpl_imagelist_get(imlist, 0);
17999  nx = cpl_image_get_size_x(image);
18000  ny = cpl_image_get_size_y(image);
18001  npix = nx * ny;
18002 
18003  out_ima = cpl_image_new(nx, ny, CPL_TYPE_FLOAT);
18004  pout_ima = cpl_image_get_data_float(out_ima);
18005 
18006  if (good) {
18007  *good = cpl_image_new(nx, ny, CPL_TYPE_FLOAT);
18008  good_ima = cpl_image_get_data_float(*good);
18009  }
18010 
18011  time_line = cpl_vector_new(ni);
18012  ptime_line = cpl_vector_get_data(time_line);
18013 
18014  data = cpl_calloc(sizeof(float *), ni);
18015 
18016  for (i = 0; i < ni; i++) {
18017  image = cpl_imagelist_get(imlist, i);
18018  data[i] = cpl_image_get_data_float(image);
18019  }
18020 
18021  for (i = 0; i < npix; i++) {
18022  for (j = 0; j < ni; j++) {
18023  ptime_line[j] = data[j][i];
18024  }
18025  pout_ima[i] = ksigma_vector(time_line, klow, khigh, kiter, &ngood);
18026  if (good) {
18027  good_ima[i] = ngood;
18028  }
18029  }
18030 
18031  cpl_free(data);
18032  cpl_vector_delete(time_line);
18033 
18034  return out_ima;
18035 
18036 }
18037 
18038 
18055 cpl_image *mos_apply_photometry(cpl_image *spectra, cpl_table *response,
18056  cpl_table *ext_table, double startwave,
18057  double dispersion, double gain,
18058  double exptime, double airmass)
18059 {
18060  cpl_image *extinction;
18061  cpl_image *outspectra;
18062  cpl_image *mapresponse;
18063  float *res_data;
18064  float *out_data;
18065  float *ext_data;
18066  int tlength, xlength, ylength;
18067  int i, j, k;
18068 
18069 
18070  if (spectra == NULL || ext_table == NULL || response == NULL) {
18071  cpl_error_set(cpl_func, CPL_ERROR_NULL_INPUT);
18072  return NULL;
18073  }
18074 
18075  if(cpl_table_has_column(response, "RESPONSE"))
18076  cpl_table_cast_column(response, "RESPONSE", "RESPONSE_F", CPL_TYPE_FLOAT);
18077  else if(cpl_table_has_column(response, "RESPONSE_FFSED"))
18078  cpl_table_cast_column(response, "RESPONSE_FFSED", "RESPONSE_F", CPL_TYPE_FLOAT);
18079  else
18080  return NULL;
18081 
18082  res_data = cpl_table_get_data_float(response, "RESPONSE_F");
18083 
18084  if (res_data == NULL) {
18085  cpl_error_set(cpl_func, CPL_ERROR_DATA_NOT_FOUND);
18086  return NULL;
18087  }
18088 
18089  tlength = cpl_table_get_nrow(response);
18090  xlength = cpl_image_get_size_x(spectra);
18091  ylength = cpl_image_get_size_y(spectra);
18092 
18093  if (xlength != tlength) {
18094  mapresponse = cpl_image_new(xlength, 1, CPL_TYPE_FLOAT);
18095  map_table(mapresponse, startwave + dispersion/2, dispersion,
18096  response, "WAVE", "RESPONSE_F");
18097  res_data = cpl_image_get_data_float(mapresponse);
18098  }
18099 
18100  /*
18101  * Map the atmospheric extinction factors to the same lambda sampling
18102  * of the extracted spectrum.
18103  */
18104 
18105  extinction = cpl_image_new(xlength, 1, CPL_TYPE_FLOAT);
18106  map_table(extinction, startwave + dispersion/2, dispersion,
18107  ext_table, "WAVE", "EXTINCTION");
18108 
18109 
18110  /*
18111  * Convert from magnitudes to actual flux loss.
18112  */
18113 
18114  cpl_image_multiply_scalar(extinction, 0.4 * airmass);
18115  cpl_image_exponential(extinction, 10.);
18116 
18117  outspectra = cpl_image_duplicate(spectra);
18118 
18119  ext_data = cpl_image_get_data_float(extinction);
18120  out_data = cpl_image_get_data_float(outspectra);
18121 
18122  for (k = 0, i = 0; i < ylength; i++) {
18123  for (j = 0; j < xlength; j++, k++) {
18124  out_data[k] *= ext_data[j] * res_data[j];
18125  }
18126  }
18127 
18128  cpl_image_delete(extinction);
18129  if (xlength != tlength) {
18130  cpl_image_delete(mapresponse);
18131  }
18132 
18133  cpl_image_multiply_scalar(outspectra, gain / exptime / dispersion);
18134 
18135  cpl_table_erase_column(response, "RESPONSE_F");
18136 
18137  return outspectra;
18138 }
18139 
18140 
18157 cpl_image *mos_propagate_photometry_error(cpl_image *spectra,
18158  cpl_image *errors,
18159  cpl_table *response,
18160  cpl_table *ext_table,
18161  double startwave,
18162  double dispersion, double gain,
18163  double exptime, double airmass)
18164 {
18165  cpl_image *extinction;
18166  cpl_image *outerrors;
18167  cpl_image *mapresponse;
18168  cpl_image *maperror;
18169  float *err_data;
18170  float *out_data;
18171  float *ext_data;
18172  float *res_data;
18173  float *spe_data;
18174  int tlength, xlength, ylength;
18175  int i, j, k;
18176 
18177 
18178  if (errors == NULL || ext_table == NULL || response == NULL) {
18179  cpl_error_set(cpl_func, CPL_ERROR_NULL_INPUT);
18180  return NULL;
18181  }
18182 
18183  if (!cpl_table_has_column(response, "ERROR")) {
18184  return mos_apply_photometry(errors, response, ext_table, startwave,
18185  dispersion, gain, exptime, airmass);
18186  }
18187 
18188  cpl_table_cast_column(response, "RESPONSE", "RESPONSE_F", CPL_TYPE_FLOAT);
18189  res_data = cpl_table_get_data_float(response, "RESPONSE_F");
18190 
18191  if (res_data == NULL) {
18192  cpl_error_set(cpl_func, CPL_ERROR_DATA_NOT_FOUND);
18193  return NULL;
18194  }
18195 
18196  err_data = cpl_table_get_data_float(response, "ERROR");
18197 
18198  if (err_data == NULL) {
18199  cpl_error_set(cpl_func, CPL_ERROR_DATA_NOT_FOUND);
18200  return NULL;
18201  }
18202 
18203  tlength = cpl_table_get_nrow(response);
18204  xlength = cpl_image_get_size_x(errors);
18205  ylength = cpl_image_get_size_y(errors);
18206 
18207  if (xlength != tlength) {
18208  mapresponse = cpl_image_new(xlength, 1, CPL_TYPE_FLOAT);
18209  map_table(mapresponse, startwave + dispersion/2, dispersion,
18210  response, "WAVE", "RESPONSE_F");
18211  res_data = cpl_image_get_data_float(mapresponse);
18212 
18213  maperror = cpl_image_new(xlength, 1, CPL_TYPE_FLOAT);
18214  map_table(maperror, startwave + dispersion/2, dispersion,
18215  response, "WAVE", "ERROR");
18216  err_data = cpl_image_get_data_float(maperror);
18217  }
18218 
18219  /*
18220  * Map the atmospheric extinction factors to the same lambda sampling
18221  * of the extracted spectrum.
18222  */
18223 
18224  extinction = cpl_image_new(xlength, 1, CPL_TYPE_FLOAT);
18225  map_table(extinction, startwave + dispersion/2, dispersion,
18226  ext_table, "WAVE", "EXTINCTION");
18227 
18228 
18229  /*
18230  * Convert from magnitudes to actual flux loss.
18231  */
18232 
18233  cpl_image_multiply_scalar(extinction, 0.4 * airmass);
18234  cpl_image_exponential(extinction, 10.);
18235 
18236  outerrors = cpl_image_duplicate(errors);
18237 
18238  ext_data = cpl_image_get_data_float(extinction);
18239  out_data = cpl_image_get_data_float(outerrors);
18240  spe_data = cpl_image_get_data_float(spectra);
18241 
18242  for (k = 0, i = 0; i < ylength; i++) {
18243  for (j = 0; j < xlength; j++, k++) {
18244  out_data[k] = ext_data[j] *
18245  sqrt(err_data[j] * err_data[j] * spe_data[k] * spe_data[k] +
18246  res_data[j] * res_data[j] * out_data[k] * out_data[k]);
18247  }
18248  }
18249 
18250  cpl_image_delete(extinction);
18251  if (xlength != tlength) {
18252  cpl_image_delete(maperror);
18253  }
18254 
18255  cpl_image_multiply_scalar(outerrors, gain / exptime / dispersion);
18256 
18257  cpl_table_erase_column(response, "RESPONSE_F");
18258  return outerrors;
18259 }
18260 
18261 
18337 int mos_check_polarisation(cpl_image *q_image, cpl_image *q_error,
18338  cpl_image *u_image, cpl_image *u_error,
18339  double startwave, double dispersion,
18340  double band, cpl_table *pol_sta,
18341  double ra, double dec, char *filter,
18342  int *polarisation,
18343  double *p_offset, double *p_error,
18344  double *a_offset, double *a_error)
18345 {
18346  cpl_table *standard;
18347  cpl_image *q_noise;
18348  cpl_image *q_signal;
18349  cpl_image *u_noise;
18350  cpl_image *u_signal;
18351  cpl_image *noise;
18352  double *q_ndata;
18353  double *q_sdata;
18354  double *u_ndata;
18355  double *u_sdata;
18356  double arctol = 0.5; /* Arc tolerance in degrees */
18357  double mindist;
18358  double cwave;
18359  double bwave[] = {3650., 4450., 5510., 6580., 8060};
18360  char *bands = "UBVRI";
18361  char p_label[] = {' ', 'p', '\0'};
18362  char dp_label[] = {' ', 'd', 'p', '\0'};
18363  char a_label[] = {' ', 'a', '\0'};
18364  char da_label[] = {' ', 'd', 'a', '\0'};
18365  int nbands = strlen(bands);
18366  int selected;
18367  int first, last, count, center;
18368  int nx;
18369  cpl_size col, row;
18370  int i, found, closest;
18371  int pband;
18372  int polarised;
18373  double q_obs;
18374  double q_err;
18375  double u_obs;
18376  double u_err;
18377  double p_obs;
18378  double p_err;
18379  double p_ref;
18380  double dp_ref;
18381  double a_obs;
18382  double a_err;
18383  double a_ref;
18384  double da_ref;
18385 
18386 
18387  *filter = '\0';
18388  *polarisation = 0;
18389  *p_offset = 0.0;
18390  *p_error = 0.0;
18391  *a_offset = 0.0;
18392  *a_error = 0.0;
18393 
18394  /*
18395  * Select reference standard star
18396  */
18397 
18398  cpl_table_select_all(pol_sta);
18399  cpl_table_and_selected_double(pol_sta, "RA", CPL_GREATER_THAN, ra-arctol);
18400  cpl_table_and_selected_double(pol_sta, "RA", CPL_LESS_THAN, ra+arctol);
18401  cpl_table_and_selected_double(pol_sta, "DEC", CPL_GREATER_THAN, dec-arctol);
18402  selected =
18403  cpl_table_and_selected_double(pol_sta, "DEC", CPL_LESS_THAN, dec+arctol);
18404 
18405  if (selected == 0) {
18406  cpl_msg_warning(cpl_func, "No standard star found in FOV");
18407  return 1;
18408  }
18409 
18410  if (selected > 1) {
18411  cpl_msg_warning(cpl_func,
18412  "Ambiguity: %d standard stars found in FOV", selected);
18413  return 1;
18414  }
18415 
18416  standard = cpl_table_extract_selected(pol_sta);
18417 
18418  cpl_msg_info(cpl_func, "Standard star: %s",
18419  cpl_table_get_string(standard, "name", 0));
18420 
18421  /*
18422  * Check whether the star is polarised or not
18423  */
18424 
18425  polarised = cpl_table_get_int(standard, "polarised", 0, NULL);
18426 
18427  cpl_msg_info(cpl_func, "This star is%sexpected to be polarised",
18428  polarised ? " " : " not ");
18429 
18430 
18431  /*
18432  * Determine the image row with the smallest median noise: this
18433  * row is assumed to refer to the standard star.
18434  * (note: the higher the S/N ratio of the original spectra, the
18435  * smaller the noise of the Stokes parameters Q and U).
18436  */
18437 
18438  nx = cpl_image_get_size_x(q_error);
18439 
18440  noise = cpl_image_collapse_median_create(q_error, 1, 0, 0);
18441  cpl_image_get_minpos(noise, &col, &row);
18442 
18443  cpl_image_delete(noise);
18444 
18445  if (col != 1) {
18446  cpl_table_delete(standard);
18447  cpl_msg_error(cpl_func,
18448  "Assertion failure!!! col = %"CPL_SIZE_FORMAT" (it should be 1)", col);
18449  return 1;
18450  }
18451 
18452  q_signal = cpl_image_extract(q_image, 1, row, nx, row);
18453  q_noise = cpl_image_extract(q_error, 1, row, nx, row);
18454  u_signal = cpl_image_extract(u_image, 1, row, nx, row);
18455  u_noise = cpl_image_extract(u_error, 1, row, nx, row);
18456 
18457  q_sdata = cpl_image_get_data_double(q_signal);
18458  q_ndata = cpl_image_get_data_double(q_noise);
18459  u_sdata = cpl_image_get_data_double(u_signal);
18460  u_ndata = cpl_image_get_data_double(u_noise);
18461 
18462 
18463  /*
18464  * Determine valid interval in input images (where error is positive).
18465  */
18466 
18467  first = -1;
18468  last = nx = cpl_image_get_size_x(q_signal);
18469  for (i = 0; i < nx; i++) {
18470  if (first < 0) {
18471  if (q_ndata[i] > 0.0) {
18472  first = i;
18473  }
18474  }
18475  else {
18476  if (q_ndata[i] <= 0.0) {
18477  last = i - 1;
18478  break;
18479  }
18480  }
18481  }
18482 
18483  count = last - first + 1;
18484 
18485  if (first < 0 || count < band) {
18486  cpl_table_delete(standard);
18487  cpl_image_delete(q_signal);
18488  cpl_image_delete(q_noise);
18489  cpl_image_delete(u_signal);
18490  cpl_image_delete(u_noise);
18491  cpl_msg_warning(cpl_func, "Too short spectrum (%d pixels)", count);
18492  return 1;
18493  }
18494 
18495  center = (first + last) / 2; // Center of valid spectrum
18496  cwave = startwave + dispersion * center; // Corresponding wavelength
18497 
18498 
18499  /*
18500  * Find the band UBVRI closest to the central wavelength.
18501  */
18502 
18503  found = 0;
18504  for (i = 0; i < nbands; i++) {
18505  p_label[0] = bands[i];
18506  if (cpl_table_is_valid(standard, p_label, 0)) {
18507  if (found == 0) {
18508  found = 1;
18509  mindist = fabs(bwave[i] - cwave);
18510  closest = i;
18511  }
18512  else if (mindist > fabs(bwave[i] - cwave)) {
18513  mindist = fabs(bwave[i] - cwave);
18514  closest = i;
18515  }
18516  }
18517  }
18518 
18519  if (!found) {
18520  cpl_table_delete(standard);
18521  cpl_image_delete(q_signal);
18522  cpl_image_delete(q_noise);
18523  cpl_image_delete(u_signal);
18524  cpl_image_delete(u_noise);
18525  cpl_msg_warning(cpl_func, "No reference value available");
18526  return 1;
18527  }
18528 
18529  center = (bwave[closest] - startwave) / dispersion; // Center of band (pix)
18530  cwave = bwave[closest]; // Wavelength of band
18531 
18532 
18533  /*
18534  * Check that the integration interval is entirely contained
18535  * in the valid interval, or give it up.
18536  */
18537 
18538  pband = floor(band / dispersion); // Band width in pixels
18539 
18540  if (center - pband/2 < first || center + pband/2 > last) {
18541  cpl_table_delete(standard);
18542  cpl_image_delete(q_signal);
18543  cpl_image_delete(q_noise);
18544  cpl_image_delete(u_signal);
18545  cpl_image_delete(u_noise);
18546  cpl_msg_warning(cpl_func, "No reference value available");
18547  return 1;
18548  }
18549 
18550  first = center - pband/2;
18551  last = center + pband/2;
18552 
18553  /*
18554  * Collect reference values. Note that if angle info is not available,
18555  * angle stuff is set automaticaly to zero.
18556  */
18557 
18558  p_label[0] = bands[closest];
18559  dp_label[0] = bands[closest];
18560  a_label[0] = bands[closest];
18561  da_label[0] = bands[closest];
18562 
18563  p_ref = cpl_table_get(standard, p_label, 0, NULL);
18564  dp_ref = cpl_table_get(standard, dp_label, 0, NULL);
18565  a_ref = cpl_table_get(standard, a_label, 0, NULL);
18566  da_ref = cpl_table_get(standard, da_label, 0, NULL);
18567 
18568  cpl_msg_info(cpl_func,
18569  "The expected polarisation is %.2f +- %.2f %%",
18570  p_ref, dp_ref);
18571 
18572  if (polarised) {
18573  cpl_msg_info(cpl_func,
18574  "The expected polarisation angle is %.2f +- %.2f degrees",
18575  a_ref, da_ref);
18576  }
18577 
18578  /*
18579  * Find median signal and median error.
18580  */
18581 
18582  q_obs = cpl_image_get_median_window(q_image, first, 1, last, 1);
18583  q_err = cpl_image_get_median_window(q_error, first, 1, last, 1);
18584  u_obs = cpl_image_get_median_window(u_image, first, 1, last, 1);
18585  u_err = cpl_image_get_median_window(u_error, first, 1, last, 1);
18586 
18587  /*
18588  * Measured linear polarisation and its error
18589  */
18590 
18591  p_obs = sqrt(q_obs * q_obs + u_obs * u_obs);
18592  p_err = CPL_MATH_SQRT1_2 * 0.5 * (q_err + u_err);
18593 
18594  /*
18595  * Measured polarisation angle
18596  */
18597 
18598  a_obs = 0.0;
18599  if (polarised) {
18600  if (fabs(q_obs) < 0.00001) {
18601  if (u_obs > 0.0) {
18602  a_obs = 45.0;
18603  }
18604  else {
18605  a_obs = 135.0;
18606  }
18607  }
18608  else {
18609  a_obs = 0.5 * atan(u_obs / q_obs) * 180 / CPL_MATH_PI;
18610  if (q_obs > 0.0) {
18611  if (u_obs < 0.0) {
18612  a_obs += 180.;
18613  }
18614  }
18615  else {
18616  a_obs += 90.;
18617  }
18618  }
18619  }
18620 
18621  /*
18622  * Error on polarisation angle
18623  */
18624 
18625  a_err = 0.0;
18626  if (polarised) {
18627  a_err = sqrt(q_obs*q_obs*u_err*u_err + u_obs*u_obs*q_err*q_err)
18628  / (p_obs * p_obs)
18629  * 90 / CPL_MATH_PI;
18630  }
18631 
18632  p_obs *= 100;
18633  p_err *= 100;
18634  cpl_msg_info(cpl_func,
18635  "The measured polarisation is %.2f +- %.2f %%",
18636  p_obs, p_err);
18637 
18638  if (polarised) {
18639  cpl_msg_info(cpl_func,
18640  "The measured polarisation angle is %.2f +- %.2f degrees",
18641  a_obs, a_err);
18642  }
18643 
18644  *filter = bands[closest];
18645  *polarisation = polarised;
18646 
18647  if (polarised) {
18648  *p_offset = (p_obs - p_ref) / p_ref;
18649  *p_error = sqrt(p_err * p_err + dp_ref * dp_ref) / p_ref;
18650  }
18651  else {
18652  *p_offset = p_obs - p_ref;
18653  *p_error = sqrt(p_err * p_err + dp_ref * dp_ref);
18654  }
18655 
18656  *a_offset = a_obs - a_ref;
18657  *a_error = sqrt(a_err*a_err + da_ref*da_ref);
18658 
18659  return 0;
18660 
18661 }
18662 
18663 
18695 int mos_compute_offset(cpl_table *reference, cpl_table *objects, double *offset)
18696 {
18697  cpl_array *offsets;
18698  int noffset;
18699  int nslits = cpl_table_get_nrow(reference);
18700  int *nref;
18701  int *nobj;
18702  int corr, maxcorr;
18703  int best_shift;
18704  int i, j, k;
18705 
18706  cpl_error_code status = CPL_ERROR_NONE;
18707 
18708 
18709  *offset = 0.0;
18710 
18711  if (objects == NULL)
18712  return CPL_ERROR_NULL_INPUT;
18713 
18714  if (nslits != cpl_table_get_nrow(objects))
18715  return CPL_ERROR_INCOMPATIBLE_INPUT;
18716 
18717  nref = fors_get_nobjs_perslit(reference);
18718  nobj = fors_get_nobjs_perslit(objects);
18719 
18720  noffset = 0;
18721  for (i = 0; i < nslits; i++)
18722  noffset += nobj[i];
18723 
18724  if (noffset == 0) {
18725  cpl_free(nref);
18726  cpl_free(nobj);
18727  return CPL_ERROR_DATA_NOT_FOUND;
18728  }
18729 
18730  noffset = 0;
18731  for (i = 0; i < nslits; i++)
18732  noffset += nref[i];
18733 
18734  if (noffset == 0) {
18735  cpl_free(nref);
18736  cpl_free(nobj);
18737  return CPL_ERROR_DATA_NOT_FOUND;
18738  }
18739 
18740  offsets = cpl_array_new(noffset, CPL_TYPE_DOUBLE);
18741 
18742  noffset = 0; // The real number of offsets found will be counted.
18743 
18744  for (i = 0; i < nslits; i++) {
18745  if (nref[i] > 0 && nobj[i] > 0) {
18746  double shift;
18747  int length = cpl_table_get_int(objects, "length", i, NULL);
18748  double ytop = cpl_table_get_double(objects, "xtop", i, NULL);
18749  double ybottom = cpl_table_get_double(objects, "xbottom", i, NULL);
18750  int *aref = cpl_calloc(length, sizeof(int));
18751  int *aobj = cpl_calloc(length, sizeof(int));
18752  float *pref = cpl_calloc(nref[i], sizeof(float));
18753  float *pobj = cpl_calloc(nobj[i], sizeof(float));
18754 
18755  for (j = 0; j < nref[i]; j++) {
18756  pref[j] = fors_get_object_position(reference, i, j + 1);
18757  aref[(int)pref[j]] = 1;
18758  }
18759 
18760  for (j = 0; j < nobj[i]; j++) {
18761  pobj[j] = fors_get_object_position(objects, i, j + 1);
18762  aobj[(int)pobj[j]] = 1;
18763  }
18764 
18765  /*
18766  * Do not consider objects at border
18767  */
18768 
18769  aref[0] = 0;
18770  aref[length - 1] = 0;
18771  aobj[0] = 0;
18772  aobj[length - 1] = 0;
18773 
18774 //for (j = 0; j < nref[i]; j++)
18775 //printf("references: %f, ", pref[j]);
18776 //printf("\n");
18777 //for (j = 0; j < nref[i]; j++)
18778 //printf("objects : %f, ", pobj[j]);
18779 //printf("\n");
18780 //for (j = 0; j < length; j++)
18781 //printf("%d", aref[j]);
18782 //printf("\n");
18783 //for (j = 0; j < length; j++)
18784 //printf("%d", aobj[j]);
18785 //printf("\n");
18786 
18787  /*
18788  * Object matching by correlation
18789  */
18790 
18791  maxcorr = 0;
18792  best_shift = length;
18793 
18794  for (shift = length/2, j = 0; j <= length; shift--, j++) {
18795  int rstart, ostart, count;
18796 
18797  if (shift > 0) {
18798  rstart = shift;
18799  ostart = 0;
18800  count = length - shift;
18801  }
18802  else {
18803  rstart = 0;
18804  ostart = -shift;
18805  count = length + shift;
18806  }
18807 
18808  corr = 0;
18809  for (k = 0; k < count; k++) {
18810  corr += aref[rstart + k] * aobj[ostart + k];
18811  }
18812 
18813  if (maxcorr < corr) {
18814  maxcorr = corr;
18815  best_shift = shift;
18816  }
18817  }
18818 
18819  if (best_shift == length) { // No shift found
18820 //printf("%d: No shift found\n", i);
18821  cpl_free(aref);
18822  cpl_free(aobj);
18823  cpl_free(pref);
18824  cpl_free(pobj);
18825  continue;
18826  }
18827 //printf("%d: Integer shift found = %d\n", i, best_shift);
18828 
18829  for (j = 0; j < nref[i]; j++) {
18830  for (k = 0; k < nobj[i]; k++) {
18831  if (fabs(pref[j] - pobj[k] - best_shift) < 2) {
18832  double ccd_offset = (pref[j] - pobj[k])
18833  * (ytop - ybottom)
18834  / length;
18835 
18836 //printf("%d: Match found: %f\n", i, ccd_offset);
18837  /*
18838  * The matching object is found, store the
18839  * corresponding offset
18840  */
18841 
18842  cpl_array_set(offsets, noffset, ccd_offset);
18843  noffset++;
18844  break;
18845  }
18846  }
18847  }
18848 
18849  cpl_free(aref);
18850  cpl_free(aobj);
18851  cpl_free(pref);
18852  cpl_free(pobj);
18853  }
18854 //else
18855 //printf("%d: No object found\n", i);
18856  }
18857 
18858  cpl_free(nref);
18859  cpl_free(nobj);
18860 
18861 //printf("%d offsets found in total\n", noffset);
18862  if (noffset > 0) {
18863  if (noffset % 2) {
18864  *offset = cpl_array_get_median(offsets);
18865  }
18866  else {
18867  double *a = cpl_malloc(sizeof(double) * noffset);
18868  for (i = 0; i < noffset; i++) {
18869  a[i] = cpl_array_get_double(offsets, i, NULL);
18870  }
18871  *offset = (fors_tools_get_kth_double(a, noffset, (noffset-1)/2) +
18872  fors_tools_get_kth_double(a, noffset, (noffset/2))) / 2.0;
18873  cpl_free(a);
18874  }
18875  }
18876  else
18877  status = CPL_ERROR_DATA_NOT_FOUND;
18878 //printf("Median offset: %f\n", *offset);
18879 
18880  cpl_array_delete(offsets);
18881 
18882  return status;
18883 
18884 }
18885 
18886 
18898 cpl_error_code mos_image_shift(cpl_image *image, double dx, double dy)
18899 {
18900  cpl_image *source;
18901  int nx = cpl_image_get_size_x(image);
18902  int ny = cpl_image_get_size_y(image);
18903  float *idata;
18904  float *sdata;
18905  int i, j, pos;
18906  double xpos, ypos, xfrac, yfrac;
18907  int xint, yint;
18908 
18909 
18910  if (fabs(dx) >= nx || fabs(dy) >= ny)
18911  return CPL_ERROR_ACCESS_OUT_OF_RANGE;
18912 
18913  source = cpl_image_duplicate(image);
18914  idata = cpl_image_get_data_float(image);
18915  sdata = cpl_image_get_data_float(source);
18916 
18917  /*
18918  * Shift in y
18919  */
18920 
18921  yfrac = - dy - floor(- dy);
18922  xfrac = - dx - floor(- dx);
18923 
18924  for (pos = 0, j = 0; j < ny; j++) {
18925  ypos = j - dy;
18926  yint = floor(ypos);
18927  for (i = 0; i < nx; i++) {
18928  xpos = i - dx;
18929  xint = floor(xpos);
18930  if (xint < 0 || yint < 0 || xint > nx - 2 || yint > ny - 2) {
18931  idata[pos] = 0.0;
18932  }
18933  else {
18934  idata[pos] = sdata[xint + nx*yint] * (1 - xfrac) * (1 - yfrac)
18935  + sdata[xint + 1 + nx*yint] * xfrac * (1 - yfrac)
18936  + sdata[xint + nx*(yint + 1)] * (1 - xfrac) * yfrac
18937  + sdata[xint + 1 + nx*(yint + 1)] * xfrac * yfrac;
18938  }
18939  pos++;
18940  }
18941  }
18942 
18943  cpl_image_delete(source);
18944 
18945  return CPL_ERROR_NONE;
18946 }
18947 
18959 int mos_slit_closest_to_center(cpl_table *slits, int nx, int ny)
18960 {
18961 #ifdef CPL_SIZE_FORMAT
18962  cpl_size row;
18963 #else
18964  int row;
18965 #endif
18966 
18967  cpl_table_duplicate_column(slits, "x", slits, "xtop");
18968  cpl_table_add_columns(slits, "x", "xbottom");
18969  cpl_table_divide_scalar(slits, "x", 2); // Mean x position
18970  cpl_table_subtract_scalar(slits, "x", nx/2); // Relative to CCD center
18971  cpl_table_multiply_columns(slits, "x", "x"); // Squared
18972 
18973  cpl_table_duplicate_column(slits, "y", slits, "ytop");
18974  cpl_table_add_columns(slits, "y", "ybottom");
18975  cpl_table_divide_scalar(slits, "y", 2); // Mean y position
18976  cpl_table_subtract_scalar(slits, "y", ny/2); // Relative to CCD center
18977  cpl_table_multiply_columns(slits, "y", "y"); // Squared
18978 
18979  cpl_table_add_columns(slits, "x", "y"); // Distance from center
18980  cpl_table_get_column_minpos(slits, "x", &row); // Min distance from center
18981 
18982  cpl_table_erase_column(slits, "x");
18983  cpl_table_erase_column(slits, "y");
18984 
18985  return row;
18986 }
18987 
19007 cpl_error_code mos_extract_flux(cpl_image *image, cpl_table *slits,
19008  double xwidth, double ywidth,
19009  int dx, double gain, double *o_flux, double *o_err)
19010 {
19011  int nx = cpl_image_get_size_x(image);
19012  int ny = cpl_image_get_size_y(image);
19013  int slit = mos_slit_closest_to_center(slits, nx, ny);
19014  int ytop = (int)cpl_table_get(slits, "ytop", slit, NULL);
19015  int ybottom = (int)cpl_table_get(slits, "ybottom", slit, NULL);
19016  int dy = ytop - ybottom;
19017  int xcenter = (int)((cpl_table_get(slits, "xtop", slit, NULL) +
19018  cpl_table_get(slits, "xbottom", slit, NULL)) / 2);
19019  int xleft = xcenter - dx;
19020  int xright = xcenter + dx + 1;
19021  double area = xwidth * ywidth; // squared mm
19022  int npix = (2*dx + 1) * dy;
19023  int count = 0;
19024  float *data = cpl_image_get_data_float(image);
19025  double flux = 0.0;
19026  double error = 0.0;
19027  int satur = 60000;
19028  int x, y;
19029 
19030 
19031  if (cpl_table_has_column(slits, "ywidth")) {
19032  area = cpl_table_get(slits, "xwidth", slit, NULL)
19033  * cpl_table_get(slits, "ywidth", slit, NULL);
19034  }
19035 
19036  *o_flux = 0.0;
19037  *o_err = 0.0;
19038 
19039  if (xleft < 0)
19040  xleft = 0;
19041 
19042  if (xleft > nx)
19043  xleft = nx;
19044 
19045  if (xright < 0)
19046  xright = 0;
19047 
19048  if (xright > nx)
19049  xright = nx;
19050 
19051  if (ytop < 0)
19052  ytop = 0;
19053 
19054  if (ytop > ny)
19055  ytop = ny;
19056 
19057  if (ybottom < 0)
19058  ybottom = 0;
19059 
19060  if (ybottom > ny)
19061  ybottom = ny;
19062 
19063  count = (xright - xleft) * (ytop - ybottom);
19064 
19065  if (count == 0)
19066  return CPL_ERROR_ACCESS_OUT_OF_RANGE;
19067 
19068  count = 0;
19069 
19070  for (y = ybottom; y < ytop; y++) {
19071  for (x = xleft; x < xright; x++) {
19072  double value = data[x + y * nx];
19073  if (value < satur) {
19074  flux += value;
19075  count++;
19076  }
19077  }
19078  }
19079 
19080  if (count == 0)
19081  return CPL_ERROR_DIVISION_BY_ZERO;
19082 
19083  error = sqrt(flux/gain);
19084 
19085  /*
19086  * Flux correction for lost pixels
19087  */
19088 
19089  flux *= (float)npix / count;
19090  error *= (float)npix / count;
19091 
19092  flux /= area;
19093  error /= area;
19094 
19095  *o_flux = flux;
19096  *o_err = error;
19097 
19098  return CPL_ERROR_NONE;
19099 }
19100 
19101 
19124 cpl_error_code mos_extract_flux_mapped(cpl_image *image, cpl_table *slits,
19125  double xwidth, double ywidth,
19126  double lambda, double startwave,
19127  double dispersion, int dx, double gain,
19128  double *o_flux, double *o_err)
19129 {
19130  int nx = cpl_image_get_size_x(image);
19131  int ny = cpl_image_get_size_y(image);
19132  int slit = mos_slit_closest_to_center(slits, nx, ny);
19133  int dy = (int)cpl_table_get(slits, "length", slit, NULL);
19134  int ybottom = (int)cpl_table_get(slits, "position", slit, NULL);
19135  int ytop = ybottom + dy;
19136  int xcenter = (int)floor((lambda - startwave) / dispersion + 0.5);
19137  int xleft = xcenter - dx;
19138  int xright = xcenter + dx + 1;
19139  double area = xwidth * ywidth;
19140  int npix = (2*dx + 1) * dy;
19141  int count = 0;
19142  float *data = cpl_image_get_data_float(image);
19143  double flux = 0.0;
19144  double error = 0.0;
19145  int satur = 60000;
19146  int x, y;
19147 
19148 
19149  if (cpl_table_has_column(slits, "ywidth")) {
19150  area = cpl_table_get(slits, "xwidth", slit, NULL)
19151  * cpl_table_get(slits, "ywidth", slit, NULL);
19152  }
19153 
19154  *o_flux = 0.0;
19155  *o_err = 0.0;
19156 
19157  if (xleft < 0)
19158  xleft = 0;
19159 
19160  if (xleft > nx)
19161  xleft = nx;
19162 
19163  if (xright < 0)
19164  xright = 0;
19165 
19166  if (xright > nx)
19167  xright = nx;
19168 
19169  if (ytop < 0)
19170  ytop = 0;
19171 
19172  if (ytop > ny)
19173  ytop = ny;
19174 
19175  if (ybottom < 0)
19176  ybottom = 0;
19177 
19178  if (ybottom > ny)
19179  ybottom = ny;
19180 
19181  count = (xright - xleft) * (ytop - ybottom);
19182 
19183  if (count == 0)
19184  return CPL_ERROR_ACCESS_OUT_OF_RANGE;
19185 
19186  count = 0;
19187 
19188  for (y = ybottom; y < ytop; y++) {
19189  for (x = xleft; x < xright; x++) {
19190  double value = data[x + y * nx];
19191  if (value < satur) {
19192  flux += value;
19193  count++;
19194  }
19195  }
19196  }
19197 
19198  if (count == 0)
19199  return CPL_ERROR_DIVISION_BY_ZERO;
19200 
19201  error = sqrt(flux/gain);
19202 
19203  /*
19204  * Flux correction for lost pixels
19205  */
19206 
19207  flux *= (float)npix / count;
19208  error *= (float)npix / count;
19209 
19210  flux /= area;
19211  error /= area;
19212 
19213  *o_flux = flux;
19214  *o_err = error;
19215 
19216  return CPL_ERROR_NONE;
19217 
19218 }
19219 
19220 
19234 int mos_median_in_slit(cpl_table *table, cpl_table *slits, int slit,
19235  char *label, double *mvalue)
19236 {
19237  int position = cpl_table_get_int(slits, "position", slit, NULL);
19238  int length = cpl_table_get_int(slits, "length", slit, NULL);
19239  cpl_table *tmp = cpl_table_extract(table, position, length);
19240 
19241  *mvalue = cpl_table_get_column_median(tmp, label);
19242  cpl_table_delete(tmp);
19243 
19244  if (cpl_error_get_code() != CPL_ERROR_NONE)
19245  return 1;
19246 
19247  return 0;
19248 }
19249 
19250 
19262 cpl_image *mos_image_filter_median(cpl_image *image, int nx, int ny)
19263 {
19264  cpl_mask *kernel = cpl_mask_new(nx, ny);
19265  cpl_image *filtered = cpl_image_new(cpl_image_get_size_x(image),
19266  cpl_image_get_size_y(image),
19267  cpl_image_get_type(image));
19268 
19269  cpl_mask_not(kernel);
19270  cpl_image_filter_mask(filtered, image, kernel,
19271  CPL_FILTER_MEDIAN, CPL_BORDER_FILTER);
19272  cpl_mask_delete(kernel);
19273 
19274  return filtered;
19275 }
19276 
19277 int fors_mos_is_lss_like(cpl_table *maskslits, int nslits_out_det)
19278 {
19279  int treat_as_lss = 1;
19280  double mxpos = cpl_table_get_column_median(maskslits, "xtop");
19281  double * slit_xpos = cpl_table_get_data_double(maskslits, "xtop");
19282  cpl_size nslits = cpl_table_get_nrow(maskslits);
19283 
19284  //If not all the slits are illuminated, then we cannot say that
19285  //it is lss-like (PIPE-4380)
19286  if(nslits_out_det != 0)
19287  return 0;
19288 
19289  for (cpl_size i = 0; i < nslits; i++) {
19290  if (fabs(mxpos-slit_xpos[i]) > 0.01) {
19291  treat_as_lss = 0;
19292  break;
19293  }
19294  }
19295  return treat_as_lss;
19296 }
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:8539
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:17503
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:1888
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:11184
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:14770
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:4356
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:4247
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:12475
cpl_error_code mos_interpolate_wavecalib(cpl_table *idscoeff, cpl_image *wavemap, int mode, int degree)
Interpolate LSS wavelength calibration.
Definition: moses.c:3104
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:5492
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:11047
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:18157
cpl_table * mos_load_slits_fors_mxu(cpl_propertylist *header)
Create slit location table from FITS header of FORS2-MXU data.
Definition: moses.c:14858
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:2333
int mos_get_nobjects(cpl_table *slits)
Get the total number of objects detected in a slits table.
Definition: moses.c:17029
cpl_error_code mos_rotate_slits(cpl_table *slits, int rotation, int nx, int ny)
Rotate a slit location table.
Definition: moses.c:6345
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:11380
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:9697
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:6459
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:12142
cpl_image * mos_sky_local(cpl_image *spectra, cpl_table *slits, int order)
Local determination of sky.
Definition: moses.c:12819
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:15098
double mos_get_gain_vimos(cpl_propertylist *header)
Return gain factor for a VIMOS exposure.
Definition: moses.c:15468
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:9986
cpl_error_code mos_validate_slits(cpl_table *slits)
Check validity of a slit location table.
Definition: moses.c:6281
cpl_error_code mos_subtract_background(cpl_image *image)
Subtract the background.
Definition: moses.c:16378
int mos_get_maxobjs_per_slit(cpl_table *slits)
Get the maximum possible number of objects in a slit.
Definition: moses.c:17003
cpl_error_code mos_interpolate_wavecalib_slit(cpl_table *idscoeff, cpl_table *slits, int order, int global)
Interpolate MOS wavelength calibration.
Definition: moses.c:2975
cpl_image * mos_remove_bias(cpl_image *image, cpl_image *bias, cpl_table *overscans)
Subtract the bias from a CCD exposure.
Definition: moses.c:3694
cpl_table * mos_poly_trace(cpl_table *slits, cpl_table *traces, int order)
Fit spectral traces.
Definition: moses.c:8197
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:13944
cpl_image * mos_sky_local_old(cpl_image *spectra, cpl_table *slits)
Local determination of sky.
Definition: moses.c:12717
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:18337
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:5100
cpl_error_code mos_image_shift(cpl_image *image, double dx, double dy)
Shift values in an image.
Definition: moses.c:18898
int mos_check_multiplex(cpl_table *slits)
Determining whether a VIMOS mask has spectral multplexing or not.
Definition: moses.c:15624
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:19007
int mos_lines_width(const float *spectrum, int length)
Estimate lines widths (in pixel) in arc lamp spectrum.
Definition: moses.c:4041
cpl_error_code mos_global_trace(cpl_table *slits, cpl_table *polytraces, int mode)
Recompute tracing coefficients globally.
Definition: moses.c:8358
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:5312
cpl_vector * mos_peak_candidates(const float *spectrum, int length, float level, float exp_width)
Find positions of peaks candidates.
Definition: moses.c:4144
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:16408
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:10539
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:2012
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:11269
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:5045
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:7763
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:8954
int mos_check_slits(cpl_table *slits, float rescale)
Check that all slit have been detected, insert them if not.
Definition: moses.c:17060
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:18055
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:16218
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:17980
cpl_image ** mos_extract_objects(cpl_image *science, cpl_image *science_var, cpl_image *sky, cpl_table *objects, int extraction, double ron, double gain, int ncombined)
Extract detected objects from rectified scientific frame.
Definition: moses.c:14295
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:13174
cpl_table * mos_global_distortion(cpl_table *slits, cpl_table *maskslits, cpl_table *ids, cpl_table *crv, double reference)
Determine all global distortions models.
Definition: moses.c:1200
int mos_spectral_resolution(cpl_image *image, double lambda, double startwave, double dispersion, int saturation, double *mfwhm, double *rmsfwhm, double *resolution, double *rmsres, int *nlines)
Compute mean spectral resolution at a given arc lamp line.
Definition: moses.c:14502
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:3867
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:14698
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:17163
cpl_table * mos_load_slits_vimos(cpl_propertylist *header)
Create slit location table from FITS header of VIMOS data.
Definition: moses.c:15513
cpl_error_code mos_randomise_image(cpl_image *image, double ron, double gain, double bias)
Randomise image.
Definition: moses.c:16174
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:1733
cpl_table * mos_locate_spectra(cpl_mask *mask)
Find the location of detected spectra on the CCD.
Definition: moses.c:6193
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:15300
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:5260
cpl_error_code mos_interpolate_wavecalib_mos(cpl_table *idscoeff, int mode, int degree)
Interpolate wavelength calibration for a single MOS slit.
Definition: moses.c:3456
cpl_image * mos_arc_background(cpl_image *image, int msize, int fsize)
Background determination on emission line spectrum (arc)
Definition: moses.c:3969
cpl_error_code mos_saturation_process(cpl_image *image)
Process saturation.
Definition: moses.c:16310
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:11730
int mos_slit_closest_to_center(cpl_table *slits, int nx, int ny)
Return slit closest to CCD center.
Definition: moses.c:18959
cpl_image * mos_image_filter_median(cpl_image *image, int nx, int ny)
Convenience function for standard median filtering.
Definition: moses.c:19262
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:13678
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:19124
int mos_compute_offset(cpl_table *reference, cpl_table *objects, double *offset)
Estimate offset between two object tables.
Definition: moses.c:18695
cpl_image * mos_normalise_longflat(cpl_image *flat, int sradius, int dradius, int polyorder)
Normalise a long slit flat field exposure.
Definition: moses.c:2784
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:15723
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:19234
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:1586