SourceXtractorPlusPlus 0.19.2
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
47
48
54
58
60
61namespace SourceXtractor {
62
63using namespace ModelFitting;
64
65static 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
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
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) {
298 engine_parameter->setEngineValue(std::numeric_limits<double>::quiet_NaN());
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) {
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) {
330 manual_parameter->setValue(std::numeric_limits<double>::quiet_NaN());
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;
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}
std::shared_ptr< DependentParameter< std::shared_ptr< EngineParameter > > > x
std::shared_ptr< DependentParameter< std::shared_ptr< EngineParameter > > > y
const double pixel_scale
Definition TestImage.cpp:74
void error(const std::string &logMessage)
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.
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 move(T... args)
constexpr double e
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.
@ OUTSIDE
The object is completely outside of the measurement frame.
@ NONE
No flag is set.
@ ERROR
Error flag: something bad happened during the measurement, model fitting, etc.
@ INSUFFICIENT_DATA
There are not enough good pixels to fit the parameters.
@ PARTIAL_FIT
Some/all of the model parameters could not be fitted.
SeFloat32 SeFloat
Definition Types.h:32
static StaticPlugin< SourceFlagsPlugin > source_flags
@ LayerVarianceMap
Definition Frame.h:45
@ LayerThresholdedImage
Definition Frame.h:41
@ LayerSubtractedImage
Definition Frame.h:39
T quiet_NaN(T... args)
T sqrt(T... args)