Skip to content

Commit

Permalink
Move conversion functions to their own modules and remove cyclic impo…
Browse files Browse the repository at this point in the history
…rts (#370)

* Move wav, edf, mat, and csv conversion functions to their own modules

* Move content to remove cyclic dependencies

* Move get_version for a PhysioNet database, to download.py

* Remove rdrecord's ability to read edf and wav files

* Update documentation
  • Loading branch information
cx1111 authored May 4, 2022
1 parent efca603 commit 0d42dfb
Show file tree
Hide file tree
Showing 25 changed files with 3,191 additions and 3,202 deletions.
35 changes: 35 additions & 0 deletions docs/convert.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
Format Conversions
==================

The wfdb.io.convert subpackage contains functions to read and write
non-WFDB files commonly used for waveforms.

CSV
---

.. automodule:: wfdb.io.convert.csv
:members: csv_to_wfdb

EDF
---

.. automodule:: wfdb.io.convert.edf
:members:

Matlab
------

.. automodule:: wfdb.io.convert.matlab
:members:

TFF
---

.. automodule:: wfdb.io.convert.tff
:members:

WAV
---

.. automodule:: wfdb.io.convert.wav
:members:
1 change: 1 addition & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ Other Content

installation
wfdb-specifications
convert


Indices and tables
Expand Down
4 changes: 2 additions & 2 deletions docs/io.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ WFDB Records
---------------

.. automodule:: wfdb.io
:members: rdrecord, rdheader, rdsamp, wrsamp, edf2mit, mit2edf, wav2mit, mit2wav, csv2mit
:members: rdrecord, rdheader, rdsamp, wrsamp

.. autoclass:: wfdb.io.Record
:members: wrsamp, adc, dac
Expand All @@ -32,4 +32,4 @@ Downloading
-----------

.. automodule:: wfdb.io
:members: get_dbs, get_record_list, dl_database, dl_files, set_db_index_url
:members: dl_files, dl_database, get_dbs, get_record_list, set_db_index_url
7 changes: 6 additions & 1 deletion docs/processing.rst
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ Heart Rate
----------

.. automodule:: wfdb.processing
:members: compute_hr
:members: compute_hr, calc_rr, calc_mean_hr, ann2rr, rr2ann


Peaks
Expand All @@ -26,6 +26,11 @@ Peaks
.. automodule:: wfdb.processing
:members: find_peaks, find_local_peaks, correct_peaks

Filters
-------

.. automodule:: wfdb.processing
:members: sigavg

QRS Detectors
-------------
Expand Down
4 changes: 2 additions & 2 deletions docs/wfdb.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ WFDB Records
---------------

.. automodule:: wfdb
:members: rdrecord, rdheader, rdsamp, wrsamp, edf2mit, mit2edf, wav2mit, mit2wav, csv2mit
:members: rdrecord, rdheader, rdsamp, wrsamp

.. autoclass:: wfdb.Record
:members: wrsamp, adc, dac
Expand All @@ -32,7 +32,7 @@ Downloading
-----------

.. automodule:: wfdb
:members: get_dbs, get_record_list, dl_database, dl_files, set_db_index_url
:members: dl_files, dl_database, get_dbs, get_record_list, set_db_index_url


Plotting
Expand Down
110 changes: 110 additions & 0 deletions tests/io/test_convert.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import numpy as np

from wfdb.io.record import rdrecord
from wfdb.io.convert.edf import read_edf


class TestConvert:
def test_edf_uniform(self):
"""
EDF format conversion to MIT for uniform sample rates.
"""
# Uniform sample rates
record_MIT = rdrecord("sample-data/n16").__dict__
record_EDF = read_edf("sample-data/n16.edf").__dict__

fields = list(record_MIT.keys())
# Original MIT format method of checksum is outdated, sometimes
# the same value though
fields.remove("checksum")
# Original MIT format units are less comprehensive since they
# default to mV if unknown.. therefore added more default labels
fields.remove("units")

test_results = []
for field in fields:
# Signal value will be slightly off due to C to Python type conversion
if field == "p_signal":
true_array = np.array(record_MIT[field]).flatten()
pred_array = np.array(record_EDF[field]).flatten()
# Prevent divide by zero warning
for i, v in enumerate(true_array):
if v == 0:
true_array[i] = 1
pred_array[i] = 1
sig_diff = np.abs((pred_array - true_array) / true_array)
sig_diff[sig_diff == -np.inf] = 0
sig_diff[sig_diff == np.inf] = 0
sig_diff = np.nanmean(sig_diff, 0)
# 5% tolerance
if np.max(sig_diff) <= 5:
test_results.append(True)
else:
test_results.append(False)
elif field == "init_value":
signal_diff = [
abs(record_MIT[field][i] - record_EDF[field][i])
for i in range(len(record_MIT[field]))
]
if abs(max(min(signal_diff), max(signal_diff), key=abs)) <= 2:
test_results.append(True)
else:
test_results.append(False)
else:
test_results.append(record_MIT[field] == record_MIT[field])

target_results = len(fields) * [True]
assert np.array_equal(test_results, target_results)

def test_edf_non_uniform(self):
"""
EDF format conversion to MIT for non-uniform sample rates.
"""
# Non-uniform sample rates
record_MIT = rdrecord("sample-data/wave_4").__dict__
record_EDF = read_edf("sample-data/wave_4.edf").__dict__

fields = list(record_MIT.keys())
# Original MIT format method of checksum is outdated, sometimes
# the same value though
fields.remove("checksum")
# Original MIT format units are less comprehensive since they
# default to mV if unknown.. therefore added more default labels
fields.remove("units")

test_results = []
for field in fields:
# Signal value will be slightly off due to C to Python type conversion
if field == "p_signal":
true_array = np.array(record_MIT[field]).flatten()
pred_array = np.array(record_EDF[field]).flatten()
# Prevent divide by zero warning
for i, v in enumerate(true_array):
if v == 0:
true_array[i] = 1
pred_array[i] = 1
sig_diff = np.abs((pred_array - true_array) / true_array)
sig_diff[sig_diff == -np.inf] = 0
sig_diff[sig_diff == np.inf] = 0
sig_diff = np.nanmean(sig_diff, 0)
# 5% tolerance
if np.max(sig_diff) <= 5:
test_results.append(True)
else:
test_results.append(False)
elif field == "init_value":
signal_diff = [
abs(record_MIT[field][i] - record_EDF[field][i])
for i in range(len(record_MIT[field]))
]
if abs(max(min(signal_diff), max(signal_diff), key=abs)) <= 2:
test_results.append(True)
else:
test_results.append(False)
else:
test_results.append(record_MIT[field] == record_MIT[field])

target_results = len(fields) * [True]
assert np.array_equal(test_results, target_results)
106 changes: 1 addition & 105 deletions tests/test_record.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import os
import pdb
import shutil
import unittest

import numpy as np

import wfdb


Expand Down Expand Up @@ -339,110 +339,6 @@ def test_2e(self):
sig_target = sig_target.reshape([977, 1])
assert np.array_equal(sig, sig_target)

def test_2f(self):
"""
EDF format conversion to MIT for uniform sample rates.
"""
# Uniform sample rates
record_MIT = wfdb.rdrecord("sample-data/n16").__dict__
record_EDF = wfdb.edf2mit("sample-data/n16.edf").__dict__

fields = list(record_MIT.keys())
# Original MIT format method of checksum is outdated, sometimes
# the same value though
fields.remove("checksum")
# Original MIT format units are less comprehensive since they
# default to mV if unknown.. therefore added more default labels
fields.remove("units")

test_results = []
for field in fields:
# Signal value will be slightly off due to C to Python type conversion
if field == "p_signal":
true_array = np.array(record_MIT[field]).flatten()
pred_array = np.array(record_EDF[field]).flatten()
# Prevent divide by zero warning
for i, v in enumerate(true_array):
if v == 0:
true_array[i] = 1
pred_array[i] = 1
sig_diff = np.abs((pred_array - true_array) / true_array)
sig_diff[sig_diff == -np.inf] = 0
sig_diff[sig_diff == np.inf] = 0
sig_diff = np.nanmean(sig_diff, 0)
# 5% tolerance
if np.max(sig_diff) <= 5:
test_results.append(True)
else:
test_results.append(False)
elif field == "init_value":
signal_diff = [
abs(record_MIT[field][i] - record_EDF[field][i])
for i in range(len(record_MIT[field]))
]
if abs(max(min(signal_diff), max(signal_diff), key=abs)) <= 2:
test_results.append(True)
else:
test_results.append(False)
else:
test_results.append(record_MIT[field] == record_MIT[field])

target_results = len(fields) * [True]
assert np.array_equal(test_results, target_results)

def test_2g(self):
"""
EDF format conversion to MIT for non-uniform sample rates.
"""
# Non-uniform sample rates
record_MIT = wfdb.rdrecord("sample-data/wave_4").__dict__
record_EDF = wfdb.edf2mit("sample-data/wave_4.edf").__dict__

fields = list(record_MIT.keys())
# Original MIT format method of checksum is outdated, sometimes
# the same value though
fields.remove("checksum")
# Original MIT format units are less comprehensive since they
# default to mV if unknown.. therefore added more default labels
fields.remove("units")

test_results = []
for field in fields:
# Signal value will be slightly off due to C to Python type conversion
if field == "p_signal":
true_array = np.array(record_MIT[field]).flatten()
pred_array = np.array(record_EDF[field]).flatten()
# Prevent divide by zero warning
for i, v in enumerate(true_array):
if v == 0:
true_array[i] = 1
pred_array[i] = 1
sig_diff = np.abs((pred_array - true_array) / true_array)
sig_diff[sig_diff == -np.inf] = 0
sig_diff[sig_diff == np.inf] = 0
sig_diff = np.nanmean(sig_diff, 0)
# 5% tolerance
if np.max(sig_diff) <= 5:
test_results.append(True)
else:
test_results.append(False)
elif field == "init_value":
signal_diff = [
abs(record_MIT[field][i] - record_EDF[field][i])
for i in range(len(record_MIT[field]))
]
if abs(max(min(signal_diff), max(signal_diff), key=abs)) <= 2:
test_results.append(True)
else:
test_results.append(False)
else:
test_results.append(record_MIT[field] == record_MIT[field])

target_results = len(fields) * [True]
assert np.array_equal(test_results, target_results)

# --------------------- 3. Multi-dat records --------------------- #

def test_3a(self):
Expand Down
13 changes: 1 addition & 12 deletions wfdb/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,34 +6,23 @@
rdsamp,
wrsamp,
dl_database,
edf2mit,
mit2edf,
wav2mit,
mit2wav,
wfdb2mat,
csv2mit,
sampfreq,
signame,
wfdbdesc,
wfdbtime,
sigavg,
)
from wfdb.io.annotation import (
Annotation,
rdann,
wrann,
show_ann_labels,
show_ann_classes,
ann2rr,
rr2ann,
csv2ann,
rdedfann,
mrgann,
)
from wfdb.io.download import (
dl_files,
get_dbs,
get_record_list,
dl_files,
set_db_index_url,
)
from wfdb.plot.plot import plot_items, plot_wfdb, plot_all_records
Expand Down
Loading

0 comments on commit 0d42dfb

Please sign in to comment.