Skip to content

Commit 32aa968

Browse files
authored
Merge pull request #2 from gitrust/docker
Additional fields and fixes
2 parents 8d62f0b + dc4b1a1 commit 32aa968

File tree

8 files changed

+173
-43
lines changed

8 files changed

+173
-43
lines changed

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
1-
*~
1+
*~
2+
__pycache__
3+
.pytest_cache

.vscode/settings.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"python.formatting.provider": "black"
3+
}

Makefile

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
2+
info:
3+
python scpinfo.py example/example.scp

scp.py

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -137,8 +137,12 @@ class Section2(Section):
137137
def __init__(self, header, pointer):
138138
super().__init__(header)
139139
self.p = pointer
140+
# Number of Huffman Tables defined (if 19 999 then the default table, defined in C.3.7.6, is used).
141+
self.nr_huffman_tables = 0
142+
# Number of code structures in table # 1
143+
self.nr_code_struct = 0
140144

141-
145+
142146
class Section3(Section):
143147
"""Section 3 - ECG Leads definition"""
144148
def __init__(self, header, pointer):
@@ -152,14 +156,19 @@ def __init__(self, header, pointer):
152156
self.nr_leads_sim = 0
153157
self.leads = []
154158

155-
159+
# QRS Locations if reference beats are encoded
156160
class Section4(Section):
157-
"""Section 4 - Reserved for legacy SCP-ECG versions"""
161+
"""Section 4 - Reserved for legacy SCP-ECG versions (SCP Versions 1.x, 2.x)"""
158162
def __init__(self, header, pointer):
159163
super().__init__(header)
160164
self.p = pointer
161-
162-
165+
# Length of reference beat type 0 in milliseconds
166+
self.ref_beat_type_len = 0
167+
# Sample number of the fiducial point (QRS trigger point), with respect to beginning of reference beat type 0
168+
self.sample_nr_fidpoint = 0
169+
# Total number of QRS complexes within the entire short-term ECG rhythm record
170+
self.total_nr_qrs = 0
171+
163172
class Section5(Section):
164173
"""Section 5 with samples"""
165174
def __init__(self, header, pointer):
@@ -206,6 +215,19 @@ def __init__(self, header, pointer):
206215
super().__init__(header)
207216
self.p = pointer
208217

218+
self.reference_count = 0
219+
# number of pacemaker spikes
220+
self.pace_count = 0
221+
# Average RR interval in milliseconds for all QRS's
222+
self.rr_interval = 0
223+
# Average PP interval in milliseconds for all QRS's
224+
self.pp_interval = 0
225+
self.pace_times = []
226+
self.pace_amplitudes = []
227+
self.pace_types = []
228+
self.pace_sources = []
229+
self.pace_indexes = []
230+
self.pace_widths = []
209231

210232
class Section8(Section):
211233
"""Section 8 - Textual diagnosis"""

