NumCpp  2.12.1
A Templatized Header Only C++ Implementation of the Python NumPy Library
ClusterMaker.hpp
Go to the documentation of this file.
1
28
29#pragma once
30
31#include <algorithm>
32#include <cmath>
33#include <set>
34#include <string>
35#include <utility>
36#include <vector>
37
40#include "NumCpp/Core/Types.hpp"
42#include "NumCpp/NdArray.hpp"
43
44namespace nc::imageProcessing
45{
46 //=============================================================================
47 // Class Description:
49 template<typename dtype>
51 {
52 private:
53 STATIC_ASSERT_ARITHMETIC(dtype);
54
55 public:
56 //================================Typedefs=====================================
57 using const_iterator = typename std::vector<Cluster<dtype>>::const_iterator;
58
59 //=============================================================================
60 // Description:
67 ClusterMaker(const NdArray<bool>* const inXcdArrayPtr,
68 const NdArray<dtype>* const inIntensityArrayPtr,
69 uint8 inBorderWidth = 0) :
70 xcds_(inXcdArrayPtr),
71 intensities_(inIntensityArrayPtr)
72 {
73 if (xcds_->shape() != intensities_->shape())
74 {
75 THROW_INVALID_ARGUMENT_ERROR("input xcd and intensity arrays must be the same shape.");
76 }
77
78 shape_ = xcds_->shape();
79
80 // convert the NdArray of booleans to a vector of exceedances
81 for (uint32 row = 0; row < shape_.rows; ++row)
82 {
83 for (uint32 col = 0; col < shape_.cols; ++col)
84 {
85 if (xcds_->operator()(row, col))
86 {
87 const Pixel<dtype> thePixel(row, col, intensities_->operator()(row, col));
88 xcdsVec_.push_back(thePixel);
89 }
90 }
91 }
92
93 runClusterMaker();
94
95 for (uint8 i = 0; i < inBorderWidth; ++i)
96 {
97 expandClusters();
98 }
99 }
100
101 //=============================================================================
102 // Description:
107 uint32 size() noexcept
108 {
109 return static_cast<uint32>(clusters_.size());
110 }
111
112 //=============================================================================
113 // Description:
120 const Cluster<dtype>& operator[](uint32 inIndex) const noexcept
121 {
122 return clusters_[inIndex];
123 }
124
125 //=============================================================================
126 // Description:
133 [[nodiscard]] const Cluster<dtype>& at(uint32 inIndex) const
134 {
135 if (inIndex >= clusters_.size())
136 {
137 THROW_INVALID_ARGUMENT_ERROR("index exceeds cluster size.");
138 }
139 return clusters_[inIndex];
140 }
141
142 //=============================================================================
143 // Description:
148 [[nodiscard]] const_iterator begin() const noexcept
149 {
150 return clusters_.cbegin();
151 }
152
153 //=============================================================================
154 // Description:
159 [[nodiscard]] const_iterator end() const noexcept
160 {
161 return clusters_.cend();
162 }
163
164 private:
165 //==================================Attributes=================================
166 const NdArray<bool>* const xcds_{};
167 const NdArray<dtype>* const intensities_{};
168 std::vector<Pixel<dtype>> xcdsVec_{};
169
170 Shape shape_{};
171
172 std::vector<Cluster<dtype>> clusters_{};
173
174 //=============================================================================
175 // Description:
183 Pixel<dtype> makePixel(int32 inRow, int32 inCol) noexcept
184 {
185 // Make sure that on the edges after i've added or subtracted 1 from the row and col that
186 // i haven't gone over the edge
187 const uint32 row = std::min(static_cast<uint32>(std::max<int32>(inRow, 0)), shape_.rows - 1);
188 const uint32 col = std::min(static_cast<uint32>(std::max<int32>(inCol, 0)), shape_.cols - 1);
189 const dtype intensity = intensities_->operator()(row, col);
190
191 return Pixel<dtype>(row, col, intensity);
192 }
193
194 //=============================================================================
195 // Description:
202 void findNeighbors(const Pixel<dtype>& inPixel, std::set<Pixel<dtype>>& outNeighbors)
203 {
204 // using a set will auto take care of adding duplicate pixels on the edges
205
206 // the 8 surrounding neighbors
207 const auto row = static_cast<int32>(inPixel.row);
208 const auto col = static_cast<int32>(inPixel.col);
209
210 outNeighbors.insert(outNeighbors.end(), makePixel(row - 1, col - 1));
211 outNeighbors.insert(outNeighbors.end(), makePixel(row - 1, col));
212 outNeighbors.insert(outNeighbors.end(), makePixel(row - 1, col + 1));
213 outNeighbors.insert(outNeighbors.end(), makePixel(row, col - 1));
214 outNeighbors.insert(outNeighbors.end(), makePixel(row, col + 1));
215 outNeighbors.insert(outNeighbors.end(), makePixel(row + 1, col - 1));
216 outNeighbors.insert(outNeighbors.end(), makePixel(row + 1, col));
217 outNeighbors.insert(outNeighbors.end(), makePixel(row + 1, col + 1));
218 }
219
220 //=============================================================================
221 // Description:
229 void findNeighborNotXcds(const Pixel<dtype>& inPixel, std::vector<Pixel<dtype>>& outNeighbors)
230 {
231 std::set<Pixel<dtype>> neighbors;
232 findNeighbors(inPixel, neighbors);
233
234 // check if the neighboring pixels are exceedances and insert into the xcd vector
235 for (auto& pixel : neighbors)
236 {
237 if (!xcds_->operator()(pixel.row, pixel.col))
238 {
239 outNeighbors.push_back(pixel);
240 }
241 }
242 }
243
244 //=============================================================================
245 // Description:
253 void findNeighborXcds(const Pixel<dtype>& inPixel, std::vector<uint32>& outNeighbors)
254 {
255 std::set<Pixel<dtype>> neighbors;
256 findNeighbors(inPixel, neighbors);
257 std::vector<Pixel<dtype>> neighborXcds;
258
259 // check if the neighboring pixels are exceedances and insert into the xcd vector
260 for (auto& pixel : neighbors)
261 {
262 if (xcds_->operator()(pixel.row, pixel.col))
263 {
264 neighborXcds.push_back(pixel);
265 }
266 }
267
268 // loop through the neighbors and find the cooresponding index into exceedances_
269 for (auto& pixel : neighborXcds)
270 {
271 auto theExceedanceIter = std::find(xcdsVec_.begin(), xcdsVec_.end(), pixel);
272 outNeighbors.push_back(static_cast<uint32>(theExceedanceIter - xcdsVec_.begin()));
273 }
274 }
275
276 //=============================================================================
277 // Description:
280 void runClusterMaker()
281 {
282 uint32 clusterId = 0;
283
284 for (auto& currentPixel : xcdsVec_)
285 {
286 // not already visited
287 if (currentPixel.clusterId == -1)
288 {
289 Cluster<dtype> newCluster(clusterId); // a new cluster
290 currentPixel.clusterId = clusterId;
291 newCluster.addPixel(currentPixel); // assign pixel to cluster
292
293 // get the neighbors
294 std::vector<uint32> neighborIds;
295 findNeighborXcds(currentPixel, neighborIds);
296 if (neighborIds.empty())
297 {
298 clusters_.push_back(newCluster);
299 ++clusterId;
300 continue;
301 }
302
303 // loop through the neighbors
304 for (uint32 neighborsIdx = 0; neighborsIdx < neighborIds.size(); ++neighborsIdx)
305 {
306 Pixel<dtype>& currentNeighborPixel = xcdsVec_[neighborIds[neighborsIdx]];
307
308 // go to neighbors
309 std::vector<uint32> newNeighborIds;
310 findNeighborXcds(currentNeighborPixel, newNeighborIds);
311
312 // loop through the new neighbors and add them to neighbors
313 for (auto newNeighborId : newNeighborIds)
314 {
315 // not already in neighbors
316 if (std::find(neighborIds.begin(), neighborIds.end(), newNeighborId) == neighborIds.end())
317 {
318 neighborIds.push_back(newNeighborId);
319 }
320 }
321
322 // not already assigned to a cluster
323 if (currentNeighborPixel.clusterId == -1)
324 {
325 currentNeighborPixel.clusterId = clusterId;
326 newCluster.addPixel(currentNeighborPixel);
327 }
328 }
329
330 clusters_.push_back(std::move(newCluster));
331 ++clusterId;
332 }
333 }
334 }
335
336 //=============================================================================
337 // Description:
340 void expandClusters()
341 {
342 // loop through the clusters
343 for (auto& theCluster : clusters_)
344 {
345 // loop through the pixels of the cluster
346 for (auto& thePixel : theCluster)
347 {
348 std::vector<Pixel<dtype>> neighborsNotXcds;
349 findNeighborNotXcds(thePixel, neighborsNotXcds);
350
351 // loop through the neighbors and if they haven't already been added to the cluster, add them
352 for (auto& newPixel : neighborsNotXcds)
353 {
354 if (std::find(theCluster.begin(), theCluster.end(), newPixel) == theCluster.end())
355 {
356 theCluster.addPixel(newPixel);
357 }
358 }
359 }
360 }
361 }
362 };
363} // namespace nc::imageProcessing
#define THROW_INVALID_ARGUMENT_ERROR(msg)
Definition: Error.hpp:37
const Shape & shape() const noexcept
Definition: NdArrayCore.hpp:4511
uint32 rows
Definition: Core/Shape.hpp:44
uint32 cols
Definition: Core/Shape.hpp:45
Holds the information for a cluster of pixels.
Definition: Cluster.hpp:53
Clusters exceedance data into contiguous groups.
Definition: ClusterMaker.hpp:51
ClusterMaker(const NdArray< bool > *const inXcdArrayPtr, const NdArray< dtype > *const inIntensityArrayPtr, uint8 inBorderWidth=0)
Definition: ClusterMaker.hpp:67
const_iterator begin() const noexcept
Definition: ClusterMaker.hpp:148
const_iterator end() const noexcept
Definition: ClusterMaker.hpp:159
typename std::vector< Cluster< dtype > >::const_iterator const_iterator
Definition: ClusterMaker.hpp:57
const Cluster< dtype > & at(uint32 inIndex) const
Definition: ClusterMaker.hpp:133
const Cluster< dtype > & operator[](uint32 inIndex) const noexcept
Definition: ClusterMaker.hpp:120
uint32 size() noexcept
Definition: ClusterMaker.hpp:107
Holds the information for a single pixel.
Definition: Pixel.hpp:46
Definition: applyThreshold.hpp:34
InputIt find(InputIt first, InputIt last, const T &value) noexcept
Definition: StlAlgorithms.hpp:205
NdArray< dtype > min(const NdArray< dtype > &inArray, Axis inAxis=Axis::NONE)
Definition: min.hpp:44
std::int32_t int32
Definition: Types.hpp:36
std::uint8_t uint8
Definition: Types.hpp:42
std::uint32_t uint32
Definition: Types.hpp:40