/* Copyright (C) 2003-2005 Peter J. Verveer
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met: 
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above
 *    copyright notice, this list of conditions and the following
 *    disclaimer in the documentation and/or other materials provided
 *    with the distribution.
 *
 * 3. The name of the author may not be used to endorse or promote
 *    products derived from this software without specific prior
 *    written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
 * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.      
 */

#define ND_IMPORT_ARRAY
#include "nd_image.h"
#undef ND_IMPORT_ARRAY
#include "ni_support.h"
#include "ni_filters.h"
#include "ni_fourier.h"
#include "ni_morphology.h"
#include "ni_interpolation.h"
#include "ni_measure.h"

typedef struct {
  PyObject *function;
  PyObject *extra_arguments;
  PyObject *extra_keywords;
} NI_PythonCallbackData;

/* Convert an input array of any type, not necessarily contiguous */
static int 
NI_ObjectToInputArray(PyObject *object, PyArrayObject **array)
{
  *array = NA_InputArray(object, tAny, NUM_ALIGNED|NUM_NOTSWAPPED);
  return *array ? 1 : 0;
}

/* Convert an input array of any type, not necessarily contiguous */
static int 
NI_ObjectToOptionalInputArray(PyObject *object, PyArrayObject **array)
{
  if (object == Py_None) {
    *array = NULL;
    return 1;
  } else {
    *array = NA_InputArray(object, tAny, NUM_ALIGNED|NUM_NOTSWAPPED);
    return *array ? 1 : 0;
  }
}

/* Convert an output array of any type, not necessarily contiguous */
static int 
NI_ObjectToOutputArray(PyObject *object, PyArrayObject **array)
{
  *array = NA_OutputArray(object, tAny, NUM_ALIGNED|NUM_NOTSWAPPED);
  return *array ? 1 : 0;
}

/* Convert an output array of any type, not necessarily contiguous */
static int 
NI_ObjectToOptionalOutputArray(PyObject *object, PyArrayObject **array)
{
  if (object == Py_None) {
    *array = NULL;
    return 1;
  } else {
    *array = NA_OutputArray(object, tAny, NUM_ALIGNED|NUM_NOTSWAPPED);
    return *array ? 1 : 0;
  }
}

/* Convert an input/output array of any type, not necessarily contiguous */
static int 
NI_ObjectToIoArray(PyObject *object, PyArrayObject **array)
{
  *array = NA_IoArray(object, tAny, NUM_ALIGNED|NUM_NOTSWAPPED);
  return *array ? 1 : 0;
}

/* Convert an Long sequence */
static maybelong
NI_ObjectToLongSequenceAndLength(PyObject *object, maybelong **sequence)
{
  long *pa, ii;
  PyArrayObject *array = NA_InputArray(object, PyArray_LONG, C_ARRAY);
  maybelong length = NA_elements(array);
  
  *sequence = (maybelong*)malloc(length * sizeof(maybelong));
  if (!*sequence) {
    PyErr_NoMemory();
    Py_XDECREF(array);
    return -1;
  }
  pa = (long*)NA_OFFSETDATA(array);
  for(ii = 0; ii < length; ii++)
    (*sequence)[ii] = pa[ii];
  Py_XDECREF(array);
  return length;
}

static int 
NI_ObjectToLongSequence(PyObject *object, maybelong **sequence)
{
  return NI_ObjectToLongSequenceAndLength(object, sequence) >= 0;
}

/*********************************************************************/
/* wrapper functions: */
/*********************************************************************/

static PyObject *Py_Correlate1D(PyObject *obj, PyObject *args)
{
  PyArrayObject *input = NULL, *output = NULL, *weights = NULL;
  int axis, mode;
  long origin;
  double cval;
  
  if (!PyArg_ParseTuple(args, "O&O&iO&idl", NI_ObjectToInputArray, &input, 
                  NI_ObjectToInputArray, &weights, &axis, 
                  NI_ObjectToOutputArray, &output, &mode, &cval, &origin))
    goto exit;
  if (!NI_Correlate1D(input, weights, axis, output,
                      (NI_ExtendMode)mode, cval, origin))
    goto exit;
exit:
  Py_XDECREF(input);
  Py_XDECREF(weights);
  Py_XDECREF(output);
  return PyErr_Occurred() ? NULL : Py_BuildValue("");
}

static PyObject *Py_Correlate(PyObject *obj, PyObject *args)
{
  PyArrayObject *input = NULL, *output = NULL, *weights = NULL;
  maybelong *origin = NULL;
  int mode;
  double cval;
  
  if (!PyArg_ParseTuple(args, "O&O&O&idO&", NI_ObjectToInputArray, &input, 
          NI_ObjectToInputArray, &weights, NI_ObjectToOutputArray, &output, 
          &mode, &cval, NI_ObjectToLongSequence, &origin))
    goto exit;
  if (!NI_Correlate(input, weights, output, (NI_ExtendMode)mode, cval, 
                    origin))
    goto exit;
exit:
  Py_XDECREF(input);
  Py_XDECREF(weights);
  Py_XDECREF(output);
  if (origin)
    free(origin);
  return PyErr_Occurred() ? NULL : Py_BuildValue("");
}

static PyObject *Py_UniformFilter1D(PyObject *obj, PyObject *args)
{
  PyArrayObject *input = NULL, *output = NULL;
  int axis, mode;
  long filter_size, origin;
  double cval;
  
  if (!PyArg_ParseTuple(args, "O&liO&idl", NI_ObjectToInputArray, &input, 
                    &filter_size, &axis, NI_ObjectToOutputArray, &output,
                    &mode, &cval, &origin))
    goto exit;
  if (!NI_UniformFilter1D(input, filter_size, axis, output,
                                       (NI_ExtendMode)mode, cval, origin))
    goto exit;
exit:
  Py_XDECREF(input);
  Py_XDECREF(output);
  return PyErr_Occurred() ? NULL : Py_BuildValue("");
}

