MUSE Pipeline Reference Manual  0.18.5
muse_qi_mask.c
1 /* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim:set sw=2 sts=2 et cin: */
3 /*
4  *
5  * This file is part of the MUSE Instrument Pipeline
6  * Copyright (C) 2005-2011 European Southern Observatory
7  *
8  * This program is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation; either version 2 of the License, or
11  * (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program; if not, write to the Free Software
20  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
21  */
22 
23 #ifdef HAVE_CONFIG_H
24 #include <config.h>
25 #endif
26 
27 /*---------------------------------------------------------------------------*
28  * Includes *
29  *---------------------------------------------------------------------------*/
30 #include <string.h>
31 
32 #include <muse.h>
33 #include "muse_qi_mask_z.h"
34 
35 /*---------------------------------------------------------------------------*
36  * Functions code *
37  *---------------------------------------------------------------------------*/
38 
39 /*----------------------------------------------------------------------------*/
49 /*----------------------------------------------------------------------------*/
50 static void
51 muse_qi_mask_wavecal_polys(cpl_propertylist *aHeader, cpl_table *aWC,
52  cpl_table *aTT)
53 {
54  if (!aHeader || !aWC || !aTT) {
55  cpl_error_set_message(__func__, CPL_ERROR_NULL_INPUT, "Could not create per"
56  "-slice wavelength polynomials in the FITS header!");
57  return;
58  }
59 
60  /* number of evaluation steps for the wavelength calibration solution */
61  const unsigned int kSteps = 50;
62 
63  /* create matrix and vector for the fitting process */
64  cpl_matrix *pos = cpl_matrix_new(1, kMuseSlicesPerCCD * kSteps);
65  cpl_vector *val = cpl_vector_new(kMuseSlicesPerCCD * kSteps),
66  *ycenters = cpl_vector_new(kMuseSlicesPerCCD);
67  cpl_size ipos = 0;
68  unsigned short nslice;
69  for (nslice = 1; nslice <= kMuseSlicesPerCCD; nslice++) {
70  cpl_polynomial *pw = muse_wave_table_get_poly_for_slice(aWC, nslice),
71  **pt = muse_trace_table_get_polys_for_slice(aTT, nslice);
72  /* evaluate central trace at vertical center of the CCD */
73  double x = cpl_polynomial_eval_1d(pt[MUSE_TRACE_CENTER], kMuseOutputYTop/2,
74  NULL);
76 
77  cpl_polynomial *pconst = cpl_polynomial_new(1);
78  cpl_size pows = 0;
79  cpl_polynomial_set_coeff(pconst, &pows, x);
80  /* reduce the polynomial to 1D at the center of the slice */
81  cpl_polynomial *pwcen = cpl_polynomial_extract(pw, 0, pconst);
82  double ycen = cpl_polynomial_eval_1d(pwcen, kMuseOutputYTop/2, NULL);
83  cpl_vector_set(ycenters, nslice - 1, ycen);
84  cpl_polynomial_delete(pconst);
85  cpl_polynomial_delete(pw);
86 
87  /* shift the polynomial to a common center */
88  pows = 0;
89  double c0 = cpl_polynomial_get_coeff(pwcen, &pows);
90  cpl_polynomial_set_coeff(pwcen, &pows, c0 - ycen);
91 
92  unsigned int iy;
93  for (iy = 1;
94  iy <= (unsigned)kMuseOutputYTop;
95  iy += ((unsigned)kMuseOutputYTop) / (kSteps - 1)) {
96  cpl_matrix_set(pos, 0, ipos, iy);
97  cpl_vector_set(val, ipos++, cpl_polynomial_eval_1d(pwcen, iy, NULL));
98  }
99  } /* for nslice */
100 
101  /* now try to fit the common 2nd-order polynomial */
102  cpl_polynomial *fit = cpl_polynomial_new(1);
103  cpl_size maxdeg = 2;
104  cpl_polynomial_fit(fit, pos, CPL_FALSE, val, NULL, CPL_TRUE, NULL, &maxdeg);
105 #if 0 /* DEBUG */
106  cpl_vector *res = cpl_vector_duplicate(val);
107  double chisq;
108  cpl_vector_fill_polynomial_fit_residual(res, val, NULL, fit, pos, &chisq);
109  const double mse = cpl_vector_product(res, res)
110  / cpl_vector_get_size(res);
111  cpl_vector_delete(res);
112  cpl_msg_debug(__func__, "MSE=%g ChiSq=%g", mse, chisq);
113 #endif
114  cpl_vector_delete(val);
115  cpl_matrix_delete(pos);
116 
117  /* now finally add the relevant keywords to the header for this IFU */
118  char keyword[KEYWORD_LENGTH];
119  cpl_size j = 0; /* accessor to polynomial coefficients */
120  for (nslice = 1; nslice <= kMuseSlicesPerCCD; nslice++) {
121  snprintf(keyword, KEYWORD_LENGTH, "ESO DET WLEN SLICE%hu POLY0", nslice);
122  /* add the vertical shift back again */
123  double zeropoint = cpl_polynomial_get_coeff(fit, &j)
124  + cpl_vector_get(ycenters, nslice - 1);
125  cpl_propertylist_append_float(aHeader, keyword, zeropoint);
126  cpl_propertylist_set_comment(aHeader, keyword,
127  "[Angstrom] Slice-specific zero-point");
128  } /* for nslice */
129  cpl_vector_delete(ycenters);
130 
131  /* also add the common coefficients for 1st and 2nd order */
132  for (j = 1; j <= 2; j++) {
133  snprintf(keyword, KEYWORD_LENGTH, "ESO DET WLEN POLY%d", (int)j);
134  cpl_propertylist_append_float(aHeader, keyword,
135  cpl_polynomial_get_coeff(fit, &j));
136  char comment[100];
137  snprintf(comment, 99, "[Angstrom] Common %s-order polynomial coefficient",
138  j == 1 ? "1st" : "2nd");
139  cpl_propertylist_set_comment(aHeader, keyword, comment);
140  } /* for j (polynomial orders) */
141  cpl_polynomial_delete(fit);
142 } /* muse_qi_mask_wavecal_polys() */
143 
144 /*----------------------------------------------------------------------------*/
155 /*----------------------------------------------------------------------------*/
156 int
157 muse_qi_mask_compute(muse_processing *aProcessing,
158  muse_qi_mask_params_t *aParams)
159 {
160  /* check for wrange and/or wmin/wmax parameters */
161  double wmin = aParams->wmin,
162  wmax = aParams->wmax;
163  const char *wrange = aParams->wrange_s;
164  if (!strncmp(wrange, "MUSE", 5)) {
165  wmin = 4650.;
166  wmax = 9300.;
167  } else if (!strncmp(wrange, "B", 2)) {
168  wmin = 3510.;
169  wmax = 5390.;
170  } else if (!strncmp(wrange, "V", 2)) {
171  wmin = 4630.;
172  wmax = 6390.;
173  } else if (!strncmp(wrange, "R", 2)) {
174  wmin = 5200.;
175  wmax = 7960.;
176  } else if (!strncmp(wrange, "I", 2)) {
177  wmin = 6570.;
178  wmax = 9550.;
179  } else {
180  /* "custom" range, do nothing */
181  }
182 
183  cpl_msg_info(__func__, "Creating mask for wavelength range %s (%.2f ... %.2f)",
184  wrange, wmin, wmax);
185 
186  /* if we find a BIAS input, add it to usedFrames to get it *
187  * to be used for creation of the DFS-compliant header */
188  cpl_frame *frame = cpl_frameset_find(aProcessing->inputFrames,
189  MUSE_TAG_BIAS);
190  if (frame) {
191  muse_processing_append_used(aProcessing, frame, CPL_FRAME_GROUP_RAW, 1);
192  }
193 
194  /* create at least minimal header and save it */
195  cpl_propertylist *header = cpl_propertylist_new();
196  char *object = cpl_sprintf("Mask %s", wrange);
197  cpl_propertylist_append_string(header, "OBJECT", object);
198  cpl_propertylist_append_string(header, "ESO DET FRS WRANGE", wrange);
199  cpl_propertylist_set_comment(header, "ESO DET FRS WRANGE", "Wavelength range");
200  cpl_propertylist_append_float(header, "ESO DET FRS WMIN", wmin);
201  cpl_propertylist_set_comment(header, "ESO DET FRS WMIN", "[Angstrom] Minimum wavelength");
202  cpl_propertylist_append_float(header, "ESO DET FRS WMAX", wmax);
203  cpl_propertylist_set_comment(header, "ESO DET FRS WMAX", "[Angstrom] Maximum wavelength");
204  muse_processing_save_header(aProcessing, aParams->nifu ? aParams->nifu : -1,
205  header, MUSE_TAG_MASK_IMAGE);
206  cpl_free(object);
207  cpl_propertylist_delete(header);
208 
209  /* get output filename from output frameset */
210  frame = cpl_frameset_find(aProcessing->outputFrames, MUSE_TAG_MASK_IMAGE);
211  const char *fn = cpl_frame_get_filename(frame);
212 
213  /* loop over all 24 IFUs */
214  int n1 = aParams->nifu ? aParams->nifu : 1,
215  n2 = aParams->nifu ? aParams->nifu : kMuseNumIFUs,
216  nifu;
217  for (nifu = n1; nifu <= n2; nifu++) {
218  /* load raw (bias) image and trim it or create minimal default image */
219  cpl_errorstate prestate = cpl_errorstate_get();
220  muse_imagelist *images = muse_basicproc_load(aProcessing, nifu, NULL);
221  muse_image *image = NULL;
222  if (images) {
223  cpl_msg_debug(__func__, "succeeded to load %u raw images, using first one",
224  muse_imagelist_get_size(images));
225  image = muse_imagelist_get(images, 0);
226  } else {
227  cpl_msg_warning(__func__, "failed to load raw image, assuming 1x1 binning");
228  cpl_errorstate_dump(prestate, CPL_FALSE, muse_cplerrorstate_dump_some);
229  image = muse_image_new();
230  /* use typical 1x1 binned trimmed MUSE image size */
231  image->data = cpl_image_new(kMuseOutputXRight, kMuseOutputYTop,
232  CPL_TYPE_FLOAT);
233  image->header = cpl_propertylist_new();
234  cpl_propertylist_append_int(image->header, "ESO DET BINX", 1);
235  cpl_propertylist_append_int(image->header, "ESO DET BINY", 1);
236  images = muse_imagelist_new();
237  muse_imagelist_set(images, image, 0); /* to easily delete it below */
238  }
239 
240  /* load wavecaltable and tracetable */
241  cpl_table *wavecaltable = muse_table_load(aProcessing,
242  MUSE_TAG_WAVECAL_TABLE, nifu);
243  cpl_table *tracetable = muse_table_load(aProcessing,
244  MUSE_TAG_TRACE_TABLE, nifu);
245 
246  /* create the wavelength map, first always unbinned */
247  int binx = muse_pfits_get_binx(image->header),
248  biny = muse_pfits_get_biny(image->header);
249  muse_image *imunbinned = image;
250  if (binx != 1 || biny != 1) { /* need unbinned image for wavemep to work */
251  imunbinned = muse_image_new();
252  imunbinned->data = cpl_image_new(kMuseOutputXRight, kMuseOutputYTop,
253  CPL_TYPE_FLOAT);
254  }
255  cpl_image *wavemap = muse_wave_map(imunbinned, wavecaltable, tracetable);
256 
257  /* now take into account the binning */
258  if (binx != 1 || biny != 1) {
259  muse_image_delete(imunbinned);
260  cpl_msg_info(__func__, "Rebinning wavelength map %dx%d", binx, biny);
261  cpl_image *binned = cpl_image_rebin(wavemap, 1, 1, binx, biny);
262  cpl_image_delete(wavemap);
263  /* cpl_image_rebin() creates summed image, we need averaged */
264  wavemap = cpl_image_divide_scalar_create(binned, binx * biny);
265  cpl_image_delete(binned);
266  }
267 
268  /* apply the threshold in wavelengths */
269  cpl_mask *mask = cpl_mask_threshold_image_create(wavemap, wmin, wmax);
270 
271  /* create the output FITS header, for properties in this extension */
272  header = cpl_propertylist_new();
273  if (binx == 1 && biny == 1) {
274  muse_qi_mask_wavecal_polys(header, wavecaltable, tracetable);
275  }
276 
277  /* Analyze mask to determine slice positions and store them in the *
278  * header. Use the trimmed mask, so that cpl_apertures_get_size() *
279  * finds 48 connected regions instead of 96 with the overscan gap. *
280  * Also make up for the pre- and overscans by adding n*32/bin. */
281  int nx = cpl_mask_get_size_x(mask),
282  ny = cpl_mask_get_size_y(mask);
283  cpl_size ndet;
284  cpl_image *labeled = cpl_image_labelise_mask_create(mask, &ndet);
285  /* it is simplest to use apertures to get the coordinates that we want */
286  cpl_apertures *apertures = cpl_apertures_new_from_image(labeled, labeled);
287  int napertures = cpl_apertures_get_size(apertures);
288  /* the apertures are not sorted by x position and we cannot *
289  * sort apertures externally to CPL, so use an x-sorted object *
290  * to access slice numbers directly */
291  cpl_matrix *mlabels = cpl_matrix_new(2, napertures);
292  int n; /* aperture number = label number */
293  for (n = 1; n <= napertures; n++) {
294  cpl_matrix_set(mlabels, 0, n - 1,
295  cpl_apertures_get_left(apertures, n)); /* x-position in row. 0 */
296  cpl_matrix_set(mlabels, 1, n - 1, n); /* aperture number in row. 1 */
297  } /* for n (all apertures) */
298  cpl_matrix_sort_columns(mlabels, 1); /* sort by x-position value */
299 #if 0
300  cpl_matrix_dump(mlabels, stdout);
301 #endif
302  int m, /* index entry in matrix */
303  nslice = 1; /* real slice number */
304  for (m = 1; m <= ndet; m++) {
305  n = cpl_matrix_get(mlabels, 1, m - 1); /* get aperture number from sorted matrix */
306  /* aperture extremes, with prescan added */
307  int x1 = cpl_apertures_get_left(apertures, n),
308  x2 = cpl_apertures_get_right(apertures, n),
309  y1 = cpl_apertures_get_bottom(apertures, n),
310  y2 = cpl_apertures_get_top(apertures, n);
311  if (x1 == x2 || y1 == y2) {
312  /* need to exclude single-pixel apertures, created by rebinning; *
313  * also replace them with zeros in the mask */
314  int i;
315  for (i = x1; i <= x2; i++) {
316  int j;
317  for (j = y1; j <= y2; j++) {
318  cpl_mask_set(mask, i, j, CPL_BINARY_0);
319  } /* for j (all y pixels of detection) */
320  } /* for i (all x pixels of detection) */
321  cpl_msg_debug(__func__, "Excluding aperture [%d:%d,%d:%d]", x1, x2, y1, y2);
322  continue;
323  }
324  /* add prescan to aperture extremes */
325  x1 += 32 / binx;
326  x2 += 32 / binx;
327  y1 += 32 / biny;
328  y2 += 32 / biny;
329  /* add overscan gaps */
330  if (x1 > nx/2+32/binx) x1 += 64 / binx;
331  if (x2 > nx/2+32/binx) x2 += 64 / binx;
332  if (y1 > ny/2+32/biny) y1 += 64 / biny;
333  if (y2 > ny/2+32/biny) y2 += 64 / biny;
334 #if 0
335  int xc = (x1 + x2) / 2,
336  yc = (y1 + y2) / 2,
337  w = x2 - x1 + 1,
338  h = y2 - y1 + 1;
339  cpl_msg_debug(__func__, "Aperture %2d Slice %2d: [%d:%d,%d:%d] center %d,%d size %dx%d",
340  n, m, x1, x2, y1, y2, xc, yc, w, h);
341 #endif
342  char keyword[KEYWORD_LENGTH];
343  snprintf(keyword, KEYWORD_LENGTH, "ESO DET SLICE%d XSTART", nslice);
344  cpl_propertylist_append_int(header, keyword, x1);
345  cpl_propertylist_set_comment(header, keyword, "[pix] Start position of the slice along X");
346  snprintf(keyword, KEYWORD_LENGTH, "ESO DET SLICE%d YSTART", nslice);
347  cpl_propertylist_append_int(header, keyword, y1);
348  cpl_propertylist_set_comment(header, keyword, "[pix] Start position of the slice along Y");
349  snprintf(keyword, KEYWORD_LENGTH, "ESO DET SLICE%d XEND", nslice);
350  cpl_propertylist_append_int(header, keyword, x2);
351  cpl_propertylist_set_comment(header, keyword, "[pix] End position of the slice along X");
352  snprintf(keyword, KEYWORD_LENGTH, "ESO DET SLICE%d YEND", nslice);
353  cpl_propertylist_append_int(header, keyword, y2);
354  cpl_propertylist_set_comment(header, keyword, "[pix] End position of the slice along Y");
355  nslice++;
356  } /* for m (all matrix entries) */
357  cpl_image_delete(labeled);
358  cpl_apertures_delete(apertures);
359  cpl_matrix_delete(mlabels);
360  if (nslice - 1 != kMuseSlicesPerCCD) {
361  cpl_msg_warning(__func__, "Found %d slices (%d apertures) instead of %d",
362  nslice - 1, napertures, kMuseSlicesPerCCD);
363  }
364 
365  /* re-add pre- and overscans, assuming 32/bin pixels on all sides */
366  cpl_mask *untrimmed = cpl_mask_new(nx + 4*32/binx, ny + 4*32/biny);
367  cpl_mask *m1 = cpl_mask_extract(mask, 1, 1, nx/2-1, ny/2-1),
368  *m2 = cpl_mask_extract(mask, 1, ny/2, nx/2-1, ny),
369  *m3 = cpl_mask_extract(mask, nx/2, 1, nx, ny/2-1),
370  *m4 = cpl_mask_extract(mask, nx/2, ny/2, nx, ny);
371  cpl_mask_copy(untrimmed, m1, 32/binx+1, 32/biny+1);
372  cpl_mask_copy(untrimmed, m2, 32/binx+1, ny/2+3*32/biny);
373  cpl_mask_copy(untrimmed, m3, nx/2+3*32/binx, 32/biny+1);
374  cpl_mask_copy(untrimmed, m4, nx/2+3*32/binx, ny/2+3*32/biny);
375  cpl_mask_delete(m1);
376  cpl_mask_delete(m2);
377  cpl_mask_delete(m3);
378  cpl_mask_delete(m4);
379  cpl_mask_delete(mask);
380  mask = untrimmed;
381 
382  /* save the image to extension CHAN%02d, as 8bit mask image */
383  if (image->header) {
384  /* copy relevant detector properties from input image */
385  cpl_propertylist_copy_property_regexp(header, image->header,
386  "ESO DET (CHIP |OUT)|EXTNAME", 0);
387  } else {
388  /* create minimal header with only EXTNAME */
389  char *extname = cpl_sprintf("CHAN%02d", nifu);
390  cpl_propertylist_append_string(header, "EXTNAME", extname);
391  cpl_free(extname);
392  }
393  cpl_mask_save(mask, fn, header, CPL_IO_EXTEND);
394  cpl_propertylist_delete(header);
395  cpl_image_delete(wavemap);
396  cpl_mask_delete(mask);
397  cpl_table_delete(tracetable);
398  cpl_table_delete(wavecaltable);
399  muse_imagelist_delete(images);
400  } /* for nifu */
401 
402  return 0;
403 } /* muse_qi_mask_compute() */
muse_imagelist * muse_basicproc_load(muse_processing *aProcessing, unsigned char aIFU, muse_basicproc_params *aBPars)
Load the raw input files from disk and do basic processing.
cpl_polynomial ** muse_trace_table_get_polys_for_slice(const cpl_table *aTable, const unsigned short aSlice)
construct polynomial from the trace table entry for the given slice
Structure definition for a collection of muse_images.
void muse_image_delete(muse_image *aImage)
Deallocate memory associated to a muse_image object.
Definition: muse_image.c:84
cpl_polynomial * muse_wave_table_get_poly_for_slice(const cpl_table *aTable, const unsigned short aSlice)
Construct polynomial from the wavelength calibration table entry for the given slice.
cpl_image * data
the data extension
Definition: muse_image.h:47
void muse_imagelist_delete(muse_imagelist *aList)
Free the memory of the MUSE image list.
Structure definition of MUSE three extension FITS file.
Definition: muse_image.h:41
cpl_propertylist * header
the FITS header
Definition: muse_image.h:73
unsigned int muse_imagelist_get_size(muse_imagelist *aList)
Return the number of stored images.
void muse_trace_polys_delete(cpl_polynomial *aPolys[])
Delete the multi-polynomial array created in relation to tracing.
cpl_error_code muse_processing_save_header(muse_processing *aProcessing, int aIFU, cpl_propertylist *aHeader, const char *aTag)
Save a FITS header to disk.
int muse_pfits_get_biny(const cpl_propertylist *aHeaders)
find out the binning factor in y direction
Definition: muse_pfits.c:350
double wmax
Lower boundary of the wavelength range [Angstrom]. Only used when wrange=custom.
muse_image * muse_imagelist_get(muse_imagelist *aList, unsigned int aIdx)
Get the muse_image of given list index.
cpl_frameset * outputFrames
void muse_cplerrorstate_dump_some(unsigned aCurrent, unsigned aFirst, unsigned aLast)
Dump some CPL errors.
cpl_image * muse_wave_map(muse_image *aImage, const cpl_table *aWave, const cpl_table *aTrace)
Write out a wavelength map for visual checks.
const char * wrange_s
Wavelength range (filter name) of the setup. Set this to "custom" to set up a new wavelength range us...
void muse_processing_append_used(muse_processing *aProcessing, cpl_frame *aFrame, cpl_frame_group aGroup, int aDuplicate)
Add a frame to the set of used frames.
double wmin
Lower boundary of the wavelength range [Angstrom]. Only used when wrange=custom.
Structure to hold the parameters of the muse_qi_mask recipe.
int muse_pfits_get_binx(const cpl_propertylist *aHeaders)
find out the binning factor in x direction
Definition: muse_pfits.c:332
muse_imagelist * muse_imagelist_new(void)
Create a new (empty) MUSE image list.
cpl_table * muse_table_load(muse_processing *aProcessing, const char *aTag, unsigned char aIFU)
load a table according to its tag and IFU/channel number
Definition: muse_utils.c:452
muse_image * muse_image_new(void)
Allocate memory for a new muse_image object.
Definition: muse_image.c:65
cpl_frameset * inputFrames
cpl_error_code muse_imagelist_set(muse_imagelist *aList, muse_image *aImage, unsigned int aIdx)
Set the muse_image of given list index.
int nifu
IFU to handle. If set to 0, all IFUs are processed serially, which is the recommendation for this rec...