SINFONI Pipeline Reference Manual  2.6.0
irplib_spectrum.c
1 /*
2  * This file is part of the irplib package
3  * Copyright (C) 2002,2003,2014 European Southern Observatory
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02111-1307 USA
18  */
19 
20 #ifdef HAVE_CONFIG_H
21 #include <config.h>
22 #endif
23 
24 /*-----------------------------------------------------------------------------
25  Includes
26  -----------------------------------------------------------------------------*/
27 
28 #include "irplib_wlxcorr.h"
29 #include "irplib_spectrum.h"
30 
31 #include <math.h>
32 #include <float.h>
33 #include <cpl.h>
34 
35 /*-----------------------------------------------------------------------------
36  Define
37  -----------------------------------------------------------------------------*/
38 
39 #define SPECTRUM_HW 16
40 #define MIN_THRESH_FACT 0.9
41 #define MAX_THRESH_FACT 1.1
42 #define SPEC_SHADOW_FACT 30.0 /* Negative spectrum intensity*/
43 #define SPEC_MAXWIDTH 48
44 
45 /*-----------------------------------------------------------------------------
46  Functions prototypes
47  -----------------------------------------------------------------------------*/
48 
49 static int select_valid_spectra(cpl_image *, cpl_apertures *, int,
50  spec_shadows, int, int *, int **) ;
51 static int valid_spectrum(cpl_image *, cpl_apertures *, int, spec_shadows, int,
52  int) ;
53 
54 /*----------------------------------------------------------------------------*/
58 /*----------------------------------------------------------------------------*/
59 
62 /*----------------------------------------------------------------------------*/
77 /*----------------------------------------------------------------------------*/
79  const cpl_image * in,
80  int offset,
81  spec_shadows shadows,
82  double min_bright,
83  int orient,
84  double * pos)
85 {
86  cpl_image * loc_ima ;
87  cpl_image * filt_image ;
88  cpl_image * collapsed ;
89  float * pcollapsed ;
90  cpl_vector * line ;
91  double * pline ;
92  cpl_vector * line_filt ;
93  double threshold ;
94  double median, stdev, max, mean ;
95  cpl_mask * mask ;
96  cpl_image * labels ;
97  cpl_size nlabels ;
98  cpl_apertures * aperts ;
99  int n_valid_specs ;
100  int * valid_specs ;
101  double brightness ;
102  int i ;
103 
104  /* Test entries */
105  if (in == NULL) return -1 ;
106  if (orient!=0 && orient!=1) return -1 ;
107 
108  /* Flip the image if necessary */
109  if (orient == 1) {
110  loc_ima = cpl_image_duplicate(in) ;
111  cpl_image_flip(loc_ima, 1) ;
112  } else {
113  loc_ima = cpl_image_duplicate(in) ;
114  }
115 
116  /* Median vertical filtering 3x3 */
117  mask = cpl_mask_new(3, 3) ;
118  cpl_mask_not(mask) ;
119  filt_image = cpl_image_new(
120  cpl_image_get_size_x(loc_ima),
121  cpl_image_get_size_y(loc_ima),
122  cpl_image_get_type(loc_ima)) ;
123  if (cpl_image_filter_mask(filt_image, loc_ima, mask,
124  CPL_FILTER_MEDIAN, CPL_BORDER_FILTER) != CPL_ERROR_NONE) {
125  cpl_msg_error(__func__, "Cannot filter the image") ;
126  cpl_mask_delete(mask) ;
127  cpl_image_delete(filt_image) ;
128  return -1 ;
129  }
130  cpl_mask_delete(mask) ;
131  cpl_image_delete(loc_ima) ;
132 
133  /* Collapse the image */
134  if ((collapsed = cpl_image_collapse_median_create(filt_image, 1, 0,
135  0)) == NULL) {
136  cpl_msg_error(cpl_func, "collapsing image: aborting spectrum detection");
137  cpl_image_delete(filt_image) ;
138  return -1 ;
139  }
140  cpl_image_delete(filt_image) ;
141 
142  /* Subtract low frequency signal */
143  line = cpl_vector_new_from_image_column(collapsed, 1) ;
144  cpl_image_delete(collapsed) ;
145  line_filt = cpl_vector_filter_median_create(line, SPECTRUM_HW) ;
146  cpl_vector_subtract(line, line_filt) ;
147  cpl_vector_delete(line_filt) ;
148 
149  /* Get relevant stats for thresholding */
150  median = cpl_vector_get_median_const(line) ;
151  stdev = cpl_vector_get_stdev(line) ;
152  max = cpl_vector_get_max(line) ;
153  mean = cpl_vector_get_mean(line) ;
154 
155  /* Set the threshold */
156  threshold = median + stdev ;
157  if (threshold > MIN_THRESH_FACT * max) threshold = MIN_THRESH_FACT * max ;
158  if (threshold < MAX_THRESH_FACT * mean) threshold = MAX_THRESH_FACT * mean;
159 
160  /* Recreate the image */
161  collapsed = cpl_image_new(1, cpl_vector_get_size(line), CPL_TYPE_FLOAT) ;
162  pcollapsed = cpl_image_get_data_float(collapsed) ;
163  pline = cpl_vector_get_data(line) ;
164  for (i=0 ; i<cpl_vector_get_size(line) ; i++)
165  pcollapsed[i] = (float)pline[i] ;
166  cpl_vector_delete(line) ;
167 
168  /* Binarise the image */
169  if ((mask = cpl_mask_threshold_image_create(collapsed, threshold,
170  DBL_MAX)) == NULL) {
171  cpl_msg_error(cpl_func, "cannot binarise") ;
172  cpl_image_delete(collapsed) ;
173  return -1 ;
174  }
175  if (cpl_mask_count(mask) < 1) {
176  cpl_msg_error(cpl_func, "not enough signal to detect spectra") ;
177  cpl_image_delete(collapsed) ;
178  cpl_mask_delete(mask) ;
179  return -1 ;
180  }
181  /* Labelise the different detected apertures */
182  if ((labels = cpl_image_labelise_mask_create(mask, &nlabels))==NULL) {
183  cpl_msg_error(cpl_func, "cannot labelise") ;
184  cpl_image_delete(collapsed) ;
185  cpl_mask_delete(mask) ;
186  return -1 ;
187  }
188  cpl_mask_delete(mask) ;
189 
190  /* Create the detected apertures list */
191  if ((aperts = cpl_apertures_new_from_image(collapsed, labels)) == NULL) {
192  cpl_msg_error(cpl_func, "cannot compute apertures") ;
193  cpl_image_delete(collapsed) ;
194  cpl_image_delete(labels) ;
195  return -1 ;
196  }
197  cpl_image_delete(labels) ;
198 
199  /* Select only relevant specs, create corresponding LUT's */
200  if (select_valid_spectra(collapsed, aperts, offset, shadows, SPEC_MAXWIDTH,
201  &n_valid_specs, &valid_specs) == -1) {
202  cpl_msg_debug(cpl_func,
203  "Could not select valid spectra from the %"CPL_SIZE_FORMAT
204  " apertures in %"CPL_SIZE_FORMAT"-col 1D-image, offset=%d"
205  ", min_bright=%d",
206  cpl_apertures_get_size(aperts),
207  cpl_image_get_size_y(collapsed), offset, SPEC_MAXWIDTH);
208  if (cpl_msg_get_level() <= CPL_MSG_DEBUG)
209  cpl_apertures_dump(aperts, stderr);
210  cpl_image_delete(collapsed);
211  cpl_apertures_delete(aperts);
212  return -1;
213  }
214  cpl_image_delete(collapsed) ;
215  if (n_valid_specs < 1) {
216  cpl_msg_error(cpl_func, "no valid spectrum detected") ;
217  cpl_free(valid_specs) ;
218  cpl_apertures_delete(aperts) ;
219  return -1 ;
220  }
221 
222  /* Look for the brightest, among the detected spectra */
223  *pos = cpl_apertures_get_centroid_y(aperts, valid_specs[0]+1) ;
224  brightness = cpl_apertures_get_flux(aperts, valid_specs[0]+1) ;
225  for (i=0 ; i<n_valid_specs ; i++) {
226  if (cpl_apertures_get_flux(aperts, valid_specs[i]+1) > brightness) {
227  *pos = cpl_apertures_get_centroid_y(aperts, valid_specs[i]+1) ;
228  brightness = cpl_apertures_get_flux(aperts, valid_specs[i]+1) ;
229  }
230  }
231  cpl_apertures_delete(aperts) ;
232  cpl_free(valid_specs) ;
233 
234  /* Minimum brightness required */
235  if (brightness < min_bright) {
236  cpl_msg_error(cpl_func, "brightness %f too low <%f", brightness,
237  min_bright) ;
238  return -1 ;
239  }
240 
241  /* Return */
242  return 0 ;
243 }
244 
245 /*----------------------------------------------------------------------------*/
257 /*----------------------------------------------------------------------------*/
259  const cpl_vector * in,
260  int fwhm,
261  double sigma,
262  int display,
263  cpl_vector ** fwhms_out,
264  cpl_vector ** areas_out)
265 {
266  cpl_vector * filtered ;
267  cpl_vector * spec_clean ;
268  cpl_vector * spec_convolved ;
269  double * pspec_convolved ;
270  int filt_size ;
271  cpl_vector * big_detected ;
272  cpl_vector * big_fwhms ;
273  cpl_vector * big_area ;
274  double * pbig_detected ;
275  double * pbig_fwhms ;
276  double * pbig_area ;
277  cpl_vector * detected ;
278  cpl_vector * fwhms ;
279  cpl_vector * area ;
280  double max, med, stdev ;
281  double x0, sig, norm, offset ;
282  int nb_det, nb_samples, hwidth, start, stop ;
283  int i, j ;
284 
285  /* Test entries */
286  if (in == NULL) return NULL ;
287 
288  /* Initialise */
289  nb_samples = cpl_vector_get_size(in) ;
290  filt_size = 5 ;
291  hwidth = 5 ;
292 
293  /* Subtract the low frequency part */
294  cpl_msg_info(__func__, "Low Frequency signal removal") ;
295  if ((filtered=cpl_vector_filter_median_create(in, filt_size))==NULL){
296  cpl_msg_error(__func__, "Cannot filter the spectrum") ;
297  return NULL ;
298  }
299  spec_clean = cpl_vector_duplicate(in) ;
300  cpl_vector_subtract(spec_clean, filtered) ;
301  cpl_vector_delete(filtered) ;
302 
303  /* Display if requested */
304  if (display) {
305  cpl_plot_vector(
306  "set grid;set xlabel 'Position (pixels)';set ylabel 'Intensity (ADU)';",
307  "t 'Filtered extracted spectrum' w lines", "", spec_clean);
308  }
309 
310  /* Convolve */
311  spec_convolved = cpl_vector_duplicate(spec_clean) ;
312  if (fwhm > 0) {
313  cpl_vector * conv_kernel ;
314  cpl_msg_info(__func__, "Spectrum convolution") ;
315  /* Create convolution kernel */
316  if ((conv_kernel = irplib_wlxcorr_convolve_create_kernel(fwhm,
317  fwhm)) == NULL) {
318  cpl_msg_error(cpl_func, "Cannot create convolution kernel") ;
319  cpl_vector_delete(spec_clean) ;
320  cpl_vector_delete(spec_convolved) ;
321  return NULL ;
322  }
323 
324  /* Smooth the instrument resolution */
325  if (irplib_wlxcorr_convolve(spec_convolved, conv_kernel)) {
326  cpl_msg_error(cpl_func, "Cannot smoothe the signal");
327  cpl_vector_delete(spec_clean) ;
328  cpl_vector_delete(spec_convolved) ;
329  cpl_vector_delete(conv_kernel) ;
330  return NULL ;
331  }
332  cpl_vector_delete(conv_kernel) ;
333 
334  /* Display if requested */
335  if (display) {
336  cpl_plot_vector(
337  "set grid;set xlabel 'Position (pixels)';set ylabel 'Intensity (ADU)';",
338  "t 'Convolved extracted spectrum' w lines", "", spec_convolved);
339  }
340  }
341 
342  /* Apply the detection */
343  big_detected = cpl_vector_duplicate(spec_convolved) ;
344  big_fwhms = cpl_vector_duplicate(spec_convolved) ;
345  big_area = cpl_vector_duplicate(spec_convolved) ;
346  pbig_detected = cpl_vector_get_data(big_detected) ;
347  pbig_fwhms = cpl_vector_get_data(big_fwhms) ;
348  pbig_area = cpl_vector_get_data(big_area) ;
349 
350  pspec_convolved = cpl_vector_get_data(spec_convolved) ;
351 
352  /* To avoid detection on the side */
353  pspec_convolved[0] = pspec_convolved[nb_samples-1] = 0.0 ;
354 
355  /* Compute stats */
356  max = cpl_vector_get_max(spec_convolved) ;
357  stdev = cpl_vector_get_stdev(spec_convolved) ;
358  med = cpl_vector_get_median_const(spec_convolved) ;
359 
360  /* Loop on the detected lines */
361  nb_det = 0 ;
362  while (max > med + stdev * sigma) {
363  cpl_vector * extract ;
364  cpl_vector * extract_x ;
365  double cur_val ;
366 
367  /* Compute the position */
368  i=0 ;
369  while (pspec_convolved[i] < max) i++ ;
370  if (i<=0 || i>=nb_samples-1) break ;
371 
372  /* Extract the line */
373  if (i - hwidth >= 0) start = i - hwidth ;
374  else start = 0 ;
375  if (i + hwidth <= nb_samples-1) stop = i + hwidth ;
376  else stop = nb_samples-1 ;
377  extract = cpl_vector_extract(spec_clean, start, stop, 1) ;
378  extract_x = cpl_vector_duplicate(extract) ;
379  for (j=0 ; j<cpl_vector_get_size(extract_x) ; j++) {
380  cpl_vector_set(extract_x, j, (double)j+1) ;
381  }
382  /* Fit the gaussian */
383  if (cpl_vector_fit_gaussian(extract_x, NULL, extract, NULL,
384  CPL_FIT_ALL, &x0, &sig, &norm, &offset, NULL, NULL,
385  NULL) != CPL_ERROR_NONE) {
386  cpl_msg_warning(__func__,
387  "Cannot fit a gaussian at [%d, %d]",
388  start, stop) ;
389  cpl_error_reset() ;
390  } else {
391  pbig_detected[nb_det] = x0+start ;
392  pbig_area[nb_det] = norm ;
393  pbig_fwhms[nb_det] = 2*sig*sqrt(2*log(2)) ;
394  cpl_msg_debug(__func__, "Line nb %d at position %g",
395  nb_det+1, pbig_detected[nb_det]) ;
396  nb_det ++ ;
397  }
398  cpl_vector_delete(extract) ;
399  cpl_vector_delete(extract_x) ;
400 
401  /* Cancel out the line on the left */
402  j = i-1 ;
403  cur_val = pspec_convolved[i] ;
404  while (j>=0 && pspec_convolved[j] < cur_val) {
405  cur_val = pspec_convolved[j] ;
406  pspec_convolved[j] = 0.0 ;
407  j-- ;
408  }
409  /* Cancel out the line on the right */
410  j = i+1 ;
411  cur_val = pspec_convolved[i] ;
412  while (j<=nb_samples-1 && pspec_convolved[j] < cur_val) {
413  cur_val = pspec_convolved[j] ;
414  pspec_convolved[j] = 0.0 ;
415  j++ ;
416  }
417  /* Cancel out the line on center */
418  pspec_convolved[i] = 0.0 ;
419 
420  /* Recompute the stats */
421  max = cpl_vector_get_max(spec_convolved) ;
422  stdev = cpl_vector_get_stdev(spec_convolved) ;
423  med = cpl_vector_get_median_const(spec_convolved) ;
424  }
425  cpl_vector_delete(spec_convolved) ;
426  cpl_vector_delete(spec_clean) ;
427 
428  /* Create the output vector */
429  if (nb_det == 0) {
430  detected = NULL ;
431  area = NULL ;
432  fwhms = NULL ;
433  } else {
434  double * pdetected ;
435  double * pfwhms ;
436  double * parea ;
437  detected = cpl_vector_new(nb_det) ;
438  area = cpl_vector_new(nb_det) ;
439  fwhms = cpl_vector_new(nb_det) ;
440  pdetected = cpl_vector_get_data(detected) ;
441  parea = cpl_vector_get_data(area) ;
442  pfwhms = cpl_vector_get_data(fwhms) ;
443  for (i=0 ; i<nb_det ; i++) {
444  pdetected[i] = pbig_detected[i] ;
445  parea[i] = pbig_area[i] ;
446  pfwhms[i] = pbig_fwhms[i] ;
447  }
448  }
449  cpl_vector_delete(big_detected) ;
450  cpl_vector_delete(big_area) ;
451  cpl_vector_delete(big_fwhms) ;
452 
453  /* Return */
454  if (fwhms_out == NULL) cpl_vector_delete(fwhms) ;
455  else *fwhms_out = fwhms ;
456  if (areas_out == NULL) cpl_vector_delete(area) ;
457  else *areas_out = area ;
458  return detected ;
459 }
460 
463 /*----------------------------------------------------------------------------*/
475 /*----------------------------------------------------------------------------*/
476 static int select_valid_spectra(
477  cpl_image * in,
478  cpl_apertures * aperts,
479  int offset,
480  spec_shadows shadows,
481  int max_spec_width,
482  int * n_valid_specs,
483  int ** valid_specs)
484 {
485  int nb_aperts ;
486  int i, j ;
487 
488  /* Initialise */
489  *valid_specs = NULL ;
490  nb_aperts = cpl_apertures_get_size(aperts) ;
491  *n_valid_specs = 0 ;
492 
493  /* Test entries */
494  if (nb_aperts < 1) return -1 ;
495 
496  /* Count nb of valid specs */
497  j = 0 ;
498  for (i=0 ; i<nb_aperts ; i++)
499  if (valid_spectrum(in, aperts, offset, shadows, max_spec_width,
500  i+1)) (*n_valid_specs)++ ;
501 
502  /* Associate to each spectrum, its object number */
503  if (*n_valid_specs) {
504  *valid_specs = cpl_calloc(*n_valid_specs, sizeof(int)) ;
505  j = 0 ;
506  for (i=0 ; i<nb_aperts ; i++)
507  if (valid_spectrum(in, aperts, offset, shadows, max_spec_width,
508  i+1)) {
509  (*valid_specs)[j] = i ;
510  j++ ;
511  }
512  } else return -1 ;
513 
514  return 0 ;
515 }
516 
517 /*---------------------------------------------------------------------------*/
528 /*----------------------------------------------------------------------------*/
529 static int valid_spectrum(
530  cpl_image * in,
531  cpl_apertures * aperts,
532  int offset,
533  spec_shadows shadows,
534  int max_spec_width,
535  int objnum)
536 {
537  int objwidth ;
538  double valover, valunder, valcenter ;
539 
540  /* Find objwidth */
541  objwidth = cpl_apertures_get_top(aperts, objnum) -
542  cpl_apertures_get_bottom(aperts, objnum) + 1 ;
543  if (objwidth > max_spec_width) {
544  cpl_msg_error(cpl_func, "object is too wide") ;
545  return 0 ;
546  }
547 
548  /* Object is too small */
549  if (cpl_apertures_get_npix(aperts, objnum) < 2) return 0 ;
550 
551  /* no shadow required */
552  if (shadows == NO_SHADOW) return 1 ;
553 
554  /* Get the median of the object (valcenter) */
555  valcenter = cpl_apertures_get_median(aperts, objnum) ;
556 
557  /* Get the black shadows medians (valunder and valover) */
558  if (cpl_apertures_get_bottom(aperts, objnum) - offset < 1) valunder = 0.0 ;
559  else valunder = cpl_image_get_median_window(in, 1,
560  cpl_apertures_get_bottom(aperts, objnum) - offset, 1,
561  cpl_apertures_get_top(aperts, objnum) - offset) ;
562 
563  if (cpl_apertures_get_top(aperts, objnum) + offset > 1024) valover = 0.0 ;
564  else valover = cpl_image_get_median_window(in, 1,
565  cpl_apertures_get_bottom(aperts, objnum) + offset, 1,
566  cpl_apertures_get_top(aperts, objnum) + offset) ;
567 
568  switch (shadows) {
569  case TWO_SHADOWS:
570  if ((valunder < -fabs(valcenter/SPEC_SHADOW_FACT)) &&
571  (valover < -fabs(valcenter/SPEC_SHADOW_FACT)) &&
572  (valunder/valover > 0.5) &&
573  (valunder/valover < 2.0)) return 1 ;
574  break;
575 
576  case ONE_SHADOW:
577  if ((valunder < -fabs(valcenter/SPEC_SHADOW_FACT)) ||
578  (valover < -fabs(valcenter/SPEC_SHADOW_FACT))) return 1 ;
579  break;
580 
581  case NO_SHADOW:
582  return 1 ;
583 
584  default:
585  cpl_msg_error(cpl_func, "unknown spec_detect_mode") ;
586  break ;
587  }
588 
589  cpl_msg_debug(cpl_func, "No spectrum(%d): under=%g, center=%g, over=%g",
590  shadows, valunder, valcenter, valover);
591 
592  return 0 ;
593 }
int irplib_spectrum_find_brightest(const cpl_image *in, int offset, spec_shadows shadows, double min_bright, int orient, double *pos)
Finds the brightest spectrum in an image.
cpl_vector * irplib_spectrum_detect_peaks(const cpl_vector *in, int fwhm, double sigma, int display, cpl_vector **fwhms_out, cpl_vector **areas_out)
Detect the brightest features in a spectrum.