38 #include "muse_instrument.h" 40 #include "muse_astro.h" 41 #include "muse_cplwrappers.h" 42 #include "muse_data_format_z.h" 44 #include "muse_pfits.h" 45 #include "muse_tracing.h" 46 #include "muse_utils.h" 47 #include "muse_wavecalib.h" 91 {
"filename", CPL_TYPE_STRING,
"",
"%s",
92 "(raw) filename from which this measurement originates", CPL_TRUE },
93 {
"image", CPL_TYPE_INT,
"",
"%03d",
"number of the image in the series", CPL_TRUE },
94 {
"POSENC2", CPL_TYPE_INT,
"",
"%d",
95 "x position of the mask in encoder steps", CPL_TRUE },
96 {
"POSPOS2", CPL_TYPE_DOUBLE,
"mm",
"%.3f",
"x position of the mask", CPL_TRUE },
97 {
"POSENC3", CPL_TYPE_INT,
"",
"%d",
98 "y position of the mask in encoder steps", CPL_TRUE },
99 {
"POSPOS3", CPL_TYPE_DOUBLE,
"mm",
"%.3f",
"y position of the mask", CPL_TRUE },
100 {
"POSENC4", CPL_TYPE_INT,
"",
"%d",
101 "z position of the mask in encoder steps", CPL_TRUE },
102 {
"POSPOS4", CPL_TYPE_DOUBLE,
"mm",
"%.3f",
"z position of the mask", CPL_TRUE },
103 {
"VPOS", CPL_TYPE_DOUBLE,
"mm",
"%.3f",
"real vertical position of the mask", CPL_TRUE },
104 {
"ScaleFOV", CPL_TYPE_DOUBLE,
"arcsec/mm",
"%.3f",
105 "focus scale in VLT focal plane (from the FITS header)", CPL_TRUE },
106 {
"SubField", CPL_TYPE_INT,
"",
"%02d",
"sub-field number", CPL_TRUE },
107 {
"SliceCCD", CPL_TYPE_INT,
"",
"%02d",
108 "slice number as counted on the CCD", CPL_TRUE },
109 {
"lambda", CPL_TYPE_DOUBLE,
"Angstrom",
"%.3f",
"wavelength", CPL_TRUE },
110 {
"SpotNo", CPL_TYPE_INT,
"",
"%04d",
111 "number of this spot within the slice (1 is left, 2 is the central one, 3 is right within the slice)", CPL_TRUE },
112 {
"xc", CPL_TYPE_DOUBLE,
"pix",
"%.3f",
"x center of this spot on the CCD", CPL_TRUE },
113 {
"yc", CPL_TYPE_DOUBLE,
"pix",
"%.3f",
"y center of this spot on the CCD", CPL_TRUE },
114 {
"xfwhm", CPL_TYPE_DOUBLE,
"pix",
"%.2f",
"FWHM in x-direction on the CCD", CPL_TRUE },
115 {
"yfwhm", CPL_TYPE_DOUBLE,
"pix",
"%.2f",
"FWHM in y-direction on the CCD", CPL_TRUE },
116 {
"flux", CPL_TYPE_DOUBLE,
"",
"%.1f",
117 "flux of the spot as integrated on the CCD image", CPL_TRUE },
118 {
"bg", CPL_TYPE_DOUBLE,
"",
"%f",
"background level around the spot", CPL_TRUE },
119 {
"dxcen", CPL_TYPE_DOUBLE,
"pix",
"%f",
120 "distance to center of slice at vertical position yc (positive: right of center)", CPL_TRUE },
121 {
"twidth", CPL_TYPE_DOUBLE,
"pix",
"%f",
122 "trace width of the slice at the vertical CCD position of the spot", CPL_TRUE },
123 { NULL, 0, NULL, NULL, NULL, CPL_FALSE }
170 { MUSE_GEOTABLE_FIELD, CPL_TYPE_INT,
"",
"%02d",
171 "sub-field (IFU / channel) number", CPL_TRUE },
172 { MUSE_GEOTABLE_CCD, CPL_TYPE_INT,
"",
"%02d",
173 "the slice number on the CCD, counted from left to right", CPL_TRUE },
174 { MUSE_GEOTABLE_SKY, CPL_TYPE_INT,
"",
"%02d",
175 "the slice number on the sky", CPL_TRUE },
176 { MUSE_GEOTABLE_X, CPL_TYPE_DOUBLE,
"pix",
"%9.4f",
177 "x position within field of view", CPL_TRUE },
178 { MUSE_GEOTABLE_Y, CPL_TYPE_DOUBLE,
"pix",
"%9.4f",
179 "y position within field of view", CPL_TRUE },
180 { MUSE_GEOTABLE_ANGLE, CPL_TYPE_DOUBLE,
"deg",
"%6.3f",
181 "rotation angle of slice", CPL_TRUE },
182 { MUSE_GEOTABLE_WIDTH, CPL_TYPE_DOUBLE,
"pix",
"%.2f",
183 "width of slice within field of view", CPL_TRUE },
184 { MUSE_GEOTABLE_X
"err", CPL_TYPE_DOUBLE,
"pix",
"%8.4f",
185 "error estimated of x position within field of view", CPL_TRUE },
186 { MUSE_GEOTABLE_Y
"err", CPL_TYPE_DOUBLE,
"pix",
"%8.4f",
187 "error estimate of y position within field of view", CPL_TRUE },
188 { MUSE_GEOTABLE_ANGLE
"err", CPL_TYPE_DOUBLE,
"deg",
"%.3f",
189 "error estimate of rotation angle", CPL_TRUE },
190 { MUSE_GEOTABLE_WIDTH
"err", CPL_TYPE_DOUBLE,
"pix",
"%.2f",
191 "error estimate of slice width", CPL_TRUE },
192 {
"stack", CPL_TYPE_INT,
"",
"%02d",
193 "slicer stack that this slice belongs to (optical numbering)", CPL_TRUE },
194 {
"spot", CPL_TYPE_INT,
"",
"%1d",
"spot number in this slice", CPL_TRUE },
195 {
"xrel", CPL_TYPE_DOUBLE,
"mm",
"%7.4f",
196 "x offset of this spot relative to the slice center", CPL_TRUE },
197 {
"xrelerr", CPL_TYPE_DOUBLE,
"mm",
"%6.4f",
198 "error of the relative x offset of this spot", CPL_TRUE },
199 {
"xc", CPL_TYPE_DOUBLE,
"pix",
"%.3f",
"x center of this spot on the CCD", CPL_TRUE },
200 {
"yc", CPL_TYPE_DOUBLE,
"pix",
"%.3f",
"y center of this spot on the CCD", CPL_TRUE },
201 {
"dxl", CPL_TYPE_DOUBLE,
"pix",
"%.3f",
"distance to left edge of slice on the CCD", CPL_TRUE },
202 {
"dxr", CPL_TYPE_DOUBLE,
"pix",
"%.3f",
"distance to right edge of slice on the CCD", CPL_TRUE },
203 {
"dx", CPL_TYPE_DOUBLE,
"pix",
"%.3f",
"pinhole distance in x on the CCD", CPL_TRUE },
204 {
"dxerr", CPL_TYPE_DOUBLE,
"pix",
"%.3f",
205 "error estimate of the pinhole distance in x on the CCD", CPL_TRUE },
206 {
"vpos", CPL_TYPE_DOUBLE,
"mm",
"%.4f",
207 "(averaged) vertical position of the mask", CPL_TRUE },
208 {
"vposerr", CPL_TYPE_DOUBLE,
"mm",
"%.4f",
209 "error estimated of the (averaged) vertical position of the mask", CPL_TRUE },
210 {
"flux", CPL_TYPE_DOUBLE,
"",
"%.1f",
211 "flux of the spot as integrated on the CCD image", CPL_TRUE },
212 {
"lambda", CPL_TYPE_DOUBLE,
"Angstrom",
"%.3f",
"wavelength", CPL_TRUE },
213 { NULL, 0, NULL, NULL, NULL, CPL_FALSE }
237 cpl_ensure(aTable, CPL_ERROR_NULL_INPUT, NULL);
238 cpl_ensure(aIFU >= 1 && aIFU <= kMuseNumIFUs, CPL_ERROR_ILLEGAL_INPUT, NULL);
241 cpl_table *intable = cpl_table_duplicate(aTable);
245 cpl_propertylist *sorting = cpl_propertylist_new();
246 cpl_propertylist_append_bool(sorting, MUSE_GEOTABLE_FIELD, CPL_FALSE);
247 cpl_propertylist_append_bool(sorting, MUSE_GEOTABLE_CCD, CPL_FALSE);
248 cpl_table_sort(intable, sorting);
249 cpl_propertylist_delete(sorting);
251 cpl_table_select_all(intable);
252 cpl_table_and_selected_int(intable, MUSE_GEOTABLE_FIELD, CPL_EQUAL_TO, aIFU);
253 cpl_table *subtable = cpl_table_extract_selected(intable);
254 cpl_table_delete(intable);
256 printf(
"table (extracted for IFU %2d)\n", aIFU);
257 cpl_table_dump(subtable, 0, 100000, stdout);
260 int nrow = cpl_table_get_nrow(subtable);
261 if (nrow != kMuseSlicesPerCCD) {
262 cpl_error_set_message(__func__, CPL_ERROR_ILLEGAL_OUTPUT,
263 "geometry table contains %d instead of %d slices for " 264 "IFU %d", nrow, kMuseSlicesPerCCD, aIFU);
265 cpl_table_delete(subtable);
300 cpl_ensure(aTable, CPL_ERROR_NULL_INPUT, 0.);
305 cpl_size nrow = cpl_table_get_nrow(table);
306 cpl_ensure(nrow == kMuseSlicesPerCCD, CPL_ERROR_ILLEGAL_INPUT, 0.);
310 cpl_propertylist *sorting = cpl_propertylist_new();
311 cpl_propertylist_append_bool(sorting, MUSE_GEOTABLE_SKY, CPL_FALSE);
312 cpl_table_sort(table, sorting);
313 cpl_propertylist_delete(sorting);
317 double area = 0., areas[4];
319 nperstack = kMuseSlicesPerCCD / 4;
320 for (istack = 0; istack < 4; istack++) {
321 cpl_table *stack = cpl_table_extract(table, 12 * istack, nperstack);
324 double height = fabs(cpl_table_get(stack, MUSE_GEOTABLE_Y, 0, NULL)
325 - cpl_table_get(stack, MUSE_GEOTABLE_Y, nperstack - 1, NULL))
327 / kMuseTypicalCubeSizeY * aScale;
328 areas[istack] = cpl_table_get_column_mean(stack, MUSE_GEOTABLE_WIDTH)
330 / kMuseTypicalCubeSizeX * aScale;
331 cpl_table_delete(stack);
333 cpl_msg_debug(__func__,
"areas[%d] = %f", istack, areas[istack]);
335 area += areas[istack];
337 cpl_table_delete(table);
356 cpl_ensure(aLines, CPL_ERROR_NULL_INPUT, NULL);
359 cpl_table *tlines = cpl_table_duplicate(aLines);
362 cpl_table_cast_column(tlines, MUSE_LINE_CATALOG_LAMBDA, MUSE_LINE_CATALOG_LAMBDA,
364 cpl_table_cast_column(tlines, MUSE_LINE_CATALOG_FLUX, MUSE_LINE_CATALOG_FLUX,
366 cpl_table_unselect_all(tlines);
372 cpl_table_or_selected_string(tlines, MUSE_LINE_CATALOG_ION, CPL_EQUAL_TO,
"XeI");
373 cpl_table_or_selected_double(tlines, MUSE_LINE_CATALOG_FLUX, CPL_LESS_THAN, 5000.);
374 cpl_table_or_selected_double(tlines, MUSE_LINE_CATALOG_LAMBDA, CPL_LESS_THAN,
375 kMuseNominalLambdaMin);
376 cpl_table_or_selected_int(tlines, MUSE_LINE_CATALOG_QUALITY, CPL_LESS_THAN, 1);
377 cpl_table_erase_selected(tlines);
382 cpl_table_or_selected_string(tlines, MUSE_LINE_CATALOG_ION, CPL_EQUAL_TO,
"NeI");
383 cpl_table_and_selected_int(tlines, MUSE_LINE_CATALOG_QUALITY, CPL_LESS_THAN, 2);
384 cpl_table_unselect_row(tlines, cpl_table_get_nrow(tlines) - 1);
385 cpl_table_erase_selected(tlines);
386 cpl_table_or_selected_string(tlines, MUSE_LINE_CATALOG_ION, CPL_EQUAL_TO,
"NeI");
387 cpl_table_and_selected_double(tlines, MUSE_LINE_CATALOG_FLUX, CPL_LESS_THAN, 10000.);
388 cpl_table_unselect_row(tlines, cpl_table_get_nrow(tlines) - 1);
389 cpl_table_erase_selected(tlines);
393 int nlines = cpl_table_get_nrow(tlines);
395 cpl_table_delete(tlines);
396 cpl_error_set_message(__func__, CPL_ERROR_DATA_NOT_FOUND,
397 "Only found %d suitable arc lines!", nlines);
400 cpl_vector *lines = cpl_vector_wrap(nlines,
401 cpl_table_unwrap(tlines, MUSE_LINE_CATALOG_LAMBDA));
402 cpl_table_delete(tlines);
403 cpl_msg_info(__func__,
"Using a list of %d arc lines (from %.1f to %.1f " 404 "Angstrom)", nlines, cpl_vector_get(lines, 0),
405 cpl_vector_get(lines, nlines - 1));
459 const cpl_table *aTrace,
const cpl_table *aWave,
460 const cpl_vector *aLines,
double aSigma,
463 cpl_ensure(aImage && aList && aTrace && aWave && aLines, CPL_ERROR_NULL_INPUT,
465 cpl_ensure(aSigma > 0., CPL_ERROR_ILLEGAL_INPUT, NULL);
467 cpl_ensure(nimages >= 5, CPL_ERROR_ILLEGAL_INPUT, NULL);
468 int nlines = cpl_vector_get_size(aLines);
469 cpl_ensure(nlines >= 3, CPL_ERROR_ILLEGAL_INPUT, NULL);
473 int ny = cpl_image_get_size_y(aImage->
data),
474 nentries = kMuseSlicesPerCCD * kMuseCUmpmSpotsPerSlice * nlines * nimages;
479 for (iline = 0; iline < nlines; iline++) {
480 double lambda = cpl_vector_get(aLines, iline);
481 cpl_msg_info(__func__,
"Searching for line %d (%.3f Angstrom) in IFU %2hhu",
482 iline + 1, lambda, ifu);
484 unsigned short nslice;
485 for (nslice = 1; nslice <= kMuseSlicesPerCCD; nslice++) {
489 if (!ptrace || !pwave) {
491 cpl_polynomial_delete(pwave);
494 double xc = cpl_polynomial_eval_1d(ptrace[MUSE_TRACE_CENTER], ny / 2, NULL);
497 cpl_polynomial *pxconst = cpl_polynomial_new(1);
499 cpl_polynomial_set_coeff(pxconst, &p, xc);
500 cpl_polynomial *pywave = cpl_polynomial_extract(pwave, 0, pxconst);
501 cpl_polynomial_delete(pxconst);
503 double yc = 1, lbda = -1;
504 while (fabs(lambda - lbda) > 1.) {
505 lbda = cpl_polynomial_eval_1d(pywave, yc, NULL);
507 if (yc > kMuseOutputYTop) {
511 cpl_polynomial_delete(pywave);
513 cpl_msg_debug(__func__,
"--> %.3f --> %f,%f in slice %2hu",
514 lbda, xc, yc, nslice);
516 cpl_polynomial_delete(pwave);
519 if (fabs(lambda - lbda) > 1.) {
520 cpl_msg_warning(__func__,
"Polynomial in slice %2hu of IFU %2hhu appears" 521 " to be faulty! Skipping measurement of line %d (%.1f " 522 "Angstrom)", nslice, ifu, iline + 1, lambda);
529 #define DETECTION_HALFSIZE 7 530 int xl = lround(cpl_polynomial_eval_1d(ptrace[MUSE_TRACE_LEFT], yc, NULL)),
531 xr = lround(cpl_polynomial_eval_1d(ptrace[MUSE_TRACE_RIGHT], yc, NULL)),
532 yb = lround(yc - DETECTION_HALFSIZE),
533 yt = lround(yc + DETECTION_HALFSIZE);
534 cpl_image *box = cpl_image_extract(aImage->
data, xl, yb, xr, yt);
536 cpl_image *fbox = cpl_image_duplicate(box);
538 cpl_image_filter(fbox, box, gkernel, CPL_FILTER_LINEAR, CPL_BORDER_FILTER);
539 cpl_matrix_delete(gkernel);
540 cpl_stats_mode mode = CPL_STATS_MEDIAN | CPL_STATS_MEDIAN_DEV;
541 cpl_stats *s = cpl_stats_new_from_image(box, mode);
542 double limit = cpl_stats_get_median(s)
543 + aSigma * cpl_stats_get_median_dev(s);
544 cpl_mask *mask = cpl_mask_threshold_image_create(fbox, limit, DBL_MAX);
546 char *fn1 = cpl_sprintf(
"box_%02hu.fits", nslice),
547 *fn2 = cpl_sprintf(
"boxf_%02hu.fits", nslice),
548 *fn3 = cpl_sprintf(
"boxf_%02hu_mask.fits", nslice);
549 cpl_image_save(box, fn1, CPL_TYPE_UNSPECIFIED, NULL, CPL_IO_CREATE);
550 cpl_image_save(fbox, fn2, CPL_TYPE_UNSPECIFIED, NULL, CPL_IO_CREATE);
551 cpl_mask_save(mask, fn3, NULL, CPL_IO_CREATE);
557 cpl_apertures *apertures = cpl_apertures_extract_mask(box, mask);
559 cpl_msg_debug(__func__,
"stats in box [%d:%d,%d:%d] --> limit = " 560 "%f + %.1f * %f = %f, apertures:", xl, xr, yb, yt,
561 cpl_stats_get_median(s), aSigma,
562 cpl_stats_get_median_dev(s), limit);
563 cpl_apertures_dump(apertures, stdout);
566 cpl_mask_delete(mask);
568 cpl_image_delete(fbox);
569 cpl_image_delete(box);
570 cpl_errorstate es = cpl_errorstate_get();
571 int nspots = cpl_apertures_get_size(apertures);
575 if (!apertures || nspots != kMuseCUmpmSpotsPerSlice) {
576 cpl_msg_debug(__func__,
"found %d spot%s (need %hhu) down to the %.1f" 577 "-sigma limit in slice %2d for wavelength %.3f Angstrom " 578 "in box [%d:%d,%d:%d]", nspots, nspots == 1 ?
"" :
"s",
579 kMuseCUmpmSpotsPerSlice, aSigma, nslice, lambda,
582 cpl_apertures_delete(apertures);
583 cpl_errorstate_set(es);
587 cpl_msg_debug(__func__,
"found %d spots using the %.1f-sigma limit in " 588 "slice %2d for wavelength %.3f Angstrom in box [%d:%d,%d:%d]",
589 nspots, aSigma, nslice, lambda, xl, xr, yb, yt);
590 cpl_apertures_dump(apertures, stdout);
596 cpl_matrix *mspots = cpl_matrix_new(nspots, 2);
598 for (naper = 1; naper <= nspots; naper++) {
599 double xpos = cpl_apertures_get_centroid_x(apertures, naper);
600 cpl_matrix_set(mspots, naper - 1, 0, xpos);
601 cpl_matrix_set(mspots, naper - 1, 1, naper);
603 cpl_matrix_sort_rows(mspots, 1);
605 printf(
"mspots sorted:\n");
606 cpl_matrix_dump(mspots, stdout);
610 int idx, *aperidx = cpl_calloc(nspots,
sizeof(
int));
611 for (naper = 1, idx = nspots - 1; naper <= nspots && idx >= 0; naper++, idx--) {
613 aperidx[naper - 1] = cpl_matrix_get(mspots, idx, 1);
615 cpl_matrix_delete(mspots);
620 for (k = 0; k < nimages; k++) {
626 for (i = 1; i <= 4; i++) {
631 #define MEASUREMENT_HALFSIZE 5 632 #define BACKGROUND_HALFSIZE 7 633 for (idx = 0; idx < nspots; idx++) {
634 naper = aperidx[idx];
635 double xpos = cpl_apertures_get_centroid_x(apertures, naper) + xl,
636 ypos = cpl_apertures_get_centroid_y(apertures, naper) + yb;
638 printf(
"aper %d idx %d xpos %f\n", naper, idx, xpos);
641 cpl_stats_mode mspot = CPL_STATS_FLUX | CPL_STATS_CENTROID,
642 mbg = CPL_STATS_MEAN;
643 cpl_stats *sspot = cpl_stats_new_from_image_window(image->
data, mspot,
644 xpos - MEASUREMENT_HALFSIZE,
645 ypos - MEASUREMENT_HALFSIZE,
646 xpos + MEASUREMENT_HALFSIZE,
647 ypos + MEASUREMENT_HALFSIZE),
648 *sbg = cpl_stats_new_from_image_window(image->
data, mbg,
649 xpos - BACKGROUND_HALFSIZE,
650 ypos - BACKGROUND_HALFSIZE,
651 xpos + BACKGROUND_HALFSIZE,
652 ypos + BACKGROUND_HALFSIZE);
653 int npix = cpl_stats_get_npix(sspot);
654 double bg = cpl_stats_get_mean(sbg),
655 flux = cpl_stats_get_flux(sspot) - bg * npix;
659 cpl_stats_delete(sbg);
660 double xcentroid = cpl_stats_get_centroid_x(sspot),
661 ycentroid = cpl_stats_get_centroid_y(sspot);
662 cpl_stats_delete(sspot);
666 cpl_array *gpars = cpl_array_new(7, CPL_TYPE_DOUBLE);
667 cpl_array_set(gpars, 0, bg);
668 cpl_array_set(gpars, 1, flux);
669 cpl_array_set(gpars, 3, xcentroid);
670 cpl_array_set(gpars, 4, ycentroid);
671 cpl_array_set(gpars, 5, 2.);
672 cpl_array_set(gpars, 6, 2.);
673 cpl_fit_image_gaussian(image->
data, NULL, lround(xcentroid), lround(ycentroid),
674 2 * MEASUREMENT_HALFSIZE + 1, 2 * MEASUREMENT_HALFSIZE + 1,
675 gpars, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
676 xcentroid = cpl_array_get(gpars, 3, NULL);
677 ycentroid = cpl_array_get(gpars, 4, NULL);
678 xfwhm = cpl_array_get(gpars, 5, NULL) * CPL_MATH_FWHM_SIG;
679 yfwhm = cpl_array_get(gpars, 6, NULL) * CPL_MATH_FWHM_SIG;
680 cpl_array_delete(gpars);
682 cpl_image_get_fwhm(image->
data, lround(xcentroid), lround(ycentroid),
685 if (cpl_propertylist_has(image->
header, MUSE_HDR_TMP_FN)) {
686 cpl_table_set_string(measurements,
"filename", irow,
687 cpl_propertylist_get_string(image->
header,
690 cpl_table_set_string(measurements,
"filename", irow,
"unknown");
692 cpl_table_set_int(measurements,
"image", irow, k + 1);
693 cpl_table_set_int(measurements,
"POSENC2", irow, posenc[1]);
694 cpl_table_set(measurements,
"POSPOS2", irow, pospos[1]);
695 cpl_table_set_int(measurements,
"POSENC3", irow, posenc[2]);
696 cpl_table_set(measurements,
"POSPOS3", irow, pospos[2]);
697 cpl_table_set_int(measurements,
"POSENC4", irow, posenc[3]);
698 cpl_table_set(measurements,
"POSPOS4", irow, pospos[3]);
701 cpl_table_set(measurements,
"VPOS", irow,
702 pospos[2] / sin(posang * CPL_MATH_RAD_DEG));
703 cpl_table_set_double(measurements,
"ScaleFOV", irow,
705 cpl_table_set_int(measurements,
"SubField", irow, ifu);
706 cpl_table_set_int(measurements,
"SliceCCD", irow, nslice);
707 cpl_table_set(measurements,
"lambda", irow, lambda);
708 cpl_table_set_int(measurements,
"SpotNo", irow, idx + 1);
709 cpl_table_set(measurements,
"xc", irow, xcentroid);
710 cpl_table_set(measurements,
"yc", irow, ycentroid);
711 cpl_table_set(measurements,
"xfwhm", irow, xfwhm);
712 cpl_table_set(measurements,
"yfwhm", irow, yfwhm);
713 cpl_table_set(measurements,
"flux", irow, flux);
714 cpl_table_set(measurements,
"bg", irow, bg);
716 double xcslice = cpl_polynomial_eval_1d(ptrace[MUSE_TRACE_CENTER], ycentroid, NULL);
717 cpl_table_set(measurements,
"dxcen", irow, xcentroid - xcslice);
719 double twidth = cpl_polynomial_eval_1d(ptrace[MUSE_TRACE_RIGHT], ycentroid, NULL)
720 - cpl_polynomial_eval_1d(ptrace[MUSE_TRACE_LEFT], ycentroid, NULL);
721 cpl_table_set(measurements,
"twidth", irow, twidth);
724 if (xfwhm < 0 || yfwhm < 0) {
725 cpl_msg_warning(__func__,
"xfwhm and/or yfwhm are invalid: %f, %f:", xfwhm, yfwhm);
727 cpl_table_set_invalid(measurements,
"xfwhm", irow);
730 cpl_table_set_invalid(measurements,
"yfwhm", irow);
732 cpl_table_dump(measurements, irow, 1, stdout);
739 cpl_apertures_delete(apertures);
746 cpl_table_erase_invalid(measurements);
749 cpl_propertylist *order = cpl_propertylist_new();
750 cpl_propertylist_append_bool(order,
"lambda", CPL_FALSE);
751 cpl_propertylist_append_bool(order,
"SliceCCD", CPL_FALSE);
752 cpl_propertylist_append_bool(order,
"SpotNo", CPL_FALSE);
753 cpl_propertylist_append_bool(order,
"VPOS", CPL_FALSE);
754 cpl_table_sort(measurements, order);
755 cpl_propertylist_delete(order);
799 unsigned short aNSlice,
unsigned char aNSpot,
800 double aLambda,
double aVPosRef, cpl_boolean aVerifyDY,
810 cpl_table_unselect_all(aSpots);
811 cpl_size irow, nrow = cpl_table_get_nrow(aSpots);
812 for (irow = 0; irow < nrow; irow++) {
813 if (cpl_table_get_int(aSpots,
"SliceCCD", irow, NULL) == aNSlice &&
814 cpl_table_get_int(aSpots,
"SpotNo", irow, NULL) == aNSpot &&
815 cpl_table_get_double(aSpots,
"lambda", irow, NULL) == aLambda) {
816 cpl_table_select_row(aSpots, irow);
819 cpl_size nextracted = cpl_table_count_selected(aSpots);
820 if (nextracted < 1) {
823 cpl_msg_debug(__func__,
"No detection for spot %1hhu in slice %2hu of IFU " 824 "%hhu at wavelength %.3f", aNSpot, aNSlice, aIFU, aLambda);
827 cpl_table *tspot = cpl_table_extract_selected(aSpots);
830 cpl_table_dump(tspot, 0, 10000, stdout);
834 int nsrow = cpl_table_get_nrow(tspot);
835 cpl_image *imflux = cpl_image_wrap(nsrow, 1,
837 cpl_table_get_data_double(tspot,
"flux"));
840 cpl_stats *s = cpl_stats_new_from_image(imflux,
841 CPL_STATS_MEDIAN | CPL_STATS_MEDIAN_DEV);
842 double limit = cpl_stats_get_median(s) + cpl_stats_get_median_dev(s) * 0.5;
847 cpl_mask *mask = cpl_mask_threshold_image_create(imflux, limit, DBL_MAX);
848 cpl_mask *kernel = cpl_mask_new(3, 1);
849 cpl_mask_not(kernel);
850 cpl_mask *mask2 = cpl_mask_duplicate(mask);
851 cpl_mask_filter(mask, mask2, kernel, CPL_FILTER_DILATION, CPL_BORDER_NOP);
852 cpl_mask_delete(mask2);
853 cpl_mask_delete(kernel);
854 cpl_apertures *aper = cpl_apertures_extract_mask(imflux, mask);
855 cpl_mask_delete(mask);
857 cpl_msg_info(__func__,
"No detection for spot %1hhu in slice %2hu of IFU " 858 "%2hhu at wavelength %.3f (hope for other wavelengths!)",
859 aNSpot, aNSlice, aIFU, aLambda);
860 cpl_table_delete(tspot);
861 cpl_image_unwrap(imflux);
865 double dcenter = DBL_MAX;
866 int naper, ndcenter = -1;
867 for (naper = 1; naper <= cpl_apertures_get_size(aper); naper++) {
869 int npos = cpl_apertures_get_npix(aper, naper);
870 if (cpl_apertures_get_size(aper) > 1 && npos < 3) {
871 cpl_msg_debug(__func__,
"IFU %2hhu SliceCCD %2d spot %1hhu lambda %.3f, " 872 "aperture %d: only %d positions -> skip", aIFU, aNSlice,
873 aNSpot, aLambda, naper, npos);
877 double xref = aVPosRef > 0 ? aVPosRef
878 : cpl_table_get_double(tspot,
"VPOS", (nsrow + 1) / 2, NULL),
879 xcentroid = cpl_apertures_get_centroid_x(aper, naper);
883 while (++irow + 1 < xcentroid) ;
884 double pp1 = cpl_table_get_double(tspot,
"VPOS", irow - 1, NULL),
885 pp2 = cpl_table_get_double(tspot,
"VPOS", irow, NULL),
886 ppfrac = xcentroid - irow;
888 cpl_msg_debug(__func__,
"%"CPL_SIZE_FORMAT
" (%f) --> %f %f ==> %f", irow,
889 xcentroid, pp1, pp2, pp1 * (1 - ppfrac) + pp2 * ppfrac);
891 double ppcentroid = pp1 * (1 - ppfrac) + pp2 * ppfrac;
893 double dc = fabs(ppcentroid - xref);
894 int x1 = cpl_apertures_get_left(aper, naper),
895 x2 = cpl_apertures_get_right(aper, naper);
896 if (dc < dcenter && x1 > 1 && x2 < nsrow) {
903 if (aDY || aVerifyDY) {
904 for (naper = 1; naper < cpl_apertures_get_size(aper); naper++) {
905 int l1 = cpl_apertures_get_left(aper, naper),
906 r1 = cpl_apertures_get_right(aper, naper),
907 l2 = cpl_apertures_get_left(aper, naper + 1),
908 r2 = cpl_apertures_get_right(aper, naper + 1);
909 if (l1 > 1 && r1 < nsrow && l2 > 1 && r2 < nsrow) {
913 for (n2aper = naper; n2aper <= naper + 1; n2aper++) {
914 cpl_size irow1 = cpl_apertures_get_left(aper, n2aper) - 1,
915 irow2 = cpl_apertures_get_right(aper, n2aper) - 1;
916 double vpos = 0., ftot = 0.;
917 for (irow = irow1; irow <= irow2; irow++) {
918 double flux = cpl_table_get(tspot,
"flux", irow, NULL);
920 vpos += cpl_table_get(tspot,
"VPOS", irow, NULL) * flux;
922 peak[n2aper - naper] = vpos / ftot;
924 double xcdiff = fabs(peak[1] - peak[0]);
927 cpl_errorstate state = cpl_errorstate_get();
928 cpl_size idy = 0, ndy = cpl_array_get_size(aDY);
929 while (cpl_array_is_valid(aDY, idy) > 0) {
932 if (cpl_array_get_size(aDY) <= idy) {
933 cpl_array_set_size(aDY, ndy * 1.5);
934 cpl_errorstate_set(state);
937 cpl_msg_debug(__func__,
"xcdiff = %f (%f - %f = %f) / index %"CPL_SIZE_FORMAT,
938 xcdiff, peak[1], peak[0], peak[1] - peak[0], idy);
940 cpl_array_set_double(aDY, idy, xcdiff);
943 printf(
"\"centroids_d_%f.dat\" u 18:16 t \"d %f (%f %f)\" w lp, \\\n",
944 xcdiff, xcdiff, peak[0], peak[1]);
945 char *fn = cpl_sprintf(
"centroids_d_%f.dat", xcdiff);
946 FILE *fp = fopen(fn,
"w");
947 fprintf(fp,
"# good centroids at %f and %f --> d = %f mm\n#", peak[0], peak[1], xcdiff);
948 cpl_table_dump(tspot, 0, 10000, fp);
957 cpl_msg_info(__func__,
"Motion of spot %1hhu in slice %2hu of IFU " 958 "%2hhu at wavelength %.3f did not result in usable " 959 "coverage (hope for other wavelengths!)", aNSpot, aNSlice,
961 cpl_table_delete(tspot);
962 cpl_apertures_delete(aper);
963 cpl_image_unwrap(imflux);
966 cpl_size irow1 = cpl_apertures_get_left(aper, ndcenter) - 1,
967 irow2 = cpl_apertures_get_right(aper, ndcenter) - 1;
968 cpl_apertures_delete(aper);
969 cpl_image_unwrap(imflux);
972 cpl_table_unselect_all(tspot);
973 for (irow = irow1; irow <= irow2; irow++) {
974 cpl_table_select_row(tspot, irow);
976 cpl_table *result = cpl_table_extract_selected(tspot);
977 cpl_table_delete(tspot);
1018 cpl_ensure_code(aDY && aSpots, CPL_ERROR_NULL_INPUT);
1019 cpl_ensure_code(cpl_array_get_type(aDY) == CPL_TYPE_DOUBLE,
1020 CPL_ERROR_INCOMPATIBLE_INPUT);
1021 cpl_size nrow = cpl_table_get_nrow(aSpots);
1022 cpl_ensure_code(nrow > 10, CPL_ERROR_ILLEGAL_INPUT);
1024 CPL_ERROR_INCOMPATIBLE_INPUT);
1025 const unsigned char ifu = cpl_table_get_column_min(aSpots,
"SubField"),
1026 ifu2 = cpl_table_get_column_max(aSpots,
"SubField");
1027 cpl_ensure_code(ifu == ifu2 && ifu >= 1 && ifu <= kMuseNumIFUs,
1028 CPL_ERROR_ILLEGAL_INPUT);
1029 cpl_ensure_code(cpl_table_get_column_stdev(aSpots,
"ScaleFOV") < 1e-10,
1030 CPL_ERROR_ILLEGAL_INPUT);
1032 cpl_boolean verifydy = getenv(
"MUSE_DEBUG_GEO_VERIFY_DY")
1033 && atoi(getenv(
"MUSE_DEBUG_GEO_VERIFY_DY")) > 0;
1035 cpl_msg_warning(__func__,
"Running with DY pinhole distance verification on" 1036 " (MUSE_DEBUG_GEO_VERIFY_DY=%s), will produce lots of files " 1037 "\"centroids_d_*.dat\"!", getenv(
"MUSE_DEBUG_GEO_VERIFY_DY"));
1041 double *lbda = cpl_table_get_data_double(aSpots,
"lambda");
1042 cpl_vector *vlbda = cpl_vector_wrap(nrow, lbda);
1044 cpl_vector_unwrap(vlbda);
1045 int nlines = cpl_vector_get_size(lambdas);
1049 cpl_array *dy = cpl_array_new(kMuseSlicesPerCCD * nlines * kMuseCUmpmSpotsPerSlice,
1055 unsigned short nslice;
1056 for (nslice = 1; nslice <= kMuseSlicesPerCCD; nslice++) {
1058 for (iline = 0; iline < nlines; iline++) {
1059 double lambda = cpl_vector_get(lambdas, iline);
1061 unsigned char nspot;
1062 double vposref = -DBL_MAX;
1063 for (nspot = 1; nspot <= kMuseCUmpmSpotsPerSlice; nspot++) {
1065 lambda, vposref, verifydy, dy);
1066 cpl_table_delete(tspot);
1070 cpl_vector_delete(lambdas);
1073 cpl_msg_debug(__func__,
"Median vertical pinhole distance in IFU %02hhu: %f mm",
1074 ifu, cpl_array_get_median(dy));
1075 #pragma omp critical (geo_dy_array_insert) 1076 cpl_array_insert(aDY, dy, cpl_array_get_size(aDY));
1077 cpl_array_delete(dy);
1079 return CPL_ERROR_NONE;
1113 double aMin,
double aMax)
1115 cpl_ensure(aDY, CPL_ERROR_NULL_INPUT, 0.);
1116 cpl_ensure(cpl_array_get_type(aDY) == CPL_TYPE_DOUBLE,
1117 CPL_ERROR_INCOMPATIBLE_INPUT, 0.);
1118 cpl_ensure(cpl_array_count_invalid(aDY) < cpl_array_get_size(aDY),
1119 CPL_ERROR_ILLEGAL_INPUT, 0.);
1124 printf(
"aDY array 1: %f +/- %f (%f)\n", cpl_array_get_mean(aDY),
1125 cpl_array_get_stdev(aDY), cpl_array_get_median(aDY));
1126 cpl_array_dump(aDY, 0, 1000000, stdout);
1127 printf(
"aDY histogram 1:\n");
1128 cpl_plot_bivector(NULL,
"w lp", NULL, histogram);
1129 cpl_bivector_dump(histogram, stdout);
1133 cpl_bivector_delete(histogram);
1134 double mean = cpl_array_get_mean(aDY),
1135 stdev = cpl_array_get_stdev(aDY),
1136 min = mean - 2 * stdev,
1137 max = mean + 2 * stdev,
1138 step = (max - min) / 20.;
1140 double median = cpl_array_get_median(aDY);
1141 printf(
"aDY array 2: %f +/- %f (%f)\n", mean, stdev, median);
1142 cpl_array_dump(aDY, 0, 1000000, stdout);
1147 printf(
"aDY histogram 2:\n");
1148 cpl_plot_bivector(NULL,
"w lp", NULL, histogram);
1149 cpl_bivector_dump(histogram, stdout);
1153 cpl_bivector_delete(histogram);
1154 mean = cpl_array_get_mean(aDY);
1155 stdev = cpl_array_get_stdev(aDY);
1157 double median2 = cpl_array_get_median(aDY);
1158 printf(
"aDY array 3: %f +/- %f (%f)\n", mean, stdev, median2);
1159 cpl_array_dump(aDY, 0, 1000000, stdout);
1165 cpl_msg_info(__func__,
"Computed vertical pinhole distance of %.6f +/- %.6f " 1166 "mm (instead of %.4f)", mean, stdev, kMuseCUmpmDY);
1167 if (getenv(
"MUSE_GEOMETRY_PINHOLE_DY")) {
1168 cpl_msg_warning(__func__,
"Vertical pinhole distance already overridden in the " 1169 "environment (%f mm)", atof(getenv(
"MUSE_GEOMETRY_PINHOLE_DY")));
1171 char *envstring = cpl_sprintf(
"%f", mean);
1172 int err = setenv(
"MUSE_GEOMETRY_PINHOLE_DY", envstring, 1);
1174 cpl_msg_info(__func__,
"Set MUSE_GEOMETRY_PINHOLE_DY=%s in the environment",
1177 cpl_free(envstring);
1219 cpl_ensure(aGeo, CPL_ERROR_NULL_INPUT, NULL);
1221 gt->
table = cpl_table_duplicate(aGeo->
table);
1242 cpl_table_delete(aGeo->
table);
1315 cpl_ensure(aSpots && aTrace, CPL_ERROR_NULL_INPUT, NULL);
1316 cpl_size nrow = cpl_table_get_nrow(aSpots);
1317 cpl_ensure(nrow > 10, CPL_ERROR_ILLEGAL_INPUT, NULL);
1319 CPL_ERROR_INCOMPATIBLE_INPUT, NULL);
1320 const unsigned char ifu = cpl_table_get_column_min(aSpots,
"SubField"),
1321 ifu2 = cpl_table_get_column_max(aSpots,
"SubField");
1322 cpl_ensure(ifu == ifu2 && ifu >= 1 && ifu <= kMuseNumIFUs,
1323 CPL_ERROR_ILLEGAL_INPUT, NULL);
1324 const double kScale = cpl_table_get_column_mean(aSpots,
"ScaleFOV");
1325 cpl_ensure(cpl_table_get_column_stdev(aSpots,
"ScaleFOV") < 1e-10,
1326 CPL_ERROR_ILLEGAL_INPUT, NULL);
1328 double maskangle = 0., fmaskrot = 1.;
1329 if (getenv(
"MUSE_GEOMETRY_MASK_ROTATION")) {
1330 maskangle = atof(getenv(
"MUSE_GEOMETRY_MASK_ROTATION"));
1331 fmaskrot = cos(maskangle * CPL_MATH_RAD_DEG);
1332 cpl_msg_warning(__func__,
"Adapting to global mask rotation of %.4f deg " 1333 "(cos = %.4e)", maskangle, fmaskrot);
1335 double pinholedy = kMuseCUmpmDY;
1336 if (getenv(
"MUSE_GEOMETRY_PINHOLE_DY")) {
1337 pinholedy = atof(getenv(
"MUSE_GEOMETRY_PINHOLE_DY"));
1338 cpl_msg_info(__func__,
"Using pinhole y distance of %f mm (instead of " 1339 "%f mm)", pinholedy, kMuseCUmpmDY);
1343 double *lbda = cpl_table_get_data_double(aSpots,
"lambda");
1344 cpl_vector *vlbda = cpl_vector_wrap(nrow, lbda);
1346 cpl_vector_unwrap(vlbda);
1347 int nlines = cpl_vector_get_size(lambdas);
1349 * kMuseCUmpmSpotsPerSlice * nlines,
1353 unsigned short nslice;
1354 for (nslice = 1; nslice <= kMuseSlicesPerCCD; nslice++) {
1358 cpl_msg_debug(__func__,
"Skipping IFU %2hhu SliceCCD %2d", ifu, nslice);
1361 cpl_msg_info(__func__,
"Handling IFU %2hhu SliceCCD %2d", ifu, nslice);
1363 for (iline = 0; iline < nlines; iline++) {
1364 double lambda = cpl_vector_get(lambdas, iline);
1366 unsigned char nspot, nslicespot = 0;
1367 double vposref = -DBL_MAX;
1368 for (nspot = 1; nspot <= kMuseCUmpmSpotsPerSlice; nspot++) {
1370 lambda, vposref, CPL_FALSE,
1376 double xcenter = 0., ycenter = 0.,
1377 vpos = 0., ftot = 0.;
1378 nrow = cpl_table_get_nrow(tspot);
1380 for (irow = 0; irow < nrow; irow++) {
1381 double flux = cpl_table_get(tspot,
"flux", irow, NULL);
1383 xcenter += cpl_table_get(tspot,
"xc", irow, NULL) * flux;
1384 ycenter += cpl_table_get(tspot,
"yc", irow, NULL) * flux;
1385 vpos += cpl_table_get(tspot,
"VPOS", irow, NULL) * flux;
1387 cpl_table_delete(tspot);
1389 cpl_msg_warning(__func__,
"Invalid integrated flux of spot %1hhu/%1hhu " 1390 "in slice %2hu of IFU %2hhu at wavelength %.3f: %e",
1391 nspot, nslicespot, nslice, ifu, lambda, ftot);
1401 double xcen = cpl_polynomial_eval_1d(ptrace[MUSE_TRACE_CENTER], ycenter,
1403 xl = cpl_polynomial_eval_1d(ptrace[MUSE_TRACE_LEFT], ycenter, NULL),
1404 xr = cpl_polynomial_eval_1d(ptrace[MUSE_TRACE_RIGHT], ycenter, NULL),
1406 cpl_msg_debug(__func__,
"IFU %2hhu SliceCCD %2d spot %1hhu/%1hhu lambda %.3f " 1407 "x/y %8.3f %8.3f (xcen %8.3f xwidth %6.3f) vpos %f flux %e",
1408 ifu, nslice, nspot, nslicespot, lambda, xcenter, ycenter,
1409 xcen, xwidth, vpos, ftot);
1410 cpl_table_set_int(gt->
table, MUSE_GEOTABLE_FIELD, irrow, ifu);
1411 cpl_table_set_int(gt->
table, MUSE_GEOTABLE_SKY, irrow,
1412 kMuseGeoSliceSky[nslice - 1]);
1413 cpl_table_set_int(gt->
table, MUSE_GEOTABLE_CCD, irrow, nslice);
1414 cpl_table_set_int(gt->
table,
"spot", irrow, nslicespot);
1417 unsigned char stack = nslice <= 12 ? 4 : (nslice <= 24 ? 3 : (nslice <= 36 ? 2 : 1));
1418 cpl_table_set_int(gt->
table,
"stack", irrow, stack);
1420 cpl_table_set_double(gt->
table,
"xc", irrow, xcenter);
1421 cpl_table_set_double(gt->
table,
"yc", irrow, ycenter);
1422 cpl_table_set_double(gt->
table,
"dxl", irrow, xcenter - xl);
1423 cpl_table_set_double(gt->
table,
"dxr", irrow, xr - xcenter);
1424 cpl_table_set_double(gt->
table,
"vpos", irrow, vpos);
1425 cpl_table_set_double(gt->
table,
"flux", irrow, ftot);
1426 cpl_table_set_double(gt->
table,
"lambda", irrow, lambda);
1429 cpl_table_set_invalid(gt->
table, MUSE_GEOTABLE_WIDTH, irrow);
1430 cpl_table_set_invalid(gt->
table, MUSE_GEOTABLE_ANGLE, irrow);
1434 if (nslicespot == kMuseCUmpmSpotsPerSlice) {
1436 int err1a, err2a, err1b, err2b;
1437 double xc1 = cpl_table_get_double(gt->
table,
"xc", irrow - 2, &err1a),
1438 xc2 = cpl_table_get_double(gt->
table,
"xc", irrow - 1, &err2a),
1439 vpos1 = cpl_table_get_double(gt->
table,
"vpos", irrow - 2, &err1b),
1440 vpos2 = cpl_table_get_double(gt->
table,
"vpos", irrow - 1, &err2b);
1441 if (!err1a && !err2a) {
1442 if (xcenter < xc2 || xc2 < xc1) {
1443 cpl_msg_warning(__func__,
"spots are not sorted left-to-right on " 1444 "the CCD (%f .. %f .. %f)!", xc1, xc2, xcenter);
1446 double dx = (xcenter - xc1) / 2.;
1449 double dxerr = sqrt((pow(xcenter - xc2 - dx, 2)
1450 + pow(xc2 - xc1 - dx, 2)) / 3.);
1457 double xreloffset = 0.;
1459 const double dxexpected = 26.04644 - 0.05537208 * ifu;
1460 double dx1 = xc2 - xc1,
1461 dx2 = xcenter - xc2,
1462 diff1 = fabs(dx1 - dxexpected),
1463 diff2 = fabs(dx2 - dxexpected),
1465 if (fmin(diff1, diff2) > 1.5) {
1467 }
else if (diff2 > diff1) {
1474 xreloffset = xwidth * kMuseCUmpmDX / fmaskrot
1475 * kMuseTypicalCubeSizeX * kScale / 60.
1476 * fabs(1. / dx - 1 / dxnew) / 2.;
1477 cpl_msg_debug(__func__,
"IFU %2hhu SliceCCD %2d spot %1hhu/%1hhu " 1478 "lambda %.3f dx %.3f +/- %.3f (%.3f %.3f): dxerr " 1479 "is large, using a guess of %.3f +/- %.3f " 1480 "(expected was %.3f for this IFU), adding %.3f to" 1481 " xrel!", ifu, nslice, nspot, nslicespot, lambda,
1482 dx, dxerr, xc2-xc1, xcenter-xc2, dxnew, dxerr / 2.,
1483 dxexpected, xreloffset);
1487 cpl_table_fill_column_window_double(gt->
table,
"dx", irrow - 2, 3, dx);
1488 cpl_table_fill_column_window_double(gt->
table,
"dxerr", irrow - 2, 3, dxerr);
1493 double scale = kMuseCUmpmDX / fmaskrot / dx,
1494 width = xwidth * scale * kMuseTypicalCubeSizeX * kScale / 60.,
1495 werr = width * dxerr / dx;
1496 if (width > kMuseSliceLoLikelyWidth && width < kMuseSliceHiLikelyWidth) {
1497 cpl_table_fill_column_window_double(gt->
table, MUSE_GEOTABLE_WIDTH,
1498 irrow - 2, 3, width);
1499 cpl_table_fill_column_window_double(gt->
table, MUSE_GEOTABLE_WIDTH
"err",
1500 irrow - 2, 3, werr);
1502 cpl_msg_info(__func__,
"IFU %2hhu slice %2d lambda %.3f: computed " 1503 "an unlikely width: %.3f +/- %.3f (hope for other" 1504 " wavelengths!)", ifu, nslice, lambda, width, werr);
1505 cpl_table_set_column_invalid(gt->
table, MUSE_GEOTABLE_WIDTH,
1507 cpl_table_set_column_invalid(gt->
table, MUSE_GEOTABLE_WIDTH
"err",
1513 double xrel = (xcen - xc1 + xreloffset) * scale,
1514 xrelerr = fabs(xrel * dxerr / dx);
1515 cpl_table_set_double(gt->
table,
"xrel", irrow - 2, xrel);
1516 cpl_table_set_double(gt->
table,
"xrelerr", irrow - 2, xrelerr);
1517 xrel = (xcen - xc2 + xreloffset) * scale;
1518 xrelerr = fabs(xrel * dxerr / dx);
1519 cpl_table_set_double(gt->
table,
"xrel", irrow - 1, xrel);
1520 cpl_table_set_double(gt->
table,
"xrelerr", irrow - 1, xrelerr);
1521 xrel = (xcen - xcenter + xreloffset) * scale;
1522 xrelerr = fabs(xrel * dxerr / dx);
1523 cpl_table_set_double(gt->
table,
"xrel", irrow, xrel);
1524 cpl_table_set_double(gt->
table,
"xrelerr", irrow, xrelerr);
1527 if (!err1b && !err2b) {
1530 double pdiff = fmax(vpos1, fmax(vpos2, vpos))
1531 - fmin(vpos1, fmin(vpos2, vpos));
1533 double pmean = (vpos1 + vpos2 + vpos) / 3.;
1534 if (vpos1 > pmean) {
1535 vpos1 -= pinholedy * fmaskrot;
1537 if (vpos2 > pmean) {
1538 vpos2 -= pinholedy * fmaskrot;
1541 vpos -= pinholedy * fmaskrot;
1543 double pdiff2 = fmax(vpos1, fmax(vpos2, vpos))
1544 - fmin(vpos1, fmin(vpos2, vpos));
1545 if (pdiff2 < pdiff) {
1546 cpl_msg_debug(__func__,
"Fixed max vpos diff from %f down to %f",
1549 double vpos1o = cpl_table_get_double(gt->
table,
"vpos", irrow - 2, NULL),
1550 vpos2o = cpl_table_get_double(gt->
table,
"vpos", irrow - 1, NULL),
1551 vpos3o = cpl_table_get_double(gt->
table,
"vpos", irrow, NULL);
1552 cpl_msg_info(__func__,
"Large max vpos diff detected but " 1553 "not fixed! (original: %f %f %f, %f -> mean %f " 1554 "-> fixed: %f %f %f, %f)", vpos1o, vpos2o, vpos3o,
1555 pdiff, pmean, vpos1, vpos2, vpos, pdiff2);
1563 double f = 1. / kMuseCUmpmDX / fmaskrot,
1564 angle1 = -atan((vpos2 - vpos1) * f) * CPL_MATH_DEG_RAD,
1565 angle2 = -atan((vpos - vpos2) * f) * CPL_MATH_DEG_RAD,
1566 angle = (angle1 + angle2) / 2.;
1568 double aerr = sqrt((pow(angle1 - angle, 2) + pow(angle2 - angle, 2)) / 3.);
1569 if (fabs(angle) < kMuseGeoSliceMaxAngle) {
1570 cpl_table_fill_column_window_double(gt->
table, MUSE_GEOTABLE_ANGLE,
1571 irrow - 2, 3, angle);
1572 cpl_table_fill_column_window_double(gt->
table, MUSE_GEOTABLE_ANGLE
"err",
1573 irrow - 2, 3, aerr);
1575 cpl_msg_info(__func__,
"IFU %2hhu slice %2d lambda %.3f: computed" 1576 " an unlikely angle: %.3f +/- %.3f (hope for other " 1577 "wavelengths!)", ifu, nslice, lambda, angle, aerr);
1578 cpl_table_set_column_invalid(gt->
table, MUSE_GEOTABLE_ANGLE,
1580 cpl_table_set_column_invalid(gt->
table, MUSE_GEOTABLE_ANGLE
"err",
1587 double dx = cpl_table_get_double(gt->
table,
"dx", irrow - 1, NULL),
1588 dxl = cpl_table_get_double(gt->
table,
"dxl", irrow - 2, NULL),
1589 dxr = cpl_table_get_double(gt->
table,
"dxr", irrow, NULL);
1590 cpl_table_fill_column_window_double(gt->
table,
"dxl", irrow - 2, 3, dxl / dx);
1591 cpl_table_fill_column_window_double(gt->
table,
"dxr", irrow - 2, 3, dxr / dx);
1599 cpl_vector_delete(lambdas);
1600 cpl_table_set_size(gt->
table, irrow);
1602 cpl_table_and_selected_invalid(gt->
table, MUSE_GEOTABLE_WIDTH);
1603 cpl_table_or_selected_invalid(gt->
table, MUSE_GEOTABLE_ANGLE);
1604 cpl_table_erase_selected(gt->
table);
1631 const char *aCol,
const char *aColErr,
1632 double *aValue,
double *aError,
1633 double *aMedian,
double aSigma)
1635 const char func[] =
"muse_geo_determine_horizontal";
1636 if (!aSlice)
return -1;
1637 if (!aCol || !aValue)
return -1;
1640 const double *vv = cpl_table_get_data_double_const(aSlice, aCol),
1643 ve = cpl_table_get_data_double_const(aSlice, aColErr);
1647 cpl_size n = cpl_table_get_nrow(aSlice);
1648 cpl_vector *vtmp = cpl_vector_wrap(n, (
double *)cpl_table_get_data_double_const(aSlice, aCol));
1649 double median = cpl_table_get_column_median(aSlice, aCol),
1651 vlo = median - aSigma * mdev,
1652 vhi = median + aSigma * mdev;
1653 cpl_vector_unwrap(vtmp);
1654 cpl_msg_debug(func,
"%s/%s: median %f +/- %f --> %f...%f", aCol, aColErr,
1655 median, mdev, vlo, vhi);
1659 double value, sigma = mdev,
1661 cpl_vector *vmedian = NULL;
1667 vmedian = cpl_vector_new(n);
1673 for (i = 0; i < n; i++) {
1674 if (vv[i] < vlo || vv[i] > vhi) {
1679 value += vv[i] / ve[i];
1680 weight += 1. / ve[i];
1692 cpl_vector_set(vmedian, im++, vv[i]);
1697 for (i = 0; i < n; i++) {
1698 if (vv[i] < vlo || vv[i] > vhi) {
1702 wmse += pow(vv[i] - value, 2) / ve[i];
1704 wmse += pow(vv[i] - value, 2);
1708 cpl_msg_debug(
"vpos",
"%f %f %f -> %f", vv[i], vv[i] - value,
1709 pow(vv[i] - value, 2), wmse);
1715 cpl_vector_set_size(vmedian, im);
1716 *aMedian = cpl_vector_get_median(vmedian);
1717 cpl_vector_delete(vmedian);
1720 if (isnormal(weight) && isfinite(wmse)) {
1723 sigma = sqrt(ve ? (n - nrejected) / (weight*weight) : 0. + wmse);
1726 cpl_msg_debug(
"vpos",
"%f", sigma);
1731 if (isfinite(value)) {
1732 vlo = value - aSigma * sigma;
1733 vhi = value + aSigma * sigma;
1736 cpl_msg_debug(func,
"%s/%s: %f +/- %f (+/- %f) --> %f...%f (rej. %" 1737 CPL_SIZE_FORMAT
" / %"CPL_SIZE_FORMAT
")", aCol, aColErr,
1738 value, sqrt(wmse), sigma, vlo, vhi, nrejected, n);
1740 }
while (nrejected < n && (max > vhi || min < vlo));
1745 if (nrejected == n) {
1746 value = (vlo + vhi) / 2.;
1747 sigma = (vhi - vlo) / (2. * aSigma);
1749 cpl_msg_debug(func,
"%s/%s: %f +/- %f (ALL rej. %"CPL_SIZE_FORMAT
" / %" 1750 CPL_SIZE_FORMAT
")", aCol, aColErr, value, sigma, nrejected, n);
1785 double *aP,
double *aPE,
double *aPM,
1786 double aDY,
double aF)
1788 const char func[] =
"muse_geo_determine_horizontal";
1789 if (!aSlice)
return;
1790 if (!aP || !aPE || !aPM)
return;
1792 cpl_vector *vvpos = cpl_vector_wrap(cpl_table_get_nrow(aSlice),
1793 (
double *)cpl_table_get_data_double_const(aSlice,
1795 *aP = cpl_vector_get_mean(vvpos);
1796 *aPE = cpl_vector_get_stdev(vvpos);
1797 *aPM = cpl_vector_get_median_const(vvpos);
1801 cpl_vector_unwrap(vvpos);
1805 cpl_size n = cpl_vector_get_size(vvpos);
1806 cpl_vector *vpp = cpl_vector_duplicate(vvpos),
1807 *vres = cpl_vector_duplicate(vvpos),
1808 *vmedian = cpl_vector_new(n);
1809 cpl_vector_unwrap(vvpos);
1810 cpl_vector_fill(vmedian, *aPM);
1811 cpl_vector_subtract(vres, vmedian);
1812 cpl_vector_delete(vmedian);
1813 double min = cpl_vector_get_min(vres),
1814 max = cpl_vector_get_max(vres);
1815 cpl_msg_debug(func,
"vector of vpos values (%.4f +/- %.4f, %.4f) and " 1816 "residuals (%.4f ... %.4f), pinhole distance %.4f",
1817 *aP, *aPE, *aPM, min, max, aDY * aF);
1819 cpl_bivector *biv = cpl_bivector_wrap_vectors(vpp, vres);
1820 cpl_bivector_dump(biv, stdout);
1822 cpl_bivector_unwrap_vectors(biv);
1827 cpl_array *ares = cpl_array_wrap_double(cpl_vector_unwrap(vres), n);
1828 cpl_array_abs(ares);
1829 double amin = cpl_array_get_min(ares),
1830 amax = cpl_array_get_max(ares);
1831 cpl_boolean halfandhalf = fabs(aDY/2. - amin) < 0.01
1832 && fabs(aDY/2. - amax) < 0.01;
1833 cpl_array_delete(ares);
1835 double p2, pe2, pm2;
1837 double limit = fabs(0.9 * aDY * aF);
1839 fabs(min) < limit && fabs(max) < limit) {
1844 &p2, &pe2, &pm2, 2.);
1845 cpl_msg_debug(func,
"values after rejection vector of vpos values (%.4f " 1846 "+/- %.4f, %.4f), %"CPL_SIZE_FORMAT
" rejected", p2, pe2, pm2,
1851 cpl_size nfixed = 0;
1852 for (i = 0; i < n; i++) {
1853 double v = cpl_vector_get(vpp, i);
1855 cpl_vector_set(vpp, i, v - aDY * aF);
1859 p2 = cpl_vector_get_mean(vpp);
1860 pe2 = cpl_vector_get_stdev(vpp);
1861 pm2 = cpl_vector_get_median_const(vpp);
1862 cpl_msg_debug(func,
"fixed vector of vpos values (%.4f +/- %.4f, %.4f), " 1863 "%"CPL_SIZE_FORMAT
" fixed", p2, pe2, pm2, nfixed);
1865 cpl_vector_dump(vpp, stdout);
1869 cpl_vector_delete(vpp);
1931 cpl_ensure(aGeo && aGeo->
table, CPL_ERROR_NULL_INPUT, NULL);
1932 cpl_size nrow = cpl_table_get_nrow(aGeo->
table);
1933 cpl_ensure(nrow >= 50, CPL_ERROR_ILLEGAL_INPUT, NULL);
1935 CPL_ERROR_INCOMPATIBLE_INPUT, NULL);
1936 const unsigned char ifu = cpl_table_get_column_min(aGeo->
table, MUSE_GEOTABLE_FIELD),
1937 ifu2 = cpl_table_get_column_max(aGeo->
table, MUSE_GEOTABLE_FIELD);
1938 cpl_ensure(ifu == ifu2 && ifu >= 1 && ifu <= kMuseNumIFUs,
1939 CPL_ERROR_ILLEGAL_INPUT, NULL);
1941 double maskangle = 0., fmaskrot = 1.;
1942 if (getenv(
"MUSE_GEOMETRY_MASK_ROTATION")) {
1943 maskangle = atof(getenv(
"MUSE_GEOMETRY_MASK_ROTATION"));
1944 fmaskrot = cos(maskangle * CPL_MATH_RAD_DEG);
1945 cpl_msg_warning(__func__,
"Adapting to global mask rotation of %.4f deg " 1946 "(cos = %.4e)", maskangle, fmaskrot);
1948 double pinholedy = kMuseCUmpmDY;
1949 if (getenv(
"MUSE_GEOMETRY_PINHOLE_DY")) {
1950 pinholedy = atof(getenv(
"MUSE_GEOMETRY_PINHOLE_DY"));
1951 cpl_msg_info(__func__,
"Using pinhole y distance of %f mm (instead of " 1952 "%f mm)", pinholedy, kMuseCUmpmDY);
1954 cpl_boolean stdgap = getenv(
"MUSE_GEOMETRY_STD_GAP")
1955 && atoi(getenv(
"MUSE_GEOMETRY_STD_GAP")) > 0;
1957 cpl_msg_warning(__func__,
"Using old (standard) gap computation");
1959 cpl_msg_info(__func__,
"Using new (alternative) gap computation");
1961 const double kScaleX = kMuseTypicalCubeSizeX * aGeo->
scale / 60.;
1965 cpl_table_and_selected_int(gt->
table,
"spot", CPL_NOT_EQUAL_TO, 2);
1966 cpl_table_erase_selected(gt->
table);
1969 FILE *f = fopen(
"bla_horizontal.dat",
"w");
1971 cpl_table_dump(gt->
table, 0, 100000000, f);
1973 f = fopen(
"bla_lambdas.dat",
"w");
1975 cpl_table_dump(aGeo->
table, 0, 100000000, f);
1978 unsigned short nslice;
1979 for (nslice = 1; nslice <= kMuseSlicesPerCCD; nslice++) {
1981 cpl_table_select_all(gt->
table);
1982 cpl_table_and_selected_int(gt->
table,
"SliceCCD", CPL_EQUAL_TO, nslice);
1983 if (cpl_table_count_selected(gt->
table) < 1) {
1986 cpl_array *asel = cpl_table_where_selected(gt->
table);
1987 cpl_table *slice = cpl_table_extract_selected(gt->
table);
1989 cpl_table_dump(slice, 0, 1000, stdout);
1994 double a, ae, w, we, xr, xre, dxl = 0., dxr = 0.;
1996 MUSE_GEOTABLE_ANGLE
"err", &a, &ae,
1999 MUSE_GEOTABLE_WIDTH
"err", &w, &we,
2007 cpl_errorstate ps = cpl_errorstate_get();
2008 double p = NAN, pe = -1, pm = NAN;
2010 pinholedy, fmaskrot);
2011 if (pe < 0 && !cpl_errorstate_is_equal(ps)) {
2013 cpl_errorstate_set(ps);
2016 cpl_msg_debug(__func__,
"IFU %2hhu stack %1d slice %2d / %2d " 2017 "angle %6.3f +/- %.3f deg width %.3f +/- %.3f pix " 2018 "xrel %.4f +/- %.4f vpos %.4f +/- %.4f (%.4f)", ifu,
2019 cpl_table_get_int(slice,
"stack", 0, NULL), nslice,
2020 cpl_table_get_int(slice,
"SliceSky", 0, NULL), a, ae, w, we,
2021 xr, xre, p, pe, pm);
2024 cpl_size irow = cpl_array_get_cplsize(asel, 0, NULL);
2025 cpl_array_delete(asel);
2026 cpl_table_set_double(gt->
table, MUSE_GEOTABLE_ANGLE, irow, a);
2027 cpl_table_set_double(gt->
table, MUSE_GEOTABLE_ANGLE
"err", irow, ae);
2028 cpl_table_set_double(gt->
table, MUSE_GEOTABLE_WIDTH, irow, w);
2029 cpl_table_set_double(gt->
table, MUSE_GEOTABLE_WIDTH
"err", irow, we);
2030 cpl_table_set_double(gt->
table,
"xrel", irow, xr);
2031 cpl_table_set_double(gt->
table,
"xrelerr", irow, xre);
2032 cpl_table_set_double(gt->
table,
"vpos", irow, p);
2033 cpl_table_set_double(gt->
table,
"vposerr", irow, pe);
2034 cpl_table_set_double(gt->
table,
"dxl", irow, dxl);
2035 cpl_table_set_double(gt->
table,
"dxr", irow, dxr);
2038 cpl_table_set_invalid(gt->
table,
"flux", irow);
2039 cpl_table_set_invalid(gt->
table,
"lambda", irow);
2040 cpl_table_set_invalid(gt->
table,
"xc", irow);
2041 cpl_table_set_invalid(gt->
table,
"yc", irow);
2042 cpl_table_unselect_row(gt->
table, irow);
2043 cpl_table_erase_selected(gt->
table);
2044 cpl_table_delete(slice);
2047 printf(
"intermediate result: weighted averages of angle, width, and xrel:\n");
2048 cpl_table_dump(gt->
table, 0, 1000000, stdout);
2058 const unsigned short nsoff = kMuseSlicesPerCCD / 4;
2059 for (nslice = 1 + nsoff; nslice <= 2*nsoff; nslice++) {
2061 cpl_table_unselect_all(gt->
table);
2062 cpl_table_or_selected_int(gt->
table,
"SliceSky", CPL_EQUAL_TO, nslice - nsoff);
2063 cpl_table_or_selected_int(gt->
table,
"SliceSky", CPL_EQUAL_TO, nslice);
2064 cpl_table_or_selected_int(gt->
table,
"SliceSky", CPL_EQUAL_TO, nslice + nsoff);
2065 cpl_table_or_selected_int(gt->
table,
"SliceSky", CPL_EQUAL_TO, nslice + 2*nsoff);
2067 cpl_table *ts = cpl_table_extract_selected(gt->
table);
2069 int irow, nrowts = cpl_table_get_nrow(ts),
2070 i1 = -1, i2 = -1, i3 = -1, i4 = -1;
2071 for (irow = 0; irow < nrowts; irow++) {
2074 switch (cpl_table_get_int(ts,
"stack", irow, NULL)) {
2075 case 1: i1 = irow;
break;
2076 case 2: i2 = irow;
break;
2077 case 3: i3 = irow;
break;
2078 case 4: i4 = irow;
break;
2082 if (i3 < 0 || i2 < 0) {
2083 char *msg = cpl_sprintf(
"For IFU %2hhu / row %2d in the slicer stacks " 2084 "(slice sky numbers %02d, %02d, %02d, %02d), at " 2085 "least one of the two middle stacks (%s/%s) is " 2086 "missing", ifu, nslice - nsoff,
2087 nslice - nsoff, nslice, nslice + nsoff, nslice + 2*nsoff,
2088 i3 < 0 ?
"left" :
"-", i2 < 0 ?
"right" :
"-");
2089 cpl_error_set_message(__func__, CPL_ERROR_DATA_NOT_FOUND,
"%s", msg);
2090 cpl_msg_error(__func__,
"%s", msg);
2092 cpl_table_dump(ts, 0, 10000, stdout);
2093 cpl_table_delete(ts);
2097 double *xrel = cpl_table_get_data_double(ts,
"xrel"),
2098 *xrerr = cpl_table_get_data_double(ts,
"xrelerr"),
2099 *width = cpl_table_get_data_double(ts, MUSE_GEOTABLE_WIDTH),
2100 *werr = cpl_table_get_data_double(ts, MUSE_GEOTABLE_WIDTH
"err"),
2101 *pdxl = cpl_table_get_data_double(ts,
"dxl"),
2102 *pdxr = cpl_table_get_data_double(ts,
"dxr");
2104 cpl_table_duplicate_column(ts,
"width_mm",
2105 ts, MUSE_GEOTABLE_WIDTH);
2106 cpl_table_multiply_scalar(ts,
"width_mm", 1. / kScaleX);
2107 cpl_table_set_column_unit(ts,
"width_mm",
"mm");
2108 cpl_table_duplicate_column(ts,
"widtherr_mm",
2109 ts, MUSE_GEOTABLE_WIDTH
"err");
2110 cpl_table_multiply_scalar(ts,
"widtherr_mm", 1. / kScaleX);
2111 cpl_table_set_column_unit(ts,
"widtherr_mm",
"mm");
2112 double *wmm = cpl_table_get_data_double(ts,
"width_mm"),
2113 *werrmm = cpl_table_get_data_double(ts,
"widtherr_mm");
2115 double cgap1 = (3. * kMuseCUmpmDX / fmaskrot
2116 - (xrel[i3] + wmm[i3] / 2. + wmm[i2] / 2. - xrel[i2]))
2118 cgerr = sqrt(pow(xrerr[i3], 2) + pow(xrerr[i2], 2) +
2119 pow(werrmm[i3] / 2., 2) + pow(werrmm[i2] / 2., 2))
2121 cgap2 = kMuseCUmpmDX * (1. - pdxl[i3] - pdxr[i2])
2123 cgap = stdgap ? cgap1 : cgap2;
2125 cpl_msg_debug(__func__,
"cgap: %f, %f +/- %f", cgap1, cgap2, cgerr);
2129 if (cgap < 0 || cgap > 0.5) {
2130 cpl_msg_debug(__func__,
"For IFU %2hhu / row %2d in the slicer stacks " 2131 "(slice sky numbers %02d, %02d, %02d, %02d), the central " 2132 "gap is unlikely (%f), reset to %.2f pix", ifu,
2133 nslice - nsoff, nslice - nsoff, nslice, nslice + nsoff,
2134 nslice + 2*nsoff, cgap, kMuseGeoMiddleGap);
2135 cgerr += sqrt(fabs(cgap));
2136 cgap = kMuseGeoMiddleGap;
2140 cpl_table_set_double(ts, MUSE_GEOTABLE_X, i3, -(cgap / 2. + width[i3] / 2.));
2141 cpl_table_set_double(ts, MUSE_GEOTABLE_X, i2, cgap / 2. + width[i2] / 2.);
2142 cpl_table_set_double(ts, MUSE_GEOTABLE_X
"err", i3,
2143 sqrt(cgerr*cgerr + werr[i3]*werr[i3]) / 2.);
2144 cpl_table_set_double(ts, MUSE_GEOTABLE_X
"err", i2,
2145 sqrt(cgerr*cgerr + werr[i2]*werr[i2]) / 2.);
2147 double lgap = NAN, lgerr = NAN;
2151 lgerr = sqrt(pow(xrerr[i4], 2) + pow(xrerr[i3], 2) +
2152 pow(werrmm[i4] / 2., 2) + pow(werrmm[i3] / 2., 2))
2154 double lgap1 = (3. * kMuseCUmpmDX / fmaskrot
2155 - (xrel[i4] + wmm[i4] / 2. + wmm[i3] / 2. - xrel[i3]))
2157 lgap2 = kMuseCUmpmDX * (1. - pdxl[i4] - pdxr[i3])
2159 lgap = stdgap ? lgap1 : lgap2;
2161 cpl_msg_debug(__func__,
"lgap: %f, %f +/- %f", lgap1, lgap2, lgerr);
2163 if (lgap < 0 || lgap > 0.5) {
2164 cpl_msg_debug(__func__,
"For IFU %2hhu / row %2d in the slicer stacks" 2165 " (slice sky numbers %02d, %02d, %02d, %02d), the left " 2166 "gap is unlikely (%f), reset to %.2f pix", ifu,
2167 nslice - nsoff, nslice - nsoff, nslice, nslice + nsoff,
2168 nslice + 2*nsoff, lgap, kMuseGeoOuterGap);
2169 lgerr += sqrt(fabs(lgap));
2170 lgap = kMuseGeoOuterGap;
2172 cpl_table_set_double(ts, MUSE_GEOTABLE_X, i4,
2173 -(cgap / 2. + width[i3] + lgap + width[i4] / 2.));
2174 cpl_table_set_double(ts, MUSE_GEOTABLE_X
"err", i4,
2175 sqrt(cgerr*cgerr / 4. + werr[i3]*werr[i3]
2176 + lgerr*lgerr + werr[i4]*werr[i4] / 4.));
2178 cpl_error_set_message(__func__, CPL_ERROR_DATA_NOT_FOUND,
"For IFU %2hhu /" 2179 " row %2d in the slicer stacks (slice sky numbers" 2180 " %02d, %02d, %02d, %02d), the leftmost stack is " 2181 "missing", ifu, nslice - nsoff,
2182 nslice - nsoff, nslice, nslice + nsoff, nslice + 2*nsoff);
2185 double rgap = NAN, rgerr = NAN;
2188 rgerr = sqrt(pow(xrerr[i2], 2) + pow(xrerr[i1], 2) +
2189 pow(werrmm[i2] / 2., 2) + pow(werrmm[i1] / 2., 2))
2191 double rgap1 = (3. * kMuseCUmpmDX / fmaskrot
2192 - (xrel[i2] + wmm[i2] / 2. + wmm[i1] / 2. - xrel[i1]))
2194 rgap2 = kMuseCUmpmDX * (1. - pdxl[i2] - pdxr[i1])
2196 rgap = stdgap ? rgap1 : rgap2;
2198 cpl_msg_debug(__func__,
"rgap: %f, %f +/- %f", rgap1, rgap2, rgerr);
2200 if (rgap < 0 || rgap > 0.5) {
2201 cpl_msg_debug(__func__,
"For IFU %2hhu / row %2d in the slicer stacks" 2202 " (slice sky numbers %02d, %02d, %02d, %02d), the right" 2203 " gap is unlikely (%f), reset to %.2f pix", ifu,
2204 nslice - nsoff, nslice - nsoff, nslice, nslice + nsoff,
2205 nslice + 2*nsoff, rgap, kMuseGeoOuterGap);
2206 rgerr += sqrt(fabs(rgap));
2207 rgap = kMuseGeoOuterGap;
2209 cpl_table_set_double(ts, MUSE_GEOTABLE_X, i1,
2210 cgap / 2. + width[i2] + rgap + width[i1] / 2.);
2211 cpl_table_set_double(ts, MUSE_GEOTABLE_X
"err", i1,
2212 sqrt(cgerr*cgerr / 4. + werr[i2]*werr[i2]
2213 + rgerr*rgerr + werr[i1]*werr[i1] / 4.));
2215 cpl_error_set_message(__func__, CPL_ERROR_DATA_NOT_FOUND,
"For IFU %2hhu /" 2216 " row %2d in the slicer stacks (slice sky numbers" 2217 " %02d, %02d, %02d, %02d), the rightmost stack is" 2218 " missing", ifu, nslice - nsoff,
2219 nslice - nsoff, nslice, nslice + nsoff, nslice + 2*nsoff);
2221 cpl_msg_debug(__func__,
"IFU %2hhu row %2d gaps (slice sky numbers %02d, " 2222 "%02d, %02d, %02d): central %.3f +/- %.3f pix, left %.3f +/- " 2223 "%.3f pix, right %.3f +/- %.3f pix", ifu, nslice - nsoff,
2224 nslice - nsoff, nslice, nslice + nsoff, nslice + 2*nsoff,
2225 cgap, cgerr, lgap, lgerr, rgap, rgerr);
2228 cpl_table_erase_column(ts,
"width_mm");
2229 cpl_table_erase_column(ts,
"widtherr_mm");
2233 cpl_table_erase_selected(gt->
table);
2234 cpl_table_insert(gt->
table, ts, cpl_table_get_nrow(gt->
table));
2235 cpl_table_delete(ts);
2239 cpl_propertylist *order = cpl_propertylist_new();
2240 cpl_propertylist_append_bool(order,
"stack", CPL_TRUE);
2241 cpl_propertylist_append_bool(order,
"SliceSky", CPL_FALSE);
2242 cpl_table_sort(gt->
table, order);
2243 cpl_propertylist_delete(order);
2245 printf(
"intermediate result: only the y position is still missing:\n");
2246 cpl_table_dump(gt->
table, 0, 1000000, stdout);
2267 static unsigned char 2268 muse_geo_select_reference(
const muse_geo_table *aGeo,
unsigned short *aSlice)
2270 unsigned char ifu = 0;
2271 unsigned short slice = 0;
2273 cpl_table *geo = cpl_table_duplicate(aGeo->
table);
2274 cpl_table_unselect_all(geo);
2275 cpl_table_or_selected_int(geo, MUSE_GEOTABLE_FIELD, CPL_EQUAL_TO, 12);
2276 cpl_table_and_selected_int(geo, MUSE_GEOTABLE_SKY, CPL_EQUAL_TO, 24);
2277 int nsel = cpl_table_count_selected(geo);
2282 unsigned char testifu = 13,
2284 short testoffset = 1;
2285 while (ifu == 0 && slice == 0) {
2286 cpl_table_unselect_all(geo);
2287 cpl_table_or_selected_int(geo, MUSE_GEOTABLE_FIELD, CPL_EQUAL_TO, testifu);
2288 cpl_table_and_selected_int(geo, MUSE_GEOTABLE_SKY, CPL_EQUAL_TO, testslice);
2289 nsel = cpl_table_count_selected(geo);
2294 testifu += testoffset;
2295 if (testifu > kMuseNumIFUs) {
2300 cpl_table_delete(geo);
2362 cpl_ensure_code(aGeo && aGeo->
table && aSpots, CPL_ERROR_NULL_INPUT);
2363 cpl_size nrow = cpl_table_get_nrow(aGeo->
table);
2364 cpl_ensure_code(nrow >= 50, CPL_ERROR_ILLEGAL_INPUT);
2366 CPL_ERROR_INCOMPATIBLE_INPUT);
2368 CPL_ERROR_INCOMPATIBLE_INPUT);
2369 const unsigned char ifu1 = cpl_table_get_column_min(aGeo->
table, MUSE_GEOTABLE_FIELD),
2370 ifu2 = cpl_table_get_column_max(aGeo->
table, MUSE_GEOTABLE_FIELD);
2371 if (!ifu1 || !ifu2 || ifu1 == ifu2) {
2372 return cpl_error_set_message(__func__, CPL_ERROR_DATA_NOT_FOUND,
2373 "input geometry table contains data of IFUs " 2374 "%2hhu .. %2hhu", ifu1, ifu2);
2376 cpl_ensure_code(cpl_table_get_column_stdev(aSpots,
"ScaleFOV") < 1e-10,
2377 CPL_ERROR_ILLEGAL_INPUT);
2378 cpl_array *hoffsets = NULL;
2379 if (getenv(
"MUSE_GEOMETRY_HORI_OFFSETS")) {
2381 getenv(
"MUSE_GEOMETRY_HORI_OFFSETS"),
",");
2382 cpl_msg_warning(__func__,
"Overriding horizontal offsets, found %" 2383 CPL_SIZE_FORMAT
" values!", cpl_array_get_size(hoffsets));
2385 const double kScaleX = kMuseTypicalCubeSizeX * aGeo->
scale / 60.;
2388 cpl_table_new_column(aSpots, MUSE_GEOTABLE_SKY, CPL_TYPE_INT);
2389 cpl_table_new_column(aSpots,
"stack", CPL_TYPE_INT);
2390 cpl_size ispot, nspots = cpl_table_get_nrow(aSpots);
2391 for (ispot = 0; ispot < nspots; ispot++) {
2392 int nslice = cpl_table_get_int(aSpots,
"SliceCCD", ispot, NULL);
2393 cpl_table_set(aSpots, MUSE_GEOTABLE_SKY, ispot, kMuseGeoSliceSky[nslice - 1]);
2394 unsigned char stack = nslice <= 12 ? 4 : (nslice <= 24 ? 3 : (nslice <= 36 ? 2 : 1));
2395 cpl_table_set_int(aSpots,
"stack", ispot, stack);
2400 const unsigned short nsoff = kMuseSlicesPerCCD / 4;
2401 cpl_table_unselect_all(aSpots);
2402 cpl_table_or_selected_int(aSpots, MUSE_GEOTABLE_SKY, CPL_EQUAL_TO, 24 - nsoff + 1);
2403 cpl_table_or_selected_int(aSpots, MUSE_GEOTABLE_SKY, CPL_EQUAL_TO, 24);
2404 cpl_table_or_selected_int(aSpots, MUSE_GEOTABLE_SKY, CPL_EQUAL_TO, 24 + 1);
2405 cpl_table_or_selected_int(aSpots, MUSE_GEOTABLE_SKY, CPL_EQUAL_TO, 24 + nsoff);
2407 cpl_msg_debug(__func__,
"All top/bottom spots selected: %" 2408 CPL_SIZE_FORMAT, cpl_table_count_selected(aSpots));
2411 cpl_table_and_selected_double(aSpots,
"flux", CPL_NOT_LESS_THAN, 500.);
2413 cpl_msg_debug(__func__,
"All bright top/bottom spots selected: %" 2414 CPL_SIZE_FORMAT, cpl_table_count_selected(aSpots));
2417 cpl_table_and_selected_int(aSpots,
"SpotNo", CPL_EQUAL_TO, 2);
2419 cpl_msg_debug(__func__,
"All bright and central top/bottom spots selected: %" 2420 CPL_SIZE_FORMAT, cpl_table_count_selected(aSpots));
2422 cpl_table *tbspots = cpl_table_extract_selected(aSpots);
2423 cpl_msg_debug(__func__,
"All spots: %"CPL_SIZE_FORMAT
", top/bottom spots to " 2424 "work with: %"CPL_SIZE_FORMAT, nspots, cpl_table_get_nrow(tbspots));
2425 nspots = cpl_table_get_nrow(tbspots);
2428 int *ifus = cpl_table_get_data_int(aGeo->
table, MUSE_GEOTABLE_FIELD),
2429 *slices = cpl_table_get_data_int(aGeo->
table, MUSE_GEOTABLE_SKY);
2430 double *xpos = cpl_table_get_data_double(aGeo->
table, MUSE_GEOTABLE_X),
2431 *xerr = cpl_table_get_data_double(aGeo->
table, MUSE_GEOTABLE_X
"err"),
2432 *xrel = cpl_table_get_data_double(aGeo->
table,
"xrel");
2437 for (nifu = ifu1; nifu < ifu2; nifu++) {
2439 cpl_table_unselect_all(tbspots);
2440 cpl_table_or_selected_int(tbspots, MUSE_GEOTABLE_SKY, CPL_EQUAL_TO, 24);
2441 cpl_table_or_selected_int(tbspots, MUSE_GEOTABLE_SKY, CPL_EQUAL_TO, 24 + nsoff);
2442 cpl_table_and_selected_int(tbspots, MUSE_GEOTABLE_FIELD, CPL_EQUAL_TO, nifu);
2443 cpl_table *xspots = cpl_table_extract_selected(tbspots);
2445 cpl_table_unselect_all(tbspots);
2446 cpl_table_or_selected_int(tbspots, MUSE_GEOTABLE_SKY, CPL_EQUAL_TO, 24 - nsoff + 1);
2447 cpl_table_or_selected_int(tbspots, MUSE_GEOTABLE_SKY, CPL_EQUAL_TO, 24 + 1);
2448 cpl_table_and_selected_int(tbspots, MUSE_GEOTABLE_FIELD, CPL_EQUAL_TO, nifu + 1);
2449 cpl_table *tmp = cpl_table_extract_selected(tbspots);
2450 cpl_table_insert(xspots, tmp, cpl_table_get_nrow(xspots));
2451 cpl_table_delete(tmp);
2452 int nxspots = cpl_table_get_nrow(xspots);
2455 cpl_propertylist *order = cpl_propertylist_new();
2456 cpl_propertylist_append_bool(order,
"lambda", CPL_FALSE);
2457 cpl_propertylist_append_bool(order,
"VPOS", CPL_FALSE);
2458 cpl_propertylist_append_bool(order,
"SliceSky", CPL_FALSE);
2459 cpl_table_sort(xspots, order);
2460 cpl_propertylist_delete(order);
2462 printf(
"IFU %2hhu:\n", nifu);
2463 cpl_table_dump(xspots, 0, nxspots, stdout);
2468 cpl_array *apos2 = cpl_array_new(nxspots, CPL_TYPE_DOUBLE),
2469 *apos3 = cpl_array_new(nxspots, CPL_TYPE_DOUBLE);
2470 int idx2 = 0, idx3 = 0;
2473 cpl_vector *vtmp = cpl_vector_wrap(nxspots,
2474 cpl_table_get_data_double(xspots,
"lambda")),
2476 cpl_vector_unwrap(vtmp);
2477 vtmp = cpl_vector_wrap(nxspots, cpl_table_get_data_double(xspots,
"VPOS"));
2479 cpl_vector_unwrap(vtmp);
2480 int ilambda, nlambda = cpl_vector_get_size(lambdas);
2481 for (ilambda = 0; ilambda < nlambda; ilambda++) {
2482 double lambda = cpl_vector_get(lambdas, ilambda);
2484 int ipos, npos = cpl_vector_get_size(positions);
2485 for (ipos = 0; ipos < npos; ipos++) {
2486 double vpos = cpl_vector_get(positions, ipos);
2489 for (nstack = 3; nstack >= 2; nstack--) {
2490 cpl_table_select_all(xspots);
2491 cpl_table_and_selected_double(xspots,
"lambda", CPL_EQUAL_TO, lambda);
2492 cpl_table_and_selected_double(xspots,
"VPOS", CPL_EQUAL_TO, vpos);
2493 cpl_table_and_selected_int(xspots,
"stack", CPL_EQUAL_TO, nstack);
2494 int nsel = cpl_table_count_selected(xspots);
2497 printf(
"IFU %2hhu, lambda = %f, VPOS = %f, stack = %d: %d selected rows!\n",
2498 nifu, lambda, vpos, nstack, nsel);
2503 cpl_table *common = cpl_table_extract_selected(xspots);
2504 order = cpl_propertylist_new();
2505 cpl_propertylist_append_bool(order,
"SubField", CPL_FALSE);
2506 cpl_propertylist_append_bool(order,
"SliceSky", CPL_FALSE);
2507 cpl_table_sort(common, order);
2508 cpl_propertylist_delete(order);
2509 int nselthis = cpl_table_and_selected_int(common,
"SubField",
2510 CPL_EQUAL_TO, nifu);
2511 cpl_table_select_all(common);
2512 int nselnext = cpl_table_and_selected_int(common,
"SubField",
2513 CPL_EQUAL_TO, nifu + 1);
2514 if (nselthis != 1 || nselnext != 1) {
2516 printf(
"\nIFU %2hhu, lambda = %f, VPOS = %f, stack = %d: " 2517 "%d rows of IFU %2hhu, %d rows of IFU %2d!\n",
2518 nifu, lambda, vpos, nstack, nselthis, nifu, nselnext,
2520 cpl_table_dump(common, 0, 100000, stdout);
2522 cpl_table_delete(common);
2528 double xdiff1 = cpl_table_get(common,
"dxcen", 0, NULL),
2529 xdiff2 = cpl_table_get(common,
"dxcen", 1, NULL),
2530 twidth1 = cpl_table_get(common,
"twidth", 0, NULL),
2531 twidth2 = cpl_table_get(common,
"twidth", 1, NULL);
2532 cpl_table_unselect_all(aGeo->
table);
2533 cpl_table_or_selected_int(aGeo->
table, MUSE_GEOTABLE_FIELD, CPL_EQUAL_TO,
2534 cpl_table_get_int(common,
"SubField", 0, NULL));
2535 cpl_table_or_selected_int(aGeo->
table, MUSE_GEOTABLE_SKY, CPL_EQUAL_TO,
2536 cpl_table_get_int(common,
"SliceSky", 0, NULL));
2537 cpl_array *sel = cpl_table_where_selected(aGeo->
table);
2538 double width1 = cpl_table_get_double(aGeo->
table, MUSE_GEOTABLE_WIDTH,
2539 cpl_array_get(sel, 0, NULL), NULL);
2540 cpl_array_delete(sel);
2541 cpl_table_unselect_all(aGeo->
table);
2542 cpl_table_or_selected_int(aGeo->
table, MUSE_GEOTABLE_FIELD, CPL_EQUAL_TO,
2543 cpl_table_get_int(common,
"SubField", 1, NULL));
2544 cpl_table_or_selected_int(aGeo->
table, MUSE_GEOTABLE_SKY, CPL_EQUAL_TO,
2545 cpl_table_get_int(common,
"SliceSky", 1, NULL));
2546 sel = cpl_table_where_selected(aGeo->
table);
2547 double width2 = cpl_table_get_double(aGeo->
table, MUSE_GEOTABLE_WIDTH,
2548 cpl_array_get(sel, 0, NULL), NULL);
2549 cpl_array_delete(sel);
2551 printf(
"\nIFU %2hhu, lambda = %f, VPOS = %f, stack + %d, twidths: %f / %f, geowidths: %f / %f:\n",
2552 nifu, lambda, vpos, nstack, twidth1, twidth2, width1, width2);
2553 cpl_table_dump(common, 0, 100000, stdout);
2554 printf(
"==> xdiff = %f pix, %f pix (corrected)\n", xdiff1 - xdiff2,
2555 xdiff1 * width1 / twidth1 - xdiff2 * width2 / twidth2);
2558 double xdiff = xdiff1 * width1 / twidth1 - xdiff2 * width2 / twidth2;
2560 cpl_array_set(apos3, idx3++, xdiff);
2563 cpl_array_set(apos2, idx2++, xdiff);
2565 cpl_table_delete(common);
2571 #define HIST_BIN_WIDTH 0.1 2577 cpl_bivector_delete(hist2);
2578 cpl_bivector_delete(hist3);
2579 cpl_array *apos = cpl_array_new(0, cpl_array_get_type(apos2));
2580 cpl_array_insert(apos, apos2, 0);
2581 cpl_array_insert(apos, apos3, cpl_array_get_size(apos));
2583 char *fn = cpl_sprintf(
"apos2_%02hhuto%02hhu.pos", nifu, nifu+1);
2584 FILE *fp = fopen(fn,
"w");
2585 fprintf(fp,
"# apos2 %2hhu to %2hhu:\n#", nifu, nifu+1);
2586 cpl_array_dump(apos2, 0, 1000000, fp);
2589 fn = cpl_sprintf(
"apos3_%02hhuto%02hhu.pos", nifu, nifu+1);
2590 fp = fopen(fn,
"w");
2591 fprintf(fp,
"# apos3 %2hhu to %2hhu:\n#", nifu, nifu+1);
2592 cpl_array_dump(apos3, 0, 1000000, fp);
2595 fn = cpl_sprintf(
"apos_%02hhuto%02hhu.pos", nifu, nifu+1);
2596 fp = fopen(fn,
"w");
2597 fprintf(fp,
"# apos %2hhu to %2hhu:\n#", nifu, nifu+1);
2598 cpl_array_dump(apos, 0, 1000000, fp);
2604 double mean2 = cpl_array_get_mean(apos2),
2605 stdev2 = cpl_array_get_stdev(apos2),
2606 var2 = stdev2 * stdev2,
2607 mean3 = cpl_array_get_mean(apos3),
2608 stdev3 = cpl_array_get_stdev(apos3),
2609 var3 = stdev3 * stdev3,
2610 mean = (mean2 + mean3) / 2.,
2611 wmean = (mean2 / var2 + mean3 / var3) / (1. / var2 + 1. / var3),
2612 wstdev = sqrt(1. / (1. / var2 + 1. / var3));
2613 cpl_array_delete(apos2);
2614 cpl_array_delete(apos3);
2615 cpl_msg_debug(__func__,
"IFU %2hhu to IFU %2d: %6.3f +/- %5.3f pix " 2616 "[stack 3: %6.3f +/- %5.3f, stack2: %6.3f +/- %5.3f ==> %6.3f" 2617 " or %6.3f +/- %5.3f (%6.3f)]", nifu, (
int)nifu + 1, wmean, wstdev,
2618 mean3, stdev3, mean2, stdev2, mean,
2619 cpl_array_get_mean(apos), cpl_array_get_stdev(apos),
2620 cpl_array_get_median(apos));
2621 cpl_array_delete(apos);
2622 cpl_vector_delete(lambdas);
2623 cpl_vector_delete(positions);
2624 cpl_table_delete(xspots);
2627 for (irow = 0; irow < nrow; irow++) {
2628 if (ifus[irow] >= nifu + 1) {
2629 xpos[irow] -= wmean;
2630 xerr[irow] = sqrt(xerr[irow]*xerr[irow] + wstdev*wstdev);
2631 xrel[irow] += wmean / kScaleX;
2635 cpl_table_delete(tbspots);
2638 for (nifu = ifu1; nifu < ifu2; nifu++) {
2641 if (cpl_array_get_size(hoffsets) >= nifu) {
2642 const char *sdiff = cpl_array_get_string(hoffsets, nifu - 1);
2644 xdiff = atof(sdiff);
2647 cpl_msg_debug(__func__,
"Subtracting extra %7.4f pix from IFU %d onwards",
2648 xdiff, (
int)nifu + 1);
2649 for (irow = 0; irow < nrow; irow++) {
2650 if (ifus[irow] >= nifu + 1) {
2651 xpos[irow] -= xdiff;
2658 printf(
"%s after correcting %d:\n", __func__, (
int)nifu + 1);
2659 cpl_table_dump(aGeo->
table, 0, 1000000, stdout);
2663 cpl_array_delete(hoffsets);
2668 unsigned short sliceref;
2669 const unsigned char ifuref = muse_geo_select_reference(aGeo, &sliceref);
2670 for (irow = 0; irow < nrow; irow++) {
2671 if (ifus[irow] == ifuref &&
2672 (slices[irow] == sliceref || slices[irow] == sliceref + 12)) {
2677 cpl_msg_debug(__func__,
"Reference point (IFU %2hhu, SliceSky %2hu/%2d) " 2678 "currently centered at %f pix, correcting this offset", ifuref,
2679 sliceref, (
int)sliceref + 12, xref);
2680 cpl_table_subtract_scalar(aGeo->
table, MUSE_GEOTABLE_X, xref);
2682 return CPL_ERROR_NONE;
2739 cpl_ensure(aGeo && aGeo->
table, CPL_ERROR_NULL_INPUT, NULL);
2740 int nrow = cpl_table_get_nrow(aGeo->
table);
2741 cpl_ensure(nrow >= 10, CPL_ERROR_ILLEGAL_INPUT, NULL);
2743 CPL_ERROR_INCOMPATIBLE_INPUT, NULL);
2744 int spotmin = cpl_table_get_column_min(aGeo->
table,
"spot"),
2745 spotmax = cpl_table_get_column_max(aGeo->
table,
"spot");
2746 cpl_ensure(spotmin == spotmax, CPL_ERROR_ILLEGAL_INPUT, NULL);
2747 const double kScaleY = 60. / aGeo->
scale / kMuseTypicalCubeSizeY;
2751 cpl_table_erase_column(gt->
table,
"dxerr");
2752 cpl_table_erase_column(gt->
table,
"dxl");
2753 cpl_table_erase_column(gt->
table,
"dxr");
2754 cpl_table_erase_column(gt->
table,
"xc");
2755 cpl_table_erase_column(gt->
table,
"yc");
2756 cpl_table_erase_column(gt->
table,
"dx");
2757 cpl_table_erase_column(gt->
table,
"flux");
2758 cpl_table_erase_column(gt->
table,
"lambda");
2762 cpl_table_fill_column_window_double(gt->
table, MUSE_GEOTABLE_Y, 0, nrow, 0.);
2763 cpl_table_add_columns(gt->
table, MUSE_GEOTABLE_Y,
"vpos");
2764 cpl_table_set_column_unit(gt->
table, MUSE_GEOTABLE_Y,
"mm");
2765 cpl_table_fill_column_window_double(gt->
table, MUSE_GEOTABLE_Y
"err", 0, nrow, 0.);
2766 cpl_table_add_columns(gt->
table, MUSE_GEOTABLE_Y
"err",
"vposerr");
2767 cpl_table_set_column_unit(gt->
table, MUSE_GEOTABLE_Y
"err",
"mm");
2769 printf(
"\nfull geometry table, with absolute \"y\" [mm]:\n");
2770 cpl_table_dump(gt->
table, 0, nrow, stdout);
2774 double maskangle = 0., fmaskrot = 1.;
2775 if (getenv(
"MUSE_GEOMETRY_MASK_ROTATION")) {
2776 maskangle = atof(getenv(
"MUSE_GEOMETRY_MASK_ROTATION"));
2777 fmaskrot = cos(maskangle * CPL_MATH_RAD_DEG);
2778 cpl_msg_warning(__func__,
"Adapting to global mask rotation of %.4f deg " 2779 "(cos = %.4e)", maskangle, fmaskrot);
2781 double pinholedy = kMuseCUmpmDY;
2782 if (getenv(
"MUSE_GEOMETRY_PINHOLE_DY")) {
2783 pinholedy = atof(getenv(
"MUSE_GEOMETRY_PINHOLE_DY"));
2784 cpl_msg_info(__func__,
"Using pinhole y distance of %f mm (instead of " 2785 "%f mm)", pinholedy, kMuseCUmpmDY);
2787 cpl_boolean printgoing = getenv(
"MUSE_DEBUG_GEO_VERTICAL")
2788 && atoi(getenv(
"MUSE_DEBUG_GEO_VERTICAL")) > 0;
2790 unsigned short middleSlice;
2791 const unsigned char middleIFU = muse_geo_select_reference(aGeo, &middleSlice);
2792 cpl_msg_info(__func__,
"Using IFU %2hhu / SliceSky %2hu as reference",
2793 middleIFU, middleSlice);
2794 double ycentral = NAN,
2795 ymax = -DBL_MAX, ymin = DBL_MAX;
2797 for (irow = 0; irow < nrow; irow++) {
2798 unsigned char ifu = cpl_table_get_int(gt->
table, MUSE_GEOTABLE_FIELD, irow, NULL);
2799 unsigned short slice = cpl_table_get_int(gt->
table, MUSE_GEOTABLE_SKY, irow, NULL);
2800 double y = cpl_table_get_double(gt->
table, MUSE_GEOTABLE_Y, irow, NULL);
2801 if (ifu == middleIFU && slice == middleSlice) {
2812 if (!isfinite(ycentral)) {
2813 ycentral = (ymin + ymax) / 2.;
2814 cpl_msg_info(__func__,
"Averaged the y range to ycentral = %f pix", ycentral);
2816 cpl_msg_info(__func__,
"Found IFU %2hhu, slice %2hu, using its y value as " 2817 "ycentral = %f pix", middleIFU, middleSlice, ycentral);
2819 cpl_table_subtract_scalar(gt->
table, MUSE_GEOTABLE_Y, ycentral);
2820 const unsigned short nsoff = kMuseSlicesPerCCD / 4,
2821 nstack[] = { 3, 2, 4, 1, 0 };
2823 for (i = 0; nstack[i] > 0; i++) {
2825 cpl_table_unselect_all(gt->
table);
2826 cpl_table_or_selected_int(gt->
table,
"stack", CPL_EQUAL_TO, nstack[i]);
2827 cpl_table *tstack = cpl_table_extract_selected(gt->
table);
2828 int ntsrow = cpl_table_get_nrow(tstack);
2830 cpl_propertylist *sorting = cpl_propertylist_new();
2831 cpl_propertylist_append_bool(sorting, MUSE_GEOTABLE_FIELD, CPL_FALSE);
2832 cpl_propertylist_append_bool(sorting, MUSE_GEOTABLE_SKY, CPL_FALSE);
2833 cpl_table_sort(tstack, sorting);
2834 cpl_propertylist_delete(sorting);
2836 const unsigned short refslice = middleSlice - (nstack[i] - 3) * nsoff;
2837 cpl_table_select_all(tstack);
2838 cpl_table_and_selected_int(tstack, MUSE_GEOTABLE_FIELD, CPL_EQUAL_TO, middleIFU);
2839 cpl_table_and_selected_int(tstack, MUSE_GEOTABLE_SKY, CPL_EQUAL_TO, refslice);
2840 if (cpl_table_count_selected(tstack) <= 0) {
2841 char *msg = cpl_sprintf(
"reference slice %2hu of reference IFU %2hhu not " 2842 "found in slicer stack %hu!", refslice,
2843 middleIFU, nstack[i]);
2844 cpl_msg_error(__func__,
"%s", msg);
2845 cpl_error_set_message(__func__, CPL_ERROR_DATA_NOT_FOUND,
"%s", msg);
2847 cpl_table_delete(tstack);
2850 cpl_table_erase_selected(gt->
table);
2851 cpl_array *asel = cpl_table_where_selected(tstack);
2852 int iref = cpl_array_get(asel, 0, NULL);
2853 cpl_array_delete(asel);
2854 double yref = cpl_table_get_double(tstack, MUSE_GEOTABLE_Y, iref, NULL);
2855 cpl_msg_info(__func__,
"reference slice %2hu of reference IFU %2hhu found at row" 2856 " %d in slicer stack %hu!", refslice, middleIFU, iref, nstack[i]);
2860 if (fabs(yref) > pinholedy / 2.) {
2861 cpl_msg_info(__func__,
"%s vertical pinhole distance (%f) to " 2862 "recenter stack %hu", yref < 0. ?
"adding" :
"subtracting",
2863 pinholedy, nstack[i]);
2864 cpl_table_add_scalar(tstack, MUSE_GEOTABLE_Y, yref < 0. ? pinholedy : -pinholedy);
2865 yref = cpl_table_get_double(tstack, MUSE_GEOTABLE_Y, iref, NULL);
2869 cpl_table_new_column(tstack,
"dy", CPL_TYPE_DOUBLE);
2870 cpl_table_set_column_unit(tstack,
"dy",
"mm");
2871 cpl_table_set_column_format(tstack,
"dy",
"%9.5f");
2872 cpl_table_set_double(tstack,
"dy", iref, yref);
2874 cpl_table_duplicate_column(tstack,
"ycopy", tstack, MUSE_GEOTABLE_Y);
2875 cpl_table_set_double(tstack, MUSE_GEOTABLE_Y, iref, yref);
2876 double yprev = cpl_table_get_double(tstack,
"ycopy", iref, NULL),
2877 dyoffset = pinholedy * fmaskrot,
2880 for (irow = iref - 1, iprev = iref; irow >= 0; iprev = irow, irow--) {
2882 int difu = cpl_table_get_int(tstack, MUSE_GEOTABLE_FIELD, iprev, NULL)
2883 - cpl_table_get_int(tstack, MUSE_GEOTABLE_FIELD, irow, NULL),
2884 dslice = cpl_table_get_int(tstack, MUSE_GEOTABLE_SKY, iprev, NULL)
2885 - cpl_table_get_int(tstack, MUSE_GEOTABLE_SKY, irow, NULL);
2889 double y = cpl_table_get_double(tstack,
"ycopy", irow, NULL),
2891 dexpect = dslice * kScaleY,
2892 dratio = dy / dexpect,
2895 dycor = 2 * dyoffset;
2896 }
else if (dratio < -2.) {
2898 }
else if (dratio > 5.) {
2902 cpl_msg_debug(__func__,
"going back: %d %d, %f %f --> diff %9.6f " 2903 "expected %f ratio %9.6f --> dy = %f", difu, dslice,
2904 yprev, y, dy, dexpect, dratio, dy + dycor);
2907 if (abs(difu) > 1) {
2908 double gap = abs(difu) * kMuseGeoIFUVGap;
2910 cpl_msg_warning(__func__,
"%d missing IFUs, guessing distance as %f",
2911 abs(difu) - 1, gap);
2913 cpl_table_set_double(tstack,
"dy", irow, dy);
2915 cpl_table_set_double(tstack, MUSE_GEOTABLE_Y, irow, ysum);
2920 yprev = cpl_table_get_double(tstack,
"ycopy", iref, NULL);
2922 for (irow = iref + 1, iprev = iref; irow < ntsrow; iprev = irow, irow++) {
2923 int difu = cpl_table_get_int(tstack, MUSE_GEOTABLE_FIELD, iprev, NULL)
2924 - cpl_table_get_int(tstack, MUSE_GEOTABLE_FIELD, irow, NULL),
2925 dslice = cpl_table_get_int(tstack, MUSE_GEOTABLE_SKY, iprev, NULL)
2926 - cpl_table_get_int(tstack, MUSE_GEOTABLE_SKY, irow, NULL);
2930 double y = cpl_table_get_double(tstack,
"ycopy", irow, NULL),
2932 dexpect = -dslice * kScaleY,
2933 dratio = dy / dexpect,
2936 dycor = -2 * dyoffset;
2937 }
else if (dratio > 2.) {
2939 }
else if (dratio < -5.) {
2943 cpl_msg_debug(__func__,
"going forward: %d %d, %f %f --> diff %9.6f " 2944 "expected %f ratio %9.6f --> dy = %f", difu, dslice,
2945 yprev, y, dy, dexpect, dratio, dy + dycor);
2948 if (abs(difu) > 1) {
2949 double gap = abs(difu) * kMuseGeoIFUVGap;
2951 cpl_msg_warning(__func__,
"%d missing IFUs, guessing distance as %f",
2952 abs(difu) - 1, gap);
2954 cpl_table_set_double(tstack,
"dy", irow, dy);
2956 cpl_table_set_double(tstack, MUSE_GEOTABLE_Y, irow, ysum);
2960 printf(
"\ngeometry table of slicer stack %hu, with \"dy\" [mm]:\n",
2962 cpl_table_dump(tstack, 0, nrow, stdout);
2966 cpl_table_erase_column(tstack,
"ycopy");
2967 cpl_table_erase_column(tstack,
"dy");
2969 cpl_table_insert(gt->
table, tstack, cpl_table_get_nrow(gt->
table));
2970 cpl_table_delete(tstack);
2980 cpl_msg_info(__func__,
"Correcting vertical position of top/bottom slices");
2982 for (ifu = 1; ifu <= kMuseNumIFUs; ifu++) {
2983 for (i = 0; nstack[i] > 0; i++) {
2984 cpl_table_unselect_all(gt->
table);
2985 cpl_table_or_selected_int(gt->
table,
"SubField", CPL_EQUAL_TO, ifu);
2986 cpl_table_and_selected_int(gt->
table,
"stack", CPL_EQUAL_TO, nstack[i]);
2987 if (!cpl_table_count_selected(gt->
table)) {
2990 cpl_table *tstack = cpl_table_extract_selected(gt->
table);
2991 int ntsrow = cpl_table_get_nrow(tstack);
2993 cpl_propertylist *sorting = cpl_propertylist_new();
2994 cpl_propertylist_append_bool(sorting, MUSE_GEOTABLE_SKY, CPL_FALSE);
2995 cpl_table_sort(tstack, sorting);
2996 cpl_propertylist_delete(sorting);
2999 unsigned short nslicetop = cpl_table_get_int(tstack, MUSE_GEOTABLE_SKY, 0, NULL),
3000 nslicebot = cpl_table_get_int(tstack, MUSE_GEOTABLE_SKY,
3002 cpl_boolean hastop = nslicetop % nsoff == 1,
3003 hasbot = nslicebot % nsoff == 0,
3004 haschanged = CPL_FALSE;
3006 printf(
"table of IFU %2hhu / SliceSky %2hu: %hu to %hu (%s, %s)\n",
3007 ifu, nstack[i], nslicetop, nslicebot,
3008 hastop ?
"has top" :
"does NOT have top",
3009 hasbot ?
"has bottom" :
"does NOT have bottom");
3010 cpl_table_dump(tstack, 0, 1000, stdout);
3014 cpl_vector *vvpos = cpl_vector_new(ntsrow - 3),
3015 *vverr = cpl_vector_new(ntsrow - 3);
3016 for (irow = 1; irow < ntsrow - 2; irow++) {
3017 double dy = fabs(cpl_table_get_double(tstack, MUSE_GEOTABLE_Y, irow + 1, NULL)
3018 - cpl_table_get_double(tstack, MUSE_GEOTABLE_Y, irow, NULL)),
3019 dye1 = cpl_table_get_double(tstack, MUSE_GEOTABLE_Y
"err", irow, NULL),
3020 dye2 = cpl_table_get_double(tstack, MUSE_GEOTABLE_Y
"err", irow + 1, NULL),
3021 dyerr = sqrt(dye1*dye1 + dye2*dye2);
3022 cpl_vector_set(vvpos, irow - 1, dy);
3023 cpl_vector_set(vverr, irow - 1, dyerr);
3027 double mean = cpl_vector_get_mean(vvpos),
3028 median = cpl_vector_get_median_const(vvpos),
3029 stdev = cpl_vector_get_stdev(vvpos),
3030 stdev2 = cpl_vector_get_mean(vverr);
3031 cpl_vector_delete(vvpos);
3032 cpl_vector_delete(vverr);
3033 cpl_msg_debug(__func__,
"dy of IFU %2hhu / SliceSky %2hu: %f +/- %f (%f) [+/- %f]",
3034 ifu, nstack[i], mean, stdev, median, stdev2);
3037 double ytop = cpl_table_get_double(tstack, MUSE_GEOTABLE_Y, 0, NULL),
3038 dyt = fabs(ytop - cpl_table_get_double(tstack, MUSE_GEOTABLE_Y, 1, NULL)),
3039 dyt1 = cpl_table_get_double(tstack, MUSE_GEOTABLE_Y
"err", 0, NULL),
3040 dyt2 = cpl_table_get_double(tstack, MUSE_GEOTABLE_Y
"err", 1, NULL),
3041 dyterr = sqrt(dyt1*dyt1 + dyt2*dyt2);
3042 cpl_msg_debug(__func__,
"dy of IFU %2hhu / SliceSky %2hu (top): %f +/- %f",
3043 ifu, nstack[i], dyt, dyterr);
3046 if (mean - dyt > sqrt(dyterr*dyterr + stdev2*stdev2)) {
3049 cpl_table_set_double(tstack, MUSE_GEOTABLE_Y, 0, ytop);
3050 haschanged = CPL_TRUE;
3052 printf(
"new top entry (+%f):\n", mean - dyt);
3053 cpl_table_dump(tstack, 0, 1, stdout);
3060 double ybot = cpl_table_get_double(tstack, MUSE_GEOTABLE_Y, ntsrow - 1, NULL),
3061 dyb = fabs(ybot - cpl_table_get_double(tstack, MUSE_GEOTABLE_Y, ntsrow - 2, NULL)),
3062 dyb1 = cpl_table_get_double(tstack, MUSE_GEOTABLE_Y
"err", ntsrow - 2, NULL),
3063 dyb2 = cpl_table_get_double(tstack, MUSE_GEOTABLE_Y
"err", ntsrow - 1, NULL),
3064 dyberr = sqrt(dyb1*dyb1 + dyb2*dyb2);
3065 cpl_msg_debug(__func__,
"dy of IFU %2hhu / SliceSky %2hu (bottom): %f +/- %f",
3066 ifu, nstack[i], dyb, dyberr);
3067 if (mean - dyb > sqrt(dyberr*dyberr + stdev2*stdev2)) {
3070 cpl_table_set_double(tstack, MUSE_GEOTABLE_Y, ntsrow - 1, ybot);
3071 haschanged = CPL_TRUE;
3073 printf(
"new bottom entry (-%f):\n", mean - dyb);
3074 cpl_table_dump(tstack, ntsrow - 1, 1, stdout);
3083 cpl_table_erase_selected(gt->
table);
3084 cpl_table_insert(gt->
table, tstack, cpl_table_get_nrow(gt->
table));
3086 cpl_table_delete(tstack);
3091 cpl_table_divide_scalar(gt->
table, MUSE_GEOTABLE_Y, kMuseSpaxelSizeY_WFM / aGeo->
scale);
3092 cpl_table_set_column_unit(gt->
table, MUSE_GEOTABLE_Y,
"pix");
3093 cpl_table_divide_scalar(gt->
table, MUSE_GEOTABLE_Y
"err", kMuseSpaxelSizeY_WFM / aGeo->
scale);
3094 cpl_table_set_column_unit(gt->
table, MUSE_GEOTABLE_Y
"err",
"pix");
3097 cpl_table_erase_column(gt->
table,
"spot");
3098 cpl_table_erase_column(gt->
table,
"xrel");
3099 cpl_table_erase_column(gt->
table,
"xrelerr");
3100 cpl_table_erase_column(gt->
table,
"vpos");
3101 cpl_table_erase_column(gt->
table,
"vposerr");
3102 cpl_table_erase_column(gt->
table,
"stack");
3144 cpl_ensure_code(aGeo && aGeo->
table, CPL_ERROR_NULL_INPUT);
3145 cpl_ensure_code(cpl_table_has_column(aGeo->
table, MUSE_GEOTABLE_FIELD) &&
3146 cpl_table_has_column(aGeo->
table, MUSE_GEOTABLE_CCD) &&
3147 cpl_table_has_column(aGeo->
table, MUSE_GEOTABLE_SKY) &&
3148 cpl_table_has_column(aGeo->
table, MUSE_GEOTABLE_X) &&
3149 cpl_table_has_column(aGeo->
table, MUSE_GEOTABLE_Y) &&
3150 cpl_table_has_column(aGeo->
table, MUSE_GEOTABLE_ANGLE) &&
3151 cpl_table_has_column(aGeo->
table, MUSE_GEOTABLE_WIDTH),
3152 CPL_ERROR_ILLEGAL_INPUT);
3155 if (getenv(
"MUSE_GEOMETRY_PINHOLE_DY")) {
3157 FILE *fn1 = fopen(
"gt1.ascii",
"w");
3158 fprintf(fn1,
"geometry table before scaling\n");
3159 cpl_table_dump(aGeo->
table, 0, 10000, fn1);
3162 double pinholedy = atof(getenv(
"MUSE_GEOMETRY_PINHOLE_DY")),
3163 fdy = kMuseCUmpmDY / pinholedy;
3164 cpl_msg_info(__func__,
"Pinhole y distance of %f mm was used instead of " 3165 "%f mm; scaling coordinates by %f!", pinholedy, kMuseCUmpmDY,
3167 int irow, nrow = cpl_table_get_nrow(aGeo->
table);
3168 for (irow = 0; irow < nrow; irow++) {
3171 double y = cpl_table_get_double(aGeo->
table, MUSE_GEOTABLE_Y, irow, &err);
3173 cpl_table_set_double(aGeo->
table, MUSE_GEOTABLE_Y, irow, y * fdy);
3178 double angleold = cpl_table_get_double(aGeo->
table, MUSE_GEOTABLE_ANGLE, irow, &err);
3180 double anglenew = atan(fdy * tan(angleold * CPL_MATH_RAD_DEG))
3182 cpl_table_set_double(aGeo->
table, MUSE_GEOTABLE_ANGLE, irow, anglenew);
3186 FILE *fn2 = fopen(
"gt2.ascii",
"w");
3187 fprintf(fn2,
"geometry table after scaling by %f\n", fdy);
3188 cpl_table_dump(aGeo->
table, 0, 10000, fn2);
3195 for (nifu = 1; nifu <= kMuseNumIFUs; nifu++) {
3196 cpl_table_select_all(aGeo->
table);
3197 cpl_table_and_selected_int(aGeo->
table, MUSE_GEOTABLE_FIELD, CPL_EQUAL_TO, nifu);
3198 if (cpl_table_count_selected(aGeo->
table) < 1) {
3202 unsigned short nslice;
3203 for (nslice = 1; nslice <= kMuseSlicesPerCCD; nslice++) {
3204 cpl_table_select_all(aGeo->
table);
3205 cpl_table_and_selected_int(aGeo->
table, MUSE_GEOTABLE_FIELD, CPL_EQUAL_TO, nifu);
3206 cpl_table_and_selected_int(aGeo->
table, MUSE_GEOTABLE_CCD, CPL_EQUAL_TO, nslice);
3207 if (cpl_table_count_selected(aGeo->
table) > 0) {
3211 cpl_table_set_size(aGeo->
table, cpl_table_get_nrow(aGeo->
table) + 1);
3212 int irow = cpl_table_get_nrow(aGeo->
table) - 1;
3213 cpl_table_set_int(aGeo->
table, MUSE_GEOTABLE_FIELD, irow, nifu);
3214 cpl_table_set_int(aGeo->
table, MUSE_GEOTABLE_CCD, irow, nslice);
3215 cpl_table_set_int(aGeo->
table, MUSE_GEOTABLE_SKY, irow,
3216 kMuseGeoSliceSky[nslice - 1]);
3217 cpl_table_set_double(aGeo->
table, MUSE_GEOTABLE_X, irow, NAN);
3218 cpl_table_set_double(aGeo->
table, MUSE_GEOTABLE_Y, irow, NAN);
3219 cpl_table_set_double(aGeo->
table, MUSE_GEOTABLE_ANGLE, irow, 0.);
3220 cpl_table_set_double(aGeo->
table, MUSE_GEOTABLE_WIDTH, irow, 0.);
3224 cpl_boolean needsnoflip = getenv(
"MUSE_GEOMETRY_NO_INVERSION")
3225 && atoi(getenv(
"MUSE_GEOMETRY_NO_INVERSION")) > 0;
3227 cpl_msg_info(__func__,
"Flipping all slices because of data inversion!");
3228 cpl_table_multiply_scalar(aGeo->
table, MUSE_GEOTABLE_Y, -1.);
3229 cpl_table_multiply_scalar(aGeo->
table, MUSE_GEOTABLE_ANGLE, -1.);
3233 cpl_propertylist *sorting = cpl_propertylist_new();
3234 cpl_propertylist_append_bool(sorting, MUSE_GEOTABLE_FIELD, CPL_FALSE);
3235 cpl_propertylist_append_bool(sorting, MUSE_GEOTABLE_SKY, CPL_FALSE);
3236 cpl_table_sort(aGeo->
table, sorting);
3237 cpl_propertylist_delete(sorting);
3240 printf(
"\nfinal geometry table with all slicer stacks [pix]:\n");
3241 cpl_table_dump(aGeo->
table, 0, nrow, stdout);
3245 const char *fn =
"GEOMETRY_TABLE.ascii";
3246 FILE *fp = fopen(fn,
"w");
3247 fprintf(fp,
"# final geometry table with all slicer stacks [pix]:\n");
3248 cpl_table_dump(aGeo->
table, 0, nrow, fp);
3250 cpl_msg_debug(__func__,
"written geometry table in ASCII to \"%s\"", fn);
3253 return CPL_ERROR_NONE;
3273 muse_geo_correct_slices_stack(cpl_table *aTStack, cpl_matrix *aPos,
3274 const char *aCol,
const char *aErr,
double aLimit,
3277 const char *
id =
"muse_geo_correct_slices";
3279 double *pval = cpl_table_get_data_double(aTStack, aCol),
3280 *perr = cpl_table_get_data_double(aTStack, aErr);
3282 int ntsrow = cpl_table_get_nrow(aTStack);
3283 cpl_vector *val = cpl_vector_wrap(ntsrow, pval),
3284 *err = cpl_vector_wrap(ntsrow, perr);
3291 cpl_vector_unwrap(val);
3292 cpl_vector_unwrap(err);
3294 cpl_msg_debug(__func__,
"poly for %s values (RMS = %f, ChiSq = %f):", aCol,
3296 cpl_polynomial_dump(poly, stdout);
3299 unsigned int nreplaced = 0;
3301 for (irow = 0; irow < ntsrow; irow++) {
3302 double pos = cpl_matrix_get(aPos, 0, irow),
3303 vpoly = cpl_polynomial_eval_1d(poly, pos, NULL),
3304 dpoly = fabs(pval[irow] - vpoly);
3305 if (perr[irow] > aLimit || dpoly > aSigma * rms) {
3306 cpl_msg_debug(__func__,
"Changing %s(%02.0f) from %.3f to %.3f " 3307 "(perr = %.3f > %.3f or dpoly = %.3f > %.3f)", aCol, pos,
3308 pval[irow], vpoly, perr[irow], aLimit, dpoly, aSigma * rms);
3313 cpl_polynomial_delete(poly);
3314 if (nreplaced > 3) {
3315 cpl_msg_warning(
id,
"Changed %d of %d %s values in IFU %02d (stack with " 3316 "sky slices %d to %d)", nreplaced, ntsrow, aCol,
3317 cpl_table_get_int(aTStack, MUSE_GEOTABLE_FIELD, 0, NULL),
3318 (
int)cpl_matrix_get(aPos, 0, 0),
3319 (
int)cpl_matrix_get(aPos, 0, ntsrow - 1));
3367 cpl_ensure_code(aGeo && aGeo->
table, CPL_ERROR_NULL_INPUT);
3368 cpl_ensure_code(aSigma > 0., CPL_ERROR_ILLEGAL_INPUT);
3369 cpl_ensure_code(cpl_table_has_column(aGeo->
table, MUSE_GEOTABLE_FIELD) &&
3370 cpl_table_has_column(aGeo->
table, MUSE_GEOTABLE_CCD) &&
3371 cpl_table_has_column(aGeo->
table, MUSE_GEOTABLE_SKY) &&
3372 cpl_table_has_column(aGeo->
table, MUSE_GEOTABLE_X) &&
3373 cpl_table_has_column(aGeo->
table, MUSE_GEOTABLE_Y) &&
3374 cpl_table_has_column(aGeo->
table, MUSE_GEOTABLE_ANGLE) &&
3375 cpl_table_has_column(aGeo->
table, MUSE_GEOTABLE_WIDTH) &&
3376 cpl_table_has_column(aGeo->
table, MUSE_GEOTABLE_X
"err") &&
3377 cpl_table_has_column(aGeo->
table, MUSE_GEOTABLE_Y
"err") &&
3378 cpl_table_has_column(aGeo->
table, MUSE_GEOTABLE_ANGLE
"err") &&
3379 cpl_table_has_column(aGeo->
table, MUSE_GEOTABLE_WIDTH
"err"),
3380 CPL_ERROR_DATA_NOT_FOUND);
3381 cpl_ensure_code(cpl_table_get_column_type(aGeo->
table, MUSE_GEOTABLE_X) == CPL_TYPE_DOUBLE &&
3382 cpl_table_get_column_type(aGeo->
table, MUSE_GEOTABLE_Y) == CPL_TYPE_DOUBLE &&
3383 cpl_table_get_column_type(aGeo->
table, MUSE_GEOTABLE_ANGLE) == CPL_TYPE_DOUBLE &&
3384 cpl_table_get_column_type(aGeo->
table, MUSE_GEOTABLE_WIDTH) == CPL_TYPE_DOUBLE &&
3385 cpl_table_get_column_type(aGeo->
table, MUSE_GEOTABLE_X
"err") == CPL_TYPE_DOUBLE &&
3386 cpl_table_get_column_type(aGeo->
table, MUSE_GEOTABLE_Y
"err") == CPL_TYPE_DOUBLE &&
3387 cpl_table_get_column_type(aGeo->
table, MUSE_GEOTABLE_ANGLE
"err") == CPL_TYPE_DOUBLE &&
3388 cpl_table_get_column_type(aGeo->
table, MUSE_GEOTABLE_WIDTH
"err") == CPL_TYPE_DOUBLE,
3389 CPL_ERROR_INCOMPATIBLE_INPUT);
3392 cpl_table_set_column_format(aGeo->
table, MUSE_GEOTABLE_X,
"%8.3f");
3393 cpl_table_set_column_format(aGeo->
table, MUSE_GEOTABLE_X
"err",
"%8.3f");
3394 cpl_table_set_column_format(aGeo->
table, MUSE_GEOTABLE_Y,
"%8.3f");
3395 cpl_table_set_column_format(aGeo->
table, MUSE_GEOTABLE_Y
"err",
"%8.3f");
3396 cpl_table_set_column_format(aGeo->
table, MUSE_GEOTABLE_ANGLE,
"%5.3f");
3397 cpl_table_set_column_format(aGeo->
table, MUSE_GEOTABLE_ANGLE
"err",
"%5.3f");
3398 cpl_table_set_column_format(aGeo->
table, MUSE_GEOTABLE_WIDTH,
"%8.3f");
3399 cpl_table_set_column_format(aGeo->
table, MUSE_GEOTABLE_WIDTH
"err",
"%8.3f");
3401 cpl_msg_info(__func__,
"Correcting %s using %.2f-sigma level",
3402 MUSE_TAG_GEOMETRY_TABLE, aSigma);
3403 cpl_msg_debug(__func__,
" median errors: x %.3f y %.3f angle %.3f width %.3f",
3404 cpl_table_get_column_median(aGeo->
table, MUSE_GEOTABLE_X
"err"),
3405 cpl_table_get_column_median(aGeo->
table, MUSE_GEOTABLE_Y
"err"),
3406 cpl_table_get_column_median(aGeo->
table, MUSE_GEOTABLE_ANGLE
"err"),
3407 cpl_table_get_column_median(aGeo->
table, MUSE_GEOTABLE_WIDTH
"err"));
3409 const double kXLimit = 0.90,
3413 cpl_msg_debug(__func__,
" table limits: x %.3f y %.3f angle %.3f width %.3f",
3414 kXLimit, kYLimit, kALimit, kWLimit);
3416 int nx = 0, ny = 0, na = 0, nw = 0;
3417 const unsigned short nsoff = kMuseSlicesPerCCD / 4;
3419 for (nifu = 1; nifu <= kMuseNumIFUs; nifu++) {
3420 unsigned char nstack;
3421 for (nstack = 1; nstack <= 4; nstack++) {
3423 unsigned short nslice1 = (nstack - 1) * nsoff + 1,
3424 nslice2 = nstack * nsoff;
3426 cpl_table_unselect_all(aGeo->
table);
3427 cpl_table_or_selected_int(aGeo->
table, MUSE_GEOTABLE_FIELD, CPL_EQUAL_TO, nifu);
3428 cpl_table_and_selected_int(aGeo->
table, MUSE_GEOTABLE_SKY,
3429 CPL_NOT_LESS_THAN, nslice1);
3430 cpl_table_and_selected_int(aGeo->
table, MUSE_GEOTABLE_SKY,
3431 CPL_NOT_GREATER_THAN, nslice2);
3432 int ntsrow = cpl_table_count_selected(aGeo->
table);
3433 cpl_msg_debug(__func__,
"IFU %2hhu stack %hhu, slices %2hu to %2hu: %d rows",
3434 nifu, nstack, nslice1, nslice2, ntsrow);
3439 cpl_table *tstack = cpl_table_extract_selected(aGeo->
table);
3441 cpl_table_dump(tstack, 0, nsoff, stdout);
3446 cpl_propertylist *sorting = cpl_propertylist_new();
3447 cpl_propertylist_append_bool(sorting, MUSE_GEOTABLE_FIELD, CPL_FALSE);
3448 cpl_propertylist_append_bool(sorting, MUSE_GEOTABLE_SKY, CPL_FALSE);
3449 cpl_table_sort(tstack, sorting);
3450 cpl_propertylist_delete(sorting);
3453 cpl_table_cast_column(tstack, MUSE_GEOTABLE_SKY,
"skydouble", CPL_TYPE_DOUBLE);
3454 cpl_matrix *pos = cpl_matrix_wrap(1, ntsrow,
3455 cpl_table_get_data_double(tstack,
"skydouble"));
3457 nx += muse_geo_correct_slices_stack(tstack, pos, MUSE_GEOTABLE_X,
3458 MUSE_GEOTABLE_X
"err", kXLimit, aSigma);
3459 ny += muse_geo_correct_slices_stack(tstack, pos, MUSE_GEOTABLE_Y,
3460 MUSE_GEOTABLE_Y
"err", kYLimit, aSigma);
3461 na += muse_geo_correct_slices_stack(tstack, pos, MUSE_GEOTABLE_ANGLE,
3462 MUSE_GEOTABLE_ANGLE
"err", kALimit, aSigma);
3463 nw += muse_geo_correct_slices_stack(tstack, pos, MUSE_GEOTABLE_WIDTH,
3464 MUSE_GEOTABLE_WIDTH
"err", kWLimit, aSigma);
3465 cpl_matrix_unwrap(pos);
3466 cpl_table_erase_column(tstack,
"skydouble");
3468 cpl_msg_debug(__func__,
"IFU %2hhu stack %hhu, final table:", nifu, nstack);
3469 cpl_table_dump(tstack, 0, nsoff, stdout);
3472 cpl_table_erase_selected(aGeo->
table);
3473 cpl_table_insert(aGeo->
table, tstack, cpl_table_get_nrow(aGeo->
table));
3474 cpl_table_delete(tstack);
3478 cpl_msg_info(__func__,
"Changed %d x values, %d y values, %d angles, and %d " 3479 "widths.", nx, ny, na, nw);
3481 cpl_propertylist_update_int(aHeader, QC_GEO_SMOOTH_NX, nx);
3482 cpl_propertylist_update_int(aHeader, QC_GEO_SMOOTH_NY, ny);
3483 cpl_propertylist_update_int(aHeader, QC_GEO_SMOOTH_NA, na);
3484 cpl_propertylist_update_int(aHeader, QC_GEO_SMOOTH_NW, nw);
3486 return CPL_ERROR_NONE;
3510 cpl_ensure_code(aGeoTable && aHeader, CPL_ERROR_NULL_INPUT);
3511 cpl_table *geotable = aGeoTable->
table;
3514 cpl_array *agaps = cpl_array_new(kMuseNumIFUs, CPL_TYPE_DOUBLE);
3516 nifu1 = cpl_table_get_column_min(geotable, MUSE_GEOTABLE_FIELD),
3517 nifu2 = cpl_table_get_column_max(geotable, MUSE_GEOTABLE_FIELD);
3518 for (nifu = nifu1; nifu <= nifu2; nifu++) {
3520 cpl_table_unselect_all(geotable);
3521 cpl_table_or_selected_int(geotable, MUSE_GEOTABLE_FIELD, CPL_EQUAL_TO, nifu);
3522 cpl_table_and_selected_int(geotable, MUSE_GEOTABLE_SKY, CPL_NOT_LESS_THAN, 13);
3523 cpl_table_and_selected_int(geotable, MUSE_GEOTABLE_SKY, CPL_NOT_GREATER_THAN, 24);
3524 cpl_table *tleft = cpl_table_extract_selected(geotable);
3526 cpl_table_unselect_all(geotable);
3527 cpl_table_or_selected_int(geotable, MUSE_GEOTABLE_FIELD, CPL_EQUAL_TO, nifu);
3528 cpl_table_and_selected_int(geotable, MUSE_GEOTABLE_SKY, CPL_NOT_LESS_THAN, 25);
3529 cpl_table_and_selected_int(geotable, MUSE_GEOTABLE_SKY, CPL_NOT_GREATER_THAN, 36);
3530 cpl_table *tright = cpl_table_extract_selected(geotable);
3533 int irow, nrow = cpl_table_get_nrow(tleft),
3534 nrow2 = cpl_table_get_nrow(tright);
3535 if (nrow < 1 || nrow2 < 1) {
3536 cpl_msg_warning(__func__,
"No slices for central stacks found, cannot " 3537 "compute gaps for QC in IFU %hhu", nifu);
3538 cpl_table_delete(tleft);
3539 cpl_table_delete(tright);
3542 if (nrow != nrow2) {
3543 cpl_msg_warning(__func__,
"Unequal slice count for central stacks, cannot" 3544 " compute gaps for QC in IFU %hhu", nifu);
3545 cpl_table_delete(tleft);
3546 cpl_table_delete(tright);
3551 cpl_propertylist *sorting = cpl_propertylist_new();
3552 cpl_propertylist_append_bool(sorting, MUSE_GEOTABLE_SKY, CPL_FALSE);
3553 cpl_table_sort(tleft, sorting);
3554 cpl_table_sort(tright, sorting);
3555 cpl_propertylist_delete(sorting);
3556 cpl_array *agap = cpl_array_new(nrow, CPL_TYPE_DOUBLE);
3557 for (irow = 0; irow < nrow; irow++) {
3558 double x1 = cpl_table_get(tleft, MUSE_GEOTABLE_X, irow, NULL),
3559 w1 = cpl_table_get(tleft, MUSE_GEOTABLE_WIDTH, irow, NULL),
3560 x2 = cpl_table_get(tright, MUSE_GEOTABLE_X, irow, NULL),
3561 w2 = cpl_table_get(tright, MUSE_GEOTABLE_WIDTH, irow, NULL);
3562 cpl_array_set_double(agap, irow, (x1 + w1/2. + x2 - w2/2.) / 2.);
3564 cpl_table_delete(tleft);
3565 cpl_table_delete(tright);
3568 double mean = cpl_array_get_mean(agap);
3569 cpl_array_set_double(agaps, nifu - 1, mean);
3570 char *kw = cpl_sprintf(QC_GEO_IFUi_GAP, nifu);
3571 cpl_propertylist_update_float(aHeader, kw, mean);
3573 cpl_array_delete(agap);
3575 double gmean = cpl_array_get_mean(agaps),
3576 gstdev = cpl_array_get_stdev(agaps);
3577 cpl_propertylist_update_float(aHeader, QC_GEO_GAPS_MEAN, gmean);
3578 cpl_propertylist_update_float(aHeader, QC_GEO_GAPS_STDEV, gstdev);
3579 cpl_array_delete(agaps);
3582 double angle = cpl_table_get_column_mean(geotable, MUSE_GEOTABLE_ANGLE),
3583 astdev = cpl_table_get_column_stdev(geotable, MUSE_GEOTABLE_ANGLE),
3584 amedian = cpl_table_get_column_median(geotable, MUSE_GEOTABLE_ANGLE);
3585 cpl_errorstate state = cpl_errorstate_get();
3586 cpl_propertylist_update_float(aHeader, QC_GEO_MASK_ANGLE, amedian);
3587 if (!cpl_errorstate_is_equal(state)) {
3589 cpl_errorstate_set(state);
3590 cpl_propertylist_update_double(aHeader, QC_GEO_MASK_ANGLE, amedian);
3593 cpl_msg_info(__func__,
"Added global QC keywords: angle = %.3f +/- %.3f " 3594 "(%.3f) deg, gap positions = %.3f +/- %.3f pix", angle, astdev,
3595 amedian, gmean, gstdev);
3596 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
cpl_error_code muse_geo_correct_slices(muse_geo_table *aGeo, cpl_propertylist *aHeader, double aSigma)
Correct deviant slices in an existing MUSE geometry table.
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.
cpl_error_code muse_geo_qc_global(const muse_geo_table *aGeoTable, cpl_propertylist *aHeader)
Add the global QC parameters to the geometry table.
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_polynomial * muse_utils_iterate_fit_polynomial(cpl_matrix *aPos, cpl_vector *aVal, cpl_vector *aErr, cpl_table *aExtra, const unsigned int aOrder, const double aRSigma, double *aMSE, double *aChiSq)
Iterate a polynomial fit.
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.