pivtools 0.1.3__cp311-cp311-win_amd64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pivtools-0.1.3.dist-info/METADATA +222 -0
- pivtools-0.1.3.dist-info/RECORD +127 -0
- pivtools-0.1.3.dist-info/WHEEL +5 -0
- pivtools-0.1.3.dist-info/entry_points.txt +3 -0
- pivtools-0.1.3.dist-info/top_level.txt +3 -0
- pivtools_cli/__init__.py +5 -0
- pivtools_cli/_build_marker.c +25 -0
- pivtools_cli/_build_marker.cp311-win_amd64.pyd +0 -0
- pivtools_cli/cli.py +225 -0
- pivtools_cli/example.py +139 -0
- pivtools_cli/lib/PIV_2d_cross_correlate.c +334 -0
- pivtools_cli/lib/PIV_2d_cross_correlate.h +22 -0
- pivtools_cli/lib/common.h +36 -0
- pivtools_cli/lib/interp2custom.c +146 -0
- pivtools_cli/lib/interp2custom.h +48 -0
- pivtools_cli/lib/peak_locate_gsl.c +711 -0
- pivtools_cli/lib/peak_locate_gsl.h +40 -0
- pivtools_cli/lib/peak_locate_gsl_print.c +736 -0
- pivtools_cli/lib/peak_locate_lm.c +751 -0
- pivtools_cli/lib/peak_locate_lm.h +27 -0
- pivtools_cli/lib/xcorr.c +342 -0
- pivtools_cli/lib/xcorr.h +31 -0
- pivtools_cli/lib/xcorr_cache.c +78 -0
- pivtools_cli/lib/xcorr_cache.h +26 -0
- pivtools_cli/piv/interp2custom/interp2custom.py +69 -0
- pivtools_cli/piv/piv.py +240 -0
- pivtools_cli/piv/piv_backend/base.py +825 -0
- pivtools_cli/piv/piv_backend/cpu_instantaneous.py +1005 -0
- pivtools_cli/piv/piv_backend/factory.py +28 -0
- pivtools_cli/piv/piv_backend/gpu_instantaneous.py +15 -0
- pivtools_cli/piv/piv_backend/infilling.py +445 -0
- pivtools_cli/piv/piv_backend/outlier_detection.py +306 -0
- pivtools_cli/piv/piv_backend/profile_cpu_instantaneous.py +230 -0
- pivtools_cli/piv/piv_result.py +40 -0
- pivtools_cli/piv/save_results.py +342 -0
- pivtools_cli/piv_cluster/cluster.py +108 -0
- pivtools_cli/preprocessing/filters.py +399 -0
- pivtools_cli/preprocessing/preprocess.py +79 -0
- pivtools_cli/tests/helpers.py +107 -0
- pivtools_cli/tests/instantaneous_piv/test_piv_integration.py +167 -0
- pivtools_cli/tests/instantaneous_piv/test_piv_integration_multi.py +553 -0
- pivtools_cli/tests/preprocessing/test_filters.py +41 -0
- pivtools_core/__init__.py +5 -0
- pivtools_core/config.py +703 -0
- pivtools_core/config.yaml +135 -0
- pivtools_core/image_handling/__init__.py +0 -0
- pivtools_core/image_handling/load_images.py +464 -0
- pivtools_core/image_handling/readers/__init__.py +53 -0
- pivtools_core/image_handling/readers/generic_readers.py +50 -0
- pivtools_core/image_handling/readers/lavision_reader.py +190 -0
- pivtools_core/image_handling/readers/registry.py +24 -0
- pivtools_core/paths.py +49 -0
- pivtools_core/vector_loading.py +248 -0
- pivtools_gui/__init__.py +3 -0
- pivtools_gui/app.py +687 -0
- pivtools_gui/calibration/__init__.py +0 -0
- pivtools_gui/calibration/app/__init__.py +0 -0
- pivtools_gui/calibration/app/views.py +1186 -0
- pivtools_gui/calibration/calibration_planar/planar_calibration_production.py +570 -0
- pivtools_gui/calibration/vector_calibration_production.py +544 -0
- pivtools_gui/config.py +703 -0
- pivtools_gui/image_handling/__init__.py +0 -0
- pivtools_gui/image_handling/load_images.py +464 -0
- pivtools_gui/image_handling/readers/__init__.py +53 -0
- pivtools_gui/image_handling/readers/generic_readers.py +50 -0
- pivtools_gui/image_handling/readers/lavision_reader.py +190 -0
- pivtools_gui/image_handling/readers/registry.py +24 -0
- pivtools_gui/masking/__init__.py +0 -0
- pivtools_gui/masking/app/__init__.py +0 -0
- pivtools_gui/masking/app/views.py +123 -0
- pivtools_gui/paths.py +49 -0
- pivtools_gui/piv_runner.py +261 -0
- pivtools_gui/pivtools.py +58 -0
- pivtools_gui/plotting/__init__.py +0 -0
- pivtools_gui/plotting/app/__init__.py +0 -0
- pivtools_gui/plotting/app/views.py +1671 -0
- pivtools_gui/plotting/plot_maker.py +220 -0
- pivtools_gui/post_processing/POD/__init__.py +0 -0
- pivtools_gui/post_processing/POD/app/__init__.py +0 -0
- pivtools_gui/post_processing/POD/app/views.py +647 -0
- pivtools_gui/post_processing/POD/pod_decompose.py +979 -0
- pivtools_gui/post_processing/POD/views.py +1096 -0
- pivtools_gui/post_processing/__init__.py +0 -0
- pivtools_gui/static/404.html +1 -0
- pivtools_gui/static/_next/static/chunks/117-d5793c8e79de5511.js +2 -0
- pivtools_gui/static/_next/static/chunks/484-cfa8b9348ce4f00e.js +1 -0
- pivtools_gui/static/_next/static/chunks/869-320a6b9bdafbb6d3.js +1 -0
- pivtools_gui/static/_next/static/chunks/app/_not-found/page-12f067ceb7415e55.js +1 -0
- pivtools_gui/static/_next/static/chunks/app/layout-b907d5f31ac82e9d.js +1 -0
- pivtools_gui/static/_next/static/chunks/app/page-334cc4e8444cde2f.js +1 -0
- pivtools_gui/static/_next/static/chunks/fd9d1056-ad15f396ddf9b7e5.js +1 -0
- pivtools_gui/static/_next/static/chunks/framework-f66176bb897dc684.js +1 -0
- pivtools_gui/static/_next/static/chunks/main-a1b3ced4d5f6d998.js +1 -0
- pivtools_gui/static/_next/static/chunks/main-app-8a63c6f5e7baee11.js +1 -0
- pivtools_gui/static/_next/static/chunks/pages/_app-72b849fbd24ac258.js +1 -0
- pivtools_gui/static/_next/static/chunks/pages/_error-7ba65e1336b92748.js +1 -0
- pivtools_gui/static/_next/static/chunks/polyfills-42372ed130431b0a.js +1 -0
- pivtools_gui/static/_next/static/chunks/webpack-4a8ca7c99e9bb3d8.js +1 -0
- pivtools_gui/static/_next/static/css/7d3f2337d7ea12a5.css +3 -0
- pivtools_gui/static/_next/static/vQeR20OUdSSKlK4vukC4q/_buildManifest.js +1 -0
- pivtools_gui/static/_next/static/vQeR20OUdSSKlK4vukC4q/_ssgManifest.js +1 -0
- pivtools_gui/static/file.svg +1 -0
- pivtools_gui/static/globe.svg +1 -0
- pivtools_gui/static/grid.svg +8 -0
- pivtools_gui/static/index.html +1 -0
- pivtools_gui/static/index.txt +8 -0
- pivtools_gui/static/next.svg +1 -0
- pivtools_gui/static/vercel.svg +1 -0
- pivtools_gui/static/window.svg +1 -0
- pivtools_gui/stereo_reconstruction/__init__.py +0 -0
- pivtools_gui/stereo_reconstruction/app/__init__.py +0 -0
- pivtools_gui/stereo_reconstruction/app/views.py +1985 -0
- pivtools_gui/stereo_reconstruction/stereo_calibration_production.py +606 -0
- pivtools_gui/stereo_reconstruction/stereo_reconstruction_production.py +544 -0
- pivtools_gui/utils.py +63 -0
- pivtools_gui/vector_loading.py +248 -0
- pivtools_gui/vector_merging/__init__.py +1 -0
- pivtools_gui/vector_merging/app/__init__.py +1 -0
- pivtools_gui/vector_merging/app/views.py +759 -0
- pivtools_gui/vector_statistics/app/__init__.py +1 -0
- pivtools_gui/vector_statistics/app/views.py +710 -0
- pivtools_gui/vector_statistics/ensemble_statistics.py +49 -0
- pivtools_gui/vector_statistics/instantaneous_statistics.py +311 -0
- pivtools_gui/video_maker/__init__.py +0 -0
- pivtools_gui/video_maker/app/__init__.py +0 -0
- pivtools_gui/video_maker/app/views.py +436 -0
- pivtools_gui/video_maker/video_maker.py +662 -0
|
@@ -0,0 +1,751 @@
|
|
|
1
|
+
#include "peak_locate_lm.h"
|
|
2
|
+
#include "common.h"
|
|
3
|
+
#include <stdio.h>
|
|
4
|
+
#include <stdlib.h>
|
|
5
|
+
#include <string.h>
|
|
6
|
+
#include <math.h>
|
|
7
|
+
#include <float.h>
|
|
8
|
+
|
|
9
|
+
/******************************************************************************
|
|
10
|
+
* Lightweight Levenberg-Marquardt Implementation for Gaussian Peak Fitting
|
|
11
|
+
*
|
|
12
|
+
* OVERVIEW:
|
|
13
|
+
* This module provides a fast, numerically stable implementation of Gaussian
|
|
14
|
+
* peak fitting for Particle Image Velocimetry (PIV) correlation analysis.
|
|
15
|
+
* It uses the Levenberg-Marquardt algorithm to fit 2D Gaussian models to
|
|
16
|
+
* correlation peaks, enabling sub-pixel displacement accuracy.
|
|
17
|
+
*
|
|
18
|
+
* SUPPORTED GAUSSIAN MODELS:
|
|
19
|
+
* - 3-point parabolic estimator (fast fallback/initial guess)
|
|
20
|
+
* - 4-DOF: Circular Gaussian (A, i0, j0, s)
|
|
21
|
+
* - 5-DOF: Elliptical Gaussian (A, i0, j0, sx, sy)
|
|
22
|
+
* - 6-DOF: Rotated elliptical Gaussian with correlation (A, i0, j0, sx, sy, sxy)
|
|
23
|
+
*
|
|
24
|
+
* ALGORITHM OVERVIEW:
|
|
25
|
+
* 1. Initial peak detection using brute-force search in central region
|
|
26
|
+
* 2. Sub-window extraction around detected peak
|
|
27
|
+
* 3. Initial parameter estimation using 3-point parabolic fit
|
|
28
|
+
* 4. Levenberg-Marquardt nonlinear least squares optimization
|
|
29
|
+
* 5. Peak subtraction for multi-peak detection
|
|
30
|
+
*
|
|
31
|
+
* KEY FEATURES:
|
|
32
|
+
* - Fast Cholesky-based linear system solving
|
|
33
|
+
* - Robust parameter bounds and damping
|
|
34
|
+
* - Automatic model selection based on fit type
|
|
35
|
+
* - Multi-peak capability with sequential fitting
|
|
36
|
+
*
|
|
37
|
+
* TECHNICAL NOTES:
|
|
38
|
+
* - Uses inverse covariance parameterization for 6-DOF model
|
|
39
|
+
* - Implements trust-region Levenberg-Marquardt with adaptive damping
|
|
40
|
+
* - Memory efficient with fixed-size matrices (max 6x6)
|
|
41
|
+
* - Thread-safe (no global state)
|
|
42
|
+
*
|
|
43
|
+
******************************************************************************/
|
|
44
|
+
|
|
45
|
+
/* Fast 3-point parabolic estimator - used as initial guess and fallback */
|
|
46
|
+
static void threept_estimate(const float *xcorr, const int *N, float *peak_loc, float *A_out, float *sx_out, float *sy_out)
|
|
47
|
+
{
|
|
48
|
+
float x_fit[3], y_fit[3];
|
|
49
|
+
int i;
|
|
50
|
+
|
|
51
|
+
/* Extract 3 points along each axis centered on the correlation peak */
|
|
52
|
+
for(i = 0; i < 3; ++i)
|
|
53
|
+
{
|
|
54
|
+
x_fit[i] = xcorr[(i - 1 + (N[0]-1)/2) * N[1] + (N[1]-1)/2];
|
|
55
|
+
y_fit[i] = xcorr[(N[0]-1)/2 * N[1] + (i - 1 + (N[1]-1)/2)];
|
|
56
|
+
/* Convert to log space for parabolic fitting (handles exponential decay) */
|
|
57
|
+
x_fit[i] = (float)log((x_fit[i] < FLT_EPSILON) ? FLT_EPSILON : x_fit[i]);
|
|
58
|
+
y_fit[i] = (float)log((y_fit[i] < FLT_EPSILON) ? FLT_EPSILON : y_fit[i]);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/* Parabolic fit: peak location is at i0 = numer/denom */
|
|
62
|
+
float denom_x = 2*x_fit[0] - 4*x_fit[1] + 2*x_fit[2];
|
|
63
|
+
float denom_y = 2*y_fit[0] - 4*y_fit[1] + 2*y_fit[2];
|
|
64
|
+
|
|
65
|
+
if(fabs(denom_x) > FLT_EPSILON)
|
|
66
|
+
peak_loc[0] = (x_fit[0] - x_fit[2]) / denom_x;
|
|
67
|
+
else
|
|
68
|
+
peak_loc[0] = 0.0f;
|
|
69
|
+
|
|
70
|
+
if(fabs(denom_y) > FLT_EPSILON)
|
|
71
|
+
peak_loc[1] = (y_fit[0] - y_fit[2]) / denom_y;
|
|
72
|
+
else
|
|
73
|
+
peak_loc[1] = 0.0f;
|
|
74
|
+
|
|
75
|
+
*A_out = xcorr[(N[0]-1)/2 * N[1] + (N[1]-1)/2];
|
|
76
|
+
*sx_out = (float)sqrt(-4.0f / (denom_x + FLT_EPSILON));
|
|
77
|
+
*sy_out = (float)sqrt(-4.0f / (denom_y + FLT_EPSILON));
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/* Evaluate 4-DOF Gaussian: A * exp(-((i-i0)^2 + (j-j0)^2)/s^2) - circular Gaussian */
|
|
81
|
+
static inline float eval_gauss4(float i, float j, float A, float i0, float j0, float s)
|
|
82
|
+
{
|
|
83
|
+
float di = (i - i0) / s;
|
|
84
|
+
float dj = (j - j0) / s;
|
|
85
|
+
return A * expf(-(di*di + dj*dj));
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/* Evaluate 5-DOF Gaussian: A * exp(-((i-i0)^2/sx^2 + (j-j0)^2/sy^2)) - elliptical */
|
|
89
|
+
static inline float eval_gauss5(float i, float j, float A, float i0, float j0, float sx, float sy)
|
|
90
|
+
{
|
|
91
|
+
float di = (i - i0) / sx;
|
|
92
|
+
float dj = (j - j0) / sy;
|
|
93
|
+
return A * expf(-(di*di + dj*dj));
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/* Evaluate 6-DOF Gaussian with correlation term - rotated elliptical
|
|
97
|
+
* NOTE: sx, sy, sxy are elements of the INVERSE covariance matrix, not standard deviations
|
|
98
|
+
* Model: A * exp(-0.5 * (di^2/sx + dj^2/sy + 2*di*dj*sxy))
|
|
99
|
+
* where sx = sigma_x^2, sy = sigma_y^2 in the inverse covariance representation
|
|
100
|
+
*/
|
|
101
|
+
static inline float eval_gauss6(float i, float j, float A, float i0, float j0, float sx, float sy, float sxy)
|
|
102
|
+
{
|
|
103
|
+
float di = i - i0;
|
|
104
|
+
float dj = j - j0;
|
|
105
|
+
return A * expf(-0.5f * (di*di/sx + dj*dj/sy + 2.0f*di*dj*sxy));
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/* Compute residual and Jacobian for 4-DOF Gaussian fit */
|
|
109
|
+
static float compute_residual_jacobian_4dof(
|
|
110
|
+
const float *xcorr, const int *N,
|
|
111
|
+
float A, float i0, float j0, float s,
|
|
112
|
+
float *JtJ, float *Jtr, int compute_jacobian)
|
|
113
|
+
{
|
|
114
|
+
int ii, jj, idx;
|
|
115
|
+
float residual_sum = 0.0f;
|
|
116
|
+
const int n_params = 4;
|
|
117
|
+
|
|
118
|
+
if(compute_jacobian) {
|
|
119
|
+
memset(JtJ, 0, n_params * n_params * sizeof(float));
|
|
120
|
+
memset(Jtr, 0, n_params * sizeof(float));
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
for(ii = 0; ii < N[0]; ++ii) {
|
|
124
|
+
float i = (float)(ii - (N[0]-1)/2);
|
|
125
|
+
|
|
126
|
+
for(jj = 0; jj < N[1]; ++jj) {
|
|
127
|
+
float j = (float)(jj - (N[1]-1)/2);
|
|
128
|
+
idx = ii * N[1] + jj;
|
|
129
|
+
|
|
130
|
+
float pred = eval_gauss4(i, j, A, i0, j0, s);
|
|
131
|
+
float r = pred - xcorr[idx];
|
|
132
|
+
residual_sum += r * r;
|
|
133
|
+
|
|
134
|
+
if(compute_jacobian) {
|
|
135
|
+
float di = (i - i0) / s;
|
|
136
|
+
float dj = (j - j0) / s;
|
|
137
|
+
float r2 = di*di + dj*dj;
|
|
138
|
+
|
|
139
|
+
/* Compute Jacobian matrix elements (partial derivatives) */
|
|
140
|
+
float J[4];
|
|
141
|
+
J[0] = pred / A; /* dF/dA */
|
|
142
|
+
J[1] = 2.0f * pred * di / s; /* dF/di0 */
|
|
143
|
+
J[2] = 2.0f * pred * dj / s; /* dF/dj0 */
|
|
144
|
+
J[3] = 2.0f * pred * r2 / s; /* dF/ds */
|
|
145
|
+
|
|
146
|
+
/* Accumulate J^T * J and J^T * r for normal equations */
|
|
147
|
+
for(int p1 = 0; p1 < n_params; ++p1) {
|
|
148
|
+
Jtr[p1] += J[p1] * r;
|
|
149
|
+
for(int p2 = 0; p2 <= p1; ++p2) {
|
|
150
|
+
JtJ[p1 * n_params + p2] += J[p1] * J[p2];
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if(compute_jacobian) {
|
|
158
|
+
for(int p1 = 0; p1 < n_params; ++p1) {
|
|
159
|
+
for(int p2 = p1 + 1; p2 < n_params; ++p2) {
|
|
160
|
+
JtJ[p1 * n_params + p2] = JtJ[p2 * n_params + p1];
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return residual_sum;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/* Compute residual and Jacobian for 5-DOF Gaussian fit */
|
|
169
|
+
static float compute_residual_jacobian_5dof(
|
|
170
|
+
const float *xcorr, const int *N,
|
|
171
|
+
float A, float i0, float j0, float sx, float sy,
|
|
172
|
+
float *JtJ, float *Jtr, int compute_jacobian)
|
|
173
|
+
{
|
|
174
|
+
int ii, jj, idx;
|
|
175
|
+
float residual_sum = 0.0f;
|
|
176
|
+
const int n_params = 5;
|
|
177
|
+
|
|
178
|
+
if(compute_jacobian) {
|
|
179
|
+
memset(JtJ, 0, n_params * n_params * sizeof(float));
|
|
180
|
+
memset(Jtr, 0, n_params * sizeof(float));
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
for(ii = 0; ii < N[0]; ++ii) {
|
|
184
|
+
float i = (float)(ii - (N[0]-1)/2);
|
|
185
|
+
|
|
186
|
+
for(jj = 0; jj < N[1]; ++jj) {
|
|
187
|
+
float j = (float)(jj - (N[1]-1)/2);
|
|
188
|
+
idx = ii * N[1] + jj;
|
|
189
|
+
|
|
190
|
+
float pred = eval_gauss5(i, j, A, i0, j0, sx, sy);
|
|
191
|
+
float r = pred - xcorr[idx];
|
|
192
|
+
residual_sum += r * r;
|
|
193
|
+
|
|
194
|
+
if(compute_jacobian) {
|
|
195
|
+
float di = (i - i0) / sx;
|
|
196
|
+
float dj = (j - j0) / sy;
|
|
197
|
+
|
|
198
|
+
/* Compute Jacobian matrix elements (partial derivatives) */
|
|
199
|
+
float J[5];
|
|
200
|
+
J[0] = pred / A; /* dF/dA */
|
|
201
|
+
J[1] = 2.0f * pred * di / sx; /* dF/di0 */
|
|
202
|
+
J[2] = 2.0f * pred * dj / sy; /* dF/dj0 */
|
|
203
|
+
J[3] = 2.0f * pred * di * di / sx; /* dF/dsx */
|
|
204
|
+
J[4] = 2.0f * pred * dj * dj / sy; /* dF/dsy */
|
|
205
|
+
|
|
206
|
+
/* Accumulate J^T * J and J^T * r for normal equations */
|
|
207
|
+
for(int p1 = 0; p1 < n_params; ++p1) {
|
|
208
|
+
Jtr[p1] += J[p1] * r;
|
|
209
|
+
for(int p2 = 0; p2 <= p1; ++p2) {
|
|
210
|
+
JtJ[p1 * n_params + p2] += J[p1] * J[p2];
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if(compute_jacobian) {
|
|
218
|
+
for(int p1 = 0; p1 < n_params; ++p1) {
|
|
219
|
+
for(int p2 = p1 + 1; p2 < n_params; ++p2) {
|
|
220
|
+
JtJ[p1 * n_params + p2] = JtJ[p2 * n_params + p1];
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
return residual_sum;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/* Compute residual and Jacobian for 6-DOF Gaussian fit */
|
|
229
|
+
static float compute_residual_jacobian_6dof(
|
|
230
|
+
const float *xcorr, const int *N,
|
|
231
|
+
float A, float i0, float j0, float sx, float sy, float sxy,
|
|
232
|
+
float *JtJ, float *Jtr, int compute_jacobian)
|
|
233
|
+
{
|
|
234
|
+
int ii, jj, idx;
|
|
235
|
+
float residual_sum = 0.0f;
|
|
236
|
+
const int n_params = 6;
|
|
237
|
+
|
|
238
|
+
if(compute_jacobian) {
|
|
239
|
+
memset(JtJ, 0, n_params * n_params * sizeof(float));
|
|
240
|
+
memset(Jtr, 0, n_params * sizeof(float));
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
for(ii = 0; ii < N[0]; ++ii) {
|
|
244
|
+
float i = (float)(ii - (N[0]-1)/2);
|
|
245
|
+
|
|
246
|
+
for(jj = 0; jj < N[1]; ++jj) {
|
|
247
|
+
float j = (float)(jj - (N[1]-1)/2);
|
|
248
|
+
idx = ii * N[1] + jj;
|
|
249
|
+
|
|
250
|
+
float pred = eval_gauss6(i, j, A, i0, j0, sx, sy, sxy);
|
|
251
|
+
float r = pred - xcorr[idx];
|
|
252
|
+
residual_sum += r * r;
|
|
253
|
+
|
|
254
|
+
if(compute_jacobian) {
|
|
255
|
+
float di = i - i0;
|
|
256
|
+
float dj = j - j0;
|
|
257
|
+
|
|
258
|
+
/* Compute Jacobian matrix elements (partial derivatives) */
|
|
259
|
+
float J[6];
|
|
260
|
+
J[0] = pred / A; /* dF/dA */
|
|
261
|
+
J[1] = pred * (di/sx + dj*sxy); /* dF/di0 */
|
|
262
|
+
J[2] = pred * (dj/sy + di*sxy); /* dF/dj0 */
|
|
263
|
+
J[3] = 0.5f * pred * di * di / (sx * sx); /* dF/dsx - */
|
|
264
|
+
J[4] = 0.5f * pred * dj * dj / (sy * sy); /* dF/dsy - */
|
|
265
|
+
J[5] = -pred * di * dj; /* dF/dsxy */
|
|
266
|
+
|
|
267
|
+
/* Accumulate J^T * J and J^T * r for normal equations */
|
|
268
|
+
for(int p1 = 0; p1 < n_params; ++p1) {
|
|
269
|
+
Jtr[p1] += J[p1] * r;
|
|
270
|
+
for(int p2 = 0; p2 <= p1; ++p2) {
|
|
271
|
+
JtJ[p1 * n_params + p2] += J[p1] * J[p2];
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
if(compute_jacobian) {
|
|
279
|
+
for(int p1 = 0; p1 < n_params; ++p1) {
|
|
280
|
+
for(int p2 = p1 + 1; p2 < n_params; ++p2) {
|
|
281
|
+
JtJ[p1 * n_params + p2] = JtJ[p2 * n_params + p1];
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
return residual_sum;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/* Solve (JtJ + lambda*diag(JtJ)) * delta = -Jtr using Cholesky decomposition */
|
|
290
|
+
static int solve_lm_step(const float *JtJ, const float *Jtr, float lambda, float *delta, int n)
|
|
291
|
+
{
|
|
292
|
+
float A[36]; /* Max 6x6 matrix */
|
|
293
|
+
float L[36];
|
|
294
|
+
float y[6];
|
|
295
|
+
int i, j, k;
|
|
296
|
+
|
|
297
|
+
/* Form damped normal matrix: A = JtJ + lambda * diag(JtJ) */
|
|
298
|
+
memcpy(A, JtJ, n * n * sizeof(float));
|
|
299
|
+
for(i = 0; i < n; ++i) {
|
|
300
|
+
A[i * n + i] *= (1.0f + lambda);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/* Cholesky decomposition: A = L * L^T */
|
|
304
|
+
memset(L, 0, n * n * sizeof(float));
|
|
305
|
+
for(i = 0; i < n; ++i) {
|
|
306
|
+
for(j = 0; j <= i; ++j) {
|
|
307
|
+
float sum = A[i * n + j];
|
|
308
|
+
for(k = 0; k < j; ++k) {
|
|
309
|
+
sum -= L[i * n + k] * L[j * n + k];
|
|
310
|
+
}
|
|
311
|
+
if(i == j) {
|
|
312
|
+
if(sum <= 0.0f) return -1; /* Matrix not positive definite */
|
|
313
|
+
L[i * n + j] = sqrtf(sum);
|
|
314
|
+
} else {
|
|
315
|
+
L[i * n + j] = sum / L[j * n + j];
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/* Forward substitution: L * y = -Jtr */
|
|
321
|
+
for(i = 0; i < n; ++i) {
|
|
322
|
+
float sum = -Jtr[i];
|
|
323
|
+
for(j = 0; j < i; ++j) {
|
|
324
|
+
sum -= L[i * n + j] * y[j];
|
|
325
|
+
}
|
|
326
|
+
y[i] = sum / L[i * n + i];
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/* Back substitution: L^T * delta = y */
|
|
330
|
+
for(i = n - 1; i >= 0; --i) {
|
|
331
|
+
float sum = y[i];
|
|
332
|
+
for(j = i + 1; j < n; ++j) {
|
|
333
|
+
sum -= L[j * n + i] * delta[j];
|
|
334
|
+
}
|
|
335
|
+
delta[i] = sum / L[i * n + i];
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
return 0;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/* Fast Levenberg-Marquardt for 4-DOF Gaussian fitting (circular) */
|
|
342
|
+
static void lm_gauss4_fit(const float *xcorr, const int *N, float *peak_loc, float *fitval, float *sig)
|
|
343
|
+
{
|
|
344
|
+
float A, i0, j0, s;
|
|
345
|
+
float JtJ[16], Jtr[4], delta[4];
|
|
346
|
+
float lambda = 0.01f;
|
|
347
|
+
float residual, new_residual;
|
|
348
|
+
int iter, ii, jj, idx;
|
|
349
|
+
const int max_iter = 20;
|
|
350
|
+
const float tol = 1e-6f;
|
|
351
|
+
|
|
352
|
+
/* Get initial guess using 3-point parabolic estimator */
|
|
353
|
+
float sx, sy;
|
|
354
|
+
threept_estimate(xcorr, N, peak_loc, &A, &sx, &sy);
|
|
355
|
+
i0 = peak_loc[0];
|
|
356
|
+
j0 = peak_loc[1];
|
|
357
|
+
s = sqrtf(sx * sx + sy * sy); /* Combined width */
|
|
358
|
+
|
|
359
|
+
/* Clamp parameters to reasonable bounds */
|
|
360
|
+
i0 = fminf(fmaxf(i0, -2.0f), 2.0f);
|
|
361
|
+
j0 = fminf(fmaxf(j0, -2.0f), 2.0f);
|
|
362
|
+
s = fminf(fmaxf(s, 0.5f), 3.0f);
|
|
363
|
+
|
|
364
|
+
/* Compute initial residual and Jacobian */
|
|
365
|
+
residual = compute_residual_jacobian_4dof(xcorr, N, A, i0, j0, s, JtJ, Jtr, 1);
|
|
366
|
+
|
|
367
|
+
/* Levenberg-Marquardt optimization loop */
|
|
368
|
+
for(iter = 0; iter < max_iter; ++iter) {
|
|
369
|
+
/* Solve for parameter update */
|
|
370
|
+
if(solve_lm_step(JtJ, Jtr, lambda, delta, 4) != 0) break;
|
|
371
|
+
|
|
372
|
+
/* Apply parameter update with bounds checking */
|
|
373
|
+
float A_new = A + delta[0];
|
|
374
|
+
float i0_new = i0 + delta[1];
|
|
375
|
+
float j0_new = j0 + delta[2];
|
|
376
|
+
float s_new = s + delta[3];
|
|
377
|
+
|
|
378
|
+
A_new = fmaxf(A_new, A * 0.5f);
|
|
379
|
+
i0_new = fminf(fmaxf(i0_new, -2.5f), 2.5f);
|
|
380
|
+
j0_new = fminf(fmaxf(j0_new, -2.5f), 2.5f);
|
|
381
|
+
s_new = fminf(fmaxf(s_new, 0.25f), 4.0f);
|
|
382
|
+
|
|
383
|
+
/* Evaluate new residual */
|
|
384
|
+
new_residual = compute_residual_jacobian_4dof(xcorr, N, A_new, i0_new, j0_new, s_new, NULL, NULL, 0);
|
|
385
|
+
|
|
386
|
+
if(new_residual < residual) {
|
|
387
|
+
/* Accept step: update parameters */
|
|
388
|
+
A = A_new; i0 = i0_new; j0 = j0_new; s = s_new;
|
|
389
|
+
float improvement = (residual - new_residual) / (residual + FLT_EPSILON);
|
|
390
|
+
residual = new_residual;
|
|
391
|
+
lambda *= 0.5f; /* Decrease damping */
|
|
392
|
+
compute_residual_jacobian_4dof(xcorr, N, A, i0, j0, s, JtJ, Jtr, 1);
|
|
393
|
+
if(improvement < tol) break; /* Converged */
|
|
394
|
+
} else {
|
|
395
|
+
lambda *= 2.0f; /* Increase damping */
|
|
396
|
+
if(lambda > 1e6f) break; /* Diverged */
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
/* Store final results */
|
|
401
|
+
peak_loc[0] = i0;
|
|
402
|
+
peak_loc[1] = j0;
|
|
403
|
+
sig[0] = s;
|
|
404
|
+
sig[1] = s;
|
|
405
|
+
sig[2] = 0.0f;
|
|
406
|
+
|
|
407
|
+
/* Generate fitted values if requested */
|
|
408
|
+
if(fitval) {
|
|
409
|
+
for(ii = 0; ii < N[0]; ++ii) {
|
|
410
|
+
float i = (float)(ii - (N[0]-1)/2);
|
|
411
|
+
for(jj = 0; jj < N[1]; ++jj) {
|
|
412
|
+
float j = (float)(jj - (N[1]-1)/2);
|
|
413
|
+
idx = ii * N[1] + jj;
|
|
414
|
+
fitval[idx] = eval_gauss4(i, j, A, i0, j0, s);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
/* Fast Levenberg-Marquardt for 5-DOF Gaussian fitting */
|
|
421
|
+
static void lm_gauss5_fit(const float *xcorr, const int *N, float *peak_loc, float *fitval, float *sig)
|
|
422
|
+
{
|
|
423
|
+
float A, i0, j0, sx, sy;
|
|
424
|
+
float JtJ[25], Jtr[5], delta[5];
|
|
425
|
+
float lambda = 0.01f;
|
|
426
|
+
float residual, new_residual;
|
|
427
|
+
int iter, ii, jj, idx;
|
|
428
|
+
const int max_iter = 20;
|
|
429
|
+
const float tol = 1e-6f;
|
|
430
|
+
|
|
431
|
+
/* Get initial guess using 3-point parabolic estimator */
|
|
432
|
+
threept_estimate(xcorr, N, peak_loc, &A, &sx, &sy);
|
|
433
|
+
i0 = peak_loc[0];
|
|
434
|
+
j0 = peak_loc[1];
|
|
435
|
+
|
|
436
|
+
/* Clamp parameters to reasonable bounds */
|
|
437
|
+
i0 = fminf(fmaxf(i0, -2.0f), 2.0f);
|
|
438
|
+
j0 = fminf(fmaxf(j0, -2.0f), 2.0f);
|
|
439
|
+
sx = fminf(fmaxf(sx, 0.5f), 3.0f);
|
|
440
|
+
sy = fminf(fmaxf(sy, 0.5f), 3.0f);
|
|
441
|
+
|
|
442
|
+
/* Compute initial residual and Jacobian */
|
|
443
|
+
residual = compute_residual_jacobian_5dof(xcorr, N, A, i0, j0, sx, sy, JtJ, Jtr, 1);
|
|
444
|
+
|
|
445
|
+
/* Levenberg-Marquardt optimization loop */
|
|
446
|
+
for(iter = 0; iter < max_iter; ++iter) {
|
|
447
|
+
/* Solve for parameter update */
|
|
448
|
+
if(solve_lm_step(JtJ, Jtr, lambda, delta, 5) != 0) break;
|
|
449
|
+
|
|
450
|
+
/* Apply parameter update with bounds checking */
|
|
451
|
+
float A_new = A + delta[0];
|
|
452
|
+
float i0_new = i0 + delta[1];
|
|
453
|
+
float j0_new = j0 + delta[2];
|
|
454
|
+
float sx_new = sx + delta[3];
|
|
455
|
+
float sy_new = sy + delta[4];
|
|
456
|
+
|
|
457
|
+
A_new = fmaxf(A_new, A * 0.5f);
|
|
458
|
+
i0_new = fminf(fmaxf(i0_new, -2.5f), 2.5f);
|
|
459
|
+
j0_new = fminf(fmaxf(j0_new, -2.5f), 2.5f);
|
|
460
|
+
sx_new = fminf(fmaxf(sx_new, 0.25f), 4.0f);
|
|
461
|
+
sy_new = fminf(fmaxf(sy_new, 0.25f), 4.0f);
|
|
462
|
+
|
|
463
|
+
/* Evaluate new residual */
|
|
464
|
+
new_residual = compute_residual_jacobian_5dof(xcorr, N, A_new, i0_new, j0_new, sx_new, sy_new, NULL, NULL, 0);
|
|
465
|
+
|
|
466
|
+
if(new_residual < residual) {
|
|
467
|
+
/* Accept step: update parameters */
|
|
468
|
+
A = A_new; i0 = i0_new; j0 = j0_new; sx = sx_new; sy = sy_new;
|
|
469
|
+
float improvement = (residual - new_residual) / (residual + FLT_EPSILON);
|
|
470
|
+
residual = new_residual;
|
|
471
|
+
lambda *= 0.5f; /* Decrease damping */
|
|
472
|
+
compute_residual_jacobian_5dof(xcorr, N, A, i0, j0, sx, sy, JtJ, Jtr, 1);
|
|
473
|
+
if(improvement < tol) break; /* Converged */
|
|
474
|
+
} else {
|
|
475
|
+
lambda *= 2.0f; /* Increase damping */
|
|
476
|
+
if(lambda > 1e6f) break; /* Diverged */
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
/* Store final results */
|
|
481
|
+
peak_loc[0] = i0;
|
|
482
|
+
peak_loc[1] = j0;
|
|
483
|
+
sig[0] = sx;
|
|
484
|
+
sig[1] = sy;
|
|
485
|
+
sig[2] = 0.0f;
|
|
486
|
+
|
|
487
|
+
/* Generate fitted values if requested */
|
|
488
|
+
if(fitval) {
|
|
489
|
+
for(ii = 0; ii < N[0]; ++ii) {
|
|
490
|
+
float i = (float)(ii - (N[0]-1)/2);
|
|
491
|
+
for(jj = 0; jj < N[1]; ++jj) {
|
|
492
|
+
float j = (float)(jj - (N[1]-1)/2);
|
|
493
|
+
idx = ii * N[1] + jj;
|
|
494
|
+
fitval[idx] = eval_gauss5(i, j, A, i0, j0, sx, sy);
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
/* Fast Levenberg-Marquardt for 6-DOF Gaussian fitting
|
|
501
|
+
*/
|
|
502
|
+
static void lm_gauss6_fit(const float *xcorr, const int *N, float *peak_loc, float *fitval, float *sig)
|
|
503
|
+
{
|
|
504
|
+
float A, i0, j0, sx, sy, sxy;
|
|
505
|
+
float JtJ[36], Jtr[6], delta[6];
|
|
506
|
+
float lambda = 0.01f;
|
|
507
|
+
float residual, new_residual;
|
|
508
|
+
int iter, ii, jj, idx;
|
|
509
|
+
const int max_iter = 20;
|
|
510
|
+
const float tol = 1e-6f;
|
|
511
|
+
|
|
512
|
+
/* Get initial guess using 3-point parabolic estimator */
|
|
513
|
+
threept_estimate(xcorr, N, peak_loc, &A, &sx, &sy);
|
|
514
|
+
i0 = peak_loc[0];
|
|
515
|
+
j0 = peak_loc[1];
|
|
516
|
+
sxy = 0.0f; /* Initial correlation coefficient */
|
|
517
|
+
|
|
518
|
+
/* Clamp parameters to reasonable bounds */
|
|
519
|
+
i0 = fminf(fmaxf(i0, -2.0f), 2.0f);
|
|
520
|
+
j0 = fminf(fmaxf(j0, -2.0f), 2.0f);
|
|
521
|
+
sx = fminf(fmaxf(sx * sx, 0.25f), 9.0f); /* Convert to variance */
|
|
522
|
+
sy = fminf(fmaxf(sy * sy, 0.25f), 9.0f);
|
|
523
|
+
|
|
524
|
+
/* Compute initial residual and Jacobian */
|
|
525
|
+
residual = compute_residual_jacobian_6dof(xcorr, N, A, i0, j0, sx, sy, sxy, JtJ, Jtr, 1);
|
|
526
|
+
|
|
527
|
+
/* Levenberg-Marquardt optimization loop */
|
|
528
|
+
for(iter = 0; iter < max_iter; ++iter) {
|
|
529
|
+
/* Solve for parameter update */
|
|
530
|
+
if(solve_lm_step(JtJ, Jtr, lambda, delta, 6) != 0) break;
|
|
531
|
+
|
|
532
|
+
/* Apply parameter update with bounds checking */
|
|
533
|
+
float A_new = A + delta[0];
|
|
534
|
+
float i0_new = i0 + delta[1];
|
|
535
|
+
float j0_new = j0 + delta[2];
|
|
536
|
+
float sx_new = sx + delta[3];
|
|
537
|
+
float sy_new = sy + delta[4];
|
|
538
|
+
float sxy_new = sxy + delta[5];
|
|
539
|
+
|
|
540
|
+
A_new = fmaxf(A_new, A * 0.5f);
|
|
541
|
+
i0_new = fminf(fmaxf(i0_new, -2.5f), 2.5f);
|
|
542
|
+
j0_new = fminf(fmaxf(j0_new, -2.5f), 2.5f);
|
|
543
|
+
sx_new = fminf(fmaxf(sx_new, 0.1f), 16.0f);
|
|
544
|
+
sy_new = fminf(fmaxf(sy_new, 0.1f), 16.0f);
|
|
545
|
+
float sxy_max = 0.95f / sqrtf(sx_new * sy_new); /* Prevent singular covariance */
|
|
546
|
+
sxy_new = fminf(fmaxf(sxy_new, -sxy_max), sxy_max);
|
|
547
|
+
|
|
548
|
+
/* Evaluate new residual */
|
|
549
|
+
new_residual = compute_residual_jacobian_6dof(xcorr, N, A_new, i0_new, j0_new, sx_new, sy_new, sxy_new, NULL, NULL, 0);
|
|
550
|
+
|
|
551
|
+
if(new_residual < residual) {
|
|
552
|
+
/* Accept step: update parameters */
|
|
553
|
+
A = A_new; i0 = i0_new; j0 = j0_new;
|
|
554
|
+
sx = sx_new; sy = sy_new; sxy = sxy_new;
|
|
555
|
+
float improvement = (residual - new_residual) / (residual + FLT_EPSILON);
|
|
556
|
+
residual = new_residual;
|
|
557
|
+
lambda *= 0.5f; /* Decrease damping */
|
|
558
|
+
compute_residual_jacobian_6dof(xcorr, N, A, i0, j0, sx, sy, sxy, JtJ, Jtr, 1);
|
|
559
|
+
if(improvement < tol) break; /* Converged */
|
|
560
|
+
} else {
|
|
561
|
+
lambda *= 2.0f; /* Increase damping */
|
|
562
|
+
if(lambda > 1e6f) break; /* Diverged */
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
/* Store final results */
|
|
567
|
+
peak_loc[0] = i0;
|
|
568
|
+
peak_loc[1] = j0;
|
|
569
|
+
/* WARNING: Output parameters are swapped! sig[0]=sy, sig[1]=sx
|
|
570
|
+
* This is confusing and should be refactored. The 6-DOF model uses
|
|
571
|
+
* inverse covariance parameterization which differs from standard
|
|
572
|
+
* Gaussian parameterization used in 4-DOF and 5-DOF models. */
|
|
573
|
+
sig[0] = sy;
|
|
574
|
+
sig[1] = sx;
|
|
575
|
+
sig[2] = -(sxy * (sx * sy - sxy * sxy)); /* Covariance determinant */
|
|
576
|
+
|
|
577
|
+
/* Generate fitted values if requested */
|
|
578
|
+
if(fitval) {
|
|
579
|
+
for(ii = 0; ii < N[0]; ++ii) {
|
|
580
|
+
float i = (float)(ii - (N[0]-1)/2);
|
|
581
|
+
for(jj = 0; jj < N[1]; ++jj) {
|
|
582
|
+
float j = (float)(jj - (N[1]-1)/2);
|
|
583
|
+
idx = ii * N[1] + jj;
|
|
584
|
+
fitval[idx] = eval_gauss6(i, j, A, i0, j0, sx, sy, sxy);
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
/******************************************************************************
|
|
591
|
+
* Main peak localization function
|
|
592
|
+
*
|
|
593
|
+
* Performs multi-peak detection and fitting on correlation data.
|
|
594
|
+
*
|
|
595
|
+
* Parameters:
|
|
596
|
+
* xcorr: Input correlation array (N[0] x N[1])
|
|
597
|
+
* N: Dimensions of correlation array [rows, cols]
|
|
598
|
+
* peak_loc: Output peak locations (3 x nPeaks array)
|
|
599
|
+
* peak_loc[0,i] = x-coordinate, peak_loc[1,i] = y-coordinate, peak_loc[2,i] = peak height
|
|
600
|
+
* nPeaks: Maximum number of peaks to find
|
|
601
|
+
* iFitType: Fitting model (3=3pt, 4=circular, 5=elliptical, 6=rotated elliptical)
|
|
602
|
+
* std_dev: Output standard deviations (3 x nPeaks array)
|
|
603
|
+
* std_dev[0,i] = sigma_x, std_dev[1,i] = sigma_y, std_dev[2,i] = correlation/covariance
|
|
604
|
+
*****************************************************************************/
|
|
605
|
+
void lsqpeaklocate_lm(const float *xcorr, const int *N, float *peak_loc, int nPeaks, int iFitType, float *std_dev)
|
|
606
|
+
{
|
|
607
|
+
int i, j, iPeak, idx;
|
|
608
|
+
int i0, j0;
|
|
609
|
+
float *xcorr_copy;
|
|
610
|
+
float fPeakHeight;
|
|
611
|
+
float subxcorr[PKSIZE_X * PKSIZE_Y];
|
|
612
|
+
float fitval[PKSIZE_X * PKSIZE_Y];
|
|
613
|
+
int Nsub[2];
|
|
614
|
+
float peak[2];
|
|
615
|
+
float sig[3];
|
|
616
|
+
|
|
617
|
+
/* Create working copy of correlation data for peak subtraction */
|
|
618
|
+
xcorr_copy = (float*)malloc(sizeof(float) * N[0] * N[1]);
|
|
619
|
+
memcpy(xcorr_copy, xcorr, N[0] * N[1] * sizeof(float));
|
|
620
|
+
Nsub[0] = PKSIZE_X;
|
|
621
|
+
Nsub[1] = PKSIZE_Y;
|
|
622
|
+
|
|
623
|
+
/* Process each peak sequentially */
|
|
624
|
+
for(iPeak = 0; iPeak < nPeaks; ++iPeak)
|
|
625
|
+
{
|
|
626
|
+
/* Find highest remaining peak in central region
|
|
627
|
+
* N[0] = number of rows, N[1] = number of columns
|
|
628
|
+
* i indexes rows, j indexes columns
|
|
629
|
+
* Row-major indexing: SUB2IND_2D(row, col, num_cols)
|
|
630
|
+
*/
|
|
631
|
+
i0 = j0 = 0;
|
|
632
|
+
fPeakHeight = 0;
|
|
633
|
+
for(i = N[0]/8; i < N[0]*7/8; ++i) {
|
|
634
|
+
for(j = N[1]/8; j < N[1]*7/8; ++j) {
|
|
635
|
+
if(xcorr_copy[SUB2IND_2D(i, j, N[1])] > fPeakHeight) {
|
|
636
|
+
fPeakHeight = xcorr_copy[SUB2IND_2D(i, j, N[1])];
|
|
637
|
+
i0 = i;
|
|
638
|
+
j0 = j;
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
/* Validate peak: check height and local maximum condition
|
|
644
|
+
* Row-major indexing: SUB2IND_2D(row, col, num_cols)
|
|
645
|
+
*/
|
|
646
|
+
if(fPeakHeight <= 0 ||
|
|
647
|
+
i0 < (PKSIZE_X-1)/2 || i0 >= N[0]-(PKSIZE_X-1)/2 ||
|
|
648
|
+
j0 < (PKSIZE_Y-1)/2 || j0 >= N[1]-(PKSIZE_Y-1)/2 ||
|
|
649
|
+
fPeakHeight <= xcorr_copy[SUB2IND_2D(i0-1, j0, N[1])] ||
|
|
650
|
+
fPeakHeight <= xcorr_copy[SUB2IND_2D(i0+1, j0, N[1])] ||
|
|
651
|
+
fPeakHeight <= xcorr_copy[SUB2IND_2D(i0, j0-1, N[1])] ||
|
|
652
|
+
fPeakHeight <= xcorr_copy[SUB2IND_2D(i0, j0+1, N[1])])
|
|
653
|
+
{
|
|
654
|
+
/* Invalid peak: mark as NaN
|
|
655
|
+
* Row-major: array[3, nPeaks] -> stride is nPeaks
|
|
656
|
+
*/
|
|
657
|
+
peak_loc[SUB2IND_2D(0, iPeak, nPeaks)] = NAN;
|
|
658
|
+
peak_loc[SUB2IND_2D(1, iPeak, nPeaks)] = NAN;
|
|
659
|
+
peak_loc[SUB2IND_2D(2, iPeak, nPeaks)] = 0;
|
|
660
|
+
std_dev[SUB2IND_2D(0, iPeak, nPeaks)] = 0;
|
|
661
|
+
std_dev[SUB2IND_2D(1, iPeak, nPeaks)] = 0;
|
|
662
|
+
std_dev[SUB2IND_2D(2, iPeak, nPeaks)] = 0;
|
|
663
|
+
continue;
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
/* Extract sub-window around peak for fitting
|
|
667
|
+
* Row-major indexing for both subxcorr and xcorr_copy
|
|
668
|
+
*/
|
|
669
|
+
for(i = 0; i < PKSIZE_X; ++i) {
|
|
670
|
+
for(j = 0; j < PKSIZE_Y; ++j) {
|
|
671
|
+
subxcorr[i * PKSIZE_Y + j] = xcorr_copy[SUB2IND_2D(i0 + i - (PKSIZE_X-1)/2, j0 + j - (PKSIZE_Y-1)/2, N[1])];
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
/* Perform fit based on type - only use higher order fits for first peak */
|
|
676
|
+
if(iPeak == 0) {
|
|
677
|
+
switch(iFitType) {
|
|
678
|
+
case 6:
|
|
679
|
+
lm_gauss6_fit(subxcorr, Nsub, peak, fitval, sig);
|
|
680
|
+
break;
|
|
681
|
+
case 5:
|
|
682
|
+
lm_gauss5_fit(subxcorr, Nsub, peak, fitval, sig);
|
|
683
|
+
break;
|
|
684
|
+
case 4:
|
|
685
|
+
lm_gauss4_fit(subxcorr, Nsub, peak, fitval, sig);
|
|
686
|
+
break;
|
|
687
|
+
case 3:
|
|
688
|
+
default:
|
|
689
|
+
/* 3-point estimator fallback */
|
|
690
|
+
{
|
|
691
|
+
float A, sx, sy;
|
|
692
|
+
threept_estimate(subxcorr, Nsub, peak, &A, &sx, &sy);
|
|
693
|
+
sig[0] = sx;
|
|
694
|
+
sig[1] = sy;
|
|
695
|
+
sig[2] = 0.0f;
|
|
696
|
+
for(i = 0; i < PKSIZE_X; ++i) {
|
|
697
|
+
float fi = (float)(i - (PKSIZE_X-1)/2);
|
|
698
|
+
for(j = 0; j < PKSIZE_Y; ++j) {
|
|
699
|
+
float fj = (float)(j - (PKSIZE_Y-1)/2);
|
|
700
|
+
fitval[i * PKSIZE_Y + j] = eval_gauss5(fi, fj, A, peak[0], peak[1], sx, sy);
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
break;
|
|
705
|
+
}
|
|
706
|
+
} else {
|
|
707
|
+
/* Subsequent peaks: use fast 3-point for speed */
|
|
708
|
+
float A, sx, sy;
|
|
709
|
+
threept_estimate(subxcorr, Nsub, peak, &A, &sx, &sy);
|
|
710
|
+
sig[0] = sx;
|
|
711
|
+
sig[1] = sy;
|
|
712
|
+
sig[2] = 0.0f;
|
|
713
|
+
for(i = 0; i < PKSIZE_X; ++i) {
|
|
714
|
+
float fi = (float)(i - (PKSIZE_X-1)/2);
|
|
715
|
+
for(j = 0; j < PKSIZE_Y; ++j) {
|
|
716
|
+
float fj = (float)(j - (PKSIZE_Y-1)/2);
|
|
717
|
+
fitval[i * PKSIZE_Y + j] = eval_gauss5(fi, fj, A, peak[0], peak[1], sx, sy);
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
/* Save results: convert sub-window coordinates back to full image
|
|
723
|
+
* peak[0] = row offset, peak[1] = col offset (both relative to sub-window center)
|
|
724
|
+
* i0 = row index of peak in full correlation plane
|
|
725
|
+
* j0 = col index of peak in full correlation plane
|
|
726
|
+
* Output: peak_loc is [3, nPeaks] array in row-major
|
|
727
|
+
* peak_loc[0, iPeak] = row (Y) position
|
|
728
|
+
* peak_loc[1, iPeak] = col (X) position
|
|
729
|
+
* peak_loc[2, iPeak] = peak height
|
|
730
|
+
*/
|
|
731
|
+
peak_loc[SUB2IND_2D(0, iPeak, nPeaks)] = peak[0] + i0; // Row (Y)
|
|
732
|
+
peak_loc[SUB2IND_2D(1, iPeak, nPeaks)] = peak[1] + j0; // Col (X)
|
|
733
|
+
peak_loc[SUB2IND_2D(2, iPeak, nPeaks)] = fPeakHeight;
|
|
734
|
+
std_dev[SUB2IND_2D(0, iPeak, nPeaks)] = sig[0];
|
|
735
|
+
std_dev[SUB2IND_2D(1, iPeak, nPeaks)] = sig[1];
|
|
736
|
+
std_dev[SUB2IND_2D(2, iPeak, nPeaks)] = sig[2];
|
|
737
|
+
|
|
738
|
+
/* Subtract fitted peak from correlation plane to find remaining peaks
|
|
739
|
+
* Row-major indexing
|
|
740
|
+
*/
|
|
741
|
+
for(i = 0; i < PKSIZE_X; ++i) {
|
|
742
|
+
for(j = 0; j < PKSIZE_Y; ++j) {
|
|
743
|
+
idx = SUB2IND_2D(i0 + i - (PKSIZE_X-1)/2, j0 + j - (PKSIZE_Y-1)/2, N[1]);
|
|
744
|
+
xcorr_copy[idx] = MAX(0, xcorr_copy[idx] - fitval[i * PKSIZE_Y + j]);
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
free(xcorr_copy);
|
|
750
|
+
}
|
|
751
|
+
|