DETMON Pipeline Reference Manual  1.2.8
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 * conv_kernel ;
272  cpl_vector * extract ;
273  cpl_vector * extract_x ;
274  cpl_vector * big_detected ;
275  cpl_vector * big_fwhms ;
276  cpl_vector * big_area ;
277  double * pbig_detected ;
278  double * pbig_fwhms ;
279  double * pbig_area ;
280  cpl_vector * detected ;
281  double * pdetected ;
282  cpl_vector * fwhms ;
283  double * pfwhms ;
284  cpl_vector * area ;
285  double * parea ;
286  double max, med, stdev, cur_val ;
287  double x0, sig, norm, offset ;
288  int nb_det, nb_samples, hwidth, start, stop ;
289  int i, j ;
290 
291  /* Test entries */
292  if (in == NULL) return NULL ;
293 
294  /* Initialise */
295  nb_samples = cpl_vector_get_size(in) ;
296  filt_size = 5 ;
297  hwidth = 5 ;
298 
299  /* Subtract the low frequency part */
300  cpl_msg_info(__func__, "Low Frequency signal removal") ;
301  if ((filtered=cpl_vector_filter_median_create(in, filt_size))==NULL){
302  cpl_msg_error(__func__, "Cannot filter the spectrum") ;
303  return NULL ;
304  }
305  spec_clean = cpl_vector_duplicate(in) ;
306  cpl_vector_subtract(spec_clean, filtered) ;
307  cpl_vector_delete(filtered) ;
308 
309  /* Display if requested */
310  if (display) {
311  cpl_plot_vector(
312  "set grid;set xlabel 'Position (pixels)';set ylabel 'Intensity (ADU)';",
313  "t 'Filtered extracted spectrum' w lines", "", spec_clean);
314  }
315 
316  /* Convolve */
317  spec_convolved = cpl_vector_duplicate(spec_clean) ;
318  if (fwhm > 0) {
319  cpl_msg_info(__func__, "Spectrum convolution") ;
320  /* Create convolution kernel */
321  if ((conv_kernel = irplib_wlxcorr_convolve_create_kernel(fwhm,
322  fwhm)) == NULL) {
323  cpl_msg_error(cpl_func, "Cannot create convolution kernel") ;
324  cpl_vector_delete(spec_clean) ;
325  cpl_vector_delete(spec_convolved) ;
326  return NULL ;
327  }
328 
329  /* Smooth the instrument resolution */
330  if (irplib_wlxcorr_convolve(spec_convolved, conv_kernel)) {
331  cpl_msg_error(cpl_func, "Cannot smoothe the signal");
332  cpl_vector_delete(spec_clean) ;
333  cpl_vector_delete(spec_convolved) ;
334  cpl_vector_delete(conv_kernel) ;
335  return NULL ;
336  }
337  cpl_vector_delete(conv_kernel) ;
338 
339  /* Display if requested */
340  if (display) {
341  cpl_plot_vector(
342  "set grid;set xlabel 'Position (pixels)';set ylabel 'Intensity (ADU)';",
343  "t 'Convolved extracted spectrum' w lines", "", spec_convolved);
344  }
345  }
346 
347  /* Apply the detection */
348  big_detected = cpl_vector_duplicate(spec_convolved) ;
349  big_fwhms = cpl_vector_duplicate(spec_convolved) ;
350  big_area = cpl_vector_duplicate(spec_convolved) ;
351  pbig_detected = cpl_vector_get_data(big_detected) ;
352  pbig_fwhms = cpl_vector_get_data(big_fwhms) ;
353  pbig_area = cpl_vector_get_data(big_area) ;
354 
355  pspec_convolved = cpl_vector_get_data(spec_convolved) ;
356 
357  /* To avoid detection on the side */
358  pspec_convolved[0] = pspec_convolved[nb_samples-1] = 0.0 ;
359 
360  /* Compute stats */
361  max = cpl_vector_get_max(spec_convolved) ;
362  stdev = cpl_vector_get_stdev(spec_convolved) ;
363  med = cpl_vector_get_median_const(spec_convolved) ;
364 
365  /* Loop on the detected lines */
366  nb_det = 0 ;
367  while (max > med + stdev * sigma) {
368  /* Compute the position */
369  i=0 ;
370  while (pspec_convolved[i] < max) i++ ;
371  if (i<=0 || i>=nb_samples-1) break ;
372 
373  /* Extract the line */
374  if (i - hwidth >= 0) start = i - hwidth ;
375  else start = 0 ;
376  if (i + hwidth <= nb_samples-1) stop = i + hwidth ;
377  else stop = nb_samples-1 ;
378  extract = cpl_vector_extract(spec_clean, start, stop, 1) ;
379  extract_x = cpl_vector_duplicate(extract) ;
380  for (j=0 ; j<cpl_vector_get_size(extract_x) ; j++) {
381  cpl_vector_set(extract_x, j, (double)j+1) ;
382  }
383  /* Fit the gaussian */
384  if (cpl_vector_fit_gaussian(extract_x, NULL, extract, NULL,
385  CPL_FIT_ALL, &x0, &sig, &norm, &offset, NULL, NULL,
386  NULL) != CPL_ERROR_NONE) {
387  cpl_msg_warning(__func__,
388  "Cannot fit a gaussian at [%d, %d]",
389  start, stop) ;
390  cpl_error_reset() ;
391  } else {
392  pbig_detected[nb_det] = x0+start ;
393  pbig_area[nb_det] = norm ;
394  pbig_fwhms[nb_det] = 2*sig*sqrt(2*log(2)) ;
395  cpl_msg_debug(__func__, "Line nb %d at position %g",
396  nb_det+1, pbig_detected[nb_det]) ;
397  nb_det ++ ;
398  }
399  cpl_vector_delete(extract) ;
400  cpl_vector_delete(extract_x) ;
401 
402  /* Cancel out the line on the left */
403  j = i-1 ;
404  cur_val = pspec_convolved[i] ;
405  while (j>=0 && pspec_convolved[j] < cur_val) {
406  cur_val = pspec_convolved[j] ;
407  pspec_convolved[j] = 0.0 ;
408  j-- ;
409  }
410  /* Cancel out the line on the right */
411  j = i+1 ;
412  cur_val = pspec_convolved[i] ;
413  while (j<=nb_samples-1 && pspec_convolved[j] < cur_val) {
414  cur_val = pspec_convolved[j] ;
415  pspec_convolved[j] = 0.0 ;
416  j++ ;
417  }
418  /* Cancel out the line on center */
419  pspec_convolved[i] = 0.0 ;
420 
421  /* Recompute the stats */
422  max = cpl_vector_get_max(spec_convolved) ;
423  stdev = cpl_vector_get_stdev(spec_convolved) ;
424  med = cpl_vector_get_median_const(spec_convolved) ;
425  }
426  cpl_vector_delete(spec_convolved) ;
427  cpl_vector_delete(spec_clean) ;
428 
429  /* Create the output vector */
430  if (nb_det == 0) {
431  detected = NULL ;
432  area = NULL ;
433  fwhms = NULL ;
434  } else {
435  detected = cpl_vector_new(nb_det) ;
436  area = cpl_vector_new(nb_det) ;
437  fwhms = cpl_vector_new(nb_det) ;
438  pdetected = cpl_vector_get_data(detected) ;
439  parea = cpl_vector_get_data(area) ;
440  pfwhms = cpl_vector_get_data(fwhms) ;
441  for (i=0 ; i<nb_det ; i++) {
442  pdetected[i] = pbig_detected[i] ;
443  parea[i] = pbig_area[i] ;
444  pfwhms[i] = pbig_fwhms[i] ;
445  }
446  }
447  cpl_vector_delete(big_detected) ;
448  cpl_vector_delete(big_area) ;
449  cpl_vector_delete(big_fwhms) ;
450 
451  /* Return */
452  if (fwhms_out == NULL) cpl_vector_delete(fwhms) ;
453  else *fwhms_out = fwhms ;
454  if (areas_out == NULL) cpl_vector_delete(area) ;
455  else *areas_out = area ;
456  return detected ;
457 }
458 
461 /*----------------------------------------------------------------------------*/
473 /*----------------------------------------------------------------------------*/
474 static int select_valid_spectra(
475  cpl_image * in,
476  cpl_apertures * aperts,
477  int offset,
478  spec_shadows shadows,
479  int max_spec_width,
480  int * n_valid_specs,
481  int ** valid_specs)
482 {
483  int nb_aperts ;
484  int i, j ;
485 
486  /* Initialise */
487  *valid_specs = NULL ;
488  nb_aperts = cpl_apertures_get_size(aperts) ;
489  *n_valid_specs = 0 ;
490 
491  /* Test entries */
492  if (nb_aperts < 1) return -1 ;
493 
494  /* Count nb of valid specs */
495  j = 0 ;
496  for (i=0 ; i<nb_aperts ; i++)
497  if (valid_spectrum(in, aperts, offset, shadows, max_spec_width,
498  i+1)) (*n_valid_specs)++ ;
499 
500  /* Associate to each spectrum, its object number */
501  if (*n_valid_specs) {
502  *valid_specs = cpl_calloc(*n_valid_specs, sizeof(int)) ;
503  j = 0 ;
504  for (i=0 ; i<nb_aperts ; i++)
505  if (valid_spectrum(in, aperts, offset, shadows, max_spec_width,
506  i+1)) {
507  (*valid_specs)[j] = i ;
508  j++ ;
509  }
510  } else return -1 ;
511 
512  return 0 ;
513 }
514 
515 /*---------------------------------------------------------------------------*/
526 /*----------------------------------------------------------------------------*/
527 static int valid_spectrum(
528  cpl_image * in,
529  cpl_apertures * aperts,
530  int offset,
531  spec_shadows shadows,
532  int max_spec_width,
533  int objnum)
534 {
535  int objwidth ;
536  double valover, valunder, valcenter ;
537 
538  /* Find objwidth */
539  objwidth = cpl_apertures_get_top(aperts, objnum) -
540  cpl_apertures_get_bottom(aperts, objnum) + 1 ;
541  if (objwidth > max_spec_width) {
542  cpl_msg_error(cpl_func, "object is too wide") ;
543  return 0 ;
544  }
545 
546  /* Object is too small */
547  if (cpl_apertures_get_npix(aperts, objnum) < 2) return 0 ;
548 
549  /* no shadow required */
550  if (shadows == NO_SHADOW) return 1 ;
551 
552  /* Get the median of the object (valcenter) */
553  valcenter = cpl_apertures_get_median(aperts, objnum) ;
554 
555  /* Get the black shadows medians (valunder and valover) */
556  if (cpl_apertures_get_bottom(aperts, objnum) - offset < 1) valunder = 0.0 ;
557  else valunder = cpl_image_get_median_window(in, 1,
558  cpl_apertures_get_bottom(aperts, objnum) - offset, 1,
559  cpl_apertures_get_top(aperts, objnum) - offset) ;
560 
561  if (cpl_apertures_get_top(aperts, objnum) + offset > 1024) valover = 0.0 ;
562  else valover = cpl_image_get_median_window(in, 1,
563  cpl_apertures_get_bottom(aperts, objnum) + offset, 1,
564  cpl_apertures_get_top(aperts, objnum) + offset) ;
565 
566  switch (shadows) {
567  case TWO_SHADOWS:
568  if ((valunder < -fabs(valcenter/SPEC_SHADOW_FACT)) &&
569  (valover < -fabs(valcenter/SPEC_SHADOW_FACT)) &&
570  (valunder/valover > 0.5) &&
571  (valunder/valover < 2.0)) return 1 ;
572  break;
573 
574  case ONE_SHADOW:
575  if ((valunder < -fabs(valcenter/SPEC_SHADOW_FACT)) ||
576  (valover < -fabs(valcenter/SPEC_SHADOW_FACT))) return 1 ;
577  break;
578 
579  case NO_SHADOW:
580  return 1 ;
581 
582  default:
583  cpl_msg_error(cpl_func, "unknown spec_detect_mode") ;
584  break ;
585  }
586 
587  cpl_msg_debug(cpl_func, "No spectrum(%d): under=%g, center=%g, over=%g",
588  shadows, valunder, valcenter, valover);
589 
590  return 0 ;
591 }
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.