scpformat.py

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
SCP section and attribute formatter
66
"""
77

8-
from scputil import b2i, bdecode, lead_dic
8+
from scputil import b2i, b2s, bdecode, lead_dic
99

1010

1111
class Section1TagsFormatter:
@@ -129,7 +129,7 @@ def __init__(self, bytes):
129129
self._print = False
130130
if bytes:
131131
# in minutes
132-
self.offset = b2i(bytes[0:2])
132+
self.offset = b2s(bytes[0:2])
133133
self.index = b2i(bytes[2:4])
134134
self.desc = bdecode(bytes[4:])
135135
self._print = True
@@ -402,6 +402,7 @@ def format_section2(s2, printer):
402402
print()
403403
printer.p('--Section2--', '----')
404404
format_header(s2.h, printer)
405+
printer.p('NrHuffmanTables', '19999 (default table)' if s2.nr_huffman_tables == 19999 else s2.nr_huffman_tables)
405406

406407

407408
def format_section3(s3, printer):
@@ -416,7 +417,6 @@ def format_section3(s3, printer):
416417
printer.p('LeadCount', len(s3.leads))
417418
LeadIdFormatter(s3.leads).format(printer)
418419

419-
420420
def format_section5(s5, printer):
421421
if not s5.p.section_has_data():
422422
return
@@ -495,15 +495,22 @@ def format_section7(s7, printer):
495495
print()
496496
printer.p('--Section7--', '----')
497497
format_header(s7.h, printer)
498+
printer.p('ReferenceCount', s7.reference_count)
499+
printer.p('PaceCount', s7.pace_count)
500+
printer.p('Avg RR Interval (ms)', "29999 (not calculated)" if 29999 == s7.rr_interval else s7.rr_interval )
501+
printer.p('Avg PP Interval (ms)', "29999 (not calculated)" if 29999 == s7.pp_interval else s7.pp_interval)
498502

499503

500-
def format_section4(s4, printer):
501-
if not s4.p.section_has_data():
504+
def format_section4(s, printer):
505+
if not s.p.section_has_data():
502506
return
503-
507+
504508
print()
505509
printer.p('--Section4--', '----')
506-
format_header(s4.h, printer)
510+
format_header(s.h, printer)
511+
printer.p('RefBeatType0Len', s.ref_beat_type_len)
512+
printer.p('SampleNr FiducialPoint', s.sample_nr_fidpoint)
513+
printer.p('TotalNrQRS', s.total_nr_qrs)
507514

508515

509516
def format_section8(s8, printer):

scpreader.py

Lines changed: 79 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,34 @@ def readint(self, n):
4040
return int(value)
4141
return int.from_bytes(bytes, 'little')
4242

43+
def read_byte(self):
44+
"""Read 1 byte (little endian)"""
45+
bytes = self.file.read(1)
46+
if len(bytes) == 0:
47+
print('ERR: Could not read_byte, corrupt structure. File position ' + self.pos())
48+
return struct.unpack("<B", bytes)[0]
49+
50+
def read_ushort(self):
51+
"""Read 2 bytes as unsigned short (little endian)"""
52+
bytes = self.file.read(2)
53+
if len(bytes) == 0:
54+
print('ERR: Could not read_ushort, corrupt structure. File position ' + self.pos())
55+
return struct.unpack("<H", bytes)[0]
56+
57+
def read_uint(self):
58+
"""Read 4 bytes as unsigned integer (little endian)"""
59+
bytes = self.file.read(4)
60+
if len(bytes) == 0:
61+
print('ERR: Could not read_uint, corrupt structure. File position ' + self.pos())
62+
return struct.unpack("<I", bytes)[0]
63+
64+
def read_ulong(self):
65+
"""Read 6 bytes as unsigned long (little endian)"""
66+
bytes = self.file.read(6)
67+
if len(bytes) == 0:
68+
print('ERR: Could not read_ulong, corrupt structure. File position ' + self.pos())
69+
return struct.unpack("<Q", bytes)[0]
70+
4371
def move(self, n):
4472
"""move n bytes from beginning of file"""
4573
return self.file.seek(n, 0)
@@ -68,8 +96,8 @@ def close(self):
6896
def read_scp(self):
6997
"""Read an scp file into memory and returns a ScpRecord"""
7098
scpRecord = ScpRecord()
71-
scpRecord.crc = self.reader.readint(2)
72-
scpRecord.len = self.reader.readint(4)
99+
scpRecord.crc = self.reader.read_ushort()
100+
scpRecord.len = self.reader.read_uint()
73101

74102
s0 = self._section0()
75103
scpRecord.sections.append(s0)
@@ -93,9 +121,9 @@ def read_scp(self):
93121
def _sectionheader(self):
94122
"""Read and return a section header"""
95123
header = SectionHeader()
96-
header.crc = self.reader.readint(2)
97-
header.id = self.reader.readint(2)
98-
header.len = self.reader.readint(4)
124+
header.crc = self.reader.read_ushort()
125+
header.id = self.reader.read_ushort()
126+
header.len = self.reader.read_uint()
99127
header.versnr = self.reader.readint(1)
100128
header.protnr = self.reader.readint(1)
101129
header.reserved = self.reader.reads(6)
@@ -107,11 +135,11 @@ def _sectionheader(self):
107135
def _sectionpointer(self):
108136
"""Read and return a section pointer"""
109137
p = SectionPointer()
110-
p.id = self.reader.readint(2)
138+
p.id = self.reader.read_ushort()
111139
# section length
112-
p.len = self.reader.readint(4)
140+
p.len = self.reader.read_uint()
113141
# index of section starting from zero
114-
p.index = self.reader.readint(4)
142+
p.index = self.reader.read_uint()
115143
return p
116144

117145
def _section0(self):
@@ -139,8 +167,8 @@ def _section0(self):
139167
def _readtag(self):
140168
"""Read and return a scp tag"""
141169
tag = Tag()
142-
tag.tag = self.reader.readint(1)
143-
tag.len = self.reader.readint(2)
170+
tag.tag = self.reader.read_byte()
171+
tag.len = self.reader.read_ushort()
144172

145173
if tag.len > 0:
146174
tag.data = self.reader.read(tag.len)
@@ -149,9 +177,9 @@ def _readtag(self):
149177
def _readleadid(self):
150178
"""Read and return a LeadId"""
151179
leadid = LeadIdentification()
152-
leadid.startsample = self.reader.readint(4)
153-
leadid.endsample = self.reader.readint(4)
154-
leadid.leadid = self.reader.readint(1)
180+
leadid.startsample = self.reader.read_uint()
181+
leadid.endsample = self.reader.read_uint()
182+
leadid.leadid = self.reader.read_byte()
155183
return leadid
156184

157185
def _read_section(self, pointer, nr_of_leads):
@@ -206,6 +234,10 @@ def _section2(self, pointer):
206234

207235
header = self._sectionheader()
208236
s = Section2(header, pointer)
237+
238+
s.nr_huffman_tables = self.reader.read_ushort()
239+
# Number of code structures in table # 1
240+
s.nr_code_struct = self.reader.read_ushort()
209241
return s
210242

211243
def _section3(self, pointer):
@@ -215,8 +247,8 @@ def _section3(self, pointer):
215247
header = self._sectionheader()
216248
s = Section3(header, pointer)
217249

218-
s.nrleads = self.reader.readint(1)
219-
s.flags = self.reader.readint(1)
250+
s.nrleads = self.reader.read_byte()
251+
s.flags = self.reader.read_byte()
220252
# first bit
221253
s.ref_beat_substr = bool(s.flags >> 1 & 1)
222254
# bits 3-7
@@ -235,6 +267,10 @@ def _section4(self, pointer):
235267
header = self._sectionheader()
236268
s = Section4(header, pointer)
237269

270+
s.ref_beat_type_len = self.reader.read_ushort()
271+
s.sample_nr_fidpoint = self.reader.read_ushort()
272+
s.total_nr_qrs = self.reader.read_ushort()
273+
238274
return s
239275

240276
def _section5(self, pointer, nr_of_leads):
@@ -244,14 +280,14 @@ def _section5(self, pointer, nr_of_leads):
244280
header = self._sectionheader()
245281
s = Section5(header, pointer)
246282

247-
s.avm = self.reader.readint(2)
248-
s.sample_time_interval = self.reader.readint(2)
249-
s.sample_encoding = self.reader.readint(1)
250-
s.reserved = self.reader.readint(1)
283+
s.avm = self.reader.read_ushort()
284+
s.sample_time_interval = self.reader.read_ushort()
285+
s.sample_encoding = self.reader.read_byte()
286+
s.reserved = self.reader.read_byte()
251287

252288
# nr of bytes for each lead
253289
for _ in range(0, nr_of_leads):
254-
s.nr_bytes_for_leads.append(self.reader.readint(2))
290+
s.nr_bytes_for_leads.append(self.reader.read_ushort())
255291

256292
# samples for each lead
257293
for nr in s.nr_bytes_for_leads:
@@ -261,7 +297,7 @@ def _section5(self, pointer, nr_of_leads):
261297
samples_len = nr / 2
262298

263299
while samples_len > 0:
264-
data.samples.append(self.reader.readint(2))
300+
data.samples.append(self.reader.read_ushort())
265301
samples_len = samples_len - 1
266302

267303
s.data.append(data)
@@ -275,16 +311,18 @@ def _section6(self, pointer, nr_of_leads):
275311
header = self._sectionheader()
276312
s = Section6(header, pointer)
277313

278-
s.avm = self.reader.readint(2)
279-
s.sample_time_interval = self.reader.readint(2)
280-
s.sample_encoding = self.reader.readint(1)
281-
s.bimodal_compression = self.reader.readint(1)
314+
s.avm = self.reader.read_ushort()
315+
s.sample_time_interval = self.reader.read_ushort()
316+
s.sample_encoding = self.reader.read_byte()
317+
s.bimodal_compression = self.reader.read_byte()
282318

283319

284320
# nr of bytes for each lead
285321
for _ in range(0, nr_of_leads):
286-
s.nr_bytes_for_leads.append(self.reader.readint(2))
322+
s.nr_bytes_for_leads.append(self.reader.read_ushort())
287323

324+
# TODO: check if section2 exists -> samples are Huffman encoded
325+
288326
# samples for each lead
289327
for nr in s.nr_bytes_for_leads:
290328
data = DataSamples()
@@ -293,6 +331,7 @@ def _section6(self, pointer, nr_of_leads):
293331
samples_len = nr / 2
294332

295333
while samples_len > 0:
334+
# signed 2byte integers
296335
data.samples.append(self.reader.readint(2))
297336
samples_len = samples_len - 1
298337

@@ -306,6 +345,20 @@ def _section7(self, pointer):
306345
header = self._sectionheader()
307346
s = Section7(header, pointer)
308347

348+
s.reference_count = self.reader.read_byte()
349+
s.pace_count = self.reader.read_byte()
350+
s.rr_interval = self.reader.read_ushort()
351+
s.pp_interval = self.reader.read_ushort()
352+
for i in range(0, s.pace_count):
353+
s.pace_times[i] = self.reader.readint(2)
354+
s.pace_amplitudes[i] = self.reader.read_ushort()
355+
356+
for i in range(0, s.pace_count):
357+
s.pace_types = self.reader.read_byte()
358+
s.pace_sources = self.reader.read_byte()
359+
s.pace_indexes = self.reader.readint(2)
360+
s.pace_widths = self.reader.readint(2)
361+
309362
return s
310363

311364

scputil.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,25 @@
11
#!/usr/bin/env python
22
# -*- coding: utf-8 -*-
33

4+
import struct
45

6+
def b2s(bytes):
7+
if len(bytes) != 2:
8+
raise ValueError("bytes must be exactly 2 bytes long")
9+
return struct.unpack('h', bytes)
10+
11+
def b2b(bytes):
12+
if len(bytes) != 1:
13+
raise ValueError("bytes must be exactly 1 byte long")
14+
return struct.unpack('B', bytes)
515

616
def b2i(bytes):
7-
"""Convert bytes to int (little endian)"""
17+
"""Convert bytes to unsigned integer (little endian)"""
818
return int.from_bytes(bytes, 'little')
919

20+
def b2si(bytes):
21+
"""Convert bytes to signed integer (little endian)"""
22+
return int.from_bytes(bytes, 'little', signed=True)
1023

1124
def bdecode(bytes):
1225
"""Decode bytes as iso-8859-1 and remove null terminators"""

0 commit comments

Comments
 (0)