diff --git a/CHANGELOG.md b/CHANGELOG.md index d80836ca..086c68ec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,7 +12,8 @@ ### 🗑️ Removed - Remove Python 3.9 support ([#457](https://github.com/cbrnr/mnelab/pull/457) by [Clemens Brunner](https://github.com/cbrnr)) -### 🔧 Fixed +### Fixed +- Fix an issue where some .xdf files were not loaded correctly (wrong dtype or multi-channel marker) ([#464](https://github.com/cbrnr/mnelab/pull/464) by [Benedikt Klöckl](https://github.com/bkloeckl)) - Fix a bug where appending data would not be correctly displayed in the history ([#446](https://github.com/cbrnr/mnelab/pull/446) by [Benedikt Klöckl](https://github.com/bkloeckl)) - Fix resetting the settings to default values ([#456](https://github.com/cbrnr/mnelab/pull/456) by [Clemens Brunner](https://github.com/cbrnr)) - Fix an issue where the channel montage figure could not be closed on macOS ([#459](https://github.com/cbrnr/mnelab/pull/459) by [Benedikt Klöckl](https://github.com/bkloeckl)) diff --git a/src/mnelab/dialogs/xdf_streams.py b/src/mnelab/dialogs/xdf_streams.py index 21208370..aacb81c6 100644 --- a/src/mnelab/dialogs/xdf_streams.py +++ b/src/mnelab/dialogs/xdf_streams.py @@ -151,7 +151,7 @@ def selected_streams(self): for row in self.view.selectionModel().selectedRows(): type_ = self.view.item(row.row(), 2).text() fs = self.view.item(row.row(), 5).value() - if type_ != "Markers" and fs != 0: + if not _is_marker(type_) and fs != 0: streams.append(self.view.item(row.row(), 0).value()) return streams @@ -160,7 +160,11 @@ def selected_markers(self): markers = [] for row in self.view.selectionModel().selectedRows(): type_ = self.view.item(row.row(), 2).text() - fs = self.view.item(row.row(), 5).value() - if type_ == "Markers" and fs == 0: + if _is_marker(type_): markers.append(self.view.item(row.row(), 0).value()) return markers + + +def _is_marker(type): + marker_types = ["Markers", "Marker", "StringMarker"] + return type in marker_types diff --git a/src/mnelab/io/xdf.py b/src/mnelab/io/xdf.py index a954d57d..12594461 100644 --- a/src/mnelab/io/xdf.py +++ b/src/mnelab/io/xdf.py @@ -34,6 +34,7 @@ def __init__( Resampling target frequency in Hz. If only one stream_id is given, this can be `None`, in which case no resampling is performed. """ + if len(stream_ids) > 1 and fs_new is None: raise ValueError( "Argument `fs_new` is required when reading multiple streams." @@ -42,7 +43,7 @@ def __init__( streams, _ = load_xdf(fname) streams = {stream["info"]["stream_id"]: stream for stream in streams} - if all(_is_markerstream(streams[stream_id]) for stream_id in stream_ids): + if (not stream_ids) and marker_ids: raise RuntimeError( "Loading only marker streams is not supported, at least one stream must" " be a regular stream." @@ -53,6 +54,23 @@ def __init__( for stream_id in stream_ids: stream = streams[stream_id] + # check if the dtype is valid, try convertion otherwise + dtype = stream["time_series"].dtype + if dtype not in [np.float64, np.complex128]: + try: + stream["time_series"] = stream["time_series"].astype(np.float64) + except ValueError: + try: + stream["time_series"] = stream["time_series"].astype( + np.complex128 + ) + except ValueError as e: + raise RuntimeError( + f"Stream {stream['info']['name']} has unsupported" + " dtype {dtype}. " + f"Conversion to float64 and complex128 failed." + ) from e + n_chans = int(stream["info"]["channel_count"][0]) labels, types, units = [], [], [] try: @@ -92,17 +110,23 @@ def __init__( super().__init__(preload=data, info=info, filenames=[fname]) # convert marker streams to annotations - for stream_id, stream in streams.items(): - if marker_ids is not None and stream_id not in marker_ids: - continue - if not _is_markerstream(stream): - continue + for marker_id in marker_ids: + stream = streams[marker_id] + channel_count = int(stream["info"]["channel_count"][0]) onsets = stream["time_stamps"] - first_time - prefix = f"{stream_id}-" if prefix_markers else "" - descriptions = [ - f"{prefix}{item}" for sub in stream["time_series"] for item in sub - ] - self.annotations.append(onsets, [0] * len(onsets), descriptions) + prefix = f"{marker_id}-" if prefix_markers else "" + + if channel_count == 1: + # handle single-channel markers + descriptions = [f"{prefix}{item[0]}" for item in stream["time_series"]] + self.annotations.append(onsets, [0] * len(onsets), descriptions) + else: + # handle multi-channel markers + for sample in stream["time_series"]: + for _, description in enumerate(sample): + self.annotations.append( + onsets, [0] * len(onsets), str(description) + ) def _resample_streams(streams, stream_ids, fs_new): @@ -196,12 +220,6 @@ def read_raw_xdf( return RawXDF(fname, stream_ids, marker_ids, prefix_markers, fs_new) -def _is_markerstream(stream): - srate = float(stream["info"]["nominal_srate"][0]) - n_chans = int(stream["info"]["channel_count"][0]) - return srate == 0 and n_chans == 1 - - def get_xml(fname): """Get XML stream headers and footers from all streams.