/* file: macview.c July 1992 Last revised: 19 January 2000 View DB records on a Macintosh This program was contributed by Patrick Hamilton. I have renamed it and reformatted it slightly so that PC types can more easily read it -- if you don't like the name or the indentation, blame me, not Pat! As written, this program assumes that the record to be displayed contains two signals, each sampled at 360 Hz (as in the original MIT-BIH Arrhythmia Database). [Jan 2000: I have revised this slightly to make it compatible with the WFDB library, but I don't have a Mac, so I can't test it. You will need to compile the WFDB library and install a copy of "wfdb.h" from the library sources into the same directory as this file. If you are able to compile this program, please let me know what you needed to do, and if it works! Needed: annotation display features (see the sources for MS-DOS 'view', MS-Windows 'wview', UNIX 'wave', or the PostScript-generating 'pschart', all available in the WFDB software package from PhysioNet, for examples of how this can be done).] --GBM (george@mit.edu) */ /****************************************************************************** * The following code allows data from the MIT/BIH arrhythmia database to be * * displayed on a Macintosh screen. Data is displayed sweeping from left to * * right. Additionally, the most recent screen's worth of data can be copied * * to the clip board. * * I developed this code primarily as an exercise in learning to use the Mac * * interface and resources. I used "Macintosh C programming Primer Volume I: * * Inside the Toolbox" as a reference. The code was compiled and run with * * THINK C 5.0. Feel free to copy this code. If you have any questions * * contact me: * * * * Patrick Hamilton * * Lafayette College * * Easton, PA 18042 * * EMAIL: hp#0@LAFAYACS.BITNET * * PHONE: 215-250-5411 * * * ******************************************************************************/ #include #include #include "wfdb.h" /* Normally, this should have been: #include ... but I don't know if it's possible to make a subdirectory of the standard include directory on a Mac, or if "/" would be understood as a directory separator as it is everywhere else. If you know, please tell me! --GBM */ /* Can someone comment these symbols? What do they mean? Are they defined in some *.h file that we can use? */ #define WNE_TRAP_NUM 0x60 #define UNIMPL_TRAP_NUM 0x9F #define OPEN_ITEM 1 #define COPY_ITEM 2 #define QUIT_ITEM 3 #define GO_ITEM 1 #define STOP_ITEM 2 #define BASE_RES_ID 128 #define NIL_POINTER 0L #define MOVE_TO_FRONT -1 #define REMOVE_ALL_EVENTS 0 #define FILE_MENU_ID BASE_RES_ID #define DISP_MENU_ID FILE_MENU_ID+1 #define DISP_LGTH 500 /* number of samples displayed at once */ #define STATUS_LINE 390 /* y-coordinate for "Record NNN" display */ #define CH1_OFFSET 140 /* y-coordinate for baseline of signal 0 */ #define CH2_OFFSET 290 /* y-coordinate for baseline of signal 1 */ WindowPtr ecgDisplayWindow, myDialog; MenuHandle FileMenu, DisplayMenu; Boolean WNEImplemented, Done, Running, FileOpen; EventRecord TheEvent; int DataBuffer[2][DISP_LGTH], DBPtr = 0, CopyChannel = 0; main() { int rval, i; WFDB_Sample v1[2], v2[2]; ToolBoxInit(); WindowInit(); MenuBarInit(); wfdbquiet(); WNEImplemented = (NGetTrapAddress (WNE_TRAP_NUM, ToolTrap) != NGetTrapAddress(UNIMPL_TRAP_NUM, ToolTrap)); Done = Running = FileOpen = FALSE; while (!Done) { HandleEvent(); /* Display data while running. */ for (i = 0; i < 5; ++i) { if (Running == TRUE) { PlotData(0); ShowTime(0); ShowTime(0); rval = getvec(v1); rval = getvec(v2); DataBuffer[0][DBPtr] = (v1[0]+v2[0]) >> 1; DataBuffer[1][DBPtr] = (v2[1]+v2[1]) >> 1; PlotData(1); if (++DBPtr == DISP_LGTH) DBPtr = 0; if (rval < 0) Running = FileOpen = FALSE; } } } } ToolBoxInit() { InitGraf(&thePort); InitFonts(); FlushEvents(everyEvent, REMOVE_ALL_EVENTS); InitWindows(); InitMenus(); TEInit(); InitDialogs(NIL_POINTER); InitCursor(); } WindowInit() { ecgDisplayWindow = GetNewWindow (BASE_RES_ID, NIL_POINTER, (WindowPtr) MOVE_TO_FRONT); ShowWindow(ecgDisplayWindow); SetPort(ecgDisplayWindow); } #define FILE_NUM_FIELD 1 #define OK_BUTTON 3 HandleFileDialog(str) char *str; { WindowPtr myDialog; Boolean dialogDone = FALSE; int itemHit; int itemType; Rect itemRect; Handle itemHandle; char *sptr; myDialog = GetNewDialog(BASE_RES_ID, NIL_POINTER, (WindowPtr) MOVE_TO_FRONT); ShowWindow(myDialog); while (dialogDone == FALSE) { ModalDialog(NIL_POINTER, &itemHit); switch(itemHit) { case OK_BUTTON: dialogDone = TRUE; } } GetDItem(myDialog, FILE_NUM_FIELD, &itemType, &itemHandle, &itemRect); GetIText(itemHandle, str); DisposeWindow(myDialog); PtoCstr(str); } #define FIRST_RADIO 1 #define SECOND_RADIO 2 #define CD_OK 4 #define ON 1 #define OFF 0 HandleCopyDialog() { WindowPtr myDialog; Boolean dialogDone = FALSE; int itemHit; int itemType; Rect itemRect; Handle itemHandle; char *sptr; myDialog = GetNewDialog(BASE_RES_ID+1, NIL_POINTER, (WindowPtr) MOVE_TO_FRONT); if (CopyChannel == 0) { GetDItem(myDialog, FIRST_RADIO, &itemType, &itemHandle, &itemRect); SetCtlValue(itemHandle, ON); } else { GetDItem(myDialog, SECOND_RADIO, &itemType, &itemHandle, &itemRect); SetCtlValue(itemHandle, ON); } ShowWindow(myDialog); while (dialogDone == FALSE) { ModalDialog(NIL_POINTER, &itemHit); switch(itemHit) { case FIRST_RADIO: CopyChannel = 0; GetDItem(myDialog, FIRST_RADIO, &itemType, &itemHandle, &itemRect); SetCtlValue(itemHandle, ON); GetDItem(myDialog, SECOND_RADIO,&itemType, &itemHandle, &itemRect); SetCtlValue(itemHandle, OFF); break; case SECOND_RADIO: CopyChannel = 1; GetDItem(myDialog, SECOND_RADIO,&itemType, &itemHandle, &itemRect); SetCtlValue(itemHandle, ON); GetDItem(myDialog, FIRST_RADIO, &itemType, &itemHandle, &itemRect); SetCtlValue(itemHandle, OFF); break; case CD_OK: dialogDone = TRUE; } } DisposeWindow(myDialog); } MenuBarInit() { Handle myMenuBar; myMenuBar = GetNewMBar(BASE_RES_ID); SetMenuBar(myMenuBar); FileMenu = GetMenu(BASE_RES_ID); DisplayMenu = GetMenu(BASE_RES_ID+1); InsertMenu(FileMenu, 0); InsertMenu(DisplayMenu, 0); DrawMenuBar(); } HandleEvent() { if (WNEImplemented) WaitNextEvent(everyEvent, &TheEvent, 0L, 0L); else { SystemTask(); GetNextEvent(everyEvent, &TheEvent); } switch (TheEvent.what) { case mouseDown: HandleMouseDown(); break; } } HandleMouseDown() { WindowPtr whichWindow; short int thePart; long int menuChoice; thePart = FindWindow(TheEvent.where, &whichWindow); switch (thePart) { case inMenuBar: menuChoice = MenuSelect(TheEvent.where); HandleMenuChoice(menuChoice); break; case inSysWindow: SystemClick(&TheEvent, whichWindow); break; } } HandleMenuChoice(menuChoice) long int menuChoice; { int theMenu, theItem; if (menuChoice != 0) { theMenu = HiWord(menuChoice); theItem = LoWord(menuChoice); switch (theMenu) { case FILE_MENU_ID: HandleFileChoice(theItem); break; case DISP_MENU_ID: HandleDispChoice(theItem); break; } HiliteMenu(0); } } HandleFileChoice(theItem) int theItem; { char recordName[80], str[80]; WFDB_Siginfo s[2]; int i; int rval, v1[2], v2[2]; switch(theItem) { case OPEN_ITEM: HandleFileDialog(recordName); if (isigopen(recordName, s, 2) < 1) { NoteAlert(128, 0L); FileOpen = FALSE; } else { FileOpen = TRUE; MoveTo(190, STATUS_LINE); sprintf(str, "RECORD: %s", recordName); TextMode(srcCopy); DrawString(CtoPstr(str)); ShowTime(1); } break; case COPY_ITEM: HandleCopyDialog(); CopyECGToClip(); break; case QUIT_ITEM: Done = TRUE; break; } } HandleDispChoice(theItem) int theItem; { switch(theItem) { case GO_ITEM: if (FileOpen == TRUE) Running = TRUE; break; case STOP_ITEM: Running = FALSE; break; } } PlotData(color) int color; { /* This depends heavily on the unusual characteristics of the MIT-BIH Arrhythmia Database signal files, which contain 11-bit samples encoded in offset binary format (i.e., the range is 0 to 2047, with the midpoint at 1024 (0x400), with a gain of 200 A/D units per millivolt, and two signals in each case. To make this program more general, you should get information about the range, baseline, gain, and number of signals from the information returned by isigopen() (called in HandleFileChoice() above). The coordinate system is inverted (y increases toward the bottom of the window), so the y-coordinates are calculated by: * averaging two samples (this is done in main()) * subtracting the assumed baseline (0x400) * dividing the result by 4 (equivalent to >>2) so that 1 mV = 50 units at the assumed gain * subtracting this from a fixed offset value for each signal --GBM */ if (color == 0) { /* erase old data */ PenMode(patBic); if (DBPtr != DISP_LGTH - 1) { MoveTo(DBPtr+10, CH1_OFFSET - ((DataBuffer[0][DBPtr ]-0x400)>>2)); LineTo(DBPtr+11, CH1_OFFSET - ((DataBuffer[0][DBPtr+1]-0x400)>>2)); MoveTo(DBPtr+10, CH2_OFFSET - ((DataBuffer[1][DBPtr ]-0x400)>>2)); LineTo(DBPtr+11, CH2_OFFSET - ((DataBuffer[1][DBPtr+1]-0x400)>>2)); } } else { PenMode(patOr); if (DBPtr != 0) { MoveTo(DBPtr+ 9, CH1_OFFSET - ((DataBuffer[0][DBPtr-1]-0x400)>>2)); LineTo(DBPtr+10, CH1_OFFSET - ((DataBuffer[0][DBPtr ]-0x400)>>2)); MoveTo(DBPtr+ 9, CH2_OFFSET - ((DataBuffer[1][DBPtr-1]-0x400)>>2)); LineTo(DBPtr+10, CH2_OFFSET - ((DataBuffer[1][DBPtr ]-0x400)>>2)); } } } ShowTime(init) int init; { static int samples, sec, min; char str[80]; /* This depends on the sampling frequency of the MIT-BIH Arrhythmia Database (360 Hz). To make this program more general, use the WFDB library function sampfreq() to find out the sampling frequency of the open record, or (better) use timstr(sample_number), where sample_number is a long integer, to convert the time into a printable [HH:]MM:SS representation. --GBM */ if (init) { samples = sec = min = 0; sprintf(str, "TIME: %.2d:%.2d", min, sec); MoveTo(290, STATUS_LINE); TextMode(srcCopy); DrawString(CtoPstr(str)); } else { if (++samples == 360) { samples = 0; if (++sec == 60) { sec = 0; ++min; } sprintf(str, "TIME: %.2d:%.2d", min, sec); MoveTo(290, STATUS_LINE); TextMode(srcCopy); DrawString(CtoPstr(str)); } } } CopyECGToClip() { char str[40]; int data[DISP_LGTH]; PicHandle wavePic; long psize; Rect pFrame, myRect; int max, min, baseline, i, j; /* Copy data from data buffer to a temporary buffer. */ for (i = 0, j = DBPtr; i < DISP_LGTH; ++i) { data[i] = DataBuffer[CopyChannel][j]; if (++j == DISP_LGTH) j = 0; } for (max = min = data[0], i = 1; i < DISP_LGTH; ++i) { if (data[i] > max) max = data[i]; if (data[i] < min) min = data[i]; } baseline = (max+min)>>1; for (i = 0; i < DISP_LGTH; ++i) data[i] = 150 - ((data[i] - baseline)>>2); pFrame.top = 0; pFrame.bottom = 300; pFrame.left = 0; pFrame.right = DISP_LGTH; wavePic = OpenPicture(&pFrame); PenMode(patOr); MoveTo(0, data[0]); for (i = 1; i < DISP_LGTH; ++i) LineTo(i, data[i]); ClosePicture(); ZeroScrap(); psize = GetHandleSize(wavePic); PutScrap(psize, 'PICT', *wavePic); /* <-- shouldn't that be "PICT"? -GBM */ KillPicture(wavePic); }