diff --git a/README.rst b/README.rst index a7b9fc3..e040120 100644 --- a/README.rst +++ b/README.rst @@ -87,7 +87,7 @@ Additionally functionality as anonymizing, dropping or renaming channels can be signals = np.random.rand(5, 256*300)*200 # 5 minutes of random signal channel_names = ['ch1', 'ch2', 'ch3', 'ch4', 'ch5'] signal_headers = highlevel.make_signal_headers(channel_names, sample_frequency=256) - header = highlevel.make_header(patientname='patient_x', gender='Female') + header = highlevel.make_header(patientname='patient_x', sex='Female') highlevel.write_edf('edf_file.edf', signals, signal_headers, header) # read an edf file diff --git a/demo/readEDFFile.py b/demo/readEDFFile.py index e770b8c..51a15a9 100644 --- a/demo/readEDFFile.py +++ b/demo/readEDFFile.py @@ -20,7 +20,7 @@ # print("patient: %s" % f.getP); # print("recording: %s" % f.getPatientAdditional()) print("patientcode: %s" % f.getPatientCode()) - print("gender: %s" % f.getGender()) + print("sex: %s" % f.getSex()) print("birthdate: %s" % f.getBirthdate()) print("patient_name: %s" % f.getPatientName()) print("patient_additional: %s" % f.getPatientAdditional()) diff --git a/pyedflib/_extensions/_pyedflib.pyx b/pyedflib/_extensions/_pyedflib.pyx index 033938f..cbac76f 100644 --- a/pyedflib/_extensions/_pyedflib.pyx +++ b/pyedflib/_extensions/_pyedflib.pyx @@ -10,7 +10,7 @@ __all__ = ['lib_version', 'CyEdfReader', 'set_patientcode', 'set_starttime_subse 'read_physical_samples', 'close_file', 'set_physical_maximum', 'open_file_writeonly', 'set_patient_additional', 'set_digital_maximum', 'set_birthdate', 'set_digital_minimum', 'write_digital_samples', 'set_equipment', 'set_samples_per_record','set_admincode', 'set_label', - 'tell', 'rewind', 'set_gender','set_physical_dimension', 'set_transducer', 'set_prefilter', + 'tell', 'rewind', 'set_sex','set_physical_dimension', 'set_transducer', 'set_prefilter', 'seek', 'set_startdatetime' ,'set_datarecord_duration', 'set_number_of_annotation_signals', 'open_errors', 'FILETYPE_EDFPLUS', 'FILETYPE_EDF','FILETYPE_BDF','FILETYPE_BDFPLUS', 'write_errors', 'get_number_of_open_files', @@ -270,8 +270,13 @@ cdef class CyEdfReader: def __get__(self): return self.hdr.patientcode + property sex: + def __get__(self): + return self.hdr.gender + property gender: def __get__(self): + warnings.warn('This property is deprecated.', DeprecationWarning, stacklevel=2) return self.hdr.gender property birthdate: @@ -615,10 +620,14 @@ def rewind(handle, edfsignal): """void edfrewind(int handle, int edfsignal)""" c_edf.edfrewind(handle, edfsignal) +def set_sex(handle, sex): + """int edf_set_sex(int handle, int sex)""" + if sex is None: return 0 #don't set sex at all to prevent default 'F' + return c_edf.edf_set_gender(handle, sex) + def set_gender(handle, gender): - """int edf_set_gender(int handle, int gender)""" - if gender is None: return 0 #don't set gender at all to prevent default 'F' - return c_edf.edf_set_gender(handle, gender) + warnings.warn('This function is deprecated.', DeprecationWarning, stacklevel=2) + set_sex(handle, gender) def set_physical_dimension(handle, edfsignal, phys_dim): """int edf_set_physical_dimension(int handle, int edfsignal, const char *phys_dim)""" diff --git a/pyedflib/edfreader.py b/pyedflib/edfreader.py index 592e96d..1a77e8c 100644 --- a/pyedflib/edfreader.py +++ b/pyedflib/edfreader.py @@ -139,8 +139,8 @@ def getHeader(self): return {"technician": self.getTechnician(), "recording_additional": self.getRecordingAdditional(), "patientname": self.getPatientName(), "patient_additional": self.getPatientAdditional(), "patientcode": self.getPatientCode(), "equipment": self.getEquipment(), - "admincode": self.getAdmincode(), "gender": self.getGender(), "startdate": self.getStartdatetime(), - "birthdate": self.getBirthdate()} + "admincode": self.getAdmincode(), "sex": self.getSex(), "startdate": self.getStartdatetime(), + "birthdate": self.getBirthdate(), "gender": self.getSex()} def getSignalHeader(self, chn): """ @@ -307,9 +307,9 @@ def getAdmincode(self): """ return self._convert_string(self.admincode.rstrip()) - def getGender(self): + def getSex(self): """ - Returns the Gender of the patient. + Returns the Sex of the patient. Parameters ---------- @@ -319,12 +319,16 @@ def getGender(self): -------- >>> import pyedflib >>> f = pyedflib.data.test_generator() - >>> f.getGender()=='' + >>> f.getSex()=='' True >>> f.close() """ - return self._convert_string(self.gender.rstrip()) + return self._convert_string(self.sex.rstrip()) + + def getGender(self): + warnings.warn('This method is deprecated.', DeprecationWarning, stacklevel=2) + return self.getSex() def getFileDuration(self): """ diff --git a/pyedflib/edfwriter.py b/pyedflib/edfwriter.py index 13db901..a58319f 100644 --- a/pyedflib/edfwriter.py +++ b/pyedflib/edfwriter.py @@ -126,17 +126,22 @@ def isbytestr(s): return isinstance(s, bytes) -def gender2int(gender): - if isinstance(gender, int) or gender is None: - return gender - elif gender.lower() in ['', 'x', 'xx', 'xxx', 'unknown', '?', '??']: +def sex2int(sex): + if isinstance(sex, int) or sex is None: + return sex + elif sex.lower() in ['', 'x', 'xx', 'xxx', 'unknown', '?', '??']: return None - elif gender.lower() in ["female", "woman", "f", "w"]: + elif sex.lower() in ["female", "woman", "f", "w"]: return 0 - elif gender.lower() in ["male", "man", "m"]: + elif sex.lower() in ["male", "man", "m"]: return 1 else: - raise ValueError(f"Unknown gender: '{gender}'") + raise ValueError(f"Unknown sex: '{sex}'") + + +def gender2int(sex): + warnings.warn('This function is deprecated.', DeprecationWarning, stacklevel=2) + sex2int(sex) class ChannelDoesNotExist(Exception): @@ -195,7 +200,7 @@ def __init__(self, file_name, n_channels, self.recording_additional = '' self.patient_additional = '' self.admincode = '' - self.gender = None + self.sex = None self.recording_start_time = datetime.now().replace(microsecond=0) self.birthdate = '' @@ -228,13 +233,13 @@ def update_header(self): """ # some checks that warn users if header fields exceed 80 chars patient_ident = len(self.patient_code) + len(self.patient_name) \ - + len(self.patient_additional) + 3 + 1 + 11 # 3 spaces 1 gender 11 birthdate + + len(self.patient_additional) + 3 + 1 + 11 # 3 spaces 1 sex 11 birthdate record_ident = len(self.equipment) + len(self.technician) \ + len(self.admincode) + len(self.recording_additional) \ + len('Startdate') + 3 + 11 # 3 spaces 11 birthdate if patient_ident>80: - warnings.warn('Patient code, name, gender and birthdate combined must not be larger than 80 chars. ' + + warnings.warn('Patient code, name, sex and birthdate combined must not be larger than 80 chars. ' + f'Currently has len of {patient_ident}. See https://www.edfplus.info/specs/edfplus.html#additionalspecs') if record_ident>80: warnings.warn('Equipment, technician, admincode and recording_additional combined must not be larger than 80 chars. ' + @@ -254,7 +259,7 @@ def update_header(self): set_patient_additional(self.handle, du(self.patient_additional)) set_equipment(self.handle, du(self.equipment)) set_admincode(self.handle, du(self.admincode)) - set_gender(self.handle, gender2int(self.gender)) + set_gender(self.handle, sex2int(self.sex)) set_datarecord_duration(self.handle, self.record_duration) set_number_of_annotation_signals(self.handle, self.number_of_annotations) @@ -294,7 +299,7 @@ def setHeader(self, fileHeader): self.patient_code = fileHeader["patientcode"] self.equipment = fileHeader["equipment"] self.admincode = fileHeader["admincode"] - self.gender = fileHeader["gender"] + self.sex = fileHeader["sex"] self.recording_start_time = fileHeader["startdate"] self.birthdate = fileHeader["birthdate"] self.update_header() @@ -440,17 +445,17 @@ def setAdmincode(self, admincode): self.admincode = admincode self.update_header() - def setGender(self, gender): + def setSex(self, sex): """ - Sets the gender. + Sets the sex. This function is optional and can be called only after opening a file in writemode and before the first sample write action. Parameters ---------- - gender : int + sex : int 1 is male, 0 is female """ - self.gender = gender2int(gender) + self.sex = sex2int(sex) self.update_header() def setDatarecordDuration(self, record_duration): diff --git a/pyedflib/highlevel.py b/pyedflib/highlevel.py index e884814..b17467e 100644 --- a/pyedflib/highlevel.py +++ b/pyedflib/highlevel.py @@ -147,7 +147,8 @@ def phys2dig(signal, dmin, dmax, pmin, pmax): def make_header(technician='', recording_additional='', patientname='', patient_additional='', patientcode= '', equipment= '', - admincode= '', gender= '', startdate=None, birthdate= ''): + admincode= '', sex= '', startdate=None, birthdate= '', + gender=None): """ A convenience function to create an EDF header (a dictionary) that can be used by pyedflib to update the main header of the EDF @@ -168,8 +169,8 @@ def make_header(technician='', recording_additional='', patientname='', which system was used. The default is ''. admincode : str, optional code of the admin. The default is ''. - gender : str, optional - gender of patient. The default is ''. + sex : str, optional + sex of patient. The default is ''. startdate : datetime.datetime, optional startdate of recording. The default is None. birthdate : str/datetime.datetime, optional @@ -191,13 +192,23 @@ def make_header(technician='', recording_additional='', patientname='', del now if isinstance(birthdate, datetime): birthdate = birthdate.strftime('%d %b %Y').lower() + + # backwards compatibility + if gender is not None: + if sex == '': + sex = gender + elif gender != sex: + raise ValueError(f'Passed both sex and gender, with different values: "{sex}" != "{gender}"') + del gender + local = locals() + header = {} - for var in local: - if isinstance(local[var], datetime): - header[var] = local[var] + for var, value in local.items(): + if isinstance(value, datetime): + header[var] = value else: - header[var] = str(local[var]) + header[var] = str(value) return header diff --git a/pyedflib/tests/test_edfreader.py b/pyedflib/tests/test_edfreader.py index e22565a..9c5f9a0 100644 --- a/pyedflib/tests/test_edfreader.py +++ b/pyedflib/tests/test_edfreader.py @@ -178,6 +178,7 @@ def test_EdfReader_headerInfos(self): np.testing.assert_equal(f.getStartdatetime(),datetimeSoll) np.testing.assert_equal(f.getPatientCode(), 'abcxyz99') np.testing.assert_equal(f.getPatientName(), 'Hans Muller') + np.testing.assert_equal(f.getSex(), 'Male') np.testing.assert_equal(f.getGender(), 'Male') np.testing.assert_equal(f.getBirthdate(), '30 jun 1969') np.testing.assert_equal(f.getPatientAdditional(), 'patient') @@ -381,7 +382,7 @@ def test_EdfReader_Legacy_Header_Info(self): 'patientcode': b'', 'equipment': b'', 'admincode': b'', - 'gender': b'', + 'sex': b'', 'birthdate': b'' } diff --git a/pyedflib/tests/test_edfwriter.py b/pyedflib/tests/test_edfwriter.py index ad8456a..51eb13c 100644 --- a/pyedflib/tests/test_edfwriter.py +++ b/pyedflib/tests/test_edfwriter.py @@ -139,7 +139,7 @@ def test_subsecond_starttime(self): startdate = datetime(2017, 1, 2, 13, 14, 15, 250) header = {'technician': 'tec1', 'recording_additional': 'recAdd1', 'patientname': 'pat1', 'patient_additional': 'patAdd1', 'patientcode': 'code1', 'equipment': 'eq1', - 'admincode':'admin1','gender':1,'startdate':startdate,'birthdate':date(1951, 8, 2)} + 'admincode':'admin1','sex':1,'startdate':startdate,'birthdate':date(1951, 8, 2)} f.setHeader(header) f.setStartdatetime(startdate) f.setSignalHeader(0, channel_info) @@ -211,7 +211,7 @@ def test_EdfWriter_BDFplus(self): f.setPatientAdditional('patAdd1') f.setAdmincode('admin1') f.setEquipment('eq1') - f.setGender(1) + f.setSex(1) f.setBirthdate(date(1951, 8, 2)) # f.setBirthdate('2.8.1951') startdate = datetime(2017, 1, 1, 1, 1, 1) @@ -240,6 +240,7 @@ def test_EdfWriter_BDFplus(self): np.testing.assert_equal(f.getPatientAdditional(), 'patAdd1') np.testing.assert_equal(f.getAdmincode(), 'admin1') np.testing.assert_equal(f.getEquipment(), 'eq1') + np.testing.assert_equal(f.getSex(), 'Male') np.testing.assert_equal(f.getGender(), 'Male') np.testing.assert_equal(f.getBirthdate(), '02 aug 1951') np.testing.assert_equal(f.getStartdatetime(), datetime(2017, 1, 1, 1, 1, 1)) @@ -281,7 +282,7 @@ def test_EdfWriter_BDFplus2(self): f.setPatientAdditional('patAdd1') f.setAdmincode('admin1') f.setEquipment('eq1') - f.setGender("Male") + f.setSex("Male") f.setBirthdate(date(1951, 8, 2)) f.setStartdatetime(datetime(2017, 1, 1, 1, 1, 1)) f.setSamplefrequency(1,100) @@ -304,6 +305,7 @@ def test_EdfWriter_BDFplus2(self): np.testing.assert_equal(f.getPatientAdditional(), 'patAdd1') np.testing.assert_equal(f.getAdmincode(), 'admin1') np.testing.assert_equal(f.getEquipment(), 'eq1') + np.testing.assert_equal(f.getSex(), 'Male') np.testing.assert_equal(f.getGender(), 'Male') np.testing.assert_equal(f.getBirthdate(), '02 aug 1951') np.testing.assert_equal(f.getStartdatetime(), datetime(2017, 1, 1, 1, 1, 1)) @@ -361,7 +363,7 @@ def test_EdfWriter_EDFplus(self): header = {'technician': 'tec1', 'recording_additional': 'recAdd1', 'patientname': 'pat1', 'patient_additional': 'patAdd1', 'patientcode': 'code1', 'equipment': 'eq1', - 'admincode':'admin1','gender':1,'startdate':datetime(2017, 1, 1, 1, 1, 1),'birthdate':date(1951, 8, 2)} + 'admincode':'admin1','sex':1,'startdate':datetime(2017, 1, 1, 1, 1, 1),'birthdate':date(1951, 8, 2)} f.setHeader(header) f.setSignalHeader(0,channel_info) data = np.ones(100) * 0.1 @@ -377,6 +379,7 @@ def test_EdfWriter_EDFplus(self): np.testing.assert_equal(f.getEquipment(), 'eq1') np.testing.assert_equal(f.getPatientAdditional(), 'patAdd1') np.testing.assert_equal(f.getAdmincode(), 'admin1') + np.testing.assert_equal(f.getSex(), 'Male') np.testing.assert_equal(f.getGender(), 'Male') np.testing.assert_equal(f.getBirthdate(), '02 aug 1951') np.testing.assert_equal(f.getStartdatetime(), datetime(2017, 1, 1, 1, 1, 1)) @@ -767,7 +770,7 @@ def test_physical_range_inequality(self): self.assertRaises(AssertionError, f.writeSamples, [channel_data1, channel_data2]) - def test_gender_setting_correctly(self): + def test_sex_setting_correctly(self): channel_info1 = {'label': 'test_label1', 'dimension': 'mV', 'sample_frequency': 100, 'physical_max': 3.0, 'physical_min': -3.0, 'digital_max': 32767, 'digital_min': -32768, @@ -777,13 +780,13 @@ def test_gender_setting_correctly(self): 'digital_max': 32767, 'digital_min': -32768, 'prefilter': 'pre1', 'transducer': 'trans1'} - gender_mapping = {'X': '', 'XX':'', 'XXX':'', '?':'', None:'', - 'M': 'Male', 'male':'Male', 'man':'Male', 1:'Male', - 'F':'Female', 'female':'Female', 0:'Female'} + sex_mapping = {'X': '', 'XX':'', 'XXX':'', '?':'', None:'', + 'M': 'Male', 'male':'Male', 'man':'Male', 1:'Male', + 'F':'Female', 'female':'Female', 0:'Female'} - for gender, expected in gender_mapping.items(): + for sex, expected in sex_mapping.items(): f = pyedflib.EdfWriter(self.edf_data_file, 2, file_type=pyedflib.FILETYPE_EDFPLUS) - f.setGender(gender) + f.setSex(sex) f.setSignalHeader(0, channel_info1) f.setSignalHeader(1, channel_info2) data = np.ones(100) * 0.1 @@ -795,8 +798,31 @@ def test_gender_setting_correctly(self): np.testing.assert_equal(f.getLabel(0), 'test_label1') np.testing.assert_equal(f.getPhysicalDimension(0), 'mV') np.testing.assert_equal(f.getSampleFrequency(0), 100) + self.assertEqual(f.getSex(), expected, + f'set {sex}, but {expected}!={f.getSex()} ') self.assertEqual(f.getGender(), expected, - f'set {gender}, but {expected}!={f.getGender()} ') + f'set {sex}, but {expected}!={f.getSex()} ') + del f + + # Test deprecated setGender/getGender + for sex, expected in sex_mapping.items(): + f = pyedflib.EdfWriter(self.edf_data_file, 2, file_type=pyedflib.FILETYPE_EDFPLUS) + f.setGender(sex) + f.setSignalHeader(0, channel_info1) + f.setSignalHeader(1, channel_info2) + data = np.ones(100) * 0.1 + assert f.writePhysicalSamples(data)==0, 'error while writing physical sample' + assert f.writePhysicalSamples(data)==0, 'error while writing physical sample' + del f + + f = pyedflib.EdfReader(self.edf_data_file) + np.testing.assert_equal(f.getLabel(0), 'test_label1') + np.testing.assert_equal(f.getPhysicalDimension(0), 'mV') + np.testing.assert_equal(f.getSampleFrequency(0), 100) + self.assertEqual(f.getSex(), expected, + f'set {sex}, but {expected}!={f.getSex()} ') + self.assertEqual(f.getGender(), expected, + f'set {sex}, but {expected}!={f.getSex()} ') del f def test_non_one_second_record_duration(self): @@ -917,7 +943,7 @@ def test_EdfWriter_more_than_80_chars(self): header = {'birthdate': '', 'startdate': datetime(2021, 6, 26, 13, 16, 1), - 'gender': '', + 'sex': '', 'admincode': '', 'equipment': '', 'patientcode': 'x'*40, @@ -936,7 +962,7 @@ def test_EdfWriter_more_than_80_chars(self): header = {'birthdate': '', 'startdate': datetime(2021, 6, 26, 13, 16, 1), - 'gender': '', + 'sex': '', 'admincode': '', 'equipment': 'e'*20, 'patientcode': 'x', diff --git a/pyedflib/tests/test_highlevel.py b/pyedflib/tests/test_highlevel.py index c9bc3ae..680bb5c 100644 --- a/pyedflib/tests/test_highlevel.py +++ b/pyedflib/tests/test_highlevel.py @@ -70,7 +70,7 @@ def test_read_write_edf(self): header = highlevel.make_header(technician='tech', recording_additional='r_add', patientname='name', patient_additional='p_add', patientcode='42', equipment='eeg', admincode='120', - gender='Male', startdate=startdate,birthdate='05.09.1980') + sex='Male', startdate=startdate,birthdate='05.09.1980') annotations = [[0.01, -1, 'begin'],[0.5, -1, 'middle'],[10, -1, 'end']] signal_headers1 = highlevel.make_signal_headers(['ch'+str(i) for i in range(5)]) @@ -242,6 +242,7 @@ def test_read_header(self): self.assertEqual(header['admincode'], 'Dr. X') self.assertEqual(header['birthdate'], '30 jun 1969') self.assertEqual(header['equipment'], 'test generator') + self.assertEqual(header['sex'], 'Male') self.assertEqual(header['gender'], 'Male') self.assertEqual(header['patient_additional'], 'patient') self.assertEqual(header['patientcode'], 'abcxyz99') @@ -254,7 +255,7 @@ def test_anonymize(self): header = highlevel.make_header(technician='tech', recording_additional='radd', patientname='name', patient_additional='padd', patientcode='42', equipment='eeg', admincode='420', - gender='Male', birthdate='05.09.1980') + sex='Male', birthdate='05.09.1980') annotations = [[0.01, -1, 'begin'],[0.5, -1, 'middle'],[10, -1, 'end']] header['annotations'] = annotations signal_headers = highlevel.make_signal_headers(['ch'+str(i) for i in range(3)]) @@ -325,7 +326,7 @@ def test_annotation_bytestring(self): header = highlevel.make_header(technician='tech', recording_additional='radd', patientname='name', patient_additional='padd', patientcode='42', equipment='eeg', admincode='420', - gender='Male', birthdate='05.09.1980') + sex='Male', birthdate='05.09.1980') annotations = [[0.01, b'-1', 'begin'],[0.5, b'-1', 'middle'],[10, -1, 'end']] header['annotations'] = annotations signal_headers = highlevel.make_signal_headers(['ch'+str(i) for i in range(3)])