/* -*- 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) 2005-2017 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.
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

/*----------------------------------------------------------------------------*
 *                              Includes                                      *
 *----------------------------------------------------------------------------*/
#include <cpl.h>
#include <math.h>
#include <string.h>
#ifndef _OPENMP
#define omp_get_max_threads() 1
#define omp_get_thread_num() 0
#else
#include <omp.h>
#endif

#include "muse_pixgrid.h"

#include "muse_pfits.h"
#include "muse_quality.h"
#include "muse_utils.h"
#include "muse_wcs.h"

/*----------------------------------------------------------------------------*/
/**
 * @defgroup muse_pixgrid      Pixel grid
 *
 * The pixel grid is an index structure that is used when resampling a pixel
 * table into a datacube but can generally be used to find neighbors in the 3D
 * data of the pixel table.
 */
/*----------------------------------------------------------------------------*/

/**@{*/

/*---------------------------------------------------------------------------*/
/**
  @private
  @brief   Create a new pixel grid.
  @param   aSizeX   X size of the grid.
  @param   aSizeY   Y size of the grid.
  @param   aSizeZ   Z size of the grid.
  @param   aNMaps   number of extensions maps.
  @return  Pointer to the newly created pixel grid.
*/
/*---------------------------------------------------------------------------*/
static muse_pixgrid *
muse_pixgrid_new(cpl_size aSizeX, cpl_size aSizeY, cpl_size aSizeZ,
                 unsigned short aNMaps)
{
  muse_pixgrid *pixels = cpl_calloc(1, sizeof(muse_pixgrid));
  pixels->nx = aSizeX;
  pixels->ny = aSizeY;
  pixels->nz = aSizeZ;
  pixels->pix = cpl_calloc(aSizeX * aSizeY * aSizeZ, sizeof(cpl_size));
  /* extension maps for possibly multiple threads */
  pixels->nmaps = aNMaps;
  pixels->nxalloc = cpl_calloc(aNMaps, sizeof(cpl_size));
  pixels->xmaps = cpl_calloc(aNMaps, sizeof(muse_pixels_ext *));
  pixels->nxmap = cpl_calloc(aNMaps, sizeof(cpl_size));
  return pixels;
} /* muse_pixgrid_new() */

