/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set sw=2 sts=2 et cin: */
/*
 * This file is part of the MUSE Instrument Pipeline
 * Copyright (C) 2007-2015 European Southern Observatory
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */

#define _BSD_SOURCE /* get mkdtemp() from stdlib.h */
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h> /* stat() */
#include <string.h> /* strcmp() */

#include <muse.h>

#define DIR_TEMPLATE "/tmp/muse_pixtable_test_XXXXXX"

/* compare function for cpl_propertylist_sort() */
static int
muse_test_pixtable_compare_name(const cpl_property *p1,
                                const cpl_property *p2)
{
    return strcmp(cpl_property_get_name(p1),
                  cpl_property_get_name(p2));
}

/* compare MUSE pixel table structure, entries, and header */
static void
muse_test_pixtable_compare(const muse_pixtable *aPt1,
                           const muse_pixtable *aPt2)
{
  /* compare table (contents) */
  cpl_test(cpl_table_compare_structure(aPt1->table, aPt2->table) == 0);
  cpl_size nrow1 = cpl_table_get_nrow(aPt1->table),
           nrow2 = cpl_table_get_nrow(aPt1->table);
  cpl_test(nrow1 == nrow2);
  cpl_array *columns = cpl_table_get_column_names(aPt1->table);
  cpl_size icol;
  for (icol = 0; icol < cpl_array_get_size(columns); icol++) {
    const char *colname = cpl_array_get_string(columns, icol);
    /* don't test all entries (takes too long!) just 10 first and 10 last ones */
    cpl_size irow;
    for (irow = 0; irow < 10 && irow < nrow1; irow++) {
      cpl_test(cpl_table_get(aPt1->table, colname, irow, NULL)
               == cpl_table_get(aPt2->table, colname, irow, NULL));
    } /* for irow */
    for (irow = nrow1 - 10; irow < nrow1; irow++) {
      cpl_test(cpl_table_get(aPt1->table, colname, irow, NULL)
               == cpl_table_get(aPt2->table, colname, irow, NULL));
    } /* for irow */
  } /* for icol */
  cpl_array_delete(columns);

  /* compare the headers, but only partially, since on reload *
   * other properties might be there (BIXPIX, SIMPLE, etc.)   */
  cpl_propertylist *h1 = cpl_propertylist_duplicate(aPt1->header),
                   *h2 = cpl_propertylist_duplicate(aPt2->header);
  cpl_propertylist_erase_regexp(h1, "DATE-OBS|EXPTIME|MUSE|PIXTABLE", 1);
  cpl_propertylist_erase_regexp(h2, "DATE-OBS|EXPTIME|MUSE|PIXTABLE", 1);
  cpl_propertylist_sort(h1, muse_test_pixtable_compare_name);
  cpl_propertylist_sort(h2, muse_test_pixtable_compare_name);
  cpl_size n1 = cpl_propertylist_get_size(h1),
           n2 = cpl_propertylist_get_size(h1);
  cpl_test(n1 == n2);
  cpl_size iprop;
  for (iprop = 0; iprop < n1; iprop++) {
    cpl_property *p1 = cpl_propertylist_get(h1, iprop),
                 *p2 = cpl_propertylist_get(h2, iprop);
    cpl_test_eq_string(cpl_property_get_name(p1), cpl_property_get_name(p2));
    if (cpl_property_get_comment(p1)) { /* test first 5 char of non-NULL comments */
      cpl_test_zero(strncmp(cpl_property_get_comment(p1),
                            cpl_property_get_comment(p2), 5));
    }
    /* not going to test all possible types and not the types  *
     * themselves, because on re-load they might have changed! */
    if (cpl_property_get_type(p1) == CPL_TYPE_DOUBLE &&
        cpl_property_get_type(p2) == CPL_TYPE_DOUBLE) {
      cpl_test_eq(cpl_property_get_double(p1), cpl_property_get_double(p2));
    } else if (cpl_property_get_type(p1) == CPL_TYPE_INT &&
               cpl_property_get_type(p2) == CPL_TYPE_INT) {
      cpl_test_eq(cpl_property_get_int(p1), cpl_property_get_int(p2));
    }
  } /* for iprop */
  cpl_propertylist_delete(h1);
  cpl_propertylist_delete(h2);
} /* muse_test_pixtable_compare() */

static cpl_table *
muse_test_pixtable_create_exptable(const char *aDir)
{
  /* create one small pixel table to use and save */
#define PTLEN 10
  muse_pixtable *pt = cpl_calloc(1, sizeof(muse_pixtable));
  pt->table = muse_cpltable_new(muse_pixtable_def, PTLEN);
  cpl_table_fill_column_window_float(pt->table, MUSE_PIXTABLE_XPOS, 0, PTLEN, 0.);
  cpl_table_fill_column_window_float(pt->table, MUSE_PIXTABLE_YPOS, 0, PTLEN, 0.);
  cpl_table_fill_column_window_float(pt->table, MUSE_PIXTABLE_LAMBDA, 0, PTLEN/3, 4800.);
  cpl_table_fill_column_window_float(pt->table, MUSE_PIXTABLE_LAMBDA, PTLEN/3, 2*PTLEN/3,
                                     5000.);
  cpl_table_fill_column_window_float(pt->table, MUSE_PIXTABLE_LAMBDA, 2*PTLEN/3, PTLEN/3+1,
                                     5500.);
  pt->header = cpl_propertylist_new();
  cpl_propertylist_append_string(pt->header, MUSE_HDR_PT_TYPE, MUSE_PIXTABLE_STRING_FULL);

  /* create exposure table a la muse_processing_sort_exposures() */
#define TABLENUM 4
  cpl_table *exptable = cpl_table_new(TABLENUM);
  cpl_table_new_column(exptable, "DATE-OBS", CPL_TYPE_STRING);
  const char *dates[] = { "2013-08-23T12:00:00.000",
                          "2007-10-27T00:13:00.010",
                          "2009-05-18T10:00:24.005" };
  cpl_table_set_string(exptable, "DATE-OBS", 0, dates[0]);
  cpl_table_set_string(exptable, "DATE-OBS", 1, dates[1]);
  cpl_table_set_string(exptable, "DATE-OBS", 2, dates[2]);
  char colname[3]; /* further used in this function, define outside loop */
  int i;
  for (i = 0; i <= kMuseNumIFUs; i++) {
    snprintf(colname, 3, "%02d", i);
    cpl_table_new_column(exptable, colname, CPL_TYPE_STRING);
    if (i == 0) {
      continue; /* don't fill the combined column yet */
    }
    /* fill the table */
    int j;
    for (j = 0; j < TABLENUM - 2; j++) {
      if (j == 1 && (i == 12 || i == 19)) {
        continue; /* skip two IFUs in the 2nd exposure */
      }
      char *fn = cpl_sprintf("%s/pt_%04d-%02d.fits", aDir, j+1, i);
      cpl_table_set_string(exptable, colname, j, fn);

      /* update the pixel table (data, variance, and header) *
       * for this IFU / exposure and save it                 */
      cpl_table_fill_column_window_float(pt->table, MUSE_PIXTABLE_DATA, 0, PTLEN, 1.);
      cpl_table_fill_column_window_float(pt->table, MUSE_PIXTABLE_STAT, 0, PTLEN, 0.5);
      cpl_propertylist_update_string(pt->header, "DATE-OBS", dates[j]);
      cpl_propertylist_update_double(pt->header, MUSE_HDR_FLAT_FLUX_LAMP, 9.8e10);
      if (j == 1) {
        cpl_propertylist_erase(pt->header, MUSE_HDR_FLAT_FLUX_SKY);
      }
      muse_pixtable_compute_limits(pt);
      muse_pixtable_save(pt, fn);
      cpl_free(fn);
    } /* for j (rows) */
  } /* for i (columns) */
  /* create combined table in last row */
  char *fn = cpl_sprintf("%s/pt_combined_missing.fits", aDir);
  cpl_table_set_string(exptable, "00", TABLENUM - 2, fn);
  cpl_free(fn);
  fn = cpl_sprintf("%s/pt_combined.fits", aDir);
  cpl_table_set_string(exptable, "00", TABLENUM - 1, fn);
  cpl_propertylist_erase(pt->header, MUSE_HDR_FLAT_FLUX_SKY);
  cpl_propertylist_erase(pt->header, MUSE_HDR_FLAT_FLUX_LAMP);
  muse_pixtable_save(pt, fn);
  muse_pixtable_delete(pt);
  cpl_free(fn);

#if 1
  cpl_table_dump(exptable, 0, 10, stdout);
  fflush(stdout);
#endif
  return exptable;
} /* muse_test_pixtable_create_exptable() */

