Skip to content

Commit

Permalink
Merge pull request #2 from gitrust/docker
Browse files Browse the repository at this point in the history
Additional fields and fixes
  • Loading branch information
gitrust authored May 31, 2024
2 parents 8d62f0b + dc4b1a1 commit 32aa968
Show file tree
Hide file tree
Showing 8 changed files with 173 additions and 43 deletions.
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
*~
*~
__pycache__
.pytest_cache
3 changes: 3 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"python.formatting.provider": "black"
}
3 changes: 3 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@

info:
python scpinfo.py example/example.scp
32 changes: 27 additions & 5 deletions scp.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,8 +137,12 @@ class Section2(Section):
def __init__(self, header, pointer):
super().__init__(header)
self.p = pointer
# Number of Huffman Tables defined (if 19 999 then the default table, defined in C.3.7.6, is used).
self.nr_huffman_tables = 0
# Number of code structures in table # 1
self.nr_code_struct = 0


class Section3(Section):
"""Section 3 - ECG Leads definition"""
def __init__(self, header, pointer):
Expand All @@ -152,14 +156,19 @@ def __init__(self, header, pointer):
self.nr_leads_sim = 0
self.leads = []


# QRS Locations if reference beats are encoded
class Section4(Section):
"""Section 4 - Reserved for legacy SCP-ECG versions"""
"""Section 4 - Reserved for legacy SCP-ECG versions (SCP Versions 1.x, 2.x)"""
def __init__(self, header, pointer):
super().__init__(header)
self.p = pointer


# Length of reference beat type 0 in milliseconds
self.ref_beat_type_len = 0
# Sample number of the fiducial point (QRS trigger point), with respect to beginning of reference beat type 0
self.sample_nr_fidpoint = 0
# Total number of QRS complexes within the entire short-term ECG rhythm record
self.total_nr_qrs = 0

class Section5(Section):
"""Section 5 with samples"""
def __init__(self, header, pointer):
Expand Down Expand Up @@ -206,6 +215,19 @@ def __init__(self, header, pointer):
super().__init__(header)
self.p = pointer

self.reference_count = 0
# number of pacemaker spikes
self.pace_count = 0
# Average RR interval in milliseconds for all QRS's
self.rr_interval = 0
# Average PP interval in milliseconds for all QRS's
self.pp_interval = 0
self.pace_times = []
self.pace_amplitudes = []
self.pace_types = []
self.pace_sources = []
self.pace_indexes = []
self.pace_widths = []