static PyObject *Py_MinOrMaxFilter1D(PyObject *obj, PyObject *args)
{
  PyArrayObject *input = NULL, *output = NULL;
  int axis, mode, minimum;
  long filter_size, origin;
  double cval;
  
  if (!PyArg_ParseTuple(args, "O&liO&idli", NI_ObjectToInputArray, &input, 
                    &filter_size, &axis, NI_ObjectToOutputArray, &output,
                    &mode, &cval, &origin, &minimum))
    goto exit;
  if (!NI_MinOrMaxFilter1D(input, filter_size, axis, output,
                              (NI_ExtendMode)mode, cval, origin, minimum))
    goto exit;
exit:
  Py_XDECREF(input);
  Py_XDECREF(output);
  return PyErr_Occurred() ? NULL : Py_BuildValue("");
}

static PyObject *Py_MinOrMaxFilter(PyObject *obj, PyObject *args)
{
  PyArrayObject *input = NULL, *output = NULL, *footprint = NULL;
  PyArrayObject *structure = NULL;
  maybelong *origin = NULL;
  int mode, minimum;
  double cval;
  
  if (!PyArg_ParseTuple(args, "O&O&O&O&idO&i", NI_ObjectToInputArray,
                    &input, NI_ObjectToInputArray, &footprint,
                    NI_ObjectToOptionalInputArray, &structure,
                    NI_ObjectToOutputArray, &output, &mode, &cval,
                    NI_ObjectToLongSequence, &origin, &minimum))
    goto exit;
  if (!NI_MinOrMaxFilter(input, footprint, structure, output,
                        (NI_ExtendMode)mode, cval, origin, minimum))
    goto exit;
exit:
  Py_XDECREF(input);
  Py_XDECREF(footprint);
  Py_XDECREF(structure);
  Py_XDECREF(output);
  if (origin)
    free(origin);
  return PyErr_Occurred() ? NULL : Py_BuildValue("");
}

static PyObject *Py_RankFilter(PyObject *obj, PyObject *args)
{
  PyArrayObject *input = NULL, *output = NULL, *footprint = NULL;
  maybelong *origin = NULL;
  int mode, rank;
  double cval;
  
  if (!PyArg_ParseTuple(args, "O&iO&O&idO&", NI_ObjectToInputArray,
                    &input, &rank, NI_ObjectToInputArray, &footprint,
                    NI_ObjectToOutputArray, &output, &mode, &cval,
                    NI_ObjectToLongSequence, &origin))
    goto exit;
  if (!NI_RankFilter(input, rank, footprint, output, (NI_ExtendMode)mode,
                     cval, origin))
    goto exit;
exit:
  Py_XDECREF(input);
  Py_XDECREF(footprint);
  Py_XDECREF(output);
  if (origin)
    free(origin);
  return PyErr_Occurred() ? NULL : Py_BuildValue("");
}

static int Py_Filter1DFunc(double *iline, maybelong ilen,
                           double *oline, maybelong olen, void *data)
{
  PyArrayObject *py_ibuffer = NULL, *py_obuffer = NULL; 
  PyObject *rv = NULL, *args = NULL, *tmp = NULL;
  maybelong ii;
  double *po = NULL;
  NI_PythonCallbackData *cbdata = (NI_PythonCallbackData*)data;
  
  py_ibuffer = NA_NewArray(iline, PyArray_DOUBLE, 1, (int)ilen);
  py_obuffer = NA_NewArray(NULL, PyArray_DOUBLE, 1, (int)olen);
  if (!py_ibuffer || !py_obuffer)
    goto exit;
  tmp = Py_BuildValue("(OO)", py_ibuffer, py_obuffer);
  if (!tmp)
    goto exit;
  args = PySequence_Concat(tmp, cbdata->extra_arguments);
  if (!args)
    goto exit;
  rv = PyObject_Call(cbdata->function, args, cbdata->extra_keywords);
  if (!rv)
    goto exit;
  po = (double*)NA_OFFSETDATA(py_obuffer);
  for(ii = 0; ii < olen; ii++)
    oline[ii] = po[ii];
exit:
  Py_XDECREF(py_ibuffer);
  Py_XDECREF(py_obuffer);
  Py_XDECREF(rv);
  Py_XDECREF(args);
  Py_XDECREF(tmp);
  return PyErr_Occurred() ? 0 : 1;
}

static PyObject *Py_GenericFilter1D(PyObject *obj, PyObject *args)
{
  PyArrayObject *input = NULL, *output = NULL;
  PyObject *fnc = NULL, *extra_arguments = NULL, *extra_keywords = NULL;
  void *func = Py_Filter1DFunc, *data = NULL;
  NI_PythonCallbackData cbdata;
  int axis, mode;
  long origin, filter_size;
  double cval;
  
  if (!PyArg_ParseTuple(args, "O&OiiO&idlOO", NI_ObjectToInputArray, 
        &input, &fnc, &filter_size, &axis, NI_ObjectToOutputArray, 
        &output, &mode, &cval, &origin, &extra_arguments, &extra_keywords))
    goto exit;
  if (!PyTuple_Check(extra_arguments)) {
    PyErr_SetString(PyExc_RuntimeError,
                    "extra_arguments must be a tuple");
    goto exit;
  }
  if (!PyDict_Check(extra_keywords)) {
    PyErr_SetString(PyExc_RuntimeError,
                    "extra_keywords must be a dictionary");
    goto exit;
  }
  if (PyCObject_Check(fnc)) {
    func = PyCObject_AsVoidPtr(fnc);
    data = PyCObject_GetDesc(fnc);
  } else if (PyCallable_Check(fnc)) {
    cbdata.function = fnc;
    cbdata.extra_arguments = extra_arguments;
    cbdata.extra_keywords = extra_keywords;
    data = (void*)&cbdata;
  } else {
    PyErr_SetString(PyExc_RuntimeError,
                    "function parameter is not callable");
    goto exit;
  }
  if (!NI_GenericFilter1D(input, func, data, filter_size, axis, output,
                                        (NI_ExtendMode)mode, cval, origin))
    goto exit;
exit:
  Py_XDECREF(input);
  Py_XDECREF(output);
  return PyErr_Occurred() ? NULL : Py_BuildValue("");
}