/*---------------------------------------------------------------------------*/
/**
  @private
  @brief   Add a table row to the pixel grid.
  @param   aGrid    Pointer to pixel grid.
  @param   aIndex   Pixel index, as computed by muse_pixgrid_get_index().
  @param   aRow     Row number to be added.
  @param   aXIdx    Index of the extension map to use.

  This function adds a new entry into the grid, either directly in the grid or
  in the extension maps (aGrid->xmaps). To do the latter, it allocates space
  for storage in one extension map, doubling the amount of allocated memory
  every time an enlargement is needed. The number of real (filled) extension map
  entries is stored in aGrid->nxmap, while the current number of allocated
  entries is tracked with aGrid->nxalloc.
*/
/*---------------------------------------------------------------------------*/
static void
muse_pixgrid_add(muse_pixgrid *aGrid, cpl_size aIndex, cpl_size aRow,
                 unsigned short aXIdx)
{
  if (aIndex < 0) {
    return;
  }

  if (aGrid->pix[aIndex] == 0 && aRow > 0) {
    /* First pixel is stored directly. */
    aGrid->pix[aIndex] = aRow;
  } else if (aGrid->pix[aIndex] == 0 && aRow == 0) {
    /* Special case: we cannot put "0" into the main map. */
    cpl_size iext = aGrid->nxmap[aXIdx]++;
    if (aGrid->nxmap[aXIdx] > aGrid->nxalloc[aXIdx]) {
      /* double the number of allocated entries */
      aGrid->nxalloc[aXIdx] = 2 * aGrid->nxmap[aXIdx];
      aGrid->xmaps[aXIdx] = cpl_realloc(aGrid->xmaps[aXIdx],
                                        aGrid->nxalloc[aXIdx]
                                        * sizeof(muse_pixels_ext));
    }
    aGrid->xmaps[aXIdx][iext].npix = 1;
    aGrid->xmaps[aXIdx][iext].pix = cpl_malloc(sizeof(cpl_size));
    aGrid->xmaps[aXIdx][iext].pix[0] = aRow;
    aGrid->pix[aIndex] = -(iext + 1 + ((cpl_size)aXIdx << XMAP_LSHIFT));
  } else if (aGrid->pix[aIndex] > 0) {
    /* When a second pixel is added, put both to the extension map. */
    cpl_size iext = aGrid->nxmap[aXIdx]++;
    if (aGrid->nxmap[aXIdx] > aGrid->nxalloc[aXIdx]) {
      /* double the number of allocated entries */
      aGrid->nxalloc[aXIdx] = 2 * aGrid->nxmap[aXIdx];
      aGrid->xmaps[aXIdx] = cpl_realloc(aGrid->xmaps[aXIdx],
                                        aGrid->nxalloc[aXIdx]
                                        * sizeof(muse_pixels_ext));
    }
    aGrid->xmaps[aXIdx][iext].npix = 2;
    aGrid->xmaps[aXIdx][iext].pix = cpl_malloc(2 * sizeof(cpl_size));
    aGrid->xmaps[aXIdx][iext].pix[0] = aGrid->pix[aIndex];
    aGrid->xmaps[aXIdx][iext].pix[1] = aRow;
    aGrid->pix[aIndex] = -(iext + 1 + ((cpl_size)aXIdx << XMAP_LSHIFT));
  } else {
    /* Append additional pixels to the extension map. */
    cpl_size iext = (-aGrid->pix[aIndex] - 1) & PT_IDX_MASK;
    /* index of the new entry in this grid point */
    unsigned int ipix = aGrid->xmaps[aXIdx][iext].npix;
    aGrid->xmaps[aXIdx][iext].npix++;
    aGrid->xmaps[aXIdx][iext].pix = cpl_realloc(aGrid->xmaps[aXIdx][iext].pix,
                                                (ipix + 1) * sizeof(cpl_size));
    aGrid->xmaps[aXIdx][iext].pix[ipix] = aRow;
  }
} /* muse_pixgrid_add() */

/*---------------------------------------------------------------------------*/
/**
  @private
  @brief   Dump the extension maps of the pixel grid to screen.
  @param   aGrid   Pointer to pixel grid.
  @param   aFull   specify, if all pixel entries should be printed.
*/
/*---------------------------------------------------------------------------*/
static void
muse_pixgrid_dump_xmaps(muse_pixgrid *aGrid, cpl_boolean aFull)
{
  if (!aGrid) {
    return;
  }
  /* clean up extension maps */
  cpl_msg_debug(__func__, "Dumping %u extension maps:", aGrid->nmaps);
  unsigned short ix;
  for (ix = 0; ix < aGrid->nmaps; ix++) {
    cpl_msg_debug(__func__, "- Map %u (%"CPL_SIZE_FORMAT" / %"CPL_SIZE_FORMAT
                  " entries):", ix + 1u, aGrid->nxmap[ix], aGrid->nxalloc[ix]);
    cpl_size iext, /* index in this extension map */
             nmax = aFull ? aGrid->nxalloc[ix] : 0;
    for (iext = 0; iext < nmax; iext++) {
      unsigned int ipix;
      for (ipix = 0; ipix < aGrid->xmaps[ix][iext].npix; ipix++) {
        cpl_size value = aGrid->xmaps[ix][iext].pix[ipix];
        char *warning = NULL;
        if (value < 0 || value > 13534057) {
          warning = cpl_sprintf("   WARNING!!!");
        }
        if (ipix == 0) {
          cpl_msg_debug(__func__, "  %08"CPL_SIZE_FORMAT" %u: %"CPL_SIZE_FORMAT"%s",
                        iext + 1, ipix + 1u, value, warning ? warning : "");
          cpl_free(warning);
          continue;
        }
        cpl_msg_debug(__func__, "           %u: %"CPL_SIZE_FORMAT"%s", ipix + 1u,
                      value, warning ? warning : "");
        cpl_free(warning);
      }
      if (1 + iext > aGrid->nxmap[ix]) {
        cpl_msg_debug(__func__, "Not initialized!");
      }
    } /* for iext (all allocated pixels in this extension map) */
  } /* for ix (all extension maps) */
} /* muse_pixgrid_dump_xmaps() */

