SourceXtractorPlusPlus 1.0.3
SourceXtractor++, the next generation SExtractor
Loading...
Searching...
No Matches
ModelFittingConfig.cpp
Go to the documentation of this file.
1
18/*
19 * @file ModelFittingConfig.cpp
20 * @author Nikolaos Apostolakos <nikoapos@gmail.com>
21 */
22
23#include <string>
24#include <boost/python/extract.hpp>
25#include <boost/python/object.hpp>
26#include <boost/python/tuple.hpp>
27#include <boost/python/dict.hpp>
28
29#include "ElementsKernel/Logging.h"
30
32
38#include "Pyston/GIL.h"
39#include "Pyston/Exceptions.h"
40#include "Pyston/ExpressionTreeBuilder.h"
41#include "Pyston/Util/GraphvizGenerator.h"
42
43#include <string>
44#include <boost/python/extract.hpp>
45#include <boost/python/object.hpp>
46
47#ifdef WITH_ONNX_MODELS
49#endif
50
52
53
54using namespace Euclid::Configuration;
55namespace py = boost::python;
57
59
60namespace SourceXtractor {
61
62template<typename Signature>
64};
65
66template<>
67struct FunctionFromPython<double(const SourceInterface&)> {
68 static
69 std::function<double(const SourceInterface&)> get(const char *readable,
71 py::object py_func,
72 const AssocModeConfig& config) {
73 auto wrapped = builder.build<double(const AttributeSet&)>(py_func, ObjectInfo(config));
74
75 if (!wrapped.isCompiled()) {
76 logger.warn() << "Could not compile " << readable << ": " << wrapped.reason()->what();
77 wrapped.reason()->log(log4cpp::Priority::DEBUG, logger);
78 }
79 else {
80 logger.info() << readable << " compiled";
81 Pyston::GraphvizGenerator gv(readable);
82 wrapped.getTree()->visit(gv);
83 logger.debug() << gv.str();
84 }
85
86 return [wrapped, config](const SourceInterface& o) -> double {
87 return wrapped(ObjectInfo(o, config));
88 };
89 }
90};
91
92template<>
93struct FunctionFromPython<double(const Pyston::Context&, const std::vector<double>&)> {
94 static
96 get(const char *readable, Pyston::ExpressionTreeBuilder& builder, py::object py_func,
97 size_t nparams) {
98 auto wrapped = builder.build<double(const std::vector<double>&)>(py_func, nparams);
99 if (!wrapped.isCompiled()) {
100 logger.warn() << "Could not compile " << readable << ": " << wrapped.reason()->what();
101 wrapped.reason()->log(log4cpp::Priority::DEBUG, logger);
102 }
103 else {
104 logger.info() << readable << " compiled";
105 Pyston::GraphvizGenerator gv(readable);
106 wrapped.getTree()->visit(gv);
107 logger.debug() << gv.str();
108 }
109
110 return wrapped;
111 }
112};
113
114template<>
115struct FunctionFromPython<double(double, const SourceInterface&)> {
116 static
117 std::function<double(double, const SourceInterface&)> get(const char *readable,
119 py::object py_func,
120 const AssocModeConfig& config) {
121 auto wrapped = builder.build<double(double, const AttributeSet&)>(py_func, ObjectInfo(config));
122
123 if (!wrapped.isCompiled()) {
124 logger.warn() << "Could not compile " << readable << ": " << wrapped.reason()->what();
125 wrapped.reason()->log(log4cpp::Priority::DEBUG, logger);
126 }
127 else {
128 logger.info() << readable << " compiled";
129 Pyston::GraphvizGenerator gv(readable);
130 wrapped.getTree()->visit(gv);
131 logger.debug() << gv.str();
132 }
133
134 return [wrapped, config](double a, const SourceInterface& o) -> double {
135 return wrapped(a, ObjectInfo(o, config));
136 };
137 }
138};
139
143
145 Pyston::GILLocker locker;
146 m_parameters.clear();
147 m_models.clear();
148 m_frames.clear();
149 m_priors.clear();
150 m_outputs.clear();
151}
152
154 Pyston::GILLocker locker;
155 try {
157 } catch (Pyston::Exception& e) {
158 throw e.log(log4cpp::Priority::ERROR, logger);
159 } catch (py::error_already_set& e) {
160 throw Pyston::Exception().log(log4cpp::Priority::ERROR, logger);
161 }
162}
163
164static double image_to_world_alpha(const Pyston::Context& context, double x, double y) {
165 auto coord_system = boost::any_cast<std::shared_ptr<CoordinateSystem>>(context.at("coordinate_system"));
166 return coord_system->imageToWorld({x, y}).m_alpha;
167}
168
169static double image_to_world_delta(const Pyston::Context& context, double x, double y) {
170 auto coord_system = boost::any_cast<std::shared_ptr<CoordinateSystem>>(context.at("coordinate_system"));
171 return coord_system->imageToWorld({x, y}).m_delta;
172}
173
176 expr_builder.registerFunction<double(const Pyston::Context&, double, double)>(
177 "image_to_world_alpha", image_to_world_alpha
178 );
179 expr_builder.registerFunction<double(const Pyston::Context&, double, double)>(
180 "image_to_world_delta", image_to_world_delta
181 );
182
183 /* Constant parameters */
184 for (auto& p : getDependency<PythonConfig>().getInterpreter().getConstantParameters()) {
185 auto value_func = FunctionFromPython<double(const SourceInterface&)>::get(
186 "Constant parameter", expr_builder, p.second.attr("get_value"), getDependency<AssocModeConfig>()
187 );
188
190 p.first, value_func);
191 }
192
193 /* Free parameters */
194 for (auto& p : getDependency<PythonConfig>().getInterpreter().getFreeParameters()) {
195 auto init_value_func = FunctionFromPython<double(const SourceInterface&)>::get(
196 "Free parameter", expr_builder, p.second.attr("get_init_value"), getDependency<AssocModeConfig>()
197 );
198
199 auto py_range_obj = p.second.attr("get_range")();
200
202 std::string type_string(py::extract<char const*>(py_range_obj.attr("__class__").attr("__name__")));
203
204 if (type_string == "Unbounded") {
205 auto factor_func = FunctionFromPython<double(double, const SourceInterface&)>::get(
206 "Unbounded", expr_builder, py_range_obj.attr("get_normalization_factor"), getDependency<AssocModeConfig>()
207 );
209 } else if (type_string == "Range") {
210 auto min_func = FunctionFromPython<double(double, const SourceInterface&)>::get(
211 "Range min", expr_builder, py_range_obj.attr("get_min"), getDependency<AssocModeConfig>()
212 );
213 auto max_func = FunctionFromPython<double(double, const SourceInterface&)>::get(
214 "Range max", expr_builder, py_range_obj.attr("get_max"), getDependency<AssocModeConfig>()
215 );
216
217 auto range_func = [min_func, max_func] (double init, const SourceInterface& o) -> std::pair<double, double> {
218 return {min_func(init, o), max_func(init, o)};
219 };
220
221 bool is_exponential = py::extract<int>(py_range_obj.attr("get_type")().attr("value")) == 2;
222
223 if (is_exponential) {
225 } else {
227 }
228 } else {
229 throw Elements::Exception("Unknown converter type: " + type_string);
230 }
232 p.first, init_value_func, converter);
233 }
234
235 /* Dependent parameters */
236 for (auto& p : getDependency<PythonConfig>().getInterpreter().getDependentParameters()) {
237 auto py_func = p.second.attr("func");
239 py::list dependees = py::extract<py::list>(p.second.attr("params"));
240 for (int i = 0; i < py::len(dependees); ++i) {
241 int id = py::extract<int>(dependees[i].attr("id"));
242 params.push_back(m_parameters.at(id));
243 }
244
245 auto dependent = FunctionFromPython<double(const Pyston::Context&, const std::vector<double>&)>
246 ::get("Dependent parameter", expr_builder, py_func, params.size());
247
248 auto dependent_func = [dependent](const std::shared_ptr<CoordinateSystem> &cs, const std::vector<double> &params) -> double {
249 Pyston::Context context;
250 context["coordinate_system"] = cs;
251 return dependent(context, params);
252 };
253
255 p.first, dependent_func, params);
256 }
257
258 for (auto& p : getDependency<PythonConfig>().getInterpreter().getConstantModels()) {
259 int value_id = py::extract<int>(p.second.attr("value").attr("id"));
261 }
262
263 for (auto& p : getDependency<PythonConfig>().getInterpreter().getPointSourceModels()) {
264 int x_coord_id = py::extract<int>(p.second.attr("x_coord").attr("id"));
265 int y_coord_id = py::extract<int>(p.second.attr("y_coord").attr("id"));
266 int flux_id = py::extract<int>(p.second.attr("flux").attr("id"));
268 m_parameters.at(x_coord_id), m_parameters.at(y_coord_id), m_parameters.at(flux_id));
269 }
270
271 for (auto& p : getDependency<PythonConfig>().getInterpreter().getSersicModels()) {
272 int x_coord_id = py::extract<int>(p.second.attr("x_coord").attr("id"));
273 int y_coord_id = py::extract<int>(p.second.attr("y_coord").attr("id"));
274 int flux_id = py::extract<int>(p.second.attr("flux").attr("id"));
275 int effective_radius_id = py::extract<int>(p.second.attr("effective_radius").attr("id"));
276 int aspect_ratio_id = py::extract<int>(p.second.attr("aspect_ratio").attr("id"));
277 int angle_id = py::extract<int>(p.second.attr("angle").attr("id"));
278 int n_id = py::extract<int>(p.second.attr("n").attr("id"));
280 m_parameters.at(x_coord_id), m_parameters.at(y_coord_id), m_parameters.at(flux_id), m_parameters.at(n_id),
281 m_parameters.at(effective_radius_id), m_parameters.at(aspect_ratio_id),
282 m_parameters.at(angle_id));
283 }
284
285 for (auto& p : getDependency<PythonConfig>().getInterpreter().getExponentialModels()) {
286 int x_coord_id = py::extract<int>(p.second.attr("x_coord").attr("id"));
287 int y_coord_id = py::extract<int>(p.second.attr("y_coord").attr("id"));
288 int flux_id = py::extract<int>(p.second.attr("flux").attr("id"));
289 int effective_radius_id = py::extract<int>(p.second.attr("effective_radius").attr("id"));
290 int aspect_ratio_id = py::extract<int>(p.second.attr("aspect_ratio").attr("id"));
291 int angle_id = py::extract<int>(p.second.attr("angle").attr("id"));
293 m_parameters.at(x_coord_id), m_parameters.at(y_coord_id), m_parameters.at(flux_id),
294 m_parameters.at(effective_radius_id), m_parameters.at(aspect_ratio_id), m_parameters.at(angle_id));
295 }
296
297 for (auto& p : getDependency<PythonConfig>().getInterpreter().getDeVaucouleursModels()) {
298 int x_coord_id = py::extract<int>(p.second.attr("x_coord").attr("id"));
299 int y_coord_id = py::extract<int>(p.second.attr("y_coord").attr("id"));
300 int flux_id = py::extract<int>(p.second.attr("flux").attr("id"));
301 int effective_radius_id = py::extract<int>(p.second.attr("effective_radius").attr("id"));
302 int aspect_ratio_id = py::extract<int>(p.second.attr("aspect_ratio").attr("id"));
303 int angle_id = py::extract<int>(p.second.attr("angle").attr("id"));
305 m_parameters.at(x_coord_id), m_parameters.at(y_coord_id), m_parameters.at(flux_id),
306 m_parameters.at(effective_radius_id), m_parameters.at(aspect_ratio_id), m_parameters.at(angle_id));
307 }
308
309#ifdef WITH_ONNX_MODELS
310 for (auto& p : getDependency<PythonConfig>().getInterpreter().getOnnxModels()) {
311 int x_coord_id = py::extract<int>(p.second.attr("x_coord").attr("id"));
312 int y_coord_id = py::extract<int>(p.second.attr("y_coord").attr("id"));
313 int flux_id = py::extract<int>(p.second.attr("flux").attr("id"));
314 int aspect_ratio_id = py::extract<int>(p.second.attr("aspect_ratio").attr("id"));
315 int angle_id = py::extract<int>(p.second.attr("angle").attr("id"));
316 int scale_id = py::extract<int>(p.second.attr("scale").attr("id"));
317
319 py::dict parameters = py::extract<py::dict>(p.second.attr("params"));
320 py::list names = parameters.keys();
321 for (int i = 0; i < py::len(names); ++i) {
322 std::string name = py::extract<std::string>(names[i]);
323 params[name] = m_parameters.at(py::extract<int>(parameters[names[i]].attr("id")));
324 }
325
327 py::list models = py::extract<py::list>(p.second.attr("models"));
328 for (int i = 0; i < py::len(models); ++i) {
329 std::string model_filename = py::extract<std::string>(models[i]);
330 onnx_models.emplace_back(std::make_shared<OnnxModel>(model_filename));
331
332 if (onnx_models.back()->getOutputType() != ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT ||
333 onnx_models.back()->getOutputShape().size() != 4 ||
334 onnx_models.back()->getOutputShape()[1] != onnx_models.back()->getOutputShape()[2] ||
335 onnx_models.back()->getOutputShape()[3] != 1)
336 {
337 throw Elements::Exception() << "ONNX models for ModelFitting must output a square array of floats";
338 }
339 }
340
342 onnx_models, m_parameters.at(x_coord_id), m_parameters.at(y_coord_id), m_parameters.at(flux_id),
343 m_parameters.at(aspect_ratio_id), m_parameters.at(angle_id), m_parameters.at(scale_id), params);
344 }
345#else
346 if (getDependency<PythonConfig>().getInterpreter().getOnnxModels().size() > 0) {
347 throw Elements::Exception("Trying to use ONNX models but ONNX support is not available");
348 }
349#endif
350
351 for (auto& p : getDependency<PythonConfig>().getInterpreter().getFrameModelsMap()) {
353 for (int x : p.second) {
354 model_list.push_back(m_models[x]);
355 }
356 m_frames.push_back(std::make_shared<FlexibleModelFittingFrame>(p.first, model_list));
357 }
358
359 for (auto& p : getDependency<PythonConfig>().getInterpreter().getPriors()) {
360 auto& prior = p.second;
361 int param_id = py::extract<int>(prior.attr("param"));
362 auto param = m_parameters.at(param_id);
363
364 auto value_func = FunctionFromPython<double(const SourceInterface&)>::get(
365 "Prior mean", expr_builder, prior.attr("value"), getDependency<AssocModeConfig>()
366 );
367 auto sigma_func = FunctionFromPython<double(const SourceInterface&)>::get(
368 "Prior sigma", expr_builder, prior.attr("sigma"), getDependency<AssocModeConfig>()
369 );
370
371 m_priors[p.first] = std::make_shared<FlexibleModelFittingPrior>(param, value_func, sigma_func);
372 }
373
374 m_outputs = getDependency<PythonConfig>().getInterpreter().getModelFittingOutputColumns();
375
376 auto parameters = getDependency<PythonConfig>().getInterpreter().getModelFittingParams();
377 m_least_squares_engine = py::extract<std::string>(parameters["engine"]);
378 if (m_least_squares_engine.empty()) {
380 }
381 m_max_iterations = py::extract<int>(parameters["max_iterations"]);
382 m_modified_chi_squared_scale = py::extract<double>(parameters["modified_chi_squared_scale"]);
383 m_use_iterative_fitting = py::extract<bool>(parameters["use_iterative_fitting"]);
384 m_meta_iterations = py::extract<int>(parameters["meta_iterations"]);
385 m_deblend_factor = py::extract<double>(parameters["deblend_factor"]);
386 m_meta_iteration_stop = py::extract<double>(parameters["meta_iteration_stop"]);
388 py::extract<int>(parameters["window_type"].attr("value"))());
389 m_ellipse_scale = py::extract<double>(parameters["ellipse_scale"]);
390}
391
395
399
403
407
411
412}
static Elements::Logging logger
T at(T... args)
T back(T... args)
static Logging getLogger(const std::string &name="")
std::map< std::string, boost::program_options::variable_value > UserValues
const Exception & log(log4cpp::Priority::Value level, Elements::Logging &logger) const
ExpressionTree< Signature > build(const boost::python::object &pyfunc, BuildParams &&... build_params) const
void registerFunction(const std::string &repr, std::function< Signature > functor)
std::string str() const
const std::vector< std::pair< std::string, std::vector< int > > > & getOutputs() const
std::map< int, std::shared_ptr< FlexibleModelFittingParameter > > m_parameters
const std::vector< std::shared_ptr< FlexibleModelFittingFrame > > & getFrames() const
const std::map< int, std::shared_ptr< FlexibleModelFittingModel > > & getModels() const
const std::map< int, std::shared_ptr< FlexibleModelFittingPrior > > & getPriors() const
const std::map< int, std::shared_ptr< FlexibleModelFittingParameter > > & getParameters() const
std::map< int, std::shared_ptr< FlexibleModelFittingPrior > > m_priors
std::map< int, std::shared_ptr< FlexibleModelFittingModel > > m_models
std::vector< std::pair< std::string, std::vector< int > > > m_outputs
void initialize(const UserValues &args) override
std::vector< std::shared_ptr< FlexibleModelFittingFrame > > m_frames
FlexibleModelFittingIterativeTask::WindowType m_window_type
The SourceInterface is an abstract "source" that has properties attached to it.
T emplace_back(T... args)
T make_shared(T... args)
static Elements::Logging logger
std::map< std::string, boost::any > Context
std::map< std::string, Attribute > AttributeSet
static double image_to_world_delta(const Pyston::Context &context, double x, double y)
static double image_to_world_alpha(const Pyston::Context &context, double x, double y)
T push_back(T... args)
T size(T... args)
static std::function< double(const Pyston::Context &, const std::vector< double > &)> get(const char *readable, Pyston::ExpressionTreeBuilder &builder, py::object py_func, size_t nparams)
static std::function< double(const SourceInterface &)> get(const char *readable, Pyston::ExpressionTreeBuilder &builder, py::object py_func, const AssocModeConfig &config)
static std::function< double(double, const SourceInterface &)> get(const char *readable, Pyston::ExpressionTreeBuilder &builder, py::object py_func, const AssocModeConfig &config)