Loading [MathJax]/extensions/tex2jax.js
LSST Applications g04dff08e69+fafbcb10e2,g0d33ba9806+e09a96fa4e,g0fba68d861+cc01b48236,g1e78f5e6d3+fb95f9dda6,g1ec0fe41b4+f536777771,g1fd858c14a+ae46bc2a71,g35bb328faa+fcb1d3bbc8,g4af146b050+dd94f3aad7,g4d2262a081+7ee6f976aa,g53246c7159+fcb1d3bbc8,g5a012ec0e7+b20b785ecb,g60b5630c4e+e09a96fa4e,g6273192d42+bf8cfc5e62,g67b6fd64d1+4086c0989b,g78460c75b0+2f9a1b4bcd,g786e29fd12+cf7ec2a62a,g7b71ed6315+fcb1d3bbc8,g87b7deb4dc+831c06c8fc,g8852436030+54b48a5987,g89139ef638+4086c0989b,g9125e01d80+fcb1d3bbc8,g94187f82dc+e09a96fa4e,g989de1cb63+4086c0989b,g9f33ca652e+64be6d9d51,g9f7030ddb1+d11454dffd,ga2b97cdc51+e09a96fa4e,gabe3b4be73+1e0a283bba,gabf8522325+fa80ff7197,gb1101e3267+23605820ec,gb58c049af0+f03b321e39,gb89ab40317+4086c0989b,gcf25f946ba+54b48a5987,gd6cbbdb0b4+af3c3595f5,gd9a9a58781+fcb1d3bbc8,gde0f65d7ad+15f2daff9d,ge278dab8ac+d65b3c2b70,ge410e46f29+4086c0989b,gf67bdafdda+4086c0989b,v29.0.0.rc5
LSST Data Management Base Package
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Modules Pages
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 for(int n=0; n<coord_x.size(); n++){
23 int px = coord_x[n];
24 int py = coord_y[n];
25
26 T ref_flux = 0;
27 for(int i=0; i<3; i++){
28 for(int j=0; j<3; j++){
29 int weight_index = 3*i + j;
30 ref_flux += image(py + i, px + j) * weights[weight_index](py, px);
31 }
32 }
33 image(py + 1, px + 1) = std::min(image(py+1, px+1), ref_flux);
34 }
35}
36
37template <typename T, typename M, typename V>
39 // Fast implementation of weighted monotonicity constraint
40 Eigen::Ref<V> flat_img,
41 Eigen::Ref<const M> weights,
42 Eigen::Ref<const IndexVector> offsets,
43 Eigen::Ref<const IndexVector> dist_idx,
44 T const &min_gradient
45){
46 // Start at the center of the image and set each pixel to the minimum
47 // between itself and its reference pixel (which is closer to the peak)
48 for(int d=0; d<dist_idx.size(); d++){
49 int didx = dist_idx(d);
50 T ref_flux = 0;
51 for(int i=0; i<offsets.size(); i++){
52 if(weights(i,didx)>0){
53 int nidx = offsets[i] + didx;
54 ref_flux += flat_img(nidx) * weights(i, didx);
55 }
56 }
57 flat_img(didx) = std::min(flat_img(didx), ref_flux*(1-min_gradient));
58 }
59}
60
61// Apply a 2D filter to an image
62template <typename M, typename V>
64 Eigen::Ref<const M> image,
65 Eigen::Ref<const V> values,
66 Eigen::Ref<const IndexVector> y_start,
67 Eigen::Ref<const IndexVector> y_end,
68 Eigen::Ref<const IndexVector> x_start,
69 Eigen::Ref<const IndexVector> x_end,
70 Eigen::Ref<M, 0, Eigen::Stride<Eigen::Dynamic, Eigen::Dynamic>> result
71){
72 result.setZero();
73 for(int n=0; n<values.size(); n++){
74 int rows = image.rows()-y_start(n)-y_end(n);
75 int cols = image.cols()-x_start(n)-x_end(n);
76 result.block(y_start(n), x_start(n), rows, cols) +=
77 values(n) * image.block(y_end(n), x_end(n), rows, cols);
78 }
79}
80
81
82// Create a boolean mask for all pixels that are monotonic from at least one neighbor,
83// and create a boolean map of "orphans" that are non-monotonic in all directions.
84template <typename M>
86 const int start_i,
87 const int start_j,
88 Eigen::Ref<M, 0, Eigen::Stride<Eigen::Dynamic, Eigen::Dynamic>> image,
89 Eigen::Ref<MatrixB, 0, Eigen::Stride<Eigen::Dynamic, Eigen::Dynamic>> unchecked,
90 Eigen::Ref<MatrixB, 0, Eigen::Stride<Eigen::Dynamic, Eigen::Dynamic>> orphans,
91 const double variance,
92 Eigen::Ref<Bounds, 0, Eigen::Stride<4, 1>> bounds,
93 const double thresh=0
94) {
95 // Create a queue to store pixels that need to be processed
97 pixel_queue.push({start_i, start_j});
98
99 // Define directions: down, up, left, right
100 const int di[] = {-1, 1, 0, 0};
101 const int dj[] = {0, 0, -1, 1};
102
103 // Define image dimensions
104 const int nrows = image.rows();
105 const int ncols = image.cols();
106
107 while (!pixel_queue.empty()) {
108 auto [i, j] = pixel_queue.front();
109 pixel_queue.pop();
110
111 const auto image_i_j_var = image(i, j) + variance;
112
113 // Check all four directions
114 for (int dir = 0; dir < 4; dir++) {
115 int ni = i + di[dir];
116 int nj = j + dj[dir];
117
118 // Check bounds
119 if ((ni < 0) || (ni >= nrows) || (nj < 0) || (nj >= ncols)) {
120 continue;
121 }
122
123 const auto image_ni_nj = image(ni, nj);
124
125 // Check if pixel needs to be processed
126 if (!unchecked(ni, nj)) {
127 continue;
128 }
129
130 // Check monotonicity condition
131 if (image_ni_nj <= image_i_j_var && (image_ni_nj > thresh)) {
132 // Mark as checked and not orphaned
133 unchecked(ni, nj) = false;
134 orphans(ni, nj) = false;
135
136 // Update bounds
137 if (dir == 0 && ni < bounds(0)) { // down
138 bounds(0) = ni;
139 } else if (dir == 1 && ni > bounds(1)) { // up
140 bounds(1) = ni;
141 } else if (dir == 2 && nj < bounds(2)) { // left
142 bounds(2) = nj;
143 } else if (dir == 3 && nj > bounds(3)) { // right
144 bounds(3) = nj;
145 }
146
147 // Add to queue for processing
148 pixel_queue.push({ni, nj});
149 } else {
150 orphans(ni, nj) = true;
151 }
152 }
153 }
154}
155
156// Fill in orphans generated by get_valid_monotonic_pixels
157template <typename M, typename P>
159 Eigen::Ref<const IndexVector> row_indices,
160 Eigen::Ref<const IndexVector> column_indices,
161 Eigen::Ref<MatrixB, 0, Eigen::Stride<Eigen::Dynamic, Eigen::Dynamic>> unchecked,
162 Eigen::Ref<M, 0, Eigen::Stride<Eigen::Dynamic, Eigen::Dynamic>> model,
163 Eigen::Ref<MatrixB, 0, Eigen::Stride<Eigen::Dynamic, Eigen::Dynamic>> orphans,
164 const double variance,
165 bool recursive,
166 Eigen::Ref<Bounds, 0, Eigen::Stride<4, 1>> bounds
167){
168 for(int n=0; n<row_indices.size(); n++){
169 int i = row_indices(n);
170 int j = column_indices(n);
171 P neighborTotal = 0.0;
172 int validNeighbors = 0;
173 bool uncheckedNeighbors = false;
174
175 if(!unchecked(i,j)){
176 // This pixel has already been updated
177 continue;
178 }
179 // Even if this orphan cannot be updated, we remove it from unchecked
180 // so that it isn't attempted again in future iterations.
181 unchecked(i,j) = false;
182
183 // Check all of the neighboring pixels with negative gradients and
184 // use the maximum value for the interpolation
185 if(i < model.rows()-2 && model(i+2,j) > model(i+1,j)){
186 if(unchecked(i+2, j) || unchecked(i+1, j)){
187 uncheckedNeighbors = true;
188 } else {
189 P grad = model(i+2,j) - model(i+1,j);
190 neighborTotal += model(i+1,j)-grad;
191 validNeighbors += 1;
192 }
193 }
194 if(i > 2 && model(i-2,j) > model(i-1,j)){
195 if(unchecked(i-2, j) || unchecked(i-1, j)){
196 uncheckedNeighbors = true;
197 } else {
198 P grad = model(i-2,j) - model(i-1,j);
199 neighborTotal += model(i-1,j)-grad;
200 validNeighbors += 1;
201 }
202 }
203 if(j < model.cols()-2 && model(i,j+2) > model(i,j+1)){
204 if(unchecked(i,j+2), unchecked(i,j+1)){
205 uncheckedNeighbors = true;
206 } else {
207 P grad = model(i,j+2) - model(i,j+1);
208 neighborTotal += model(i,j+1)-grad;
209 validNeighbors += 1;
210 }
211 }
212 if(j > 2 && model(i,j-2) > model(i,j-1)){
213 if(unchecked(i, j-2), unchecked(i,j-1)){
214 uncheckedNeighbors = true;
215 } else {
216 P grad = model(i,j-2) - model(i,j-1);
217 neighborTotal += model(i,j-1)-grad;
218 validNeighbors += 1;
219 }
220 }
221 // If the non-monotonic pixel was updated then update the
222 // model with the interpolated value and search for more monotonic pixels
223 if(neighborTotal > 0){
224 // Update the model and orphan status _before_ checking neighbors
225 model(i,j) = neighborTotal / validNeighbors;
226 orphans(i,j) = false;
227
228 if(i < bounds(0)){
229 bounds(0) = i;
230 } else if(i > bounds(1)){
231 bounds(1) = i;
232 }
233 if(j < bounds(2)){
234 bounds(2) = j;
235 } else if(j > bounds(3)){
236 bounds(3) = j;
237 }
238 if(recursive){
239 get_valid_monotonic_pixels(i, j, model, unchecked, orphans, variance, bounds);
240 } else {
241 if(unchecked(i-1,j)){
242 orphans(i-1,j) = true;
243 }
244 if(unchecked(i+1,j)){
245 orphans(i+1,j) = true;
246 }
247 if(unchecked(i,j-1)){
248 orphans(i,j-1) = true;
249 }
250 if(unchecked(i,j+1)){
251 orphans(i,j+1) = true;
252 }
253 }
254 } else if(uncheckedNeighbors){
255 unchecked(i,j) = false;
256 } else {
257 // This is still an orphan, but we checked it so it won't be iterated on again.
258 orphans(i,j) = true;
259 model(i,j) = 0;
260 }
261 }
262}
263
264PYBIND11_MODULE(operators_pybind11, mod)
265{
266 mod.doc() = "operators_pybind11", "Fast proximal operators";
267
268 typedef Eigen::Matrix<float, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor> MatrixF;
269 typedef Eigen::Matrix<float, Eigen::Dynamic, 1> VectorF;
270 typedef Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor> MatrixD;
271 typedef Eigen::Matrix<double, Eigen::Dynamic, 1> VectorD;
272
273 mod.def("new_monotonicity", &new_monotonicity<float, MatrixF>, "Weighted Monotonic Proximal Operator");
274 mod.def("new_monotonicity", &new_monotonicity<double, MatrixD>, "Weighted Monotonic Proximal Operator");
275
276 mod.def("prox_weighted_monotonic", &prox_weighted_monotonic<float, MatrixF, VectorF>,
277 "Weighted Monotonic Proximal Operator");
278 mod.def("prox_weighted_monotonic", &prox_weighted_monotonic<double, MatrixD, VectorD>,
279 "Weighted Monotonic Proximal Operator");
280
281 mod.def("apply_filter", &apply_filter<MatrixF, VectorF>, "Apply a filter to a 2D image");
282 mod.def("apply_filter", &apply_filter<MatrixD, VectorD>, "Apply a filter to a 2D image");
283
284 mod.def("get_valid_monotonic_pixels", &get_valid_monotonic_pixels<MatrixF>,
285 "Create a boolean mask for pixels that are monotonic from the center along some path");
286 mod.def("get_valid_monotonic_pixels", &get_valid_monotonic_pixels<MatrixD>,
287 "Create a boolean mask for pixels that are monotonic from the center along some path");
288
289 mod.def("linear_interpolate_invalid_pixels", &linear_interpolate_invalid_pixels<MatrixF, float>,
290 "Fill in non-monotonic pixels by interpolating based on the gradients of its neighbors");
291 mod.def("linear_interpolate_invalid_pixels", &linear_interpolate_invalid_pixels<MatrixD, double>,
292 "Fill in non-monotonic pixels by interpolating based on the gradients of its neighbors");
293}
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 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 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 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)