From 7aea9a9d3f5673a751f5e60b35694cdf7f960862 Mon Sep 17 00:00:00 2001 From: Peter Somhorst Date: Mon, 16 Dec 2024 15:07:24 +0100 Subject: [PATCH 01/10] Add parametrization for the frame size and medibus fields --- eitprocessing/datahandling/loading/draeger.py | 147 ++++++++++-------- 1 file changed, 80 insertions(+), 67 deletions(-) diff --git a/eitprocessing/datahandling/loading/draeger.py b/eitprocessing/datahandling/loading/draeger.py index 0f8affa1a..508bd0d84 100644 --- a/eitprocessing/datahandling/loading/draeger.py +++ b/eitprocessing/datahandling/loading/draeger.py @@ -22,7 +22,6 @@ from numpy.typing import NDArray -_FRAME_SIZE_BYTES = 4358 load_draeger_data = partial(load_eit_data, vendor=Vendor.DRAEGER) @@ -34,15 +33,21 @@ def load_from_single_path( ) -> dict[str, DataCollection]: """Load Dräger EIT data from path.""" file_size = path.stat().st_size - if file_size % _FRAME_SIZE_BYTES: + + for _file_format_data in _bin_file_formats.values(): + frame_size: int = _file_format_data["frame_size"] + if file_size % frame_size == 0: + medibus_fields = _file_format_data["medibus_fields"] + break + else: msg = ( - f"File size {file_size} of file {path!s} not divisible by {_FRAME_SIZE_BYTES}.\n" + f"File size {file_size} of file {path!s} does not match the supported *.bin file formats.\n" "Currently this package does not support loading files containing " "esophageal pressure or other non-standard data. " "Make sure this is a valid and uncorrupted Dräger data file." ) raise OSError(msg) - total_frames = file_size // _FRAME_SIZE_BYTES + total_frames = file_size // frame_size if (f0 := first_frame) > (fn := total_frames): msg = f"Invalid input: `first_frame` ({f0}) is larger than the total number of frames in the file ({fn})." @@ -68,10 +73,10 @@ def load_from_single_path( time = np.zeros((n_frames,)) events: list[tuple[float, Event]] = [] phases: list[tuple[float, int]] = [] - medibus_data = np.zeros((52, n_frames)) + medibus_data = np.zeros((len(medibus_fields), n_frames)) with path.open("br") as fo, mmap.mmap(fo.fileno(), length=0, access=mmap.ACCESS_READ) as fh: - fh.seek(first_frame_to_load * _FRAME_SIZE_BYTES) + fh.seek(first_frame_to_load * frame_size) reader = BinReader(fh) previous_marker = None @@ -83,6 +88,7 @@ def load_from_single_path( time, pixel_impedance, medibus_data, + len(medibus_fields), events, phases, previous_marker, @@ -117,7 +123,7 @@ def load_from_single_path( ( continuousdata_collection, sparsedata_collection, - ) = _convert_medibus_data(medibus_data, time, sample_frequency) + ) = _convert_medibus_data(medibus_data, medibus_fields, time, sample_frequency) intervaldata_collection = DataCollection(IntervalData) # TODO: move some medibus data to sparse / interval # TODO: move phases and events to sparse / interval @@ -182,13 +188,14 @@ def load_from_single_path( def _convert_medibus_data( medibus_data: NDArray, + medibus_fields: list, time: NDArray, sample_frequency: float, ) -> tuple[DataCollection, DataCollection]: continuousdata_collection = DataCollection(ContinuousData) sparsedata_collection = DataCollection(SparseData) - for field_info, data in zip(_medibus_fields, medibus_data, strict=True): + for field_info, data in zip(medibus_fields, medibus_data, strict=True): if field_info.continuous: continuous_data = ContinuousData( label=field_info.signal_name, @@ -216,6 +223,7 @@ def _read_frame( time: NDArray, pixel_impedance: NDArray, medibus_data: NDArray, + n_medibus_fields: int, events: list, phases: list, previous_marker: int | None, @@ -236,7 +244,7 @@ def _read_frame( event_text = reader.string(length=30) timing_error = reader.int32() - frame_medibus_data = reader.npfloat32(length=52) + frame_medibus_data = reader.npfloat32(length=n_medibus_fields) if index < 0: # do not keep any loaded data, just return the event marker @@ -266,61 +274,66 @@ class _MedibusField(NamedTuple): continuous: bool -_medibus_fields = [ - _MedibusField("airway pressure", "mbar", True), - _MedibusField("flow", "L/min", True), - _MedibusField("volume", "mL", True), - _MedibusField("CO2 (%)", "%", True), - _MedibusField("CO2 (kPa)", "kPa", True), - _MedibusField("CO2 (mmHg)", "mmHg", True), - _MedibusField("dynamic compliance", "mL/mbar", False), - _MedibusField("resistance", "mbar/L/s", False), - _MedibusField("r^2", "", False), - _MedibusField("spontaneous inspiratory time", "s", False), - _MedibusField("minimal pressure", "mbar", False), - _MedibusField("P0.1", "mbar", False), - _MedibusField("mean pressure", "mbar", False), - _MedibusField("plateau pressure", "mbar", False), - _MedibusField("PEEP", "mbar", False), - _MedibusField("intrinsic PEEP", "mbar", False), - _MedibusField("mandatory respiratory rate", "/min", False), - _MedibusField("mandatory minute volume", "L/min", False), - _MedibusField("peak inspiratory pressure", "mbar", False), - _MedibusField("mandatory tidal volume", "L", False), - _MedibusField("spontaneous tidal volume", "L", False), - _MedibusField("trapped volume", "mL", False), - _MedibusField("mandatory expiratory tidal volume", "mL", False), - _MedibusField("spontaneous expiratory tidal volume", "mL", False), - _MedibusField("mandatory inspiratory tidal volume", "mL", False), - _MedibusField("tidal volume", "mL", False), - _MedibusField("spontaneous inspiratory tidal volume", "mL", False), - _MedibusField("negative inspiratory force", "mbar", False), - _MedibusField("leak minute volume", "L/min", False), - _MedibusField("leak percentage", "%", False), - _MedibusField("spontaneous respiratory rate", "/min", False), - _MedibusField("percentage of spontaneous minute volume", "%", False), - _MedibusField("spontaneous minute volume", "L/min", False), - _MedibusField("minute volume", "L/min", False), - _MedibusField("airway temperature", "degrees C", False), - _MedibusField("rapid shallow breating index", "1/min/L", False), - _MedibusField("respiratory rate", "/min", False), - _MedibusField("inspiratory:expiratory ratio", "", False), - _MedibusField("CO2 flow", "mL/min", False), - _MedibusField("dead space volume", "mL", False), - _MedibusField("percentage dead space of expiratory tidal volume", "%", False), - _MedibusField("end-tidal CO2", "%", False), - _MedibusField("end-tidal CO2", "kPa", False), - _MedibusField("end-tidal CO2", "mmHg", False), - _MedibusField("fraction inspired O2", "%", False), - _MedibusField("spontaneous inspiratory:expiratory ratio", "", False), - _MedibusField("elastance", "mbar/L", False), - _MedibusField("time constant", "s", False), - _MedibusField( - "ratio between upper 20% pressure range and total dynamic compliance", - "", - False, - ), - _MedibusField("end-inspiratory pressure", "mbar", False), - _MedibusField("expiratory tidal volume", "mL", False), - _MedibusField("time at low pressure", "s", False), -] +_bin_file_formats = { + "original": { + "frame_size": 4358, + "medibus_fields": [ + _MedibusField("airway pressure", "mbar", True), + _MedibusField("flow", "L/min", True), + _MedibusField("volume", "mL", True), + _MedibusField("CO2 (%)", "%", True), + _MedibusField("CO2 (kPa)", "kPa", True), + _MedibusField("CO2 (mmHg)", "mmHg", True), + _MedibusField("dynamic compliance", "mL/mbar", False), + _MedibusField("resistance", "mbar/L/s", False), + _MedibusField("r^2", "", False), + _MedibusField("spontaneous inspiratory time", "s", False), + _MedibusField("minimal pressure", "mbar", False), + _MedibusField("P0.1", "mbar", False), + _MedibusField("mean pressure", "mbar", False), + _MedibusField("plateau pressure", "mbar", False), + _MedibusField("PEEP", "mbar", False), + _MedibusField("intrinsic PEEP", "mbar", False), + _MedibusField("mandatory respiratory rate", "/min", False), + _MedibusField("mandatory minute volume", "L/min", False), + _MedibusField("peak inspiratory pressure", "mbar", False), + _MedibusField("mandatory tidal volume", "L", False), + _MedibusField("spontaneous tidal volume", "L", False), + _MedibusField("trapped volume", "mL", False), + _MedibusField("mandatory expiratory tidal volume", "mL", False), + _MedibusField("spontaneous expiratory tidal volume", "mL", False), + _MedibusField("mandatory inspiratory tidal volume", "mL", False), + _MedibusField("tidal volume", "mL", False), + _MedibusField("spontaneous inspiratory tidal volume", "mL", False), + _MedibusField("negative inspiratory force", "mbar", False), + _MedibusField("leak minute volume", "L/min", False), + _MedibusField("leak percentage", "%", False), + _MedibusField("spontaneous respiratory rate", "/min", False), + _MedibusField("percentage of spontaneous minute volume", "%", False), + _MedibusField("spontaneous minute volume", "L/min", False), + _MedibusField("minute volume", "L/min", False), + _MedibusField("airway temperature", "degrees C", False), + _MedibusField("rapid shallow breating index", "1/min/L", False), + _MedibusField("respiratory rate", "/min", False), + _MedibusField("inspiratory:expiratory ratio", "", False), + _MedibusField("CO2 flow", "mL/min", False), + _MedibusField("dead space volume", "mL", False), + _MedibusField("percentage dead space of expiratory tidal volume", "%", False), + _MedibusField("end-tidal CO2", "%", False), + _MedibusField("end-tidal CO2", "kPa", False), + _MedibusField("end-tidal CO2", "mmHg", False), + _MedibusField("fraction inspired O2", "%", False), + _MedibusField("spontaneous inspiratory:expiratory ratio", "", False), + _MedibusField("elastance", "mbar/L", False), + _MedibusField("time constant", "s", False), + _MedibusField( + "ratio between upper 20% pressure range and total dynamic compliance", + "", + False, + ), + _MedibusField("end-inspiratory pressure", "mbar", False), + _MedibusField("expiratory tidal volume", "mL", False), + _MedibusField("time at low pressure", "s", False), + ], + } +} From ac233838eba13644fcdc54ffde66eaa6d305a6e4 Mon Sep 17 00:00:00 2001 From: Peter Somhorst Date: Mon, 16 Dec 2024 15:26:52 +0100 Subject: [PATCH 02/10] Medibus data will contain NaN instead of smallest possible float --- eitprocessing/datahandling/loading/draeger.py | 1 + 1 file changed, 1 insertion(+) diff --git a/eitprocessing/datahandling/loading/draeger.py b/eitprocessing/datahandling/loading/draeger.py index 508bd0d84..a8a48b887 100644 --- a/eitprocessing/datahandling/loading/draeger.py +++ b/eitprocessing/datahandling/loading/draeger.py @@ -196,6 +196,7 @@ def _convert_medibus_data( sparsedata_collection = DataCollection(SparseData) for field_info, data in zip(medibus_fields, medibus_data, strict=True): + data[data < -1e30] = np.nan if field_info.continuous: continuous_data = ContinuousData( label=field_info.signal_name, From 5337b470ed4858cfcb96f6f2accbe0d7e8113cf7 Mon Sep 17 00:00:00 2001 From: Peter Somhorst Date: Mon, 16 Dec 2024 15:27:01 +0100 Subject: [PATCH 03/10] Add pressure pod bin file format --- eitprocessing/datahandling/loading/draeger.py | 69 ++++++++++++++++++- 1 file changed, 68 insertions(+), 1 deletion(-) diff --git a/eitprocessing/datahandling/loading/draeger.py b/eitprocessing/datahandling/loading/draeger.py index a8a48b887..3ce638176 100644 --- a/eitprocessing/datahandling/loading/draeger.py +++ b/eitprocessing/datahandling/loading/draeger.py @@ -336,5 +336,72 @@ class _MedibusField(NamedTuple): _MedibusField("expiratory tidal volume", "mL", False), _MedibusField("time at low pressure", "s", False), ], - } + }, + "pressure_pod": { + "frame_size": 4382, + "medibus_fields": [ + _MedibusField("airway pressure", "mbar", True), + _MedibusField("flow", "L/min", True), + _MedibusField("volume", "mL", True), + _MedibusField("CO2 (%)", "%", True), + _MedibusField("CO2 (kPa)", "kPa", True), + _MedibusField("CO2 (mmHg)", "mmHg", True), + _MedibusField("dynamic compliance", "mL/mbar", False), + _MedibusField("resistance", "mbar/L/s", False), + _MedibusField("r^2", "", False), + _MedibusField("spontaneous inspiratory time", "s", False), + _MedibusField("minimal pressure", "mbar", False), + _MedibusField("P0.1", "mbar", False), + _MedibusField("mean pressure", "mbar", False), + _MedibusField("plateau pressure", "mbar", False), + _MedibusField("PEEP", "mbar", False), + _MedibusField("intrinsic PEEP", "mbar", False), + _MedibusField("mandatory respiratory rate", "/min", False), + _MedibusField("mandatory minute volume", "L/min", False), + _MedibusField("peak inspiratory pressure", "mbar", False), + _MedibusField("mandatory tidal volume", "L", False), + _MedibusField("spontaneous tidal volume", "L", False), + _MedibusField("trapped volume", "mL", False), + _MedibusField("mandatory expiratory tidal volume", "mL", False), + _MedibusField("spontaneous expiratory tidal volume", "mL", False), + _MedibusField("mandatory inspiratory tidal volume", "mL", False), + _MedibusField("tidal volume", "mL", False), + _MedibusField("spontaneous inspiratory tidal volume", "mL", False), + _MedibusField("negative inspiratory force", "mbar", False), + _MedibusField("leak minute volume", "L/min", False), + _MedibusField("leak percentage", "%", False), + _MedibusField("spontaneous respiratory rate", "/min", False), + _MedibusField("percentage of spontaneous minute volume", "%", False), + _MedibusField("spontaneous minute volume", "L/min", False), + _MedibusField("minute volume", "L/min", False), + _MedibusField("airway temperature", "degrees C", False), + _MedibusField("rapid shallow breating index", "1/min/L", False), + _MedibusField("respiratory rate", "/min", False), + _MedibusField("inspiratory:expiratory ratio", "", False), + _MedibusField("CO2 flow", "mL/min", False), + _MedibusField("dead space volume", "mL", False), + _MedibusField("percentage dead space of expiratory tidal volume", "%", False), + _MedibusField("end-tidal CO2", "%", False), + _MedibusField("end-tidal CO2", "kPa", False), + _MedibusField("end-tidal CO2", "mmHg", False), + _MedibusField("fraction inspired O2", "%", False), + _MedibusField("spontaneous inspiratory:expiratory ratio", "", False), + _MedibusField("elastance", "mbar/L", False), + _MedibusField("time constant", "s", False), + _MedibusField( + "ratio between upper 20% pressure range and total dynamic compliance", + "", + False, + ), + _MedibusField("end-inspiratory pressure", "mbar", False), + _MedibusField("expiratory tidal volume", "mL", False), + _MedibusField("high pressure", "mbar", False), + _MedibusField("low pressure", "mbar", False), + _MedibusField("time at low pressure", "s", False), + _MedibusField("airway pressure (pod)", "mbar", True), + _MedibusField("esophageal pressure (pod)", "mbar", True), + _MedibusField("transpulmonary pressure (pod)", "mbar", True), + _MedibusField("gastric pressure/auxiliary pressure (pod)", "mbar", True), + ], + }, } From 829159b67bcad1337f1b37d346680e491d975f82 Mon Sep 17 00:00:00 2001 From: Peter Somhorst Date: Mon, 16 Dec 2024 15:46:54 +0100 Subject: [PATCH 04/10] Replace fixed value with constant --- eitprocessing/datahandling/loading/draeger.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/eitprocessing/datahandling/loading/draeger.py b/eitprocessing/datahandling/loading/draeger.py index 3ce638176..4c580bfd0 100644 --- a/eitprocessing/datahandling/loading/draeger.py +++ b/eitprocessing/datahandling/loading/draeger.py @@ -23,6 +23,7 @@ from numpy.typing import NDArray load_draeger_data = partial(load_eit_data, vendor=Vendor.DRAEGER) +NAN_VALUE_INDICATOR = -1e30 def load_from_single_path( @@ -196,7 +197,7 @@ def _convert_medibus_data( sparsedata_collection = DataCollection(SparseData) for field_info, data in zip(medibus_fields, medibus_data, strict=True): - data[data < -1e30] = np.nan + data[data < NAN_VALUE_INDICATOR] = np.nan if field_info.continuous: continuous_data = ContinuousData( label=field_info.signal_name, From db0d104764a843dc5c33ed9e25ca5c640d1d44a1 Mon Sep 17 00:00:00 2001 From: Peter Somhorst Date: Mon, 16 Dec 2024 20:48:05 +0100 Subject: [PATCH 05/10] Add test for loading draeger data including pressure pod data --- tests/conftest.py | 6 ++++++ tests/test_loading.py | 5 +++++ 2 files changed, 11 insertions(+) diff --git a/tests/conftest.py b/tests/conftest.py index 748099f67..9ec0e72b1 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -16,6 +16,7 @@ draeger_file2 = data_directory / "Draeger_Test.bin" draeger_file3 = data_directory / "Draeger_Test_event_on_first_frame.bin" draeger_wrapped_time_axis_file = data_directory / "Draeger_wrapped_time_axis.bin" +draeger_file_pp = data_directory / "Draeger_PP_data.bin" timpel_file = data_directory / "Timpel_test.txt" dummy_file = data_directory / "not_a_file.dummy" @@ -35,6 +36,11 @@ def draeger_both(): return load_eit_data([draeger_file2, draeger_file1], vendor="draeger", sample_frequency=20, label="draeger_both") +@pytest.fixture(scope="session") +def draeger_pp(): + return load_eit_data(draeger_file_pp, vendor="draeger", sample_frequency=20, label="draeger2") + + @pytest.fixture(scope="session") def timpel1(): return load_eit_data(timpel_file, vendor="timpel", label="timpel") diff --git a/tests/test_loading.py b/tests/test_loading.py index 46c748d26..27cf65326 100644 --- a/tests/test_loading.py +++ b/tests/test_loading.py @@ -19,6 +19,7 @@ def test_loading_draeger( draeger1: Sequence, draeger2: Sequence, draeger_both: Sequence, + draeger_pp: Sequence, ): assert isinstance(draeger1, Sequence) assert isinstance(draeger1.eit_data["raw"], EITData) @@ -35,6 +36,10 @@ def test_loading_draeger( draeger2.eit_data["raw"], ) + # draeger data with pressure pod data has 10 continuous medibus fields, 'normal' only 6 + assert len(draeger_pp.continuous_data) == 10 + 1 + assert len(draeger1.continuous_data) == 6 + 1 + # test below not possible due to requirement of axis 1 ending before axis b starts # draeger_inverted = load_eit_data([draeger_file1, draeger_file2], vendor="draeger", label="inverted") # assert len(draeger_both) == len(draeger_inverted) From ffbf411da2a125bbaafe4ffa5c0ef156c1bb67b2 Mon Sep 17 00:00:00 2001 From: Peter Somhorst Date: Mon, 16 Dec 2024 20:49:01 +0100 Subject: [PATCH 06/10] Remove mention of draeger data limitation --- eitprocessing/datahandling/loading/__init__.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/eitprocessing/datahandling/loading/__init__.py b/eitprocessing/datahandling/loading/__init__.py index ca3c37d9f..e037e4085 100644 --- a/eitprocessing/datahandling/loading/__init__.py +++ b/eitprocessing/datahandling/loading/__init__.py @@ -18,10 +18,6 @@ def load_eit_data( ) -> Sequence: """Load EIT data from path(s). - Current limitations: - - Dräger data is assumed to have a limited set of (Medibus) data. Newer additions that add data like pleural - pressure are not yet supported. - Args: path: relative or absolute path(s) to data file. vendor: vendor indicating the device used. From a864c163050f1a679114a570ef727075a950327a Mon Sep 17 00:00:00 2001 From: Peter Somhorst Date: Mon, 16 Dec 2024 21:02:01 +0100 Subject: [PATCH 07/10] Fix sample frequency of pressure pod data --- tests/conftest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/conftest.py b/tests/conftest.py index 9ec0e72b1..b1fd7d619 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -38,7 +38,7 @@ def draeger_both(): @pytest.fixture(scope="session") def draeger_pp(): - return load_eit_data(draeger_file_pp, vendor="draeger", sample_frequency=20, label="draeger2") + return load_eit_data(draeger_file_pp, vendor="draeger", sample_frequency=50, label="draeger2") @pytest.fixture(scope="session") From 917eca8981aaaf9ae7f5a3aaf85072685e66f665 Mon Sep 17 00:00:00 2001 From: Peter Somhorst Date: Mon, 20 Jan 2025 14:21:59 +0100 Subject: [PATCH 08/10] Clarify determination of file format --- eitprocessing/datahandling/loading/draeger.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/eitprocessing/datahandling/loading/draeger.py b/eitprocessing/datahandling/loading/draeger.py index 4c580bfd0..a8d3816af 100644 --- a/eitprocessing/datahandling/loading/draeger.py +++ b/eitprocessing/datahandling/loading/draeger.py @@ -35,9 +35,14 @@ def load_from_single_path( """Load Dräger EIT data from path.""" file_size = path.stat().st_size + frame_size: int + medibus_fields: list + + # iterate over the supported file formats to find the frame size that matches the file size for _file_format_data in _bin_file_formats.values(): - frame_size: int = _file_format_data["frame_size"] + frame_size = _file_format_data["frame_size"] if file_size % frame_size == 0: + # if the file size is an integer multiple of the frame size, assume this is the correct format medibus_fields = _file_format_data["medibus_fields"] break else: From 094dd9454553b2c49d0765d10625784e8dc788d6 Mon Sep 17 00:00:00 2001 From: Peter Somhorst Date: Mon, 20 Jan 2025 14:22:19 +0100 Subject: [PATCH 09/10] Fix type of medibus data to 32 bit float --- eitprocessing/datahandling/loading/draeger.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eitprocessing/datahandling/loading/draeger.py b/eitprocessing/datahandling/loading/draeger.py index a8d3816af..d7184125f 100644 --- a/eitprocessing/datahandling/loading/draeger.py +++ b/eitprocessing/datahandling/loading/draeger.py @@ -79,7 +79,7 @@ def load_from_single_path( time = np.zeros((n_frames,)) events: list[tuple[float, Event]] = [] phases: list[tuple[float, int]] = [] - medibus_data = np.zeros((len(medibus_fields), n_frames)) + medibus_data = np.zeros((len(medibus_fields), n_frames), dtype=np.float32) with path.open("br") as fo, mmap.mmap(fo.fileno(), length=0, access=mmap.ACCESS_READ) as fh: fh.seek(first_frame_to_load * frame_size) From 098dc69f60fd3c85ab213148513ee015ed7e59b2 Mon Sep 17 00:00:00 2001 From: Peter Somhorst Date: Mon, 20 Jan 2025 14:22:30 +0100 Subject: [PATCH 10/10] Move estimation of sample frequency to separate function --- eitprocessing/datahandling/loading/draeger.py | 31 ++++++++++++------- 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/eitprocessing/datahandling/loading/draeger.py b/eitprocessing/datahandling/loading/draeger.py index d7184125f..c23fc665a 100644 --- a/eitprocessing/datahandling/loading/draeger.py +++ b/eitprocessing/datahandling/loading/draeger.py @@ -100,21 +100,11 @@ def load_from_single_path( previous_marker, ) - estimated_sample_frequency = round((len(time) - 1) / (time[-1] - time[0]), 4) - - if not sample_frequency: - sample_frequency = estimated_sample_frequency - - elif sample_frequency != estimated_sample_frequency: - msg = ( - f"Provided sample frequency ({sample_frequency}) does not match " - f"the estimated sample frequency ({estimated_sample_frequency})." - ) - warnings.warn(msg, RuntimeWarning) - # time wraps around the number of seconds in a day time = np.unwrap(time, period=24 * 60 * 60) + sample_frequency = _estimate_sample_frequency(time, sample_frequency) + eit_data = EITData( vendor=Vendor.DRAEGER, path=path, @@ -192,6 +182,23 @@ def load_from_single_path( } +def _estimate_sample_frequency(time: np.ndarray, sample_frequency: float | None) -> float: + """Estimate the sample frequency from the time axis, and check with provided sample frequency.""" + estimated_sample_frequency = round((len(time) - 1) / (time[-1] - time[0]), 4) + + if sample_frequency is None: + return estimated_sample_frequency + + if sample_frequency != estimated_sample_frequency: + msg = ( + f"Provided sample frequency ({sample_frequency}) does not match " + f"the estimated sample frequency ({estimated_sample_frequency})." + ) + warnings.warn(msg, RuntimeWarning) + + return sample_frequency + + def _convert_medibus_data( medibus_data: NDArray, medibus_fields: list,