SourceXtractorPlusPlus 1.0.3
SourceXtractor++, the next generation SExtractor
Loading...
Searching...
No Matches
FlexibleModelFittingIterativeTask.cpp
Go to the documentation of this file.
1
17
22
25
27
31
37
41
45
46namespace SourceXtractor {
47
48using namespace ModelFitting;
49
50static auto logger = Elements::Logging::getLogger("FlexibleModelFitting");
51
53 unsigned int max_iterations, double modified_chi_squared_scale,
57 std::vector<bool> should_renormalize,
58 double scale_factor,
59 int meta_iterations,
60 double deblend_factor,
61 double meta_iteration_stop,
62 size_t max_fit_size,
63 WindowType window_type,
64 double ellipse_scale
65 )
66 : m_least_squares_engine(least_squares_engine), m_max_iterations(max_iterations),
67 m_modified_chi_squared_scale(modified_chi_squared_scale), m_scale_factor(scale_factor),
68 m_meta_iterations(meta_iterations), m_deblend_factor(deblend_factor), m_meta_iteration_stop(meta_iteration_stop),
69 m_max_fit_size(max_fit_size * max_fit_size), m_parameters(parameters), m_frames(frames), m_priors(priors),
70 m_should_renormalize(should_renormalize),
71 m_window_type(window_type), m_ellipse_scale(ellipse_scale) {}
72
75
77 auto fitting_rect = source.getProperty<MeasurementFrameRectangle>(frame_index).getRect();
78
80 auto ellipse = getFittingEllipse(source, frame_index);
81
82 ellipse.m_a *= m_ellipse_scale;
83 ellipse.m_b *= m_ellipse_scale;
84 return getEllipseRect(ellipse);
85 }
86
87 if (fitting_rect.getWidth() <= 0 || fitting_rect.getHeight() <= 0) {
88 return PixelRectangle();
89 } else {
90 auto min = fitting_rect.getTopLeft();
91 auto max = fitting_rect.getBottomRight();
92
93 // FIXME temporary, for now just enlarge the area by a fixed amount of pixels
94 PixelCoordinate border = (max - min) * .8 + PixelCoordinate(2, 2);
95
96 min -= border;
97 max += border;
98
100 if (max.m_x - min.m_x > max.m_y - min.m_y) {
101 min.m_x += ((max.m_x - min.m_x) - (max.m_y - min.m_y)) / 2;
102 max.m_x = min.m_x + (max.m_y - min.m_y);
103 } else {
104 min.m_y += ((max.m_y - min.m_y) - (max.m_x - min.m_x)) / 2;
105 max.m_y = min.m_y + (max.m_x - min.m_x);
106 }
107 }
108
110 if (max.m_x - min.m_x < max.m_y - min.m_y) {
111 min.m_x += ((max.m_x - min.m_x) - (max.m_y - min.m_y)) / 2;
112 max.m_x = min.m_x + (max.m_y - min.m_y);
113 } else {
114 min.m_y += ((max.m_y - min.m_y) - (max.m_x - min.m_x)) / 2;
115 max.m_y = min.m_y + (max.m_x - min.m_x);
116 }
117 }
118
120 int area = (max.m_x - min.m_x) * (max.m_y - min.m_y);
121 int size = int(sqrt(area));
122 min.m_x += ((max.m_x - min.m_x) - size) / 2;
123 max.m_x = min.m_x + size;
124 min.m_y += ((max.m_y - min.m_y) - size) / 2;
125 max.m_y = min.m_y + size;
126 }
127
128 return PixelRectangle(min, max);
129 }
130}
131
133 SourceInterface& source, int frame_index) const {
134 const auto& frame_info = source.getProperty<MeasurementFrameInfo>(frame_index);
135
136 auto min = fitting_rect.getTopLeft();
137 auto max = fitting_rect.getBottomRight();
138
139 // clip to image size
140 min.m_x = std::max(min.m_x, 0);
141 min.m_y = std::max(min.m_y, 0);
142 max.m_x = std::min(max.m_x, frame_info.getWidth() - 1);
143 max.m_y = std::min(max.m_y, frame_info.getHeight() - 1);
144
145 return PixelRectangle(min, max);
146}
147
149 return clipFittingRect(getUnclippedFittingRect(source, frame_index), source, frame_index);
150}
151
153 auto source_shape = source.getProperty<ShapeParameters>();
154 auto centroid = source.getProperty<PixelCentroid>();
155
156 // Ellipse in detection frame
157 auto detect_a = source_shape.getEllipseA();
158 auto detect_b = source_shape.getEllipseB();
159 auto detect_theta = source_shape.getEllipseTheta();
160
161 FittingEllipse ellipse;
162
163 ellipse.m_x = centroid.getCentroidX();
164 ellipse.m_y = centroid.getCentroidY();
165
166 ellipse.m_a = detect_a;
167 ellipse.m_b = detect_b;
168 ellipse.m_theta = detect_theta;
169
170 return transformEllipse(ellipse, source, frame_index);
171}
172
174 double cos_theta = std::cos(ellipse.m_theta);
175 double sin_theta = std::sin(ellipse.m_theta);
176
177 // horizontal and vertical components for the a and b axes of ellipses
178 double a_cos = ellipse.m_a * std::abs(cos_theta);
179 double a_sin = ellipse.m_a * std::abs(sin_theta);
180 double b_sin = ellipse.m_b * std::abs(sin_theta);
181 double b_cos = ellipse.m_b * std::abs(cos_theta);
182
183 double half_width = std::sqrt(a_cos*a_cos + b_sin*b_sin) + 1;
184 double half_height = std::sqrt(a_sin*a_sin + b_cos*b_cos) + 1;
185
186 PixelCoordinate min_corner, max_corner;
187 min_corner.m_x = ellipse.m_x - half_width;
188 min_corner.m_y = ellipse.m_y - half_height;
189 max_corner.m_x = ellipse.m_x + half_width;
190 max_corner.m_y = ellipse.m_y + half_height;
191
192 return {min_corner, max_corner};
193}
194
196 FittingEllipse ellipse, SourceInterface& source, int frame_index) const {
197 auto frame_coordinates = source.getProperty<MeasurementFrameCoordinates>(frame_index).getCoordinateSystem();
198 auto ref_coordinates = source.getProperty<ReferenceCoordinates>().getCoordinateSystem();
199 auto jacobian = source.getProperty<JacobianSource>(frame_index).asTuple();
200
201 auto coord = frame_coordinates->worldToImage(
202 ref_coordinates->imageToWorld(ImageCoordinate(ellipse.m_x, ellipse.m_y)));
203
204 double cos_theta = std::cos(ellipse.m_theta);
205 double sin_theta = std::sin(ellipse.m_theta);
206
207 double x_basis_x = std::get<0>(jacobian);
208 double x_basis_y = std::get<1>(jacobian);
209 double y_basis_x = std::get<2>(jacobian);
210 double y_basis_y = std::get<3>(jacobian);
211
212 // get vectors for the axis
213 double major_x = ellipse.m_a * cos_theta;
214 double major_y = ellipse.m_a * sin_theta;
215 double minor_x = -ellipse.m_b * sin_theta;
216 double minor_y = ellipse.m_b * cos_theta;
217
218 // Project major axis into the new coordinate system
219 double new_major_x = major_x * x_basis_x + major_y * y_basis_x;
220 double new_major_y = major_x * x_basis_y + major_y * y_basis_y;
221
222 // Project minor axis into the new coordinate system
223 double new_minor_x = minor_x * x_basis_x + minor_y * y_basis_x;
224 double new_minor_y = minor_x * x_basis_y + minor_y * y_basis_y;
225
226 // Calculate new semi-major and semi-minor lengths
227 double new_a = std::sqrt(new_major_x * new_major_x + new_major_y * new_major_y);
228 double new_b = std::sqrt(new_minor_x * new_minor_x + new_minor_y * new_minor_y);
229
230 // Calculate new angle (atan2 handles angle wrapping automatically)
231 double new_theta = std::atan2(new_major_y, new_major_x);
232
233 return {coord.m_x, coord.m_y, new_a, new_b, new_theta};
234}
235
237 auto stamp_rect = getFittingRect(source, frame_index);
238 return stamp_rect.getWidth() > 0 && stamp_rect.getHeight() > 0;
239}
240
242 SourceInterface& source, int frame_index) const {
243 const auto& frame_images = source.getProperty<MeasurementFrameImages>(frame_index);
244 auto rect = getFittingRect(source, frame_index);
245
246 auto image = VectorImage<SeFloat>::create(frame_images.getImageChunk(
247 LayerSubtractedImage, rect.getTopLeft().m_x, rect.getTopLeft().m_y, rect.getWidth(), rect.getHeight()));
248
249 return image;
250}
251
252
256 std::shared_ptr<FlexibleModelFittingFrame> frame, PixelRectangle stamp_rect, double down_scaling) const {
257
258 int frame_index = frame->getFrameNb();
259
260 auto frame_coordinates = source.getProperty<MeasurementFrameCoordinates>(frame_index).getCoordinateSystem();
261 auto ref_coordinates = source.getProperty<ReferenceCoordinates>().getCoordinateSystem();
262
263 auto psf_property = source.getProperty<SourcePsfProperty>(frame_index);
264 auto jacobian = source.getProperty<JacobianSource>(frame_index).asTuple();
265
266 // The model fitting module expects to get a PSF with a pixel scale, but we have the pixel sampling step size
267 // It will be used to compute the rastering grid size, and after convolving with the PSF the result will be
268 // downscaled before copied into the frame image.
269 // We can multiply here then, as the unit is pixel/pixel, rather than "/pixel or similar
270 auto source_psf = DownSampledImagePsf(psf_property.getPixelSampling(), psf_property.getPsf(),
271 down_scaling, m_should_renormalize[frame_index]);
272
273 std::vector<ConstantModel> constant_models;
274 std::vector<PointModel> point_models;
276
277 double model_size = std::max(stamp_rect.getWidth(), stamp_rect.getHeight());
278 for (auto model : frame->getModels()) {
279 model->addForSource(manager, source, constant_models, point_models, extended_models, model_size,
280 jacobian, ref_coordinates, frame_coordinates, stamp_rect.getTopLeft());
281 }
282
283 // Full frame model with all sources
285 pixel_scale, (size_t) stamp_rect.getWidth(), (size_t) stamp_rect.getHeight(),
286 std::move(constant_models), std::move(point_models), std::move(extended_models), source_psf);
287
288 return frame_model;
289}
290
292 SourceInterface& source, int frame_index) const {
293 const auto& frame_images = source.getProperty<MeasurementFrameImages>(frame_index);
294 auto frame_coordinates = source.getProperty<MeasurementFrameCoordinates>(frame_index).getCoordinateSystem();
295 auto ref_coordinates = source.getProperty<ReferenceCoordinates>().getCoordinateSystem();
296
297 auto frame_image = frame_images.getLockedImage(LayerSubtractedImage);
298 auto frame_image_thresholded = frame_images.getLockedImage(LayerThresholdedImage);
299 auto variance_map = frame_images.getLockedImage(LayerVarianceMap);
300
301 const auto& frame_info = source.getProperty<MeasurementFrameInfo>(frame_index);
302 SeFloat gain = frame_info.getGain();
303 SeFloat saturation = frame_info.getSaturation();
304
305 auto unclipped_rect = getUnclippedFittingRect(source, frame_index);
306 auto rect = clipFittingRect(unclipped_rect, source, frame_index);
307 auto weight = VectorImage<SeFloat>::create(rect.getWidth(), rect.getHeight());
308
309 // Precompute ellipse parameters
310
311 FittingEllipse ellipse;
312 auto rad = std::min(unclipped_rect.getWidth() / 2.0, unclipped_rect.getHeight() / 2.0) - 1.0;
313
315 ellipse = getFittingEllipse(source, frame_index);
316 } else {
317 // we don't want to require the ShapeParameters property when not using ROTATED_ELLIPSE
318 auto centroid = source.getProperty<WorldCentroid>();
319 auto coord = frame_coordinates->worldToImage(centroid.getCentroid());
320 ellipse.m_x = coord.m_x;
321 ellipse.m_y = coord.m_y;
322 ellipse.m_a = rad;
323 ellipse.m_b = rad;
324 ellipse.m_theta = 0;
325 }
326
327 double cos_theta = std::cos(ellipse.m_theta);
328 double sin_theta = std::sin(ellipse.m_theta);
329
330 double a_squared = ellipse.m_a * ellipse.m_a * m_ellipse_scale * m_ellipse_scale;
331 double b_squared = ellipse.m_b * ellipse.m_b * m_ellipse_scale * m_ellipse_scale;
332
333 // Precompute components of the ellipse's quadratic form
334 double A = (cos_theta * cos_theta) / a_squared + (sin_theta * sin_theta) / b_squared;
335 double B = 2 * cos_theta * sin_theta * (1 / a_squared - 1 / b_squared);
336 double C = (sin_theta * sin_theta) / a_squared + (cos_theta * cos_theta) / b_squared;
337
338 for (int y = 0; y < rect.getHeight(); y++) {
339 for (int x = 0; x < rect.getWidth(); x++) {
340 auto back_var = variance_map->getValue(rect.getTopLeft().m_x + x, rect.getTopLeft().m_y + y);
341 auto pixel_val = frame_image->getValue(rect.getTopLeft().m_x + x, rect.getTopLeft().m_y + y);
342
343 auto dx = x + rect.getTopLeft().m_x - ellipse.m_x;
344 auto dy = y + rect.getTopLeft().m_y - ellipse.m_y;
345
346 if (saturation > 0 && pixel_val > saturation) {
347 weight->at(x, y) = 0;
348 } else if (gain > 0.0 && pixel_val > 0.0) {
349 weight->at(x, y) = sqrt(1.0 / (back_var + pixel_val / gain));
350 } else {
351 weight->at(x, y) = sqrt(1.0 / back_var); // infinite gain
352 }
353
356 if (dx * dx + dy * dy > rad * rad) {
357 weight->at(x, y) = 0;
358 }
360 auto w = unclipped_rect.getWidth() / 2.0;
361 auto h = unclipped_rect.getHeight() / 2.0;
362 if (dx / w * dx / w + dy / h * dy / h > 1) {
363 weight->at(x, y) = 0;
364 }
366 // Apply the quadratic form of the ellipse equation
367 double value = A * dx * dx + B * dx * dy + C * dy * dy;
368 if (value > 1.0) {
369 weight->at(x, y) = 0;
370 }
371 }
372 }
373 }
374
375 return weight;
376}
377
379 FittingState fitting_state;
380
381 for (auto& source : group) {
382 SourceState initial_state;
383 initial_state.flags = Flags::NONE;
384 initial_state.iterations = 0;
385 initial_state.stop_reason = 0;
386 initial_state.reduced_chi_squared = 0.0;
387 initial_state.duration = 0.0;
388
389 for (auto parameter : m_parameters) {
391 if (free_parameter != nullptr) {
392 initial_state.parameters_initial_values[free_parameter->getId()] =
393 initial_state.parameters_values[free_parameter->getId()] = free_parameter->getInitialValue(source);
394 } else {
395 initial_state.parameters_initial_values[parameter->getId()] =
396 initial_state.parameters_values[parameter->getId()] = 0.0;
397 }
398 // Make sure we have a default value for sigmas in case we cannot do the fit
399 initial_state.parameters_sigmas[parameter->getId()] = std::numeric_limits<double>::quiet_NaN();
400 }
401
402 // Store fitting areas for later output
403 for (auto frame : m_frames) {
404 int frame_index = frame->getFrameNb();
405 // Validate that each frame covers the model fitting region
406 if (isFrameValid(source, frame_index)) {
407 auto stamp_rect = getFittingRect(source, frame_index);
408 initial_state.fitting_areas_x.push_back(stamp_rect.getWidth());
409 initial_state.fitting_areas_y.push_back(stamp_rect.getHeight());
410 } else {
411 initial_state.fitting_areas_x.push_back(-1.f);
412 initial_state.fitting_areas_y.push_back(-1.f);
413 }
414 }
415
416 fitting_state.source_states.emplace_back(std::move(initial_state));
417 }
418
419 // TODO Sort sources by flux to fit brightest sources first?
420
421 // iterate over the whole group, fitting sources one at a time
422
423 double prev_chi_squared = 999999.9;
424 for (int iteration = 0; iteration < m_meta_iterations; iteration++) {
425 int index = 0;
426 for (auto& source : group) {
427 fitSource(group, source, index, fitting_state);
428 index++;
429 }
430
431 // evaluate reduced chi squared to bail out of meta iterations if no longer improving the fit
432
433 double chi_squared = 0.0;
434 for (auto& source_state : fitting_state.source_states) {
435 chi_squared += source_state.reduced_chi_squared;
436 }
437 chi_squared /= fitting_state.source_states.size();
438
439 if (fabs(chi_squared - prev_chi_squared) / chi_squared < m_meta_iteration_stop) {
440 break;
441 }
442
443 prev_chi_squared = chi_squared;
444 }
445
446
447 // Remove parameters that couldn't be fit from the output
448
449 for (size_t index = 0; index < group.size(); index++){
450 auto& source_state = fitting_state.source_states.at(index);
451
452 for (auto parameter : m_parameters) {
454
455 if (free_parameter != nullptr && !source_state.parameters_fitted[parameter->getId()]) {
456 source_state.parameters_values[parameter->getId()] = std::numeric_limits<double>::quiet_NaN();
457 source_state.parameters_sigmas[parameter->getId()] = std::numeric_limits<double>::quiet_NaN();
458 }
459 }
460 }
461
462 // output a property for every source
463 size_t index = 0;
464 for (auto& source : group) {
465 auto& source_state = fitting_state.source_states.at(index);
466
467 int meta_iterations = source_state.chi_squared_per_meta.size();
468 source_state.chi_squared_per_meta.resize(m_meta_iterations);
469 source_state.iterations_per_meta.resize(m_meta_iterations);
470
471 source.setProperty<FlexibleModelFitting>(source_state.iterations, source_state.stop_reason,
472 source_state.reduced_chi_squared, source_state.duration, source_state.flags,
473 source_state.parameters_values, source_state.parameters_sigmas,
474 source_state.chi_squared_per_meta, source_state.iterations_per_meta,
475 meta_iterations, source_state.fitting_areas_x, source_state.fitting_areas_y);
476
477 index++;
478 }
479
480 updateCheckImages(group, 1.0, fitting_state);
481}
482
483
485 SourceGroupInterface& group, SourceInterface& source, int source_index,
487 int frame_index = frame->getFrameNb();
488 auto rect = getFittingRect(source, frame_index);
489
490 double pixel_scale = 1.0;
491 FlexibleModelFittingParameterManager parameter_manager;
492 ModelFitting::EngineParameterManager engine_parameter_manager {};
493 int n_free_parameters = 0;
494
495 int index = 0;
496 for (auto& src : group) {
497 if (index != source_index) {
498 for (auto parameter : m_parameters) {
500
501 if (free_parameter != nullptr) {
502 ++n_free_parameters;
503
504 // Initial with the values from the current iteration run
505 parameter_manager.addParameter(src, parameter,
506 free_parameter->create(parameter_manager, engine_parameter_manager, src,
507 state.source_states[index].parameters_initial_values.at(free_parameter->getId()),
508 state.source_states[index].parameters_values.at(free_parameter->getId())));
509 } else {
510 parameter_manager.addParameter(src, parameter,
511 parameter->create(parameter_manager, engine_parameter_manager, src));
512 }
513 }
514 }
515 index++;
516 }
517
518 auto deblend_image = VectorImage<SeFloat>::create(rect.getWidth(), rect.getHeight());
519 index = 0;
520 for (auto& src : group) {
521 if (index != source_index) {
522 auto frame_model = createFrameModel(src, pixel_scale, parameter_manager, frame, rect);
523 auto final_stamp = frame_model.getImage();
524
525 for (int y = 0; y < final_stamp->getHeight(); ++y) {
526 for (int x = 0; x < final_stamp->getWidth(); ++x) {
527 deblend_image->at(x, y) += final_stamp->at(x, y);
528 }
529 }
530 }
531 index++;
532 }
533
534 return deblend_image;
535}
536
538 FlexibleModelFittingParameterManager& parameter_manager,
539 ModelFitting::EngineParameterManager& engine_parameter_manager,
540 SourceInterface& source, int index, FittingState& state) const {
541 int free_parameters_nb = 0;
542 for (auto parameter : m_parameters) {
544
545 if (free_parameter != nullptr) {
546 ++free_parameters_nb;
547
548 // Initial with the values from the current iteration run
549 parameter_manager.addParameter(source, parameter,
550 free_parameter->create(parameter_manager, engine_parameter_manager, source,
551 state.source_states[index].parameters_initial_values.at(free_parameter->getId()),
552 state.source_states[index].parameters_values.at(free_parameter->getId())));
553 } else {
554 parameter_manager.addParameter(source, parameter,
555 parameter->create(parameter_manager, engine_parameter_manager, source));
556 }
557
558 }
559
560 // Reset access checks, as a dependent parameter could have triggered it
561 parameter_manager.clearAccessCheck();
562
563 return free_parameters_nb;
564}
565
567 ResidualEstimator& res_estimator, int& good_pixels,
568 SourceGroupInterface& group, SourceInterface& source, int index, FittingState& state, double down_scaling) const {
569
570 double pixel_scale = 1.0;
571
572 int valid_frames = 0;
573 for (auto frame : m_frames) {
574 int frame_index = frame->getFrameNb();
575 // Validate that each frame covers the model fitting region
576 if (isFrameValid(source, frame_index)) {
577 valid_frames++;
578
579 auto stamp_rect = getFittingRect(source, frame_index);
580 auto frame_model = createFrameModel(source, pixel_scale, parameter_manager, frame, stamp_rect, down_scaling);
581
582 auto image = createImageCopy(source, frame_index);
583
584 auto deblend_image = createDeblendImage(group, source, index, frame, state);
585 for (int y = 0; y < image->getHeight(); ++y) {
586 for (int x = 0; x < image->getWidth(); ++x) {
587 image->at(x, y) -= m_deblend_factor * deblend_image->at(x, y);
588 }
589 }
590
591 auto weight = createWeightImage(source, frame_index);
592
593 // count number of pixels that can be used for fitting
594 for (int y = 0; y < weight->getHeight(); ++y) {
595 for (int x = 0; x < weight->getWidth(); ++x) {
596 good_pixels += (weight->at(x, y) != 0.);
597 }
598 }
599
600 // Setup residuals
601 auto data_vs_model =
602 createDataVsModelResiduals(image, std::move(frame_model), weight,
603 //LogChiSquareComparator(m_modified_chi_squared_scale));
605 res_estimator.registerBlockProvider(std::move(data_vs_model));
606 }
607 }
608
609 return valid_frames;
610}
611
613 SourceGroupInterface& group, SourceInterface& source, int index, FittingState& state) const {
614
615 double pixel_scale = 1.0;
616
617 int total_data_points = 0;
618 SeFloat avg_reduced_chi_squared = computeChiSquared(group, source, index,pixel_scale, parameter_manager, total_data_points, state);
619
620 int nb_of_free_parameters = 0;
621 for (auto parameter : m_parameters) {
622 bool is_free_parameter = std::dynamic_pointer_cast<FlexibleModelFittingFreeParameter>(parameter).get();
623 bool accessed_by_modelfitting = parameter_manager.isParamAccessed(source, parameter);
624 if (is_free_parameter && accessed_by_modelfitting) {
625 nb_of_free_parameters++;
626 }
627 }
628 avg_reduced_chi_squared /= (total_data_points - nb_of_free_parameters);
629
630 return avg_reduced_chi_squared;
631}
632
634 FlexibleModelFittingParameterManager& parameter_manager, SourceInterface& source,
635 SeFloat avg_reduced_chi_squared, SeFloat duration, unsigned int iterations, unsigned int stop_reason, Flags flags,
637 int index, FittingState& state) const {
639 // Collect parameters for output
640 std::unordered_map<int, double> parameters_values, parameters_sigmas;
641 std::unordered_map<int, bool> parameters_fitted;
642
643 for (auto parameter : m_parameters) {
644 bool is_dependent_parameter = std::dynamic_pointer_cast<FlexibleModelFittingDependentParameter>(parameter).get();
645 bool is_constant_parameter = std::dynamic_pointer_cast<FlexibleModelFittingConstantParameter>(parameter).get();
646 bool accessed_by_modelfitting = parameter_manager.isParamAccessed(source, parameter);
647 auto modelfitting_parameter = parameter_manager.getParameter(source, parameter);
648
649 if (is_constant_parameter || is_dependent_parameter || accessed_by_modelfitting) {
650 parameters_values[parameter->getId()] = modelfitting_parameter->getValue();
651 parameters_sigmas[parameter->getId()] = parameter->getSigma(parameter_manager, source, solution.parameter_sigmas);
652 parameters_fitted[parameter->getId()] = true;
653 } else {
654 parameters_values[parameter->getId()] = state.source_states[index].parameters_values[parameter->getId()];
655 parameters_sigmas[parameter->getId()] = state.source_states[index].parameters_sigmas[parameter->getId()];
656 parameters_fitted[parameter->getId()] = false;
657
658 // Need to cascade the NaN to any potential dependent parameter
659 auto engine_parameter = std::dynamic_pointer_cast<EngineParameter>(modelfitting_parameter);
660 if (engine_parameter) {
661 engine_parameter->setEngineValue(std::numeric_limits<double>::quiet_NaN());
662 }
663
664 flags |= Flags::PARTIAL_FIT;
665 }
666 }
667
668 state.source_states[index].parameters_values = parameters_values;
669 state.source_states[index].parameters_sigmas = parameters_sigmas;
670 state.source_states[index].parameters_fitted = parameters_fitted;
671 state.source_states[index].reduced_chi_squared = avg_reduced_chi_squared;
672 state.source_states[index].chi_squared_per_meta.emplace_back(avg_reduced_chi_squared);
673 state.source_states[index].duration += duration;
674 state.source_states[index].iterations += iterations;
675 state.source_states[index].iterations_per_meta.emplace_back(iterations);
676 state.source_states[index].stop_reason = stop_reason;
677 state.source_states[index].flags = flags;
678}
679
681
683 // Determine size of fitted area and if needed downsize factor
684
685 double fit_size = 0;
686 for (auto frame : m_frames) {
687 int frame_index = frame->getFrameNb();
688 // Validate that each frame covers the model fitting region
689 if (isFrameValid(source, frame_index)) {
690 auto psf_property = source.getProperty<SourcePsfProperty>(frame_index);
691 auto stamp_rect = getFittingRect(source, frame_index);
692 fit_size = std::max(fit_size, stamp_rect.getWidth() * stamp_rect.getHeight() /
693 (psf_property.getPixelSampling() * psf_property.getPixelSampling()));
694 }
695 }
696
697 double down_scaling = m_scale_factor;
698 if (fit_size > m_max_fit_size * 2.0) {
699 down_scaling *= sqrt(double(m_max_fit_size) / fit_size);
700 logger.warn() << "Exceeding max fit size: " << fit_size << " / " << m_max_fit_size
701 << " scaling factor: " << down_scaling;
702 }
703
705 // Prepare parameters
706
707 FlexibleModelFittingParameterManager parameter_manager;
708 ModelFitting::EngineParameterManager engine_parameter_manager{};
709 int n_free_parameters = fitSourcePrepareParameters(
710 parameter_manager, engine_parameter_manager, source, index, state);
711
713 // Add models for all frames
714 ResidualEstimator res_estimator {};
715 int n_good_pixels = 0;
716 int valid_frames = fitSourcePrepareModels(
717 parameter_manager, res_estimator, n_good_pixels, group, source, index, state, down_scaling);
718
720 // Check that we had enough data for the fit
721
722 Flags flags = Flags::NONE;
723
724 if (valid_frames == 0) {
725 flags = Flags::OUTSIDE;
726 }
727 else if (n_good_pixels < n_free_parameters) {
729 }
730
731 // Do not run the model fitting for the flags above
732 if (flags != Flags::NONE) {
733 return;
734 }
735
736 if (down_scaling < 1.0) {
737 flags |= Flags::DOWNSAMPLED;
738 }
739
740
742 // Add priors
743 for (auto prior : m_priors) {
744 prior->setupPrior(parameter_manager, source, res_estimator);
745 }
746
748 // Model fitting
749
751 auto solution = engine->solveProblem(engine_parameter_manager, res_estimator);
752
753 auto iterations = solution.iteration_no;
754 auto stop_reason = solution.engine_stop_reason;
755 if (solution.status_flag == LeastSquareSummary::ERROR) {
756 flags |= Flags::ERROR;
757 }
758 auto duration = solution.duration;
759
761 // compute chi squared
762
763 SeFloat avg_reduced_chi_squared = fitSourceComputeChiSquared(parameter_manager, group, source, index, state);
764
766 // update state with results
767 fitSourceUpdateState(parameter_manager, source, avg_reduced_chi_squared, duration, iterations, stop_reason, flags, solution,
768 index, state);
769}
770
772 double pixel_scale, FittingState& state) const {
773
774 // recreate parameters
775
776 FlexibleModelFittingParameterManager parameter_manager;
777 ModelFitting::EngineParameterManager engine_parameter_manager {};
778
779 int index = 0;
780 for (auto& src : group) {
781 for (auto parameter : m_parameters) {
783
784 if (free_parameter != nullptr) {
785 // Initialize with the values from the current iteration run
786 parameter_manager.addParameter(src, parameter,
787 free_parameter->create(parameter_manager, engine_parameter_manager, src,
788 state.source_states[index].parameters_initial_values.at(free_parameter->getId()),
789 state.source_states[index].parameters_values.at(free_parameter->getId())));
790 } else {
791 parameter_manager.addParameter(src, parameter,
792 parameter->create(parameter_manager, engine_parameter_manager, src));
793 }
794 }
795 index++;
796 }
797
798 for (auto& src : group) {
799 for (auto frame : m_frames) {
800 int frame_index = frame->getFrameNb();
801
802 if (isFrameValid(src, frame_index)) {
803 auto stamp_rect = getFittingRect(src, frame_index);
804
805 auto frame_model = createFrameModel(src, pixel_scale, parameter_manager, frame, stamp_rect);
806 auto final_stamp = frame_model.getImage();
807
808 auto weight_image = createWeightImage(src, frame_index);
809
810 {
811 auto debug_image = CheckImages::getInstance().getModelFittingImage(frame_index);
812 if (debug_image) {
813 ImageAccessor<SeFloat> debug_accessor(debug_image);
814 for (int x = 0; x < final_stamp->getWidth(); x++) {
815 for (int y = 0; y < final_stamp->getHeight(); y++) {
816 auto x_coord = stamp_rect.getTopLeft().m_x + x;
817 auto y_coord = stamp_rect.getTopLeft().m_y + y;
818 debug_image->setValue(x_coord, y_coord,
819 debug_accessor.getValue(x_coord, y_coord) + final_stamp->getValue(x, y));
820 }
821 }
822 }
823 }
824
825 {
826 auto window_image = CheckImages::getInstance().getFittingWindowImage(frame_index);
827 if (window_image) {
828 ImageAccessor<int> window_accessor(window_image);
829 for (int x = 0; x < final_stamp->getWidth(); x++) {
830 for (int y = 0; y < final_stamp->getHeight(); y++) {
831 auto x_coord = stamp_rect.getTopLeft().m_x + x;
832 auto y_coord = stamp_rect.getTopLeft().m_y + y;
833 if (weight_image->getValue(x, y) > 0.0) {
834 window_image->setValue(x_coord, y_coord, window_accessor.getValue(x_coord, y_coord) + 2);
835 } else {
836 window_image->setValue(x_coord, y_coord, window_accessor.getValue(x_coord, y_coord) + 1);
837 }
838 }
839 }
840 }
841 }
842
843 }
844 }
845 }
846}
847
849 std::shared_ptr<const Image<SeFloat>> model, std::shared_ptr<const Image<SeFloat>> weights, int& data_points) const {
850 double reduced_chi_squared = 0.0;
851 data_points = 0;
852
853 ImageAccessor<SeFloat> imageAccessor(image), modelAccessor(model);
854 ImageAccessor<SeFloat> weightAccessor(weights);
855
856 for (int y=0; y < image->getHeight(); y++) {
857 for (int x=0; x < image->getWidth(); x++) {
858 double tmp = imageAccessor.getValue(x, y) - modelAccessor.getValue(x, y);
859 reduced_chi_squared += tmp * tmp * weightAccessor.getValue(x, y) * weightAccessor.getValue(x, y);
860 if (weightAccessor.getValue(x, y) > 0) {
861 data_points++;
862 }
863 }
864 }
865 return reduced_chi_squared;
866}
867
869 double pixel_scale, FlexibleModelFittingParameterManager& manager, int& total_data_points, FittingState& state) const {
870 SeFloat total_chi_squared = 0;
871 total_data_points = 0;
872 int valid_frames = 0;
873 for (auto frame : m_frames) {
874 int frame_index = frame->getFrameNb();
875 // Validate that each frame covers the model fitting region
876 if (isFrameValid(source, frame_index)) {
877 valid_frames++;
878 auto stamp_rect = getFittingRect(source, frame_index);
879 auto frame_model = createFrameModel(source, pixel_scale, manager, frame, stamp_rect);
880 auto final_stamp = frame_model.getImage();
881
882 auto image = createImageCopy(source, frame_index);
883 auto deblend_image = createDeblendImage(group, source, index, frame, state);
884 for (int y = 0; y < image->getHeight(); ++y) {
885 for (int x = 0; x < image->getWidth(); ++x) {
886 image->at(x, y) -= deblend_image->at(x, y);
887 }
888 }
889
890 auto weight = createWeightImage(source, frame_index);
891
892 int data_points = 0;
893 SeFloat chi_squared = computeChiSquaredForFrame(image, final_stamp, weight, data_points);
894
895 total_data_points += data_points;
896 total_chi_squared += chi_squared;
897 }
898 }
899
900 return total_chi_squared;
901}
902
903}
904
const double pixel_scale
Definition TestImage.cpp:74
T atan2(T... args)
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.
std::shared_ptr< WriteableImage< int > > getFittingWindowImage(unsigned int frame_number)
static CheckImages & getInstance()
std::shared_ptr< WriteableImage< MeasurementImage::PixelType > > getModelFittingImage(unsigned int frame_number)
void fitSourceUpdateState(FlexibleModelFittingParameterManager &parameter_manager, SourceInterface &source, SeFloat avg_reduced_chi_squared, SeFloat duration, unsigned int iterations, unsigned int stop_reason, Flags flags, ModelFitting::LeastSquareSummary solution, int index, FittingState &state) const
void fitSource(SourceGroupInterface &group, SourceInterface &source, int index, FittingState &state) const
int fitSourcePrepareParameters(FlexibleModelFittingParameterManager &parameter_manager, ModelFitting::EngineParameterManager &engine_parameter_manager, SourceInterface &source, int index, FittingState &state) const
void computeProperties(SourceGroupInterface &group) const override
Computes one or more properties for the SourceGroup and/or the Sources it contains.
SeFloat fitSourceComputeChiSquared(FlexibleModelFittingParameterManager &parameter_manager, SourceGroupInterface &group, SourceInterface &source, int index, FittingState &state) const
PixelRectangle getUnclippedFittingRect(SourceInterface &source, int frame_index) const
std::shared_ptr< VectorImage< SeFloat > > createImageCopy(SourceInterface &source, int frame_index) const
std::vector< std::shared_ptr< FlexibleModelFittingFrame > > m_frames
PixelRectangle getFittingRect(SourceInterface &source, int frame_index) const
FlexibleModelFittingIterativeTask(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, std::vector< bool > should_renormalize, double scale_factor=1.0, int meta_iterations=3, double deblend_factor=1.0, double meta_iteration_stop=0.0001, size_t max_fit_size=100, WindowType window_type=WindowType::RECTANGLE, double ellipse_scale=3.0)
std::shared_ptr< VectorImage< SeFloat > > createDeblendImage(SourceGroupInterface &group, SourceInterface &source, int source_index, std::shared_ptr< FlexibleModelFittingFrame > frame, FittingState &state) const
FlexibleModelFittingIterativeTask::FittingEllipse transformEllipse(FittingEllipse ellipse, SourceInterface &source, int frame_index) const
int fitSourcePrepareModels(FlexibleModelFittingParameterManager &parameter_manager, ModelFitting::ResidualEstimator &res_estimator, int &good_pixels, SourceGroupInterface &group, SourceInterface &source, int index, FittingState &state, double downscaling) const
bool isFrameValid(SourceInterface &source, int frame_index) const
std::shared_ptr< VectorImage< SeFloat > > createWeightImage(SourceInterface &source, int frame_index) const
ModelFitting::FrameModel< DownSampledImagePsf, std::shared_ptr< VectorImage< SourceXtractor::SeFloat > > > createFrameModel(SourceInterface &source, double pixel_scale, FlexibleModelFittingParameterManager &manager, std::shared_ptr< FlexibleModelFittingFrame > frame, PixelRectangle stamp_rect, double down_scaling=1.0) const
std::vector< std::shared_ptr< FlexibleModelFittingParameter > > m_parameters
SeFloat computeChiSquared(SourceGroupInterface &group, SourceInterface &source, int index, double pixel_scale, FlexibleModelFittingParameterManager &manager, int &total_data_points, FittingState &state) const
PixelRectangle clipFittingRect(PixelRectangle fitting_rect, SourceInterface &source, int frame_index) 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
FlexibleModelFittingIterativeTask::FittingEllipse getFittingEllipse(SourceInterface &source, int frame_index) const
void updateCheckImages(SourceGroupInterface &group, double pixel_scale, FittingState &state) const
std::vector< std::shared_ptr< FlexibleModelFittingPrior > > m_priors
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
Interface representing an image.
Definition Image.h:44
The centroid of all the pixels in the source, weighted by their DetectionImage pixel values.
PixelCoordinate getTopLeft() const
PixelCoordinate getBottomRight() const
Defines the interface used to group sources.
virtual unsigned int size() const =0
The SourceInterface is an abstract "source" that has properties attached to it.
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 cos(T... args)
T fabs(T... args)
T max(T... args)
T min(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
@ DOWNSAMPLED
The fit was done on a downsampled image due to exceeding max size.
Definition SourceFlags.h:50
@ 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
@ 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 push_back(T... args)
T quiet_NaN(T... args)
T sin(T... args)
T sqrt(T... args)
Class containing the summary information of solving a least square minimization problem.
std::vector< double > parameter_sigmas
1-sigma margin of error for all the parameters
A pixel coordinate made of two integers m_x and m_y.