static void
muse_test_pixtable_delete_exptable(cpl_table *aExpTable)
{
  char colname[3]; /* further used in this function, define outside loop */
  int i;
  for (i = 0; i <= kMuseNumIFUs; i++) {
    snprintf(colname, 3, "%02d", i);
    int j, n = cpl_table_get_nrow(aExpTable);
    for (j = 0; j < n; j++) {
      const char *fn = cpl_table_get_string(aExpTable, colname, j);
      if (fn) {
        cpl_msg_debug(__func__, "removing \"%s\"", fn);
        remove(fn);
      }
    } /* for j (rows) */
  } /* for i (columns) */
  cpl_table_delete(aExpTable);
} /* muse_test_pixtable_delete_exptable() */

/*----------------------------------------------------------------------------*/
/**
  @brief   Test program to check that the functions from the muse_pixtable
           module work when called with the necessary data.

  This program explicitely tests
    muse_pixtable_origin_encode
    muse_pixtable_origin_get_y
    muse_pixtable_origin_get_ifu
    muse_pixtable_origin_get_slice
    muse_pixtable_create
    muse_pixtable_delete
    muse_pixtable_save
    muse_pixtable_load_window
    muse_pixtable_load
    muse_pixtable_load_restricted_wavelength
    muse_pixtable_load_merge_channels
    muse_pixtable_duplicate
    muse_pixtable_get_type
    muse_pixtable_flux_multiply
    muse_pixtable_wcs_check (only failure cases)
    muse_pixtable_erase_ifu_slice
    muse_pixtable_is_fluxcal
    muse_pixtable_is_skysub
    muse_pixtable_is_rvcorr
    muse_pixtable_to_imagelist
    muse_pixtable_from_imagelist
    muse_pixtable_extracted_get_slices (only failure case)
    muse_pixtable_extracted_get_size (only failure case)
    muse_pixtable_extracted_delete (only failure case)
 */
