Skip to content

Commit 1c8a14c

Browse files
committed
Prepare phy 2.1.0 release
1 parent b10b7db commit 1c8a14c

38 files changed

Lines changed: 160 additions & 347 deletions

.release-smoke/latest-testpypi-version.txt

Lines changed: 0 additions & 1 deletion
This file was deleted.

README.md

Lines changed: 6 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2,36 +2,29 @@
22

33
[![Install and Test with Pip](https://github.com/cortex-lab/phy/actions/workflows/python-test.yml/badge.svg)](https://github.com/cortex-lab/phy/actions/workflows/python-test.yml)
44
[![codecov.io](https://img.shields.io/codecov/c/github/cortex-lab/phy.svg)](http://codecov.io/github/cortex-lab/phy)
5-
[![Documentation](https://img.shields.io/badge/docs-phy.cortexlab.net-blue.svg)](https://phy.cortexlab.net)
5+
[![Documentation](https://img.shields.io/badge/docs-Read_the_Docs-blue.svg)](https://phy.readthedocs.io/en/latest/)
66
[![GitHub release](https://img.shields.io/github/release/cortex-lab/phy.svg)](https://github.com/cortex-lab/phy/releases/latest)
77
[![PyPI release](https://img.shields.io/pypi/v/phy.svg)](https://pypi.python.org/pypi/phy)
88

99
[**phy**](https://github.com/cortex-lab/phy) is an open-source Python library providing a graphical user interface for visualization and manual curation of large-scale electrophysiological data. It is optimized for high-density multielectrode arrays containing hundreds to thousands of recording sites, especially Neuropixels recordings.
1010

11-
> **Release candidate available:** `phy 2.1.0rc1` is available for testing on PyPI. This maintenance-focused release candidate aims to improve installation and GUI reliability on current systems. See the [release notes](https://phy.readthedocs.io/en/latest/release/) for installation instructions, compatibility notes, and testing guidance.
11+
> **Current release:** `phy 2.1.0` is a maintenance-focused release that improves installation and GUI reliability on current systems. See the [release notes](https://phy.readthedocs.io/en/latest/release/) for details and compatibility notes.
1212
13-
[![phy 2.1.0rc1 screenshot](https://user-images.githubusercontent.com/1942359/74028054-c284b880-49a9-11ea-8815-1b7e727a8644.png)](https://user-images.githubusercontent.com/1942359/74028054-c284b880-49a9-11ea-8815-1b7e727a8644.png)
13+
[![phy 2.1.0 screenshot](https://user-images.githubusercontent.com/1942359/74028054-c284b880-49a9-11ea-8815-1b7e727a8644.png)](https://user-images.githubusercontent.com/1942359/74028054-c284b880-49a9-11ea-8815-1b7e727a8644.png)
1414

1515
## Current status
1616

17-
As of March 2026, `phy 2.1.0rc1` is a maintenance-focused release candidate for the current 2.x line.
17+
As of July 2026, `phy 2.1.0` is the current stable release for the 2.x line.
1818

1919
The main goals of this release are:
2020

2121
* dependency and packaging modernization
2222
* replacing a fragile legacy web-based GUI component with a Qt-native implementation
2323
* improving display reliability on modern systems
24-
* collecting feedback from beta testers before a final `2.1.0` release
24+
* incorporating fixes identified during release-candidate testing
2525

2626
Dataset formats are unchanged. Some plugins that relied on internal HTML or web-based GUI components may need updates.
2727

28-
If you would like to test this release candidate, install it in a fresh Python 3.10+ environment:
29-
30-
```bash
31-
python -m pip install --upgrade pip
32-
pip install "phy==2.1.0rc1"
33-
```
34-
3528
Please report any issues or compatibility regressions on [GitHub issues](https://github.com/cortex-lab/phy/issues).
3629

3730
## Supported workflows
@@ -42,7 +35,7 @@ phy currently provides three main entry points:
4235
* **Kwik GUI**: a legacy workflow for datasets sorted with klusta and klustakwik2
4336
* **Trace GUI**: an experimental raw-data viewer for opening continuous electrophysiology recordings directly
4437

45-
Current testing and maintenance work is focused on modern Linux, macOS, and Windows environments. Linux is still the best-covered platform, but cross-platform testing is active during the `2.1.0rc1` cycle.
38+
Current testing and maintenance work is focused on modern Linux, macOS, and Windows environments. Linux is still the best-covered platform.
4639

4740
## Installation
4841

@@ -61,12 +54,6 @@ If you plan to use the legacy Kwik GUI, also install:
6154
pip install klusta klustakwik2
6255
```
6356

64-
For release-candidate testing specifically, install the exact RC version instead:
65-
66-
```bash
67-
pip install "phy==2.1.0rc1"
68-
```
69-
7057
## Quick start
7158

7259
Open the Template GUI on a spike sorting output directory containing `params.py`:
Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
# phy 2.1.0rc1 announcement text
1+
# phy 2.1.0 announcement text
22

3-
Short copy-paste announcement text for the `2.1.0rc1` release candidate.
3+
Short copy-paste announcement text for the final `2.1.0` release.
44

55
For installation, upgrade, compatibility, and testing details, point people to the live release notes page:
66
https://phy.readthedocs.io/en/latest/release/
@@ -10,19 +10,19 @@ https://phy.readthedocs.io/en/latest/release/
1010
Subject:
1111

1212
```text
13-
phy 2.1.0rc1 release candidate available for testing
13+
phy 2.1.0 released
1414
```
1515

1616
Body:
1717

1818
```text
1919
Dear phy users,
2020
21-
phy 2.1.0rc1 is now available on PyPI for testing. This is a release candidate for a maintenance release of the current 2.x line, focused on fixing several long-standing issues that have built up over recent years while the project had very limited maintenance.
21+
phy 2.1.0 is now available on PyPI. This maintenance release of the current 2.x line focuses on fixing several long-standing issues that built up over recent years while the project had very limited maintenance.
2222
2323
This release does not aim to introduce major new features. The goal is to make phy easier to install and more reliable again on current systems, especially for users who have run into packaging or GUI problems.
2424
25-
If you still use phy, testing and feedback would be very helpful before the final 2.1.0 release.
25+
Thank you to everyone who tested the release candidate and reported issues.
2626
2727
Release notes and compatibility notes:
2828
https://phy.readthedocs.io/en/latest/release/
@@ -34,12 +34,12 @@ https://github.com/cortex-lab/phy/issues
3434
## Copy-paste announcement for GitHub release notes
3535

3636
```text
37-
phy 2.1.0rc1 is now available on PyPI for testing.
37+
phy 2.1.0 is now available on PyPI.
3838
39-
This is a release candidate for a maintenance release of the current 2.x line, focused on fixing several long-standing issues that have accumulated over recent years.
39+
This maintenance release of the current 2.x line focuses on fixing several long-standing issues that accumulated over recent years.
4040
4141
The goal of this release is not to add major new features, but to make phy easier to install and more reliable again on current systems.
4242
43-
For release notes, compatibility notes, and testing guidance, see:
43+
For release notes and compatibility notes, see:
4444
https://phy.readthedocs.io/en/latest/release/
4545
```

docs/index.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22

33
[**phy**](https://github.com/cortex-lab/phy) is an open-source Python library providing a graphical user interface for visualization and manual curation of large-scale electrophysiological data. It is optimized for high-density multielectrode arrays containing hundreds to thousands of recording sites (mostly [Neuropixels probes](https://www.ucl.ac.uk/neuropixels/)).
44

5-
> **Current release candidate:** [phy 2.1.0rc1](release.md) is available for testing on PyPI. This maintenance-focused release candidate aims to improve installation and GUI reliability on current systems. See the [release notes](release.md) for installation instructions, compatibility notes, and testing guidance.
5+
> **Current release:** [phy 2.1.0](release.md) is a maintenance-focused release that improves installation and GUI reliability on current systems. See the [release notes](release.md) for details and compatibility notes.
66
7-
[![phy 2.1.0rc1 screenshot](https://user-images.githubusercontent.com/1942359/74028054-c284b880-49a9-11ea-8815-1b7e727a8644.png)](https://user-images.githubusercontent.com/1942359/74028054-c284b880-49a9-11ea-8815-1b7e727a8644.png)
7+
[![phy 2.1.0 screenshot](https://user-images.githubusercontent.com/1942359/74028054-c284b880-49a9-11ea-8815-1b7e727a8644.png)](https://user-images.githubusercontent.com/1942359/74028054-c284b880-49a9-11ea-8815-1b7e727a8644.png)
88

99
## Spike sorting programs
1010

@@ -20,4 +20,4 @@ klusta is a legacy spike sorting program based on an automatic clustering algori
2020

2121
## Installation instructions
2222

23-
See the [GitHub repository](https://github.com/cortex-lab/phy/) for stable-install instructions, or go to the [release notes](release.md) for the `2.1.0rc1` release-candidate install and testing instructions.
23+
See the [GitHub repository](https://github.com/cortex-lab/phy/) for installation instructions, or go to the [release notes](release.md) for details about `2.1.0`.

docs/release.md

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,20 @@
11
# Release notes
22

3-
Current version is phy v2.1.0rc1 (release candidate 1) (11 Mar 2026).
3+
Current version is phy v2.1.0 (17 Jul 2026).
44

5-
## phy 2.1.0 (upcoming)
5+
## phy 2.1.0 (17 Jul 2026)
6+
7+
This final release incorporates the fixes identified during the `2.1.0rc1` testing period.
68

79
### Added
810

911
* Code of Conduct for project participation and incident reporting.
1012

13+
### Fixed
14+
15+
* Preserve the ClusterView sort order when cluster metadata changes ([#1375](https://github.com/cortex-lab/phy/issues/1375)).
16+
* Display NumPy-typed values correctly in native Qt ClusterView columns ([#1377](https://github.com/cortex-lab/phy/issues/1377)).
17+
1118

1219
## phy 2.1.0rc1
1320

@@ -105,8 +112,7 @@ When reporting issues, please include:
105112

106113
## Historical notes for older releases
107114

108-
The notes below are kept for reference only and concern older releases, not the
109-
current `2.1.0rc1` release candidate.
115+
The notes below are kept for reference only and concern older releases.
110116

111117
### phy 2.0 beta 1 (2020-02)
112118

docs/troubleshooting.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ Here are a few tips if something goes wrong.
66

77
Look at existing [GitHub issues](https://github.com/cortex-lab/phy/issues) to see if someone else had the same problem. If not, feel free to open a new issue by giving a descriptive title and a comprehensive description of the problem, including screenshots if needed.
88

9-
If you are testing `phy 2.1.0rc1`, please also include:
9+
If you are reporting a problem with `phy 2.1.0`, please also include:
1010

1111
* operating system
1212
* Python version

phy/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727

2828
__author__ = 'Cyrille Rossant'
2929
__email__ = 'cyrille.rossant at gmail.com'
30-
__version__ = '2.1.0rc1'
30+
__version__ = '2.1.0'
3131
__version_git__ = __version__ + _git_version()
3232

3333

phy/apps/base.py

Lines changed: 16 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -190,9 +190,7 @@ def get_mean_spike_raw_amplitudes(self, cluster_id):
190190
spike_ids = self._get_amplitude_spike_ids(cluster_id)
191191
return np.mean(self.get_spike_raw_amplitudes(spike_ids))
192192

193-
def _get_waveforms_with_n_spikes(
194-
self, cluster_id, n_spikes_waveforms, current_filter=None
195-
):
193+
def _get_waveforms_with_n_spikes(self, cluster_id, n_spikes_waveforms, current_filter=None):
196194
# HACK: we pass self.raw_data_filter.current_filter so that it is cached properly.
197195
pos = self.model.channel_positions
198196

@@ -205,9 +203,7 @@ def _get_waveforms_with_n_spikes(
205203
# Or keep spikes from a subset of the chunks for performance reasons (decompression will
206204
# happen on the fly here).
207205
else:
208-
spike_ids = self.selector(
209-
n_spikes_waveforms, [cluster_id], subset_chunks=True
210-
)
206+
spike_ids = self.selector(n_spikes_waveforms, [cluster_id], subset_chunks=True)
211207

212208
# Get the best channels.
213209
channel_ids = self.get_best_channels(cluster_id)
@@ -250,9 +246,7 @@ def _set_view_creator(self):
250246
self.view_creator['WaveformView'] = self.create_waveform_view
251247

252248
def _get_waveforms_dict(self):
253-
waveform_functions = _concatenate_parents_attributes(
254-
self.__class__, '_waveform_functions'
255-
)
249+
waveform_functions = _concatenate_parents_attributes(self.__class__, '_waveform_functions')
256250
return {name: getattr(self, method) for name, method in waveform_functions}
257251

258252
def create_waveform_view(self):
@@ -378,9 +372,7 @@ def _get_feature_view_spike_times(self, cluster_id=None, load_all=False):
378372
if len(spike_ids) == 0:
379373
return
380374
spike_times = self._get_spike_times_reordered(spike_ids)
381-
return Bunch(
382-
data=spike_times, spike_ids=spike_ids, lim=(0.0, self.model.duration)
383-
)
375+
return Bunch(data=spike_times, spike_ids=spike_ids, lim=(0.0, self.model.duration))
384376

385377
def _get_spike_features(self, spike_ids, channel_ids):
386378
if len(spike_ids) == 0: # pragma: no cover
@@ -412,10 +404,7 @@ def _get_features(self, cluster_id=None, channel_ids=None, load_all=False):
412404
return self._get_spike_features(spike_ids, channel_ids)
413405

414406
def create_feature_view(self):
415-
if (
416-
self.model.features is None
417-
and getattr(self.model, 'spike_waveforms', None) is None
418-
):
407+
if self.model.features is None and getattr(self.model, 'spike_waveforms', None) is None:
419408
# NOTE: we can still construct the feature view when there are spike waveforms.
420409
return
421410
view = FeatureView(
@@ -579,9 +568,7 @@ def _get_template_waveforms(self, cluster_id):
579568
masks = count / float(count.max())
580569
masks = np.tile(masks.reshape((-1, 1)), (1, len(channel_ids)))
581570
# Get all templates from which this cluster stems from.
582-
templates = [
583-
self.model.get_template(template_id) for template_id in template_ids
584-
]
571+
templates = [self.model.get_template(template_id) for template_id in template_ids]
585572
# Construct the waveforms array.
586573
ns = self.model.n_samples_waveforms
587574
data = np.zeros((len(template_ids), ns, self.model.n_channels))
@@ -922,9 +909,7 @@ def __init__(
922909
_add_log_file(Path(dir_path) / 'phy.log')
923910

924911
# Create or reuse a Model instance (any object)
925-
self.model = (
926-
self._create_model(dir_path=dir_path, **kwargs) if model is None else model
927-
)
912+
self.model = self._create_model(dir_path=dir_path, **kwargs) if model is None else model
928913

929914
# Set up the cache.
930915
self._set_cache(clear_cache)
@@ -945,19 +930,15 @@ def __init__(
945930
# The controller.default_views can be set by the child class, otherwise it is computed
946931
# by concatenating all parents _new_views.
947932
if getattr(self, 'default_views', None) is None:
948-
self.default_views = _concatenate_parents_attributes(
949-
self.__class__, '_new_views'
950-
)
933+
self.default_views = _concatenate_parents_attributes(self.__class__, '_new_views')
951934
self._async_callers = {}
952935
self.config_dir = config_dir
953936

954937
# Clear the GUI state files if needed.
955938
if clear_state:
956939
self._clear_state()
957940

958-
self.selection = Selection(
959-
self
960-
) # keep track of selected clusters, spikes, channels, etc.
941+
self.selection = Selection(self) # keep track of selected clusters, spikes, channels, etc.
961942

962943
# Attach plugins before setting up the supervisor, so that plugins
963944
# can register callbacks to events raised during setup.
@@ -1021,9 +1002,7 @@ def _set_view_creator(self):
10211002
'ClusterScatterView': self.create_cluster_scatter_view,
10221003
'CorrelogramView': self.create_correlogram_view,
10231004
'ISIView': self._make_histogram_view(ISIView, self._get_isi),
1024-
'FiringRateView': self._make_histogram_view(
1025-
FiringRateView, self._get_firing_rate
1026-
),
1005+
'FiringRateView': self._make_histogram_view(FiringRateView, self._get_firing_rate),
10271006
'AmplitudeView': self.create_amplitude_view,
10281007
'ProbeView': self.create_probe_view,
10291008
'RasterView': self.create_raster_view,
@@ -1032,9 +1011,7 @@ def _set_view_creator(self):
10321011
# Spike attributes.
10331012
for name, arr in getattr(self.model, 'spike_attributes', {}).items():
10341013
view_name = f'Spike{name.title()}View'
1035-
self.view_creator[view_name] = self._make_spike_attributes_view(
1036-
view_name, name, arr
1037-
)
1014+
self.view_creator[view_name] = self._make_spike_attributes_view(view_name, name, arr)
10381015

10391016
def _set_cluster_metrics(self):
10401017
"""Set the cluster metrics dictionary with some default metrics."""
@@ -1375,9 +1352,7 @@ def _amplitude_getter(self, cluster_ids, name=None, load_all=False):
13751352
out = []
13761353
n = self.n_spikes_amplitudes if not load_all else None
13771354
# Find the first cluster, used to determine the best channels.
1378-
first_cluster = next(
1379-
cluster_id for cluster_id in cluster_ids if cluster_id is not None
1380-
)
1355+
first_cluster = next(cluster_id for cluster_id in cluster_ids if cluster_id is not None)
13811356
# Best channels of the first cluster.
13821357
channel_ids = self.get_best_channels(first_cluster)
13831358
# Best channel of the first cluster.
@@ -1694,9 +1669,7 @@ def create_misc_actions(self, gui):
16941669
)
16951670
def toggle_spike_reorder(checked):
16961671
"""Toggle spike time reordering."""
1697-
logger.debug(
1698-
'%s spike time reordering.', 'Enable' if checked else 'Disable'
1699-
)
1672+
logger.debug('%s spike time reordering.', 'Enable' if checked else 'Disable')
17001673
emit('toggle_spike_reorder', self, checked)
17011674

17021675
# Action to switch the raw data filter in the trace and waveform views.
@@ -1713,9 +1686,7 @@ def switch_raw_data_filter():
17131686
# Update the waveform view.
17141687
for v in gui.list_views(WaveformView):
17151688
if v.auto_update:
1716-
v.on_select_threaded(
1717-
self.supervisor, self.supervisor.selected, gui=gui
1718-
)
1689+
v.on_select_threaded(self.supervisor, self.supervisor.selected, gui=gui)
17191690
v.ex_status = filter_name
17201691
v.update_status()
17211692

@@ -1734,9 +1705,7 @@ def _add_default_color_schemes(self, view):
17341705

17351706
def group_index(cluster_id):
17361707
group = self.supervisor.cluster_meta.get('group', cluster_id)
1737-
return group_colors.get(
1738-
group, 0
1739-
) # TODO: better handling of colors for custom groups
1708+
return group_colors.get(group, 0) # TODO: better handling of colors for custom groups
17401709

17411710
depth = self.supervisor.cluster_metrics['depth']
17421711
fr = self.supervisor.cluster_metrics['fr']
@@ -1777,8 +1746,7 @@ def create_gui(self, default_views=None, **kwargs):
17771746
subtitle=str(self.dir_path),
17781747
config_dir=self.config_dir,
17791748
local_path=self.cache_dir / 'state.json',
1780-
default_state_path=Path(inspect.getfile(self.__class__)).parent
1781-
/ 'static/state.json',
1749+
default_state_path=Path(inspect.getfile(self.__class__)).parent / 'static/state.json',
17821750
view_creator=self.view_creator,
17831751
default_views=default_views,
17841752
enable_threading=self._enable_threading,

0 commit comments

Comments
 (0)