MUSE Pipeline Reference Manual  2.1.1
muse_resampling.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  * This file is part of the MUSE Instrument Pipeline
5  * Copyright (C) 2005-2017 European Southern Observatory
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20  */
21 
22 #ifdef HAVE_CONFIG_H
23 #include <config.h>
24 #endif
25 
26 /*----------------------------------------------------------------------------*
27  * Includes *
28  *----------------------------------------------------------------------------*/
29 #include <cpl.h>
30 #include <math.h>
31 #include <string.h>
32 #ifndef _OPENMP
33 #define omp_get_max_threads() 1
34 #else
35 #include <omp.h>
36 #endif
37 
38 #include "muse_resampling.h"
39 #include "muse_instrument.h"
40 
41 #include "muse_astro.h"
42 #include "muse_cplwrappers.h"
43 #include "muse_dar.h"
44 #include "muse_dfs.h"
45 #include "muse_flux.h"
46 #include "muse_pfits.h"
47 #include "muse_pixgrid.h"
48 #include "muse_pixtable.h"
49 #include "muse_quadrants.h"
50 #include "muse_quality.h"
51 #include "muse_utils.h"
52 #include "muse_wcs.h"
53 #include "muse_data_format_z.h"
54 
55 /*----------------------------------------------------------------------------*/
59 /*----------------------------------------------------------------------------*/
60 
63 /*----------------------------------------------------------------------------*
64  * Resampling, collapsing, and saving in 3D *
65  *----------------------------------------------------------------------------*/
66 
67 /*---------------------------------------------------------------------------*/
80 /*---------------------------------------------------------------------------*/
83 {
84  cpl_ensure(aMethod <= MUSE_RESAMPLE_NONE, CPL_ERROR_ILLEGAL_INPUT, NULL);
85  muse_resampling_params *params = cpl_calloc(1, sizeof(muse_resampling_params));
86  params->method = aMethod;
87  /* leave crtype zero == MUSE_RESAMPLING_CRSTATS_IRAF by default */
88  /* leave crsigma zero, to not do CR rejection by default */
89  params->ld = 1;
90  params->pfx = 0.6;
91  params->pfy = 0.6;
92  params->pfl = 0.6;
93  params->rc = 1.25;
94  /* dx, dy, and dlambda, and wcs stay zero to trigger defaults */
95  /* leave tlambda zero == MUSE_RESAMPLING_DISP_AWAV by default */
96  return params;
97 } /* muse_resampling_params_new() */
98 
99 /*---------------------------------------------------------------------------*/
116 /*---------------------------------------------------------------------------*/
117 cpl_error_code
119  const char *aString)
120 {
121  cpl_ensure_code(aParams && aString, CPL_ERROR_NULL_INPUT);
122  cpl_array *pf = muse_cplarray_new_from_delimited_string(aString, ",");
123  int npf = cpl_array_get_size(pf);
124  cpl_error_code rc = CPL_ERROR_NONE;
125  switch (npf) {
126  case 1:
127  aParams->pfx = aParams->pfy = aParams->pfl = atof(cpl_array_get_string(pf, 0));
128  break;
129  case 2:
130  aParams->pfx = aParams->pfy = atof(cpl_array_get_string(pf, 0));
131  aParams->pfl = atof(cpl_array_get_string(pf, 1));
132  break;
133  case 3:
134  aParams->pfx = atof(cpl_array_get_string(pf, 0));
135  aParams->pfy = atof(cpl_array_get_string(pf, 1));
136  aParams->pfl = atof(cpl_array_get_string(pf, 2));
137  break;
138  default:
139  cpl_msg_warning(__func__, "%d instead of 1-3 values (\"%s\") were given as "
140  "pixfrac, values remain unchanged (%.2f, %.2f, %.2f)!",
141  npf, aString, aParams->pfx, aParams->pfy, aParams->pfl);
142  rc = CPL_ERROR_ILLEGAL_INPUT;
143  } /* switch */
144  cpl_array_delete(pf);
145  return rc;
146 } /* muse_resampling_params_set_pixfrac() */
147 
148 /*---------------------------------------------------------------------------*/
167 /*---------------------------------------------------------------------------*/
168 cpl_error_code
170  const cpl_propertylist *aWCS)
171 {
172  cpl_ensure_code(aParams, CPL_ERROR_NULL_INPUT);
173  if (!aWCS) {
175  cpl_wcs_delete(aParams->wcs);
176  aParams->wcs = NULL;
177  return CPL_ERROR_NONE;
178  }
179  /* first make sure it wasn't set to rubbish elsewhere */
181  if (cpl_propertylist_has(aWCS, "CTYPE3")) {
182  if (!strncmp(muse_pfits_get_ctype(aWCS, 3), "AWAV-LOG", 9)) {
184  } else if (!strncmp(muse_pfits_get_ctype(aWCS, 3), "WAVE", 5)) {
186  } else if (!strncmp(muse_pfits_get_ctype(aWCS, 3), "WAVE-LOG", 9)) {
188  } else {
189  /* MUSE_RESAMPLING_DISP_AWAV, do nothing */
190  }
191  } /* if CTYPE3 given */
192  cpl_errorstate state = cpl_errorstate_get();
193  cpl_error_code rc = CPL_ERROR_NONE;
194  aParams->wcs = cpl_wcs_new_from_propertylist(aWCS);
195  if (!cpl_errorstate_is_equal(state)) {
196  cpl_wcs_delete(aParams->wcs);
197  aParams->wcs = NULL;
198  rc = cpl_error_get_code();
199  }
200 #if 0
201  printf("CDi_j matrix:\n");
202  cpl_matrix_dump(cpl_wcs_get_cd(aParams->wcs), stdout);
203  fflush(stdout);
204 #endif
205  return rc;
206 } /* muse_resampling_params_set_wcs() */
207 
208 /*---------------------------------------------------------------------------*/
216 /*---------------------------------------------------------------------------*/
217 void
219 {
220  if (!aParams) {
221  return;
222  }
223  cpl_wcs_delete(aParams->wcs);
224  cpl_free(aParams);
225 } /* muse_resampling_params_delete() */
226 
227 /*---------------------------------------------------------------------------*/
238 /*---------------------------------------------------------------------------*/
239 static inline double
240 muse_resampling_weight_function_linear(double r)
241 {
242  return r == 0 ? FLT_MAX : 1. / r;
243 }
244 
245 /*---------------------------------------------------------------------------*/
256 /*---------------------------------------------------------------------------*/
257 static inline double
258 muse_resampling_weight_function_quadratic(double r2)
259 {
260  return r2 == 0 ? FLT_MAX : 1. / r2;
261 }
262 
263 /*---------------------------------------------------------------------------*/
276 /*---------------------------------------------------------------------------*/
277 static inline double
278 muse_resampling_weight_function_renka(double r, double r_c)
279 {
280  if (r == 0) {
281  return FLT_MAX;
282  } else if (r >= r_c) {
283  return DBL_MIN;
284  } else {
285  double p = (r_c - r) / (r_c * r);
286  return p*p;
287  }
288 }
289 
290 /*---------------------------------------------------------------------------*/
297 /*---------------------------------------------------------------------------*/
298 static inline double
299 muse_resampling_weight_function_sinc(double r)
300 {
301  return fabs(r) < DBL_EPSILON ? 1. : sin(CPL_MATH_PI * r) / (CPL_MATH_PI * r);
302 }
303 
304 /*---------------------------------------------------------------------------*/
314 /*---------------------------------------------------------------------------*/
315 static inline double
316 muse_resampling_weight_function_lanczos(double dx, double dy, double dz, unsigned int n)
317 {
318  return (fabs(dx) >= n || fabs(dy) >= n || fabs(dz) > n) ? 0.
319  : muse_resampling_weight_function_sinc(dx) * muse_resampling_weight_function_sinc(dx / n)
320  * muse_resampling_weight_function_sinc(dy) * muse_resampling_weight_function_sinc(dy / n)
321  * muse_resampling_weight_function_sinc(dz) * muse_resampling_weight_function_sinc(dz / n);
322 }
323 
324 /*---------------------------------------------------------------------------*/
343 /*---------------------------------------------------------------------------*/
344 static inline double
345 muse_resampling_weight_function_drizzle(double xin, double yin, double zin,
346  double xout, double yout, double zout,
347  double dx, double dy, double dz)
348 {
349  /* compute the three terms in the numerator: if the offset *
350  * plus the output halfsize is less than the input halfsize, *
351  * then that side is fully contained in the input pixel */
352  double x = (dx + xout / 2.) <= xin / 2. ? xout : (xin + xout) / 2. - dx,
353  y = (dy + yout / 2.) <= yin / 2. ? yout : (yin + yout) / 2. - dy,
354  z = (dz + zout / 2.) <= zin / 2. ? zout : (zin + zout) / 2. - dz;
355  /* any negative value means that the input pixel is completely *
356  * outside the target voxel, so it doesn't contribute */
357  if (x <= 0 || y <= 0 || z <= 0) {
358  return 0.;
359  }
360  /* any value > input size means this dimension of the input pixel *
361  * is completely inside the target voxel, so that's the limit! */
362  return (x > xin ? xin : x) * (y > yin ? yin : y) * (z > zin ? zin : z)
363  / (xin * yin * zin);
364 } /* muse_resampling_weight_function_drizzle() */
365 
366 /*---------------------------------------------------------------------------*/
382 /*---------------------------------------------------------------------------*/
383 static cpl_error_code
384 muse_resampling_cube_nearest(muse_datacube *aCube, muse_pixtable *aPixtable,
385  muse_pixgrid *aGrid)
386 {
387  cpl_ensure_code(aCube && aPixtable && aGrid, CPL_ERROR_NULL_INPUT);
388  double ptxoff = 0., /* zero by default ... */
389  ptyoff = 0., /* for pixel coordinates */
390  crval3 = muse_pfits_get_crval(aCube->header, 3),
391  crpix3 = muse_pfits_get_crpix(aCube->header, 3),
392  cd33 = muse_pfits_get_cd(aCube->header, 3, 3);
393  const char *ctype3 = muse_pfits_get_ctype(aCube->header, 3);
394  muse_wcs *wcs = muse_wcs_new(aCube->header);
396  cpl_boolean loglambda = ctype3 && (!strncmp(ctype3, "AWAV-LOG", 9) ||
397  !strncmp(ctype3, "WAVE-LOG", 9));
398  float *xpos = cpl_table_get_data_float(aPixtable->table, MUSE_PIXTABLE_XPOS),
399  *ypos = cpl_table_get_data_float(aPixtable->table, MUSE_PIXTABLE_YPOS),
400  *lbda = cpl_table_get_data_float(aPixtable->table, MUSE_PIXTABLE_LAMBDA),
401  *data = cpl_table_get_data_float(aPixtable->table, MUSE_PIXTABLE_DATA),
402  *stat = cpl_table_get_data_float(aPixtable->table, MUSE_PIXTABLE_STAT);
403  int *dq = cpl_table_get_data_int(aPixtable->table, MUSE_PIXTABLE_DQ);
404 
405  /* If our data was astrometrically calibrated, we need to scale the *
406  * data units to the pixel size in all three dimensions so that the *
407  * radius computation works again. *
408  * Otherwise dx~5.6e-5deg won't contribute to the weighting at all. */
409  double xnorm = 1., ynorm = 1., znorm = 1. / kMuseSpectralSamplingA;
410  if (wcs->iscelsph) {
411  muse_wcs_get_scales(aPixtable->header, &xnorm, &ynorm);
412  xnorm = 1. / xnorm;
413  ynorm = 1. / ynorm;
414  /* need to use the real coordinate offset for celestial spherical */
415  ptxoff = muse_pfits_get_crval(aPixtable->header, 1);
416  ptyoff = muse_pfits_get_crval(aPixtable->header, 2);
417  }
418 
419 #ifdef ESO_ENABLE_DEBUG
420  int debug = 0;
421  if (getenv("MUSE_DEBUG_NEAREST")) {
422  debug = atoi(getenv("MUSE_DEBUG_NEAREST"));
423  }
424 #endif
425 
426  int l;
427 #ifdef ESO_ENABLE_DEBUG
428  #pragma omp parallel for default(none) /* as req. by Ralf */ \
429  shared(aCube, aGrid, cd33, crpix3, crval3, data, debug, dq, lbda, \
430  loglambda, ptxoff, ptyoff, stat, stdout, wcs, xnorm, xpos, \
431  ynorm, ypos, znorm)
432 #else
433  #pragma omp parallel for default(none) /* as req. by Ralf */ \
434  shared(aCube, aGrid, cd33, crpix3, crval3, data, dq, lbda, \
435  loglambda, ptxoff, ptyoff, stat, stdout, wcs, xnorm, xpos, \
436  ynorm, ypos, znorm)
437 #endif
438  for (l = 0; l < aGrid->nz; l++) {
439  float *pdata = cpl_image_get_data_float(cpl_imagelist_get(aCube->data, l)),
440  *pstat = cpl_image_get_data_float(cpl_imagelist_get(aCube->stat, l));
441  int *pdq = cpl_image_get_data_int(cpl_imagelist_get(aCube->dq, l));
442  /* wavelength of center of current grid cell (l is index starting at 0) */
443  double lambda = (l + 1. - crpix3) * cd33 + crval3;
444  if (loglambda) {
445  lambda = crval3 * exp((l + 1. - crpix3) * cd33 / crval3);
446  }
447 
448  int i;
449  for (i = 0; i < aGrid->nx; i++) {
450  int j;
451  for (j = 0; j < aGrid->ny; j++) {
452  cpl_size idx = muse_pixgrid_get_index(aGrid, i, j, l, CPL_FALSE),
453  n_rows = muse_pixgrid_get_count(aGrid, idx);
454  const cpl_size *rows = muse_pixgrid_get_rows(aGrid, idx);
455 
456  /* x and y position of center of current grid cell (i, j start at 0) */
457  double x, y;
458  if (wcs->iscelsph) {
459  muse_wcs_celestial_from_pixel_fast(wcs, i + 1, j + 1, &x, &y);
460  } else {
461  muse_wcs_projplane_from_pixel_fast(wcs, i + 1, j + 1, &x, &y);
462  }
463 
464  if (n_rows == 1) {
465  /* if there is only one pixel in the cell, just use it */
466  pdata[i + j * aGrid->nx] = data[rows[0]];
467  pstat[i + j * aGrid->nx] = stat[rows[0]];
468  pdq[i + j * aGrid->nx] = dq[rows[0]];
469 
470 #ifdef ESO_ENABLE_DEBUG
471  if (debug) {
472  printf("only: %f,%f\n", data[rows[0]], stat[rows[0]]);
473  fflush(stdout);
474  }
475 #endif
476  } else if (n_rows >= 2) {
477  /* loop through all available values and take the closest one */
478  cpl_size n, nbest = -1;
479  double dbest = FLT_MAX; /* some unlikely large value to start with*/
480  for (n = 0; n < n_rows; n++) {
481  /* the differences for the cell center and the current pixel */
482  double dx = fabs(x - xpos[rows[n]] + ptxoff) * xnorm,
483  dy = fabs(y - ypos[rows[n]] + ptyoff) * ynorm,
484  dlambda = fabs(lambda - lbda[rows[n]]) * znorm,
485  dthis = sqrt(dx*dx + dy*dy + dlambda*dlambda);
486  if (wcs->iscelsph) {
487  /* Not strictly necessary for NN, but still scale the RA *
488  * distance properly, see muse_resampling_cube_weighted(). */
489  dx *= cos(y * CPL_MATH_RAD_DEG);
490  }
491 #ifdef ESO_ENABLE_DEBUG
492  if (debug) {
493  printf("%f %f %f, %f %f %f, d: %f %f %f -> %f best: %f (%f,%f)\n",
494  x, y, lambda, xpos[rows[n]] + ptxoff, ypos[rows[n]] + ptyoff,
495  lbda[rows[n]], dx, dy, dlambda, dthis, dbest, data[rows[n]],
496  stat[rows[n]]);
497  }
498 #endif
499  if (dthis < dbest) {
500  nbest = n;
501  dbest = dthis;
502  }
503  }
504  pdata[i + j * aGrid->nx] = data[rows[nbest]];
505  pstat[i + j * aGrid->nx] = stat[rows[nbest]];
506  pdq[i + j * aGrid->nx] = dq[rows[nbest]];
507 #ifdef ESO_ENABLE_DEBUG
508  if (debug) {
509  printf("closest: %f,%f\n", data[rows[nbest]], stat[rows[nbest]]);
510  fflush(stdout);
511  }
512 #endif
513  } else {
514  /* npix == 0: do nothing, pixel stays zero */
515  pdq[i + j * aGrid->nx] = EURO3D_MISSDATA;
516  }
517  } /* for j (y direction) */
518  } /* for i (x direction) */
519  } /* for l (wavelength planes) */
520  cpl_free(wcs);
521 
522  return CPL_ERROR_NONE;
523 } /* muse_resampling_cube_nearest() */
524 
525 /*---------------------------------------------------------------------------*/
546 /*---------------------------------------------------------------------------*/
547 static cpl_error_code
548 muse_resampling_cube_weighted(muse_datacube *aCube, muse_pixtable *aPixtable,
549  muse_pixgrid *aGrid,
550  muse_resampling_params *aParams)
551 {
552  cpl_ensure_code(aCube && aPixtable && aGrid && aParams,
553  CPL_ERROR_NULL_INPUT);
554  double ptxoff = 0., /* zero by default ... */
555  ptyoff = 0., /* for pixel coordinates */
556  crval3 = muse_pfits_get_crval(aCube->header, 3),
557  crpix3 = muse_pfits_get_crpix(aCube->header, 3),
558  cd33 = muse_pfits_get_cd(aCube->header, 3, 3);
559  const char *ctype3 = muse_pfits_get_ctype(aCube->header, 3);
560  muse_wcs *wcs = muse_wcs_new(aCube->header);
562  cpl_boolean loglambda = ctype3 && (!strncmp(ctype3, "AWAV-LOG", 9) ||
563  !strncmp(ctype3, "WAVE-LOG", 9));
564  cpl_errorstate prestate = cpl_errorstate_get();
565  float *xpos = cpl_table_get_data_float(aPixtable->table, MUSE_PIXTABLE_XPOS),
566  *ypos = cpl_table_get_data_float(aPixtable->table, MUSE_PIXTABLE_YPOS),
567  *lbda = cpl_table_get_data_float(aPixtable->table, MUSE_PIXTABLE_LAMBDA),
568  *data = cpl_table_get_data_float(aPixtable->table, MUSE_PIXTABLE_DATA),
569  *stat = cpl_table_get_data_float(aPixtable->table, MUSE_PIXTABLE_STAT),
570  *wght = cpl_table_get_data_float(aPixtable->table, MUSE_PIXTABLE_WEIGHT);
571  if (!cpl_errorstate_is_equal(prestate)) {
572  cpl_errorstate_set(prestate); /* recover from missing weight column */
573  }
574  int *dq = cpl_table_get_data_int(aPixtable->table, MUSE_PIXTABLE_DQ),
575  *orgn = cpl_table_get_data_int(aPixtable->table, MUSE_PIXTABLE_ORIGIN);
576 
577  /* If our data was astrometrically calibrated, we need to scale the *
578  * data units to the pixel size in all three dimensions so that the *
579  * radius computation works again. *
580  * Otherwise dx~5.6e-5deg won't contribute to the weighting at all. */
581  double xnorm = 1., ynorm = 1., znorm = 1. / kMuseSpectralSamplingA;
582  if (wcs->iscelsph) {
583  muse_wcs_get_scales(aPixtable->header, &xnorm, &ynorm);
584  xnorm = 1. / xnorm;
585  ynorm = 1. / ynorm;
586  /* need to use the real coordinate offset for celestial spherical */
587  ptxoff = muse_pfits_get_crval(aPixtable->header, 1);
588  ptyoff = muse_pfits_get_crval(aPixtable->header, 2);
589  }
590  /* precomputed factor for output voxel size calculation in *
591  * wavelength direction, only needed for log-lambda axis */
592  double zoutefac = exp(1.5 * cd33 / crval3) - exp(0.5 * cd33 / crval3);
593  /* scale the input critical radius by the voxel radius */
594  double renka_rc = aParams->rc /* XXX beware of rotation! */
595  * sqrt((wcs->cd11*xnorm)*(wcs->cd11*xnorm) + (wcs->cd22*ynorm)*(wcs->cd22*ynorm)
596  + (cd33*znorm)*(cd33*znorm));
597  /* loop distance (to take into account surrounding pixels) verification */
598  int ld = aParams->ld;
599  if (ld <= 0) {
600  ld = 1;
601  cpl_msg_info(__func__, "Overriding loop distance ld=%d", ld);
602  }
603 
604  /* pixel sizes in all three directions, scaled by pixfrac, and *
605  * output pixel sizes (absolute values), as needed for drizzle */
606  double xsz = aParams->pfx / xnorm,
607  ysz = aParams->pfy / ynorm,
608  zsz = aParams->pfl / znorm,
609  xout = fabs(wcs->cd11), yout = fabs(wcs->cd22), zout = fabs(cd33);
610 
611  /* set up masking cube to create a bad pixel table */
612  char *envmaskcube = getenv("MUSE_BADPIX_FROM_MASKCUBE"),
613  *badpix = NULL,
614  *maskname = NULL;
615  cpl_table *bpt = NULL;
616  cpl_imagelist *maskcube = NULL;
617  if (envmaskcube) {
618  /* try to get both filenames from the variable */
619  cpl_array *files = muse_cplarray_new_from_delimited_string(envmaskcube, ",");
620  int nfiles = cpl_array_get_size(files);
621  if (nfiles >= 2) {
622  maskname = cpl_strdup(cpl_array_get_string(files, 0));
623  badpix = cpl_strdup(cpl_array_get_string(files, 1));
624  } else if (nfiles == 1) {
625  maskname = cpl_strdup(cpl_array_get_string(files, 0));
626  badpix = cpl_sprintf("BADPIX_FROM_MASKCUBE.fits");
627  cpl_msg_warning(__func__, "MUSE_BADPIX_FROM_MASKCUBE with only one "
628  "argument, using \"%s\" forthe bad pixel table", badpix);
629  } else {
630  cpl_msg_warning(__func__, "MUSE_BADPIX_FROM_MASKCUBE without arguments, "
631  "not creating a mask table!");
632  }
633  if (maskname) {
634  maskcube = cpl_imagelist_load(maskname, CPL_TYPE_INT, 0);
635  }
636  /* check against pixel grid size */
637  if (maskcube) {
638  int xsize = cpl_image_get_size_x(cpl_imagelist_get(maskcube, 0)),
639  ysize = cpl_image_get_size_y(cpl_imagelist_get(maskcube, 0)),
640  zsize = cpl_imagelist_get_size(maskcube);
641  if (zsize != aGrid->nz) {
642  cpl_msg_warning(__func__, "Invalid z size of mask cube (%d vs %d)",
643  zsize, (int)aGrid->nz);
644  cpl_imagelist_delete(maskcube);
645  maskcube = NULL;
646  } else if (xsize != aGrid->nx) {
647  cpl_msg_warning(__func__, "Invalid x size of mask cube (%d vs %d)",
648  xsize, (int)aGrid->nx);
649  cpl_imagelist_delete(maskcube);
650  maskcube = NULL;
651  } else if (ysize != aGrid->ny) {
652  cpl_msg_warning(__func__, "Invalid y size of mask cube (%d vs %d)",
653  ysize, (int)aGrid->ny);
654  cpl_imagelist_delete(maskcube);
655  maskcube = NULL;
656  } else {
657  cpl_msg_info(__func__, "Mask cube with valid size found (%d x %d x %d)",
658  xsize, ysize, zsize);
659  }
660  } /* if maskcube */
661 
662  /* if the cube is still there, it should be valid, so *
663  * create output bad pixel table */
664  if (maskcube) {
666  /* add a few temporary columns to the standard bad pixel table */
667  cpl_table_new_column(bpt, "pweight", CPL_TYPE_DOUBLE);
668  cpl_table_new_column(bpt, "ifu", CPL_TYPE_INT);
669  } else {
670  cpl_msg_warning(__func__, "No mask cube found, not creating output bad "
671  "pixel table!");
672  }
673  cpl_array_delete(files);
674  } /* if envmaskcube */
675 
676 #ifdef ESO_ENABLE_DEBUG
677  int debug = 0, debugx = 0, debugy = 0, debugz = 0;
678  if (getenv("MUSE_DEBUG_WEIGHTED")) {
679  debug = atoi(getenv("MUSE_DEBUG_WEIGHTED"));
680  }
681  if (debug & 2) { /* need coordinates */
682  if (getenv("MUSE_DEBUG_WEIGHTED_X")) {
683  debugx = atoi(getenv("MUSE_DEBUG_WEIGHTED_X"));
684  if (debugx < 1 || debugx > aGrid->nx) {
685  debugx = 0;
686  }
687  }
688  if (getenv("MUSE_DEBUG_WEIGHTED_Y")) {
689  debugy = atoi(getenv("MUSE_DEBUG_WEIGHTED_Y"));
690  if (debugy < 1 || debugy > aGrid->ny) {
691  debugy = 0;
692  }
693  }
694  if (getenv("MUSE_DEBUG_WEIGHTED_Z")) {
695  debugz = atoi(getenv("MUSE_DEBUG_WEIGHTED_Z"));
696  if (debugz < 1 || debugz > aGrid->nz) {
697  debugz = 0;
698  }
699  }
700  }
701  if (debug & 8) {
702  printf("parameters:\n cd=%e %e %e\n"
703  " corrected crpix3=%e\n norm=%e %e %e\n",
704  wcs->cd11, wcs->cd22, cd33, crpix3, xnorm, ynorm, znorm);
705  if (aParams->method == MUSE_RESAMPLE_WEIGHTED_DRIZZLE) {
706  printf(" drop sizes %e %e %e (pixfrac %f,%f,%f)\n"
707  " output sizes %e %e %e\n",
708  xsz, ysz, zsz, aParams->pfx, aParams->pfy, aParams->pfl,
709  xout, yout, zout);
710  } else {
711  printf(" resulting renka_rc: %e %e %e / %e %e %e --> %e\n",
712  pow(wcs->cd11, 2), pow(wcs->cd22, 2), cd33*cd33,
713  pow(wcs->cd11*xnorm, 2), pow(wcs->cd22*ynorm, 2),
714  pow(cd33*znorm, 2), renka_rc);
715  }
716  fflush(stdout);
717  }
718 #endif
719  cpl_imagelist *wcube = NULL;
720  if (getenv("MUSE_DEBUG_WEIGHT_CUBE")) { /* create a weight cube */
721  cpl_msg_debug(__func__, "Weighted resampling: creating weight cube");
722  wcube = cpl_imagelist_new();
723  int i;
724  for (i = 0; i < aGrid->nz; i++) {
725  cpl_image *image = cpl_image_new(aGrid->nx, aGrid->ny,
726  CPL_TYPE_FLOAT);
727  cpl_imagelist_set(wcube, image, i);
728  } /* for i (all wavelength planes) */
729  } /* if weight cube */
730 
731  if (getenv("MUSE_DEBUG_WEIGHTED_GRID")) {
732  char *fn = getenv("MUSE_DEBUG_WEIGHTED_GRID");
733  FILE *grid = fopen(fn, "w");
734  if (grid) {
735  cpl_msg_info(__func__, "Writing grid to \"%s\"", fn);
736  fprintf(grid, "# i j l x y lambda\n");
737  int l;
738  for (l = 0; l < aGrid->nz; l++) {
739  double lambda = (l + 1. - crpix3) * cd33 + crval3;
740  int i;
741  for (i = 0; i < aGrid->nx; i++) {
742  int j;
743  for (j = 0; j < aGrid->ny; j++) {
744  /* x and y position of center of current grid cell (i, j start at 0) */
745  double x, y;
746  if (wcs->iscelsph) {
747  muse_wcs_celestial_from_pixel_fast(wcs, i + 1, j + 1, &x, &y);
748  } else {
749  muse_wcs_projplane_from_pixel_fast(wcs, i + 1, j + 1, &x, &y);
750  }
751  fprintf(grid, "%03d %03d %04d %.10f %.10f %8.3f\n", i+1, j+1, l+1,
752  x, y, lambda);
753  } /* for j (y direction) */
754  } /* for i (x direction) */
755  } /* for l (wavelength planes) */
756  fclose(grid);
757  } else {
758  cpl_msg_warning(__func__, "Writing grid to \"%s\" failed!", fn);
759  }
760  } /* if grid output */
761 
762  int l;
763 #ifdef ESO_ENABLE_DEBUG
764  #pragma omp parallel for default(none) /* as req. by Ralf */ \
765  shared(aCube, aParams, aGrid, aPixtable, bpt, cd33, crpix3, \
766  crval3, data, debug, debugx, debugy, debugz, dq, lbda, ld, \
767  loglambda, maskcube, orgn, ptxoff, ptyoff, renka_rc, stat, \
768  stdout, wcs, wcube, wght, xnorm, xout, xpos, xsz, ynorm, \
769  yout, ypos, ysz, znorm, zout, zoutefac, zsz)
770 #else
771  #pragma omp parallel for default(none) /* as req. by Ralf */ \
772  shared(aCube, aParams, aGrid, aPixtable, bpt, cd33, crpix3, \
773  crval3, data, dq, lbda, ld, loglambda, maskcube, orgn, ptxoff,\
774  ptyoff, renka_rc, stat, stdout, wcs, wcube, wght, xnorm, xout,\
775  xpos, xsz, ynorm, yout, ypos, ysz, znorm, zout, zoutefac, zsz)
776 #endif
777  for (l = 0; l < aGrid->nz; l++) {
778  float *pdata = cpl_image_get_data_float(cpl_imagelist_get(aCube->data, l)),
779  *pstat = cpl_image_get_data_float(cpl_imagelist_get(aCube->stat, l));
780  int *pdq = cpl_image_get_data_int(cpl_imagelist_get(aCube->dq, l));
781  /* wavelength of center of current grid cell (l is index starting at 0) */
782  double lambda = (l + 1. - crpix3) * cd33 + crval3;
783  double zout2 = zout; /* correct the output pixel size for log-lambda */
784  if (loglambda) {
785  lambda = crval3 * exp((l + 1. - crpix3) * cd33 / crval3);
786  zout2 = crval3 * exp((l - crpix3) * cd33 / crval3) * zoutefac;
787  }
788  float *pwcube = NULL;
789  if (wcube) { /* weight cube */
790  pwcube = cpl_image_get_data_float(cpl_imagelist_get(wcube, l));
791  } /* if weight cube */
792 
793  cpl_image *mask = NULL;
794  if (maskcube) {
795  mask = cpl_imagelist_get(maskcube, l);
796  }
797 
798  int i;
799  for (i = 0; i < aGrid->nx; i++) {
800  int j;
801  for (j = 0; j < aGrid->ny; j++) {
802  /* x and y position of center of current grid cell (i, j start at 0) */
803  double x, y;
804  if (wcs->iscelsph) {
805  muse_wcs_celestial_from_pixel_fast(wcs, i + 1, j + 1, &x, &y);
806  } else {
807  muse_wcs_projplane_from_pixel_fast(wcs, i + 1, j + 1, &x, &y);
808  }
809  double sumdata = 0, sumstat = 0, sumweight = 0;
810  int npoints = 0;
811 #ifdef ESO_ENABLE_DEBUG
812  cpl_size *pointlist = NULL;
813  double *pointweights = NULL;
814  int npointlist = 0;
815  if (debug & 2 && i+1 == debugx && j+1 == debugy && l+1 == debugz) {
816  pointlist = cpl_calloc(100, sizeof(cpl_size));
817  pointweights = cpl_malloc(100 * sizeof(double));
818  npointlist = 100;
819  }
820 #endif
821  /* set up point list for bad pixel table writing */
822  cpl_size *plist = NULL;
823  double *pweights = NULL;
824  int nplist = 0,
825  err;
826  if (mask && cpl_image_get(mask, i+1, j+1, &err) != 0) {
827  plist = cpl_calloc(100, sizeof(cpl_size));
828  pweights = cpl_malloc(100 * sizeof(double));
829  nplist = 100;
830  }
831 
832 #ifdef ESO_ENABLE_DEBUG
833  if (debug & 1) {
834  printf("cell %d %d %d:\n", i, j, l);
835  }
836 #endif
837  /* loop through surrounding cells and their contained pixels */
838  int i2;
839  for (i2 = i - ld; i2 <= i + ld; i2++) {
840  int j2;
841  for (j2 = j - ld; j2 <= j + ld; j2++) {
842  int l2;
843  for (l2 = l - ld; l2 <= l + ld; l2++) {
844  cpl_size idx2 = muse_pixgrid_get_index(aGrid, i2, j2, l2, CPL_FALSE),
845  n_rows2 = muse_pixgrid_get_count(aGrid, idx2);
846 #ifdef ESO_ENABLE_DEBUG
847  if (debug & 8 && n_rows2 < 1) {
848  printf("%d %d %d / %d %d %d (%"CPL_SIZE_FORMAT"): no rows!\n",
849  i+1, j+1, l+1, i2+1, j2+1, l2+1, idx2);
850  }
851 #endif
852  const cpl_size *rows2 = muse_pixgrid_get_rows(aGrid, idx2);
853  cpl_size n;
854  for (n = 0; n < n_rows2; n++) {
855  if (dq[rows2[n]]) { /* exclude all bad pixels */
856 #ifdef ESO_ENABLE_DEBUG
857  if (debug & 8) {
858  printf("%d %d %d / %d %d %d (%"CPL_SIZE_FORMAT", "
859  "%"CPL_SIZE_FORMAT"): bad!\n",
860  i+1, j+1, l+1, i2+1, j2+1, l2+1, idx2, n);
861  fflush(stdout);
862  }
863 #endif
864  continue;
865  }
866 
867  double dx = fabs(x - (xpos[rows2[n]] + ptxoff)),
868  dy = fabs(y - (ypos[rows2[n]] + ptyoff)),
869  dlambda = fabs(lambda - lbda[rows2[n]]),
870  r2 = 0;
871  if (wcs->iscelsph) {
872  /* Since the distances of RA in degrees get larger the *
873  * closer we get to the celestial pole, we have to *
874  * compensate for that by multiplying the distance in *
875  * RA by cos(delta), to make it comparable to the *
876  * distances in pixels for the differnt kernels below. */
877  dx *= cos(y * CPL_MATH_RAD_DEG);
878  }
879  if (aParams->method != MUSE_RESAMPLE_WEIGHTED_DRIZZLE) {
880  dx *= xnorm;
881  dy *= ynorm;
882  dlambda *= znorm;
883  r2 = dx*dx + dy*dy + dlambda*dlambda;
884  }
885  double weight = 0.;
886  if (aParams->method == MUSE_RESAMPLE_WEIGHTED_RENKA) {
887  weight = muse_resampling_weight_function_renka(sqrt(r2), renka_rc);
888  } else if (aParams->method == MUSE_RESAMPLE_WEIGHTED_DRIZZLE) {
889  weight = muse_resampling_weight_function_drizzle(xsz, ysz, zsz,
890  xout, yout, zout2,
891  dx, dy, dlambda);
892  } else if (aParams->method == MUSE_RESAMPLE_WEIGHTED_LINEAR) {
893  weight = muse_resampling_weight_function_linear(sqrt(r2));
894  } else if (aParams->method == MUSE_RESAMPLE_WEIGHTED_QUADRATIC) {
895  weight = muse_resampling_weight_function_quadratic(r2);
896  } else if (aParams->method == MUSE_RESAMPLE_WEIGHTED_LANCZOS) {
897  weight = muse_resampling_weight_function_lanczos(dx, dy, dlambda, ld);
898  }
899 
900  if (wght) { /* the pixel table does contain weights */
901  /* apply it on top of the weight computed here */
902  weight *= wght[rows2[n]];
903  }
904 #ifdef ESO_ENABLE_DEBUG
905  if (debug & 8) {
906  printf("%d %d %d / %d %d %d (%"CPL_SIZE_FORMAT", %"CPL_SIZE_FORMAT"):"
907  " x %e %e %e %e y %e %e %e %e l %e %e %e %e --> %e %e %e\n",
908  i+1, j+1, l+1, i2+1, j2+1, l2+1, idx2, n,
909  x, xpos[rows2[n]]+ptxoff, fabs(x - (xpos[rows2[n]]+ptxoff)), dx,
910  y, ypos[rows2[n]]+ptyoff, fabs(y - (ypos[rows2[n]]+ptyoff)), dy,
911  lambda, lbda[rows2[n]], fabs(lambda - lbda[rows2[n]]), dlambda,
912  r2, sqrt(r2), weight);
913  fflush(stdout);
914  }
915 #endif
916  sumweight += weight;
917  sumdata += data[rows2[n]] * weight;
918  sumstat += stat[rows2[n]] * weight*weight;
919  npoints++;
920 #ifdef ESO_ENABLE_DEBUG
921  if (debug & 2 && i+1 == debugx && j+1 == debugy && l+1 == debugz) {
922  if (npoints > npointlist) {
923  pointlist = cpl_realloc(pointlist,
924  (npointlist + 100) * sizeof(cpl_size));
925  memset(pointlist + npointlist, 0, 100 * sizeof(cpl_size));
926  pointweights = cpl_realloc(pointweights,
927  (npointlist + 100) * sizeof(double));
928  npointlist += 100;
929  }
930  /* store row number instead of index, because we cannot be zero: */
931  pointlist[npoints-1] = rows2[n] + 1;
932  pointweights[npoints-1] = weight;
933  }
934 
935  if (debug & 4) {
936  cpl_size idx = muse_pixgrid_get_index(aGrid, i, j, l, CPL_FALSE),
937  count = muse_pixgrid_get_count(aGrid, idx);
938  if (count) {
939  printf(" pixel %4d,%4d,%4d (%8"CPL_SIZE_FORMAT"): "
940  "%2"CPL_SIZE_FORMAT" %2"CPL_SIZE_FORMAT" %f %f %f, "
941  " %e -> %e ==> %e %e (%d)\n", i+1, j+1, l+1, idx,
942  n, count, dx, dy, dlambda,
943  data[muse_pixgrid_get_rows(aGrid, idx)[n]],
944  weight, sumweight, sumdata, npoints);
945  }
946  }
947 #endif
948  /* fill into point list for bad pixel table writing */
949  if (plist && pweights) {
950  if (npoints > nplist) {
951  plist = cpl_realloc(plist, (nplist + 100) * sizeof(cpl_size));
952  memset(plist + nplist, 0, 100 * sizeof(cpl_size));
953  pweights = cpl_realloc(pweights, (nplist + 100) * sizeof(double));
954  nplist += 100;
955  }
956  /* store row number instead of index, because we cannot be zero: */
957  plist[npoints-1] = rows2[n] + 1;
958  pweights[npoints-1] = weight;
959  } /* if plist and pweights */
960  } /* for n (all pixels in grid cell) */
961  } /* for l2 (lambda direction) */
962  } /* for j2 (y direction) */
963  } /* for i2 (x direction) */
964 
965 #ifdef ESO_ENABLE_DEBUG
966  if (debug & 2 && i+1 == debugx && j+1 == debugy && l+1 == debugz) {
967  printf("cell center (%d, %d, %d): %14.7e %14.7e %9.3f\npixelnumber "
968  "weight ", debugx, debugy, debugz, x, y, lambda);
969  muse_pixtable_dump(aPixtable, 0, 0, 2);
970  int m = -1;
971  while (++m < npointlist && pointlist[m] != 0) {
972  /* access row using row index again instead of stored row number: */
973  printf("%12"CPL_SIZE_FORMAT" %8.5f ", pointlist[m] - 1,
974  pointweights[m]);
975  muse_pixtable_dump(aPixtable, pointlist[m] - 1, 1, 0);
976  }
977  printf("sums: %g %g %g --> %g %g\n", sumdata, sumstat, sumweight,
978  sumdata / sumweight, sumstat / pow(sumweight, 2));
979  cpl_free(pointlist);
980  cpl_free(pointweights);
981  }
982 
983  if (debug & 1 && npoints) {
984  printf(" sumdata: %e %e (%d)", sumdata, sumweight, npoints);
985  }
986 #endif
987  /* evaluate point list and write to bad pixel table */
988  if (plist && pweights) {
989  cpl_size ibad = -1;
990  #pragma omp critical(muse_bpt_size)
991  {
992  /* original size and first row index */
993  ibad = cpl_table_get_nrow(bpt);
994  /* enlarge to fit the new bad pixels */
995  cpl_table_set_size(bpt, ibad + nplist);
996  } /* omp critical */
997  int m = -1;
998  while (++m < nplist && plist[m] != 0) {
999  /* access row using row index again instead of stored row number */
1000  cpl_size ipt = plist[m] - 1;
1001  int xraw = muse_pixtable_origin_get_x(orgn[ipt], aPixtable, ipt),
1002  yraw = muse_pixtable_origin_get_y(orgn[ipt]);
1003  muse_quadrants_coords_to_raw(NULL, &xraw, &yraw);
1004  unsigned short ifu = muse_pixtable_origin_get_ifu(orgn[ipt]);
1005  #pragma omp critical(muse_bpt_size)
1006  {
1007  /* now set all of them in the current table row */
1008  cpl_table_set_int(bpt, MUSE_BADPIX_X, ibad, xraw);
1009  cpl_table_set_int(bpt, MUSE_BADPIX_Y, ibad, yraw);
1010  cpl_table_set_int(bpt, MUSE_BADPIX_DQ, ibad, EURO3D_BADOTHER);
1011  cpl_table_set_double(bpt, "pweight", ibad, pweights[m]);
1012  cpl_table_set_int(bpt, "ifu", ibad, ifu);
1013  } /* omp critical */
1014  ibad++;
1015 #ifdef ESO_ENABLE_DEBUG
1016  if (debug & (i+1 == debugx && j+1 == debugy && l+1 == debugz)) {
1017  printf("%12"CPL_SIZE_FORMAT" %8.5f %02hhu %4d %4d\n",
1018  ipt, pweights[m], ifu, xraw, yraw);
1019  }
1020  fflush(stdout);
1021 #endif
1022  } /* while */
1023  cpl_free(plist);
1024  cpl_free(pweights);
1025  /* mark this voxel in the STAT extension */
1026  sumstat = NAN;
1027  } /* if plist and pweights */
1028 
1029  /* if no points were found, we cannot divide by the summed weight *
1030  * and don't need to set the output pixel value (it's 0 already), *
1031  * only set the relevant Euro3D bad pixel flag */
1032  if (!npoints || !isnormal(sumweight)) {
1033  pdq[i + j * aGrid->nx] = EURO3D_MISSDATA;
1034 #ifdef ESO_ENABLE_DEBUG
1035  if (debug & 1) {
1036  printf(" -> no points or weird weight\n");
1037  }
1038 #endif
1039  continue;
1040  }
1041 
1042  /* divide results by weight of summed pixels */
1043  sumdata /= sumweight;
1044  sumstat /= sumweight*sumweight;
1045 #ifdef ESO_ENABLE_DEBUG
1046  if (debug & 1) {
1047  printf(" -> %e (%e)\n", sumdata, sumstat);
1048  }
1049  if (debug) {
1050  fflush(stdout); /* flush the output in any debug case */
1051  }
1052 #endif
1053  pdata[i + j * aGrid->nx] = sumdata;
1054  pstat[i + j * aGrid->nx] = sumstat;
1055  pdq[i + j * aGrid->nx] = EURO3D_GOODPIXEL; /* now we can mark it as good */
1056  if (pwcube) {
1057  pwcube[i + j * aGrid->nx] = sumweight;
1058  } /* if weight cube */
1059  } /* for j (y direction) */
1060  } /* for i (x direction) */
1061  } /* for l (wavelength planes) */
1062  cpl_free(wcs);
1063 
1064  if (wcube) { /* weight cube */
1065  const char *fn = getenv("MUSE_DEBUG_WEIGHT_CUBE");
1066  cpl_error_code rc = cpl_imagelist_save(wcube, fn, CPL_TYPE_UNSPECIFIED,
1067  NULL, CPL_IO_CREATE);
1068  if (rc != CPL_ERROR_NONE) {
1069  cpl_msg_warning(__func__, "Failure to save weight cube as \"%s\": %s", fn,
1070  cpl_error_get_message());
1071  } else {
1072  cpl_msg_info(__func__, "Saved weight cube as \"%s\"", fn);
1073  }
1074  cpl_imagelist_delete(wcube);
1075  } /* if weight cube */
1076 
1077  /* clean up after the mask-cube and the new bad pixel table, *
1078  * but first write that to disk */
1079  if (badpix) {
1080  unsigned char nifu;
1081  cpl_boolean isfirst = CPL_TRUE;
1082  for (nifu = 1; nifu <= kMuseNumIFUs; nifu++) {
1083  /* select all good rows belonging to this IFU with positive weight */
1084  cpl_table_unselect_all(bpt);
1085  cpl_table_or_selected_int(bpt, "ifu", CPL_EQUAL_TO, nifu);
1086  cpl_table_and_selected_double(bpt, "pweight", CPL_GREATER_THAN, 0.);
1087 
1088  /* extract the relevant rows for this IFU, and save the table */
1089  cpl_table *bptifu = cpl_table_extract_selected(bpt);
1090  cpl_size nbad = cpl_table_get_nrow(bptifu);
1091  if (nbad < 1) {
1092  cpl_msg_info(__func__, "No bad pixels from mask in IFU %02hhu, "
1093  "nothing saved.", nifu);
1094  cpl_table_delete(bptifu);
1095  continue;
1096  }
1097 
1098  /* sort and "uniquify" the table rows */
1099  cpl_propertylist *sorting = cpl_propertylist_new();
1100  cpl_propertylist_append_bool(sorting, MUSE_BADPIX_X, CPL_FALSE);
1101  cpl_propertylist_append_bool(sorting, MUSE_BADPIX_Y, CPL_FALSE);
1102  cpl_table_sort(bptifu, sorting);
1103  cpl_propertylist_delete(sorting);
1104  /* now loop through all rows, and compare with the previous row *
1105  * and select duplicate coordinates */
1106  cpl_table_unselect_all(bptifu);
1107  int irow,
1108  xprev = cpl_table_get_int(bptifu, MUSE_BADPIX_X, 0, NULL),
1109  yprev = cpl_table_get_int(bptifu, MUSE_BADPIX_Y, 0, NULL);
1110  for (irow = 1; irow < nbad; irow++) {
1111  int x = cpl_table_get_int(bptifu, MUSE_BADPIX_X, irow, NULL),
1112  y = cpl_table_get_int(bptifu, MUSE_BADPIX_Y, irow, NULL);
1113  if (x == xprev && y == yprev) {
1114  cpl_table_select_row(bptifu, irow);
1115  continue; /* do not move the comparison values */
1116  }
1117  /* replace the comparison values with the current row */
1118  xprev = x;
1119  yprev = y;
1120  } /* for irow (all but the first table rows) */
1121  cpl_table_erase_selected(bptifu);
1122  /* also remove the extra columns again, they *
1123  * should not appear in the output table */
1124  cpl_table_erase_column(bptifu, "pweight");
1125  cpl_table_erase_column(bptifu, "ifu");
1126 
1127  /* save the table *
1128  * and propagate a few important headers from the pixel table */
1129  cpl_propertylist *header = cpl_propertylist_new(),
1130  *pheader = NULL;
1131  char *chan = cpl_sprintf("CHAN%02hhu", nifu);
1132  cpl_propertylist_append_string(header, "EXTNAME", chan);
1133  if (isfirst) {
1134  pheader = cpl_propertylist_new();
1135  cpl_propertylist_append_string(pheader, "INSTRUME", "MUSE");
1136  cpl_propertylist_append_double(pheader, "MJD-OBS",
1137  muse_pfits_get_mjdobs(aPixtable->header));
1138  cpl_propertylist_append_string(pheader, "DATE-OBS",
1139  muse_pfits_get_dateobs(aPixtable->header));
1140  cpl_propertylist_append_string(pheader, "ESO DRS MUSE MASKCUBE",
1141  maskname);
1142  } /* if isfirst */
1143  cpl_table_save(bptifu, pheader, header, badpix,
1144  isfirst ? CPL_IO_CREATE : CPL_IO_EXTEND);
1145  cpl_free(chan);
1146  cpl_propertylist_delete(header);
1147  cpl_propertylist_delete(pheader); /* NULL check in there */
1148  cpl_table_delete(bptifu);
1149  cpl_msg_info(__func__, "Bad pixel table from mask cube for IFU %02hhu "
1150  "saved to \"%s\" (%"CPL_SIZE_FORMAT" pixels).", nifu,
1151  badpix, nbad);
1152  isfirst = CPL_FALSE;
1153  } /* for all IFUs */
1154  cpl_table_delete(bpt);
1155 
1156  /* also sign the table "product"; do this manually here until *
1157  * (if ever) it is made a full pipeline product */
1158  cpl_frameset *fset = cpl_frameset_new();
1159  cpl_frame *frame = cpl_frame_new();
1160  cpl_frame_set_filename(frame, badpix);
1161  cpl_frame_set_tag(frame, MUSE_TAG_BADPIX_TABLE);
1162  cpl_frame_set_type(frame, CPL_FRAME_TYPE_TABLE);
1163  cpl_frame_set_group(frame, CPL_FRAME_GROUP_PRODUCT);
1164  cpl_frame_set_level(frame, CPL_FRAME_LEVEL_FINAL);
1165  cpl_frameset_insert(fset, frame);
1166  cpl_dfs_sign_products(fset, CPL_DFS_SIGNATURE_DATAMD5 | CPL_DFS_SIGNATURE_CHECKSUM);
1167  cpl_frameset_delete(fset);
1168  } /* if badpix */
1169  cpl_free(maskname);
1170  cpl_free(badpix);
1171  cpl_imagelist_delete(maskcube);
1172 
1173  return CPL_ERROR_NONE;
1174 } /* muse_resampling_cube_weighted() */
1175 
1176 /*---------------------------------------------------------------------------*/
1186 /*---------------------------------------------------------------------------*/
1187 static cpl_error_code
1188 muse_resampling_check_deltas(muse_pixtable *aPixtable,
1189  muse_resampling_params *aParams)
1190 {
1191  cpl_ensure_code(aPixtable && aParams, CPL_ERROR_NULL_INPUT);
1192  const char func[] = "muse_resampling_cube"; /* pretend to be in that fct... */
1193 
1194  /* wavelength direction */
1195  if (aParams->dlambda == 0.0) {
1196  aParams->dlambda = kMuseSpectralSamplingA;
1197  if (aParams->tlambda == MUSE_RESAMPLING_DISP_AWAV_LOG ||
1198  aParams->tlambda == MUSE_RESAMPLING_DISP_WAVE_LOG) {
1199  aParams->dlambda /= 1.6; /* XXX seems to be a reasonable value... */
1200  }
1201  }
1202 
1204  /* if pixel table in pixel units, take a shortcut */
1205  if (aParams->dx == 0.0) {
1206  aParams->dx = 1.0;
1207  }
1208  if (aParams->dy == 0.0) {
1209  aParams->dy = 1.0;
1210  }
1211  cpl_msg_debug(func, "steps from parameters: %.2f pix, %.2f pix, %.3f Angstrom",
1212  aParams->dx, aParams->dy, aParams->dlambda);
1213  return CPL_ERROR_NONE;
1214  }
1215  if (aParams->dx == 0.0) {
1216  aParams->dx = kMuseSpaxelSizeX_WFM / 3600.;
1217  if (muse_pfits_get_mode(aPixtable->header) == MUSE_MODE_NFM_AO_N) {
1218  aParams->dx = kMuseSpaxelSizeX_NFM / 3600.;
1219  }
1220  } else {
1221  /* convert from arcsec to degrees */
1222  aParams->dx /= 3600.;
1223  }
1224  if (aParams->dy == 0.0) {
1225  aParams->dy = kMuseSpaxelSizeY_WFM / 3600.;
1226  if (muse_pfits_get_mode(aPixtable->header) == MUSE_MODE_NFM_AO_N) {
1227  aParams->dy = kMuseSpaxelSizeY_NFM / 3600.;
1228  }
1229  } else {
1230  /* anything else should be interpreted as arcsec, but we need deg */
1231  aParams->dy /= 3600.;
1232  }
1233  cpl_msg_debug(func, "steps from parameters: %f arcsec, %f arcsec, %.3f Angstrom",
1234  aParams->dx * 3600., aParams->dy * 3600., aParams->dlambda);
1235  return CPL_ERROR_NONE;
1236 } /* muse_resampling_check_deltas() */
1237 
1238 /*---------------------------------------------------------------------------*/
1252 /*---------------------------------------------------------------------------*/
1253 static cpl_error_code
1254 muse_resampling_compute_size(muse_pixtable *aPixtable,
1255  muse_resampling_params *aParams,
1256  int *aX, int *aY, int *aZ)
1257 {
1258  cpl_ensure_code(aPixtable && aParams && aX && aY && aZ, CPL_ERROR_NULL_INPUT);
1259  const char func[] = "muse_resampling_cube"; /* pretend to be in that fct... */
1260  double x1, y1, x2, y2;
1261  float xmin = cpl_propertylist_get_float(aPixtable->header, MUSE_HDR_PT_XLO),
1262  xmax = cpl_propertylist_get_float(aPixtable->header, MUSE_HDR_PT_XHI),
1263  ymin = cpl_propertylist_get_float(aPixtable->header, MUSE_HDR_PT_YLO),
1264  ymax = cpl_propertylist_get_float(aPixtable->header, MUSE_HDR_PT_YHI);
1265  muse_pixtable_wcs wcstype = muse_pixtable_wcs_check(aPixtable);
1266  if (wcstype == MUSE_PIXTABLE_WCS_CELSPH) {
1267  muse_wcs_projplane_from_celestial(aPixtable->header, xmin, ymin, &x1, &y1);
1268  muse_wcs_projplane_from_celestial(aPixtable->header, xmax, ymax, &x2, &y2);
1269  } else {
1270  muse_wcs_projplane_from_pixel(aPixtable->header, xmin, ymin, &x1, &y1);
1271  muse_wcs_projplane_from_pixel(aPixtable->header, xmax, ymax, &x2, &y2);
1272  }
1273  *aX = lround(fabs(x2 - x1) / aParams->dx) + 1;
1274  *aY = lround(fabs(y2 - y1) / aParams->dy) + 1;
1275  float lmin = cpl_propertylist_get_float(aPixtable->header, MUSE_HDR_PT_LLO),
1276  lmax = cpl_propertylist_get_float(aPixtable->header, MUSE_HDR_PT_LHI);
1277  *aZ = (int)ceil((lmax - lmin) / aParams->dlambda) + 1;
1278  if (aParams->tlambda == MUSE_RESAMPLING_DISP_AWAV_LOG ||
1279  aParams->tlambda == MUSE_RESAMPLING_DISP_WAVE_LOG) {
1280  *aZ = (int)ceil(lmin / aParams->dlambda * log(lmax / lmin)) + 1;
1281  }
1282  cpl_msg_info(func, "Output cube size %d x %d x %d (fit to data)",
1283  *aX, *aY, *aZ);
1284  return CPL_ERROR_NONE;
1285 } /* muse_resampling_compute_size() */
1286 
1287 /*---------------------------------------------------------------------------*/
1297 /*---------------------------------------------------------------------------*/
1298 static void
1299 muse_resampling_override_size_int(int *aV, const char *aKeyword, int aValue)
1300 {
1301  if (aValue <= 0 || !aV) {
1302  return;
1303  }
1304  const char func[] = "muse_resampling_cube"; /* pretend to be in that fct... */
1305  cpl_msg_info(func, "Overriding %s=%d (was %d)", aKeyword, aValue, *aV);
1306  *aV = aValue;
1307 } /* muse_resampling_override_size_int() */
1308 
1309 /*---------------------------------------------------------------------------*/
1323 /*---------------------------------------------------------------------------*/
1324 static cpl_error_code
1325 muse_resampling_override_size(int *aX, int *aY, int *aZ,
1326  muse_resampling_params *aParams)
1327 {
1328  cpl_ensure_code(aX && aY && aZ && aParams, CPL_ERROR_NULL_INPUT);
1329  if (!aParams->wcs) { /* quietly return */
1330  return CPL_ERROR_NONE;
1331  }
1332  const char func[] = "muse_resampling_cube"; /* pretend to be in that fct... */
1333  /* cube size overrides */
1334  const cpl_array *dims = cpl_wcs_get_image_dims(aParams->wcs);
1335  if (!dims) {
1336  cpl_msg_debug(func, "No dimensions to override were specified");
1337  return CPL_ERROR_NONE;
1338  }
1339  muse_resampling_override_size_int(aX, "NAXIS1", cpl_array_get_int(dims, 0, NULL));
1340  muse_resampling_override_size_int(aY, "NAXIS2", cpl_array_get_int(dims, 1, NULL));
1341  if (cpl_wcs_get_image_naxis(aParams->wcs) >= 3) {
1342  muse_resampling_override_size_int(aZ, "NAXIS3",
1343  cpl_array_get_int(dims, 2, NULL));
1344  }
1345  return CPL_ERROR_NONE;
1346 } /* muse_resampling_override_size() */
1347 
1348 /*---------------------------------------------------------------------------*/
1359 /*---------------------------------------------------------------------------*/
1360 static void
1361 muse_resampling_override_wcs_double(muse_datacube *aCube, const char *aKeyword,
1362  double aValue, int aError)
1363 {
1364  if (aError || !aCube) {
1365  cpl_msg_debug("double", "%s=%#g (%d)", aKeyword, aValue, aError);
1366  return;
1367  }
1368  const char func[] = "muse_resampling_cube"; /* pretend to be in that fct... */
1369  double old = cpl_propertylist_has(aCube->header, aKeyword)
1370  ? cpl_propertylist_get_double(aCube->header, aKeyword) : NAN;
1371  cpl_msg_info(func, "Overriding %s=%#g (was %#g)", aKeyword, aValue, old);
1372  cpl_propertylist_update_double(aCube->header, aKeyword, aValue);
1373  /* Leave the marked that something was overridden, will *
1374  * be evaluated and removed in muse_pixgrid_create()! */
1375  cpl_propertylist_update_bool(aCube->header, "MUSE_RESAMPLING_WCS_OVERRIDDEN",
1376  CPL_TRUE);
1377 } /* muse_resampling_override_wcs_double() */
1378 
1379 /*---------------------------------------------------------------------------*/
1391 /*---------------------------------------------------------------------------*/
1392 static cpl_error_code
1393 muse_resampling_override_wcs(muse_datacube *aCube,
1394  muse_resampling_params *aParams)
1395 {
1396  cpl_ensure_code(aCube && aCube->header && aParams, CPL_ERROR_NULL_INPUT);
1397  if (!aParams->wcs) { /* quietly return */
1398  return CPL_ERROR_NONE;
1399  }
1400  const char func[] = "muse_resampling_cube"; /* pretend to be in that fct... */
1401  const cpl_array *crval = cpl_wcs_get_crval(aParams->wcs),
1402  *crpix = cpl_wcs_get_crpix(aParams->wcs);
1403  const cpl_matrix *cd = cpl_wcs_get_cd(aParams->wcs);
1404  int err = 0;
1405  /* spatial axes overrides */
1406  if (crval) {
1407  muse_resampling_override_wcs_double(aCube, "CRVAL1", cpl_array_get_double(crval, 0, &err), err);
1408  muse_resampling_override_wcs_double(aCube, "CRVAL2", cpl_array_get_double(crval, 1, &err), err);
1409  } else {
1410  cpl_msg_debug(func, "No CRVALj to override were specified");
1411  }
1412  if (crpix) {
1413  muse_resampling_override_wcs_double(aCube, "CRPIX1", cpl_array_get_double(crpix, 0, &err), err);
1414  muse_resampling_override_wcs_double(aCube, "CRPIX2", cpl_array_get_double(crpix, 1, &err), err);
1415  } else {
1416  cpl_msg_debug(func, "No CRPIXi to override were specified");
1417  }
1418  if (cd) {
1419  int naxes = cpl_matrix_get_ncol(cd); /* nrow should be the same */
1420  if (cpl_matrix_get_determinant(cd) == 0.) {
1421  cpl_msg_warning(func, "det(CDi_j) = 0, not overriding CDi_j!");
1422  cd = NULL; /* don't override dispersion direction, either */
1423  } else if (naxes > 2 && (cpl_matrix_get(cd, 0, 2) != 0. ||
1424  cpl_matrix_get(cd, 2, 0) != 0. ||
1425  cpl_matrix_get(cd, 2, 1) != 0. ||
1426  cpl_matrix_get(cd, 1, 2) != 0.)) {
1427  cpl_msg_warning(func, "Axis 3 (dispersion) is not cleanly separated from "
1428  "axes 1 and 2, not overriding CDi_j!");
1429  cd = NULL; /* don't override dispersion direction, either */
1430  } else {
1431  cpl_errorstate es = cpl_errorstate_get();
1432  muse_resampling_override_wcs_double(aCube, "CD1_1", cpl_matrix_get(cd, 0, 0),
1433  !cpl_errorstate_is_equal(es));
1434  es = cpl_errorstate_get();
1435  muse_resampling_override_wcs_double(aCube, "CD1_2", cpl_matrix_get(cd, 0, 1),
1436  !cpl_errorstate_is_equal(es));
1437  es = cpl_errorstate_get();
1438  muse_resampling_override_wcs_double(aCube, "CD2_1", cpl_matrix_get(cd, 1, 0),
1439  !cpl_errorstate_is_equal(es));
1440  es = cpl_errorstate_get();
1441  muse_resampling_override_wcs_double(aCube, "CD2_2", cpl_matrix_get(cd, 1, 1),
1442  !cpl_errorstate_is_equal(es));
1443  }
1444  } else {
1445  cpl_msg_debug(func, "No CDi_j to override were specified");
1446  }
1447  /* wavelength axis overrides; apparently, values originally in Angstrom *
1448  * values are here in meters (no way to check this here, since we cannot *
1449  * access aParams->wcs->wcsptr!), so need to convert them back */
1450  if (cpl_array_get_size(crval) > 2) {
1451  if (crval) {
1452  muse_resampling_override_wcs_double(aCube, "CRVAL3",
1453  cpl_array_get_double(crval, 2, &err) * 1e10, err);
1454  }
1455  if (crpix) {
1456  muse_resampling_override_wcs_double(aCube, "CRPIX3", cpl_array_get_double(crpix, 2, &err), err);
1457  }
1458  if (cd) {
1459  cpl_errorstate es = cpl_errorstate_get();
1460  muse_resampling_override_wcs_double(aCube, "CD3_3", cpl_matrix_get(cd, 2, 2) * 1e10,
1461  !cpl_errorstate_is_equal(es));
1462  }
1463  } /* if 3D */
1464  return CPL_ERROR_NONE;
1465 } /* muse_resampling_override_wcs() */
1466 
1467 /*---------------------------------------------------------------------------*/
1477 /*---------------------------------------------------------------------------*/
1478 static cpl_error_code
1479 muse_resampling_fit_data_to_grid(muse_datacube *aCube, muse_pixtable *aPixtable)
1480 {
1481  cpl_ensure_code(aCube && aPixtable, CPL_ERROR_NULL_INPUT);
1482  const char func[] = "muse_resampling_cube"; /* pretend to be in that fct... */
1483  /* Check if we need to move the spatial zeropoint to fit into the *
1484  * computed grid at all. This is only the case, if the grid was *
1485  * computed automatically, not overridden. */
1486  if (cpl_propertylist_has(aCube->header, "MUSE_RESAMPLING_WCS_OVERRIDDEN") &&
1487  cpl_propertylist_get_bool(aCube->header, "MUSE_RESAMPLING_WCS_OVERRIDDEN")) {
1488  cpl_msg_debug(func, "Output WCS was forced, won't update CRPIX[12]!");
1489  cpl_propertylist_erase(aCube->header, "MUSE_RESAMPLING_WCS_OVERRIDDEN");
1490  return CPL_ERROR_NONE;
1491  }
1492  /* do determine offset of coordinate to the first (1,1) pixel */
1493  double xoff1, yoff1, xoff2, yoff2;
1494  float xlo = cpl_propertylist_get_float(aPixtable->header, MUSE_HDR_PT_XLO),
1495  xhi = cpl_propertylist_get_float(aPixtable->header, MUSE_HDR_PT_XHI),
1496  ylo = cpl_propertylist_get_float(aPixtable->header, MUSE_HDR_PT_YLO),
1497  yhi = cpl_propertylist_get_float(aPixtable->header, MUSE_HDR_PT_YHI);
1498  muse_pixtable_wcs wcstype = muse_pixtable_wcs_check(aPixtable);
1499  if (wcstype == MUSE_PIXTABLE_WCS_CELSPH) {
1500  muse_wcs_pixel_from_celestial(aCube->header, xlo, ylo, &xoff1, &yoff1);
1501  muse_wcs_pixel_from_celestial(aCube->header, xhi, yhi, &xoff2, &yoff2);
1502  } else {
1503  muse_wcs_pixel_from_projplane(aCube->header, xlo, ylo, &xoff1, &yoff1);
1504  muse_wcs_pixel_from_projplane(aCube->header, xhi, yhi, &xoff2, &yoff2);
1505  }
1506  /* the actual minimum offset we need depends on the axis-direction, *
1507  * i.e. the sign of CDi_j, so we take the minimum of the result here */
1508  double xoff = fmin(xoff1, xoff2) - 1,
1509  yoff = fmin(yoff1, yoff2) - 1;
1510  if (xoff != 0. || yoff != 0.) {
1511  double crpix1 = muse_pfits_get_crpix(aCube->header, 1),
1512  crpix2 = muse_pfits_get_crpix(aCube->header, 2);
1513  cpl_msg_debug(func, "Updating CRPIX[12]=%#g,%#g (were %#g,%#g)",
1514  crpix1 - xoff, crpix2 - yoff, crpix1, crpix2);
1515  crpix1 -= xoff;
1516  crpix2 -= yoff;
1517  cpl_propertylist_update_double(aCube->header, "CRPIX1", crpix1);
1518  cpl_propertylist_update_double(aCube->header, "CRPIX2", crpix2);
1519  } /* if offsets */
1520  return CPL_ERROR_NONE;
1521 } /* muse_resampling_fit_data_to_grid() */
1522 
1523 /* corresponds to muse_resampling_crstats_type, *
1524  * keep in sync with those values! */
1525 const char *crtypestring[] = {
1526  "IRAF-like",
1527  "average",
1528  "median"
1529 };
1530 
1531 #define MUSE_RESAMPLING_CRREJECT_COMPUTE_STATS_START \
1532  cpl_size idx = muse_pixgrid_get_index(aGrid, i2, j2, l2, CPL_FALSE), \
1533  nrow = muse_pixgrid_get_count(aGrid, idx), \
1534  irow; \
1535  const cpl_size *rows = muse_pixgrid_get_rows(aGrid, idx); \
1536  for (irow = 0; irow < nrow; irow++) { \
1537  if (muse_quality_is_usable(dq[rows[irow]], badinclude)) { \
1538  /* do nothing */ \
1539  } else if (dq[rows[irow]]) { \
1540  continue; /* exclude all other bad pixels */ \
1541  }
1542 #define MUSE_RESAMPLING_CRREJECT_COMPUTE_STATS_DEBUG \
1543  if (debug & 4 && i+1 == debugx && j+1 == debugy && l+1 == debugz) { \
1544  printf("%s: data / stat (%"CPL_SIZE_FORMAT") = %e / %e\n", \
1545  __func__, npixels+1, data[rows[irow]], stat[rows[irow]]); \
1546  }
1547 #define MUSE_RESAMPLING_CRREJECT_COMPUTE_STATS_END \
1548  if (aType == MUSE_RESAMPLING_CRSTATS_IRAF) { \
1549  /* add the point from the pixel table to the vector */ \
1550  level += data[rows[irow]]; \
1551  dev += stat[rows[irow]]; \
1552  } else { \
1553  /* add the point to the npixels'th column of the image */ \
1554  if (npixels >= sxsize) { \
1555  /* is there no cpl_image_resize()?! */ \
1556  sxsize += MUSE_CRREJECT_MAX_NPIX; \
1557  cpl_image *tmp = cpl_image_new(sxsize, 2, CPL_TYPE_DOUBLE); \
1558  cpl_image_copy(tmp, sdata, 1, 1); \
1559  cpl_image_delete(sdata); \
1560  sdata = tmp; \
1561  vdata = cpl_image_get_data_double(sdata); \
1562  } \
1563  vdata[npixels] = data[rows[irow]]; \
1564  vdata[npixels + sxsize] = stat[rows[irow]]; \
1565  } \
1566  /* increase the index for all methods */ \
1567  npixels++; \
1568  } /* for irow (all pixels in grid cell) */
1569 
1570 /*---------------------------------------------------------------------------*/
1593 /*---------------------------------------------------------------------------*/
1594 static void
1595 muse_resampling_crreject(muse_pixtable *aPixtable, muse_pixgrid *aGrid,
1596  muse_resampling_crstats_type aType, double aSigma)
1597 {
1598  const char *id = "muse_resampling_cube"; /* pretend to be in that function */
1599  if (aType > MUSE_RESAMPLING_CRSTATS_MEDIAN) {
1600  cpl_msg_warning(id, "Unknown type (%u) for cosmic-ray rejection statistics,"
1601  " resetting to \"%s\"", aType,
1602  crtypestring[MUSE_RESAMPLING_CRSTATS_MEDIAN]);
1604  }
1605  cpl_msg_info(id, "Using %s statistics (%.3f sigma level) for cosmic-ray"
1606  " rejection", crtypestring[aType], aSigma);
1607 
1608 #ifdef ESO_ENABLE_DEBUG
1609  int debug = 0, debugx = 0, debugy = 0, debugz = 0;
1610  if (getenv("MUSE_DEBUG_CRREJECT")) {
1611  debug = atoi(getenv("MUSE_DEBUG_CRREJECT"));
1612  }
1613  if (debug & 2) { /* need coordinates */
1614  if (getenv("MUSE_DEBUG_CRREJECT_X")) {
1615  debugx = atoi(getenv("MUSE_DEBUG_CRREJECT_X"));
1616  if (debugx < 1 || debugx > aGrid->nx) {
1617  debugx = 0;
1618  }
1619  }
1620  if (getenv("MUSE_DEBUG_CRREJECT_Y")) {
1621  debugy = atoi(getenv("MUSE_DEBUG_CRREJECT_Y"));
1622  if (debugy < 1 || debugy > aGrid->ny) {
1623  debugy = 0;
1624  }
1625  }
1626  if (getenv("MUSE_DEBUG_CRREJECT_Z")) {
1627  debugz = atoi(getenv("MUSE_DEBUG_CRREJECT_Z"));
1628  if (debugz < 1 || debugz > aGrid->nz) {
1629  debugz = 0;
1630  }
1631  }
1632  }
1633 #endif
1634 
1635  enum dirtype {
1636  DIR_X = 0,
1637  DIR_Y = 1,
1638  DIR_NONE = 2
1639  };
1640  enum dirtype dir = DIR_NONE;
1641  /* XXX also exclude multiple exposures with different POSANG! */
1642  cpl_boolean haswcs = muse_pixtable_wcs_check(aPixtable) == MUSE_PIXTABLE_WCS_CELSPH;
1643  double posang = muse_astro_posangle(aPixtable->header);
1644  const double palimit = 5.;
1645  if (!haswcs || (fabs(posang) < palimit || fabs(fabs(posang) - 180.) < palimit ||
1646  fabs(fabs(posang) - 360.) < palimit)) {
1647  cpl_msg_debug(id, "CR rejection: posang = %f deg --> DIR_X "
1648  "(%s / %s / %s / %s)", posang, haswcs ? "yes": "no",
1649  fabs(posang) < palimit ? "true" : "false",
1650  fabs(fabs(posang) - 180.) < palimit ? "true" : "false",
1651  fabs(fabs(posang) - 360.) < palimit ? "true" : "false");
1652  dir = DIR_X;
1653  } else if (fabs(fabs(posang) - 90.) < palimit ||
1654  fabs(fabs(posang) - 270.) < palimit) {
1655  cpl_msg_debug(id, "CR rejection: posang = %f deg --> DIR_Y "
1656  "(%s / %s / %s)", posang, haswcs ? "yes": "no",
1657  fabs(fabs(posang) - 90.) < palimit ? "true" : "false",
1658  fabs(fabs(posang) - 270.) < palimit ? "true" : "false");
1659  dir = DIR_Y;
1660  } else {
1661  cpl_msg_debug(id, "CR rejection: posang = %f deg --> DIR_NONE "
1662  "(%s / %s / %s / %s / %s / %s)", posang, haswcs ? "yes": "no",
1663  fabs(posang) < palimit ? "true" : "false",
1664  fabs(fabs(posang) - 90.) < palimit ? "true" : "false",
1665  fabs(fabs(posang) - 180.) < palimit ? "true" : "false",
1666  fabs(fabs(posang) - 270.) < palimit ? "true" : "false",
1667  fabs(fabs(posang) - 360.) < palimit ? "true" : "false");
1668  }
1669 
1670  /* pointer access to the relevant pixel table columns */
1671  float *data = cpl_table_get_data_float(aPixtable->table, MUSE_PIXTABLE_DATA),
1672  *stat = cpl_table_get_data_float(aPixtable->table, MUSE_PIXTABLE_STAT);
1673  int *dq = cpl_table_get_data_int(aPixtable->table, MUSE_PIXTABLE_DQ);
1674 
1675  /* when computing surrounding statistics, include these types of bad pixels */
1676  uint32_t badinclude = EURO3D_COSMICRAY;
1677  int l;
1678 #ifdef ESO_ENABLE_DEBUG
1679  #pragma omp parallel for default(none) /* as req. by Ralf */ \
1680  shared(aGrid, aPixtable, aSigma, badinclude, aType, crtypestring, \
1681  data, debug, debugx, debugy, debugz, dir, dq, id, stat, stdout)
1682 #else
1683  #pragma omp parallel for default(none) /* as req. by Ralf */ \
1684  shared(aGrid, aSigma, aType, badinclude, crtypestring, data, dir, \
1685  dq, id, stat)
1686 #endif
1687  for (l = 0; l < aGrid->nz; l++) {
1688 #define MUSE_CRREJECT_MAX_NPIX 1000 /* XXX compute size dynamically somehow */
1689  int sxsize = MUSE_CRREJECT_MAX_NPIX;
1690  cpl_image *sdata = NULL;
1691  double *vdata = NULL;
1692  if (aType > MUSE_RESAMPLING_CRSTATS_IRAF) {
1693  /* create image for statistics computations */
1694  /* XXX would memset'ting the buffer not be faster? */
1695  sdata = cpl_image_new(sxsize, 2, CPL_TYPE_DOUBLE);
1696  vdata = cpl_image_get_data_double(sdata);
1697  }
1698 
1699  int i;
1700  for (i = 0; i < aGrid->nx; i++) {
1701  int j;
1702  for (j = 0; j < aGrid->ny; j++) {
1703  /* in the IRAF-like case, compute local statistics, i.e. set *
1704  * the variables to zero (mean, and stdev from the variance) */
1705  double level = 0., dev = 0;
1706  /* loop through surrounding cells: compute local statistics */
1707 #define CR_LD 1 /* loop distance for CR rejection statistics */
1708  cpl_size npixels = 0; /* count pixels again, without bad ones */
1709  int i2, j2, l2;
1710  if (dir == DIR_Y) {
1711  for (i2 = i - CR_LD; i2 <= i + CR_LD; i2++) {
1712  int nwidth = CR_LD;
1713  if (i2 == i) {
1714  nwidth = 0; /* don't use neighbors in the same slice */
1715  }
1716  for (j2 = j - nwidth; j2 <= j + nwidth; j2++) {
1717  for (l2 = l - nwidth; l2 <= l + nwidth; l2++) {
1718  MUSE_RESAMPLING_CRREJECT_COMPUTE_STATS_START
1719 #ifdef ESO_ENABLE_DEBUG
1720  MUSE_RESAMPLING_CRREJECT_COMPUTE_STATS_DEBUG
1721 #endif
1722  MUSE_RESAMPLING_CRREJECT_COMPUTE_STATS_END
1723  } /* for l2 (wavelength/z direction) */
1724  } /* for i2 (x direction) */
1725  } /* for j2 (y direction) */
1726  } else {
1727  for (j2 = j - CR_LD; j2 <= j + CR_LD; j2++) {
1728  int nwidth = CR_LD;
1729  if (dir == DIR_X && j2 == j) {
1730  nwidth = 0; /* don't use neighbors in the same slice */
1731  }
1732  for (i2 = i - nwidth; i2 <= i + nwidth; i2++) {
1733  for (l2 = l - nwidth; l2 <= l + nwidth; l2++) {
1734  MUSE_RESAMPLING_CRREJECT_COMPUTE_STATS_START
1735 #ifdef ESO_ENABLE_DEBUG
1736  MUSE_RESAMPLING_CRREJECT_COMPUTE_STATS_DEBUG
1737 #endif
1738  MUSE_RESAMPLING_CRREJECT_COMPUTE_STATS_END
1739  } /* for l2 (wavelength/z direction) */
1740  } /* for i2 (x direction) */
1741  } /* for j2 (y direction) */
1742  } /* else */
1743  /* need at least 3 values to do something sensible */
1744  if (npixels < 3) {
1745  continue;
1746  }
1747  if (aType == MUSE_RESAMPLING_CRSTATS_IRAF) {
1748  level /= npixels; /* local mean value */
1749  dev = sqrt(dev) / npixels; /* local mean sigma value */
1750  } else {
1751  unsigned sflags;
1752  if (aType == MUSE_RESAMPLING_CRSTATS_MEDIAN) {
1753  sflags = CPL_STATS_MEDIAN | CPL_STATS_MAD;
1754  } else {
1755  sflags = CPL_STATS_MEAN | CPL_STATS_STDEV;
1756  }
1757  cpl_stats *sstats = cpl_stats_new_from_image_window(sdata, sflags,
1758  1, 1, npixels, 1);
1759  if (aType == MUSE_RESAMPLING_CRSTATS_MEDIAN) {
1760  level = cpl_stats_get_median(sstats); /* local median value */
1761  dev = cpl_stats_get_mad(sstats); /* local MAD */
1762  } else {
1763  level = cpl_stats_get_mean(sstats); /* local mean value */
1764  dev = cpl_stats_get_stdev(sstats); /* local standard deviation */
1765  }
1766  cpl_stats_delete(sstats);
1767  } /* else (not IRAF) */
1768  double limit = level + aSigma * dev;
1769 #ifdef ESO_ENABLE_DEBUG
1770  if (debug & 2 && i+1 == debugx && j+1 == debugy && l+1 == debugz) {
1771  if (aType == MUSE_RESAMPLING_CRSTATS_IRAF) {
1772  printf("%s: %03d,%03d,%04d: %.3f+/-%.3f (stats), %.3f (limit) (%"
1773  CPL_SIZE_FORMAT" values)\n", __func__, i+1, j+1, l+1, level,
1774  dev, limit, npixels);
1775  } else {
1776  cpl_stats *ssdata = cpl_stats_new_from_image_window(sdata, CPL_STATS_ALL,
1777  1, 1, npixels, 1),
1778  *ssstat = cpl_stats_new_from_image_window(sdata, CPL_STATS_ALL,
1779  1, 2, npixels, 2);
1780  printf("%s: %03d,%03d,%04d: %e +/- %e (%s), %e (limit) (%"CPL_SIZE_FORMAT
1781  " values); data stats:\n", __func__, i+1, j+1, l+1,
1782  level, dev, crtypestring[aType], limit, npixels);
1783  cpl_stats_dump(ssdata, CPL_STATS_ALL, stdout);
1784  printf("%s: variance stats:\n", __func__);
1785  cpl_stats_dump(ssstat, CPL_STATS_ALL, stdout);
1786  fflush(stdout);
1787  cpl_stats_delete(ssdata);
1788  cpl_stats_delete(ssstat);
1789  }
1790  }
1791 #endif
1792 
1793  /* loop through pixels in the central cell: mark cosmic rays */
1794  cpl_size idx = muse_pixgrid_get_index(aGrid, i, j, l, CPL_FALSE),
1795  nrow = muse_pixgrid_get_count(aGrid, idx),
1796  irow;
1797  const cpl_size *rows = muse_pixgrid_get_rows(aGrid, idx);
1798  for (irow = 0; irow < nrow; irow++) {
1799  if (data[rows[irow]] > limit) {
1800  dq[rows[irow]] |= EURO3D_COSMICRAY;
1801 #ifdef ESO_ENABLE_DEBUG
1802  if (debug & 1 && i+1 == debugx && j+1 == debugy && l+1 == debugz) {
1803  printf("%s: %03d,%03d,%04d: rejected row %"CPL_SIZE_FORMAT" (%"
1804  CPL_SIZE_FORMAT" of %"CPL_SIZE_FORMAT" in this gridcell):\t",
1805  __func__, i+1, j+1, l+1, rows[irow], irow+1, nrow);
1806  muse_pixtable_dump(aPixtable, rows[irow], 1, 0);
1807  }
1808 #endif
1809  }
1810  } /* for irow (all pixels in grid cell) */
1811 #ifdef ESO_ENABLE_DEBUG
1812  if (debug) {
1813  fflush(stdout);
1814  }
1815 #endif
1816  } /* for j (y direction) */
1817  } /* for i (x direction) */
1818  cpl_image_delete(sdata);
1819  } /* for l (wavelength planes) */
1820 } /* muse_resampling_crreject() */
1821 
1822 /*---------------------------------------------------------------------------*/
1845 /*---------------------------------------------------------------------------*/
1848  muse_resampling_params *aParams)
1849 {
1850  cpl_ensure(aParams, CPL_ERROR_NULL_INPUT, NULL);
1851  cpl_ensure(aParams->method < MUSE_RESAMPLE_NONE, CPL_ERROR_ILLEGAL_INPUT, NULL);
1852  /* all other error checking is already done in muse_resampling_cube() */
1853 
1854  /* call the existing resampling routine for datacubes */
1855  muse_datacube *cube = muse_resampling_cube(aPixtable, aParams, NULL);
1856  if (!cube) {
1857  return NULL;
1858  }
1859 
1860  /* create Euro3D object, copy header and set typical Euro3D primary headers */
1861  muse_euro3dcube *e3d = cpl_calloc(1, sizeof(muse_euro3dcube));
1862  e3d->header = cpl_propertylist_duplicate(cube->header);
1863  cpl_propertylist_erase_regexp(e3d->header,
1864  "^SIMPLE$|^BITPIX$|^NAXIS|^EURO3D$|^E3D_",
1865  0);
1866  cpl_propertylist_append_char(e3d->header, "EURO3D", 'T');
1867  cpl_propertylist_set_comment(e3d->header, "EURO3D",
1868  "file conforms to Euro3D standard");
1869  /* E3D_VERS is supposed to be a string! */
1870  cpl_propertylist_append_string(e3d->header, "E3D_VERS", "1.0");
1871  cpl_propertylist_set_comment(e3d->header, "E3D_VERS",
1872  "version number of the Euro3D format");
1873  cpl_errorstate prestate = cpl_errorstate_get();
1874  double darlambdaref = cpl_propertylist_get_double(e3d->header,
1876  if (!cpl_errorstate_is_equal(prestate)) {
1877  darlambdaref = -1.; /* set to negative value to be sure on error */
1878  cpl_errorstate_set(prestate);
1879  }
1880  if (darlambdaref > 0.) {
1881  cpl_propertylist_append_char(e3d->header, "E3D_ADC", 'T');
1882  cpl_propertylist_set_comment(e3d->header, "E3D_ADC",
1883  "data was corrected for atmospheric dispersion");
1884  } else {
1885  cpl_propertylist_append_char(e3d->header, "E3D_ADC", 'F');
1886  cpl_propertylist_set_comment(e3d->header, "E3D_ADC",
1887  "data not corrected for atmospheric dispersion");
1888  }
1889 
1890  /* fill WCS info in data header */
1891  e3d->hdata = cpl_propertylist_new();
1892  cpl_propertylist_append_string(e3d->hdata, "EXTNAME", "E3D_DATA");
1893  cpl_propertylist_set_comment(e3d->hdata, "EXTNAME",
1894  "This is the Euro3D data table extension");
1895  cpl_propertylist_append_string(e3d->hdata, "CTYPES",
1896  muse_pfits_get_ctype(e3d->header, 3));
1897  cpl_propertylist_set_comment(e3d->hdata, "CTYPES",
1898  cpl_propertylist_get_comment(e3d->header, "CTYPE3"));
1899  cpl_propertylist_append_string(e3d->hdata, "CUNITS",
1900  muse_pfits_get_cunit(e3d->header, 3));
1901  cpl_propertylist_set_comment(e3d->hdata, "CUNITS",
1902  cpl_propertylist_get_comment(e3d->header, "CUNIT3"));
1903  cpl_propertylist_append_double(e3d->hdata, "CRVALS",
1904  muse_pfits_get_crval(e3d->header, 3));
1905  cpl_propertylist_set_comment(e3d->hdata, "CRVALS",
1906  cpl_propertylist_get_comment(e3d->header, "CRVAL3"));
1907  cpl_propertylist_append_double(e3d->hdata, "CDELTS",
1908  muse_pfits_get_cd(e3d->header, 3, 3));
1909  cpl_propertylist_set_comment(e3d->hdata, "CDELTS",
1910  cpl_propertylist_get_comment(e3d->header, "CD3_3"));
1911  /* remove WCS for spatial axis from main header */
1912  cpl_propertylist_erase_regexp(e3d->header, "^C...*3$|^CD3_.$", 0);
1913 
1914  /* create and fill Euro3D table structure */
1915  int nx = cpl_image_get_size_x(cpl_imagelist_get(cube->data, 0)),
1916  ny = cpl_image_get_size_y(cpl_imagelist_get(cube->data, 0)),
1917  nz = cpl_imagelist_get_size(cube->data);
1919  /* set array columns to initial (expected) depth */
1920  /* XXX this is very expensive for big tables, we should make *
1921  * muse_cpltable_new() take an extra argument for the depth */
1922  cpl_table_set_column_depth(e3d->dtable, "DATA_SPE", nz);
1923  cpl_table_set_column_depth(e3d->dtable, "QUAL_SPE", nz);
1924  cpl_table_set_column_depth(e3d->dtable, "STAT_SPE", nz);
1925  /* set column units depending on data unit type in the pixel table */
1926  cpl_table_set_column_unit(e3d->dtable, "DATA_SPE",
1927  cpl_table_get_column_unit(aPixtable->table,
1929  cpl_table_set_column_unit(e3d->dtable, "STAT_SPE",
1930  cpl_table_get_column_unit(aPixtable->table,
1932  /* set column save types as needed */
1933  cpl_table_set_column_savetype(e3d->dtable, "SELECTED", CPL_TYPE_BOOL);
1934 
1935  muse_wcs *wcs = muse_wcs_new(cube->header);
1937  if (wcs->iscelsph) { /* if not, then the preset unit of "pix" is valid */
1938  cpl_table_set_column_unit(e3d->dtable, "XPOS", "deg");
1939  cpl_table_set_column_unit(e3d->dtable, "YPOS", "deg");
1940  }
1941 
1942  /* Loop over all spaxels in the cube and transfer the data */
1943  unsigned euro3d_ignore = EURO3D_OUTSDRANGE | EURO3D_MISSDATA;
1944  cpl_vector *vusable = cpl_vector_new(nx * ny); /* for statistics */
1945  int i, itable = 0;
1946  for (i = 1; i <= nx; i++) {
1947  int j;
1948  for (j = 1; j <= ny; j++) {
1949  cpl_array *adata = cpl_array_new(nz, CPL_TYPE_FLOAT),
1950  *adq = cpl_array_new(nz, CPL_TYPE_INT),
1951  *astat = cpl_array_new(nz, CPL_TYPE_FLOAT);
1952  /* WCS coordinates */
1953  double x, y;
1954  if (wcs->iscelsph) {
1955  muse_wcs_celestial_from_pixel_fast(wcs, i, j, &x, &y);
1956  } else {
1957  muse_wcs_projplane_from_pixel_fast(wcs, i, j, &x, &y);
1958  }
1959 
1960  int l, nusable = 0, nstart = -1; /* numbers needed for Euro3D format */
1961  for (l = 0; l < nz; l++) {
1962  int err;
1963  unsigned dq = cpl_image_get(cpl_imagelist_get(cube->dq, l), i, j, &err);
1964  /* check if we can ignore this as bad pixel at the start */
1965  if (nstart < 0 && (dq & euro3d_ignore)) {
1966  continue;
1967  }
1968  cpl_array_set_int(adq, nusable, dq);
1969  cpl_array_set_float(adata, nusable,
1970  cpl_image_get(cpl_imagelist_get(cube->data, l),
1971  i, j, &err));
1972  /* take the square root so that we get errors and not variances */
1973  cpl_array_set_float(astat, nusable,
1974  sqrt(cpl_image_get(cpl_imagelist_get(cube->stat, l),
1975  i, j, &err)));
1976  nusable++;
1977  if (nstart < 0) {
1978  nstart = l + 1;
1979  }
1980  } /* for l (z / wavelengths) */
1981 
1982  cpl_table_set_int(e3d->dtable, "SPEC_ID", itable, itable + 1);
1983  cpl_table_set_int(e3d->dtable, "SPEC_LEN", itable, nusable);
1984  cpl_table_set_int(e3d->dtable, "SPEC_STA", itable, nstart);
1985  cpl_table_set_double(e3d->dtable, "XPOS", itable, x);
1986  cpl_table_set_double(e3d->dtable, "YPOS", itable, y);
1987  /* not sure what to use for SPAX_ID, just leave it unset for now */
1988  /*cpl_table_set_string(e3d->dtable, "SPAX_ID", itable, ""); */
1989  cpl_table_set_array(e3d->dtable, "DATA_SPE", itable, adata);
1990  cpl_table_set_array(e3d->dtable, "QUAL_SPE", itable, adq);
1991  cpl_table_set_array(e3d->dtable, "STAT_SPE", itable, astat);
1992 
1993  cpl_array_delete(adata);
1994  cpl_array_delete(adq);
1995  cpl_array_delete(astat);
1996 
1997  cpl_vector_set(vusable, itable, nusable);
1998  if (nstart != -1 && nusable > 0) {
1999  /* good spectrum, unselect to not remove it below */
2000  cpl_table_unselect_row(e3d->dtable, itable);
2001  }
2002  itable++; /* move to next table row */
2003  } /* for j (y spaxels) */
2004  } /* for i (x spaxels) */
2005  cpl_free(wcs);
2006 
2007  cpl_vector_set_size(vusable, itable);
2008  int maxusable = cpl_vector_get_max(vusable);
2009 #if 0
2010  cpl_msg_debug(__func__, "filled %d of %d spaxels, usable are "
2011  "%f..%f(%f)+/-%f..%d pixels per spectrum", itable, nx * ny,
2012  cpl_vector_get_min(vusable), cpl_vector_get_mean(vusable),
2013  cpl_vector_get_median(vusable), cpl_vector_get_stdev(vusable),
2014  maxusable);
2015 #endif
2016  cpl_msg_debug(__func__, "filled %"CPL_SIZE_FORMAT" of %d spaxels, usable "
2017  "are max. %d of %d pixels per spectrum%s",
2018  itable - cpl_table_count_selected(e3d->dtable), nx * ny,
2019  maxusable, nz, maxusable == nz ? "" : ", cutting table");
2020  if (maxusable != nz) {
2021  /* set array columns to real depth (maximum usable spectral pixels found) */
2022  cpl_table_set_column_depth(e3d->dtable, "DATA_SPE", maxusable);
2023  cpl_table_set_column_depth(e3d->dtable, "QUAL_SPE", maxusable);
2024  cpl_table_set_column_depth(e3d->dtable, "STAT_SPE", maxusable);
2025  }
2026  /* resize table to number of used spaxels */
2027  cpl_table_erase_selected(e3d->dtable); /* erase empty spectra */
2028  cpl_vector_delete(vusable);
2029 
2030  /* fill columns that are the same for all spaxels */
2031  /* by default, all spectra are supposed to be selected */
2032  cpl_table_fill_column_window_int(e3d->dtable, "SELECTED", 0, itable,
2033  1 /* TRUE */);
2034  /* assume that we only used one resolution element */
2035  cpl_table_fill_column_window_int(e3d->dtable, "NSPAX", 0, itable, 1);
2036  /* we have only one group for MUSE */
2037  cpl_table_fill_column_window_int(e3d->dtable, "GROUP_N", 0, itable, 1);
2038 
2039  /* done converting the data, free the datacube */
2040  muse_datacube_delete(cube);
2041 
2042  /* fill info in group table */
2043  e3d->hgroup = cpl_propertylist_new();
2044  cpl_propertylist_append_string(e3d->hgroup, "EXTNAME", "E3D_GRP");
2045  cpl_propertylist_set_comment(e3d->hgroup, "EXTNAME",
2046  "This is the Euro3D group table extension");
2047  e3d->gtable = muse_cpltable_new(muse_euro3dcube_e3d_grp_def, 1); /* single group */
2048  cpl_table_set_int(e3d->gtable, "GROUP_N", 0, 1);
2049  cpl_table_set_string(e3d->gtable, "G_SHAPE", 0, "R"/*ECTANG" ULAR*/);
2050  cpl_table_set_float(e3d->gtable, "G_SIZE1", 0, aParams->dx);
2051  cpl_table_set_float(e3d->gtable, "G_ANGLE", 0, 0.);
2052  cpl_table_set_float(e3d->gtable, "G_SIZE2", 0, aParams->dy);
2053  if (darlambdaref > 0.) {
2054  /* set all properties to NaN as required by the Euro3D specs */
2055  cpl_table_set_float(e3d->gtable, "G_POSWAV", 0, NAN);
2056  cpl_table_set_float(e3d->gtable, "G_AIRMAS", 0, NAN);
2057  cpl_table_set_float(e3d->gtable, "G_PARANG", 0, NAN);
2058  cpl_table_set_float(e3d->gtable, "G_PRESSU", 0, NAN);
2059  cpl_table_set_float(e3d->gtable, "G_TEMPER", 0, NAN);
2060  cpl_table_set_float(e3d->gtable, "G_HUMID", 0, NAN);
2061  } else {
2062  /* because we then don't know any better, use the central MUSE wavelength */
2063  cpl_table_set_float(e3d->gtable, "G_POSWAV", 0,
2064  (kMuseNominalLambdaMin + kMuseNominalLambdaMax) / 2.);
2065  cpl_table_set_float(e3d->gtable, "G_AIRMAS", 0,
2066  muse_astro_airmass(e3d->header));
2067  cpl_table_set_float(e3d->gtable, "G_PARANG", 0,
2068  muse_astro_parangle(e3d->header));
2069  cpl_table_set_float(e3d->gtable, "G_PRESSU", 0,
2071  + muse_pfits_get_pres_start(e3d->header)) / 2.);
2072  cpl_table_set_float(e3d->gtable, "G_TEMPER", 0,
2073  muse_pfits_get_temp(e3d->header) + 273.15);
2074  cpl_table_set_float(e3d->gtable, "G_HUMID", 0,
2075  muse_pfits_get_rhum(e3d->header));
2076  }
2077 
2078  return e3d;
2079 } /* muse_resampling_euro3d() */
2080 
2081 /*---------------------------------------------------------------------------*/
2132 /*---------------------------------------------------------------------------*/
2133 muse_datacube *
2135  muse_pixgrid **aGrid)
2136 {
2137  cpl_ensure(aPixtable && aParams, CPL_ERROR_NULL_INPUT, NULL);
2138  cpl_ensure(muse_pixtable_get_type(aPixtable) == MUSE_PIXTABLE_TYPE_FULL,
2139  CPL_ERROR_ILLEGAL_INPUT, NULL);
2140  muse_pixtable_wcs wcstype = muse_pixtable_wcs_check(aPixtable);
2141  cpl_ensure(wcstype == MUSE_PIXTABLE_WCS_CELSPH ||
2142  wcstype == MUSE_PIXTABLE_WCS_PIXEL, CPL_ERROR_UNSUPPORTED_MODE,
2143  NULL);
2144 
2145  /* compute or set the size of the output grid depending on *
2146  * the inputs and the data available in the pixel table */
2147  muse_resampling_check_deltas(aPixtable, aParams);
2148  /* compute output sizes; wavelength is different in that it is *
2149  * more useful to contain partly empty areas within the field *
2150  * for the extreme ends, so use ceil() */
2151  int xsize = 0, ysize = 0, zsize = 0;
2152  muse_resampling_compute_size(aPixtable, aParams, &xsize, &ysize, &zsize);
2153  muse_resampling_override_size(&xsize, &ysize, &zsize, aParams);
2154  cpl_ensure(xsize > 0 && ysize > 0 && zsize > 0, CPL_ERROR_ILLEGAL_OUTPUT,
2155  NULL);
2156 
2157  double time = cpl_test_get_walltime();
2158 
2159  /* create the structure for the output datacube */
2160  muse_datacube *cube = cpl_calloc(1, sizeof(muse_datacube));
2161 
2162  /* copy and clean up incoming FITS headers */
2163  cube->header = cpl_propertylist_duplicate(aPixtable->header);
2164  cpl_propertylist_erase_regexp(cube->header,
2165  "^SIMPLE$|^BITPIX$|^NAXIS|^EXTEND$|^XTENSION$|"
2166  "^DATASUM$|^DATAMIN$|^DATAMAX$|^DATAMD5$|"
2167  "^PCOUNT$|^GCOUNT$|^HDUVERS$|^BLANK$|"
2168  "^BZERO$|^BSCALE$|^CHECKSUM$|^INHERIT$|"
2169  "^EXTNAME$|"MUSE_WCS_KEYS"|"MUSE_HDR_PT_REGEXP,
2170  0);
2171  /* set data unit depending on data unit type in the pixel table */
2172  cpl_propertylist_update_string(cube->header, "BUNIT",
2173  cpl_table_get_column_unit(aPixtable->table,
2175  /* set NAXIS for later handling of the WCS */
2176  cpl_propertylist_update_int(cube->header, "NAXIS", 3);
2177  cpl_propertylist_update_int(cube->header, "NAXIS1", xsize);
2178  cpl_propertylist_update_int(cube->header, "NAXIS2", ysize);
2179  cpl_propertylist_update_int(cube->header, "NAXIS3", zsize);
2180  /* if pixel table was astrometrically calibrated, use its WCS headers *
2181  * Axis 1: x or RA, axis 2: y or DEC, axis 3: lambda */
2182  if (wcstype == MUSE_PIXTABLE_WCS_CELSPH) {
2183  cpl_propertylist_copy_property_regexp(cube->header, aPixtable->header,
2184  MUSE_WCS_KEYS, 0);
2185  cpl_propertylist_update_double(cube->header, "CD1_1", -aParams->dx);
2186  cpl_propertylist_update_double(cube->header, "CD2_2", aParams->dy);
2187  cpl_propertylist_update_double(cube->header, "CD1_2", 0.);
2188  cpl_propertylist_update_double(cube->header, "CD2_1", 0.);
2189  /* correct CRPIX by the central pixel coordinate */
2190  cpl_propertylist_update_double(cube->header, "CRPIX1",
2191  muse_pfits_get_crpix(cube->header, 1)
2192  + (1. + xsize) / 2.);
2193  cpl_propertylist_update_double(cube->header, "CRPIX2",
2194  muse_pfits_get_crpix(cube->header, 2)
2195  + (1. + ysize) / 2.);
2196  /* the pixel table had WCSAXES=2 which should not be propagated to the cube! */
2197  cpl_propertylist_erase(cube->header, "WCSAXES");
2198  } else {
2199  /* add basic WCS headers that are also required by the functions below. */
2200  cpl_propertylist_append_double(cube->header, "CRPIX1", 1.);
2201  cpl_propertylist_append_double(cube->header, "CRVAL1",
2202  cpl_propertylist_get_float(aPixtable->header,
2203  MUSE_HDR_PT_XLO));
2204  /* for linear axes CTYPE doesn't matter, as long as it's not in 4-3 form, *
2205  * see Greisen & Calabretta 2002 A&A 395, 1061 */
2206  cpl_propertylist_append_string(cube->header, "CTYPE1", "PIXEL");
2207  cpl_propertylist_append_string(cube->header, "CUNIT1", "pixel");
2208  cpl_propertylist_append_double(cube->header, "CD1_1", aParams->dx);
2209  cpl_propertylist_append_double(cube->header, "CRPIX2", 1.);
2210  cpl_propertylist_append_double(cube->header, "CRVAL2",
2211  cpl_propertylist_get_float(aPixtable->header,
2212  MUSE_HDR_PT_YLO));
2213  cpl_propertylist_append_string(cube->header, "CTYPE2", "PIXEL");
2214  cpl_propertylist_append_string(cube->header, "CUNIT2", "pixel");
2215  cpl_propertylist_append_double(cube->header, "CD2_2", aParams->dy);
2216  /* fill in empty cross-terms of the CDi_j matrix */
2217  cpl_propertylist_append_double(cube->header, "CD1_2", 0.);
2218  cpl_propertylist_append_double(cube->header, "CD2_1", 0.);
2219  }
2220  switch (aParams->tlambda) {
2222  cpl_propertylist_append_string(cube->header, "CTYPE3", "AWAV");
2223  break;
2225  cpl_propertylist_append_string(cube->header, "CTYPE3", "AWAV-LOG");
2226  break;
2228  cpl_propertylist_append_string(cube->header, "CTYPE3", "WAVE");
2229  break;
2231  cpl_propertylist_append_string(cube->header, "CTYPE3", "WAVE-LOG");
2232  break;
2233  default:
2234  cpl_propertylist_append_string(cube->header, "CTYPE3", "UNKNOWN");
2235  } /* switch */
2236  cpl_propertylist_append_string(cube->header, "CUNIT3", "Angstrom");
2237  cpl_propertylist_append_double(cube->header, "CD3_3", aParams->dlambda);
2238  cpl_propertylist_append_double(cube->header, "CRPIX3", 1.);
2239  cpl_propertylist_append_double(cube->header, "CRVAL3",
2240  cpl_propertylist_get_float(aPixtable->header,
2241  MUSE_HDR_PT_LLO));
2242  /* fill in empty cross-terms of the CDi_j matrix */
2243  cpl_propertylist_append_double(cube->header, "CD1_3", 0.);
2244  cpl_propertylist_append_double(cube->header, "CD2_3", 0.);
2245  cpl_propertylist_append_double(cube->header, "CD3_1", 0.);
2246  cpl_propertylist_append_double(cube->header, "CD3_2", 0.);
2247  /* transfer flat-spectrum correction status */
2248  if (cpl_propertylist_has(aPixtable->header, MUSE_HDR_PT_FFCORR)) {
2249  cpl_propertylist_append_bool(cube->header, MUSE_HDR_FLUX_FFCORR, CPL_TRUE);
2250  cpl_propertylist_set_comment(cube->header, MUSE_HDR_FLUX_FFCORR,
2251  MUSE_HDR_FLUX_FFCORR_C);
2252  }
2253  /* after everything has been transferred from the pixel table headers, *
2254  * see if we want to override anything from an OUTPUT_WCS setup */
2255  muse_resampling_override_wcs(cube, aParams);
2256  /* check, if we need to move the zeropoint to fit the data into the grid */
2257  muse_resampling_fit_data_to_grid(cube, aPixtable);
2258 
2259  if (aParams->method < MUSE_RESAMPLE_NONE) {
2260  /* fill the cube for the data */
2261  cube->data = cpl_imagelist_new();
2262  cube->dq = cpl_imagelist_new();
2263  cube->stat = cpl_imagelist_new();
2264  int i;
2265  for (i = 0; i < zsize; i++) {
2266  cpl_image *image = cpl_image_new(xsize, ysize, CPL_TYPE_FLOAT);
2267  /* set as last image in the list to extend the list size by one */
2268  cpl_imagelist_set(cube->data, image, i);
2269  /* can use the same (empty) image for the variance for now */
2270  cpl_imagelist_set(cube->stat, cpl_image_duplicate(image), i);
2271 
2272  /* the other two are part of their lists now, we can reuse *
2273  * the variable without leaking */
2274  image = cpl_image_new(xsize, ysize, CPL_TYPE_INT);
2275  /* pre-fill with Euro3D status for outside data range */
2276  cpl_image_add_scalar(image, EURO3D_OUTSDRANGE);
2277  cpl_imagelist_set(cube->dq, image, i);
2278  } /* for i (all wavelength planes) */
2279  } /* if method not MUSE_RESAMPLE_NONE */
2280 
2281  muse_utils_memory_dump("muse_resampling_cube() before pixgrid");
2282  /* convert the pixel table into a pixel grid */
2283  muse_pixgrid *grid = muse_pixgrid_create(aPixtable, cube->header,
2284  xsize, ysize, zsize);
2285 
2286  if (aParams->crsigma > 0) {
2287  muse_resampling_crreject(aPixtable, grid, aParams->crtype,
2288  aParams->crsigma);
2289  }
2290  muse_utils_memory_dump("muse_resampling_cube() after pixgrid and crreject");
2291 
2292  double timeinit = cpl_test_get_walltime(),
2293  cpuinit = cpl_test_get_cputime();
2294 
2295  /* do the resampling */
2296  cpl_error_code rc = CPL_ERROR_NONE;
2297  switch (aParams->method) {
2298  case MUSE_RESAMPLE_NEAREST:
2299  cpl_msg_info(__func__, "Starting resampling, using method \"nearest\"");
2300  rc = muse_resampling_cube_nearest(cube, aPixtable, grid);
2301  break;
2303  cpl_msg_info(__func__, "Starting resampling, using method \"renka\" "
2304  "(critical radius rc=%f, loop distance ld=%d)", aParams->rc,
2305  aParams->ld);
2306  rc = muse_resampling_cube_weighted(cube, aPixtable, grid, aParams);
2307  break;
2311  cpl_msg_info(__func__, "Starting resampling, using method \"%s\" (loop "
2312  "distance ld=%d)",
2314  ? "linear"
2316  ? "quadratic"
2317  : "lanczos"),
2318  aParams->ld);
2319  rc = muse_resampling_cube_weighted(cube, aPixtable, grid, aParams);
2320  break;
2322  cpl_msg_info(__func__, "Starting resampling, using method \"drizzle\" "
2323  "(pixfrac f=%.3f,%.3f,%.3f, loop distance ld=%d)",
2324  aParams->pfx, aParams->pfy, aParams->pfl, aParams->ld);
2325  rc = muse_resampling_cube_weighted(cube, aPixtable, grid, aParams);
2326  break;
2327  case MUSE_RESAMPLE_NONE:
2328  cpl_msg_debug(__func__, "Method %d (no resampling)", aParams->method);
2329  break;
2330  default:
2331  cpl_msg_error(__func__, "Don't know this resampling method: %d",
2332  aParams->method);
2333  cpl_error_set(__func__, CPL_ERROR_UNSUPPORTED_MODE);
2334  rc = CPL_ERROR_UNSUPPORTED_MODE;
2335  }
2336  muse_utils_memory_dump("muse_resampling_cube() after resampling");
2337 
2338  double timefini = cpl_test_get_walltime(),
2339  cpufini = cpl_test_get_cputime();
2340 
2341  /* now that we have resampled we can either remove the pixel grid or save it */
2342  if (aGrid) {
2343  *aGrid = grid;
2344  } else {
2345  muse_pixgrid_delete(grid);
2346  }
2347 
2348  cpl_msg_debug(__func__, "resampling took %.3fs (wall-clock) and %.3fs "
2349  "(%.3fs CPU, %d CPUs) for muse_resampling_cube*() alone",
2350  timefini - time, timefini - timeinit, cpufini - cpuinit,
2351  omp_get_max_threads());
2352  if (rc != CPL_ERROR_NONE) {
2353  cpl_msg_error(__func__, "resampling failed: %s", cpl_error_get_message());
2354  muse_datacube_delete(cube);
2355  return NULL;
2356  }
2357 
2358  muse_utils_memory_dump("muse_resampling_cube() end");
2359  return cube;
2360 } /* muse_resampling_cube() */
2361 
2362 /*---------------------------------------------------------------------------*/
2392 /*---------------------------------------------------------------------------*/
2393 muse_image *
2395  muse_datacube *aCube, const muse_table *aFilter,
2396  muse_resampling_params *aParams)
2397 {
2398  cpl_ensure(aPixtable && aPixtable->table && aGrid && aParams &&
2399  aCube && aCube->header, CPL_ERROR_NULL_INPUT, NULL);
2400  cpl_ensure(aParams->method < MUSE_RESAMPLE_NONE &&
2401  aParams->method > MUSE_RESAMPLE_NEAREST, CPL_ERROR_ILLEGAL_INPUT,
2402  NULL);
2403 
2404  muse_wcs *wcs = muse_wcs_new(aCube->header);
2406  cpl_errorstate prestate = cpl_errorstate_get();
2407  float *xpos = cpl_table_get_data_float(aPixtable->table, MUSE_PIXTABLE_XPOS),
2408  *ypos = cpl_table_get_data_float(aPixtable->table, MUSE_PIXTABLE_YPOS),
2409  *lbda = cpl_table_get_data_float(aPixtable->table, MUSE_PIXTABLE_LAMBDA),
2410  *data = cpl_table_get_data_float(aPixtable->table, MUSE_PIXTABLE_DATA),
2411  *stat = cpl_table_get_data_float(aPixtable->table, MUSE_PIXTABLE_STAT),
2412  *wght = cpl_table_get_data_float(aPixtable->table, MUSE_PIXTABLE_WEIGHT);
2413  if (!cpl_errorstate_is_equal(prestate)) {
2414  cpl_errorstate_set(prestate); /* recover from missing weight column */
2415  }
2416  int *dq = cpl_table_get_data_int(aPixtable->table, MUSE_PIXTABLE_DQ);
2417 
2418  /* If our data was astrometrically calibrated, we need to scale the *
2419  * data units to the pixel size in all three dimensions so that the *
2420  * radius computation works again. *
2421  * Otherwise dx~5.6e-5deg won't contribute to the weighting at all. */
2422  double xnorm = 1., ynorm = 1.,
2423  ptxoff = 0., /* zero by default ... */
2424  ptyoff = 0.; /* for pixel coordinates */
2425  if (wcs->iscelsph) {
2426  muse_wcs_get_scales(aPixtable->header, &xnorm, &ynorm);
2427  xnorm = 1. / xnorm;
2428  ynorm = 1. / ynorm;
2429  /* need to use the real coordinate offset for celestial spherical */
2430  ptxoff = muse_pfits_get_crval(aPixtable->header, 1);
2431  ptyoff = muse_pfits_get_crval(aPixtable->header, 2);
2432  }
2433  /* scale the input critical radius by the voxel radius */
2434  double renka_rc = aParams->rc /* XXX beware of rotation! */
2435  * sqrt(pow(wcs->cd11*xnorm, 2) + pow(wcs->cd22*xnorm, 2));
2436  /* loop distance (to take into account surrounding pixels) verification */
2437  int ld = aParams->ld;
2438  if (ld <= 0) {
2439  ld = 1;
2440  cpl_msg_info(__func__, "Overriding loop distance ld=%d", ld);
2441  }
2442  /* pixel sizes in the spatial directions, scaled by pixfrac, and *
2443  * output pixel sizes (absolute values), as needed for drizzle */
2444  double xsz = aParams->pfx / xnorm,
2445  ysz = aParams->pfy / ynorm,
2446  xout = fabs(wcs->cd11), yout = fabs(wcs->cd22);
2447 
2448  muse_image *image = muse_image_new();
2449  image->data = cpl_image_new(aGrid->nx, aGrid->ny, CPL_TYPE_FLOAT);
2450  image->dq = cpl_image_new(aGrid->nx, aGrid->ny, CPL_TYPE_INT);
2451  image->stat = cpl_image_new(aGrid->nx, aGrid->ny, CPL_TYPE_FLOAT);
2452  image->header = cpl_propertylist_duplicate(aCube->header);
2453  cpl_propertylist_erase_regexp(image->header, "^C...*3$|^CD3_.$", 0);
2454  float *pdata = cpl_image_get_data_float(image->data),
2455  *pstat = cpl_image_get_data_float(image->stat);
2456  int *pdq = cpl_image_get_data_int(image->dq);
2457 
2458  /* check if we need to use the variance for weighting */
2459  cpl_boolean usevariance = CPL_FALSE;
2460  if (getenv("MUSE_COLLAPSE_USE_VARIANCE") &&
2461  atoi(getenv("MUSE_COLLAPSE_USE_VARIANCE")) > 0) {
2462  usevariance = CPL_TRUE;
2463  }
2464 
2465  /* find filter range and coverage fraction */
2466  cpl_table *filter = aFilter && aFilter->table ? aFilter->table : NULL;
2467  double lmin, lmax;
2468  if (filter) {
2469  const double *flbda = cpl_table_get_data_double_const(filter, "lambda"),
2470  *fresp = cpl_table_get_data_double_const(filter, "throughput");
2471  int l = 0, nl = cpl_table_get_nrow(filter);
2472  while (l < nl && fabs(fresp[l]) < DBL_EPSILON) {
2473  lmin = flbda[l++];
2474  }
2475  l = nl - 1;
2476  while (l > 0 && fabs(fresp[l]) < DBL_EPSILON) {
2477  lmax = flbda[l--];
2478  }
2479  cpl_msg_debug(__func__, "filter wavelength range %.1f..%.1fA", lmin, lmax);
2480  /* compute filter coverage and propagate filter properties */
2481  double ffraction = muse_utils_filter_fraction(aFilter, lmin, lmax);
2482  muse_utils_filter_copy_properties(image->header, aFilter, ffraction);
2483  } else {
2484  lmin = cpl_propertylist_get_float(aPixtable->header, MUSE_HDR_PT_LLO);
2485  lmax = cpl_propertylist_get_float(aPixtable->header, MUSE_HDR_PT_LHI);
2486  cpl_msg_debug(__func__, "full wavelength range %.1f..%.1fA", lmin, lmax);
2487  }
2488 
2489  int i;
2490  #pragma omp parallel for default(none) /* as req. by Ralf */ \
2491  shared(aParams, aGrid, data, dq, filter, lbda, ld, lmax, lmin, pdata,\
2492  pdq, pstat, ptxoff, ptyoff, renka_rc, stat, usevariance, wcs, \
2493  wght, xnorm, xout, xpos, xsz, ynorm, yout, ypos, ysz)
2494  for (i = 0; i < aGrid->nx; i++) {
2495  int j;
2496  for (j = 0; j < aGrid->ny; j++) {
2497  /* x and y position of center of current grid cell (i, j start at 0) */
2498  double x, y;
2499  if (wcs->iscelsph) {
2500  muse_wcs_celestial_from_pixel_fast(wcs, i + 1, j + 1, &x, &y);
2501  } else {
2502  muse_wcs_projplane_from_pixel_fast(wcs, i + 1, j + 1, &x, &y);
2503  }
2504  double sumdata = 0, sumstat = 0, sumweight = 0;
2505  cpl_size npoints = 0;
2506 
2507  /* loop through surrounding cells and their contained pixels */
2508  int i2;
2509  for (i2 = i - ld; i2 <= i + ld; i2++) {
2510  int j2;
2511  for (j2 = j - ld; j2 <= j + ld; j2++) {
2512  /* loop over full wavelength range! */
2513  int l2;
2514  for (l2 = 0; l2 < aGrid->nz; l2++) {
2515  cpl_size idx2 = muse_pixgrid_get_index(aGrid, i2, j2, l2, CPL_FALSE),
2516  n, n_rows2 = muse_pixgrid_get_count(aGrid, idx2);
2517  const cpl_size *rows2 = muse_pixgrid_get_rows(aGrid, idx2);
2518  for (n = 0; n < n_rows2; n++) {
2519  if (dq[rows2[n]]) { /* exclude all bad pixels */
2520  continue;
2521  }
2522  if (usevariance && !isnormal(stat[rows2[n]])) {
2523  continue; /* weighting by inv. variance doesn`t work with extremes */
2524  }
2525  if (lbda[rows2[n]] > lmax || lbda[rows2[n]] < lmin) {
2526  continue; /* exclude pixels outside the filter range */
2527  }
2528 
2529  double dx = fabs(x - (xpos[rows2[n]] + ptxoff)),
2530  dy = fabs(y - (ypos[rows2[n]] + ptyoff)),
2531  r2 = 0;
2532  if (wcs->iscelsph) {
2533  /* Make the RA distance comparable to pixel *
2534  * sizes, see muse_resampling_cube_weighted(). */
2535  dx *= cos(y * CPL_MATH_RAD_DEG);
2536  }
2537  if (aParams->method != MUSE_RESAMPLE_WEIGHTED_DRIZZLE) {
2538  dx *= xnorm;
2539  dy *= ynorm;
2540  r2 = dx*dx + dy*dy;
2541  }
2542  double weight = 0.;
2543  if (aParams->method == MUSE_RESAMPLE_WEIGHTED_RENKA) {
2544  weight = muse_resampling_weight_function_renka(sqrt(r2), renka_rc);
2545  } else if (aParams->method == MUSE_RESAMPLE_WEIGHTED_DRIZZLE) {
2546  weight = muse_resampling_weight_function_drizzle(xsz, ysz, 1.,
2547  xout, yout, 1.,
2548  dx, dy, 0.);
2549  } else if (aParams->method == MUSE_RESAMPLE_WEIGHTED_LINEAR) {
2550  weight = muse_resampling_weight_function_linear(sqrt(r2));
2551  } else if (aParams->method == MUSE_RESAMPLE_WEIGHTED_QUADRATIC) {
2552  weight = muse_resampling_weight_function_quadratic(r2);
2553  } else if (aParams->method == MUSE_RESAMPLE_WEIGHTED_LANCZOS) {
2554  weight = muse_resampling_weight_function_lanczos(dx, dy, 0., ld);
2555  }
2556 
2557  if (wght) { /* the pixel table does contain weights */
2558  /* apply it on top of the weight computed here */
2559  weight *= wght[rows2[n]];
2560  }
2561  /* apply the filter curve on top of the weights, if a filter *
2562  * was given; otherwise the weight stays unchanged */
2563  if (filter) {
2564  weight *= muse_flux_response_interpolate(filter, lbda[rows2[n]],
2565  NULL, MUSE_FLUX_RESP_FILTER);
2566  }
2567  if (usevariance) { /* weight by inverse variance */
2568  weight /= stat[rows2[n]];
2569  }
2570  sumweight += weight;
2571  sumdata += data[rows2[n]] * weight;
2572  sumstat += stat[rows2[n]] * weight*weight;
2573  npoints++;
2574  } /* for n (all pixels in grid cell) */
2575  } /* for l2 (lambda direction) */
2576  } /* for j2 (y direction) */
2577  } /* for i2 (x direction) */
2578 
2579  /* if no points were found, we cannot divide by the summed weight *
2580  * and don't need to set the output pixel value (it's 0 already), *
2581  * only set the relevant Euro3D bad pixel flag */
2582  if (!npoints || !isnormal(sumweight)) {
2583  pdq[i + j * aGrid->nx] = EURO3D_MISSDATA;
2584  continue;
2585  }
2586 
2587  /* divide results by weight of summed pixels */
2588  sumdata /= sumweight;
2589  sumstat /= sumweight*sumweight;
2590  pdata[i + j * aGrid->nx] = sumdata;
2591  pstat[i + j * aGrid->nx] = sumstat;
2592  pdq[i + j * aGrid->nx] = EURO3D_GOODPIXEL; /* now we can mark it as good */
2593  } /* for j (y direction) */
2594  } /* for i (x direction) */
2595  cpl_free(wcs);
2596 
2597  return image;
2598 } /* muse_resampling_collapse_pixgrid() */
2599 
2600 
2601 /*----------------------------------------------------------------------------*
2602  * Resampling and collapsing in 2D *
2603  *----------------------------------------------------------------------------*/
2604 
2605 /*---------------------------------------------------------------------------*/
2621 /*---------------------------------------------------------------------------*/
2622 static cpl_error_code
2623 muse_resampling_image_nearest(muse_image *aImage, muse_pixtable *aPixtable,
2624  muse_pixgrid *aGrid)
2625 {
2626  cpl_ensure_code(aImage && aPixtable && aGrid, CPL_ERROR_NULL_INPUT);
2627  aImage->data = cpl_image_new(aGrid->nx, aGrid->nz,
2628  CPL_TYPE_FLOAT);
2629 
2630  double crval2 = muse_pfits_get_crval(aImage->header, 2),
2631  cd22 = muse_pfits_get_cd(aImage->header, 2, 2);
2632  /* get all (relevant) table columns for easy pointer access */
2633  float *lbda = cpl_table_get_data_float(aPixtable->table, MUSE_PIXTABLE_LAMBDA),
2634  *data = cpl_table_get_data_float(aPixtable->table, MUSE_PIXTABLE_DATA);
2635  int *dq = cpl_table_get_data_int(aPixtable->table, MUSE_PIXTABLE_DQ);
2636  float *imagedata = cpl_image_get_data_float(aImage->data);
2637 
2638 #ifdef ESO_ENABLE_DEBUG
2639  int debug = 0;
2640  if (getenv("MUSE_DEBUG_NEAREST")) {
2641  debug = atoi(getenv("MUSE_DEBUG_NEAREST"));
2642  }
2643 #endif
2644 
2645  int i;
2646  for (i = 0; i < aGrid->nx; i++) {
2647  int j;
2648  for (j = 0; j < aGrid->nz; j++) {
2649  cpl_size idx = muse_pixgrid_get_index(aGrid, i, 0, j, CPL_FALSE),
2650  n_rows = muse_pixgrid_get_count(aGrid, idx);
2651  const cpl_size *rows = muse_pixgrid_get_rows(aGrid, idx);
2652  if (n_rows == 1 && !dq[rows[0]]) {
2653  /* if there is only one (good) pixel in the cell, just use it */
2654  imagedata[i + j * aGrid->nx] = data[rows[0]];
2655 #ifdef ESO_ENABLE_DEBUG
2656  if (debug) {
2657  printf("only: %f\n", data[rows[0]]);
2658  fflush(stdout);
2659  }
2660 #endif
2661  } else if (n_rows >= 2) {
2662  /* loop through all available values and take the closest one */
2663  cpl_size n, nbest = -1;
2664  double dbest = FLT_MAX; /* some unlikely large value to start with*/
2665  for (n = 0; n < n_rows; n++) {
2666  if (dq[rows[n]]) { /* exclude all bad pixels */
2667  continue;
2668  }
2669  /* the differences for the cell center and the current pixel; *
2670  * for simplicitly, just compare the difference in lambda */
2671  double dlambda = fabs(j * cd22 + crval2 - lbda[rows[n]]);
2672 #ifdef ESO_ENABLE_DEBUG
2673  if (debug) {
2674  printf("%f, %f, d: %f best: %f (%f)\n",
2675  j * cd22 + crval2, lbda[rows[n]],
2676  dlambda, dbest, data[rows[n]]);
2677  }
2678 #endif
2679  if (dlambda < dbest) {
2680  nbest = n;
2681  dbest = dlambda;
2682  }
2683  }
2684  imagedata[i + j * aGrid->nx] = data[rows[nbest]];
2685 #ifdef ESO_ENABLE_DEBUG
2686  if (debug) {
2687  printf("closest: %f\n", data[rows[nbest]]);
2688  fflush(stdout);
2689  }
2690 #endif
2691  } else {
2692  /* npix == 0: do nothing, pixel stays zero */
2693  }
2694  } /* for j (y direction = wavelength planes) */
2695  } /* for i (x direction) */
2696 
2697  return CPL_ERROR_NONE;
2698 } /* muse_resampling_image_nearest() */
2699 
2700 /*---------------------------------------------------------------------------*/
2717 /*---------------------------------------------------------------------------*/
2718 static cpl_error_code
2719 muse_resampling_image_weighted(muse_image *aImage, muse_pixtable *aPixtable,
2720  muse_pixgrid *aGrid)
2721 {
2722  cpl_ensure_code(aImage && aPixtable && aGrid, CPL_ERROR_NULL_INPUT);
2723  aImage->data = cpl_image_new(aGrid->nx, aGrid->nz,
2724  CPL_TYPE_FLOAT);
2725 
2726  double crval2 = muse_pfits_get_crval(aImage->header, 2),
2727  cd22 = muse_pfits_get_cd(aImage->header, 2, 2);
2728  /* get all (relevant) table columns for easy pointer access */
2729  float *lbda = cpl_table_get_data_float(aPixtable->table, MUSE_PIXTABLE_LAMBDA),
2730  *data = cpl_table_get_data_float(aPixtable->table, MUSE_PIXTABLE_DATA);
2731  int *dq = cpl_table_get_data_int(aPixtable->table, MUSE_PIXTABLE_DQ);
2732  float *imagedata = cpl_image_get_data_float(aImage->data);
2733 
2734 #ifdef ESO_ENABLE_DEBUG
2735  int debug = 0, debugx = 0, debugz = 0;
2736  if (getenv("MUSE_DEBUG_WEIGHTED")) {
2737  debug = atoi(getenv("MUSE_DEBUG_WEIGHTED"));
2738  }
2739  if (debug & 128) { /* need coordinates */
2740  if (getenv("MUSE_DEBUG_WEIGHTED_X")) {
2741  debugx = atoi(getenv("MUSE_DEBUG_WEIGHTED_X"));
2742  if (debugx < 1 || debugx > aGrid->nx) {
2743  debugx = 0;
2744  }
2745  }
2746  if (getenv("MUSE_DEBUG_WEIGHTED_Z")) {
2747  debugz = atoi(getenv("MUSE_DEBUG_WEIGHTED_Z"));
2748  if (debugz < 1 || debugz > aGrid->nz) {
2749  debugz = 0;
2750  }
2751  }
2752  }
2753 #endif
2754 
2755  int ld = 1; /* loop distance, to take into account surrounding pixels */
2756  /* Renka critical radius; only 1.25 to 1.5 makes sense in *
2757  * this 1D interpolation, hardcode to 1.25 for the moment */
2758  double renka_rc = 1.25;
2759  /* scale critical radius by the scale factor in wavelength */
2760  renka_rc *= cd22;
2761 
2762  int i;
2763  for (i = 0; i < aGrid->nx; i++) {
2764  int j;
2765  for (j = 0; j < aGrid->nz; j++) {
2766  double sumdata = 0, sumweight = 0,
2767  lambda = j * cd22 + crval2;
2768  int npoints = 0;
2769 #ifdef ESO_ENABLE_DEBUG
2770 #define MAX_NPIX_2D 50 /* allow for quite a few pixels */
2771  int *pointlist = NULL;
2772  double *pointweights = NULL;
2773  if (debug & 128 && i+1 == debugx && j+1 == debugz) {
2774  pointlist = cpl_calloc(MAX_NPIX_2D, sizeof(int));
2775  pointweights = cpl_malloc(MAX_NPIX_2D * sizeof(double));
2776  }
2777 #endif
2778 
2779  /* go through this and the surrounding cells and their contained pixels */
2780  int j2;
2781  for (j2 = j - ld; j2 <= j + ld; j2++) {
2782  cpl_size idx = muse_pixgrid_get_index(aGrid, i, 0, j2, CPL_FALSE),
2783  n, n_rows = muse_pixgrid_get_count(aGrid, idx);
2784  const cpl_size *rows = muse_pixgrid_get_rows(aGrid, idx);
2785  for (n = 0; n < n_rows; n++) {
2786  if (dq[rows[n]]) { /* exclude all bad pixels */
2787  continue;
2788  }
2789  double dlambda = fabs(lambda - lbda[rows[n]]),
2790  weight = muse_resampling_weight_function_renka(dlambda,
2791  renka_rc);
2792  sumweight += weight;
2793  sumdata += data[rows[n]] * weight;
2794  npoints++;
2795 #ifdef ESO_ENABLE_DEBUG
2796  if (debug & 128 && i+1 == debugx && j+1 == debugz &&
2797  npoints-1 < MAX_NPIX_2D) {
2798  /* store row number instead of index, because we cannot be zero: */
2799  pointlist[npoints-1] = rows[n] + 1;
2800  pointweights[npoints-1] = weight;
2801  }
2802 
2803  if (debug & 256) {
2804  printf(" pixel %4d,%4d (%8"CPL_SIZE_FORMAT"): %2"CPL_SIZE_FORMAT
2805  " %2"CPL_SIZE_FORMAT" %f, %f -> %f ==> %f %f (%d)\n",
2806  i, j2, idx, n, muse_pixgrid_get_count(aGrid, idx), dlambda,
2807  data[muse_pixgrid_get_rows(aGrid, idx)[n]],
2808  weight, sumweight, sumdata, npoints);
2809  }
2810 #endif
2811  } /* for n (all pixels in grid cell) */
2812  } /* for j2 (x direction) */
2813 
2814 #ifdef ESO_ENABLE_DEBUG
2815  if (debug & 128 && i+1 == debugx && j+1 == debugz) {
2816  printf("pixelnumber weight ");
2817  muse_pixtable_dump(aPixtable, 0, 0, 2);
2818  int m = -1;
2819  while (++m < MAX_NPIX_2D && pointlist[m] != 0) {
2820  /* access row using index again: */
2821  printf("%12d %8.5f ", pointlist[m] - 1, pointweights[m]);
2822  muse_pixtable_dump(aPixtable, pointlist[m] - 1, 1, 0);
2823  }
2824  fflush(stdout);
2825  cpl_free(pointlist);
2826  cpl_free(pointweights);
2827  }
2828  if (debug) {
2829  fflush(stdout); /* flush the output in any debug case */
2830  }
2831 #endif
2832  /* if no points were found, we cannot divide by the summed weight *
2833  * and don't need to set the output pixel value (it's 0 already) */
2834  if (!npoints) {
2835  continue;
2836  }
2837  /* divide results by weight of summed pixels */
2838  imagedata[i + j * aGrid->nx] = sumdata / sumweight;
2839  } /* for j (y direction = wavelength planes) */
2840  } /* for i (x direction) */
2841 
2842  return CPL_ERROR_NONE;
2843 } /* muse_resampling_image_weighted() */
2844 
2845 /*---------------------------------------------------------------------------*/
2862 /*---------------------------------------------------------------------------*/
2863 static muse_image *
2864 muse_resampling_image_selected(muse_pixtable *aPixtable,
2865  muse_resampling_type aMethod,
2866  double aDX, double aLambdaMin,
2867  double aLambdaMax, double aDLambda)
2868 {
2869  double dlambda = aDLambda;
2870  float xmin = 0.;
2871  muse_pixgrid *grid = muse_pixgrid_2d_create(aPixtable->table, aDX,
2872  aLambdaMin, aLambdaMax,
2873  dlambda, &xmin);
2874 
2875  /* create output image and add FITS header to it (we already need *
2876  * CRVAL2 and CD2_2 in the sub-functions called from here) */
2877  muse_image *image = muse_image_new();
2878  image->header = cpl_propertylist_new();
2879  const char *unit = cpl_table_get_column_unit(aPixtable->table, "data");
2880  cpl_propertylist_append_string(image->header, "BUNIT", unit);
2881  /* copy input header and append the necessary WCS keywords *
2882  * WCSAXES must preceed all other keywords, but defaults to NAXIS, *
2883  * so we can ignore this here for simplicity */
2884  cpl_propertylist_copy_property_regexp(image->header, aPixtable->header,
2885  MUSE_HDR_PT_REGEXP"|"MUSE_WCS_KEYS, 1);
2886  cpl_propertylist_append_double(image->header, "CRPIX1", 1.);
2887  cpl_propertylist_append_double(image->header, "CRPIX2", 1.);
2888  cpl_propertylist_append_double(image->header, "CRVAL1", 1.);
2889  cpl_propertylist_append_double(image->header, "CRVAL2", aLambdaMin);
2890  cpl_propertylist_append_double(image->header, "CD1_1", 1.);
2891  cpl_propertylist_append_double(image->header, "CD2_2", dlambda);
2892  /* units verified using FITS standard v3.0 */
2893  cpl_propertylist_append_string(image->header, "CUNIT1", "pixel");
2894  cpl_propertylist_append_string(image->header, "CUNIT2", "Angstrom");
2895  /* for linear axes CTYPE doesn't matter, as long as it's not in 4-3 form, *
2896  * see Greisen & Calabretta 2002 A&A 395, 1061 */
2897  cpl_propertylist_append_string(image->header, "CTYPE1", "PIXEL");
2898  /* although DS9 seems to like "LAMBDA" better, the type (for the wavelength *
2899  * direction) was verified using Greisen et al. 2006 A&A 446, 747 */
2900  cpl_propertylist_append_string(image->header, "CTYPE2", "AWAV");
2901  /* fill in empty cross-terms of the CDi_j matrix */
2902  cpl_propertylist_append_double(image->header, "CD1_2", 0.);
2903  cpl_propertylist_append_double(image->header, "CD2_1", 0.);
2904 
2905  /* do the resampling */
2906  cpl_error_code rc = CPL_ERROR_NONE;
2907  switch (aMethod) {
2908  case MUSE_RESAMPLE_NEAREST:
2909  rc = muse_resampling_image_nearest(image, aPixtable, grid);
2910  break;
2911  default: /* MUSE_RESAMPLE_WEIGHTED_RENKA */
2912  /* the caller is responsible for checking for other methods */
2913  rc = muse_resampling_image_weighted(image, aPixtable, grid);
2914  }
2915 
2916  muse_pixgrid_delete(grid);
2917  if (rc != CPL_ERROR_NONE) {
2918  cpl_msg_error(__func__, "resampling failed: %s", cpl_error_get_message());
2919  muse_image_delete(image);
2920  return NULL;
2921  }
2922 
2923  return image;
2924 } /* muse_resampling_image_selected() */
2925 
2926 /*---------------------------------------------------------------------------*/
2966 /*---------------------------------------------------------------------------*/
2967 muse_image *
2969  double aDX, double aDLambda)
2970 {
2971  cpl_ensure(aPixtable, CPL_ERROR_NULL_INPUT, NULL);
2972  if (aDLambda == 0.0) {
2973  aDLambda = kMuseSpectralSamplingA;
2974  }
2975  muse_pixtable_wcs wcstype = muse_pixtable_wcs_check(aPixtable);
2976  cpl_ensure(wcstype == MUSE_PIXTABLE_WCS_CELSPH ||
2977  wcstype == MUSE_PIXTABLE_WCS_PIXEL, CPL_ERROR_UNSUPPORTED_MODE,
2978  NULL);
2979 
2980  /* check method and give info message */
2981  switch (aMethod) {
2982  case MUSE_RESAMPLE_NEAREST:
2983  cpl_msg_info(__func__, "Using nearest neighbor sampling (%d) along "
2984  "wavelengths.", aMethod);
2985  break;
2987  cpl_msg_info(__func__, "Using renka-weighted interpolation (%d) along "
2988  "wavelengths.", aMethod);
2989  break;
2990  default:
2991  cpl_msg_error(__func__, "Don't know this resampling method: %d", aMethod);
2992  cpl_error_set(__func__, CPL_ERROR_UNSUPPORTED_MODE);
2993  return NULL;
2994  }
2995 
2996  /* compute the size of the output grid depending on the inputs and the *
2997  * data available in the pixel table */
2998  float lmin = cpl_propertylist_get_float(aPixtable->header, MUSE_HDR_PT_LLO),
2999  lmax = cpl_propertylist_get_float(aPixtable->header, MUSE_HDR_PT_LHI);
3000 
3002  /* Pretend that we are dealing with only one slice. *
3003  * We can do that because the pixel table was created in such *
3004  * a way that no slice overlaps in x-direction with another. */
3005  return muse_resampling_image_selected(aPixtable, aMethod,
3006  aDX == 0.0 ? 1. : aDX,
3007  lmin, lmax, aDLambda);
3008  }
3009 
3010  /* For pixel tables with full geometry information, resample each *
3011  * slice of each IFU into an image one by one and stack them next *
3012  * to each other (horizontally) in a large output image. */
3013  muse_pixtable **slice_pixtable = muse_pixtable_extracted_get_slices(aPixtable);
3014  int n_slices = muse_pixtable_extracted_get_size(slice_pixtable);
3015 
3016  double dx = aDX;
3017  if (dx == 0.0) {
3019  dx = 1.0;
3020  } else {
3021  double xsc, ysc;
3022  muse_wcs_get_scales(aPixtable->header, &xsc, &ysc);
3023  /* XXX need such an extra 1.2 factor to get good results: */
3024  dx = 1.20 * xsc;
3025  }
3026  }
3027  cpl_msg_debug(__func__, "Resampling %d slices to a 2D image, using bins of"
3028  " %e %s x %.3f Angstrom", n_slices, dx,
3029  cpl_table_get_column_unit(aPixtable->table, MUSE_PIXTABLE_XPOS),
3030  aDLambda);
3031 
3032  /* build one stacked image per slice */
3033  muse_image *slice_image[n_slices];
3034  int i_slice;
3035  #pragma omp parallel for default(none) /* as req. by Ralf */ \
3036  shared(aDLambda, aMethod, dx, lmax, lmin, n_slices, \
3037  slice_image, slice_pixtable)
3038  for (i_slice = 0; i_slice < n_slices; i_slice++) {
3039 #if 0
3040  cpl_msg_debug(__func__, "slice pixel table %d", i_slice + 1);
3041 #endif
3042  muse_pixtable *pt = slice_pixtable[i_slice];
3043  if (muse_pixtable_get_nrow(pt) < 1) {
3044  slice_image[i_slice] = NULL;
3045  continue;
3046  }
3047  slice_image[i_slice] = muse_resampling_image_selected(pt, aMethod, dx,
3048  lmin, lmax, aDLambda);
3049  } /* for i_slice */
3050 
3051  /* concatenate all images into one large output image */
3052  muse_image *image = muse_image_new();
3053  for (i_slice = 0; i_slice < n_slices; i_slice++) {
3054  muse_image *slice = slice_image[i_slice];
3055 #if 0
3056  cpl_msg_debug(__func__, "slice %d: %ldx%ld", i_slice + 1,
3057  cpl_image_get_size_x(slice->data), cpl_image_get_size_y(slice->data));
3058 #endif
3059  if (slice == NULL) {
3060  continue;
3061  }
3062  if (!image->header) {
3063  /* use FITS header from the first slice */
3064  image->header = cpl_propertylist_duplicate(slice->header);
3065  }
3066  cpl_image *data = muse_cplimage_concat_x(image->data, slice->data);
3067  cpl_image_delete(image->data);
3068  image->data = data;
3069  if (slice->dq) {
3070  cpl_image *dq = muse_cplimage_concat_x(image->dq, slice->dq);
3071  cpl_image_delete(image->dq);
3072  image->dq = dq;
3073  }
3074  if (slice->stat) {
3075  cpl_image *stat = muse_cplimage_concat_x(image->stat, slice->stat);
3076  cpl_image_delete(image->stat);
3077  image->stat = stat;
3078  }
3079  muse_image_delete(slice);
3080  slice_image[i_slice] = NULL;
3081  } /* for i_slice */
3082  muse_pixtable_extracted_delete(slice_pixtable);
3083 
3084  /* transfer flat-spectrum correction status */
3085  if (cpl_propertylist_has(aPixtable->header, MUSE_HDR_PT_FFCORR)) {
3086  cpl_propertylist_append_bool(image->header, MUSE_HDR_FLUX_FFCORR, CPL_TRUE);
3087  cpl_propertylist_set_comment(image->header, MUSE_HDR_FLUX_FFCORR,
3088  MUSE_HDR_FLUX_FFCORR_C);
3089  }
3090 
3091  return image;
3092 } /* muse_resampling_image() */
3093 
3094 /*---------------------------------------------------------------------------*/
3110 /*---------------------------------------------------------------------------*/
3111 cpl_table *
3112 muse_resampling_spectrum(muse_pixtable *aPixtable, double aBinwidth)
3113 {
3114  cpl_ensure(aPixtable && aPixtable->header && aPixtable->table,
3115  CPL_ERROR_NULL_INPUT, NULL);
3116  cpl_ensure(muse_cpltable_check(aPixtable->table, muse_pixtable_def)
3117  == CPL_ERROR_NONE, CPL_ERROR_ILLEGAL_INPUT, NULL);
3118 
3119  /* determine spectral range */
3120  double lmin = cpl_propertylist_get_float(aPixtable->header, MUSE_HDR_PT_LLO);
3121  double lmax = cpl_propertylist_get_float(aPixtable->header, MUSE_HDR_PT_LHI);
3122  cpl_size lsize = floor((lmax - lmin) / aBinwidth) + 2;
3123 
3124  /* create the empty spectral table */
3125  cpl_table *spectrum = muse_cpltable_new(muse_dataspectrum_def, lsize);
3126  cpl_table_fill_column_window(spectrum, "lambda", 0, lsize, 0);
3127  cpl_table_fill_column_window(spectrum, "data", 0, lsize, 0);
3128  cpl_table_fill_column_window(spectrum, "stat", 0, lsize, 0);
3129  cpl_table_fill_column_window(spectrum, "dq", 0, lsize, 0);
3130  double *spec_data = cpl_table_get_data_double(spectrum, "data");
3131  double *spec_stat = cpl_table_get_data_double(spectrum, "stat");
3132  double *spec_lbda = cpl_table_get_data_double(spectrum, "lambda");
3133  /* inherit spectral data units from the pixel table */
3134  cpl_table_set_column_unit(spectrum, "data",
3135  cpl_table_get_column_unit(aPixtable->table,
3137  cpl_table_set_column_unit(spectrum, "stat",
3138  cpl_table_get_column_unit(aPixtable->table,
3140 
3141  /* add a temporary column for the integrated weight of each bin */
3142  cpl_table_new_column(spectrum, "weight", CPL_TYPE_DOUBLE);
3143  cpl_table_fill_column_window(spectrum, "weight", 0, lsize, 0);
3144  double *spec_weight = cpl_table_get_data_double(spectrum, "weight");
3145 
3146  /* accessors for the pixel table */
3147  float *lbda = cpl_table_get_data_float(aPixtable->table, MUSE_PIXTABLE_LAMBDA);
3148  float *data = cpl_table_get_data_float(aPixtable->table, MUSE_PIXTABLE_DATA);
3149  float *stat = cpl_table_get_data_float(aPixtable->table, MUSE_PIXTABLE_STAT);
3150  float *wght = NULL;
3151  if (cpl_table_has_column(aPixtable->table, MUSE_PIXTABLE_WEIGHT)) {
3152  wght = cpl_table_get_data_float(aPixtable->table, MUSE_PIXTABLE_WEIGHT);
3153  }
3154  int *dq = cpl_table_get_data_int(aPixtable->table, MUSE_PIXTABLE_DQ);
3155 
3156  /* loop through all selected pixel table rows and sort them *
3157  * into the output bins, assigning them linear weights */
3158  cpl_array *asel = cpl_table_where_selected(aPixtable->table);
3159  const cpl_size *sel = cpl_array_get_data_cplsize_const(asel);
3160  cpl_size isel, nsel = cpl_array_get_size(asel);
3161  for (isel = 0; isel < nsel; isel++) {
3162  cpl_size i_row = sel[isel];
3163  if ((dq[i_row] != EURO3D_GOODPIXEL)) {
3164  continue;
3165  }
3166  // We ignore "inf" and "nan" values here.
3167  // See http://urania1.univ-lyon1.fr/mpdaf/ticket/374
3168  if (! isfinite(data[i_row])) {
3169  continue;
3170  }
3171  /* compute target bins and weights */
3172  double l = (lbda[i_row] - lmin) / aBinwidth;
3173  if (l < 0) l = 0;
3174  cpl_size il = floor(l);
3175  l -= il;
3176  double w0 = 1-l;
3177  double w1 = l;
3178  if (wght) {
3179  w0 *= wght[i_row];
3180  w1 *= wght[i_row];
3181  }
3182  /* weight the data in the two involved bins */
3183  spec_data[il] += w0 * data[i_row];
3184  spec_data[il+1] += w1 * data[i_row];
3185  spec_stat[il] += w0 * stat[i_row];
3186  spec_stat[il+1] += w1 * stat[i_row];
3187  spec_weight[il] += w0;
3188  spec_weight[il+1] += w1;
3189  } /* for isel (selected pixel table rows) */
3190  cpl_array_delete(asel);
3191 
3192  /* assign wavelengths and erase empty spectral bins */
3193  cpl_size ispec;
3194  for (ispec = 0; ispec < lsize; ispec++) {
3195  if (spec_weight[ispec] > 0) {
3196  spec_lbda[ispec] = ispec * aBinwidth + lmin;
3197  cpl_table_unselect_row(spectrum, ispec);
3198  }
3199  } /* for ispec (all spectrum points) */
3200  cpl_table_erase_selected(spectrum);
3201 
3202  /* apply the weights and delete the temporary weight column */
3203  cpl_table_divide_columns(spectrum, "data", "weight");
3204  cpl_table_divide_columns(spectrum, "stat", "weight");
3205  cpl_table_erase_column(spectrum, "weight");
3206  return spectrum;
3207 } /* muse_resampling_spectrum() */
3208 
3209 /*---------------------------------------------------------------------------*/
3240 /*---------------------------------------------------------------------------*/
3241 cpl_table *
3243  float aLo, float aHi, unsigned char aIter)
3244 {
3245  cpl_ensure(aPixtable && aPixtable->header && aPixtable->table,
3246  CPL_ERROR_NULL_INPUT, NULL);
3247  cpl_ensure(muse_cpltable_check(aPixtable->table, muse_pixtable_def)
3248  == CPL_ERROR_NONE, CPL_ERROR_ILLEGAL_INPUT, NULL);
3249 
3250  /* initial resampling */
3251  cpl_table *spectrum = muse_resampling_spectrum(aPixtable, aBinwidth);
3252  if (aIter == 0) {
3253  return spectrum;
3254  }
3255 
3256  /* create easy accessors for the pixel table */
3257  float *lbda = cpl_table_get_data_float(aPixtable->table, MUSE_PIXTABLE_LAMBDA),
3258  *data = cpl_table_get_data_float(aPixtable->table, MUSE_PIXTABLE_DATA);
3259  int *dq = cpl_table_get_data_int(aPixtable->table, MUSE_PIXTABLE_DQ);
3260  /* get row selection of the pixel table */
3261  cpl_array *asel = cpl_table_where_selected(aPixtable->table);
3262  const cpl_size *sel = cpl_array_get_data_cplsize_const(asel);
3263  cpl_size isel, nsel = cpl_array_get_size(asel);
3264 
3265  /* now iterate the spectrum creation */
3266  cpl_size nhi = 0, nlo = 0; /* sum up rejections of all iterations */
3267  unsigned char i;
3268  for (i = 1; i <= aIter; i++) {
3269  cpl_size ispec, nbins = cpl_table_get_nrow(spectrum);
3270  double *sdata = cpl_table_get_data_double(spectrum, "data"),
3271  *sstat = cpl_table_get_data_double(spectrum, "stat"),
3272  *sstddev = cpl_malloc(nbins * sizeof(double));
3273  /* compute standard deviation from the variance of the spectrum */
3274  for (ispec = 0; ispec < nbins; ispec++) {
3275  sstddev[ispec] = sqrt(sstat[ispec]);
3276  }
3277 
3278  /* loop through all selected pixel table rows */
3279  for (isel = 0; isel < nsel; isel++) {
3280  cpl_size irow = sel[isel];
3281  if ((dq[irow] != EURO3D_GOODPIXEL)) {
3282  continue;
3283  }
3284  double l = lbda[irow];
3285  ispec = muse_cpltable_find_sorted(spectrum, "lambda", l);
3286  /* search for the right wavelength */
3287  if ((ispec < nbins - 1) && (sdata[ispec] < sdata[ispec + 1])) {
3288  ispec++;
3289  }
3290  /* flag the pixel if it's outside given high... */
3291  if (aHi > 0. && data[irow] > sdata[ispec] + aHi * sstddev[ispec]) {
3292  dq[irow] = EURO3D_OUTLIER;
3293  nhi++;
3294  }
3295  /* or low boundaries */
3296  if (aLo > 0. && data[irow] < sdata[ispec] - aLo * sstddev[ispec]) {
3297  dq[irow] = EURO3D_OUTLIER;
3298  nlo++;
3299  }
3300  } /* for isel (all selected pixel table rows) */
3301  cpl_free(sstddev);
3302 
3303  /* give some output, only in debug-level (because otherwise *
3304  * users would not understand where the message comes from) */
3305  cpl_msg_debug(__func__, "%"CPL_SIZE_FORMAT" of %"CPL_SIZE_FORMAT" pixels "
3306  "are outliers (%"CPL_SIZE_FORMAT" low and %"CPL_SIZE_FORMAT
3307  " high, after %hhu iteration%s)", nlo + nhi, nsel, nlo, nhi,
3308  i, i == 1 ? "" : "s");
3309 
3310  cpl_table_delete(spectrum);
3311  spectrum = muse_resampling_spectrum(aPixtable, aBinwidth);
3312  } /* for i (all iterations) */
3313  cpl_array_delete(asel);
3314 
3315  /* reinstate the original pixel table flags */
3316  muse_pixtable_reset_dq(aPixtable, EURO3D_OUTLIER);
3317 
3318  return spectrum;
3319 } /* muse_resampling_spectrum_iterate() */
3320 
#define MUSE_PIXTABLE_DQ
Definition: muse_pixtable.h:49
#define MUSE_PIXTABLE_XPOS
Definition: muse_pixtable.h:51
muse_image * muse_resampling_collapse_pixgrid(muse_pixtable *aPixtable, muse_pixgrid *aGrid, muse_datacube *aCube, const muse_table *aFilter, muse_resampling_params *aParams)
Integrate a pixel table / pixel grid along the wavelength direction.
muse_pixtable_wcs
State of the astrometric calibration of a MUSE pixel table.
Structure definition of a MUSE datacube.
Definition: muse_datacube.h:48
muse_image * muse_resampling_image(muse_pixtable *aPixtable, muse_resampling_type aMethod, double aDX, double aDLambda)
Resample a pixel table onto a two-dimensional regular grid.
cpl_error_code muse_wcs_get_scales(cpl_propertylist *aHeader, double *aXScale, double *aYScale)
Compute the spatial scales (in degrees) from the FITS header WCS.
Definition: muse_wcs.c:1825
cpl_error_code muse_wcs_pixel_from_projplane(cpl_propertylist *aHeader, double aX, double aY, double *aXOut, double *aYOut)
Convert from projection plane coordinates to pixel coordinates.
Definition: muse_wcs.c:1739
void muse_image_delete(muse_image *aImage)
Deallocate memory associated to a muse_image object.
Definition: muse_image.c:85
void muse_pixtable_extracted_delete(muse_pixtable **aPixtables)
Delete a pixel table array.
double muse_pfits_get_cd(const cpl_propertylist *aHeaders, unsigned int aAxisI, unsigned int aAxisJ)
find out the WCS coordinate at the reference point
Definition: muse_pfits.c:446
muse_pixgrid * muse_pixgrid_2d_create(cpl_table *aTable, double aDX, double aZMin, double aZMax, double aDZ, float *aXMin)
Convert selected rows of a pixel table into 2D pixel grid, linking the grid points to entries (=rows)...
Definition: muse_pixgrid.c:507
#define MUSE_HDR_PT_XLO
FITS header keyword contains the lower limit of the data in x-direction.
cpl_size muse_pixtable_extracted_get_size(muse_pixtable **aPixtables)
Get the size of an array of extracted pixel tables.
double muse_pfits_get_rhum(const cpl_propertylist *aHeaders)
find out the relavtive humidity (in %)
Definition: muse_pfits.c:1024
double muse_pfits_get_crval(const cpl_propertylist *aHeaders, unsigned int aAxis)
find out the WCS coordinate at the reference point
Definition: muse_pfits.c:423
A structure containing a spatial two-axis WCS.
Definition: muse_wcs.h:105
cpl_table * muse_resampling_spectrum_iterate(muse_pixtable *aPixtable, double aBinwidth, float aLo, float aHi, unsigned char aIter)
Iteratively resample selected pixels of a pixel table into spectrum.
double cd22
Definition: muse_wcs.h:108
cpl_error_code muse_resampling_params_set_wcs(muse_resampling_params *aParams, const cpl_propertylist *aWCS)
Set an output WCS (and wavelength scale) in the resampling parameters.
muse_pixgrid * muse_pixgrid_create(muse_pixtable *aPixtable, cpl_propertylist *aHeader, cpl_size aXSize, cpl_size aYSize, cpl_size aZSize)
Convert selected rows of a pixel table into pixel grid, linking the grid points to entries (=rows) in...
Definition: muse_pixgrid.c:237
muse_pixtable ** muse_pixtable_extracted_get_slices(muse_pixtable *aPixtable)
Extract one pixel table per IFU and slice.
cpl_image * data
the data extension
Definition: muse_image.h:46
cpl_size muse_pixtable_get_nrow(const muse_pixtable *aPixtable)
get the number of rows within the pixel table
#define MUSE_HDR_PT_LHI
FITS header keyword contains the upper limit of the data in spectral direction.
int muse_pixtable_get_type(muse_pixtable *aPixtable)
Determine the type of pixel table.
const muse_cpltable_def muse_dataspectrum_def[]
cpl_error_code muse_utils_filter_copy_properties(cpl_propertylist *aHeader, const muse_table *aFilter, double aFraction)
Add/propagate filter properties to header of collapsed image.
Definition: muse_utils.c:934
void muse_utils_memory_dump(const char *aMarker)
Display the current memory usage of the given program.
Definition: muse_utils.c:2702
cpl_error_code muse_pixtable_dump(muse_pixtable *aPixtable, cpl_size aStart, cpl_size aCount, unsigned char aDisplayHeader)
Dump a MUSE pixel table to the screen, resolving the origin column.
muse_datacube * muse_resampling_cube(muse_pixtable *aPixtable, muse_resampling_params *aParams, muse_pixgrid **aGrid)
Resample a pixel table onto a regular grid structure representing a FITS NAXIS=3 datacube.
double muse_pfits_get_pres_start(const cpl_propertylist *aHeaders)
find out the ambient pressure at start of exposure (in mbar)
Definition: muse_pfits.c:1042
cpl_image * stat
the statistics extension
Definition: muse_image.h:64
cpl_error_code muse_wcs_projplane_from_celestial(cpl_propertylist *aHeader, double aRA, double aDEC, double *aX, double *aY)
Convert from celestial spherical coordinates to projection plane coordinates.
Definition: muse_wcs.c:1658
const char * muse_pfits_get_dateobs(const cpl_propertylist *aHeaders)
find out the date of observations
Definition: muse_pfits.c:364
void muse_datacube_delete(muse_datacube *aCube)
Deallocate memory associated to a muse_datacube object.
cpl_propertylist * hgroup
the group FITS header
#define MUSE_HDR_PT_FFCORR
cpl_propertylist * header
the primary FITS header
The pixel grid.
Definition: muse_pixgrid.h:65
double muse_astro_parangle(const cpl_propertylist *aHeader)
Properly average parallactic angle values of one exposure.
Definition: muse_astro.c:449
Structure definition of MUSE three extension FITS file.
Definition: muse_image.h:40
cpl_table * table
The pixel table.
cpl_propertylist * header
the FITS header
Definition: muse_image.h:72
const char * muse_pfits_get_cunit(const cpl_propertylist *aHeaders, unsigned int aAxis)
find out the WCS axis unit string
Definition: muse_pfits.c:491
cpl_error_code muse_pixtable_reset_dq(muse_pixtable *aPixtable, unsigned int aDQ)
Reset a given bad pixel status (DQ flag) for all pixels in the table.
cpl_error_code muse_cpltable_check(const cpl_table *aTable, const muse_cpltable_def *aDef)
Check whether the table contains the fields of the definition.
#define MUSE_PIXTABLE_DATA
Definition: muse_pixtable.h:48
muse_resampling_crstats_type crtype
cpl_image * dq
the data quality extension
Definition: muse_image.h:56
cpl_table * muse_cpltable_new(const muse_cpltable_def *aDef, cpl_size aLength)
Create an empty table according to the specified definition.
double muse_pfits_get_crpix(const cpl_propertylist *aHeaders, unsigned int aAxis)
find out the WCS reference point
Definition: muse_pfits.c:401
void muse_pixgrid_delete(muse_pixgrid *aGrid)
Delete a pixel grid and remove its memory.
Definition: muse_pixgrid.c:575
unsigned int muse_pixtable_origin_get_x(uint32_t aOrigin, muse_pixtable *aPixtable, cpl_size aRow)
Get the horizontal coordinate from the encoded 32bit origin number.
double muse_flux_response_interpolate(const cpl_table *aResponse, double aLambda, double *aError, muse_flux_interpolation_type aType)
Compute linearly interpolated response of some kind at given wavelength.
Definition: muse_flux.c:338
muse_wcs * muse_wcs_new(cpl_propertylist *aHeader)
Create a new WCS structure from a given FITS header.
Definition: muse_wcs.c:1870
cpl_array * muse_cplarray_new_from_delimited_string(const char *aString, const char *aDelim)
Convert a delimited string into an array of strings.
#define MUSE_PIXTABLE_WEIGHT
Definition: muse_pixtable.h:56
Structure definition of MUSE pixel table.
static const cpl_size * muse_pixgrid_get_rows(muse_pixgrid *aGrid, cpl_size aIndex)
Return a pointer to the rows stored in one pixel.
Definition: muse_pixgrid.h:171
#define MUSE_WCS_KEYS
regular expression for WCS properties
Definition: muse_wcs.h:48
muse_pixtable_wcs muse_pixtable_wcs_check(muse_pixtable *aPixtable)
Check the state of the world coordinate system of a pixel table.
#define MUSE_HDR_PT_YLO
FITS header keyword contains the lower limit of the data in y-direction.
static void muse_wcs_celestial_from_pixel_fast(muse_wcs *aWCS, double aX, double aY, double *aRA, double *aDEC)
Convert from pixel coordinates to celestial spherical coordinates.
Definition: muse_wcs.h:138
muse_resampling_crstats_type
Cosmic ray rejection statistics type.
muse_resampling_params * muse_resampling_params_new(muse_resampling_type aMethod)
Create the resampling parameters structure.
const muse_cpltable_def muse_badpix_table_def[]
cpl_error_code muse_wcs_projplane_from_pixel(cpl_propertylist *aHeader, double aX, double aY, double *aXOut, double *aYOut)
Convert from pixel coordinates to projection plane coordinates.
Definition: muse_wcs.c:1706
cpl_table * table
The table.
Definition: muse_table.h:49
cpl_imagelist * data
the cube containing the actual data values
Definition: muse_datacube.h:76
cpl_error_code muse_wcs_pixel_from_celestial(cpl_propertylist *aHeader, double aRA, double aDEC, double *aX, double *aY)
Convert from celestial spherical coordinates to pixel coordinates.
Definition: muse_wcs.c:1603
Structure definition of a Euro3D datacube.
Definition: muse_datacube.h:97
Structure to store a table together with a property list.
Definition: muse_table.h:43
#define MUSE_PIXTABLE_ORIGIN
Definition: muse_pixtable.h:54
double muse_utils_filter_fraction(const muse_table *aFilter, double aLambda1, double aLambda2)
Compute fraction of filter area covered by the data range.
Definition: muse_utils.c:893
cpl_imagelist * dq
the optional cube containing the bad pixel status
Definition: muse_datacube.h:81
muse_resampling_dispersion_type tlambda
#define MUSE_HDR_PT_LLO
FITS header keyword contains the lower limit of the data in spectral direction.
cpl_size muse_cpltable_find_sorted(const cpl_table *aTable, const char *aColumn, double aValue)
Find a row in a table.
cpl_table * dtable
the table containing the actual Euro3D data
const char * muse_pfits_get_ctype(const cpl_propertylist *aHeaders, unsigned int aAxis)
find out the WCS axis type string
Definition: muse_pfits.c:469
cpl_image * muse_cplimage_concat_x(const cpl_image *aImage1, const cpl_image *aImage2)
Concatenate two images in x direction.
cpl_error_code muse_resampling_params_set_pixfrac(muse_resampling_params *aParams, const char *aString)
Set resampling pixfrac given a string that can contain up to three floating-point values...
cpl_propertylist * hdata
the data FITS header
unsigned short muse_pixtable_origin_get_ifu(uint32_t aOrigin)
Get the IFU number from the encoded 32bit origin number.
cpl_table * gtable
the table containing the Euro3D groups
cpl_propertylist * header
the FITS header
Definition: muse_datacube.h:57
const muse_cpltable_def muse_euro3dcube_e3d_data_def[]
double muse_astro_posangle(const cpl_propertylist *aHeader)
Derive the position angle of an observation from information in a FITS header.
Definition: muse_astro.c:413
#define MUSE_PIXTABLE_STAT
Definition: muse_pixtable.h:50
double muse_pfits_get_mjdobs(const cpl_propertylist *aHeaders)
find out the Julian Date of the observation
Definition: muse_pfits.c:346
const muse_cpltable_def muse_pixtable_def[]
MUSE pixel table definition.
#define MUSE_PIXTABLE_LAMBDA
Definition: muse_pixtable.h:53
static cpl_size muse_pixgrid_get_index(muse_pixgrid *aGrid, cpl_size aX, cpl_size aY, cpl_size aZ, cpl_boolean aAllowOutside)
Get the grid index determined from all three coordinates.
Definition: muse_pixgrid.h:100
cpl_error_code muse_quadrants_coords_to_raw(cpl_propertylist *aHeader, int *aX, int *aY)
Convert coordinates of a trimmed image to raw-image coordinates.
static void muse_wcs_projplane_from_pixel_fast(muse_wcs *aWCS, double aX, double aY, double *aXOut, double *aYOut)
Convert from pixel coordinates to projection plane coordinates.
Definition: muse_wcs.h:216
cpl_boolean iscelsph
Definition: muse_wcs.h:112
#define MUSE_HDR_PT_DAR_NAME
muse_resampling_type
Resampling types.
double muse_pfits_get_temp(const cpl_propertylist *aHeaders)
find out the ambient temperature (in degrees Celsius)
Definition: muse_pfits.c:1006
muse_euro3dcube * muse_resampling_euro3d(muse_pixtable *aPixtable, muse_resampling_params *aParams)
Resample a pixel table onto a regular grid structure representing a Euro3D format file...
Resampling parameters.
void muse_resampling_params_delete(muse_resampling_params *aParams)
Delete a resampling parameters structure.
muse_resampling_type method
muse_image * muse_image_new(void)
Allocate memory for a new muse_image object.
Definition: muse_image.c:66
cpl_table * muse_resampling_spectrum(muse_pixtable *aPixtable, double aBinwidth)
Resample the selected pixels of a pixel table into a spectrum.
unsigned int muse_pixtable_origin_get_y(uint32_t aOrigin)
Get the vertical coordinate from the encoded 32bit origin number.
#define MUSE_PIXTABLE_YPOS
Definition: muse_pixtable.h:52
#define MUSE_HDR_PT_YHI
FITS header keyword contains the upper limit of the data in y-direction.
static cpl_size muse_pixgrid_get_count(muse_pixgrid *aGrid, cpl_size aIndex)
Return the number of rows stored in one pixel.
Definition: muse_pixgrid.h:140
const muse_cpltable_def muse_euro3dcube_e3d_grp_def[]
muse_ins_mode muse_pfits_get_mode(const cpl_propertylist *aHeaders)
find out the observation mode
Definition: muse_pfits.c:1352
double muse_astro_airmass(cpl_propertylist *aHeader)
Derive the effective airmass of an observation from information in a FITS header. ...
Definition: muse_astro.c:331
cpl_imagelist * stat
the cube containing the data variance
Definition: muse_datacube.h:86
cpl_propertylist * header
The FITS header.
#define MUSE_HDR_PT_XHI
FITS header keyword contains the upper limit of the data in x-ion.