/*----------------------------------------------------------------------------*/
int main(int argc, char **argv)
{
  UNUSED_ARGUMENTS(argc, argv);
  cpl_test_init(PACKAGE_BUGREPORT, CPL_MSG_DEBUG);

  /* create temporary dir */
  char dirtemplate[FILENAME_MAX] = DIR_TEMPLATE;
  char *dir = mkdtemp(dirtemplate);

  /* test origin en-/decoding around some ranges */
  cpl_errorstate ps = cpl_errorstate_get();
  int xrange[] = { 1, 2077, 4224, 8191, -1 }, /* use int to be able to use */
      yrange[] = { 1, 2199, 4240, 8191, -1 }, /* negative as end-of-list */
      ifurange[] = { 1, 13, kMuseNumIFUs, -1 },
      slicerange[] = { 1, 27, kMuseSlicesPerCCD, -1 },
      offrange[] = { 0, 10, 50, 100, 500, 8191, -1 };
  uint32_t origin;
  unsigned int y;
  unsigned char ifu;
  unsigned short slice;
  int ix, iy, ii, is, io;
  for (ix = 0 ; xrange[ix] >= 0; ix++) {
    for (iy = 0 ; yrange[iy] >= 0; iy++) {
      for (ii = 0 ; ifurange[ii] >= 0; ii++) {
        for (is = 0 ; slicerange[is] >= 0; is++) {
          for (io = 0 ; offrange[io] >= 0; io++) {
            ps = cpl_errorstate_get();
            origin = muse_pixtable_origin_encode(xrange[ix], yrange[iy],
                                                 ifurange[ii], slicerange[is],
                                                 offrange[io]);
            cpl_test(cpl_errorstate_is_equal(ps) && origin);
            if (!cpl_errorstate_is_equal(ps)) {
              cpl_msg_debug(__func__, "%u %u %hhu %hu %u --> %u", xrange[ix],
                            yrange[iy], ifurange[ii], slicerange[is],
                            offrange[io], origin);
            }
            // XXX this is not so simple to test as it needs a
            //     pixel table with full header keywords set up
            // unsigned int x = muse_pixtable_origin_get_x(origin, pt, irow);
            // XXX the same applies to
            // muse_pixtable_origin_set_offset
            // muse_pixtable_origin_get_offset
            // muse_pixtable_origin_copy_offsets
            // muse_pixtable_get_expnum
            y = muse_pixtable_origin_get_y(origin);
            cpl_test(y == (unsigned int)yrange[iy]);
            ifu = muse_pixtable_origin_get_ifu(origin);
            cpl_test(ifu == ifurange[ii]);
            slice = muse_pixtable_origin_get_slice(origin);
            cpl_test(slice == slicerange[is]);
          } /* for io */
        } /* for is */
      } /* for ii */
    } /* for iy */
  } /* for ix */
  /* test failure cases */
  ps = cpl_errorstate_get();
  /* bad x */
  origin = muse_pixtable_origin_encode(0, 1, 1, 1, 1);
  cpl_test(!cpl_errorstate_is_equal(ps) && cpl_error_get_code() == CPL_ERROR_ILLEGAL_INPUT && origin == 0);
  cpl_errorstate_set(ps);
  origin = muse_pixtable_origin_encode(8192, 1, 1, 1, 1);
  cpl_test(!cpl_errorstate_is_equal(ps) && cpl_error_get_code() == CPL_ERROR_ILLEGAL_INPUT && origin == 0);
  cpl_errorstate_set(ps);
  /* bad y */
  origin = muse_pixtable_origin_encode(1, 0, 1, 1, 1);
  cpl_test(!cpl_errorstate_is_equal(ps) && cpl_error_get_code() == CPL_ERROR_ILLEGAL_INPUT && origin == 0);
  cpl_errorstate_set(ps);
  origin = muse_pixtable_origin_encode(1, 8192, 1, 1, 1);
  cpl_test(!cpl_errorstate_is_equal(ps) && cpl_error_get_code() == CPL_ERROR_ILLEGAL_INPUT && origin == 0);
  cpl_errorstate_set(ps);
  /* bad ifu */
  origin = muse_pixtable_origin_encode(1, 1, 0, 1, 1);
  cpl_test(!cpl_errorstate_is_equal(ps) && cpl_error_get_code() == CPL_ERROR_ILLEGAL_INPUT && origin == 0);
  cpl_errorstate_set(ps);
  origin = muse_pixtable_origin_encode(1, 1, kMuseNumIFUs + 1, 1, 1);
  cpl_test(!cpl_errorstate_is_equal(ps) && cpl_error_get_code() == CPL_ERROR_ILLEGAL_INPUT && origin == 0);
  cpl_errorstate_set(ps);
  /* bad slice */
  origin = muse_pixtable_origin_encode(1, 1, 1, 0, 1);
  cpl_test(!cpl_errorstate_is_equal(ps) && cpl_error_get_code() == CPL_ERROR_ILLEGAL_INPUT && origin == 0);
  cpl_errorstate_set(ps);
  origin = muse_pixtable_origin_encode(1, 1, 1, kMuseSlicesPerCCD + 1, 1);
  cpl_test(!cpl_errorstate_is_equal(ps) && cpl_error_get_code() == CPL_ERROR_ILLEGAL_INPUT && origin == 0);
  cpl_errorstate_set(ps);
  /* bad offset */
  origin = muse_pixtable_origin_encode(1, 1, 1, 1, -1);
  cpl_test(!cpl_errorstate_is_equal(ps) && cpl_error_get_code() == CPL_ERROR_ILLEGAL_INPUT && origin == 0);
  cpl_errorstate_set(ps);
  origin = muse_pixtable_origin_encode(1, 1, 1, 1, 8192);
  cpl_test(!cpl_errorstate_is_equal(ps) && cpl_error_get_code() == CPL_ERROR_ILLEGAL_INPUT && origin == 0);
  cpl_errorstate_set(ps);
  /* bad origins */
  y = muse_pixtable_origin_get_y(0);
  cpl_test(!cpl_errorstate_is_equal(ps) && cpl_error_get_code() == CPL_ERROR_ILLEGAL_OUTPUT && y == 0);
  cpl_errorstate_set(ps);
  ifu = muse_pixtable_origin_get_ifu(0);
  cpl_test(!cpl_errorstate_is_equal(ps) && cpl_error_get_code() == CPL_ERROR_ILLEGAL_OUTPUT && ifu == 0);
  cpl_errorstate_set(ps);
  slice = muse_pixtable_origin_get_slice(0);
  cpl_test(!cpl_errorstate_is_equal(ps) && cpl_error_get_code() == CPL_ERROR_ILLEGAL_OUTPUT && slice == 0);
  cpl_errorstate_set(ps);

  /* test muse_pixtable_create() */
  cpl_table *wavecaltable = cpl_table_load(BASEFILENAME"_create_wavecal_in.fits", 1, 1);
  cpl_table *tracetable = cpl_table_load(BASEFILENAME"_create_trace_in.fits", 1, 1);
  cpl_table *geotable = cpl_table_load(BASEFILENAME"_create_geotable.fits", 1, 1);
  /* just take any image into which the normal MUSE pattern fits */
  muse_image *inimage = muse_image_load(BASEFILENAME"_create_data.fits");

  /* inimage does not contain a BUNIT, this should first fail because of that */
  ps = cpl_errorstate_get();
  muse_pixtable *pt = muse_pixtable_create(inimage, tracetable, wavecaltable,
                                           geotable);
  cpl_test(!cpl_errorstate_is_equal(ps) &&
           cpl_error_get_code() == CPL_ERROR_INCOMPATIBLE_INPUT);
  cpl_errorstate_set(ps);
  cpl_test_null(pt);
  /* add a wrong BUNIT, should still fail the same */
  cpl_propertylist_append_string(inimage->header, "BUNIT", "adu");
  pt = muse_pixtable_create(inimage, tracetable, wavecaltable, geotable);
  cpl_test(!cpl_errorstate_is_equal(ps) &&
           cpl_error_get_code() == CPL_ERROR_INCOMPATIBLE_INPUT);
  cpl_errorstate_set(ps);
  /* add the correct BUNIT, now fails because of the geotable without "width" */
  cpl_propertylist_update_string(inimage->header, "BUNIT", "count");
  pt = muse_pixtable_create(inimage, tracetable, wavecaltable, geotable);
  cpl_test(!cpl_errorstate_is_equal(ps) &&
           cpl_error_get_code() == CPL_ERROR_BAD_FILE_FORMAT);
  cpl_errorstate_set(ps);
  /* try again, now with a "width" column */
  cpl_table_new_column(geotable, MUSE_GEOTABLE_WIDTH, CPL_TYPE_DOUBLE);
  cpl_table_fill_column_window(geotable, MUSE_GEOTABLE_WIDTH,
                               0, cpl_table_get_nrow(geotable),
                               kMuseSliceNominalWidth);
  pt = muse_pixtable_create(inimage, tracetable, wavecaltable, geotable);
  cpl_test_nonnull(pt);
  cpl_test(muse_pixtable_get_type(pt) == MUSE_PIXTABLE_TYPE_FULL);
  cpl_size nrows = muse_pixtable_get_nrow(pt);
  cpl_msg_info(__func__, "created pixel table with %"CPL_SIZE_FORMAT" rows", nrows);
  /* the table of one IFU should contain a min number of pixels that is *
   * based on the number of slices and their nominal width and a max of *
   * the number of CCD pixels                                           */
  cpl_test(nrows >= 48*75*cpl_image_get_size_y(inimage->data));
  cpl_test(nrows <= cpl_image_get_size_x(inimage->data)
                    * cpl_image_get_size_y(inimage->data));
  /* keep this pixel table for the test of converting into an imagelist */
  muse_pixtable *ptkeep = muse_pixtable_duplicate(pt);
  /* test a few more failure cases */
  ps = cpl_errorstate_get();
  muse_pixtable *pt2 = muse_pixtable_create(NULL, tracetable, wavecaltable,
                                            geotable);
  cpl_test(!cpl_errorstate_is_equal(ps) &&
           cpl_error_get_code() == CPL_ERROR_NULL_INPUT);
  cpl_errorstate_set(ps);
  cpl_test_null(pt2);
  ps = cpl_errorstate_get();
  muse_image *inimage2 = muse_image_duplicate(inimage);
  cpl_propertylist_erase_regexp(inimage2->header, "EXTNAME", 0);
  pt2 = muse_pixtable_create(inimage2, tracetable, wavecaltable, geotable);
  muse_image_delete(inimage2);
  cpl_test(!cpl_errorstate_is_equal(ps) &&
           cpl_error_get_code() == CPL_ERROR_DATA_NOT_FOUND);
  cpl_errorstate_set(ps);
  cpl_test_null(pt2);
  ps = cpl_errorstate_get();
  pt2 = muse_pixtable_create(inimage, NULL, wavecaltable, geotable);
  cpl_test(!cpl_errorstate_is_equal(ps) &&
           cpl_error_get_code() == CPL_ERROR_NULL_INPUT);
  cpl_errorstate_set(ps);
  cpl_test_null(pt2);
  ps = cpl_errorstate_get();
  pt2 = muse_pixtable_create(inimage, tracetable, NULL, geotable);
  cpl_test(!cpl_errorstate_is_equal(ps) &&
           cpl_error_get_code() == CPL_ERROR_NULL_INPUT);
  cpl_errorstate_set(ps);
  cpl_test_null(pt2);
  /* a NULL geometry table should succeed */
  ps = cpl_errorstate_get();
  pt2 = muse_pixtable_create(inimage, tracetable, wavecaltable, NULL);
  cpl_test(cpl_errorstate_is_equal(ps));
  cpl_test_nonnull(pt2);
  cpl_test(muse_pixtable_get_type(pt2) == MUSE_PIXTABLE_TYPE_SIMPLE);
  /* A partially broken trace table should succeed, too, but       *
   * only contain that part of the slice that was not well-defined. *
   * To set this test up, use the example from slice 46 of IFU 24   *
   * as originally traced in January 2014 on Paranal, at least using *
   * the coefficients that are present in this testing tracetable.   */
  cpl_table *tr46 = cpl_table_extract(tracetable, 45, 1);
  cpl_table_set(tracetable, "Width", 45, 78.58753);
  cpl_table_set(tracetable, "tc0_00", 45, 3848.23931839034);
  cpl_table_set(tracetable, "tc0_01", 45, -0.000178341417506141);
  cpl_table_set(tracetable, "tc0_02", 45, 1.10609075533106E-05);
  cpl_table_set(tracetable, "tc0_03", 45, -1.73609539085184E-08);
  /* cpl_table_set(tracetable, "tc0_04", 45, 8.87842838335494E-12); */
  /* cpl_table_set(tracetable, "tc0_05", 45, -1.51337333904576E-15); */
  cpl_table_set(tracetable, "MSE0", 45, 4.96537658905216E-07);
  cpl_table_set(tracetable, "tc1_00", 45, 3808.95529212496);
  cpl_table_set(tracetable, "tc1_01", 45, 0.00262511193392701);
  cpl_table_set(tracetable, "tc1_02", 45, -1.08439315040227E-06);
  cpl_table_set(tracetable, "tc1_03", 45, -3.14353356737499E-09);
  /* cpl_table_set(tracetable, "tc1_04", 45, 2.31766748107034E-12); */
  /* cpl_table_set(tracetable, "tc1_05", 45, -4.46730257366279E-16); */
  cpl_table_set(tracetable, "MSE1", 45, 2.03588680227243E-06);
  cpl_table_set(tracetable, "tc2_00", 45, 3887.5233446837);
  cpl_table_set(tracetable, "tc2_01", 45, -0.00298179511483656);
  cpl_table_set(tracetable, "tc2_02", 45, 2.32062095346736E-05);
  cpl_table_set(tracetable, "tc2_03", 45, -3.15783757320432E-08);
  /* cpl_table_set(tracetable, "tc2_04", 45, 1.54391899749506E-11); */
  /* cpl_table_set(tracetable, "tc2_05", 45, -2.58001653375017E-15); */
  cpl_table_set(tracetable, "MSE2", 45, 1.6756966321601E-07);
  ps = cpl_errorstate_get();
  muse_pixtable *pt5 = muse_pixtable_create(inimage, tracetable, wavecaltable, geotable);
  cpl_test(cpl_errorstate_is_equal(ps));
  cpl_test_nonnull(pt5);
  /* check that the resultung part for table for slice 46 stops *
   * at around 6700 Angstrom, while slice 45 and 47 go to 9300  */
  muse_pixtable **pts5 = muse_pixtable_extracted_get_slices(pt5);
  double lambda45 = cpl_table_get(pts5[44]->table, MUSE_PIXTABLE_LAMBDA,
                                  muse_pixtable_get_nrow(pts5[44]) - 1, NULL),
         lambda46 = cpl_table_get(pts5[45]->table, MUSE_PIXTABLE_LAMBDA,
                                  muse_pixtable_get_nrow(pts5[45]) - 1, NULL),
         lambda47 = cpl_table_get(pts5[46]->table, MUSE_PIXTABLE_LAMBDA,
                                  muse_pixtable_get_nrow(pts5[46]) - 1, NULL);
  cpl_msg_debug(__func__, "last wavelength in slice 45/46/47: %f/%f/%f",
                lambda45, lambda46, lambda47);
  cpl_test(lambda46 < 6700.);
  cpl_test(lambda45 > 9300. && lambda47 > 9300.);
  muse_pixtable_extracted_delete(pts5);
  muse_pixtable_delete(pt5);
  /* test with completely missing entry for one slice 46, this should *
   * propagate error from muse_trace_table_get_polys_for_slice() but  *
   * otherwise get good results for 47 slices                         */
  cpl_table_erase_window(tracetable, 45, 1);
  /* should fail without MUSE_EXPERT_USER */
  ps = cpl_errorstate_get();
  pt5 = muse_pixtable_create(inimage, tracetable, wavecaltable, geotable);
  cpl_test(!cpl_errorstate_is_equal(ps) &&
           cpl_error_get_code() == CPL_ERROR_ILLEGAL_INPUT);
  cpl_errorstate_set(ps);
  cpl_test_null(pt5);
  /* should succeed partially with MUSE_EXPERT_USER=1 */
  cpl_test_zero(setenv("MUSE_EXPERT_USER", "1", 1));
  ps = cpl_errorstate_get();
  pt5 = muse_pixtable_create(inimage, tracetable, wavecaltable, geotable);
  cpl_test(!cpl_errorstate_is_equal(ps) &&
           cpl_error_get_code() == CPL_ERROR_ILLEGAL_INPUT);
  cpl_errorstate_set(ps);
  cpl_test_nonnull(pt5);
  cpl_test_zero(unsetenv("MUSE_EXPERT_USER"));
  pts5 = muse_pixtable_extracted_get_slices(pt5);
  cpl_test(muse_pixtable_extracted_get_size(pts5) == 47); /* one missing slice */
  muse_pixtable_extracted_delete(pts5);
  muse_pixtable_delete(pt5);
  cpl_table_insert(tracetable, tr46, 45); /* put that missing row back */
  cpl_table_delete(tr46);
  /* try with broken wavecal table (one slice missing) */
  cpl_table *wv46 = cpl_table_extract(wavecaltable, 45, 1);
  cpl_table_erase_window(wavecaltable, 45, 1);
  /* should fail without MUSE_EXPERT_USER */
  ps = cpl_errorstate_get();
  muse_pixtable *pt6 = muse_pixtable_create(inimage, tracetable, wavecaltable, geotable);
  cpl_test(!cpl_errorstate_is_equal(ps) &&
           cpl_error_get_code() == CPL_ERROR_ILLEGAL_INPUT);
  cpl_errorstate_set(ps);
  cpl_test_null(pt6);
  /* should succeed partially with MUSE_EXPERT_USER=1 */
  cpl_test_zero(setenv("MUSE_EXPERT_USER", "1", 1));
  ps = cpl_errorstate_get();
  pt6 = muse_pixtable_create(inimage, tracetable, wavecaltable, geotable);
  cpl_test(!cpl_errorstate_is_equal(ps) &&
           cpl_error_get_code() == CPL_ERROR_ILLEGAL_INPUT);
  cpl_errorstate_set(ps);
  cpl_test_nonnull(pt6);
  cpl_test_zero(unsetenv("MUSE_EXPERT_USER"));
  muse_pixtable **pts6 = muse_pixtable_extracted_get_slices(pt6);
  cpl_test(muse_pixtable_extracted_get_size(pts6) == 47); /* one missing slice */
  muse_pixtable_extracted_delete(pts6);
  muse_pixtable_delete(pt6);
  cpl_table_insert(wavecaltable, wv46, 45); /* put that missing row back */
  /* try with wavecal for one slice giving unlikely values */
  cpl_table_set(wavecaltable, "wlc00", 45, 2000.);
  cpl_table_set(wavecaltable, "wlc01", 45, 0.);
  cpl_table_set(wavecaltable, "wlc02", 45, 0.);
  cpl_table_set(wavecaltable, "wlc03", 45, 0.);
  cpl_table_set(wavecaltable, "wlc04", 45, 0.);
  cpl_table_set(wavecaltable, "wlc05", 45, 0.);
  cpl_table_set(wavecaltable, "wlc10", 45, 0.);
  cpl_table_set(wavecaltable, "wlc11", 45, 0.);
  cpl_table_set(wavecaltable, "wlc12", 45, 0.);
  cpl_table_set(wavecaltable, "wlc13", 45, 0.);
  cpl_table_set(wavecaltable, "wlc14", 45, 0.);
  cpl_table_set(wavecaltable, "wlc15", 45, 0.);
  cpl_table_set(wavecaltable, "wlc20", 45, 0.);
  cpl_table_set(wavecaltable, "wlc21", 45, 0.);
  cpl_table_set(wavecaltable, "wlc22", 45, 0.);
  cpl_table_set(wavecaltable, "wlc23", 45, 0.);
  cpl_table_set(wavecaltable, "wlc24", 45, 0.);
  cpl_table_set(wavecaltable, "wlc25", 45, 0.);
  cpl_table_set(wavecaltable, "MSE", 45, 1000.);
  ps = cpl_errorstate_get();
  pt6 = muse_pixtable_create(inimage, tracetable, wavecaltable, geotable);
  cpl_test(cpl_errorstate_is_equal(ps));
  cpl_errorstate_set(ps);
  cpl_test_nonnull(pt6);
  pts6 = muse_pixtable_extracted_get_slices(pt6);
  cpl_test(muse_pixtable_extracted_get_size(pts6) == 47); /* one missing slice */
  muse_pixtable_extracted_delete(pts6);
  muse_pixtable_delete(pt6);
  cpl_table_erase_window(wavecaltable, 45, 1);
  cpl_table_insert(wavecaltable, wv46, 45); /* put that missing row back */
  cpl_table_delete(wv46);
  /* test with broken geometry table, should completely fail without this IFU */
  cpl_table *geo1a = cpl_table_extract(geotable, 0, 48),
            *geo1b = cpl_table_duplicate(geo1a);
  cpl_table_erase_window(geotable, 0, 48);
  ps = cpl_errorstate_get();
  muse_pixtable *pt7 = muse_pixtable_create(inimage, tracetable, wavecaltable, geotable);
  cpl_test(!cpl_errorstate_is_equal(ps) &&
           cpl_error_get_code() == CPL_ERROR_ILLEGAL_INPUT);
  cpl_errorstate_set(ps);
  cpl_test_null(pt7);
  /* without x column */
  cpl_table_erase_column(geo1a, MUSE_GEOTABLE_X);
  ps = cpl_errorstate_get();
  pt7 = muse_pixtable_create(inimage, tracetable, wavecaltable, geo1a);
  cpl_test(!cpl_errorstate_is_equal(ps) &&
           cpl_error_get_code() == CPL_ERROR_ILLEGAL_INPUT);
  cpl_errorstate_set(ps);
  cpl_test_null(pt7);
  ps = cpl_errorstate_get();
  /* without y column */
  cpl_table_erase_column(geo1b, MUSE_GEOTABLE_Y);
  ps = cpl_errorstate_get();
  pt7 = muse_pixtable_create(inimage, tracetable, wavecaltable, geo1b);
  cpl_test(!cpl_errorstate_is_equal(ps) &&
           cpl_error_get_code() == CPL_ERROR_ILLEGAL_INPUT);
  cpl_errorstate_set(ps);
  /* without x and y columns */
  cpl_table_erase_column(geo1b, MUSE_GEOTABLE_X);
  ps = cpl_errorstate_get();
  pt7 = muse_pixtable_create(inimage, tracetable, wavecaltable, geo1b);
  cpl_test(!cpl_errorstate_is_equal(ps) &&
           cpl_error_get_code() == CPL_ERROR_ILLEGAL_INPUT);
  cpl_errorstate_set(ps);
  cpl_test_null(pt7);
  cpl_table_delete(geo1a);
  cpl_table_delete(geo1b);
  cpl_table_delete(wavecaltable);
  cpl_table_delete(tracetable);
  cpl_table_delete(geotable);
  muse_image_delete(inimage);

  /* test failure cases of muse_pixtable_get_type() */
  ps = cpl_errorstate_get();
  cpl_test(muse_pixtable_get_type(NULL) == MUSE_PIXTABLE_TYPE_UNKNOWN);
  cpl_test(!cpl_errorstate_is_equal(ps) &&
           cpl_error_get_code() == CPL_ERROR_NULL_INPUT);
  cpl_errorstate_set(ps);
  /* set bad pixel table type header */
  cpl_propertylist_update_string(pt2->header, MUSE_HDR_PT_TYPE, "blabla");
  ps = cpl_errorstate_get();
  cpl_test(muse_pixtable_get_type(pt2) == MUSE_PIXTABLE_TYPE_UNKNOWN);
  cpl_test(cpl_errorstate_is_equal(ps)); /* no new error set */

  /* test muse_pixtable_flux_multiply() */
  ps = cpl_errorstate_get();
  cpl_size nrow2 = muse_pixtable_get_nrow(pt2);
  float d1 = cpl_table_get(pt2->table, MUSE_PIXTABLE_DATA, 0, NULL),
        d2 = cpl_table_get(pt2->table, MUSE_PIXTABLE_DATA, nrow2 / 2, NULL),
        d3 = cpl_table_get(pt2->table, MUSE_PIXTABLE_DATA, nrow2 - 1, NULL);
  cpl_error_code rc = muse_pixtable_flux_multiply(pt2, 1.33);
  cpl_test(cpl_errorstate_is_equal(ps) && rc == CPL_ERROR_NONE);
  cpl_test_rel(cpl_table_get(pt2->table, MUSE_PIXTABLE_DATA, 0, NULL), d1 * 1.33, 5e-8);
  cpl_test_rel(cpl_table_get(pt2->table, MUSE_PIXTABLE_DATA, nrow2 / 2, NULL), d2 * 1.33, 5e-8);
  cpl_test_rel(cpl_table_get(pt2->table, MUSE_PIXTABLE_DATA, nrow2 - 1, NULL), d3 * 1.33, 5e-8);
  rc = muse_pixtable_flux_multiply(pt2, 1. / 1.33); /* reverse it */
  cpl_test(cpl_errorstate_is_equal(ps) && rc == CPL_ERROR_NONE);
  cpl_test_rel(cpl_table_get(pt2->table, MUSE_PIXTABLE_DATA, 0, NULL), d1, DBL_EPSILON);
  cpl_test_rel(cpl_table_get(pt2->table, MUSE_PIXTABLE_DATA, nrow2 / 2, NULL), d2, DBL_EPSILON);
  cpl_test_rel(cpl_table_get(pt2->table, MUSE_PIXTABLE_DATA, nrow2 - 1, NULL), d3, DBL_EPSILON);
  /* failure cases */
  ps = cpl_errorstate_get();
  rc = muse_pixtable_flux_multiply(NULL, 2.);
  cpl_test(!cpl_errorstate_is_equal(ps) && rc == CPL_ERROR_NULL_INPUT);
  cpl_errorstate_set(ps);
  cpl_table_delete(pt2->table);
  pt2->table = NULL;
  ps = cpl_errorstate_get();
  rc = muse_pixtable_flux_multiply(pt2, 2.);
  cpl_test(!cpl_errorstate_is_equal(ps) && rc == CPL_ERROR_NULL_INPUT);
  cpl_errorstate_set(ps);
  muse_pixtable_delete(pt2);

  /* test muse_pixtable_duplicate() */
  ps = cpl_errorstate_get();
  pt2 = muse_pixtable_duplicate(NULL);
  cpl_test(cpl_errorstate_is_equal(ps)); /* no new errors */
  cpl_test_null(pt2);
  ps = cpl_errorstate_get();
  pt2 = muse_pixtable_duplicate(pt); /* test success */
  cpl_test(cpl_errorstate_is_equal(ps));
  cpl_test_nonnull(pt2);
  /* compare first and last entry of the data column to be sure */
  muse_test_pixtable_compare(pt, pt2);

  /* test muse_pixtable_save() and muse_pixtable_load() as *
   * well as muse_pixtable_load_restricted_wavelength()    */
  cpl_test_zero(setenv("MUSE_PIXTABLE_SAVE_AS_TABLE", "1", 1)); /* bintable */
  char *fn = cpl_sprintf("%s/%s", dir, "muse_pixtable_as_table.fits");
  ps = cpl_errorstate_get();
  rc = muse_pixtable_save(pt, fn);
  cpl_test(cpl_errorstate_is_equal(ps) && rc == CPL_ERROR_NONE);
  muse_pixtable_delete(pt);
  struct stat sb;
  cpl_test_zero(stat(fn, &sb));
  /* the file written should be a normal file with size greater than zero... */
  cpl_test(S_ISREG(sb.st_mode) && sb.st_size > 0);
  /* ... and contain one (binary table) extension */
  cpl_test(cpl_fits_count_extensions(fn) == 1);
  /* load it again and compare to its old duplicate */
  ps = cpl_errorstate_get();
  pt = muse_pixtable_load(fn);
  cpl_test(cpl_errorstate_is_equal(ps));
  cpl_test_nonnull(pt);
  muse_test_pixtable_compare(pt2, pt);
  ps = cpl_errorstate_get();
  muse_pixtable *pt2x = muse_pixtable_load_restricted_wavelength(fn, 4000., 10000.);
  cpl_test(cpl_errorstate_is_equal(ps));
  cpl_test_nonnull(pt2x);
  muse_test_pixtable_compare(pt2x, pt); /* full range, should match */
  muse_pixtable_delete(pt2x);
  pt2x = muse_pixtable_load_restricted_wavelength(fn, 4999., 5001.);
  cpl_test(cpl_errorstate_is_equal(ps));
  cpl_test_nonnull(pt2x);
  cpl_test_eq(muse_pixtable_get_nrow(pt2x), 5688);
  cpl_test_abs(cpl_table_get_column_min(pt2x->table, MUSE_PIXTABLE_LAMBDA), 4999., 0.02);
  cpl_test_abs(cpl_table_get_column_max(pt2x->table, MUSE_PIXTABLE_LAMBDA), 5001., 0.025);
  muse_pixtable_delete(pt2x);
  /* test failure cases of muse_pixtable_load_restricted_wavelength() */
  ps = cpl_errorstate_get();
  pt2x = muse_pixtable_load_restricted_wavelength(NULL, 4000., 10000.);
  cpl_test(!cpl_errorstate_is_equal(ps) &&
           cpl_error_get_code() == CPL_ERROR_NULL_INPUT);
  cpl_errorstate_set(ps);
  /* don't know how to test failure of muse_pixtable_restrict_wavelength() */
  cpl_test_null(pt2x);
  pt2x = muse_pixtable_load_restricted_wavelength(fn, 5001., 4999.);
  cpl_test(!cpl_errorstate_is_equal(ps) && /* backwards range */
           cpl_error_get_code() == CPL_ERROR_DATA_NOT_FOUND);
  cpl_errorstate_set(ps);
  cpl_test_null(pt2x);
  cpl_test_zero(unsetenv("MUSE_PIXTABLE_SAVE_AS_TABLE"));

  /* test saving and re-loading as image */
  char *fn3 = cpl_sprintf("%s/%s", dir, "muse_pixtable_as_image.fits");
  ps = cpl_errorstate_get();
  rc = muse_pixtable_save(pt, fn3);
  cpl_test(cpl_errorstate_is_equal(ps));
  cpl_test_zero(stat(fn3, &sb));
  cpl_test(S_ISREG(sb.st_mode) && sb.st_size > 0);
  /* ... and contain seven (image) extensions */
  cpl_test(cpl_fits_count_extensions(fn3) == 7);
  ps = cpl_errorstate_get();
  muse_pixtable *pt3 = muse_pixtable_load(fn3);
  cpl_test(cpl_errorstate_is_equal(ps));
  cpl_test_nonnull(pt3);
  muse_test_pixtable_compare(pt, pt3);
  muse_pixtable_delete(pt3);
  /* should still detect it without env. var set */
  pt3 = muse_pixtable_load(fn3);
  cpl_test(cpl_errorstate_is_equal(ps));
  cpl_test_nonnull(pt3);
  muse_test_pixtable_compare(pt, pt3);
  muse_pixtable_delete(pt3);
  /* test muse_pixtable_load_window(), should work equally well on both formats */
  muse_pixtable *ptcut = muse_pixtable_duplicate(pt),
                *ptw0 = muse_pixtable_load_window(fn, 10, 12),
                *ptw3 = muse_pixtable_load_window(fn3, 10, 12);
  /* cut part of the original table */
  cpl_table_erase_window(ptcut->table, 10+12, cpl_table_get_nrow(ptcut->table));
  cpl_table_erase_window(ptcut->table, 0, 10);
  muse_test_pixtable_compare(ptcut, ptw3); /* one should be equal to part of original */
  muse_test_pixtable_compare(ptw0, ptw3); /* both should be the same */
  muse_pixtable_delete(ptcut);
  muse_pixtable_delete(ptw0);
  muse_pixtable_delete(ptw3);
  cpl_test_zero(remove(fn));
  cpl_test_zero(remove(fn3));
  cpl_free(fn3);
  /* test failure cases of muse_pixtable_save() */
  ps = cpl_errorstate_get();
  rc = muse_pixtable_save(NULL, fn);
  cpl_test(!cpl_errorstate_is_equal(ps) && rc == CPL_ERROR_NULL_INPUT);
  cpl_errorstate_set(ps);
  rc = muse_pixtable_save(pt2, NULL);
  cpl_test(!cpl_errorstate_is_equal(ps) && rc == CPL_ERROR_NULL_INPUT);
  cpl_errorstate_set(ps);
  cpl_test(cpl_errorstate_is_equal(ps));
  rc = muse_pixtable_save(pt2, "/pt.fits"); /* somewhere unaccessible */
  cpl_test(!cpl_errorstate_is_equal(ps) && rc == CPL_ERROR_FILE_IO);
  cpl_errorstate_set(ps);
  muse_pixtable_delete(pt2);
  cpl_free(fn);
  /* test failure cases of muse_pixtable_load() */
  ps = cpl_errorstate_get();
  pt2 = muse_pixtable_load(NULL);
  cpl_test(!cpl_errorstate_is_equal(ps) &&
           cpl_error_get_code() == CPL_ERROR_NULL_INPUT);
  cpl_errorstate_set(ps);
  cpl_test_null(pt2);
  pt2 = muse_pixtable_load("/bla_fail/pt2.fits"); /* somewhere inaccessible */
  cpl_test(!cpl_errorstate_is_equal(ps) &&
           cpl_error_get_code() == CPL_ERROR_FILE_IO);
  cpl_errorstate_set(ps);
  cpl_test_null(pt2);

  /* test muse_pixtable_load_merge_channels() */
  /* start with a simple failure case */
  ps = cpl_errorstate_get();
  muse_pixtable *ptmerged = muse_pixtable_load_merge_channels(NULL, 4000., 10000.);
  cpl_test(!cpl_errorstate_is_equal(ps) &&
           cpl_error_get_code() == CPL_ERROR_NULL_INPUT);
  cpl_test_null(ptmerged);
  cpl_errorstate_set(ps);
  /* now construct an exposure table and save small dummy pixel tables */
  cpl_table *exptable = muse_test_pixtable_create_exptable(dir);
  cpl_size irow;
  for (irow = 0; irow < cpl_table_get_nrow(exptable); irow++) {
    cpl_table *et = cpl_table_extract(exptable, irow, 1);
    if (irow == 0) {
      cpl_table_erase_column(et, "00");
    }
    ptmerged = muse_pixtable_load_merge_channels(et, 4000., 10000.);
    if (irow != 2) { /* this was is missing on purpose! */
      cpl_test(cpl_errorstate_is_equal(ps));
      cpl_test_nonnull(ptmerged);
      if (irow == 0) { /* all IFUs there */
        cpl_test(muse_pixtable_get_nrow(ptmerged) == 10 * 24);
      } else if (irow == 1) { /* two missing ones */
        cpl_test(muse_pixtable_get_nrow(ptmerged) == 10 * 22);
      } else if (irow == 3) { /* was "merged" */
        cpl_test(muse_pixtable_get_nrow(ptmerged) == 10);
      }
      /* all wavelengths should be left */
      cpl_test_rel(cpl_table_get_column_min(ptmerged->table, MUSE_PIXTABLE_LAMBDA),
                   4800., DBL_EPSILON);
      cpl_test_rel(cpl_table_get_column_max(ptmerged->table, MUSE_PIXTABLE_LAMBDA),
                   5500., DBL_EPSILON);
      muse_pixtable_delete(ptmerged);
    } else {
      cpl_test(!cpl_errorstate_is_equal(ps)
               && cpl_error_get_code() == CPL_ERROR_FILE_NOT_FOUND);
      cpl_test_null(ptmerged);
      cpl_errorstate_set(ps);
    }
    /* with with restricted range, only 3 of 10 input rows should be left */
    muse_pixtable *ptmergedx =  muse_pixtable_load_merge_channels(et, 4999., 5001.);
    if (irow != 2) { /* this was is missing on purpose! */
      cpl_test(cpl_errorstate_is_equal(ps));
      cpl_test_nonnull(ptmergedx);
      if (irow == 0) { /* all IFUs there */
        cpl_test(muse_pixtable_get_nrow(ptmergedx) == 3 * 24);
      } else if (irow == 1) { /* two missing ones */
        cpl_test(muse_pixtable_get_nrow(ptmergedx) == 3 * 22);
      } else if (irow == 3) { /* was "merged" */
        cpl_test(muse_pixtable_get_nrow(ptmergedx) == 3);
      }
      /* only wavelengths of 5000. Angstrom should be left */
      cpl_test_rel(cpl_table_get_column_min(ptmergedx->table, MUSE_PIXTABLE_LAMBDA),
                   5000., DBL_EPSILON);
      cpl_test_rel(cpl_table_get_column_max(ptmergedx->table, MUSE_PIXTABLE_LAMBDA),
                   5000., DBL_EPSILON);
      muse_pixtable_delete(ptmergedx);
    } else {
      cpl_test(!cpl_errorstate_is_equal(ps)
               && cpl_error_get_code() == CPL_ERROR_FILE_NOT_FOUND);
      cpl_test_null(ptmergedx);
      cpl_errorstate_set(ps);
    }
    cpl_table_delete(et);
  } /* for irow (exposure table rows) */
  muse_test_pixtable_delete_exptable(exptable);

  /* pseudo-test for muse_pixtable_dump(), check the output that it worked... */
  cpl_msg_debug(__func__, "muse_pixtable_dump() with header with units");
  muse_pixtable_dump(pt, 0, 10, 1);
  cpl_msg_debug(__func__, "muse_pixtable_dump() with header without units");
  muse_pixtable_dump(pt, 0, 10, 2);
  cpl_msg_debug(__func__, "muse_pixtable_dump() without header");
  muse_pixtable_dump(pt, 0, 10, 0);
  ps = cpl_errorstate_get();
  muse_pixtable_dump(NULL, 0, 10, 0);
  cpl_test(!cpl_errorstate_is_equal(ps) &&
           cpl_error_get_code() == CPL_ERROR_NULL_INPUT);
  cpl_errorstate_set(ps);
  muse_pixtable_dump(pt, -5, 10, 0);
  cpl_test(!cpl_errorstate_is_equal(ps) &&
           cpl_error_get_code() == CPL_ERROR_ILLEGAL_INPUT);
  cpl_errorstate_set(ps);
  muse_pixtable_dump(pt, 2, -5, 0);
  cpl_test(!cpl_errorstate_is_equal(ps) &&
           cpl_error_get_code() == CPL_ERROR_ILLEGAL_INPUT);
  cpl_errorstate_set(ps);
  cpl_table_erase_column(pt->table, MUSE_PIXTABLE_LAMBDA);
  muse_pixtable_dump(pt, 0, 10, 0); /* column missing */
  cpl_test(!cpl_errorstate_is_equal(ps) &&
           cpl_error_get_code() == CPL_ERROR_BAD_FILE_FORMAT);
  cpl_errorstate_set(ps);

  /* test muse_pixtable_is_fluxcal(), muse_pixtable_is_skysub(), *
   * and muse_pixtable_is_rvcorr()                               */
  ps = cpl_errorstate_get();
  cpl_boolean fluxcal = muse_pixtable_is_fluxcal(pt);
  cpl_test(!fluxcal); /* set up as not flux-calibrated */
  cpl_boolean skysub = muse_pixtable_is_skysub(pt);
  cpl_test(!skysub); /* set up as not sky subtracted */
  cpl_test(cpl_errorstate_is_equal(ps));
  cpl_boolean rvcorr = muse_pixtable_is_rvcorr(pt);
  cpl_test(!rvcorr); /* set up as not RV corrected */
  muse_pixtable *ptdupe = muse_pixtable_duplicate(pt);
  cpl_propertylist_append_double(ptdupe->header, MUSE_HDR_PT_RVCORR, 10.);
  rvcorr = muse_pixtable_is_rvcorr(ptdupe);
  cpl_test(rvcorr); /* set up as RV corrected */
  muse_pixtable_delete(ptdupe);
  /* failure cases */
  ps = cpl_errorstate_get();
  fluxcal = muse_pixtable_is_fluxcal(NULL);
  cpl_test(!cpl_errorstate_is_equal(ps) && cpl_error_get_code() == CPL_ERROR_NULL_INPUT);
  cpl_errorstate_set(ps);
  skysub = muse_pixtable_is_skysub(NULL);
  cpl_test(!cpl_errorstate_is_equal(ps) && cpl_error_get_code() == CPL_ERROR_NULL_INPUT);
  cpl_errorstate_set(ps);
  rvcorr = muse_pixtable_is_rvcorr(NULL);
  cpl_test(!cpl_errorstate_is_equal(ps) && cpl_error_get_code() == CPL_ERROR_NULL_INPUT);
  cpl_errorstate_set(ps);
  /* success cases are tested in muse_test_flux and sky_master, respectively */

  /* test muse_pixtable_delete() */
  ps = cpl_errorstate_get();
  muse_pixtable_delete(NULL); /* "failure" case */
  cpl_test(cpl_errorstate_is_equal(ps));
  muse_pixtable_delete(pt); /* normal case */
  cpl_test(cpl_errorstate_is_equal(ps));

  /* test muse_pixtable_to_imagelist() and muse_pixtable_from_imagelist(); *
   * for these tests, start with the original input data again             */
  pt = muse_pixtable_duplicate(ptkeep); /* duplicate for the work */
  ps = cpl_errorstate_get();
  muse_imagelist *images = muse_pixtable_to_imagelist(pt);
  cpl_test(cpl_errorstate_is_equal(ps));
  cpl_test(muse_imagelist_get_size(images) == 1);
  /* cross check valid datapoints with the original input image */
  inimage = muse_image_load(BASEFILENAME"_create_data.fits");
  float *indata = cpl_image_get_data_float(inimage->data),
        *instat = cpl_image_get_data_float(inimage->stat),
        *outdata = cpl_image_get_data_float(muse_imagelist_get(images, 0)->data),
        *outstat = cpl_image_get_data_float(muse_imagelist_get(images, 0)->stat);
  int *indq = cpl_image_get_data_int(inimage->dq),
      *outdq = cpl_image_get_data_int(muse_imagelist_get(images, 0)->dq);
  int i, j, nmissing = 0,
      nx = cpl_image_get_size_x(inimage->data),
      ny = cpl_image_get_size_y(inimage->data);
  cpl_boolean ok = CPL_TRUE;
  for (i = 0; i < nx; i++) {
    for (j = 0; j < ny; j++) {
      cpl_size pos = i + j*nx;
      if (outdq[pos] & EURO3D_MISSDATA) {
        nmissing++;
        continue; /* region outside a slice, this will be different! */
      }
      /* all other pixels should be identical */
      if ((indq[pos] != outdq[pos]) ||
          (fabs(indata[pos] - outdata[pos]) > FLT_EPSILON) ||
          (fabs(instat[pos] - outstat[pos]) > FLT_EPSILON)) {
        ok = CPL_FALSE;
      } /* if */
    } /* for j */
  } /* for i */
  muse_image_delete(inimage);
  cpl_test(ok);
  cpl_msg_debug(__func__, "found %d pixels outside slices", nmissing);
  /* should be at least 6 x 49 x 4112 = 1.2e6 pixels outside slices,   *
   * but no more than 20% of the whole CCD (4096 x 4112 * 0.2 = 3.3e6) */
  cpl_test(nmissing > 1.2e6 && nmissing < 3.3e6);
  /* check that the slice centers are all there and in increasing order */
  float oldcenter = 0;
  int nslice;
  for (nslice = 1; nslice <= kMuseSlicesPerCCD; nslice++) {
    char *kw = cpl_sprintf("ESO DRS MUSE SLICE%0d CENTER", nslice);
    float center = cpl_propertylist_get_float(muse_imagelist_get(images, 0)->header,
                                              kw);
    cpl_test(center > oldcenter);
    cpl_free(kw);
    oldcenter = center;
  } /* for nslice */
  /* fill the data and stat columns with some values *
   * before, to check that they get overwritten      */
  cpl_size nrow = muse_pixtable_get_nrow(pt);
  cpl_table_fill_column_window(pt->table, MUSE_PIXTABLE_DATA, 0, nrow, -999.);
  cpl_table_fill_column_window(pt->table, MUSE_PIXTABLE_STAT, 0, nrow, 999e9);
  ps = cpl_errorstate_get();
  rc = muse_pixtable_from_imagelist(pt, images);
  cpl_test(cpl_errorstate_is_equal(ps) && rc == CPL_ERROR_NONE);
  muse_imagelist_delete(images);
  ok = CPL_TRUE;
  for (irow = 0; irow < nrow; irow++) {
    double ddiff = fabs(cpl_table_get_float(pt->table, MUSE_PIXTABLE_DATA, irow, NULL)
                        - cpl_table_get_float(ptkeep->table, MUSE_PIXTABLE_DATA, irow, NULL)),
           sdiff = fabs(cpl_table_get_float(pt->table, MUSE_PIXTABLE_STAT, irow, NULL)
                        - cpl_table_get_float(ptkeep->table, MUSE_PIXTABLE_STAT, irow, NULL));
    if (ddiff > FLT_EPSILON || sdiff > FLT_EPSILON) {
      ok = CPL_FALSE;
    } /* if bad comparison */
  } /* for irow */
  cpl_test(ok);
  muse_pixtable_delete(pt);

  /* test failure cases that are not otherwise covered */
  cpl_test(muse_pixtable_wcs_check(ptkeep) == MUSE_PIXTABLE_WCS_PIXEL);
  cpl_table_set_column_unit(ptkeep->table, MUSE_PIXTABLE_XPOS, "bla");
  ps = cpl_errorstate_get();
  cpl_test(muse_pixtable_wcs_check(ptkeep) == MUSE_PIXTABLE_WCS_UNKNOWN);
  cpl_test(!cpl_errorstate_is_equal(ps) /* different units */
           && cpl_error_get_code() == CPL_ERROR_INCOMPATIBLE_INPUT);
  cpl_errorstate_set(ps);
  cpl_table_set_column_unit(ptkeep->table, MUSE_PIXTABLE_YPOS, "bla");
  cpl_test(muse_pixtable_wcs_check(ptkeep) == MUSE_PIXTABLE_WCS_UNKNOWN);
  cpl_test(!cpl_errorstate_is_equal(ps)
           && cpl_error_get_code() == CPL_ERROR_ILLEGAL_INPUT);
  cpl_errorstate_set(ps);
  cpl_propertylist_erase(ptkeep->header, MUSE_HDR_PT_TYPE);
  cpl_test(muse_pixtable_get_type(ptkeep) == MUSE_PIXTABLE_TYPE_UNKNOWN);
  cpl_test(!cpl_errorstate_is_equal(ps)
           && cpl_error_get_code() == CPL_ERROR_DATA_NOT_FOUND);
  cpl_errorstate_set(ps);
  muse_pixtable_delete(ptkeep);

  /* muse_pixtable_extracted_*() are already well covered implicitly by  *
   * the muse_test_resampling program, and muse_pixtable_to_imagelist(), *
   * so just test failure cases here                                     */
  ps = cpl_errorstate_get();
  muse_pixtable **pts = muse_pixtable_extracted_get_slices(NULL);
  cpl_test(!cpl_errorstate_is_equal(ps)
           && cpl_error_get_code() == CPL_ERROR_NULL_INPUT);
  cpl_errorstate_set(ps);
  cpl_test_null(pts);
  muse_pixtable_extracted_get_size(NULL);
  cpl_test(!cpl_errorstate_is_equal(ps)
           && cpl_error_get_code() == CPL_ERROR_NULL_INPUT);
  cpl_errorstate_set(ps);
  muse_pixtable_extracted_delete(NULL);
  cpl_test(cpl_errorstate_is_equal(ps));

  /* test muse_pixtable_erase_ifu_slice() */
  muse_pixtable *ptsmall = cpl_calloc(1, sizeof(muse_pixtable));
  ptsmall->table = muse_cpltable_new(muse_pixtable_def, PTLEN);
  cpl_table_fill_column_window_float(ptsmall->table, MUSE_PIXTABLE_XPOS, 0, PTLEN, 0.);
  cpl_table_fill_column_window_float(ptsmall->table, MUSE_PIXTABLE_YPOS, 0, PTLEN, 0.);
  cpl_table_fill_column_window_float(ptsmall->table, MUSE_PIXTABLE_LAMBDA, 0, PTLEN, 0.);
  cpl_table_fill_column_window_float(ptsmall->table, MUSE_PIXTABLE_DATA, 0, PTLEN, 0.);
  cpl_table_fill_column_window_int(ptsmall->table, MUSE_PIXTABLE_DQ, 0, PTLEN, 0.);
  cpl_table_fill_column_window_float(ptsmall->table, MUSE_PIXTABLE_STAT, 0, PTLEN, 0.);
  ptsmall->header = cpl_propertylist_new(); /* needed for dump */
  int ir;
  for (ir = 0; ir < PTLEN; ir++) {
    ifu = ir < 4 ? 1 : ir < 8 ? 3 : 5;
    slice = ir == 6 ? ir : ir + 1; /* get two IFU 3 / Slice 6 */
    cpl_table_set_int(ptsmall->table, MUSE_PIXTABLE_ORIGIN, ir,
                      muse_pixtable_origin_encode(1, 1, ifu, slice, 100));
  } /* for ir */
  cpl_errorstate pstate = cpl_errorstate_get();
#if 0
  printf("ptsmall original:\n");
  muse_pixtable_dump(ptsmall, 0, 100, 1);
  fflush(stdout);
  cpl_errorstate_set(pstate); /* swallow missing-header-info errors */
#endif
  int norig = muse_pixtable_get_nrow(ptsmall);
  pstate = cpl_errorstate_get();
  cpl_error_code ec = muse_pixtable_erase_ifu_slice(ptsmall, 1, 1);
  cpl_test(ec == CPL_ERROR_NONE && cpl_errorstate_is_equal(pstate));
  cpl_test_eq(muse_pixtable_get_nrow(ptsmall), norig - 1); /* one row less */
#if 0
  pstate = cpl_errorstate_get();
  printf("ptsmall erased 1/1:\n");
  muse_pixtable_dump(ptsmall, 0, 100, 1);
  fflush(stdout);
  cpl_errorstate_set(pstate); /* swallow missing-header-info errors */
#endif
  /* should work when the given IFU/slice is not there */
  pstate = cpl_errorstate_get();
  ec = muse_pixtable_erase_ifu_slice(ptsmall, 2, 1);
  cpl_test(ec == CPL_ERROR_NONE && cpl_errorstate_is_equal(pstate));
  ec = muse_pixtable_erase_ifu_slice(ptsmall, 6, 10);
  cpl_test(ec == CPL_ERROR_NONE && cpl_errorstate_is_equal(pstate));
  cpl_test_eq(muse_pixtable_get_nrow(ptsmall), norig - 1); /* still the same */
  /* should delete all entries for a slice */
  pstate = cpl_errorstate_get();
  ec = muse_pixtable_erase_ifu_slice(ptsmall, 3, 6);
  cpl_test(ec == CPL_ERROR_NONE && cpl_errorstate_is_equal(pstate));
  cpl_test_eq(muse_pixtable_get_nrow(ptsmall), norig - 3); /* now 3 less */
#if 0
  pstate = cpl_errorstate_get();
  printf("ptsmall erased 3/6:\n");
  muse_pixtable_dump(ptsmall, 0, 100, 1);
  fflush(stdout);
  cpl_errorstate_set(pstate); /* swallow missing-header-info errors */
#endif
  /* failure cases */
  pstate = cpl_errorstate_get();
  ec = muse_pixtable_erase_ifu_slice(NULL, 5, 9);
  cpl_test(ec == CPL_ERROR_NULL_INPUT && !cpl_errorstate_is_equal(pstate));
  cpl_errorstate_set(pstate);
  cpl_table_select_all(ptsmall->table);
  cpl_table_erase_selected(ptsmall->table);
  ec = muse_pixtable_erase_ifu_slice(ptsmall, 5, 9); /* no rows */
  cpl_test(ec == CPL_ERROR_DATA_NOT_FOUND && !cpl_errorstate_is_equal(pstate));
  cpl_errorstate_set(pstate);
  muse_pixtable_delete(ptsmall);

  /* also remove the temporary dir */
  cpl_test_zero(rmdir(dir));

  return cpl_test_end(0);
}