static int Py_FilterFunc(double *buffer, maybelong filter_size,
                         double *output, void *data)
{
  PyArrayObject *py_buffer = NULL;
  PyObject *rv = NULL, *args = NULL, *tmp = NULL;
  NI_PythonCallbackData *cbdata = (NI_PythonCallbackData*)data;

  py_buffer = NA_NewArray(buffer, PyArray_DOUBLE, 1, filter_size);
  if (!py_buffer)
    goto exit;
  tmp = Py_BuildValue("(O)", py_buffer);
  if (!tmp)
    goto exit;
  args = PySequence_Concat(tmp, cbdata->extra_arguments);
  if (!args)
    goto exit;
  rv = PyObject_Call(cbdata->function, args, cbdata->extra_keywords);
  if (!rv)
    goto exit;
  *output = PyFloat_AsDouble(rv);
exit:
  Py_XDECREF(py_buffer);
  Py_XDECREF(rv);
  Py_XDECREF(args);
  Py_XDECREF(tmp);
  return PyErr_Occurred() ? 0 : 1;
}

static PyObject *Py_GenericFilter(PyObject *obj, PyObject *args)
{
  PyArrayObject *input = NULL, *output = NULL, *footprint = NULL;
  PyObject *fnc = NULL, *extra_arguments = NULL, *extra_keywords = NULL;
  void *func = Py_FilterFunc, *data = NULL;
  NI_PythonCallbackData cbdata;
  int mode;
  maybelong *origin = NULL;
  double cval;
  
  if (!PyArg_ParseTuple(args, "O&OO&O&idO&OO", NI_ObjectToInputArray, 
                        &input, &fnc, NI_ObjectToInputArray, &footprint,
                        NI_ObjectToOutputArray, &output, &mode, &cval,
                        NI_ObjectToLongSequence, &origin,
                        &extra_arguments, &extra_keywords))
    goto exit;
  if (!PyTuple_Check(extra_arguments)) {
    PyErr_SetString(PyExc_RuntimeError,
                    "extra_arguments must be a tuple");
    goto exit;
  }
  if (!PyDict_Check(extra_keywords)) {
    PyErr_SetString(PyExc_RuntimeError,
                    "extra_keywords must be a dictionary");
    goto exit;
  }
  if (PyCObject_Check(fnc)) {
    func = PyCObject_AsVoidPtr(fnc);
    data = PyCObject_GetDesc(fnc);
  } else if (PyCallable_Check(fnc)) {
    cbdata.function = fnc;
    cbdata.extra_arguments = extra_arguments;
    cbdata.extra_keywords = extra_keywords;
    data = (void*)&cbdata;
  } else {
    PyErr_SetString(PyExc_RuntimeError,
                    "function parameter is not callable");
    goto exit;
  }
  if (!NI_GenericFilter(input, func, data, footprint, output,
                        (NI_ExtendMode)mode, cval, origin))
    goto exit;
exit:
  Py_XDECREF(input);
  Py_XDECREF(output);
  Py_XDECREF(footprint);
  if (origin)
    free(origin);
  return PyErr_Occurred() ? NULL : Py_BuildValue("");
}

static PyObject *Py_FourierFilter(PyObject *obj, PyObject *args)
{
  PyArrayObject *input = NULL, *output = NULL, *parameters = NULL;
  int axis, filter_type;
  long n;
  
  if (!PyArg_ParseTuple(args, "O&O&liO&i", NI_ObjectToInputArray, &input, 
                    NI_ObjectToInputArray, &parameters, &n, &axis,
                    NI_ObjectToOutputArray, &output, &filter_type))
    goto exit;   
                
  if (!NI_FourierFilter(input, parameters, n, axis, output, filter_type))
    goto exit;
    
exit:
  Py_XDECREF(input);
  Py_XDECREF(parameters);
  Py_XDECREF(output);
  return PyErr_Occurred() ? NULL : Py_BuildValue("");
}

static PyObject *Py_FourierShift(PyObject *obj, PyObject *args)
{
  PyArrayObject *input = NULL, *output = NULL, *shifts = NULL;
  int axis;
  long n;
  
  if (!PyArg_ParseTuple(args, "O&O&liO&", NI_ObjectToInputArray, &input, 
                    NI_ObjectToInputArray, &shifts, &n, &axis,
                    NI_ObjectToOutputArray, &output))
    goto exit;   
                
  if (!NI_FourierShift(input, shifts, n, axis, output))
    goto exit;
    
exit:
  Py_XDECREF(input);
  Py_XDECREF(shifts);
  Py_XDECREF(output);
  return PyErr_Occurred() ? NULL : Py_BuildValue("");
}

static PyObject *Py_SplineFilter1D(PyObject *obj, PyObject *args)
{
  PyArrayObject *input = NULL, *output = NULL;
  int axis, order;
  
  if (!PyArg_ParseTuple(args, "O&iiO&", NI_ObjectToInputArray, &input,
          &order, &axis, NI_ObjectToOutputArray, &output))
    goto exit;
    
  if (!NI_SplineFilter1D(input, order, axis, output))
    goto exit;

exit:
  Py_XDECREF(input);
  Py_XDECREF(output);
  return PyErr_Occurred() ? NULL : Py_BuildValue("");
}

