|
| 1 | +import numpy as np |
| 2 | +import re |
| 3 | +import os |
| 4 | +import sys |
| 5 | +import requests |
| 6 | + |
| 7 | +def rdheader(recordname): # For reading signal headers |
| 8 | + |
| 9 | + # To do: Allow exponential input format for some fields |
| 10 | + |
| 11 | + # Output dictionary |
| 12 | + fields = { |
| 13 | + 'nseg': [], |
| 14 | + 'nsig': [], |
| 15 | + 'fs': [], |
| 16 | + 'nsamp': [], |
| 17 | + 'basetime': [], |
| 18 | + 'basedate': [], |
| 19 | + 'filename': [], |
| 20 | + 'fmt': [], |
| 21 | + 'sampsperframe': [], |
| 22 | + 'skew': [], |
| 23 | + 'byteoffset': [], |
| 24 | + 'gain': [], |
| 25 | + 'units': [], |
| 26 | + 'baseline': [], |
| 27 | + 'initvalue': [], |
| 28 | + 'signame': [], |
| 29 | + 'nsampseg': [], |
| 30 | + 'comments': []} |
| 31 | + # filename stores file names for both multi and single segment headers. |
| 32 | + # nsampseg is only for multi-segment |
| 33 | + |
| 34 | + |
| 35 | + # RECORD LINE fields (o means optional, delimiter is space or tab unless specified): |
| 36 | + # record name, nsegments (o, delim=/), nsignals, fs (o), counter freq (o, delim=/, needs fs), |
| 37 | + # base counter (o, delim=(), needs counter freq), nsamples (o, needs fs), base time (o), |
| 38 | + # base date (o needs base time). |
| 39 | + |
| 40 | + # Regexp object for record line |
| 41 | + rxRECORD = re.compile( |
| 42 | + ''.join( |
| 43 | + [ |
| 44 | + "(?P<name>[\w]+)/?(?P<nseg>\d*)[ \t]+", |
| 45 | + "(?P<nsig>\d+)[ \t]*", |
| 46 | + "(?P<fs>\d*\.?\d*)/*(?P<counterfs>\d*\.?\d*)\(?(?P<basecounter>\d*\.?\d*)\)?[ \t]*", |
| 47 | + "(?P<nsamples>\d*)[ \t]*", |
| 48 | + "(?P<basetime>\d*:?\d{,2}:?\d{,2}\.?\d*)[ \t]*", |
| 49 | + "(?P<basedate>\d{,2}/?\d{,2}/?\d{,4})"])) |
| 50 | + # Watch out for potential floats: fs (and also exponent notation...), |
| 51 | + # counterfs, basecounter |
| 52 | + |
| 53 | + # SIGNAL LINE fields (o means optional, delimiter is space or tab unless specified): |
| 54 | + # file name, format, samplesperframe(o, delim=x), skew(o, delim=:), byteoffset(o,delim=+), |
| 55 | + # ADCgain(o), baseline(o, delim=(), requires ADCgain), units(o, delim=/, requires baseline), |
| 56 | + # ADCres(o, requires ADCgain), ADCzero(o, requires ADCres), initialvalue(o, requires ADCzero), |
| 57 | + # checksum(o, requires initialvalue), blocksize(o, requires checksum), |
| 58 | + # signame(o, requires block) |
| 59 | + |
| 60 | + # Regexp object for signal lines. Consider flexible filenames, and also ~ |
| 61 | + rxSIGNAL = re.compile( |
| 62 | + ''.join( |
| 63 | + [ |
| 64 | + "(?P<filename>[\w]*\.?[\w]*~?)[ \t]+(?P<format>\d+)x?" |
| 65 | + "(?P<sampsperframe>\d*):?(?P<skew>\d*)\+?(?P<byteoffset>\d*)[ \t]*", |
| 66 | + "(?P<ADCgain>-?\d*\.?\d*e?[\+-]?\d*)\(?(?P<baseline>-?\d*)\)?/?(?P<units>[\w\^/-]*)[ \t]*", |
| 67 | + "(?P<ADCres>\d*)[ \t]*(?P<ADCzero>-?\d*)[ \t]*(?P<initialvalue>-?\d*)[ \t]*", |
| 68 | + "(?P<checksum>-?\d*)[ \t]*(?P<blocksize>\d*)[ \t]*(?P<signame>[\S]*)"])) |
| 69 | + |
| 70 | + # Units characters: letters, numbers, /, ^, -, |
| 71 | + # Watch out for potentially negative fields: baseline, ADCzero, initialvalue, checksum, |
| 72 | + # Watch out for potential float: ADCgain. |
| 73 | + |
| 74 | + # Read the header file and get the comment and non-comment lines |
| 75 | + headerlines, commentlines = getheaderlines(recordname) |
| 76 | + |
| 77 | + # Get record line parameters |
| 78 | + (_, nseg, nsig, fs, counterfs, basecounter, nsamp, |
| 79 | + basetime, basedate) = rxRECORD.findall(headerlines[0])[0] |
| 80 | + |
| 81 | + # These fields are either mandatory or set to defaults. |
| 82 | + if not nseg: |
| 83 | + nseg = '1' |
| 84 | + if not fs: |
| 85 | + fs = '250' |
| 86 | + |
| 87 | + fields['nseg'] = int(nseg) |
| 88 | + fields['fs'] = float(fs) |
| 89 | + fields['nsig'] = int(nsig) |
| 90 | + |
| 91 | + # These fields might by empty |
| 92 | + if nsamp: |
| 93 | + fields['nsamp'] = int(nsamp) |
| 94 | + fields['basetime'] = basetime |
| 95 | + fields['basedate'] = basedate |
| 96 | + |
| 97 | + |
| 98 | + # Signal or Segment line paramters |
| 99 | + # Multi segment header - Process segment spec lines in current master |
| 100 | + # header. |
| 101 | + if int(nseg) > 1: |
| 102 | + for i in range(0, int(nseg)): |
| 103 | + (filename, nsampseg) = re.findall( |
| 104 | + '(?P<filename>\w*~?)[ \t]+(?P<nsampseg>\d+)', headerlines[i + 1])[0] |
| 105 | + fields["filename"].append(filename) |
| 106 | + fields["nsampseg"].append(int(nsampseg)) |
| 107 | + # Single segment header - Process signal spec lines in regular header. |
| 108 | + else: |
| 109 | + for i in range(0, int(nsig)): # will not run if nsignals=0 |
| 110 | + # get signal line parameters |
| 111 | + (filename, |
| 112 | + fmt, |
| 113 | + sampsperframe, |
| 114 | + skew, |
| 115 | + byteoffset, |
| 116 | + adcgain, |
| 117 | + baseline, |
| 118 | + units, |
| 119 | + adcres, |
| 120 | + adczero, |
| 121 | + initvalue, |
| 122 | + checksum, |
| 123 | + blocksize, |
| 124 | + signame) = rxSIGNAL.findall(headerlines[i + 1])[0] |
| 125 | + |
| 126 | + # Setting defaults |
| 127 | + if not sampsperframe: |
| 128 | + # Setting strings here so we can always convert strings case |
| 129 | + # below. |
| 130 | + sampsperframe = '1' |
| 131 | + if not skew: |
| 132 | + skew = '0' |
| 133 | + if not byteoffset: |
| 134 | + byteoffset = '0' |
| 135 | + if not adcgain: |
| 136 | + adcgain = '200' |
| 137 | + if not baseline: |
| 138 | + if not adczero: |
| 139 | + baseline = '0' |
| 140 | + else: |
| 141 | + baseline = adczero # missing baseline actually takes adczero value if present |
| 142 | + if not units: |
| 143 | + units = 'mV' |
| 144 | + if not initvalue: |
| 145 | + initvalue = '0' |
| 146 | + if not signame: |
| 147 | + signame = "ch" + str(i + 1) |
| 148 | + if not initvalue: |
| 149 | + initvalue = '0' |
| 150 | + |
| 151 | + fields["filename"].append(filename) |
| 152 | + fields["fmt"].append(fmt) |
| 153 | + fields["sampsperframe"].append(int(sampsperframe)) |
| 154 | + fields["skew"].append(int(skew)) |
| 155 | + fields['byteoffset'].append(int(byteoffset)) |
| 156 | + fields["gain"].append(float(adcgain)) |
| 157 | + fields["baseline"].append(int(baseline)) |
| 158 | + fields["units"].append(units) |
| 159 | + fields["initvalue"].append(int(initvalue)) |
| 160 | + fields["signame"].append(signame) |
| 161 | + |
| 162 | + for comment in commentlines: |
| 163 | + fields["comments"].append(comment.strip('\s#')) |
| 164 | + |
| 165 | + return fields |
| 166 | + |
| 167 | + |
| 168 | +# Read header file to get comment and non-comment lines |
| 169 | +def getheaderlines(recordname): |
| 170 | + with open(recordname + ".hea", 'r') as fp: |
| 171 | + headerlines = [] # Store record line followed by the signal lines if any |
| 172 | + commentlines = [] # Comments |
| 173 | + for line in fp: |
| 174 | + line = line.strip() |
| 175 | + if line.startswith('#'): # comment line |
| 176 | + commentlines.append(line) |
| 177 | + elif line: # Non-empty non-comment line = header line. |
| 178 | + ci = line.find('#') |
| 179 | + if ci > 0: |
| 180 | + headerlines.append(line[:ci]) # header line |
| 181 | + # comment on same line as header line |
| 182 | + commentlines.append(line[ci:]) |
| 183 | + else: |
| 184 | + headerlines.append(line) |
| 185 | + return headerlines, commentlines |
| 186 | + |
0 commit comments