LSST Applications g04e9c324dd+8c5ae1fdc5,g134cb467dc+1b3060144d,g18429d2f64+f642bf4753,g199a45376c+0ba108daf9,g1fd858c14a+2dcf163641,g262e1987ae+7b8c96d2ca,g29ae962dfc+3bd6ecb08a,g2cef7863aa+aef1011c0b,g35bb328faa+8c5ae1fdc5,g3fd5ace14f+53e1a9e7c5,g4595892280+fef73a337f,g47891489e3+2efcf17695,g4d44eb3520+642b70b07e,g53246c7159+8c5ae1fdc5,g67b6fd64d1+2efcf17695,g67fd3c3899+b70e05ef52,g74acd417e5+317eb4c7d4,g786e29fd12+668abc6043,g87389fa792+8856018cbb,g89139ef638+2efcf17695,g8d7436a09f+3be3c13596,g8ea07a8fe4+9f5ccc88ac,g90f42f885a+a4e7b16d9b,g97be763408+ad77d7208f,g9dd6db0277+b70e05ef52,ga681d05dcb+a3f46e7fff,gabf8522325+735880ea63,gac2eed3f23+2efcf17695,gb89ab40317+2efcf17695,gbf99507273+8c5ae1fdc5,gd8ff7fe66e+b70e05ef52,gdab6d2f7ff+317eb4c7d4,gdc713202bf+b70e05ef52,gdfd2d52018+b10e285e0f,ge365c994fd+310e8507c4,ge410e46f29+2efcf17695,geaed405ab2+562b3308c0,gffca2db377+8c5ae1fdc5,w.2025.35
LSST Data Management Base Package
Loading...
Searching...
No Matches
operators_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 <queue>
8
9namespace py = pybind11;
10
11typedef Eigen::Array<int, Eigen::Dynamic, 1> IndexVector;
12typedef Eigen::Array<int, 4, 1> Bounds;
13typedef Eigen::Matrix<bool, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor> MatrixB;
14
15template <typename T, typename M>
17 Eigen::Ref<const IndexVector> coord_y,
18 Eigen::Ref<const IndexVector> coord_x,
19 std::vector<M>& weights,
20 Eigen::Ref<M, 0, Eigen::Stride<Eigen::Dynamic, Eigen::Dynamic>> image
21){
22 const int height = image.rows();
23 const int width = image.cols();
24
25 for(int n=0; n<coord_x.size(); n++){
26 int px = coord_x[n];
27 int py = coord_y[n];
28
29 // Check bounds for 3x3 neighborhood access
30 if (py < 0 || py + 2 >= height || px < 0 || px + 2 >= width) {
31 throw std::out_of_range("Coordinate (" + std::to_string(py) + ", " +
32 std::to_string(px) + ") requires 3x3 neighborhood that exceeds image bounds [0, " +
33 std::to_string(height) + ") x [0, " + std::to_string(width) + ")");
34 }
35
36 T ref_flux = 0;
37 for(int i=0; i<3; i++){
38 for(int j=0; j<3; j++){
39 int weight_index = 3*i + j;
40 ref_flux += image(py + i, px + j) * weights[weight_index](py, px);
41 }
42 }
43 image(py + 1, px + 1) = std::min(image(py+1, px+1), ref_flux);
44 }
45}
46
47
48template <typename T, typename M, typename V>
50 // Fast implementation of weighted monotonicity constraint
51 Eigen::Ref<V> flat_img,
52 Eigen::Ref<const M> weights,
53 Eigen::Ref<const IndexVector> offsets,
54 Eigen::Ref<const IndexVector> dist_idx,
55 T const &min_gradient
56){
57 // Start at the center of the image and set each pixel to the minimum
58 // between itself and its reference pixel (which is closer to the peak)
59 for(int d=0; d<dist_idx.size(); d++){
60 int didx = dist_idx(d);
61 T ref_flux = 0;
62 for(int i=0; i<offsets.size(); i++){
63 if(weights(i,didx)>0){
64 int nidx = offsets[i] + didx;
65 ref_flux += flat_img(nidx) * weights(i, didx);
66 }
67 }
68 flat_img(didx) = std::min(flat_img(didx), ref_flux*(1-min_gradient));
69 }
70}
71
72// Apply a 2D filter to an image
73template <typename M, typename V>
75 Eigen::Ref<const M> image,
76 Eigen::Ref<const V> values,
77 Eigen::Ref<const IndexVector> y_start,
78 Eigen::Ref<const IndexVector> y_end,
79 Eigen::Ref<const IndexVector> x_start,
80 Eigen::Ref<const IndexVector> x_end,
81 Eigen::Ref<M, 0, Eigen::Stride<Eigen::Dynamic, Eigen::Dynamic>> result
82){
83 result.setZero();
84 for(int n=0; n<values.size(); n++){
85 int rows = image.rows()-y_start(n)-y_end(n);
86 int cols = image.cols()-x_start(n)-x_end(n);
87 result.block(y_start(n), x_start(n), rows, cols) +=
88 values(n) * image.block(y_end(n), x_end(n), rows, cols);
89 }
90}
91
92
93// Create a boolean mask for all pixels that are monotonic from at least one neighbor,
94// and create a boolean map of "orphans" that are non-monotonic in all directions.
95template <typename M>
97 const int start_i,
98 const int start_j,
99 Eigen::Ref<M, 0, Eigen::Stride<Eigen::Dynamic, Eigen::Dynamic>> image,
100 Eigen::Ref<MatrixB, 0, Eigen::Stride<Eigen::Dynamic, Eigen::Dynamic>> unchecked,
101 Eigen::Ref<MatrixB, 0, Eigen::Stride<Eigen::Dynamic, Eigen::Dynamic>> orphans,
102 const double variance,
103 Eigen::Ref<Bounds, 0, Eigen::Stride<4, 1>> bounds,
104 const double thresh=0
105) {
106 // Create a queue to store pixels that need to be processed
108 pixel_queue.push({start_i, start_j});
109
110 // Define directions: down, up, left, right
111 const int di[] = {-1, 1, 0, 0};
112 const int dj[] = {0, 0, -1, 1};
113
114 // Define image dimensions
115 const int nrows = image.rows();
116 const int ncols = image.cols();
117
118 while (!pixel_queue.empty()) {
119 auto [i, j] = pixel_queue.front();
120 pixel_queue.pop();
121
122 const auto image_i_j_var = image(i, j) + variance;
123
124 // Check all four directions
125 for (int dir = 0; dir < 4; dir++) {
126 int ni = i + di[dir];
127 int nj = j + dj[dir];
128
129 // Check bounds
130 if ((ni < 0) || (ni >= nrows) || (nj < 0) || (nj >= ncols)) {
131 continue;
132 }
133
134 const auto image_ni_nj = image(ni, nj);
135
136 // Check if pixel needs to be processed
137 if (!unchecked(ni, nj)) {
138 continue;
139 }
140
141 // Check monotonicity condition
142 if (image_ni_nj <= image_i_j_var && (image_ni_nj > thresh)) {
143 // Mark as checked and not orphaned
144 unchecked(ni, nj) = false;
145 orphans(ni, nj) = false;
146
147 // Update bounds
148 if (dir == 0 && ni < bounds(0)) { // down
149 bounds(0) = ni;
150 } else if (dir == 1 && ni > bounds(1)) { // up
151 bounds(1) = ni;
152 } else if (dir == 2 && nj < bounds(2)) { // left
153 bounds(2) = nj;
154 } else if (dir == 3 && nj > bounds(3)) { // right
155 bounds(3) = nj;
156 }
157
158 // Add to queue for processing
159 pixel_queue.push({ni, nj});
160 } else {
161 orphans(ni, nj) = true;
162 }
163 }
164 }
165}
166
167// Fill in orphans generated by get_valid_monotonic_pixels
168template <typename M, typename P>
170 Eigen::Ref<const IndexVector> row_indices,
171 Eigen::Ref<const IndexVector> column_indices,
172 Eigen::Ref<MatrixB, 0, Eigen::Stride<Eigen::Dynamic, Eigen::Dynamic>> unchecked,
173 Eigen::Ref<M, 0, Eigen::Stride<Eigen::Dynamic, Eigen::Dynamic>> model,
174 Eigen::Ref<MatrixB, 0, Eigen::Stride<Eigen::Dynamic, Eigen::Dynamic>> orphans,
175 const double variance,
176 bool recursive,
177 Eigen::Ref<Bounds, 0, Eigen::Stride<4, 1>> bounds
178){
179 const int nrows = model.rows();
180 const int ncols = model.cols();
181
182 for(int n=0; n<row_indices.size(); n++){
183 int i = row_indices(n);
184 int j = column_indices(n);
185
186 // Add bounds check for the current pixel
187 if (i < 0 || i >= nrows || j < 0 || j >= ncols) {
188 throw std::out_of_range("Pixel coordinates (" + std::to_string(i) + ", " +
189 std::to_string(j) + ") are out of model bounds [0, " +
190 std::to_string(nrows) + ") x [0, " + std::to_string(ncols) + ")");
191 }
192
193 P neighborTotal = 0.0;
194 int validNeighbors = 0;
195 bool uncheckedNeighbors = false;
196
197 if(!unchecked(i,j)){
198 // This pixel has already been updated
199 continue;
200 }
201 // Even if this orphan cannot be updated, we remove it from unchecked
202 // so that it isn't attempted again in future iterations.
203 unchecked(i,j) = false;
204
205 // Check all of the neighboring pixels with negative gradients and
206 // use the maximum value for the interpolation
207 if(i < nrows-2 && model(i+2,j) > model(i+1,j)){
208 if(unchecked(i+2, j) || unchecked(i+1, j)){
209 uncheckedNeighbors = true;
210 } else {
211 P grad = model(i+2,j) - model(i+1,j);
212 neighborTotal += model(i+1,j)-grad;
213 validNeighbors += 1;
214 }
215 }
216 if(i >= 2 && model(i-2,j) > model(i-1,j)){
217 if(unchecked(i-2, j) || unchecked(i-1, j)){
218 uncheckedNeighbors = true;
219 } else {
220 P grad = model(i-2,j) - model(i-1,j);
221 neighborTotal += model(i-1,j)-grad;
222 validNeighbors += 1;
223 }
224 }
225 if(j < ncols-2 && model(i,j+2) > model(i,j+1)){
226 if(unchecked(i,j+2) || unchecked(i,j+1)){
227 uncheckedNeighbors = true;
228 } else {
229 P grad = model(i,j+2) - model(i,j+1);
230 neighborTotal += model(i,j+1)-grad;
231 validNeighbors += 1;
232 }
233 }
234 if(j >= 2 && model(i,j-2) > model(i,j-1)){
235 if(unchecked(i, j-2) || unchecked(i,j-1)){
236 uncheckedNeighbors = true;
237 } else {
238 P grad = model(i,j-2) - model(i,j-1);
239 neighborTotal += model(i,j-1)-grad;
240 validNeighbors += 1;
241 }
242 }
243 // If the non-monotonic pixel was updated then update the
244 // model with the interpolated value and search for more monotonic pixels
245 if(neighborTotal > 0){
246 // Update the model and orphan status _before_ checking neighbors
247 model(i,j) = neighborTotal / validNeighbors;
248 orphans(i,j) = false;
249
250 if(i < bounds(0)){
251 bounds(0) = i;
252 } else if(i > bounds(1)){
253 bounds(1) = i;
254 }
255 if(j < bounds(2)){
256 bounds(2) = j;
257 } else if(j > bounds(3)){
258 bounds(3) = j;
259 }
260 if(recursive){
261 get_valid_monotonic_pixels(i, j, model, unchecked, orphans, variance, bounds);
262 } else {
263 if(i > 0 && unchecked(i-1,j)){
264 orphans(i-1,j) = true;
265 }
266 if(i < nrows-1 && unchecked(i+1,j)){
267 orphans(i+1,j) = true;
268 }
269 if(j > 0 && unchecked(i,j-1)){
270 orphans(i,j-1) = true;
271 }
272 if(j < ncols-1 && unchecked(i,j+1)){
273 orphans(i,j+1) = true;
274 }
275 }
276 } else if(uncheckedNeighbors){
277 unchecked(i,j) = false;
278 } else {
279 // This is still an orphan, but we checked it so it won't be iterated on again.
280 orphans(i,j) = true;
281 model(i,j) = 0;
282 }
283 }
284}
285
286PYBIND11_MODULE(operators_pybind11, mod)
287{
288 mod.doc() = "operators_pybind11", "Fast proximal operators";
289
290 typedef Eigen::Matrix<float, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor> MatrixF;
291 typedef Eigen::Matrix<float, Eigen::Dynamic, 1> VectorF;
292 typedef Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor> MatrixD;
293 typedef Eigen::Matrix<double, Eigen::Dynamic, 1> VectorD;
294
295 mod.def("new_monotonicity", &new_monotonicity<float, MatrixF>, "Weighted Monotonic Proximal Operator");
296 mod.def("new_monotonicity", &new_monotonicity<double, MatrixD>, "Weighted Monotonic Proximal Operator");
297
298 mod.def("prox_weighted_monotonic", &prox_weighted_monotonic<float, MatrixF, VectorF>,
299 "Weighted Monotonic Proximal Operator");
300 mod.def("prox_weighted_monotonic", &prox_weighted_monotonic<double, MatrixD, VectorD>,
301 "Weighted Monotonic Proximal Operator");
302
303 mod.def("apply_filter", &apply_filter<MatrixF, VectorF>, "Apply a filter to a 2D image");
304 mod.def("apply_filter", &apply_filter<MatrixD, VectorD>, "Apply a filter to a 2D image");
305
306 mod.def("get_valid_monotonic_pixels", &get_valid_monotonic_pixels<MatrixF>,
307 "Create a boolean mask for pixels that are monotonic from the center along some path");
308 mod.def("get_valid_monotonic_pixels", &get_valid_monotonic_pixels<MatrixD>,
309 "Create a boolean mask for pixels that are monotonic from the center along some path");
310
311 mod.def("linear_interpolate_invalid_pixels", &linear_interpolate_invalid_pixels<MatrixF, float>,
312 "Fill in non-monotonic pixels by interpolating based on the gradients of its neighbors");
313 mod.def("linear_interpolate_invalid_pixels", &linear_interpolate_invalid_pixels<MatrixD, double>,
314 "Fill in non-monotonic pixels by interpolating based on the gradients of its neighbors");
315}
std::vector< std::ptrdiff_t > IndexVector
Definition Eigenstuff.h:38
Eigen::Array< int, 4, 1 > Bounds
Eigen::Matrix< bool, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor > MatrixB
T empty(T... args)
T front(T... args)
T min(T... args)
PYBIND11_MODULE(operators_pybind11, mod)
void new_monotonicity(Eigen::Ref< const IndexVector > coord_y, Eigen::Ref< const IndexVector > coord_x, std::vector< M > &weights, Eigen::Ref< M, 0, Eigen::Stride< Eigen::Dynamic, Eigen::Dynamic > > image)
void get_valid_monotonic_pixels(const int start_i, const int start_j, Eigen::Ref< M, 0, Eigen::Stride< Eigen::Dynamic, Eigen::Dynamic > > image, Eigen::Ref< MatrixB, 0, Eigen::Stride< Eigen::Dynamic, Eigen::Dynamic > > unchecked, Eigen::Ref< MatrixB, 0, Eigen::Stride< Eigen::Dynamic, Eigen::Dynamic > > orphans, const double variance, Eigen::Ref< Bounds, 0, Eigen::Stride< 4, 1 > > bounds, const double thresh=0)
void prox_weighted_monotonic(Eigen::Ref< V > flat_img, Eigen::Ref< const M > weights, Eigen::Ref< const IndexVector > offsets, Eigen::Ref< const IndexVector > dist_idx, T const &min_gradient)
void apply_filter(Eigen::Ref< const M > image, Eigen::Ref< const V > values, Eigen::Ref< const IndexVector > y_start, Eigen::Ref< const IndexVector > y_end, Eigen::Ref< const IndexVector > x_start, Eigen::Ref< const IndexVector > x_end, Eigen::Ref< M, 0, Eigen::Stride< Eigen::Dynamic, Eigen::Dynamic > > result)
void linear_interpolate_invalid_pixels(Eigen::Ref< const IndexVector > row_indices, Eigen::Ref< const IndexVector > column_indices, Eigen::Ref< MatrixB, 0, Eigen::Stride< Eigen::Dynamic, Eigen::Dynamic > > unchecked, Eigen::Ref< M, 0, Eigen::Stride< Eigen::Dynamic, Eigen::Dynamic > > model, Eigen::Ref< MatrixB, 0, Eigen::Stride< Eigen::Dynamic, Eigen::Dynamic > > orphans, const double variance, bool recursive, Eigen::Ref< Bounds, 0, Eigen::Stride< 4, 1 > > bounds)
T pop(T... args)
T push(T... args)
T to_string(T... args)