static int Py_Map(maybelong *ocoor, double* icoor, int orank, int irank, 
                  void *data)
{
  PyObject *coors = NULL, *rets = NULL, *args = NULL, *tmp = NULL;
  maybelong ii;
  NI_PythonCallbackData *cbdata = (NI_PythonCallbackData*)data;

  coors = PyTuple_New(orank);
  if (!coors)
    goto exit;
  for(ii = 0; ii < orank; ii++) {
    PyTuple_SetItem(coors, ii, PyInt_FromLong(ocoor[ii]));
    if (PyErr_Occurred())
      goto exit;
  }
  tmp = Py_BuildValue("(O)", coors);
  if (!tmp)
    goto exit;
  args = PySequence_Concat(tmp, cbdata->extra_arguments);
  if (!args)
    goto exit;
  rets = PyObject_Call(cbdata->function, args, cbdata->extra_keywords);
  if (!rets)
    goto exit;
  for(ii = 0; ii < irank; ii++) {
    icoor[ii] = PyFloat_AsDouble(PyTuple_GetItem(rets, ii));
    if (PyErr_Occurred())
      goto exit;
  }
exit:
  Py_XDECREF(coors);
  Py_XDECREF(tmp);
  Py_XDECREF(rets);
  Py_XDECREF(args);
  return PyErr_Occurred() ? 0 : 1;
}


static PyObject *Py_GeometricTransform(PyObject *obj, PyObject *args)
{
  PyArrayObject *input = NULL, *output = NULL;
  PyArrayObject *coordinates = NULL, *matrix = NULL, *shift = NULL;
  PyObject *fnc = NULL, *extra_arguments = NULL, *extra_keywords = NULL;
  int mode, order;
  double cval;
  void *func = NULL, *data = NULL;
  NI_PythonCallbackData cbdata;
  
  if (!PyArg_ParseTuple(args, "O&OO&O&O&O&iidOO", NI_ObjectToInputArray,
                        &input, &fnc, NI_ObjectToOptionalInputArray,
                        &coordinates, NI_ObjectToOptionalInputArray, 
                        &matrix, NI_ObjectToOptionalInputArray, &shift,
                        NI_ObjectToOutputArray, &output, &order, &mode,
                        &cval, &extra_arguments, &extra_keywords))
    goto exit;

  if (fnc != Py_None) {
    if (!PyTuple_Check(extra_arguments)) {
      PyErr_SetString(PyExc_RuntimeError,
                      "extra_arguments must be a tuple");
      goto exit;
    }
    if (!PyDict_Check(extra_keywords)) {
      PyErr_SetString(PyExc_RuntimeError,
                      "extra_keywords must be a dictionary");
      goto exit;
    }
    if (PyCObject_Check(fnc)) {
      func = PyCObject_AsVoidPtr(fnc);
      data = PyCObject_GetDesc(fnc);
    } else if (PyCallable_Check(fnc)) {
      func = Py_Map;
      cbdata.function = fnc;
      cbdata.extra_arguments = extra_arguments;
      cbdata.extra_keywords = extra_keywords;
      data = (void*)&cbdata;
    } else {
      PyErr_SetString(PyExc_RuntimeError,
                      "function parameter is not callable");
      goto exit;
    }
  }
  
  if (!NI_GeometricTransform(input, func, data, matrix, shift, coordinates, 
                          output, order, (NI_ExtendMode)mode, cval))
    goto exit;

exit:
  Py_XDECREF(input);
  Py_XDECREF(output);
  Py_XDECREF(coordinates);
  Py_XDECREF(matrix);
  Py_XDECREF(shift);
  return PyErr_Occurred() ? NULL : Py_BuildValue("");
}

static PyObject *Py_ZoomShift(PyObject *obj, PyObject *args)
{
  PyArrayObject *input = NULL, *output = NULL, *shift = NULL;
  PyArrayObject *zoom = NULL;
  int mode, order;
  double cval;
  
  if (!PyArg_ParseTuple(args, "O&O&O&O&iid", NI_ObjectToInputArray, 
          &input, NI_ObjectToOptionalInputArray, &zoom,
          NI_ObjectToOptionalInputArray, &shift, NI_ObjectToOutputArray,
          &output, &order, &mode, &cval))
    goto exit;

  if (!NI_ZoomShift(input, zoom, shift, output, order, (NI_ExtendMode)mode,
                    cval))
    goto exit;

exit:
  Py_XDECREF(input);
  Py_XDECREF(shift);
  Py_XDECREF(zoom);
  Py_XDECREF(output);
  return PyErr_Occurred() ? NULL : Py_BuildValue("");
}

static PyObject *Py_Label(PyObject *obj, PyObject *args)
{
  PyArrayObject *input = NULL, *output = NULL, *strct = NULL;
  maybelong max_label;

  if (!PyArg_ParseTuple(args, "O&O&O&", NI_ObjectToInputArray, &input, 
          NI_ObjectToInputArray, &strct, NI_ObjectToOutputArray, &output))
    goto exit;
    
  if (!NI_Label(input, strct, &max_label, output))
    goto exit;

exit:
  Py_XDECREF(input);
  Py_XDECREF(strct);
  Py_XDECREF(output);
  return PyErr_Occurred() ? NULL : Py_BuildValue("l", (long)max_label);
}

