/* * Copyright (C) 2007-2008 Anael Orlinski * * This file is part of Panomatic. * * Panomatic is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * Panomatic is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Panomatic; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "KDTree.h" #include #include #include namespace KDTreeSpace { /****************************************************************************** * * KDTree() * * Constructor for the tree, use this one to build the tree * ******************************************************************************/ template KDTree::KDTree(const ItemVector_t& iElemsList, int iDimensions) : _pivot(NULL), _leftKD(NULL), _rightKD(NULL), _dims(iDimensions) { // disallow to build a tree with 0 elements. if (!iElemsList.size()) return; // construct a vector with pointers to the elems lists, and run init on it. ItemPtrVector_t aElemsPtrList; ItemVectorIt_t aElemsIt = iElemsList.begin(); ItemVectorIt_t aElemsItEnd = iElemsList.end(); for(; aElemsIt != aElemsItEnd; ++aElemsIt) { const KE &aTmp = *aElemsIt; aElemsPtrList.push_back(&aTmp); } init(aElemsPtrList); } /****************************************************************************** * * KDTree() * * Constructor for the sub KDTrees * ******************************************************************************/ template KDTree::KDTree(const ItemPtrVector_t& iElemsPtrList, int iDimensions) : _pivot(NULL), _leftKD(NULL), _rightKD(NULL), _dims(iDimensions) { init(iElemsPtrList); } /****************************************************************************** * * ~KDTree() * * Destructor * ******************************************************************************/ template KDTree::~KDTree() { delete(_leftKD); delete(_rightKD); } /****************************************************************************** * * init() * * Build the local KDTree and recurse to create childs * ******************************************************************************/ template void KDTree::init(const ItemPtrVector_t& iElemsPtrList) { // choose the pivot element typename std::vector::const_iterator aPivotIt = choosePivot(iElemsPtrList); _pivot = *aPivotIt; // create the left/right vectors. ItemPtrVector_t aLeftElems, aRightElems; // split the elements set according to the split dimension ItemPtrVectorIt_t aElemsIt = iElemsPtrList.begin(); ItemPtrVectorIt_t aElemsItEnd = iElemsPtrList.end(); VTYPE aPivotDimValue = _pivot->getVectorElem(_splitDim); for(; aElemsIt != aElemsItEnd; ++aElemsIt) { // skip the pivot element. if (aElemsIt == aPivotIt) continue; if ((*aElemsIt)->getVectorElem(_splitDim) <= aPivotDimValue) aLeftElems.push_back(*aElemsIt); else aRightElems.push_back(*aElemsIt); } //int aLeftSize = (int)aLeftElems.size(); //int aRightSize = (int)aRightElems.size(); // recursively create the sub-KDTrees if (aLeftElems.size()) _leftKD = new KDTree (aLeftElems, _dims); else _leftKD = NULL; if (aRightElems.size()) _rightKD = new KDTree (aRightElems, _dims); else _rightKD = NULL; } /****************************************************************************** * * choosePivot() * * Will choose a splitting dimension for the current tree and get closest * point * ******************************************************************************/ template typename std::vector::const_iterator KDTree::choosePivot(const ItemPtrVector_t& iElemsPtrList) { // search for minimum and maximum in each dimension std::vector aMinVals(_dims, std::numeric_limits::max()); std::vector aMaxVals(_dims, - std::numeric_limits::max()); ItemPtrVectorIt_t aElemsIt = iElemsPtrList.begin(); ItemPtrVectorIt_t aElemsEnd = iElemsPtrList.end(); for(; aElemsIt != aElemsEnd; ++aElemsIt) { for (int aDim = 0; aDim < _dims; ++aDim) { VTYPE & aCurValue = (*aElemsIt)->getVectorElem(aDim); if (aCurValue < aMinVals[aDim]) aMinVals[aDim] = aCurValue; if (aCurValue > aMaxVals[aDim]) aMaxVals[aDim] = aCurValue; } } // look for the dimension that has the largest range of values _splitDim = 0; VTYPE aLargestRangeValue = - std::numeric_limits::max(); for (int aDim = 0; aDim < _dims; ++aDim) { if ( (aMaxVals[aDim] - aMinVals[aDim]) > aLargestRangeValue ) { _splitDim = aDim; aLargestRangeValue = aMaxVals[aDim] - aMinVals[aDim]; } } // compute the median value in the chosen dimension VTYPE aMedian = aLargestRangeValue / 2 + aMinVals[_splitDim]; // look for the element closest to the median in the chosen dimension typename std::vector::const_iterator aClosestElemIt; VTYPE aClosestDiff = std::numeric_limits::max(); for(aElemsIt = iElemsPtrList.begin(); aElemsIt != aElemsEnd; ++aElemsIt) { VTYPE aCurValue = fabs((*aElemsIt)->getVectorElem(_splitDim) - aMedian); if (aCurValue < aClosestDiff) { aClosestDiff = aCurValue; aClosestElemIt = aElemsIt; } } return aClosestElemIt; } /****************************************************************************** * * CalcSqDist() * * calc the square distance between 2 entries * ******************************************************************************/ template double KDTree::calcSqDist(const KE * i1, const KE * i2) { double aDist = 0.0; for (int n = 0 ; n < _dims ; ++n) { double aDiff = i1->getVectorElem(n) - i2->getVectorElem(n); aDist += aDiff * aDiff; } return (aDist); } template std::set, std::greater > > KDTree::getNearestNeighboursBBF(const KE & iTarget, int iNbBestMatches, int iNbSearchSteps) { // create a hyper rectangle for whole space HyperRectangle aHR(_dims); // create a limited set to hold best matches. BestMatchLimitedSet_t aBestMatches(iNbBestMatches); // create a limited set to hold the search queue std::list > aSearchQueue; // recursively process the search recurseNearestNeighboursBBF(iTarget, aHR, aBestMatches, aSearchQueue, iNbSearchSteps); return aBestMatches.getSet(); } template void KDTree::recurseNearestNeighboursBBF(const KE & iTarget, HyperRectangle & iHR, BestMatchLimitedSet_t &ioBestMatches, QueueEntryList_t &ioSearchQueue, int & ioRemainingUnqueues) { // add the node entry to the bestmatches set ioBestMatches.insert(BestMatch(_pivot, calcSqDist(&iTarget, _pivot))); //std::set, std::greater > >::iterator aBEB; //for (aBEB = ioBestMatches.begin(); aBEB!= ioBestMatches.end(); ++aBEB) // cout << "Best Match " << aBEB->_distance << endl; // split the space by a plane perpendicular to the current node dimension // and going through the node point. HyperRectangle aLeftHR(iHR); HyperRectangle aRightHR(iHR); //cout << "split dim :" << _splitDim << " " << _pivot->getVectorElem(_splitDim) << endl; //cout << "iHR" << endl; //iHR.display(); if (!iHR.split(aLeftHR, aRightHR, _splitDim, _pivot->getVectorElem(_splitDim))) return; //cout << "leftHR" << endl; //aLeftHR.display(); //cout << "rightHR" << endl; //aRightHR.display(); // Create some pointers and refs to the nearer and further HyperRectangles and KDTrees. KDTree * aNearKD = _leftKD; KDTree * aFarKD = _rightKD; HyperRectangle & aNearHR = aLeftHR; HyperRectangle & aFarHR = aRightHR; // Determine which HR and sub-KD tree is near/far // by comparing the Target and pivot values at node dimension if (iTarget.getVectorElem(_splitDim) > _pivot->getVectorElem(_splitDim)) { aNearKD = _rightKD; aFarKD = _leftKD; aNearHR = aRightHR; aFarHR = aLeftHR; } //std::cout << "Near HR, target is in " << aNearHR.isTargetIn(iTarget) << endl; //std::cout << "Far HR, target is in " << aFarHR.isTargetIn(iTarget) << endl; // add the far HyperRectangle to the queue. The queue is sorted by // the distance from target point to the hyperrectangle : // "The distance to a bin is defined to be the minimum distance between q and any // point on the bin boundary." if (aFarKD) ioSearchQueue.push_back(QueueEntry(aFarHR, aFarKD, aFarHR.calcSqDistance(iTarget))); //list >::iterator aSQ; //for (aSQ = ioSearchQueue.begin(); aSQ!= ioSearchQueue.end(); ++aSQ) // cout << "SQ " << aSQ->_dist << endl; // go direcly in depth until a leaf is found. if (aNearKD) { //cout << "Going in depth" << endl; aNearKD->recurseNearestNeighboursBBF(iTarget, aNearHR, ioBestMatches, ioSearchQueue, ioRemainingUnqueues); } // else process the queue else if (ioRemainingUnqueues > 0 && ioBestMatches.size() && ioSearchQueue.size()) { //cout << "Going in queue" << endl; // decrease the number of 'queue processings' ioRemainingUnqueues--; // get the (squared) distance of the worse 'best match'. this is the first one in the best list. double aHyperSphereRadius = ioBestMatches.begin()->_distance; // get the first element out of the queue, then remove it typename std::list >::iterator aSQ, aSmallestIt; double aSmallestDist = std::numeric_limits::max(); for (aSQ = ioSearchQueue.begin(); aSQ!= ioSearchQueue.end(); ++aSQ) { if (aSQ->_dist < aSmallestDist) { aSmallestDist = aSQ->_dist; aSmallestIt = aSQ; } } QueueEntry aQueueElem = *(aSmallestIt); ioSearchQueue.erase(aSmallestIt); //QueueEntry aQueueElem = *(ioSearchQueue.begin()); //ioSearchQueue.erase(ioSearchQueue.begin()); // if the hypersphere and hyper rectangle intersect, there is maybe a better match in the intersection if (aQueueElem._HR.hasHyperSphereIntersect(iTarget, aHyperSphereRadius)) { aQueueElem._kdTree->recurseNearestNeighboursBBF(iTarget, aQueueElem._HR, ioBestMatches, ioSearchQueue, ioRemainingUnqueues); } else { //cout << "No intersect" << endl; } } } // default constructor template HyperRectangle::HyperRectangle() : _dim(0) { } // constructor template HyperRectangle::HyperRectangle(int iDim) : _leftTop(std::vector(iDim, -std::numeric_limits::max())), _rightBottom(std::vector(iDim, std::numeric_limits::max())), _dim(iDim) { } // copy constructor template HyperRectangle::HyperRectangle(HyperRectangle & iOther) : _leftTop(std::vector(iOther._leftTop)), _rightBottom(std::vector(iOther._rightBottom)), _dim(iOther._dim) { } // split into smaller HyperRectangle template bool HyperRectangle::split(HyperRectangle &oLeft, HyperRectangle &oRight, int iSplitDim, TYPE iSplitVal) { // check if the split value is in the range for the given dimension if (_leftTop[iSplitDim] >= iSplitVal || _rightBottom[iSplitDim] < iSplitVal) { return false; } // make copies of the current vector to destination bin //oLeft._leftTop = _leftTop; //oRight._leftTop = _leftTop; //oLeft._rightBottom = _rightBottom; //oRight._rightBottom = _rightBottom; //oLeft._dim = _dim; //oRight._dim = _dim; // split oLeft._rightBottom[iSplitDim] = iSplitVal; oRight._leftTop[iSplitDim] = iSplitVal; return true; } template double HyperRectangle::calcSqDistance (const KE & iTarget) { double aSqDistance = 0; // compute the closest point within hr to the target. for (int n = 0 ; n < _dim ; ++n) { TYPE aTargetVal = iTarget.getVectorElem(n); TYPE aHRMin = _leftTop[n]; TYPE aHRMax = _rightBottom[n]; double aDimDist = aTargetVal; if (aTargetVal <= aHRMin) { aDimDist = aTargetVal - aHRMin; } else if (aTargetVal > aHRMin && aTargetVal < aHRMax) { aDimDist = 0; } else if (aTargetVal >= aHRMax) { aDimDist = aTargetVal - aHRMax; } aSqDistance += aDimDist * aDimDist; } return aSqDistance; } template bool HyperRectangle::hasHyperSphereIntersect(const KE & iTarget, double iSqDistance) { // avoid calculating the square root by using basic math. // double aDist = calcSqDistance(iTarget); //if (aDist > 1.0 && iSqDistance) //cout << "hypersphereintersect " << sqrt(aDist) << " " << sqrt(iSqDistance) << endl; return (aDist < iSqDistance); } template void HyperRectangle::display() { for (int n = 0 ; n < _dim ; ++n) { if (_leftTop[n] > -std::numeric_limits::max() || _rightBottom[n] < std::numeric_limits::max()) { std::cout << "dim[" << n << "] = {" << _leftTop[n] << " , " << _rightBottom[n] << "}" << std::endl; } } std::cout << std::endl; } template bool HyperRectangle::isTargetIn (const KE & iTarget) { if (iTarget.getVectorSize() != _dim) { std::cout << "is target in dimension mismatch" << std::endl; } for (int n = 0 ; n < _dim ; ++n) { TYPE aDimVal = iTarget.getVectorElem(n); if (aDimVal <= _leftTop[n] || aDimVal >= _rightBottom[n]) return (false); } return true; } template bool operator < (const QueueEntry & iA, const QueueEntry & iB) { return (iA._dist < iB._dist); } template bool operator > (const BestMatch & iA, const BestMatch & iB) { return (iA._distance > iB._distance); } } //namespace KDTree