| |
| |
|
|
| #pragma once |
|
|
| #include <memory> |
| #include <vector> |
| #include <stack> |
|
|
| #include "../geometry.h" |
|
|
|
|
| #define MODE_GEOMETRY 0x02ull |
| #define MODE_CHILDREN 0x00ull |
|
|
| #define DIM_X 0x0ull |
| #define DIM_Y 0x1ull |
|
|
| static const size_t INVALID_IDX = -1; |
|
|
| template<typename T> |
| struct NMS_BoundsWrapper |
| { |
| typedef std::unique_ptr<NMS_BoundsWrapper> Ptr; |
| typedef AABB_<typename T::inner_type> bds_t; |
|
|
| size_t GeoIdx; |
| const T *Geometry; |
| bds_t Bounds; |
|
|
| NMS_BoundsWrapper(size_t geoIdx, const T *geometry) : GeoIdx(geoIdx), Geometry(geometry), Bounds(geometry->Bounds()) { } |
| }; |
|
|
| template<typename T> |
| class NMS_NodeAllocator; |
|
|
| template<typename T> |
| class NMS_KDTree; |
|
|
| template<typename T> |
| class NMS_BuildCache; |
|
|
| template<typename T> |
| class NMS_KDNode |
| { |
| friend class NMS_KDTree<T>; |
|
|
| public: |
| typedef NMS_BoundsWrapper<T> bds_t; |
| typedef std::unique_ptr<NMS_KDNode[]> UPtr; |
| typedef typename T::inner_type inner_type; |
| typedef std::vector<bds_t*> geo_vec_t; |
| typedef std::unique_ptr<geo_vec_t> geo_vec_ptr; |
|
|
| void Build(geo_vec_ptr geometries, const typename bds_t::bds_t &envelope, |
| NMS_NodeAllocator<T> &allocator, NMS_BuildCache<T> &buildCache); |
|
|
| template<typename Fn> |
| void FindIntersections(size_t geoIdx, const typename bds_t::bds_t &bds, const Fn &fn) const; |
|
|
| private: |
| inline uintptr_t Dim() const { return reinterpret_cast<uintptr_t>(m_ptr) & 0x01ull; } |
| inline uintptr_t Mode() const { return reinterpret_cast<uintptr_t>(m_ptr) & 0x02ull; } |
| inline void Children(NMS_KDNode *&children, inner_type &splitPos) const |
| { |
| auto vPtr = Geometries(); |
| splitPos = *reinterpret_cast<inner_type*>(vPtr); |
| children = reinterpret_cast<NMS_KDNode*>(vPtr + sizeof(inner_type)); |
| } |
| inline uint8_t* Geometries() const |
| { |
| return reinterpret_cast<uint8_t*>(reinterpret_cast<uintptr_t>(m_ptr) & ~0x3ull); |
| } |
| inline void SetPtr(uint8_t *vPtr, uintptr_t mode, uintptr_t dim) |
| { |
| m_ptr = reinterpret_cast<uint8_t*>( |
| reinterpret_cast<uintptr_t>(vPtr) | mode | dim |
| ); |
| } |
| void AssignGeometries(geo_vec_ptr geometries, NMS_BuildCache<T> &buildCache); |
|
|
| uint8_t *m_ptr; |
| }; |
|
|
| template<typename T> |
| class NMS_NodeAllocator |
| { |
| public: |
| typedef NMS_KDNode<T> node_t; |
| typedef typename node_t::inner_type inner_type; |
|
|
| NMS_NodeAllocator(size_t initialGuess = 512); |
| ~NMS_NodeAllocator(); |
|
|
| void Get(size_t numNodes, NMS_KDNode<T> *&outNodes, inner_type *&outSplitPos, uint8_t *&outRawPtr); |
|
|
| private: |
| std::vector<std::pair<size_t, uint8_t*>> m_buffers; |
| size_t m_offset; |
| }; |
|
|
| template<typename T> |
| class NMS_BuildCache |
| { |
| public: |
| typedef typename NMS_KDNode<T>::bds_t bds_t; |
| typedef std::unique_ptr<NMS_BuildCache> Ptr; |
| typedef std::vector<bds_t*> geo_vec_t; |
| typedef std::unique_ptr<geo_vec_t> geo_vec_ptr; |
|
|
| NMS_BuildCache(size_t initialSize); |
| ~NMS_BuildCache(); |
|
|
| geo_vec_ptr Get(size_t sizeHint); |
| bds_t** GetRawBuffer(size_t numGeos, uint8_t *&rawPtr); |
|
|
| void Release(geo_vec_ptr buff); |
|
|
| private: |
| std::stack<geo_vec_ptr> m_cache; |
| std::vector<std::pair<size_t, uint8_t*>> m_rawBuffers; |
| size_t m_rawOffset; |
| }; |
|
|
|
|
| template<typename T> |
| class NMS_KDTree |
| { |
| typedef typename T::inner_type inner_type; |
| typedef NMS_BoundsWrapper<T> bds_t; |
| typedef NMS_KDNode<T> node_t; |
|
|
| public: |
| NMS_KDTree(); |
| ~NMS_KDTree(); |
|
|
| void Build(const std::vector<T> &geometries); |
|
|
| template<typename Fn> |
| void FindIntersections(size_t geoIdx, const Fn &fn) const; |
|
|
| template<typename Fn> |
| void FindIntersections(const T &geo, const Fn &fn) const; |
|
|
| private: |
| bds_t *m_wrappers; |
| NMS_NodeAllocator<T> m_allocator; |
| node_t m_root; |
| typename NMS_BuildCache<T>::Ptr m_buildCache; |
| }; |
|
|
| template<typename T> |
| NMS_KDTree<T>::NMS_KDTree() |
| : m_wrappers(nullptr) |
| { |
| m_root.m_ptr = nullptr; |
| } |
|
|
| template<typename T> |
| NMS_KDTree<T>::~NMS_KDTree() |
| { |
| free(m_wrappers); |
| } |
|
|
| template<typename T> |
| void NMS_KDTree<T>::Build(const std::vector<T> &geometries) |
| { |
| if (geometries.empty()) { |
| m_root.m_ptr = nullptr; |
| return; |
| } |
|
|
| |
| |
| m_wrappers = reinterpret_cast<bds_t*>(malloc(sizeof(bds_t) * geometries.size())); |
|
|
| m_buildCache.reset(new NMS_BuildCache<T>(geometries.size())); |
|
|
| auto bdsGeos = m_buildCache->Get(geometries.size()); |
|
|
| typename bds_t::bds_t envelope; |
|
|
| for (size_t i = 0; i < geometries.size(); ++i) { |
| |
| new (m_wrappers + i) bds_t(i, &geometries[i]); |
|
|
| bdsGeos->push_back(m_wrappers + i); |
| if (i == 0) { |
| envelope = m_wrappers[i].Bounds; |
| } else { |
| envelope = envelope.Union(m_wrappers[i].Bounds); |
| } |
| } |
|
|
|
|
| m_root.Build(std::move(bdsGeos), envelope, m_allocator, *m_buildCache); |
| } |
|
|
| template<typename T> |
| void NMS_KDNode<T>::Build(geo_vec_ptr geometries, const typename bds_t::bds_t &envelope, |
| NMS_NodeAllocator<T> &allocator, NMS_BuildCache<T> &buildCache) |
| { |
| static const size_t MAX_GEOMETRIES = 8; |
|
|
| if (geometries->size() <= MAX_GEOMETRIES) { |
| AssignGeometries(std::move(geometries), buildCache); |
| } else { |
| geo_vec_ptr leftGeos = buildCache.Get(geometries->size()), |
| rightGeos = buildCache.Get(geometries->size()); |
|
|
| inner_type szX = envelope[2] - envelope[0]; |
| inner_type szY = envelope[3] - envelope[1]; |
|
|
| int64_t dim = szX > szY ? 0 : 1; |
| auto emn = envelope[dim]; |
| auto emx = envelope[dim + 2]; |
|
|
| auto pivotPos = (emn + emx) / 2; |
| for (bds_t *g : *geometries) { |
| auto mn = g->Bounds[dim]; |
| auto mx = g->Bounds[dim + 2]; |
|
|
| if (mn < pivotPos) { |
| leftGeos->push_back(g); |
| } |
| if (mx > pivotPos) { |
| rightGeos->push_back(g); |
| } |
| } |
|
|
| if (leftGeos->size() == geometries->size() || rightGeos->size() == geometries->size()) { |
| AssignGeometries(std::move(geometries), buildCache); |
| buildCache.Release(std::move(leftGeos)); |
| buildCache.Release(std::move(rightGeos)); |
| } else { |
| buildCache.Release(std::move(geometries)); |
|
|
| inner_type *nodeSplitPos; |
| uint8_t *nodeRawPtr; |
| NMS_KDNode *children; |
| allocator.Get(2, children, nodeSplitPos, nodeRawPtr); |
|
|
| SetPtr(nodeRawPtr, MODE_CHILDREN, dim); |
| *nodeSplitPos = pivotPos; |
|
|
| typename bds_t::bds_t leftEnv{envelope}, rightEnv{envelope}; |
| |
| leftEnv[dim + 2] = pivotPos; |
| |
| rightEnv[dim] = pivotPos; |
|
|
| children[0].Build(std::move(leftGeos), leftEnv, allocator, buildCache); |
| children[1].Build(std::move(rightGeos), rightEnv, allocator, buildCache); |
| } |
| } |
| } |
|
|
| template<typename T> |
| void NMS_KDNode<T>::AssignGeometries(geo_vec_ptr geometries, NMS_BuildCache<T> &buildCache) |
| { |
| if (geometries->empty()) { |
| SetPtr(nullptr, MODE_GEOMETRY, 0); |
| } else { |
| uint8_t *vPtr; |
| bds_t **geoPtr = buildCache.GetRawBuffer(geometries->size(), vPtr); |
| std::copy(geometries->begin(), geometries->end(), geoPtr); |
|
|
| SetPtr(vPtr, MODE_GEOMETRY, 0); |
| } |
| buildCache.Release(std::move(geometries)); |
| } |
|
|
| template<typename T> |
| template<typename Fn> |
| void NMS_KDTree<T>::FindIntersections(size_t geoIdx, const Fn &fn) const |
| { |
| if (!m_wrappers) return; |
|
|
| auto &bds = m_wrappers[geoIdx].Bounds; |
|
|
| m_root.FindIntersections(geoIdx, bds, fn); |
| } |
|
|
| template<typename T> |
| template<typename Fn> |
| void NMS_KDTree<T>::FindIntersections(const T &geo, const Fn &fn) const |
| { |
| if (!m_wrappers) return; |
|
|
| NMS_BoundsWrapper<T> bdsWrapper(INVALID_IDX, &geo); |
|
|
| m_root.FindIntersections(INVALID_IDX, bdsWrapper.Bounds, fn); |
| } |
|
|
| template<typename T> |
| template<typename Fn> |
| void NMS_KDNode<T>::FindIntersections(size_t geoIdx, const typename bds_t::bds_t &bds, const Fn &fn) const |
| { |
| auto mode = Mode(); |
|
|
| if (mode == MODE_GEOMETRY) { |
| auto *vPtr = Geometries(); |
|
|
| size_t numGeos = *reinterpret_cast<size_t*>(vPtr); |
| bds_t **geoPtr = reinterpret_cast<bds_t**>(vPtr + sizeof(size_t)); |
|
|
| bds_t **endPtr = geoPtr + numGeos; |
| for (; geoPtr != endPtr; ++geoPtr) { |
| const bds_t *child = *geoPtr; |
|
|
| |
| if (geoIdx != INVALID_IDX && child->GeoIdx <= geoIdx) continue; |
|
|
| typename bds_t::bds_t::inner_type pctN, pctM, iou; |
| std::tie(pctN, pctM, iou) = geometry_region_sizes(bds, child->Bounds); |
|
|
| if (iou > 0) { |
| fn(child->GeoIdx, pctN, pctM, iou); |
| } |
| } |
| } else { |
| auto dim = Dim(); |
|
|
| auto mn = bds[dim]; |
| auto mx = bds[dim + 2]; |
|
|
| NMS_KDNode *children; |
| inner_type splitPos; |
| Children(children, splitPos); |
|
|
| if (mn < splitPos) { |
| children[0].FindIntersections(geoIdx, bds, fn); |
| } |
| if (mx > splitPos) { |
| children[1].FindIntersections(geoIdx, bds, fn); |
| } |
| } |
| } |
|
|
| template<typename T> |
| NMS_NodeAllocator<T>::NMS_NodeAllocator(size_t initialGuess) |
| : m_offset(0) |
| { |
| size_t allocSize = initialGuess * (sizeof(inner_type) + 2 * sizeof(node_t)); |
| auto ptr = reinterpret_cast<uint8_t*>(malloc(allocSize)); |
| m_buffers.emplace_back(initialGuess, ptr); |
| } |
|
|
| template<typename T> |
| NMS_NodeAllocator<T>::~NMS_NodeAllocator() |
| { |
| for (auto &p : m_buffers) { |
| free(p.second); |
| } |
| } |
|
|
| template<typename T> |
| void NMS_NodeAllocator<T>::Get(size_t numNodes, node_t *&outNodes, inner_type *&outSplitPos, uint8_t *&outRawPtr) |
| { |
| auto &currBuff = m_buffers.back(); |
|
|
| size_t rem = currBuff.first - m_offset; |
|
|
| size_t reqSize = sizeof(inner_type) + sizeof(node_t) * numNodes; |
|
|
| if (rem >= reqSize) { |
| outRawPtr = currBuff.second + m_offset; |
| outSplitPos = reinterpret_cast<inner_type*>(outRawPtr); |
| outNodes = reinterpret_cast<node_t*>(outRawPtr + sizeof(inner_type)); |
| m_offset += reqSize; |
| return; |
| } |
|
|
| |
| size_t allocSize = (std::max(currBuff.first * 2, reqSize) + 1) & ~0x01ull; |
| auto ptr = reinterpret_cast<uint8_t*>(malloc(allocSize)); |
| m_buffers.emplace_back(allocSize, ptr); |
| m_offset = 0; |
|
|
| Get(numNodes, outNodes, outSplitPos, outRawPtr); |
| } |
|
|
| template<typename T> |
| NMS_BuildCache<T>::NMS_BuildCache(size_t initialSize) |
| : m_rawOffset(0) |
| { |
| auto allocSize = sizeof(bds_t*) * initialSize * 2; |
| auto raw1 = reinterpret_cast<uint8_t*>(malloc(allocSize)); |
| m_rawBuffers.emplace_back(allocSize, raw1); |
| } |
|
|
| template<typename T> |
| NMS_BuildCache<T>::~NMS_BuildCache() |
| { |
| for (auto &p : m_rawBuffers) { |
| free(p.second); |
| } |
| } |
|
|
| template<typename T> |
| typename NMS_BuildCache<T>::geo_vec_ptr NMS_BuildCache<T>::Get(size_t sizeHint) |
| { |
| geo_vec_ptr ret; |
| if (! m_cache.empty()) { |
| ret = std::move(m_cache.top()); |
| m_cache.pop(); |
| ret->clear(); |
| } else { |
| ret.reset(new std::vector<bds_t*>); |
| } |
|
|
| ret->reserve(sizeHint); |
| return ret; |
| } |
|
|
| template<typename T> |
| typename NMS_BuildCache<T>::bds_t** NMS_BuildCache<T>::GetRawBuffer(size_t numGeos, uint8_t *&rawPtr) |
| { |
| auto &currBuff = m_rawBuffers.back(); |
| size_t rem = currBuff.first - m_rawOffset; |
|
|
| size_t reqSize = sizeof(size_t) + sizeof(bds_t*) * numGeos; |
|
|
| if (rem >= reqSize) { |
| rawPtr = currBuff.second + m_rawOffset; |
| m_rawOffset += reqSize; |
| reinterpret_cast<size_t*>(rawPtr)[0] = numGeos; |
| return reinterpret_cast<bds_t**>(rawPtr + sizeof(size_t)); |
| } |
|
|
| size_t allocSize = (std::max(currBuff.first * 2, reqSize) + 1) & ~0x01ull; |
| auto ptr = reinterpret_cast<uint8_t*>(malloc(allocSize)); |
| m_rawBuffers.emplace_back(allocSize, ptr); |
| m_rawOffset = 0; |
|
|
| return GetRawBuffer(numGeos, rawPtr); |
| } |
|
|
| template<typename T> |
| void NMS_BuildCache<T>::Release(geo_vec_ptr buff) |
| { |
| m_cache.push(std::move(buff)); |
| } |
|
|
| #undef MODE_GEOMETRY |
| #undef MODE_CHILDREN |
| #undef DIM_X |
| #undef DIM_Y |
|
|