/* file: dbtool.c G. Moody 3 December 1988 Last revised: 16 July 1991 ECG database browser for SunView Copyright (C) Massachusetts Institute of Technology 1991. All rights reserved. */ #include #include #include #include #include #include #include /* Miscellaneous constants and macros. */ #ifndef DPMM #define DPMM 4.0 /* screen resolution (pixels per millimeter) */ #endif #define mm(A) ((int)((A)*DPMM)) /* convert millimeters to pixels */ #define SCR_WIDTH mm(250) /* display width in pixels */ #define SCR_HEIGHT mm(125) /* display height in pixels */ #define MAX_DISPLAY_LISTS 8 /* cache size */ /* Color map definitions. The color map uses 16 slots, but only 8 distinct colors (not all of which are used by the program). When the grid is visible, GRID_GREY has a value which is distinct from WHITE; at other times it is identical to WHITE (so that the grid is hidden). The grid is protected by disabling bitplane 1 during erasure of the pixwin and subsequent writing. Since each of the other colors occupies two consecutive slots in the map, the effect of setting a pixel to another color is the same whether or not it was originally GRID_GREY, and its former state (GRID_GREY or WHITE) is restored when all but bitplane 0 are cleared. Several functions assist management of these features: protect_grid() is used to disable bitplane 1. The pixwin may then be erased or written to without disturbing the grid. unprotect_grid() enables bitplane 1. This allows the grid to be redrawn when the display scales change. hide_grid() changes the color map so that the grid becomes invisible. unhide_grid() makes the grid visible again. */ #define CMS_NAME "dbtool" #define CMS_SIZE 16 #define WHITE 0 #define GRID_GREY 1 #define LBLUE 2 #define DBLUE 4 #define GREEN 6 #define RED 8 #define YELLOW 10 #define GREY 12 #define BLACK 14 /* Mask bits for find_annot(). */ #define M_ANNTYP 1 #define M_SUBTYP 2 #define M_CHAN 4 #define M_NUM 8 #define M_AUX 16 #define M_MAP2 32 static void disp_proc(), quit_proc(), dismiss_mode(), show_mode(), show_log(), log_select(), dismiss_log(), repaint(); Window frame, panel; Canvas canvas; Panel_item annot_item, find_item, record_item, time_item, time2_item; Pixwin *pw; /* The mpr_static macro (defined in /usr/include/pixrect/memvar.h) cannot be compiled properly using the rules of ANSI C. For this reason, the code which defines the icon for `dbtool' is not compiled by such compilers. */ #ifndef __STDC__ Icon icon; static short icon_image[] = { /* Format_version=1, Width=64, Height=64, Depth=1, Valid_bits_per_item=16 */ 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, 0x0200,0x0200,0x0200,0x0200,0x0000,0x0000,0x0000,0x0000, 0x0200,0x0208,0x0200,0x0200,0x0000,0x0014,0x0000,0x0000, 0x0200,0x0214,0x0200,0x0200,0x0000,0x0012,0x0000,0x0000, 0x0200,0x0212,0x0200,0x0200,0x5555,0x5577,0x5555,0x5554, 0x0200,0x0222,0x0200,0x0200,0x0000,0x0022,0x0000,0x0000, 0x0200,0x0222,0x0200,0x0200,0x0000,0x0022,0x0000,0x0000, 0x0200,0x0222,0x0200,0x0200,0x0000,0x0022,0x0000,0x0000, 0x0200,0x0222,0x0200,0x0200,0x0000,0x0022,0x0000,0x0000, 0x0200,0x0222,0x0200,0x0200,0x0000,0x0022,0x0000,0x0000, 0x0200,0x0222,0x0200,0x0200,0x0000,0x0022,0x0000,0x0000, 0x0200,0x0222,0x0200,0x0200,0x0000,0x0022,0x0000,0x0000, 0x0200,0x0222,0x0200,0x0200,0x5555,0x5577,0x5555,0x5554, 0x0200,0x0221,0x0200,0x4200,0x0000,0x0041,0x0003,0xA000, 0x0200,0x0241,0x0202,0x1A00,0x0000,0x0041,0x0004,0x0400, 0x0202,0x0241,0x0204,0x0200,0x0005,0x8041,0x0008,0x0100, 0x020C,0x4241,0x0208,0x0280,0x0010,0x2041,0x0010,0x0040, 0x7A10,0x2241,0x0210,0x0240,0x07E0,0x1841,0x0010,0x0020, 0x0200,0x0741,0x0220,0x0218,0x0000,0x0141,0x0020,0x0004, 0x0200,0x02C1,0x0220,0x0200,0x0000,0x00C1,0x0020,0x0000, 0x0200,0x02C1,0x0240,0x0200,0x5555,0x55D5,0x5555,0x5554, 0x0200,0x0241,0x0280,0x0200,0x0000,0x0001,0x0100,0x0000, 0x0200,0x0201,0x0600,0x0200,0x0000,0x0000,0x9800,0x0000, 0x0200,0x0200,0x5200,0x0200,0x0000,0x0000,0x5000,0x0000, 0x0200,0x0200,0x3200,0x0200,0x0000,0x0000,0x1000,0x0000, 0x0200,0x0200,0x0200,0x0200,0x0000,0x0000,0x0000,0x0000, 0x0F8E,0x1C00,0x0200,0x0200,0x0811,0x2200,0x0000,0x0000, 0x0811,0x2000,0x0200,0x0200,0x0810,0x2000,0x0000,0x0000, 0x0F10,0x2600,0x0200,0x0200,0x0810,0x2255,0x5555,0x5554, 0x0811,0x2200,0x0200,0x0200,0x0811,0x2600,0x0000,0x0000, 0x0F8E,0x1A00,0x0200,0x0200,0x0000,0x0000,0x0000,0x0000, 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000 }; mpr_static(ecg_icon, 64, 64, 1, icon_image); #endif u_char red[CMS_SIZE], green[CMS_SIZE], blue[CMS_SIZE]; int an; char annotator[80], record[20]; struct anninfo af[MAXANN]; main(argc, argv) int argc; char *argv[]; { int i, planes; if (argc == 2) strncpy(record, argv[1], 19); else { for (i = 1; i < argc; i++) { if (*argv[i] == '-') switch (*(argv[i] + 1)) { case 'a': /* annotator name */ if (++i >= argc) { fprintf(stderr, "%s: annotator name must follow -a\n", argv[0]); exit(1); } strncpy(annotator, argv[i], 79); break; case 'r': /* record name */ if (++i >= argc) { fprintf(stderr, "%s: record name must follow -r\n", argv[0]); exit(1); } strncpy(record, argv[i], 19); break; } } } if (record[0] == '\0') { fprintf(stderr, "usage: %s -r record-name [-a annotator]\n", argv[0]); exit(1); } #ifndef __STDC__ icon = icon_create(ICON_IMAGE, &ecg_icon, 0); frame = window_create(NULL, FRAME, FRAME_ARGS, argc, argv, FRAME_LABEL, argv[0], FRAME_ICON, icon, WIN_WIDTH, SCR_WIDTH+10, WIN_HEIGHT, SCR_HEIGHT+110, WIN_X, 100, WIN_Y, 140, WIN_ERROR_MSG, "Can't create window.", 0); #else frame = window_create(NULL, FRAME, FRAME_ARGS, argc, argv, FRAME_LABEL, argv[0], WIN_WIDTH, SCR_WIDTH+10, WIN_HEIGHT, SCR_HEIGHT+110, WIN_X, 100, WIN_Y, 140, WIN_ERROR_MSG, "Can't create window.", 0); #endif panel = window_create(frame, PANEL, WIN_HEIGHT, 80, 0); create_panel_items(); create_mode_popup(); create_log_popup(); canvas = window_create(frame, CANVAS, CANVAS_AUTO_SHRINK, FALSE, CANVAS_AUTO_EXPAND, FALSE, /* CANVAS_RETAINED, FALSE, CANVAS_REPAINT_PROC, repaint, */ CANVAS_WIDTH, SCR_WIDTH, CANVAS_HEIGHT, SCR_HEIGHT, 0); red[WHITE] = 255; green[WHITE] = 255; blue[WHITE] = 255; red[WHITE+1] = 255; green[WHITE+1] = 255; blue[WHITE+1] = 255; red[LBLUE] = 127; green[LBLUE] = 0; blue[LBLUE] = 255; red[LBLUE+1] = 127; green[LBLUE+1] = 0; blue[LBLUE+1] = 255; red[DBLUE] = 0; green[DBLUE] = 0; blue[DBLUE] = 255; red[DBLUE+1] = 0; green[DBLUE+1] = 0; blue[DBLUE+1] = 255; red[GREEN] = 0; green[GREEN] = 128; blue[GREEN] = 0; red[GREEN+1] = 0; green[GREEN+1] = 128; blue[GREEN+1] = 0; red[RED] = 255; green[RED] = 0; blue[RED] = 0; red[RED+1] = 255; green[RED+1] = 0; blue[RED+1] = 0; red[YELLOW] = 255; green[YELLOW] = 255; blue[YELLOW] = 0; red[YELLOW+1] = 255;green[YELLOW+1] = 255; blue[YELLOW+1] = 0; red[GREY] = 200; green[GREY] = 200; blue[GREY] = 200; red[GREY+1] = 200; green[GREY+1] = 200; blue[GREY+1] = 200; red[BLACK] = 0; green[BLACK] = 0; blue[BLACK] = 0; red[BLACK+1] = 0; green[BLACK+1] = 0; blue[BLACK+1] = 0; pw = (Pixwin *)window_get(canvas, WIN_PIXWIN); pw_setcmsname(pw, CMS_NAME); pw_putcolormap(pw, 0, CMS_SIZE, red, green, blue); pw = (Pixwin *)canvas_pixwin(canvas); pw_setcmsname(pw, CMS_NAME); pw_putcolormap(pw, 0, CMS_SIZE, red, green, blue); window_set(canvas, CANVAS_RETAINED, TRUE, 0); if (record_init(record)) { if (*annotator) { af[0].name = annotator; af[0].stat = READ; an = 1; annot_init(); } do_disp(); } window_main_loop(frame); exit(0); } /* Repaint() is invoked by the window manager to restore the canvas if another window has damaged it. */ static void repaint(canvas, pw, repaint_area) Canvas canvas; Pixwin *pw; Rectlist *repaint_area; { do_disp(); } /* Set up control/status panel at top of frame. */ create_panel_items() { record_item = panel_create_item(panel, PANEL_TEXT, PANEL_LABEL_STRING, "Record: ", PANEL_VALUE_DISPLAY_LENGTH, 15, PANEL_NOTIFY_PROC, disp_proc, PANEL_VALUE, record, 0); annot_item = panel_create_item(panel, PANEL_TEXT, PANEL_LABEL_STRING, "Annotator: ", PANEL_VALUE_DISPLAY_LENGTH, 15, PANEL_NOTIFY_PROC, disp_proc, PANEL_VALUE, annotator, 0); time_item = panel_create_item(panel, PANEL_TEXT, PANEL_LABEL_STRING, "Start time: ", PANEL_VALUE_DISPLAY_LENGTH, 15, PANEL_NOTIFY_PROC, disp_proc, PANEL_VALUE, "0", 0); time2_item = panel_create_item(panel, PANEL_TEXT, PANEL_LABEL_STRING, "End time: ", PANEL_VALUE_DISPLAY_LENGTH, 15, PANEL_NOTIFY_PROC, disp_proc, PANEL_CLIENT_DATA, (caddr_t) ':', PANEL_VALUE, "10", 0); find_item = panel_create_item(panel, PANEL_TEXT, PANEL_LABEL_STRING, "Find: ", PANEL_VALUE_DISPLAY_LENGTH, 6, PANEL_NOTIFY_PROC, disp_proc, PANEL_CLIENT_DATA, (caddr_t) ']', 0); panel_create_item(panel, PANEL_BUTTON, PANEL_LABEL_IMAGE, panel_button_image(panel, "<<", 5, 0), PANEL_NOTIFY_PROC, disp_proc, PANEL_CLIENT_DATA, (caddr_t) '<', 0); panel_create_item(panel, PANEL_BUTTON, PANEL_LABEL_IMAGE, panel_button_image(panel, "<", 5, 0), PANEL_NOTIFY_PROC, disp_proc, PANEL_CLIENT_DATA, (caddr_t) '(', 0); panel_create_item(panel, PANEL_BUTTON, PANEL_LABEL_IMAGE, panel_button_image(panel, ">", 5, 0), PANEL_NOTIFY_PROC, disp_proc, PANEL_CLIENT_DATA, (caddr_t) ')', 0); panel_create_item(panel, PANEL_BUTTON, PANEL_LABEL_IMAGE, panel_button_image(panel, ">>", 5, 0), PANEL_NOTIFY_PROC, disp_proc, PANEL_CLIENT_DATA, (caddr_t) '>', 0); panel_create_item(panel, PANEL_BUTTON, PANEL_LABEL_IMAGE, panel_button_image(panel, "Set modes", 5, 0), PANEL_NOTIFY_PROC, show_mode, 0); panel_create_item(panel, PANEL_BUTTON, PANEL_LABEL_IMAGE, panel_button_image(panel, "Log", 5, 0), PANEL_NOTIFY_PROC, show_log, 0); panel_create_item(panel, PANEL_BUTTON, PANEL_LABEL_IMAGE, panel_button_image(panel, "Quit", 5, 0), PANEL_NOTIFY_PROC, quit_proc, 0); } Panel_item redraw_item; Window mode_frame, mode_panel; /* Set up popup window for adjusting display modes. */ create_mode_popup() { mode_frame = window_create(frame, FRAME, 0); mode_panel = window_create(mode_frame, PANEL, 0); panel_create_item(mode_panel, PANEL_MESSAGE, PANEL_ITEM_X, ATTR_COL(20), PANEL_ITEM_Y, ATTR_ROW(0), PANEL_LABEL_STRING, "Display mode", PANEL_CLIENT_DATA, " ", 0); panel_create_item(mode_panel, PANEL_CYCLE, PANEL_ITEM_X, ATTR_COL(0), PANEL_ITEM_Y, ATTR_ROW(1), PANEL_DISPLAY_LEVEL, PANEL_CURRENT, PANEL_LABEL_STRING, "Grid: ", PANEL_CHOICE_STRINGS, "None", "0.5 mV x 0.2 s", "0.2 s", "0.5 mV", 0, PANEL_CLIENT_DATA, "o+|-", 0); panel_create_item(mode_panel, PANEL_CYCLE, PANEL_ITEM_X, ATTR_COL(0), PANEL_ITEM_Y, ATTR_ROW(2), PANEL_DISPLAY_LEVEL, PANEL_CURRENT, PANEL_LABEL_STRING, "Gain: ", PANEL_CHOICE_STRINGS, "10 mm/mV", "20 mm/mV", "40 mm/mV", "2.5 mm/mV", "5 mm/mV", 0, PANEL_CLIENT_DATA, "cdeab", 0); panel_create_item(mode_panel, PANEL_CYCLE, PANEL_ITEM_X, ATTR_COL(0), PANEL_ITEM_Y, ATTR_ROW(3), PANEL_DISPLAY_LEVEL, PANEL_CURRENT, PANEL_LABEL_STRING, "Time scale (duration): ", PANEL_CHOICE_STRINGS, "25 mm/sec (10 sec)", "50 mm/sec (5 sec)", "125 mm/sec (2 sec)", "250 mm/sec (1 sec)", "250 mm/min (1 min)", "500 mm/min (30 sec)", "12.5 mm/sec (20 sec)", 0, PANEL_CLIENT_DATA, "DEFGABC", 0); panel_create_item(mode_panel, PANEL_CYCLE, PANEL_ITEM_X, ATTR_COL(0), PANEL_ITEM_Y, ATTR_ROW(4), PANEL_DISPLAY_LEVEL, PANEL_CURRENT, PANEL_LABEL_STRING, "Show annotation subtype: ", PANEL_CHOICE_STRINGS, "No", "Yes", 0, PANEL_CLIENT_DATA, "12", 0); panel_create_item(mode_panel, PANEL_CYCLE, PANEL_ITEM_X, ATTR_COL(0), PANEL_ITEM_Y, ATTR_ROW(5), PANEL_DISPLAY_LEVEL, PANEL_CURRENT, PANEL_LABEL_STRING, "Show annotation chan: ", PANEL_CHOICE_STRINGS, "No", "Yes", 0, PANEL_CLIENT_DATA, "34", 0); panel_create_item(mode_panel, PANEL_CYCLE, PANEL_ITEM_X, ATTR_COL(0), PANEL_ITEM_Y, ATTR_ROW(6), PANEL_DISPLAY_LEVEL, PANEL_CURRENT, PANEL_LABEL_STRING, "Show annotation num: ", PANEL_CHOICE_STRINGS, "No", "Yes", 0, PANEL_CLIENT_DATA, "56", 0); redraw_item = panel_create_item(mode_panel, PANEL_BUTTON, PANEL_ITEM_X, ATTR_COL(0), PANEL_ITEM_Y, ATTR_ROW(7), PANEL_LABEL_IMAGE, panel_button_image(panel, "Redraw", 0, 0), PANEL_NOTIFY_PROC, disp_proc, PANEL_CLIENT_DATA, (caddr_t) '.', 0); window_fit(mode_panel); window_fit(mode_frame); } #define screen_mv (SCR_HEIGHT/screen_height_in_mv) #define screen_second (SCR_WIDTH/screen_width_in_seconds) double screen_height_in_mv = SCR_HEIGHT/mm(10); double screen_width_in_seconds = 10.; int ghflag, gvflag, show_subtype, show_chan, show_num, visible; int mode_popup_active; /* Make the display mode popup window appear. */ static void show_mode() { protect_grid(); window_set(mode_frame, WIN_SHOW, TRUE, 0); mode_popup_active = 1; } /* Effect any mode changes which were selected and make the popup disappear. */ static void dismiss_mode() { char *client_data; int index; Panel_item item; double osh = screen_height_in_mv, osw = screen_width_in_seconds; window_set(mode_frame, WIN_SHOW, FALSE, 0); unprotect_grid(); mode_popup_active = 0; panel_each_item(mode_panel, item) if (item != redraw_item) { client_data = panel_get(item, PANEL_CLIENT_DATA, 0); index = (int)panel_get_value(item); switch (client_data[index]) { case 'a': screen_height_in_mv = SCR_HEIGHT/mm(2.5); break; case 'b': screen_height_in_mv = SCR_HEIGHT/mm(5); break; case 'c': screen_height_in_mv = SCR_HEIGHT/mm(10); break; case 'd': screen_height_in_mv = SCR_HEIGHT/mm(20); break; case 'e': screen_height_in_mv = SCR_HEIGHT/mm(40); break; case 'A': screen_width_in_seconds = 60.; break; case 'B': screen_width_in_seconds = 30.; break; case 'C': screen_width_in_seconds = 20.; break; case 'D': screen_width_in_seconds = 10.; break; case 'E': screen_width_in_seconds = 5.; break; case 'F': screen_width_in_seconds = 2.; break; case 'G': screen_width_in_seconds = 1.; break; case 'o': ghflag = gvflag = visible = 0; break; case '+': ghflag = gvflag = visible = 1; break; case '|': ghflag = 0; gvflag = visible = 1; break; case '-': ghflag = visible = 1; gvflag = 0; break; case '1': show_subtype = 0; break; case '2': show_subtype = 1; break; case '3': show_chan = 0; break; case '4': show_chan = 1; break; case '5': show_num = 0; break; case '6': show_num = 1; break; } } panel_end_each if (osh != screen_height_in_mv || osw != screen_width_in_seconds) calibrate(); } char log_desc[80], log_file_name[80]; Panel_item log_name_item, log_desc_item; FILE *lfile; Panel_item log_done_item; Window log_frame, log_panel; /* Set up popup window for strip logging. */ create_log_popup() { log_frame = window_create(frame, FRAME, 0); log_panel = window_create(log_frame, PANEL, 0); panel_create_item(log_panel, PANEL_MESSAGE, PANEL_ITEM_X, ATTR_COL(20), PANEL_ITEM_Y, ATTR_ROW(0), PANEL_LABEL_STRING, "Add strip to log", PANEL_CLIENT_DATA, " ", 0); log_name_item = panel_create_item(log_panel, PANEL_TEXT, PANEL_ITEM_X, ATTR_COL(0), PANEL_ITEM_Y, ATTR_ROW(1), PANEL_DISPLAY_LEVEL, PANEL_CURRENT, PANEL_LABEL_STRING, "Log file name: ", 0); log_desc_item = panel_create_item(log_panel, PANEL_TEXT, PANEL_ITEM_X, ATTR_COL(0), PANEL_ITEM_Y, ATTR_ROW(2), PANEL_DISPLAY_LEVEL, PANEL_CURRENT, PANEL_LABEL_STRING, "Description: ", PANEL_VALUE_DISPLAY_LENGTH, 60, 0); panel_create_item(log_panel, PANEL_BUTTON, PANEL_ITEM_X, ATTR_COL(0), PANEL_ITEM_Y, ATTR_ROW(3), PANEL_LABEL_IMAGE, panel_button_image(log_panel, "Save", 0, 0), PANEL_NOTIFY_PROC, log_select, PANEL_CLIENT_DATA, (caddr_t) 's', 0); panel_create_item(log_panel, PANEL_BUTTON, PANEL_ITEM_Y, ATTR_ROW(3), PANEL_LABEL_IMAGE, panel_button_image(log_panel, "Cancel", 0, 0), PANEL_NOTIFY_PROC, log_select, PANEL_CLIENT_DATA, (caddr_t) 'c', 0); window_fit(log_panel); window_fit(log_frame); } int log_popup_active; /* Make the log popup window appear. */ static void show_log() { protect_grid(); window_set(log_frame, WIN_SHOW, TRUE, 0); log_popup_active = 1; } int nsamp; long display_start_time; /* Handle selections in the log popup window. */ static void log_select(item, event) Panel_item item; Event *event; { char *ts; switch ((int)panel_get(item, PANEL_CLIENT_DATA)) { case 'c': /* dismiss log popup without logging anything */ dismiss_log(); break; case 's': /* save strip data in log file */ if (strncmp(log_file_name, panel_get_value(log_name_item), 79)) { strncpy(log_file_name, panel_get_value(log_name_item), 79); if (lfile) { fclose(lfile); lfile = NULL; } if ((lfile = fopen(log_file_name, "a")) == NULL) { alert_prompt((Frame)log_frame, (Event *)NULL, ALERT_MESSAGE_STRINGS, "Can't open file:", log_file_name, 0, ALERT_BUTTON_YES, "Continue", 0); return; } } if (lfile == NULL) { alert_prompt((Frame)log_frame, (Event *)NULL, ALERT_MESSAGE_STRINGS, "You must choose a name for the log file", "before logging anything.", 0, ALERT_BUTTON_YES, "Continue", 0); return; } fprintf(lfile, "%s ", record); ts = timstr(display_start_time); while (*ts == ' ') ts++; fprintf(lfile, "%s-", ts); ts = timstr(display_start_time+nsamp); while (*ts == ' ') ts++; fprintf(lfile, "%s %s\n", ts, panel_get_value(log_desc_item)); dismiss_log(); break; } } /* Make the log popup window disappear. */ static void dismiss_log() { window_set(log_frame, WIN_SHOW, FALSE, 0); log_popup_active = 0; unprotect_grid(); } double freq; /* Handle a display request. */ static void disp_proc(item, event) Panel_item item; Event *event; { long find_annot(); /* Dismiss any popups which are currently active. */ if (mode_popup_active) dismiss_mode(); if (log_popup_active) dismiss_log(); /* If a new record has been selected, re-initialize. */ if (strcmp(record, panel_get_value(record_item))) { dbquit(); *annotator = '\0'; if (!record_init(panel_get_value(record_item))) return; } /* If a new annotator has been selected, re-initialize. */ if (strcmp(annotator, panel_get_value(annot_item))) { strcpy(annotator, panel_get_value(annot_item)); if (*annotator) { af[0].name = annotator; af[0].stat = READ; an = 1; } else an = 0; annot_init(); } /* Find out which button was pushed, and act on it. */ switch ((int)panel_get(item, PANEL_CLIENT_DATA)) { default: case '.': /* Start at time specified on panel. */ display_start_time = strtim(panel_get_value(time_item)); if (display_start_time < 0L) display_start_time = -display_start_time; break; case ':': /* End at time specified on panel. */ display_start_time = strtim(panel_get_value(time2_item)); if (display_start_time < 0L) display_start_time = -display_start_time; if ((display_start_time -= nsamp) < 0) display_start_time = 0; break; case ']': /* Find next occurrence of specified annotation. */ if (*annotator) { char *fp = panel_get_value(find_item); static char auxstr[8]; int mask, target; long t; struct ann template; if (target = strann(fp)) { template.anntyp = target; mask = M_ANNTYP; } else if (('0' <= *fp && *fp <= '9') || strcmp(fp, "-1") == 0) { template.anntyp = NOISE; template.subtyp = atoi(fp); mask = M_ANNTYP | M_SUBTYP; } else if (strcmp(fp, "*n") == 0) { template.anntyp = NORMAL; mask = M_MAP2; } else if (strcmp(fp, "*s") == 0) { template.anntyp = SVPB; mask = M_MAP2; } else if (strcmp(fp, "*v") == 0) { template.anntyp = PVC; mask = M_MAP2; } else if (strcmp(fp, "*") == 0 || *fp == '\0') mask = 0; /* match next annotation of any type */ else { strncpy(auxstr+1, fp, 6); auxstr[0] = strlen(auxstr+1); template.aux = auxstr; mask = M_AUX; } if ((t = find_annot(&template, mask)) < 0L) alert_prompt((Frame)frame, (Event *)NULL, ALERT_MESSAGE_STRINGS, "No match found!", 0, ALERT_BUTTON_YES, "Continue", 0); else display_start_time = strtim(timstr(t-(long)((nsamp-freq)/2))); } break; case '<': /* Go backwards one frame. */ if ((display_start_time -= nsamp) < 0) display_start_time = 0; break; case '(': /* Go backwards one-half frame. */ if ((display_start_time -= nsamp/2) < 0) display_start_time = 0; break; case ')': /* Go forwards one-half frame. */ display_start_time += nsamp/2; break; case '>': /* Go forwards one frame. */ display_start_time += nsamp; break; } /* Erase the drawing region. */ protect_grid(); pw_writebackground(pw, 0, 0, SCR_WIDTH, SCR_HEIGHT, PIX_SRC | PIX_COLOR(WHITE)); unprotect_grid(); /* Display the selected data. */ do_disp(); } /* Exit from the tool. */ static void quit_proc(/* ARGS UNUSED */) { /* window_set(frame, FRAME_NO_CONFIRM, TRUE, 0); */ window_destroy(frame); if (lfile) fclose(lfile); } char titlestring[40]; double tscale, vscale[MAXSIG]; int nsig, base[MAXSIG], abase; struct siginfo df[MAXSIG]; /* Open up a new ECG record. */ record_init(s) char *s; { int i; /* Suppress error messages from the DB library. */ dbquiet(); /* Save the name of the new record in local storage. */ strcpy(record, s); /* Open as many channels as possible; quit unless at least one can be read. */ if ((nsig = isigopen(record, df, MAXSIG)) < 1) { protect_grid(); sprintf(titlestring, "Record %s is unavailable\n", record); alert_prompt((Frame)frame, (Event *)NULL, ALERT_MESSAGE_STRINGS, titlestring, 0, ALERT_BUTTON_YES, "Continue", 0); unprotect_grid(); return (0); } /* By convention, a zero or negative sampling frequency is interpreted as if the value were DEFFREQ (from db.h); the units are samples per second per signal. */ if ((freq = sampfreq(NULL)) <= 0.) freq = DEFFREQ; /* Replace any unspecified signal gains with the default gain from db.h; the units of gain are ADC units per mV. Also calculate the base levels (in display units) for each signal, and for annotation display. */ for (i = 0; i < nsig; i++) { if (df[i].gain == 0) df[i].gain = DEFGAIN; base[i] = SCR_HEIGHT*(2*i+1.)/(2.*nsig); } abase = (nsig > 1) ? (base[nsig/2] + base[nsig/2 -1])/2 : SCR_HEIGHT*4/5; vscale[0] = 0.; /* force clear_cache() -- see calibrate() */ calibrate(); return (1); } /* Calibrate() sets scales for the display. Ordinate (amplitude) scaling is determined for each channel from the gain recorded in the header file, but calibrate() scales the abscissa (time) for all channels based on the sampling frequency for channel 0. */ calibrate() { int i, j; static char *cfname, *getenv(); struct calinfo ci; /* Vscale is a multiplicative scale factor which converts sample values to pixwin ordinates. Since pixwin ordinates are inverted, vscale includes a factor of -1. */ if (vscale[0] != - screen_mv / df[0].gain) { clear_cache(); /* If specified, read the calibration file to get standard scales. */ if (cfname == (char *)NULL && (cfname = getenv("DBCAL"))) calopen(cfname); for (i = 0; i < nsig; i++) { vscale[i] = - screen_mv / df[i].gain; if (getcal(df[i].desc, df[i].units, &ci) == 0 && ci.scale != 0.0) vscale[i] /= ci.scale; } } /* Tscale is a multiplicative scale factor which converts sample intervals to pixwin abscissas. */ nsamp = screen_width_in_seconds * freq; tscale = screen_second / freq; } /* Display lists A display list contains all information needed to draw a screenful of ECG. For each of the nsig signals, a display list contains a list of the (x, y) pixel coordinate pairs which specify the vertices of the polyline which represents the signal. A cache of recently-used display lists (up to MAX_DISPLAY_LISTS of them) is maintained as a singly-linked list; the first display list is pointed to by first_list. */ struct tr { struct pr_pos vertex[SCR_WIDTH]; }; struct display_list { struct display_list *next; /* link to next display list */ long start; /* time of first sample */ int nsig; /* number of signals */ int npoints; /* number of (input) points per signal */ int ndpts; /* number of (output) points per signal */ int dy[MAXSIG]; /* pixwin baselines for each signal */ struct tr *trace[MAXSIG]; /* vertex list pointers for each signal */ } *find_display_list(); int annotations; /* non-zero if there are annotations to be shown */ /* Do_disp() executes a display request. The display will show nsamp samples of nsig signals, starting at display_start_time. Note that do_disp reads ahead to the next screen after displaying the current screen in order to reduce the time needed to service the (most likely) next request. */ do_disp() { int c, i; /* Make sure that the requested time is reasonable. */ if (display_start_time < 0) display_start_time = 0; /* Update the panel items which indicate the start and end times. */ panel_set_value(time_item, timstr(display_start_time)); panel_set_value(time2_item, timstr(display_start_time + nsamp)); /* Cache a display list for the screen before the requested screen. */ if (display_start_time >= nsamp) find_display_list(display_start_time - nsamp); /* Show the grid if requested. */ show_grid(); /* Get a display list for the requested screen, and show it. */ show_display_list(find_display_list(display_start_time)); if (annotations) show_annotations(display_start_time, nsamp); /* Cache a display list for the screen after the requested screen. */ find_display_list(display_start_time + nsamp); } int nlists; struct display_list *first_list; /* Get_display_list() obtains storage for display lists from the heap (via malloc) or by recycling a display list in the cache. Since the screen duration (nsamp) does not change frequently, get_display_list() calculates the pixel abscissas when a new display list is obtained from the heap, and recalculates them only when nsamp has been changed. */ struct display_list *get_display_list() { int i, x; static int max_nlists = MAX_DISPLAY_LISTS; struct display_list *lp = NULL, *lpl; if (nsig > MAXSIG) return (NULL); if (nlists < max_nlists) { lp = (struct display_list *)calloc(1, sizeof(struct display_list)); if (lp) nlists++; } if (lp == NULL) switch (nlists) { case 0: return (NULL); case 1: lp = first_list; break; default: lpl = first_list; lp = lpl->next; while (lp->next) { lpl = lp; lp = lp->next; } lpl->next = NULL; break; } for (i = 0; i < nsig; i++) { if (lp->trace[i] == NULL) { if ((lp->trace[i]=(struct tr *)malloc(sizeof(struct tr)))==NULL) { while (--i >= 0) free(lp->trace[i]); free(lp); max_nlists = --nlists; return (get_display_list()); } lp->npoints = 0; /* force calculation of abscissas */ } if (nsamp > SCR_WIDTH) { if (lp->npoints <= SCR_WIDTH) for (x = 0; x < SCR_WIDTH; x++) lp->trace[i]->vertex[x].x = x; } else if (nsamp != lp->npoints) for (x = 0; x < nsamp; x++) lp->trace[i]->vertex[x].x = x*tscale; } lp->next = first_list; lp->nsig = nsig; lp->npoints = nsamp; return (first_list = lp); } /* Find_display_list() obtains a display list beginning at the sample number specified by its argument. If such a list (with the correct duration) is found in the cache, it can be returned immediately. Otherwise, the function reads the requested segment and determines the pixel ordinates of the vertices of the polylines for each signal. */ struct display_list *find_display_list(fdl_time) long fdl_time; { int c, i, j, x, x0, y, ymax, ymin; int v[MAXSIG], v0[MAXSIG], vmax[MAXSIG], vmin[MAXSIG]; struct display_list *lp; struct tr *tp; if (fdl_time < 0L) fdl_time = -fdl_time; /* If the requested display list is in the cache, return it at once. */ for (lp = first_list; lp; lp = lp->next) if (lp->start == fdl_time && lp->npoints == nsamp) return (lp); /* Give up if a display list can't be allocated, or if we can't skip to the requested segment, or if we can't read at least one sample. */ if ((lp = get_display_list()) == NULL || (fdl_time != strtim("i") && isigsettime(fdl_time) < 0) || getvec(v0) < 0) return (NULL); /* Record the starting time. */ lp->start = fdl_time; /* Set the starting point for each trace. */ for (c = 0; c < nsig; c++) { vmin[c] = vmax[c] = v0[c]; lp->trace[c]->vertex[0].y = v0[c]*vscale[c]; } /* If there are more than SCR_WIDTH samples to be shown, compress the data. */ if (nsamp > SCR_WIDTH) { for (i = 1, x0 = 0; i < nsamp && getvec(v) > 0; i++) { if ((x = i*tscale) > x0) { x0 = x; for (c = 0; c < nsig; c++) { if (v[c] > vmax[c]) vmax[c] = v[c]; else if (v[c] < vmin[c]) vmin[c] = v[c]; if (vmax[c] - v0[c] > v0[c] - vmin[c]) v0[c] = vmin[c] = vmax[c]; else v0[c] = vmax[c] = vmin[c]; lp->trace[c]->vertex[x0].y = v0[c]*vscale[c]; } } else { for (c = 0; c < nsig; c++) { if (v[c] > vmax[c]) vmax[c] = v[c]; else if (v[c] < vmin[c]) vmin[c] = v[c]; } } } i = x0+1; } /* If there are SCR_WIDTH or fewer samples to be shown, no compression is necessary. */ else for (i = 1; i < nsamp && getvec(v) > 0; i++) for (c = 0; c < nsig; c++) lp->trace[c]->vertex[i].y = v[c]*vscale[c]; /* Record the number of displayed points. This may be less than expected at the end of the record. */ lp->ndpts = i; /* Set the y-offset so that the trace will be vertically centered about the nominal baseline. */ for (c = 0; c < nsig; c++) { tp = lp->trace[c]; ymax = ymin = tp->vertex[0].y; for (j = i-1; j > 0; j--) { y = tp->vertex[j].y; if (y > ymax) ymax = y; else if (y < ymin) ymin = y; } lp->dy[c] = base[c] - (ymax + ymin)/2; } return (lp); } /* Clear_cache() marks all of the display lists in the cache as invalid. This function should be executed whenever the gain (vscale) or record is changed. In principle, additional fields specifying the record name and the gain could be added to the display list structure, and to the cache-searching code in find_display_list(). */ clear_cache() { struct display_list *lp; for (lp = first_list; lp; lp = lp->next) lp->start = -1; } /* Show_display_list() plots the display list pointed to by its argument. */ show_display_list(lp) struct display_list *lp; { int i; if (!lp) return; protect_grid(); for (i = 0; i < lp->nsig && lp->trace[i]; i++) pw_polyline(pw, 0, lp->dy[i], lp->ndpts, lp->trace[i]->vertex, POLY_DONTCLOSE, NULL, NULL, PIX_SRC | PIX_COLOR(LBLUE)); unprotect_grid(); } protect_grid() { int planes; pw_getattributes(pw, &planes); planes &= ~1; /* disable plane 1 */ pw_putattributes(pw, &planes); } unprotect_grid() { int planes; pw_getattributes(pw, &planes); planes |= 1; /* enable plane 1 */ pw_putattributes(pw, &planes); } /* Hide_grid() makes the grid invisible by modifying the color map so that the color used for the grid (GRID_GREY) is identical to that used for the background (WHITE). */ hide_grid() { pw_putcolormap(pw, GRID_GREY, 1, &red[WHITE], &green[WHITE], &blue[WHITE]); } /* Unhide_grid() reverses the action of hide_grid to make the grid visible again. */ unhide_grid() { pw_putcolormap(pw, GRID_GREY, 1, &red[GREY], &green[GREY], &blue[GREY]); } /* Show_grid() does what is necessary to display the grid in the requested style. Note that the grid can be made to disappear and reappear by show_grid() without redrawing it, by manipulating the color map. */ show_grid() { int i, x, y; double dx, dy; static int oghf, ogvf, ovis; static double odx, ody; if (!visible) { if (ovis) { hide_grid(); ovis = 0; } return; } /* Calculate the grid spacing in pixels (0.5mV x 0.2 seconds). */ dx = 0.2*screen_second; dy = 0.5*screen_mv; /* The grid must be redrawn if the grid spacing or style has changed. */ if (ghflag != oghf || gvflag != ogvf || (ghflag && dy != ody) || (gvflag && dx != odx)) { pw_batch_on(pw); pw_writebackground(pw, 0, 0, SCR_WIDTH, SCR_HEIGHT, PIX_SRC | PIX_COLOR(WHITE)); /* If horizontal grid lines are enabled, draw them. */ if (ghflag) for (i = 1, y = dy; y < SCR_HEIGHT; i++, y = i*dy) pw_vector(pw, 0, y, SCR_WIDTH, y, PIX_SRC, GRID_GREY); /* If vertical grid lines are enabled, draw them. */ if (gvflag) for (i = 1, x = dx; x < SCR_WIDTH; i++, x = i*dx) pw_vector(pw, x, 0, x, SCR_HEIGHT, PIX_SRC, GRID_GREY); pw_batch_off(pw); oghf = ghflag; ogvf = gvflag; odx = dx; ody = dy; } /* If the grid was hidden, make it visible by changing the color map. */ if (!ovis) unhide_grid(); ovis = visible; } long last_annot_time; struct ann annot; /* Annot_init() (re)opens annotation file(s) for the current record. */ annot_init() { annot.time = last_annot_time = -1; if (an < 1) return (annotations = 0); return (annotations = (annopen(record, af, an) >= 0)); } /* Find_annot() returns the time of the next annotation (i.e., the next one not currently displayed) which matches the template annotation. The mask specifies which fields must match. */ long find_annot(template, mask) struct ann *template; int mask; { if (annotations) do { last_annot_time = annot.time; if ((mask&M_ANNTYP) && template->anntyp != annot.anntyp) continue; if ((mask&M_SUBTYP) && template->subtyp != annot.subtyp) continue; if ((mask&M_CHAN) && template->chan != annot.chan ) continue; if ((mask&M_NUM) && template->num != annot.num ) continue; if ((mask&M_AUX) && (annot.aux == NULL || strncmp(template->aux+1, annot.aux+1, 6))) continue; if ((mask&M_MAP2)&&template->anntyp!=map2(annot.anntyp)) continue; return (annot.time); } while (getann(0, &annot) == 0); return (-1L); } #define YHIGH (SCR_HEIGHT*45/100) #define YMED (SCR_HEIGHT*50/100) #define YLOW (SCR_HEIGHT*55/100) #define YDEL (SCR_HEIGHT*5/100) /* Show_annotations() displays annotations between times left and left+dt at appropriate x-locations in the pixwin. */ show_annotations(left, dt) long left; int dt; { char buf[5], *p; int x, y; long t, right = left + dt; /* Position the annotation reader properly, so that annot contains the first annotation after left. */ if (last_annot_time >= left) annot_init(); while (annot.time < left) { last_annot_time = annot.time; if (getann(0, &annot) < 0) return; } /* Display all of the annotations in the window. */ protect_grid(); pw_batch_on(pw); while (annot.time < right) { x = (int)((annot.time - left)*tscale); switch (annot.anntyp) { case NOISE: y = abase-YDEL; sprintf(buf, "%d", annot.subtyp); p = buf; break; case STCH: case TCH: case NOTE: y = abase-YDEL; p = annot.aux+1; break; case RHYTHM: y = abase+YDEL; p = annot.aux+1; break; default: y = abase; p = annstr(annot.anntyp); break; } pw_ttext(pw, x, y, PIX_SRC | PIX_COLOR(GREEN), 0, p); if (show_subtype) { sprintf(buf, "%d", annot.subtyp); p = buf; y += YDEL; pw_ttext(pw, x, y, PIX_SRC | PIX_COLOR(DBLUE), 0, p); } if (show_chan) { sprintf(buf, "%d", annot.chan); p = buf; y += YDEL; pw_ttext(pw, x, y, PIX_SRC | PIX_COLOR(DBLUE), 0, p); } if (show_num) { sprintf(buf, "%d", annot.num); p = buf; y += YDEL; pw_ttext(pw, x, y, PIX_SRC | PIX_COLOR(DBLUE), 0, p); } last_annot_time = annot.time; if (getann(0, &annot) < 0) break; } pw_batch_off(pw); unprotect_grid(); } show_time(left, dt) long left; int dt; { int tick_interval = (int)freq; long t, right = left + dt; if (screen_width_in_seconds > 10.) tick_interval *= 5; for (t = left; t < right; t += tick_interval); pw_ttext(pw, (int)((t-left)*tscale), SCR_HEIGHT, PIX_SRC | PIX_COLOR(DBLUE), 0, timstr(t)); }