@@ -952,9 +952,85 @@ int fit2DPeaks(unsigned nPeaks, int nrPixelsThisRegion, double *z,
952952
953953 if (nPeaks > 1 ) {
954954 // -----------------------------------------------------------
955- // Joint fit with SBPLX: internally decomposes the high-dimensional
956- // space into lower-dimensional subspaces, making it efficient for
957- // multi-peak fitting while properly handling overlapping peaks.
955+ // Stage 1: Fit each peak INDIVIDUALLY with Nelder-Mead using
956+ // RESIDUAL SUBTRACTION. Before fitting peak p, subtract the
957+ // model contributions of all OTHER peaks from the raw data so
958+ // that peak p only fits its own intensity.
959+ // -----------------------------------------------------------
960+ double x1 [9 ], xl1 [9 ], xu1 [9 ];
961+ double z_residual [nrPixelsThisRegion ];
962+ FunctionData f_data_single = f_data ;
963+ f_data_single .nPeaks = 1 ;
964+ f_data_single .z = z_residual ; // Fit against residual, not raw data
965+
966+ for (unsigned p = 0 ; p < nPeaks ; p ++ ) {
967+ // Compute residual: z - contributions of all peaks except p
968+ for (int i = 0 ; i < nrPixelsThisRegion ; i ++ ) {
969+ double otherContrib = 0.0 ;
970+ for (unsigned j = 0 ; j < nPeaks ; j ++ ) {
971+ if (j == p )
972+ continue ;
973+ double imax_j = x [(8 * j ) + 1 ];
974+ double r_j = x [(8 * j ) + 2 ];
975+ double eta_j = x [(8 * j ) + 3 ];
976+ double mu_j = x [(8 * j ) + 4 ];
977+ double sgr_j = x [(8 * j ) + 5 ];
978+ double slr_j = x [(8 * j ) + 6 ];
979+ double sge_j = x [(8 * j ) + 7 ];
980+ double sle_j = x [(8 * j ) + 8 ];
981+
982+ double DR = Rs [i ] - r_j ;
983+ double R2 = DR * DR ;
984+ double DE = Etas [i ] - eta_j ;
985+ double E2 = DE * DE ;
986+
987+ double invSLR2 = 1.0 / (slr_j * slr_j );
988+ double invSLE2 = 1.0 / (sle_j * sle_j );
989+ double L = 1.0 / ((R2 * invSLR2 + 1.0 ) * (E2 * invSLE2 + 1.0 ));
990+
991+ double invSGR2 = 1.0 / (sgr_j * sgr_j );
992+ double invSGE2 = 1.0 / (sge_j * sge_j );
993+ double G = exp (-0.5 * (R2 * invSGR2 + E2 * invSGE2 ));
994+
995+ otherContrib += imax_j * ((mu_j * L ) + ((1 - mu_j ) * G ));
996+ }
997+ z_residual [i ] = z [i ] - otherContrib ;
998+ }
999+
1000+ // bg
1001+ x1 [0 ] = x [0 ];
1002+ xl1 [0 ] = xl [0 ];
1003+ xu1 [0 ] = xu [0 ];
1004+ // Copy this peak's 8 params & bounds
1005+ for (int j = 1 ; j <= 8 ; j ++ ) {
1006+ x1 [j ] = x [8 * p + j ];
1007+ xl1 [j ] = xl [8 * p + j ];
1008+ xu1 [j ] = xu [8 * p + j ];
1009+ }
1010+
1011+ nlopt_opt opt1 = nlopt_create (NLOPT_LN_NELDERMEAD , 9 );
1012+ if (!opt1 )
1013+ continue ;
1014+
1015+ nlopt_set_lower_bounds (opt1 , xl1 );
1016+ nlopt_set_upper_bounds (opt1 , xu1 );
1017+ nlopt_set_maxtime (opt1 , 2.0 );
1018+ nlopt_set_min_objective (opt1 , peakFittingObjectiveFunction ,
1019+ & f_data_single );
1020+
1021+ double minf1 ;
1022+ nlopt_optimize (opt1 , x1 , & minf1 );
1023+ nlopt_destroy (opt1 );
1024+
1025+ // Write back fitted peak params (NOT background — keep shared bg
1026+ // consistent across the decomposition)
1027+ for (int j = 1 ; j <= 8 ; j ++ ) {
1028+ x [8 * p + j ] = x1 [j ];
1029+ }
1030+ }
1031+
1032+ // -----------------------------------------------------------
1033+ // Stage 2: Joint refinement with SBPLX using corrected initials
9581034 // -----------------------------------------------------------
9591035 nlopt_opt opt = nlopt_create (NLOPT_LN_SBPLX , n );
9601036 if (!opt )
0 commit comments