38 #include "muse_instrument.h"
40 #include "muse_astro.h"
41 #include "muse_cplwrappers.h"
43 #include "muse_pfits.h"
44 #include "muse_tracing.h"
45 #include "muse_utils.h"
46 #include "muse_wavecalib.h"
90 {
"filename", CPL_TYPE_STRING,
"",
"%s",
91 "(raw) filename from which this measurement originates", CPL_TRUE },
92 {
"image", CPL_TYPE_INT,
"",
"%03d",
"number of the image in the series", CPL_TRUE },
93 {
"POSENC2", CPL_TYPE_INT,
"",
"%d",
94 "x position of the mask in encoder steps", CPL_TRUE },
95 {
"POSPOS2", CPL_TYPE_DOUBLE,
"mm",
"%.3f",
"x position of the mask", CPL_TRUE },
96 {
"POSENC3", CPL_TYPE_INT,
"",
"%d",
97 "y position of the mask in encoder steps", CPL_TRUE },
98 {
"POSPOS3", CPL_TYPE_DOUBLE,
"mm",
"%.3f",
"y position of the mask", CPL_TRUE },
99 {
"POSENC4", CPL_TYPE_INT,
"",
"%d",
100 "z position of the mask in encoder steps", CPL_TRUE },
101 {
"POSPOS4", CPL_TYPE_DOUBLE,
"mm",
"%.3f",
"z position of the mask", CPL_TRUE },
102 {
"VPOS", CPL_TYPE_DOUBLE,
"mm",
"%.3f",
"real vertical position of the mask", CPL_TRUE },
103 {
"ScaleFOV", CPL_TYPE_DOUBLE,
"arcsec/mm",
"%.3f",
104 "focus scale in VLT focal plane (from the FITS header)", CPL_TRUE },
105 {
"SubField", CPL_TYPE_INT,
"",
"%02d",
"sub-field number", CPL_TRUE },
106 {
"SliceCCD", CPL_TYPE_INT,
"",
"%02d",
107 "slice number as counted on the CCD", CPL_TRUE },
108 {
"lambda", CPL_TYPE_DOUBLE,
"Angstrom",
"%.3f",
"wavelength", CPL_TRUE },
109 {
"SpotNo", CPL_TYPE_INT,
"",
"%04d",
110 "number of this spot within the slice (1 is left, 2 is the central one, 3 is right within the slice)", CPL_TRUE },
111 {
"xc", CPL_TYPE_DOUBLE,
"pix",
"%.3f",
"x center of this spot on the CCD", CPL_TRUE },
112 {
"yc", CPL_TYPE_DOUBLE,
"pix",
"%.3f",
"y center of this spot on the CCD", CPL_TRUE },
113 {
"xfwhm", CPL_TYPE_DOUBLE,
"pix",
"%.2f",
"FWHM in x-direction on the CCD", CPL_TRUE },
114 {
"yfwhm", CPL_TYPE_DOUBLE,
"pix",
"%.2f",
"FWHM in y-direction on the CCD", CPL_TRUE },
115 {
"flux", CPL_TYPE_DOUBLE,
"",
"%.1f",
116 "flux of the spot as integrated on the CCD image", CPL_TRUE },
117 {
"bg", CPL_TYPE_DOUBLE,
"",
"%f",
"background level around the spot", CPL_TRUE },
118 {
"dxcen", CPL_TYPE_DOUBLE,
"pix",
"%f",
119 "distance to center of slice at vertical position yc (positive: right of center)", CPL_TRUE },
120 {
"twidth", CPL_TYPE_DOUBLE,
"pix",
"%f",
121 "trace width of the slice at the vertical CCD position of the spot", CPL_TRUE },
122 { NULL, 0, NULL, NULL, NULL, CPL_FALSE }
169 { MUSE_GEOTABLE_FIELD, CPL_TYPE_INT,
"",
"%02d",
"sub-field number", CPL_TRUE },
170 { MUSE_GEOTABLE_CCD, CPL_TYPE_INT,
"",
"%02d",
171 "the slice number on the CCD, counted from left to right", CPL_TRUE },
172 { MUSE_GEOTABLE_SKY, CPL_TYPE_INT,
"",
"%02d",
173 "the slice number on the sky", CPL_TRUE },
174 { MUSE_GEOTABLE_X, CPL_TYPE_DOUBLE,
"pix",
"%9.4f",
175 "x position within field of view", CPL_TRUE },
176 { MUSE_GEOTABLE_Y, CPL_TYPE_DOUBLE,
"pix",
"%9.4f",
177 "y position within field of view", CPL_TRUE },
178 { MUSE_GEOTABLE_ANGLE, CPL_TYPE_DOUBLE,
"deg",
"%6.3f",
179 "rotation angle of slice", CPL_TRUE },
180 { MUSE_GEOTABLE_WIDTH, CPL_TYPE_DOUBLE,
"pix",
"%.2f",
181 "width of slice within field of view", CPL_TRUE },
182 { MUSE_GEOTABLE_X
"err", CPL_TYPE_DOUBLE,
"pix",
"%8.4f",
183 "error estimated of x position within field of view", CPL_TRUE },
184 { MUSE_GEOTABLE_Y
"err", CPL_TYPE_DOUBLE,
"pix",
"%8.4f",
185 "error estimate of y position within field of view", CPL_TRUE },
186 { MUSE_GEOTABLE_ANGLE
"err", CPL_TYPE_DOUBLE,
"deg",
"%.3f",
187 "error estimate of rotation angle", CPL_TRUE },
188 { MUSE_GEOTABLE_WIDTH
"err", CPL_TYPE_DOUBLE,
"pix",
"%.2f",
189 "error estimate of slice width", CPL_TRUE },
190 {
"stack", CPL_TYPE_INT,
"",
"%02d",
191 "slicer stack that this slice belongs to (optical numbering)", CPL_TRUE },
192 {
"spot", CPL_TYPE_INT,
"",
"%1d",
"spot number in this slice", CPL_TRUE },
193 {
"xrel", CPL_TYPE_DOUBLE,
"mm",
"%7.4f",
194 "x offset of this spot relative to the slice center", CPL_TRUE },
195 {
"xrelerr", CPL_TYPE_DOUBLE,
"mm",
"%6.4f",
196 "error of the relative x offset of this spot", CPL_TRUE },
197 {
"xc", CPL_TYPE_DOUBLE,
"pix",
"%.3f",
"x center of this spot on the CCD", CPL_TRUE },
198 {
"yc", CPL_TYPE_DOUBLE,
"pix",
"%.3f",
"y center of this spot on the CCD", CPL_TRUE },
199 {
"dxl", CPL_TYPE_DOUBLE,
"pix",
"%.3f",
"distance to left edge of slice on the CCD", CPL_TRUE },
200 {
"dxr", CPL_TYPE_DOUBLE,
"pix",
"%.3f",
"distance to right edge of slice on the CCD", CPL_TRUE },
201 {
"dx", CPL_TYPE_DOUBLE,
"pix",
"%.3f",
"pinhole distance in x on the CCD", CPL_TRUE },
202 {
"dxerr", CPL_TYPE_DOUBLE,
"pix",
"%.3f",
203 "error estimate of the pinhole distance in x on the CCD", CPL_TRUE },
204 {
"vpos", CPL_TYPE_DOUBLE,
"mm",
"%.4f",
205 "(averaged) vertical position of the mask", CPL_TRUE },
206 {
"vposerr", CPL_TYPE_DOUBLE,
"mm",
"%.4f",
207 "error estimated of the (averaged) vertical position of the mask", CPL_TRUE },
208 {
"flux", CPL_TYPE_DOUBLE,
"",
"%.1f",
209 "flux of the spot as integrated on the CCD image", CPL_TRUE },
210 {
"lambda", CPL_TYPE_DOUBLE,
"Angstrom",
"%.3f",
"wavelength", CPL_TRUE },
211 { NULL, 0, NULL, NULL, NULL, CPL_FALSE }
235 cpl_ensure(aTable, CPL_ERROR_NULL_INPUT, NULL);
236 cpl_ensure(aIFU >= 1 && aIFU <= kMuseNumIFUs, CPL_ERROR_ILLEGAL_INPUT, NULL);
239 cpl_table *intable = cpl_table_duplicate(aTable);
243 cpl_propertylist *sorting = cpl_propertylist_new();
244 cpl_propertylist_append_bool(sorting, MUSE_GEOTABLE_FIELD, CPL_FALSE);
245 cpl_propertylist_append_bool(sorting, MUSE_GEOTABLE_CCD, CPL_FALSE);
246 cpl_table_sort(intable, sorting);
247 cpl_propertylist_delete(sorting);
249 cpl_table_select_all(intable);
250 cpl_table_and_selected_int(intable, MUSE_GEOTABLE_FIELD, CPL_EQUAL_TO, aIFU);
251 cpl_table *subtable = cpl_table_extract_selected(intable);
252 cpl_table_delete(intable);
254 printf(
"table (extracted for IFU %2d)\n", aIFU);
255 cpl_table_dump(subtable, 0, 100000, stdout);
258 int nrow = cpl_table_get_nrow(subtable);
259 if (nrow != kMuseSlicesPerCCD) {
260 cpl_error_set_message(__func__, CPL_ERROR_ILLEGAL_OUTPUT,
261 "geometry table contains %d instead of %d slices for "
262 "IFU %d", nrow, kMuseSlicesPerCCD, aIFU);
263 cpl_table_delete(subtable);
298 cpl_ensure(aTable, CPL_ERROR_NULL_INPUT, 0.);
303 cpl_size nrow = cpl_table_get_nrow(table);
304 cpl_ensure(nrow == kMuseSlicesPerCCD, CPL_ERROR_ILLEGAL_INPUT, 0.);
308 cpl_propertylist *sorting = cpl_propertylist_new();
309 cpl_propertylist_append_bool(sorting, MUSE_GEOTABLE_SKY, CPL_FALSE);
310 cpl_table_sort(table, sorting);
311 cpl_propertylist_delete(sorting);
315 double area = 0., areas[4];
317 nperstack = kMuseSlicesPerCCD / 4;
318 for (istack = 0; istack < 4; istack++) {
319 cpl_table *stack = cpl_table_extract(table, 12 * istack, nperstack);
322 double height = fabs(cpl_table_get(stack, MUSE_GEOTABLE_Y, 0, NULL)
323 - cpl_table_get(stack, MUSE_GEOTABLE_Y, nperstack - 1, NULL))
325 / kMuseTypicalCubeSizeY * aScale;
326 areas[istack] = cpl_table_get_column_mean(stack, MUSE_GEOTABLE_WIDTH)
328 / kMuseTypicalCubeSizeX * aScale;
329 cpl_table_delete(stack);
331 cpl_msg_debug(__func__,
"areas[%d] = %f", istack, areas[istack]);
333 area += areas[istack];
335 cpl_table_delete(table);
354 cpl_ensure(aLines, CPL_ERROR_NULL_INPUT, NULL);
357 cpl_table *tlines = cpl_table_duplicate(aLines);
360 cpl_table_cast_column(tlines, MUSE_LINE_CATALOG_LAMBDA, MUSE_LINE_CATALOG_LAMBDA,
362 cpl_table_cast_column(tlines, MUSE_LINE_CATALOG_FLUX, MUSE_LINE_CATALOG_FLUX,
364 cpl_table_unselect_all(tlines);
370 cpl_table_or_selected_string(tlines, MUSE_LINE_CATALOG_ION, CPL_EQUAL_TO,
"XeI");
371 cpl_table_or_selected_double(tlines, MUSE_LINE_CATALOG_FLUX, CPL_LESS_THAN, 5000.);
372 cpl_table_or_selected_double(tlines, MUSE_LINE_CATALOG_LAMBDA, CPL_LESS_THAN,
373 kMuseNominalLambdaMin);
374 cpl_table_or_selected_int(tlines, MUSE_LINE_CATALOG_QUALITY, CPL_LESS_THAN, 1);
375 cpl_table_erase_selected(tlines);
380 cpl_table_or_selected_string(tlines, MUSE_LINE_CATALOG_ION, CPL_EQUAL_TO,
"NeI");
381 cpl_table_and_selected_int(tlines, MUSE_LINE_CATALOG_QUALITY, CPL_LESS_THAN, 2);
382 cpl_table_unselect_row(tlines, cpl_table_get_nrow(tlines) - 1);
383 cpl_table_erase_selected(tlines);
384 cpl_table_or_selected_string(tlines, MUSE_LINE_CATALOG_ION, CPL_EQUAL_TO,
"NeI");
385 cpl_table_and_selected_double(tlines, MUSE_LINE_CATALOG_FLUX, CPL_LESS_THAN, 10000.);
386 cpl_table_unselect_row(tlines, cpl_table_get_nrow(tlines) - 1);
387 cpl_table_erase_selected(tlines);
391 int nlines = cpl_table_get_nrow(tlines);
393 cpl_table_delete(tlines);
394 cpl_error_set_message(__func__, CPL_ERROR_DATA_NOT_FOUND,
395 "Only found %d suitable arc lines!", nlines);
398 cpl_vector *lines = cpl_vector_wrap(nlines,
399 cpl_table_unwrap(tlines, MUSE_LINE_CATALOG_LAMBDA));
400 cpl_table_delete(tlines);
401 cpl_msg_info(__func__,
"Using a list of %d arc lines (from %.1f to %.1f "
402 "Angstrom)", nlines, cpl_vector_get(lines, 0),
403 cpl_vector_get(lines, nlines - 1));
457 const cpl_table *aTrace,
const cpl_table *aWave,
458 const cpl_vector *aLines,
double aSigma,
461 cpl_ensure(aImage && aList && aTrace && aWave && aLines, CPL_ERROR_NULL_INPUT,
463 cpl_ensure(aSigma > 0., CPL_ERROR_ILLEGAL_INPUT, NULL);
465 cpl_ensure(nimages >= 5, CPL_ERROR_ILLEGAL_INPUT, NULL);
466 int nlines = cpl_vector_get_size(aLines);
467 cpl_ensure(nlines >= 3, CPL_ERROR_ILLEGAL_INPUT, NULL);
471 int ny = cpl_image_get_size_y(aImage->
data),
472 nentries = kMuseSlicesPerCCD * kMuseCUmpmSpotsPerSlice * nlines * nimages;
477 for (iline = 0; iline < nlines; iline++) {
478 double lambda = cpl_vector_get(aLines, iline);
479 cpl_msg_info(__func__,
"line %d at %.3f Angstrom", iline + 1, lambda);
481 unsigned short nslice;
482 for (nslice = 1; nslice <= kMuseSlicesPerCCD; nslice++) {
486 if (!ptrace || !pwave) {
488 cpl_polynomial_delete(pwave);
491 double xc = cpl_polynomial_eval_1d(ptrace[MUSE_TRACE_CENTER], ny / 2, NULL);
494 cpl_polynomial *pxconst = cpl_polynomial_new(1);
496 cpl_polynomial_set_coeff(pxconst, &p, xc);
497 cpl_polynomial *pywave = cpl_polynomial_extract(pwave, 0, pxconst);
498 cpl_polynomial_delete(pxconst);
500 double yc = 1, lbda = -1;
501 while (fabs(lambda - lbda) > 1.) {
502 lbda = cpl_polynomial_eval_1d(pywave, yc, NULL);
504 if (yc > kMuseOutputYTop) {
508 cpl_polynomial_delete(pywave);
510 cpl_msg_debug(__func__,
"--> %.3f --> %f,%f in slice %hu",
511 lbda, xc, yc, nslice);
513 cpl_polynomial_delete(pwave);
516 if (fabs(lambda - lbda) > 1.) {
517 cpl_msg_warning(__func__,
"Polynomial in slice %hu of IFU %hhu appears "
518 "to be faulty! Skipping measurement of line %d (%.1f "
519 "Angstrom)", nslice, ifu, iline + 1, lambda);
525 #define DETECTION_HALFSIZE 7
526 int xl = lround(cpl_polynomial_eval_1d(ptrace[MUSE_TRACE_LEFT], yc, NULL)),
527 xr = lround(cpl_polynomial_eval_1d(ptrace[MUSE_TRACE_RIGHT], yc, NULL)),
528 yb = lround(yc - DETECTION_HALFSIZE),
529 yt = lround(yc + DETECTION_HALFSIZE);
530 cpl_image *box = cpl_image_extract(aImage->
data, xl, yb, xr, yt);
532 cpl_image *fbox = cpl_image_duplicate(box);
534 cpl_image_filter(fbox, box, gkernel, CPL_FILTER_LINEAR, CPL_BORDER_FILTER);
535 cpl_matrix_delete(gkernel);
536 cpl_stats_mode mode = CPL_STATS_MEDIAN | CPL_STATS_MEDIAN_DEV;
537 cpl_stats *s = cpl_stats_new_from_image(box, mode);
538 double limit = cpl_stats_get_median(s)
539 + aSigma * cpl_stats_get_median_dev(s);
540 cpl_mask *mask = cpl_mask_threshold_image_create(fbox, limit, DBL_MAX);
542 char *fn1 = cpl_sprintf(
"box_%02hu.fits", nslice),
543 *fn2 = cpl_sprintf(
"boxf_%02hu.fits", nslice),
544 *fn3 = cpl_sprintf(
"boxf_%02hu_mask.fits", nslice);
545 cpl_image_save(box, fn1, CPL_TYPE_UNSPECIFIED, NULL, CPL_IO_CREATE);
546 cpl_image_save(fbox, fn2, CPL_TYPE_UNSPECIFIED, NULL, CPL_IO_CREATE);
547 cpl_mask_save(mask, fn3, NULL, CPL_IO_CREATE);
553 cpl_apertures *apertures = cpl_apertures_extract_mask(box, mask);
555 cpl_msg_debug(__func__,
"stats in box [%d:%d,%d:%d] --> limit = "
556 "%f + %.1f * %f = %f, apertures:", xl, xr, yb, yt,
557 cpl_stats_get_median(s), aSigma,
558 cpl_stats_get_median_dev(s), limit);
559 cpl_apertures_dump(apertures, stdout);
562 cpl_mask_delete(mask);
564 cpl_image_delete(fbox);
565 cpl_image_delete(box);
566 cpl_errorstate es = cpl_errorstate_get();
567 int nspots = cpl_apertures_get_size(apertures);
571 if (!apertures || nspots != kMuseCUmpmSpotsPerSlice) {
572 cpl_msg_warning(__func__,
"found %d spot%s (need %hhu) down to the %.1f"
573 "-sigma limit in slice %d for wavelength %.3f Angstrom "
574 "in box [%d:%d,%d:%d]", nspots, nspots == 1 ?
"" :
"s",
575 kMuseCUmpmSpotsPerSlice, aSigma, nslice, lambda,
577 cpl_apertures_delete(apertures);
578 cpl_errorstate_set(es);
581 cpl_msg_debug(__func__,
"found %d spots using the %.1f-sigma limit in "
582 "slice %d for wavelength %.3f Angstrom in box [%d:%d,%d:%d]",
583 nspots, aSigma, nslice, lambda, xl, xr, yb, yt);
585 cpl_apertures_dump(apertures, stdout);
591 cpl_matrix *mspots = cpl_matrix_new(nspots, 2);
593 for (naper = 1; naper <= nspots; naper++) {
594 double xpos = cpl_apertures_get_centroid_x(apertures, naper);
595 cpl_matrix_set(mspots, naper - 1, 0, xpos);
596 cpl_matrix_set(mspots, naper - 1, 1, naper);
598 cpl_matrix_sort_rows(mspots, 1);
600 printf(
"mspots sorted:\n");
601 cpl_matrix_dump(mspots, stdout);
605 int idx, *aperidx = cpl_calloc(nspots,
sizeof(
int));
606 for (naper = 1, idx = nspots - 1; naper <= nspots && idx >= 0; naper++, idx--) {
608 aperidx[naper - 1] = cpl_matrix_get(mspots, idx, 1);
610 cpl_matrix_delete(mspots);
615 for (k = 0; k < nimages; k++) {
621 for (i = 1; i <= 4; i++) {
626 #define MEASUREMENT_HALFSIZE 5
627 #define BACKGROUND_HALFSIZE 7
628 for (idx = 0; idx < nspots; idx++) {
629 naper = aperidx[idx];
630 double xpos = cpl_apertures_get_centroid_x(apertures, naper) + xl,
631 ypos = cpl_apertures_get_centroid_y(apertures, naper) + yb;
633 printf(
"aper %d idx %d xpos %f\n", naper, idx, xpos);
636 cpl_stats_mode mspot = CPL_STATS_FLUX | CPL_STATS_CENTROID,
637 mbg = CPL_STATS_MEAN;
638 cpl_stats *sspot = cpl_stats_new_from_image_window(image->
data, mspot,
639 xpos - MEASUREMENT_HALFSIZE,
640 ypos - MEASUREMENT_HALFSIZE,
641 xpos + MEASUREMENT_HALFSIZE,
642 ypos + MEASUREMENT_HALFSIZE),
643 *sbg = cpl_stats_new_from_image_window(image->
data, mbg,
644 xpos - BACKGROUND_HALFSIZE,
645 ypos - BACKGROUND_HALFSIZE,
646 xpos + BACKGROUND_HALFSIZE,
647 ypos + BACKGROUND_HALFSIZE);
648 int npix = cpl_stats_get_npix(sspot);
649 double bg = cpl_stats_get_mean(sbg),
650 flux = cpl_stats_get_flux(sspot) - bg * npix;
654 cpl_stats_delete(sbg);
655 double xcentroid = cpl_stats_get_centroid_x(sspot),
656 ycentroid = cpl_stats_get_centroid_y(sspot);
657 cpl_stats_delete(sspot);
661 cpl_array *gpars = cpl_array_new(7, CPL_TYPE_DOUBLE);
662 cpl_array_set(gpars, 0, bg);
663 cpl_array_set(gpars, 1, flux);
664 cpl_array_set(gpars, 3, xcentroid);
665 cpl_array_set(gpars, 4, ycentroid);
666 cpl_array_set(gpars, 5, 2.);
667 cpl_array_set(gpars, 6, 2.);
668 cpl_fit_image_gaussian(image->
data, NULL, lround(xcentroid), lround(ycentroid),
669 2 * MEASUREMENT_HALFSIZE + 1, 2 * MEASUREMENT_HALFSIZE + 1,
670 gpars, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
671 xcentroid = cpl_array_get(gpars, 3, NULL);
672 ycentroid = cpl_array_get(gpars, 4, NULL);
673 xfwhm = cpl_array_get(gpars, 5, NULL) * CPL_MATH_FWHM_SIG;
674 yfwhm = cpl_array_get(gpars, 6, NULL) * CPL_MATH_FWHM_SIG;
675 cpl_array_delete(gpars);
677 cpl_image_get_fwhm(image->
data, lround(xcentroid), lround(ycentroid),
680 if (cpl_propertylist_has(image->
header, MUSE_HDR_TMP_FN)) {
681 cpl_table_set_string(measurements,
"filename", irow,
682 cpl_propertylist_get_string(image->
header,
685 cpl_table_set_string(measurements,
"filename", irow,
"unknown");
687 cpl_table_set_int(measurements,
"image", irow, k + 1);
688 cpl_table_set_int(measurements,
"POSENC2", irow, posenc[1]);
689 cpl_table_set(measurements,
"POSPOS2", irow, pospos[1]);
690 cpl_table_set_int(measurements,
"POSENC3", irow, posenc[2]);
691 cpl_table_set(measurements,
"POSPOS3", irow, pospos[2]);
692 cpl_table_set_int(measurements,
"POSENC4", irow, posenc[3]);
693 cpl_table_set(measurements,
"POSPOS4", irow, pospos[3]);
696 cpl_table_set(measurements,
"VPOS", irow,
697 pospos[2] / sin(posang * CPL_MATH_RAD_DEG));
698 cpl_table_set_double(measurements,
"ScaleFOV", irow,
700 cpl_table_set_int(measurements,
"SubField", irow, ifu);
701 cpl_table_set_int(measurements,
"SliceCCD", irow, nslice);
702 cpl_table_set(measurements,
"lambda", irow, lambda);
703 cpl_table_set_int(measurements,
"SpotNo", irow, idx + 1);
704 cpl_table_set(measurements,
"xc", irow, xcentroid);
705 cpl_table_set(measurements,
"yc", irow, ycentroid);
706 cpl_table_set(measurements,
"xfwhm", irow, xfwhm);
707 cpl_table_set(measurements,
"yfwhm", irow, yfwhm);
708 cpl_table_set(measurements,
"flux", irow, flux);
709 cpl_table_set(measurements,
"bg", irow, bg);
711 double xcslice = cpl_polynomial_eval_1d(ptrace[MUSE_TRACE_CENTER], ycentroid, NULL);
712 cpl_table_set(measurements,
"dxcen", irow, xcentroid - xcslice);
714 double twidth = cpl_polynomial_eval_1d(ptrace[MUSE_TRACE_RIGHT], ycentroid, NULL)
715 - cpl_polynomial_eval_1d(ptrace[MUSE_TRACE_LEFT], ycentroid, NULL);
716 cpl_table_set(measurements,
"twidth", irow, twidth);
719 if (xfwhm < 0 || yfwhm < 0) {
720 cpl_msg_warning(__func__,
"xfwhm and/or yfwhm are invalid: %f, %f:", xfwhm, yfwhm);
722 cpl_table_set_invalid(measurements,
"xfwhm", irow);
725 cpl_table_set_invalid(measurements,
"yfwhm", irow);
727 cpl_table_dump(measurements, irow, 1, stdout);
734 cpl_apertures_delete(apertures);
741 cpl_table_erase_invalid(measurements);
744 cpl_propertylist *order = cpl_propertylist_new();
745 cpl_propertylist_append_bool(order,
"lambda", CPL_FALSE);
746 cpl_propertylist_append_bool(order,
"SliceCCD", CPL_FALSE);
747 cpl_propertylist_append_bool(order,
"SpotNo", CPL_FALSE);
748 cpl_propertylist_append_bool(order,
"VPOS", CPL_FALSE);
749 cpl_table_sort(measurements, order);
750 cpl_propertylist_delete(order);
794 unsigned short aNSlice,
unsigned char aNSpot,
795 double aLambda,
double aVPosRef, cpl_boolean aVerifyDY,
805 cpl_table_unselect_all(aSpots);
806 cpl_size irow, nrow = cpl_table_get_nrow(aSpots);
807 for (irow = 0; irow < nrow; irow++) {
808 if (cpl_table_get_int(aSpots,
"SliceCCD", irow, NULL) == aNSlice &&
809 cpl_table_get_int(aSpots,
"SpotNo", irow, NULL) == aNSpot &&
810 cpl_table_get_double(aSpots,
"lambda", irow, NULL) == aLambda) {
811 cpl_table_select_row(aSpots, irow);
814 cpl_size nextracted = cpl_table_count_selected(aSpots);
815 if (nextracted < 1) {
818 cpl_msg_debug(__func__,
"No detection for spot %1hhu in slice %2hu of IFU "
819 "%hhu at wavelength %.3f", aNSpot, aNSlice, aIFU, aLambda);
822 cpl_table *tspot = cpl_table_extract_selected(aSpots);
825 cpl_table_dump(tspot, 0, 10000, stdout);
829 int nsrow = cpl_table_get_nrow(tspot);
830 cpl_image *imflux = cpl_image_wrap(nsrow, 1,
832 cpl_table_get_data_double(tspot,
"flux"));
835 cpl_stats *s = cpl_stats_new_from_image(imflux,
836 CPL_STATS_MEDIAN | CPL_STATS_MEDIAN_DEV);
837 double limit = cpl_stats_get_median(s) + cpl_stats_get_median_dev(s) * 0.5;
842 cpl_mask *mask = cpl_mask_threshold_image_create(imflux, limit, DBL_MAX);
843 cpl_mask *kernel = cpl_mask_new(3, 1);
844 cpl_mask_not(kernel);
845 cpl_mask *mask2 = cpl_mask_duplicate(mask);
846 cpl_mask_filter(mask, mask2, kernel, CPL_FILTER_DILATION, CPL_BORDER_NOP);
847 cpl_mask_delete(mask2);
848 cpl_mask_delete(kernel);
849 cpl_apertures *aper = cpl_apertures_extract_mask(imflux, mask);
850 cpl_mask_delete(mask);
852 cpl_msg_warning(__func__,
"No detection for spot %1hhu in slice %2hu of IFU "
853 "%2hhu at wavelength %.3f", aNSpot, aNSlice, aIFU, aLambda);
854 cpl_table_delete(tspot);
855 cpl_image_unwrap(imflux);
859 double dcenter = DBL_MAX;
860 int naper, ndcenter = -1;
861 for (naper = 1; naper <= cpl_apertures_get_size(aper); naper++) {
863 int npos = cpl_apertures_get_npix(aper, naper);
864 if (cpl_apertures_get_size(aper) > 1 && npos < 3) {
865 cpl_msg_debug(__func__,
"ifu %2hhu sliceccd %2d spot %1hhu lambda %.3f, "
866 "aperture %d: only %d positions -> skip", aIFU, aNSlice,
867 aNSpot, aLambda, naper, npos);
871 double xref = aVPosRef > 0 ? aVPosRef
872 : cpl_table_get_double(tspot,
"VPOS", (nsrow + 1) / 2, NULL),
873 xcentroid = cpl_apertures_get_centroid_x(aper, naper);
877 while (++irow + 1 < xcentroid) ;
878 double pp1 = cpl_table_get_double(tspot,
"VPOS", irow - 1, NULL),
879 pp2 = cpl_table_get_double(tspot,
"VPOS", irow, NULL),
880 ppfrac = xcentroid - irow;
882 cpl_msg_debug(__func__,
"%"CPL_SIZE_FORMAT
" (%f) --> %f %f ==> %f", irow,
883 xcentroid, pp1, pp2, pp1 * (1 - ppfrac) + pp2 * ppfrac);
885 double ppcentroid = pp1 * (1 - ppfrac) + pp2 * ppfrac;
887 double dc = fabs(ppcentroid - xref);
888 int x1 = cpl_apertures_get_left(aper, naper),
889 x2 = cpl_apertures_get_right(aper, naper);
890 if (dc < dcenter && x1 > 1 && x2 < nsrow) {
897 if (aDY || aVerifyDY) {
898 for (naper = 1; naper < cpl_apertures_get_size(aper); naper++) {
899 int l1 = cpl_apertures_get_left(aper, naper),
900 r1 = cpl_apertures_get_right(aper, naper),
901 l2 = cpl_apertures_get_left(aper, naper + 1),
902 r2 = cpl_apertures_get_right(aper, naper + 1);
903 if (l1 > 1 && r1 < nsrow && l2 > 1 && r2 < nsrow) {
907 for (n2aper = naper; n2aper <= naper + 1; n2aper++) {
908 cpl_size irow1 = cpl_apertures_get_left(aper, n2aper) - 1,
909 irow2 = cpl_apertures_get_right(aper, n2aper) - 1;
910 double vpos = 0., ftot = 0.;
911 for (irow = irow1; irow <= irow2; irow++) {
912 double flux = cpl_table_get(tspot,
"flux", irow, NULL);
914 vpos += cpl_table_get(tspot,
"VPOS", irow, NULL) * flux;
916 peak[n2aper - naper] = vpos / ftot;
918 double xcdiff = fabs(peak[1] - peak[0]);
921 cpl_errorstate state = cpl_errorstate_get();
922 cpl_size idy = 0, ndy = cpl_array_get_size(aDY);
923 while (cpl_array_is_valid(aDY, idy) > 0) {
926 if (cpl_array_get_size(aDY) <= idy) {
927 cpl_array_set_size(aDY, ndy * 1.5);
928 cpl_errorstate_set(state);
931 cpl_msg_debug(__func__,
"xcdiff = %f (%f - %f = %f) / index %"CPL_SIZE_FORMAT,
932 xcdiff, peak[1], peak[0], peak[1] - peak[0], idy);
934 cpl_array_set_double(aDY, idy, xcdiff);
937 printf(
"\"centroids_d_%f.dat\" u 18:16 t \"d %f (%f %f)\" w lp, \\\n",
938 xcdiff, xcdiff, peak[0], peak[1]);
939 char *fn = cpl_sprintf(
"centroids_d_%f.dat", xcdiff);
940 FILE *fp = fopen(fn,
"w");
941 fprintf(fp,
"# good centroids at %f and %f --> d = %f mm\n#", peak[0], peak[1], xcdiff);
942 cpl_table_dump(tspot, 0, 10000, fp);
951 cpl_msg_warning(__func__,
"Motion of spot %1hhu in slice %2hu of IFU "
952 "%2hhu at wavelength %.3f did not result in usable "
953 "coverage", aNSpot, aNSlice, aIFU, aLambda);
954 cpl_table_delete(tspot);
955 cpl_apertures_delete(aper);
956 cpl_image_unwrap(imflux);
959 cpl_size irow1 = cpl_apertures_get_left(aper, ndcenter) - 1,
960 irow2 = cpl_apertures_get_right(aper, ndcenter) - 1;
961 cpl_apertures_delete(aper);
962 cpl_image_unwrap(imflux);
965 cpl_table_unselect_all(tspot);
966 for (irow = irow1; irow <= irow2; irow++) {
967 cpl_table_select_row(tspot, irow);
969 cpl_table *result = cpl_table_extract_selected(tspot);
970 cpl_table_delete(tspot);
1011 cpl_ensure_code(aDY && aSpots, CPL_ERROR_NULL_INPUT);
1012 cpl_ensure_code(cpl_array_get_type(aDY) == CPL_TYPE_DOUBLE,
1013 CPL_ERROR_INCOMPATIBLE_INPUT);
1014 cpl_size nrow = cpl_table_get_nrow(aSpots);
1015 cpl_ensure_code(nrow > 10, CPL_ERROR_ILLEGAL_INPUT);
1017 CPL_ERROR_INCOMPATIBLE_INPUT);
1018 const unsigned char ifu = cpl_table_get_column_min(aSpots,
"SubField"),
1019 ifu2 = cpl_table_get_column_max(aSpots,
"SubField");
1020 cpl_ensure_code(ifu == ifu2 && ifu >= 1 && ifu <= kMuseNumIFUs,
1021 CPL_ERROR_ILLEGAL_INPUT);
1022 cpl_ensure_code(cpl_table_get_column_stdev(aSpots,
"ScaleFOV") < 1e-10,
1023 CPL_ERROR_ILLEGAL_INPUT);
1025 cpl_boolean verifydy = getenv(
"MUSE_DEBUG_GEO_VERIFY_DY")
1026 && atoi(getenv(
"MUSE_DEBUG_GEO_VERIFY_DY")) > 0;
1028 cpl_msg_warning(__func__,
"Running with DY pinhole distance verification on"
1029 " (MUSE_DEBUG_GEO_VERIFY_DY=%s), will produce lots of files "
1030 "\"centroids_d_*.dat\"!", getenv(
"MUSE_DEBUG_GEO_VERIFY_DY"));
1034 double *lbda = cpl_table_get_data_double(aSpots,
"lambda");
1035 cpl_vector *vlbda = cpl_vector_wrap(nrow, lbda);
1037 cpl_vector_unwrap(vlbda);
1038 int nlines = cpl_vector_get_size(lambdas);
1042 cpl_array *dy = cpl_array_new(kMuseSlicesPerCCD * nlines * kMuseCUmpmSpotsPerSlice,
1048 unsigned short nslice;
1049 for (nslice = 1; nslice <= kMuseSlicesPerCCD; nslice++) {
1051 for (iline = 0; iline < nlines; iline++) {
1052 double lambda = cpl_vector_get(lambdas, iline);
1054 unsigned char nspot;
1055 double vposref = -DBL_MAX;
1056 for (nspot = 1; nspot <= kMuseCUmpmSpotsPerSlice; nspot++) {
1058 lambda, vposref, verifydy, dy);
1059 cpl_table_delete(tspot);
1063 cpl_vector_delete(lambdas);
1066 cpl_msg_debug(__func__,
"Median vertical pinhole distance in IFU %02hhu: %f mm",
1067 ifu, cpl_array_get_median(dy));
1068 #pragma omp critical (geo_dy_array_insert)
1069 cpl_array_insert(aDY, dy, cpl_array_get_size(aDY));
1070 cpl_array_delete(dy);
1072 return CPL_ERROR_NONE;
1106 double aMin,
double aMax)
1108 cpl_ensure(aDY, CPL_ERROR_NULL_INPUT, 0.);
1109 cpl_ensure(cpl_array_get_type(aDY) == CPL_TYPE_DOUBLE,
1110 CPL_ERROR_INCOMPATIBLE_INPUT, 0.);
1111 cpl_ensure(cpl_array_count_invalid(aDY) < cpl_array_get_size(aDY),
1112 CPL_ERROR_ILLEGAL_INPUT, 0.);
1117 printf(
"aDY array 1: %f +/- %f (%f)\n", cpl_array_get_mean(aDY),
1118 cpl_array_get_stdev(aDY), cpl_array_get_median(aDY));
1119 cpl_array_dump(aDY, 0, 1000000, stdout);
1120 printf(
"aDY histogram 1:\n");
1121 cpl_plot_bivector(NULL,
"w lp", NULL, histogram);
1122 cpl_bivector_dump(histogram, stdout);
1126 cpl_bivector_delete(histogram);
1127 double mean = cpl_array_get_mean(aDY),
1128 stdev = cpl_array_get_stdev(aDY),
1129 min = mean - 2 * stdev,
1130 max = mean + 2 * stdev,
1131 step = (max - min) / 20.;
1133 double median = cpl_array_get_median(aDY);
1134 printf(
"aDY array 2: %f +/- %f (%f)\n", mean, stdev, median);
1135 cpl_array_dump(aDY, 0, 1000000, stdout);
1140 printf(
"aDY histogram 2:\n");
1141 cpl_plot_bivector(NULL,
"w lp", NULL, histogram);
1142 cpl_bivector_dump(histogram, stdout);
1146 cpl_bivector_delete(histogram);
1147 mean = cpl_array_get_mean(aDY);
1148 stdev = cpl_array_get_stdev(aDY);
1150 double median2 = cpl_array_get_median(aDY);
1151 printf(
"aDY array 3: %f +/- %f (%f)\n", mean, stdev, median2);
1152 cpl_array_dump(aDY, 0, 1000000, stdout);
1158 cpl_msg_info(__func__,
"Computed vertical pinhole distance of %.6f +/- %.6f "
1159 "mm (instead of %.4f)", mean, stdev, kMuseCUmpmDY);
1160 if (getenv(
"MUSE_GEOMETRY_PINHOLE_DY")) {
1161 cpl_msg_warning(__func__,
"Vertical pinhole distance already overridden in the "
1162 "environment (%f mm)", atof(getenv(
"MUSE_GEOMETRY_PINHOLE_DY")));
1164 char *envstring = cpl_sprintf(
"%f", mean);
1165 int err = setenv(
"MUSE_GEOMETRY_PINHOLE_DY", envstring, 1);
1167 cpl_msg_info(__func__,
"Set MUSE_GEOMETRY_PINHOLE_DY=%s in the environment",
1170 cpl_free(envstring);
1212 cpl_ensure(aGeo, CPL_ERROR_NULL_INPUT, NULL);
1214 gt->
table = cpl_table_duplicate(aGeo->
table);
1235 cpl_table_delete(aGeo->
table);
1308 cpl_ensure(aSpots && aTrace, CPL_ERROR_NULL_INPUT, NULL);
1309 cpl_size nrow = cpl_table_get_nrow(aSpots);
1310 cpl_ensure(nrow > 10, CPL_ERROR_ILLEGAL_INPUT, NULL);
1312 CPL_ERROR_INCOMPATIBLE_INPUT, NULL);
1313 const unsigned char ifu = cpl_table_get_column_min(aSpots,
"SubField"),
1314 ifu2 = cpl_table_get_column_max(aSpots,
"SubField");
1315 cpl_ensure(ifu == ifu2 && ifu >= 1 && ifu <= kMuseNumIFUs,
1316 CPL_ERROR_ILLEGAL_INPUT, NULL);
1317 const double kScale = cpl_table_get_column_mean(aSpots,
"ScaleFOV");
1318 cpl_ensure(cpl_table_get_column_stdev(aSpots,
"ScaleFOV") < 1e-10,
1319 CPL_ERROR_ILLEGAL_INPUT, NULL);
1321 cpl_boolean noinvertangle = getenv(
"MUSE_GEOMETRY_NO_INVERT_ANGLE")
1322 && atoi(getenv(
"MUSE_GEOMETRY_NO_INVERT_ANGLE")) > 0;
1323 double maskangle = 0., fmaskrot = 1.;
1324 if (getenv(
"MUSE_GEOMETRY_MASK_ROTATION")) {
1325 maskangle = atof(getenv(
"MUSE_GEOMETRY_MASK_ROTATION"));
1326 fmaskrot = cos(maskangle * CPL_MATH_RAD_DEG);
1327 cpl_msg_warning(__func__,
"Adapting to global mask rotation of %.4f deg "
1328 "(cos = %.4e)", maskangle, fmaskrot);
1330 double pinholedy = kMuseCUmpmDY;
1331 if (getenv(
"MUSE_GEOMETRY_PINHOLE_DY")) {
1332 pinholedy = atof(getenv(
"MUSE_GEOMETRY_PINHOLE_DY"));
1333 cpl_msg_warning(__func__,
"Using pinhole y distance of %f mm (instead of "
1334 "%f mm)", pinholedy, kMuseCUmpmDY);
1338 double *lbda = cpl_table_get_data_double(aSpots,
"lambda");
1339 cpl_vector *vlbda = cpl_vector_wrap(nrow, lbda);
1341 cpl_vector_unwrap(vlbda);
1342 int nlines = cpl_vector_get_size(lambdas);
1344 * kMuseCUmpmSpotsPerSlice * nlines,
1348 unsigned short nslice;
1349 for (nslice = 1; nslice <= kMuseSlicesPerCCD; nslice++) {
1356 for (iline = 0; iline < nlines; iline++) {
1357 double lambda = cpl_vector_get(lambdas, iline);
1359 unsigned char nspot, nslicespot = 0;
1360 double vposref = -DBL_MAX;
1361 for (nspot = 1; nspot <= kMuseCUmpmSpotsPerSlice; nspot++) {
1363 lambda, vposref, CPL_FALSE,
1369 double xcenter = 0., ycenter = 0.,
1370 vpos = 0., ftot = 0.;
1371 nrow = cpl_table_get_nrow(tspot);
1373 for (irow = 0; irow < nrow; irow++) {
1374 double flux = cpl_table_get(tspot,
"flux", irow, NULL);
1376 xcenter += cpl_table_get(tspot,
"xc", irow, NULL) * flux;
1377 ycenter += cpl_table_get(tspot,
"yc", irow, NULL) * flux;
1378 vpos += cpl_table_get(tspot,
"VPOS", irow, NULL) * flux;
1380 cpl_table_delete(tspot);
1382 cpl_msg_warning(__func__,
"Invalid integrated flux of spot %1hhu/%1hhu "
1383 "in slice %2hu of IFU %2hhu at wavelength %.3f: %e",
1384 nspot, nslicespot, nslice, ifu, lambda, ftot);
1394 double xcen = cpl_polynomial_eval_1d(ptrace[MUSE_TRACE_CENTER], ycenter,
1396 xl = cpl_polynomial_eval_1d(ptrace[MUSE_TRACE_LEFT], ycenter, NULL),
1397 xr = cpl_polynomial_eval_1d(ptrace[MUSE_TRACE_RIGHT], ycenter, NULL),
1399 cpl_msg_info(__func__,
"ifu %2hhu sliceccd %2d spot %1hhu/%1hhu lambda %.3f "
1400 "x/y %8.3f %8.3f (xcen %8.3f xwidth %6.3f) vpos %f flux %e",
1401 ifu, nslice, nspot, nslicespot, lambda, xcenter, ycenter,
1402 xcen, xwidth, vpos, ftot);
1403 cpl_table_set_int(gt->
table, MUSE_GEOTABLE_FIELD, irrow, ifu);
1404 cpl_table_set_int(gt->
table, MUSE_GEOTABLE_SKY, irrow,
1405 kMuseGeoSliceSky[nslice - 1]);
1406 cpl_table_set_int(gt->
table, MUSE_GEOTABLE_CCD, irrow, nslice);
1407 cpl_table_set_int(gt->
table,
"spot", irrow, nslicespot);
1410 unsigned char stack = nslice <= 12 ? 4 : (nslice <= 24 ? 3 : (nslice <= 36 ? 2 : 1));
1411 cpl_table_set_int(gt->
table,
"stack", irrow, stack);
1413 cpl_table_set_double(gt->
table,
"xc", irrow, xcenter);
1414 cpl_table_set_double(gt->
table,
"yc", irrow, ycenter);
1415 cpl_table_set_double(gt->
table,
"dxl", irrow, xcenter - xl);
1416 cpl_table_set_double(gt->
table,
"dxr", irrow, xr - xcenter);
1417 cpl_table_set_double(gt->
table,
"vpos", irrow, vpos);
1418 cpl_table_set_double(gt->
table,
"flux", irrow, ftot);
1419 cpl_table_set_double(gt->
table,
"lambda", irrow, lambda);
1422 cpl_table_set_invalid(gt->
table, MUSE_GEOTABLE_WIDTH, irrow);
1423 cpl_table_set_invalid(gt->
table, MUSE_GEOTABLE_ANGLE, irrow);
1427 if (nslicespot == kMuseCUmpmSpotsPerSlice) {
1429 int err1a, err2a, err1b, err2b;
1430 double xc1 = cpl_table_get_double(gt->
table,
"xc", irrow - 2, &err1a),
1431 xc2 = cpl_table_get_double(gt->
table,
"xc", irrow - 1, &err2a),
1432 vpos1 = cpl_table_get_double(gt->
table,
"vpos", irrow - 2, &err1b),
1433 vpos2 = cpl_table_get_double(gt->
table,
"vpos", irrow - 1, &err2b);
1434 if (!err1a && !err2a) {
1435 if (xcenter < xc2 || xc2 < xc1) {
1436 cpl_msg_warning(__func__,
"spots are not sorted left-to-right on "
1437 "the CCD (%f .. %f .. %f)!", xc1, xc2, xcenter);
1439 double dx = (xcenter - xc1) / 2.;
1442 double dxerr = sqrt((pow(xcenter - xc2 - dx, 2)
1443 + pow(xc2 - xc1 - dx, 2)) / 3.);
1450 double xreloffset = 0.;
1452 const double dxexpected = 26.04644 - 0.05537208 * ifu;
1453 double dx1 = xc2 - xc1,
1454 dx2 = xcenter - xc2,
1455 diff1 = fabs(dx1 - dxexpected),
1456 diff2 = fabs(dx2 - dxexpected),
1458 if (fmin(diff1, diff2) > 1.5) {
1460 }
else if (diff2 > diff1) {
1467 xreloffset = xwidth * kMuseCUmpmDX / fmaskrot
1468 * kMuseTypicalCubeSizeX * kScale / 60.
1469 * fabs(1. / dx - 1 / dxnew) / 2.;
1470 cpl_msg_warning(__func__,
"ifu %2hhu sliceccd %2d spot %1hhu/%1hhu "
1471 "lambda %.3f dx %.3f +/- %.3f (%.3f %.3f): dxerr "
1472 "is large, using a guess of %.3f +/- %.3f "
1473 "(expected was %.3f for this IFU), adding %.3f to"
1474 " xrel!", ifu, nslice, nspot, nslicespot, lambda,
1475 dx, dxerr, xc2-xc1, xcenter-xc2, dxnew, dxerr / 2.,
1476 dxexpected, xreloffset);
1480 cpl_table_fill_column_window_double(gt->
table,
"dx", irrow - 2, 3, dx);
1481 cpl_table_fill_column_window_double(gt->
table,
"dxerr", irrow - 2, 3, dxerr);
1486 double scale = kMuseCUmpmDX / fmaskrot / dx,
1487 width = xwidth * scale * kMuseTypicalCubeSizeX * kScale / 60.,
1488 werr = width * dxerr / dx;
1489 if (width > kMuseSliceLoLikelyWidth && width < kMuseSliceHiLikelyWidth) {
1490 cpl_table_fill_column_window_double(gt->
table, MUSE_GEOTABLE_WIDTH,
1491 irrow - 2, 3, width);
1492 cpl_table_fill_column_window_double(gt->
table, MUSE_GEOTABLE_WIDTH
"err",
1493 irrow - 2, 3, werr);
1495 cpl_msg_warning(__func__,
"ifu %2hhu slice %2d lambda %.3f: computed "
1496 "an unlikely width: %.3f +/- %.3f", ifu, nslice,
1497 lambda, width, werr);
1498 cpl_table_set_column_invalid(gt->
table, MUSE_GEOTABLE_WIDTH,
1500 cpl_table_set_column_invalid(gt->
table, MUSE_GEOTABLE_WIDTH
"err",
1506 double xrel = (xcen - xc1 + xreloffset) * scale,
1507 xrelerr = fabs(xrel * dxerr / dx);
1508 cpl_table_set_double(gt->
table,
"xrel", irrow - 2, xrel);
1509 cpl_table_set_double(gt->
table,
"xrelerr", irrow - 2, xrelerr);
1510 xrel = (xcen - xc2 + xreloffset) * scale;
1511 xrelerr = fabs(xrel * dxerr / dx);
1512 cpl_table_set_double(gt->
table,
"xrel", irrow - 1, xrel);
1513 cpl_table_set_double(gt->
table,
"xrelerr", irrow - 1, xrelerr);
1514 xrel = (xcen - xcenter + xreloffset) * scale;
1515 xrelerr = fabs(xrel * dxerr / dx);
1516 cpl_table_set_double(gt->
table,
"xrel", irrow, xrel);
1517 cpl_table_set_double(gt->
table,
"xrelerr", irrow, xrelerr);
1520 if (!err1b && !err2b) {
1523 double pdiff = fmax(vpos1, fmax(vpos2, vpos))
1524 - fmin(vpos1, fmin(vpos2, vpos));
1526 double pmean = (vpos1 + vpos2 + vpos) / 3.;
1527 if (vpos1 > pmean) {
1528 vpos1 -= pinholedy * fmaskrot;
1530 if (vpos2 > pmean) {
1531 vpos2 -= pinholedy * fmaskrot;
1534 vpos -= pinholedy * fmaskrot;
1536 double pdiff2 = fmax(vpos1, fmax(vpos2, vpos))
1537 - fmin(vpos1, fmin(vpos2, vpos));
1538 if (pdiff2 < pdiff) {
1539 cpl_msg_debug(__func__,
"Fixed max vpos diff from %f down to %f",
1542 double vpos1o = cpl_table_get_double(gt->
table,
"vpos", irrow - 2, NULL),
1543 vpos2o = cpl_table_get_double(gt->
table,
"vpos", irrow - 1, NULL),
1544 vpos3o = cpl_table_get_double(gt->
table,
"vpos", irrow, NULL);
1545 cpl_msg_warning(__func__,
"Large max vpos diff detected but "
1546 "not fixed! (original: %f %f %f, %f -> mean %f "
1547 "-> fixed: %f %f %f, %f)", vpos1o, vpos2o, vpos3o,
1548 pdiff, pmean, vpos1, vpos2, vpos, pdiff2);
1555 double f = 1. / kMuseCUmpmDX / fmaskrot,
1556 angle1 = atan((vpos2 - vpos1) * f) * CPL_MATH_DEG_RAD,
1557 angle2 = atan((vpos - vpos2) * f) * CPL_MATH_DEG_RAD,
1558 angle = (angle1 + angle2) / 2.;
1559 if (!noinvertangle) {
1565 double aerr = sqrt((pow(angle1 - angle, 2) + pow(angle2 - angle, 2)) / 3.);
1566 if (fabs(angle) < kMuseGeoSliceMaxAngle) {
1567 cpl_table_fill_column_window_double(gt->
table, MUSE_GEOTABLE_ANGLE,
1568 irrow - 2, 3, angle);
1569 cpl_table_fill_column_window_double(gt->
table, MUSE_GEOTABLE_ANGLE
"err",
1570 irrow - 2, 3, aerr);
1572 cpl_msg_warning(__func__,
"ifu %2hhu slice %2d lambda %.3f: computed "
1573 "an unlikely angle: %.3f +/- %.3f", ifu, nslice,
1574 lambda, angle, aerr);
1575 cpl_table_set_column_invalid(gt->
table, MUSE_GEOTABLE_ANGLE,
1577 cpl_table_set_column_invalid(gt->
table, MUSE_GEOTABLE_ANGLE
"err",
1584 double dx = cpl_table_get_double(gt->
table,
"dx", irrow - 1, NULL),
1585 dxl = cpl_table_get_double(gt->
table,
"dxl", irrow - 2, NULL),
1586 dxr = cpl_table_get_double(gt->
table,
"dxr", irrow, NULL);
1587 cpl_table_fill_column_window_double(gt->
table,
"dxl", irrow - 2, 3, dxl / dx);
1588 cpl_table_fill_column_window_double(gt->
table,
"dxr", irrow - 2, 3, dxr / dx);
1596 cpl_vector_delete(lambdas);
1597 cpl_table_set_size(gt->
table, irrow);
1599 cpl_table_and_selected_invalid(gt->
table, MUSE_GEOTABLE_WIDTH);
1600 cpl_table_or_selected_invalid(gt->
table, MUSE_GEOTABLE_ANGLE);
1601 cpl_table_erase_selected(gt->
table);
1628 const char *aCol,
const char *aColErr,
1629 double *aValue,
double *aError,
1630 double *aMedian,
double aSigma)
1632 const char func[] =
"muse_geo_determine_horizontal";
1633 if (!aSlice)
return -1;
1634 if (!aCol || !aValue)
return -1;
1637 const double *vv = cpl_table_get_data_double_const(aSlice, aCol),
1640 ve = cpl_table_get_data_double_const(aSlice, aColErr);
1644 cpl_size n = cpl_table_get_nrow(aSlice);
1645 cpl_vector *vtmp = cpl_vector_wrap(n, (
double *)cpl_table_get_data_double_const(aSlice, aCol));
1646 double median = cpl_table_get_column_median(aSlice, aCol),
1648 vlo = median - aSigma * mdev,
1649 vhi = median + aSigma * mdev;
1650 cpl_vector_unwrap(vtmp);
1651 cpl_msg_debug(func,
"%s/%s: median %f +/- %f --> %f...%f", aCol, aColErr,
1652 median, mdev, vlo, vhi);
1656 double value, sigma = mdev,
1658 cpl_vector *vmedian = NULL;
1664 vmedian = cpl_vector_new(n);
1670 for (i = 0; i < n; i++) {
1671 if (vv[i] < vlo || vv[i] > vhi) {
1676 value += vv[i] / ve[i];
1677 weight += 1. / ve[i];
1689 cpl_vector_set(vmedian, im++, vv[i]);
1694 for (i = 0; i < n; i++) {
1695 if (vv[i] < vlo || vv[i] > vhi) {
1699 wmse += pow(vv[i] - value, 2) / ve[i];
1701 wmse += pow(vv[i] - value, 2);
1705 cpl_msg_debug(
"vpos",
"%f %f %f -> %f", vv[i], vv[i] - value,
1706 pow(vv[i] - value, 2), wmse);
1712 cpl_vector_set_size(vmedian, im);
1713 *aMedian = cpl_vector_get_median(vmedian);
1714 cpl_vector_delete(vmedian);
1717 if (isnormal(weight) && isfinite(wmse)) {
1720 sigma = sqrt(ve ? (n - nrejected) / (weight*weight) : 0. + wmse);
1723 cpl_msg_debug(
"vpos",
"%f", sigma);
1728 if (isfinite(value)) {
1729 vlo = value - aSigma * sigma;
1730 vhi = value + aSigma * sigma;
1733 cpl_msg_debug(func,
"%s/%s: %f +/- %f (+/- %f) --> %f...%f (rej. %"
1734 CPL_SIZE_FORMAT
" / %"CPL_SIZE_FORMAT
")", aCol, aColErr,
1735 value, sqrt(wmse), sigma, vlo, vhi, nrejected, n);
1737 }
while (nrejected < n && (max > vhi || min < vlo));
1742 if (nrejected == n) {
1743 value = (vlo + vhi) / 2.;
1744 sigma = (vhi - vlo) / (2. * aSigma);
1746 cpl_msg_debug(func,
"%s/%s: %f +/- %f (ALL rej. %"CPL_SIZE_FORMAT
" / %"
1747 CPL_SIZE_FORMAT
")", aCol, aColErr, value, sigma, nrejected, n);
1782 double *aP,
double *aPE,
double *aPM,
1783 double aDY,
double aF)
1785 const char func[] =
"muse_geo_determine_horizontal";
1786 if (!aSlice)
return;
1787 if (!aP || !aPE || !aPM)
return;
1789 cpl_vector *vvpos = cpl_vector_wrap(cpl_table_get_nrow(aSlice),
1790 (
double *)cpl_table_get_data_double_const(aSlice,
1792 *aP = cpl_vector_get_mean(vvpos);
1793 *aPE = cpl_vector_get_stdev(vvpos);
1794 *aPM = cpl_vector_get_median_const(vvpos);
1798 cpl_vector_unwrap(vvpos);
1802 cpl_size n = cpl_vector_get_size(vvpos);
1803 cpl_vector *vpp = cpl_vector_duplicate(vvpos),
1804 *vres = cpl_vector_duplicate(vvpos),
1805 *vmedian = cpl_vector_new(n);
1806 cpl_vector_unwrap(vvpos);
1807 cpl_vector_fill(vmedian, *aPM);
1808 cpl_vector_subtract(vres, vmedian);
1809 cpl_vector_delete(vmedian);
1810 double min = cpl_vector_get_min(vres),
1811 max = cpl_vector_get_max(vres);
1812 cpl_msg_debug(func,
"vector of vpos values (%.4f +/- %.4f, %.4f) and "
1813 "residuals (%.4f ... %.4f), pinhole distance %.4f",
1814 *aP, *aPE, *aPM, min, max, aDY * aF);
1816 cpl_bivector *biv = cpl_bivector_wrap_vectors(vpp, vres);
1817 cpl_bivector_dump(biv, stdout);
1819 cpl_bivector_unwrap_vectors(biv);
1824 cpl_array *ares = cpl_array_wrap_double(cpl_vector_unwrap(vres), n);
1825 cpl_array_abs(ares);
1826 double amin = cpl_array_get_min(ares),
1827 amax = cpl_array_get_max(ares);
1828 cpl_boolean halfandhalf = fabs(aDY/2. - amin) < 0.01
1829 && fabs(aDY/2. - amax) < 0.01;
1830 cpl_array_delete(ares);
1832 double p2, pe2, pm2;
1834 double limit = fabs(0.9 * aDY * aF);
1836 fabs(min) < limit && fabs(max) < limit) {
1841 &p2, &pe2, &pm2, 2.);
1842 cpl_msg_debug(func,
"values after rejection vector of vpos values (%.4f "
1843 "+/- %.4f, %.4f), %"CPL_SIZE_FORMAT
" rejected", p2, pe2, pm2,
1848 cpl_size nfixed = 0;
1849 for (i = 0; i < n; i++) {
1850 double v = cpl_vector_get(vpp, i);
1852 cpl_vector_set(vpp, i, v - aDY * aF);
1856 p2 = cpl_vector_get_mean(vpp);
1857 pe2 = cpl_vector_get_stdev(vpp);
1858 pm2 = cpl_vector_get_median_const(vpp);
1859 cpl_msg_debug(func,
"fixed vector of vpos values (%.4f +/- %.4f, %.4f), "
1860 "%"CPL_SIZE_FORMAT
" fixed", p2, pe2, pm2, nfixed);
1862 cpl_vector_dump(vpp, stdout);
1866 cpl_vector_delete(vpp);
1928 cpl_ensure(aGeo && aGeo->
table, CPL_ERROR_NULL_INPUT, NULL);
1929 cpl_size nrow = cpl_table_get_nrow(aGeo->
table);
1930 cpl_ensure(nrow >= 50, CPL_ERROR_ILLEGAL_INPUT, NULL);
1932 CPL_ERROR_INCOMPATIBLE_INPUT, NULL);
1933 const unsigned char ifu = cpl_table_get_column_min(aGeo->
table, MUSE_GEOTABLE_FIELD),
1934 ifu2 = cpl_table_get_column_max(aGeo->
table, MUSE_GEOTABLE_FIELD);
1935 cpl_ensure(ifu == ifu2 && ifu >= 1 && ifu <= kMuseNumIFUs,
1936 CPL_ERROR_ILLEGAL_INPUT, NULL);
1938 double maskangle = 0., fmaskrot = 1.;
1939 if (getenv(
"MUSE_GEOMETRY_MASK_ROTATION")) {
1940 maskangle = atof(getenv(
"MUSE_GEOMETRY_MASK_ROTATION"));
1941 fmaskrot = cos(maskangle * CPL_MATH_RAD_DEG);
1942 cpl_msg_warning(__func__,
"Adapting to global mask rotation of %.4f deg "
1943 "(cos = %.4e)", maskangle, fmaskrot);
1945 double pinholedy = kMuseCUmpmDY;
1946 if (getenv(
"MUSE_GEOMETRY_PINHOLE_DY")) {
1947 pinholedy = atof(getenv(
"MUSE_GEOMETRY_PINHOLE_DY"));
1948 cpl_msg_warning(__func__,
"Using pinhole y distance of %f mm (instead of "
1949 "%f mm)", pinholedy, kMuseCUmpmDY);
1951 cpl_boolean stdgap = getenv(
"MUSE_GEOMETRY_STD_GAP")
1952 && atoi(getenv(
"MUSE_GEOMETRY_STD_GAP")) > 0;
1954 cpl_msg_warning(__func__,
"Using old (standard) gap computation");
1956 cpl_msg_info(__func__,
"Using new (alternative) gap computation");
1958 const double kScaleX = kMuseTypicalCubeSizeX * aGeo->
scale / 60.;
1962 cpl_table_and_selected_int(gt->
table,
"spot", CPL_NOT_EQUAL_TO, 2);
1963 cpl_table_erase_selected(gt->
table);
1966 FILE *f = fopen(
"bla_horizontal.dat",
"w");
1968 cpl_table_dump(gt->
table, 0, 100000000, f);
1970 f = fopen(
"bla_lambdas.dat",
"w");
1972 cpl_table_dump(aGeo->
table, 0, 100000000, f);
1975 unsigned short nslice;
1976 for (nslice = 1; nslice <= kMuseSlicesPerCCD; nslice++) {
1978 cpl_table_select_all(gt->
table);
1979 cpl_table_and_selected_int(gt->
table,
"SliceCCD", CPL_EQUAL_TO, nslice);
1980 if (cpl_table_count_selected(gt->
table) < 1) {
1983 cpl_array *asel = cpl_table_where_selected(gt->
table);
1984 cpl_table *slice = cpl_table_extract_selected(gt->
table);
1986 cpl_table_dump(slice, 0, 1000, stdout);
1991 double a, ae, w, we, xr, xre, dxl, dxr;
1993 MUSE_GEOTABLE_ANGLE
"err", &a, &ae,
1996 MUSE_GEOTABLE_WIDTH
"err", &w, &we,
2004 cpl_errorstate ps = cpl_errorstate_get();
2005 double p = NAN, pe = -1, pm = NAN;
2007 pinholedy, fmaskrot);
2008 if (pe < 0 && !cpl_errorstate_is_equal(ps)) {
2010 cpl_errorstate_set(ps);
2013 cpl_msg_debug(__func__,
"IFU %2hhu stack %1d slice %2d / %2d "
2014 "angle %6.3f +/- %.3f deg width %.3f +/- %.3f pix "
2015 "xrel %.4f +/- %.4f vpos %.4f +/- %.4f (%.4f)", ifu,
2016 cpl_table_get_int(slice,
"stack", 0, NULL), nslice,
2017 cpl_table_get_int(slice,
"SliceSky", 0, NULL), a, ae, w, we,
2018 xr, xre, p, pe, pm);
2021 cpl_size irow = cpl_array_get_cplsize(asel, 0, NULL);
2022 cpl_array_delete(asel);
2023 cpl_table_set_double(gt->
table, MUSE_GEOTABLE_ANGLE, irow, a);
2024 cpl_table_set_double(gt->
table, MUSE_GEOTABLE_ANGLE
"err", irow, ae);
2025 cpl_table_set_double(gt->
table, MUSE_GEOTABLE_WIDTH, irow, w);
2026 cpl_table_set_double(gt->
table, MUSE_GEOTABLE_WIDTH
"err", irow, we);
2027 cpl_table_set_double(gt->
table,
"xrel", irow, xr);
2028 cpl_table_set_double(gt->
table,
"xrelerr", irow, xre);
2029 cpl_table_set_double(gt->
table,
"vpos", irow, p);
2030 cpl_table_set_double(gt->
table,
"vposerr", irow, pe);
2031 cpl_table_set_double(gt->
table,
"dxl", irow, dxl);
2032 cpl_table_set_double(gt->
table,
"dxr", irow, dxr);
2035 cpl_table_set_invalid(gt->
table,
"flux", irow);
2036 cpl_table_set_invalid(gt->
table,
"lambda", irow);
2037 cpl_table_set_invalid(gt->
table,
"xc", irow);
2038 cpl_table_set_invalid(gt->
table,
"yc", irow);
2039 cpl_table_unselect_row(gt->
table, irow);
2040 cpl_table_erase_selected(gt->
table);
2041 cpl_table_delete(slice);
2044 printf(
"intermediate result: weighted averages of angle, width, and xrel:\n");
2045 cpl_table_dump(gt->
table, 0, 1000000, stdout);
2055 const unsigned short nsoff = kMuseSlicesPerCCD / 4;
2056 for (nslice = 1 + nsoff; nslice <= 2*nsoff; nslice++) {
2058 cpl_table_unselect_all(gt->
table);
2059 cpl_table_or_selected_int(gt->
table,
"SliceSky", CPL_EQUAL_TO, nslice - nsoff);
2060 cpl_table_or_selected_int(gt->
table,
"SliceSky", CPL_EQUAL_TO, nslice);
2061 cpl_table_or_selected_int(gt->
table,
"SliceSky", CPL_EQUAL_TO, nslice + nsoff);
2062 cpl_table_or_selected_int(gt->
table,
"SliceSky", CPL_EQUAL_TO, nslice + 2*nsoff);
2064 cpl_table *ts = cpl_table_extract_selected(gt->
table);
2066 int irow, nrowts = cpl_table_get_nrow(ts),
2067 i1 = -1, i2 = -1, i3 = -1, i4 = -1;
2068 for (irow = 0; irow < nrowts; irow++) {
2071 switch (cpl_table_get_int(ts,
"stack", irow, NULL)) {
2072 case 1: i1 = irow;
break;
2073 case 2: i2 = irow;
break;
2074 case 3: i3 = irow;
break;
2075 case 4: i4 = irow;
break;
2079 if (i3 < 0 || i2 < 0) {
2080 char *msg = cpl_sprintf(
"For IFU %2hhu / row %2d in the slicer stacks "
2081 "(slice sky numbers %02d, %02d, %02d, %02d), at "
2082 "least one of the two middle stacks (%s/%s) is "
2083 "missing", ifu, nslice - nsoff,
2084 nslice - nsoff, nslice, nslice + nsoff, nslice + 2*nsoff,
2085 i3 < 0 ?
"left" :
"-", i2 < 0 ?
"right" :
"-");
2086 cpl_error_set_message(__func__, CPL_ERROR_DATA_NOT_FOUND,
"%s", msg);
2087 cpl_msg_error(__func__,
"%s", msg);
2089 cpl_table_dump(ts, 0, 10000, stdout);
2090 cpl_table_delete(ts);
2094 double *xrel = cpl_table_get_data_double(ts,
"xrel"),
2095 *xrerr = cpl_table_get_data_double(ts,
"xrelerr"),
2096 *width = cpl_table_get_data_double(ts, MUSE_GEOTABLE_WIDTH),
2097 *werr = cpl_table_get_data_double(ts, MUSE_GEOTABLE_WIDTH
"err"),
2098 *pdxl = cpl_table_get_data_double(ts,
"dxl"),
2099 *pdxr = cpl_table_get_data_double(ts,
"dxr");
2101 cpl_table_duplicate_column(ts,
"width_mm",
2102 ts, MUSE_GEOTABLE_WIDTH);
2103 cpl_table_multiply_scalar(ts,
"width_mm", 1. / kScaleX);
2104 cpl_table_set_column_unit(ts,
"width_mm",
"mm");
2105 cpl_table_duplicate_column(ts,
"widtherr_mm",
2106 ts, MUSE_GEOTABLE_WIDTH
"err");
2107 cpl_table_multiply_scalar(ts,
"widtherr_mm", 1. / kScaleX);
2108 cpl_table_set_column_unit(ts,
"widtherr_mm",
"mm");
2109 double *wmm = cpl_table_get_data_double(ts,
"width_mm"),
2110 *werrmm = cpl_table_get_data_double(ts,
"widtherr_mm");
2112 double cgap1 = (3. * kMuseCUmpmDX / fmaskrot
2113 - (xrel[i3] + wmm[i3] / 2. + wmm[i2] / 2. - xrel[i2]))
2115 cgerr = sqrt(pow(xrerr[i3], 2) + pow(xrerr[i2], 2) +
2116 pow(werrmm[i3] / 2., 2) + pow(werrmm[i2] / 2., 2))
2118 cgap2 = kMuseCUmpmDX * (1. - pdxl[i3] - pdxr[i2])
2120 cgap = stdgap ? cgap1 : cgap2;
2122 cpl_msg_debug(__func__,
"cgap: %f, %f +/- %f", cgap1, cgap2, cgerr);
2126 if (cgap < 0 || cgap > 0.5) {
2127 cpl_msg_warning(__func__,
"For IFU %2hhu / row %2d in the slicer stacks "
2128 "(slice sky numbers %02d, %02d, %02d, %02d), the central "
2129 "gap is unlikely (%f), reset to %.2f pix", ifu,
2130 nslice - nsoff, nslice - nsoff, nslice, nslice + nsoff,
2131 nslice + 2*nsoff, cgap, kMuseGeoMiddleGap);
2132 cgerr += sqrt(fabs(cgap));
2133 cgap = kMuseGeoMiddleGap;
2137 cpl_table_set_double(ts, MUSE_GEOTABLE_X, i3, -(cgap / 2. + width[i3] / 2.));
2138 cpl_table_set_double(ts, MUSE_GEOTABLE_X, i2, cgap / 2. + width[i2] / 2.);
2139 cpl_table_set_double(ts, MUSE_GEOTABLE_X
"err", i3,
2140 sqrt(cgerr*cgerr + werr[i3]*werr[i3]) / 2.);
2141 cpl_table_set_double(ts, MUSE_GEOTABLE_X
"err", i2,
2142 sqrt(cgerr*cgerr + werr[i2]*werr[i2]) / 2.);
2144 double lgap = NAN, lgerr = NAN;
2148 lgerr = sqrt(pow(xrerr[i4], 2) + pow(xrerr[i3], 2) +
2149 pow(werrmm[i4] / 2., 2) + pow(werrmm[i3] / 2., 2))
2151 double lgap1 = (3. * kMuseCUmpmDX / fmaskrot
2152 - (xrel[i4] + wmm[i4] / 2. + wmm[i3] / 2. - xrel[i3]))
2154 lgap2 = kMuseCUmpmDX * (1. - pdxl[i4] - pdxr[i3])
2156 lgap = stdgap ? lgap1 : lgap2;
2158 cpl_msg_debug(__func__,
"lgap: %f, %f +/- %f", lgap1, lgap2, lgerr);
2160 if (lgap < 0 || lgap > 0.5) {
2161 cpl_msg_warning(__func__,
"For IFU %2hhu / row %2d in the slicer stacks"
2162 " (slice sky numbers %02d, %02d, %02d, %02d), the left "
2163 "gap is unlikely (%f), reset to %.2f pix", ifu,
2164 nslice - nsoff, nslice - nsoff, nslice, nslice + nsoff,
2165 nslice + 2*nsoff, lgap, kMuseGeoOuterGap);
2166 lgerr += sqrt(fabs(lgap));
2167 lgap = kMuseGeoOuterGap;
2169 cpl_table_set_double(ts, MUSE_GEOTABLE_X, i4,
2170 -(cgap / 2. + width[i3] + lgap + width[i4] / 2.));
2171 cpl_table_set_double(ts, MUSE_GEOTABLE_X
"err", i4,
2172 sqrt(cgerr*cgerr / 4. + werr[i3]*werr[i3]
2173 + lgerr*lgerr + werr[i4]*werr[i4] / 4.));
2175 cpl_error_set_message(__func__, CPL_ERROR_DATA_NOT_FOUND,
"For IFU %2hhu /"
2176 " row %2d in the slicer stacks (slice sky numbers"
2177 " %02d, %02d, %02d, %02d), the leftmost stack is "
2178 "missing", ifu, nslice - nsoff,
2179 nslice - nsoff, nslice, nslice + nsoff, nslice + 2*nsoff);
2182 double rgap = NAN, rgerr = NAN;
2185 rgerr = sqrt(pow(xrerr[i2], 2) + pow(xrerr[i1], 2) +
2186 pow(werrmm[i2] / 2., 2) + pow(werrmm[i1] / 2., 2))
2188 double rgap1 = (3. * kMuseCUmpmDX / fmaskrot
2189 - (xrel[i2] + wmm[i2] / 2. + wmm[i1] / 2. - xrel[i1]))
2191 rgap2 = kMuseCUmpmDX * (1. - pdxl[i2] - pdxr[i1])
2193 rgap = stdgap ? rgap1 : rgap2;
2195 cpl_msg_debug(__func__,
"rgap: %f, %f +/- %f", rgap1, rgap2, rgerr);
2197 if (rgap < 0 || rgap > 0.5) {
2198 cpl_msg_warning(__func__,
"For IFU %2hhu / row %2d in the slicer stacks"
2199 " (slice sky numbers %02d, %02d, %02d, %02d), the right"
2200 " gap is unlikely (%f), reset to %.2f pix", ifu,
2201 nslice - nsoff, nslice - nsoff, nslice, nslice + nsoff,
2202 nslice + 2*nsoff, rgap, kMuseGeoOuterGap);
2203 rgerr += sqrt(fabs(rgap));
2204 rgap = kMuseGeoOuterGap;
2206 cpl_table_set_double(ts, MUSE_GEOTABLE_X, i1,
2207 cgap / 2. + width[i2] + rgap + width[i1] / 2.);
2208 cpl_table_set_double(ts, MUSE_GEOTABLE_X
"err", i1,
2209 sqrt(cgerr*cgerr / 4. + werr[i2]*werr[i2]
2210 + rgerr*rgerr + werr[i1]*werr[i1] / 4.));
2212 cpl_error_set_message(__func__, CPL_ERROR_DATA_NOT_FOUND,
"For IFU %2hhu /"
2213 " row %2d in the slicer stacks (slice sky numbers"
2214 " %02d, %02d, %02d, %02d), the rightmost stack is"
2215 " missing", ifu, nslice - nsoff,
2216 nslice - nsoff, nslice, nslice + nsoff, nslice + 2*nsoff);
2218 cpl_msg_debug(__func__,
"IFU %2hhu row %2d gaps (slice sky numbers %02d, "
2219 "%02d, %02d, %02d): central %.3f +/- %.3f pix, left %.3f +/- "
2220 "%.3f pix, right %.3f +/- %.3f pix", ifu, nslice - nsoff,
2221 nslice - nsoff, nslice, nslice + nsoff, nslice + 2*nsoff,
2222 cgap, cgerr, lgap, lgerr, rgap, rgerr);
2225 cpl_table_erase_column(ts,
"width_mm");
2226 cpl_table_erase_column(ts,
"widtherr_mm");
2230 cpl_table_erase_selected(gt->
table);
2231 cpl_table_insert(gt->
table, ts, cpl_table_get_nrow(gt->
table));
2232 cpl_table_delete(ts);
2236 cpl_propertylist *order = cpl_propertylist_new();
2237 cpl_propertylist_append_bool(order,
"stack", CPL_TRUE);
2238 cpl_propertylist_append_bool(order,
"SliceSky", CPL_FALSE);
2239 cpl_table_sort(gt->
table, order);
2240 cpl_propertylist_delete(order);
2242 printf(
"intermediate result: only the y position is still missing:\n");
2243 cpl_table_dump(gt->
table, 0, 1000000, stdout);
2264 static unsigned char
2265 muse_geo_select_reference(
const muse_geo_table *aGeo,
unsigned short *aSlice)
2267 unsigned char ifu = 0;
2268 unsigned short slice = 0;
2270 cpl_table *geo = cpl_table_duplicate(aGeo->
table);
2271 cpl_table_unselect_all(geo);
2272 cpl_table_or_selected_int(geo, MUSE_GEOTABLE_FIELD, CPL_EQUAL_TO, 12);
2273 cpl_table_and_selected_int(geo, MUSE_GEOTABLE_SKY, CPL_EQUAL_TO, 24);
2274 int nsel = cpl_table_count_selected(geo);
2279 unsigned char testifu = 13,
2281 short testoffset = 1;
2282 while (ifu == 0 && slice == 0) {
2283 cpl_table_unselect_all(geo);
2284 cpl_table_or_selected_int(geo, MUSE_GEOTABLE_FIELD, CPL_EQUAL_TO, testifu);
2285 cpl_table_and_selected_int(geo, MUSE_GEOTABLE_SKY, CPL_EQUAL_TO, testslice);
2286 nsel = cpl_table_count_selected(geo);
2291 testifu += testoffset;
2292 if (testifu > kMuseNumIFUs) {
2297 cpl_table_delete(geo);
2359 cpl_ensure_code(aGeo && aGeo->
table && aSpots, CPL_ERROR_NULL_INPUT);
2360 cpl_size nrow = cpl_table_get_nrow(aGeo->
table);
2361 cpl_ensure_code(nrow >= 50, CPL_ERROR_ILLEGAL_INPUT);
2363 CPL_ERROR_INCOMPATIBLE_INPUT);
2365 CPL_ERROR_INCOMPATIBLE_INPUT);
2366 const unsigned char ifu1 = cpl_table_get_column_min(aGeo->
table, MUSE_GEOTABLE_FIELD),
2367 ifu2 = cpl_table_get_column_max(aGeo->
table, MUSE_GEOTABLE_FIELD);
2368 if (!ifu1 || !ifu2 || ifu1 == ifu2) {
2369 return cpl_error_set_message(__func__, CPL_ERROR_DATA_NOT_FOUND,
2370 "input geometry table contains data of IFUs "
2371 "%2hhu .. %2hhu", ifu1, ifu2);
2373 cpl_ensure_code(cpl_table_get_column_stdev(aSpots,
"ScaleFOV") < 1e-10,
2374 CPL_ERROR_ILLEGAL_INPUT);
2375 cpl_array *hoffsets = NULL;
2376 if (getenv(
"MUSE_GEOMETRY_HORI_OFFSETS")) {
2378 getenv(
"MUSE_GEOMETRY_HORI_OFFSETS"),
",");
2379 cpl_msg_warning(__func__,
"Overriding horizontal offsets, found %"
2380 CPL_SIZE_FORMAT
" values!", cpl_array_get_size(hoffsets));
2382 const double kScaleX = kMuseTypicalCubeSizeX * aGeo->
scale / 60.;
2385 cpl_table_new_column(aSpots, MUSE_GEOTABLE_SKY, CPL_TYPE_INT);
2386 cpl_table_new_column(aSpots,
"stack", CPL_TYPE_INT);
2387 cpl_size ispot, nspots = cpl_table_get_nrow(aSpots);
2388 for (ispot = 0; ispot < nspots; ispot++) {
2389 int nslice = cpl_table_get_int(aSpots,
"SliceCCD", ispot, NULL);
2390 cpl_table_set(aSpots, MUSE_GEOTABLE_SKY, ispot, kMuseGeoSliceSky[nslice - 1]);
2391 unsigned char stack = nslice <= 12 ? 4 : (nslice <= 24 ? 3 : (nslice <= 36 ? 2 : 1));
2392 cpl_table_set_int(aSpots,
"stack", ispot, stack);
2397 const unsigned short nsoff = kMuseSlicesPerCCD / 4;
2398 cpl_table_unselect_all(aSpots);
2399 cpl_table_or_selected_int(aSpots, MUSE_GEOTABLE_SKY, CPL_EQUAL_TO, 24 - nsoff + 1);
2400 cpl_table_or_selected_int(aSpots, MUSE_GEOTABLE_SKY, CPL_EQUAL_TO, 24);
2401 cpl_table_or_selected_int(aSpots, MUSE_GEOTABLE_SKY, CPL_EQUAL_TO, 24 + 1);
2402 cpl_table_or_selected_int(aSpots, MUSE_GEOTABLE_SKY, CPL_EQUAL_TO, 24 + nsoff);
2404 cpl_msg_debug(__func__,
"All top/bottom spots selected: %"
2405 CPL_SIZE_FORMAT, cpl_table_count_selected(aSpots));
2408 cpl_table_and_selected_double(aSpots,
"flux", CPL_NOT_LESS_THAN, 500.);
2410 cpl_msg_debug(__func__,
"All bright top/bottom spots selected: %"
2411 CPL_SIZE_FORMAT, cpl_table_count_selected(aSpots));
2414 cpl_table_and_selected_int(aSpots,
"SpotNo", CPL_EQUAL_TO, 2);
2416 cpl_msg_debug(__func__,
"All bright and central top/bottom spots selected: %"
2417 CPL_SIZE_FORMAT, cpl_table_count_selected(aSpots));
2419 cpl_table *tbspots = cpl_table_extract_selected(aSpots);
2420 cpl_msg_debug(__func__,
"All spots: %"CPL_SIZE_FORMAT
", top/bottom spots to "
2421 "work with: %"CPL_SIZE_FORMAT, nspots, cpl_table_get_nrow(tbspots));
2422 nspots = cpl_table_get_nrow(tbspots);
2425 int *ifus = cpl_table_get_data_int(aGeo->
table, MUSE_GEOTABLE_FIELD),
2426 *slices = cpl_table_get_data_int(aGeo->
table, MUSE_GEOTABLE_SKY);
2427 double *xpos = cpl_table_get_data_double(aGeo->
table, MUSE_GEOTABLE_X),
2428 *xerr = cpl_table_get_data_double(aGeo->
table, MUSE_GEOTABLE_X
"err"),
2429 *xrel = cpl_table_get_data_double(aGeo->
table,
"xrel");
2434 for (nifu = ifu1; nifu < ifu2; nifu++) {
2436 cpl_table_unselect_all(tbspots);
2437 cpl_table_or_selected_int(tbspots, MUSE_GEOTABLE_SKY, CPL_EQUAL_TO, 24);
2438 cpl_table_or_selected_int(tbspots, MUSE_GEOTABLE_SKY, CPL_EQUAL_TO, 24 + nsoff);
2439 cpl_table_and_selected_int(tbspots, MUSE_GEOTABLE_FIELD, CPL_EQUAL_TO, nifu);
2440 cpl_table *xspots = cpl_table_extract_selected(tbspots);
2442 cpl_table_unselect_all(tbspots);
2443 cpl_table_or_selected_int(tbspots, MUSE_GEOTABLE_SKY, CPL_EQUAL_TO, 24 - nsoff + 1);
2444 cpl_table_or_selected_int(tbspots, MUSE_GEOTABLE_SKY, CPL_EQUAL_TO, 24 + 1);
2445 cpl_table_and_selected_int(tbspots, MUSE_GEOTABLE_FIELD, CPL_EQUAL_TO, nifu + 1);
2446 cpl_table *tmp = cpl_table_extract_selected(tbspots);
2447 cpl_table_insert(xspots, tmp, cpl_table_get_nrow(xspots));
2448 cpl_table_delete(tmp);
2449 int nxspots = cpl_table_get_nrow(xspots);
2452 cpl_propertylist *order = cpl_propertylist_new();
2453 cpl_propertylist_append_bool(order,
"lambda", CPL_FALSE);
2454 cpl_propertylist_append_bool(order,
"VPOS", CPL_FALSE);
2455 cpl_propertylist_append_bool(order,
"SliceSky", CPL_FALSE);
2456 cpl_table_sort(xspots, order);
2457 cpl_propertylist_delete(order);
2459 printf(
"ifu %2hhu:\n", nifu);
2460 cpl_table_dump(xspots, 0, nxspots, stdout);
2465 cpl_array *apos2 = cpl_array_new(nxspots, CPL_TYPE_DOUBLE),
2466 *apos3 = cpl_array_new(nxspots, CPL_TYPE_DOUBLE);
2467 int idx2 = 0, idx3 = 0;
2470 cpl_vector *vtmp = cpl_vector_wrap(nxspots,
2471 cpl_table_get_data_double(xspots,
"lambda")),
2473 cpl_vector_unwrap(vtmp);
2474 vtmp = cpl_vector_wrap(nxspots, cpl_table_get_data_double(xspots,
"VPOS"));
2476 cpl_vector_unwrap(vtmp);
2477 int ilambda, nlambda = cpl_vector_get_size(lambdas);
2478 for (ilambda = 0; ilambda < nlambda; ilambda++) {
2479 double lambda = cpl_vector_get(lambdas, ilambda);
2481 int ipos, npos = cpl_vector_get_size(positions);
2482 for (ipos = 0; ipos < npos; ipos++) {
2483 double vpos = cpl_vector_get(positions, ipos);
2486 for (nstack = 3; nstack >= 2; nstack--) {
2487 cpl_table_select_all(xspots);
2488 cpl_table_and_selected_double(xspots,
"lambda", CPL_EQUAL_TO, lambda);
2489 cpl_table_and_selected_double(xspots,
"VPOS", CPL_EQUAL_TO, vpos);
2490 cpl_table_and_selected_int(xspots,
"stack", CPL_EQUAL_TO, nstack);
2491 int nsel = cpl_table_count_selected(xspots);
2494 printf(
"ifu %2hhu, lambda = %f, VPOS = %f, stack = %d: %d selected rows!\n",
2495 nifu, lambda, vpos, nstack, nsel);
2500 cpl_table *common = cpl_table_extract_selected(xspots);
2501 order = cpl_propertylist_new();
2502 cpl_propertylist_append_bool(order,
"SubField", CPL_FALSE);
2503 cpl_propertylist_append_bool(order,
"SliceSky", CPL_FALSE);
2504 cpl_table_sort(common, order);
2505 cpl_propertylist_delete(order);
2506 int nselthis = cpl_table_and_selected_int(common,
"SubField",
2507 CPL_EQUAL_TO, nifu);
2508 cpl_table_select_all(common);
2509 int nselnext = cpl_table_and_selected_int(common,
"SubField",
2510 CPL_EQUAL_TO, nifu + 1);
2511 if (nselthis != 1 || nselnext != 1) {
2513 printf(
"\nifu %2hhu, lambda = %f, VPOS = %f, stack = %d: "
2514 "%d rows of IFU %2hhu, %d rows of IFU %2hhu!\n",
2515 nifu, lambda, vpos, nstack, nselthis, nifu, nselnext, nifu + 1);
2518 cpl_table_delete(common);
2524 double xdiff1 = cpl_table_get(common,
"dxcen", 0, NULL),
2525 xdiff2 = cpl_table_get(common,
"dxcen", 1, NULL),
2526 twidth1 = cpl_table_get(common,
"twidth", 0, NULL),
2527 twidth2 = cpl_table_get(common,
"twidth", 1, NULL);
2528 cpl_table_unselect_all(aGeo->
table);
2529 cpl_table_or_selected_int(aGeo->
table, MUSE_GEOTABLE_FIELD, CPL_EQUAL_TO,
2530 cpl_table_get_int(common,
"SubField", 0, NULL));
2531 cpl_table_or_selected_int(aGeo->
table, MUSE_GEOTABLE_SKY, CPL_EQUAL_TO,
2532 cpl_table_get_int(common,
"SliceSky", 0, NULL));
2533 cpl_array *sel = cpl_table_where_selected(aGeo->
table);
2534 double width1 = cpl_table_get_double(aGeo->
table, MUSE_GEOTABLE_WIDTH,
2535 cpl_array_get(sel, 0, NULL), NULL);
2536 cpl_array_delete(sel);
2537 cpl_table_unselect_all(aGeo->
table);
2538 cpl_table_or_selected_int(aGeo->
table, MUSE_GEOTABLE_FIELD, CPL_EQUAL_TO,
2539 cpl_table_get_int(common,
"SubField", 1, NULL));
2540 cpl_table_or_selected_int(aGeo->
table, MUSE_GEOTABLE_SKY, CPL_EQUAL_TO,
2541 cpl_table_get_int(common,
"SliceSky", 1, NULL));
2542 sel = cpl_table_where_selected(aGeo->
table);
2543 double width2 = cpl_table_get_double(aGeo->
table, MUSE_GEOTABLE_WIDTH,
2544 cpl_array_get(sel, 0, NULL), NULL);
2545 cpl_array_delete(sel);
2547 printf(
"\nifu %2hhu, lambda = %f, VPOS = %f, stack + %d, twidths: %f / %f, geowidths: %f / %f:\n",
2548 nifu, lambda, vpos, nstack, twidth1, twidth2, width1, width2);
2549 cpl_table_dump(common, 0, 100000, stdout);
2550 printf(
"==> xdiff = %f pix, %f pix (corrected)\n", xdiff1 - xdiff2,
2551 xdiff1 * width1 / twidth1 - xdiff2 * width2 / twidth2);
2554 double xdiff = xdiff1 * width1 / twidth1 - xdiff2 * width2 / twidth2;
2556 cpl_array_set(apos3, idx3++, xdiff);
2559 cpl_array_set(apos2, idx2++, xdiff);
2561 cpl_table_delete(common);
2567 #define HIST_BIN_WIDTH 0.1
2573 cpl_bivector_delete(hist2);
2574 cpl_bivector_delete(hist3);
2575 cpl_array *apos = cpl_array_new(0, cpl_array_get_type(apos2));
2576 cpl_array_insert(apos, apos2, 0);
2577 cpl_array_insert(apos, apos3, cpl_array_get_size(apos));
2579 char *fn = cpl_sprintf(
"apos2_%02hhuto%02hhu.pos", nifu, nifu+1);
2580 FILE *fp = fopen(fn,
"w");
2581 fprintf(fp,
"# apos2 %2hhu to %2hhu:\n#", nifu, nifu+1);
2582 cpl_array_dump(apos2, 0, 1000000, fp);
2585 fn = cpl_sprintf(
"apos3_%02hhuto%02hhu.pos", nifu, nifu+1);
2586 fp = fopen(fn,
"w");
2587 fprintf(fp,
"# apos3 %2hhu to %2hhu:\n#", nifu, nifu+1);
2588 cpl_array_dump(apos3, 0, 1000000, fp);
2591 fn = cpl_sprintf(
"apos_%02hhuto%02hhu.pos", nifu, nifu+1);
2592 fp = fopen(fn,
"w");
2593 fprintf(fp,
"# apos %2hhu to %2hhu:\n#", nifu, nifu+1);
2594 cpl_array_dump(apos, 0, 1000000, fp);
2600 double mean2 = cpl_array_get_mean(apos2),
2601 stdev2 = cpl_array_get_stdev(apos2),
2602 var2 = stdev2 * stdev2,
2603 mean3 = cpl_array_get_mean(apos3),
2604 stdev3 = cpl_array_get_stdev(apos3),
2605 var3 = stdev3 * stdev3,
2606 mean = (mean2 + mean3) / 2.,
2607 wmean = (mean2 / var2 + mean3 / var3) / (1. / var2 + 1. / var3),
2608 wstdev = sqrt(1. / (1. / var2 + 1. / var3));
2609 cpl_array_delete(apos2);
2610 cpl_array_delete(apos3);
2611 cpl_msg_debug(__func__,
"IFU %2hhu to IFU %2hhu: %6.3f +/- %5.3f pix "
2612 "[stack 3: %6.3f +/- %5.3f, stack2: %6.3f +/- %5.3f ==> %6.3f"
2613 " or %6.3f +/- %5.3f (%6.3f)]", nifu, nifu + 1, wmean, wstdev,
2614 mean3, stdev3, mean2, stdev2, mean,
2615 cpl_array_get_mean(apos), cpl_array_get_stdev(apos),
2616 cpl_array_get_median(apos));
2617 cpl_array_delete(apos);
2618 cpl_vector_delete(lambdas);
2619 cpl_vector_delete(positions);
2620 cpl_table_delete(xspots);
2623 for (irow = 0; irow < nrow; irow++) {
2624 if (ifus[irow] >= nifu + 1) {
2625 xpos[irow] -= wmean;
2626 xerr[irow] = sqrt(xerr[irow]*xerr[irow] + wstdev*wstdev);
2627 xrel[irow] += wmean / kScaleX;
2631 cpl_table_delete(tbspots);
2634 for (nifu = ifu1; nifu < ifu2; nifu++) {
2637 if (cpl_array_get_size(hoffsets) >= nifu) {
2638 const char *sdiff = cpl_array_get_string(hoffsets, nifu - 1);
2640 xdiff = atof(sdiff);
2643 cpl_msg_debug(__func__,
"Subtracting extra %7.4f pix from IFU %hhu onwards",
2645 for (irow = 0; irow < nrow; irow++) {
2646 if (ifus[irow] >= nifu + 1) {
2647 xpos[irow] -= xdiff;
2654 printf(
"%s after correcting %hhu:\n", __func__, nifu + 1);
2655 cpl_table_dump(aGeo->
table, 0, 1000000, stdout);
2659 cpl_array_delete(hoffsets);
2664 unsigned short sliceref;
2665 const unsigned char ifuref = muse_geo_select_reference(aGeo, &sliceref);
2666 for (irow = 0; irow < nrow; irow++) {
2667 if (ifus[irow] == ifuref &&
2668 (slices[irow] == sliceref || slices[irow] == sliceref + 12)) {
2673 cpl_msg_debug(__func__,
"Reference point (ifu %hhu, slicesky %hu/%hu) "
2674 "currently centered at %f pix, correcting this offset", ifuref,
2675 sliceref, sliceref + 12, xref);
2676 cpl_table_subtract_scalar(aGeo->
table, MUSE_GEOTABLE_X, xref);
2678 return CPL_ERROR_NONE;
2735 cpl_ensure(aGeo && aGeo->
table, CPL_ERROR_NULL_INPUT, NULL);
2736 int nrow = cpl_table_get_nrow(aGeo->
table);
2737 cpl_ensure(nrow >= 10, CPL_ERROR_ILLEGAL_INPUT, NULL);
2739 CPL_ERROR_INCOMPATIBLE_INPUT, NULL);
2740 int spotmin = cpl_table_get_column_min(aGeo->
table,
"spot"),
2741 spotmax = cpl_table_get_column_max(aGeo->
table,
"spot");
2742 cpl_ensure(spotmin == spotmax, CPL_ERROR_ILLEGAL_INPUT, NULL);
2743 const double kScaleY = 60. / aGeo->
scale / kMuseTypicalCubeSizeY;
2747 cpl_table_erase_column(gt->
table,
"dxerr");
2748 cpl_table_erase_column(gt->
table,
"dxl");
2749 cpl_table_erase_column(gt->
table,
"dxr");
2750 cpl_table_erase_column(gt->
table,
"xc");
2751 cpl_table_erase_column(gt->
table,
"yc");
2752 cpl_table_erase_column(gt->
table,
"dx");
2753 cpl_table_erase_column(gt->
table,
"flux");
2754 cpl_table_erase_column(gt->
table,
"lambda");
2758 cpl_table_fill_column_window_double(gt->
table, MUSE_GEOTABLE_Y, 0, nrow, 0.);
2759 cpl_table_add_columns(gt->
table, MUSE_GEOTABLE_Y,
"vpos");
2760 cpl_table_set_column_unit(gt->
table, MUSE_GEOTABLE_Y,
"mm");
2761 cpl_table_fill_column_window_double(gt->
table, MUSE_GEOTABLE_Y
"err", 0, nrow, 0.);
2762 cpl_table_add_columns(gt->
table, MUSE_GEOTABLE_Y
"err",
"vposerr");
2763 cpl_table_set_column_unit(gt->
table, MUSE_GEOTABLE_Y
"err",
"mm");
2765 printf(
"\nfull geometry table, with absolute \"y\" [mm]:\n");
2766 cpl_table_dump(gt->
table, 0, nrow, stdout);
2770 double maskangle = 0., fmaskrot = 1.;
2771 if (getenv(
"MUSE_GEOMETRY_MASK_ROTATION")) {
2772 maskangle = atof(getenv(
"MUSE_GEOMETRY_MASK_ROTATION"));
2773 fmaskrot = cos(maskangle * CPL_MATH_RAD_DEG);
2774 cpl_msg_warning(__func__,
"Adapting to global mask rotation of %.4f deg "
2775 "(cos = %.4e)", maskangle, fmaskrot);
2777 double pinholedy = kMuseCUmpmDY;
2778 if (getenv(
"MUSE_GEOMETRY_PINHOLE_DY")) {
2779 pinholedy = atof(getenv(
"MUSE_GEOMETRY_PINHOLE_DY"));
2780 cpl_msg_warning(__func__,
"Using pinhole y distance of %f mm (instead of "
2781 "%f mm)", pinholedy, kMuseCUmpmDY);
2783 cpl_boolean printgoing = getenv(
"MUSE_DEBUG_GEO_VERTICAL")
2784 && atoi(getenv(
"MUSE_DEBUG_GEO_VERTICAL")) > 0;
2786 unsigned short middleSlice;
2787 const unsigned char middleIFU = muse_geo_select_reference(aGeo, &middleSlice);
2788 cpl_msg_info(__func__,
"Using IFU %hhu / SliceSky %hu as reference",
2789 middleIFU, middleSlice);
2790 double ycentral = NAN,
2791 ymax = -DBL_MAX, ymin = DBL_MAX;
2793 for (irow = 0; irow < nrow; irow++) {
2794 unsigned char ifu = cpl_table_get_int(gt->
table, MUSE_GEOTABLE_FIELD, irow, NULL);
2795 unsigned short slice = cpl_table_get_int(gt->
table, MUSE_GEOTABLE_SKY, irow, NULL);
2796 double y = cpl_table_get_double(gt->
table, MUSE_GEOTABLE_Y, irow, NULL);
2797 if (ifu == middleIFU && slice == middleSlice) {
2808 if (!isfinite(ycentral)) {
2809 ycentral = (ymin + ymax) / 2.;
2810 cpl_msg_info(__func__,
"Averaged the y range to ycentral = %f pix", ycentral);
2812 cpl_msg_info(__func__,
"Found IFU %hhu, slice %hu, using its y value as "
2813 "ycentral = %f pix", middleIFU, middleSlice, ycentral);
2815 cpl_table_subtract_scalar(gt->
table, MUSE_GEOTABLE_Y, ycentral);
2816 const unsigned short nsoff = kMuseSlicesPerCCD / 4,
2817 nstack[] = { 3, 2, 4, 1, 0 };
2819 for (i = 0; nstack[i] > 0; i++) {
2821 cpl_table_unselect_all(gt->
table);
2822 cpl_table_or_selected_int(gt->
table,
"stack", CPL_EQUAL_TO, nstack[i]);
2823 cpl_table *tstack = cpl_table_extract_selected(gt->
table);
2824 int ntsrow = cpl_table_get_nrow(tstack);
2826 cpl_propertylist *sorting = cpl_propertylist_new();
2827 cpl_propertylist_append_bool(sorting, MUSE_GEOTABLE_FIELD, CPL_FALSE);
2828 cpl_propertylist_append_bool(sorting, MUSE_GEOTABLE_SKY, CPL_FALSE);
2829 cpl_table_sort(tstack, sorting);
2830 cpl_propertylist_delete(sorting);
2832 const unsigned short refslice = middleSlice - (nstack[i] - 3) * nsoff;
2833 cpl_table_select_all(tstack);
2834 cpl_table_and_selected_int(tstack, MUSE_GEOTABLE_FIELD, CPL_EQUAL_TO, middleIFU);
2835 cpl_table_and_selected_int(tstack, MUSE_GEOTABLE_SKY, CPL_EQUAL_TO, refslice);
2836 if (cpl_table_count_selected(tstack) <= 0) {
2837 char *msg = cpl_sprintf(
"reference slice %hu of reference IFU %hhu not "
2838 "found in slicer stack %hu!", refslice,
2839 middleIFU, nstack[i]);
2840 cpl_msg_error(__func__,
"%s", msg);
2841 cpl_error_set_message(__func__, CPL_ERROR_DATA_NOT_FOUND,
"%s", msg);
2843 cpl_table_delete(tstack);
2846 cpl_table_erase_selected(gt->
table);
2847 cpl_array *asel = cpl_table_where_selected(tstack);
2848 int iref = cpl_array_get(asel, 0, NULL);
2849 cpl_array_delete(asel);
2850 double yref = cpl_table_get_double(tstack, MUSE_GEOTABLE_Y, iref, NULL);
2851 cpl_msg_info(__func__,
"reference slice %hu of reference IFU %hhu found at row "
2852 "%d in slicer stack %hu!", refslice, middleIFU, iref, nstack[i]);
2856 if (fabs(yref) > pinholedy / 2.) {
2857 cpl_msg_info(__func__,
"%s vertical pinhole distance (%f) to "
2858 "recenter stack %hu", yref < 0. ?
"adding" :
"subtracting",
2859 pinholedy, nstack[i]);
2860 cpl_table_add_scalar(tstack, MUSE_GEOTABLE_Y, yref < 0. ? pinholedy : -pinholedy);
2861 yref = cpl_table_get_double(tstack, MUSE_GEOTABLE_Y, iref, NULL);
2865 cpl_table_new_column(tstack,
"dy", CPL_TYPE_DOUBLE);
2866 cpl_table_set_column_unit(tstack,
"dy",
"mm");
2867 cpl_table_set_column_format(tstack,
"dy",
"%9.5f");
2868 cpl_table_set_double(tstack,
"dy", iref, yref);
2870 cpl_table_duplicate_column(tstack,
"ycopy", tstack, MUSE_GEOTABLE_Y);
2871 cpl_table_set_double(tstack, MUSE_GEOTABLE_Y, iref, yref);
2872 double yprev = cpl_table_get_double(tstack,
"ycopy", iref, NULL),
2873 dyoffset = pinholedy * fmaskrot,
2876 for (irow = iref - 1, iprev = iref; irow >= 0; iprev = irow, irow--) {
2878 int difu = cpl_table_get_int(tstack, MUSE_GEOTABLE_FIELD, iprev, NULL)
2879 - cpl_table_get_int(tstack, MUSE_GEOTABLE_FIELD, irow, NULL),
2880 dslice = cpl_table_get_int(tstack, MUSE_GEOTABLE_SKY, iprev, NULL)
2881 - cpl_table_get_int(tstack, MUSE_GEOTABLE_SKY, irow, NULL);
2885 double y = cpl_table_get_double(tstack,
"ycopy", irow, NULL),
2887 dexpect = dslice * kScaleY,
2888 dratio = dy / dexpect,
2891 dycor = 2 * dyoffset;
2892 }
else if (dratio < -2.) {
2894 }
else if (dratio > 5.) {
2898 cpl_msg_debug(__func__,
"going back: %d %d, %f %f --> diff %9.6f "
2899 "expected %f ratio %9.6f --> dy = %f", difu, dslice,
2900 yprev, y, dy, dexpect, dratio, dy + dycor);
2903 if (abs(difu) > 1) {
2904 double gap = abs(difu) * kMuseGeoIFUVGap;
2906 cpl_msg_warning(__func__,
"%d missing IFUs, subtracted %f from dy",
2907 abs(difu) - 1, gap);
2909 cpl_table_set_double(tstack,
"dy", irow, dy);
2911 cpl_table_set_double(tstack, MUSE_GEOTABLE_Y, irow, ysum);
2916 yprev = cpl_table_get_double(tstack,
"ycopy", iref, NULL);
2918 for (irow = iref + 1, iprev = iref; irow < ntsrow; iprev = irow, irow++) {
2919 int difu = cpl_table_get_int(tstack, MUSE_GEOTABLE_FIELD, iprev, NULL)
2920 - cpl_table_get_int(tstack, MUSE_GEOTABLE_FIELD, irow, NULL),
2921 dslice = cpl_table_get_int(tstack, MUSE_GEOTABLE_SKY, iprev, NULL)
2922 - cpl_table_get_int(tstack, MUSE_GEOTABLE_SKY, irow, NULL);
2926 double y = cpl_table_get_double(tstack,
"ycopy", irow, NULL),
2928 dexpect = -dslice * kScaleY,
2929 dratio = dy / dexpect,
2932 dycor = -2 * dyoffset;
2933 }
else if (dratio > 2.) {
2935 }
else if (dratio < -5.) {
2939 cpl_msg_debug(__func__,
"going forward: %d %d, %f %f --> diff %9.6f "
2940 "expected %f ratio %9.6f --> dy = %f", difu, dslice,
2941 yprev, y, dy, dexpect, dratio, dy + dycor);
2944 if (abs(difu) > 1) {
2945 double gap = abs(difu) * kMuseGeoIFUVGap;
2947 cpl_msg_warning(__func__,
"%d missing IFUs, added %f to dy",
2948 abs(difu) - 1, gap);
2950 cpl_table_set_double(tstack,
"dy", irow, dy);
2952 cpl_table_set_double(tstack, MUSE_GEOTABLE_Y, irow, ysum);
2956 printf(
"\ngeometry table of slicer stack %hu, with \"dy\" [mm]:\n",
2958 cpl_table_dump(tstack, 0, nrow, stdout);
2962 cpl_table_erase_column(tstack,
"ycopy");
2963 cpl_table_erase_column(tstack,
"dy");
2965 cpl_table_insert(gt->
table, tstack, cpl_table_get_nrow(gt->
table));
2966 cpl_table_delete(tstack);
2976 cpl_msg_info(__func__,
"Correcting vertical position of top/bottom slices");
2978 for (ifu = 1; ifu <= kMuseNumIFUs; ifu++) {
2979 for (i = 0; nstack[i] > 0; i++) {
2980 cpl_table_unselect_all(gt->
table);
2981 cpl_table_or_selected_int(gt->
table,
"SubField", CPL_EQUAL_TO, ifu);
2982 cpl_table_and_selected_int(gt->
table,
"stack", CPL_EQUAL_TO, nstack[i]);
2983 if (!cpl_table_count_selected(gt->
table)) {
2986 cpl_table *tstack = cpl_table_extract_selected(gt->
table);
2987 int ntsrow = cpl_table_get_nrow(tstack);
2989 cpl_propertylist *sorting = cpl_propertylist_new();
2990 cpl_propertylist_append_bool(sorting, MUSE_GEOTABLE_SKY, CPL_FALSE);
2991 cpl_table_sort(tstack, sorting);
2992 cpl_propertylist_delete(sorting);
2995 unsigned short nslicetop = cpl_table_get_int(tstack, MUSE_GEOTABLE_SKY, 0, NULL),
2996 nslicebot = cpl_table_get_int(tstack, MUSE_GEOTABLE_SKY,
2998 cpl_boolean hastop = nslicetop % nsoff == 1,
2999 hasbot = nslicebot % nsoff == 0,
3000 haschanged = CPL_FALSE;
3002 printf(
"table of IFU %hhu / SliceSky %hu: %hu to %hu (%s, %s)\n",
3003 ifu, nstack[i], nslicetop, nslicebot,
3004 hastop ?
"has top" :
"does NOT have top",
3005 hasbot ?
"has bottom" :
"does NOT have bottom");
3006 cpl_table_dump(tstack, 0, 1000, stdout);
3010 cpl_vector *vvpos = cpl_vector_new(ntsrow - 3),
3011 *vverr = cpl_vector_new(ntsrow - 3);
3012 for (irow = 1; irow < ntsrow - 2; irow++) {
3013 double dy = fabs(cpl_table_get_double(tstack, MUSE_GEOTABLE_Y, irow + 1, NULL)
3014 - cpl_table_get_double(tstack, MUSE_GEOTABLE_Y, irow, NULL)),
3015 dye1 = cpl_table_get_double(tstack, MUSE_GEOTABLE_Y
"err", irow, NULL),
3016 dye2 = cpl_table_get_double(tstack, MUSE_GEOTABLE_Y
"err", irow + 1, NULL),
3017 dyerr = sqrt(dye1*dye1 + dye2*dye2);
3018 cpl_vector_set(vvpos, irow - 1, dy);
3019 cpl_vector_set(vverr, irow - 1, dyerr);
3023 double mean = cpl_vector_get_mean(vvpos),
3024 median = cpl_vector_get_median_const(vvpos),
3025 stdev = cpl_vector_get_stdev(vvpos),
3026 stdev2 = cpl_vector_get_mean(vverr);
3027 cpl_vector_delete(vvpos);
3028 cpl_vector_delete(vverr);
3029 cpl_msg_debug(__func__,
"dy of IFU %hhu / SliceSky %hu: %f +/- %f (%f) [+/- %f]",
3030 ifu, nstack[i], mean, stdev, median, stdev2);
3033 double ytop = cpl_table_get_double(tstack, MUSE_GEOTABLE_Y, 0, NULL),
3034 dyt = fabs(ytop - cpl_table_get_double(tstack, MUSE_GEOTABLE_Y, 1, NULL)),
3035 dyt1 = cpl_table_get_double(tstack, MUSE_GEOTABLE_Y
"err", 0, NULL),
3036 dyt2 = cpl_table_get_double(tstack, MUSE_GEOTABLE_Y
"err", 1, NULL),
3037 dyterr = sqrt(dyt1*dyt1 + dyt2*dyt2);
3038 cpl_msg_debug(__func__,
"dy of IFU %hhu / SliceSky %hu (top): %f +/- %f",
3039 ifu, nstack[i], dyt, dyterr);
3042 if (mean - dyt > sqrt(dyterr*dyterr + stdev2*stdev2)) {
3045 cpl_table_set_double(tstack, MUSE_GEOTABLE_Y, 0, ytop);
3046 haschanged = CPL_TRUE;
3048 printf(
"new top entry (+%f):\n", mean - dyt);
3049 cpl_table_dump(tstack, 0, 1, stdout);
3056 double ybot = cpl_table_get_double(tstack, MUSE_GEOTABLE_Y, ntsrow - 1, NULL),
3057 dyb = fabs(ybot - cpl_table_get_double(tstack, MUSE_GEOTABLE_Y, ntsrow - 2, NULL)),
3058 dyb1 = cpl_table_get_double(tstack, MUSE_GEOTABLE_Y
"err", ntsrow - 2, NULL),
3059 dyb2 = cpl_table_get_double(tstack, MUSE_GEOTABLE_Y
"err", ntsrow - 1, NULL),
3060 dyberr = sqrt(dyb1*dyb1 + dyb2*dyb2);
3061 cpl_msg_debug(__func__,
"dy of IFU %hhu / SliceSky %hu (bottom): %f +/- %f",
3062 ifu, nstack[i], dyb, dyberr);
3063 if (mean - dyb > sqrt(dyberr*dyberr + stdev2*stdev2)) {
3066 cpl_table_set_double(tstack, MUSE_GEOTABLE_Y, ntsrow - 1, ybot);
3067 haschanged = CPL_TRUE;
3069 printf(
"new bottom entry (-%f):\n", mean - dyb);
3070 cpl_table_dump(tstack, ntsrow - 1, 1, stdout);
3079 cpl_table_erase_selected(gt->
table);
3080 cpl_table_insert(gt->
table, tstack, cpl_table_get_nrow(gt->
table));
3082 cpl_table_delete(tstack);
3087 cpl_table_divide_scalar(gt->
table, MUSE_GEOTABLE_Y, kMuseSpaxelSizeY_WFM / aGeo->
scale);
3088 cpl_table_set_column_unit(gt->
table, MUSE_GEOTABLE_Y,
"pix");
3089 cpl_table_divide_scalar(gt->
table, MUSE_GEOTABLE_Y
"err", kMuseSpaxelSizeY_WFM / aGeo->
scale);
3090 cpl_table_set_column_unit(gt->
table, MUSE_GEOTABLE_Y
"err",
"pix");
3093 cpl_table_erase_column(gt->
table,
"spot");
3094 cpl_table_erase_column(gt->
table,
"xrel");
3095 cpl_table_erase_column(gt->
table,
"xrelerr");
3096 cpl_table_erase_column(gt->
table,
"vpos");
3097 cpl_table_erase_column(gt->
table,
"vposerr");
3098 cpl_table_erase_column(gt->
table,
"stack");
3139 cpl_ensure_code(aGeo && aGeo->
table, CPL_ERROR_NULL_INPUT);
3140 cpl_ensure_code(cpl_table_has_column(aGeo->
table, MUSE_GEOTABLE_FIELD) &&
3141 cpl_table_has_column(aGeo->
table, MUSE_GEOTABLE_CCD) &&
3142 cpl_table_has_column(aGeo->
table, MUSE_GEOTABLE_SKY) &&
3143 cpl_table_has_column(aGeo->
table, MUSE_GEOTABLE_X) &&
3144 cpl_table_has_column(aGeo->
table, MUSE_GEOTABLE_Y) &&
3145 cpl_table_has_column(aGeo->
table, MUSE_GEOTABLE_ANGLE) &&
3146 cpl_table_has_column(aGeo->
table, MUSE_GEOTABLE_WIDTH),
3147 CPL_ERROR_ILLEGAL_INPUT);
3150 if (getenv(
"MUSE_GEOMETRY_PINHOLE_DY")) {
3152 FILE *fn1 = fopen(
"gt1.ascii",
"w");
3153 fprintf(fn1,
"geometry table before scaling\n");
3154 cpl_table_dump(aGeo->
table, 0, 10000, fn1);
3157 double pinholedy = atof(getenv(
"MUSE_GEOMETRY_PINHOLE_DY")),
3158 fdy = kMuseCUmpmDY / pinholedy;
3159 cpl_msg_warning(__func__,
"Pinhole y distance of %f mm was used instead of "
3160 "%f mm; scaling coordinates by %f!", pinholedy, kMuseCUmpmDY,
3162 int irow, nrow = cpl_table_get_nrow(aGeo->
table);
3163 for (irow = 0; irow < nrow; irow++) {
3166 double y = cpl_table_get_double(aGeo->
table, MUSE_GEOTABLE_Y, irow, &err);
3168 cpl_table_set_double(aGeo->
table, MUSE_GEOTABLE_Y, irow, y * fdy);
3173 double angleold = cpl_table_get_double(aGeo->
table, MUSE_GEOTABLE_ANGLE, irow, &err);
3175 double anglenew = atan(fdy * tan(angleold * CPL_MATH_RAD_DEG))
3177 cpl_table_set_double(aGeo->
table, MUSE_GEOTABLE_ANGLE, irow, anglenew);
3181 FILE *fn2 = fopen(
"gt2.ascii",
"w");
3182 fprintf(fn2,
"geometry table after scaling by %f\n", fdy);
3183 cpl_table_dump(aGeo->
table, 0, 10000, fn2);
3190 for (nifu = 1; nifu <= kMuseNumIFUs; nifu++) {
3191 cpl_table_select_all(aGeo->
table);
3192 cpl_table_and_selected_int(aGeo->
table, MUSE_GEOTABLE_FIELD, CPL_EQUAL_TO, nifu);
3193 if (cpl_table_count_selected(aGeo->
table) < 1) {
3197 unsigned short nslice;
3198 for (nslice = 1; nslice <= kMuseSlicesPerCCD; nslice++) {
3199 cpl_table_select_all(aGeo->
table);
3200 cpl_table_and_selected_int(aGeo->
table, MUSE_GEOTABLE_FIELD, CPL_EQUAL_TO, nifu);
3201 cpl_table_and_selected_int(aGeo->
table, MUSE_GEOTABLE_CCD, CPL_EQUAL_TO, nslice);
3202 if (cpl_table_count_selected(aGeo->
table) > 0) {
3206 cpl_table_set_size(aGeo->
table, cpl_table_get_nrow(aGeo->
table) + 1);
3207 int irow = cpl_table_get_nrow(aGeo->
table) - 1;
3208 cpl_table_set_int(aGeo->
table, MUSE_GEOTABLE_FIELD, irow, nifu);
3209 cpl_table_set_int(aGeo->
table, MUSE_GEOTABLE_CCD, irow, nslice);
3210 cpl_table_set_int(aGeo->
table, MUSE_GEOTABLE_SKY, irow,
3211 kMuseGeoSliceSky[nslice - 1]);
3212 cpl_table_set_double(aGeo->
table, MUSE_GEOTABLE_X, irow, NAN);
3213 cpl_table_set_double(aGeo->
table, MUSE_GEOTABLE_Y, irow, NAN);
3214 cpl_table_set_double(aGeo->
table, MUSE_GEOTABLE_ANGLE, irow, 0.);
3215 cpl_table_set_double(aGeo->
table, MUSE_GEOTABLE_WIDTH, irow, 0.);
3219 cpl_boolean needsnoflip = getenv(
"MUSE_GEOMETRY_NO_INVERSION")
3220 && atoi(getenv(
"MUSE_GEOMETRY_NO_INVERSION")) > 0;
3222 cpl_msg_info(__func__,
"Flipping all slices because of data inversion!");
3223 cpl_table_multiply_scalar(aGeo->
table, MUSE_GEOTABLE_Y, -1.);
3224 cpl_table_multiply_scalar(aGeo->
table, MUSE_GEOTABLE_ANGLE, -1.);
3228 cpl_propertylist *sorting = cpl_propertylist_new();
3229 cpl_propertylist_append_bool(sorting, MUSE_GEOTABLE_FIELD, CPL_FALSE);
3230 cpl_propertylist_append_bool(sorting, MUSE_GEOTABLE_SKY, CPL_FALSE);
3231 cpl_table_sort(aGeo->
table, sorting);
3232 cpl_propertylist_delete(sorting);
3235 printf(
"\nfinal geometry table with all slicer stacks [pix]:\n");
3236 cpl_table_dump(aGeo->
table, 0, nrow, stdout);
3240 const char *fn =
"GEOMETRY_TABLE.ascii";
3241 FILE *fp = fopen(fn,
"w");
3242 fprintf(fp,
"# final geometry table with all slicer stacks [pix]:\n");
3243 cpl_table_dump(aGeo->
table, 0, nrow, fp);
3245 cpl_msg_debug(__func__,
"written geometry table in ASCII to \"%s\"", fn);
3248 return CPL_ERROR_NONE;
cpl_error_code muse_cplarray_erase_invalid(cpl_array *aArray)
Erase all invalid values from an array.
cpl_polynomial ** muse_trace_table_get_polys_for_slice(const cpl_table *aTable, const unsigned short aSlice)
construct polynomial from the trace table entry for the given slice
muse_geo_table * muse_geo_table_duplicate(const muse_geo_table *aGeo)
Make a copy of a MUSE geometry table.
Structure definition for a collection of muse_images.
cpl_error_code muse_geo_refine_horizontal(muse_geo_table *aGeo, cpl_table *aSpots)
Refine relative horizontal positions of adjacent IFUs.
const muse_cpltable_def muse_geo_measurements_def[]
Spots measurement table definition for geometrical calibration.
cpl_polynomial * muse_wave_table_get_poly_for_slice(const cpl_table *aTable, const unsigned short aSlice)
Construct polynomial from the wavelength calibration table entry for the given slice.
unsigned char muse_utils_get_ifu(const cpl_propertylist *aHeaders)
Find out the IFU/channel from which this header originated.
cpl_image * data
the data extension
static void muse_geo_determine_horizontal_vpos(const cpl_table *aSlice, double *aP, double *aPE, double *aPM, double aDY, double aF)
Compute properly weighted vertical slice position mean from intermediate geometry table columns...
static cpl_size muse_geo_determine_horizontal_wmean(const cpl_table *aSlice, const char *aCol, const char *aColErr, double *aValue, double *aError, double *aMedian, double aSigma)
Interatively reject outliers and compute (weighted) statistics of intermediate geometry table columns...
const muse_cpltable_def muse_geo_table_def[]
Geometry table definition.
double muse_geo_table_ifu_area(const cpl_table *aTable, const unsigned char aIFU, double aScale)
Compute the area of an IFU in the VLT focal plane.
double muse_geo_compute_pinhole_global_distance(cpl_array *aDY, double aWidth, double aMin, double aMax)
Use vertical pinhole distance measurements of all IFUs to compute the effective global value...
double scale
The VLT focal plane scale factor of the data. output file.
muse_geo_table * muse_geo_determine_vertical(const muse_geo_table *aGeo)
Use all properties of the central spot and the horizontal properties in each slice to compute the ver...
cpl_vector * muse_cplvector_get_unique(const cpl_vector *aVector)
Separate out all unique entries in a given vector into a new one.
Structure definition of MUSE three extension FITS file.
double muse_pfits_get_focu_scale(const cpl_propertylist *aHeaders)
find out the scale in the VLT focal plane
static cpl_table * muse_geo_get_spot_peaks(cpl_table *aSpots, unsigned char aIFU, unsigned short aNSlice, unsigned char aNSpot, double aLambda, double aVPosRef, cpl_boolean aVerifyDY, cpl_array *aDY)
Use spot measurements of one IFU to compute vertical pinhole distance.
cpl_propertylist * header
the FITS header
cpl_error_code muse_cpltable_check(const cpl_table *aTable, const muse_cpltable_def *aDef)
Check whether the table contains the fields of the definition.
unsigned int muse_imagelist_get_size(muse_imagelist *aList)
Return the number of stored images.
void muse_trace_polys_delete(cpl_polynomial *aPolys[])
Delete the multi-polynomial array created in relation to tracing.
muse_geo_table * muse_geo_determine_horizontal(const muse_geo_table *aGeo)
Use per-spot and per-wavelength partial geometry to determine the horizontal geometrical properties f...
cpl_error_code muse_geo_compute_pinhole_local_distance(cpl_array *aDY, cpl_table *aSpots)
Use spot measurements of one IFU to compute vertical pinhole distance.
cpl_table * muse_cpltable_new(const muse_cpltable_def *aDef, cpl_size aLength)
Create an empty table according to the specified definition.
cpl_table * muse_geo_measure_spots(muse_image *aImage, muse_imagelist *aList, const cpl_table *aTrace, const cpl_table *aWave, const cpl_vector *aLines, double aSigma, muse_geo_centroid_type aCentroid)
Detect spots on a combined image and measure them on the corresponding series of images.
cpl_array * muse_cplarray_new_from_delimited_string(const char *aString, const char *aDelim)
Convert a delimited string into an array of strings.
muse_image * muse_imagelist_get(muse_imagelist *aList, unsigned int aIdx)
Get the muse_image of given list index.
cpl_table * table
The geometry table.
Structure definition of MUSE geometry table.
cpl_error_code muse_geo_finalize(muse_geo_table *aGeo)
Create a final version of a geometry table.
muse_geo_centroid_type
Type of centroiding algorithm to use.
cpl_bivector * muse_cplarray_histogram(const cpl_array *aArray, double aWidth, double aMin, double aMax)
Create a histogram for a numerical array.
int muse_pfits_get_posenc(const cpl_propertylist *aHeaders, unsigned short aEncoder)
query the absolute encoder position of one encoder
void muse_geo_table_delete(muse_geo_table *aGeo)
Deallocate memory associated to a geometry table object.
muse_geo_table * muse_geo_table_new(cpl_size aNRows, double aScale)
Create a new MUSE geometry table.
double muse_astro_posangle(const cpl_propertylist *aHeader)
Derive the position angle of an observation from information in a FITS header.
cpl_size muse_cplarray_erase_outliers(cpl_array *aArray, const cpl_bivector *aHistogram, cpl_size aGap, double aLimit)
Erase outliers from an array using histogram information.
double muse_pfits_get_pospos(const cpl_propertylist *aHeaders, unsigned short aEncoder)
query the position in user units of one encoder
cpl_table * muse_geo_table_extract_ifu(const cpl_table *aTable, const unsigned char aIFU)
Extract the part of a geometry table dealing with a given IFU.
Definition of a cpl table structure.
double muse_cplvector_get_adev_const(const cpl_vector *aVector, double aCenter)
Compute the average absolute deviation of a (constant) vector.
cpl_matrix * muse_matrix_new_gaussian_2d(int aXHalfwidth, int aYHalfwidth, double aSigma)
Create a matrix that contains a normalized 2D Gaussian.
cpl_vector * muse_geo_lines_get(const cpl_table *aLines)
Select lines suitable for geometrical calibration from a line list.
muse_geo_table * muse_geo_determine_initial(cpl_table *aSpots, const cpl_table *aTrace)
Use spot measurements to compute initial geometrical properties.