00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021 #ifdef HAVE_CONFIG_H
00022 #include <config.h>
00023 #endif
00024
00025
00026
00027
00028 #define _BSD_SOURCE
00029 #include <stdlib.h>
00030
00031 #include <cpl.h>
00032 #include <float.h>
00033 #include <math.h>
00034 #include <string.h>
00035 #include <cpl.h>
00036
00037 #include "muse_geo.h"
00038 #include "muse_instrument.h"
00039
00040 #include "muse_astro.h"
00041 #include "muse_cplwrappers.h"
00042 #include "muse_data_format_z.h"
00043 #include "muse_dfs.h"
00044 #include "muse_pfits.h"
00045 #include "muse_tracing.h"
00046 #include "muse_utils.h"
00047 #include "muse_wavecalib.h"
00048
00049
00057
00060
00089
00090 const muse_cpltable_def muse_geo_measurements_def[] = {
00091 { "filename", CPL_TYPE_STRING, "", "%s",
00092 "(raw) filename from which this measurement originates", CPL_TRUE },
00093 { "image", CPL_TYPE_INT, "", "%03d", "number of the image in the series", CPL_TRUE },
00094 { "POSENC2", CPL_TYPE_INT, "", "%d",
00095 "x position of the mask in encoder steps", CPL_TRUE },
00096 { "POSPOS2", CPL_TYPE_DOUBLE, "mm", "%.3f", "x position of the mask", CPL_TRUE },
00097 { "POSENC3", CPL_TYPE_INT, "", "%d",
00098 "y position of the mask in encoder steps", CPL_TRUE },
00099 { "POSPOS3", CPL_TYPE_DOUBLE, "mm", "%.3f", "y position of the mask", CPL_TRUE },
00100 { "POSENC4", CPL_TYPE_INT, "", "%d",
00101 "z position of the mask in encoder steps", CPL_TRUE },
00102 { "POSPOS4", CPL_TYPE_DOUBLE, "mm", "%.3f", "z position of the mask", CPL_TRUE },
00103 { "VPOS", CPL_TYPE_DOUBLE, "mm", "%.3f", "real vertical position of the mask", CPL_TRUE },
00104 { "ScaleFOV", CPL_TYPE_DOUBLE, "arcsec/mm", "%.3f",
00105 "focus scale in VLT focal plane (from the FITS header)", CPL_TRUE },
00106 { "SubField", CPL_TYPE_INT, "", "%02d", "sub-field number", CPL_TRUE },
00107 { "SliceCCD", CPL_TYPE_INT, "", "%02d",
00108 "slice number as counted on the CCD", CPL_TRUE },
00109 { "lambda", CPL_TYPE_DOUBLE, "Angstrom", "%.3f", "wavelength", CPL_TRUE },
00110 { "SpotNo", CPL_TYPE_INT, "", "%04d",
00111 "number of this spot within the slice (1 is left, 2 is the central one, 3 is right within the slice)", CPL_TRUE },
00112 { "xc", CPL_TYPE_DOUBLE, "pix", "%.3f", "x center of this spot on the CCD", CPL_TRUE },
00113 { "yc", CPL_TYPE_DOUBLE, "pix", "%.3f", "y center of this spot on the CCD", CPL_TRUE },
00114 { "xfwhm", CPL_TYPE_DOUBLE, "pix", "%.2f", "FWHM in x-direction on the CCD", CPL_TRUE },
00115 { "yfwhm", CPL_TYPE_DOUBLE, "pix", "%.2f", "FWHM in y-direction on the CCD", CPL_TRUE },
00116 { "flux", CPL_TYPE_DOUBLE, "", "%.1f",
00117 "flux of the spot as integrated on the CCD image", CPL_TRUE },
00118 { "bg", CPL_TYPE_DOUBLE, "", "%f", "background level around the spot", CPL_TRUE },
00119 { "dxcen", CPL_TYPE_DOUBLE, "pix", "%f",
00120 "distance to center of slice at vertical position yc (positive: right of center)", CPL_TRUE },
00121 { "twidth", CPL_TYPE_DOUBLE, "pix", "%f",
00122 "trace width of the slice at the vertical CCD position of the spot", CPL_TRUE },
00123 { NULL, 0, NULL, NULL, NULL, CPL_FALSE }
00124 };
00125
00126
00168
00169 const muse_cpltable_def muse_geo_table_def[] = {
00170 { MUSE_GEOTABLE_FIELD, CPL_TYPE_INT, "", "%02d",
00171 "sub-field (IFU / channel) number", CPL_TRUE },
00172 { MUSE_GEOTABLE_CCD, CPL_TYPE_INT, "", "%02d",
00173 "the slice number on the CCD, counted from left to right", CPL_TRUE },
00174 { MUSE_GEOTABLE_SKY, CPL_TYPE_INT, "", "%02d",
00175 "the slice number on the sky", CPL_TRUE },
00176 { MUSE_GEOTABLE_X, CPL_TYPE_DOUBLE, "pix", "%9.4f",
00177 "x position within field of view", CPL_TRUE },
00178 { MUSE_GEOTABLE_Y, CPL_TYPE_DOUBLE, "pix", "%9.4f",
00179 "y position within field of view", CPL_TRUE },
00180 { MUSE_GEOTABLE_ANGLE, CPL_TYPE_DOUBLE, "deg", "%6.3f",
00181 "rotation angle of slice", CPL_TRUE },
00182 { MUSE_GEOTABLE_WIDTH, CPL_TYPE_DOUBLE, "pix", "%.2f",
00183 "width of slice within field of view", CPL_TRUE },
00184 { MUSE_GEOTABLE_X"err", CPL_TYPE_DOUBLE, "pix", "%8.4f",
00185 "error estimated of x position within field of view", CPL_TRUE },
00186 { MUSE_GEOTABLE_Y"err", CPL_TYPE_DOUBLE, "pix", "%8.4f",
00187 "error estimate of y position within field of view", CPL_TRUE },
00188 { MUSE_GEOTABLE_ANGLE"err", CPL_TYPE_DOUBLE, "deg", "%.3f",
00189 "error estimate of rotation angle", CPL_TRUE },
00190 { MUSE_GEOTABLE_WIDTH"err", CPL_TYPE_DOUBLE, "pix", "%.2f",
00191 "error estimate of slice width", CPL_TRUE },
00192 { "stack", CPL_TYPE_INT, "", "%02d",
00193 "slicer stack that this slice belongs to (optical numbering)", CPL_TRUE },
00194 { "spot", CPL_TYPE_INT, "", "%1d", "spot number in this slice", CPL_TRUE },
00195 { "xrel", CPL_TYPE_DOUBLE, "mm", "%7.4f",
00196 "x offset of this spot relative to the slice center", CPL_TRUE },
00197 { "xrelerr", CPL_TYPE_DOUBLE, "mm", "%6.4f",
00198 "error of the relative x offset of this spot", CPL_TRUE },
00199 { "xc", CPL_TYPE_DOUBLE, "pix", "%.3f", "x center of this spot on the CCD", CPL_TRUE },
00200 { "yc", CPL_TYPE_DOUBLE, "pix", "%.3f", "y center of this spot on the CCD", CPL_TRUE },
00201 { "dxl", CPL_TYPE_DOUBLE, "pix", "%.3f", "distance to left edge of slice on the CCD", CPL_TRUE },
00202 { "dxr", CPL_TYPE_DOUBLE, "pix", "%.3f", "distance to right edge of slice on the CCD", CPL_TRUE },
00203 { "dx", CPL_TYPE_DOUBLE, "pix", "%.3f", "pinhole distance in x on the CCD", CPL_TRUE },
00204 { "dxerr", CPL_TYPE_DOUBLE, "pix", "%.3f",
00205 "error estimate of the pinhole distance in x on the CCD", CPL_TRUE },
00206 { "vpos", CPL_TYPE_DOUBLE, "mm", "%.4f",
00207 "(averaged) vertical position of the mask", CPL_TRUE },
00208 { "vposerr", CPL_TYPE_DOUBLE, "mm", "%.4f",
00209 "error estimated of the (averaged) vertical position of the mask", CPL_TRUE },
00210 { "flux", CPL_TYPE_DOUBLE, "", "%.1f",
00211 "flux of the spot as integrated on the CCD image", CPL_TRUE },
00212 { "lambda", CPL_TYPE_DOUBLE, "Angstrom", "%.3f", "wavelength", CPL_TRUE },
00213 { NULL, 0, NULL, NULL, NULL, CPL_FALSE }
00214 };
00215
00216
00233
00234 cpl_table *
00235 muse_geo_table_extract_ifu(const cpl_table *aTable, const unsigned char aIFU)
00236 {
00237 cpl_ensure(aTable, CPL_ERROR_NULL_INPUT, NULL);
00238 cpl_ensure(aIFU >= 1 && aIFU <= kMuseNumIFUs, CPL_ERROR_ILLEGAL_INPUT, NULL);
00239
00240
00241 cpl_table *intable = cpl_table_duplicate(aTable);
00242
00243
00244
00245 cpl_propertylist *sorting = cpl_propertylist_new();
00246 cpl_propertylist_append_bool(sorting, MUSE_GEOTABLE_FIELD, CPL_FALSE);
00247 cpl_propertylist_append_bool(sorting, MUSE_GEOTABLE_CCD, CPL_FALSE);
00248 cpl_table_sort(intable, sorting);
00249 cpl_propertylist_delete(sorting);
00250
00251 cpl_table_select_all(intable);
00252 cpl_table_and_selected_int(intable, MUSE_GEOTABLE_FIELD, CPL_EQUAL_TO, aIFU);
00253 cpl_table *subtable = cpl_table_extract_selected(intable);
00254 cpl_table_delete(intable);
00255 #if 0
00256 printf("table (extracted for IFU %2d)\n", aIFU);
00257 cpl_table_dump(subtable, 0, 100000, stdout);
00258 fflush(stdout);
00259 #endif
00260 int nrow = cpl_table_get_nrow(subtable);
00261 if (nrow != kMuseSlicesPerCCD) {
00262 cpl_error_set_message(__func__, CPL_ERROR_ILLEGAL_OUTPUT,
00263 "geometry table contains %d instead of %d slices for "
00264 "IFU %d", nrow, kMuseSlicesPerCCD, aIFU);
00265 cpl_table_delete(subtable);
00266 subtable = NULL;
00267 }
00268 return subtable;
00269 }
00270
00271
00295
00296 double
00297 muse_geo_table_ifu_area(const cpl_table *aTable, const unsigned char aIFU,
00298 double aScale)
00299 {
00300 cpl_ensure(aTable, CPL_ERROR_NULL_INPUT, 0.);
00301
00302
00303 cpl_table *table = muse_geo_table_extract_ifu(aTable, aIFU);
00304
00305 cpl_size nrow = cpl_table_get_nrow(table);
00306 cpl_ensure(nrow == kMuseSlicesPerCCD, CPL_ERROR_ILLEGAL_INPUT, 0.);
00307
00308
00309
00310 cpl_propertylist *sorting = cpl_propertylist_new();
00311 cpl_propertylist_append_bool(sorting, MUSE_GEOTABLE_SKY, CPL_FALSE);
00312 cpl_table_sort(table, sorting);
00313 cpl_propertylist_delete(sorting);
00314
00315
00316
00317 double area = 0., areas[4];
00318 int istack,
00319 nperstack = kMuseSlicesPerCCD / 4;
00320 for (istack = 0; istack < 4; istack++) {
00321 cpl_table *stack = cpl_table_extract(table, 12 * istack, nperstack);
00322
00323
00324 double height = fabs(cpl_table_get(stack, MUSE_GEOTABLE_Y, 0, NULL)
00325 - cpl_table_get(stack, MUSE_GEOTABLE_Y, nperstack - 1, NULL))
00326 / (nperstack - 1.)
00327 / kMuseTypicalCubeSizeY * aScale;
00328 areas[istack] = cpl_table_get_column_mean(stack, MUSE_GEOTABLE_WIDTH)
00329 * height * nperstack
00330 / kMuseTypicalCubeSizeX * aScale;
00331 cpl_table_delete(stack);
00332 #if 0
00333 cpl_msg_debug(__func__, "areas[%d] = %f", istack, areas[istack]);
00334 #endif
00335 area += areas[istack];
00336 }
00337 cpl_table_delete(table);
00338 return area;
00339 }
00340
00341
00352
00353 cpl_vector *
00354 muse_geo_lines_get(const cpl_table *aLines)
00355 {
00356 cpl_ensure(aLines, CPL_ERROR_NULL_INPUT, NULL);
00357
00358
00359 cpl_table *tlines = cpl_table_duplicate(aLines);
00360
00361
00362 cpl_table_cast_column(tlines, MUSE_LINE_CATALOG_LAMBDA, MUSE_LINE_CATALOG_LAMBDA,
00363 CPL_TYPE_DOUBLE);
00364 cpl_table_cast_column(tlines, MUSE_LINE_CATALOG_FLUX, MUSE_LINE_CATALOG_FLUX,
00365 CPL_TYPE_DOUBLE);
00366 cpl_table_unselect_all(tlines);
00367
00368
00369
00370
00371
00372 cpl_table_or_selected_string(tlines, MUSE_LINE_CATALOG_ION, CPL_EQUAL_TO, "XeI");
00373 cpl_table_or_selected_double(tlines, MUSE_LINE_CATALOG_FLUX, CPL_LESS_THAN, 5000.);
00374 cpl_table_or_selected_double(tlines, MUSE_LINE_CATALOG_LAMBDA, CPL_LESS_THAN,
00375 kMuseNominalLambdaMin);
00376 cpl_table_or_selected_int(tlines, MUSE_LINE_CATALOG_QUALITY, CPL_LESS_THAN, 1);
00377 cpl_table_erase_selected(tlines);
00378
00379
00380
00381
00382 cpl_table_or_selected_string(tlines, MUSE_LINE_CATALOG_ION, CPL_EQUAL_TO, "NeI");
00383 cpl_table_and_selected_int(tlines, MUSE_LINE_CATALOG_QUALITY, CPL_LESS_THAN, 2);
00384 cpl_table_unselect_row(tlines, cpl_table_get_nrow(tlines) - 1);
00385 cpl_table_erase_selected(tlines);
00386 cpl_table_or_selected_string(tlines, MUSE_LINE_CATALOG_ION, CPL_EQUAL_TO, "NeI");
00387 cpl_table_and_selected_double(tlines, MUSE_LINE_CATALOG_FLUX, CPL_LESS_THAN, 10000.);
00388 cpl_table_unselect_row(tlines, cpl_table_get_nrow(tlines) - 1);
00389 cpl_table_erase_selected(tlines);
00390
00391
00392
00393 int nlines = cpl_table_get_nrow(tlines);
00394 if (nlines <= 5) {
00395 cpl_table_delete(tlines);
00396 cpl_error_set_message(__func__, CPL_ERROR_DATA_NOT_FOUND,
00397 "Only found %d suitable arc lines!", nlines);
00398 return NULL;
00399 }
00400 cpl_vector *lines = cpl_vector_wrap(nlines,
00401 cpl_table_unwrap(tlines, MUSE_LINE_CATALOG_LAMBDA));
00402 cpl_table_delete(tlines);
00403 cpl_msg_info(__func__, "Using a list of %d arc lines (from %.1f to %.1f "
00404 "Angstrom)", nlines, cpl_vector_get(lines, 0),
00405 cpl_vector_get(lines, nlines - 1));
00406 return lines;
00407 }
00408
00409
00456
00457 cpl_table *
00458 muse_geo_measure_spots(muse_image *aImage, muse_imagelist *aList,
00459 const cpl_table *aTrace, const cpl_table *aWave,
00460 const cpl_vector *aLines, double aSigma,
00461 muse_geo_centroid_type aCentroid)
00462 {
00463 cpl_ensure(aImage && aList && aTrace && aWave && aLines, CPL_ERROR_NULL_INPUT,
00464 NULL);
00465 cpl_ensure(aSigma > 0., CPL_ERROR_ILLEGAL_INPUT, NULL);
00466 unsigned int nimages = muse_imagelist_get_size(aList);
00467 cpl_ensure(nimages >= 5, CPL_ERROR_ILLEGAL_INPUT, NULL);
00468 int nlines = cpl_vector_get_size(aLines);
00469 cpl_ensure(nlines >= 3, CPL_ERROR_ILLEGAL_INPUT, NULL);
00470 cpl_ensure(aCentroid <= MUSE_GEO_CENTROID_GAUSSIAN, CPL_ERROR_ILLEGAL_INPUT,
00471 NULL);
00472
00473 int ny = cpl_image_get_size_y(aImage->data),
00474 nentries = kMuseSlicesPerCCD * kMuseCUmpmSpotsPerSlice * nlines * nimages;
00475 cpl_table *measurements = muse_cpltable_new(muse_geo_measurements_def,
00476 nentries);
00477 const unsigned char ifu = muse_utils_get_ifu(aImage->header);
00478 int iline, irow = 0;
00479 for (iline = 0; iline < nlines; iline++) {
00480 double lambda = cpl_vector_get(aLines, iline);
00481 cpl_msg_info(__func__, "Searching for line %d (%.3f Angstrom) in IFU %2hhu",
00482 iline + 1, lambda, ifu);
00483
00484 unsigned short nslice;
00485 for (nslice = 1; nslice <= kMuseSlicesPerCCD; nslice++) {
00486 cpl_polynomial **ptrace = muse_trace_table_get_polys_for_slice(aTrace,
00487 nslice),
00488 *pwave = muse_wave_table_get_poly_for_slice(aWave, nslice);
00489 if (!ptrace || !pwave) {
00490 muse_trace_polys_delete(ptrace);
00491 cpl_polynomial_delete(pwave);
00492 continue;
00493 }
00494 double xc = cpl_polynomial_eval_1d(ptrace[MUSE_TRACE_CENTER], ny / 2, NULL);
00495
00496
00497 cpl_polynomial *pxconst = cpl_polynomial_new(1);
00498 cpl_size p = 0;
00499 cpl_polynomial_set_coeff(pxconst, &p, xc);
00500 cpl_polynomial *pywave = cpl_polynomial_extract(pwave, 0, pxconst);
00501 cpl_polynomial_delete(pxconst);
00502
00503 double yc = 1, lbda = -1;
00504 while (fabs(lambda - lbda) > 1.) {
00505 lbda = cpl_polynomial_eval_1d(pywave, yc, NULL);
00506 yc += 0.5;
00507 if (yc > kMuseOutputYTop) {
00508 break;
00509 }
00510 }
00511 cpl_polynomial_delete(pywave);
00512 #if 0
00513 cpl_msg_debug(__func__, "--> %.3f --> %f,%f in slice %2hu",
00514 lbda, xc, yc, nslice);
00515 #endif
00516 cpl_polynomial_delete(pwave);
00517
00518
00519 if (fabs(lambda - lbda) > 1.) {
00520 cpl_msg_warning(__func__, "Polynomial in slice %2hu of IFU %2hhu appears"
00521 " to be faulty! Skipping measurement of line %d (%.1f "
00522 "Angstrom)", nslice, ifu, iline + 1, lambda);
00523 muse_trace_polys_delete(ptrace);
00524 continue;
00525 }
00526
00527
00528
00529 #define DETECTION_HALFSIZE 7
00530 int xl = lround(cpl_polynomial_eval_1d(ptrace[MUSE_TRACE_LEFT], yc, NULL)),
00531 xr = lround(cpl_polynomial_eval_1d(ptrace[MUSE_TRACE_RIGHT], yc, NULL)),
00532 yb = lround(yc - DETECTION_HALFSIZE),
00533 yt = lround(yc + DETECTION_HALFSIZE);
00534 cpl_image *box = cpl_image_extract(aImage->data, xl, yb, xr, yt);
00535
00536 cpl_image *fbox = cpl_image_duplicate(box);
00537 cpl_matrix *gkernel = muse_matrix_new_gaussian_2d(2, 2, 1.);
00538 cpl_image_filter(fbox, box, gkernel, CPL_FILTER_LINEAR, CPL_BORDER_FILTER);
00539 cpl_matrix_delete(gkernel);
00540 cpl_stats_mode mode = CPL_STATS_MEDIAN | CPL_STATS_MEDIAN_DEV;
00541 cpl_stats *s = cpl_stats_new_from_image(box, mode);
00542 double limit = cpl_stats_get_median(s)
00543 + aSigma * cpl_stats_get_median_dev(s);
00544 cpl_mask *mask = cpl_mask_threshold_image_create(fbox, limit, DBL_MAX);
00545 #if 0
00546 char *fn1 = cpl_sprintf("box_%02hu.fits", nslice),
00547 *fn2 = cpl_sprintf("boxf_%02hu.fits", nslice),
00548 *fn3 = cpl_sprintf("boxf_%02hu_mask.fits", nslice);
00549 cpl_image_save(box, fn1, CPL_TYPE_UNSPECIFIED, NULL, CPL_IO_CREATE);
00550 cpl_image_save(fbox, fn2, CPL_TYPE_UNSPECIFIED, NULL, CPL_IO_CREATE);
00551 cpl_mask_save(mask, fn3, NULL, CPL_IO_CREATE);
00552 cpl_free(fn1);
00553 cpl_free(fn2);
00554 cpl_free(fn3);
00555 #endif
00556
00557 cpl_apertures *apertures = cpl_apertures_extract_mask(box, mask);
00558 #if 0
00559 cpl_msg_debug(__func__, "stats in box [%d:%d,%d:%d] --> limit = "
00560 "%f + %.1f * %f = %f, apertures:", xl, xr, yb, yt,
00561 cpl_stats_get_median(s), aSigma,
00562 cpl_stats_get_median_dev(s), limit);
00563 cpl_apertures_dump(apertures, stdout);
00564 fflush(stdout);
00565 #endif
00566 cpl_mask_delete(mask);
00567 cpl_stats_delete(s);
00568 cpl_image_delete(fbox);
00569 cpl_image_delete(box);
00570 cpl_errorstate es = cpl_errorstate_get();
00571 int nspots = cpl_apertures_get_size(apertures);
00572 if (nspots < 0) {
00573 nspots = 0;
00574 }
00575 if (!apertures || nspots != kMuseCUmpmSpotsPerSlice) {
00576 cpl_msg_debug(__func__, "found %d spot%s (need %hhu) down to the %.1f"
00577 "-sigma limit in slice %2d for wavelength %.3f Angstrom "
00578 "in box [%d:%d,%d:%d]", nspots, nspots == 1 ? "" : "s",
00579 kMuseCUmpmSpotsPerSlice, aSigma, nslice, lambda,
00580 xl, xr, yb, yt);
00581 muse_trace_polys_delete(ptrace);
00582 cpl_apertures_delete(apertures);
00583 cpl_errorstate_set(es);
00584 continue;
00585 }
00586 #if 0
00587 cpl_msg_debug(__func__, "found %d spots using the %.1f-sigma limit in "
00588 "slice %2d for wavelength %.3f Angstrom in box [%d:%d,%d:%d]",
00589 nspots, aSigma, nslice, lambda, xl, xr, yb, yt);
00590 cpl_apertures_dump(apertures, stdout);
00591 fflush(stdout);
00592 #endif
00593
00594
00595
00596 cpl_matrix *mspots = cpl_matrix_new(nspots, 2);
00597 int naper;
00598 for (naper = 1; naper <= nspots; naper++) {
00599 double xpos = cpl_apertures_get_centroid_x(apertures, naper);
00600 cpl_matrix_set(mspots, naper - 1, 0, xpos);
00601 cpl_matrix_set(mspots, naper - 1, 1, naper);
00602 }
00603 cpl_matrix_sort_rows(mspots, 1);
00604 #if 0
00605 printf("mspots sorted:\n");
00606 cpl_matrix_dump(mspots, stdout);
00607 fflush(stdout);
00608 #endif
00609
00610 int idx, *aperidx = cpl_calloc(nspots, sizeof(int));
00611 for (naper = 1, idx = nspots - 1; naper <= nspots && idx >= 0; naper++, idx--) {
00612
00613 aperidx[naper - 1] = cpl_matrix_get(mspots, idx, 1);
00614 }
00615 cpl_matrix_delete(mspots);
00616
00617
00618
00619 unsigned int k;
00620 for (k = 0; k < nimages; k++) {
00621 muse_image *image = muse_imagelist_get(aList, k);
00622 int posenc[4];
00623 double pospos[4],
00624 posang = muse_astro_posangle(image->header);
00625 unsigned short i;
00626 for (i = 1; i <= 4; i++) {
00627 posenc[i-1] = muse_pfits_get_posenc(image->header, i);
00628 pospos[i-1] = muse_pfits_get_pospos(image->header, i);
00629 }
00630
00631 #define MEASUREMENT_HALFSIZE 5
00632 #define BACKGROUND_HALFSIZE 7
00633 for (idx = 0; idx < nspots; idx++) {
00634 naper = aperidx[idx];
00635 double xpos = cpl_apertures_get_centroid_x(apertures, naper) + xl,
00636 ypos = cpl_apertures_get_centroid_y(apertures, naper) + yb;
00637 #if 0
00638 printf("aper %d idx %d xpos %f\n", naper, idx, xpos);
00639 fflush(stdout);
00640 #endif
00641 cpl_stats_mode mspot = CPL_STATS_FLUX | CPL_STATS_CENTROID,
00642 mbg = CPL_STATS_MEAN;
00643 cpl_stats *sspot = cpl_stats_new_from_image_window(image->data, mspot,
00644 xpos - MEASUREMENT_HALFSIZE,
00645 ypos - MEASUREMENT_HALFSIZE,
00646 xpos + MEASUREMENT_HALFSIZE,
00647 ypos + MEASUREMENT_HALFSIZE),
00648 *sbg = cpl_stats_new_from_image_window(image->data, mbg,
00649 xpos - BACKGROUND_HALFSIZE,
00650 ypos - BACKGROUND_HALFSIZE,
00651 xpos + BACKGROUND_HALFSIZE,
00652 ypos + BACKGROUND_HALFSIZE);
00653 int npix = cpl_stats_get_npix(sspot);
00654 double bg = cpl_stats_get_mean(sbg),
00655 flux = cpl_stats_get_flux(sspot) - bg * npix;
00656 if (flux < 0.) {
00657 flux = 0.;
00658 }
00659 cpl_stats_delete(sbg);
00660 double xcentroid = cpl_stats_get_centroid_x(sspot),
00661 ycentroid = cpl_stats_get_centroid_y(sspot);
00662 cpl_stats_delete(sspot);
00663 double xfwhm, yfwhm;
00664 if (aCentroid == MUSE_GEO_CENTROID_GAUSSIAN) {
00665
00666 cpl_array *gpars = cpl_array_new(7, CPL_TYPE_DOUBLE);
00667 cpl_array_set(gpars, 0, bg);
00668 cpl_array_set(gpars, 1, flux);
00669 cpl_array_set(gpars, 3, xcentroid);
00670 cpl_array_set(gpars, 4, ycentroid);
00671 cpl_array_set(gpars, 5, 2.);
00672 cpl_array_set(gpars, 6, 2.);
00673 cpl_fit_image_gaussian(image->data, NULL, lround(xcentroid), lround(ycentroid),
00674 2 * MEASUREMENT_HALFSIZE + 1, 2 * MEASUREMENT_HALFSIZE + 1,
00675 gpars, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
00676 xcentroid = cpl_array_get(gpars, 3, NULL);
00677 ycentroid = cpl_array_get(gpars, 4, NULL);
00678 xfwhm = cpl_array_get(gpars, 5, NULL) * CPL_MATH_FWHM_SIG;
00679 yfwhm = cpl_array_get(gpars, 6, NULL) * CPL_MATH_FWHM_SIG;
00680 cpl_array_delete(gpars);
00681 } else {
00682 cpl_image_get_fwhm(image->data, lround(xcentroid), lround(ycentroid),
00683 &xfwhm, &yfwhm);
00684 }
00685 if (cpl_propertylist_has(image->header, MUSE_HDR_TMP_FN)) {
00686 cpl_table_set_string(measurements, "filename", irow,
00687 cpl_propertylist_get_string(image->header,
00688 MUSE_HDR_TMP_FN));
00689 } else {
00690 cpl_table_set_string(measurements, "filename", irow, "unknown");
00691 }
00692 cpl_table_set_int(measurements, "image", irow, k + 1);
00693 cpl_table_set_int(measurements, "POSENC2", irow, posenc[1]);
00694 cpl_table_set(measurements, "POSPOS2", irow, pospos[1]);
00695 cpl_table_set_int(measurements, "POSENC3", irow, posenc[2]);
00696 cpl_table_set(measurements, "POSPOS3", irow, pospos[2]);
00697 cpl_table_set_int(measurements, "POSENC4", irow, posenc[3]);
00698 cpl_table_set(measurements, "POSPOS4", irow, pospos[3]);
00699
00700
00701 cpl_table_set(measurements, "VPOS", irow,
00702 pospos[2] / sin(posang * CPL_MATH_RAD_DEG));
00703 cpl_table_set_double(measurements, "ScaleFOV", irow,
00704 muse_pfits_get_focu_scale(image->header));
00705 cpl_table_set_int(measurements, "SubField", irow, ifu);
00706 cpl_table_set_int(measurements, "SliceCCD", irow, nslice);
00707 cpl_table_set(measurements, "lambda", irow, lambda);
00708 cpl_table_set_int(measurements, "SpotNo", irow, idx + 1);
00709 cpl_table_set(measurements, "xc", irow, xcentroid);
00710 cpl_table_set(measurements, "yc", irow, ycentroid);
00711 cpl_table_set(measurements, "xfwhm", irow, xfwhm);
00712 cpl_table_set(measurements, "yfwhm", irow, yfwhm);
00713 cpl_table_set(measurements, "flux", irow, flux);
00714 cpl_table_set(measurements, "bg", irow, bg);
00715
00716 double xcslice = cpl_polynomial_eval_1d(ptrace[MUSE_TRACE_CENTER], ycentroid, NULL);
00717 cpl_table_set(measurements, "dxcen", irow, xcentroid - xcslice);
00718
00719 double twidth = cpl_polynomial_eval_1d(ptrace[MUSE_TRACE_RIGHT], ycentroid, NULL)
00720 - cpl_polynomial_eval_1d(ptrace[MUSE_TRACE_LEFT], ycentroid, NULL);
00721 cpl_table_set(measurements, "twidth", irow, twidth);
00722 #if 0
00723
00724 if (xfwhm < 0 || yfwhm < 0) {
00725 cpl_msg_warning(__func__, "xfwhm and/or yfwhm are invalid: %f, %f:", xfwhm, yfwhm);
00726 if (xfwhm < 0) {
00727 cpl_table_set_invalid(measurements, "xfwhm", irow);
00728 }
00729 if (yfwhm < 0) {
00730 cpl_table_set_invalid(measurements, "yfwhm", irow);
00731 }
00732 cpl_table_dump(measurements, irow, 1, stdout);
00733 fflush(stdout);
00734 }
00735 #endif
00736 irow++;
00737 }
00738 }
00739 cpl_apertures_delete(apertures);
00740 cpl_free(aperidx);
00741 muse_trace_polys_delete(ptrace);
00742 }
00743 }
00744
00745
00746 cpl_table_erase_invalid(measurements);
00747
00748
00749 cpl_propertylist *order = cpl_propertylist_new();
00750 cpl_propertylist_append_bool(order, "lambda", CPL_FALSE);
00751 cpl_propertylist_append_bool(order, "SliceCCD", CPL_FALSE);
00752 cpl_propertylist_append_bool(order, "SpotNo", CPL_FALSE);
00753 cpl_propertylist_append_bool(order, "VPOS", CPL_FALSE);
00754 cpl_table_sort(measurements, order);
00755 cpl_propertylist_delete(order);
00756
00757 return measurements;
00758 }
00759
00760
00796
00797 static cpl_table *
00798 muse_geo_get_spot_peaks(cpl_table *aSpots, unsigned char aIFU,
00799 unsigned short aNSlice, unsigned char aNSpot,
00800 double aLambda, double aVPosRef, cpl_boolean aVerifyDY,
00801 cpl_array *aDY)
00802 {
00803 if (!aSpots) {
00804 return NULL;
00805 }
00806
00807
00808
00809
00810 cpl_table_unselect_all(aSpots);
00811 cpl_size irow, nrow = cpl_table_get_nrow(aSpots);
00812 for (irow = 0; irow < nrow; irow++) {
00813 if (cpl_table_get_int(aSpots, "SliceCCD", irow, NULL) == aNSlice &&
00814 cpl_table_get_int(aSpots, "SpotNo", irow, NULL) == aNSpot &&
00815 cpl_table_get_double(aSpots, "lambda", irow, NULL) == aLambda) {
00816 cpl_table_select_row(aSpots, irow);
00817 }
00818 }
00819 cpl_size nextracted = cpl_table_count_selected(aSpots);
00820 if (nextracted < 1) {
00821
00822
00823 cpl_msg_debug(__func__, "No detection for spot %1hhu in slice %2hu of IFU "
00824 "%hhu at wavelength %.3f", aNSpot, aNSlice, aIFU, aLambda);
00825 return NULL;
00826 }
00827 cpl_table *tspot = cpl_table_extract_selected(aSpots);
00828 #if 0
00829 printf("tspot:\n");
00830 cpl_table_dump(tspot, 0, 10000, stdout);
00831 fflush(stdout);
00832 #endif
00833
00834 int nsrow = cpl_table_get_nrow(tspot);
00835 cpl_image *imflux = cpl_image_wrap(nsrow, 1,
00836 CPL_TYPE_DOUBLE,
00837 cpl_table_get_data_double(tspot, "flux"));
00838
00839
00840 cpl_stats *s = cpl_stats_new_from_image(imflux,
00841 CPL_STATS_MEDIAN | CPL_STATS_MEDIAN_DEV);
00842 double limit = cpl_stats_get_median(s) + cpl_stats_get_median_dev(s) * 0.5;
00843 cpl_stats_delete(s);
00844 if (limit > 500.) {
00845 limit = 500.;
00846 }
00847 cpl_mask *mask = cpl_mask_threshold_image_create(imflux, limit, DBL_MAX);
00848 cpl_mask *kernel = cpl_mask_new(3, 1);
00849 cpl_mask_not(kernel);
00850 cpl_mask *mask2 = cpl_mask_duplicate(mask);
00851 cpl_mask_filter(mask, mask2, kernel, CPL_FILTER_DILATION, CPL_BORDER_NOP);
00852 cpl_mask_delete(mask2);
00853 cpl_mask_delete(kernel);
00854 cpl_apertures *aper = cpl_apertures_extract_mask(imflux, mask);
00855 cpl_mask_delete(mask);
00856 if (!aper) {
00857 cpl_msg_info(__func__, "No detection for spot %1hhu in slice %2hu of IFU "
00858 "%2hhu at wavelength %.3f (hope for other wavelengths!)",
00859 aNSpot, aNSlice, aIFU, aLambda);
00860 cpl_table_delete(tspot);
00861 cpl_image_unwrap(imflux);
00862 return NULL;
00863 }
00864
00865 double dcenter = DBL_MAX;
00866 int naper, ndcenter = -1;
00867 for (naper = 1; naper <= cpl_apertures_get_size(aper); naper++) {
00868
00869 int npos = cpl_apertures_get_npix(aper, naper);
00870 if (cpl_apertures_get_size(aper) > 1 && npos < 3) {
00871 cpl_msg_debug(__func__, "IFU %2hhu SliceCCD %2d spot %1hhu lambda %.3f, "
00872 "aperture %d: only %d positions -> skip", aIFU, aNSlice,
00873 aNSpot, aLambda, naper, npos);
00874 continue;
00875 }
00876
00877 double xref = aVPosRef > 0 ? aVPosRef
00878 : cpl_table_get_double(tspot, "VPOS", (nsrow + 1) / 2, NULL),
00879 xcentroid = cpl_apertures_get_centroid_x(aper, naper);
00880
00881
00882 irow = 0;
00883 while (++irow + 1 < xcentroid) ;
00884 double pp1 = cpl_table_get_double(tspot, "VPOS", irow - 1, NULL),
00885 pp2 = cpl_table_get_double(tspot, "VPOS", irow, NULL),
00886 ppfrac = xcentroid - irow;
00887 #if 0
00888 cpl_msg_debug(__func__, "%"CPL_SIZE_FORMAT" (%f) --> %f %f ==> %f", irow,
00889 xcentroid, pp1, pp2, pp1 * (1 - ppfrac) + pp2 * ppfrac);
00890 #endif
00891 double ppcentroid = pp1 * (1 - ppfrac) + pp2 * ppfrac;
00892
00893 double dc = fabs(ppcentroid - xref);
00894 int x1 = cpl_apertures_get_left(aper, naper),
00895 x2 = cpl_apertures_get_right(aper, naper);
00896 if (dc < dcenter && x1 > 1 && x2 < nsrow) {
00897 dcenter = dc;
00898 ndcenter = naper;
00899 }
00900 }
00901
00902
00903 if (aDY || aVerifyDY) {
00904 for (naper = 1; naper < cpl_apertures_get_size(aper); naper++) {
00905 int l1 = cpl_apertures_get_left(aper, naper),
00906 r1 = cpl_apertures_get_right(aper, naper),
00907 l2 = cpl_apertures_get_left(aper, naper + 1),
00908 r2 = cpl_apertures_get_right(aper, naper + 1);
00909 if (l1 > 1 && r1 < nsrow && l2 > 1 && r2 < nsrow) {
00910
00911 double peak[2];
00912 int n2aper;
00913 for (n2aper = naper; n2aper <= naper + 1; n2aper++) {
00914 cpl_size irow1 = cpl_apertures_get_left(aper, n2aper) - 1,
00915 irow2 = cpl_apertures_get_right(aper, n2aper) - 1;
00916 double vpos = 0., ftot = 0.;
00917 for (irow = irow1; irow <= irow2; irow++) {
00918 double flux = cpl_table_get(tspot, "flux", irow, NULL);
00919 ftot += flux;
00920 vpos += cpl_table_get(tspot, "VPOS", irow, NULL) * flux;
00921 }
00922 peak[n2aper - naper] = vpos / ftot;
00923 }
00924 double xcdiff = fabs(peak[1] - peak[0]);
00925 if (aDY) {
00926
00927 cpl_errorstate state = cpl_errorstate_get();
00928 cpl_size idy = 0, ndy = cpl_array_get_size(aDY);
00929 while (cpl_array_is_valid(aDY, idy) > 0) {
00930 idy++;
00931 }
00932 if (cpl_array_get_size(aDY) <= idy) {
00933 cpl_array_set_size(aDY, ndy * 1.5);
00934 cpl_errorstate_set(state);
00935 }
00936 #if 0
00937 cpl_msg_debug(__func__, "xcdiff = %f (%f - %f = %f) / index %"CPL_SIZE_FORMAT,
00938 xcdiff, peak[1], peak[0], peak[1] - peak[0], idy);
00939 #endif
00940 cpl_array_set_double(aDY, idy, xcdiff);
00941 }
00942 if (aVerifyDY) {
00943 printf("\"centroids_d_%f.dat\" u 18:16 t \"d %f (%f %f)\" w lp, \\\n",
00944 xcdiff, xcdiff, peak[0], peak[1]);
00945 char *fn = cpl_sprintf("centroids_d_%f.dat", xcdiff);
00946 FILE *fp = fopen(fn, "w");
00947 fprintf(fp, "# good centroids at %f and %f --> d = %f mm\n#", peak[0], peak[1], xcdiff);
00948 cpl_table_dump(tspot, 0, 10000, fp);
00949 fflush(fp);
00950 fclose(fp);
00951 cpl_free(fn);
00952 }
00953 }
00954 }
00955 }
00956 if (ndcenter < 1) {
00957 cpl_msg_info(__func__, "Motion of spot %1hhu in slice %2hu of IFU "
00958 "%2hhu at wavelength %.3f did not result in usable "
00959 "coverage (hope for other wavelengths!)", aNSpot, aNSlice,
00960 aIFU, aLambda);
00961 cpl_table_delete(tspot);
00962 cpl_apertures_delete(aper);
00963 cpl_image_unwrap(imflux);
00964 return NULL;
00965 }
00966 cpl_size irow1 = cpl_apertures_get_left(aper, ndcenter) - 1,
00967 irow2 = cpl_apertures_get_right(aper, ndcenter) - 1;
00968 cpl_apertures_delete(aper);
00969 cpl_image_unwrap(imflux);
00970
00971
00972 cpl_table_unselect_all(tspot);
00973 for (irow = irow1; irow <= irow2; irow++) {
00974 cpl_table_select_row(tspot, irow);
00975 }
00976 cpl_table *result = cpl_table_extract_selected(tspot);
00977 cpl_table_delete(tspot);
00978 return result;
00979 }
00980
00981
01014
01015 cpl_error_code
01016 muse_geo_compute_pinhole_local_distance(cpl_array *aDY, cpl_table *aSpots)
01017 {
01018 cpl_ensure_code(aDY && aSpots, CPL_ERROR_NULL_INPUT);
01019 cpl_ensure_code(cpl_array_get_type(aDY) == CPL_TYPE_DOUBLE,
01020 CPL_ERROR_INCOMPATIBLE_INPUT);
01021 cpl_size nrow = cpl_table_get_nrow(aSpots);
01022 cpl_ensure_code(nrow > 10, CPL_ERROR_ILLEGAL_INPUT);
01023 cpl_ensure_code(muse_cpltable_check(aSpots, muse_geo_measurements_def) == CPL_ERROR_NONE,
01024 CPL_ERROR_INCOMPATIBLE_INPUT);
01025 const unsigned char ifu = cpl_table_get_column_min(aSpots, "SubField"),
01026 ifu2 = cpl_table_get_column_max(aSpots, "SubField");
01027 cpl_ensure_code(ifu == ifu2 && ifu >= 1 && ifu <= kMuseNumIFUs,
01028 CPL_ERROR_ILLEGAL_INPUT);
01029 cpl_ensure_code(cpl_table_get_column_stdev(aSpots, "ScaleFOV") < 1e-10,
01030 CPL_ERROR_ILLEGAL_INPUT);
01031
01032 cpl_boolean verifydy = getenv("MUSE_DEBUG_GEO_VERIFY_DY")
01033 && atoi(getenv("MUSE_DEBUG_GEO_VERIFY_DY")) > 0;
01034 if (verifydy) {
01035 cpl_msg_warning(__func__, "Running with DY pinhole distance verification on"
01036 " (MUSE_DEBUG_GEO_VERIFY_DY=%s), will produce lots of files "
01037 "\"centroids_d_*.dat\"!", getenv("MUSE_DEBUG_GEO_VERIFY_DY"));
01038 }
01039
01040
01041 double *lbda = cpl_table_get_data_double(aSpots, "lambda");
01042 cpl_vector *vlbda = cpl_vector_wrap(nrow, lbda);
01043 cpl_vector *lambdas = muse_cplvector_get_unique(vlbda);
01044 cpl_vector_unwrap(vlbda);
01045 int nlines = cpl_vector_get_size(lambdas);
01046
01047
01048
01049 cpl_array *dy = cpl_array_new(kMuseSlicesPerCCD * nlines * kMuseCUmpmSpotsPerSlice,
01050 CPL_TYPE_DOUBLE);
01051
01052
01053
01054
01055 unsigned short nslice;
01056 for (nslice = 1; nslice <= kMuseSlicesPerCCD; nslice++) {
01057 int iline;
01058 for (iline = 0; iline < nlines; iline++) {
01059 double lambda = cpl_vector_get(lambdas, iline);
01060
01061 unsigned char nspot;
01062 double vposref = -DBL_MAX;
01063 for (nspot = 1; nspot <= kMuseCUmpmSpotsPerSlice; nspot++) {
01064 cpl_table *tspot = muse_geo_get_spot_peaks(aSpots, ifu, nslice, nspot,
01065 lambda, vposref, verifydy, dy);
01066 cpl_table_delete(tspot);
01067 }
01068 }
01069 }
01070 cpl_vector_delete(lambdas);
01071
01072 muse_cplarray_erase_invalid(dy);
01073 cpl_msg_debug(__func__, "Median vertical pinhole distance in IFU %02hhu: %f mm",
01074 ifu, cpl_array_get_median(dy));
01075 #pragma omp critical (geo_dy_array_insert)
01076 cpl_array_insert(aDY, dy, cpl_array_get_size(aDY));
01077 cpl_array_delete(dy);
01078
01079 return CPL_ERROR_NONE;
01080 }
01081
01082
01110
01111 double
01112 muse_geo_compute_pinhole_global_distance(cpl_array *aDY, double aWidth,
01113 double aMin, double aMax)
01114 {
01115 cpl_ensure(aDY, CPL_ERROR_NULL_INPUT, 0.);
01116 cpl_ensure(cpl_array_get_type(aDY) == CPL_TYPE_DOUBLE,
01117 CPL_ERROR_INCOMPATIBLE_INPUT, 0.);
01118 cpl_ensure(cpl_array_count_invalid(aDY) < cpl_array_get_size(aDY),
01119 CPL_ERROR_ILLEGAL_INPUT, 0.);
01120
01121
01122 cpl_bivector *histogram = muse_cplarray_histogram(aDY, aWidth, aMin, aMax);
01123 #if 0
01124 printf("aDY array 1: %f +/- %f (%f)\n", cpl_array_get_mean(aDY),
01125 cpl_array_get_stdev(aDY), cpl_array_get_median(aDY));
01126 cpl_array_dump(aDY, 0, 1000000, stdout);
01127 printf("aDY histogram 1:\n");
01128 cpl_plot_bivector(NULL, "w lp", NULL, histogram);
01129 cpl_bivector_dump(histogram, stdout);
01130 fflush(stdout);
01131 #endif
01132 muse_cplarray_erase_outliers(aDY, histogram, 1, 0.5);
01133 cpl_bivector_delete(histogram);
01134 double mean = cpl_array_get_mean(aDY),
01135 stdev = cpl_array_get_stdev(aDY),
01136 min = mean - 2 * stdev,
01137 max = mean + 2 * stdev,
01138 step = (max - min) / 20.;
01139 #if 0
01140 double median = cpl_array_get_median(aDY);
01141 printf("aDY array 2: %f +/- %f (%f)\n", mean, stdev, median);
01142 cpl_array_dump(aDY, 0, 1000000, stdout);
01143 fflush(stdout);
01144 #endif
01145 histogram = muse_cplarray_histogram(aDY, step, min, max);
01146 #if 0
01147 printf("aDY histogram 2:\n");
01148 cpl_plot_bivector(NULL, "w lp", NULL, histogram);
01149 cpl_bivector_dump(histogram, stdout);
01150 fflush(stdout);
01151 #endif
01152 muse_cplarray_erase_outliers(aDY, histogram, 1, 0.5);
01153 cpl_bivector_delete(histogram);
01154 mean = cpl_array_get_mean(aDY);
01155 stdev = cpl_array_get_stdev(aDY);
01156 #if 0
01157 double median2 = cpl_array_get_median(aDY);
01158 printf("aDY array 3: %f +/- %f (%f)\n", mean, stdev, median2);
01159 cpl_array_dump(aDY, 0, 1000000, stdout);
01160 fflush(stdout);
01161 #endif
01162
01163
01164
01165 cpl_msg_info(__func__, "Computed vertical pinhole distance of %.6f +/- %.6f "
01166 "mm (instead of %.4f)", mean, stdev, kMuseCUmpmDY);
01167 if (getenv("MUSE_GEOMETRY_PINHOLE_DY")) {
01168 cpl_msg_warning(__func__, "Vertical pinhole distance already overridden in the "
01169 "environment (%f mm)", atof(getenv("MUSE_GEOMETRY_PINHOLE_DY")));
01170 } else {
01171 char *envstring = cpl_sprintf("%f", mean);
01172 int err = setenv("MUSE_GEOMETRY_PINHOLE_DY", envstring, 1);
01173 if (!err) {
01174 cpl_msg_info(__func__, "Set MUSE_GEOMETRY_PINHOLE_DY=%s in the environment",
01175 envstring);
01176 }
01177 cpl_free(envstring);
01178 }
01179
01180 return mean;
01181 }
01182
01183
01194
01195 muse_geo_table *
01196 muse_geo_table_new(cpl_size aNRows, double aScale)
01197 {
01198 muse_geo_table *gt = cpl_calloc(1, sizeof(muse_geo_table));
01199 gt->table = muse_cpltable_new(muse_geo_table_def, aNRows);
01200 gt->scale = aScale;
01201 return gt;
01202 }
01203
01204
01215
01216 muse_geo_table *
01217 muse_geo_table_duplicate(const muse_geo_table *aGeo)
01218 {
01219 cpl_ensure(aGeo, CPL_ERROR_NULL_INPUT, NULL);
01220 muse_geo_table *gt = cpl_calloc(1, sizeof(muse_geo_table));
01221 gt->table = cpl_table_duplicate(aGeo->table);
01222 gt->scale = aGeo->scale;
01223 return gt;
01224 }
01225
01226
01235
01236 void
01237 muse_geo_table_delete(muse_geo_table *aGeo)
01238 {
01239 if (!aGeo) {
01240 return;
01241 }
01242 cpl_table_delete(aGeo->table);
01243 aGeo->table = NULL;
01244 cpl_free(aGeo);
01245 }
01246
01247
01311
01312 muse_geo_table *
01313 muse_geo_determine_initial(cpl_table *aSpots, const cpl_table *aTrace)
01314 {
01315 cpl_ensure(aSpots && aTrace, CPL_ERROR_NULL_INPUT, NULL);
01316 cpl_size nrow = cpl_table_get_nrow(aSpots);
01317 cpl_ensure(nrow > 10, CPL_ERROR_ILLEGAL_INPUT, NULL);
01318 cpl_ensure(muse_cpltable_check(aSpots, muse_geo_measurements_def) == CPL_ERROR_NONE,
01319 CPL_ERROR_INCOMPATIBLE_INPUT, NULL);
01320 const unsigned char ifu = cpl_table_get_column_min(aSpots, "SubField"),
01321 ifu2 = cpl_table_get_column_max(aSpots, "SubField");
01322 cpl_ensure(ifu == ifu2 && ifu >= 1 && ifu <= kMuseNumIFUs,
01323 CPL_ERROR_ILLEGAL_INPUT, NULL);
01324 const double kScale = cpl_table_get_column_mean(aSpots, "ScaleFOV");
01325 cpl_ensure(cpl_table_get_column_stdev(aSpots, "ScaleFOV") < 1e-10,
01326 CPL_ERROR_ILLEGAL_INPUT, NULL);
01327
01328 double maskangle = 0., fmaskrot = 1.;
01329 if (getenv("MUSE_GEOMETRY_MASK_ROTATION")) {
01330 maskangle = atof(getenv("MUSE_GEOMETRY_MASK_ROTATION"));
01331 fmaskrot = cos(maskangle * CPL_MATH_RAD_DEG);
01332 cpl_msg_warning(__func__, "Adapting to global mask rotation of %.4f deg "
01333 "(cos = %.4e)", maskangle, fmaskrot);
01334 }
01335 double pinholedy = kMuseCUmpmDY;
01336 if (getenv("MUSE_GEOMETRY_PINHOLE_DY")) {
01337 pinholedy = atof(getenv("MUSE_GEOMETRY_PINHOLE_DY"));
01338 cpl_msg_info(__func__, "Using pinhole y distance of %f mm (instead of "
01339 "%f mm)", pinholedy, kMuseCUmpmDY);
01340 }
01341
01342
01343 double *lbda = cpl_table_get_data_double(aSpots, "lambda");
01344 cpl_vector *vlbda = cpl_vector_wrap(nrow, lbda);
01345 cpl_vector *lambdas = muse_cplvector_get_unique(vlbda);
01346 cpl_vector_unwrap(vlbda);
01347 int nlines = cpl_vector_get_size(lambdas);
01348 muse_geo_table *gt = muse_geo_table_new(kMuseSlicesPerCCD
01349 * kMuseCUmpmSpotsPerSlice * nlines,
01350 kScale);
01351
01352 cpl_size irrow = 0;
01353 unsigned short nslice;
01354 for (nslice = 1; nslice <= kMuseSlicesPerCCD; nslice++) {
01355 cpl_polynomial **ptrace = muse_trace_table_get_polys_for_slice(aTrace,
01356 nslice);
01357 if (!ptrace) {
01358 cpl_msg_debug(__func__, "Skipping IFU %2hhu SliceCCD %2d", ifu, nslice);
01359 continue;
01360 }
01361 cpl_msg_info(__func__, "Handling IFU %2hhu SliceCCD %2d", ifu, nslice);
01362 int iline;
01363 for (iline = 0; iline < nlines; iline++) {
01364 double lambda = cpl_vector_get(lambdas, iline);
01365
01366 unsigned char nspot, nslicespot = 0;
01367 double vposref = -DBL_MAX;
01368 for (nspot = 1; nspot <= kMuseCUmpmSpotsPerSlice; nspot++) {
01369 cpl_table *tspot = muse_geo_get_spot_peaks(aSpots, ifu, nslice, nspot,
01370 lambda, vposref, CPL_FALSE,
01371 NULL);
01372 if (!tspot) {
01373 break;
01374 }
01375 nslicespot++;
01376 double xcenter = 0., ycenter = 0.,
01377 vpos = 0., ftot = 0.;
01378 nrow = cpl_table_get_nrow(tspot);
01379 cpl_size irow;
01380 for (irow = 0; irow < nrow; irow++) {
01381 double flux = cpl_table_get(tspot, "flux", irow, NULL);
01382 ftot += flux;
01383 xcenter += cpl_table_get(tspot, "xc", irow, NULL) * flux;
01384 ycenter += cpl_table_get(tspot, "yc", irow, NULL) * flux;
01385 vpos += cpl_table_get(tspot, "VPOS", irow, NULL) * flux;
01386 }
01387 cpl_table_delete(tspot);
01388 if (ftot <= 0.) {
01389 cpl_msg_warning(__func__, "Invalid integrated flux of spot %1hhu/%1hhu "
01390 "in slice %2hu of IFU %2hhu at wavelength %.3f: %e",
01391 nspot, nslicespot, nslice, ifu, lambda, ftot);
01392 break;
01393 }
01394 xcenter /= ftot;
01395 ycenter /= ftot;
01396 vpos /= ftot;
01397 if (vposref < 0) {
01398
01399 vposref = vpos;
01400 }
01401 double xcen = cpl_polynomial_eval_1d(ptrace[MUSE_TRACE_CENTER], ycenter,
01402 NULL),
01403 xl = cpl_polynomial_eval_1d(ptrace[MUSE_TRACE_LEFT], ycenter, NULL),
01404 xr = cpl_polynomial_eval_1d(ptrace[MUSE_TRACE_RIGHT], ycenter, NULL),
01405 xwidth = xr - xl;
01406 cpl_msg_debug(__func__, "IFU %2hhu SliceCCD %2d spot %1hhu/%1hhu lambda %.3f "
01407 "x/y %8.3f %8.3f (xcen %8.3f xwidth %6.3f) vpos %f flux %e",
01408 ifu, nslice, nspot, nslicespot, lambda, xcenter, ycenter,
01409 xcen, xwidth, vpos, ftot);
01410 cpl_table_set_int(gt->table, MUSE_GEOTABLE_FIELD, irrow, ifu);
01411 cpl_table_set_int(gt->table, MUSE_GEOTABLE_SKY, irrow,
01412 kMuseGeoSliceSky[nslice - 1]);
01413 cpl_table_set_int(gt->table, MUSE_GEOTABLE_CCD, irrow, nslice);
01414 cpl_table_set_int(gt->table, "spot", irrow, nslicespot);
01415
01416
01417 unsigned char stack = nslice <= 12 ? 4 : (nslice <= 24 ? 3 : (nslice <= 36 ? 2 : 1));
01418 cpl_table_set_int(gt->table, "stack", irrow, stack);
01419
01420 cpl_table_set_double(gt->table, "xc", irrow, xcenter);
01421 cpl_table_set_double(gt->table, "yc", irrow, ycenter);
01422 cpl_table_set_double(gt->table, "dxl", irrow, xcenter - xl);
01423 cpl_table_set_double(gt->table, "dxr", irrow, xr - xcenter);
01424 cpl_table_set_double(gt->table, "vpos", irrow, vpos);
01425 cpl_table_set_double(gt->table, "flux", irrow, ftot);
01426 cpl_table_set_double(gt->table, "lambda", irrow, lambda);
01427
01428
01429 cpl_table_set_invalid(gt->table, MUSE_GEOTABLE_WIDTH, irrow);
01430 cpl_table_set_invalid(gt->table, MUSE_GEOTABLE_ANGLE, irrow);
01431
01432
01433
01434 if (nslicespot == kMuseCUmpmSpotsPerSlice) {
01435
01436 int err1a, err2a, err1b, err2b;
01437 double xc1 = cpl_table_get_double(gt->table, "xc", irrow - 2, &err1a),
01438 xc2 = cpl_table_get_double(gt->table, "xc", irrow - 1, &err2a),
01439 vpos1 = cpl_table_get_double(gt->table, "vpos", irrow - 2, &err1b),
01440 vpos2 = cpl_table_get_double(gt->table, "vpos", irrow - 1, &err2b);
01441 if (!err1a && !err2a) {
01442 if (xcenter < xc2 || xc2 < xc1) {
01443 cpl_msg_warning(__func__, "spots are not sorted left-to-right on "
01444 "the CCD (%f .. %f .. %f)!", xc1, xc2, xcenter);
01445 }
01446 double dx = (xcenter - xc1) / 2.;
01447
01448
01449 double dxerr = sqrt((pow(xcenter - xc2 - dx, 2)
01450 + pow(xc2 - xc1 - dx, 2)) / 3.);
01451
01452
01453
01454
01455
01456
01457 double xreloffset = 0.;
01458 if (dxerr > 0.3) {
01459 const double dxexpected = 26.04644 - 0.05537208 * ifu;
01460 double dx1 = xc2 - xc1,
01461 dx2 = xcenter - xc2,
01462 diff1 = fabs(dx1 - dxexpected),
01463 diff2 = fabs(dx2 - dxexpected),
01464 dxnew;
01465 if (fmin(diff1, diff2) > 1.5) {
01466 dxnew = 99.;
01467 } else if (diff2 > diff1) {
01468 dxnew = dx1;
01469 } else {
01470 dxnew = dx2;
01471 }
01472
01473
01474 xreloffset = xwidth * kMuseCUmpmDX / fmaskrot
01475 * kMuseTypicalCubeSizeX * kScale / 60.
01476 * fabs(1. / dx - 1 / dxnew) / 2.;
01477 cpl_msg_debug(__func__, "IFU %2hhu SliceCCD %2d spot %1hhu/%1hhu "
01478 "lambda %.3f dx %.3f +/- %.3f (%.3f %.3f): dxerr "
01479 "is large, using a guess of %.3f +/- %.3f "
01480 "(expected was %.3f for this IFU), adding %.3f to"
01481 " xrel!", ifu, nslice, nspot, nslicespot, lambda,
01482 dx, dxerr, xc2-xc1, xcenter-xc2, dxnew, dxerr / 2.,
01483 dxexpected, xreloffset);
01484 dx = dxnew;
01485 dxerr /= 2.;
01486 }
01487 cpl_table_fill_column_window_double(gt->table, "dx", irrow - 2, 3, dx);
01488 cpl_table_fill_column_window_double(gt->table, "dxerr", irrow - 2, 3, dxerr);
01489
01490
01491
01492
01493 double scale = kMuseCUmpmDX / fmaskrot / dx,
01494 width = xwidth * scale * kMuseTypicalCubeSizeX * kScale / 60.,
01495 werr = width * dxerr / dx;
01496 if (width > kMuseSliceLoLikelyWidth && width < kMuseSliceHiLikelyWidth) {
01497 cpl_table_fill_column_window_double(gt->table, MUSE_GEOTABLE_WIDTH,
01498 irrow - 2, 3, width);
01499 cpl_table_fill_column_window_double(gt->table, MUSE_GEOTABLE_WIDTH"err",
01500 irrow - 2, 3, werr);
01501 } else {
01502 cpl_msg_info(__func__, "IFU %2hhu slice %2d lambda %.3f: computed "
01503 "an unlikely width: %.3f +/- %.3f (hope for other"
01504 " wavelengths!)", ifu, nslice, lambda, width, werr);
01505 cpl_table_set_column_invalid(gt->table, MUSE_GEOTABLE_WIDTH,
01506 irrow - 2, 3);
01507 cpl_table_set_column_invalid(gt->table, MUSE_GEOTABLE_WIDTH"err",
01508 irrow - 2, 3);
01509 }
01510
01511
01512
01513 double xrel = (xcen - xc1 + xreloffset) * scale,
01514 xrelerr = fabs(xrel * dxerr / dx);
01515 cpl_table_set_double(gt->table, "xrel", irrow - 2, xrel);
01516 cpl_table_set_double(gt->table, "xrelerr", irrow - 2, xrelerr);
01517 xrel = (xcen - xc2 + xreloffset) * scale;
01518 xrelerr = fabs(xrel * dxerr / dx);
01519 cpl_table_set_double(gt->table, "xrel", irrow - 1, xrel);
01520 cpl_table_set_double(gt->table, "xrelerr", irrow - 1, xrelerr);
01521 xrel = (xcen - xcenter + xreloffset) * scale;
01522 xrelerr = fabs(xrel * dxerr / dx);
01523 cpl_table_set_double(gt->table, "xrel", irrow, xrel);
01524 cpl_table_set_double(gt->table, "xrelerr", irrow, xrelerr);
01525 }
01526
01527 if (!err1b && !err2b) {
01528
01529
01530 double pdiff = fmax(vpos1, fmax(vpos2, vpos))
01531 - fmin(vpos1, fmin(vpos2, vpos));
01532 if (pdiff > 0.5) {
01533 double pmean = (vpos1 + vpos2 + vpos) / 3.;
01534 if (vpos1 > pmean) {
01535 vpos1 -= pinholedy * fmaskrot;
01536 }
01537 if (vpos2 > pmean) {
01538 vpos2 -= pinholedy * fmaskrot;
01539 }
01540 if (vpos > pmean) {
01541 vpos -= pinholedy * fmaskrot;
01542 }
01543 double pdiff2 = fmax(vpos1, fmax(vpos2, vpos))
01544 - fmin(vpos1, fmin(vpos2, vpos));
01545 if (pdiff2 < pdiff) {
01546 cpl_msg_debug(__func__, "Fixed max vpos diff from %f down to %f",
01547 pdiff, pdiff2);
01548 } else {
01549 double vpos1o = cpl_table_get_double(gt->table, "vpos", irrow - 2, NULL),
01550 vpos2o = cpl_table_get_double(gt->table, "vpos", irrow - 1, NULL),
01551 vpos3o = cpl_table_get_double(gt->table, "vpos", irrow, NULL);
01552 cpl_msg_info(__func__, "Large max vpos diff detected but "
01553 "not fixed! (original: %f %f %f, %f -> mean %f "
01554 "-> fixed: %f %f %f, %f)", vpos1o, vpos2o, vpos3o,
01555 pdiff, pmean, vpos1, vpos2, vpos, pdiff2);
01556 vpos1 = vpos1o;
01557 vpos2 = vpos2o;
01558 vpos = vpos3o;
01559 }
01560 }
01561
01562
01563 double f = 1. / kMuseCUmpmDX / fmaskrot,
01564 angle1 = -atan((vpos2 - vpos1) * f) * CPL_MATH_DEG_RAD,
01565 angle2 = -atan((vpos - vpos2) * f) * CPL_MATH_DEG_RAD,
01566 angle = (angle1 + angle2) / 2.;
01567
01568 double aerr = sqrt((pow(angle1 - angle, 2) + pow(angle2 - angle, 2)) / 3.);
01569 if (fabs(angle) < kMuseGeoSliceMaxAngle) {
01570 cpl_table_fill_column_window_double(gt->table, MUSE_GEOTABLE_ANGLE,
01571 irrow - 2, 3, angle);
01572 cpl_table_fill_column_window_double(gt->table, MUSE_GEOTABLE_ANGLE"err",
01573 irrow - 2, 3, aerr);
01574 } else {
01575 cpl_msg_info(__func__, "IFU %2hhu slice %2d lambda %.3f: computed"
01576 " an unlikely angle: %.3f +/- %.3f (hope for other "
01577 "wavelengths!)", ifu, nslice, lambda, angle, aerr);
01578 cpl_table_set_column_invalid(gt->table, MUSE_GEOTABLE_ANGLE,
01579 irrow - 2, 3);
01580 cpl_table_set_column_invalid(gt->table, MUSE_GEOTABLE_ANGLE"err",
01581 irrow - 2, 3);
01582 }
01583 }
01584
01585
01586
01587 double dx = cpl_table_get_double(gt->table, "dx", irrow - 1, NULL),
01588 dxl = cpl_table_get_double(gt->table, "dxl", irrow - 2, NULL),
01589 dxr = cpl_table_get_double(gt->table, "dxr", irrow, NULL);
01590 cpl_table_fill_column_window_double(gt->table, "dxl", irrow - 2, 3, dxl / dx);
01591 cpl_table_fill_column_window_double(gt->table, "dxr", irrow - 2, 3, dxr / dx);
01592 }
01593
01594 irrow++;
01595 }
01596 }
01597 muse_trace_polys_delete(ptrace);
01598 }
01599 cpl_vector_delete(lambdas);
01600 cpl_table_set_size(gt->table, irrow);
01601
01602 cpl_table_and_selected_invalid(gt->table, MUSE_GEOTABLE_WIDTH);
01603 cpl_table_or_selected_invalid(gt->table, MUSE_GEOTABLE_ANGLE);
01604 cpl_table_erase_selected(gt->table);
01605 return gt;
01606 }
01607
01608
01628
01629 static cpl_size
01630 muse_geo_determine_horizontal_wmean(const cpl_table *aSlice,
01631 const char *aCol, const char *aColErr,
01632 double *aValue, double *aError,
01633 double *aMedian, double aSigma)
01634 {
01635 const char func[] = "muse_geo_determine_horizontal";
01636 if (!aSlice) return -1;
01637 if (!aCol || !aValue) return -1;
01638
01639
01640 const double *vv = cpl_table_get_data_double_const(aSlice, aCol),
01641 *ve = NULL;
01642 if (aColErr) {
01643 ve = cpl_table_get_data_double_const(aSlice, aColErr);
01644 }
01645 if (!vv) return -1;
01646
01647 cpl_size n = cpl_table_get_nrow(aSlice);
01648 cpl_vector *vtmp = cpl_vector_wrap(n, (double *)cpl_table_get_data_double_const(aSlice, aCol));
01649 double median = cpl_table_get_column_median(aSlice, aCol),
01650 mdev = muse_cplvector_get_adev_const(vtmp, median),
01651 vlo = median - aSigma * mdev,
01652 vhi = median + aSigma * mdev;
01653 cpl_vector_unwrap(vtmp);
01654 cpl_msg_debug(func, "%s/%s: median %f +/- %f --> %f...%f", aCol, aColErr,
01655 median, mdev, vlo, vhi);
01656
01657
01658
01659 double value, sigma = mdev,
01660 min, max;
01661 cpl_vector *vmedian = NULL;
01662 cpl_size nrejected;
01663 do {
01664 min = DBL_MAX;
01665 max = -DBL_MAX;
01666 if (aMedian) {
01667 vmedian = cpl_vector_new(n);
01668 }
01669 double weight = 0.;
01670 value = 0.;
01671 nrejected = 0;
01672 cpl_size i, im = 0;
01673 for (i = 0; i < n; i++) {
01674 if (vv[i] < vlo || vv[i] > vhi) {
01675 nrejected++;
01676 continue;
01677 }
01678 if (ve) {
01679 value += vv[i] / ve[i];
01680 weight += 1. / ve[i];
01681 } else {
01682 value += vv[i];
01683 weight += 1.;
01684 }
01685 if (vv[i] < min) {
01686 min = vv[i];
01687 }
01688 if (vv[i] > max) {
01689 max = vv[i];
01690 }
01691 if (aMedian) {
01692 cpl_vector_set(vmedian, im++, vv[i]);
01693 }
01694 }
01695 value /= weight;
01696 double wmse = 0.;
01697 for (i = 0; i < n; i++) {
01698 if (vv[i] < vlo || vv[i] > vhi) {
01699 continue;
01700 }
01701 if (ve) {
01702 wmse += pow(vv[i] - value, 2) / ve[i];
01703 } else {
01704 wmse += pow(vv[i] - value, 2);
01705 }
01706 #if 0
01707 if (aMedian) {
01708 cpl_msg_debug("vpos", "%f %f %f -> %f", vv[i], vv[i] - value,
01709 pow(vv[i] - value, 2), wmse);
01710 }
01711 #endif
01712 }
01713 wmse /= weight;
01714 if (aMedian) {
01715 cpl_vector_set_size(vmedian, im);
01716 *aMedian = cpl_vector_get_median(vmedian);
01717 cpl_vector_delete(vmedian);
01718 }
01719
01720 if (isnormal(weight) && isfinite(wmse)) {
01721
01722
01723 sigma = sqrt(ve ? (n - nrejected) / (weight*weight) : 0. + wmse);
01724 #if 0
01725 if (aMedian) {
01726 cpl_msg_debug("vpos", "%f", sigma);
01727 }
01728 #endif
01729 }
01730
01731 if (isfinite(value)) {
01732 vlo = value - aSigma * sigma;
01733 vhi = value + aSigma * sigma;
01734 }
01735 #if 0
01736 cpl_msg_debug(func, "%s/%s: %f +/- %f (+/- %f) --> %f...%f (rej. %"
01737 CPL_SIZE_FORMAT" / %"CPL_SIZE_FORMAT")", aCol, aColErr,
01738 value, sqrt(wmse), sigma, vlo, vhi, nrejected, n);
01739 #endif
01740 } while (nrejected < n && (max > vhi || min < vlo));
01741
01742
01743
01744
01745 if (nrejected == n) {
01746 value = (vlo + vhi) / 2.;
01747 sigma = (vhi - vlo) / (2. * aSigma);
01748 #if 0
01749 cpl_msg_debug(func, "%s/%s: %f +/- %f (ALL rej. %"CPL_SIZE_FORMAT" / %"
01750 CPL_SIZE_FORMAT")", aCol, aColErr, value, sigma, nrejected, n);
01751 #endif
01752 }
01753
01754 *aValue = value;
01755 if (aError) {
01756 *aError = sigma;
01757 }
01758 return nrejected;
01759 }
01760
01761
01782
01783 static void
01784 muse_geo_determine_horizontal_vpos(const cpl_table *aSlice,
01785 double *aP, double *aPE, double *aPM,
01786 double aDY, double aF)
01787 {
01788 const char func[] = "muse_geo_determine_horizontal";
01789 if (!aSlice) return;
01790 if (!aP || !aPE || !aPM) return;
01791
01792 cpl_vector *vvpos = cpl_vector_wrap(cpl_table_get_nrow(aSlice),
01793 (double *)cpl_table_get_data_double_const(aSlice,
01794 "vpos"));
01795 *aP = cpl_vector_get_mean(vvpos);
01796 *aPE = cpl_vector_get_stdev(vvpos);
01797 *aPM = cpl_vector_get_median_const(vvpos);
01798
01799
01800 if (*aPE < 0.01) {
01801 cpl_vector_unwrap(vvpos);
01802 return;
01803 }
01804
01805 cpl_size n = cpl_vector_get_size(vvpos);
01806 cpl_vector *vpp = cpl_vector_duplicate(vvpos),
01807 *vres = cpl_vector_duplicate(vvpos),
01808 *vmedian = cpl_vector_new(n);
01809 cpl_vector_unwrap(vvpos);
01810 cpl_vector_fill(vmedian, *aPM);
01811 cpl_vector_subtract(vres, vmedian);
01812 cpl_vector_delete(vmedian);
01813 double min = cpl_vector_get_min(vres),
01814 max = cpl_vector_get_max(vres);
01815 cpl_msg_debug(func, "vector of vpos values (%.4f +/- %.4f, %.4f) and "
01816 "residuals (%.4f ... %.4f), pinhole distance %.4f",
01817 *aP, *aPE, *aPM, min, max, aDY * aF);
01818 #if 0
01819 cpl_bivector *biv = cpl_bivector_wrap_vectors(vpp, vres);
01820 cpl_bivector_dump(biv, stdout);
01821 fflush(stdout);
01822 cpl_bivector_unwrap_vectors(biv);
01823 #endif
01824
01825
01826
01827 cpl_array *ares = cpl_array_wrap_double(cpl_vector_unwrap(vres), n);
01828 cpl_array_abs(ares);
01829 double amin = cpl_array_get_min(ares),
01830 amax = cpl_array_get_max(ares);
01831 cpl_boolean halfandhalf = fabs(aDY/2. - amin) < 0.01
01832 && fabs(aDY/2. - amax) < 0.01;
01833 cpl_array_delete(ares);
01834
01835 double p2, pe2, pm2;
01836 cpl_size i;
01837 double limit = fabs(0.9 * aDY * aF);
01838 if (!halfandhalf &&
01839 fabs(min) < limit && fabs(max) < limit) {
01840
01841
01842
01843 cpl_size nrej = muse_geo_determine_horizontal_wmean(aSlice, "vpos", NULL,
01844 &p2, &pe2, &pm2, 2.);
01845 cpl_msg_debug(func, "values after rejection vector of vpos values (%.4f "
01846 "+/- %.4f, %.4f), %"CPL_SIZE_FORMAT" rejected", p2, pe2, pm2,
01847 nrej);
01848 } else {
01849
01850
01851 cpl_size nfixed = 0;
01852 for (i = 0; i < n; i++) {
01853 double v = cpl_vector_get(vpp, i);
01854 if (v > *aP) {
01855 cpl_vector_set(vpp, i, v - aDY * aF);
01856 nfixed++;
01857 }
01858 }
01859 p2 = cpl_vector_get_mean(vpp);
01860 pe2 = cpl_vector_get_stdev(vpp);
01861 pm2 = cpl_vector_get_median_const(vpp);
01862 cpl_msg_debug(func, "fixed vector of vpos values (%.4f +/- %.4f, %.4f), "
01863 "%"CPL_SIZE_FORMAT" fixed", p2, pe2, pm2, nfixed);
01864 #if 0
01865 cpl_vector_dump(vpp, stdout);
01866 fflush(stdout);
01867 #endif
01868 }
01869 cpl_vector_delete(vpp);
01870 if (pe2 < *aPE) {
01871 *aP = p2;
01872 *aPE = pe2;
01873 *aPM = pm2;
01874 }
01875 }
01876
01877
01927
01928 muse_geo_table *
01929 muse_geo_determine_horizontal(const muse_geo_table *aGeo)
01930 {
01931 cpl_ensure(aGeo && aGeo->table, CPL_ERROR_NULL_INPUT, NULL);
01932 cpl_size nrow = cpl_table_get_nrow(aGeo->table);
01933 cpl_ensure(nrow >= 50, CPL_ERROR_ILLEGAL_INPUT, NULL);
01934 cpl_ensure(muse_cpltable_check(aGeo->table, muse_geo_table_def) == CPL_ERROR_NONE,
01935 CPL_ERROR_INCOMPATIBLE_INPUT, NULL);
01936 const unsigned char ifu = cpl_table_get_column_min(aGeo->table, MUSE_GEOTABLE_FIELD),
01937 ifu2 = cpl_table_get_column_max(aGeo->table, MUSE_GEOTABLE_FIELD);
01938 cpl_ensure(ifu == ifu2 && ifu >= 1 && ifu <= kMuseNumIFUs,
01939 CPL_ERROR_ILLEGAL_INPUT, NULL);
01940
01941 double maskangle = 0., fmaskrot = 1.;
01942 if (getenv("MUSE_GEOMETRY_MASK_ROTATION")) {
01943 maskangle = atof(getenv("MUSE_GEOMETRY_MASK_ROTATION"));
01944 fmaskrot = cos(maskangle * CPL_MATH_RAD_DEG);
01945 cpl_msg_warning(__func__, "Adapting to global mask rotation of %.4f deg "
01946 "(cos = %.4e)", maskangle, fmaskrot);
01947 }
01948 double pinholedy = kMuseCUmpmDY;
01949 if (getenv("MUSE_GEOMETRY_PINHOLE_DY")) {
01950 pinholedy = atof(getenv("MUSE_GEOMETRY_PINHOLE_DY"));
01951 cpl_msg_info(__func__, "Using pinhole y distance of %f mm (instead of "
01952 "%f mm)", pinholedy, kMuseCUmpmDY);
01953 }
01954 cpl_boolean stdgap = getenv("MUSE_GEOMETRY_STD_GAP")
01955 && atoi(getenv("MUSE_GEOMETRY_STD_GAP")) > 0;
01956 if (stdgap) {
01957 cpl_msg_warning(__func__, "Using old (standard) gap computation");
01958 } else {
01959 cpl_msg_info(__func__, "Using new (alternative) gap computation");
01960 }
01961 const double kScaleX = kMuseTypicalCubeSizeX * aGeo->scale / 60.;
01962
01963
01964 muse_geo_table *gt = muse_geo_table_duplicate(aGeo);
01965 cpl_table_and_selected_int(gt->table, "spot", CPL_NOT_EQUAL_TO, 2);
01966 cpl_table_erase_selected(gt->table);
01967
01968 #if 0
01969 FILE *f = fopen("bla_horizontal.dat", "w");
01970 fprintf(f, "#");
01971 cpl_table_dump(gt->table, 0, 100000000, f);
01972 fclose(f);
01973 f = fopen("bla_lambdas.dat", "w");
01974 fprintf(f, "#");
01975 cpl_table_dump(aGeo->table, 0, 100000000, f);
01976 fclose(f);
01977 #endif
01978 unsigned short nslice;
01979 for (nslice = 1; nslice <= kMuseSlicesPerCCD; nslice++) {
01980
01981 cpl_table_select_all(gt->table);
01982 cpl_table_and_selected_int(gt->table, "SliceCCD", CPL_EQUAL_TO, nslice);
01983 if (cpl_table_count_selected(gt->table) < 1) {
01984 continue;
01985 }
01986 cpl_array *asel = cpl_table_where_selected(gt->table);
01987 cpl_table *slice = cpl_table_extract_selected(gt->table);
01988 #if 0
01989 cpl_table_dump(slice, 0, 1000, stdout);
01990 fflush(stdout);
01991 #endif
01992
01993
01994 double a, ae, w, we, xr, xre, dxl = 0., dxr = 0.;
01995 muse_geo_determine_horizontal_wmean(slice, MUSE_GEOTABLE_ANGLE,
01996 MUSE_GEOTABLE_ANGLE"err", &a, &ae,
01997 NULL, 2.);
01998 muse_geo_determine_horizontal_wmean(slice, MUSE_GEOTABLE_WIDTH,
01999 MUSE_GEOTABLE_WIDTH"err", &w, &we,
02000 NULL, 1.5);
02001 muse_geo_determine_horizontal_wmean(slice, "xrel", "xrelerr", &xr, &xre,
02002 NULL, 1.5);
02003 muse_geo_determine_horizontal_wmean(slice, "dxl", NULL, &dxl, NULL, NULL, 2.);
02004 muse_geo_determine_horizontal_wmean(slice, "dxr", NULL, &dxr, NULL, NULL, 2.);
02005
02006
02007 cpl_errorstate ps = cpl_errorstate_get();
02008 double p = NAN, pe = -1, pm = NAN;
02009 muse_geo_determine_horizontal_vpos(slice, &p, &pe, &pm,
02010 pinholedy, fmaskrot);
02011 if (pe < 0 && !cpl_errorstate_is_equal(ps)) {
02012 pe = 0.1;
02013 cpl_errorstate_set(ps);
02014 }
02015
02016 cpl_msg_debug(__func__, "IFU %2hhu stack %1d slice %2d / %2d "
02017 "angle %6.3f +/- %.3f deg width %.3f +/- %.3f pix "
02018 "xrel %.4f +/- %.4f vpos %.4f +/- %.4f (%.4f)", ifu,
02019 cpl_table_get_int(slice, "stack", 0, NULL), nslice,
02020 cpl_table_get_int(slice, "SliceSky", 0, NULL), a, ae, w, we,
02021 xr, xre, p, pe, pm);
02022
02023
02024 cpl_size irow = cpl_array_get_cplsize(asel, 0, NULL);
02025 cpl_array_delete(asel);
02026 cpl_table_set_double(gt->table, MUSE_GEOTABLE_ANGLE, irow, a);
02027 cpl_table_set_double(gt->table, MUSE_GEOTABLE_ANGLE"err", irow, ae);
02028 cpl_table_set_double(gt->table, MUSE_GEOTABLE_WIDTH, irow, w);
02029 cpl_table_set_double(gt->table, MUSE_GEOTABLE_WIDTH"err", irow, we);
02030 cpl_table_set_double(gt->table, "xrel", irow, xr);
02031 cpl_table_set_double(gt->table, "xrelerr", irow, xre);
02032 cpl_table_set_double(gt->table, "vpos", irow, p);
02033 cpl_table_set_double(gt->table, "vposerr", irow, pe);
02034 cpl_table_set_double(gt->table, "dxl", irow, dxl);
02035 cpl_table_set_double(gt->table, "dxr", irow, dxr);
02036
02037
02038 cpl_table_set_invalid(gt->table, "flux", irow);
02039 cpl_table_set_invalid(gt->table, "lambda", irow);
02040 cpl_table_set_invalid(gt->table, "xc", irow);
02041 cpl_table_set_invalid(gt->table, "yc", irow);
02042 cpl_table_unselect_row(gt->table, irow);
02043 cpl_table_erase_selected(gt->table);
02044 cpl_table_delete(slice);
02045 }
02046 #if 0
02047 printf("intermediate result: weighted averages of angle, width, and xrel:\n");
02048 cpl_table_dump(gt->table, 0, 1000000, stdout);
02049 fflush(stdout);
02050 #endif
02051
02052
02053
02054
02055
02056
02057
02058 const unsigned short nsoff = kMuseSlicesPerCCD / 4;
02059 for (nslice = 1 + nsoff; nslice <= 2*nsoff; nslice++) {
02060
02061 cpl_table_unselect_all(gt->table);
02062 cpl_table_or_selected_int(gt->table, "SliceSky", CPL_EQUAL_TO, nslice - nsoff);
02063 cpl_table_or_selected_int(gt->table, "SliceSky", CPL_EQUAL_TO, nslice);
02064 cpl_table_or_selected_int(gt->table, "SliceSky", CPL_EQUAL_TO, nslice + nsoff);
02065 cpl_table_or_selected_int(gt->table, "SliceSky", CPL_EQUAL_TO, nslice + 2*nsoff);
02066
02067 cpl_table *ts = cpl_table_extract_selected(gt->table);
02068
02069 int irow, nrowts = cpl_table_get_nrow(ts),
02070 i1 = -1, i2 = -1, i3 = -1, i4 = -1;
02071 for (irow = 0; irow < nrowts; irow++) {
02072
02073
02074 switch (cpl_table_get_int(ts, "stack", irow, NULL)) {
02075 case 1: i1 = irow; break;
02076 case 2: i2 = irow; break;
02077 case 3: i3 = irow; break;
02078 case 4: i4 = irow; break;
02079 }
02080 }
02081
02082 if (i3 < 0 || i2 < 0) {
02083 char *msg = cpl_sprintf("For IFU %2hhu / row %2d in the slicer stacks "
02084 "(slice sky numbers %02d, %02d, %02d, %02d), at "
02085 "least one of the two middle stacks (%s/%s) is "
02086 "missing", ifu, nslice - nsoff,
02087 nslice - nsoff, nslice, nslice + nsoff, nslice + 2*nsoff,
02088 i3 < 0 ? "left" : "-", i2 < 0 ? "right" : "-");
02089 cpl_error_set_message(__func__, CPL_ERROR_DATA_NOT_FOUND, "%s", msg);
02090 cpl_msg_error(__func__, "%s", msg);
02091 cpl_free(msg);
02092 cpl_table_dump(ts, 0, 10000, stdout);
02093 cpl_table_delete(ts);
02094 continue;
02095 }
02096
02097 double *xrel = cpl_table_get_data_double(ts, "xrel"),
02098 *xrerr = cpl_table_get_data_double(ts, "xrelerr"),
02099 *width = cpl_table_get_data_double(ts, MUSE_GEOTABLE_WIDTH),
02100 *werr = cpl_table_get_data_double(ts, MUSE_GEOTABLE_WIDTH"err"),
02101 *pdxl = cpl_table_get_data_double(ts, "dxl"),
02102 *pdxr = cpl_table_get_data_double(ts, "dxr");
02103
02104 cpl_table_duplicate_column(ts, "width_mm",
02105 ts, MUSE_GEOTABLE_WIDTH);
02106 cpl_table_multiply_scalar(ts, "width_mm", 1. / kScaleX);
02107 cpl_table_set_column_unit(ts, "width_mm", "mm");
02108 cpl_table_duplicate_column(ts, "widtherr_mm",
02109 ts, MUSE_GEOTABLE_WIDTH"err");
02110 cpl_table_multiply_scalar(ts, "widtherr_mm", 1. / kScaleX);
02111 cpl_table_set_column_unit(ts, "widtherr_mm", "mm");
02112 double *wmm = cpl_table_get_data_double(ts, "width_mm"),
02113 *werrmm = cpl_table_get_data_double(ts, "widtherr_mm");
02114
02115 double cgap1 = (3. * kMuseCUmpmDX / fmaskrot
02116 - (xrel[i3] + wmm[i3] / 2. + wmm[i2] / 2. - xrel[i2]))
02117 * kScaleX,
02118 cgerr = sqrt(pow(xrerr[i3], 2) + pow(xrerr[i2], 2) +
02119 pow(werrmm[i3] / 2., 2) + pow(werrmm[i2] / 2., 2))
02120 * kScaleX,
02121 cgap2 = kMuseCUmpmDX * (1. - pdxl[i3] - pdxr[i2])
02122 * kScaleX,
02123 cgap = stdgap ? cgap1 : cgap2;
02124 #if 0
02125 cpl_msg_debug(__func__, "cgap: %f, %f +/- %f", cgap1, cgap2, cgerr);
02126 #endif
02127
02128
02129 if (cgap < 0 || cgap > 0.5) {
02130 cpl_msg_debug(__func__, "For IFU %2hhu / row %2d in the slicer stacks "
02131 "(slice sky numbers %02d, %02d, %02d, %02d), the central "
02132 "gap is unlikely (%f), reset to %.2f pix", ifu,
02133 nslice - nsoff, nslice - nsoff, nslice, nslice + nsoff,
02134 nslice + 2*nsoff, cgap, kMuseGeoMiddleGap);
02135 cgerr += sqrt(fabs(cgap));
02136 cgap = kMuseGeoMiddleGap;
02137 }
02138
02139
02140 cpl_table_set_double(ts, MUSE_GEOTABLE_X, i3, -(cgap / 2. + width[i3] / 2.));
02141 cpl_table_set_double(ts, MUSE_GEOTABLE_X, i2, cgap / 2. + width[i2] / 2.);
02142 cpl_table_set_double(ts, MUSE_GEOTABLE_X"err", i3,
02143 sqrt(cgerr*cgerr + werr[i3]*werr[i3]) / 2.);
02144 cpl_table_set_double(ts, MUSE_GEOTABLE_X"err", i2,
02145 sqrt(cgerr*cgerr + werr[i2]*werr[i2]) / 2.);
02146
02147 double lgap = NAN, lgerr = NAN;
02148 if (i4 >= 0) {
02149
02150
02151 lgerr = sqrt(pow(xrerr[i4], 2) + pow(xrerr[i3], 2) +
02152 pow(werrmm[i4] / 2., 2) + pow(werrmm[i3] / 2., 2))
02153 * kScaleX;
02154 double lgap1 = (3. * kMuseCUmpmDX / fmaskrot
02155 - (xrel[i4] + wmm[i4] / 2. + wmm[i3] / 2. - xrel[i3]))
02156 * kScaleX,
02157 lgap2 = kMuseCUmpmDX * (1. - pdxl[i4] - pdxr[i3])
02158 * kScaleX;
02159 lgap = stdgap ? lgap1 : lgap2;
02160 #if 0
02161 cpl_msg_debug(__func__, "lgap: %f, %f +/- %f", lgap1, lgap2, lgerr);
02162 #endif
02163 if (lgap < 0 || lgap > 0.5) {
02164 cpl_msg_debug(__func__, "For IFU %2hhu / row %2d in the slicer stacks"
02165 " (slice sky numbers %02d, %02d, %02d, %02d), the left "
02166 "gap is unlikely (%f), reset to %.2f pix", ifu,
02167 nslice - nsoff, nslice - nsoff, nslice, nslice + nsoff,
02168 nslice + 2*nsoff, lgap, kMuseGeoOuterGap);
02169 lgerr += sqrt(fabs(lgap));
02170 lgap = kMuseGeoOuterGap;
02171 }
02172 cpl_table_set_double(ts, MUSE_GEOTABLE_X, i4,
02173 -(cgap / 2. + width[i3] + lgap + width[i4] / 2.));
02174 cpl_table_set_double(ts, MUSE_GEOTABLE_X"err", i4,
02175 sqrt(cgerr*cgerr / 4. + werr[i3]*werr[i3]
02176 + lgerr*lgerr + werr[i4]*werr[i4] / 4.));
02177 } else {
02178 cpl_error_set_message(__func__, CPL_ERROR_DATA_NOT_FOUND, "For IFU %2hhu /"
02179 " row %2d in the slicer stacks (slice sky numbers"
02180 " %02d, %02d, %02d, %02d), the leftmost stack is "
02181 "missing", ifu, nslice - nsoff,
02182 nslice - nsoff, nslice, nslice + nsoff, nslice + 2*nsoff);
02183 }
02184
02185 double rgap = NAN, rgerr = NAN;
02186 if (i1 >= 0) {
02187
02188 rgerr = sqrt(pow(xrerr[i2], 2) + pow(xrerr[i1], 2) +
02189 pow(werrmm[i2] / 2., 2) + pow(werrmm[i1] / 2., 2))
02190 * kScaleX;
02191 double rgap1 = (3. * kMuseCUmpmDX / fmaskrot
02192 - (xrel[i2] + wmm[i2] / 2. + wmm[i1] / 2. - xrel[i1]))
02193 * kScaleX,
02194 rgap2 = kMuseCUmpmDX * (1. - pdxl[i2] - pdxr[i1])
02195 * kScaleX;
02196 rgap = stdgap ? rgap1 : rgap2;
02197 #if 0
02198 cpl_msg_debug(__func__, "rgap: %f, %f +/- %f", rgap1, rgap2, rgerr);
02199 #endif
02200 if (rgap < 0 || rgap > 0.5) {
02201 cpl_msg_debug(__func__, "For IFU %2hhu / row %2d in the slicer stacks"
02202 " (slice sky numbers %02d, %02d, %02d, %02d), the right"
02203 " gap is unlikely (%f), reset to %.2f pix", ifu,
02204 nslice - nsoff, nslice - nsoff, nslice, nslice + nsoff,
02205 nslice + 2*nsoff, rgap, kMuseGeoOuterGap);
02206 rgerr += sqrt(fabs(rgap));
02207 rgap = kMuseGeoOuterGap;
02208 }
02209 cpl_table_set_double(ts, MUSE_GEOTABLE_X, i1,
02210 cgap / 2. + width[i2] + rgap + width[i1] / 2.);
02211 cpl_table_set_double(ts, MUSE_GEOTABLE_X"err", i1,
02212 sqrt(cgerr*cgerr / 4. + werr[i2]*werr[i2]
02213 + rgerr*rgerr + werr[i1]*werr[i1] / 4.));
02214 } else {
02215 cpl_error_set_message(__func__, CPL_ERROR_DATA_NOT_FOUND, "For IFU %2hhu /"
02216 " row %2d in the slicer stacks (slice sky numbers"
02217 " %02d, %02d, %02d, %02d), the rightmost stack is"
02218 " missing", ifu, nslice - nsoff,
02219 nslice - nsoff, nslice, nslice + nsoff, nslice + 2*nsoff);
02220 }
02221 cpl_msg_debug(__func__, "IFU %2hhu row %2d gaps (slice sky numbers %02d, "
02222 "%02d, %02d, %02d): central %.3f +/- %.3f pix, left %.3f +/- "
02223 "%.3f pix, right %.3f +/- %.3f pix", ifu, nslice - nsoff,
02224 nslice - nsoff, nslice, nslice + nsoff, nslice + 2*nsoff,
02225 cgap, cgerr, lgap, lgerr, rgap, rgerr);
02226
02227
02228 cpl_table_erase_column(ts, "width_mm");
02229 cpl_table_erase_column(ts, "widtherr_mm");
02230
02231
02232
02233 cpl_table_erase_selected(gt->table);
02234 cpl_table_insert(gt->table, ts, cpl_table_get_nrow(gt->table));
02235 cpl_table_delete(ts);
02236 }
02237
02238
02239 cpl_propertylist *order = cpl_propertylist_new();
02240 cpl_propertylist_append_bool(order, "stack", CPL_TRUE);
02241 cpl_propertylist_append_bool(order, "SliceSky", CPL_FALSE);
02242 cpl_table_sort(gt->table, order);
02243 cpl_propertylist_delete(order);
02244 #if 0
02245 printf("intermediate result: only the y position is still missing:\n");
02246 cpl_table_dump(gt->table, 0, 1000000, stdout);
02247 fflush(stdout);
02248 #endif
02249 return gt;
02250 }
02251
02252
02266
02267 static unsigned char
02268 muse_geo_select_reference(const muse_geo_table *aGeo, unsigned short *aSlice)
02269 {
02270 unsigned char ifu = 0;
02271 unsigned short slice = 0;
02272
02273 cpl_table *geo = cpl_table_duplicate(aGeo->table);
02274 cpl_table_unselect_all(geo);
02275 cpl_table_or_selected_int(geo, MUSE_GEOTABLE_FIELD, CPL_EQUAL_TO, 12);
02276 cpl_table_and_selected_int(geo, MUSE_GEOTABLE_SKY, CPL_EQUAL_TO, 24);
02277 int nsel = cpl_table_count_selected(geo);
02278 if (nsel > 0) {
02279 ifu = 12;
02280 slice = 24;
02281 }
02282 unsigned char testifu = 13,
02283 testslice = 18;
02284 short testoffset = 1;
02285 while (ifu == 0 && slice == 0) {
02286 cpl_table_unselect_all(geo);
02287 cpl_table_or_selected_int(geo, MUSE_GEOTABLE_FIELD, CPL_EQUAL_TO, testifu);
02288 cpl_table_and_selected_int(geo, MUSE_GEOTABLE_SKY, CPL_EQUAL_TO, testslice);
02289 nsel = cpl_table_count_selected(geo);
02290 if (nsel > 0) {
02291 ifu = testifu;
02292 slice = testslice;
02293 }
02294 testifu += testoffset;
02295 if (testifu > kMuseNumIFUs) {
02296 testifu = 11;
02297 testoffset = -1;
02298 }
02299 }
02300 cpl_table_delete(geo);
02301 if (aSlice) {
02302 *aSlice = slice;
02303 }
02304 return ifu;
02305 }
02306
02307
02358
02359 cpl_error_code
02360 muse_geo_refine_horizontal(muse_geo_table *aGeo, cpl_table *aSpots)
02361 {
02362 cpl_ensure_code(aGeo && aGeo->table && aSpots, CPL_ERROR_NULL_INPUT);
02363 cpl_size nrow = cpl_table_get_nrow(aGeo->table);
02364 cpl_ensure_code(nrow >= 50, CPL_ERROR_ILLEGAL_INPUT);
02365 cpl_ensure_code(muse_cpltable_check(aGeo->table, muse_geo_table_def) == CPL_ERROR_NONE,
02366 CPL_ERROR_INCOMPATIBLE_INPUT);
02367 cpl_ensure_code(muse_cpltable_check(aSpots, muse_geo_measurements_def) == CPL_ERROR_NONE,
02368 CPL_ERROR_INCOMPATIBLE_INPUT);
02369 const unsigned char ifu1 = cpl_table_get_column_min(aGeo->table, MUSE_GEOTABLE_FIELD),
02370 ifu2 = cpl_table_get_column_max(aGeo->table, MUSE_GEOTABLE_FIELD);
02371 if (!ifu1 || !ifu2 || ifu1 == ifu2) {
02372 return cpl_error_set_message(__func__, CPL_ERROR_DATA_NOT_FOUND,
02373 "input geometry table contains data of IFUs "
02374 "%2hhu .. %2hhu", ifu1, ifu2);
02375 }
02376 cpl_ensure_code(cpl_table_get_column_stdev(aSpots, "ScaleFOV") < 1e-10,
02377 CPL_ERROR_ILLEGAL_INPUT);
02378 cpl_array *hoffsets = NULL;
02379 if (getenv("MUSE_GEOMETRY_HORI_OFFSETS")) {
02380 hoffsets = muse_cplarray_new_from_delimited_string(
02381 getenv("MUSE_GEOMETRY_HORI_OFFSETS"), ",");
02382 cpl_msg_warning(__func__, "Overriding horizontal offsets, found %"
02383 CPL_SIZE_FORMAT" values!", cpl_array_get_size(hoffsets));
02384 }
02385 const double kScaleX = kMuseTypicalCubeSizeX * aGeo->scale / 60.;
02386
02387
02388 cpl_table_new_column(aSpots, MUSE_GEOTABLE_SKY, CPL_TYPE_INT);
02389 cpl_table_new_column(aSpots, "stack", CPL_TYPE_INT);
02390 cpl_size ispot, nspots = cpl_table_get_nrow(aSpots);
02391 for (ispot = 0; ispot < nspots; ispot++) {
02392 int nslice = cpl_table_get_int(aSpots, "SliceCCD", ispot, NULL);
02393 cpl_table_set(aSpots, MUSE_GEOTABLE_SKY, ispot, kMuseGeoSliceSky[nslice - 1]);
02394 unsigned char stack = nslice <= 12 ? 4 : (nslice <= 24 ? 3 : (nslice <= 36 ? 2 : 1));
02395 cpl_table_set_int(aSpots, "stack", ispot, stack);
02396 }
02397
02398
02399
02400 const unsigned short nsoff = kMuseSlicesPerCCD / 4;
02401 cpl_table_unselect_all(aSpots);
02402 cpl_table_or_selected_int(aSpots, MUSE_GEOTABLE_SKY, CPL_EQUAL_TO, 24 - nsoff + 1);
02403 cpl_table_or_selected_int(aSpots, MUSE_GEOTABLE_SKY, CPL_EQUAL_TO, 24);
02404 cpl_table_or_selected_int(aSpots, MUSE_GEOTABLE_SKY, CPL_EQUAL_TO, 24 + 1);
02405 cpl_table_or_selected_int(aSpots, MUSE_GEOTABLE_SKY, CPL_EQUAL_TO, 24 + nsoff);
02406 #if 0
02407 cpl_msg_debug(__func__, "All top/bottom spots selected: %"
02408 CPL_SIZE_FORMAT, cpl_table_count_selected(aSpots));
02409 #endif
02410
02411 cpl_table_and_selected_double(aSpots, "flux", CPL_NOT_LESS_THAN, 500.);
02412 #if 0
02413 cpl_msg_debug(__func__, "All bright top/bottom spots selected: %"
02414 CPL_SIZE_FORMAT, cpl_table_count_selected(aSpots));
02415 #endif
02416
02417 cpl_table_and_selected_int(aSpots, "SpotNo", CPL_EQUAL_TO, 2);
02418 #if 0
02419 cpl_msg_debug(__func__, "All bright and central top/bottom spots selected: %"
02420 CPL_SIZE_FORMAT, cpl_table_count_selected(aSpots));
02421 #endif
02422 cpl_table *tbspots = cpl_table_extract_selected(aSpots);
02423 cpl_msg_debug(__func__, "All spots: %"CPL_SIZE_FORMAT", top/bottom spots to "
02424 "work with: %"CPL_SIZE_FORMAT, nspots, cpl_table_get_nrow(tbspots));
02425 nspots = cpl_table_get_nrow(tbspots);
02426
02427
02428 int *ifus = cpl_table_get_data_int(aGeo->table, MUSE_GEOTABLE_FIELD),
02429 *slices = cpl_table_get_data_int(aGeo->table, MUSE_GEOTABLE_SKY);
02430 double *xpos = cpl_table_get_data_double(aGeo->table, MUSE_GEOTABLE_X),
02431 *xerr = cpl_table_get_data_double(aGeo->table, MUSE_GEOTABLE_X"err"),
02432 *xrel = cpl_table_get_data_double(aGeo->table, "xrel");
02433 int irow;
02434
02435
02436 unsigned char nifu;
02437 for (nifu = ifu1; nifu < ifu2; nifu++) {
02438
02439 cpl_table_unselect_all(tbspots);
02440 cpl_table_or_selected_int(tbspots, MUSE_GEOTABLE_SKY, CPL_EQUAL_TO, 24);
02441 cpl_table_or_selected_int(tbspots, MUSE_GEOTABLE_SKY, CPL_EQUAL_TO, 24 + nsoff);
02442 cpl_table_and_selected_int(tbspots, MUSE_GEOTABLE_FIELD, CPL_EQUAL_TO, nifu);
02443 cpl_table *xspots = cpl_table_extract_selected(tbspots);
02444
02445 cpl_table_unselect_all(tbspots);
02446 cpl_table_or_selected_int(tbspots, MUSE_GEOTABLE_SKY, CPL_EQUAL_TO, 24 - nsoff + 1);
02447 cpl_table_or_selected_int(tbspots, MUSE_GEOTABLE_SKY, CPL_EQUAL_TO, 24 + 1);
02448 cpl_table_and_selected_int(tbspots, MUSE_GEOTABLE_FIELD, CPL_EQUAL_TO, nifu + 1);
02449 cpl_table *tmp = cpl_table_extract_selected(tbspots);
02450 cpl_table_insert(xspots, tmp, cpl_table_get_nrow(xspots));
02451 cpl_table_delete(tmp);
02452 int nxspots = cpl_table_get_nrow(xspots);
02453
02454
02455 cpl_propertylist *order = cpl_propertylist_new();
02456 cpl_propertylist_append_bool(order, "lambda", CPL_FALSE);
02457 cpl_propertylist_append_bool(order, "VPOS", CPL_FALSE);
02458 cpl_propertylist_append_bool(order, "SliceSky", CPL_FALSE);
02459 cpl_table_sort(xspots, order);
02460 cpl_propertylist_delete(order);
02461 #if 0
02462 printf("IFU %2hhu:\n", nifu);
02463 cpl_table_dump(xspots, 0, nxspots, stdout);
02464 fflush(stdout);
02465 #endif
02466
02467
02468 cpl_array *apos2 = cpl_array_new(nxspots, CPL_TYPE_DOUBLE),
02469 *apos3 = cpl_array_new(nxspots, CPL_TYPE_DOUBLE);
02470 int idx2 = 0, idx3 = 0;
02471
02472
02473 cpl_vector *vtmp = cpl_vector_wrap(nxspots,
02474 cpl_table_get_data_double(xspots, "lambda")),
02475 *lambdas = muse_cplvector_get_unique(vtmp);
02476 cpl_vector_unwrap(vtmp);
02477 vtmp = cpl_vector_wrap(nxspots, cpl_table_get_data_double(xspots, "VPOS"));
02478 cpl_vector *positions = muse_cplvector_get_unique(vtmp);
02479 cpl_vector_unwrap(vtmp);
02480 int ilambda, nlambda = cpl_vector_get_size(lambdas);
02481 for (ilambda = 0; ilambda < nlambda; ilambda++) {
02482 double lambda = cpl_vector_get(lambdas, ilambda);
02483
02484 int ipos, npos = cpl_vector_get_size(positions);
02485 for (ipos = 0; ipos < npos; ipos++) {
02486 double vpos = cpl_vector_get(positions, ipos);
02487
02488 int nstack;
02489 for (nstack = 3; nstack >= 2; nstack--) {
02490 cpl_table_select_all(xspots);
02491 cpl_table_and_selected_double(xspots, "lambda", CPL_EQUAL_TO, lambda);
02492 cpl_table_and_selected_double(xspots, "VPOS", CPL_EQUAL_TO, vpos);
02493 cpl_table_and_selected_int(xspots, "stack", CPL_EQUAL_TO, nstack);
02494 int nsel = cpl_table_count_selected(xspots);
02495 if (nsel != 2) {
02496 #if 0
02497 printf("IFU %2hhu, lambda = %f, VPOS = %f, stack = %d: %d selected rows!\n",
02498 nifu, lambda, vpos, nstack, nsel);
02499 #endif
02500 continue;
02501 }
02502
02503 cpl_table *common = cpl_table_extract_selected(xspots);
02504 order = cpl_propertylist_new();
02505 cpl_propertylist_append_bool(order, "SubField", CPL_FALSE);
02506 cpl_propertylist_append_bool(order, "SliceSky", CPL_FALSE);
02507 cpl_table_sort(common, order);
02508 cpl_propertylist_delete(order);
02509 int nselthis = cpl_table_and_selected_int(common, "SubField",
02510 CPL_EQUAL_TO, nifu);
02511 cpl_table_select_all(common);
02512 int nselnext = cpl_table_and_selected_int(common, "SubField",
02513 CPL_EQUAL_TO, nifu + 1);
02514 if (nselthis != 1 || nselnext != 1) {
02515 #if 0
02516 printf("\nIFU %2hhu, lambda = %f, VPOS = %f, stack = %d: "
02517 "%d rows of IFU %2hhu, %d rows of IFU %2d!\n",
02518 nifu, lambda, vpos, nstack, nselthis, nifu, nselnext,
02519 (int)nifu + 1);
02520 cpl_table_dump(common, 0, 100000, stdout);
02521 #endif
02522 cpl_table_delete(common);
02523 continue;
02524 }
02525
02526
02527
02528 double xdiff1 = cpl_table_get(common, "dxcen", 0, NULL),
02529 xdiff2 = cpl_table_get(common, "dxcen", 1, NULL),
02530 twidth1 = cpl_table_get(common, "twidth", 0, NULL),
02531 twidth2 = cpl_table_get(common, "twidth", 1, NULL);
02532 cpl_table_unselect_all(aGeo->table);
02533 cpl_table_or_selected_int(aGeo->table, MUSE_GEOTABLE_FIELD, CPL_EQUAL_TO,
02534 cpl_table_get_int(common, "SubField", 0, NULL));
02535 cpl_table_or_selected_int(aGeo->table, MUSE_GEOTABLE_SKY, CPL_EQUAL_TO,
02536 cpl_table_get_int(common, "SliceSky", 0, NULL));
02537 cpl_array *sel = cpl_table_where_selected(aGeo->table);
02538 double width1 = cpl_table_get_double(aGeo->table, MUSE_GEOTABLE_WIDTH,
02539 cpl_array_get(sel, 0, NULL), NULL);
02540 cpl_array_delete(sel);
02541 cpl_table_unselect_all(aGeo->table);
02542 cpl_table_or_selected_int(aGeo->table, MUSE_GEOTABLE_FIELD, CPL_EQUAL_TO,
02543 cpl_table_get_int(common, "SubField", 1, NULL));
02544 cpl_table_or_selected_int(aGeo->table, MUSE_GEOTABLE_SKY, CPL_EQUAL_TO,
02545 cpl_table_get_int(common, "SliceSky", 1, NULL));
02546 sel = cpl_table_where_selected(aGeo->table);
02547 double width2 = cpl_table_get_double(aGeo->table, MUSE_GEOTABLE_WIDTH,
02548 cpl_array_get(sel, 0, NULL), NULL);
02549 cpl_array_delete(sel);
02550 #if 0
02551 printf("\nIFU %2hhu, lambda = %f, VPOS = %f, stack + %d, twidths: %f / %f, geowidths: %f / %f:\n",
02552 nifu, lambda, vpos, nstack, twidth1, twidth2, width1, width2);
02553 cpl_table_dump(common, 0, 100000, stdout);
02554 printf("==> xdiff = %f pix, %f pix (corrected)\n", xdiff1 - xdiff2,
02555 xdiff1 * width1 / twidth1 - xdiff2 * width2 / twidth2);
02556 fflush(stdout);
02557 #endif
02558 double xdiff = xdiff1 * width1 / twidth1 - xdiff2 * width2 / twidth2;
02559 if (nstack == 3) {
02560 cpl_array_set(apos3, idx3++, xdiff);
02561 }
02562 if (nstack == 2) {
02563 cpl_array_set(apos2, idx2++, xdiff);
02564 }
02565 cpl_table_delete(common);
02566 }
02567 }
02568 }
02569 muse_cplarray_erase_invalid(apos2);
02570 muse_cplarray_erase_invalid(apos3);
02571 #define HIST_BIN_WIDTH 0.1
02572 cpl_bivector *hist2 = muse_cplarray_histogram(apos2, HIST_BIN_WIDTH, -5., 5.),
02573 *hist3 = muse_cplarray_histogram(apos3, HIST_BIN_WIDTH, -5., 5.);
02574
02575 muse_cplarray_erase_outliers(apos2, hist2, 2, 0.5);
02576 muse_cplarray_erase_outliers(apos3, hist3, 2, 0.5);
02577 cpl_bivector_delete(hist2);
02578 cpl_bivector_delete(hist3);
02579 cpl_array *apos = cpl_array_new(0, cpl_array_get_type(apos2));
02580 cpl_array_insert(apos, apos2, 0);
02581 cpl_array_insert(apos, apos3, cpl_array_get_size(apos));
02582 #if 0
02583 char *fn = cpl_sprintf("apos2_%02hhuto%02hhu.pos", nifu, nifu+1);
02584 FILE *fp = fopen(fn, "w");
02585 fprintf(fp, "# apos2 %2hhu to %2hhu:\n#", nifu, nifu+1);
02586 cpl_array_dump(apos2, 0, 1000000, fp);
02587 fclose(fp);
02588 cpl_free(fn);
02589 fn = cpl_sprintf("apos3_%02hhuto%02hhu.pos", nifu, nifu+1);
02590 fp = fopen(fn, "w");
02591 fprintf(fp, "# apos3 %2hhu to %2hhu:\n#", nifu, nifu+1);
02592 cpl_array_dump(apos3, 0, 1000000, fp);
02593 fclose(fp);
02594 cpl_free(fn);
02595 fn = cpl_sprintf("apos_%02hhuto%02hhu.pos", nifu, nifu+1);
02596 fp = fopen(fn, "w");
02597 fprintf(fp, "# apos %2hhu to %2hhu:\n#", nifu, nifu+1);
02598 cpl_array_dump(apos, 0, 1000000, fp);
02599 fclose(fp);
02600 cpl_free(fn);
02601 #endif
02602
02603
02604 double mean2 = cpl_array_get_mean(apos2),
02605 stdev2 = cpl_array_get_stdev(apos2),
02606 var2 = stdev2 * stdev2,
02607 mean3 = cpl_array_get_mean(apos3),
02608 stdev3 = cpl_array_get_stdev(apos3),
02609 var3 = stdev3 * stdev3,
02610 mean = (mean2 + mean3) / 2.,
02611 wmean = (mean2 / var2 + mean3 / var3) / (1. / var2 + 1. / var3),
02612 wstdev = sqrt(1. / (1. / var2 + 1. / var3));
02613 cpl_array_delete(apos2);
02614 cpl_array_delete(apos3);
02615 cpl_msg_debug(__func__, "IFU %2hhu to IFU %2d: %6.3f +/- %5.3f pix "
02616 "[stack 3: %6.3f +/- %5.3f, stack2: %6.3f +/- %5.3f ==> %6.3f"
02617 " or %6.3f +/- %5.3f (%6.3f)]", nifu, (int)nifu + 1, wmean, wstdev,
02618 mean3, stdev3, mean2, stdev2, mean,
02619 cpl_array_get_mean(apos), cpl_array_get_stdev(apos),
02620 cpl_array_get_median(apos));
02621 cpl_array_delete(apos);
02622 cpl_vector_delete(lambdas);
02623 cpl_vector_delete(positions);
02624 cpl_table_delete(xspots);
02625
02626
02627 for (irow = 0; irow < nrow; irow++) {
02628 if (ifus[irow] >= nifu + 1) {
02629 xpos[irow] -= wmean;
02630 xerr[irow] = sqrt(xerr[irow]*xerr[irow] + wstdev*wstdev);
02631 xrel[irow] += wmean / kScaleX;
02632 }
02633 }
02634 }
02635 cpl_table_delete(tbspots);
02636
02637
02638 for (nifu = ifu1; nifu < ifu2; nifu++) {
02639 if (hoffsets) {
02640 double xdiff = 0.;
02641 if (cpl_array_get_size(hoffsets) >= nifu) {
02642 const char *sdiff = cpl_array_get_string(hoffsets, nifu - 1);
02643 if (sdiff) {
02644 xdiff = atof(sdiff);
02645 }
02646 }
02647 cpl_msg_debug(__func__, "Subtracting extra %7.4f pix from IFU %d onwards",
02648 xdiff, (int)nifu + 1);
02649 for (irow = 0; irow < nrow; irow++) {
02650 if (ifus[irow] >= nifu + 1) {
02651 xpos[irow] -= xdiff;
02652
02653 }
02654 }
02655 continue;
02656 }
02657 #if 0
02658 printf("%s after correcting %d:\n", __func__, (int)nifu + 1);
02659 cpl_table_dump(aGeo->table, 0, 1000000, stdout);
02660 fflush(stdout);
02661 #endif
02662 }
02663 cpl_array_delete(hoffsets);
02664
02665
02666
02667 double xref = 0.;
02668 unsigned short sliceref;
02669 const unsigned char ifuref = muse_geo_select_reference(aGeo, &sliceref);
02670 for (irow = 0; irow < nrow; irow++) {
02671 if (ifus[irow] == ifuref &&
02672 (slices[irow] == sliceref || slices[irow] == sliceref + 12)) {
02673 xref += xpos[irow];
02674 }
02675 }
02676 xref /= 2.;
02677 cpl_msg_debug(__func__, "Reference point (IFU %2hhu, SliceSky %2hu/%2d) "
02678 "currently centered at %f pix, correcting this offset", ifuref,
02679 sliceref, (int)sliceref + 12, xref);
02680 cpl_table_subtract_scalar(aGeo->table, MUSE_GEOTABLE_X, xref);
02681
02682 return CPL_ERROR_NONE;
02683 }
02684
02685
02735
02736 muse_geo_table *
02737 muse_geo_determine_vertical(const muse_geo_table *aGeo)
02738 {
02739 cpl_ensure(aGeo && aGeo->table, CPL_ERROR_NULL_INPUT, NULL);
02740 int nrow = cpl_table_get_nrow(aGeo->table);
02741 cpl_ensure(nrow >= 10, CPL_ERROR_ILLEGAL_INPUT, NULL);
02742 cpl_ensure(muse_cpltable_check(aGeo->table, muse_geo_table_def) == CPL_ERROR_NONE,
02743 CPL_ERROR_INCOMPATIBLE_INPUT, NULL);
02744 int spotmin = cpl_table_get_column_min(aGeo->table, "spot"),
02745 spotmax = cpl_table_get_column_max(aGeo->table, "spot");
02746 cpl_ensure(spotmin == spotmax, CPL_ERROR_ILLEGAL_INPUT, NULL);
02747 const double kScaleY = 60. / aGeo->scale / kMuseTypicalCubeSizeY;
02748
02749
02750 muse_geo_table *gt = muse_geo_table_duplicate(aGeo);
02751 cpl_table_erase_column(gt->table, "dxerr");
02752 cpl_table_erase_column(gt->table, "dxl");
02753 cpl_table_erase_column(gt->table, "dxr");
02754 cpl_table_erase_column(gt->table, "xc");
02755 cpl_table_erase_column(gt->table, "yc");
02756 cpl_table_erase_column(gt->table, "dx");
02757 cpl_table_erase_column(gt->table, "flux");
02758 cpl_table_erase_column(gt->table, "lambda");
02759
02760
02761
02762 cpl_table_fill_column_window_double(gt->table, MUSE_GEOTABLE_Y, 0, nrow, 0.);
02763 cpl_table_add_columns(gt->table, MUSE_GEOTABLE_Y, "vpos");
02764 cpl_table_set_column_unit(gt->table, MUSE_GEOTABLE_Y, "mm");
02765 cpl_table_fill_column_window_double(gt->table, MUSE_GEOTABLE_Y"err", 0, nrow, 0.);
02766 cpl_table_add_columns(gt->table, MUSE_GEOTABLE_Y"err", "vposerr");
02767 cpl_table_set_column_unit(gt->table, MUSE_GEOTABLE_Y"err", "mm");
02768 #if 0
02769 printf("\nfull geometry table, with absolute \"y\" [mm]:\n");
02770 cpl_table_dump(gt->table, 0, nrow, stdout);
02771 fflush(stdout);
02772 #endif
02773
02774 double maskangle = 0., fmaskrot = 1.;
02775 if (getenv("MUSE_GEOMETRY_MASK_ROTATION")) {
02776 maskangle = atof(getenv("MUSE_GEOMETRY_MASK_ROTATION"));
02777 fmaskrot = cos(maskangle * CPL_MATH_RAD_DEG);
02778 cpl_msg_warning(__func__, "Adapting to global mask rotation of %.4f deg "
02779 "(cos = %.4e)", maskangle, fmaskrot);
02780 }
02781 double pinholedy = kMuseCUmpmDY;
02782 if (getenv("MUSE_GEOMETRY_PINHOLE_DY")) {
02783 pinholedy = atof(getenv("MUSE_GEOMETRY_PINHOLE_DY"));
02784 cpl_msg_info(__func__, "Using pinhole y distance of %f mm (instead of "
02785 "%f mm)", pinholedy, kMuseCUmpmDY);
02786 }
02787 cpl_boolean printgoing = getenv("MUSE_DEBUG_GEO_VERTICAL")
02788 && atoi(getenv("MUSE_DEBUG_GEO_VERTICAL")) > 0;
02789
02790 unsigned short middleSlice;
02791 const unsigned char middleIFU = muse_geo_select_reference(aGeo, &middleSlice);
02792 cpl_msg_info(__func__, "Using IFU %2hhu / SliceSky %2hu as reference",
02793 middleIFU, middleSlice);
02794 double ycentral = NAN,
02795 ymax = -DBL_MAX, ymin = DBL_MAX;
02796 int irow;
02797 for (irow = 0; irow < nrow; irow++) {
02798 unsigned char ifu = cpl_table_get_int(gt->table, MUSE_GEOTABLE_FIELD, irow, NULL);
02799 unsigned short slice = cpl_table_get_int(gt->table, MUSE_GEOTABLE_SKY, irow, NULL);
02800 double y = cpl_table_get_double(gt->table, MUSE_GEOTABLE_Y, irow, NULL);
02801 if (ifu == middleIFU && slice == middleSlice) {
02802 ycentral = y;
02803 break;
02804 }
02805 if (y > ymax) {
02806 ymax = y;
02807 }
02808 if (y < ymin) {
02809 ymin = y;
02810 }
02811 }
02812 if (!isfinite(ycentral)) {
02813 ycentral = (ymin + ymax) / 2.;
02814 cpl_msg_info(__func__, "Averaged the y range to ycentral = %f pix", ycentral);
02815 } else {
02816 cpl_msg_info(__func__, "Found IFU %2hhu, slice %2hu, using its y value as "
02817 "ycentral = %f pix", middleIFU, middleSlice, ycentral);
02818 }
02819 cpl_table_subtract_scalar(gt->table, MUSE_GEOTABLE_Y, ycentral);
02820 const unsigned short nsoff = kMuseSlicesPerCCD / 4,
02821 nstack[] = { 3, 2, 4, 1, 0 };
02822 unsigned char i;
02823 for (i = 0; nstack[i] > 0; i++) {
02824
02825 cpl_table_unselect_all(gt->table);
02826 cpl_table_or_selected_int(gt->table, "stack", CPL_EQUAL_TO, nstack[i]);
02827 cpl_table *tstack = cpl_table_extract_selected(gt->table);
02828 int ntsrow = cpl_table_get_nrow(tstack);
02829
02830 cpl_propertylist *sorting = cpl_propertylist_new();
02831 cpl_propertylist_append_bool(sorting, MUSE_GEOTABLE_FIELD, CPL_FALSE);
02832 cpl_propertylist_append_bool(sorting, MUSE_GEOTABLE_SKY, CPL_FALSE);
02833 cpl_table_sort(tstack, sorting);
02834 cpl_propertylist_delete(sorting);
02835
02836 const unsigned short refslice = middleSlice - (nstack[i] - 3) * nsoff;
02837 cpl_table_select_all(tstack);
02838 cpl_table_and_selected_int(tstack, MUSE_GEOTABLE_FIELD, CPL_EQUAL_TO, middleIFU);
02839 cpl_table_and_selected_int(tstack, MUSE_GEOTABLE_SKY, CPL_EQUAL_TO, refslice);
02840 if (cpl_table_count_selected(tstack) <= 0) {
02841 char *msg = cpl_sprintf("reference slice %2hu of reference IFU %2hhu not "
02842 "found in slicer stack %hu!", refslice,
02843 middleIFU, nstack[i]);
02844 cpl_msg_error(__func__, "%s", msg);
02845 cpl_error_set_message(__func__, CPL_ERROR_DATA_NOT_FOUND, "%s", msg);
02846 cpl_free(msg);
02847 cpl_table_delete(tstack);
02848 continue;
02849 }
02850 cpl_table_erase_selected(gt->table);
02851 cpl_array *asel = cpl_table_where_selected(tstack);
02852 int iref = cpl_array_get(asel, 0, NULL);
02853 cpl_array_delete(asel);
02854 double yref = cpl_table_get_double(tstack, MUSE_GEOTABLE_Y, iref, NULL);
02855 cpl_msg_info(__func__, "reference slice %2hu of reference IFU %2hhu found at row"
02856 " %d in slicer stack %hu!", refslice, middleIFU, iref, nstack[i]);
02857
02858
02859
02860 if (fabs(yref) > pinholedy / 2.) {
02861 cpl_msg_info(__func__, "%s vertical pinhole distance (%f) to "
02862 "recenter stack %hu", yref < 0. ? "adding" : "subtracting",
02863 pinholedy, nstack[i]);
02864 cpl_table_add_scalar(tstack, MUSE_GEOTABLE_Y, yref < 0. ? pinholedy : -pinholedy);
02865 yref = cpl_table_get_double(tstack, MUSE_GEOTABLE_Y, iref, NULL);
02866 }
02867
02868
02869 cpl_table_new_column(tstack, "dy", CPL_TYPE_DOUBLE);
02870 cpl_table_set_column_unit(tstack, "dy", "mm");
02871 cpl_table_set_column_format(tstack, "dy", "%9.5f");
02872 cpl_table_set_double(tstack, "dy", iref, yref);
02873
02874 cpl_table_duplicate_column(tstack, "ycopy", tstack, MUSE_GEOTABLE_Y);
02875 cpl_table_set_double(tstack, MUSE_GEOTABLE_Y, iref, yref);
02876 double yprev = cpl_table_get_double(tstack, "ycopy", iref, NULL),
02877 dyoffset = pinholedy * fmaskrot,
02878 ysum = yref;
02879 int iprev;
02880 for (irow = iref - 1, iprev = iref; irow >= 0; iprev = irow, irow--) {
02881
02882 int difu = cpl_table_get_int(tstack, MUSE_GEOTABLE_FIELD, iprev, NULL)
02883 - cpl_table_get_int(tstack, MUSE_GEOTABLE_FIELD, irow, NULL),
02884 dslice = cpl_table_get_int(tstack, MUSE_GEOTABLE_SKY, iprev, NULL)
02885 - cpl_table_get_int(tstack, MUSE_GEOTABLE_SKY, irow, NULL);
02886 if (difu != 0) {
02887 dslice += nsoff;
02888 }
02889 double y = cpl_table_get_double(tstack, "ycopy", irow, NULL),
02890 dy = y - yprev,
02891 dexpect = dslice * kScaleY,
02892 dratio = dy / dexpect,
02893 dycor = 0.;
02894 if (dratio < -8.) {
02895 dycor = 2 * dyoffset;
02896 } else if (dratio < -2.) {
02897 dycor = dyoffset;
02898 } else if (dratio > 5.) {
02899 dycor = -dyoffset;
02900 }
02901 if (printgoing) {
02902 cpl_msg_debug(__func__, "going back: %d %d, %f %f --> diff %9.6f "
02903 "expected %f ratio %9.6f --> dy = %f", difu, dslice,
02904 yprev, y, dy, dexpect, dratio, dy + dycor);
02905 }
02906 dy += dycor;
02907 if (abs(difu) > 1) {
02908 double gap = abs(difu) * kMuseGeoIFUVGap;
02909 dy -= gap;
02910 cpl_msg_warning(__func__, "%d missing IFUs, guessing distance as %f",
02911 abs(difu) - 1, gap);
02912 }
02913 cpl_table_set_double(tstack, "dy", irow, dy);
02914 ysum += dy;
02915 cpl_table_set_double(tstack, MUSE_GEOTABLE_Y, irow, ysum);
02916 yprev = y;
02917 }
02918
02919
02920 yprev = cpl_table_get_double(tstack, "ycopy", iref, NULL);
02921 ysum = yref;
02922 for (irow = iref + 1, iprev = iref; irow < ntsrow; iprev = irow, irow++) {
02923 int difu = cpl_table_get_int(tstack, MUSE_GEOTABLE_FIELD, iprev, NULL)
02924 - cpl_table_get_int(tstack, MUSE_GEOTABLE_FIELD, irow, NULL),
02925 dslice = cpl_table_get_int(tstack, MUSE_GEOTABLE_SKY, iprev, NULL)
02926 - cpl_table_get_int(tstack, MUSE_GEOTABLE_SKY, irow, NULL);
02927 if (difu != 0) {
02928 dslice -= nsoff;
02929 }
02930 double y = cpl_table_get_double(tstack, "ycopy", irow, NULL),
02931 dy = y - yprev,
02932 dexpect = -dslice * kScaleY,
02933 dratio = dy / dexpect,
02934 dycor = 0.;
02935 if (dratio > 8.) {
02936 dycor = -2 * dyoffset;
02937 } else if (dratio > 2.) {
02938 dycor = -dyoffset;
02939 } else if (dratio < -5.) {
02940 dycor = dyoffset;
02941 }
02942 if (printgoing) {
02943 cpl_msg_debug(__func__, "going forward: %d %d, %f %f --> diff %9.6f "
02944 "expected %f ratio %9.6f --> dy = %f", difu, dslice,
02945 yprev, y, dy, dexpect, dratio, dy + dycor);
02946 }
02947 dy += dycor;
02948 if (abs(difu) > 1) {
02949 double gap = abs(difu) * kMuseGeoIFUVGap;
02950 dy += gap;
02951 cpl_msg_warning(__func__, "%d missing IFUs, guessing distance as %f",
02952 abs(difu) - 1, gap);
02953 }
02954 cpl_table_set_double(tstack, "dy", irow, dy);
02955 ysum += dy;
02956 cpl_table_set_double(tstack, MUSE_GEOTABLE_Y, irow, ysum);
02957 yprev = y;
02958 }
02959 if (printgoing) {
02960 printf("\ngeometry table of slicer stack %hu, with \"dy\" [mm]:\n",
02961 nstack[i]);
02962 cpl_table_dump(tstack, 0, nrow, stdout);
02963 fflush(stdout);
02964 }
02965
02966 cpl_table_erase_column(tstack, "ycopy");
02967 cpl_table_erase_column(tstack, "dy");
02968
02969 cpl_table_insert(gt->table, tstack, cpl_table_get_nrow(gt->table));
02970 cpl_table_delete(tstack);
02971 }
02972
02973
02974
02975
02976
02977
02978
02979
02980 cpl_msg_info(__func__, "Correcting vertical position of top/bottom slices");
02981 unsigned char ifu;
02982 for (ifu = 1; ifu <= kMuseNumIFUs; ifu++) {
02983 for (i = 0; nstack[i] > 0; i++) {
02984 cpl_table_unselect_all(gt->table);
02985 cpl_table_or_selected_int(gt->table, "SubField", CPL_EQUAL_TO, ifu);
02986 cpl_table_and_selected_int(gt->table, "stack", CPL_EQUAL_TO, nstack[i]);
02987 if (!cpl_table_count_selected(gt->table)) {
02988 continue;
02989 }
02990 cpl_table *tstack = cpl_table_extract_selected(gt->table);
02991 int ntsrow = cpl_table_get_nrow(tstack);
02992
02993 cpl_propertylist *sorting = cpl_propertylist_new();
02994 cpl_propertylist_append_bool(sorting, MUSE_GEOTABLE_SKY, CPL_FALSE);
02995 cpl_table_sort(tstack, sorting);
02996 cpl_propertylist_delete(sorting);
02997
02998
02999 unsigned short nslicetop = cpl_table_get_int(tstack, MUSE_GEOTABLE_SKY, 0, NULL),
03000 nslicebot = cpl_table_get_int(tstack, MUSE_GEOTABLE_SKY,
03001 ntsrow - 1, NULL);
03002 cpl_boolean hastop = nslicetop % nsoff == 1,
03003 hasbot = nslicebot % nsoff == 0,
03004 haschanged = CPL_FALSE;
03005 #if 0
03006 printf("table of IFU %2hhu / SliceSky %2hu: %hu to %hu (%s, %s)\n",
03007 ifu, nstack[i], nslicetop, nslicebot,
03008 hastop ? "has top" : "does NOT have top",
03009 hasbot ? "has bottom" : "does NOT have bottom");
03010 cpl_table_dump(tstack, 0, 1000, stdout);
03011 fflush(stdout);
03012 #endif
03013
03014 cpl_vector *vvpos = cpl_vector_new(ntsrow - 3),
03015 *vverr = cpl_vector_new(ntsrow - 3);
03016 for (irow = 1; irow < ntsrow - 2; irow++) {
03017 double dy = fabs(cpl_table_get_double(tstack, MUSE_GEOTABLE_Y, irow + 1, NULL)
03018 - cpl_table_get_double(tstack, MUSE_GEOTABLE_Y, irow, NULL)),
03019 dye1 = cpl_table_get_double(tstack, MUSE_GEOTABLE_Y"err", irow, NULL),
03020 dye2 = cpl_table_get_double(tstack, MUSE_GEOTABLE_Y"err", irow + 1, NULL),
03021 dyerr = sqrt(dye1*dye1 + dye2*dye2);
03022 cpl_vector_set(vvpos, irow - 1, dy);
03023 cpl_vector_set(vverr, irow - 1, dyerr);
03024 }
03025
03026
03027 double mean = cpl_vector_get_mean(vvpos),
03028 median = cpl_vector_get_median_const(vvpos),
03029 stdev = cpl_vector_get_stdev(vvpos),
03030 stdev2 = cpl_vector_get_mean(vverr);
03031 cpl_vector_delete(vvpos);
03032 cpl_vector_delete(vverr);
03033 cpl_msg_debug(__func__, "dy of IFU %2hhu / SliceSky %2hu: %f +/- %f (%f) [+/- %f]",
03034 ifu, nstack[i], mean, stdev, median, stdev2);
03035 if (hastop) {
03036
03037 double ytop = cpl_table_get_double(tstack, MUSE_GEOTABLE_Y, 0, NULL),
03038 dyt = fabs(ytop - cpl_table_get_double(tstack, MUSE_GEOTABLE_Y, 1, NULL)),
03039 dyt1 = cpl_table_get_double(tstack, MUSE_GEOTABLE_Y"err", 0, NULL),
03040 dyt2 = cpl_table_get_double(tstack, MUSE_GEOTABLE_Y"err", 1, NULL),
03041 dyterr = sqrt(dyt1*dyt1 + dyt2*dyt2);
03042 cpl_msg_debug(__func__, "dy of IFU %2hhu / SliceSky %2hu (top): %f +/- %f",
03043 ifu, nstack[i], dyt, dyterr);
03044
03045
03046 if (mean - dyt > sqrt(dyterr*dyterr + stdev2*stdev2)) {
03047
03048 ytop += mean - dyt;
03049 cpl_table_set_double(tstack, MUSE_GEOTABLE_Y, 0, ytop);
03050 haschanged = CPL_TRUE;
03051 #if 0
03052 printf("new top entry (+%f):\n", mean - dyt);
03053 cpl_table_dump(tstack, 0, 1, stdout);
03054 fflush(stdout);
03055 #endif
03056 }
03057 }
03058 if (hasbot) {
03059
03060 double ybot = cpl_table_get_double(tstack, MUSE_GEOTABLE_Y, ntsrow - 1, NULL),
03061 dyb = fabs(ybot - cpl_table_get_double(tstack, MUSE_GEOTABLE_Y, ntsrow - 2, NULL)),
03062 dyb1 = cpl_table_get_double(tstack, MUSE_GEOTABLE_Y"err", ntsrow - 2, NULL),
03063 dyb2 = cpl_table_get_double(tstack, MUSE_GEOTABLE_Y"err", ntsrow - 1, NULL),
03064 dyberr = sqrt(dyb1*dyb1 + dyb2*dyb2);
03065 cpl_msg_debug(__func__, "dy of IFU %2hhu / SliceSky %2hu (bottom): %f +/- %f",
03066 ifu, nstack[i], dyb, dyberr);
03067 if (mean - dyb > sqrt(dyberr*dyberr + stdev2*stdev2)) {
03068
03069 ybot -= mean - dyb;
03070 cpl_table_set_double(tstack, MUSE_GEOTABLE_Y, ntsrow - 1, ybot);
03071 haschanged = CPL_TRUE;
03072 #if 0
03073 printf("new bottom entry (-%f):\n", mean - dyb);
03074 cpl_table_dump(tstack, ntsrow - 1, 1, stdout);
03075 fflush(stdout);
03076 #endif
03077 }
03078 }
03079
03080
03081
03082 if (haschanged) {
03083 cpl_table_erase_selected(gt->table);
03084 cpl_table_insert(gt->table, tstack, cpl_table_get_nrow(gt->table));
03085 }
03086 cpl_table_delete(tstack);
03087 }
03088 }
03089
03090
03091 cpl_table_divide_scalar(gt->table, MUSE_GEOTABLE_Y, kMuseSpaxelSizeY_WFM / aGeo->scale);
03092 cpl_table_set_column_unit(gt->table, MUSE_GEOTABLE_Y, "pix");
03093 cpl_table_divide_scalar(gt->table, MUSE_GEOTABLE_Y"err", kMuseSpaxelSizeY_WFM / aGeo->scale);
03094 cpl_table_set_column_unit(gt->table, MUSE_GEOTABLE_Y"err", "pix");
03095
03096
03097 cpl_table_erase_column(gt->table, "spot");
03098 cpl_table_erase_column(gt->table, "xrel");
03099 cpl_table_erase_column(gt->table, "xrelerr");
03100 cpl_table_erase_column(gt->table, "vpos");
03101 cpl_table_erase_column(gt->table, "vposerr");
03102 cpl_table_erase_column(gt->table, "stack");
03103 return gt;
03104 }
03105
03106
03140
03141 cpl_error_code
03142 muse_geo_finalize(muse_geo_table *aGeo)
03143 {
03144 cpl_ensure_code(aGeo && aGeo->table, CPL_ERROR_NULL_INPUT);
03145 cpl_ensure_code(cpl_table_has_column(aGeo->table, MUSE_GEOTABLE_FIELD) &&
03146 cpl_table_has_column(aGeo->table, MUSE_GEOTABLE_CCD) &&
03147 cpl_table_has_column(aGeo->table, MUSE_GEOTABLE_SKY) &&
03148 cpl_table_has_column(aGeo->table, MUSE_GEOTABLE_X) &&
03149 cpl_table_has_column(aGeo->table, MUSE_GEOTABLE_Y) &&
03150 cpl_table_has_column(aGeo->table, MUSE_GEOTABLE_ANGLE) &&
03151 cpl_table_has_column(aGeo->table, MUSE_GEOTABLE_WIDTH),
03152 CPL_ERROR_ILLEGAL_INPUT);
03153
03154
03155 if (getenv("MUSE_GEOMETRY_PINHOLE_DY")) {
03156 #if 0
03157 FILE *fn1 = fopen("gt1.ascii", "w");
03158 fprintf(fn1, "geometry table before scaling\n");
03159 cpl_table_dump(aGeo->table, 0, 10000, fn1);
03160 fclose(fn1);
03161 #endif
03162 double pinholedy = atof(getenv("MUSE_GEOMETRY_PINHOLE_DY")),
03163 fdy = kMuseCUmpmDY / pinholedy;
03164 cpl_msg_info(__func__, "Pinhole y distance of %f mm was used instead of "
03165 "%f mm; scaling coordinates by %f!", pinholedy, kMuseCUmpmDY,
03166 fdy);
03167 int irow, nrow = cpl_table_get_nrow(aGeo->table);
03168 for (irow = 0; irow < nrow; irow++) {
03169 int err;
03170
03171 double y = cpl_table_get_double(aGeo->table, MUSE_GEOTABLE_Y, irow, &err);
03172 if (!err) {
03173 cpl_table_set_double(aGeo->table, MUSE_GEOTABLE_Y, irow, y * fdy);
03174 }
03175
03176
03177
03178 double angleold = cpl_table_get_double(aGeo->table, MUSE_GEOTABLE_ANGLE, irow, &err);
03179 if (!err) {
03180 double anglenew = atan(fdy * tan(angleold * CPL_MATH_RAD_DEG))
03181 * CPL_MATH_DEG_RAD;
03182 cpl_table_set_double(aGeo->table, MUSE_GEOTABLE_ANGLE, irow, anglenew);
03183 }
03184 }
03185 #if 0
03186 FILE *fn2 = fopen("gt2.ascii", "w");
03187 fprintf(fn2, "geometry table after scaling by %f\n", fdy);
03188 cpl_table_dump(aGeo->table, 0, 10000, fn2);
03189 fclose(fn2);
03190 #endif
03191 }
03192
03193
03194 unsigned char nifu;
03195 for (nifu = 1; nifu <= kMuseNumIFUs; nifu++) {
03196 cpl_table_select_all(aGeo->table);
03197 cpl_table_and_selected_int(aGeo->table, MUSE_GEOTABLE_FIELD, CPL_EQUAL_TO, nifu);
03198 if (cpl_table_count_selected(aGeo->table) < 1) {
03199 continue;
03200 }
03201
03202 unsigned short nslice;
03203 for (nslice = 1; nslice <= kMuseSlicesPerCCD; nslice++) {
03204 cpl_table_select_all(aGeo->table);
03205 cpl_table_and_selected_int(aGeo->table, MUSE_GEOTABLE_FIELD, CPL_EQUAL_TO, nifu);
03206 cpl_table_and_selected_int(aGeo->table, MUSE_GEOTABLE_CCD, CPL_EQUAL_TO, nslice);
03207 if (cpl_table_count_selected(aGeo->table) > 0) {
03208 continue;
03209 }
03210
03211 cpl_table_set_size(aGeo->table, cpl_table_get_nrow(aGeo->table) + 1);
03212 int irow = cpl_table_get_nrow(aGeo->table) - 1;
03213 cpl_table_set_int(aGeo->table, MUSE_GEOTABLE_FIELD, irow, nifu);
03214 cpl_table_set_int(aGeo->table, MUSE_GEOTABLE_CCD, irow, nslice);
03215 cpl_table_set_int(aGeo->table, MUSE_GEOTABLE_SKY, irow,
03216 kMuseGeoSliceSky[nslice - 1]);
03217 cpl_table_set_double(aGeo->table, MUSE_GEOTABLE_X, irow, NAN);
03218 cpl_table_set_double(aGeo->table, MUSE_GEOTABLE_Y, irow, NAN);
03219 cpl_table_set_double(aGeo->table, MUSE_GEOTABLE_ANGLE, irow, 0.);
03220 cpl_table_set_double(aGeo->table, MUSE_GEOTABLE_WIDTH, irow, 0.);
03221 }
03222 }
03223
03224 cpl_boolean needsnoflip = getenv("MUSE_GEOMETRY_NO_INVERSION")
03225 && atoi(getenv("MUSE_GEOMETRY_NO_INVERSION")) > 0;
03226 if (!needsnoflip) {
03227 cpl_msg_info(__func__, "Flipping all slices because of data inversion!");
03228 cpl_table_multiply_scalar(aGeo->table, MUSE_GEOTABLE_Y, -1.);
03229 cpl_table_multiply_scalar(aGeo->table, MUSE_GEOTABLE_ANGLE, -1.);
03230 }
03231
03232
03233 cpl_propertylist *sorting = cpl_propertylist_new();
03234 cpl_propertylist_append_bool(sorting, MUSE_GEOTABLE_FIELD, CPL_FALSE);
03235 cpl_propertylist_append_bool(sorting, MUSE_GEOTABLE_SKY, CPL_FALSE);
03236 cpl_table_sort(aGeo->table, sorting);
03237 cpl_propertylist_delete(sorting);
03238
03239 #if 0
03240 printf("\nfinal geometry table with all slicer stacks [pix]:\n");
03241 cpl_table_dump(aGeo->table, 0, nrow, stdout);
03242 fflush(stdout);
03243 #endif
03244 #if 0
03245 const char *fn = "GEOMETRY_TABLE.ascii";
03246 FILE *fp = fopen(fn, "w");
03247 fprintf(fp, "# final geometry table with all slicer stacks [pix]:\n");
03248 cpl_table_dump(aGeo->table, 0, nrow, fp);
03249 fclose(fp);
03250 cpl_msg_debug(__func__, "written geometry table in ASCII to \"%s\"", fn);
03251 #endif
03252
03253 return CPL_ERROR_NONE;
03254 }
03255
03256
03271
03272 static unsigned int
03273 muse_geo_correct_slices_stack(cpl_table *aTStack, cpl_matrix *aPos,
03274 const char *aCol, const char *aErr, double aLimit,
03275 double aSigma)
03276 {
03277 const char *id = "muse_geo_correct_slices";
03278
03279 double *pval = cpl_table_get_data_double(aTStack, aCol),
03280 *perr = cpl_table_get_data_double(aTStack, aErr);
03281
03282 int ntsrow = cpl_table_get_nrow(aTStack);
03283 cpl_vector *val = cpl_vector_wrap(ntsrow, pval),
03284 *err = cpl_vector_wrap(ntsrow, perr);
03285
03286 double rms, chisq;
03287 cpl_polynomial *poly = muse_utils_iterate_fit_polynomial(aPos, val, err, NULL,
03288 1, DBL_MAX, &rms,
03289 &chisq);
03290 rms = sqrt(rms);
03291 cpl_vector_unwrap(val);
03292 cpl_vector_unwrap(err);
03293 #if 0
03294 cpl_msg_debug(__func__, "poly for %s values (RMS = %f, ChiSq = %f):", aCol,
03295 rms, chisq);
03296 cpl_polynomial_dump(poly, stdout);
03297 fflush(stdout);
03298 #endif
03299 unsigned int nreplaced = 0;
03300 int irow;
03301 for (irow = 0; irow < ntsrow; irow++) {
03302 double pos = cpl_matrix_get(aPos, 0, irow),
03303 vpoly = cpl_polynomial_eval_1d(poly, pos, NULL),
03304 dpoly = fabs(pval[irow] - vpoly);
03305 if (perr[irow] > aLimit || dpoly > aSigma * rms) {
03306 cpl_msg_debug(__func__, "Changing %s(%02.0f) from %.3f to %.3f "
03307 "(perr = %.3f > %.3f or dpoly = %.3f > %.3f)", aCol, pos,
03308 pval[irow], vpoly, perr[irow], aLimit, dpoly, aSigma * rms);
03309 pval[irow] = vpoly;
03310 nreplaced++;
03311 }
03312 }
03313 cpl_polynomial_delete(poly);
03314 if (nreplaced > 3) {
03315 cpl_msg_warning(id, "Changed %d of %d %s values in IFU %02d (stack with "
03316 "sky slices %d to %d)", nreplaced, ntsrow, aCol,
03317 cpl_table_get_int(aTStack, MUSE_GEOTABLE_FIELD, 0, NULL),
03318 (int)cpl_matrix_get(aPos, 0, 0),
03319 (int)cpl_matrix_get(aPos, 0, ntsrow - 1));
03320 }
03321 return nreplaced;
03322 }
03323
03324
03362
03363 cpl_error_code
03364 muse_geo_correct_slices(muse_geo_table *aGeo, cpl_propertylist *aHeader,
03365 double aSigma)
03366 {
03367 cpl_ensure_code(aGeo && aGeo->table, CPL_ERROR_NULL_INPUT);
03368 cpl_ensure_code(aSigma > 0., CPL_ERROR_ILLEGAL_INPUT);
03369 cpl_ensure_code(cpl_table_has_column(aGeo->table, MUSE_GEOTABLE_FIELD) &&
03370 cpl_table_has_column(aGeo->table, MUSE_GEOTABLE_CCD) &&
03371 cpl_table_has_column(aGeo->table, MUSE_GEOTABLE_SKY) &&
03372 cpl_table_has_column(aGeo->table, MUSE_GEOTABLE_X) &&
03373 cpl_table_has_column(aGeo->table, MUSE_GEOTABLE_Y) &&
03374 cpl_table_has_column(aGeo->table, MUSE_GEOTABLE_ANGLE) &&
03375 cpl_table_has_column(aGeo->table, MUSE_GEOTABLE_WIDTH) &&
03376 cpl_table_has_column(aGeo->table, MUSE_GEOTABLE_X"err") &&
03377 cpl_table_has_column(aGeo->table, MUSE_GEOTABLE_Y"err") &&
03378 cpl_table_has_column(aGeo->table, MUSE_GEOTABLE_ANGLE"err") &&
03379 cpl_table_has_column(aGeo->table, MUSE_GEOTABLE_WIDTH"err"),
03380 CPL_ERROR_DATA_NOT_FOUND);
03381 cpl_ensure_code(cpl_table_get_column_type(aGeo->table, MUSE_GEOTABLE_X) == CPL_TYPE_DOUBLE &&
03382 cpl_table_get_column_type(aGeo->table, MUSE_GEOTABLE_Y) == CPL_TYPE_DOUBLE &&
03383 cpl_table_get_column_type(aGeo->table, MUSE_GEOTABLE_ANGLE) == CPL_TYPE_DOUBLE &&
03384 cpl_table_get_column_type(aGeo->table, MUSE_GEOTABLE_WIDTH) == CPL_TYPE_DOUBLE &&
03385 cpl_table_get_column_type(aGeo->table, MUSE_GEOTABLE_X"err") == CPL_TYPE_DOUBLE &&
03386 cpl_table_get_column_type(aGeo->table, MUSE_GEOTABLE_Y"err") == CPL_TYPE_DOUBLE &&
03387 cpl_table_get_column_type(aGeo->table, MUSE_GEOTABLE_ANGLE"err") == CPL_TYPE_DOUBLE &&
03388 cpl_table_get_column_type(aGeo->table, MUSE_GEOTABLE_WIDTH"err") == CPL_TYPE_DOUBLE,
03389 CPL_ERROR_INCOMPATIBLE_INPUT);
03390
03391
03392 cpl_table_set_column_format(aGeo->table, MUSE_GEOTABLE_X, "%8.3f");
03393 cpl_table_set_column_format(aGeo->table, MUSE_GEOTABLE_X"err", "%8.3f");
03394 cpl_table_set_column_format(aGeo->table, MUSE_GEOTABLE_Y, "%8.3f");
03395 cpl_table_set_column_format(aGeo->table, MUSE_GEOTABLE_Y"err", "%8.3f");
03396 cpl_table_set_column_format(aGeo->table, MUSE_GEOTABLE_ANGLE, "%5.3f");
03397 cpl_table_set_column_format(aGeo->table, MUSE_GEOTABLE_ANGLE"err", "%5.3f");
03398 cpl_table_set_column_format(aGeo->table, MUSE_GEOTABLE_WIDTH, "%8.3f");
03399 cpl_table_set_column_format(aGeo->table, MUSE_GEOTABLE_WIDTH"err", "%8.3f");
03400
03401 cpl_msg_info(__func__, "Correcting %s using %.2f-sigma level",
03402 MUSE_TAG_GEOMETRY_TABLE, aSigma);
03403 cpl_msg_debug(__func__, " median errors: x %.3f y %.3f angle %.3f width %.3f",
03404 cpl_table_get_column_median(aGeo->table, MUSE_GEOTABLE_X"err"),
03405 cpl_table_get_column_median(aGeo->table, MUSE_GEOTABLE_Y"err"),
03406 cpl_table_get_column_median(aGeo->table, MUSE_GEOTABLE_ANGLE"err"),
03407 cpl_table_get_column_median(aGeo->table, MUSE_GEOTABLE_WIDTH"err"));
03408
03409 const double kXLimit = 0.90,
03410 kYLimit = 0.10,
03411 kALimit = 0.07,
03412 kWLimit = 0.25;
03413 cpl_msg_debug(__func__, " table limits: x %.3f y %.3f angle %.3f width %.3f",
03414 kXLimit, kYLimit, kALimit, kWLimit);
03415
03416 int nx = 0, ny = 0, na = 0, nw = 0;
03417 const unsigned short nsoff = kMuseSlicesPerCCD / 4;
03418 unsigned char nifu;
03419 for (nifu = 1; nifu <= kMuseNumIFUs; nifu++) {
03420 unsigned char nstack;
03421 for (nstack = 1; nstack <= 4; nstack++) {
03422
03423 unsigned short nslice1 = (nstack - 1) * nsoff + 1,
03424 nslice2 = nstack * nsoff;
03425
03426 cpl_table_unselect_all(aGeo->table);
03427 cpl_table_or_selected_int(aGeo->table, MUSE_GEOTABLE_FIELD, CPL_EQUAL_TO, nifu);
03428 cpl_table_and_selected_int(aGeo->table, MUSE_GEOTABLE_SKY,
03429 CPL_NOT_LESS_THAN, nslice1);
03430 cpl_table_and_selected_int(aGeo->table, MUSE_GEOTABLE_SKY,
03431 CPL_NOT_GREATER_THAN, nslice2);
03432 int ntsrow = cpl_table_count_selected(aGeo->table);
03433 cpl_msg_debug(__func__, "IFU %2hhu stack %hhu, slices %2hu to %2hu: %d rows",
03434 nifu, nstack, nslice1, nslice2, ntsrow);
03435 if (ntsrow < 1) {
03436 continue;
03437 }
03438
03439 cpl_table *tstack = cpl_table_extract_selected(aGeo->table);
03440 #if 0
03441 cpl_table_dump(tstack, 0, nsoff, stdout);
03442 fflush(stdout);
03443 #endif
03444
03445
03446 cpl_propertylist *sorting = cpl_propertylist_new();
03447 cpl_propertylist_append_bool(sorting, MUSE_GEOTABLE_FIELD, CPL_FALSE);
03448 cpl_propertylist_append_bool(sorting, MUSE_GEOTABLE_SKY, CPL_FALSE);
03449 cpl_table_sort(tstack, sorting);
03450 cpl_propertylist_delete(sorting);
03451
03452
03453 cpl_table_cast_column(tstack, MUSE_GEOTABLE_SKY, "skydouble", CPL_TYPE_DOUBLE);
03454 cpl_matrix *pos = cpl_matrix_wrap(1, ntsrow,
03455 cpl_table_get_data_double(tstack, "skydouble"));
03456
03457 nx += muse_geo_correct_slices_stack(tstack, pos, MUSE_GEOTABLE_X,
03458 MUSE_GEOTABLE_X"err", kXLimit, aSigma);
03459 ny += muse_geo_correct_slices_stack(tstack, pos, MUSE_GEOTABLE_Y,
03460 MUSE_GEOTABLE_Y"err", kYLimit, aSigma);
03461 na += muse_geo_correct_slices_stack(tstack, pos, MUSE_GEOTABLE_ANGLE,
03462 MUSE_GEOTABLE_ANGLE"err", kALimit, aSigma);
03463 nw += muse_geo_correct_slices_stack(tstack, pos, MUSE_GEOTABLE_WIDTH,
03464 MUSE_GEOTABLE_WIDTH"err", kWLimit, aSigma);
03465 cpl_matrix_unwrap(pos);
03466 cpl_table_erase_column(tstack, "skydouble");
03467 #if 0
03468 cpl_msg_debug(__func__, "IFU %2hhu stack %hhu, final table:", nifu, nstack);
03469 cpl_table_dump(tstack, 0, nsoff, stdout);
03470 fflush(stdout);
03471 #endif
03472 cpl_table_erase_selected(aGeo->table);
03473 cpl_table_insert(aGeo->table, tstack, cpl_table_get_nrow(aGeo->table));
03474 cpl_table_delete(tstack);
03475 }
03476 }
03477
03478 cpl_msg_info(__func__, "Changed %d x values, %d y values, %d angles, and %d "
03479 "widths.", nx, ny, na, nw);
03480 if (aHeader) {
03481 cpl_propertylist_update_int(aHeader, QC_GEO_SMOOTH_NX, nx);
03482 cpl_propertylist_update_int(aHeader, QC_GEO_SMOOTH_NY, ny);
03483 cpl_propertylist_update_int(aHeader, QC_GEO_SMOOTH_NA, na);
03484 cpl_propertylist_update_int(aHeader, QC_GEO_SMOOTH_NW, nw);
03485 }
03486 return CPL_ERROR_NONE;
03487 }
03488
03489
03506
03507 cpl_error_code
03508 muse_geo_qc_global(const muse_geo_table *aGeoTable, cpl_propertylist *aHeader)
03509 {
03510 cpl_ensure_code(aGeoTable && aHeader, CPL_ERROR_NULL_INPUT);
03511 cpl_table *geotable = aGeoTable->table;
03512
03513
03514 cpl_array *agaps = cpl_array_new(kMuseNumIFUs, CPL_TYPE_DOUBLE);
03515 unsigned char nifu,
03516 nifu1 = cpl_table_get_column_min(geotable, MUSE_GEOTABLE_FIELD),
03517 nifu2 = cpl_table_get_column_max(geotable, MUSE_GEOTABLE_FIELD);
03518 for (nifu = nifu1; nifu <= nifu2; nifu++) {
03519
03520 cpl_table_unselect_all(geotable);
03521 cpl_table_or_selected_int(geotable, MUSE_GEOTABLE_FIELD, CPL_EQUAL_TO, nifu);
03522 cpl_table_and_selected_int(geotable, MUSE_GEOTABLE_SKY, CPL_NOT_LESS_THAN, 13);
03523 cpl_table_and_selected_int(geotable, MUSE_GEOTABLE_SKY, CPL_NOT_GREATER_THAN, 24);
03524 cpl_table *tleft = cpl_table_extract_selected(geotable);
03525
03526 cpl_table_unselect_all(geotable);
03527 cpl_table_or_selected_int(geotable, MUSE_GEOTABLE_FIELD, CPL_EQUAL_TO, nifu);
03528 cpl_table_and_selected_int(geotable, MUSE_GEOTABLE_SKY, CPL_NOT_LESS_THAN, 25);
03529 cpl_table_and_selected_int(geotable, MUSE_GEOTABLE_SKY, CPL_NOT_GREATER_THAN, 36);
03530 cpl_table *tright = cpl_table_extract_selected(geotable);
03531
03532
03533 int irow, nrow = cpl_table_get_nrow(tleft),
03534 nrow2 = cpl_table_get_nrow(tright);
03535 if (nrow < 1 || nrow2 < 1) {
03536 cpl_msg_warning(__func__, "No slices for central stacks found, cannot "
03537 "compute gaps for QC in IFU %hhu", nifu);
03538 cpl_table_delete(tleft);
03539 cpl_table_delete(tright);
03540 continue;
03541 }
03542 if (nrow != nrow2) {
03543 cpl_msg_warning(__func__, "Unequal slice count for central stacks, cannot"
03544 " compute gaps for QC in IFU %hhu", nifu);
03545 cpl_table_delete(tleft);
03546 cpl_table_delete(tright);
03547 continue;
03548 }
03549
03550
03551 cpl_propertylist *sorting = cpl_propertylist_new();
03552 cpl_propertylist_append_bool(sorting, MUSE_GEOTABLE_SKY, CPL_FALSE);
03553 cpl_table_sort(tleft, sorting);
03554 cpl_table_sort(tright, sorting);
03555 cpl_propertylist_delete(sorting);
03556 cpl_array *agap = cpl_array_new(nrow, CPL_TYPE_DOUBLE);
03557 for (irow = 0; irow < nrow; irow++) {
03558 double x1 = cpl_table_get(tleft, MUSE_GEOTABLE_X, irow, NULL),
03559 w1 = cpl_table_get(tleft, MUSE_GEOTABLE_WIDTH, irow, NULL),
03560 x2 = cpl_table_get(tright, MUSE_GEOTABLE_X, irow, NULL),
03561 w2 = cpl_table_get(tright, MUSE_GEOTABLE_WIDTH, irow, NULL);
03562 cpl_array_set_double(agap, irow, (x1 + w1/2. + x2 - w2/2.) / 2.);
03563 }
03564 cpl_table_delete(tleft);
03565 cpl_table_delete(tright);
03566
03567
03568 double mean = cpl_array_get_mean(agap);
03569 cpl_array_set_double(agaps, nifu - 1, mean);
03570 char *kw = cpl_sprintf(QC_GEO_IFUi_GAP, nifu);
03571 cpl_propertylist_update_float(aHeader, kw, mean);
03572 cpl_free(kw);
03573 cpl_array_delete(agap);
03574 }
03575 double gmean = cpl_array_get_mean(agaps),
03576 gstdev = cpl_array_get_stdev(agaps);
03577 cpl_propertylist_update_float(aHeader, QC_GEO_GAPS_MEAN, gmean);
03578 cpl_propertylist_update_float(aHeader, QC_GEO_GAPS_STDEV, gstdev);
03579 cpl_array_delete(agaps);
03580
03581
03582 double angle = cpl_table_get_column_mean(geotable, MUSE_GEOTABLE_ANGLE),
03583 astdev = cpl_table_get_column_stdev(geotable, MUSE_GEOTABLE_ANGLE),
03584 amedian = cpl_table_get_column_median(geotable, MUSE_GEOTABLE_ANGLE);
03585 cpl_errorstate state = cpl_errorstate_get();
03586 cpl_propertylist_update_float(aHeader, QC_GEO_MASK_ANGLE, amedian);
03587 if (!cpl_errorstate_is_equal(state)) {
03588
03589 cpl_errorstate_set(state);
03590 cpl_propertylist_update_double(aHeader, QC_GEO_MASK_ANGLE, amedian);
03591 }
03592
03593 cpl_msg_info(__func__, "Added global QC keywords: angle = %.3f +/- %.3f "
03594 "(%.3f) deg, gap positions = %.3f +/- %.3f pix", angle, astdev,
03595 amedian, gmean, gstdev);
03596 return CPL_ERROR_NONE;
03597 }
03598