// // Tool to create stacked strip charts, or "zebragraphs" // // - useful for analysis of GPS 1PPS "sawtooth" jitter // // 17-May-2007 tvb // #include #include #include #include "bmp1_lib.h" char Usage[] = "\n" " Create black & white stacked strip charts (zebragraphs)\n" "\n" "Usage: zebra2 [options] OUT.bmp < IN.txt\n" "\n" "Options:\n" " /b:#x# set bitmap width and height (default 640x480)\n" " /s:# set number of stripes (default 12)\n" " /h:# override stripe height (pixels)\n" "\n" " /x:# change x-scale (default 1)\n" " /y:# set y-scale (data scaling factor)\n" " /fs:# or, set y-scale (full scale value)\n" " /g:# set number of grid lines (default 3)\n" " /dots display dots only (no lines)\n" "\n" " /in:f specify input data filename\n" " /out:f specify output bitmap filename\n" "\n" " /skip:# number of data points to skip over\n" " /take:# maximum number of data points to take\n" " /mult:# multiplier for skip and take (default 1)\n" "\n" "Examples:\n" " zebra2 /s:4 /y:1e2 /out:jitter.bmp < 1pps.txt\n" " zebra2 /s:8 /y:1e2 /x:5 /out:jitter8.bmp < 1pps.txt\n" " zebra2 /s:6 /y:1e2 /x:1 /b:600x600 /out:1hour.bmp < 1pps.txt\n" " zebra2 /s:24 /y:1e2 /b:3600x2700 /out:24hour.bmp < 1pps.txt\n" " zebra2 /in:1pps.txt /out:1day.bmp /s:144 /x:2 /y:1e2 /b:1200x8000\n" "\n" "Notes:\n" " - Detrend (zero slope) and normalized (zero mean) input data.\n" " - Use the yscale parameter to scale input data to +/- 1.0 range.\n" " - Use xscale to spread data out horizontally, if desired.\n" " - Specify input file or use stdin.\n" " - Specify output file give as command argument.\n" ; struct { char *input_path; FILE *input; char *output_path; int bmp_width, bmp_height; int stripes, stripe_height; int xscale, grids, dots; double yscale, fs; long skip, take, mult; } opt; long line_count, data_count, take_count; int main (int argc, char *argv[]) { int stripe_index, i, ymax; int x, y, prev_x, prev_y, y_half, y_center; double value; int get_data (double *); int get_options (int, char *[]); if (argc < 2) { fprintf(stderr, Usage); exit(1); } // Get user options and allocate bitmap. get_options(argc, argv); bmp1_alloc(opt.bmp_width, opt.bmp_height); // N.B. bitmap x=0 is left (grows right) and y=0 is top (grows down). stripe_index = 0; y_half = opt.stripe_height / 2; y_center = y_half; x = 0; // Split image into multiple stacked equally spaced horizontal stripes. while (get_data(&value) != 0) { // Once each new stripe, draw grid lines and set new clip region. if (x == 0 && opt.grids > 0) { bmp1_clip_rect(0, y_center - y_half, opt.bmp_width, y_center + y_half); for (i = -opt.grids / 2; i <= opt.grids / 2; i += 1) { y = y_center + i * opt.stripe_height / (opt.grids + 1); bmp1_lineto(0, y, opt.bmp_width - 1, y); } } // For each data point, plot square and draw connecting line. y = y_center + (int) ((y_half / -2.0) * (value / opt.fs) + 0.5); bmp1_square(x, y, 1); if (x > 0 && !opt.dots) { bmp1_lineto(prev_x, prev_y, x, y); } prev_x = x; prev_y = y; // Advance to next location, or wrap around and down to next stripe. x += opt.xscale; if (x > opt.bmp_width - 1) { x = 0; y_center += opt.stripe_height; stripe_index += 1; if (stripe_index == opt.stripes) { break; } } } // Warn if any points were out of range. i = bmp1_clip_count(); if (i) { fprintf(stderr, "WARNING: %ld points clipped; y-scale too large?\n", i); } // Write finished bitmap to output file. ymax = (stripe_index == opt.stripes) ? opt.bmp_height : (y_center + y_half); bmp1_write(opt.output_path, ymax); // Display summary information. fprintf(stderr, "** %ld points read\n", take_count); if (stripe_index != opt.stripes) { fprintf(stderr, "** only %.2lf / %d stripes filled\n", stripe_index + (double)x / opt.bmp_width, opt.stripes); } if (ymax != opt.bmp_height) { fprintf(stderr, "** %dx%d bitmap written\n", opt.bmp_width, ymax); } fprintf(stderr, "** done\n"); return 0; } // // Get raw data; after scaling it should range from -1 min to +1 max. // int get_data (double *value) { char line[100]; while (fgets(line, sizeof line, opt.input) != NULL) { line_count += 1; if (line[0] != '#') { data_count += 1; if (opt.skip > 0 && data_count <= opt.skip) { continue; } if (opt.take > 0 && take_count >= opt.take) { return 0; } if (1 != sscanf(line, "%lf", value)) { fprintf(stderr, "Line %ld: non-numeric input data: `%s'\n", line_count, line); exit(1); } *value *= opt.yscale; take_count += 1; return 1; } } return 0; } // // Get user options. // int get_options (int argc, char *argv[]) { char *arg; // Set non-zero defaults. opt.grids = 3; opt.xscale = 2; opt.yscale = opt.fs = 1.0; // Parse user options. while (argc > 1 && (arg = argv[1])[0] == '/') { if (0 == strcmp(arg, "/?") || 0 == strcmp(arg, "/help")) { fprintf(stderr, Usage); exit(0); } else if (2 == sscanf(arg, "/b:%dx%d", &opt.bmp_width, &opt.bmp_height)) { fprintf(stderr, "** %dx%d bitmap dimensions\n", opt.bmp_width, opt.bmp_height); } else if (1 == sscanf(arg, "/s:%d", &opt.stripes)) { fprintf(stderr, "** %d stripes\n", opt.stripes); } else if (1 == sscanf(arg, "/h:%d", &opt.stripe_height)) { fprintf(stderr, "** %d stripe height\n", opt.stripe_height); } else if (1 == sscanf(arg, "/x:%d", &opt.xscale)) { fprintf(stderr, "** %d x-scale factor\n", opt.xscale); } else if (1 == sscanf(arg, "/y:%lf", &opt.yscale)) { fprintf(stderr, "** %lg y-scale factor\n", opt.yscale); } else if (1 == sscanf(arg, "/fs:%lf", &opt.fs)) { fprintf(stderr, "** %lg y full-scale\n", opt.fs); } else if (1 == sscanf(arg, "/g:%d", &opt.grids)) { fprintf(stderr, "** %d grid lines per stripe\n", opt.grids); } else if (0 == strcmp(arg, "/dot")) { opt.dots = 1; fprintf(stderr, "** dots only (no lines)\n"); } else if (strstr(arg, "/in:")) { opt.input_path = strchr(arg, ':') + 1; fprintf(stderr, "** %s input file\n", opt.input_path); } else if (strstr(arg, "/out:")) { opt.output_path = strchr(arg, ':') + 1; fprintf(stderr, "** %s output file\n", opt.output_path); } else if (1 == sscanf(arg, "/skip:%ld", &opt.skip)) { fprintf(stderr, "** %ld data skip count\n", opt.skip); } else if (1 == sscanf(arg, "/take:%ld", &opt.take)) { fprintf(stderr, "** %ld data limit\n", opt.take); } else if (1 == sscanf(arg, "/mult:%ld", &opt.mult)) { fprintf(stderr, "** %ld data batch count\n", opt.mult); } else { fprintf(stderr, "%s: unknown option\n", arg); exit(1); } argc -= 1; argv += 1; } // Input file options. if (opt.input_path) { if ((opt.input = fopen(opt.input_path, "r")) == NULL) { fprintf(stderr, "%s: cannot open\n", opt.input_path); exit(1); } } else { opt.input = stdin; opt.input_path = "(stdin)"; } // Input batch (multiplier) options. if (opt.mult) { opt.skip *= opt.mult; opt.take *= opt.mult; } #define DEFAULT_WIDTH 640 if (opt.bmp_width <= 0) { opt.bmp_width = DEFAULT_WIDTH; } #define DEFAULT_HEIGHT 480 #define DEFAULT_STRIPES 12 // If no options set, then use defaults for all. if (opt.bmp_height == 0 && opt.stripes == 0 && opt.stripe_height == 0) { opt.bmp_height = DEFAULT_HEIGHT; } // If one option set, then set others to default. if (opt.bmp_height != 0 && opt.stripes == 0 && opt.stripe_height == 0) { opt.stripes = DEFAULT_STRIPES; } if (opt.bmp_height == 0 && opt.stripes != 0 && opt.stripe_height == 0) { opt.bmp_height = DEFAULT_HEIGHT; } if (opt.bmp_height == 0 && opt.stripes == 0 && opt.stripe_height != 0) { opt.stripes = DEFAULT_STRIPES; } // If two options set, then calculate the remaining one. if (opt.bmp_height == 0 && opt.stripes != 0 && opt.stripe_height != 0) { opt.bmp_height = opt.stripes * opt.stripe_height; } if (opt.bmp_height != 0 && opt.stripes == 0 && opt.stripe_height != 0) { opt.stripes = opt.bmp_height / opt.stripe_height; } if (opt.bmp_height != 0 && opt.stripes != 0 && opt.stripe_height == 0) { opt.stripe_height = opt.bmp_height / opt.stripes; } // Sanity checks. if (opt.stripes <= 0) { fprintf(stderr, "Invalid number of stripes: %d\n", opt.stripes); exit(1); } if (opt.stripe_height <= 0) { fprintf(stderr, "Invalid stripe height: %d\n", opt.stripe_height); exit(1); } if (opt.xscale <= 0) { fprintf(stderr, "Invalid x-scale: %d\n", opt.xscale); exit(1); } if (opt.bmp_height < opt.stripe_height * opt.stripes) { fprintf(stderr, "Invalid bitmap height vs. stripes height\n"); exit(1); } if (argc > 1) { if (opt.output_path == NULL) { opt.output_path = argv[1]; } else { fprintf(stderr, "Output filename given twice\n"); exit(1); } } if (opt.output_path == NULL) { fprintf(stderr, "Output filename missing\n"); exit(1); } return argc; } // Poor man's makefile... #include "bmp1_lib.c"