From 8ded6e12daa6f2313092bd558eb4ce0280884288 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 10 Sep 2026 15:34:17 +0000 Subject: [PATCH] Port Intan "one file per signal type" support from NDR-matlab MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Mirrors NDR-matlab #138 (merge b461bf6): NDR-python previously only handled the "one file per channel" Intan directory layout (amp-A-000.dat, aux-A-AUX1.dat, ...). Some Intan recordings — including the KJNielsen lab's output that surfaced the sibling bug in vhlab-toolbox — save in the alternative "one file per signal type" layout, where all channels of a given signal type share one interleaved file (amplifier.dat, auxiliary.dat, supply.dat, analogin.dat, digitalin.dat, digitalout.dat). Add ndr.format.intan.read_IntanRHD2000_one_file_per_channel_type (regular_port, matching MATLAB semantics 1-for-1) and teach read_Intan_RHD2000_directory to detect the layout from a *amplifier.dat probe and dispatch to the new helper on hit. Resolve prefixed info.rhd and time.dat (e.g., "febc0_u000_000_info.rhd") through a local fixdatfilename helper. For digital channels in per-signal-type mode, extract the requested channel's bit from the packed 16-bit word using its native_order. ndr.reader.intan_rhd.filenamefromepochfiles now recognizes "_info.rhd" as the directory-mode header when a *time.dat sibling is in the epoch file list. Bump matlab_last_sync_hash to b461bf6 on the three affected entries and add a bridge entry for the new function; all pass NDR_BRIDGE_CHECK_STRICT. 9 new unit tests mirror the MATLAB TestOneFilePerSignalType suite. Co-Authored-By: Claude Opus 4.7 Claude-Session: https://claude.ai/code/session_01N2wVrGvkW9xWFGcoP8ed4k --- src/ndr/format/intan/__init__.py | 3 + .../intan/ndr_matlab_python_bridge.yaml | 38 ++- ..._IntanRHD2000_one_file_per_channel_type.py | 131 ++++++++++ .../intan/read_Intan_RHD2000_directory.py | 91 +++++-- src/ndr/reader/intan_rhd.py | 7 +- src/ndr/reader/ndr_matlab_python_bridge.yaml | 7 +- tests/test_intan_one_file_per_signal_type.py | 231 ++++++++++++++++++ 7 files changed, 486 insertions(+), 22 deletions(-) create mode 100644 src/ndr/format/intan/read_IntanRHD2000_one_file_per_channel_type.py create mode 100644 tests/test_intan_one_file_per_signal_type.py diff --git a/src/ndr/format/intan/__init__.py b/src/ndr/format/intan/__init__.py index f4c1b79..244958a 100644 --- a/src/ndr/format/intan/__init__.py +++ b/src/ndr/format/intan/__init__.py @@ -15,3 +15,6 @@ ) from ndr.format.intan.read_Intan_RHD2000_directory import read_Intan_RHD2000_directory from ndr.format.intan.read_Intan_RHD2000_header import read_Intan_RHD2000_header +from ndr.format.intan.read_IntanRHD2000_one_file_per_channel_type import ( + read_IntanRHD2000_one_file_per_channel_type, +) diff --git a/src/ndr/format/intan/ndr_matlab_python_bridge.yaml b/src/ndr/format/intan/ndr_matlab_python_bridge.yaml index 1cd083e..fdbed05 100644 --- a/src/ndr/format/intan/ndr_matlab_python_bridge.yaml +++ b/src/ndr/format/intan/ndr_matlab_python_bridge.yaml @@ -94,7 +94,16 @@ functions: matlab_path: "+ndr/+format/+intan/read_Intan_RHD2000_directory.m" status: regular_port python_path: "ndr/format/intan/read_Intan_RHD2000_directory.py" - matlab_last_sync_hash: "cae4b00" + matlab_last_sync_hash: "b461bf6" + decision_log: > + Adds "one file per signal type" support alongside the existing + "one file per channel" path (NDR-matlab #138). Layout is detected + from a *amplifier.dat probe; on hit the reader dispatches to + read_IntanRHD2000_one_file_per_channel_type and, for digital + channels, extracts each requested channel's bit from the packed + 16-bit word using its native_order. info.rhd and time.dat are + resolved through a local fixdatfilename helper so a lab or Intan + timestamp prefix (e.g., "febc0_u000_000_") still matches. input_arguments: - name: directoryname type_python: "str | Path" @@ -116,6 +125,33 @@ functions: - name: total_time type_python: "float" + - name: read_IntanRHD2000_one_file_per_channel_type + matlab_path: "+ndr/+format/+intan/read_IntanRHD2000_one_file_per_channel_type.m" + status: regular_port + python_path: "ndr/format/intan/read_IntanRHD2000_one_file_per_channel_type.py" + matlab_last_sync_hash: "b461bf6" + decision_log: > + New helper introduced in NDR-matlab #138 that reads one interleaved + signal-type file at a time from the Intan "one file per signal type" + layout, resolving optional filename prefixes and returning the raw + packed 16-bit word for digital channels (the caller extracts the bit). + input_arguments: + - name: directory_name + type_python: "str | Path" + - name: channel_type + type_python: "int" + - name: channel_index + type_python: "int" + - name: num_channels + type_python: "int" + - name: s0 + type_python: "int" + - name: s1 + type_python: "int" + output_arguments: + - name: data + type_python: "numpy.ndarray" + - name: cat_Intan_RHD2000_files matlab_path: "+ndr/+format/+intan/cat_Intan_RHD2000_files.m" status: regular_port diff --git a/src/ndr/format/intan/read_IntanRHD2000_one_file_per_channel_type.py b/src/ndr/format/intan/read_IntanRHD2000_one_file_per_channel_type.py new file mode 100644 index 0000000..b085adb --- /dev/null +++ b/src/ndr/format/intan/read_IntanRHD2000_one_file_per_channel_type.py @@ -0,0 +1,131 @@ +"""Read Intan "one file per signal type" data. + +Port of +ndr/+format/+intan/read_IntanRHD2000_one_file_per_channel_type.m +""" + +from __future__ import annotations + +from pathlib import Path + +import numpy as np + +_FILENAME_BY_CHANNEL_TYPE = { + 1: "time.dat", + 2: "amplifier.dat", + 3: "auxiliary.dat", + 4: "supply.dat", + # 5: temperature -- Intan does not write a standalone file for this in + # the "one file per signal type" format; temperature is interleaved + # with the supply-voltage stream. Kept out of the map on purpose so a + # caller that requests it hits the ValueError below. + 6: "analogin.dat", + 7: "digitalin.dat", + 8: "digitalout.dat", +} + +_SAMPLE_SIZE_BYTES = {1: 4, 2: 2, 3: 2, 4: 2, 6: 2, 7: 2, 8: 2} +_SAMPLE_DTYPE = { + 1: np.int32, + 2: np.int16, + 3: np.uint16, + 4: np.uint16, + 6: np.uint16, + 7: np.uint16, + 8: np.uint16, +} + + +def read_IntanRHD2000_one_file_per_channel_type( + directory_name: str | Path, + channel_type: int, + channel_index: int, + num_channels: int, + s0: int, + s1: int, +) -> np.ndarray: + """Read one channel's samples from the Intan "one file per signal type" layout. + + Parameters + ---------- + directory_name : str or Path + Directory containing the ``.dat`` files. Files may carry an Intan + timestamp prefix or a lab-specific prefix + (e.g. ``febc0_u000_000_amplifier.dat``); the reader resolves the + prefix automatically. + channel_type : int + Signal type: ``1`` time.dat, ``2`` amplifier.dat, ``3`` auxiliary.dat, + ``4`` supply.dat, ``6`` analogin.dat, ``7`` digitalin.dat, + ``8`` digitalout.dat. Temperature (5) is not saved as a standalone + file in this layout. + channel_index : int + 1-based position of the channel within the enabled channels of this + type. Ignored for digital channels (the caller extracts the bit). + num_channels : int + Total number of enabled channels of this type. + s0, s1 : int + 1-based inclusive sample range. + + Returns + ------- + numpy.ndarray + For analog channels, a 1-D array of the requested samples in the + file's native dtype. For digital channels, the full packed 16-bit + word per sample; the caller extracts the requested bit. + + Filenames match the Intan-RHX / IntanToNWB / python-neo spec. + + See also: ``read_Intan_RHD2000_directory``. + """ + directory_name = Path(directory_name) + if not directory_name.is_dir(): + raise NotADirectoryError(f"Directory does not exist: {directory_name}") + if channel_type not in _FILENAME_BY_CHANNEL_TYPE: + raise ValueError(f"Unknown channel_type {channel_type}") + if channel_index < 1 or num_channels < 1: + raise ValueError("channel_index and num_channels must be positive integers") + if s0 < 1 or s1 < s0: + raise ValueError("s0 must be >= 1 and s1 must be >= s0") + + filename_post = _FILENAME_BY_CHANNEL_TYPE[channel_type] + fn = _fixdatfilename(directory_name / filename_post) + if fn is None: + raise FileNotFoundError( + f"Could not find data file matching *{filename_post} in {directory_name}." + ) + + sample_size = _SAMPLE_SIZE_BYTES[channel_type] + dtype = np.dtype(_SAMPLE_DTYPE[channel_type]).newbyteorder("<") + count = s1 - s0 + 1 + + with open(fn, "rb") as fid: + if channel_type < 7: + # Interleaved: seek past (s0-1) full sample groups, then past + # (channel_index-1) samples within the current group, and stride + # over the other channels on each subsequent read. + fid.seek(sample_size * (num_channels * (s0 - 1) + (channel_index - 1))) + if num_channels == 1: + return np.fromfile(fid, dtype=dtype, count=count) + # Read count samples with a stride of num_channels. numpy has no + # strided fread, so read the whole run and slice. + span = num_channels * (count - 1) + 1 + raw = np.fromfile(fid, dtype=dtype, count=span) + return raw[::num_channels] + # Digital: one packed 16-bit word per sample; caller extracts bit. + fid.seek(sample_size * (s0 - 1)) + return np.fromfile(fid, dtype=dtype, count=count) + + +def _fixdatfilename(filename: Path) -> Path | None: + """Resolve a filename that may carry an Intan timestamp or lab prefix. + + Returns the exact path if it exists, otherwise the first sibling matching + ``*`` in the same directory, otherwise ``None``. + """ + if filename.is_file(): + return filename + parent = filename.parent + stem_ext = filename.name + matches = sorted(parent.glob(f"*{stem_ext}")) + if matches: + return matches[0] + return None diff --git a/src/ndr/format/intan/read_Intan_RHD2000_directory.py b/src/ndr/format/intan/read_Intan_RHD2000_directory.py index c3a9c04..7495073 100644 --- a/src/ndr/format/intan/read_Intan_RHD2000_directory.py +++ b/src/ndr/format/intan/read_Intan_RHD2000_directory.py @@ -11,6 +11,25 @@ import numpy as np from ndr.format.intan.read_Intan_RHD2000_header import read_Intan_RHD2000_header +from ndr.format.intan.read_IntanRHD2000_one_file_per_channel_type import ( + read_IntanRHD2000_one_file_per_channel_type, +) + + +def _fixdatfilename(path: Path) -> Path | None: + """Resolve a filename that may carry an Intan timestamp or lab prefix. + + Returns the exact path if it exists, otherwise the first sibling matching + ``*`` in the same directory, otherwise ``None``. Mirrors the + ``fixdatfilename`` helper in the MATLAB reader so recordings named + ``_amplifier.dat`` (Intan timestamp or lab prefix) still resolve. + """ + if path.is_file(): + return path + matches = sorted(path.parent.glob(f"*{path.name}")) + if matches: + return matches[0] + return None def read_Intan_RHD2000_directory( @@ -64,15 +83,22 @@ def read_Intan_RHD2000_directory( directoryname = Path(directoryname) if header is None: - header = read_Intan_RHD2000_header(directoryname / "info.rhd") + info_rhd = _fixdatfilename(directoryname / "info.rhd") + if info_rhd is None: + raise FileNotFoundError( + f"Could not find info.rhd (or a *info.rhd variant) in {directoryname}." + ) + header = read_Intan_RHD2000_header(info_rhd) if isinstance(channel_numbers, int): channel_numbers = [channel_numbers] # Determine total samples from time.dat file size - time_file = directoryname / "time.dat" - if not time_file.exists(): - raise FileNotFoundError(f"No file {time_file}, required file.") + time_file = _fixdatfilename(directoryname / "time.dat") + if time_file is None: + raise FileNotFoundError( + f"No file {directoryname / 'time.dat'} (or a *time.dat variant), required file." + ) total_samples = time_file.stat().st_size // 4 # int32 = 4 bytes sr = header["frequency_parameters"]["amplifier_sample_rate"] @@ -160,6 +186,15 @@ def read_Intan_RHD2000_directory( conversion_shift[6] = -32768 conversion_scale[6] = 0.0003125 + # Detect the on-disk layout. "one file per signal type" writes one .dat + # per signal type (amplifier.dat, auxiliary.dat, supply.dat, analogin.dat, + # digitalin.dat, digitalout.dat) with all channels of that type + # interleaved sample-by-sample; "one file per channel" writes one .dat + # per channel (amp-A-000.dat, aux-A-AUX1.dat, ...). The presence of a + # *amplifier.dat file (possibly with an Intan timestamp or lab prefix) + # distinguishes them. + one_file_per_signal_type = _fixdatfilename(directoryname / "amplifier.dat") is not None + data = np.empty((num_samples, 0), dtype=np.float64) if channel_type_int == 1: @@ -189,21 +224,41 @@ def read_Intan_RHD2000_directory( f"Channel {ch_num} not in range 1 ... {len(hinfo)} listed in header." ) ch_info = hinfo[ch_num - 1] # convert 1-based to 0-based - fname = f"{prefix}{ch_info['custom_channel_name']}.dat" - fpath = directoryname / fname - with open(fpath, "rb") as fid: - fid.seek(sbytes * s0) - raw = np.fromfile(fid, dtype=dtype, count=num_samples) - raw_f = raw.astype(np.float64) - if shift != 0: - raw_f -= shift - if channel_type_int in (7, 8): - # Per-channel files store the 16-bit packed word with only the - # corresponding native_order bit potentially set; normalize - # to 0/1 rather than applying the analog conversion scale. - raw_f = (raw_f != 0).astype(np.float64) + if one_file_per_signal_type: + raw = read_IntanRHD2000_one_file_per_channel_type( + directoryname, + channel_type_int, + ch_num, + len(hinfo), + s0 + 1, # helper is 1-based like the MATLAB source + s1 + 1, + ) + raw_f = raw.astype(np.float64) + if channel_type_int in (7, 8): + # "one file per signal type" digital files store the + # full 16-bit packed word each sample; extract this + # channel's bit using its native_order (0..15). + bit_pos = int(ch_info["native_order"]) + raw_f = ( + (np.asarray(raw, dtype=np.uint16) & np.uint16(1 << bit_pos)) != 0 + ).astype(np.float64) else: - raw_f *= scale + fname = f"{prefix}{ch_info['custom_channel_name']}.dat" + fpath = directoryname / fname + with open(fpath, "rb") as fid: + fid.seek(sbytes * s0) + raw = np.fromfile(fid, dtype=dtype, count=num_samples) + raw_f = raw.astype(np.float64) + if channel_type_int in (7, 8): + # Per-channel files store the 16-bit packed word with only + # the corresponding native_order bit potentially set; + # normalize to 0/1 rather than applying the analog + # conversion scale. + raw_f = (raw_f != 0).astype(np.float64) + if shift != 0: + raw_f = raw_f - shift + if channel_type_int not in (7, 8): + raw_f = raw_f * scale columns.append(raw_f) data = np.column_stack(columns) if columns else np.empty((num_samples, 0)) diff --git a/src/ndr/reader/intan_rhd.py b/src/ndr/reader/intan_rhd.py index 87aa1ad..4eb4721 100644 --- a/src/ndr/reader/intan_rhd.py +++ b/src/ndr/reader/intan_rhd.py @@ -368,8 +368,11 @@ def filenamefromepochfiles(self, filename_array: list[str]) -> tuple[str, str, b fileMode = "multiFile" else: filename = rhd_files[0] - # Check if this is the one-file-per-channel format - if Path(filename).stem == "info": + # Directory mode when the .rhd is Intan's per-signal-type header + # ("info.rhd" or a "_info.rhd" variant) AND a sibling + # *time.dat is present in the epoch file list. + stem = Path(filename).stem + if stem == "info" or stem.endswith("info"): time_dat_files = [f for f in filename_array if f.endswith("time.dat")] if time_dat_files: isdirectory = True diff --git a/src/ndr/reader/ndr_matlab_python_bridge.yaml b/src/ndr/reader/ndr_matlab_python_bridge.yaml index a699c07..8738692 100644 --- a/src/ndr/reader/ndr_matlab_python_bridge.yaml +++ b/src/ndr/reader/ndr_matlab_python_bridge.yaml @@ -303,7 +303,12 @@ classes: matlab_path: "+ndr/+reader/intan_rhd.m" status: regular_port python_path: "ndr/reader/intan_rhd.py" - matlab_last_sync_hash: "67addc7" + matlab_last_sync_hash: "b461bf6" + decision_log: > + filenamefromepochfiles now recognizes a "_info.rhd" variant + as the directory-mode header (NDR-matlab #138) in addition to the + bare info.rhd, matching how KJNielsen-style Intan recordings put + their per-signal-type files on disk. python_class: "ndr_reader_intan__rhd" inherits: base methods: diff --git a/tests/test_intan_one_file_per_signal_type.py b/tests/test_intan_one_file_per_signal_type.py new file mode 100644 index 0000000..49b6a78 --- /dev/null +++ b/tests/test_intan_one_file_per_signal_type.py @@ -0,0 +1,231 @@ +"""Intan "one file per signal type" layout support. + +MATLAB counterpart: +``tools/tests/+ndr/+unittest/+format/+intan/TestOneFilePerSignalType.m``. + +Drives ``read_IntanRHD2000_one_file_per_channel_type`` and +``read_Intan_RHD2000_directory`` with synthetic ``.dat`` files: interleaved +multi-channel amplifier reads, an ``auxiliary.dat`` regression pin, digital +in/out returning the packed 16-bit word, sub-range slicing, prefix +resolution (Intan timestamp or lab prefix like ``febc0_u000_000_``), and +an end-to-end directory-reader path with a synthetic header covering the +per-signal-type dispatch and ``native_order``-driven digital bit extraction. +""" + +from __future__ import annotations + +from pathlib import Path + +import numpy as np +import pytest + +from ndr.format.intan.read_Intan_RHD2000_directory import read_Intan_RHD2000_directory +from ndr.format.intan.read_IntanRHD2000_one_file_per_channel_type import ( + read_IntanRHD2000_one_file_per_channel_type, +) +from ndr.reader.intan_rhd import ndr_reader_intan__rhd + + +def _write_dat(path: Path, values: np.ndarray, dtype: np.dtype) -> None: + values.astype(dtype, copy=False).tofile(path) + + +# --------------------------------------------------------------------------- +# read_IntanRHD2000_one_file_per_channel_type +# --------------------------------------------------------------------------- + + +def test_read_amplifier_interleaved(tmp_path: Path) -> None: + num_channels = 3 + num_samples = 5 + ch1 = np.array([100, 200, 300, 400, 500], dtype=np.int16) + ch2 = np.array([-100, -200, -300, -400, -500], dtype=np.int16) + ch3 = np.array([1, 2, 3, 4, 5], dtype=np.int16) + interleaved = np.empty(num_channels * num_samples, dtype=np.int16) + interleaved[0::num_channels] = ch1 + interleaved[1::num_channels] = ch2 + interleaved[2::num_channels] = ch3 + _write_dat(tmp_path / "amplifier.dat", interleaved, np.dtype(" None: + # Regression: MATLAB used to look for 'auxin.dat'; Intan writes 'auxiliary.dat'. + num_channels = 2 + num_samples = 4 + a = np.array([10, 20, 30, 40], dtype=np.uint16) + b = np.array([1000, 2000, 3000, 4000], dtype=np.uint16) + interleaved = np.empty(num_channels * num_samples, dtype=np.uint16) + interleaved[0::2] = a + interleaved[1::2] = b + _write_dat(tmp_path / "auxiliary.dat", interleaved, np.dtype(" None: + times = np.arange(10, dtype=np.int32) + _write_dat(tmp_path / "time.dat", times, np.dtype(" None: + # Digital in/out is a single packed 16-bit word per sample: the helper + # returns it verbatim; the caller extracts the requested bit. + words = np.array([0, 1, 2, 4, 8, 16, 32, 0xFFFF], dtype=np.uint16) + + _write_dat(tmp_path / "digitalin.dat", words, np.dtype(" None: + num_channels = 2 + num_samples = 8 + ch1 = np.array([10, 20, 30, 40, 50, 60, 70, 80], dtype=np.int16) + ch2 = np.array([11, 22, 33, 44, 55, 66, 77, 88], dtype=np.int16) + interleaved = np.empty(num_channels * num_samples, dtype=np.int16) + interleaved[0::2] = ch1 + interleaved[1::2] = ch2 + _write_dat(tmp_path / "amplifier.dat", interleaved, np.dtype(" None: + num_channels = 2 + num_samples = 3 + ch1 = np.array([7, 8, 9], dtype=np.int16) + ch2 = np.array([70, 80, 90], dtype=np.int16) + interleaved = np.empty(num_channels * num_samples, dtype=np.int16) + interleaved[0::2] = ch1 + interleaved[1::2] = ch2 + _write_dat(tmp_path / "febc0_u000_000_amplifier.dat", interleaved, np.dtype(" None: + with pytest.raises(FileNotFoundError): + read_IntanRHD2000_one_file_per_channel_type(tmp_path, 2, 1, 1, 1, 1) + + +# --------------------------------------------------------------------------- +# read_Intan_RHD2000_directory end-to-end (with a synthetic header) +# --------------------------------------------------------------------------- + + +def _synthetic_header( + sample_rate: float, + amp_channels: list[dict], + din_channels: list[dict], +) -> dict: + return { + "fileinfo": {"filename": "synthetic", "filesize": 0, "headersize": 0}, + "frequency_parameters": { + "amplifier_sample_rate": sample_rate, + "aux_input_sample_rate": sample_rate / 4, + "supply_voltage_sample_rate": sample_rate / 128, + "board_adc_sample_rate": sample_rate, + "board_dig_in_sample_rate": sample_rate, + }, + "amplifier_channels": amp_channels, + "aux_input_channels": [], + "supply_voltage_channels": [], + "board_adc_channels": [], + "board_dig_in_channels": din_channels, + "board_dig_out_channels": [], + "num_temp_sensor_channels": 0, + "eval_board_mode": 0, + } + + +def test_directory_reader_dispatches_to_per_signal_type(tmp_path: Path) -> None: + sample_rate = 20000.0 + num_samples = 4 + times = np.arange(num_samples, dtype=np.int32) + ch1 = np.array([100, 200, 300, 400], dtype=np.int16) + ch2 = np.array([-100, -200, -300, -400], dtype=np.int16) + amp = np.empty(2 * num_samples, dtype=np.int16) + amp[0::2] = ch1 + amp[1::2] = ch2 + # bit 0 always on, bit 3 on for the last two samples. + din_word = np.array([1, 1, 1 + 8, 1 + 8], dtype=np.uint16) + + _write_dat(tmp_path / "time.dat", times, np.dtype("= num_samples - 1 + np.testing.assert_allclose(data[:, 0], ch2[:n].astype(np.float64) * 0.195, atol=1e-10) + + data_din, _, _ = read_Intan_RHD2000_directory( + tmp_path, header, "din", [1, 2], 0.0, float("inf") + ) + n_din = data_din.shape[0] + assert n_din >= num_samples - 1 + expected_bit0 = np.array([1, 1, 1, 1], dtype=np.float64) + expected_bit3 = np.array([0, 0, 1, 1], dtype=np.float64) + np.testing.assert_array_equal(data_din[:, 0], expected_bit0[:n_din]) + np.testing.assert_array_equal(data_din[:, 1], expected_bit3[:n_din]) + + data_t, _, _ = read_Intan_RHD2000_directory(tmp_path, header, "time", 1, 0.0, float("inf")) + n_t = data_t.shape[0] + expected_t = times.astype(np.float64) / sample_rate + assert n_t >= num_samples - 1 + np.testing.assert_array_equal(data_t[:, 0], expected_t[:n_t]) + + +def test_reader_detects_directory_from_prefixed_info(tmp_path: Path) -> None: + reader = ndr_reader_intan__rhd() + + info_path = tmp_path / "febc0_u000_000_info.rhd" + time_path = tmp_path / "febc0_u000_000_time.dat" + info_path.touch() + time_path.touch() + + _, _, isdirectory, _ = reader.filenamefromepochfiles([str(info_path), str(time_path)]) + assert isdirectory is True + + info_bare = tmp_path / "info.rhd" + time_bare = tmp_path / "time.dat" + info_bare.touch() + time_bare.touch() + _, _, isdirectory_bare, _ = reader.filenamefromepochfiles([str(info_bare), str(time_bare)]) + assert isdirectory_bare is True