/* * This file is part of the loccorr project. * Copyright 2021 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 3 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, see . */ #include #include #include #include "cmdlnopts.h" #include "config.h" #include "debug.h" static char *conffile = NULL; // configuration file name configuration theconf = { .maxUsteps=DEFAULT_MAXUSTEPS, .maxVsteps=DEFAULT_MAXVSTEPS, .maxFpos=Fmaxsteps, .minFpos=0, .minarea=DEFAULT_MINAREA, .maxarea=DEFAULT_MAXAREA, .maxwh = 1.1, .minwh = 0.9, .Nerosions=DEFAULT_NEROSIONS, .Ndilations=DEFAULT_NDILATIONS, .xoff=0, .yoff=0, .width=0, .height=0, .equalize=1, .naverage=DEFAULT_NAVERAGE, .stpserverport=DEFAULT_PUSIPORT, .starssort=0, .Kxu=0, .Kyu=0, .Kxv=0, .Kyv=0, .xtarget=-1, .ytarget=-1, .throwpart=DEFAULT_THROWPART, .maxexp=EXPOS_MAX + DBL_EPSILON, .minexp=EXPOS_MIN - DBL_EPSILON, .fixedexp=EXPOS_MIN, .gain = 20., .intensthres=DEFAULT_INTENSTHRES, .medseed=MIN_MEDIAN_SEED, }; // {"", PAR_DOUBLE, (void*)&theconf., 0}, static confparam parvals[] = { {"maxarea", PAR_INT, (void*)&theconf.maxarea, 0, MINAREA-DBL_EPSILON, MAXAREA+DBL_EPSILON, "maximal area (in square pixels) of recognized star image"}, {"minarea", PAR_INT, (void*)&theconf.minarea, 0, MINAREA-DBL_EPSILON, MAXAREA+DBL_EPSILON, "minimal area (in square pixels) of recognized star image"}, {"minwh", PAR_DOUBLE, (void*)&theconf.minwh, 0, MINWH-DBL_EPSILON, 1., "minimal value of W/H roundness parameter"}, {"maxwh", PAR_DOUBLE, (void*)&theconf.maxwh, 0, 1., MAXWH+DBL_EPSILON, "maximal value of W/H roundness parameter"}, {"ndilat", PAR_INT, (void*)&theconf.Ndilations, 0, 1.-DBL_EPSILON, MAX_NDILAT+DBL_EPSILON, "amount of dilations on binarized image"}, {"neros", PAR_INT, (void*)&theconf.Nerosions, 0, 1.-DBL_EPSILON, MAX_NEROS+DBL_EPSILON, "amount of erosions after dilations"}, {"xoffset", PAR_INT, (void*)&theconf.xoff, 0, -DBL_EPSILON, MAX_OFFSET+DBL_EPSILON, "X offset of subimage"}, {"yoffset", PAR_INT, (void*)&theconf.yoff, 0, -DBL_EPSILON, MAX_OFFSET+DBL_EPSILON, "Y offset of subimage"}, {"width", PAR_INT, (void*)&theconf.width, 0, -DBL_EPSILON, MAX_OFFSET+DBL_EPSILON, "subimage width"}, {"height", PAR_INT, (void*)&theconf.height, 0, -DBL_EPSILON, MAX_OFFSET+DBL_EPSILON, "subimage height"}, {"equalize", PAR_INT, (void*)&theconf.equalize, 0, -DBL_EPSILON, 1.+DBL_EPSILON, "make histogram equalization"}, {"expmethod", PAR_INT, (void*)&theconf.expmethod, 0, -DBL_EPSILON, 1.+DBL_EPSILON, "exposition method: 0 - auto, 1 - fixed"}, {"naverage", PAR_INT, (void*)&theconf.naverage, 0, 1-DBL_EPSILON, NAVER_MAX+DBL_EPSILON, "calculate mean position by N images"}, {"umax", PAR_INT, (void*)&theconf.maxUsteps, 0, MINSTEPS-DBL_EPSILON, MAXSTEPS+DBL_EPSILON, "maximal value of steps on U semi-axe"}, {"vmax", PAR_INT, (void*)&theconf.maxVsteps, 0, MINSTEPS-DBL_EPSILON, MAXSTEPS+DBL_EPSILON, "maximal value of steps on V semi-axe"}, {"focmax", PAR_INT, (void*)&theconf.maxFpos, 0, 0., Fmaxsteps, "maximal focus position in microsteps"}, {"focmin", PAR_INT, (void*)&theconf.minFpos, 0, -Fmaxsteps, 0., "minimal focus position in microsteps"}, {"stpservport", PAR_INT, (void*)&theconf.stpserverport, 0, -DBL_EPSILON, 65536., "port number of steppers' server"}, {"Kxu", PAR_DOUBLE, (void*)&theconf.Kxu, 0, KUVMIN-DBL_EPSILON, KUVMAX+DBL_EPSILON, "dU = Kxu*dX + Kyu*dY"}, {"Kyu", PAR_DOUBLE, (void*)&theconf.Kyu, 0, KUVMIN-DBL_EPSILON, KUVMAX+DBL_EPSILON, "dU = Kxu*dX + Kyu*dY"}, {"Kxv", PAR_DOUBLE, (void*)&theconf.Kxv, 0, KUVMIN-DBL_EPSILON, KUVMAX+DBL_EPSILON, "dV = Kxv*dX + Kyv*dY"}, {"Kyv", PAR_DOUBLE, (void*)&theconf.Kyv, 0, KUVMIN-DBL_EPSILON, KUVMAX+DBL_EPSILON, "dV = Kxv*dX + Kyv*dY"}, {"xtarget", PAR_DOUBLE, (void*)&theconf.xtarget, 0, 1.-DBL_EPSILON, MAX_OFFSET+DBL_EPSILON, "X coordinate of target position"}, {"ytarget", PAR_DOUBLE, (void*)&theconf.ytarget, 0, 1.-DBL_EPSILON, MAX_OFFSET+DBL_EPSILON, "Y coordinate of target position"}, {"eqthrowpart", PAR_DOUBLE, (void*)&theconf.throwpart, 0, -DBL_EPSILON, MAX_THROWPART+DBL_EPSILON, "a part of low intensity pixels to throw away when histogram equalized"}, {"minexp", PAR_DOUBLE, (void*)&theconf.minexp, 0, -DBL_EPSILON, EXPOS_MAX+DBL_EPSILON, "minimal exposition time"}, {"maxexp", PAR_DOUBLE, (void*)&theconf.maxexp, 0, -DBL_EPSILON, EXPOS_MAX+DBL_EPSILON, "maximal exposition time"}, {"fixedexp", PAR_DOUBLE, (void*)&theconf.fixedexp, 0, EXPOS_MIN-DBL_EPSILON, EXPOS_MAX+DBL_EPSILON, "fixed (in manual mode) exposition time"}, {"intensthres", PAR_DOUBLE, (void*)&theconf.intensthres, 0, DBL_EPSILON, 1.+DBL_EPSILON, "threshold by total object intensity when sorting = |I1-I2|/(I1+I2)"}, {"gain", PAR_DOUBLE, (void*)&theconf.gain, 0, GAIN_MIN-DBL_EPSILON, GAIN_MAX+DBL_EPSILON, "gain value in manual mode"}, {"brightness", PAR_DOUBLE, (void*)&theconf.brightness, 0, BRIGHT_MIN-DBL_EPSILON, BRIGHT_MAX-DBL_EPSILON, "brightness value"}, {"starssort", PAR_INT, (void*)&theconf.starssort, 0, -DBL_EPSILON, 1.+DBL_EPSILON, "stars sorting algorithm: by distance from target (0) or by intensity (1)"}, {"medfilt", PAR_INT, (void*)&theconf.medfilt, 0, -DBL_EPSILON, 1.+DBL_EPSILON, "use median filter"}, {"medseed", PAR_INT, (void*)&theconf.medseed, 0, MIN_MEDIAN_SEED-DBL_EPSILON, MAX_MEDIAN_SEED+DBL_EPSILON, "median filter radius"}, {"fixedbg", PAR_INT, (void*)&theconf.fixedbkg, 0, -DBL_EPSILON, 1.+DBL_EPSILON, "don't calculate background, use fixed value instead"}, {"fbglevel", PAR_INT, (void*)&theconf.fixedbkgval, 0, FIXED_BK_MIN-DBL_EPSILON, FIXED_BK_MAX+DBL_EPSILON, "fixed background level"}, {NULL, 0, NULL, 0, 0., 0., NULL} }; // return pointer to buff with size l filled with list of all commands (+help messages + low/high values) char *get_cmd_list(char *buff, int l){ if(!buff || l < 1) return NULL; int L = l; char *ptr = buff; confparam *par = parvals; while(L > 0 && par->name){ int s = snprintf(ptr, L, "%s=newval - %s (from %g to %g)\n", par->name, par->help, par->minval+DBL_EPSILON, par->maxval-DBL_EPSILON); if(s < 1) break; L -= s; ptr += s; ++par; } return buff; } static char *omitspaces(char *v){ if(!v) return NULL; while(*v && (*v == ' ' || *v == '\t')) ++v; char *ptr = strchr(v, ' '); if(ptr) *ptr = 0; ptr = strchr(v, '\t'); if(ptr) *ptr = 0; return v; } // Read key/value from `pair` (key = value) // RETURNed value should be FREEd char *get_keyval(const char *pair, char value[128]){ char key[128]; char val[128]; if(!pair || strlen(pair) < 3) return strdup("#"); // empty line char *keyptr = key, *valptr = val; int x = sscanf(pair, "%127[^=]=%127[^\n]%*c", key, val); //DBG("x=%d, key='%s', val='%s'", x, key, val); if(x < 0 || x > 2) return NULL; // wrong data or EOF if(x == 0) return strdup("#"); // empty line if(x == 2){ // param = value keyptr = omitspaces(key); valptr = omitspaces(val); sprintf(value, "%s", valptr); return strdup(keyptr); } keyptr = omitspaces(key); if(*keyptr == '#' || *keyptr == '%'){ // comment *value = 0; return strdup("#"); } return NULL; } // Read key/value from file static char *read_key(FILE *file, char value[128]){ char *line = NULL; size_t n = 0; int got = getline(&line, &n, file); if(!line) return NULL; if(got < 0){ free(line); return NULL; } char *kv = get_keyval(line, value); return kv; } static int str2int(int *num, const char *str){ long res; char *endptr; if(!str) return 0; res = strtol(str, &endptr, 0); if(endptr == str || *str == '\0' || *endptr != '\0'){ return FALSE; } if(res > INT_MAX || res < INT_MIN) return FALSE; if(num) *num = (int)res; return TRUE; } /** * @brief chk_keyval - check key for presence in theconf and calculate its value * @param key (i) - keyword * @param val (i) - value * @param result - result calculated from `val` * @return pointer to confparam if found & checked */ confparam *chk_keyval(const char *key, const char *val, key_value *result){ if(!key || !val || !result) return NULL; confparam *par = parvals; while(par->name){ if(strcmp(key, par->name) == 0){ //DBG("key='%s', par->name='%s'", key, par->name); result->type = par->type; switch(par->type){ case PAR_INT: //DBG("INTEGER"); if(!str2int(&result->val.intval, val)){ WARNX("Wrong integer value '%s' of parameter '%s'", val, key); return NULL; } if(result->val.intval > par->minval && result->val.intval < par->maxval) return par; else WARNX("Value (%d) of parameter %s out of range %g..%g", result->val.intval, par->name, par->minval, par->maxval); break; case PAR_DOUBLE: //DBG("DOUBLE"); if(!str2double(&result->val.dblval, val)){ WARNX("Wrong double value '%s' of parameter '%s'", val, key); return NULL; } //DBG("val: %g, min: %g, max: %g", result->val.dblval, par->minval, par->maxval); if(result->val.dblval > par->minval && result->val.dblval < par->maxval) return par; else WARNX("Value (%g) of parameter %s out of range %g..%g", result->val.dblval, par->name, par->minval, par->maxval); break; } return NULL; } ++par; } return NULL; } /** * @brief chkconfig - check configuration file and init variables * @param confname - name of file * @return FALSE if configuration file wrong or absent */ int chkconfig(const char *confname){ DBG("Config name: %s", confname); if(conffile){ free(conffile); conffile = NULL; } conffile = strdup(confname); FILE *f = fopen(confname, "r"); int ret = TRUE; if(!f){ WARN("Can't open %s", confname); return FALSE; } char *key, val[128]; confparam *par = parvals; while(par->name){ par->got = 0; ++par; } while((key = read_key(f, val))){ if(*key == '#'){ free(key); key = NULL; continue; // comment } //DBG("key: %s", key); key_value kv; par = chk_keyval(key, val, &kv); if(!par){ WARNX("Parameter '%s' is wrong or out of range", key); free(key); key = NULL; continue; } switch(par->type){ case PAR_INT: *((int*)par->ptr) = kv.val.intval; break; case PAR_DOUBLE: *((double*)par->ptr) = kv.val.dblval; break; } ++par->got; free(key); key = NULL; } fclose(f); int found = 0; par = parvals; while(par->name){ //DBG("parvals[]={%s, %d, %d(%g), %d}", par->name, par->type, *((int*)par->ptr), *((double*)par->ptr), par->got); int k = par->got; if(!k){ ++par; continue; } if(k > 1){ WARNX("parameter '%s' meets %d times", par->name, k); ret = FALSE; } ++found; ++par; } DBG("chkconfig(): found %d", found); return ret; } /** * @brief saveconf - try to save configuration into file * @param confname - config file name * @return FALSE if failed */ int saveconf(const char *confname){ if(!confname){ if(!conffile){ WARNX("no conffile given"); return FALSE; } confname = conffile; } FILE *f = fopen(confname, "w"); if(!f){ WARN("Can't open %s", confname); LOGERR("Can't open %s to store configuration", confname); return FALSE; } confparam *par = parvals; while(par->name){ par->got = 1; switch(par->type){ case PAR_INT: fprintf(f, "%s = %d\n", par->name, *((int*)par->ptr)); DBG("%s = %d", par->name, *((int*)par->ptr)); break; case PAR_DOUBLE: fprintf(f, "%s = %.3f\n", par->name, *((double*)par->ptr)); DBG("%s = %.3f", par->name, *((double*)par->ptr)); break; } ++par; } DBG("%s saved", confname); LOGDBG("Configuration file '%s' saved", confname); fclose(f); return TRUE; } // return buffer filled with current configuration char *listconf(const char *messageid, char *buf, int buflen){ int L; char *ptr = buf; confparam *par = parvals; L = snprintf(ptr, buflen, "{ \"%s\": \"%s\", ", MESSAGEID, messageid); buflen -= L; ptr += L; while(par->name && buflen > 0){ switch(par->type){ case PAR_INT: L = snprintf(ptr, buflen, "\"%s\": %d", par->name, *((int*)par->ptr)); break; case PAR_DOUBLE: L = snprintf(ptr, buflen, "\"%s\": %.3f", par->name, *((double*)par->ptr)); break; default: L = 0; } ++par; if(L > -1){ buflen -= L; ptr += L; }else{ buf[buflen-1] = 0; break; } if(par->name){ // put comma L = snprintf(ptr, buflen, ", "); if(L > -1){buflen -= L; ptr += L;} } } snprintf(ptr, buflen, " }\n"); return buf; }