Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add new record info data classes #417

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
115 changes: 115 additions & 0 deletions wfdb/io/recordinfo.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
from dataclasses import dataclass
import datetime
from typing import List, Optional, Union


@dataclass
class SignalInfo:
"""
Signal specification fields for one signal
"""

file_name: Optional[str] = None
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Open to suggestions about nullability/validation here.

fmt: Optional[str] = None
samps_per_frame: Optional[int] = None
skew: Optional[int] = None
byte_offset: Optional[int] = None
adc_gain: Optional[float] = None
baseline: Optional[int] = None
units: Optional[str] = None
adc_res: Optional[int] = None
adc_zero: Optional[int] = None
init_value: Optional[int] = None
checksum: Optional[int] = None
block_size: Optional[int] = None
sig_name: Optional[str] = None


class SignalSet:
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No unit tests for this because I need to think more about how these data classes are to be constructed.

"""
Wrapper for a set of signal information. Provides useful access/modify methods.
"""

def __init__(self, signals: List[SignalInfo]):
self._signal_info = signals
try:
self._generate_name_map()
except ValueError:
pass

def _generate_name_map(self):
"""
Generate mapping of channel names to channel indices to allow
for access by both index and name.

Raises
------
ValueError
Raises unless all channel names are present and unique.
"""
self._channel_inds = None
channel_inds = {}

for ch, signal in enumerate(self._signal_info):
sig_name = signal.sig_name
if not sig_name or sig_name in channel_inds:
raise ValueError(
"Cannot generate name map: channel names are not unique"
)
channel_inds[sig_name] = ch

self._channel_inds = channel_inds

def __getitem__(self, key: Union[int, str]):
if isinstance(key, str):
if not self._channel_inds:
raise KeyError("Channel name mapping not available")

return self._signal_info[key]


@dataclass
class SegmentFields:
"""
Segment specification fields for one segment.
"""

seg_name: Optional[str] = None
seg_len: Optional[int] = None


@dataclass
class RecordInfo:
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thought about having a separate class for multi-segment headers but it seemed overkill.

"""
Encapsulates WFDB metadata for a single or multi-segment record.
"""

record_name: Optional[str] = None
n_seg: Optional[int] = None
n_sig: Optional[int] = None
fs: Optional[float] = None
counter_freq: Optional[float] = None
base_counter: Optional[float] = None
sig_len: Optional[int] = None
base_time: Optional[datetime.time] = None
base_date: Optional[datetime.date] = None

# All signal fields are encapsulated under this field
signals: Optional[SignalSet] = None

# Only present for multi-segment headers
segments: List[SegmentFields] = None
comments: List[str] = None

@property
def is_multi(self):
return self.n_seg is not None

@property
def base_datetime(self):
if self.base_date is None or self.base_time is None:
return None
else:
return datetime.datetime.combine(
date=self.base_date, time=self.base_time
)