From 312321d5bc9e33a661453e4f2e201500b97a40a6 Mon Sep 17 00:00:00 2001 From: Cyrille Rossant Date: Wed, 9 Sep 2026 22:45:42 +0200 Subject: [PATCH 1/3] style: fix existing flake8 violations --- phylib/io/alf.py | 8 +++++--- phylib/io/model.py | 14 +++++++------- phylib/io/tests/test_alf.py | 3 ++- phylib/io/tests/test_array.py | 1 + phylib/io/tests/test_datasets.py | 4 ++-- phylib/utils/event.py | 4 ++-- 6 files changed, 19 insertions(+), 15 deletions(-) diff --git a/phylib/io/alf.py b/phylib/io/alf.py index e429204..b2d8305 100644 --- a/phylib/io/alf.py +++ b/phylib/io/alf.py @@ -373,10 +373,11 @@ def get_waveforms_amp(templates): cha = np.max(templates, axis=1) - np.min(templates, axis=1) return np.max(cha, axis=1) - # we unwhiten the templates waveforms, this will expand the templates to the original non-sparse size + # Unwhitening expands the template waveforms to the original non-sparse size. templates_phy = np.zeros([nclu, templates['waveforms'].shape[1], nch], dtype=np.float32) for i in np.arange(templates_phy.shape[0]): - templates_phy[i] = np.matmul(templates['waveforms'][i], wm[templates['waveformsChannels'][i], :]) + templates_phy[i] = np.matmul( + templates['waveforms'][i], wm[templates['waveformsChannels'][i], :]) # the original templates have a rms of 1.0, so here we just need to normalize by rms rms_templates = np.sum(np.sum(templates['waveforms'] ** 2, axis=1), axis=1) ** 0.5 @@ -390,7 +391,8 @@ def get_waveforms_amp(templates): np.save(target_path.joinpath('channel_map.npy'), np.arange(nch)) np.save(target_path.joinpath('templates.npy'), templates_phy) - np.save(target_path.joinpath('templates_ind.npy'), np.tile(np.arange(nclu)[np.newaxis, :], reps=[nch, 1])) + template_indices = np.tile(np.arange(nclu)[np.newaxis, :], reps=[nch, 1]) + np.save(target_path.joinpath('templates_ind.npy'), template_indices) # if we have metrics information, output the ks2_label information if alf_path.joinpath('cluster.metrics.pqt').exists(): diff --git a/phylib/io/model.py b/phylib/io/model.py index 7368071..b82aadf 100644 --- a/phylib/io/model.py +++ b/phylib/io/model.py @@ -994,14 +994,14 @@ def get_waveforms(self, spike_ids, channel_ids=None): # Load from precomputed spikes. try: return get_spike_waveforms( - spike_ids, channel_ids, spike_waveforms=self.spike_waveforms, - n_samples_waveforms=nsw) + spike_ids, channel_ids, spike_waveforms=self.spike_waveforms, + n_samples_waveforms=nsw) except AssertionError: - logger.warning( - "Error when loading waveforms from precomputed waveforms, trying to load the raw data.") - spike_samples = self.spike_samples[spike_ids] - return extract_waveforms( - self.traces, spike_samples, channel_ids, n_samples_waveforms=nsw) + logger.warning( + "Error when loading precomputed waveforms; trying the raw data instead.") + spike_samples = self.spike_samples[spike_ids] + return extract_waveforms( + self.traces, spike_samples, channel_ids, n_samples_waveforms=nsw) else: # Or load directly from raw data (slower). spike_samples = self.spike_samples[spike_ids] diff --git a/phylib/io/tests/test_alf.py b/phylib/io/tests/test_alf.py index 400cef2..80fa0d1 100644 --- a/phylib/io/tests/test_alf.py +++ b/phylib/io/tests/test_alf.py @@ -41,7 +41,8 @@ def __init__(self, tempdir): np.save(p / 'amplitudes.npy', nr.uniform(low=0.5, high=1.5, size=self.ns)) np.save(p / 'channel_positions.npy', np.c_[np.arange(self.nc), np.zeros(self.nc)]) templates = np.random.normal(size=(self.nt, 50, self.nc)) - templates = templates / (np.sum(np.sum(templates ** 2, axis=1), axis=1) ** .5)[:, np.newaxis, np.newaxis] + template_norms = np.sum(np.sum(templates ** 2, axis=1), axis=1) ** .5 + templates = templates / template_norms[:, np.newaxis, np.newaxis] np.save(p / 'templates.npy', templates) np.save(p / 'similar_templates.npy', np.tile(np.arange(self.nt), (self.nt, 1))) np.save(p / 'channel_map.npy', np.c_[np.arange(self.nc)]) diff --git a/phylib/io/tests/test_array.py b/phylib/io/tests/test_array.py index d80e58b..2088ee3 100644 --- a/phylib/io/tests/test_array.py +++ b/phylib/io/tests/test_array.py @@ -425,6 +425,7 @@ def __getitem__(self, indices): sampled = _sample_spikes_evenly(SortedSpikeIDs(), 10) ae(sampled, np.linspace(0, 999_999, 10, dtype=np.int64)) + def test_select_spikes_1(): spike_times = np.array([0., 1., 2., 3.3, 4.4]) spike_clusters = np.array([1, 2, 1, 2, 4]) diff --git a/phylib/io/tests/test_datasets.py b/phylib/io/tests/test_datasets.py index 6651593..f38c0a3 100644 --- a/phylib/io/tests/test_datasets.py +++ b/phylib/io/tests/test_datasets.py @@ -141,10 +141,10 @@ def test_download_file(tempdir, mock_urls): assert_succeeds = (data_here and data_valid and ((checksum_here == checksum_valid) or - (not(checksum_here) and checksum_valid))) + (not checksum_here and checksum_valid))) download_succeeds = (assert_succeeds or (data_here and - (not(data_valid) and not(checksum_here)))) + (not data_valid and not checksum_here))) if download_succeeds: data = _dl(path) diff --git a/phylib/utils/event.py b/phylib/utils/event.py index 4e15fe5..903ce16 100644 --- a/phylib/utils/event.py +++ b/phylib/utils/event.py @@ -69,9 +69,9 @@ def silent(self): """Prevent all callbacks to be called if events are raised in the context manager. """ - self.is_silent = not(self.is_silent) + self.is_silent = not self.is_silent yield - self.is_silent = not(self.is_silent) + self.is_silent = not self.is_silent def connect(self, func=None, event=None, sender=None, **kwargs): """Register a callback function to a given event. From ed7524e1ad42f60345bbc5847576c9489d6284a8 Mon Sep 17 00:00:00 2001 From: Cyrille Rossant Date: Wed, 9 Sep 2026 22:46:14 +0200 Subject: [PATCH 2/3] ci: enforce flake8 checks --- .github/workflows/ci.yml | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7b1f509..38b2629 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,6 +15,26 @@ concurrency: cancel-in-progress: true jobs: + lint: + name: Lint + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + + - name: Set up Python + uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0 + with: + python-version: "3.12" + cache: pip + cache-dependency-path: requirements-dev.txt + + - name: Install lint dependencies + run: python -m pip install flake8 + + - name: Run lint + run: python -m flake8 phylib + test: name: ${{ matrix.scope }} (${{ matrix.os }}, Python ${{ matrix.python-version }}) runs-on: ${{ matrix.os }} @@ -66,7 +86,7 @@ jobs: build: name: Build package runs-on: ubuntu-latest - needs: test + needs: [lint, test] steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 From f3b80b2e723c585561d480b5ba7a7a4c60d71786 Mon Sep 17 00:00:00 2001 From: Cyrille Rossant Date: Wed, 9 Sep 2026 22:46:51 +0200 Subject: [PATCH 3/3] release: prepare phylib 2.7.1 --- CHANGELOG.md | 25 ++++++++++++++++--------- phylib/__init__.py | 2 +- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 821f468..8073460 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,20 +5,27 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [Unreleased] +## [2.7.1] 2026-09-09 ### Added -- Code of Conduct for project participation and incident reporting. +- Added a Code of Conduct for project participation and incident reporting. + +### Changed +- Optimized spike selection and stored sparse-waveform extraction for large datasets, + including bounded display sampling and batched memory-mapped reads. +- Unknown raw-data file extensions now emit a warning instead of preventing the dataset + from loading. ### Fixed +- Prevented geometry range searches from hanging with `float32` input. - Development checkouts now report a development version with a Git commit suffix. -- Template datasets without waveform templates can be reloaded after cluster assignments change, - and stored spike-waveform subsets provide their waveform sample count. -- Cluster assignments are written atomically, so a crash during a save no longer truncates `spike_clusters.npy`. -- TSV, JSON, text and `params.py` files are written atomically, so a crash during a save no longer - truncates the file it was replacing, for instance `cluster_group.tsv`. -- #57 a blank `dat_path` in `params.py` is now read as "no raw data file" instead of resolving to - the dataset directory. +- Fixed reloading curated template-less datasets after cluster assignments change. +- Derived the waveform sample count from stored spike-waveform subsets when templates are + unavailable. +- Treated a blank `dat_path` in `params.py` as no raw-data file instead of the dataset + directory (#57). +- Made cluster-assignment, TSV, JSON, text, and `params.py` writes atomic to prevent + truncation if saving is interrupted. ## [2.7.0] 2025-12-10 diff --git a/phylib/__init__.py b/phylib/__init__.py index 1d486b0..2437fdc 100644 --- a/phylib/__init__.py +++ b/phylib/__init__.py @@ -22,7 +22,7 @@ __author__ = 'Cyrille Rossant' __email__ = 'cyrille.rossant at gmail.com' -__version__ = '2.7.1.dev0' +__version__ = '2.7.1' __version_git__ = __version__ + _git_version()