class Section8(Section):
"""Section 8 - Textual diagnosis"""
Expand Down
21 changes: 14 additions & 7 deletions scpformat.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
SCP section and attribute formatter
"""

from scputil import b2i, bdecode, lead_dic
from scputil import b2i, b2s, bdecode, lead_dic


class Section1TagsFormatter:
Expand Down Expand Up @@ -129,7 +129,7 @@ def __init__(self, bytes):
self._print = False
if bytes:
# in minutes
self.offset = b2i(bytes[0:2])
self.offset = b2s(bytes[0:2])
self.index = b2i(bytes[2:4])
self.desc = bdecode(bytes[4:])
self._print = True
Expand Down Expand Up @@ -402,6 +402,7 @@ def format_section2(s2, printer):
print()
printer.p('--Section2--', '----')
format_header(s2.h, printer)
printer.p('NrHuffmanTables', '19999 (default table)' if s2.nr_huffman_tables == 19999 else s2.nr_huffman_tables)


def format_section3(s3, printer):
Expand All @@ -416,7 +417,6 @@ def format_section3(s3, printer):
printer.p('LeadCount', len(s3.leads))
LeadIdFormatter(s3.leads).format(printer)


def format_section5(s5, printer):
if not s5.p.section_has_data():
return
Expand Down Expand Up @@ -495,15 +495,22 @@ def format_section7(s7, printer):
print()
printer.p('--Section7--', '----')
format_header(s7.h, printer)
printer.p('ReferenceCount', s7.reference_count)
printer.p('PaceCount', s7.pace_count)
printer.p('Avg RR Interval (ms)', "29999 (not calculated)" if 29999 == s7.rr_interval else s7.rr_interval )
printer.p('Avg PP Interval (ms)', "29999 (not calculated)" if 29999 == s7.pp_interval else s7.pp_interval)


def format_section4(s4, printer):
if not s4.p.section_has_data():
def format_section4(s, printer):
if not s.p.section_has_data():
return

print()
printer.p('--Section4--', '----')
format_header(s4.h, printer)
format_header(s.h, printer)
printer.p('RefBeatType0Len', s.ref_beat_type_len)
printer.p('SampleNr FiducialPoint', s.sample_nr_fidpoint)
printer.p('TotalNrQRS', s.total_nr_qrs)


def format_section8(s8, printer):
Expand Down
105 changes: 79 additions & 26 deletions scpreader.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,34 @@ def readint(self, n):
return int(value)
return int.from_bytes(bytes, 'little')

def read_byte(self):
"""Read 1 byte (little endian)"""
bytes = self.file.read(1)
if len(bytes) == 0:
print('ERR: Could not read_byte, corrupt structure. File position ' + self.pos())
return struct.unpack("<B", bytes)[0]

def read_ushort(self):
"""Read 2 bytes as unsigned short (little endian)"""
bytes = self.file.read(2)
if len(bytes) == 0:
print('ERR: Could not read_ushort, corrupt structure. File position ' + self.pos())
return struct.unpack("<H", bytes)[0]

def read_uint(self):
"""Read 4 bytes as unsigned integer (little endian)"""
bytes = self.file.read(4)
if len(bytes) == 0:
print('ERR: Could not read_uint, corrupt structure. File position ' + self.pos())
return struct.unpack("<I", bytes)[0]

def read_ulong(self):
"""Read 6 bytes as unsigned long (little endian)"""
bytes = self.file.read(6)
if len(bytes) == 0:
print('ERR: Could not read_ulong, corrupt structure. File position ' + self.pos())
return struct.unpack("<Q", bytes)[0]

def move(self, n):
"""move n bytes from beginning of file"""
return self.file.seek(n, 0)
Expand Down Expand Up @@ -68,8 +96,8 @@ def close(self):
def read_scp(self):
"""Read an scp file into memory and returns a ScpRecord"""
scpRecord = ScpRecord()
scpRecord.crc = self.reader.readint(2)
scpRecord.len = self.reader.readint(4)
scpRecord.crc = self.reader.read_ushort()
scpRecord.len = self.reader.read_uint()

s0 = self._section0()
scpRecord.sections.append(s0)
Expand All @@ -93,9 +121,9 @@ def read_scp(self):
def _sectionheader(self):
"""Read and return a section header"""
header = SectionHeader()
header.crc = self.reader.readint(2)
header.id = self.reader.readint(2)
header.len = self.reader.readint(4)
header.crc = self.reader.read_ushort()
header.id = self.reader.read_ushort()
header.len = self.reader.read_uint()
header.versnr = self.reader.readint(1)
header.protnr = self.reader.readint(1)
header.reserved = self.reader.reads(6)
Expand All @@ -107,11 +135,11 @@ def _sectionheader(self):
def _sectionpointer(self):
"""Read and return a section pointer"""
p = SectionPointer()
p.id = self.reader.readint(2)
p.id = self.reader.read_ushort()
# section length
p.len = self.reader.readint(4)
p.len = self.reader.read_uint()
# index of section starting from zero
p.index = self.reader.readint(4)
p.index = self.reader.read_uint()
return p

def _section0(self):
Expand Down Expand Up @@ -139,8 +167,8 @@ def _section0(self):
def _readtag(self):
"""Read and return a scp tag"""
tag = Tag()
tag.tag = self.reader.readint(1)
tag.len = self.reader.readint(2)
tag.tag = self.reader.read_byte()
tag.len = self.reader.read_ushort()

if tag.len > 0:
tag.data = self.reader.read(tag.len)
Expand All @@ -149,9 +177,9 @@ def _readtag(self):
def _readleadid(self):
"""Read and return a LeadId"""
leadid = LeadIdentification()
leadid.startsample = self.reader.readint(4)
leadid.endsample = self.reader.readint(4)
leadid.leadid = self.reader.readint(1)
leadid.startsample = self.reader.read_uint()
leadid.endsample = self.reader.read_uint()
leadid.leadid = self.reader.read_byte()
return leadid

def _read_section(self, pointer, nr_of_leads):
Expand Down Expand Up @@ -206,6 +234,10 @@ def _section2(self, pointer):

header = self._sectionheader()
s = Section2(header, pointer)

s.nr_huffman_tables = self.reader.read_ushort()
# Number of code structures in table # 1
s.nr_code_struct = self.reader.read_ushort()
return s

def _section3(self, pointer):
Expand All @@ -215,8 +247,8 @@ def _section3(self, pointer):
header = self._sectionheader()
s = Section3(header, pointer)

s.nrleads = self.reader.readint(1)
s.flags = self.reader.readint(1)
s.nrleads = self.reader.read_byte()
s.flags = self.reader.read_byte()
# first bit
s.ref_beat_substr = bool(s.flags >> 1 & 1)
# bits 3-7
Expand All @@ -235,6 +267,10 @@ def _section4(self, pointer):
header = self._sectionheader()
s = Section4(header, pointer)

s.ref_beat_type_len = self.reader.read_ushort()
s.sample_nr_fidpoint = self.reader.read_ushort()
s.total_nr_qrs = self.reader.read_ushort()

return s

def _section5(self, pointer, nr_of_leads):
Expand All @@ -244,14 +280,14 @@ def _section5(self, pointer, nr_of_leads):
header = self._sectionheader()
s = Section5(header, pointer)

s.avm = self.reader.readint(2)
s.sample_time_interval = self.reader.readint(2)
s.sample_encoding = self.reader.readint(1)
s.reserved = self.reader.readint(1)
s.avm = self.reader.read_ushort()
s.sample_time_interval = self.reader.read_ushort()
s.sample_encoding = self.reader.read_byte()
s.reserved = self.reader.read_byte()

# nr of bytes for each lead
for _ in range(0, nr_of_leads):
s.nr_bytes_for_leads.append(self.reader.readint(2))
s.nr_bytes_for_leads.append(self.reader.read_ushort())

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

while samples_len > 0:
data.samples.append(self.reader.readint(2))
data.samples.append(self.reader.read_ushort())
samples_len = samples_len - 1

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

s.avm = self.reader.readint(2)
s.sample_time_interval = self.reader.readint(2)
s.sample_encoding = self.reader.readint(1)
s.bimodal_compression = self.reader.readint(1)
s.avm = self.reader.read_ushort()
s.sample_time_interval = self.reader.read_ushort()
s.sample_encoding = self.reader.read_byte()
s.bimodal_compression = self.reader.read_byte()


# nr of bytes for each lead
for _ in range(0, nr_of_leads):
s.nr_bytes_for_leads.append(self.reader.readint(2))
s.nr_bytes_for_leads.append(self.reader.read_ushort())

# TODO: check if section2 exists -> samples are Huffman encoded

# samples for each lead
for nr in s.nr_bytes_for_leads:
data = DataSamples()
Expand All @@ -293,6 +331,7 @@ def _section6(self, pointer, nr_of_leads):
samples_len = nr / 2

while samples_len > 0:
# signed 2byte integers
data.samples.append(self.reader.readint(2))
samples_len = samples_len - 1

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

s.reference_count = self.reader.read_byte()
s.pace_count = self.reader.read_byte()
s.rr_interval = self.reader.read_ushort()
s.pp_interval = self.reader.read_ushort()
for i in range(0, s.pace_count):
s.pace_times[i] = self.reader.readint(2)
s.pace_amplitudes[i] = self.reader.read_ushort()

for i in range(0, s.pace_count):
s.pace_types = self.reader.read_byte()
s.pace_sources = self.reader.read_byte()
s.pace_indexes = self.reader.readint(2)
s.pace_widths = self.reader.readint(2)

return s


Expand Down
15 changes: 14 additions & 1 deletion scputil.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,25 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-

import struct

def b2s(bytes):
if len(bytes) != 2:
raise ValueError("bytes must be exactly 2 bytes long")
return struct.unpack('h', bytes)

def b2b(bytes):
if len(bytes) != 1:
raise ValueError("bytes must be exactly 1 byte long")
return struct.unpack('B', bytes)

def b2i(bytes):
"""Convert bytes to int (little endian)"""
"""Convert bytes to unsigned integer (little endian)"""
return int.from_bytes(bytes, 'little')

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

def bdecode(bytes):
"""Decode bytes as iso-8859-1 and remove null terminators"""
Expand Down
Loading

0 comments on commit 32aa968

Please sign in to comment.