File indexing completed on 2024-05-12 03:47:50
0001 /* 0002 File : nsl_conv.c 0003 Project : LabPlot 0004 Description : NSL discrete convolution functions 0005 -------------------------------------------------------------------- 0006 SPDX-FileCopyrightText: 2018 Stefan Gerlach <stefan.gerlach@uni.kn> 0007 SPDX-License-Identifier: GPL-2.0-or-later 0008 */ 0009 0010 #include "nsl_conv.h" 0011 #include "nsl_common.h" 0012 #include <gsl/gsl_cblas.h> 0013 #include <gsl/gsl_fft_halfcomplex.h> 0014 #ifdef HAVE_FFTW3 0015 #include <fftw3.h> 0016 #endif 0017 #include "backend/nsl/nsl_stats.h" 0018 0019 const char* nsl_conv_direction_name[] = {i18n("Forward (Convolution)"), i18n("Backward (Deconvolution)")}; 0020 const char* nsl_conv_type_name[] = {i18n("Linear (Zero-padded)"), i18n("Circular")}; 0021 const char* nsl_conv_method_name[] = {i18n("Auto"), i18n("Direct"), i18n("FFT")}; 0022 const char* nsl_conv_norm_name[] = {i18n("None"), i18n("Sum"), i18n("Euclidean")}; 0023 const char* nsl_conv_wrap_name[] = {i18n("None"), i18n("Maximum"), i18n("Center (Acausal)")}; 0024 const char* nsl_conv_kernel_name[] = {i18n("Sliding Average"), 0025 i18n("Triangular Smooth"), 0026 i18n("Pseudo-Gaussian Smooth"), 0027 i18n("First Derivative"), 0028 i18n("Smooth First Derivative"), 0029 i18n("Second Derivative"), 0030 i18n("Third Derivative"), 0031 i18n("Fourth Derivative"), 0032 i18n("Gaussian"), 0033 i18n("Lorentzian")}; 0034 0035 int nsl_conv_standard_kernel(double k[], size_t n, nsl_conv_kernel_type type) { 0036 size_t i; 0037 0038 int validn = 1; 0039 switch (type) { 0040 case nsl_conv_kernel_avg: 0041 for (i = 0; i < n; i++) 0042 k[i] = 1.; 0043 break; 0044 case nsl_conv_kernel_smooth_triangle: 0045 for (i = 0; i < n / 2; i++) 0046 k[i] = i + 1.; 0047 for (i = n / 2; i < n; i++) 0048 k[i] = (double)(n - i); 0049 break; 0050 case nsl_conv_kernel_smooth_gaussian: 0051 switch (n) { 0052 case 5: 0053 k[0] = 1; 0054 k[1] = 4; 0055 k[2] = 6; 0056 k[3] = 4; 0057 k[4] = 1; 0058 break; 0059 case 7: 0060 k[0] = 1; 0061 k[1] = 4; 0062 k[2] = 8; 0063 k[3] = 10; 0064 k[4] = 8; 0065 k[5] = 4; 0066 k[6] = 1; 0067 break; 0068 case 9: 0069 k[0] = 1; 0070 k[1] = 4; 0071 k[2] = 9; 0072 k[3] = 14; 0073 k[4] = 17; 0074 k[5] = 14; 0075 k[6] = 9; 0076 k[7] = 4; 0077 k[8] = 1; 0078 break; 0079 default: 0080 validn = 0; 0081 } 0082 break; 0083 case nsl_conv_kernel_first_derivative: 0084 if (n == 2) { 0085 k[0] = -1; 0086 k[1] = 1; 0087 } else 0088 validn = 0; 0089 break; 0090 case nsl_conv_kernel_smooth_first_derivative: 0091 if (n % 2) { 0092 for (i = 0; i < n; i++) 0093 k[i] = (int)(i - n / 2); 0094 } else 0095 validn = 0; 0096 break; 0097 case nsl_conv_kernel_second_derivative: 0098 if (n == 3) { 0099 k[0] = 1; 0100 k[1] = -2; 0101 k[2] = 1; 0102 } else 0103 validn = 0; 0104 break; 0105 case nsl_conv_kernel_third_derivative: 0106 if (n == 4) { 0107 k[0] = 1; 0108 k[1] = -3; 0109 k[2] = 3; 0110 k[3] = -1; 0111 } else 0112 validn = 0; 0113 break; 0114 case nsl_conv_kernel_fourth_derivative: 0115 if (n == 5) { 0116 k[0] = 1; 0117 k[1] = -4; 0118 k[2] = 6; 0119 k[3] = -4; 0120 k[4] = 1; 0121 } else 0122 validn = 0; 0123 break; 0124 case nsl_conv_kernel_gaussian: { 0125 double s = n / 5.; /* relative width */ 0126 for (i = 0; i < n; i++) { 0127 double x = i - (n - 1.) / 2.; 0128 k[i] = M_SQRT1_2 / M_SQRTPI / s * exp(-x * x / 2. / s / s); 0129 } 0130 break; 0131 } 0132 case nsl_conv_kernel_lorentzian: { 0133 double s = n / 5.; /* relative width */ 0134 for (i = 0; i < n; i++) { 0135 double x = i - (n - 1.) / 2.; 0136 k[i] = s / M_PI / (s * s + x * x); 0137 } 0138 break; 0139 } 0140 } 0141 0142 if (!validn) { 0143 printf("ERROR: kernel size %lu not supported for kernel %s\n", (unsigned long)n, nsl_conv_kernel_name[type]); 0144 return -1; 0145 } 0146 0147 /* debug */ 0148 printf("["); 0149 for (i = 0; i < n; i++) 0150 printf("%g ", k[i]); 0151 puts("]"); 0152 0153 return 0; 0154 } 0155 0156 int nsl_conv_convolution_direction(double s[], 0157 size_t n, 0158 double r[], 0159 size_t m, 0160 nsl_conv_direction_type dir, 0161 nsl_conv_type_type type, 0162 nsl_conv_method_type method, 0163 nsl_conv_norm_type normalize, 0164 nsl_conv_wrap_type wrap, 0165 double out[]) { 0166 if (dir == nsl_conv_direction_forward) 0167 return nsl_conv_convolution(s, n, r, m, type, method, normalize, wrap, out); 0168 else 0169 return nsl_conv_deconvolution(s, n, r, m, type, normalize, wrap, out); 0170 } 0171 0172 int nsl_conv_convolution(double s[], 0173 size_t n, 0174 double r[], 0175 size_t m, 0176 nsl_conv_type_type type, 0177 nsl_conv_method_type method, 0178 nsl_conv_norm_type normalize, 0179 nsl_conv_wrap_type wrap, 0180 double out[]) { 0181 if (method == nsl_conv_method_direct || (method == nsl_conv_method_auto && GSL_MAX_INT(n, m) <= NSL_CONV_METHOD_BORDER)) { 0182 if (type == nsl_conv_type_linear) 0183 return nsl_conv_linear_direct(s, n, r, m, normalize, wrap, out); 0184 else if (type == nsl_conv_type_circular) 0185 return nsl_conv_circular_direct(s, n, r, m, normalize, wrap, out); 0186 } else { 0187 return nsl_conv_fft_type(s, n, r, m, nsl_conv_direction_forward, type, normalize, wrap, out); 0188 } 0189 0190 return 0; 0191 } 0192 0193 int nsl_conv_deconvolution(double s[], 0194 size_t n, 0195 double r[], 0196 size_t m, 0197 nsl_conv_type_type type, 0198 nsl_conv_norm_type normalize, 0199 nsl_conv_wrap_type wrap, 0200 double out[]) { 0201 /* only supported by FFT method */ 0202 return nsl_conv_fft_type(s, n, r, m, nsl_conv_direction_backward, type, normalize, wrap, out); 0203 } 0204 0205 int nsl_conv_linear_direct(const double s[], size_t n, double r[], size_t m, nsl_conv_norm_type normalize, nsl_conv_wrap_type wrap, double out[]) { 0206 size_t i, j, size = n + m - 1, wi = 0; 0207 double norm = 1; 0208 if (normalize == nsl_conv_norm_euclidean) { 0209 if ((norm = cblas_dnrm2((int)m, r, 1)) == 0) 0210 norm = 1.; 0211 } else if (normalize == nsl_conv_norm_sum) { 0212 if ((norm = cblas_dasum((int)m, r, 1)) == 0) 0213 norm = 1.; 0214 } 0215 0216 if (wrap == nsl_conv_wrap_max) 0217 nsl_stats_maximum(r, m, &wi); 0218 else if (wrap == nsl_conv_wrap_center) 0219 wi = m / 2; 0220 0221 for (j = 0; j < size; j++) { 0222 int index; // can be negative 0223 double res = 0; 0224 for (i = 0; i < n; i++) { 0225 index = (int)(j - i); 0226 if (index >= 0 && index < (int)m) 0227 res += s[i] * r[index] / norm; 0228 } 0229 index = (int)(j - wi); 0230 if (index < 0) 0231 index += (int)size; 0232 out[index] = res; 0233 } 0234 0235 return 0; 0236 } 0237 0238 int nsl_conv_circular_direct(const double s[], size_t n, double r[], size_t m, nsl_conv_norm_type normalize, nsl_conv_wrap_type wrap, double out[]) { 0239 size_t i, j, size = GSL_MAX(n, m), wi = 0; 0240 double norm = 1; 0241 if (normalize == nsl_conv_norm_euclidean) { 0242 if ((norm = cblas_dnrm2((int)m, r, 1)) == 0) 0243 norm = 1.; 0244 } else if (normalize == nsl_conv_norm_sum) { 0245 if ((norm = cblas_dasum((int)m, r, 1)) == 0) 0246 norm = 1.; 0247 } 0248 0249 if (wrap == nsl_conv_wrap_max) 0250 nsl_stats_maximum(r, m, &wi); 0251 else if (wrap == nsl_conv_wrap_center) 0252 wi = m / 2; 0253 0254 for (j = 0; j < size; j++) { 0255 int index; // can be negative 0256 double res = 0; 0257 for (i = 0; i < n; i++) { 0258 index = (int)(j - i); 0259 if (index < 0) 0260 index += (int)size; 0261 if (index < (int)m) 0262 res += s[i] * r[index] / norm; 0263 } 0264 index = (int)(j - wi); 0265 if (index < 0) 0266 index += (int)size; 0267 out[index] = res; 0268 } 0269 0270 return 0; 0271 } 0272 0273 int nsl_conv_fft_type(const double s[], 0274 size_t n, 0275 double r[], 0276 size_t m, 0277 nsl_conv_direction_type dir, 0278 nsl_conv_type_type type, 0279 nsl_conv_norm_type normalize, 0280 nsl_conv_wrap_type wrap, 0281 double out[]) { 0282 size_t i, size, wi = 0; 0283 if (type == nsl_conv_type_linear) 0284 size = n + m - 1; 0285 else // circular 0286 size = GSL_MAX(n, m); 0287 0288 double norm = 1.; 0289 if (normalize == nsl_conv_norm_euclidean) { 0290 if ((norm = cblas_dnrm2((int)m, r, 1)) == 0) 0291 norm = 1.; 0292 } else if (normalize == nsl_conv_norm_sum) { 0293 if ((norm = cblas_dasum((int)m, r, 1)) == 0) 0294 norm = 1.; 0295 } 0296 0297 if (wrap == nsl_conv_wrap_max) 0298 nsl_stats_maximum(r, m, &wi); 0299 else if (wrap == nsl_conv_wrap_center) 0300 wi = m / 2; 0301 0302 #ifdef HAVE_FFTW3 0303 // already zero-pad here for FFT method and FFTW r2c 0304 size_t oldsize = size; 0305 size = 2 * (size / 2 + 1); 0306 #endif 0307 0308 // zero-padded arrays 0309 double* stmp = (double*)malloc(size * sizeof(double)); 0310 if (stmp == NULL) { 0311 printf("nsl_conv_fft_type(): ERROR allocating memory for 'stmp'!\n"); 0312 return -1; 0313 } 0314 0315 double* rtmp = (double*)malloc(size * sizeof(double)); 0316 if (rtmp == NULL) { 0317 free(stmp); 0318 printf("nsl_corr_fft_type(): ERROR allocating memory for 'rtmp'!\n"); 0319 return -1; 0320 } 0321 0322 for (i = 0; i < n; i++) 0323 stmp[i] = s[i]; 0324 for (i = n; i < size; i++) 0325 stmp[i] = 0; 0326 for (i = 0; i < m; i++) { 0327 rtmp[i] = r[i] / norm; /* norm response */ 0328 } 0329 for (i = m; i < size; i++) 0330 rtmp[i] = 0; 0331 0332 int status; 0333 #ifdef HAVE_FFTW3 // already wraps output 0334 status = nsl_conv_fft_FFTW(stmp, rtmp, oldsize, dir, wi, out); 0335 #else // GSL 0336 status = nsl_conv_fft_GSL(stmp, rtmp, size, dir, out); 0337 for (i = 0; i < size; i++) { // wrap output 0338 size_t index = (i + wi) % size; 0339 stmp[i] = out[index]; 0340 } 0341 memcpy(out, stmp, size * sizeof(double)); 0342 #endif 0343 0344 free(stmp); 0345 free(rtmp); 0346 0347 return status; 0348 } 0349 0350 #ifdef HAVE_FFTW3 0351 int nsl_conv_fft_FFTW(double s[], double r[], size_t n, nsl_conv_direction_type dir, size_t wi, double out[]) { 0352 size_t i; 0353 const size_t size = 2 * (n / 2 + 1); 0354 double* in = (double*)malloc(size * sizeof(double)); 0355 fftw_plan rpf = fftw_plan_dft_r2c_1d((int)n, in, (fftw_complex*)in, FFTW_ESTIMATE); 0356 0357 fftw_execute_dft_r2c(rpf, s, (fftw_complex*)s); 0358 fftw_execute_dft_r2c(rpf, r, (fftw_complex*)r); 0359 fftw_destroy_plan(rpf); 0360 free(in); 0361 0362 // multiply/divide 0363 if (dir == nsl_conv_direction_forward) { 0364 for (i = 0; i < size; i += 2) { 0365 double re = s[i] * r[i] - s[i + 1] * r[i + 1]; 0366 double im = s[i] * r[i + 1] + s[i + 1] * r[i]; 0367 0368 s[i] = re; 0369 s[i + 1] = im; 0370 } 0371 } else { 0372 for (i = 0; i < size; i += 2) { 0373 double norm = r[i] * r[i] + r[i + 1] * r[i + 1]; 0374 if (norm < DBL_MIN) 0375 norm = 1.; 0376 double re = (s[i] * r[i] + s[i + 1] * r[i + 1]) / norm; 0377 double im = (s[i + 1] * r[i] - s[i] * r[i + 1]) / norm; 0378 0379 s[i] = re; 0380 s[i + 1] = im; 0381 } 0382 } 0383 0384 // back transform 0385 double* o = (double*)malloc(size * sizeof(double)); 0386 fftw_plan rpb = fftw_plan_dft_c2r_1d((int)n, (fftw_complex*)o, o, FFTW_ESTIMATE); 0387 0388 fftw_execute_dft_c2r(rpb, (fftw_complex*)s, s); 0389 fftw_destroy_plan(rpb); 0390 0391 for (i = 0; i < n; i++) { 0392 size_t index = (i + wi) % n; 0393 out[i] = s[index] / n; 0394 } 0395 free(o); 0396 0397 return 0; 0398 } 0399 #endif 0400 0401 int nsl_conv_fft_GSL(double s[], double r[], size_t n, nsl_conv_direction_type dir, double out[]) { 0402 gsl_fft_real_workspace* work = gsl_fft_real_workspace_alloc(n); 0403 gsl_fft_real_wavetable* real = gsl_fft_real_wavetable_alloc(n); 0404 0405 /* FFT s and r */ 0406 gsl_fft_real_transform(s, 1, n, real, work); 0407 gsl_fft_real_transform(r, 1, n, real, work); 0408 gsl_fft_real_wavetable_free(real); 0409 0410 size_t i; 0411 /* calculate halfcomplex product/quotient depending on direction */ 0412 if (dir == nsl_conv_direction_forward) { 0413 out[0] = s[0] * r[0]; 0414 for (i = 1; i < n; i++) { 0415 if (i % 2) { /* Re */ 0416 out[i] = s[i] * r[i]; 0417 if (i < n - 1) /* when n is even last value is real */ 0418 out[i] -= s[i + 1] * r[i + 1]; 0419 } else /* Im */ 0420 out[i] = s[i - 1] * r[i] + s[i] * r[i - 1]; 0421 } 0422 } else { 0423 out[0] = s[0] / r[0]; 0424 for (i = 1; i < n; i++) { 0425 if (i % 2) { /* Re */ 0426 if (i == n - 1) /* when n is even last value is real */ 0427 out[i] = s[i] / r[i]; 0428 else { 0429 double norm = r[i] * r[i] + r[i + 1] * r[i + 1]; 0430 if (norm < DBL_MIN) 0431 norm = 1.; 0432 out[i] = (s[i] * r[i] + s[i + 1] * r[i + 1]) / norm; 0433 } 0434 } else { /* Im */ 0435 double norm = r[i - 1] * r[i - 1] + r[i] * r[i]; 0436 if (norm < DBL_MIN) 0437 norm = 1.; 0438 out[i] = (s[i] * r[i - 1] - s[i - 1] * r[i]) / norm; 0439 } 0440 } 0441 } 0442 0443 /* back transform */ 0444 gsl_fft_halfcomplex_wavetable* hc = gsl_fft_halfcomplex_wavetable_alloc(n); 0445 gsl_fft_halfcomplex_inverse(out, 1, n, hc, work); 0446 gsl_fft_halfcomplex_wavetable_free(hc); 0447 gsl_fft_real_workspace_free(work); 0448 0449 return 0; 0450 }