/* 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. */ #include "ni_support.h" /* initialize iterations over single array elements: */ int NI_InitPointIterator(PyArrayObject *array, NI_Iterator *iterator) { int ii; iterator->rank_m1 = array->nd - 1; for(ii = 0; ii < array->nd; ii++) { /* adapt dimensions for use in the macros: */ iterator->dimensions[ii] = array->dimensions[ii] - 1; /* initialize coordinates: */ iterator->coordinates[ii] = 0; /* initialize strides: */ iterator->strides[ii] = array->strides[ii]; /* calculate the strides to move back at the end of an axis: */ iterator->backstrides[ii] = array->strides[ii] * iterator->dimensions[ii]; } return 1; } /* initialize iteration over a lower sub-space: */ int NI_SubspaceIterator(NI_Iterator *iterator, UInt32 axes) { int ii, last = 0; for(ii = 0; ii <= iterator->rank_m1; ii++) { if (axes & (((UInt32)1) << ii)) { if (last != ii) { iterator->dimensions[last] = iterator->dimensions[ii]; iterator->strides[last] = iterator->strides[ii]; iterator->backstrides[last] = iterator->backstrides[ii]; } ++last; } } iterator->rank_m1 = last - 1; return 1; } /* initialize iteration over array lines: */ int NI_LineIterator(NI_Iterator *iterator, int axis) { UInt32 axes = ((UInt32)1) << axis; return NI_SubspaceIterator(iterator, ~axes); } /******************************************************************/ /* Line buffers */ /******************************************************************/ /* Allocate line buffer data */ int NI_AllocateLineBuffer(PyArrayObject* array, int axis, maybelong size1, maybelong size2, maybelong *lines, maybelong max_size, double **buffer) { maybelong line_size, max_lines; int ii; /* the number of lines of the array is an upper limit for the number of lines in the buffer: */ max_lines = 1; for(ii = 0; ii < array->nd; ii++) max_lines *= array->dimensions[ii]; if (array->nd > 0 && array->dimensions[axis] > 0) max_lines /= array->dimensions[axis]; /* calculate the space needed for one line, including space to support the boundary conditions: */ line_size = sizeof(double) * (array->dimensions[axis] + size1 + size2); /* if *lines < 1, no number of lines is proposed, so we calculate it from the maximum size allowed: */ if (*lines < 1) { *lines = line_size > 0 ? max_size / line_size : 0; if (*lines < 1) *lines = 1; } /* no need to allocate too many lines: */ if (*lines > max_lines) *lines = max_lines; /* allocate data for the buffer: */ *buffer = (double*)malloc(*lines * line_size); if (!*buffer) { PyErr_NoMemory(); return 0; } return 1; } /* 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 = NA_OFFSETDATA(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; } /* Extend a line in memory to implement boundary conditions: */ int NI_ExtendLine(double *line, maybelong length, maybelong size1, maybelong size2, NI_ExtendMode mode, double constant_value) { maybelong ii, jj, length1, nextend, rextend; double *l1, *l2, *l3, val; switch (mode) { case NI_EXTEND_WRAP: nextend = size1 / length; rextend = size1 - nextend * length; l1 = line + size1 + length - rextend; l2 = line; for(ii = 0; ii < rextend; ii++) *l2++ = *l1++; for(ii = 0; ii < nextend; ii++) { l1 = line + size1; for(jj = 0; jj < length; jj++) *l2++ = *l1++; } nextend = size2 / length; rextend = size2 - nextend * length; l1 = line + size1; l2 = line + size1 + length; for(ii = 0; ii < nextend; ii++) { l3 = l1; for(jj = 0; jj < length; jj++) *l2++ = *l3++; } for(ii = 0; ii < rextend; ii++) *l2++ = *l1++; break; case NI_EXTEND_MIRROR: if (length == 1) { l1 = line; val = line[size1]; for(ii = 0; ii < size1; ii++) *l1++ = val; l1 = line + size1 + length; val = line[size1 + length - 1]; for(ii = 0; ii < size2; ii++) *l1++ = val; } else { length1 = length - 1; nextend = size1 / length1; rextend = size1 - nextend * length1; l1 = line + size1 + 1; l2 = l1 - 2; for(ii = 0; ii < nextend; ii++) { l3 = l1; for(jj = 0; jj < length1; jj++) *l2-- = *l3++; l1 -= length1; } for(ii = 0; ii < rextend; ii++) *l2-- = *l1++; nextend = size2 / length1; rextend = size2 - nextend * length1; l1 = line + size1 + length1 - 1; l2 = l1 + 2; for(ii = 0; ii < nextend; ii++) { l3 = l1; for(jj = 0; jj < length1; jj++) *l2++ = *l3--; l1 += length1; } for(ii = 0; ii < rextend; ii++) *l2++ = *l1--; } break; case NI_EXTEND_REFLECT: nextend = size1 / length; rextend = size1 - nextend * length; l1 = line + size1; l2 = l1 - 1; for(ii = 0; ii < nextend; ii++) { l3 = l1; for(jj = 0; jj < length; jj++) *l2-- = *l3++; l1 -= length; } l3 = l1; for(ii = 0; ii < rextend; ii++) *l2-- = *l3++; nextend = size2 / length; rextend = size2 - nextend * length; l1 = line + size1 + length - 1; l2 = l1 + 1; for(ii = 0; ii < nextend; ii++) { l3 = l1; for(jj = 0; jj < length; jj++) *l2++ = *l3--; l1 += length; } for(ii = 0; ii < rextend; ii++) *l2++ = *l1--; break; case NI_EXTEND_NEAREST: l1 = line; val = line[size1]; for(ii = 0; ii < size1; ii++) *l1++ = val; l1 = line + size1 + length; val = line[size1 + length - 1]; for(ii = 0; ii < size2; ii++) *l1++ = val; break; case NI_EXTEND_CONSTANT: l1 = line; for(ii = 0; ii < size1; ii++) *l1++ = constant_value; l1 = line + size1 + length; for(ii = 0; ii < size2; ii++) *l1++ = constant_value; break; default: PyErr_SetString(PyExc_RuntimeError, "mode not supported"); return 0; } return 1; } #define CASE_COPY_DATA_TO_LINE(_pi, _po, _length, _stride, _type) \ case t ## _type: \ { \ maybelong _ii; \ for(_ii = 0; _ii < _length; _ii++) { \ _po[_ii] = (double)*(_type*)_pi; \ _pi += _stride; \ } \ } \ break /* Copy a line from an array to a buffer: */ int NI_ArrayToLineBuffer(NI_LineBuffer *buffer, maybelong *number_of_lines, int *more) { double *pb = buffer->buffer_data; char *pa; maybelong length = buffer->line_length; pb += buffer->size1; *number_of_lines = 0; /* fill until all lines in the array have been processed, or until the buffer is full: */ while (buffer->next_line < buffer->array_lines && *number_of_lines < buffer->buffer_lines) { pa = buffer->array_data; /* copy the data from the array to the buffer: */ switch (buffer->array_type) { CASE_COPY_DATA_TO_LINE(pa, pb, length, buffer->line_stride, Bool); CASE_COPY_DATA_TO_LINE(pa, pb, length, buffer->line_stride, UInt8); CASE_COPY_DATA_TO_LINE(pa, pb, length, buffer->line_stride, UInt16); CASE_COPY_DATA_TO_LINE(pa, pb, length, buffer->line_stride, UInt32); #if HAS_UINT64 CASE_COPY_DATA_TO_LINE(pa, pb, length, buffer->line_stride, UInt64); #endif CASE_COPY_DATA_TO_LINE(pa, pb, length, buffer->line_stride, Int8); CASE_COPY_DATA_TO_LINE(pa, pb, length, buffer->line_stride, Int16); CASE_COPY_DATA_TO_LINE(pa, pb, length, buffer->line_stride, Int32); CASE_COPY_DATA_TO_LINE(pa, pb, length, buffer->line_stride, Int64); CASE_COPY_DATA_TO_LINE(pa, pb, length, buffer->line_stride, Float32); CASE_COPY_DATA_TO_LINE(pa, pb, length, buffer->line_stride, Float64); default: PyErr_SetString(PyExc_RuntimeError, "array type not supported"); return 0; } /* goto next line in the array: */ NI_ITERATOR_NEXT(buffer->iterator, buffer->array_data); /* implement boundary conditions to the line: */ if (buffer->size1 + buffer->size2 > 0) if (!NI_ExtendLine(pb - buffer->size1, length, buffer->size1, buffer->size2, buffer->extend_mode, buffer->extend_value)) return 0; /* The number of the array lines copied: */ ++(buffer->next_line); /* keep track of (and return) the number of lines in the buffer: */ ++(*number_of_lines); pb += buffer->line_length + buffer->size1 + buffer->size2; } /* if not all array lines were processed, *more is set true: */ *more = buffer->next_line < buffer->array_lines; return 1; } #define CASE_COPY_LINE_TO_DATA(_pi, _po, _length, _stride, _type) \ case t ## _type: \ { \ maybelong _ii; \ for(_ii = 0; _ii < _length; _ii++) { \ *(_type*)_po = (_type)_pi[_ii]; \ _po += _stride; \ } \ } \ break /* Copy a line from a buffer to an array: */ int NI_LineBufferToArray(NI_LineBuffer *buffer) { double *pb = buffer->buffer_data; char *pa; maybelong jj, length = buffer->line_length; pb += buffer->size1; for(jj = 0; jj < buffer->buffer_lines; jj++) { /* if all array lines are copied return: */ if (buffer->next_line == buffer->array_lines) break; pa = buffer->array_data; /* copy data from the buffer to the array: */ switch (buffer->array_type) { CASE_COPY_LINE_TO_DATA(pb, pa, length, buffer->line_stride, Bool); CASE_COPY_LINE_TO_DATA(pb, pa, length, buffer->line_stride, UInt8); CASE_COPY_LINE_TO_DATA(pb, pa, length, buffer->line_stride, UInt16); CASE_COPY_LINE_TO_DATA(pb, pa, length, buffer->line_stride, UInt32); #if HAS_UINT64 CASE_COPY_LINE_TO_DATA(pb, pa, length, buffer->line_stride, UInt64); #endif CASE_COPY_LINE_TO_DATA(pb, pa, length, buffer->line_stride, Int8); CASE_COPY_LINE_TO_DATA(pb, pa, length, buffer->line_stride, Int16); CASE_COPY_LINE_TO_DATA(pb, pa, length, buffer->line_stride, Int32); CASE_COPY_LINE_TO_DATA(pb, pa, length, buffer->line_stride, Int64); CASE_COPY_LINE_TO_DATA(pb, pa, length, buffer->line_stride, Float32); CASE_COPY_LINE_TO_DATA(pb, pa, length, buffer->line_stride, Float64); default: PyErr_SetString(PyExc_RuntimeError, "array type not supported"); return 0; } /* move to the next line in the array: */ NI_ITERATOR_NEXT(buffer->iterator, buffer->array_data); /* number of lines copied: */ ++(buffer->next_line); /* move the buffer data pointer to the next line: */ pb += buffer->line_length + buffer->size1 + buffer->size2; } return 1; } /******************************************************************/ /* Multi-dimensional filter support functions */ /******************************************************************/ /* Initialize a filter iterator: */ int NI_InitFilterIterator(int rank, maybelong *filter_shape, maybelong filter_size, maybelong *array_shape, maybelong *origins, NI_FilterIterator *iterator) { int ii; maybelong fshape[MAXDIM], forigins[MAXDIM]; for(ii = 0; ii < rank; ii++) { fshape[ii] = *filter_shape++; forigins[ii] = origins ? *origins++ : 0; } /* calculate the strides, used to move the offsets pointer through the offsets table: */ if (rank > 0) { iterator->strides[rank - 1] = filter_size; for(ii = rank - 2; ii >= 0; ii--) { maybelong step = array_shape[ii + 1] < fshape[ii + 1] ? array_shape[ii + 1] : fshape[ii + 1]; iterator->strides[ii] = iterator->strides[ii + 1] * step; } } for(ii = 0; ii < rank; ii++) { maybelong step = array_shape[ii] < fshape[ii] ? array_shape[ii] : fshape[ii]; maybelong orgn = fshape[ii] / 2 + forigins[ii]; /* stride for stepping back to previous offsets: */ iterator->backstrides[ii] = (step - 1) * iterator->strides[ii]; /* initialize boundary extension sizes: */ iterator->bound1[ii] = orgn; iterator->bound2[ii] = array_shape[ii] - fshape[ii] + orgn; } return 1; } /* Calculate the offsets to the filter points, for all border regions and the interior of the array: */ int NI_InitFilterOffsets(PyArrayObject *array, Bool *footprint, maybelong *filter_shape, maybelong* origins, NI_ExtendMode mode, maybelong **offsets, maybelong *border_flag_value, maybelong **coordinate_offsets) { int rank, ii; maybelong kk, ll, filter_size = 1, offsets_size = 1, max_size = 0; maybelong max_stride = 0, *ashape = NULL, *astrides = NULL; maybelong footprint_size = 0, coordinates[MAXDIM], position[MAXDIM]; maybelong fshape[MAXDIM], forigins[MAXDIM], *po, *pc = NULL; rank = array->nd; ashape = array->dimensions; astrides = array->strides; for(ii = 0; ii < rank; ii++) { fshape[ii] = *filter_shape++; forigins[ii] = origins ? *origins++ : 0.0; } /* the size of the footprint array: */ for(ii = 0; ii < rank; ii++) filter_size *= fshape[ii]; /* calculate the number of non-zero elements in the footprint: */ if (footprint) { for(kk = 0; kk < filter_size; kk++) if (footprint[kk]) ++footprint_size; } else { footprint_size = filter_size; } /* calculate how many sets of offsets must be stored: */ for(ii = 0; ii < rank; ii++) offsets_size *= (ashape[ii] < fshape[ii] ? ashape[ii] : fshape[ii]); /* allocate offsets data: */ *offsets = (maybelong*)malloc(offsets_size * footprint_size * sizeof(maybelong)); if (!*offsets) { PyErr_NoMemory(); goto exit; } if (coordinate_offsets) { *coordinate_offsets = (maybelong*)malloc(offsets_size * rank * footprint_size * sizeof(maybelong)); if (!*coordinate_offsets) { PyErr_NoMemory(); goto exit; } } for(ii = 0; ii < rank; ii++) { maybelong stride; /* find maximum axis size: */ if (ashape[ii] > max_size) max_size = ashape[ii]; /* find maximum stride: */ stride = astrides[ii] < 0 ? -astrides[ii] : astrides[ii]; if (stride > max_stride) max_stride = stride; /* coordinates for iterating over the kernel elements: */ coordinates[ii] = 0; /* keep track of the kernel position: */ position[ii] = 0; } /* the flag to indicate that we are outside the border must have a value that is larger than any possible offset: */ *border_flag_value = max_size * max_stride + 1; /* calculate all possible offsets to elements in the filter kernel, for all regions in the array (interior and border regions): */ po = *offsets; if (coordinate_offsets) { pc = *coordinate_offsets; } /* iterate over all regions: */ for(ll = 0; ll < offsets_size; ll++) { /* iterate over the elements in the footprint array: */ for(kk = 0; kk < filter_size; kk++) { maybelong offset = 0; /* only calculate an offset if the footprint is 1: */ if (!footprint || footprint[kk]) { /* find offsets along all axes: */ for(ii = 0; ii < rank; ii++) { maybelong orgn = fshape[ii] / 2 + forigins[ii]; maybelong cc = coordinates[ii] - orgn + position[ii]; maybelong len = ashape[ii]; /* apply boundary conditions, if necessary: */ switch (mode) { case NI_EXTEND_MIRROR: if (cc < 0) { if (len <= 1) { cc = 0; } else { int sz2 = 2 * len - 2; cc = sz2 * (int)(-cc / sz2) + cc; cc = cc <= 1 - len ? cc + sz2 : -cc; } } else if (cc >= len) { if (len <= 1) { cc = 0; } else { int sz2 = 2 * len - 2; cc -= sz2 * (int)(cc / sz2); if (cc >= len) cc = sz2 - cc; } } break; case NI_EXTEND_REFLECT: if (cc < 0) { if (len <= 1) { cc = 0; } else { int sz2 = 2 * len; if (cc < -sz2) cc = sz2 * (int)(-cc / sz2) + cc; cc = cc < -len ? cc + sz2 : -cc - 1; } } else if (cc >= len) { if (len <= 1) {cc = 0; } else { int sz2 = 2 * len; cc -= sz2 * (int)(cc / sz2); if (cc >= len) cc = sz2 - cc - 1; } } break; case NI_EXTEND_WRAP: if (cc < 0) { if (len <= 1) { cc = 0; } else { int sz = len; cc += sz * (int)(-cc / sz); if (cc < 0) cc += sz; } } else if (cc >= len) { if (len <= 1) { cc = 0; } else { int sz = len; cc -= sz * (int)(cc / sz); } } break; case NI_EXTEND_NEAREST: if (cc < 0) { cc = 0; } else if (cc >= len) { cc = len - 1; } break; case NI_EXTEND_CONSTANT: if (cc < 0 || cc >= len) cc = *border_flag_value; break; default: PyErr_SetString(PyExc_RuntimeError, "boundary mode not supported"); goto exit; } /* calculate offset along current axis: */ if (cc == *border_flag_value) { /* just flag that we are outside the border */ offset = *border_flag_value; if (coordinate_offsets) pc[ii] = 0; break; } else { /* use an offset that is possibly mapped from outside the border: */ cc = cc - position[ii]; offset += astrides[ii] * cc; if (coordinate_offsets) pc[ii] = cc; } } /* store the offset */ *po++ = offset; if (coordinate_offsets) pc += rank; } /* next point in the filter: */ for(ii = rank - 1; ii >= 0; ii--) { if (coordinates[ii] < fshape[ii] - 1) { coordinates[ii]++; break; } else { coordinates[ii] = 0; } } } /* move to the next array region: */ for(ii = rank - 1; ii >= 0; ii--) { int orgn = fshape[ii] / 2 + forigins[ii]; if (position[ii] == orgn) { position[ii] += ashape[ii] - fshape[ii] + 1; if (position[ii] <= orgn) position[ii] = orgn + 1; } else { position[ii]++; } if (position[ii] < ashape[ii]) { break; } else { position[ii] = 0; } } } exit: if (PyErr_Occurred()) { if (*offsets) free(*offsets); if (coordinate_offsets && *coordinate_offsets) free(*coordinate_offsets); return 0; } else { return 1; } } NI_CoordinateList* NI_InitCoordinateList(int size, int rank) { NI_CoordinateList *list = \ (NI_CoordinateList*)malloc(sizeof(NI_CoordinateList)); if (!list) { PyErr_NoMemory(); return NULL; } list->block_size = size; list->rank = rank; list->blocks = NULL; return list; } int NI_CoordinateListStealBlocks(NI_CoordinateList *list1, NI_CoordinateList *list2) { if (list1->block_size != list2->block_size || list1->rank != list2->rank) { PyErr_SetString(PyExc_RuntimeError, "coordinate lists not compatible"); return 1; } if (list1->blocks) { PyErr_SetString(PyExc_RuntimeError, "first is list not empty"); return 1; } list1->blocks = list2->blocks; list2->blocks = NULL; return 0; } NI_CoordinateBlock* NI_CoordinateListAddBlock(NI_CoordinateList *list) { NI_CoordinateBlock* block = NULL; block = (NI_CoordinateBlock*)malloc(sizeof(NI_CoordinateBlock)); if (!block) { PyErr_NoMemory(); goto exit; } block->coordinates = (int*)malloc(list->block_size * list->rank * sizeof(int)); if (!block->coordinates) { PyErr_NoMemory(); goto exit; } block->next = list->blocks; list->blocks = block; block->size = 0; exit: if (PyErr_Occurred()) { if (block) free(block); return NULL; } return block; } NI_CoordinateBlock* NI_CoordinateListDeleteBlock(NI_CoordinateList *list) { NI_CoordinateBlock* block = list->blocks; if (block) { list->blocks = block->next; if (block->coordinates) free(block->coordinates); free(block); } return list->blocks; } void NI_FreeCoordinateList(NI_CoordinateList *list) { if (list) { NI_CoordinateBlock *block = list->blocks; while (block) { NI_CoordinateBlock *tmp = block; block = block->next; if (tmp->coordinates) free(tmp->coordinates); free(tmp); } list->blocks = NULL; free(list); } }