SourceXtractorPlusPlus  0.19
SourceXtractor++, the next generation SExtractor
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
FlexibleModelFittingTask.cpp
Go to the documentation of this file.
1 
17 /*
18  * FlexibleModelFittingTask.cpp
19  *
20  * Created on: Sep 17, 2018
21  * Author: mschefer
22  */
23 
24 #include <mutex>
25 
35 
37 
40 
43 
47 
48 
54 
58 
60 
61 namespace SourceXtractor {
62 
63 using namespace ModelFitting;
64 
65 static auto logger = Elements::Logging::getLogger("FlexibleModelFitting");
66 
68  unsigned int max_iterations, double modified_chi_squared_scale,
72  double scale_factor)
73  : m_least_squares_engine(least_squares_engine),
74  m_max_iterations(max_iterations), m_modified_chi_squared_scale(modified_chi_squared_scale),
75  m_parameters(parameters), m_frames(frames), m_priors(priors), m_scale_factor(scale_factor) {}
76 
77 bool FlexibleModelFittingTask::isFrameValid(SourceGroupInterface& group, int frame_index) const {
78  auto stamp_rect = group.getProperty<MeasurementFrameGroupRectangle>(frame_index);
79  return stamp_rect.getWidth() > 0 && stamp_rect.getHeight() > 0;
80 }
81 
83  SourceGroupInterface& group, int frame_index) const {
84  const auto& frame_images = group.begin()->getProperty<MeasurementFrameImages>(frame_index);
85  auto rect = group.getProperty<MeasurementFrameGroupRectangle>(frame_index);
86  auto image = VectorImage<SeFloat>::create(frame_images.getImageChunk(
87  LayerSubtractedImage, rect.getTopLeft().m_x, rect.getTopLeft().m_y, rect.getWidth(), rect.getHeight()));
88 
89  return image;
90 }
91 
93  SourceGroupInterface& group, int frame_index) const {
94  const auto& frame_images = group.begin()->getProperty<MeasurementFrameImages>(frame_index);
95 
96  auto frame_image = frame_images.getLockedImage(LayerSubtractedImage);
97  auto frame_image_thresholded = frame_images.getLockedImage(LayerThresholdedImage);
98  auto variance_map = frame_images.getLockedImage(LayerVarianceMap);
99 
100  const auto& frame_info = group.begin()->getProperty<MeasurementFrameInfo>(frame_index);
101  SeFloat gain = frame_info.getGain();
102  SeFloat saturation = frame_info.getSaturation();
103 
104  auto rect = group.getProperty<MeasurementFrameGroupRectangle>(frame_index);
105  auto weight = VectorImage<SeFloat>::create(rect.getWidth(), rect.getHeight());
106 
107  for (int y = 0; y < rect.getHeight(); y++) {
108  for (int x = 0; x < rect.getWidth(); x++) {
109  auto back_var = variance_map->getValue(rect.getTopLeft().m_x + x, rect.getTopLeft().m_y + y);
110  auto pixel_val = frame_image->getValue(rect.getTopLeft().m_x + x, rect.getTopLeft().m_y + y);
111  if (saturation > 0 && pixel_val > saturation) {
112  weight->at(x, y) = 0;
113  }
114  else if (gain > 0.0 && pixel_val > 0.0) {
115  weight->at(x, y) = sqrt(1.0 / (back_var + pixel_val / gain));
116  }
117  else {
118  weight->at(x, y) = sqrt(1.0 / back_var); // infinite gain
119  }
120  }
121  }
122 
123  return weight;
124 }
125 
127  SourceGroupInterface& group,
130 
131  int frame_index = frame->getFrameNb();
132 
133  auto frame_coordinates =
134  group.begin()->getProperty<MeasurementFrameCoordinates>(frame_index).getCoordinateSystem();
135  auto ref_coordinates =
136  group.begin()->getProperty<DetectionFrameCoordinates>().getCoordinateSystem();
137 
138  auto stamp_rect = group.getProperty<MeasurementFrameGroupRectangle>(frame_index);
139  auto psf_property = group.getProperty<PsfProperty>(frame_index);
140  auto jacobian = group.getProperty<JacobianGroup>(frame_index).asTuple();
141 
142  if (psf_property.getPsf() == nullptr) {
143  throw Elements::Exception() << "Missing PSF. No PSF mode is not supported in legacy model fitting";
144  }
145 
146  // The model fitting module expects to get a PSF with a pixel scale, but we have the pixel sampling step size
147  // It will be used to compute the rastering grid size, and after convolving with the PSF the result will be
148  // downscaled before copied into the frame image.
149  // We can multiply here then, as the unit is pixel/pixel, rather than "/pixel or similar
150  auto group_psf = ImagePsf(pixel_scale * psf_property.getPixelSampling(), psf_property.getPsf());
151 
152  std::vector<ConstantModel> constant_models;
153  std::vector<PointModel> point_models;
155 
156  for (auto& source : group) {
157  for (auto model : frame->getModels()) {
158  model->addForSource(manager, source, constant_models, point_models, extended_models, jacobian, ref_coordinates, frame_coordinates,
159  stamp_rect.getTopLeft());
160  }
161  }
162 
163  // Full frame model with all sources
165  pixel_scale, (size_t) stamp_rect.getWidth(), (size_t) stamp_rect.getHeight(),
166  std::move(constant_models), std::move(point_models), std::move(extended_models), group_psf);
167 
168  return frame_model;
169 }
170 
171 
173  double pixel_scale = 1 / m_scale_factor;
174  FlexibleModelFittingParameterManager parameter_manager;
175  ModelFitting::EngineParameterManager engine_parameter_manager{};
176  int n_free_parameters = 0;
177 
178  // Prepare parameters
179  for (auto& source : group) {
180  for (auto parameter : m_parameters) {
181  if (std::dynamic_pointer_cast<FlexibleModelFittingFreeParameter>(parameter)) {
182  ++n_free_parameters;
183  }
184  parameter_manager.addParameter(source, parameter,
185  parameter->create(parameter_manager, engine_parameter_manager, source));
186  }
187  }
188 
189  try {
190  // Reset access checks, as a dependent parameter could have triggered it
191  parameter_manager.clearAccessCheck();
192 
193  // Add models for all frames
194  ResidualEstimator res_estimator{};
195 
196  int valid_frames = 0;
197  int n_good_pixels = 0;
198  for (auto frame : m_frames) {
199  int frame_index = frame->getFrameNb();
200  // Validate that each frame covers the model fitting region
201  if (isFrameValid(group, frame_index)) {
202  valid_frames++;
203 
204  auto frame_model = createFrameModel(group, pixel_scale, parameter_manager, frame);
205 
206  auto image = createImageCopy(group, frame_index);
207  auto weight = createWeightImage(group, frame_index);
208 
209  for (int y = 0; y < weight->getHeight(); ++y) {
210  for (int x = 0; x < weight->getWidth(); ++x) {
211  n_good_pixels += (weight->at(x, y) != 0.);
212  }
213  }
214 
215  // Setup residuals
216  auto data_vs_model =
217  createDataVsModelResiduals(image, std::move(frame_model), weight,
218  //LogChiSquareComparator(m_modified_chi_squared_scale));
220  res_estimator.registerBlockProvider(std::move(data_vs_model));
221  }
222  }
223 
224  // Check that we had enough data for the fit
225  Flags group_flags = Flags::NONE;
226  if (valid_frames == 0) {
227  group_flags = Flags::OUTSIDE;
228  }
229  else if (n_good_pixels < n_free_parameters) {
230  group_flags = Flags::INSUFFICIENT_DATA;
231  }
232 
233  if (group_flags != Flags::NONE) {
234  setDummyProperty(group, parameter_manager, group_flags);
235  return;
236  }
237 
238  // Add priors
239  for (auto& source : group) {
240  for (auto prior : m_priors) {
241  prior->setupPrior(parameter_manager, source, res_estimator);
242  }
243  }
244 
245  // Model fitting
246 
247  // FIXME we can no longer specify different settings with LeastSquareEngineManager!!
248  // LevmarEngine engine{m_max_iterations, 1E-3, 1E-6, 1E-6, 1E-6, 1E-4};
250  auto solution = engine->solveProblem(engine_parameter_manager, res_estimator);
251  auto iterations = solution.iteration_no;
252  auto stop_reason = solution.engine_stop_reason;
253  switch (solution.status_flag) {
255  group_flags |= (Flags::MEMORY | Flags::ERROR);
256  break;
258  group_flags |= Flags::ERROR;
259  break;
260  default:
261  break;
262  }
263 
264  int total_data_points = 0;
265  SeFloat avg_reduced_chi_squared = computeChiSquared(group, pixel_scale, parameter_manager, total_data_points);
266 
267  int nb_of_free_parameters = 0;
268  for (auto& source : group) {
269  for (auto parameter : m_parameters) {
270  bool is_free_parameter = std::dynamic_pointer_cast<FlexibleModelFittingFreeParameter>(parameter).get();
271  bool accessed_by_modelfitting = parameter_manager.isParamAccessed(source, parameter);
272  if (is_free_parameter && accessed_by_modelfitting) {
273  nb_of_free_parameters++;
274  }
275  }
276  }
277  avg_reduced_chi_squared /= (total_data_points - nb_of_free_parameters);
278 
279  // Collect parameters for output
280  for (auto& source : group) {
281  std::unordered_map<int, double> parameter_values, parameter_sigmas;
282  auto source_flags = group_flags;
283 
284  for (auto parameter : m_parameters) {
285  bool is_dependent_parameter = std::dynamic_pointer_cast<FlexibleModelFittingDependentParameter>(parameter).get();
286  bool is_constant_parameter = std::dynamic_pointer_cast<FlexibleModelFittingConstantParameter>(parameter).get();
287  bool accessed_by_modelfitting = parameter_manager.isParamAccessed(source, parameter);
288  auto modelfitting_parameter = parameter_manager.getParameter(source, parameter);
289 
290  if (is_constant_parameter || is_dependent_parameter || accessed_by_modelfitting) {
291  parameter_values[parameter->getId()] = modelfitting_parameter->getValue();
292  parameter_sigmas[parameter->getId()] = parameter->getSigma(parameter_manager, source, solution.parameter_sigmas);
293  }
294  else {
295  // Need to cascade the NaN to any potential dependent parameter
296  auto engine_parameter = std::dynamic_pointer_cast<EngineParameter>(modelfitting_parameter);
297  if (engine_parameter) {
299  }
300  parameter_values[parameter->getId()] = std::numeric_limits<double>::quiet_NaN();
301  parameter_sigmas[parameter->getId()] = std::numeric_limits<double>::quiet_NaN();
303  }
304  }
305  source.setProperty<FlexibleModelFitting>(iterations, stop_reason,
306  avg_reduced_chi_squared, solution.duration, source_flags,
307  parameter_values, parameter_sigmas,
308  std::vector<SeFloat>({avg_reduced_chi_squared}),
309  std::vector<int>({(int) iterations}), (int) 1);
310  }
311  updateCheckImages(group, pixel_scale, parameter_manager);
312 
313  }
314  catch (const Elements::Exception& e) {
315  logger.error() << "An exception occured during model fitting: " << e.what();
316  setDummyProperty(group, parameter_manager, Flags::ERROR);
317  }
318 }
319 
320 // Used to set a dummy property in case of error that contains no result but just an error flag
322  FlexibleModelFittingParameterManager& parameter_manager,
323  Flags flags) const {
324  for (auto& source : group) {
325  std::unordered_map<int, double> dummy_values;
326  for (auto parameter : m_parameters) {
327  auto modelfitting_parameter = parameter_manager.getParameter(source, parameter);
328  auto manual_parameter = std::dynamic_pointer_cast<ManualParameter>(modelfitting_parameter);
329  if (manual_parameter) {
331  }
332  dummy_values[parameter->getId()] = std::numeric_limits<double>::quiet_NaN();
333  }
334  source.setProperty<FlexibleModelFitting>(0, 0, std::numeric_limits<double>::quiet_NaN(), 0., flags,
335  dummy_values, dummy_values,
337  }
338 }
339 
341  double pixel_scale, FlexibleModelFittingParameterManager& manager) const {
342 
343  int frame_id = 0;
344  for (auto frame : m_frames) {
345  int frame_index = frame->getFrameNb();
346  // Validate that each frame covers the model fitting region
347  if (isFrameValid(group, frame_index)) {
348  auto frame_model = createFrameModel(group, pixel_scale, manager, frame);
349  auto final_stamp = frame_model.getImage();
350 
351  auto stamp_rect = group.getProperty<MeasurementFrameGroupRectangle>(frame_index);
352 
353  auto debug_image = CheckImages::getInstance().getModelFittingImage(frame_index);
354  if (debug_image) {
355  ImageAccessor<SeFloat> debugAccessor(debug_image);
356  for (int x = 0; x < final_stamp->getWidth(); x++) {
357  for (int y = 0; y < final_stamp->getHeight(); y++) {
358  auto x_coord = stamp_rect.getTopLeft().m_x + x;
359  auto y_coord = stamp_rect.getTopLeft().m_y + y;
360  debug_image->setValue(x_coord, y_coord,
361  debugAccessor.getValue(x_coord, y_coord) + final_stamp->getValue(x, y));
362  }
363  }
364  }
365  }
366  frame_id++;
367  }
368 }
369 
371  std::shared_ptr<const Image<SeFloat>> model, std::shared_ptr<const Image<SeFloat>> weights, int& data_points) const {
372  double reduced_chi_squared = 0.0;
373  data_points = 0;
374 
375  ImageAccessor<SeFloat> imageAccessor(image), modelAccessor(model);
376  ImageAccessor<SeFloat> weightAccessor(weights);
377 
378  for (int y=0; y < image->getHeight(); y++) {
379  for (int x=0; x < image->getWidth(); x++) {
380  double tmp = imageAccessor.getValue(x, y) - modelAccessor.getValue(x, y);
381  reduced_chi_squared += tmp * tmp * weightAccessor.getValue(x, y) * weightAccessor.getValue(x, y);
382  if (weightAccessor.getValue(x, y) > 0) {
383  data_points++;
384  }
385  }
386  }
387  return reduced_chi_squared;
388 }
389 
391  double pixel_scale, FlexibleModelFittingParameterManager& manager, int& total_data_points) const {
392 
393  SeFloat total_chi_squared = 0;
394  total_data_points = 0;
395  int valid_frames = 0;
396  for (auto frame : m_frames) {
397  int frame_index = frame->getFrameNb();
398  // Validate that each frame covers the model fitting region
399  if (isFrameValid(group, frame_index)) {
400  valid_frames++;
401  auto frame_model = createFrameModel(group, pixel_scale, manager, frame);
402  auto final_stamp = frame_model.getImage();
403 
404  auto stamp_rect = group.getProperty<MeasurementFrameGroupRectangle>(frame_index);
405  auto image = createImageCopy(group, frame_index);
406  auto weight = createWeightImage(group, frame_index);
407 
408  int data_points = 0;
409  SeFloat chi_squared = computeChiSquaredForFrame(
410  image, final_stamp, weight, data_points);
411 
412  total_data_points += data_points;
413  total_chi_squared += chi_squared;
414  }
415  }
416 
417  return total_chi_squared;
418 }
419 
421 }
422 
423 }
static StaticPlugin< SourceFlagsPlugin > source_flags
void addParameter(const SourceInterface &source, std::shared_ptr< const FlexibleModelFittingParameter > parameter, std::shared_ptr< ModelFitting::BasicParameter > engine_parameter)
static auto logger
Definition: WCS.cpp:41
const PropertyType & getProperty(unsigned int index=0) const
Convenience template method to call getProperty() with a more user-friendly syntax.
void updateCheckImages(SourceGroupInterface &group, double pixel_scale, FlexibleModelFittingParameterManager &manager) const
EngineParameter are those derived from the minimization process.
Failed to allocate an object, buffer, etc.
The object is completely outside of the measurement frame.
std::shared_ptr< DependentParameter< std::shared_ptr< EngineParameter > > > x
void setEngineValue(const double engine_value)
void computeProperties(SourceGroupInterface &group) const override
Computes one or more properties for the SourceGroup and/or the Sources it contains.
std::shared_ptr< VectorImage< SeFloat > > createWeightImage(SourceGroupInterface &group, int frame_index) const
SeFloat32 SeFloat
Definition: Types.h:32
static std::shared_ptr< VectorImage< T > > create(Args &&...args)
Definition: VectorImage.h:100
std::shared_ptr< DependentParameter< std::shared_ptr< EngineParameter > > > y
STL class.
constexpr double e
static CheckImages & getInstance()
Definition: CheckImages.h:150
SeFloat computeChiSquaredForFrame(std::shared_ptr< const Image< SeFloat >> image, std::shared_ptr< const Image< SeFloat >> model, std::shared_ptr< const Image< SeFloat >> weights, int &data_points) const
T dynamic_pointer_cast(T...args)
T move(T...args)
Data vs model comparator which computes a modified residual, using asinh.
bool isParamAccessed(const SourceInterface &source, std::shared_ptr< const FlexibleModelFittingParameter > parameter) const
void setValue(const double new_value)
Defines the interface used to group sources.
FlexibleModelFittingTask(const std::string &least_squares_engine, unsigned int max_iterations, double modified_chi_squared_scale, std::vector< std::shared_ptr< FlexibleModelFittingParameter >> parameters, std::vector< std::shared_ptr< FlexibleModelFittingFrame >> frames, std::vector< std::shared_ptr< FlexibleModelFittingPrior >> priors, double scale_factor=1.0)
std::vector< std::shared_ptr< FlexibleModelFittingPrior > > m_priors
std::vector< std::shared_ptr< FlexibleModelFittingFrame > > m_frames
bool isFrameValid(SourceGroupInterface &group, int frame_index) const
STL class.
There are not enough good pixels to fit the parameters.
std::shared_ptr< ModelFitting::BasicParameter > getParameter(const SourceInterface &source, std::shared_ptr< const FlexibleModelFittingParameter > parameter) const
SeFloat computeChiSquared(SourceGroupInterface &group, double pixel_scale, FlexibleModelFittingParameterManager &manager, int &total_data_points) const
Some/all of the model parameters could not be fitted.
Flags
Flagging of bad sources.
Definition: SourceFlags.h:37
Interface representing an image.
Definition: Image.h:43
std::shared_ptr< VectorImage< SeFloat > > createImageCopy(SourceGroupInterface &group, int frame_index) const
Class responsible for managing the parameters the least square engine minimizes.
void setDummyProperty(SourceGroupInterface &group, FlexibleModelFittingParameterManager &parameter_manager, Flags flags) const
static std::shared_ptr< LeastSquareEngine > create(const std::string &name, unsigned max_iterations=1000)
std::vector< std::shared_ptr< FlexibleModelFittingParameter > > m_parameters
std::unique_ptr< DataVsModelResiduals< typename std::remove_reference< DataType >::type, typename std::remove_reference< ModelType >::type, typename std::remove_reference< WeightType >::type, typename std::remove_reference< Comparator >::type > > createDataVsModelResiduals(DataType &&data, ModelType &&model, WeightType &&weight, Comparator &&comparator)
T quiet_NaN(T...args)
T sqrt(T...args)
Provides to the LeastSquareEngine the residual values.
const char * what() const noexceptoverride
static Logging getLogger(const std::string &name="")
const double pixel_scale
Definition: TestImage.cpp:74
ModelFitting::FrameModel< ImagePsf, std::shared_ptr< VectorImage< SourceXtractor::SeFloat > > > createFrameModel(SourceGroupInterface &group, double pixel_scale, FlexibleModelFittingParameterManager &manager, std::shared_ptr< FlexibleModelFittingFrame > frame) const
Error flag: something bad happened during the measurement, model fitting, etc.
std::shared_ptr< WriteableImage< MeasurementImage::PixelType > > getModelFittingImage(unsigned int frame_number)
std::shared_ptr< ImageAccessor< SeFloat > > getLockedImage(FrameImageLayer layer) const