static PyObject *Py_FindObjects(PyObject *obj, PyObject *args)
{
  PyArrayObject *input = NULL;
  PyObject *result = NULL, *tuple = NULL, *start = NULL, *end = NULL;
  PyObject *slc = NULL;
  int jj;
  long max_label;
  maybelong ii, *regions = NULL;

  if (!PyArg_ParseTuple(args, "O&l", NI_ObjectToInputArray, &input,
                        &max_label))
    goto exit;
    
  if (max_label < 0)
    max_label = 0;
  if (max_label > 0) {
    if (input->nd > 0) {
      regions = (maybelong*)malloc(2 * max_label * input->nd * 
                                                    sizeof(maybelong));
    } else {
      regions = (maybelong*)malloc(max_label * sizeof(maybelong));
    }
    if (!regions) {
      PyErr_NoMemory();
      goto exit;
    }
  }
   
  if (!NI_FindObjects(input, max_label, regions))
    goto exit;

  result = PyList_New(max_label);
  if (!result) {
    PyErr_NoMemory();
    goto exit;
  }

  for(ii = 0; ii < max_label; ii++) {
    maybelong idx = input->nd > 0 ? 2 * input->nd * ii : ii;
    if (regions[idx] >= 0) {
      PyObject *tuple = PyTuple_New(input->nd);
      if (!tuple) {
        PyErr_NoMemory();
        goto exit;
      }
      for(jj = 0; jj < input->nd; jj++) {
        start = PyInt_FromLong(regions[idx + jj]);
        end = PyInt_FromLong(regions[idx + jj + input->nd]);
        if (!start || !end) {
          PyErr_NoMemory();
          goto exit;
        }
        slc = PySlice_New(start, end, NULL);
        if (!slc) {
          PyErr_NoMemory();
          goto exit;
        }
        Py_XDECREF(start);
        Py_XDECREF(end);
        start = end = NULL;
        PyTuple_SetItem(tuple, jj, slc);
        slc = NULL;
      }
      PyList_SetItem(result, ii, tuple);
      tuple = NULL;
    } else {
      Py_INCREF(Py_None);
      PyList_SetItem(result, ii, Py_None);
    }
  }

  Py_INCREF(result);

 exit:
  Py_XDECREF(input);
  Py_XDECREF(result);
  Py_XDECREF(tuple);
  Py_XDECREF(start);
  Py_XDECREF(end);
  Py_XDECREF(slc);
  if (regions)
    free(regions);
  if (PyErr_Occurred()) {
    Py_XDECREF(result);
    return NULL;
  } else {
    return result;
  }
}

static PyObject *Py_WatershedIFT(PyObject *obj, PyObject *args)
{
  PyArrayObject *input = NULL, *output = NULL, *markers = NULL;
  PyArrayObject *strct = NULL;

  if (!PyArg_ParseTuple(args, "O&O&O&O&", NI_ObjectToInputArray, &input, 
          NI_ObjectToInputArray, &markers, NI_ObjectToInputArray,
          &strct, NI_ObjectToOutputArray, &output))
    goto exit;
    
  if (!NI_WatershedIFT(input, markers, strct, output))
    goto exit;

exit:
  Py_XDECREF(input);
  Py_XDECREF(markers);
  Py_XDECREF(strct);
  Py_XDECREF(output);
  return PyErr_Occurred() ? NULL : Py_BuildValue("");
}

static int _NI_GetIndices(PyObject* indices_object, 
                          maybelong** result_indices, maybelong* min_label,
                          maybelong* max_label, maybelong* n_results)
{
  maybelong *indices = NULL, n_indices, ii;

  if (indices_object == Py_None) {
    *min_label = -1;
    *n_results = 1;
  } else {
    n_indices = NI_ObjectToLongSequenceAndLength(indices_object, &indices);
    if (n_indices < 0)
      goto exit;
    if (n_indices < 1) {
      PyErr_SetString(PyExc_RuntimeError, "no correct indices provided");
      goto exit;
    } else {
      *min_label = *max_label = indices[0];
      if (*min_label < 0) {
        PyErr_SetString(PyExc_RuntimeError,
                        "negative indices not allowed");
        goto exit;
      }
      for(ii = 1; ii < n_indices; ii++) {
        if (indices[ii] < 0) {
          PyErr_SetString(PyExc_RuntimeError,
                          "negative indices not allowed");
          goto exit;
        }
        if (indices[ii] < *min_label)
          *min_label = indices[ii];
        if (indices[ii] > *max_label)
          *max_label = indices[ii];
      }
      *result_indices = (maybelong*)malloc((*max_label - *min_label + 1) * 
                                           sizeof(maybelong));
      if (!*result_indices) {
        PyErr_NoMemory();
        goto exit;
      }
      for(ii = 0; ii < *max_label - *min_label + 1; ii++)
        (*result_indices)[ii] = -1;
      *n_results = 0;
      for(ii = 0; ii < n_indices; ii++) {
        if ((*result_indices)[indices[ii] - *min_label] >= 0) {
          PyErr_SetString(PyExc_RuntimeError, "duplicate index");
          goto exit;
        }
        (*result_indices)[indices[ii] - *min_label] = ii;
        ++(*n_results);
      }
    }
  }
 exit:
  if (indices)
    free(indices);
  return PyErr_Occurred() == NULL;
}


PyObject* _NI_BuildMeasurementResultArrayObject(maybelong n_results,
                                                PyArrayObject** values)
{
  PyObject *result = NULL;
  if (n_results > 1) {
    result = PyList_New(n_results);
    if (result) {
      maybelong ii;
      for(ii = 0; ii < n_results; ii++) {
        PyList_SET_ITEM(result, ii, (PyObject*)values[ii]);
        Py_XINCREF(values[ii]);
      }
    }
  } else {
    result = (PyObject*)values[0];
    Py_XINCREF(values[0]);
  }
  return result;
}


PyObject* _NI_BuildMeasurementResultDouble(maybelong n_results,
                                           double* values)
{
  PyObject *result = NULL;
  if (n_results > 1) {
    result = PyList_New(n_results);
    if (result) {
      int ii;
      for(ii = 0; ii < n_results; ii++) {
        PyObject* val = PyFloat_FromDouble(values[ii]);
        if (!val) {
          Py_XDECREF(result);
          return NULL;
        }
        PyList_SET_ITEM(result, ii, val);
      }
    }
  } else {
    result = Py_BuildValue("d", values[0]);
  }
  return result;
}


