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