SINFONI Pipeline Reference Manual  2.5.2
irplib_spectrum.c
1 /* $Id: irplib_spectrum.c,v 1.29 2012-01-12 11:50:41 llundin Exp $
2  *
3  * This file is part of the irplib package
4  * Copyright (C) 2002,2003 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 02111-1307 USA
19  */
20 
21 /*
22  * $Author: llundin $
23  * $Date: 2012-01-12 11:50:41 $
24  * $Revision: 1.29 $
25  * $Name: not supported by cvs2svn $
26  */
27 
28 #ifdef HAVE_CONFIG_H
29 #include <config.h>
30 #endif
31 
32 /*-----------------------------------------------------------------------------
33  Includes
34  -----------------------------------------------------------------------------*/
35 
36 #include <math.h>
37 #include <float.h>
38 #include <cpl.h>
39 
40 #include "irplib_wlxcorr.h"
41 #include "irplib_spectrum.h"
42 
43 /*-----------------------------------------------------------------------------
44  Define
45  -----------------------------------------------------------------------------*/
46 
47 /* TEMPORARY SUPPORT OF CPL 5.x */
48 #ifndef CPL_SIZE_FORMAT
49 #define CPL_SIZE_FORMAT "d"
50 #define cpl_size int
51 #endif
52 /* END TEMPORARY SUPPORT OF CPL 5.x */
53 
54 #define SPECTRUM_HW 16
55 #define MIN_THRESH_FACT 0.9
56 #define MAX_THRESH_FACT 1.1
57 #define SPEC_SHADOW_FACT 30.0 /* Negative spectrum intensity*/
58 #define SPEC_MAXWIDTH 48
59 
60 /*-----------------------------------------------------------------------------
61  Functions prototypes
62  -----------------------------------------------------------------------------*/
63 
64 static int select_valid_spectra(cpl_image *, cpl_apertures *, int,
65  spec_shadows, int, int *, int **) ;
66 static int valid_spectrum(cpl_image *, cpl_apertures *, int, spec_shadows, int,
67  int) ;
68 
69 /*----------------------------------------------------------------------------*/
73 /*----------------------------------------------------------------------------*/
74 
77 /*----------------------------------------------------------------------------*/
92 /*----------------------------------------------------------------------------*/
94  const cpl_image * in,
95  int offset,
96  spec_shadows shadows,
97  double min_bright,
98  int orient,
99  double * pos)
100 {
101  cpl_image * loc_ima ;
102  cpl_image * filt_image ;
103  cpl_image * collapsed ;
104  float * pcollapsed ;
105  cpl_vector * line ;
106  double * pline ;
107  cpl_vector * line_filt ;
108  double threshold ;
109  double median, stdev, max, mean ;
110  cpl_mask * mask ;
111  cpl_image * labels ;
112  cpl_size nlabels ;
113  cpl_apertures * aperts ;
114  int n_valid_specs ;
115  int * valid_specs ;
116  double brightness ;
117  int i ;
118 
119  /* Test entries */
120  if (in == NULL) return -1 ;
121  if (orient!=0 && orient!=1) return -1 ;
122 
123  /* Flip the image if necessary */
124  if (orient == 1) {
125  loc_ima = cpl_image_duplicate(in) ;
126  cpl_image_flip(loc_ima, 1) ;
127  } else {
128  loc_ima = cpl_image_duplicate(in) ;
129  }
130 
131  /* Median vertical filtering 3x3 */
132  mask = cpl_mask_new(3, 3) ;
133  cpl_mask_not(mask) ;
134  filt_image = cpl_image_new(
135  cpl_image_get_size_x(loc_ima),
136  cpl_image_get_size_y(loc_ima),
137  cpl_image_get_type(loc_ima)) ;
138  if (cpl_image_filter_mask(filt_image, loc_ima, mask,
139  CPL_FILTER_MEDIAN, CPL_BORDER_FILTER) != CPL_ERROR_NONE) {
140  cpl_msg_error(__func__, "Cannot filter the image") ;
141  cpl_mask_delete(mask) ;
142  cpl_image_delete(filt_image) ;
143  return -1 ;
144  }
145  cpl_mask_delete(mask) ;
146  cpl_image_delete(loc_ima) ;
147 
148  /* Collapse the image */
149  if ((collapsed = cpl_image_collapse_median_create(filt_image, 1, 0,
150  0)) == NULL) {
151  cpl_msg_error(cpl_func, "collapsing image: aborting spectrum detection");
152  cpl_image_delete(filt_image) ;
153  return -1 ;
154  }
155  cpl_image_delete(filt_image) ;
156 
157  /* Subtract low frequency signal */
158  line = cpl_vector_new_from_image_column(collapsed, 1) ;
159  cpl_image_delete(collapsed) ;
160  line_filt = cpl_vector_filter_median_create(line, SPECTRUM_HW) ;
161  cpl_vector_subtract(line, line_filt) ;
162  cpl_vector_delete(line_filt) ;
163 
164  /* Get relevant stats for thresholding */
165  median = cpl_vector_get_median_const(line) ;
166  stdev = cpl_vector_get_stdev(line) ;
167  max = cpl_vector_get_max(line) ;
168  mean = cpl_vector_get_mean(line) ;
169 
170  /* Set the threshold */
171  threshold = median + stdev ;
172  if (threshold > MIN_THRESH_FACT * max) threshold = MIN_THRESH_FACT * max ;
173  if (threshold < MAX_THRESH_FACT * mean) threshold = MAX_THRESH_FACT * mean;
174 
175  /* Recreate the image */
176  collapsed = cpl_image_new(1, cpl_vector_get_size(line), CPL_TYPE_FLOAT) ;
177  pcollapsed = cpl_image_get_data_float(collapsed) ;
178  pline = cpl_vector_get_data(line) ;
179  for (i=0 ; i<cpl_vector_get_size(line) ; i++)
180  pcollapsed[i] = (float)pline[i] ;
181  cpl_vector_delete(line) ;
182 
183  /* Binarise the image */
184  if ((mask = cpl_mask_threshold_image_create(collapsed, threshold,
185  DBL_MAX)) == NULL) {
186  cpl_msg_error(cpl_func, "cannot binarise") ;
187  cpl_image_delete(collapsed) ;
188  return -1 ;
189  }
190  if (cpl_mask_count(mask) < 1) {
191  cpl_msg_error(cpl_func, "not enough signal to detect spectra") ;
192  cpl_image_delete(collapsed) ;
193  cpl_mask_delete(mask) ;
194  return -1 ;
195  }
196  /* Labelise the different detected apertures */
197  if ((labels = cpl_image_labelise_mask_create(mask, &nlabels))==NULL) {
198  cpl_msg_error(cpl_func, "cannot labelise") ;
199  cpl_image_delete(collapsed) ;
200  cpl_mask_delete(mask) ;
201  return -1 ;
202  }
203  cpl_mask_delete(mask) ;
204 
205  /* Create the detected apertures list */
206  if ((aperts = cpl_apertures_new_from_image(collapsed, labels)) == NULL) {
207  cpl_msg_error(cpl_func, "cannot compute apertures") ;
208  cpl_image_delete(collapsed) ;
209  cpl_image_delete(labels) ;
210  return -1 ;
211  }
212  cpl_image_delete(labels) ;
213 
214  /* Select only relevant specs, create corresponding LUT's */
215  if (select_valid_spectra(collapsed, aperts, offset, shadows, SPEC_MAXWIDTH,
216  &n_valid_specs, &valid_specs) == -1) {
217  cpl_msg_debug(cpl_func,
218  "Could not select valid spectra from the %"CPL_SIZE_FORMAT
219  " apertures in %"CPL_SIZE_FORMAT"-col 1D-image, offset=%d"
220  ", min_bright=%d",
221  cpl_apertures_get_size(aperts),
222  cpl_image_get_size_y(collapsed), offset, SPEC_MAXWIDTH);
223  if (cpl_msg_get_level() <= CPL_MSG_DEBUG)
224  cpl_apertures_dump(aperts, stderr);
225  cpl_image_delete(collapsed);
226  cpl_apertures_delete(aperts);
227  return -1;
228  }
229  cpl_image_delete(collapsed) ;
230  if (n_valid_specs < 1) {
231  cpl_msg_error(cpl_func, "no valid spectrum detected") ;
232  cpl_free(valid_specs) ;
233  cpl_apertures_delete(aperts) ;
234  return -1 ;
235  }
236 
237  /* Look for the brightest, among the detected spectra */
238  *pos = cpl_apertures_get_centroid_y(aperts, valid_specs[0]+1) ;
239  brightness = cpl_apertures_get_flux(aperts, valid_specs[0]+1) ;
240  for (i=0 ; i<n_valid_specs ; i++) {
241  if (cpl_apertures_get_flux(aperts, valid_specs[i]+1) > brightness) {
242  *pos = cpl_apertures_get_centroid_y(aperts, valid_specs[i]+1) ;
243  brightness = cpl_apertures_get_flux(aperts, valid_specs[i]+1) ;
244  }
245  }
246  cpl_apertures_delete(aperts) ;
247  cpl_free(valid_specs) ;
248 
249  /* Minimum brightness required */
250  if (brightness < min_bright) {
251  cpl_msg_error(cpl_func, "brightness %f too low <%f", brightness,
252  min_bright) ;
253  return -1 ;
254  }
255 
256  /* Return */
257  return 0 ;
258 }
259 
260 /*----------------------------------------------------------------------------*/
272 /*----------------------------------------------------------------------------*/
274  const cpl_vector * in,
275  int fwhm,
276  double sigma,
277  int display,
278  cpl_vector ** fwhms_out,
279  cpl_vector ** areas_out)
280 {
281  cpl_vector * filtered ;
282  cpl_vector * spec_clean ;
283  cpl_vector * spec_convolved ;
284  double * pspec_convolved ;
285  int filt_size ;
286  cpl_vector * conv_kernel ;
287  cpl_vector * extract ;
288  cpl_vector * extract_x ;
289  cpl_vector * big_detected ;
290  cpl_vector * big_fwhms ;
291  cpl_vector * big_area ;
292  double * pbig_detected ;
293  double * pbig_fwhms ;
294  double * pbig_area ;
295  cpl_vector * detected ;
296  double * pdetected ;
297  cpl_vector * fwhms ;
298  double * pfwhms ;
299  cpl_vector * area ;
300  double * parea ;
301  double max, med, stdev, cur_val ;
302  double x0, sig, norm, offset ;
303  int nb_det, nb_samples, hwidth, start, stop ;
304  int i, j ;
305 
306  /* Test entries */
307  if (in == NULL) return NULL ;
308 
309  /* Initialise */
310  nb_samples = cpl_vector_get_size(in) ;
311  filt_size = 5 ;
312  hwidth = 5 ;
313 
314  /* Subtract the low frequency part */
315  cpl_msg_info(__func__, "Low Frequency signal removal") ;
316  if ((filtered=cpl_vector_filter_median_create(in, filt_size))==NULL){
317  cpl_msg_error(__func__, "Cannot filter the spectrum") ;
318  return NULL ;
319  }
320  spec_clean = cpl_vector_duplicate(in) ;
321  cpl_vector_subtract(spec_clean, filtered) ;
322  cpl_vector_delete(filtered) ;
323 
324  /* Display if requested */
325  if (display) {
326  cpl_plot_vector(
327  "set grid;set xlabel 'Position (pixels)';set ylabel 'Intensity (ADU)';",
328  "t 'Filtered extracted spectrum' w lines", "", spec_clean);
329  }
330 
331  /* Convolve */
332  spec_convolved = cpl_vector_duplicate(spec_clean) ;
333  if (fwhm > 0) {
334  cpl_msg_info(__func__, "Spectrum convolution") ;
335  /* Create convolution kernel */
336  if ((conv_kernel = irplib_wlxcorr_convolve_create_kernel(fwhm,
337  fwhm)) == NULL) {
338  cpl_msg_error(cpl_func, "Cannot create convolution kernel") ;
339  cpl_vector_delete(spec_clean) ;
340  cpl_vector_delete(spec_convolved) ;
341  return NULL ;
342  }
343 
344  /* Smooth the instrument resolution */
345  if (irplib_wlxcorr_convolve(spec_convolved, conv_kernel)) {
346  cpl_msg_error(cpl_func, "Cannot smoothe the signal");
347  cpl_vector_delete(spec_clean) ;
348  cpl_vector_delete(spec_convolved) ;
349  cpl_vector_delete(conv_kernel) ;
350  return NULL ;
351  }
352  cpl_vector_delete(conv_kernel) ;
353 
354  /* Display if requested */
355  if (display) {
356  cpl_plot_vector(
357  "set grid;set xlabel 'Position (pixels)';set ylabel 'Intensity (ADU)';",
358  "t 'Convolved extracted spectrum' w lines", "", spec_convolved);
359  }
360  }
361 
362  /* Apply the detection */
363  big_detected = cpl_vector_duplicate(spec_convolved) ;
364  big_fwhms = cpl_vector_duplicate(spec_convolved) ;
365  big_area = cpl_vector_duplicate(spec_convolved) ;
366  pbig_detected = cpl_vector_get_data(big_detected) ;
367  pbig_fwhms = cpl_vector_get_data(big_fwhms) ;
368  pbig_area = cpl_vector_get_data(big_area) ;
369 
370  pspec_convolved = cpl_vector_get_data(spec_convolved) ;
371 
372  /* To avoid detection on the side */
373  pspec_convolved[0] = pspec_convolved[nb_samples-1] = 0.0 ;
374 
375  /* Compute stats */
376  max = cpl_vector_get_max(spec_convolved) ;
377  stdev = cpl_vector_get_stdev(spec_convolved) ;
378  med = cpl_vector_get_median_const(spec_convolved) ;
379 
380  /* Loop on the detected lines */
381  nb_det = 0 ;
382  while (max > med + stdev * sigma) {
383  /* Compute the position */
384  i=0 ;
385  while (pspec_convolved[i] < max) i++ ;
386  if (i<=0 || i>=nb_samples-1) break ;
387 
388  /* Extract the line */
389  if (i - hwidth >= 0) start = i - hwidth ;
390  else start = 0 ;
391  if (i + hwidth <= nb_samples-1) stop = i + hwidth ;
392  else stop = nb_samples-1 ;
393  extract = cpl_vector_extract(spec_clean, start, stop, 1) ;
394  extract_x = cpl_vector_duplicate(extract) ;
395  for (j=0 ; j<cpl_vector_get_size(extract_x) ; j++) {
396  cpl_vector_set(extract_x, j, (double)j+1) ;
397  }
398  /* Fit the gaussian */
399  if (cpl_vector_fit_gaussian(extract_x, NULL, extract, NULL,
400  CPL_FIT_ALL, &x0, &sig, &norm, &offset, NULL, NULL,
401  NULL) != CPL_ERROR_NONE) {
402  cpl_msg_warning(__func__,
403  "Cannot fit a gaussian at [%d, %d]",
404  start, stop) ;
405  cpl_error_reset() ;
406  } else {
407  pbig_detected[nb_det] = x0+start ;
408  pbig_area[nb_det] = norm ;
409  pbig_fwhms[nb_det] = 2*sig*sqrt(2*log(2)) ;
410  cpl_msg_debug(__func__, "Line nb %d at position %g",
411  nb_det+1, pbig_detected[nb_det]) ;
412  nb_det ++ ;
413  }
414  cpl_vector_delete(extract) ;
415  cpl_vector_delete(extract_x) ;
416 
417  /* Cancel out the line on the left */
418  j = i-1 ;
419  cur_val = pspec_convolved[i] ;
420  while (j>=0 && pspec_convolved[j] < cur_val) {
421  cur_val = pspec_convolved[j] ;
422  pspec_convolved[j] = 0.0 ;
423  j-- ;
424  }
425  /* Cancel out the line on the right */
426  j = i+1 ;
427  cur_val = pspec_convolved[i] ;
428  while (j<=nb_samples-1 && pspec_convolved[j] < cur_val) {
429  cur_val = pspec_convolved[j] ;
430  pspec_convolved[j] = 0.0 ;
431  j++ ;
432  }
433  /* Cancel out the line on center */
434  pspec_convolved[i] = 0.0 ;
435 
436  /* Recompute the stats */
437  max = cpl_vector_get_max(spec_convolved) ;
438  stdev = cpl_vector_get_stdev(spec_convolved) ;
439  med = cpl_vector_get_median_const(spec_convolved) ;
440  }
441  cpl_vector_delete(spec_convolved) ;
442  cpl_vector_delete(spec_clean) ;
443 
444  /* Create the output vector */
445  if (nb_det == 0) {
446  detected = NULL ;
447  area = NULL ;
448  fwhms = NULL ;
449  } else {
450  detected = cpl_vector_new(nb_det) ;
451  area = cpl_vector_new(nb_det) ;
452  fwhms = cpl_vector_new(nb_det) ;
453  pdetected = cpl_vector_get_data(detected) ;
454  parea = cpl_vector_get_data(area) ;
455  pfwhms = cpl_vector_get_data(fwhms) ;
456  for (i=0 ; i<nb_det ; i++) {
457  pdetected[i] = pbig_detected[i] ;
458  parea[i] = pbig_area[i] ;
459  pfwhms[i] = pbig_fwhms[i] ;
460  }
461  }
462  cpl_vector_delete(big_detected) ;
463  cpl_vector_delete(big_area) ;
464  cpl_vector_delete(big_fwhms) ;
465 
466  /* Return */
467  if (fwhms_out == NULL) cpl_vector_delete(fwhms) ;
468  else *fwhms_out = fwhms ;
469  if (areas_out == NULL) cpl_vector_delete(area) ;
470  else *areas_out = area ;
471  return detected ;
472 }
473 
476 /*----------------------------------------------------------------------------*/
488 /*----------------------------------------------------------------------------*/
489 static int select_valid_spectra(
490  cpl_image * in,
491  cpl_apertures * aperts,
492  int offset,
493  spec_shadows shadows,
494  int max_spec_width,
495  int * n_valid_specs,
496  int ** valid_specs)
497 {
498  int nb_aperts ;
499  int i, j ;
500 
501  /* Initialise */
502  *valid_specs = NULL ;
503  nb_aperts = cpl_apertures_get_size(aperts) ;
504  *n_valid_specs = 0 ;
505 
506  /* Test entries */
507  if (nb_aperts < 1) return -1 ;
508 
509  /* Count nb of valid specs */
510  j = 0 ;
511  for (i=0 ; i<nb_aperts ; i++)
512  if (valid_spectrum(in, aperts, offset, shadows, max_spec_width,
513  i+1)) (*n_valid_specs)++ ;
514 
515  /* Associate to each spectrum, its object number */
516  if (*n_valid_specs) {
517  *valid_specs = cpl_calloc(*n_valid_specs, sizeof(int)) ;
518  j = 0 ;
519  for (i=0 ; i<nb_aperts ; i++)
520  if (valid_spectrum(in, aperts, offset, shadows, max_spec_width,
521  i+1)) {
522  (*valid_specs)[j] = i ;
523  j++ ;
524  }
525  } else return -1 ;
526 
527  return 0 ;
528 }
529 
530 /*---------------------------------------------------------------------------*/
541 /*----------------------------------------------------------------------------*/
542 static int valid_spectrum(
543  cpl_image * in,
544  cpl_apertures * aperts,
545  int offset,
546  spec_shadows shadows,
547  int max_spec_width,
548  int objnum)
549 {
550  int objwidth ;
551  double valover, valunder, valcenter ;
552 
553  /* Find objwidth */
554  objwidth = cpl_apertures_get_top(aperts, objnum) -
555  cpl_apertures_get_bottom(aperts, objnum) + 1 ;
556  if (objwidth > max_spec_width) {
557  cpl_msg_error(cpl_func, "object is too wide") ;
558  return 0 ;
559  }
560 
561  /* Object is too small */
562  if (cpl_apertures_get_npix(aperts, objnum) < 2) return 0 ;
563 
564  /* no shadow required */
565  if (shadows == NO_SHADOW) return 1 ;
566 
567  /* Get the median of the object (valcenter) */
568  valcenter = cpl_apertures_get_median(aperts, objnum) ;
569 
570  /* Get the black shadows medians (valunder and valover) */
571  if (cpl_apertures_get_bottom(aperts, objnum) - offset < 1) valunder = 0.0 ;
572  else valunder = cpl_image_get_median_window(in, 1,
573  cpl_apertures_get_bottom(aperts, objnum) - offset, 1,
574  cpl_apertures_get_top(aperts, objnum) - offset) ;
575 
576  if (cpl_apertures_get_top(aperts, objnum) + offset > 1024) valover = 0.0 ;
577  else valover = cpl_image_get_median_window(in, 1,
578  cpl_apertures_get_bottom(aperts, objnum) + offset, 1,
579  cpl_apertures_get_top(aperts, objnum) + offset) ;
580 
581  switch (shadows) {
582  case TWO_SHADOWS:
583  if ((valunder < -fabs(valcenter/SPEC_SHADOW_FACT)) &&
584  (valover < -fabs(valcenter/SPEC_SHADOW_FACT)) &&
585  (valunder/valover > 0.5) &&
586  (valunder/valover < 2.0)) return 1 ;
587  break;
588 
589  case ONE_SHADOW:
590  if ((valunder < -fabs(valcenter/SPEC_SHADOW_FACT)) ||
591  (valover < -fabs(valcenter/SPEC_SHADOW_FACT))) return 1 ;
592  break;
593 
594  case NO_SHADOW:
595  return 1 ;
596 
597  default:
598  cpl_msg_error(cpl_func, "unknown spec_detect_mode") ;
599  break ;
600  }
601 
602  cpl_msg_debug(cpl_func, "No spectrum(%d): under=%g, center=%g, over=%g",
603  shadows, valunder, valcenter, valover);
604 
605  return 0 ;
606 }