PyObject* _NI_BuildMeasurementResultDoubleTuple(maybelong n_results,
                                            int tuple_size, double* values)
{
  PyObject *result = NULL;
  maybelong ii;
  int jj;

  if (n_results > 1) {
    result = PyList_New(n_results);
    if (result) {
      for(ii = 0; ii < n_results; ii++) {
        PyObject* val = PyTuple_New(tuple_size);
        if (!val) {
          Py_XDECREF(result);
          return NULL;
        }
        for(jj = 0; jj < tuple_size; jj++) {
          maybelong idx = jj + ii * tuple_size;
          PyTuple_SetItem(val, jj, PyFloat_FromDouble(values[idx]));
          if (PyErr_Occurred()) {
            Py_XDECREF(result);
            return NULL;
          }
        }
        PyList_SET_ITEM(result, ii, val);
      }
    }
  } else {
    result = PyTuple_New(tuple_size);
    if (result) {
      for(ii = 0; ii < tuple_size; ii++) {
        PyTuple_SetItem(result, ii, PyFloat_FromDouble(values[ii]));
        if (PyErr_Occurred()) {
          Py_XDECREF(result);
          return NULL;
        }
      }
    }
  }
  return result;
}


PyObject* _NI_BuildMeasurementResultInt(maybelong n_results,
                                        maybelong* values)
{
  PyObject *result = NULL;
  if (n_results > 1) {
    result = PyList_New(n_results);
    if (result) {
      maybelong ii;
      for(ii = 0; ii < n_results; ii++) {
        PyObject* val = PyInt_FromLong(values[ii]);
        if (!val) {
          Py_XDECREF(result);
          return NULL;
        }
        PyList_SET_ITEM(result, ii, val);
      }
    }
  } else {
    result = Py_BuildValue("l", values[0]);
  }
  return result;
}


static PyObject *Py_Statistics(PyObject *obj, PyObject *args)
{
  PyArrayObject *input = NULL, *labels = NULL;
  PyObject *indices_object, *result = NULL;
  PyObject *res1 = NULL, *res2 = NULL, *res3 = NULL, *res4 = NULL;
  double *dresult1 = NULL, *dresult2 = NULL;
  maybelong *lresult1 = NULL, *lresult2 = NULL;
  maybelong min_label, max_label, *result_indices = NULL, n_results, ii;
  int type;
  
  if (!PyArg_ParseTuple(args, "O&O&Oi", NI_ObjectToInputArray, &input, 
          NI_ObjectToOptionalInputArray, &labels, &indices_object, &type))
    goto exit;

  if (!_NI_GetIndices(indices_object, &result_indices, &min_label,
                      &max_label, &n_results))
    goto exit;

  if (type >= 0 && type <= 7) {
    dresult1 = (double*)malloc(n_results * sizeof(double));
    if (!dresult1) {
      PyErr_NoMemory();
      goto exit;
    }
  }
  if (type == 2 || type == 7) {
    dresult2 = (double*)malloc(n_results * sizeof(double));
    if (!dresult2) {
      PyErr_NoMemory();
      goto exit;
    }
  }
  if (type == 1 || type == 2 || (type >= 5 && type <= 7)) {
    lresult1 = (maybelong*)malloc(n_results * sizeof(maybelong));
    if (!lresult1) {
      PyErr_NoMemory();
      goto exit;
    }
  }
  if (type == 7) {
    lresult2 = (maybelong*)malloc(n_results * sizeof(maybelong));
    if (!lresult2) {
      PyErr_NoMemory();
      goto exit;
    }
  }
  switch(type) {
  case 0:
    if (!NI_Statistics(input, labels, min_label, max_label, result_indices, 
                  n_results, dresult1, NULL, NULL, NULL, NULL, NULL, NULL))
      goto exit;
    result = _NI_BuildMeasurementResultDouble(n_results, dresult1);
    break;
  case 1:
    if (!NI_Statistics(input, labels, min_label, max_label, result_indices, 
              n_results, dresult1, lresult1, NULL, NULL, NULL, NULL, NULL))
      goto exit;
    for(ii = 0; ii < n_results; ii++)
      dresult1[ii] = lresult1[ii] > 0 ? dresult1[ii] / lresult1[ii] : 0.0;
  
    result = _NI_BuildMeasurementResultDouble(n_results, dresult1);
    break;    
  case 2:
    if (!NI_Statistics(input, labels, min_label, max_label, result_indices, 
          n_results, dresult1, lresult1, dresult2, NULL, NULL, NULL, NULL))
      goto exit;
    result = _NI_BuildMeasurementResultDouble(n_results, dresult2);
    break;
  case 3:
    if (!NI_Statistics(input, labels, min_label, max_label, result_indices, 
                  n_results, NULL, NULL, NULL, dresult1, NULL, NULL, NULL))
      goto exit;
    result = _NI_BuildMeasurementResultDouble(n_results, dresult1);
    break;
  case 4:
    if (!NI_Statistics(input, labels, min_label, max_label, result_indices, 
                  n_results, NULL, NULL, NULL, NULL, dresult1, NULL, NULL))
      goto exit;
    result = _NI_BuildMeasurementResultDouble(n_results, dresult1);
    break;
  case 5:
    if (!NI_Statistics(input, labels, min_label, max_label, result_indices, 
              n_results, NULL, NULL, NULL, dresult1, NULL, lresult1, NULL))
      goto exit;
    result = _NI_BuildMeasurementResultInt(n_results, lresult1);
    break;
  case 6:
    if (!NI_Statistics(input, labels, min_label, max_label, result_indices, 
              n_results, NULL, NULL, NULL, NULL, dresult1, NULL, lresult1))
      goto exit;
    result = _NI_BuildMeasurementResultInt(n_results, lresult1);
    break;
  case 7:
    if (!NI_Statistics(input, labels, min_label, max_label, result_indices, 
                       n_results, NULL, NULL, NULL, dresult1, dresult2, 
                       lresult1, lresult2))
      goto exit;
    res1 = _NI_BuildMeasurementResultDouble(n_results, dresult1);
    res2 = _NI_BuildMeasurementResultDouble(n_results, dresult2);
    res3 = _NI_BuildMeasurementResultInt(n_results, lresult1);
    res4 = _NI_BuildMeasurementResultInt(n_results, lresult2);
    if (!res1 || !res2 || !res3 || !res4)
      goto exit;
    result = Py_BuildValue("OOOO", res1, res2, res3, res4);
    break;
  default:
    PyErr_SetString(PyExc_RuntimeError, "operation not supported");
    goto exit;
  }

 exit:
  Py_XDECREF(input);
  Py_XDECREF(labels);
  if (result_indices)
    free(result_indices);
  if (dresult1)
    free(dresult1);
  if (dresult2)
    free(dresult2);
  if (lresult1)
    free(lresult1);
  if (lresult2)
    free(lresult2);
  return result;
}


