/* Initialize a line buffer */ int NI_InitLineBuffer(PyArrayObject *array, int axis, maybelong size1, maybelong size2, maybelong buffer_lines, double *buffer_data, NI_ExtendMode extend_mode, double extend_value, NI_LineBuffer *buffer) { maybelong line_length = 0, array_lines = 0, size; int ii; size = 1; for(ii = 0; ii < array->nd; ii++) size *= array->dimensions[ii]; /* check if the buffer is big enough: */ if (size > 0 && buffer_lines < 1) { PyErr_SetString(PyExc_RuntimeError, "buffer too small"); return 0; } /* Initialize a line iterator to move over the array: */ if (!NI_InitPointIterator(array, &(buffer->iterator))) return 0; if (!NI_LineIterator(&(buffer->iterator), axis)) return 0; line_length = array->nd > 0 ? array->dimensions[axis] : 1; if (line_length > 0) array_lines = line_length > 0 ? size / line_length : 1; /* initialize the buffer structure: */ buffer->array_data = (void *)PyArray_DATA(array); buffer->buffer_data = buffer_data; buffer->buffer_lines = buffer_lines; buffer->array_type = array->descr->type_num; buffer->array_lines = array_lines; buffer->next_line = 0; buffer->size1 = size1; buffer->size2 = size2; buffer->line_length = line_length; buffer->line_stride = array->nd > 0 ? array->strides[axis] : 0; buffer->extend_mode = extend_mode; buffer->extend_value = extend_value; return 1; }
/* Initialize a line buffer */ int NI_InitLineBuffer(PyArrayObject *array, int axis, npy_intp size1, npy_intp size2, npy_intp buffer_lines, double *buffer_data, NI_ExtendMode extend_mode, double extend_value, NI_LineBuffer *buffer) { npy_intp line_length = 0, array_lines = 0, size; size = PyArray_SIZE(array); /* check if the buffer is big enough: */ if (size > 0 && buffer_lines < 1) { PyErr_SetString(PyExc_RuntimeError, "buffer too small"); return 0; } /* Initialize a line iterator to move over the array: */ if (!NI_InitPointIterator(array, &(buffer->iterator))) return 0; if (!NI_LineIterator(&(buffer->iterator), axis)) return 0; line_length = PyArray_NDIM(array) > 0 ? PyArray_DIM(array, axis) : 1; if (line_length > 0) { array_lines = line_length > 0 ? size / line_length : 1; } /* initialize the buffer structure: */ buffer->array_data = (void *)PyArray_DATA(array); buffer->buffer_data = buffer_data; buffer->buffer_lines = buffer_lines; buffer->array_type = NI_CanonicalType(PyArray_TYPE(array)); buffer->array_lines = array_lines; buffer->next_line = 0; buffer->size1 = size1; buffer->size2 = size2; buffer->line_length = line_length; buffer->line_stride = PyArray_NDIM(array) > 0 ? PyArray_STRIDE(array, axis) : 0; buffer->extend_mode = extend_mode; buffer->extend_value = extend_value; return 1; }
int NI_GeometricTransform(PyArrayObject *input, int (*map)(npy_intp*, double*, int, int, void*), void* map_data, PyArrayObject* matrix_ar, PyArrayObject* shift_ar, PyArrayObject *coordinates, PyArrayObject *output, int order, int mode, double cval) { char *po, *pi, *pc = NULL; npy_intp **edge_offsets = NULL, **data_offsets = NULL, filter_size; npy_intp ftmp[NPY_MAXDIMS], *fcoordinates = NULL, *foffsets = NULL; npy_intp cstride = 0, kk, hh, ll, jj; npy_intp size; double **splvals = NULL, icoor[NPY_MAXDIMS]; npy_intp idimensions[NPY_MAXDIMS], istrides[NPY_MAXDIMS]; NI_Iterator io, ic; npy_double *matrix = matrix_ar ? (npy_double*)PyArray_DATA(matrix_ar) : NULL; npy_double *shift = shift_ar ? (npy_double*)PyArray_DATA(shift_ar) : NULL; int irank = 0, orank; NPY_BEGIN_THREADS_DEF; NPY_BEGIN_THREADS; for(kk = 0; kk < PyArray_NDIM(input); kk++) { idimensions[kk] = PyArray_DIM(input, kk); istrides[kk] = PyArray_STRIDE(input, kk); } irank = PyArray_NDIM(input); orank = PyArray_NDIM(output); /* if the mapping is from array coordinates: */ if (coordinates) { /* initialize a line iterator along the first axis: */ if (!NI_InitPointIterator(coordinates, &ic)) goto exit; cstride = ic.strides[0]; if (!NI_LineIterator(&ic, 0)) goto exit; pc = (void *)(PyArray_DATA(coordinates)); } /* offsets used at the borders: */ edge_offsets = malloc(irank * sizeof(npy_intp*)); data_offsets = malloc(irank * sizeof(npy_intp*)); if (NPY_UNLIKELY(!edge_offsets || !data_offsets)) { NPY_END_THREADS; PyErr_NoMemory(); goto exit; } for(jj = 0; jj < irank; jj++) data_offsets[jj] = NULL; for(jj = 0; jj < irank; jj++) { data_offsets[jj] = malloc((order + 1) * sizeof(npy_intp)); if (NPY_UNLIKELY(!data_offsets[jj])) { NPY_END_THREADS; PyErr_NoMemory(); goto exit; } } /* will hold the spline coefficients: */ splvals = malloc(irank * sizeof(double*)); if (NPY_UNLIKELY(!splvals)) { NPY_END_THREADS; PyErr_NoMemory(); goto exit; } for(jj = 0; jj < irank; jj++) splvals[jj] = NULL; for(jj = 0; jj < irank; jj++) { splvals[jj] = malloc((order + 1) * sizeof(double)); if (NPY_UNLIKELY(!splvals[jj])) { NPY_END_THREADS; PyErr_NoMemory(); goto exit; } } filter_size = 1; for(jj = 0; jj < irank; jj++) filter_size *= order + 1; /* initialize output iterator: */ if (!NI_InitPointIterator(output, &io)) goto exit; /* get data pointers: */ pi = (void *)PyArray_DATA(input); po = (void *)PyArray_DATA(output); /* make a table of all possible coordinates within the spline filter: */ fcoordinates = malloc(irank * filter_size * sizeof(npy_intp)); /* make a table of all offsets within the spline filter: */ foffsets = malloc(filter_size * sizeof(npy_intp)); if (NPY_UNLIKELY(!fcoordinates || !foffsets)) { NPY_END_THREADS; PyErr_NoMemory(); goto exit; } for(jj = 0; jj < irank; jj++) ftmp[jj] = 0; kk = 0; for(hh = 0; hh < filter_size; hh++) { for(jj = 0; jj < irank; jj++) fcoordinates[jj + hh * irank] = ftmp[jj]; foffsets[hh] = kk; for(jj = irank - 1; jj >= 0; jj--) { if (ftmp[jj] < order) { ftmp[jj]++; kk += istrides[jj]; break; } else { ftmp[jj] = 0; kk -= istrides[jj] * order; } } } size = PyArray_SIZE(output); for(kk = 0; kk < size; kk++) { double t = 0.0; int constant = 0, edge = 0; npy_intp offset = 0; if (map) { NPY_END_THREADS; /* call mappint functions: */ if (!map(io.coordinates, icoor, orank, irank, map_data)) { if (!PyErr_Occurred()) PyErr_SetString(PyExc_RuntimeError, "unknown error in mapping function"); goto exit; } NPY_BEGIN_THREADS; } else if (matrix) { /* do an affine transformation: */ npy_double *p = matrix; for(hh = 0; hh < irank; hh++) { icoor[hh] = 0.0; for(ll = 0; ll < orank; ll++) icoor[hh] += io.coordinates[ll] * *p++; icoor[hh] += shift[hh]; } } else if (coordinates) { /* mapping is from an coordinates array: */ char *p = pc; switch (PyArray_TYPE(coordinates)) { CASE_MAP_COORDINATES(NPY_BOOL, npy_bool, p, icoor, irank, cstride); CASE_MAP_COORDINATES(NPY_UBYTE, npy_ubyte, p, icoor, irank, cstride); CASE_MAP_COORDINATES(NPY_USHORT, npy_ushort, p, icoor, irank, cstride); CASE_MAP_COORDINATES(NPY_UINT, npy_uint, p, icoor, irank, cstride); CASE_MAP_COORDINATES(NPY_ULONG, npy_ulong, p, icoor, irank, cstride); CASE_MAP_COORDINATES(NPY_ULONGLONG, npy_ulonglong, p, icoor, irank, cstride); CASE_MAP_COORDINATES(NPY_BYTE, npy_byte, p, icoor, irank, cstride); CASE_MAP_COORDINATES(NPY_SHORT, npy_short, p, icoor, irank, cstride); CASE_MAP_COORDINATES(NPY_INT, npy_int, p, icoor, irank, cstride); CASE_MAP_COORDINATES(NPY_LONG, npy_long, p, icoor, irank, cstride); CASE_MAP_COORDINATES(NPY_LONGLONG, npy_longlong, p, icoor, irank, cstride); CASE_MAP_COORDINATES(NPY_FLOAT, npy_float, p, icoor, irank, cstride); CASE_MAP_COORDINATES(NPY_DOUBLE, npy_double, p, icoor, irank, cstride); default: NPY_END_THREADS; PyErr_SetString(PyExc_RuntimeError, "coordinate array data type not supported"); goto exit; } } /* iterate over axes: */ for(hh = 0; hh < irank; hh++) { /* if the input coordinate is outside the borders, map it: */ double cc = map_coordinate(icoor[hh], idimensions[hh], mode); if (cc > -1.0) { /* find the filter location along this axis: */ npy_intp start; if (order & 1) { start = (npy_intp)floor(cc) - order / 2; } else { start = (npy_intp)floor(cc + 0.5) - order / 2; } /* get the offset to the start of the filter: */ offset += istrides[hh] * start; if (start < 0 || start + order >= idimensions[hh]) { /* implement border mapping, if outside border: */ edge = 1; edge_offsets[hh] = data_offsets[hh]; for(ll = 0; ll <= order; ll++) { npy_intp idx = start + ll; npy_intp len = idimensions[hh]; if (len <= 1) { idx = 0; } else { npy_intp s2 = 2 * len - 2; if (idx < 0) { idx = s2 * (int)(-idx / s2) + idx; idx = idx <= 1 - len ? idx + s2 : -idx; } else if (idx >= len) { idx -= s2 * (int)(idx / s2); if (idx >= len) idx = s2 - idx; } } /* calculate and store the offests at this edge: */ edge_offsets[hh][ll] = istrides[hh] * (idx - start); } } else { /* we are not at the border, use precalculated offsets: */ edge_offsets[hh] = NULL; } spline_coefficients(cc, order, splvals[hh]); } else { /* we use the constant border condition: */ constant = 1; break; } } if (!constant) { npy_intp *ff = fcoordinates; const int type_num = PyArray_TYPE(input); t = 0.0; for(hh = 0; hh < filter_size; hh++) { double coeff = 0.0; npy_intp idx = 0; if (NPY_UNLIKELY(edge)) { for(ll = 0; ll < irank; ll++) { if (edge_offsets[ll]) idx += edge_offsets[ll][ff[ll]]; else idx += ff[ll] * istrides[ll]; } } else { idx = foffsets[hh]; } idx += offset; switch (type_num) { CASE_INTERP_COEFF(NPY_BOOL, npy_bool, coeff, pi, idx); CASE_INTERP_COEFF(NPY_UBYTE, npy_ubyte, coeff, pi, idx); CASE_INTERP_COEFF(NPY_USHORT, npy_ushort, coeff, pi, idx); CASE_INTERP_COEFF(NPY_UINT, npy_uint, coeff, pi, idx); CASE_INTERP_COEFF(NPY_ULONG, npy_ulong, coeff, pi, idx); CASE_INTERP_COEFF(NPY_ULONGLONG, npy_ulonglong, coeff, pi, idx); CASE_INTERP_COEFF(NPY_BYTE, npy_byte, coeff, pi, idx); CASE_INTERP_COEFF(NPY_SHORT, npy_short, coeff, pi, idx); CASE_INTERP_COEFF(NPY_INT, npy_int, coeff, pi, idx); CASE_INTERP_COEFF(NPY_LONG, npy_long, coeff, pi, idx); CASE_INTERP_COEFF(NPY_LONGLONG, npy_longlong, coeff, pi, idx); CASE_INTERP_COEFF(NPY_FLOAT, npy_float, coeff, pi, idx); CASE_INTERP_COEFF(NPY_DOUBLE, npy_double, coeff, pi, idx); default: NPY_END_THREADS; PyErr_SetString(PyExc_RuntimeError, "data type not supported"); goto exit; } /* calculate the interpolated value: */ for(ll = 0; ll < irank; ll++) if (order > 0) coeff *= splvals[ll][ff[ll]]; t += coeff; ff += irank; } } else { t = cval; } /* store output value: */ switch (PyArray_TYPE(output)) { CASE_INTERP_OUT(NPY_BOOL, npy_bool, po, t); CASE_INTERP_OUT_UINT(UBYTE, npy_ubyte, po, t); CASE_INTERP_OUT_UINT(USHORT, npy_ushort, po, t); CASE_INTERP_OUT_UINT(UINT, npy_uint, po, t); CASE_INTERP_OUT_UINT(ULONG, npy_ulong, po, t); CASE_INTERP_OUT_UINT(ULONGLONG, npy_ulonglong, po, t); CASE_INTERP_OUT_INT(BYTE, npy_byte, po, t); CASE_INTERP_OUT_INT(SHORT, npy_short, po, t); CASE_INTERP_OUT_INT(INT, npy_int, po, t); CASE_INTERP_OUT_INT(LONG, npy_long, po, t); CASE_INTERP_OUT_INT(LONGLONG, npy_longlong, po, t); CASE_INTERP_OUT(NPY_FLOAT, npy_float, po, t); CASE_INTERP_OUT(NPY_DOUBLE, npy_double, po, t); default: NPY_END_THREADS; PyErr_SetString(PyExc_RuntimeError, "data type not supported"); goto exit; } if (coordinates) { NI_ITERATOR_NEXT2(io, ic, po, pc); } else { NI_ITERATOR_NEXT(io, po); } } exit: NPY_END_THREADS; free(edge_offsets); if (data_offsets) { for(jj = 0; jj < irank; jj++) free(data_offsets[jj]); free(data_offsets); } if (splvals) { for(jj = 0; jj < irank; jj++) free(splvals[jj]); free(splvals); } free(foffsets); free(fcoordinates); return PyErr_Occurred() ? 0 : 1; }
int NI_GeometricTransform(PyArrayObject *input, int (*map)(npy_intp*, double*, int, int, void*), void* map_data, PyArrayObject* matrix_ar, PyArrayObject* shift_ar, PyArrayObject *coordinates, PyArrayObject *output, int order, int mode, double cval) { char *po, *pi, *pc = NULL; npy_intp **edge_offsets = NULL, **data_offsets = NULL, filter_size; npy_intp ftmp[MAXDIM], *fcoordinates = NULL, *foffsets = NULL; npy_intp cstride = 0, kk, hh, ll, jj, *idxs = NULL; npy_intp size; double **splvals = NULL, icoor[MAXDIM]; npy_intp idimensions[MAXDIM], istrides[MAXDIM]; NI_Iterator io, ic; Float64 *matrix = matrix_ar ? (Float64*)PyArray_DATA(matrix_ar) : NULL; Float64 *shift = shift_ar ? (Float64*)PyArray_DATA(shift_ar) : NULL; int irank = 0, orank, qq; for(kk = 0; kk < input->nd; kk++) { idimensions[kk] = input->dimensions[kk]; istrides[kk] = input->strides[kk]; } irank = input->nd; orank = output->nd; /* if the mapping is from array coordinates: */ if (coordinates) { /* initialze a line iterator along the first axis: */ if (!NI_InitPointIterator(coordinates, &ic)) goto exit; cstride = ic.strides[0]; if (!NI_LineIterator(&ic, 0)) goto exit; pc = (void *)(PyArray_DATA(coordinates)); } /* offsets used at the borders: */ edge_offsets = (npy_intp**)malloc(irank * sizeof(npy_intp*)); data_offsets = (npy_intp**)malloc(irank * sizeof(npy_intp*)); if (!edge_offsets || !data_offsets) { PyErr_NoMemory(); goto exit; } for(jj = 0; jj < irank; jj++) data_offsets[jj] = NULL; for(jj = 0; jj < irank; jj++) { data_offsets[jj] = (npy_intp*)malloc((order + 1) * sizeof(npy_intp)); if (!data_offsets[jj]) { PyErr_NoMemory(); goto exit; } } /* will hold the spline coefficients: */ splvals = (double**)malloc(irank * sizeof(double*)); if (!splvals) { PyErr_NoMemory(); goto exit; } for(jj = 0; jj < irank; jj++) splvals[jj] = NULL; for(jj = 0; jj < irank; jj++) { splvals[jj] = (double*)malloc((order + 1) * sizeof(double)); if (!splvals[jj]) { PyErr_NoMemory(); goto exit; } } filter_size = 1; for(jj = 0; jj < irank; jj++) filter_size *= order + 1; idxs = (npy_intp*)malloc(filter_size * sizeof(idxs)); if (!idxs) { PyErr_NoMemory(); goto exit; } /* initialize output iterator: */ if (!NI_InitPointIterator(output, &io)) goto exit; /* get data pointers: */ pi = (void *)PyArray_DATA(input); po = (void *)PyArray_DATA(output); /* make a table of all possible coordinates within the spline filter: */ fcoordinates = (npy_intp*)malloc(irank * filter_size * sizeof(npy_intp)); /* make a table of all offsets within the spline filter: */ foffsets = (npy_intp*)malloc(filter_size * sizeof(npy_intp)); if (!fcoordinates || !foffsets) { PyErr_NoMemory(); goto exit; } for(jj = 0; jj < irank; jj++) ftmp[jj] = 0; kk = 0; for(hh = 0; hh < filter_size; hh++) { for(jj = 0; jj < irank; jj++) fcoordinates[jj + hh * irank] = ftmp[jj]; foffsets[hh] = kk; for(jj = irank - 1; jj >= 0; jj--) { if (ftmp[jj] < order) { ftmp[jj]++; kk += istrides[jj]; break; } else { ftmp[jj] = 0; kk -= istrides[jj] * order; } } } size = 1; for(qq = 0; qq < output->nd; qq++) size *= output->dimensions[qq]; for(kk = 0; kk < size; kk++) { double t = 0.0; int constant = 0, edge = 0, offset = 0; if (map) { /* call mappint functions: */ if (!map(io.coordinates, icoor, orank, irank, map_data)) { if (!PyErr_Occurred()) PyErr_SetString(PyExc_RuntimeError, "unknown error in mapping function"); goto exit; } } else if (matrix) { /* do an affine transformation: */ Float64 *p = matrix; for(hh = 0; hh < irank; hh++) { icoor[hh] = 0.0; for(ll = 0; ll < orank; ll++) icoor[hh] += io.coordinates[ll] * *p++; icoor[hh] += shift[hh]; } } else if (coordinates) { /* mapping is from an coordinates array: */ char *p = pc; switch(coordinates->descr->type_num) { CASE_MAP_COORDINATES(p, icoor, irank, cstride, Bool); CASE_MAP_COORDINATES(p, icoor, irank, cstride, UInt8); CASE_MAP_COORDINATES(p, icoor, irank, cstride, UInt16); CASE_MAP_COORDINATES(p, icoor, irank, cstride, UInt32); #if HAS_UINT64 CASE_MAP_COORDINATES(p, icoor, irank, cstride, UInt64); #endif CASE_MAP_COORDINATES(p, icoor, irank, cstride, Int8); CASE_MAP_COORDINATES(p, icoor, irank, cstride, Int16); CASE_MAP_COORDINATES(p, icoor, irank, cstride, Int32); CASE_MAP_COORDINATES(p, icoor, irank, cstride, Int64); CASE_MAP_COORDINATES(p, icoor, irank, cstride, Float32); CASE_MAP_COORDINATES(p, icoor, irank, cstride, Float64); default: PyErr_SetString(PyExc_RuntimeError, "coordinate array data type not supported"); goto exit; } } /* iterate over axes: */ for(hh = 0; hh < irank; hh++) { /* if the input coordinate is outside the borders, map it: */ double cc = map_coordinate(icoor[hh], idimensions[hh], mode); if (cc > -1.0) { /* find the filter location along this axis: */ int start; if (order & 1) { start = (int)floor(cc) - order / 2; } else { start = (int)floor(cc + 0.5) - order / 2; } /* get the offset to the start of the filter: */ offset += istrides[hh] * start; if (start < 0 || start + order >= idimensions[hh]) { /* implement border mapping, if outside border: */ edge = 1; edge_offsets[hh] = data_offsets[hh]; for(ll = 0; ll <= order; ll++) { int idx = start + ll; int len = idimensions[hh]; if (len <= 1) { idx = 0; } else { int s2 = 2 * len - 2; if (idx < 0) { idx = s2 * (int)(-idx / s2) + idx; idx = idx <= 1 - len ? idx + s2 : -idx; } else if (idx >= len) { idx -= s2 * (int)(idx / s2); if (idx >= len) idx = s2 - idx; } } /* calculate and store the offests at this edge: */ edge_offsets[hh][ll] = istrides[hh] * (idx - start); } } else { /* we are not at the border, use precalculated offsets: */ edge_offsets[hh] = NULL; } spline_coefficients(cc, order, splvals[hh]); } else { /* we use the constant border condition: */ constant = 1; break; } } if (!constant) { npy_intp *ff = fcoordinates; for(hh = 0; hh < filter_size; hh++) { int idx = 0; if (edge) { for(ll = 0; ll < irank; ll++) { if (edge_offsets[ll]) idx += edge_offsets[ll][ff[ll]]; else idx += ff[ll] * istrides[ll]; } } else { idx = foffsets[hh]; } idx += offset; idxs[hh] = idx; ff += irank; } } if (!constant) { npy_intp *ff = fcoordinates; t = 0.0; for(hh = 0; hh < filter_size; hh++) { double coeff = 0.0; switch(input->descr->type_num) { CASE_INTERP_COEFF(coeff, pi, idxs[hh], Bool); CASE_INTERP_COEFF(coeff, pi, idxs[hh], UInt8); CASE_INTERP_COEFF(coeff, pi, idxs[hh], UInt16); CASE_INTERP_COEFF(coeff, pi, idxs[hh], UInt32); #if HAS_UINT64 CASE_INTERP_COEFF(coeff, pi, idxs[hh], UInt64); #endif CASE_INTERP_COEFF(coeff, pi, idxs[hh], Int8); CASE_INTERP_COEFF(coeff, pi, idxs[hh], Int16); CASE_INTERP_COEFF(coeff, pi, idxs[hh], Int32); CASE_INTERP_COEFF(coeff, pi, idxs[hh], Int64); CASE_INTERP_COEFF(coeff, pi, idxs[hh], Float32); CASE_INTERP_COEFF(coeff, pi, idxs[hh], Float64); default: PyErr_SetString(PyExc_RuntimeError, "data type not supported"); goto exit; } /* calculate the interpolated value: */ for(ll = 0; ll < irank; ll++) if (order > 0) coeff *= splvals[ll][ff[ll]]; t += coeff; ff += irank; } } else { t = cval; } /* store output value: */ switch (output->descr->type_num) { CASE_INTERP_OUT(po, t, Bool); CASE_INTERP_OUT_UINT(po, t, UInt8, 0, MAX_UINT8); CASE_INTERP_OUT_UINT(po, t, UInt16, 0, MAX_UINT16); CASE_INTERP_OUT_UINT(po, t, UInt32, 0, MAX_UINT32); #if HAS_UINT64 /* FIXME */ CASE_INTERP_OUT_UINT(po, t, UInt64, 0, MAX_UINT32); #endif CASE_INTERP_OUT_INT(po, t, Int8, MIN_INT8, MAX_INT8); CASE_INTERP_OUT_INT(po, t, Int16, MIN_INT16, MAX_INT16); CASE_INTERP_OUT_INT(po, t, Int32, MIN_INT32, MAX_INT32); CASE_INTERP_OUT_INT(po, t, Int64, MIN_INT64, MAX_INT64); CASE_INTERP_OUT(po, t, Float32); CASE_INTERP_OUT(po, t, Float64); default: PyErr_SetString(PyExc_RuntimeError, "data type not supported"); goto exit; } if (coordinates) { NI_ITERATOR_NEXT2(io, ic, po, pc); } else { NI_ITERATOR_NEXT(io, po); } } exit: if (edge_offsets) free(edge_offsets); if (data_offsets) { for(jj = 0; jj < irank; jj++) free(data_offsets[jj]); free(data_offsets); } if (splvals) { for(jj = 0; jj < irank; jj++) free(splvals[jj]); free(splvals); } if (foffsets) free(foffsets); if (fcoordinates) free(fcoordinates); if (idxs) free(idxs); return PyErr_Occurred() ? 0 : 1; }