/*---------------------------------------------------------------------------*/
/**
  @brief   Convert selected rows of a pixel table into pixel grid, linking the
           grid points to entries (=rows) in the pixel table.
  @param   aPixtable   the input pixel table
  @param   aHeader     the FITS header of the MUSE datacube to be created
  @param   aXSize      x size of the output grid
  @param   aYSize      y size of the output grid
  @param   aZSize      z size of the output grid (wavelength direction)
  @return  A muse_pixels * buffer for the output pixel grid or NULL on error.
  @remark  The returned pixel grid has to be deallocated after use with
           muse_pixgrid_delete().

  Construct a standard C array, where the array indices representing the 3D grid
  in the sense the the x-coordinate is varying fastest, the lambda-coordinate
  varying slowest (like in FITS buffers), i.e.
    index = [i + nx * j + nx*ny * l]
    (i: x-axis index, j: y-axis index, l: lambda-axis index,
    nx: x-axis length, ny: y-axis length).
  For each pixel table row search for the closest grid point. Store the pixel
  table row number in that grid point.

  @error{set CPL_ERROR_NULL_INPUT\, return NULL,
         aPixtable is NULL or it contains zero rows}
  @error{set CPL_ERROR_ILLEGAL_INPUT\, return NULL,
         one of the sizes is not positive}
  @error{set CPL_ERROR_UNSUPPORTED_MODE\, return NULL,
         the WCS in the pixel table is neither in pixels nor degrees}
  @error{set CPL_ERROR_DATA_NOT_FOUND\, return NULL,
         aPixtable is missing one of the coordinate columns}
  @error{set CPL_ERROR_ILLEGAL_OUTPUT and ERROR message\, but return created grid,
         aPixtable contains different number of pixels than the output pixel grid}
 */