static PyObject *Py_CenterOfMass(PyObject *obj, PyObject *args)
{
  PyArrayObject *input = NULL, *labels = NULL;
  PyObject *indices_object, *result = NULL;
  double *center_of_mass = NULL;
  maybelong min_label, max_label, *result_indices = NULL, n_results;
  
  if (!PyArg_ParseTuple(args, "O&O&O", NI_ObjectToInputArray, &input, 
                  NI_ObjectToOptionalInputArray, &labels, &indices_object))
    goto exit;

  if (!_NI_GetIndices(indices_object, &result_indices, &min_label,
                      &max_label, &n_results))
    goto exit;

  center_of_mass = (double*)malloc(input->nd * n_results * 
                                   sizeof(double));
  if (!center_of_mass) {
    PyErr_NoMemory();
    goto exit;
  }
  
  if (!NI_CenterOfMass(input, labels, min_label, max_label,
                       result_indices, n_results, center_of_mass))
    goto exit;

  result = _NI_BuildMeasurementResultDoubleTuple(n_results, input->nd,
                                                 center_of_mass);

 exit:
  Py_XDECREF(input);
  Py_XDECREF(labels);
  if (result_indices)
    free(result_indices);
  if (center_of_mass)
    free(center_of_mass);
  return result;
}

static PyObject *Py_Histogram(PyObject *obj, PyObject *args)
{
  PyArrayObject *input = NULL, *labels = NULL, **histograms = NULL;
  PyObject *indices_object, *result = NULL;
  maybelong min_label, max_label, *result_indices = NULL, n_results;
  maybelong jj, nbins;
  double min, max;
  
  if (!PyArg_ParseTuple(args, "O&ddiO&O", NI_ObjectToInputArray, &input, 
                        &min, &max, &nbins, NI_ObjectToOptionalInputArray,
                        &labels, &indices_object))
    goto exit;

  if (!_NI_GetIndices(indices_object, &result_indices, &min_label,
                      &max_label, &n_results))
    goto exit;

  histograms = (PyArrayObject**)malloc(input->nd * n_results *
                                       sizeof(PyArrayObject*));
  if (!histograms) {
    PyErr_NoMemory();
    goto exit;
  }
  for(jj = 0; jj < n_results; jj++) {
    histograms[jj] = NA_NewArray(NULL, tInt32, 1, nbins);
    if (!histograms[jj]) {
      PyErr_NoMemory();
      goto exit;
    }
  }
  
  if (!NI_Histogram(input, labels, min_label, max_label, result_indices, 
                    n_results, histograms, min, max, nbins))
    goto exit;

  result = _NI_BuildMeasurementResultArrayObject(n_results, histograms);

 exit:
  Py_XDECREF(input);
  Py_XDECREF(labels);
  if (result_indices)
    free(result_indices);
  if (histograms) {
    for(jj = 0; jj < n_results; jj++) {
      Py_XDECREF(histograms[jj]);
    }
    free(histograms);
  }
  return result;
}

static PyObject *Py_DistanceTransformBruteForce(PyObject *obj,
                                                PyObject *args)
{
  PyArrayObject *input = NULL, *output = NULL, *features = NULL;
  PyArrayObject *sampling = NULL;
  int metric;

  if (!PyArg_ParseTuple(args, "O&iO&O&O&", NI_ObjectToInputArray, &input,
                        &metric, NI_ObjectToOptionalInputArray, &sampling, 
                        NI_ObjectToOptionalOutputArray, &output, 
                        NI_ObjectToOptionalOutputArray, &features))
    goto exit;
  if (!NI_DistanceTransformBruteForce(input, metric, sampling,  
                                      output, features))
    goto exit;
exit:
  Py_XDECREF(input);
  Py_XDECREF(sampling);
  Py_XDECREF(output);
  Py_XDECREF(features);
  return PyErr_Occurred() ? NULL : Py_BuildValue("");
}

static PyObject *Py_DistanceTransformOnePass(PyObject *obj, PyObject *args)
{
  PyArrayObject *strct = NULL, *distances = NULL, *features = NULL;

  if (!PyArg_ParseTuple(args, "O&O&O&", NI_ObjectToInputArray, &strct,
                        NI_ObjectToIoArray, &distances,
                        NI_ObjectToOptionalOutputArray, &features))
    goto exit;
  if (!NI_DistanceTransformOnePass(strct, distances, features))
    goto exit;
exit:
  Py_XDECREF(strct); 
  Py_XDECREF(distances); 
  Py_XDECREF(features); 
  return PyErr_Occurred() ? NULL : Py_BuildValue("");
}

static PyObject *Py_EuclideanFeatureTransform(PyObject *obj,
                                              PyObject *args)
{
  PyArrayObject *input = NULL, *features = NULL, *sampling = NULL;

  if (!PyArg_ParseTuple(args, "O&O&O&", NI_ObjectToInputArray, &input, 
                        NI_ObjectToOptionalInputArray, &sampling,
                        NI_ObjectToOutputArray, &features))
    goto exit;
  if (!NI_EuclideanFeatureTransform(input, sampling, features))
    goto exit;
exit:
  Py_XDECREF(input);
  Py_XDECREF(sampling);
  Py_XDECREF(features);
  return PyErr_Occurred() ? NULL : Py_BuildValue("");
}

