SourceXtractorPlusPlus 0.19.2
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
30
32
38#include "Pyston/GIL.h"
39#include "Pyston/Exceptions.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 auto wrapped = builder.build<double(const AttributeSet&)>(py_func, ObjectInfo{});
73
74 if (!wrapped.isCompiled()) {
75 logger.warn() << "Could not compile " << readable << ": " << wrapped.reason()->what();
76 wrapped.reason()->log(log4cpp::Priority::DEBUG, logger);
77 }
78 else {
79 logger.info() << readable << " compiled";
80 Pyston::GraphvizGenerator gv(readable);
81 wrapped.getTree()->visit(gv);
82 logger.debug() << gv.str();
83 }
84
85 return [wrapped](const SourceInterface& o) -> double {
86 return wrapped(ObjectInfo{o});
87 };
88 }
89};
90
91template<>
92struct FunctionFromPython<double(const Pyston::Context&, const std::vector<double>&)> {
93 static
95 get(const char *readable, Pyston::ExpressionTreeBuilder& builder, py::object py_func,
96 size_t nparams) {
97 auto wrapped = builder.build<double(const std::vector<double>&)>(py_func, nparams);
98 if (!wrapped.isCompiled()) {
99 logger.warn() << "Could not compile " << readable << ": " << wrapped.reason()->what();
100 wrapped.reason()->log(log4cpp::Priority::DEBUG, logger);
101 }
102 else {
103 logger.info() << readable << " compiled";
104 Pyston::GraphvizGenerator gv(readable);
105 wrapped.getTree()->visit(gv);
106 logger.debug() << gv.str();
107 }
108
109 return wrapped;
110 }
111};
112
113template<>
114struct FunctionFromPython<double(double, const SourceInterface&)> {
115 static
116 std::function<double(double, const SourceInterface&)> get(const char *readable,
118 py::object py_func) {
119 auto wrapped = builder.build<double(double, const AttributeSet&)>(py_func, ObjectInfo{});
120
121 if (!wrapped.isCompiled()) {
122 logger.warn() << "Could not compile " << readable << ": " << wrapped.reason()->what();
123 wrapped.reason()->log(log4cpp::Priority::DEBUG, logger);
124 }
125 else {
126 logger.info() << readable << " compiled";
127 Pyston::GraphvizGenerator gv(readable);
128 wrapped.getTree()->visit(gv);
129 logger.debug() << gv.str();
130 }
131
132 return [wrapped](double a, const SourceInterface& o) -> double {
133 return wrapped(a, ObjectInfo{o});
134 };
135 }
136};
137
139 declareDependency<PythonConfig>();
140}
141
143 Pyston::GILLocker locker;
144 m_parameters.clear();
145 m_models.clear();
146 m_frames.clear();
147 m_priors.clear();
149}
150
152 Pyston::GILLocker locker;
153 try {
155 } catch (Pyston::Exception& e) {
156 throw e.log(log4cpp::Priority::ERROR, logger);
157 } catch (py::error_already_set& e) {
158 throw Pyston::Exception().log(log4cpp::Priority::ERROR, logger);
159 }
160}
161
162static double image_to_world_alpha(const Pyston::Context& context, double x, double y) {
163 auto coord_system = boost::any_cast<std::shared_ptr<CoordinateSystem>>(context.at("coordinate_system"));
164 return coord_system->imageToWorld({x, y}).m_alpha;
165}
166
167static double image_to_world_delta(const Pyston::Context& context, double x, double y) {
168 auto coord_system = boost::any_cast<std::shared_ptr<CoordinateSystem>>(context.at("coordinate_system"));
169 return coord_system->imageToWorld({x, y}).m_delta;
170}
171
174 expr_builder.registerFunction<double(const Pyston::Context&, double, double)>(
175 "image_to_world_alpha", image_to_world_alpha
176 );
177 expr_builder.registerFunction<double(const Pyston::Context&, double, double)>(
178 "image_to_world_delta", image_to_world_delta
179 );
180
181 /* Constant parameters */
182 for (auto& p : getDependency<PythonConfig>().getInterpreter().getConstantParameters()) {
183 auto value_func = FunctionFromPython<double(const SourceInterface&)>::get(
184 "Constant parameter", expr_builder, p.second.attr("get_value")
185 );
186
187 m_parameters[p.first] = std::make_shared<FlexibleModelFittingConstantParameter>(
188 p.first, value_func);
189 }
190
191 /* Free parameters */
192 for (auto& p : getDependency<PythonConfig>().getInterpreter().getFreeParameters()) {
193 auto init_value_func = FunctionFromPython<double(const SourceInterface&)>::get(
194 "Free parameter", expr_builder, p.second.attr("get_init_value")
195 );
196
197 auto py_range_obj = p.second.attr("get_range")();
198
200 std::string type_string(py::extract<char const*>(py_range_obj.attr("__class__").attr("__name__")));
201
202 if (type_string == "Unbounded") {
203 auto factor_func = FunctionFromPython<double(double, const SourceInterface&)>::get(
204 "Unbounded", expr_builder, py_range_obj.attr("get_normalization_factor")
205 );
206 converter = std::make_shared<FlexibleModelFittingUnboundedConverterFactory>(factor_func);
207 } else if (type_string == "Range") {
208 auto min_func = FunctionFromPython<double(double, const SourceInterface&)>::get(
209 "Range min", expr_builder, py_range_obj.attr("get_min")
210 );
211 auto max_func = FunctionFromPython<double(double, const SourceInterface&)>::get(
212 "Range max", expr_builder, py_range_obj.attr("get_max")
213 );
214
215 auto range_func = [min_func, max_func] (double init, const SourceInterface& o) -> std::pair<double, double> {
216 return {min_func(init, o), max_func(init, o)};
217 };
218
219 bool is_exponential = py::extract<int>(py_range_obj.attr("get_type")().attr("value")) == 2;
220
221 if (is_exponential) {
222 converter = std::make_shared<FlexibleModelFittingExponentialRangeConverterFactory>(range_func);
223 } else {
224 converter = std::make_shared<FlexibleModelFittingLinearRangeConverterFactory>(range_func);
225 }
226 } else {
227 throw Elements::Exception("Unknown converter type: " + type_string);
228 }
229 m_parameters[p.first] = std::make_shared<FlexibleModelFittingFreeParameter>(
230 p.first, init_value_func, converter);
231 }
232
233 /* Dependent parameters */
234 for (auto& p : getDependency<PythonConfig>().getInterpreter().getDependentParameters()) {
235 auto py_func = p.second.attr("func");
237 py::list dependees = py::extract<py::list>(p.second.attr("params"));
238 for (int i = 0; i < py::len(dependees); ++i) {
239 int id = py::extract<int>(dependees[i].attr("id"));
240 params.push_back(m_parameters.at(id));
241 }
242
243 auto dependent = FunctionFromPython<double(const Pyston::Context&, const std::vector<double>&)>
244 ::get("Dependent parameter", expr_builder, py_func, params.size());
245
246 auto dependent_func = [dependent](const std::shared_ptr<CoordinateSystem> &cs, const std::vector<double> &params) -> double {
247 Pyston::Context context;
248 context["coordinate_system"] = cs;
249 return dependent(context, params);
250 };
251
252 m_parameters[p.first] = std::make_shared<FlexibleModelFittingDependentParameter>(
253 p.first, dependent_func, params);
254 }
255
256 for (auto& p : getDependency<PythonConfig>().getInterpreter().getConstantModels()) {
257 int value_id = py::extract<int>(p.second.attr("value").attr("id"));
258 m_models[p.first] = std::make_shared<FlexibleModelFittingConstantModel>(m_parameters.at(value_id));
259 }
260
261 for (auto& p : getDependency<PythonConfig>().getInterpreter().getPointSourceModels()) {
262 int x_coord_id = py::extract<int>(p.second.attr("x_coord").attr("id"));
263 int y_coord_id = py::extract<int>(p.second.attr("y_coord").attr("id"));
264 int flux_id = py::extract<int>(p.second.attr("flux").attr("id"));
265 m_models[p.first] = std::make_shared<FlexibleModelFittingPointModel>(
266 m_parameters.at(x_coord_id), m_parameters.at(y_coord_id), m_parameters.at(flux_id));
267 }
268
269 for (auto& p : getDependency<PythonConfig>().getInterpreter().getSersicModels()) {
270 int x_coord_id = py::extract<int>(p.second.attr("x_coord").attr("id"));
271 int y_coord_id = py::extract<int>(p.second.attr("y_coord").attr("id"));
272 int flux_id = py::extract<int>(p.second.attr("flux").attr("id"));
273 int effective_radius_id = py::extract<int>(p.second.attr("effective_radius").attr("id"));
274 int aspect_ratio_id = py::extract<int>(p.second.attr("aspect_ratio").attr("id"));
275 int angle_id = py::extract<int>(p.second.attr("angle").attr("id"));
276 int n_id = py::extract<int>(p.second.attr("n").attr("id"));
277 m_models[p.first] = std::make_shared<FlexibleModelFittingSersicModel>(
278 m_parameters.at(x_coord_id), m_parameters.at(y_coord_id), m_parameters.at(flux_id), m_parameters.at(n_id),
279 m_parameters.at(effective_radius_id), m_parameters.at(aspect_ratio_id),
280 m_parameters.at(angle_id));
281 }
282
283 for (auto& p : getDependency<PythonConfig>().getInterpreter().getExponentialModels()) {
284 int x_coord_id = py::extract<int>(p.second.attr("x_coord").attr("id"));
285 int y_coord_id = py::extract<int>(p.second.attr("y_coord").attr("id"));
286 int flux_id = py::extract<int>(p.second.attr("flux").attr("id"));
287 int effective_radius_id = py::extract<int>(p.second.attr("effective_radius").attr("id"));
288 int aspect_ratio_id = py::extract<int>(p.second.attr("aspect_ratio").attr("id"));
289 int angle_id = py::extract<int>(p.second.attr("angle").attr("id"));
290 m_models[p.first] = std::make_shared<FlexibleModelFittingExponentialModel>(
291 m_parameters.at(x_coord_id), m_parameters.at(y_coord_id), m_parameters.at(flux_id),
292 m_parameters.at(effective_radius_id), m_parameters.at(aspect_ratio_id), m_parameters.at(angle_id));
293 }
294
295 for (auto& p : getDependency<PythonConfig>().getInterpreter().getDeVaucouleursModels()) {
296 int x_coord_id = py::extract<int>(p.second.attr("x_coord").attr("id"));
297 int y_coord_id = py::extract<int>(p.second.attr("y_coord").attr("id"));
298 int flux_id = py::extract<int>(p.second.attr("flux").attr("id"));
299 int effective_radius_id = py::extract<int>(p.second.attr("effective_radius").attr("id"));
300 int aspect_ratio_id = py::extract<int>(p.second.attr("aspect_ratio").attr("id"));
301 int angle_id = py::extract<int>(p.second.attr("angle").attr("id"));
302 m_models[p.first] = std::make_shared<FlexibleModelFittingDevaucouleursModel>(
303 m_parameters.at(x_coord_id), m_parameters.at(y_coord_id), m_parameters.at(flux_id),
304 m_parameters.at(effective_radius_id), m_parameters.at(aspect_ratio_id), m_parameters.at(angle_id));
305 }
306
307#ifdef WITH_ONNX_MODELS
308 for (auto& p : getDependency<PythonConfig>().getInterpreter().getOnnxModels()) {
309 int x_coord_id = py::extract<int>(p.second.attr("x_coord").attr("id"));
310 int y_coord_id = py::extract<int>(p.second.attr("y_coord").attr("id"));
311 int flux_id = py::extract<int>(p.second.attr("flux").attr("id"));
312 int aspect_ratio_id = py::extract<int>(p.second.attr("aspect_ratio").attr("id"));
313 int angle_id = py::extract<int>(p.second.attr("angle").attr("id"));
314 int scale_id = py::extract<int>(p.second.attr("scale").attr("id"));
315
317 py::dict parameters = py::extract<py::dict>(p.second.attr("params"));
318 py::list names = parameters.keys();
319 for (int i = 0; i < py::len(names); ++i) {
320 std::string name = py::extract<std::string>(names[i]);
321 params[name] = m_parameters.at(py::extract<int>(parameters[names[i]].attr("id")));
322 }
323
325 py::list models = py::extract<py::list>(p.second.attr("models"));
326 for (int i = 0; i < py::len(models); ++i) {
327 std::string model_filename = py::extract<std::string>(models[i]);
328 onnx_models.emplace_back(std::make_shared<OnnxModel>(model_filename));
329
330 if (onnx_models.back()->getOutputType() != ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT ||
331 onnx_models.back()->getOutputShape().size() != 4 ||
332 onnx_models.back()->getOutputShape()[1] != onnx_models.back()->getOutputShape()[2] ||
333 onnx_models.back()->getOutputShape()[3] != 1)
334 {
335 throw Elements::Exception() << "ONNX models for ModelFitting must output a square array of floats";
336 }
337 }
338
339 m_models[p.first] = std::make_shared<FlexibleModelFittingOnnxModel>(
340 onnx_models, m_parameters.at(x_coord_id), m_parameters.at(y_coord_id), m_parameters.at(flux_id),
341 m_parameters.at(aspect_ratio_id), m_parameters.at(angle_id), m_parameters.at(scale_id), params);
342 }
343#else
344 if (getDependency<PythonConfig>().getInterpreter().getOnnxModels().size() > 0) {
345 throw Elements::Exception("Trying to use ONNX models but ONNX support is not available");
346 }
347#endif
348
349 for (auto& p : getDependency<PythonConfig>().getInterpreter().getFrameModelsMap()) {
351 for (int x : p.second) {
352 model_list.push_back(m_models[x]);
353 }
354 m_frames.push_back(std::make_shared<FlexibleModelFittingFrame>(p.first, model_list));
355 }
356
357 for (auto& p : getDependency<PythonConfig>().getInterpreter().getPriors()) {
358 auto& prior = p.second;
359 int param_id = py::extract<int>(prior.attr("param"));
360 auto param = m_parameters.at(param_id);
361
362 auto value_func = FunctionFromPython<double(const SourceInterface&)>::get(
363 "Prior mean", expr_builder, prior.attr("value")
364 );
365 auto sigma_func = FunctionFromPython<double(const SourceInterface&)>::get(
366 "Prior sigma", expr_builder, prior.attr("sigma")
367 );
368
369 m_priors[p.first] = std::make_shared<FlexibleModelFittingPrior>(param, value_func, sigma_func);
370 }
371
372 m_outputs = getDependency<PythonConfig>().getInterpreter().getModelFittingOutputColumns();
373
374 auto parameters = getDependency<PythonConfig>().getInterpreter().getModelFittingParams();
375 m_least_squares_engine = py::extract<std::string>(parameters["engine"]);
378 }
379 m_max_iterations = py::extract<int>(parameters["max_iterations"]);
380 m_modified_chi_squared_scale = py::extract<double>(parameters["modified_chi_squared_scale"]);
381 m_use_iterative_fitting = py::extract<bool>(parameters["use_iterative_fitting"]);
382 m_meta_iterations = py::extract<int>(parameters["meta_iterations"]);
383 m_deblend_factor = py::extract<double>(parameters["deblend_factor"]);
384 m_meta_iteration_stop = py::extract<double>(parameters["meta_iteration_stop"]);
385}
386
388 return m_parameters;
389}
390
392 return m_models;
393}
394
396 return m_frames;
397}
398
400 return m_priors;
401}
402
404 return m_outputs;
405}
406
407}
static Elements::Logging logger
std::shared_ptr< DependentParameter< std::shared_ptr< EngineParameter > > > x
std::shared_ptr< DependentParameter< std::shared_ptr< EngineParameter > > > y
T at(T... args)
T back(T... args)
void debug(const std::string &logMessage)
static Logging getLogger(const std::string &name="")
void warn(const std::string &logMessage)
void info(const std::string &logMessage)
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
The SourceInterface is an abstract "source" that has properties attached to it.
T clear(T... args)
T emplace_back(T... args)
T empty(T... args)
constexpr double e
static Elements::Logging logger
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)
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)
static std::function< double(double, const SourceInterface &)> get(const char *readable, Pyston::ExpressionTreeBuilder &builder, py::object py_func)