/* * zernike.c * * Copyright 2016 Edward V. Emelianov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. */ #ifndef _GNU_SOURCE #define _GNU_SOURCE (1) // for math.h #endif #include #include #include // INT_MAX #include "zernike.h" #include "usefull_macros.h" #include "saveimg.h" #ifndef iabs #define iabs(a) (((a)<(0)) ? (-a) : (a)) #endif // scaling factor static double zscale = 1.; // coordinate step on a grid static double coord_step = DEFAULT_CRD_STEP; // default wavelength for wavefront (650nm) in meters static double wavelength = DEFAULT_WAVELENGTH; // default coefficient to transform vawefront from wavelengths into user value static double wf_coeff = 1.; // array of factorials 1..100 static double *FK = NULL; // unit for WF measurement static char *outpunit = DEFAULT_WF_UNIT; // amount of first polynomials to reset static int Zern_zero = 0; // array with indexes to be reset to 0 (copy of G.tozero) static int* tozero = NULL; static int tozerosz = 0; // size of array // list of additional coefficients static coeff *addcoefflist = NULL; static int addcoefsz = 0; // its size // matrix rotation angle (in radians) static double rotangle = 0.; /** * setter & getter for zscale */ void z_set_scale(double s){zscale = s;} double z_get_scale(){return zscale;} /** * Set/get value of Zern_zero */ int z_set_Nzero(int val){ if(val > 0) Zern_zero = val; return Zern_zero; } /** * Fill list of coefficients to be reset */ void z_set_tozero(int **val){ if(!val) return; size_t cur = tozerosz; while(val[tozerosz]) ++tozerosz; //printf("2zero: %zd\n", tozerosz); tozero = realloc(tozero, tozerosz*sizeof(int)); if(!tozero) ERR("realloc()"); while(*val){ tozero[cur++] = **val++; } //for(cur = 0; cur < tozerosz; ++cur) printf(":: %d\n", tozero[cur]); } /** * @return size of `tozero` array * @param idxs (o) - `tozero` itself */ int z_get_tozero(int **idxs){ if(!idxs) return -1; *idxs = tozero; return tozerosz; } // parse additional coefficient record (N=val) and fill data void parse_coeff(char *rec, coeff *c){ if(!rec) ERRX(_("Empty record")); char *s = strdup(rec); if(!s) ERR("strdup()"); char *eq = strchr(s, '='); if(!eq || *rec == '=') ERRX(_("Coefficients' records should be like N=val, where N is Znumber, val is its additional value")); *eq++ = 0; char *endptr; long tmp = strtol(s, &endptr, 0); //printf("tmp=%ld, eptr=%s\n", tmp, endptr); if(endptr == rec || *endptr != '\0' || tmp < 0 || tmp > INT_MAX) ERRX(_("Coefficient's index should be a non-negative integer")); c->idx = (int) tmp; if(!str2double(&c->addval, eq)) ERRX(_("Wrong coefficient")); free(s); } /** * Fill list of additional coefficients */ void z_set_addcoef(char **list){ if(!list || !*list) return; size_t cur = addcoefsz; while(list[addcoefsz]) ++addcoefsz; //printf("add coeffs: %zd\n", addcoefsz); addcoefflist = realloc(addcoefflist, addcoefsz*sizeof(coeff)); if(!addcoefflist) ERR("realloc()"); while(*list){ parse_coeff(*list++, &addcoefflist[cur++]); } //for(cur = 0; cur < addcoefsz; ++cur) printf("coeff %d += %g\n", addcoefflist[cur].idx, addcoefflist[cur].addval); } /** * @return length of `addcoeflist` array * @param vals (o) - `addcoeflist` itself */ int z_get_addcoef(coeff **vals){ if(!vals) return -1; *vals = addcoefflist; return addcoefsz; } /** * Set default coordinate grid step on an unity circle * @param step - new step * @return 0 if all OK, -1 or 1 if `step` bad */ int z_set_step(double step){ printf("Set step to %g\n", step); if(step < DBL_EPSILON) return -1; if(step > 1.) return 1; coord_step = step; return 0; } double z_get_step(){ return coord_step; } /** * Set value of default wavelength * @param w - new wavelength (from 100nm to 10um) in meters, microns or nanometers * @return 0 if all OK */ int z_set_wavelength(double w){ if(w > 1e-7 && w < 1e-5) // meters wavelength = w; else if(w > 0.1 && w < 10.) // micron wavelength = w * 1e-6; else if(w > 100. && w < 10000.) // nanometer wavelength = w * 1e-9; else return 1; return 0; } double z_get_wavelength(){ return wavelength; } static wf_units wfunits[] = { { 1. , (const char * const []){"meter", "m", NULL}}, {1e3 , (const char * const []){"millimeter", "mm", NULL}}, {1e6 , (const char * const []){"micrometer", "um", "u", NULL}}, {1e9 , (const char * const []){"nanometer", "nm", "n", NULL}}, {-1. , (const char * const []){"wavelength", "wave", "lambda", "w", "l", NULL}}, {0. , (const char * const []){NULL}} }; /** * Set coefficient `wf_coeff` to user defined unit */ int z_set_wfunit(char *U){ wf_units *u = wfunits; while(u->units[0]){ const char * const * unit = u->units; while(*unit){ if(strcasecmp(*unit, U) == 0){ wf_coeff = u->wf_coeff; if(wf_coeff < 0.){ // wavelengths wf_coeff = 1./wavelength; // in wavelengths } outpunit = (char*)u->units[0]; printf("wf_coeff = %g\n", wf_coeff); return 0; } ++unit; } ++u; } return 1; } char *z_get_wfunit(){ return outpunit; } double z_get_wfcoeff(){ return wf_coeff; } /** * Print all wf_units available */ void z_print_wfunits(){ wf_units *u = wfunits; printf(_("Unit (meters)\tAvailable values\n")); do{ const char * const*unit = u->units; double val = 1./u->wf_coeff; if(val > 0.) printf("%-8g\t", val); else printf("(wavelength)\t"); do{ printf("%s ", *unit); }while(*(++unit)); printf("\n"); }while((++u)->units[0]); printf("\n"); } /** * Convert polynomial order in Noll notation into n/m * @param p (i) - order of Zernike polynomial in Noll notation * @param N (o) - order of polynomial * @param M (o) - angular parameter */ void convert_Zidx(int p, int *N, int *M){ int n = (int) floor((-1.+sqrt(1.+8.*p)) / 2.); if(M) *M = (int)(2.0*(p - n*(n+1.)/2. - 0.5*(double)n)); if(N) *N = n; } /** * Build array with R powers (from 0 to n inclusive) * @param n - power of Zernike polinomial (array size = n+1) * @param Sz - size of P array * @param P (i) - polar coordinates of points */ double **build_rpow(int n, int Sz, polar *P){ int i, j, N = n + 1; double **Rpow = MALLOC(double*, N); Rpow[0] = MALLOC(double, Sz); for(i = 0; i < Sz; i++) Rpow[0][i] = 1.; // zero's power for(i = 1; i < N; i++){ // Rpow - is quater I of cartesian coordinates ('cause other are fully simmetrical) Rpow[i] = MALLOC(double, Sz); double *rp = Rpow[i], *rpo = Rpow[i-1]; polar *p = P; for(j = 0; j < Sz; j++, rp++, rpo++, p++){ *rp = (*rpo) * p->r; } } return Rpow; } /** * Free array of R powers with power n * @param Rpow (i) - array to free * @param n - power of Zernike polinomial for that array (array size = n+1) */ void free_rpow(double ***Rpow, int n){ int i, N = n+1; for(i = 0; i < N; i++) FREE((*Rpow)[i]); FREE(*Rpow); } /** * Free array of coordinates */ void free_coords(polcrds *p){ FREE(p->P); free_rpow(&p->Rpow, p->N); free(p); } /** * Generate polar coordinates for grid [-1..1] by both coordinates * with default step. Correct to rotangle * @return array of coordinates **without any point outside unitary circle** */ polcrds *gen_coords(){ int WH = 1 + (int)(2. / coord_step), max_sz = WH * WH, L = 0, idx = -1; polar *coordinates = malloc(max_sz * sizeof(polar)), *cptr = coordinates; if(!cptr) return NULL; double x, y, cmax = 1. + coord_step/2.; for(y = -1.; y < cmax; y += coord_step){ for(x = -1.; x < cmax; x += coord_step){ double R = sqrt(x*x + y*y); ++idx; if(R > 1.) continue; cptr->r = R; cptr->theta = atan2(y, x) + rotangle; cptr->idx = idx; ++cptr; ++L; } } DBG("%d points outside circle (ratio = %g, ideal = %g)", max_sz - L, ((double)L)/max_sz, M_PI/4.); polcrds *crds = MALLOC(polcrds, 1); crds->P = coordinates; crds->Sz = L; crds->N = 40; // start from 40 crds->Rpow = build_rpow(crds->N, L, coordinates); crds->WH = WH; return crds; } /** * Build pre-computed array of factorials from 1 to 100 */ void build_factorial(){ double F = 1.; int i; if(FK) return; FK = MALLOC(double, ZERNIKE_MAX_POWER); FK[0] = 1.; for(i = 1; i < ZERNIKE_MAX_POWER; i++) FK[i] = (F *= (double)i); } /** * Validation check of zernfun parameters * return 1 in case of error */ int check_parameters(int n, int m, polcrds *P){ if(!P || P->Sz < 3 || !P->P || !P->Rpow){ WARNX(_("Size of matrix must be > 2!")); return 1; } if(n > ZERNIKE_MAX_POWER){ WARNX(_("Order of Zernike polynomial must be <= %d!"), ZERNIKE_MAX_POWER); return 1; } int erparm = 0; if(n < 0) erparm = 1; if(n < iabs(m)) erparm = 1; // |m| must be <= n if((n - m) % 2) erparm = 1; // n-m must differ by a prod of 2 if(erparm) WARNX(_("Wrong parameters of Zernike polynomial (%d, %d)"), n, m); else if(!FK) build_factorial(); return erparm; } /** * Zernike function for scattering data * @param n,m - orders of polynomial * @param P(i) - array with points coordinates (polar, r<=1) * @param norm(o) - (optional) norm coefficient * @return dynamically allocated array with Z(n,m) for given array P */ double *zernfun(int n, int m, polcrds *P, double *norm){ if(check_parameters(n, m, P)) return NULL; int j, k, m_abs = iabs(m), iup = (n-m_abs)/2, Sz = P->Sz; if(n > P->N){ free_rpow(&P->Rpow, P->N); P->N = n+10; P->Rpow = build_rpow(P->N, Sz, P->P); } double ZSum = 0.; // now fill output matrix double *Zarr = MALLOC(double, Sz); // output matrix double *Zptr = Zarr; polar *p = P->P; double **Rpow = P->Rpow; for(j = 0; j < Sz; j++, p++, Zptr++){ double Z = 0.; if(p->r > 1.) continue; // throw out points with R>1 // calculate R_n^m for(k = 0; k <= iup; k++){ // Sum double z = (1. - 2. * (k % 2)) * FK[n - k] // (-1)^k * (n-k)! /(//----------------------------------- ----- ------------------------------- FK[k]*FK[(n+m_abs)/2-k]*FK[(n-m_abs)/2-k] // k!((n+|m|)/2-k)!((n-|m|)/2-k)! ); Z += z * Rpow[n-2*k][j]; // *R^{n-2k} } // normalize double eps_m = (m) ? 1. : 2.; Z *= sqrt(2.*(n+1.) / M_PI / eps_m ); double m_theta = (double)m_abs * p->theta; // multiply to angular function: if(m){ if(m > 0) Z *= cos(m_theta); else Z *= sin(m_theta); } *Zptr = Z; ZSum += Z*Z; } if(norm) *norm = ZSum; return Zarr; } /** * Restoration of image in points P by Zernike polynomials' coefficients * @param Zsz (i) - number of actual elements in coefficients array * @param Zidxs(i) - array with Zernike coefficients * @param P(i) - points coordinates & R powers * @return restored image */ double *Zcompose(int Zsz, double *Zidxs, polcrds *P){ if(!P || !P->P || !P->Rpow) return NULL; int i, Sz = P->Sz; for(i = 0; i < Zern_zero; ++i) Zidxs[i] = 0.; for(i = 0; i < tozerosz; i++){ int C = tozero[i]; if(C > -1 && C < Zsz) Zidxs[C] = 0.; else WARNX(_("Not zero idx %d (should be from 0 to %d)"), C, Zsz-1); } for(i = 0; i < addcoefsz; i++){ int C = addcoefflist[i].idx; if(C > -1 && C < Zsz) Zidxs[C] += addcoefflist[i].addval; else WARNX(_("Not change idx %d (should be from 0 to %d)"), C, Zsz-1); } double *image = MALLOC(double, Sz); for(i = 0; i < Zsz; i++){ // now we fill array double K = Zidxs[i]; //printf("Z[%d]=%g\n", i, K); if(fabs(K) < DBL_EPSILON) continue; // 0.0m int n, m; convert_Zidx(i, &n, &m); double *Zcoeff = zernfun(n, m, P, NULL); if(!Zcoeff){ WARNX(_("Can't compute coefficients for n=%d, m=%d!"), n,m); continue; } int j; double *iptr = image, *zptr = Zcoeff; for(j = 0; j < Sz; j++, iptr++, zptr++) *iptr += K * (*zptr); // add next Zernike polynomial FREE(Zcoeff); } return image; } /** * Save restored wavefront into file `filename` * @param P (i) - points coordinates & R powers * @param Z (i) - wavefront shift (in lambdas) * @param std (i) - std of shift in each point * @param filename (i) - name of output file * @return 1 if failed */ int z_save_wavefront(polcrds *P, double *Z, double *std, char *fprefix){ int Sz = P->Sz, i, ret = 1; polar *p = P->P; if(!P || !p || !Z || Sz < 0 || !fprefix) return 1; /*************** Step 1 - save points coordinates table ***************/ char *filename = MALLOC(char, strlen(fprefix) + 10); sprintf(filename, "%s.points", fprefix); printf("try to save to %s\n", filename); FILE *f = fopen(filename, "w"); if(!f) goto returning; int WH = P->WH, max_sz = WH * WH; double *WF = calloc(max_sz, sizeof(double)); if(!WF) goto returning; // calculate std & scope by all wavefront double *z = Z, sum = 0., sum2 = 0., min = 1e12, max = -1e12; for(i = 0; i < Sz; ++i, ++z){ double pt = *z; if(pt < min) min = pt; if(pt > max) max = pt; sum += pt; sum2 += pt*pt; } double coef = wf_coeff * zscale; sum2 *= coef, sum *= coef, max *= coef, min *= coef; fprintf(f, "# Wavefront units: %ss, wavelength: %gnm, std by all WF: %g, Scope: %g, max: %g, min: %g\n", outpunit, wavelength*1e9, sum2/Sz + sum*sum/Sz/Sz, max - min, max, min); if(Zern_zero) fprintf(f, "# First %d coefficients were cleared\n", Zern_zero); fprintf(f, "# X (-1..1)\tY (-1..1)\tZ \tstd_Z\n"); for(i = 0, z = Z; i < Sz; ++i, ++p, ++z, ++std){ double x, y, s, c, r = p->r, zdat = (*z) * coef; sincos(p->theta - rotangle, &s, &c); x = r * c, y = r * s; fprintf(f, "%6.3f\t%6.3f\t%9.3g\t%9.3g\n", x, y, zdat, (*std) * coef); WF[p->idx] = zdat; //DBG("WF[%d] = %g; x=%.1f, y=%.1f", p->idx, zdat,x,y); } fclose(f); /*************** Step 2 - save matrix of data ***************/ sprintf(filename, "%s.matrix", fprefix); printf("try to save to %s\n", filename); f = fopen(filename, "w"); if(!f) goto returning; fprintf(f, "# Wavefront data\n# Units: %ss, wavelength: %gnm\n# Step: %g\n", outpunit, wavelength*1e9, coord_step); int x, y; // Invert Y axe to have matrix with right Y direction (towards up) for(y = WH-1; y > -1; --y){ double *wptr = &WF[y*WH]; for(x = 0; x < WH; ++x, ++wptr) fprintf(f, "%6.3g\t", *wptr); fprintf(f, "\n"); } writeimg(fprefix, WH, WF); ret = 0; returning: FREE(filename); return ret; } /** * change rotation angle (0..2pi) */ void z_set_rotangle(double angle){ angle -= floor(angle/360.) * 360.; rotangle = angle * M_PI/180.; printf("angle: %g\n", rotangle*180/M_PI); } double z_get_rotangle(){ return rotangle; }