@@ -3089,6 +3089,8 @@ namespace TinyDIP
30893089 }
30903090
30913091 // estimate_gaussian_parameters_2d template function implementation
3092+ // Test1: https://godbolt.org/z/Ee6xjPETE
3093+ // Test2: https://godbolt.org/z/7rcnqWff6
30923094 template <
30933095 class ExecutionPolicy ,
30943096 typename ElementT,
@@ -3106,6 +3108,7 @@ namespace TinyDIP
31063108 throw std::invalid_argument (" Input image must be 2-dimensional." );
31073109 }
31083110
3111+ constexpr std::size_t num_params = GaussianParameters2D<FloatingPointT>::num_params;
31093112 const std::size_t count = image.count ();
31103113 const std::size_t width = image.getWidth ();
31113114
@@ -3127,11 +3130,10 @@ namespace TinyDIP
31273130 FloatingPointT x0 = static_cast <FloatingPointT>(max_idx % width);
31283131 FloatingPointT y0 = static_cast <FloatingPointT>(max_idx / width);
31293132
3130- // Estimate standard deviations & correlation via 2nd moments utilizing OpenMP reduction natively
3131- FloatingPointT sum_z = static_cast <FloatingPointT>(0.0 );
3132- FloatingPointT sum_dx2_z = static_cast <FloatingPointT>(0.0 );
3133- FloatingPointT sum_dy2_z = static_cast <FloatingPointT>(0.0 );
3134- FloatingPointT sum_dxdy_z = static_cast <FloatingPointT>(0.0 );
3133+ FloatingPointT sum_z{};
3134+ FloatingPointT sum_dx2_z{};
3135+ FloatingPointT sum_dy2_z{};
3136+ FloatingPointT sum_dxdy_z{};
31353137 const std::ptrdiff_t signed_count = static_cast <std::ptrdiff_t >(count);
31363138
31373139 #pragma omp parallel for reduction(+:sum_z, sum_dx2_z, sum_dy2_z, sum_dxdy_z)
@@ -3153,7 +3155,11 @@ namespace TinyDIP
31533155 }
31543156
31553157 FloatingPointT sigma_x = (sum_z > static_cast <FloatingPointT>(0.0 )) ? std::sqrt (sum_dx2_z / sum_z) : static_cast <FloatingPointT>(10.0 );
3158+ sigma_x = std::max (static_cast <FloatingPointT>(1e-6 ), sigma_x);
3159+
31563160 FloatingPointT sigma_y = (sum_z > static_cast <FloatingPointT>(0.0 )) ? std::sqrt (sum_dy2_z / sum_z) : static_cast <FloatingPointT>(10.0 );
3161+ sigma_y = std::max (static_cast <FloatingPointT>(1e-6 ), sigma_y);
3162+
31573163 FloatingPointT rho = (sum_z > static_cast <FloatingPointT>(0.0 )) ? (sum_dxdy_z / sum_z) / (sigma_x * sigma_y) : static_cast <FloatingPointT>(0.0 );
31583164
31593165 // Clamp initial rho to valid bound
@@ -3179,24 +3185,45 @@ namespace TinyDIP
31793185 mapper
31803186 );
31813187
3182- std::array<std::array<FloatingPointT, 6 >, 6 > H_damped = acc.H ;
3183-
3184- // Apply damping
3185- for (int i = 0 ; i < 6 ; ++i)
3188+ // Marquardt Scaling to prevent ill-conditioning due to disparate parameter scales
3189+ std::array<FloatingPointT, num_params> scale;
3190+ for (std::size_t i = 0 ; i < num_params; ++i)
3191+ {
3192+ scale[i] = std::sqrt (std::abs (acc.H [i][i]));
3193+ if (scale[i] < static_cast <FloatingPointT>(1e-12 ))
3194+ {
3195+ scale[i] = static_cast <FloatingPointT>(1.0 ); // Prevent division by zero
3196+ }
3197+ }
3198+
3199+ std::array<std::array<FloatingPointT, num_params>, num_params> H_scaled{};
3200+ std::array<FloatingPointT, num_params> g_scaled{};
3201+
3202+ for (std::size_t i = 0 ; i < num_params; ++i)
3203+ {
3204+ g_scaled[i] = acc.g [i] / scale[i];
3205+ for (std::size_t j = 0 ; j < num_params; ++j)
3206+ {
3207+ H_scaled[i][j] = acc.H [i][j] / (scale[i] * scale[j]);
3208+ }
3209+ }
3210+
3211+ // Apply damping to the normalized Hessian
3212+ for (std::size_t i = 0 ; i < num_params; ++i)
31863213 {
3187- H_damped [i][i] *= ( static_cast <FloatingPointT>( 1.0 ) + lambda);
3214+ H_scaled [i][i] += lambda;
31883215 }
31893216
3190- std::array<FloatingPointT, 6 > delta = solve_linear_system_6x6<FloatingPointT>(H_damped, acc.g );
3217+ std::array<FloatingPointT, num_params> delta_scaled = solve_linear_system<num_params, FloatingPointT>(H_scaled, g_scaled);
3218+
3219+ std::array<FloatingPointT, num_params> delta;
3220+ for (std::size_t i = 0 ; i < num_params; ++i)
3221+ {
3222+ delta[i] = delta_scaled[i] / scale[i];
3223+ }
31913224
31923225 // Check if gradient is small enough (convergence)
3193- FloatingPointT delta_sq_sum = std::inner_product (
3194- std::ranges::begin (delta),
3195- std::ranges::end (delta),
3196- std::ranges::begin (delta),
3197- static_cast <FloatingPointT>(0.0 )
3198- );
3199-
3226+ FloatingPointT delta_sq_sum = std::inner_product (std::ranges::begin (delta), std::ranges::end (delta), std::ranges::begin (delta), static_cast <FloatingPointT>(0.0 ));
32003227 if (delta_sq_sum < tolerance)
32013228 {
32023229 break ;
@@ -3205,8 +3232,8 @@ namespace TinyDIP
32053232 FloatingPointT new_A = A + delta[0 ];
32063233 FloatingPointT new_x0 = x0 + delta[1 ];
32073234 FloatingPointT new_y0 = y0 + delta[2 ];
3208- FloatingPointT new_sigma_x = sigma_x + delta[3 ];
3209- FloatingPointT new_sigma_y = sigma_y + delta[4 ];
3235+ FloatingPointT new_sigma_x = std::max ( static_cast <FloatingPointT>( 1e-6 ), std::abs ( sigma_x + delta[3 ])) ;
3236+ FloatingPointT new_sigma_y = std::max ( static_cast <FloatingPointT>( 1e-6 ), std::abs ( sigma_y + delta[4 ])) ;
32103237 FloatingPointT new_rho = rho + delta[5 ];
32113238
32123239 // Clamp new rho strictly inside (-1, 1) range to prevent zero division in W
@@ -3231,8 +3258,8 @@ namespace TinyDIP
32313258 A = new_A;
32323259 x0 = new_x0;
32333260 y0 = new_y0;
3234- sigma_x = std::abs ( new_sigma_x); // Standard deviations must remain positive
3235- sigma_y = std::abs ( new_sigma_y) ;
3261+ sigma_x = new_sigma_x;
3262+ sigma_y = new_sigma_y;
32363263 rho = new_rho;
32373264 current_sse = new_sse;
32383265 lambda /= static_cast <FloatingPointT>(10.0 ); // Decrease damping factor
0 commit comments