LSST Applications g0f08755f38+82efc23009,g12f32b3c4e+e7bdf1200e,g1653933729+a8ce1bb630,g1a0ca8cf93+50eff2b06f,g28da252d5a+52db39f6a5,g2bbee38e9b+37c5a29d61,g2bc492864f+37c5a29d61,g2cdde0e794+c05ff076ad,g3156d2b45e+41e33cbcdc,g347aa1857d+37c5a29d61,g35bb328faa+a8ce1bb630,g3a166c0a6a+37c5a29d61,g3e281a1b8c+fb992f5633,g414038480c+7f03dfc1b0,g41af890bb2+11b950c980,g5fbc88fb19+17cd334064,g6b1c1869cb+12dd639c9a,g781aacb6e4+a8ce1bb630,g80478fca09+72e9651da0,g82479be7b0+04c31367b4,g858d7b2824+82efc23009,g9125e01d80+a8ce1bb630,g9726552aa6+8047e3811d,ga5288a1d22+e532dc0a0b,gae0086650b+a8ce1bb630,gb58c049af0+d64f4d3760,gc28159a63d+37c5a29d61,gcf0d15dbbd+2acd6d4d48,gd7358e8bfb+778a810b6e,gda3e153d99+82efc23009,gda6a2b7d83+2acd6d4d48,gdaeeff99f8+1711a396fd,ge2409df99d+6b12de1076,ge79ae78c31+37c5a29d61,gf0baf85859+d0a5978c5a,gf3967379c6+4954f8c433,gfb92a5be7c+82efc23009,gfec2e1e490+2aaed99252,w.2024.46
LSST Data Management Base Package
Loading...
Searching...
No Matches
evaluate.h
Go to the documentation of this file.
1// -*- LSST-C++ -*-
2/*
3 * This file is part of gauss2d.
4 *
5 * Developed for the LSST Data Management System.
6 * This product includes software developed by the LSST Project
7 * (https://www.lsst.org).
8 * See the COPYRIGHT file at the top-level directory of this distribution
9 * for details of code ownership.
10 *
11 * This program is free software: you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation, either version 3 of the License, or
14 * (at your option) any later version.
15 *
16 * This program is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU General Public License for more details.
20 *
21 * You should have received a copy of the GNU General Public License
22 * along with this program. If not, see <https://www.gnu.org/licenses/>.
23 */
24
25#ifndef LSST_GAUSS2D_EVALUATE_H
26#define LSST_GAUSS2D_EVALUATE_H
27
28#include <iostream>
29#include <memory>
30#include <set>
31#include <stdexcept>
32#include <string>
33
34#include <vector>
35
36#include "gaussian.h"
37#include "image.h"
38#include "to_string.h"
39
40namespace lsst::gauss2d {
41
42static const ConvolvedGaussians GAUSSIANS_NULL{std::nullopt};
43
44enum class BackgroundType : unsigned char {
45 none = 0,
46 constant = 1,
47};
48enum class OutputType : unsigned char {
49 none = 0,
50 overwrite = 1,
51 add = 2,
52};
53enum class GradientType : unsigned char {
54 none = 0,
55 loglike = 1,
56 jacobian = 2,
57};
58
59const size_t N_EXTRA_MAP = 2;
60const size_t N_EXTRA_FACTOR = 3;
61const size_t N_PARAMS_GAUSS2D = 6;
62
63namespace detail {
64
67
69 double cen_x;
70 double cen_y;
71 double L;
72 double sigma_x;
73 double sigma_y;
74 double rho;
75};
76
82
83inline TermsMoments gaussian_pixel_x_xx(const double cen_x, const double x_min, const double bin_x,
84 const unsigned int dim_x, const double xx_weight,
85 const double xy_weight) {
86 const double x_init = x_min - cen_x + bin_x / 2.;
87 vecdptr x = std::make_unique<vecd>(dim_x);
88 vecdptr xx = std::make_unique<vecd>(dim_x);
89 vecdptr x_norm = std::make_unique<vecd>(dim_x);
90 for (unsigned int i = 0; i < dim_x; i++) {
91 double dist = x_init + i * bin_x;
92 (*x)[i] = dist;
93 (*xx)[i] = dist * dist * xx_weight;
94 (*x_norm)[i] = dist * xy_weight;
95 }
96 return TermsMoments({std::move(x), std::move(x_norm), std::move(xx)});
97}
98
99/*
100This is some largely unnecessary algebra to derive conversions between the ellipse parameterization and the
101covariance matrix parameterization of a bivariate Gaussian.
102
103See e.g. http://mathworld.wolfram.com/BivariateNormalDistribution.html
104... and https://www.unige.ch/sciences/astro/files/5413/8971/4090/2_Segransan_StatClassUnige.pdf
105
106tan 2th = 2*rho*sig_x*sig_y/(sig_x^2 - sig_y^2)
107
108sigma_maj^2 = (cos2t*sig_x^2 - sin2t*sig_y^2)/(cos2t-sin2t)
109sigma_min^2 = (cos2t*sig_y^2 - sin2t*sig_x^2)/(cos2t-sin2t)
110
111(sigma_maj^2*(cos2t-sin2t) + sin2t*sig_y^2)/cos2t = sig_x^2
112-(sigma_min^2*(cos2t-sin2t) - cos2t*sig_y^2)/sin2t = sig_x^2
113
114(sigma_maj^2*(cos2t-sin2t) + sin2t*sig_y^2)/cos2t = -(sigma_min^2(cos2t-sin2t) - cos2t*sig_y^2)/sin2t
115sin2t*(sigma_maj^2*(cos2t-sin2t) + sin2t*sig_y^2)c + cos2t*(sigma_min^2*(cos2t-sin2t) - cos2t*sig_y^2) = 0
116cos4t*sig_y^2 - sin^4th*sig_y^2 = sin2t*(sigma_maj^2*(cos2t-sin2t)) + cos2t*(sigma_min^2*(cos2t-sin2t))
117
118cos^4x - sin^4x = (cos^2 + sin^2)*(cos^2-sin^2) = cos^2 - sin^2
119
120sig_y^2 = (sin2t*(sigma_maj^2*(cos2t-sin2t)) + cos2t*(sigma_min^2*(cos2t-sin2t)))/(cos2t - sin2t)
121 = (sin2t*sigma_maj^2 + cos2t*sigma_min^2)
122
123sigma_maj^2*(cos2t-sin2t) + sin2t*sig_y^2 = cos2t*sig_x^2
124sig_x^2 = (sigma_maj^2*(cos2t-sin2t) + sin2t*sig_y^2)/cos2t
125 = (sigma_maj^2*(cos2t-sin2t) + sin2t*(sin2t*sigma_maj^2 + cos2t*sigma_min^2))/cos2t
126 = (sigma_maj^2*(cos2t-sin2t+sin4t) + sin2tcos2t*sigma_min^2)/cos2t
127 = (sigma_maj^2*cos4t + sin2t*cos2t*sigma_min^2)/cos2t
128 = (sigma_maj^2*cos2t + sigma_min^2*sin2t)
129
130sig_x^2 - sig_y^2 = sigma_maj^2*cos2t + sigma_min^2*sin2t - sigma_maj^2*sin2t - sigma_min^2*cos2t
131 = (sigma_maj^2 - sigma_min^2*)*(cos2t-sin2t)
132 = (sigma_maj^2 - sigma_min^2*)*(1-tan2t)*cos2t
133
134rho = tan2th/2/(sig_x*sig_y)*(sig_x^2 - sig_y^2)
135 = tanth/(1-tan2t)/(sig_x*sig_y)*(sig_x^2 - sig_y^2)
136 = tanth/(1-tan2t)/(sig_x*sig_y)*(sigma_maj^2 - sigma_min^2*)*(1-tan2t)*cos2t
137 = tanth/(sig_x*sig_y)*(sigma_maj^2 - sigma_min^2)*cos2t
138 = sint*cost/(sig_x*sig_y)*(sigma_maj^2 - sigma_min^2)
139
140Derivation of 2D Gaussian function gradients (Jacobian):
141
142 xmc = x - cen_x
143
144 m = L*bin_x*bin_y/(2.*M_PI*sig_x*sig_y)/sqrt(1-rho*rho) * exp(-(
145 + xmc[g]^2/sig_x^2/(2*(1-rho*rho))
146 + ymc[g][j]^2/sig_y^2/(2*(1-rho*rho))
147 - rho*xmc[g]*ymc[g][j]/(1-rho*rho)/sig_x/sig_y
148 ))
149
150 norm_exp = 1./(2*(1-rho*rho))
151 weight = L*bin_x*bin_y
152 T = Terms
153 T.weight = weight/(2.*M_PI*sig_x*sig_y)*sqrt(2.*norm_exp),
154 T.xx = norm_exp/sig_x/sig_x,
155 T.yy = norm_exp/sig_y/sig_y,
156 T.xy = 2.*rho*norm_exp/sig_x/sig_y
157
158 m = T.weight * exp(-(xmc[g]^2*T.xx + ymc[g][j]^2*T.yy - rho*xmc[g]*ymc[g][j]*T.xy))
159
160 rho -> r; sig_x/y -> s;
161
162 weight_xx[g] = T.xx
163 dxmc^2/dcen_x = -2*xmc[g] - r*ymc[g][j]*T.xy
164
165 ymc_weighted = r*ymc*T.xy
166
167 dm/dL = m/L
168 dm/dcen_x = (2*xmc[g]*weight_xx[g] - ymc_weighted[g][j])*m
169 dm/ds = -m/s + m*(2/s*[xy]sqs[g] - xy/s) = m/s*(2*[xy]sqs[g] - (1+xy))
170 dm/dr = -m*(r/(1-r^2) + 4*r*(1-r^2)*(xmc_sq_norm[g] + yy_weighted[g][j]) -
171 (1/r + 2*r/(1+r)*xmc[g]*ymc[g][j])
172
173 To verify (all on one line):
174
175 https://www.wolframalpha.com/input/?i=differentiate+F*s*t%2F(2*pi*sqrt(1-r%5E2))*
176 exp(-((x-m)%5E2*s%5E2+%2B+(y-n)%5E2*t%5E2+-+2*r*(x-m)*(y-n)*s*t)%2F(2*(1-r%5E2)))+wrt+s
177*/
178
180
181// Various multiplicative terms that appread in a Gaussian PDF
182struct Terms {
183 double weight;
184 double xx;
185 double yy;
186 double xy;
187};
188
189Terms terms_from_covar(double weight, const Ellipse& ell);
190
195public:
196 double weight = 0;
197 double weight_kernel = 0;
198 double xmc = 0;
199 double xmc_weighted = 0;
201 double weight_xx = 0;
202 double xmc_sq_norm = 0;
204 // TODO: Give these variables more compelling names
205
206 explicit TermsPixel(double weight_i = 0, double weight_kernel = 0, double xmc_i = 0,
207 double xmc_weighted_i = 0, vecdptr ymc_weighted_i = nullptr, double weight_xx_i = 0,
208 double xmc_sq_norm_i = 0, vecdptr yy_weighted_i = nullptr)
209 : weight(weight_i),
210 xmc(xmc_i),
211 xmc_weighted(xmc_weighted_i),
212 ymc_weighted(std::move(ymc_weighted_i)),
213 weight_xx(weight_xx_i),
214 xmc_sq_norm(xmc_sq_norm_i),
215 yy_weighted(std::move(yy_weighted_i)) {}
216
217 // Set values that are specific to a given gaussian
218 void set(double weight_, double weight_kernel_, double xmc_, double xx, vecdptr ymc_weighted_,
219 vecdptr yy_weighted_) {
220 this->weight = weight_;
221 this->weight_kernel = weight_kernel_;
222 this->xmc = xmc_;
223 this->weight_xx = xx;
224 this->ymc_weighted = std::move(ymc_weighted_);
225 this->yy_weighted = std::move(yy_weighted_);
226 }
227};
228
230
232public:
233 vecdptr ymc = nullptr;
234 double xx_weight = 0;
235 double xy_weight = 0;
236 double yy_weight = 0;
237 double rho_factor = 0;
238 double sig_x_inv = 0;
239 double sig_y_inv = 0;
240 double rho_xy_factor = 0;
245 double drho_c_drho_s = 0;
246 double xmc_t_xy_weight = 0;
247
248 explicit TermsGradient(vecdptr ymc_i = nullptr, double xx_weight_i = 0, double xy_weight_i = 0,
249 double yy_weight_i = 0, double rho_factor_i = 0, double sig_x_inv_i = 0,
250 double sig_y_inv_i = 0, double rho_xy_factor_i = 0,
251 double sig_x_src_div_conv_i = 0, double sig_y_src_div_conv_i = 0,
252 double drho_c_dsig_x_src_i = 0, double drho_c_dsig_y_src_i = 0,
253 double drho_c_drho_s_i = 0, double xmc_t_xy_weight_i = 0)
254 : ymc(std::move(ymc_i)),
255 xx_weight(xx_weight_i),
256 xy_weight(xy_weight_i),
257 yy_weight(yy_weight_i),
258 rho_factor(rho_factor_i),
259 sig_x_inv(sig_x_inv_i),
260 sig_y_inv(sig_y_inv_i),
261 rho_xy_factor(rho_xy_factor_i),
262 sig_x_src_div_conv(sig_x_src_div_conv_i),
263 sig_y_src_div_conv(sig_y_src_div_conv_i),
264 drho_c_dsig_x_src(drho_c_dsig_x_src_i),
265 drho_c_dsig_y_src(drho_c_dsig_y_src_i),
266 drho_c_drho_s(drho_c_drho_s_i),
267 xmc_t_xy_weight(xmc_t_xy_weight_i) {}
268
269 void set(const Terms& terms, const Ellipse& ell_src, const Ellipse& ell_psf, const Ellipse& ell,
270 vecdptr ymc_) {
271 double sig_x = ell.get_sigma_x();
272 double sig_y = ell.get_sigma_y();
273 double rho = ell.get_rho();
274
275 this->ymc = std::move(ymc_);
276 const double sig_xy = sig_x * sig_y;
277 this->xx_weight = terms.xx;
278 this->xy_weight = terms.xy;
279 this->yy_weight = terms.yy;
280 this->rho_factor = rho / (1. - rho * rho);
281 this->sig_x_inv = 1 / sig_x;
282 this->sig_y_inv = 1 / sig_y;
283 this->rho_xy_factor = 1. / (1. - rho * rho) / sig_xy;
284 /*
285 sigma_conv = sqrt(sigma_src^2 + sigma_psf^2)
286 https://www.wolframalpha.com/input/?i=d(sqrt(x%5E2%2By%5E2))%2Fdx
287 dsigma_conv/dsigma_src = sigma_src/sigma_conv
288 df/dsigma_src = df/d_sigma_conv * dsigma_conv/dsigma_src
289
290 rho_conv*sigmaxy_conv = rho_src*sigmaxy_src + rho_psf*sigmaxy_psf
291 rho_conv = (rho_src*sigmaxy_src + rho_psf*sigmaxy_psf)/sigmaxy_conv
292 drho_conv/drho_src = sigmaxy_src/sigmaxy_conv
293
294 drho_conv/dsigmax_src = rho_src*sigmay_src/sigmay_conv*sigmax_psf^2/sigmax_conv^3 (*sigmax_src/sigmax_src)
295 = rho_conv/sigmax_src*(sigmax_psf/sigmax_conv)^2
296 + rho_psf*sigmax_psf*sigmay_psf*sigmax_src/sigmay_conv/sigmax_conv^3
297
298 */
299 double sig_x_src = ell_src.get_sigma_x();
300 double sig_y_src = ell_src.get_sigma_y();
301 double rho_src = ell_src.get_rho();
302 double sig_x_psf = ell_psf.get_sigma_x();
303 double sig_y_psf = ell_psf.get_sigma_y();
304 double rho_psf = ell_psf.get_rho();
305
306 this->sig_x_src_div_conv = sig_x_src / sig_x;
307 this->sig_y_src_div_conv = sig_y_src / sig_y;
308
309 double covar_psf_dsig_xy = rho_psf * sig_x_psf * sig_y_psf / sig_xy;
310 double drho = 0;
311 if (sig_x_psf > 0) {
312 double sig_p_ratio = sig_x_psf / sig_x;
313 drho = rho_src * this->sig_y_src_div_conv * sig_p_ratio * sig_p_ratio / sig_x
314 - covar_psf_dsig_xy * this->sig_x_src_div_conv / sig_x;
315 }
316 this->drho_c_dsig_x_src = drho;
317 if (sig_y_psf > 0) {
318 double sig_p_ratio = sig_y_psf / sig_y;
319 drho = rho_src * this->sig_x_src_div_conv * sig_p_ratio * sig_p_ratio / sig_y
320 - covar_psf_dsig_xy * this->sig_y_src_div_conv / sig_y;
321 }
322 this->drho_c_dsig_y_src = drho;
323 this->drho_c_drho_s = sig_x_src * sig_y_src / sig_xy;
324 }
325};
326
328
329template <typename t, class Data, class Indices>
330class GradientsExtra : public Object {
331public:
332 GradientsExtra(const Image<idx_type, Indices>& param_map_in, const Image<t, Data>& param_factor_in,
333 std::shared_ptr<ImageArray<t, Data>> output, size_t n_gauss)
334 : _param_map(param_map_in), _param_factor(param_factor_in), _output(output) {
335 if(output == nullptr) {
336 throw std::invalid_argument("GradientsExtra output must not be a nullptr");
337 }
338 const auto n_extra_map_rows = param_map_in.get_n_rows();
339 const auto n_extra_fac_rows = param_factor_in.get_n_rows();
340 const auto n_extra_map_cols = param_map_in.get_n_cols();
341 const auto n_extra_fac_cols = param_factor_in.get_n_cols();
342 std::string errmsg = "";
343 if (n_extra_map_rows != n_gauss) {
344 errmsg += "extra_param_map n_rows=" + std::to_string(n_extra_map_rows)
345 + " != n_gauss=" + std::to_string(n_gauss) + ". ";
346 }
347 if (n_extra_fac_rows != n_gauss) {
348 errmsg += "extra_param_factor n_rows=" + std::to_string(n_extra_fac_rows)
349 + " != n_gauss=" + std::to_string(n_gauss) + ". ";
350 }
351 if (n_extra_map_cols != N_EXTRA_MAP) {
352 errmsg += "extra_param_map n_cols=" + std::to_string(n_extra_map_cols) + " != 2. ";
353 }
354 if (n_extra_fac_cols != N_EXTRA_FACTOR) {
355 errmsg += "extra_param_factor n_cols=" + std::to_string(n_extra_fac_cols) + " != 3. ";
356 }
357 if (!errmsg.empty()) throw std::runtime_error(errmsg);
358 }
359
360 GradientsExtra() = delete;
361
362 inline void add_index_to_set(size_t g, std::set<size_t>& set) { set.insert(_param_map.get_value(g, 1)); }
363
364 inline void add(size_t g, size_t dim1, size_t dim2, const ValuesGauss& gradients) {
365 // Reset g_check to zero once g rolls back to zero itself
366 _g_check *= (g != 0);
367 const bool to_add = g == _param_map.get_value(_g_check, 0);
368 const auto idx = _param_map.get_value(g, 1);
369 double value = gradients.L * _param_factor.get_value(g, 0)
370 + gradients.sigma_x * _param_factor.get_value(g, 1)
371 + gradients.sigma_y * _param_factor.get_value(g, 2);
372 (*_output)[idx].add_value_unchecked(dim1, dim2, value);
373 _g_check += to_add;
374 }
375
376 std::string repr(bool name_keywords = false,
377 std::string_view namespace_separator = Object::CC_NAMESPACE_SEPARATOR) const override {
378 bool is_kw = name_keywords;
379 std::string rval = ( //
380 type_name_str<GradientsExtra<t, Data, Indices>>(false, namespace_separator) + "("
381 + (is_kw ? "param_map=" : "") + _param_map.repr(is_kw, namespace_separator) + ", "
382 + (is_kw ? "param_factor=" : "") + _param_factor.repr(is_kw, namespace_separator) + ", "
383 + (is_kw ? "output=" : "") + _output->repr(is_kw, namespace_separator) + ", "
384 + (is_kw ? "n_gauss=" : "") + std::to_string(_param_map.get_n_rows()) + ")" //
385 );
386 return rval;
387 }
388 std::string str() const override {
389 std::string rval = ( //
390 type_name_str<GradientsExtra<t, Data, Indices>>(true) + "(" //
391 + "param_map=" + _param_map.str() + ", " //
392 + "param_factor=" + _param_factor.str() + ", " //
393 + "output=" + _output->str() + ", " //
394 + "n_gauss=" + std::to_string(_param_map.get_n_rows()) + ")" //
395 );
396 return rval;
397 }
398
399private:
400 const Image<idx_type, Indices>& _param_map;
401 const Image<t, Data>& _param_factor;
403 size_t _g_check = 0;
404};
405
406template <class Data>
407inline void gaussian_pixel(Data& image, const double weight, const double cen_x, const double cen_y,
408 const double xx_weight, const double yy_weight, const double xy_weight) {
409 const unsigned int dim_x = image.get_n_cols();
410 const unsigned int dim_y = image.get_n_rows();
411 const auto coordsys = image.get_coordsys();
412 const double x_min = coordsys.x_min;
413 const double y_min = coordsys.y_min;
414 const double bin_x = coordsys.get_dx1() / image.get_n_cols();
415 const double bin_y = coordsys.get_dy2() / image.get_n_rows();
416
417 const double weight_pix = weight * bin_x * bin_y;
418
419 const auto moment_terms_y = gaussian_pixel_x_xx(cen_y, y_min, bin_y, dim_y, yy_weight, xy_weight);
420 const vecd& y_weighted = *(moment_terms_y.x_norm);
421 const vecd& yy_weighted = *(moment_terms_y.xx);
422
423 double x = x_min - cen_x + bin_x / 2.;
424 // TODO: Consider a version with cached xy, although it doubles memory usage
425 for (unsigned int i = 0; i < dim_x; i++) {
426 const double xx_weighted = x * x * xx_weight;
427 for (unsigned int j = 0; j < dim_y; j++) {
428 image.set_value_unchecked(j, i,
429 weight_pix * exp(-(xx_weighted + yy_weighted[j] - x * y_weighted[j])));
430 }
431 x += bin_x;
432 }
433}
434
435/*
436// Evaluate a Gaussian on a grid given the three elements of the symmetric covariance matrix
437// Actually, rho is scaled by sig_x and sig_y (i.e. the covariance is rho*sig_x*sig_y)
438template <class Data>
439void add_gaussian_pixel(const Gaussian & gauss, Data & image)
440{
441 const double L = gauss.get_integral();
442 const Terms terms = terms_from_covar(L, *gauss.ell);
443
444 gaussian_pixel(image, terms.weight, gauss.cen->x, gauss.cen->y, terms.xx, terms.yy, terms.xy);
445}
446
447template <class Data>
448void add_gaussian_pixel_extra(const Gaussian & gauss, Data & data)
449{
450 // I don't remember why this isn't just 1/(2*ln(2)) but anyway it isn't
451 const double weight_sersic = 0.69314718055994528622676398299518;
452
453 size_t dim_x = data.get_n_cols();
454 size_t dim_y = data.get_n_rows();
455
456 const auto coordsys = data.get_coordsys();
457 const double bin_x = coordsys.get_dx1();
458 const double bin_y = coordsys.get_dy2();
459
460 const auto ell = EllipseMajor(*gauss.ell);
461 double r_eff = ell.get_r_major();
462 double axrat = ell.get_axrat();
463 double ang = ell.get_angle_radians();
464 double cen_x = gauss.cen->x;
465 double cen_y = gauss.cen->y;
466
467 const double weight = gauss.get_integral()*weight_sersic/(M_PI*axrat)/(r_eff*r_eff)*bin_x*bin_y;
468 const double axrat_sq_inv = 1.0/axrat/axrat;
469
470 double x,y;
471 const double bin_x_half=bin_x/2.;
472 const double bin_y_half=bin_y/2.;
473
474 const double reff_y_inv = sin(ang)*sqrt(weight_sersic)/r_eff;
475 const double reff_x_inv = cos(ang)*sqrt(weight_sersic)/r_eff;
476
477
478 unsigned int i=0,j=0;
479 x = coordsys.x_min - cen_x + bin_x_half;
480 for(i = 0; i < dim_x; i++)
481 {
482 y = coordsys.y_min - cen_y + bin_y_half;
483 for(j = 0; j < dim_y; j++)
484 {
485 const double dist_1 = (x*reff_x_inv + y*reff_y_inv);
486 const double dist_2 = (x*reff_y_inv - y*reff_x_inv);
487 // mat(j,i) = ... is slower, but perhaps allows for Indices with dim_x*dim_y > INT_MAX ?
488 data.set_value_unchecked(j, i, weight*exp(-(dist_1*dist_1 + dist_2*dist_2*axrat_sq_inv)));
489 y += bin_y;
490 }
491 x += bin_x;
492 }
493}
494*/
495inline void gaussian_pixel_get_jacobian(ValuesGauss& out, const double m, const double m_unweight,
496 const double xmc_norm, const double ymc_weighted, const double xmc,
497 const double ymc, const double norms_yy, const double xmc_t_xy_factor,
498 const double sig_x_inv, const double sig_y_inv, const double xy_norm,
499 const double xx_norm, const double yy_weighted,
500 const double rho_div_one_m_rhosq, const double norms_xy_div_rho,
501 const double dsig_x_conv_src = 1, const double dsig_y_conv_src = 1,
502 const double drho_c_dsig_x_src = 0,
503 const double drho_c_dsig_y_src = 0, const double drho_c_drho_s = 1) {
504 out.cen_x = m * (2 * xmc_norm - ymc_weighted);
505 out.cen_y = m * (2 * ymc * norms_yy - xmc_t_xy_factor);
506 out.L = m_unweight;
507 const double one_p_xy = 1. + xy_norm;
508 // df/dsigma_src = df/d_sigma_conv * dsigma_conv/dsigma_src
509 const double dfdrho_c = m
510 * (rho_div_one_m_rhosq * (1 - 2 * (xx_norm + yy_weighted - xy_norm))
511 + xmc * ymc * norms_xy_div_rho);
512 out.sigma_x = dfdrho_c * drho_c_dsig_x_src + dsig_x_conv_src * (m * sig_x_inv * (2 * xx_norm - one_p_xy));
513 out.sigma_y
514 = dfdrho_c * drho_c_dsig_y_src + dsig_y_conv_src * (m * sig_y_inv * (2 * yy_weighted - one_p_xy));
515 // drho_conv/drho_src = sigmaxy_src/sigmaxy_conv
516 out.rho = dfdrho_c * drho_c_drho_s;
517}
518
519inline void gaussian_pixel_get_jacobian_from_terms(ValuesGauss& out, const size_t dim1,
520 const TermsPixel& terms_pixel,
521 const TermsGradient& terms_grad, double m,
522 double m_unweighted, double xy_norm) {
524 out, m, m_unweighted * terms_pixel.weight_kernel, terms_pixel.xmc_weighted,
525 (*terms_pixel.ymc_weighted)[dim1], terms_pixel.xmc, (*terms_grad.ymc)[dim1], terms_grad.yy_weight,
526 terms_grad.xmc_t_xy_weight, terms_grad.sig_x_inv, terms_grad.sig_y_inv, xy_norm,
527 terms_pixel.xmc_sq_norm, (*terms_pixel.yy_weighted)[dim1], terms_grad.rho_factor,
528 terms_grad.rho_xy_factor, terms_grad.sig_x_src_div_conv, terms_grad.sig_y_src_div_conv,
529 terms_grad.drho_c_dsig_x_src, terms_grad.drho_c_dsig_y_src, terms_grad.drho_c_drho_s);
530}
531
532template <typename t>
533inline void gaussian_pixel_add_values(t& cen_x, t& cen_y, t& L, t& sig_x, t& sig_y, t& rho,
534 const ValuesGauss& values, const t weight_cen_x = 1,
535 const t weight_cen_y = 1, const t weight_L = 1,
536 const t weight_sig_x = 1, const t weight_sig_y = 1,
537 const t weight_rho = 1) {
538 cen_x += weight_cen_x * values.cen_x;
539 cen_y += weight_cen_y * values.cen_y;
540 L += weight_L * values.L;
541 sig_x += weight_sig_x * values.sigma_x;
542 sig_y += weight_sig_y * values.sigma_y;
543 rho += weight_rho * values.rho;
544}
545
546// Computes and stores LL along with dll/dx for all components
547template <typename t, class Data, class Indices, bool do_extra>
549 Image<t, Data> * output, const Image<idx_type, Indices>& grad_param_map,
550 const Image<t, Data>& grad_param_factor, const size_t N_GAUSS,
551 const std::vector<Weights>& gaussweights, double& chi_pix, double& loglike, const double model,
552 double data, double sigma_inv, unsigned int dim1, unsigned int dim2, const TermsPixelVec& terms_pixel,
553 const TermsGradientVec& terms_grad, ValuesGauss& gradients,
554 const std::shared_ptr<const Image<idx_type, Indices>> extra_param_map,
555 const std::shared_ptr<const Image<t, Data>> extra_param_factor) {
556 double diff = data - model;
557 chi_pix = sigma_inv * diff;
558 double diffvar = sigma_inv * chi_pix;
559 loglike -= diff * diffvar / 2.;
560 for (size_t g = 0; g < N_GAUSS; ++g) {
561 const Weights& weights = gaussweights[g];
562 /*
563 * Derivation:
564 *
565 ll = sum(-(data-model)^2*varinv/2)
566 dll/dx = --2*dmodel/dx*(data-model)*varinv/2
567 dmodelsum/dx = d(.. + model[g])/dx = dmodel[g]/dx
568 dll/dx = dmodel[g]/dx*diffvar
569 */
570 gaussian_pixel_get_jacobian_from_terms(gradients, dim1, terms_pixel[g], terms_grad[g],
571 weights[0] * diffvar, weights[1] * diffvar, weights[2]);
572 gaussian_pixel_add_values<t>(
573 output->_get_value_unchecked(0, grad_param_map.get_value_unchecked(g, 0)),
574 output->_get_value_unchecked(0, grad_param_map.get_value_unchecked(g, 1)),
575 output->_get_value_unchecked(0, grad_param_map.get_value_unchecked(g, 2)),
576 output->_get_value_unchecked(0, grad_param_map.get_value_unchecked(g, 3)),
577 output->_get_value_unchecked(0, grad_param_map.get_value_unchecked(g, 4)),
578 output->_get_value_unchecked(0, grad_param_map.get_value_unchecked(g, 5)), gradients,
579 grad_param_factor.get_value_unchecked(g, 0), grad_param_factor.get_value_unchecked(g, 1),
580 grad_param_factor.get_value_unchecked(g, 2), grad_param_factor.get_value_unchecked(g, 3),
581 grad_param_factor.get_value_unchecked(g, 4), grad_param_factor.get_value_unchecked(g, 5));
582 if constexpr (do_extra) {
583 double value = gradients.L * extra_param_factor->get_value_unchecked(g, 0)
584 + gradients.sigma_x * extra_param_factor->get_value_unchecked(g, 1)
585 + gradients.sigma_y * extra_param_factor->get_value_unchecked(g, 2);
586 output->_get_value_unchecked(0, extra_param_map->get_value_unchecked(g, 1)) += value;
587 }
588 }
589}
590
591template <typename T, class Data, class Indices, GradientType gradient_type, bool do_extra>
592inline T gaussian_pixel_add_all(size_t g, size_t j, size_t i, double weight, double sigma_inv,
593 const TermsPixelVec& terms_pixel_vec, ImageArray<T, Data>& output_jac_ref,
594 const Image<idx_type, Indices>& grad_param_map,
595 const Image<T, Data>& grad_param_factor, std::vector<Weights>& gradweights,
596 const TermsGradientVec& terms_grad_vec, ValuesGauss& gradients,
598 const TermsPixel& terms_pixel = terms_pixel_vec[g];
599 const double xy_norm = terms_pixel.xmc * (*terms_pixel.ymc_weighted)[j];
600 const double value_unweight
601 = terms_pixel.weight * exp(-(terms_pixel.xmc_sq_norm + (*terms_pixel.yy_weighted)[j] - xy_norm));
602 const double value = weight * value_unweight;
603 // Computes dmodel/dx for x in [cen_x, cen_y, flux, sigma_x, sigma_y, rho]
604 if constexpr (gradient_type == GradientType::loglike) {
605 Weights& output = gradweights[g];
606 output[0] = value;
607 output[1] = value_unweight;
608 output[2] = xy_norm;
609 } else if constexpr (gradient_type == GradientType::jacobian) {
610 gaussian_pixel_get_jacobian_from_terms(gradients, j, terms_pixel, terms_grad_vec[g],
611 sigma_inv * value, sigma_inv * value_unweight, xy_norm);
612 gaussian_pixel_add_values<T>(
613 output_jac_ref[grad_param_map.get_value_unchecked(g, 0)]._get_value_unchecked(j, i),
614 output_jac_ref[grad_param_map.get_value_unchecked(g, 1)]._get_value_unchecked(j, i),
615 output_jac_ref[grad_param_map.get_value_unchecked(g, 2)]._get_value_unchecked(j, i),
616 output_jac_ref[grad_param_map.get_value_unchecked(g, 3)]._get_value_unchecked(j, i),
617 output_jac_ref[grad_param_map.get_value_unchecked(g, 4)]._get_value_unchecked(j, i),
618 output_jac_ref[grad_param_map.get_value_unchecked(g, 5)]._get_value_unchecked(j, i),
619 gradients, grad_param_factor.get_value_unchecked(g, 0),
620 grad_param_factor.get_value_unchecked(g, 1), grad_param_factor.get_value_unchecked(g, 2),
621 grad_param_factor.get_value_unchecked(g, 3), grad_param_factor.get_value_unchecked(g, 4),
622 grad_param_factor.get_value_unchecked(g, 5));
623 if constexpr (do_extra) grad_extra->add(g, j, i, gradients);
624 }
625 return value;
626}
627
628template <typename Indices>
630 size_t n_params = N_PARAMS_GAUSS2D,
631 size_t increment = 1) {
632 auto param_map = std::make_shared<Indices>(n_gaussians, n_params);
633 size_t index = 0;
634 for (size_t g = 0; g < n_gaussians; ++g) {
635 for (size_t p = 0; p < n_params; ++p) {
636 param_map->set_value(g, p, index);
637 index += increment;
638 }
639 }
640 return std::const_pointer_cast<const Indices>(param_map);
641}
642
643template <typename Data>
645 size_t n_params = N_PARAMS_GAUSS2D,
646 double value = 1.) {
647 auto param_factor = std::make_shared<Data>(n_gaussians, n_params);
648 for (size_t g = 0; g < n_gaussians; ++g) {
649 for (size_t p = 0; p < n_params; ++p) {
650 param_factor->set_value(g, p, value);
651 }
652 }
653 return std::const_pointer_cast<const Data>(param_factor);
654}
655} // namespace detail
656
667template <typename T, class Data, class Indices>
668class GaussianEvaluator : public Object {
669public:
673
674 GaussianEvaluator(int x = 0, const std::shared_ptr<const ConvolvedGaussians> gaussians = nullptr) {}
675
700 const std::shared_ptr<const Image<T, Data>> data = nullptr,
701 const std::shared_ptr<const Image<T, Data>> sigma_inv = nullptr,
702 const std::shared_ptr<Image<T, Data>> output = nullptr,
703 const std::shared_ptr<Image<T, Data>> residual = nullptr,
704 const std::shared_ptr<ImageArrayT> grads = nullptr,
705 const std::shared_ptr<const Image<idx_type, Indices>> grad_param_map = nullptr,
706 const std::shared_ptr<const Image<T, Data>> grad_param_factor = nullptr,
707 const std::shared_ptr<const Image<idx_type, Indices>> extra_param_map = nullptr,
708 const std::shared_ptr<const Image<T, Data>> extra_param_factor = nullptr,
709 const std::shared_ptr<const Image<T, Data>> background = nullptr)
710 : _gaussians(gaussians == nullptr ? GAUSSIANS_NULL : *gaussians),
711 _gaussians_ptr(gaussians == nullptr ? nullptr : gaussians),
712 _n_gaussians(_gaussians.size()),
713 _do_output(output != nullptr),
714 _is_sigma_image((sigma_inv != nullptr) && (sigma_inv->size() > 1)),
715 _do_residual(residual != nullptr),
716 _has_background(background != nullptr),
717 _gradienttype((grads != nullptr && grads->size() > 0)
718 ? (((grads->size() == 1) && ((*grads)[0].get_n_rows() == 1))
721 : GradientType::none),
722 _do_extra(
723 extra_param_map != nullptr && extra_param_factor != nullptr
724 && (_gradienttype == GradientType::loglike || _gradienttype == GradientType::jacobian)),
725 _backgroundtype(background != nullptr ? (_background->size() == 1 ? BackgroundType::constant
728 _get_likelihood((data != nullptr) && (sigma_inv != nullptr)),
729 _data(data),
730 _sigma_inv(sigma_inv),
731 _output(output),
732 _residual(residual),
733 _grads(grads),
734 _grad_param_map((_gradienttype == GradientType::none)
735 ? nullptr
736 : ((grad_param_map != nullptr)
737 ? grad_param_map
738 : detail::_param_map_default<Indices>(_gaussians.size()))),
739 _grad_param_factor(
740 (_gradienttype == GradientType::none)
741 ? nullptr
742 : ((grad_param_factor != nullptr)
743 ? grad_param_factor
744 : detail::_param_factor_default<Data>(_gaussians.size()))),
745 _extra_param_map((_gradienttype == GradientType::none)
746 ? nullptr
747 : ((extra_param_map != nullptr)
748 ? extra_param_map
749 : detail::_param_map_default<Indices>(_gaussians.size(),
750 N_EXTRA_MAP, 0))),
751 _extra_param_factor((_gradienttype == GradientType::none)
752 ? nullptr
753 : ((extra_param_factor != nullptr)
754 ? extra_param_factor
755 : detail::_param_factor_default<Data>(
756 _gaussians.size(), N_EXTRA_FACTOR, 0.))),
757 _grad_extra(_do_extra ? std::make_unique<detail::GradientsExtra<T, Data, Indices>>(
758 *extra_param_map, *extra_param_factor, _grads, _n_gaussians)
759 : nullptr),
760 _grad_param_idx(_grad_param_map == nullptr ? std::vector<size_t>{} : _get_grad_param_idx()),
761 _n_cols(_data == nullptr ? (_output == nullptr ? (_gradienttype == GradientType::jacobian
762 ? (*_grads)[0].get_n_cols()
763 : 0)
764 : _output->get_n_cols())
765 : _data->get_n_cols()),
766 _n_rows(_data == nullptr ? (_output == nullptr ? (_gradienttype == GradientType::jacobian
767 ? (*_grads)[0].get_n_rows()
768 : 0)
769 : _output->get_n_rows())
770 : _data->get_n_rows()),
771 _size(_n_cols * _n_rows),
772 _coordsys(_data == nullptr ? (_output == nullptr ? (_gradienttype == GradientType::jacobian
773 ? (*_grads)[0].get_coordsys()
774 : COORDS_DEFAULT)
775 : _output->get_coordsys())
776 : _data->get_coordsys()) {
777 if (gaussians == nullptr) throw std::runtime_error("Gaussians can't be null");
778 if (_has_background && background->size() > 1)
779 throw std::runtime_error("Background model size can't be > 1");
780 if (!(_n_cols > 0 && _n_rows > 0))
781 throw std::runtime_error("Data can't have n_rows/cols == 0; "
782 + std::to_string(output == nullptr));
783 if (!_do_extra && ((extra_param_map != nullptr) || (extra_param_factor != nullptr))) {
784 throw std::runtime_error(
785 "Must pass all of extra_param_map, extra_param_factor and grads"
786 " to compute extra gradients");
787 }
788 if (_get_likelihood) {
789 // The case of constant variance per pixel
790 if (_is_sigma_image) {
791 if (_n_cols != _sigma_inv->get_n_cols() || _n_rows != _sigma_inv->get_n_rows()) {
792 throw std::runtime_error("Data matrix dimensions [" + std::to_string(_n_cols) + ','
793 + std::to_string(_n_rows)
794 + "] don't match inverse variance dimensions ["
795 + std::to_string(_sigma_inv->get_n_cols()) + ','
796 + std::to_string(_sigma_inv->get_n_rows()) + ']');
797 }
798 if (_sigma_inv->get_coordsys() != _data->get_coordsys()) {
799 throw std::runtime_error("sigma_inv coordsys=" + _sigma_inv->get_coordsys().str()
800 + "!= coordsys=" + get_coordsys().str());
801 }
802 }
803 } else if ((data != nullptr) || (sigma_inv != nullptr)) {
804 throw std::runtime_error("Passed only one non-null data/sigma_inv");
805 }
806 // TODO: Add more coordsys compatibility checks
807 if (_gradienttype != GradientType::none) {
808 const size_t n_gpm = _grad_param_map->get_n_rows();
809 const size_t n_gpf = _grad_param_factor->get_n_rows();
810
811 if ((n_gpm != _n_gaussians) || (n_gpf != _n_gaussians)) {
812 throw std::invalid_argument("nrows for grad_param_map,factor=" + std::to_string(n_gpm) + ","
813 + std::to_string(n_gpf)
814 + " != n_gaussians=" + std::to_string(_n_gaussians));
815 }
816
817 if ((_grad_param_map->get_n_cols() != N_PARAMS_GAUSS2D)
818 || (_grad_param_factor->get_n_cols() != N_PARAMS_GAUSS2D)) {
819 throw std::invalid_argument("n_cols for grad_param_map,factor=" + std::to_string(n_gpm) + ","
820 + std::to_string(n_gpf));
821 }
822
823 const size_t n_grad = (_gradienttype == GradientType::jacobian) ? _grads->size()
824 : (_grads->at(0).get_n_cols());
825 size_t idx_g_max = 0;
826 size_t idx_e_gauss_max = 0;
827 size_t idx_e_param_max = 0;
828 for (size_t g = 0; g < _n_gaussians; ++g) {
829 for (size_t p = 0; p < N_PARAMS_GAUSS2D; ++p) {
830 auto value = _grad_param_map->get_value_unchecked(g, p);
831 if (value > idx_g_max) idx_g_max = value;
832 }
833 auto value = _extra_param_map->get_value_unchecked(g, 0);
834 if (value > idx_e_gauss_max) idx_e_gauss_max = value;
835 value = _extra_param_map->get_value_unchecked(g, 1);
836 if (value > idx_e_param_max) idx_e_param_max = value;
837 }
838 if (!((idx_e_gauss_max < _n_gaussians) && (idx_e_param_max < n_grad))) {
839 throw std::invalid_argument(" max extra_param_map[0]=" + std::to_string(idx_e_gauss_max)
840 + " !< n_gaussians=" + std::to_string(_n_gaussians) + " and/or "
841 + " max extra_param_map[1]=" + std::to_string(idx_e_param_max)
842 + " !< n_grad=" + std::to_string(n_grad));
843 }
844 if (!(idx_g_max < n_grad)) {
845 throw std::invalid_argument("max grad_param_map,extra_param_map[1]="
846 + std::to_string(idx_g_max)
847 + " !< n_grad=" + std::to_string(n_grad));
848 }
849 }
850 if (_gradienttype == GradientType::loglike) {
851 if (!_get_likelihood) {
852 throw std::runtime_error(
853 "Can't compute likelihood gradient without computing likelihood;"
854 " did you pass data and sigma_inv arrays?");
855 }
856 } else if (_gradienttype == GradientType::jacobian) {
857 // This should never happen because the ImageArray constructor requires identical dimensions, but
858 // anyway
859 size_t idx_jac = 0;
860 for (const auto& jac_img : *_grads) {
861 if (!(jac_img->get_n_rows() == _n_rows && jac_img->get_n_cols() == _n_cols)) {
862 throw std::runtime_error("grads[0] matrix dimensions [" + std::to_string(_n_cols) + ','
863 + std::to_string(_n_rows)
864 + "] don't match Jacobian matrix (grads["
865 + std::to_string(idx_jac) + "]) dimensions ["
866 + std::to_string(jac_img->get_n_cols()) + ','
867 + std::to_string(jac_img->get_n_rows()) + ']');
868 }
869 }
870 }
871 }
872 ~GaussianEvaluator() override = default;
873
874 Data const& IMAGE_NULL_CONST() const { return this->_data_null_const; }
875 Indices const& INDICES_NULL_CONST() const { return this->_indices_null_const; }
876 ImageArray<T, Data> const& IMAGEARRAY_NULL_CONST() const { return this->_data_array_null_const; }
877
878 const CoordinateSystem& get_coordsys() const { return _coordsys; }
879 size_t get_n_cols() const { return (_n_cols); }
880 size_t get_n_rows() const { return (_n_rows); }
881 size_t get_size() const { return (_size); }
882
897 double loglike_pixel(bool to_add = false) {
898 const OutputType output_type
899 = this->_do_output ? (to_add ? OutputType::add : OutputType::overwrite) : OutputType::none;
900 return output_type == OutputType::overwrite
901 ? loglike_gaussians_pixel_output<OutputType::overwrite>()
902 : (output_type == OutputType::add
903 ? loglike_gaussians_pixel_output<OutputType::add>()
904 : loglike_gaussians_pixel_output<OutputType::none>());
905 }
906
907 std::string repr(bool name_keywords = false,
908 std::string_view namespace_separator = Object::CC_NAMESPACE_SEPARATOR) const override {
909 std::string null = namespace_separator == Object::CC_NAMESPACE_SEPARATOR ? "nullptr" : "None";
910 // These are sadly just to keep formatting aligned nicely
911 auto is_kw = name_keywords;
912 auto name_sep = namespace_separator;
913 std::string c = ", ";
914 std::string rval = ( // I really want this on a separate line; I'd also prefer single indent, but...
915 type_name_str<GaussianEvaluator>(false, name_sep) + "(" // make clang-format align nicely
916 + (is_kw ? "gaussians=" : "") + repr_ptr(_gaussians_ptr, is_kw, name_sep) + ", " //
917 + (is_kw ? "data=" : "") + repr_ptr(_data, is_kw, name_sep) + ", " //
918 + (is_kw ? "sigma_inv=" : "") + repr_ptr(_sigma_inv, is_kw, name_sep) + ", " //
919 + (is_kw ? "output=" : "") + repr_ptr(_output, is_kw, name_sep) + ", " //
920 + (is_kw ? "residual=" : "") + repr_ptr(_residual, is_kw, name_sep) + ", " //
921 + (is_kw ? "grads=" : "") + repr_ptr(_grads, is_kw, name_sep) + ", " //
922 + (is_kw ? "grad_param_map=" : "") + repr_ptr(_grad_param_map, is_kw, name_sep) + ", "
923 + (is_kw ? "grad_param_factor=" : "") + repr_ptr(_grad_param_factor, is_kw, name_sep) + c
924 + (is_kw ? "extra_param_map=" : "") + repr_ptr(_extra_param_map, is_kw, name_sep) + ", "
925 + (is_kw ? "extra_param_factor=" : "") + repr_ptr(_extra_param_factor, is_kw, name_sep) + c
926 + (is_kw ? "background=" : "") + repr_ptr(_background, is_kw, name_sep) + ")");
927 return rval;
928 }
929 std::string str() const override {
930 std::string c = ", ";
931 // The comments force clang-format to keep to a more readable one var per line
932 std::string rval = ( //
933 type_name_str<GaussianEvaluator>(true) + "(" //
934 + "gaussians=" + str_ptr(_gaussians_ptr) + c //
935 + "do_extra=" + std::to_string(_do_extra) + c //
936 + "do_output=" + std::to_string(_do_output) + c //
937 + "do_residual=" + std::to_string(_do_residual) + c //
938 + "has_background=" + std::to_string(_has_background) + c //
939 + "is_sigma_image=" + std::to_string(_is_sigma_image) + c //
940 + "backgroundtype=" + std::to_string(static_cast<int>(_backgroundtype)) + c //
941 + "get_likelihood=" + std::to_string(_get_likelihood) + c //
942 + "data=" + str_ptr(_data) + c //
943 + "sigma_inv=" + str_ptr(_sigma_inv) + c //
944 + "output=" + str_ptr(_output) + c //
945 + "residual=" + str_ptr(_residual) + c //
946 + "grads=" + str_ptr(_grads) + c //
947 + "grad_param_map=" + str_ptr(_grad_param_map) + c //
948 + "grad_param_factor=" + str_ptr(_grad_param_factor) + c //
949 + "extra_param_map=" + str_ptr(_extra_param_map) + c //
950 + "extra_param_factor=" + str_ptr(_extra_param_factor) + c //
951 + "grad_extra=" + str_ptr(_grad_extra ? _grad_extra.get() : nullptr) + c //
952 + "grad_param_idx=" + to_string_iter(_grad_param_idx) + c //
953 + "n_cols=" + std::to_string(_n_cols) + c //
954 + "n_rows=" + std::to_string(_n_rows) + c //
955 + "coordsys=" + _coordsys.str() //
956 + ")" //
957 );
958 return rval;
959 }
960
961private:
962 // These are private because they originally return static objects that
963 // were (and are) not meant to be mutated, although they probably can be
964 ImageArrayT& IMAGEARRAY_NULL() const;
965
966 const ConvolvedGaussians& _gaussians;
968 const size_t _n_gaussians;
969
970 const bool _do_output;
971 const bool _is_sigma_image;
972 const bool _do_residual;
973 const bool _has_background;
974 const GradientType _gradienttype;
975 const bool _do_extra;
976 const BackgroundType _backgroundtype;
977 const bool _get_likelihood;
978
980 const std::shared_ptr<const DataT> _sigma_inv;
981 const std::shared_ptr<DataT> _output;
982 const std::shared_ptr<DataT> _residual;
983 const std::shared_ptr<ImageArrayT> _grads;
984 const std::shared_ptr<const IndicesT> _grad_param_map;
985 const std::shared_ptr<const DataT> _grad_param_factor;
986 const std::shared_ptr<const IndicesT> _extra_param_map;
987 const std::shared_ptr<const DataT> _extra_param_factor;
989 const std::shared_ptr<const DataT> _background;
990 const std::vector<size_t> _grad_param_idx;
991 const size_t _n_cols;
992 const size_t _n_rows;
993 const size_t _size;
994 const CoordinateSystem& _coordsys;
995
996 // TODO: Make static if possible - static PyImage does not work on
997 // Python 3.12; see https://rubinobs.atlassian.net/browse/DM-45445
998 const Data _data_null_const{0, 0};
999 const ImageArray<T, Data> _data_array_null_const{nullptr};
1000 const Indices _indices_null_const{0, 0};
1001
1002 std::vector<size_t> _get_grad_param_idx() const {
1003 std::vector<size_t> grad_param_idx;
1004 std::set<size_t> grad_param_idx_uniq;
1005 for (size_t g = 0; g < _n_gaussians; ++g) {
1006 for (size_t p = 0; p < N_PARAMS_GAUSS2D; ++p) {
1007 grad_param_idx_uniq.insert(_grad_param_map->get_value(g, p));
1008 }
1009 if (_do_extra) _grad_extra->add_index_to_set(g, grad_param_idx_uniq);
1010 }
1011 std::copy(grad_param_idx_uniq.begin(), grad_param_idx_uniq.end(), std::back_inserter(grad_param_idx));
1012 return grad_param_idx;
1013 }
1014
1015 // Compute Gaussian mixtures with the option to write output and/or evaluate the log likelihood
1016 // TODO: Reconsider whether there's a better way to do this
1017 // The template arguments ensure that there is no performance penalty to any of the versions of this
1018 // function. However, some messy validation is required as a result.
1019 template <OutputType output_type, bool getlikelihood, BackgroundType background_type, bool do_residual,
1020 GradientType gradient_type, bool do_extra>
1021 double gaussians_pixel_template() {
1022 constexpr bool writeoutput = output_type != OutputType::none;
1023 constexpr bool do_gradient = gradient_type != GradientType::none;
1024 constexpr bool is_loglike = gradient_type == GradientType::loglike;
1025
1026 double background_flat = 0;
1027 if (background_type == BackgroundType::constant) {
1028 background_flat += _background->get_value_unchecked(0, 0);
1029 }
1030 const size_t n_gaussians = _gaussians.size();
1031 auto data_null = Data{0, 0};
1032 auto array_null = ImageArray<T, Data>{nullptr};
1033
1034 // TODO: Return to this>IMAGE_NULL(), etc. when DM-45445 is fixed
1035 DataT * output_grad = is_loglike ? &(_grads->at(0)) : nullptr;
1036 ImageArrayT& output_jac_ref = gradient_type == GradientType::jacobian ? (*_grads) : array_null;
1037 size_t grad_param_idx_size = _grad_param_idx.size();
1038
1039 DataT& outputref = writeoutput ? (*_output) : data_null;
1040 DataT& residual_ref = do_residual ? (*_residual) : data_null;
1041 const IndicesT& grad_param_map_ref = do_gradient ? (*_grad_param_map) : INDICES_NULL_CONST();
1042 const DataT& grad_param_factor_ref = do_gradient ? (*_grad_param_factor) : IMAGE_NULL_CONST();
1043
1044 if (writeoutput) {
1045 if (_n_cols != _output->get_n_cols() || _n_rows != _output->get_n_rows()) {
1046 throw std::runtime_error(
1047 "Data matrix dimensions [" + std::to_string(_n_cols) + ',' + std::to_string(_n_rows)
1048 + "] don't match _output matrix dimensions [" + std::to_string(_output->get_n_cols())
1049 + ',' + std::to_string(_output->get_n_rows()) + ']');
1050 }
1051 }
1052
1053 const auto& coordsys = this->get_coordsys();
1054 const double x_min = coordsys.get_x_min();
1055 const double y_min = coordsys.get_y_min();
1056 const double bin_x = coordsys.get_dx1();
1057 const double bin_y = coordsys.get_dy2();
1058 const double bin_x_half = bin_x / 2.;
1059
1060 detail::TermsPixelVec terms_pixel(n_gaussians);
1061 // These are to store pre-computed values for gradients and are unused otherwise
1062 const size_t ngaussgrad = n_gaussians * (do_gradient);
1063 detail::TermsGradientVec terms_grad(ngaussgrad);
1064 std::vector<detail::Weights> weights_grad(n_gaussians * (gradient_type == GradientType::loglike));
1065
1066 std::vector<double> weights_conv(n_gaussians);
1067
1068 auto* grad_extra_ptr = do_extra ? _grad_extra.get() : nullptr;
1069
1070 for (size_t g = 0; g < n_gaussians; ++g) {
1071 const auto& src = _gaussians[g].get_source();
1072 const auto& kernel = _gaussians[g].get_kernel();
1073 const auto weight_src = src.get_integral_value();
1074 const auto weight_kernel = kernel.get_integral_value();
1075 // TODO: intelligently skip if weight is zero
1076 weights_conv[g] = weight_src * weight_kernel;
1077 const double cen_x = src.get_centroid_const().get_x();
1078 const double cen_y = src.get_centroid_const().get_y();
1079 const auto ell_psf = kernel.get_ellipse_const();
1080 const auto ell_src = src.get_ellipse_const();
1081 const Covariance cov_psf = Covariance(ell_psf);
1082 const Covariance cov_src = Covariance(ell_src);
1083 try {
1084 const auto cov_conv = cov_src.make_convolution(cov_psf);
1085 const auto ell_conv = Ellipse(*cov_conv);
1086
1087 // Deliberately omit weights for now
1088 const detail::Terms terms = detail::terms_from_covar(bin_x * bin_y, ell_conv);
1089 auto yvals = detail::gaussian_pixel_x_xx(cen_y, y_min, bin_y, _n_rows, terms.yy, terms.xy);
1090
1091 terms_pixel[g].set(terms.weight, weight_kernel, x_min - cen_x + bin_x_half, terms.xx,
1092 std::move(yvals.x_norm), std::move(yvals.xx));
1093 if (do_gradient) {
1094 terms_grad[g].set(terms, ell_src, ell_psf, ell_conv, std::move(yvals.x));
1095 }
1096 } catch (const std::invalid_argument& err) {
1097 throw std::runtime_error("Failed to convolve cov_src=" + cov_src.str() + " with "
1098 + cov_psf.str() + " due to: " + err.what());
1099 }
1100 }
1101 const DataT& data_ref = getlikelihood ? *_data : IMAGE_NULL_CONST();
1102 const DataT& sigma_inv_ref = getlikelihood ? *_sigma_inv : IMAGE_NULL_CONST();
1103 double loglike = 0;
1104 double model = 0;
1105 double data_pix = 0;
1106 double sigma_inv_pix = 0;
1107 double chi_pix = 0;
1108 detail::ValuesGauss gradients = {0, 0, 0, 0, 0, 0};
1109 // TODO: Consider a version with cached xy, although it doubles memory usage
1110 for (unsigned int i = 0; i < _n_cols; i++) {
1111 for (size_t g = 0; g < n_gaussians; ++g) {
1112 terms_pixel[g].xmc_weighted = terms_pixel[g].xmc * terms_pixel[g].weight_xx;
1113 terms_pixel[g].xmc_sq_norm = terms_pixel[g].xmc_weighted * terms_pixel[g].xmc;
1114 if constexpr (gradient_type != GradientType::none) {
1115 terms_grad[g].xmc_t_xy_weight = terms_pixel[g].xmc * terms_grad[g].xy_weight;
1116 }
1117 }
1118 for (unsigned int j = 0; j < _n_rows; j++) {
1119 model = background_flat;
1120 if constexpr (gradient_type == GradientType::jacobian) {
1121 for (size_t k = 0; k < grad_param_idx_size; ++k) {
1122 output_jac_ref[_grad_param_idx[k]].set_value_unchecked(j, i, 0);
1123 }
1124 }
1125 if constexpr (getlikelihood || (gradient_type == GradientType::jacobian)) {
1126 data_pix = data_ref.get_value_unchecked(j, i);
1127 sigma_inv_pix
1128 = sigma_inv_ref.get_value_unchecked(j * _is_sigma_image, i * _is_sigma_image);
1129 }
1130 for (size_t g = 0; g < n_gaussians; ++g) {
1131 model += detail::gaussian_pixel_add_all<T, Data, Indices, gradient_type, do_extra>(
1132 g, j, i, weights_conv[g], sigma_inv_pix, terms_pixel, output_jac_ref,
1133 grad_param_map_ref, grad_param_factor_ref, weights_grad, terms_grad, gradients,
1134 grad_extra_ptr);
1135 }
1136 if constexpr (output_type == OutputType::overwrite) {
1137 outputref.set_value_unchecked(j, i, model);
1138 } else if constexpr (output_type == OutputType::add) {
1139 outputref.add_value_unchecked(j, i, model);
1140 }
1141 if constexpr (getlikelihood) {
1142 static_assert(getlikelihood);
1143 if constexpr ((gradient_type == GradientType::none)
1144 || (gradient_type == GradientType::jacobian)) {
1145 chi_pix = (data_pix - model) * sigma_inv_pix;
1146 loglike -= chi_pix * chi_pix / 2.;
1147 }
1148 // gaussians_pixel_add_like_grad adds to the loglike to avoid redundant calculations
1149 else if constexpr (gradient_type == GradientType::loglike) {
1150 detail::gaussians_pixel_add_like_grad<T, Data, Indices, do_extra>(
1151 output_grad, grad_param_map_ref, grad_param_factor_ref, n_gaussians,
1152 weights_grad, chi_pix, loglike, model, data_pix, sigma_inv_pix, j, i,
1153 terms_pixel, terms_grad, gradients, _extra_param_map, _extra_param_factor);
1154 }
1155 }
1156 if constexpr (do_residual) residual_ref.set_value_unchecked(j, i, chi_pix);
1157 }
1158 for (size_t g = 0; g < n_gaussians; ++g) terms_pixel[g].xmc += bin_x;
1159 }
1160 return loglike;
1161 }
1162
1163 // See loglike_gaussians_pixel for docs. This is just for explicit template instantiation.
1164 template <OutputType output_type, bool get_likelihood, BackgroundType background_type, bool do_residual,
1165 bool do_extra>
1166 double loglike_gaussians_pixel_getlike() {
1167 // Simplified version for testing compilation (will not generally work at runtime)
1168 /*
1169 return gaussians_pixel_template<output_type, get_likelihood, background_type, do_residual,
1170 GradientType::jacobian, do_extra>();
1171 */
1172 return (_gradienttype == GradientType::loglike)
1173 ? gaussians_pixel_template<output_type, get_likelihood, background_type, do_residual,
1174 GradientType::loglike, do_extra>()
1175 : (_gradienttype == GradientType::jacobian
1176 ? gaussians_pixel_template<output_type, get_likelihood, background_type,
1177 do_residual, GradientType::jacobian, do_extra>()
1178 : gaussians_pixel_template<output_type, get_likelihood, background_type,
1179 do_residual, GradientType::none, do_extra>());
1180 }
1181
1182 // See loglike_gaussians_pixel for docs. This is just for explicit template instantiation.
1183 template <OutputType output_type, bool get_likelihood, BackgroundType background_type>
1184 double loglike_gaussians_pixel_extra() {
1185 // Simplified version for testing compilation (will not generally work at runtime)
1186 /*
1187 return loglike_gaussians_pixel_getlike<output_type, get_likelihood, background_type, true, true>();
1188 */
1189 return _do_residual ? (_do_extra ? loglike_gaussians_pixel_getlike<output_type, get_likelihood,
1190 background_type, true, true>()
1191 : loglike_gaussians_pixel_getlike<output_type, get_likelihood,
1192 background_type, true, false>())
1193 : (_do_extra ? loglike_gaussians_pixel_getlike<output_type, get_likelihood,
1194 background_type, false, true>()
1195 : loglike_gaussians_pixel_getlike<output_type, get_likelihood,
1196 background_type, false, false>());
1197 }
1198
1199 // See loglike_gaussians_pixel for docs. This is just for explicit template instantiation.
1200 template <OutputType output_type>
1201 double loglike_gaussians_pixel_output() {
1202 // Simplified version for testing compilation (will not generally work at runtime)
1203 /*
1204 return loglike_gaussians_pixel_extra<output_type, true, BackgroundType::constant>();
1205 */
1206 return _get_likelihood
1207 ? (_has_background
1208 ? loglike_gaussians_pixel_extra<output_type, true,
1210 : loglike_gaussians_pixel_extra<output_type, true, BackgroundType::none>())
1211 : (_has_background ? loglike_gaussians_pixel_extra<output_type, false,
1213 : loglike_gaussians_pixel_extra<output_type, false,
1214 BackgroundType::none>());
1215 }
1216};
1217
1232template <typename T, class Data, class Indices>
1234 std::shared_ptr<Data> output = nullptr,
1235 const unsigned int n_rows = 0, const unsigned int n_cols = 0,
1236 const std::shared_ptr<const CoordinateSystem> coordsys = nullptr,
1237 bool to_add = false) {
1238 if (output == nullptr) {
1239 if (to_add) {
1240 throw std::invalid_argument("Cannot set to_add if output is nullptr");
1241 }
1242 output = std::make_shared<Data>(n_rows, n_cols, nullptr, coordsys);
1243 }
1244 auto evaluator
1245 = std::make_unique<GaussianEvaluator<T, Data, Indices>>(gaussians, nullptr, nullptr, output);
1246 evaluator->loglike_pixel(to_add);
1247 return output;
1248}
1249} // namespace lsst::gauss2d
1250#endif
char * data
Definition BaseRecord.cc:61
std::shared_ptr< RecordT > src
Definition Match.cc:48
int m
Definition SpanSet.cc:48
T back_inserter(T... args)
T begin(T... args)
A collection of ConvolvedGaussian objects.
Definition gaussian.h:249
A coordinate system specifying image scale and orientation.
std::string str() const override
Return a brief, human-readable string representation of this.
An Ellipse with sigma_x, sigma_y, and rho values.
Definition ellipse.h:283
double get_rho() const override
Get rho.
Definition ellipse.cc:319
double get_sigma_x() const override
Get sigma_x.
Definition ellipse.cc:320
double get_sigma_y() const override
Get sigma_y.
Definition ellipse.cc:321
A class that evaluates 2D Gaussians and renders them in images.
Definition evaluate.h:668
~GaussianEvaluator() override=default
Image< idx_type, Indices > IndicesT
Definition evaluate.h:672
ImageArray< T, Data > const & IMAGEARRAY_NULL_CONST() const
Definition evaluate.h:876
Indices const & INDICES_NULL_CONST() const
Definition evaluate.h:875
Data const & IMAGE_NULL_CONST() const
Definition evaluate.h:874
std::string repr(bool name_keywords=false, std::string_view namespace_separator=Object::CC_NAMESPACE_SEPARATOR) const override
Return a full, callable string representation of this.
Definition evaluate.h:907
std::string str() const override
Return a brief, human-readable string representation of this.
Definition evaluate.h:929
GaussianEvaluator(int x=0, const std::shared_ptr< const ConvolvedGaussians > gaussians=nullptr)
Definition evaluate.h:674
ImageArray< T, Data > ImageArrayT
Definition evaluate.h:671
const CoordinateSystem & get_coordsys() const
Definition evaluate.h:878
double loglike_pixel(bool to_add=false)
Compute the model and/or log-likelihood and/or gradient (d(log-likelihood)/dx) and/or Jacobian (dmode...
Definition evaluate.h:897
GaussianEvaluator(const std::shared_ptr< const ConvolvedGaussians > gaussians, const std::shared_ptr< const Image< T, Data > > data=nullptr, const std::shared_ptr< const Image< T, Data > > sigma_inv=nullptr, const std::shared_ptr< Image< T, Data > > output=nullptr, const std::shared_ptr< Image< T, Data > > residual=nullptr, const std::shared_ptr< ImageArrayT > grads=nullptr, const std::shared_ptr< const Image< idx_type, Indices > > grad_param_map=nullptr, const std::shared_ptr< const Image< T, Data > > grad_param_factor=nullptr, const std::shared_ptr< const Image< idx_type, Indices > > extra_param_map=nullptr, const std::shared_ptr< const Image< T, Data > > extra_param_factor=nullptr, const std::shared_ptr< const Image< T, Data > > background=nullptr)
Construct a GaussianEvaluator, inferring outputs from inputs.
Definition evaluate.h:699
An array of compatible Images.
Definition image.h:268
A 2D image with scalar numeric values, using CRTP.
Definition image.h:107
std::string str() const override
Return a brief, human-readable string representation of this.
Definition image.h:197
T get_value_unchecked(size_t row, size_t col) const
Definition image.h:173
T get_value(size_t row, size_t col) const
Definition image.h:168
size_t get_n_rows() const
Definition image.h:147
size_t get_n_cols() const
Definition image.h:145
std::string repr(bool name_keywords, std::string_view namespace_separator) const override
Return a full, callable string representation of this.
Definition image.h:190
T & _get_value_unchecked(size_t row, size_t col)
Definition image.h:128
A generic object from the gauss2d library.
Definition object.h:40
static constexpr std::string_view CC_NAMESPACE_SEPARATOR
The C++ namespace separator.
Definition object.h:45
void add_index_to_set(size_t g, std::set< size_t > &set)
Definition evaluate.h:362
std::string repr(bool name_keywords=false, std::string_view namespace_separator=Object::CC_NAMESPACE_SEPARATOR) const override
Return a full, callable string representation of this.
Definition evaluate.h:376
std::string str() const override
Return a brief, human-readable string representation of this.
Definition evaluate.h:388
GradientsExtra(const Image< idx_type, Indices > &param_map_in, const Image< t, Data > &param_factor_in, std::shared_ptr< ImageArray< t, Data > > output, size_t n_gauss)
Definition evaluate.h:332
void add(size_t g, size_t dim1, size_t dim2, const ValuesGauss &gradients)
Definition evaluate.h:364
void set(const Terms &terms, const Ellipse &ell_src, const Ellipse &ell_psf, const Ellipse &ell, vecdptr ymc_)
Definition evaluate.h:269
TermsGradient(vecdptr ymc_i=nullptr, double xx_weight_i=0, double xy_weight_i=0, double yy_weight_i=0, double rho_factor_i=0, double sig_x_inv_i=0, double sig_y_inv_i=0, double rho_xy_factor_i=0, double sig_x_src_div_conv_i=0, double sig_y_src_div_conv_i=0, double drho_c_dsig_x_src_i=0, double drho_c_dsig_y_src_i=0, double drho_c_drho_s_i=0, double xmc_t_xy_weight_i=0)
Definition evaluate.h:248
Storage for terms common to Gaussians for a single pixel.
Definition evaluate.h:194
TermsPixel(double weight_i=0, double weight_kernel=0, double xmc_i=0, double xmc_weighted_i=0, vecdptr ymc_weighted_i=nullptr, double weight_xx_i=0, double xmc_sq_norm_i=0, vecdptr yy_weighted_i=nullptr)
Definition evaluate.h:206
void set(double weight_, double weight_kernel_, double xmc_, double xx, vecdptr ymc_weighted_, vecdptr yy_weighted_)
Definition evaluate.h:218
T copy(T... args)
T empty(T... args)
T end(T... args)
T get(T... args)
T insert(T... args)
T move(T... args)
void gaussians_pixel_add_like_grad(Image< t, Data > *output, const Image< idx_type, Indices > &grad_param_map, const Image< t, Data > &grad_param_factor, const size_t N_GAUSS, const std::vector< Weights > &gaussweights, double &chi_pix, double &loglike, const double model, double data, double sigma_inv, unsigned int dim1, unsigned int dim2, const TermsPixelVec &terms_pixel, const TermsGradientVec &terms_grad, ValuesGauss &gradients, const std::shared_ptr< const Image< idx_type, Indices > > extra_param_map, const std::shared_ptr< const Image< t, Data > > extra_param_factor)
Definition evaluate.h:548
std::array< double, N_PARAMS_GAUSS2D > Weights
Definition evaluate.h:179
std::vector< double > vecd
Definition evaluate.h:65
const std::shared_ptr< const Data > _param_factor_default(size_t n_gaussians, size_t n_params=N_PARAMS_GAUSS2D, double value=1.)
Definition evaluate.h:644
std::vector< TermsPixel > TermsPixelVec
Definition evaluate.h:229
void gaussian_pixel_get_jacobian_from_terms(ValuesGauss &out, const size_t dim1, const TermsPixel &terms_pixel, const TermsGradient &terms_grad, double m, double m_unweighted, double xy_norm)
Definition evaluate.h:519
Terms terms_from_covar(double weight, const Ellipse &ell)
Definition evaluate.cc:30
const std::shared_ptr< const Indices > _param_map_default(size_t n_gaussians, size_t n_params=N_PARAMS_GAUSS2D, size_t increment=1)
Definition evaluate.h:629
std::unique_ptr< vecd > vecdptr
Definition evaluate.h:66
T gaussian_pixel_add_all(size_t g, size_t j, size_t i, double weight, double sigma_inv, const TermsPixelVec &terms_pixel_vec, ImageArray< T, Data > &output_jac_ref, const Image< idx_type, Indices > &grad_param_map, const Image< T, Data > &grad_param_factor, std::vector< Weights > &gradweights, const TermsGradientVec &terms_grad_vec, ValuesGauss &gradients, GradientsExtra< T, Data, Indices > *grad_extra)
Definition evaluate.h:592
void gaussian_pixel(Data &image, const double weight, const double cen_x, const double cen_y, const double xx_weight, const double yy_weight, const double xy_weight)
Definition evaluate.h:407
TermsMoments gaussian_pixel_x_xx(const double cen_x, const double x_min, const double bin_x, const unsigned int dim_x, const double xx_weight, const double xy_weight)
Definition evaluate.h:83
std::vector< TermsGradient > TermsGradientVec
Definition evaluate.h:327
void gaussian_pixel_get_jacobian(ValuesGauss &out, const double m, const double m_unweight, const double xmc_norm, const double ymc_weighted, const double xmc, const double ymc, const double norms_yy, const double xmc_t_xy_factor, const double sig_x_inv, const double sig_y_inv, const double xy_norm, const double xx_norm, const double yy_weighted, const double rho_div_one_m_rhosq, const double norms_xy_div_rho, const double dsig_x_conv_src=1, const double dsig_y_conv_src=1, const double drho_c_dsig_x_src=0, const double drho_c_dsig_y_src=0, const double drho_c_drho_s=1)
Definition evaluate.h:495
void gaussian_pixel_add_values(t &cen_x, t &cen_y, t &L, t &sig_x, t &sig_y, t &rho, const ValuesGauss &values, const t weight_cen_x=1, const t weight_cen_y=1, const t weight_L=1, const t weight_sig_x=1, const t weight_sig_y=1, const t weight_rho=1)
Definition evaluate.h:533
std::string str_ptr(T ptr)
Definition object.h:132
std::string repr_ptr(T ptr, bool name_keywords, std::string_view namespace_separator)
Definition object.h:82
std::shared_ptr< Data > make_gaussians_pixel(const std::shared_ptr< const ConvolvedGaussians > gaussians, std::shared_ptr< Data > output=nullptr, const unsigned int n_rows=0, const unsigned int n_cols=0, const std::shared_ptr< const CoordinateSystem > coordsys=nullptr, bool to_add=false)
Add gaussians to an image, creating one if needed.
Definition evaluate.h:1233
const size_t N_EXTRA_FACTOR
Definition evaluate.h:60
const size_t N_EXTRA_MAP
Definition evaluate.h:59
const size_t N_PARAMS_GAUSS2D
Definition evaluate.h:61
std::string to_string_iter(const Container< Value > &container)
Definition to_string.h:38
STL namespace.
model(data, psfmodels, sources)
T size(T... args)
afw::table::Key< double > weight
T to_string(T... args)