SourceXtractorPlusPlus  0.19
SourceXtractor++, the next generation SExtractor
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
PythonInterpreter.cpp
Go to the documentation of this file.
1 /*
2  * Copyright © 2019-2022 Université de Genève, LMU Munich - Faculty of Physics, IAP-CNRS/Sorbonne Université
3  *
4  * This library is free software; you can redistribute it and/or modify it under
5  * the terms of the GNU Lesser General Public License as published by the Free
6  * Software Foundation; either version 3.0 of the License, or (at your option)
7  * any later version.
8  *
9  * This library is distributed in the hope that it will be useful, but WITHOUT
10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
11  * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
12  * details.
13  *
14  * You should have received a copy of the GNU Lesser General Public License
15  * along with this library; if not, write to the Free Software Foundation, Inc.,
16  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17  */
18 /*
19  * @file PythonInterpreter.cpp
20  * @author Nikolaos Apostolakos <nikoapos@gmail.com>
21  */
22 
23 #include <Pyston/Exceptions.h>
24 #include <Pyston/GIL.h>
25 #include <Pyston/Module.h>
26 #include <Python.h>
29 #include <boost/python/dict.hpp>
30 #include <boost/python/exec.hpp>
31 #include <boost/python/extract.hpp>
32 #include <boost/python/import.hpp>
33 #include <boost/python/object.hpp>
34 #include <fstream>
35 #include <signal.h>
36 #include <system_error>
37 #include <utility>
38 
39 namespace py = boost::python;
40 
41 static Elements::Logging logger = Elements::Logging::getLogger("Python::Interpreter");
44 
45 namespace SourceXtractor {
46 
48  static PythonInterpreter singleton{};
49  return singleton;
50 }
51 
52 PythonInterpreter::PythonInterpreter() : m_out_wrapper(stdout_logger), m_err_wrapper(stderr_logger) {
53  // Python sets its own signal handler for SIGINT (Ctrl+C), so it can throw a KeyboardInterrupt
54  // Here we are not interested on this behaviour, so we get whatever handler we've got (normally
55  // the default one) and restore it after initializing the interpreter
56  struct sigaction sigint_handler;
57  sigaction(SIGINT, nullptr, &sigint_handler);
58 
59  PyImport_AppendInittab("pyston", PYSTON_MODULE_INIT);
60  Py_Initialize();
61 #if PY_VERSION_HEX < 3090000
62  PyEval_InitThreads();
63 #endif
64  PyEval_SaveThread();
65 
66  sigaction(SIGINT, &sigint_handler, nullptr);
67 }
68 
70  logger.info() << "Python GIL acquired " << Pyston::GILLocker::getLockCount() << " times";
71 }
72 
74  Pyston::GILLocker locker;
75 
76  try {
77  // Setup argv
78  // Python expects to have the ownership!
79 #if PY_MAJOR_VERSION == 2
80  using py_argv_char_t = char;
81 #define py_argv_assign(d, s, l) d = strndup(s, l)
82 #else
83  using py_argv_char_t = wchar_t;
84 #define py_argv_assign(d, s, l) d = Py_DecodeLocale(s, &(l))
85 #endif
86 
87  py_argv_char_t** py_argv = static_cast<py_argv_char_t**>(PyMem_MALLOC((argv.size() + 1) * sizeof(py_argv_char_t*)));
88  size_t wlen = filename.size();
89  py_argv_assign(py_argv[0], filename.c_str(), wlen);
90  for (size_t i = 0; i < argv.size(); ++i) {
91  wlen = argv[i].size();
92  py_argv_assign(py_argv[i + 1], argv[i].c_str(), wlen);
93  }
94  PySys_SetArgv(argv.size() + 1, py_argv);
95 
96  // Import ourselves so the conversions are registered
97  py::import("_SourceXtractorPy");
98 
99  // Setup stdout and stderr
100  PySys_SetObject("stdout", py::object(boost::ref(m_out_wrapper)).ptr());
101  PySys_SetObject("stderr", py::object(boost::ref(m_err_wrapper)).ptr());
102 
103  // Run the file
104  py::object main_module = py::import("__main__");
105  py::setattr(main_module, "__file__", py::object(filename));
106  py::object main_namespace = main_module.attr("__dict__");
107 
108  // boost 1.75 up to 1.77 has a bug that trashes the heap
109  // See https://github.com/boostorg/python/issues/371
110  // So we read the file ourselves as a workaround
111  std::ifstream fs(filename);
112  if (fs.fail()) {
113  throw std::system_error(errno, std::system_category(), filename);
114  }
116  py::exec(pycode.c_str(), main_namespace);
117  } catch (const py::error_already_set& e) {
118  throw Pyston::Exception().log(log4cpp::Priority::ERROR, logger);
119  } catch (const std::system_error& e) {
120  throw Elements::Exception() << e.what() << ": " << e.code().message();
121  }
122 }
123 
125  Pyston::GILLocker locker;
126  try {
127  m_measurement_config = py::import("sourcextractor.config.measurement_config").attr("global_measurement_config");
128  } catch (const py::error_already_set& e) {
129  throw Pyston::Exception().log(log4cpp::Priority::ERROR, logger);
130  }
131 }
132 
134  Pyston::GILLocker locker;
135 
136  try {
137  py::dict images = py::extract<py::dict>(m_measurement_config.attr("measurement_images"));
138  py::list ids = images.keys();
140  for (int i = 0; i < py::len(ids); ++i) {
141  int id = py::extract<int>(ids[i]);
142  PyMeasurementImage im = py::extract<PyMeasurementImage>(images[ids[i]]);
143  result.emplace(std::make_pair(id, im));
144  }
145  return result;
146  } catch (const py::error_already_set& e) {
147  throw Pyston::Exception().log(log4cpp::Priority::ERROR, logger);
148  }
149 }
150 
152  Pyston::GILLocker locker;
153 
154  try {
155  py::dict apertures = py::extract<py::dict>(m_measurement_config.attr("apertures_for_image"));
156  py::list ids = apertures.keys();
158  for (int i = 0; i < py::len(ids); ++i) {
159  int id = py::extract<int>(ids[i]);
160  PyAperture ap = py::extract<PyAperture>(apertures[ids[i]]);
161  result.emplace(std::make_pair(id, ap));
162  }
163  return result;
164  } catch (const py::error_already_set& e) {
165  throw Pyston::Exception().log(log4cpp::Priority::ERROR, logger);
166  }
167 }
168 
170  Pyston::GILLocker locker;
171 
172  try {
173  py::list output = py::extract<py::list>(m_measurement_config.attr("model_fitting_parameter_columns"));
175  for (int i = 0; i < py::len(output); ++i) {
176  py::tuple t = py::extract<py::tuple>(output[i]);
177  std::string name = py::extract<std::string>(t[0]);
178  auto extract_list = py::extract<py::list>(t[1]);
179 
180  std::vector<int> ids{};
181  if (extract_list.check()) {
182  py::list cs = extract_list;
183  for (int j = 0; j < py::len(cs); ++j) {
184  int c = py::extract<int>(cs[j].attr("id"));
185  ids.push_back(c);
186  }
187  } else {
188  int c = py::extract<int>(t[1]);
189  ids.push_back(c);
190  }
191  result.emplace_back(name, std::move(ids));
192  }
193  return result;
194  } catch (const py::error_already_set& e) {
195  throw Pyston::Exception().log(log4cpp::Priority::ERROR, logger);
196  }
197 }
198 
200  Pyston::GILLocker locker;
201 
202  try {
203  py::list output = py::extract<py::list>(m_measurement_config.attr("aperture_columns"));
205  for (int i = 0; i < py::len(output); ++i) {
206  py::tuple t = py::extract<py::tuple>(output[i]);
207  std::string name = py::extract<std::string>(t[0]);
208  auto extract_list = py::extract<py::list>(t[1]);
209 
210  if (extract_list.check()) {
211  py::list cs = extract_list;
212  for (int j = 0; j < py::len(cs); ++j) {
213  int c = py::extract<int>(cs[j].attr("id"));
214  result[name].push_back(c);
215  }
216  } else {
217  int c = py::extract<int>(t[1]);
218  result[name].push_back(c);
219  }
220  }
221  return result;
222  } catch (const py::error_already_set& e) {
223  throw Pyston::Exception().log(log4cpp::Priority::ERROR, logger);
224  }
225 }
226 
228  const char* dict_name) {
229  Pyston::GILLocker locker;
230 
231  try {
232  py::object obj = m_measurement_config.attr(object_name);
233  py::dict parameters = py::extract<py::dict>(obj.attr(dict_name));
234  py::list ids = parameters.keys();
235 
237  for (int i = 0; i < py::len(ids); ++i) {
238  int id = py::extract<int>(ids[i]);
239  auto par = parameters[ids[i]];
240  result.emplace(std::make_pair(id, par));
241  }
242  return result;
243  } catch (const py::error_already_set& e) {
244  throw Pyston::Exception().log(log4cpp::Priority::ERROR, logger);
245  }
246 }
247 
249  return getMapFromDict("model_fitting", "constant_parameter_dict");
250 }
251 
253  return getMapFromDict("model_fitting", "free_parameter_dict");
254 }
255 
257  return getMapFromDict("model_fitting", "dependent_parameter_dict");
258 }
259 
261  return getMapFromDict("model_fitting", "prior_dict");
262 }
263 
265  return getMapFromDict("model_fitting", "constant_model_dict");
266 }
267 
269  return getMapFromDict("model_fitting", "point_source_model_dict");
270 }
271 
273  return getMapFromDict("model_fitting", "sersic_model_dict");
274 }
275 
277  return getMapFromDict("model_fitting", "exponential_model_dict");
278 }
279 
281  return getMapFromDict("model_fitting", "de_vaucouleurs_model_dict");
282 }
283 
285  return getMapFromDict("model_fitting", "onnx_model_dict");
286 }
287 
289  Pyston::GILLocker locker;
290  try {
292  py::object model_fitting = m_measurement_config.attr("model_fitting");
293  py::dict frame_dict = py::extract<py::dict>(model_fitting.attr("frame_models_dict"));
294  py::list frame_ids = frame_dict.keys();
295  for (int i = 0; i < py::len(frame_ids); ++i) {
296  int frame_id = py::extract<int>(frame_ids[i]);
297  py::list model_ids = py::extract<py::list>(frame_dict[frame_ids[i]]);
298  for (int j = 0; j < py::len(model_ids); ++j) {
299  int model_id = py::extract<int>(model_ids[j]);
300  result[frame_id].push_back(model_id);
301  }
302  }
303  return result;
304  } catch (const py::error_already_set& e) {
305  throw Pyston::Exception().log(log4cpp::Priority::ERROR, logger);
306  }
307 }
308 
310  Pyston::GILLocker locker;
311 
312  py::object model_fitting = m_measurement_config.attr("model_fitting");
313  py::dict parameters = py::extract<py::dict>(model_fitting.attr("params_dict"));
314  py::list ids = parameters.keys();
315 
317  for (int i = 0; i < py::len(ids); ++i) {
318  std::string id = py::extract<std::string>(ids[i]);
319  auto par = parameters[ids[i]];
320  result.emplace(std::make_pair(id, par));
321  }
322  return result;
323 }
324 
325 } // namespace SourceXtractor
std::map< int, boost::python::object > getConstantModels()
std::map< int, boost::python::object > getFreeParameters()
std::map< int, PyMeasurementImage > getMeasurementImages()
std::map< int, boost::python::object > getPointSourceModels()
static size_t getLockCount()
T fail(T...args)
const Exception & log(log4cpp::Priority::Value level, Elements::Logging &logger) const
boost::python::object m_measurement_config
std::map< std::string, std::vector< int > > getApertureOutputColumns()
void info(const std::string &logMessage)
std::map< int, boost::python::object > getOnnxModels()
STL class.
std::map< int, boost::python::object > getDeVaucouleursModels()
STL class.
std::map< int, boost::python::object > getMapFromDict(const char *object_name, const char *dict_name)
std::map< int, boost::python::object > getSersicModels()
T system_category(T...args)
void runFile(const std::string &filename, const std::vector< std::string > &argv)
T push_back(T...args)
constexpr double e
std::map< std::string, boost::python::object > getModelFittingParams()
std::map< int, boost::python::object > getPriors()
T what(T...args)
std::map< int, std::vector< int > > getFrameModelsMap()
string filename
Definition: conf.py:65
std::map< int, boost::python::object > getExponentialModels()
static Elements::Logging stdout_logger
std::map< int, boost::python::object > getConstantParameters()
T make_pair(T...args)
T code(T...args)
std::vector< std::pair< std::string, std::vector< int > > > getModelFittingOutputColumns()
#define py_argv_assign(d, s, l)
T move(T...args)
#define PYSTON_MODULE_INIT
std::map< int, PyAperture > getApertures()
T size(T...args)
static Elements::Logging stderr_logger
T c_str(T...args)
T emplace(T...args)
std::map< int, boost::python::object > getDependentParameters()
static Logging getLogger(const std::string &name="")
static PythonInterpreter & getSingleton()
STL class.
static Elements::Logging logger