Skip to content

Commit 97118ca

Browse files
Read a blank dat_path as no raw data file (#61)
A dataset with no raw data file on disk has a blank dat_path in params.py. SpikeInterface writes one for recordings with an unknown source, such as simulated recordings. Path('') is Path('.'), so a blank entry was resolved against dir_path and silently became the dataset directory itself. The model then reported the directory as its raw data file, and get_ephys_reader() warned twice about an unknown file extension before giving up. Drop blank entries instead, so dat_path ends up empty, which is the path the model already supports for datasets with no raw data.
1 parent d99464b commit 97118ca

3 files changed

Lines changed: 66 additions & 11 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1414
- Development checkouts now report a development version with a Git commit suffix.
1515
- Template datasets without waveform templates can be reloaded after cluster assignments change,
1616
and stored spike-waveform subsets provide their waveform sample count.
17+
- #57 a blank `dat_path` in `params.py` is now read as "no raw data file" instead of resolving to
18+
the dataset directory.
1719

1820
## [2.7.0] 2025-12-10
1921

‎phylib/io/model.py‎

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -322,13 +322,10 @@ def __init__(self, **kwargs):
322322
assert isinstance(self.dir_path, Path)
323323
assert self.dir_path.exists()
324324

325-
# Set dat_path.
326-
if not self.dat_path: # pragma: no cover
327-
self.dat_path = []
328-
elif not isinstance(self.dat_path, (list, tuple)):
329-
self.dat_path = [self.dat_path]
330-
assert isinstance(self.dat_path, (list, tuple))
331-
self.dat_path = [Path(p).resolve() if not Path(p).is_symlink() else p for p in self.dat_path]
325+
# Set dat_path. Blank entries mean "no raw data file", see _clean_dat_path().
326+
self.dat_path = [
327+
Path(p).resolve() if not Path(p).is_symlink() else p
328+
for p in _clean_dat_path(self.dat_path)]
332329

333330
self.dtype = getattr(self, 'dtype', np.int16)
334331
if not self.sample_rate: # pragma: no cover
@@ -1416,6 +1413,23 @@ def close(self):
14161413
_close_memmap(k, v)
14171414

14181415

1416+
def _clean_dat_path(dat_path):
1417+
"""Normalize a raw data path specification into a list of non-empty paths.
1418+
1419+
A blank `dat_path` means the dataset has no raw data file at all. Some sorters write
1420+
`dat_path = ''` in `params.py` when the recording has no file on disk, for instance
1421+
SpikeInterface with a simulated or in-memory recording. Such entries have to be dropped
1422+
rather than resolved, because `Path('')` is `Path('.')`, which would silently turn the
1423+
missing raw data file into the dataset directory itself.
1424+
1425+
"""
1426+
if not dat_path:
1427+
return []
1428+
if not isinstance(dat_path, (list, tuple)):
1429+
dat_path = [dat_path]
1430+
return [p for p in dat_path if str(p).strip()]
1431+
1432+
14191433
def _make_abs_path(p, dir_path):
14201434
p = Path(p)
14211435
if not op.isabs(p):
@@ -1438,9 +1452,8 @@ def get_template_params(params_path):
14381452
assert params['dir_path'].is_dir()
14391453
assert params['dir_path'].exists()
14401454

1441-
if isinstance(params['dat_path'], str):
1442-
params['dat_path'] = [params['dat_path']]
1443-
params['dat_path'] = [_make_abs_path(_, params['dir_path']) for _ in params['dat_path']]
1455+
params['dat_path'] = [
1456+
_make_abs_path(_, params['dir_path']) for _ in _clean_dat_path(params['dat_path'])]
14441457
return params
14451458

14461459

‎phylib/io/tests/test_model.py‎

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
# from phylib.utils import Bunch
1616
from phylib.utils.testing import captured_output
17-
from ..model import from_sparse, load_model
17+
from ..model import from_sparse, get_template_params, load_model
1818

1919
logger = logging.getLogger(__name__)
2020

@@ -135,6 +135,46 @@ def test_model_save(template_model_full):
135135
m.save_spike_clusters(m.spike_clusters)
136136

137137

138+
def _set_dat_path(params_path, value):
139+
"""Replace the `dat_path` assignment of a `params.py` file by a literal value."""
140+
lines = params_path.read_text().splitlines()
141+
lines = [
142+
'dat_path = %s' % value if line.startswith('dat_path') else line for line in lines]
143+
params_path.write_text('\n'.join(lines) + '\n')
144+
145+
146+
def test_model_blank_dat_path(template_path):
147+
# Regression test for #57: a dataset with no raw data file on disk has a blank `dat_path`
148+
# in params.py. `Path('')` is `Path('.')`, so a blank entry used to resolve to the dataset
149+
# directory instead of being dropped.
150+
_set_dat_path(template_path, "''")
151+
assert get_template_params(template_path)['dat_path'] == []
152+
153+
model = load_model(template_path)
154+
try:
155+
assert model.dat_path == []
156+
assert model.traces is None
157+
with captured_output() as (stdout, stderr):
158+
model.describe()
159+
assert stdout.getvalue().splitlines()[0].strip() == 'Data files'
160+
finally:
161+
model.close()
162+
163+
164+
def test_model_blank_dat_path_in_list(template_path):
165+
# Blank entries are dropped from a list too, and the real file is kept.
166+
_set_dat_path(template_path, "['', 'sim_binary.dat']")
167+
dat_path = get_template_params(template_path)['dat_path']
168+
assert [p.name for p in dat_path] == ['sim_binary.dat']
169+
170+
model = load_model(template_path)
171+
try:
172+
assert [p.name for p in model.dat_path] == ['sim_binary.dat']
173+
assert model.traces is not None
174+
finally:
175+
model.close()
176+
177+
138178
def test_model_reload_curated_dataset_without_templates(template_path):
139179
model = load_model(template_path)
140180
model.save_spikes_subset_waveforms(10, 16)

0 commit comments

Comments
 (0)