/* 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" #include "ni_morphology.h" #include #include #include #include #define LIST_SIZE 100000 #define CASE_GET_MASK(_msk_value, _pm, _type) \ case t ## _type: \ _msk_value = *(_type*)_pm ? 1 : 0; \ break #define CASE_OUTPUT(_po, _out, _type) \ case t ## _type: \ *(_type*)_po = (_type)_out; \ break #define CASE_NI_ERODE_POINT(_pi, _out, _offsets, _filter_size, _type, \ _mv, _border_value, _bv, _center_is_true,\ _true, _false, _changed) \ case t ## _type: \ { \ maybelong _ii, _oo; \ int _in = *(_type*)_pi ? 1 : 0; \ if (_mv) { \ if (_center_is_true && _in == false) { \ _changed = 0; \ _out = _in; \ } else { \ _out = _true; \ for(_ii = 0; _ii < _filter_size; _ii++) { \ _oo = _offsets[_ii]; \ if (_oo == _bv) { \ if (!_border_value) { \ _out = _false; \ break; \ } \ } else { \ int _nn = *(_type*)(_pi + _oo) ? _true : _false; \ if (!_nn) { \ _out = _false; \ break; \ } \ } \ } \ _changed = _out != _in; \ } \ } else { \ _out = _in; \ } \ } \ break int NI_BinaryErosion(PyArrayObject* input, PyArrayObject* strct, PyArrayObject* mask, PyArrayObject* output, int bdr_value, maybelong *origins, int invert, int center_is_true, int* changed, NI_CoordinateList **coordinate_list) { maybelong struct_size = 0, *offsets = NULL, size, *oo, jj; maybelong ssize, block_size = 0, *current = NULL; int kk, border_flag_value, true, false, msk_value; NI_Iterator ii, io, mi; NI_FilterIterator fi; Bool *ps, out = 0; char *pi, *po, *pm = NULL; NI_CoordinateBlock *block = NULL; ps = (Bool*)NA_OFFSETDATA(strct); ssize = 1; for(kk = 0; kk < strct->nd; kk++) ssize *= strct->dimensions[kk]; for(jj = 0; jj < ssize; jj++) if (ps[jj]) ++struct_size; if (mask) { if (!NI_InitPointIterator(mask, &mi)) return 0; pm = NA_OFFSETDATA(mask); } /* calculate the filter offsets: */ if (!NI_InitFilterOffsets(input, ps, strct->dimensions, origins, NI_EXTEND_CONSTANT, &offsets, &border_flag_value, NULL)) goto exit; /* initialize input element iterator: */ if (!NI_InitPointIterator(input, &ii)) goto exit; /* initialize output element iterator: */ if (!NI_InitPointIterator(output, &io)) goto exit; /* initialize filter iterator: */ if (!NI_InitFilterIterator(input->nd, strct->dimensions, struct_size, input->dimensions, origins, &fi)) goto exit; /* get data pointers an size: */ pi = NA_OFFSETDATA(input); po = NA_OFFSETDATA(output); size = 1; for(kk = 0; kk < input->nd; kk++) size *= input->dimensions[kk]; if (invert) { bdr_value = bdr_value ? 0 : 1; true = 0; false = 1; } else { bdr_value = bdr_value ? 1 : 0; true = 1; false = 0; } if (coordinate_list) { block_size = LIST_SIZE / input->nd / sizeof(int); if (block_size < 1) block_size = 1; if (block_size > size) block_size = size; *coordinate_list = NI_InitCoordinateList(block_size, input->nd); if (!*coordinate_list) goto exit; } /* iterator over the elements: */ oo = offsets; *changed = 0; msk_value = 1; for(jj = 0; jj < size; jj++) { int pchange = 0; if (mask) { switch(mask->descr->type_num) { CASE_GET_MASK(msk_value, pm, Bool); CASE_GET_MASK(msk_value, pm, UInt8); CASE_GET_MASK(msk_value, pm, UInt16); CASE_GET_MASK(msk_value, pm, UInt32); #if HAS_UINT64 CASE_GET_MASK(msk_value, pm, UInt64); #endif CASE_GET_MASK(msk_value, pm, Int8); CASE_GET_MASK(msk_value, pm, Int16); CASE_GET_MASK(msk_value, pm, Int32); CASE_GET_MASK(msk_value, pm, Int64); CASE_GET_MASK(msk_value, pm, Float32); CASE_GET_MASK(msk_value, pm, Float64); default: PyErr_SetString(PyExc_RuntimeError, "data type not supported"); return 0; } } switch (input->descr->type_num) { CASE_NI_ERODE_POINT(pi, out, oo, struct_size, Bool, msk_value, bdr_value, border_flag_value, center_is_true, true, false, pchange); CASE_NI_ERODE_POINT(pi, out, oo, struct_size, UInt8, msk_value, bdr_value, border_flag_value, center_is_true, true, false, pchange); CASE_NI_ERODE_POINT(pi, out, oo, struct_size, UInt16, msk_value, bdr_value, border_flag_value, center_is_true, true, false, pchange); CASE_NI_ERODE_POINT(pi, out, oo, struct_size, UInt32, msk_value, bdr_value, border_flag_value, center_is_true, true, false, pchange); #if HAS_UINT64 CASE_NI_ERODE_POINT(pi, out, oo, struct_size, UInt64, msk_value, bdr_value, border_flag_value, center_is_true, true, false, pchange); #endif CASE_NI_ERODE_POINT(pi, out, oo, struct_size, Int8, msk_value, bdr_value, border_flag_value, center_is_true, true, false, pchange); CASE_NI_ERODE_POINT(pi, out, oo, struct_size, Int16, msk_value, bdr_value, border_flag_value, center_is_true, true, false, pchange); CASE_NI_ERODE_POINT(pi, out, oo, struct_size, Int32, msk_value, bdr_value, border_flag_value, center_is_true, true, false, pchange); CASE_NI_ERODE_POINT(pi, out, oo, struct_size, Int64, msk_value, bdr_value, border_flag_value, center_is_true, true, false, pchange); CASE_NI_ERODE_POINT(pi, out, oo, struct_size, Float32, msk_value, bdr_value, border_flag_value, center_is_true, true, false, pchange); CASE_NI_ERODE_POINT(pi, out, oo, struct_size, Float64, msk_value, bdr_value, border_flag_value, center_is_true, true, false, pchange); default: PyErr_SetString(PyExc_RuntimeError, "data type not supported"); goto exit; } switch (output->descr->type_num) { CASE_OUTPUT(po, out, Bool); CASE_OUTPUT(po, out, UInt8); CASE_OUTPUT(po, out, UInt16); CASE_OUTPUT(po, out, UInt32); #if HAS_UINT64 CASE_OUTPUT(po, out, UInt64); #endif CASE_OUTPUT(po, out, Int8); CASE_OUTPUT(po, out, Int16); CASE_OUTPUT(po, out, Int32); CASE_OUTPUT(po, out, Int64); CASE_OUTPUT(po, out, Float32); CASE_OUTPUT(po, out, Float64); default: PyErr_SetString(PyExc_RuntimeError, "data type not supported"); goto exit; } if (pchange) { *changed = 1; if (coordinate_list) { if (block == NULL || block->size == block_size) { block = NI_CoordinateListAddBlock(*coordinate_list); current = block->coordinates; } for(kk = 0; kk < input->nd; kk++) *current++ = ii.coordinates[kk]; block->size++; } } if (mask) { NI_FILTER_NEXT3(fi, ii, io, mi, oo, pi, po, pm); } else { NI_FILTER_NEXT2(fi, ii, io, oo, pi, po); } } exit: if (offsets) free(offsets); if (PyErr_Occurred()) { if (coordinate_list) { NI_FreeCoordinateList(*coordinate_list); *coordinate_list = NULL; } return 0; } else { return 1; } return PyErr_Occurred() ? 0 : 1; } #define CASE_ERODE_POINT2(_struct_size, _offsets, _coordinate_offsets, \ _pi, _oo, _irank, _list1, _list2, \ _current_coors1, _current_coors2, _block1, \ _block2, _bf_value, _true, _false, _type, \ _mklist) \ case t ## _type: \ { \ maybelong _hh, _kk; \ for(_hh = 0; _hh < _struct_size; _hh++) { \ maybelong _to = _offsets[_oo + _hh]; \ if (_to != _bf_value && *(_type*)(_pi + _to) == _true) { \ if (_mklist) { \ maybelong *_tc = &(_coordinate_offsets[(_oo + _hh) * _irank]); \ if (_block2 == NULL || _block2->size == _list2->block_size) { \ _block2 = NI_CoordinateListAddBlock(_list2); \ _current_coors2 = _block2->coordinates; \ } \ for(_kk = 0; _kk < _irank; _kk++) \ *_current_coors2++ = _current_coors1[_kk] + _tc[_kk]; \ _block2->size++; \ } \ *(_type*)(_pi + _to) = _false; \ } \ } \ } \ break int NI_BinaryErosion2(PyArrayObject* array, PyArrayObject* strct, PyArrayObject* mask, int niter, maybelong *origins, int invert, NI_CoordinateList **iclist) { maybelong struct_size = 0, *offsets = NULL, oo, jj, ssize; maybelong *coordinate_offsets = NULL, size = 0; maybelong *current_coordinates1 = NULL, *current_coordinates2 = NULL; maybelong kk, border_flag_value, current = 0; int true, false; NI_Iterator ii, mi; NI_FilterIterator fi, ci; Bool *ps; char *pi, *ibase, *pm = NULL; NI_CoordinateBlock *block1 = NULL, *block2 = NULL; NI_CoordinateList *list1 = NULL, *list2 = NULL; ps = (Bool*)NA_OFFSETDATA(strct); ssize = 1; for(kk = 0; kk < strct->nd; kk++) ssize *= strct->dimensions[kk]; for(jj = 0; jj < ssize; jj++) if (ps[jj]) ++struct_size; /* calculate the filter offsets: */ if (!NI_InitFilterOffsets(array, ps, strct->dimensions, origins, NI_EXTEND_CONSTANT, &offsets, &border_flag_value, &coordinate_offsets)) goto exit; /* initialize input element iterator: */ if (!NI_InitPointIterator(array, &ii)) goto exit; /* initialize filter iterator: */ if (!NI_InitFilterIterator(array->nd, strct->dimensions, struct_size, array->dimensions, origins, &fi)) goto exit; if (!NI_InitFilterIterator(array->nd, strct->dimensions, struct_size * array->nd, array->dimensions, origins, &ci)) goto exit; /* get data pointers and size: */ ibase = pi = NA_OFFSETDATA(array); if (invert) { true = 0; false = 1; } else { true = 1; false = 0; } if (mask) { /* iterator, data pointer and type of mask array: */ if (!NI_InitPointIterator(mask, &mi)) return 0; pm = NA_OFFSETDATA(mask); size = 1; for(kk = 0; kk < array->nd; kk++) size *= array->dimensions[kk]; for(jj = 0; jj < size; jj++) { if (*(Int8*)pm) { *(Int8*)pm = -1; } else { *(Int8*)pm = (Int8)*(Bool*)pi; *(Bool*)pi = false; } NI_ITERATOR_NEXT2(ii, mi, pi, pm) } NI_ITERATOR_RESET(ii) pi = NA_OFFSETDATA(array); } list1 = NI_InitCoordinateList((*iclist)->block_size, (*iclist)->rank); list2 = NI_InitCoordinateList((*iclist)->block_size, (*iclist)->rank); if (!list1 || !list2) goto exit; if (NI_CoordinateListStealBlocks(list2, *iclist)) goto exit; block2 = list2->blocks; jj = 0; while(block1 || block2) { int mklist = 1; if (!block1) { if (niter <= 0 || jj < niter) { if (NI_CoordinateListStealBlocks(list1, list2)) goto exit; block1 = list1->blocks; block2 = NULL; current_coordinates1 = block1->coordinates; current = 0; ++jj; mklist = niter <= 0 || jj < niter; } else { break; } } NI_ITERATOR_GOTO(ii, current_coordinates1, ibase, pi); NI_FILTER_GOTO(fi, ii, 0, oo); switch (array->descr->type_num) { CASE_ERODE_POINT2(struct_size, offsets, coordinate_offsets, pi, oo, array->nd, list1, list2, current_coordinates1, current_coordinates2, block1, block2, border_flag_value, true, false, Bool, mklist); CASE_ERODE_POINT2(struct_size, offsets, coordinate_offsets, pi, oo, array->nd, list1, list2, current_coordinates1, current_coordinates2, block1, block2, border_flag_value, true, false, UInt8, mklist); CASE_ERODE_POINT2(struct_size, offsets, coordinate_offsets, pi, oo, array->nd, list1, list2, current_coordinates1, current_coordinates2, block1, block2, border_flag_value, true, false, UInt16, mklist); CASE_ERODE_POINT2(struct_size, offsets, coordinate_offsets, pi, oo, array->nd, list1, list2, current_coordinates1, current_coordinates2, block1, block2, border_flag_value, true, false, UInt32, mklist); #if HAS_UINT64 CASE_ERODE_POINT2(struct_size, offsets, coordinate_offsets, pi, oo, array->nd, list1, list2, current_coordinates1, current_coordinates2, block1, block2, border_flag_value, true, false, UInt64, mklist); #endif CASE_ERODE_POINT2(struct_size, offsets, coordinate_offsets, pi, oo, array->nd, list1, list2, current_coordinates1, current_coordinates2, block1, block2, border_flag_value, true, false, Int8, mklist); CASE_ERODE_POINT2(struct_size, offsets, coordinate_offsets, pi, oo, array->nd, list1, list2, current_coordinates1, current_coordinates2, block1, block2, border_flag_value, true, false, Int16, mklist); CASE_ERODE_POINT2(struct_size, offsets, coordinate_offsets, pi, oo, array->nd, list1, list2, current_coordinates1, current_coordinates2, block1, block2, border_flag_value, true, false, Int32, mklist); CASE_ERODE_POINT2(struct_size, offsets, coordinate_offsets, pi, oo, array->nd, list1, list2, current_coordinates1, current_coordinates2, block1, block2, border_flag_value, true, false, Int64, mklist); CASE_ERODE_POINT2(struct_size, offsets, coordinate_offsets, pi, oo, array->nd, list1, list2, current_coordinates1, current_coordinates2, block1, block2, border_flag_value, true, false, Float32, mklist); CASE_ERODE_POINT2(struct_size, offsets, coordinate_offsets, pi, oo, array->nd, list1, list2, current_coordinates1, current_coordinates2, block1, block2, border_flag_value, true, false, Float64, mklist); default: PyErr_SetString(PyExc_RuntimeError, "data type not supported"); goto exit; } ++current; if (current == block1->size) { block1 = NI_CoordinateListDeleteBlock(list1); if (block1) { current_coordinates1 = block1->coordinates; current = 0; } } else { current_coordinates1 += array->nd; } } if (mask) { NI_ITERATOR_RESET(ii) NI_ITERATOR_RESET(mi) pi = NA_OFFSETDATA(array); pm = NA_OFFSETDATA(mask); for(jj = 0; jj < size; jj++) { int value = *(Int8*)pm; if (value >= 0) *(Bool*)pi = value; NI_ITERATOR_NEXT2(ii, mi, pi, pm) } } exit: if (offsets) free(offsets); if (coordinate_offsets) free(coordinate_offsets); NI_FreeCoordinateList(list1); NI_FreeCoordinateList(list2); if (PyErr_Occurred()) { return 0; } else { return 1; } return PyErr_Occurred() ? 0 : 1; } #define NI_DISTANCE_EUCLIDIAN 1 #define NI_DISTANCE_CITY_BLOCK 2 #define NI_DISTANCE_CHESSBOARD 3 typedef struct { maybelong *coordinates; maybelong index; void *next; } NI_BorderElement; int NI_DistanceTransformBruteForce(PyArrayObject* input, int metric, PyArrayObject *sampling_arr, PyArrayObject* distances, PyArrayObject* features) { maybelong size, jj, min_index = 0; int kk; NI_BorderElement *border_elements = NULL, *temp; NI_Iterator ii, di, fi; char *pi, *pd = NULL, *pf = NULL; Float64 *sampling = sampling_arr ? NA_OFFSETDATA(sampling_arr) : NULL; /* check the output arrays: */ if (distances) { pd = NA_OFFSETDATA(distances); if (!NI_InitPointIterator(distances, &di)) goto exit; } if (features) { pf = NA_OFFSETDATA(features); if (!NI_InitPointIterator(features, &fi)) goto exit; } size = 1; for(kk = 0; kk < input->nd; kk++) size *= input->dimensions[kk]; pi = NA_OFFSETDATA(input); if (!NI_InitPointIterator(input, &ii)) goto exit; for(jj = 0; jj < size; jj++) { if (*(Int8*)pi < 0) { temp = (NI_BorderElement*)malloc(sizeof(NI_BorderElement)); if (!temp) { PyErr_NoMemory(); goto exit; } temp->next = border_elements; border_elements = temp; temp->index = jj; temp->coordinates = (int*)malloc(input->nd * sizeof(int)); for(kk = 0; kk < input->nd; kk++) temp->coordinates[kk] = ii.coordinates[kk]; } NI_ITERATOR_NEXT(ii, pi); } NI_ITERATOR_RESET(ii); pi = NA_OFFSETDATA(input); switch(metric) { case NI_DISTANCE_EUCLIDIAN: for(jj = 0; jj < size; jj++) { if (*(Int8*)pi > 0) { double distance = DBL_MAX; temp = border_elements; while(temp) { double d = 0.0, t; for(kk = 0; kk < input->nd; kk++) { t = ii.coordinates[kk] - temp->coordinates[kk]; if (sampling) t *= sampling[kk]; d += t * t; } if (d < distance) { distance = d; if (features) min_index = temp->index; } temp = temp->next; } if (distances) *(Float64*)pd = sqrt(distance); if (features) *(Int32*)pf = min_index; } else { if (distances) *(Float64*)pd = 0.0; if (features) *(Int32*)pf = jj; } if (features && distances) { NI_ITERATOR_NEXT3(ii, di, fi, pi, pd, pf); } else if (distances) { NI_ITERATOR_NEXT2(ii, di, pi, pd); } else { NI_ITERATOR_NEXT2(ii, fi, pi, pf); } } break; case NI_DISTANCE_CITY_BLOCK: case NI_DISTANCE_CHESSBOARD: for(jj = 0; jj < size; jj++) { if (*(Int8*)pi > 0) { unsigned long distance = ULONG_MAX; temp = border_elements; while(temp) { unsigned int d = 0; maybelong t; for(kk = 0; kk < input->nd; kk++) { t = ii.coordinates[kk] - temp->coordinates[kk]; if (t < 0) t = -t; if (metric == NI_DISTANCE_CITY_BLOCK) { d += t; } else { if ((unsigned int)t > d) d = t; } } if (d < distance) { distance = d; if (features) min_index = temp->index; } temp = temp->next; } if (distances) *(UInt32*)pd = distance; if (features) *(Int32*)pf = min_index; } else { if (distances) *(UInt32*)pd = 0; if (features) *(Int32*)pf = jj; } if (features && distances) { NI_ITERATOR_NEXT3(ii, di, fi, pi, pd, pf); } else if (distances) { NI_ITERATOR_NEXT2(ii, di, pi, pd); } else { NI_ITERATOR_NEXT2(ii, fi, pi, pf); } } break; default: PyErr_SetString(PyExc_RuntimeError, "distance metric not supported"); goto exit; } exit: while (border_elements) { temp = border_elements; border_elements = border_elements->next; if (temp->coordinates) free(temp->coordinates); free(temp); } return PyErr_Occurred() ? 0 : 1; } int NI_DistanceTransformOnePass(PyArrayObject *strct, PyArrayObject* distances, PyArrayObject *features) { int kk; maybelong jj, ii, ssize, size, filter_size, mask_value, *oo; maybelong *foffsets = NULL, *foo = NULL, *offsets = NULL; Bool *ps, *pf = NULL, *footprint = NULL; char *pd; NI_FilterIterator si, ti; NI_Iterator di, fi; ssize = 1; for(kk = 0; kk < strct->nd; kk++) ssize *= strct->dimensions[kk]; /* we only use the first half of the structure data, so we make a temporary structure for use with the filter functions: */ footprint = (Bool*)malloc(ssize * sizeof(Bool)); if (!footprint) { PyErr_NoMemory(); goto exit; } ps = (Bool*)NA_OFFSETDATA(strct); filter_size = 0; for(jj = 0; jj < ssize / 2; jj++) { footprint[jj] = ps[jj]; if (ps[jj]) ++filter_size; } for(jj = ssize / 2; jj < ssize; jj++) footprint[jj] = 0; /* get data and size */ pd = NA_OFFSETDATA(distances); size = 1; for(kk = 0; kk < distances->nd; kk++) size *= distances->dimensions[kk]; if (!NI_InitPointIterator(distances, &di)) goto exit; /* calculate the filter offsets: */ if (!NI_InitFilterOffsets(distances, footprint, strct->dimensions, NULL, NI_EXTEND_CONSTANT, &offsets, &mask_value, NULL)) goto exit; /* initialize filter iterator: */ if (!NI_InitFilterIterator(distances->nd, strct->dimensions, filter_size, distances->dimensions, NULL, &si)) goto exit; if (features) { int dummy; /* initialize point iterator: */ pf = NA_OFFSETDATA(features); if (!NI_InitPointIterator(features, &fi)) goto exit; /* calculate the filter offsets: */ if (!NI_InitFilterOffsets(features, footprint, strct->dimensions, NULL, NI_EXTEND_CONSTANT, &foffsets, &dummy, NULL)) goto exit; /* initialize filter iterator: */ if (!NI_InitFilterIterator(distances->nd, strct->dimensions, filter_size, distances->dimensions, NULL, &ti)) goto exit; } /* iterator over the elements: */ oo = offsets; if (features) foo = foffsets; for(jj = 0; jj < size; jj++) { Int32 value = *(Int32*)pd; if (value != 0) { Int32 min = value; maybelong min_offset = 0; /* iterate over structuring element: */ for(ii = 0; ii < filter_size; ii++) { maybelong offset = oo[ii]; Int32 tt = -1; if (offset < mask_value) tt = *(Int32*)(pd + offset); if (tt >= 0) { if ((min < 0) || (tt + 1 < min)) { min = tt + 1; if (features) min_offset = foo[ii]; } } } *(Int32*)pd = min; if (features) *(Int32*)pf = *(Int32*)(pf + min_offset); } if (features) { NI_FILTER_NEXT(ti, fi, foo, pf); } NI_FILTER_NEXT(si, di, oo, pd); } exit: if (offsets) free(offsets); if (foffsets) free(foffsets); if (footprint) free(footprint); return PyErr_Occurred() ? 0 : 1; } static void _VoronoiFT(char *pf, maybelong len, maybelong *coor, int rank, int d, maybelong stride, maybelong cstride, maybelong **f, maybelong *g, Float64 *sampling) { maybelong l = -1, ii, maxl, idx1, idx2; int jj; for(ii = 0; ii < len; ii++) for(jj = 0; jj < rank; jj++) f[ii][jj] = *(Int32*)(pf + ii * stride + cstride * jj); for(ii = 0; ii < len; ii++) { if (*(Int32*)(pf + ii * stride) >= 0) { double fd = f[ii][d]; double wR = 0.0; for(jj = 0; jj < rank; jj++) { if (jj != d) { double tw = f[ii][jj] - coor[jj]; if (sampling) tw *= sampling[jj]; wR += tw * tw; } } while(l >= 1) { double a, b, c, uR = 0.0, vR = 0.0, f1; idx1 = g[l]; f1 = f[idx1][d]; idx2 = g[l - 1]; a = f1 - f[idx2][d]; b = fd - f1; if (sampling) { a *= sampling[d]; b *= sampling[d]; } c = a + b; for(jj = 0; jj < rank; jj++) { if (jj != d) { double cc = coor[jj]; double tu = f[idx2][jj] - cc; double tv = f[idx1][jj] - cc; if (sampling) { tu *= sampling[jj]; tv *= sampling[jj]; } uR += tu * tu; vR += tv * tv; } } if (c * vR - b * uR - a * wR - a * b * c <= 0.0) break; --l; } ++l; g[l] = ii; } } maxl = l; if (maxl >= 0) { l = 0; for (ii = 0; ii < len; ii++) { double delta1 = 0.0, t; for(jj = 0; jj < rank; jj++) { t = jj == d ? f[g[l]][jj] - ii : f[g[l]][jj] - coor[jj]; if (sampling) t *= sampling[jj]; delta1 += t * t; } while (l < maxl) { double delta2 = 0.0; for(jj = 0; jj < rank; jj++) { t = jj == d ? f[g[l + 1]][jj] - ii : f[g[l + 1]][jj] - coor[jj]; if (sampling) t *= sampling[jj]; delta2 += t * t; } if (delta1 <= delta2) break; delta1 = delta2; ++l; } idx1 = g[l]; for(jj = 0; jj < rank; jj++) *(Int32*)(pf + ii * stride + jj * cstride) = f[idx1][jj]; } } } /* Recursive feature transform */ static void _ComputeFT(char *pi, char *pf, maybelong *ishape, maybelong *istrides, maybelong *fstrides, int rank, int d, maybelong *coor, maybelong **f, maybelong *g, PyArrayObject *features, Float64 *sampling) { int kk; maybelong jj; if (d == 0) { char *tf1 = pf; for(jj = 0; jj < ishape[0]; jj++) { if (*(Int8*)pi) { *(Int32*)tf1 = -1; } else { char *tf2 = tf1; *(Int32*)tf2 = jj; for(kk = 1; kk < rank; kk++) { tf2 += fstrides[0]; *(Int32*)tf2 = coor[kk]; } } pi += istrides[0]; tf1 += fstrides[1]; } _VoronoiFT(pf, ishape[0], coor, rank, 0, fstrides[1], fstrides[0], f, g, sampling); } else { UInt32 axes = 0; char *tf = pf; maybelong size = 1; NI_Iterator ii; for(jj = 0; jj < ishape[d]; jj++) { coor[d] = jj; _ComputeFT(pi, tf, ishape, istrides, fstrides, rank, d - 1, coor, f, g, features, sampling); pi += istrides[d]; tf += fstrides[d + 1]; } for(jj = 0; jj < d; jj++) { axes |= (UInt32)1 << (jj + 1); size *= ishape[jj]; } NI_InitPointIterator(features, &ii); NI_SubspaceIterator(&ii, axes); tf = pf; for(jj = 0; jj < size; jj++) { for(kk = 0; kk < d; kk++) coor[kk] = ii.coordinates[kk]; _VoronoiFT(tf, ishape[d], coor, rank, d, fstrides[d + 1], fstrides[0], f, g, sampling); NI_ITERATOR_NEXT(ii, tf); } for(kk = 0; kk < d; kk++) coor[kk] = 0; } } /* Exact euclidean feature transform, as described in: C. R. Maurer, Jr., R. Qi, V. Raghavan, "A linear time algorithm for computing exact euclidean distance transforms of binary images in arbitrary dimensions. IEEE Trans. PAMI 25, 265-270, 2003. */ int NI_EuclideanFeatureTransform(PyArrayObject* input, PyArrayObject *sampling_arr, PyArrayObject* features) { int ii; maybelong coor[NI_MAXDIM], mx = 0, jj; maybelong *tmp = NULL, **f = NULL, *g = NULL; char *pi, *pf; Float64 *sampling = sampling_arr ? NA_OFFSETDATA(sampling_arr) : NULL; pi = NA_OFFSETDATA(input); pf = NA_OFFSETDATA(features); for(ii = 0; ii < input->nd; ii++) { coor[ii] = 0; if (input->dimensions[ii] > mx) mx = input->dimensions[ii]; } /* Some temporaries */ f = (maybelong**)malloc(mx * sizeof(maybelong*)); g = (maybelong*)malloc(mx * sizeof(maybelong)); tmp = (maybelong*)malloc(mx * input->nd * sizeof(maybelong)); if (!f || !g || !tmp) { PyErr_NoMemory(); goto exit; } for(jj = 0; jj < mx; jj++) f[jj] = tmp + jj * input->nd; /* First call of recursive feature transform */ _ComputeFT(pi, pf, input->dimensions, input->strides, features->strides, input->nd, input->nd - 1, coor, f, g, features, sampling); exit: if (f) free(f); if (g) free(g); if (tmp) free(tmp); return PyErr_Occurred() ? 0 : 1; }