diff --git a/.github/workflows/caches_cron_job.yml b/.github/workflows/caches_cron_job.yml index 31d409478c..b0587186d9 100644 --- a/.github/workflows/caches_cron_job.yml +++ b/.github/workflows/caches_cron_job.yml @@ -2,11 +2,11 @@ name: Create caches for gin ecephys data and virtual env on: workflow_dispatch: - push: # When something is pushed into main this checks if caches need to be re-created + push: # When something is pushed into main this checks if caches need to be re-created branches: - main schedule: - - cron: "0 12 * * *" # Daily at noon UTC + - cron: "0 12 * * *" # Daily at noon UTC jobs: create-gin-data-cache-if-missing: @@ -19,10 +19,10 @@ jobs: steps: - uses: actions/setup-python@v6 with: - python-version: '3.11' + python-version: "3.10" - uses: astral-sh/setup-uv@v7 with: - python-version: '3.11' + python-version: "3.10" enable-cache: true ignore-nothing-to-cache: true # some runs do not require building a cache (ie macOS) this says this is okay - name: Create the directory to store the data @@ -41,7 +41,7 @@ jobs: with: path: ~/spikeinterface_datasets key: ${{ runner.os }}-datasets-${{ steps.repo_hash.outputs.dataset_hash }} - lookup-only: 'true' # Avoids downloading the data, saving behavior is not affected. + lookup-only: "true" # Avoids downloading the data, saving behavior is not affected. - name: Cache found? run: echo "Cache-hit == ${{steps.cache-datasets.outputs.cache-hit == 'true'}}" shell: bash diff --git a/.github/workflows/core-test.yml b/.github/workflows/core-test.yml index 9142c11705..e64c49851e 100644 --- a/.github/workflows/core-test.yml +++ b/.github/workflows/core-test.yml @@ -6,7 +6,7 @@ on: branches: - main -concurrency: # Cancel previous workflows on the same pull request +concurrency: # Cancel previous workflows on the same pull request group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true @@ -22,10 +22,10 @@ jobs: - uses: actions/checkout@v6 - uses: actions/setup-python@v6 with: - python-version: '3.11' + python-version: "3.10" - uses: astral-sh/setup-uv@v7 with: - python-version: '3.11' + python-version: "3.10" enable-cache: true - name: Install dependencies run: | @@ -35,7 +35,7 @@ jobs: - name: Test core with pytest run: | pytest -m "core" -vv -ra --durations=0 --durations-min=0.001 | tee report.txt; test $? -eq 0 || exit 1 - shell: bash # Necessary for pipeline to work on windows + shell: bash # Necessary for pipeline to work on windows - name: Build test summary run: | uv pip install --system pandas @@ -45,4 +45,4 @@ jobs: python ./.github/scripts/build_job_summary.py report.txt >> $GITHUB_STEP_SUMMARY cat $GITHUB_STEP_SUMMARY rm report.txt - shell: bash # Necessary for pipeline to work on windows + shell: bash # Necessary for pipeline to work on windows diff --git a/.github/workflows/cross_version_serialization.yml b/.github/workflows/cross_version_serialization.yml index 10e9829c65..4712a3976d 100644 --- a/.github/workflows/cross_version_serialization.yml +++ b/.github/workflows/cross_version_serialization.yml @@ -20,11 +20,11 @@ on: # watches core only; a break introduced in generation/ or preprocessing/ would not # trigger it. paths: - - 'src/spikeinterface/core/**' - - '.github/scripts/serialization/**' - - '.github/workflows/cross_version_serialization.yml' + - "src/spikeinterface/core/**" + - ".github/scripts/serialization/**" + - ".github/workflows/cross_version_serialization.yml" schedule: - - cron: '0 4 * * 0' # weekly, Sunday 04:00 UTC + - cron: "0 4 * * 0" # weekly, Sunday 04:00 UTC workflow_dispatch: concurrency: @@ -43,13 +43,13 @@ jobs: steps: - uses: actions/setup-python@v6 with: - python-version: '3.11' + python-version: "3.10" - id: set env: GITHUB_EVENT_NAME: ${{ github.event_name }} # Support floor: oldest minor to test. Below this, installs fail on the CI # Python (numcodecs dependency rot in 0.100/0.101). Raise when 0.102 rots. - MIN_VERSION_TO_TEST: "0.102" # We will change this when making breaking changes (hopefully not too often) + MIN_VERSION_TO_TEST: "0.102" # We will change this when making breaking changes (hopefully not too often) run: | python -m pip install -q packaging available_versions=$(python -m pip index versions spikeinterface | sed -n 's/.*Available versions: //p') @@ -93,17 +93,17 @@ jobs: - name: Set up Python uses: actions/setup-python@v6 with: - python-version: '3.11' + python-version: "3.10" - name: Set up uv uses: astral-sh/setup-uv@v7 with: - python-version: '3.11' + python-version: "3.10" enable-cache: false - name: Generate fixtures with spikeinterface ${{ matrix.si-version }} run: | - uv run --isolated --no-project --python 3.11 \ + uv run --isolated --no-project --python 3.10 \ --with "spikeinterface[core]==${{ matrix.si-version }}" \ python .github/scripts/serialization/serialize_objects.py "$SI_SERIALIZATION_FIXTURES_DIR" diff --git a/.github/workflows/deepinterpolation.yml b/.github/workflows/deepinterpolation.yml index 037d9c566e..913022ac6e 100644 --- a/.github/workflows/deepinterpolation.yml +++ b/.github/workflows/deepinterpolation.yml @@ -6,7 +6,7 @@ on: branches: - main -concurrency: # Cancel previous workflows on the same pull request +concurrency: # Cancel previous workflows on the same pull request group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true @@ -22,10 +22,10 @@ jobs: - uses: actions/checkout@v6 - uses: actions/setup-python@v6 with: - python-version: '3.10' + python-version: "3.10" - uses: astral-sh/setup-uv@v7 with: - python-version: '3.10' + python-version: "3.10" enable-cache: false - name: Get changed files id: changed-files @@ -55,4 +55,4 @@ jobs: if: ${{ steps.modules-changed.outputs.DEEPINTERPOLATION_CHANGED == 'true' }} run: | pytest -v src/spikeinterface/preprocessing/deepinterpolation - shell: bash # Necessary for pipeline to work on windows + shell: bash # Necessary for pipeline to work on windows diff --git a/.github/workflows/test_containers_docker.yml b/.github/workflows/test_containers_docker.yml index bd459be440..65bb2f90e9 100644 --- a/.github/workflows/test_containers_docker.yml +++ b/.github/workflows/test_containers_docker.yml @@ -2,7 +2,6 @@ name: Test sorter images in Docker on: workflow_dispatch - jobs: test-images: name: Test on (${{ matrix.os }}) @@ -15,17 +14,15 @@ jobs: - uses: actions/checkout@v6 - uses: actions/setup-python@v6 with: - python-version: '3.10' + python-version: "3.10" - uses: astral-sh/setup-uv@v7 with: - python-version: '3.10' + python-version: "3.10" enable-cache: true - name: Python version - run: - python --version + run: python --version - name: Docker version - run: - docker --version + run: docker --version - name: Install dependencies run: | uv pip install --system pytest diff --git a/.github/workflows/test_containers_singularity.yml b/.github/workflows/test_containers_singularity.yml index 0c92163f46..c39b98357b 100644 --- a/.github/workflows/test_containers_singularity.yml +++ b/.github/workflows/test_containers_singularity.yml @@ -2,7 +2,6 @@ name: Test sorter images in singularity on: workflow_dispatch - jobs: test-images: name: Test on (${{ matrix.os }}) @@ -11,25 +10,23 @@ jobs: fail-fast: false matrix: # "macos-latest", "windows-latest" - os: ["ubuntu-latest", ] + os: ["ubuntu-latest"] steps: - uses: actions/checkout@v6 - uses: actions/setup-python@v6 with: - python-version: '3.10' + python-version: "3.10" - uses: astral-sh/setup-uv@v7 with: - python-version: '3.10' + python-version: "3.10" enable-cache: true - uses: eWaterCycle/setup-singularity@v7 with: singularity-version: 3.8.7 - name: Python version - run: - python --version + run: python --version - name: Singularity version - run: - singularity --version + run: singularity --version - name: Install dependencies run: | uv pip install --system pytest diff --git a/.github/workflows/test_imports.yml b/.github/workflows/test_imports.yml index 6af1e1a253..d3d5b5dbb1 100644 --- a/.github/workflows/test_imports.yml +++ b/.github/workflows/test_imports.yml @@ -22,10 +22,10 @@ jobs: - uses: actions/checkout@v6 - uses: actions/setup-python@v6 with: - python-version: "3.11" + python-version: "3.10" - uses: astral-sh/setup-uv@v7 with: - python-version: "3.11" + python-version: "3.10" enable-cache: true - name: Install Spikeinterface with only core dependencies run: | diff --git a/pyproject.toml b/pyproject.toml index 31951dd026..0fc7a6a9bb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -68,16 +68,15 @@ extractors = [ "pynwb>=2.6.0", "hdmf-zarr>=0.11.0", "pyedflib>=0.1.30", - "sonpy;python_version<'3.10'", "lxml", # lxml for neuroscope "scipy", - "ibllib>=3.4.1;python_version>='3.10'", # streaming IBL + "ibllib>=3.4.1", # streaming IBL "pymatreader>=0.0.32", # For cell explorer matlab files "zugbruecke>=0.2; sys_platform!='win32'", # For plexon2 ] streaming_extractors = [ - "ibllib>=3.4.1;python_version>='3.10'", # streaming IBL + "ibllib>=3.4.1", # streaming IBL # Following dependencies are for streaming with nwb files "pynwb>=2.6.0", "fsspec", @@ -104,19 +103,19 @@ postprocessing = [ metrics = [ "scikit-learn<1.8", "scipy", - "pandas<3", + "pandas", "numba>=0.59", ] comparison = [ "scipy", - "pandas<3", + "pandas", "networkx", ] generation = [ "scipy", - "pandas<3", + "pandas", ] sorters = [ @@ -149,7 +148,7 @@ curation = [ "spikeinterface[metrics]", "spikeinterface[postprocessing]", "spikeinterface[comparison]", - "pandas<3", + "pandas", "scikit-learn<1.8", "skops", "huggingface_hub", @@ -160,7 +159,7 @@ curation = [ exporters = [ "spikeinterface[postprocessing]", "spikeinterface[widgets]", - "pandas<3", + "pandas", "pynapple", ] @@ -221,9 +220,9 @@ docs = [ # for notebooks in the gallery "MEArec", # Use as an example - "pandas<3", # in the modules gallery comparison tutorial + "pandas", # in the modules gallery comparison tutorial "hdbscan>=0.8.33", # For sorters spykingcircus2 + tridesclous - "numba", # For many postprocessing functions + "numba>=0.59", # For many postprocessing functions "networkx", "seaborn", "skops", # For automated curation @@ -248,7 +247,7 @@ test-common = [ test-core = [ {include-group = "test-common"}, - "pandas<3", + "pandas", ] test-extractors = [ @@ -270,7 +269,7 @@ test-streaming-extractors = [ test-preprocessing = [ {include-group = "test-common"}, - "ibllib>=3.4.1;python_version>='3.10'", + "ibllib>=3.4.1", "pooch", "datalad", "torch", # preprocessing tests exercise sortingcomponents.motion.dredge @@ -278,7 +277,7 @@ test-preprocessing = [ test-postprocessing = [ {include-group = "test-common"}, - "pandas<3", + "pandas", ] test-metrics = [{include-group = "test-common"}] @@ -305,6 +304,7 @@ test-widgets = [ "scikit-learn", "pyvips", "networkx", + "upsetplot-bombcell", # pandas>=3 compatible fork used by BombcellUpsetPlotWidget ] test-exporters = [{include-group = "test-common"}] diff --git a/readthedocs.yml b/readthedocs.yml index 486ace7d53..c63f9e5053 100644 --- a/readthedocs.yml +++ b/readthedocs.yml @@ -4,7 +4,7 @@ version: 2 build: os: "ubuntu-24.04" tools: - python: "3.10" + python: "3.13" commands: - asdf plugin add uv - asdf install uv latest diff --git a/src/spikeinterface/extractors/tests/test_iblextractors.py b/src/spikeinterface/extractors/tests/test_iblextractors.py index fa9d59ec47..eb87b34829 100644 --- a/src/spikeinterface/extractors/tests/test_iblextractors.py +++ b/src/spikeinterface/extractors/tests/test_iblextractors.py @@ -11,9 +11,6 @@ EID = "e2b845a1-e313-4a08-bc61-a5f662ed295e" PID = "80f6ffdd-f692-450f-ab19-cd6d45bfd73e" -if sys.version_info < (3, 10): - pytest.skip("IBL support requires Python 3.10 or higher", allow_module_level=True) - @pytest.mark.streaming_extractors class TestDefaultIblRecordingExtractorApBand(TestCase): diff --git a/src/spikeinterface/extractors/tests/test_neoextractors.py b/src/spikeinterface/extractors/tests/test_neoextractors.py index 5601c08c6f..7522218d52 100644 --- a/src/spikeinterface/extractors/tests/test_neoextractors.py +++ b/src/spikeinterface/extractors/tests/test_neoextractors.py @@ -392,20 +392,20 @@ class Spike2RecordingTest(RecordingCommonTestSuite, unittest.TestCase): ] -@pytest.mark.skipif( - version.parse(platform.python_version()) >= version.parse("3.10") or platform.system() == "Darwin", - reason="Sonpy only testing with Python < 3.10 and not supported on macOS!", -) -class CedRecordingTest(RecordingCommonTestSuite, unittest.TestCase): - ExtractorClass = CedRecordingExtractor - downloads = [ - "spike2/130322-1LY.smr", - "spike2/m365_1sec.smrx", - ] - entities = [ - ("spike2/130322-1LY.smr", {"stream_id": "1"}), - "spike2/m365_1sec.smrx", - ] +# @pytest.mark.skipif( +# version.parse(platform.python_version()) >= version.parse("3.10") or platform.system() == "Darwin", +# reason="Sonpy only testing with Python < 3.10 and not supported on macOS!", +# ) +# class CedRecordingTest(RecordingCommonTestSuite, unittest.TestCase): +# ExtractorClass = CedRecordingExtractor +# downloads = [ +# "spike2/130322-1LY.smr", +# "spike2/m365_1sec.smrx", +# ] +# entities = [ +# ("spike2/130322-1LY.smr", {"stream_id": "1"}), +# "spike2/m365_1sec.smrx", +# ] @pytest.mark.skipif(platform.system() == "Darwin", reason="Maxwell plugin not supported on macOS") diff --git a/src/spikeinterface/widgets/bombcell_curation.py b/src/spikeinterface/widgets/bombcell_curation.py index ee2cc92832..38b4e6c751 100644 --- a/src/spikeinterface/widgets/bombcell_curation.py +++ b/src/spikeinterface/widgets/bombcell_curation.py @@ -153,6 +153,12 @@ def plot_matplotlib(self, data_plot, **backend_kwargs): if len(upset_data) == 0: continue + if upset_data.index.nlevels == 1: + warnings.warn( + f"Skipping UpSet plot: only one metric, {memberships[0][0]}, was responsible for all failures." + ) + continue + fig = plt.figure(figsize=(12, 6)) UpSet( upset_data, diff --git a/src/spikeinterface/widgets/comparison.py b/src/spikeinterface/widgets/comparison.py index 2762c78e6b..d111124522 100644 --- a/src/spikeinterface/widgets/comparison.py +++ b/src/spikeinterface/widgets/comparison.py @@ -125,8 +125,8 @@ def plot_matplotlib(self, data_plot, **backend_kwargs): N1 = scores.shape[0] N2 = scores.shape[1] - unit_ids1 = scores.index.values - unit_ids2 = scores.columns.values + unit_ids1 = scores.index.to_numpy() + unit_ids2 = scores.columns.to_numpy() # Using matshow here just because it sets the ticks up nicely. imshow is faster. self.ax.matshow(scores.values, cmap="Greens") diff --git a/src/spikeinterface/widgets/metrics.py b/src/spikeinterface/widgets/metrics.py index 37bf138424..0631dc52c9 100644 --- a/src/spikeinterface/widgets/metrics.py +++ b/src/spikeinterface/widgets/metrics.py @@ -95,7 +95,7 @@ def plot_matplotlib(self, data_plot, **backend_kwargs): backend_kwargs["num_axes"] = num_metrics**2 backend_kwargs["ncols"] = num_metrics - all_unit_ids = metrics.index.values + all_unit_ids = metrics.index.to_numpy() self.figure, self.axes, self.ax = make_mpl_figure(**backend_kwargs) @@ -277,7 +277,7 @@ def plot_figpack(self, data_plot, **backend_kwargs): metric_names = list(metrics.columns) if dp.unit_ids is None: - unit_ids = metrics.index.values + unit_ids = metrics.index.to_numpy() else: unit_ids = dp.unit_ids unit_ids = make_serializable(unit_ids) diff --git a/src/spikeinterface/widgets/utils_figpack.py b/src/spikeinterface/widgets/utils_figpack.py index 4aa28b38f1..9d150980a5 100644 --- a/src/spikeinterface/widgets/utils_figpack.py +++ b/src/spikeinterface/widgets/utils_figpack.py @@ -36,7 +36,15 @@ def import_figpack_or_sortingview(use_sortingview: bool): def make_serializable(*args): - dict_to_serialize = {int(i): a for i, a in enumerate(args)} + def _to_native(a): + # Under pandas >= 3, string Index/columns return Arrow-backed ExtensionArrays + # (e.g. ArrowStringArray) from `.values`, which are not JSON serializable. + # Convert any pandas array/Index to a plain numpy array before serialization. + if not isinstance(a, np.ndarray) and hasattr(a, "to_numpy"): + a = a.to_numpy() + return a + + dict_to_serialize = {int(i): _to_native(a) for i, a in enumerate(args)} serializable_dict = check_json(dict_to_serialize) returns = () for i in range(len(args)):