/*---------------------------------------------------------------------------*/
muse_pixgrid *
muse_pixgrid_create(muse_pixtable *aPixtable, cpl_propertylist *aHeader,
                    cpl_size aXSize, cpl_size aYSize, cpl_size aZSize)
{
  cpl_ensure(aPixtable, CPL_ERROR_NULL_INPUT, NULL);
  cpl_size nrow = muse_pixtable_get_nrow(aPixtable);
  if (nrow == 0) {
    cpl_msg_error(__func__, "Invalid pixel table (no entries?)");
    cpl_error_set(__func__, CPL_ERROR_NULL_INPUT);
    return NULL;
  }
  cpl_ensure(aXSize > 0 && aYSize > 0 && aZSize > 0, CPL_ERROR_ILLEGAL_INPUT,
             NULL);
  muse_pixtable_wcs wcstype = muse_pixtable_wcs_check(aPixtable);
  cpl_ensure(wcstype == MUSE_PIXTABLE_WCS_CELSPH ||
             wcstype == MUSE_PIXTABLE_WCS_PIXEL, CPL_ERROR_UNSUPPORTED_MODE,
             NULL);

  double crval3 = muse_pfits_get_crval(aHeader, 3),
         crpix3 = muse_pfits_get_crpix(aHeader, 3),
         cd33 = muse_pfits_get_cd(aHeader, 3, 3);
  const char *ctype3 = muse_pfits_get_ctype(aHeader, 3);
  muse_wcs *wcs = muse_wcs_new(aHeader);
  wcs->iscelsph = wcstype == MUSE_PIXTABLE_WCS_CELSPH;
  cpl_boolean loglambda = ctype3 && (!strncmp(ctype3, "AWAV-LOG", 9) ||
                                     !strncmp(ctype3, "WAVE-LOG", 9));
  /* get all (relevant) table columns for easy pointer access */
  double ptxoff = 0., ptyoff = 0.;
  if (wcs->iscelsph) {
    ptxoff = muse_pfits_get_crval(aPixtable->header, 1);
    ptyoff = muse_pfits_get_crval(aPixtable->header, 2);
  }
  float *xpos = cpl_table_get_data_float(aPixtable->table, MUSE_PIXTABLE_XPOS),
        *ypos = cpl_table_get_data_float(aPixtable->table, MUSE_PIXTABLE_YPOS),
        *lbda = cpl_table_get_data_float(aPixtable->table, MUSE_PIXTABLE_LAMBDA);
  if (!xpos || !ypos || !lbda) {
    cpl_msg_error(__func__, "Missing pixel table column (%p %p %p): %s",
                  (void *)xpos, (void *)ypos, (void *)lbda,
                  cpl_error_get_message());
    cpl_error_set(__func__, CPL_ERROR_DATA_NOT_FOUND);
    cpl_free(wcs);
    return NULL;
  }
#ifdef ESO_ENABLE_DEBUG
  int debug = 0;
  if (getenv("MUSE_DEBUG_GRID_CONVERSION")) {
    debug = atoi(getenv("MUSE_DEBUG_GRID_CONVERSION"));
  }
  if (debug) {
    printf("crpix=%f %f %f, crval=%f %f %f, cd=%e %e %f\n",
           wcs->crpix1, wcs->crpix2, crpix3, wcs->crval1, wcs->crval2, crval3,
           wcs->cd11, wcs->cd22, cd33);
  }
#endif
  if (wcs->iscelsph) {
    wcs->crval1 /= CPL_MATH_DEG_RAD; /* convert to radians before calling...    */
    wcs->crval2 /= CPL_MATH_DEG_RAD; /* ...muse_wcs_pixel_from_celestial_fast() */
  }
  double timeinit = cpl_test_get_walltime(),
         timeprogress = timeinit,
         cpuinit = cpl_test_get_cputime();
  cpl_boolean showprogress = cpl_msg_get_level() == CPL_MSG_DEBUG
                           || cpl_msg_get_log_level() == CPL_MSG_DEBUG;

  /* check for the selected pixels in the pixel table, only those *
   * are used to construct the pixel grid; since constructing the *
   * array of selected pixels costs significant amounts of time,  *
   * do that only when not all pixels are selected!               */
  cpl_array *asel = NULL;
  const cpl_size *sel = NULL;
  cpl_size nsel = cpl_table_count_selected(aPixtable->table);
  if (nsel < nrow) {
    asel = cpl_table_where_selected(aPixtable->table);
    sel = cpl_array_get_data_cplsize_const(asel);
  }

  /* can use at most XMAP_BITMASK threads so that the bitmask does not       *
   * overflow, but ensure that we are not using more cores than available... */
  int nth = omp_get_max_threads() > XMAP_BITMASK ? XMAP_BITMASK
                                                 : omp_get_max_threads();
  /* prepare the ranges for the different threads, store them in arrays */
  cpl_array *az1 = cpl_array_new(nth, CPL_TYPE_INT),
            *az2 = cpl_array_new(nth, CPL_TYPE_INT);
  if (aZSize < nth) {
    /* pre-fill arrays with values that cause the threads to do nothing */
    cpl_array_fill_window_int(az1, aZSize, nth, -1);
    cpl_array_fill_window_int(az2, aZSize, nth, -2);
  }
  /* now fill the (first) ones with real ranges */
  double base = nth > aZSize ? 1. : (double)aZSize / nth;
  int ith;
  for (ith = 0; ith < nth && ith < aZSize; ith++) {
    cpl_array_set_int(az1, ith, lround(base * ith));
    cpl_array_set_int(az2, ith, lround(base * (ith + 1) - 1));
  } /* for */
  /* make sure that we don't lose pixels at the edges of the wavelength      *
   * range, put them into the extreme threads by making their ranges larger; *
   * set the relevant array entries to something close to the largest value, *
   * that we can still add as an integer (to compute the z-range)            */
  cpl_array_set_int(az1, 0, -INT_MAX / 2 + 1);
  cpl_array_set_int(az2, ith - 1, INT_MAX / 2 - 1);
#if 0 /* DEBUG */
  if (aZSize < 2*nth) {
    printf("arrays (base = %f, aZSize = %d, nth = %d):\n",
           base, (int)aZSize, nth);
    cpl_array_dump(az1, 0, nth, stdout);
    cpl_array_dump(az2, 0, nth, stdout);
    fflush(stdout);
  }
#endif

  /* create the pixel grid with extension maps for threads */
  muse_pixgrid *grid = muse_pixgrid_new(aXSize, aYSize, aZSize, nth);

  /* parallel region to fill the pixel grid */
#ifdef ESO_ENABLE_DEBUG
  #pragma omp parallel num_threads(nth) default(none)    /* as req. by Ralf */ \
          shared(aXSize, aYSize, aZSize, az1, az2, cd33, crpix3, crval3, debug,\
                 grid, lbda, loglambda, nsel, ptxoff, ptyoff, sel,             \
                 showprogress, timeinit, timeprogress, wcs, xpos, ypos)
#else
  #pragma omp parallel num_threads(nth) default(none)    /* as req. by Ralf */ \
          shared(aXSize, aYSize, aZSize, az1, az2, cd33, crpix3, crval3, grid, \
                 lbda, loglambda, nsel, ptxoff, ptyoff, sel, showprogress,     \
                 timeinit, timeprogress, wcs, xpos, ypos)
#endif
  {
    /* split the work up into threads, for non-overlapping wavelength ranges */
    unsigned short ithread = omp_get_thread_num(); /* index of this thread */
    int z1 = cpl_array_get_int(az1, ithread, NULL),
        z2 = cpl_array_get_int(az2, ithread, NULL),
        zrange = z2 - z1 + 1;

    /* check if we actually need to enter the (parallel) loop, i.e. *
     * if the current thread is handling any wavelength planes      */
#if 0 /* DEBUG */
    if (zrange > 0) {
      cpl_msg_debug(__func__, "%lld z pixels, thread index %hu, z range: %d..%d "
                    "(%d planes)", aZSize, ithread, z1, z2, zrange);
    } else {
      cpl_msg_debug(__func__, "%lld z pixels, thread index %hu, z range: %d..%d "
                    "(%d planes) --> thread not used!", aZSize, ithread, z1, z2,
                    zrange);
    }
#endif

    /* now the actual parallel loop */
    cpl_size isel;
    for (isel = 0 ; zrange > 0 && isel < nsel; isel++) {
      #pragma omp master /* only output progress from the master thread */
      if (showprogress && !((isel+1) % 1000000ll)) { /* output before every millionth entry */
        double timenow = cpl_test_get_walltime();
        if (timenow - timeprogress > 30.) { /* and more than half a minute passed */
          timeprogress = timenow;
          double percent = 100. * (isel + 1.) / nsel,
                 elapsed = timeprogress - timeinit,
                 remaining = (100. - percent) * elapsed / percent;
          /* overwritable only exists for INFO mode, but we check  *
           * above that we want this only for DEBUG mode output... */
          cpl_msg_info_overwritable(__func__, "pixel grid creation is %.1f%% "
                                    "complete, %.1fs elapsed, ~%.1fs remaining",
                                    percent, elapsed, remaining);
        } /* if: 1/2 min passed */
      } /* if: want debug output */

      /* either use the index from the array of selected rows   *
       * or the row numberdirectly (for a fully selected table) */
      cpl_size n = sel ? sel[isel] : isel;
      int z = -1;
      if (loglambda) {
        z = lround(crval3 / cd33 * log(lbda[n] / crval3)) + crpix3 - 1;
      } else {
        z = lround((lbda[n] - crval3) / cd33 + crpix3) - 1;
      }
      if (z < z1 || z > z2) {
        continue; /* skip this entry, one of the other threads handles it */
      }

      /* determine the pixel coordinates in the grid (indices, starting at 0) */
      double xpx, ypx;
      if (wcs->iscelsph) {
        muse_wcs_pixel_from_celestial_fast(wcs, (xpos[n] + ptxoff) / CPL_MATH_DEG_RAD,
                                           (ypos[n] + ptyoff) / CPL_MATH_DEG_RAD,
                                           &xpx, &ypx);
      } else {
        muse_wcs_pixel_from_projplane_fast(wcs, xpos[n], ypos[n],
                                           &xpx, &ypx);
      }
      int x = lround(xpx) - 1,
          y = lround(ypx) - 1;
      cpl_size idx = muse_pixgrid_get_index(grid, x, y, z, CPL_TRUE);
#ifdef ESO_ENABLE_DEBUG
      if (debug) {
        printf("%"CPL_SIZE_FORMAT": %f %f %f -> %d %d %d (%"CPL_SIZE_FORMAT")\n",
               n, xpos[n] + ptxoff, ypos[n] + ptyoff, lbda[n], x, y, z, idx);
      }
#endif

      /* write the pixel values to the correct place in the grid */
      muse_pixgrid_add(grid, idx, n, ithread);
    } /* for isel (all selected pixel table rows) */

    /* Clean up the possibly too many allocations; this is not strictly *
     * needed but nice to only consume as much memory as we need.       */
    grid->xmaps[ithread] = cpl_realloc(grid->xmaps[ithread],
                                       grid->nxmap[ithread]
                                       * sizeof(muse_pixels_ext));
    grid->nxalloc[ithread] = grid->nxmap[ithread];
  } /* omp parallel */
  cpl_array_delete(asel);
  cpl_free(wcs);
  cpl_array_delete(az1);
  cpl_array_delete(az2);
#ifdef ESO_ENABLE_DEBUG
  if (debug) {
    fflush(stdout);
  }
#endif

  cpl_size idx, npix = 0;
  for (idx = 0; idx < aXSize * aYSize * aZSize; idx++) {
    npix += muse_pixgrid_get_count(grid, idx);
  }
  cpl_size nxmap = 0;
  for (idx = 0; idx < (cpl_size)grid->nmaps; idx++) {
    nxmap += grid->nxmap[idx];
  }
  if (npix != nsel) {
    char *msg = cpl_sprintf("Pixels got lost while creating the cube (input "
                            "pixel table: %"CPL_SIZE_FORMAT", output pixel grid"
                            ": %"CPL_SIZE_FORMAT")", nsel, npix);
    cpl_msg_error(__func__, "%s:", msg);
    muse_pixgrid_dump_xmaps(grid, CPL_FALSE);
    cpl_error_set_message(__func__, CPL_ERROR_ILLEGAL_OUTPUT, "%s!", msg);
    cpl_free(msg);
  }
  double timefini = cpl_test_get_walltime(),
         cpufini = cpl_test_get_cputime();
  cpl_msg_debug(__func__, "pixel grid: %dx%dx%d, %"CPL_SIZE_FORMAT" pixels "
                "total, %"CPL_SIZE_FORMAT" (%.1f%%) in %hu extension maps; took"
                " %gs (wall-clock) and %gs (CPU) to create", (int)grid->nx,
                (int)grid->ny, (int)grid->nz, npix, nxmap,
                (double)nxmap / npix * 100., grid->nmaps, timefini - timeinit,
                cpufini - cpuinit);

  return grid;
} /* muse_pixgrid_create() */

