SourceXtractorPlusPlus  0.19
SourceXtractor++, the next generation SExtractor
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
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"
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 
54 using namespace Euclid::Configuration;
55 namespace py = boost::python;
57 
59 
60 namespace SourceXtractor {
61 
62 template<typename Signature>
64 };
65 
66 template<>
67 struct FunctionFromPython<double(const SourceInterface&)> {
68  static
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 
91 template<>
92 struct 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 
113 template<>
114 struct FunctionFromPython<double(double, const SourceInterface&)> {
115  static
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 
138 ModelFittingConfig::ModelFittingConfig(long manager_id) : Configuration(manager_id) {
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();
148  m_outputs.clear();
149 }
150 
151 void ModelFittingConfig::initialize(const UserValues&) {
152  Pyston::GILLocker locker;
153  try {
154  initializeInner();
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 
162 static 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 
167 static 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 
173  Pyston::ExpressionTreeBuilder expr_builder;
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()) {
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()) {
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") {
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") {
209  "Range min", expr_builder, py_range_obj.attr("get_min")
210  );
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 
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 
363  "Prior mean", expr_builder, prior.attr("value")
364  );
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 }
std::string str() const
const std::map< int, std::shared_ptr< FlexibleModelFittingParameter > > & getParameters() const
T empty(T...args)
static auto logger
Definition: WCS.cpp:41
std::map< int, std::shared_ptr< FlexibleModelFittingPrior > > m_priors
const std::vector< std::pair< std::string, std::vector< int > > > & getOutputs() const
std::shared_ptr< DependentParameter< std::shared_ptr< EngineParameter > > > x
const std::vector< std::shared_ptr< FlexibleModelFittingFrame > > & getFrames() const
const Exception & log(log4cpp::Priority::Value level, Elements::Logging &logger) const
void initialize(const UserValues &args) override
void info(const std::string &logMessage)
void debug(const std::string &logMessage)
std::shared_ptr< DependentParameter< std::shared_ptr< EngineParameter > > > y
std::map< int, std::shared_ptr< FlexibleModelFittingParameter > > m_parameters
STL class.
T at(T...args)
T push_back(T...args)
constexpr double e
std::map< int, std::shared_ptr< FlexibleModelFittingModel > > m_models
void warn(const std::string &logMessage)
const std::map< int, std::shared_ptr< FlexibleModelFittingModel > > & getModels() const
static Elements::Logging logger
static double image_to_world_delta(const Pyston::Context &context, double x, double y)
T clear(T...args)
void registerFunction(const std::string &repr, std::function< Signature > functor)
STL class.
ExpressionTree< Signature > build(const boost::python::object &pyfunc, BuildParams &&...build_params) const
T back(T...args)
std::map< std::string, Attribute > AttributeSet
std::vector< std::shared_ptr< FlexibleModelFittingFrame > > m_frames
const std::map< int, std::shared_ptr< FlexibleModelFittingPrior > > & getPriors() const
The SourceInterface is an abstract &quot;source&quot; that has properties attached to it.
static Logging getLogger(const std::string &name="")
static double image_to_world_alpha(const Pyston::Context &context, double x, double y)
std::vector< std::pair< std::string, std::vector< int > > > m_outputs
T emplace_back(T...args)