LSST Applications g063fba187b+cac8b7c890,g0f08755f38+6aee506743,g1653933729+a8ce1bb630,g168dd56ebc+a8ce1bb630,g1a2382251a+b4475c5878,g1dcb35cd9c+8f9bc1652e,g20f6ffc8e0+6aee506743,g217e2c1bcf+73dee94bd0,g28da252d5a+1f19c529b9,g2bbee38e9b+3f2625acfc,g2bc492864f+3f2625acfc,g3156d2b45e+6e55a43351,g32e5bea42b+1bb94961c2,g347aa1857d+3f2625acfc,g35bb328faa+a8ce1bb630,g3a166c0a6a+3f2625acfc,g3e281a1b8c+c5dd892a6c,g3e8969e208+a8ce1bb630,g414038480c+5927e1bc1e,g41af890bb2+8a9e676b2a,g7af13505b9+809c143d88,g80478fca09+6ef8b1810f,g82479be7b0+f568feb641,g858d7b2824+6aee506743,g89c8672015+f4add4ffd5,g9125e01d80+a8ce1bb630,ga5288a1d22+2903d499ea,gb58c049af0+d64f4d3760,gc28159a63d+3f2625acfc,gcab2d0539d+b12535109e,gcf0d15dbbd+46a3f46ba9,gda6a2b7d83+46a3f46ba9,gdaeeff99f8+1711a396fd,ge79ae78c31+3f2625acfc,gef2f8181fd+0a71e47438,gf0baf85859+c1f95f4921,gfa517265be+6aee506743,gfa999e8aa5+17cd334064,w.2024.51
LSST Data Management Base Package
Loading...
Searching...
No Matches
detect_pybind11.cc
Go to the documentation of this file.
1#include <pybind11/pybind11.h>
2#include <pybind11/numpy.h>
3#include <pybind11/stl.h>
4#include <pybind11/eigen.h>
5#include <math.h>
6#include <algorithm>
7#include <stack>
8#include <queue>
9#include <vector>
10#include <utility> // For std::pair
11#include <stdexcept>
12#include <iostream>
13
14namespace py = pybind11;
15using namespace pybind11::literals;
16
17typedef Eigen::Array<int, 4, 1> Bounds;
18typedef Eigen::Matrix<bool, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor> MatrixB;
19
20
21// Create a boolean mask `footprint` for all of the pixels that are connected to the pixel
22// located at `i,j` and create the bounding box for the `footprint` in `image`.
23template <typename M>
25 const int start_i,
26 const int start_j,
27 py::EigenDRef<const M> image,
28 py::EigenDRef<MatrixB> unchecked,
29 py::EigenDRef<MatrixB> footprint,
30 Eigen::Ref<Bounds> bounds,
31 const double thresh=0
32){
34 stack.push(std::make_pair(start_i, start_j));
35
36 while (!stack.empty()) {
37 int i, j;
38 std::tie(i, j) = stack.top();
39 stack.pop();
40
41 if (!unchecked(i, j)) {
42 continue;
43 }
44 unchecked(i, j) = false;
45
46 if (image(i, j) > thresh) {
47 footprint(i, j) = true;
48
49 if (i < bounds[0]) {
50 bounds[0] = i;
51 } else if (i > bounds[1]) {
52 bounds[1] = i;
53 }
54 if (j < bounds[2]) {
55 bounds[2] = j;
56 } else if (j > bounds[3]) {
57 bounds[3] = j;
58 }
59
60 if (i > 0 && unchecked(i-1, j)) {
61 stack.push(std::make_pair(i-1, j));
62 }
63 if (i < image.rows() - 1 && unchecked(i+1, j)) {
64 stack.push(std::make_pair(i+1, j));
65 }
66 if (j > 0 && unchecked(i, j-1)) {
67 stack.push(std::make_pair(i, j-1));
68 }
69 if (j < image.cols() - 1 && unchecked(i, j+1)) {
70 stack.push(std::make_pair(i, j+1));
71 }
72 }
73 }
74}
75
76
78template <typename M>
80 py::EigenDRef<const M> image,
81 const std::vector<std::vector<int>>& centers,
82 const double thresh=0
83){
84 const int height = image.rows();
85 const int width = image.cols();
86 MatrixB footprint = MatrixB::Zero(height, width);
88
89 // Seed the queue with peaks
90 for(const auto& center : centers){
91 const int y = center[0];
92 const int x = center[1];
93 if (!footprint(y, x) && image(y, x) > thresh) {
94 footprint(y, x) = true;
95 pixel_queue.emplace(y, x);
96 }
97 }
98
99 // 4-connectivity offsets
100 const std::vector<std::pair<int, int>> offsets = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
101
102 // Flood fill
103 while (!pixel_queue.empty()) {
104 auto [i, j] = pixel_queue.front();
105 pixel_queue.pop();
106
107 for (const auto& [di, dj] : offsets) {
108 int ni = i + di;
109 int nj = j + dj;
110 if (ni >= 0 && ni < height && nj >= 0 && nj < width) {
111 if (!footprint(ni, nj) && image(ni, nj) > thresh) {
112 footprint(ni, nj) = true;
113 pixel_queue.emplace(ni, nj);
114 }
115 }
116 }
117 }
118
119 return footprint;
120}
121
122
126class Peak {
127public:
128 Peak(int y, int x, double flux){
129 _y = y;
130 _x = x;
131 _flux = flux;
132 }
133
134 int getY(){
135 return _y;
136 }
137
138 int getX(){
139 return _x;
140 }
141
142 double getFlux(){
143 return _flux;
144 }
145
146
147private:
148 int _y;
149 int _x;
150 double _flux;
151};
152
153
156 return a.getFlux() > b.getFlux();
157}
158
159
160// Get a list of peaks found in an image.
161// To make ut easier to cull peaks that are too close together
162// and ensure that every footprint has at least one peak,
163// this algorithm is meant to be run on a single footprint
164// created by `get_connected_pixels`.
165template <typename M>
167 M& image,
168 const double min_separation,
169 const double peak_thresh,
170 const int y0,
171 const int x0
172){
173 const int height = image.rows();
174 const int width = image.cols();
175
176 std::vector<Peak> peaks;
177
178 for(int i=0; i<height; i++){
179 for(int j=0; j<width; j++){
180 if(image(i, j) < peak_thresh){
181 continue;
182 }
183 if(i > 0 && image(i, j) <= image(i-1, j)){
184 continue;
185 }
186 if(i < height-1 && image(i,j) <= image(i+1, j)){
187 continue;
188 }
189 if(j > 0 && image(i, j) <= image(i, j-1)){
190 continue;
191 }
192 if(j < width-1 && image(i,j) <= image(i, j+1)){
193 continue;
194 }
195
196 if(i > 0 && j > 0 && image(i, j) <= image(i-1, j-1)){
197 continue;
198 }
199 if(i < height-1 && j < width-1 && image(i,j) <= image(i+1, j+1)){
200 continue;
201 }
202 if(i < height-1 && j > 0 && image(i, j) <= image(i+1, j-1)){
203 continue;
204 }
205 if(i > 0 && j < width-1 && image(i,j) <= image(i-1, j+1)){
206 continue;
207 }
208
209 peaks.push_back(Peak(i+y0, j+x0, static_cast<double>(image(i, j))));
210 }
211 }
212
213 if(peaks.empty()){
214 return peaks;
215 }
216
218 std::sort (peaks.begin(), peaks.end(), sortBrightness);
219
220 // Remove peaks within min_separation
221 double min_separation2 = min_separation * min_separation;
222 for (size_t i = 0; i < peaks.size() - 1; ++i) {
223 for (size_t j = i + 1; j < peaks.size();) {
224 Peak *p1 = &peaks[i];
225 Peak *p2 = &peaks[j];
226 double dy = p1->getY() - p2->getY();
227 double dx = p1->getX() - p2->getX();
228 double separation2 = dy*dy + dx*dx;
229 if (separation2 < min_separation2) {
230 peaks.erase(peaks.begin() + j);
231 } else {
232 ++j;
233 }
234 }
235 }
236 return peaks;
237}
238
239
240// A detected footprint
242public:
244 _data = footprint;
245 this->peaks = peaks;
246 _bounds = bounds;
247 }
248
250 return _data;
251 }
252
254
256 return _bounds;
257 }
258
259private:
260 MatrixB _data;
261 Bounds _bounds;
262};
263
264
265template <typename M>
267 py::EigenDRef<M> image,
268 py::EigenDRef<MatrixB> footprint
269){
270 const int height = image.rows();
271 const int width = image.cols();
272
273 for(int i=0; i<height; i++){
274 for(int j=0; j<width; j++){
275 if(!footprint(i,j)){
276 image(i,j) = 0;
277 }
278 }
279 }
280}
281
296template <typename M, typename P>
298 py::EigenDRef<const M> image,
299 const double min_separation,
300 const int min_area,
301 const double peak_thresh,
302 const double footprint_thresh,
303 const bool find_peaks=true,
304 const int y0=0,
305 const int x0=0
306){
307 const int height = image.rows();
308 const int width = image.cols();
309
310 std::vector<Footprint> footprints;
311 MatrixB unchecked = MatrixB::Ones(height, width);
312 MatrixB footprint = MatrixB::Zero(height, width);
313
314 for(int i=0; i<height; i++){
315 for(int j=0; j<width; j++){
316 Bounds bounds; bounds << i, i, j, j;
317 get_connected_pixels(i, j, image, unchecked, footprint, bounds, footprint_thresh);
318 int subHeight = bounds[1]-bounds[0]+1;
319 int subWidth = bounds[3]-bounds[2]+1;
320 if(subHeight * subWidth > min_area){
321 MatrixB subFootprint = footprint.block(bounds[0], bounds[2], subHeight, subWidth);
322 int area = subFootprint.count();
323 if(area >= min_area){
324 M patch = image.block(bounds[0], bounds[2], subHeight, subWidth);
325 maskImage<M>(patch, subFootprint);
326 std::vector<Peak> _peaks;
327 if(find_peaks){
328 _peaks = get_peaks(
329 patch,
330 min_separation,
331 peak_thresh,
332 bounds[0] + y0,
333 bounds[2] + x0
334 );
335 }
336 // Only add footprints that have at least one peak above the
337 // minimum peak_thresh.
338 if(!_peaks.empty() || !find_peaks){
339 Bounds trueBounds; trueBounds << bounds[0] + y0,
340 bounds[1] + y0, bounds[2] + x0, bounds[3] + x0;
341 footprints.push_back(Footprint(subFootprint, _peaks, trueBounds));
342 }
343 }
344 }
345 footprint.block(bounds[0], bounds[2], subHeight, subWidth) = MatrixB::Zero(subHeight, subWidth);
346 }
347 }
348 return footprints;
349}
350
351
352
353PYBIND11_MODULE(detect_pybind11, mod) {
354 typedef Eigen::Matrix<float, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor> MatrixF;
355 typedef Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor> MatrixD;
356
357 mod.doc() = "Fast detection algorithms implemented in C++";
358
359 mod.def("get_connected_pixels", &get_connected_pixels<MatrixB>,
360 "Create a boolean mask for pixels that are connected");
361 mod.def("get_connected_pixels", &get_connected_pixels<MatrixF>,
362 "Create a boolean mask for pixels that are connected");
363 mod.def("get_connected_pixels", &get_connected_pixels<MatrixD>,
364 "Create a boolean mask for pixels that are connected");
365
366 mod.def("get_connected_multipeak", &get_connected_multipeak<MatrixB>,
367 "Trim pixels not conencted to a center from a list of centers");
368 mod.def("get_connected_multipeak", &get_connected_multipeak<MatrixF>,
369 "Trim pixels not conencted to a center from a list of centers");
370 mod.def("get_connected_multipeak", &get_connected_multipeak<MatrixD>,
371 "Trim pixels not conencted to a center from a list of centers");
372
373 mod.def("get_peaks", &get_peaks<MatrixF>,
374 "Get a list of peaks in a footprint created by get_connected_pixels");
375 mod.def("get_peaks", &get_peaks<MatrixD>,
376 "Get a list of peaks in a footprint created by get_connected_pixels");
377
378 mod.def("get_footprints", &get_footprints<MatrixF, float>,
379 "Create a list of all of the footprints in an image, with their peaks"
380 "image"_a, "min_separation"_a, "min_area"_a, "peak_thresh"_a, "footprint_thresh"_a,
381 "find_peaks"_a=true, "y0"_a=0, "x0"_a=0);
382 mod.def("get_footprints", &get_footprints<MatrixD, double>,
383 "Create a list of all of the footprints in an image, with their peaks"
384 "image"_a, "min_separation"_a, "min_area"_a, "peak_thresh"_a, "footprint_thresh"_a,
385 "find_peaks"_a=true, "y0"_a=0, "x0"_a=0);
386
387 py::class_<Footprint>(mod, "Footprint")
388 .def(py::init<MatrixB, std::vector<Peak>, Bounds>(),
389 "footprint"_a, "peaks"_a, "bounds"_a)
390 .def_property_readonly("data", &Footprint::getFootprint)
391 .def_readwrite("peaks", &Footprint::peaks)
392 .def_property_readonly("bounds", &Footprint::getBounds);
393
394 py::class_<Peak>(mod, "Peak")
395 .def(py::init<int, int, double>(),
396 "y"_a, "x"_a, "flux"_a)
397 .def_property_readonly("y", &Peak::getY)
398 .def_property_readonly("x", &Peak::getX)
399 .def_property_readonly("flux", &Peak::getFlux);
400}
afw::table::Key< afw::table::Array< ImagePixelT > > image
int y
Definition SpanSet.cc:48
table::Key< int > b
T begin(T... args)
std::vector< Peak > peaks
Bounds getBounds()
Footprint(MatrixB footprint, std::vector< Peak > peaks, Bounds bounds)
MatrixB getFootprint()
A Peak in a Footprint This class is meant to keep track of both the position and flux at the location...
Peak(int y, int x, double flux)
double getFlux()
bool sortBrightness(Peak a, Peak b)
Sort two peaks, placing the brightest peak first.
Eigen::Array< int, 4, 1 > Bounds
PYBIND11_MODULE(detect_pybind11, mod)
void get_connected_pixels(const int start_i, const int start_j, py::EigenDRef< const M > image, py::EigenDRef< MatrixB > unchecked, py::EigenDRef< MatrixB > footprint, Eigen::Ref< Bounds > bounds, const double thresh=0)
std::vector< Peak > get_peaks(M &image, const double min_separation, const double peak_thresh, const int y0, const int x0)
MatrixB get_connected_multipeak(py::EigenDRef< const M > image, const std::vector< std::vector< int > > &centers, const double thresh=0)
Proximal operator to trim pixels not connected to one of the source centers.
Eigen::Matrix< bool, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor > MatrixB
std::vector< Footprint > get_footprints(py::EigenDRef< const M > image, const double min_separation, const int min_area, const double peak_thresh, const double footprint_thresh, const bool find_peaks=true, const int y0=0, const int x0=0)
Get all footprints in an image.
void maskImage(py::EigenDRef< M > image, py::EigenDRef< MatrixB > footprint)
T emplace(T... args)
T empty(T... args)
T end(T... args)
T erase(T... args)
T front(T... args)
T make_pair(T... args)
T pop(T... args)
T push_back(T... args)
T push(T... args)
T size(T... args)
T sort(T... args)
T tie(T... args)
T top(T... args)