/*---------------------------------------------------------------------------*/
/**
  @brief   Convert selected rows of a pixel table into 2D pixel grid, linking
           the grid points to entries (=rows) in the pixel table.
  @param   aTable   the table component of a MUSE pixel table
  @param   aDX      X (spaxel) bin size
  @param   aZMin    Lower z (wavelength) limit
  @param   aZMax    Upper z (wavelength) limit
  @param   aDZ      Z (wavelength) bin size
  @param   aXMin    Lower x (spaxel) limit computed here (can be NULL)
  @return  The output pixel grid or NULL on error.

  The Y coordinate is ignored in this function.

  @error{set CPL_ERROR_NULL_INPUT\, return NULL,
         aTable is NULL or it contains zero rows}
  @error{set CPL_ERROR_ILLEGAL_INPUT\, return NULL,
         one of the sizes is not positive}
  @error{set CPL_ERROR_DATA_NOT_FOUND\, return NULL,
         aTable is missing one of the coordinate columns}
 */
/*---------------------------------------------------------------------------*/
muse_pixgrid *
muse_pixgrid_2d_create(cpl_table *aTable, double aDX,
                       double aZMin, double aZMax, double aDZ, float *aXMin)
{
  cpl_ensure(aTable, CPL_ERROR_NULL_INPUT, NULL);
  cpl_size nrow = cpl_table_get_nrow(aTable);
  if (nrow == 0) {
    cpl_msg_error(__func__, "Invalid pixel table (no entries?)");
    cpl_error_set(__func__, CPL_ERROR_NULL_INPUT);
    return NULL;
  }

  /* get the (relevant) table columns for easy pointer access */
  float *xpos = cpl_table_get_data_float(aTable, MUSE_PIXTABLE_XPOS),
        *lbda = cpl_table_get_data_float(aTable, MUSE_PIXTABLE_LAMBDA);
  if (!xpos || !lbda) {
    cpl_msg_error(__func__, "Missing pixel table column (%p %p): %s",
                  (void *)xpos, (void *)lbda, cpl_error_get_message());
    cpl_error_set(__func__, CPL_ERROR_DATA_NOT_FOUND);
    return NULL;
  }

  /* get the selection for fast access to the relevant rows */
  cpl_array *selection = cpl_table_where_selected(aTable);
  cpl_size nsel = cpl_array_get_size(selection);
  const cpl_size *sel = cpl_array_get_data_cplsize_const(selection);

  /* search for lowest x value in selected rows */
  float xlo = FLT_MAX, xhi = -FLT_MAX;
  cpl_size i;
  for (i = 0; i < nsel; i++) {
    if (xpos[sel[i]] > xhi) xhi = xpos[sel[i]];
    if (xpos[sel[i]] < xlo) xlo = xpos[sel[i]];
  } /* for i (all selected pixel table rows) */
  if (aXMin) {
    *aXMin = xlo;
  }

  /* create the empty 2D grid depending on size of input data */
  cpl_size xsize = ceil((xhi - xlo) / aDX) + 1,
           zsize = ceil((aZMax - aZMin) / aDZ) + 1;
  muse_pixgrid *grid = muse_pixgrid_new(xsize, 1, zsize, 1);

  /* loop through the pixel table and write values into the pixel grid */
  for (i = 0; i < nsel; i++) {
    /* determine the pixel coordinates in the grid; offset is min-1 to *
     * have all these indices start at 0 for easy buffer access        */
    int x = lround((xpos[sel[i]] - xlo) / aDX),
        z = lround((lbda[sel[i]] - aZMin) / aDZ);
    cpl_size idx = muse_pixgrid_get_index(grid, x, 0, z, CPL_TRUE);
    /* write the pixel values to the correct place in the grid */
    muse_pixgrid_add(grid, idx, sel[i], 0);
  } /* for i (all selected pixel table rows) */
  cpl_array_delete(selection);
  /* clean up the possibly too many allocations */
  grid->xmaps[0] = cpl_realloc(grid->xmaps[0],
                               grid->nxmap[0] * sizeof(muse_pixels_ext));
  grid->nxalloc[0] = grid->nxmap[0];

  return grid;
} /* muse_pixgrid_2d_create() */

/*---------------------------------------------------------------------------*/
/**
  @brief   Delete a pixel grid and remove its memory.
  @param   aGrid   Pointer to pixel grid.
*/
/*---------------------------------------------------------------------------*/
void
muse_pixgrid_delete(muse_pixgrid *aGrid)
{
  if (!aGrid) {
    return;
  }
  cpl_free(aGrid->pix);
  aGrid->pix = NULL;
  /* clean up extension maps */
  unsigned short ix;
  for (ix = 0; ix < aGrid->nmaps; ix++) {
    cpl_size iext; /* index in this extension map */
    for (iext = 0; iext < aGrid->nxalloc[ix]; iext++) {
      cpl_free(aGrid->xmaps[ix][iext].pix);
    } /* for iext (all allocated pixels in this extension map) */

    cpl_free(aGrid->xmaps[ix]);
  } /* for ix (all extension maps) */
  cpl_free(aGrid->xmaps);
  aGrid->xmaps = NULL;
  cpl_free(aGrid->nxmap);
  aGrid->nxmap = NULL;
  cpl_free(aGrid->nxalloc);
  aGrid->nxalloc = NULL;
  cpl_free(aGrid);
} /* muse_pixgrid_delete() */

/**@}*/
