| 1 | # include "psphot.h"
|
|---|
| 2 |
|
|---|
| 3 | static psHash *timers = NULL;
|
|---|
| 4 |
|
|---|
| 5 | // start/restart a named timer
|
|---|
| 6 | bool psTimerStart (char *name) {
|
|---|
| 7 |
|
|---|
| 8 | psTime *start;
|
|---|
| 9 |
|
|---|
| 10 | if (timers == NULL) timers = psHashAlloc (16);
|
|---|
| 11 |
|
|---|
| 12 | start = psTimeGetTime (PS_TIME_UTC);
|
|---|
| 13 |
|
|---|
| 14 | psHashAdd (timers, name, start);
|
|---|
| 15 |
|
|---|
| 16 | return (TRUE);
|
|---|
| 17 | }
|
|---|
| 18 |
|
|---|
| 19 | // get current elapsed time on named timer
|
|---|
| 20 | psF64 psTimerMark (char *name) {
|
|---|
| 21 |
|
|---|
| 22 | psTime *start;
|
|---|
| 23 | psTime *mark;
|
|---|
| 24 | psF64 delta;
|
|---|
| 25 |
|
|---|
| 26 | if (timers == NULL) return (0);
|
|---|
| 27 |
|
|---|
| 28 | start = psHashLookup (timers, name);
|
|---|
| 29 | if (start == NULL) return (0);
|
|---|
| 30 |
|
|---|
| 31 | mark = psTimeGetTime (PS_TIME_UTC);
|
|---|
| 32 | delta = psTimeDelta (mark, start);
|
|---|
| 33 | return (delta);
|
|---|
| 34 | }
|
|---|
| 35 |
|
|---|
| 36 | // we have log levels 1 (Error), 2 (Warning), 3 (Info), 4 (Details), 5 (Minutiae)
|
|---|
| 37 | // 2 = default, -v = 3, -vv = 4, -vvv = 5
|
|---|
| 38 | psS32 psLogArguments (int *argc, char **argv) {
|
|---|
| 39 |
|
|---|
| 40 | int N, level;
|
|---|
| 41 |
|
|---|
| 42 | // default log level is 2
|
|---|
| 43 | level = 2;
|
|---|
| 44 |
|
|---|
| 45 | // set in order, so that -vvv overrides -vv overrides -v
|
|---|
| 46 | if ((N = get_argument (*argc, argv, "-v"))) {
|
|---|
| 47 | remove_argument (N, argc, argv);
|
|---|
| 48 | level = 3;
|
|---|
| 49 | }
|
|---|
| 50 | if ((N = get_argument (*argc, argv, "-vv"))) {
|
|---|
| 51 | remove_argument (N, argc, argv);
|
|---|
| 52 | level = 4;
|
|---|
| 53 | }
|
|---|
| 54 | if ((N = get_argument (*argc, argv, "-vvv"))) {
|
|---|
| 55 | remove_argument (N, argc, argv);
|
|---|
| 56 | level = 5;
|
|---|
| 57 | }
|
|---|
| 58 |
|
|---|
| 59 | if ((N = get_argument (*argc, argv, "-logfmt"))) {
|
|---|
| 60 | if (*argc < N + 2) {
|
|---|
| 61 | psAbort ("psLogArguments", "USAGE: -logfmt (format)");
|
|---|
| 62 | }
|
|---|
| 63 | remove_argument (N, argc, argv);
|
|---|
| 64 | psLogSetFormat (argv[N]); // XXX EAM : this function should return an error if the log format is invalid
|
|---|
| 65 | remove_argument (N, argc, argv);
|
|---|
| 66 | }
|
|---|
| 67 |
|
|---|
| 68 | // set the level, return the level
|
|---|
| 69 | psLogSetLevel (level);
|
|---|
| 70 | return (level);
|
|---|
| 71 | }
|
|---|
| 72 |
|
|---|
| 73 | // set trace levels by facility
|
|---|
| 74 | psS32 psTraceArguments (int *argc, char **argv) {
|
|---|
| 75 |
|
|---|
| 76 | int N;
|
|---|
| 77 |
|
|---|
| 78 | // argument format is: -trace (facil) (level)
|
|---|
| 79 | while ((N = get_argument (*argc, argv, "-trace"))) {
|
|---|
| 80 | if (*argc < N + 3) {
|
|---|
| 81 | psAbort ("psTraceArguments", "USAGE: -trace (facility) (level)");
|
|---|
| 82 | }
|
|---|
| 83 | remove_argument (N, argc, argv);
|
|---|
| 84 | psTraceSetLevel (argv[N], atoi(argv[N+1]));
|
|---|
| 85 | remove_argument (N, argc, argv);
|
|---|
| 86 | remove_argument (N, argc, argv);
|
|---|
| 87 | }
|
|---|
| 88 | if ((N = get_argument (*argc, argv, "-trace-levels"))) {
|
|---|
| 89 | psTracePrintLevels ();
|
|---|
| 90 | exit (2);
|
|---|
| 91 | }
|
|---|
| 92 | return (TRUE);
|
|---|
| 93 | }
|
|---|
| 94 |
|
|---|
| 95 | // find the location of the specified argument
|
|---|
| 96 | int psArgumentGet (int argc, char **argv, char *arg) {
|
|---|
| 97 |
|
|---|
| 98 | int i;
|
|---|
| 99 |
|
|---|
| 100 | for (i = 0; i < argc; i++) {
|
|---|
| 101 | if (!strcmp(argv[i], arg))
|
|---|
| 102 | return (i);
|
|---|
| 103 | }
|
|---|
| 104 |
|
|---|
| 105 | return ((int)NULL);
|
|---|
| 106 | }
|
|---|
| 107 |
|
|---|
| 108 | // remove the specified argument (by location)
|
|---|
| 109 | int psArgumentRemove (int N, int *argc, char **argv) {
|
|---|
| 110 |
|
|---|
| 111 | int i;
|
|---|
| 112 |
|
|---|
| 113 | if ((N != (int)NULL) && (N != 0)) {
|
|---|
| 114 | (*argc)--;
|
|---|
| 115 | for (i = N; i < *argc; i++) {
|
|---|
| 116 | argv[i] = argv[i+1];
|
|---|
| 117 | }
|
|---|
| 118 | }
|
|---|
| 119 |
|
|---|
| 120 | return (N);
|
|---|
| 121 | }
|
|---|
| 122 |
|
|---|
| 123 | // find the location of the specified argument
|
|---|
| 124 | int get_argument (int argc, char **argv, char *arg) {
|
|---|
| 125 |
|
|---|
| 126 | int i;
|
|---|
| 127 |
|
|---|
| 128 | for (i = 0; i < argc; i++) {
|
|---|
| 129 | if (!strcmp(argv[i], arg))
|
|---|
| 130 | return (i);
|
|---|
| 131 | }
|
|---|
| 132 |
|
|---|
| 133 | return ((int)NULL);
|
|---|
| 134 | }
|
|---|
| 135 |
|
|---|
| 136 | // remove the specified argument (by location)
|
|---|
| 137 | int remove_argument (int N, int *argc, char **argv) {
|
|---|
| 138 |
|
|---|
| 139 | int i;
|
|---|
| 140 |
|
|---|
| 141 | if ((N != (int)NULL) && (N != 0)) {
|
|---|
| 142 | (*argc)--;
|
|---|
| 143 | for (i = N; i < *argc; i++) {
|
|---|
| 144 | argv[i] = argv[i+1];
|
|---|
| 145 | }
|
|---|
| 146 | }
|
|---|
| 147 |
|
|---|
| 148 | return (N);
|
|---|
| 149 | }
|
|---|
| 150 |
|
|---|
| 151 | // alternate implementation of this function from pmObjects.c
|
|---|
| 152 | psVector *psGetRowVectorFromImage(psImage *image,
|
|---|
| 153 | psU32 row)
|
|---|
| 154 | {
|
|---|
| 155 | PS_IMAGE_CHECK_NULL(image, NULL);
|
|---|
| 156 | PS_IMAGE_CHECK_TYPE(image, PS_TYPE_F32, NULL);
|
|---|
| 157 |
|
|---|
| 158 | psVector *tmpVector = psVectorAlloc(image->numCols, PS_TYPE_F32);
|
|---|
| 159 | memcpy (tmpVector->data.F32, image->data.F32[row], image->numCols*sizeof(psF32));
|
|---|
| 160 | return(tmpVector);
|
|---|
| 161 | }
|
|---|
| 162 |
|
|---|
| 163 | // extract config informatin from config data or from image header, if specified
|
|---|
| 164 | psF32 pmConfigLookupF32 (bool *status, psMetadata *config, psMetadata *header, char *name) {
|
|---|
| 165 |
|
|---|
| 166 | char *source;
|
|---|
| 167 | char *keyword;
|
|---|
| 168 | psF32 value;
|
|---|
| 169 | psMetadataItem *item;
|
|---|
| 170 |
|
|---|
| 171 | // look for the entry in the config collection
|
|---|
| 172 | item = psMetadataLookup (config, name);
|
|---|
| 173 | if (item == NULL) {
|
|---|
| 174 | psLogMsg ("pmConfigLookupF32", 2, "no key %s in config data, trying as header keyword", name);
|
|---|
| 175 | value = psMetadataLookupF32 (status, header, name);
|
|---|
| 176 | if (*status == false) {
|
|---|
| 177 | psAbort ("pmConfigLookupF32", "no key %s in header", name);
|
|---|
| 178 | }
|
|---|
| 179 | *status = true;
|
|---|
| 180 | return (value);
|
|---|
| 181 | }
|
|---|
| 182 |
|
|---|
| 183 | // I'm either expecting a string, with the name "HD:keyword"...
|
|---|
| 184 | if (item->type == PS_META_STR) {
|
|---|
| 185 | source = item->data.V;
|
|---|
| 186 | if (!strncasecmp (source, "HD:", 3)) {
|
|---|
| 187 | keyword = &source[3];
|
|---|
| 188 | value = psMetadataLookupF32 (status, header, keyword);
|
|---|
| 189 | if (*status == false) {
|
|---|
| 190 | psAbort ("pmConfigLookupF32", "no key %s in config", name);
|
|---|
| 191 | }
|
|---|
| 192 | *status = true;
|
|---|
| 193 | return (value);
|
|---|
| 194 | }
|
|---|
| 195 | }
|
|---|
| 196 |
|
|---|
| 197 | // ... or a value (F32?)
|
|---|
| 198 | if (item->type == PS_META_F32) {
|
|---|
| 199 | value = item->data.F32;
|
|---|
| 200 | return (value);
|
|---|
| 201 | }
|
|---|
| 202 |
|
|---|
| 203 | *status = false;
|
|---|
| 204 | psError(PS_ERR_UNKNOWN, true, "invalid item");
|
|---|
| 205 | return (0);
|
|---|
| 206 | }
|
|---|
| 207 |
|
|---|
| 208 | void psImageSmooth (psImage *image, float sigma, float Nsigma) {
|
|---|
| 209 |
|
|---|
| 210 | int Nx, Ny, Npixel, Nrange;
|
|---|
| 211 | float factor, g, s;
|
|---|
| 212 | psVector *temp;
|
|---|
| 213 |
|
|---|
| 214 | // relevant terms
|
|---|
| 215 | Nrange = sigma*Nsigma + 0.5;
|
|---|
| 216 | Npixel = 2*Nrange + 1;
|
|---|
| 217 | factor = -0.5/(sigma*sigma);
|
|---|
| 218 |
|
|---|
| 219 | Nx = image->numCols;
|
|---|
| 220 | Ny = image->numRows;
|
|---|
| 221 |
|
|---|
| 222 | // generate gaussian
|
|---|
| 223 | psVector *gaussnorm = psVectorAlloc (Npixel, PS_TYPE_F32);
|
|---|
| 224 | for (int i = -Nrange; i < Nrange + 1; i++) {
|
|---|
| 225 | gaussnorm->data.F32[i+Nrange] = exp (factor*i*i);
|
|---|
| 226 | }
|
|---|
| 227 | psF32 *gauss = &gaussnorm->data.F32[Nrange];
|
|---|
| 228 |
|
|---|
| 229 | // smooth in X direction
|
|---|
| 230 | temp = psVectorAlloc (Nx, PS_TYPE_F32);
|
|---|
| 231 | for (int j = 0; j < Ny; j++) {
|
|---|
| 232 | psF32 *vi = image->data.F32[j];
|
|---|
| 233 | psF32 *vo = temp->data.F32;
|
|---|
| 234 | for (int i = 0; i < Nx; i++) {
|
|---|
| 235 | g = s = 0;
|
|---|
| 236 | for (int n = -Nrange; n < Nrange + 1; n++) {
|
|---|
| 237 | if (i+n < 0) continue;
|
|---|
| 238 | if (i+n >= Nx) continue;
|
|---|
| 239 | s += gauss[n]*vi[i+n];
|
|---|
| 240 | g += gauss[n];
|
|---|
| 241 | }
|
|---|
| 242 | vo[i] = s / g;
|
|---|
| 243 | }
|
|---|
| 244 | memcpy (image->data.F32[j], temp->data.F32, Nx*sizeof(psF32));
|
|---|
| 245 | }
|
|---|
| 246 | psFree (temp);
|
|---|
| 247 |
|
|---|
| 248 | // smooth in Y direction
|
|---|
| 249 | temp = psVectorAlloc (image->numRows, PS_TYPE_F32);
|
|---|
| 250 | for (int i = 0; i < Nx; i++) {
|
|---|
| 251 | psF32 *vo = temp->data.F32;
|
|---|
| 252 | psF32 **vi = image->data.F32;
|
|---|
| 253 | for (int j = 0; j < Ny; j++) {
|
|---|
| 254 | g = s = 0;
|
|---|
| 255 | for (int n = -Nrange; n < Nrange + 1; n++) {
|
|---|
| 256 | if (j+n < 0) continue;
|
|---|
| 257 | if (j+n >= Ny) continue;
|
|---|
| 258 | s += gauss[n]*vi[j+n][i];
|
|---|
| 259 | g += gauss[n];
|
|---|
| 260 | }
|
|---|
| 261 | vo[j] = s / g;
|
|---|
| 262 | }
|
|---|
| 263 | // replace temp in image
|
|---|
| 264 | for (int j = 0; j < Ny; j++) {
|
|---|
| 265 | vi[j][i] = vo[j];
|
|---|
| 266 | }
|
|---|
| 267 | }
|
|---|
| 268 | psFree (temp);
|
|---|
| 269 | psFree (gaussnorm);
|
|---|
| 270 | }
|
|---|
| 271 |
|
|---|
| 272 | bool psImageInit (psImage *image,...) {
|
|---|
| 273 |
|
|---|
| 274 | va_list argp;
|
|---|
| 275 | psU8 vU8;
|
|---|
| 276 | psF32 vF32;
|
|---|
| 277 | psF64 vF64;
|
|---|
| 278 |
|
|---|
| 279 | if (image == NULL) return (false);
|
|---|
| 280 |
|
|---|
| 281 | va_start (argp, image);
|
|---|
| 282 |
|
|---|
| 283 | switch (image->type.type) {
|
|---|
| 284 | case PS_TYPE_U8:
|
|---|
| 285 | vU8 = va_arg (argp, psU32);
|
|---|
| 286 |
|
|---|
| 287 | for (int iy = 0; iy < image->numRows; iy++) {
|
|---|
| 288 | for (int ix = 0; ix < image->numCols; ix++) {
|
|---|
| 289 | image->data.U8[iy][ix] = vU8;
|
|---|
| 290 | }
|
|---|
| 291 | }
|
|---|
| 292 | break;
|
|---|
| 293 |
|
|---|
| 294 | case PS_TYPE_F32:
|
|---|
| 295 | vF32 = va_arg (argp, psF64);
|
|---|
| 296 |
|
|---|
| 297 | for (int iy = 0; iy < image->numRows; iy++) {
|
|---|
| 298 | for (int ix = 0; ix < image->numCols; ix++) {
|
|---|
| 299 | image->data.F32[iy][ix] = vF32;
|
|---|
| 300 | }
|
|---|
| 301 | }
|
|---|
| 302 | return (true);
|
|---|
| 303 |
|
|---|
| 304 | case PS_TYPE_F64:
|
|---|
| 305 | vF64 = va_arg (argp, psF64);
|
|---|
| 306 |
|
|---|
| 307 | for (int iy = 0; iy < image->numRows; iy++) {
|
|---|
| 308 | for (int ix = 0; ix < image->numCols; ix++) {
|
|---|
| 309 | image->data.F64[iy][ix] = vF64;
|
|---|
| 310 | }
|
|---|
| 311 | }
|
|---|
| 312 | return (true);
|
|---|
| 313 |
|
|---|
| 314 | default:
|
|---|
| 315 | psAbort ("psphot.psUtils", "datatype %d not defined in psImageInit\n", image->type);
|
|---|
| 316 | return (false);
|
|---|
| 317 | }
|
|---|
| 318 | return (false);
|
|---|
| 319 | }
|
|---|
| 320 |
|
|---|
| 321 | // define a square region centered on the given coordinate
|
|---|
| 322 | psRegion *psRegionSquare (psF32 x, psF32 y, psF32 radius) {
|
|---|
| 323 | psRegion *region;
|
|---|
| 324 | region = psRegionAlloc (x - radius, x + radius + 1,
|
|---|
| 325 | y - radius, y + radius + 1);
|
|---|
| 326 | return (region);
|
|---|
| 327 | }
|
|---|
| 328 |
|
|---|
| 329 | // set actual region based on image parameters:
|
|---|
| 330 | // compensate for negative upper limits
|
|---|
| 331 | // XXX this is inconsistent: the coordindates should always be in the parent
|
|---|
| 332 | // frame, which means the negative values should subtract from Nx,Ny of
|
|---|
| 333 | // the parent, not the child. but, we don't carry the dimensions of the
|
|---|
| 334 | // parent in the psImage container. for now, us the child Nx,Ny
|
|---|
| 335 | // force range to be on this subimage
|
|---|
| 336 | // XXX EAM : this needs to be changes to use psRegion rather than psRegion*
|
|---|
| 337 | psRegion *psRegionForImage (psRegion *out, psImage *image, psRegion *in) {
|
|---|
| 338 |
|
|---|
| 339 | // x0,y0, x1,y1 are in *parent* units
|
|---|
| 340 |
|
|---|
| 341 | if (out == NULL) {
|
|---|
| 342 | out = psRegionAlloc(in->x0, in->x1, in->y0, in->y1);
|
|---|
| 343 | } else {
|
|---|
| 344 | *out = *in;
|
|---|
| 345 | }
|
|---|
| 346 | // XXX these are probably wrong (see above)
|
|---|
| 347 | if (out->x1 <= 0) {
|
|---|
| 348 | out->x1 = image->col0 + image->numCols + out->x1;
|
|---|
| 349 | }
|
|---|
| 350 | if (out->y1 <= 0) {
|
|---|
| 351 | out->y1 = image->row0 + image->numRows + out->y1;
|
|---|
| 352 | }
|
|---|
| 353 |
|
|---|
| 354 | // force the lower-limits to be on the child
|
|---|
| 355 | out->x0 = PS_MAX(image->col0, out->x0);
|
|---|
| 356 | out->y0 = PS_MAX(image->row0, out->y0);
|
|---|
| 357 |
|
|---|
| 358 | // force the upper-limits to be on the child
|
|---|
| 359 | out->x1 = PS_MIN(image->col0 + image->numCols, out->x1);
|
|---|
| 360 | out->y1 = PS_MIN(image->row0 + image->numRows, out->y1);
|
|---|
| 361 | return (out);
|
|---|
| 362 | }
|
|---|
| 363 |
|
|---|
| 364 | // mask the area contained by the region
|
|---|
| 365 | // the region is defined wrt the parent image
|
|---|
| 366 | void psImageMaskRegion (psImage *image, psRegion *region, bool logical_and, int maskValue) {
|
|---|
| 367 |
|
|---|
| 368 | for (int iy = 0; iy < image->numRows; iy++) {
|
|---|
| 369 | for (int ix = 0; ix < image->numCols; ix++) {
|
|---|
| 370 | if (ix + image->col0 < region->x0) continue;
|
|---|
| 371 | if (ix + image->col0 >= region->x1) continue;
|
|---|
| 372 | if (iy + image->row0 < region->y0) continue;
|
|---|
| 373 | if (iy + image->row0 >= region->y1) continue;
|
|---|
| 374 | if (logical_and) {
|
|---|
| 375 | image->data.U8[iy][ix] &= maskValue;
|
|---|
| 376 | } else {
|
|---|
| 377 | image->data.U8[iy][ix] |= maskValue;
|
|---|
| 378 | }
|
|---|
| 379 | }
|
|---|
| 380 | }
|
|---|
| 381 | }
|
|---|
| 382 |
|
|---|
| 383 | // mask the area not contained by the region
|
|---|
| 384 | // the region is defined wrt the parent image
|
|---|
| 385 | void psImageKeepRegion (psImage *image, psRegion *region, bool logical_and, int maskValue) {
|
|---|
| 386 |
|
|---|
| 387 | for (int iy = 0; iy < image->numRows; iy++) {
|
|---|
| 388 | for (int ix = 0; ix < image->numCols; ix++) {
|
|---|
| 389 | if (ix + image->col0 < region->x0) goto maskit;
|
|---|
| 390 | if (ix + image->col0 >= region->x1) goto maskit;
|
|---|
| 391 | if (iy + image->row0 < region->y0) goto maskit;
|
|---|
| 392 | if (iy + image->row0 >= region->y1) goto maskit;
|
|---|
| 393 | continue;
|
|---|
| 394 | maskit:
|
|---|
| 395 | if (logical_and) {
|
|---|
| 396 | image->data.U8[iy][ix] &= maskValue;
|
|---|
| 397 | } else {
|
|---|
| 398 | image->data.U8[iy][ix] |= maskValue;
|
|---|
| 399 | }
|
|---|
| 400 | }
|
|---|
| 401 | }
|
|---|
| 402 | }
|
|---|
| 403 |
|
|---|
| 404 | // mask the area contained by the region
|
|---|
| 405 | // the region is defined wrt the parent image
|
|---|
| 406 | void psImageMaskCircle (psImage *image, double x, double y, double radius, bool logical_and, int maskValue) {
|
|---|
| 407 |
|
|---|
| 408 | double dx, dy, r2, R2;
|
|---|
| 409 |
|
|---|
| 410 | R2 = PS_SQR(radius);
|
|---|
| 411 |
|
|---|
| 412 | for (int iy = 0; iy < image->numRows; iy++) {
|
|---|
| 413 | for (int ix = 0; ix < image->numCols; ix++) {
|
|---|
| 414 | dx = ix + image->col0 - x;
|
|---|
| 415 | dy = iy + image->row0 - y;
|
|---|
| 416 | r2 = PS_SQR(dx) + PS_SQR(dy);
|
|---|
| 417 | if (r2 > R2) continue;
|
|---|
| 418 | if (logical_and) {
|
|---|
| 419 | image->data.U8[iy][ix] &= maskValue;
|
|---|
| 420 | } else {
|
|---|
| 421 | image->data.U8[iy][ix] |= maskValue;
|
|---|
| 422 | }
|
|---|
| 423 | }
|
|---|
| 424 | }
|
|---|
| 425 | }
|
|---|
| 426 |
|
|---|
| 427 | // mask the area contained by the region
|
|---|
| 428 | // the region is defined wrt the parent image
|
|---|
| 429 | void psImageKeepCircle (psImage *image, double x, double y, double radius, bool logical_and, int maskValue) {
|
|---|
| 430 |
|
|---|
| 431 | double dx, dy, r2, R2;
|
|---|
| 432 |
|
|---|
| 433 | R2 = PS_SQR(radius);
|
|---|
| 434 |
|
|---|
| 435 | for (int iy = 0; iy < image->numRows; iy++) {
|
|---|
| 436 | for (int ix = 0; ix < image->numCols; ix++) {
|
|---|
| 437 | dx = ix + image->col0 - x;
|
|---|
| 438 | dy = iy + image->row0 - y;
|
|---|
| 439 | r2 = PS_SQR(dx) + PS_SQR(dy);
|
|---|
| 440 | if (r2 < R2) continue;
|
|---|
| 441 | if (logical_and) {
|
|---|
| 442 | image->data.U8[iy][ix] &= maskValue;
|
|---|
| 443 | } else {
|
|---|
| 444 | image->data.U8[iy][ix] |= maskValue;
|
|---|
| 445 | }
|
|---|
| 446 | }
|
|---|
| 447 | }
|
|---|
| 448 | }
|
|---|
| 449 |
|
|---|
| 450 | psVector *psVectorCreate (double lower, double upper, double delta, psElemType type) {
|
|---|
| 451 |
|
|---|
| 452 | int nBin = (upper - lower) / delta;
|
|---|
| 453 |
|
|---|
| 454 | psVector *out = psVectorAlloc (nBin, type);
|
|---|
| 455 |
|
|---|
| 456 | for (int i = 0; i < nBin; i++) {
|
|---|
| 457 | out->data.F64[i] = lower + i * delta;
|
|---|
| 458 | }
|
|---|
| 459 |
|
|---|
| 460 | return (out);
|
|---|
| 461 | }
|
|---|