@@ -1706,6 +1706,278 @@ def wav2mit(record_name, pn_dir=None, delete_file=True, record_only=False):
1706
1706
pass
1707
1707
1708
1708
1709
+ def wfdb2mat (record_name , pn_dir = None , sampfrom = 0 , sampto = None , channels = None ):
1710
+ """
1711
+ This program converts the signals of any PhysioNet record (or one in any
1712
+ compatible format) into a .mat file that can be read directly using any version
1713
+ of Matlab, and a short text file containing information about the signals
1714
+ (names, gains, baselines, units, sampling frequency, and start time/date if
1715
+ known). If the input record name is REC, the output files are RECm.mat and
1716
+ RECm.hea. The output files can also be read by any WFDB application as record
1717
+ RECm.
1718
+
1719
+ This program does not convert annotation files; for that task, 'rdann' is
1720
+ recommended.
1721
+
1722
+ The output .mat file contains a single matrix named `val` containing raw
1723
+ (unshifted, unscaled) samples from the selected record. Using various options,
1724
+ you can select any time interval within a record, or any subset of the signals,
1725
+ which can be rearranged as desired within the rows of the matrix. Since .mat
1726
+ files are written in column-major order (i.e., all of column n precedes all of
1727
+ column n+1), each vector of samples is written as a column rather than as a
1728
+ row, so that the column number in the .mat file equals the sample number in the
1729
+ input record (minus however many samples were skipped at the beginning of the
1730
+ record, as specified using the `start_time` option). If this seems odd, transpose
1731
+ your matrix after reading it!
1732
+
1733
+ This program writes version 5 MAT-file format output files, as documented in
1734
+ http://www.mathworks.com/access/helpdesk/help/pdf_doc/matlab/matfile_format.pdf
1735
+ The samples are written as 32-bit signed integers (mattype=20 below) in
1736
+ little-endian format if the record contains any format 24 or format 32 signals,
1737
+ as 8-bit unsigned integers (mattype=50) if the record contains only format 80
1738
+ signals, or as 16-bit signed integers in little-endian format (mattype=30)
1739
+ otherwise.
1740
+
1741
+ The maximum size of the output variable is 2^31 bytes. `wfdb2mat` from versions
1742
+ 10.5.24 and earlier of the original WFDB software package writes version 4 MAT-
1743
+ files which have the additional constraint of 100,000,000 elements per variable.
1744
+
1745
+ The output files (recordm.mat + recordm.hea) are still WFDB-compatible, given
1746
+ the .hea file constructed by this program.
1747
+
1748
+ Parameters
1749
+ ----------
1750
+ record_name : str
1751
+ The name of the input WFDB record to be read. Can also work with both
1752
+ EDF and WAV files.
1753
+ pn_dir : str, optional
1754
+ Option used to stream data from Physionet. The Physionet
1755
+ database directory from which to find the required record files.
1756
+ eg. For record '100' in 'http://physionet.org/content/mitdb'
1757
+ pn_dir='mitdb'.
1758
+ sampfrom : int, optional
1759
+ The starting sample number to read for all channels.
1760
+ sampto : int, 'end', optional
1761
+ The sample number at which to stop reading for all channels.
1762
+ Reads the entire duration by default.
1763
+ channels : list, optional
1764
+ List of integer indices specifying the channels to be read.
1765
+ Reads all channels by default.
1766
+
1767
+ Returns
1768
+ -------
1769
+ N/A
1770
+
1771
+ Notes
1772
+ -----
1773
+ The entire file is composed of:
1774
+
1775
+ Bytes 0 - 127: descriptive text
1776
+ Bytes 128 - 131: master tag (data type = matrix)
1777
+ Bytes 132 - 135: master tag (data size)
1778
+ Bytes 136 - 151: array flags (4 byte tag with data type, 4 byte
1779
+ tag with subelement size, 8 bytes of content)
1780
+ Bytes 152 - 167: array dimension (4 byte tag with data type, 4
1781
+ byte tag with subelement size, 8 bytes of content)
1782
+ Bytes 168 - 183: array name (4 byte tag with data type, 4 byte
1783
+ tag with subelement size, 8 bytes of content)
1784
+ Bytes 184 - ...: array content (4 byte tag with data type, 4 byte
1785
+ tag with subelement size, ... bytes of content)
1786
+
1787
+ Examples
1788
+ --------
1789
+ >>> wfdb.wfdb2mat('100', pn_dir='pwave')
1790
+
1791
+ The output file name is 100m.mat and 100m.hea
1792
+
1793
+ """
1794
+ record = rdrecord (record_name , pn_dir = pn_dir , sampfrom = sampfrom , sampto = sampto )
1795
+ record_name_out = record_name .split (os .sep )[- 1 ].replace ('-' ,'_' ) + 'm'
1796
+
1797
+ # Some variables describing the format of the .mat file
1798
+ field_version = 256 # 0x0100 or 256
1799
+ endian_indicator = b'IM' # little endian
1800
+ master_type = 14 # matrix
1801
+ sub1_type = 6 # UINT32
1802
+ sub2_type = 5 # INT32
1803
+ sub3_type = 1 # INT8
1804
+ sub1_class = 6 # double precision array
1805
+
1806
+ # Determine if we can write 8-bit unsigned samples, or if 16 or 32 bits
1807
+ # are needed per sample
1808
+ bytes_per_element = 1
1809
+ for i in range (record .n_sig ):
1810
+ if (record .adc_res [i ] > 0 ):
1811
+ if (record .adc_res [i ] > 16 ):
1812
+ bytes_per_element = 4
1813
+ elif (record .adc_res [i ] > 8 ) and (bytes_per_element < 2 ):
1814
+ bytes_per_element = 2
1815
+ else :
1816
+ # adc_res not specified.. try to guess from format
1817
+ if (record .fmt [i ] == '24' ) or (record .fmt [i ] == '32' ):
1818
+ bytes_per_element = 4
1819
+ elif (record .fmt [i ] != '80' ) and (bytes_per_element < 2 ):
1820
+ bytes_per_element = 2
1821
+
1822
+ if (bytes_per_element == 1 ):
1823
+ sub4_type = 2 # MAT8
1824
+ out_type = '<u1' # np.uint8
1825
+ wfdb_type = '80' # Offset binary form (80)
1826
+ offset = 128 # Offset between sample values and the raw
1827
+ # byte/word values as interpreted by Matlab/Octave
1828
+ elif (bytes_per_element == 2 ):
1829
+ sub4_type = 3 # MAT16
1830
+ out_type = '<i2' # np.int16
1831
+ wfdb_type = '16' # Align with byte boundary (16)
1832
+ offset = 0 # Offset between sample values and the raw
1833
+ # byte/word values as interpreted by Matlab/Octave
1834
+ else :
1835
+ sub4_type = 5 # MAT32
1836
+ out_type = '<i4' # np.int32
1837
+ wfdb_type = '32' # Align with byte boundary (32)
1838
+ offset = 0 # Offset between sample values and the raw
1839
+ # byte/word values as interpreted by Matlab/Octave
1840
+
1841
+ # Ensure the signal size does not exceed the 2^31 byte limit
1842
+ max_length = int ((2 ** 31 ) / bytes_per_element / record .n_sig )
1843
+ if sampto is None :
1844
+ sampto = record .p_signal .shape [0 ]
1845
+ desired_length = sampto - sampfrom
1846
+ # Snip record
1847
+ if desired_length > max_length :
1848
+ raise Exception ("Can't write .mat file: data size exceeds 2GB limit" )
1849
+
1850
+ # Bytes of actual data
1851
+ bytes_of_data = bytes_per_element * record .n_sig * desired_length
1852
+ # This is the remaining number of bytes that don't fit into integer
1853
+ # multiple of 8: i.e. if 18 bytes, bytes_remain = 2, from 17 to 18
1854
+ bytes_remain = bytes_of_data % 8
1855
+
1856
+ # master_bytes = (8 + 8) + (8 + 8) + (8 + 8) + (8 + bytes_of_data) + padding
1857
+ # Must be integer multiple 8
1858
+ if bytes_remain == 0 :
1859
+ master_bytes = bytes_of_data + 56
1860
+ else :
1861
+ master_bytes = bytes_of_data + 64 - (bytes_remain )
1862
+
1863
+ # Start writing the file
1864
+ output_file = record_name_out + '.mat'
1865
+ with open (output_file , 'wb' ) as f :
1866
+ # Descriptive text (124 bytes)
1867
+ f .write (struct .pack ('<124s' , b'MATLAB 5.0' ))
1868
+ # Version (2 bytes)
1869
+ f .write (struct .pack ('<H' , field_version ))
1870
+ # Endian indicator (2 bytes)
1871
+ f .write (struct .pack ('<2s' , endian_indicator ))
1872
+
1873
+ # Master tag data type (4 bytes)
1874
+ f .write (struct .pack ('<I' , master_type ))
1875
+ # Master tag number of bytes (4 bytes)
1876
+ # Number of bytes of data element
1877
+ # = (8 + 8) + (8 + 8) + (8 + 8) + (8 + bytes_of_data)
1878
+ # = 56 + bytes_of_data
1879
+ f .write (struct .pack ('<I' , master_bytes ))
1880
+
1881
+ # Matrix data has 4 subelements (5 if imaginary):
1882
+ # Array flags, dimensions array, array name, real part
1883
+ # Each subelement has its own subtag, and subdata
1884
+
1885
+ # Subelement 1: Array flags
1886
+ # Subtag 1: data type (4 bytes)
1887
+ f .write (struct .pack ('<I' , sub1_type ))
1888
+ # Subtag 1: number of bytes (4 bytes)
1889
+ f .write (struct .pack ('<I' , 8 ))
1890
+ # Value class indication the MATLAB data type (8 bytes)
1891
+ f .write (struct .pack ('<Q' , sub1_class ))
1892
+
1893
+ # Subelement 2: Rows and columns
1894
+ # Subtag 2: data type (4 bytes)
1895
+ f .write (struct .pack ('<I' , sub2_type ))
1896
+ # Subtag 2: number of bytes (4 bytes)
1897
+ f .write (struct .pack ('<I' , 8 ))
1898
+ # Number of signals (4 bytes)
1899
+ f .write (struct .pack ('<I' , record .n_sig ))
1900
+ # Number of rows (4 bytes)
1901
+ f .write (struct .pack ('<I' , desired_length ))
1902
+
1903
+ # Subelement 3: Array name
1904
+ # Subtag 3: data type (4 bytes)
1905
+ f .write (struct .pack ('<I' , sub3_type ))
1906
+ # Subtag 3: number of bytes (4 bytes)
1907
+ f .write (struct .pack ('<I' , 3 ))
1908
+ # Subtag 3: name of the array (8 bytes)
1909
+ f .write (struct .pack ('<8s' , b'val' ))
1910
+
1911
+ # Subelement 4: Signal data
1912
+ # Subtag 4: data type (4 bytes)
1913
+ f .write (struct .pack ('<I' , sub4_type ))
1914
+ # Subtag 4: number of bytes (4 bytes)
1915
+ f .write (struct .pack ('<I' , bytes_of_data ))
1916
+
1917
+ # Total size of everything before actual data:
1918
+ # 128 byte header
1919
+ # + 8 byte master tag
1920
+ # + 56 byte subelements (48 byte default + 8 byte name)
1921
+ # = 192
1922
+
1923
+ # Copy the selected data into the .mat file
1924
+ out_data = record .p_signal * record .adc_gain + record .baseline - record .adc_zero
1925
+ # Cast the data to the correct type base on the bytes_per_element
1926
+ out_data = np .around (out_data ).astype (out_type )
1927
+ # out_data should be [r1c1, r1c2, r2c1, r2c2, etc.]
1928
+ out_data = out_data .flatten ()
1929
+ out_fmt = '<%sh' % len (out_data )
1930
+ f .write (struct .pack (out_fmt , * out_data ))
1931
+
1932
+ # Display some useful information
1933
+ if record .base_time is None :
1934
+ if record .base_date is None :
1935
+ datetime_string = '[None]'
1936
+ else :
1937
+ datetime_string = '[{}]' .format (record .base_date .strftime ('%d/%m/%Y' ))
1938
+ else :
1939
+ if record .base_date is None :
1940
+ datetime_string = '[{}]' .format (record .base_time .strftime ('%H:%M:%S.%f' ))
1941
+ else :
1942
+ datetime_string = '[{} {}]' .format (record .base_time .strftime ('%H:%M:%S.%f' ),
1943
+ record .base_date .strftime ('%d/%m/%Y' ))
1944
+
1945
+ print ('Source: record {}\t \t Start: {}' .format (record_name , datetime_string ))
1946
+ print ('val has {} rows (signals) and {} columns (samples/signal)' .format (record .n_sig ,
1947
+ desired_length ))
1948
+ duration_string = str (datetime .timedelta (seconds = desired_length / record .fs ))
1949
+ print ('Duration: {}' .format (duration_string ))
1950
+ print ('Sampling frequency: {} Hz\t Sampling interval: {} sec' .format (record .fs ,
1951
+ 1 / record .fs ))
1952
+ print ('{:<7}{:<20}{:<17}{:<10}{:<10}' .format ('Row' ,'Signal' ,'Gain' ,'Base' ,'Units' ))
1953
+ record .sig_name = [s .replace (' ' ,'_' ) for s in record .sig_name ]
1954
+ for i in range (record .n_sig ):
1955
+ print ('{:<7}{:<20}{:<17}{:<10}{:<10}' .format (i ,
1956
+ record .sig_name [i ],
1957
+ record .adc_gain [i ],
1958
+ record .baseline [i ]- record .adc_zero [i ]+ offset ,
1959
+ record .units [i ]))
1960
+
1961
+ # Modify the record file to reflect the new data
1962
+ num_channels = record .n_sig if (channels is None ) else len (channels )
1963
+ record .record_name = record_name_out
1964
+ record .n_sig = num_channels
1965
+ record .samps_per_frame = num_channels * [1 ]
1966
+ record .file_name = num_channels * [output_file ]
1967
+ record .fmt = num_channels * [wfdb_type ]
1968
+ record .byte_offset = num_channels * [192 ]
1969
+ record .baseline = [b - record .adc_zero [i ] for i ,b in enumerate (record .baseline )]
1970
+ record .adc_zero = num_channels * [0 ]
1971
+ record .init_value = out_data [:record .n_sig ].tolist ()
1972
+
1973
+ # Write the header file RECm.hea
1974
+ record .wrheader ()
1975
+ # Append the following lines to create a signature
1976
+ with open (record_name_out + '.hea' ,'a' ) as f :
1977
+ f .write ('#Creator: wfdb2mat\n ' )
1978
+ f .write ('#Source: record {}\n ' .format (record_name ))
1979
+
1980
+
1709
1981
#------------------------- Reading Records --------------------------- #
1710
1982
1711
1983
0 commit comments