static void _FreeCoordinateList(void* ptr) 
{
  NI_FreeCoordinateList((NI_CoordinateList*)ptr);
}

static PyObject *Py_BinaryErosion(PyObject *obj, PyObject *args)
{
  PyArrayObject *input = NULL, *output = NULL, *strct = NULL;
  PyArrayObject *mask = NULL;
  PyObject *cobj = NULL;
  int border_value, invert, center_is_true;
  int changed = 0, return_coordinates;
  NI_CoordinateList *coordinate_list = NULL;
  maybelong *origins = NULL;

  if (!PyArg_ParseTuple(args, "O&O&O&O&iO&iii", NI_ObjectToInputArray,
                        &input, NI_ObjectToInputArray, &strct,
                        NI_ObjectToOptionalInputArray, &mask,
                        NI_ObjectToOutputArray, &output, &border_value, 
                        NI_ObjectToLongSequence, &origins,  &invert,
                        &center_is_true, &return_coordinates))
    goto exit;
  if (!NI_BinaryErosion(input, strct, mask, output, border_value,
                        origins, invert, center_is_true, &changed, 
                        return_coordinates ? &coordinate_list : NULL))
    goto exit;
  if (return_coordinates) {
    cobj = PyCObject_FromVoidPtr(coordinate_list, _FreeCoordinateList);
  }
exit:
  Py_XDECREF(input);
  Py_XDECREF(strct);
  Py_XDECREF(mask);
  Py_XDECREF(output);
  if (origins)
    free(origins);
  if (PyErr_Occurred()) {
    Py_XDECREF(cobj);
    return NULL;
  } else {
    if (return_coordinates) {
      return Py_BuildValue("iN", changed, cobj);
    } else {
      return Py_BuildValue("i", changed);
    }
  }
}

static PyObject *Py_BinaryErosion2(PyObject *obj, PyObject *args)
{
  PyArrayObject *array = NULL, *strct = NULL, *mask = NULL;
  PyObject *cobj = NULL;
  int invert, niter;
  maybelong *origins = NULL;

  if (!PyArg_ParseTuple(args, "O&O&O&iO&iO", NI_ObjectToIoArray, &array, 
          NI_ObjectToInputArray, &strct, NI_ObjectToOptionalInputArray, 
          &mask, &niter, NI_ObjectToLongSequence, &origins, &invert,
          &cobj))
    goto exit;
    
  if (PyCObject_Check(cobj)) {
    NI_CoordinateList *cobj_data = PyCObject_AsVoidPtr(cobj);
    if (!NI_BinaryErosion2(array, strct, mask, niter, origins, invert, 
                           &cobj_data))
      goto exit;
  } else {
    PyErr_SetString(PyExc_RuntimeError, "cannot convert CObject");
    goto exit;
  }
exit:
  Py_XDECREF(array);
  Py_XDECREF(strct);
  Py_XDECREF(mask);
  if (origins) free(origins);
  return PyErr_Occurred() ? NULL : Py_BuildValue("");
}

static PyMethodDef methods[] = {
  {"correlate1d",           (PyCFunction)Py_Correlate1D,
   METH_VARARGS, ""},
  {"correlate",             (PyCFunction)Py_Correlate,
   METH_VARARGS, ""},
  {"uniform_filter1d",      (PyCFunction)Py_UniformFilter1D,
   METH_VARARGS, ""},
  {"min_or_max_filter1d",   (PyCFunction)Py_MinOrMaxFilter1D,
    METH_VARARGS, ""},
  {"min_or_max_filter",     (PyCFunction)Py_MinOrMaxFilter,
    METH_VARARGS, ""},
  {"rank_filter",           (PyCFunction)Py_RankFilter,
   METH_VARARGS, ""},
  {"generic_filter",        (PyCFunction)Py_GenericFilter,
   METH_VARARGS, ""},
  {"generic_filter1d",      (PyCFunction)Py_GenericFilter1D,
   METH_VARARGS, ""},
  {"fourier_filter",        (PyCFunction)Py_FourierFilter,
   METH_VARARGS, ""},
  {"fourier_shift",         (PyCFunction)Py_FourierShift,
   METH_VARARGS, ""},
  {"spline_filter1d",       (PyCFunction)Py_SplineFilter1D,
   METH_VARARGS, ""},
  {"geometric_transform",   (PyCFunction)Py_GeometricTransform,
    METH_VARARGS, ""},
  {"zoom_shift",            (PyCFunction)Py_ZoomShift,
   METH_VARARGS, ""},
  {"label",                 (PyCFunction)Py_Label,
   METH_VARARGS, ""},
  {"find_objects",          (PyCFunction)Py_FindObjects,
   METH_VARARGS, ""},
  {"watershed_ift",         (PyCFunction)Py_WatershedIFT,
   METH_VARARGS, ""},
  {"statistics",            (PyCFunction)Py_Statistics,
   METH_VARARGS, ""},
  {"center_of_mass",        (PyCFunction)Py_CenterOfMass,
   METH_VARARGS, ""},
  {"histogram",             (PyCFunction)Py_Histogram,
   METH_VARARGS, ""},
  {"distance_transform_bf", (PyCFunction)Py_DistanceTransformBruteForce, 
   METH_VARARGS, ""},
  {"distance_transform_op", (PyCFunction)Py_DistanceTransformOnePass, 
   METH_VARARGS, ""},
  {"euclidean_feature_transform",
   (PyCFunction)Py_EuclideanFeatureTransform, METH_VARARGS, ""},
  {"binary_erosion",        (PyCFunction)Py_BinaryErosion,
   METH_VARARGS, ""},
  {"binary_erosion2",       (PyCFunction)Py_BinaryErosion2,
   METH_VARARGS, ""},
  {NULL, NULL, 0, NULL}
};

void init_nd_image(void)
{
  Py_InitModule("_nd_image", methods);
  import_libnumarray();
}
