Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
ed5ba89
wip--remove class from init
zm711 May 2, 2025
270d8af
fix __all__
zm711 May 2, 2025
20c3d97
add __all__ to extractors
zm711 May 2, 2025
3970624
different strategy for displaying dicts
zm711 May 2, 2025
21bea25
different strategy again for __all__
zm711 May 2, 2025
3e9119a
fix sorters
zm711 May 2, 2025
95fd9ea
fix ironclust
zm711 May 2, 2025
85c65af
WIP --fix testing
zm711 May 2, 2025
1d6ccb5
finish fixing testing
zm711 May 2, 2025
c7f58d3
additional fixes
zm711 May 3, 2025
d5f2cf0
preprocessing return function too
zm711 May 5, 2025
ab0081f
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] May 5, 2025
92d7a60
list -> classes
zm711 May 15, 2025
433874b
more list-> classes
zm711 May 15, 2025
71c922f
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] May 15, 2025
9555f85
another classes
zm711 May 15, 2025
9b341d6
fix two docs
zm711 May 15, 2025
dc28af4
Merge branch 'main' into remove-class
zm711 May 15, 2025
b8fcac7
add dep warning to classes
zm711 May 16, 2025
84662f8
fix * import for si.full
zm711 May 16, 2025
8819de9
heberto suggestion
zm711 May 16, 2025
3283df5
Heberto suggestion again
zm711 May 16, 2025
8d5bb91
more doc fixes
zm711 May 16, 2025
3790f32
add dep warning to neo classes
zm711 May 18, 2025
fda7de5
Merge branch 'main' into remove-class
zm711 May 18, 2025
6c6a756
oops
zm711 May 18, 2025
e392a54
merge-main
zm711 Jun 2, 2025
900cdfe
heberto feedback
zm711 Jun 2, 2025
a21b257
better comment + typo fix
zm711 Jun 2, 2025
b0c72c0
typo from fixing merge conflict
zm711 Jun 2, 2025
fbde75f
Merge branch 'main' into remove-class
zm711 Jun 3, 2025
14bfb79
Update src/spikeinterface/extractors/extractor_classes.py
zm711 Jun 3, 2025
59ebeb3
final heberto comments
zm711 Jun 3, 2025
23a024d
Merge branch 'main' into remove-class
zm711 Jun 3, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions examples/tutorials/core/plot_4_sorting_analyzer.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,8 @@
##############################################################################
# Let's now instantiate the recording and sorting objects:

recording = se.MEArecRecordingExtractor(local_path)
recording, sorting = se.read_mearec(local_path)
print(recording)
sorting = se.MEArecSortingExtractor(local_path)
print(sorting)

###############################################################################
Expand Down
7 changes: 1 addition & 6 deletions examples/tutorials/extractors/plot_1_read_various_formats.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@
# :py:class:`~spikeinterface.extractors.Spike2RecordingExtractor` object:
#

recording = se.Spike2RecordingExtractor(spike2_file_path, stream_id="0")
recording = se.read_spike2(spike2_file_path, stream_id="0")
print(recording)

##############################################################################
Expand All @@ -75,11 +75,6 @@
print(sorting)
print(type(sorting))

##############################################################################
# The :py:func:`~spikeinterface.extractors.read_mearec` function is equivalent to:

recording = se.MEArecRecordingExtractor(mearec_folder_path)
sorting = se.MEArecSortingExtractor(mearec_folder_path)

##############################################################################
# SI objects (:py:class:`~spikeinterface.core.BaseRecording` and :py:class:`~spikeinterface.core.BaseSorting`)
Expand Down
61 changes: 58 additions & 3 deletions src/spikeinterface/extractors/__init__.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,64 @@
from .extractorlist import *
from .extractor_classes import *

from .toy_example import toy_example
from .bids import read_bids
from .toy_example import toy_example as toy_example
Comment thread
zm711 marked this conversation as resolved.
from .bids import read_bids as read_bids


from .neuropixels_utils import get_neuropixels_channel_groups, get_neuropixels_sample_shifts

from .neoextractors import get_neo_num_blocks, get_neo_streams

from warnings import warn


# deprecation of class import idea from neuroconv
# this __getattr__ is only triggered if the normal lookup fails so import
# any of our functions is fine but if someone tries to import a class this raises
# the warning and then returns the "function" version which will look the same
# to the end-user
# to be removed after version 0.105.0
def __getattr__(extractor_name):
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Amazing, you are doing very sophisticated stuff. Maybe too much for me but I really admire.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was a pain to get working. But Heberto makes a great point that we don't want to pull the rug out from under our devs who build off of spikeinterface. So we warn them for a couple versions then wee can clean this all up.

# we need this trick to allow us to use import * for spikeinterface.full
if extractor_name == "__all__":
__all__ = []
for imp in globals():
# need to remove a bunch of builtins etc that shouldn't be part of all
if imp[0] != "_" and imp != "warn" and imp != "extractor_name":
__all__.append(imp)
return __all__
all_extractors = list(recording_extractor_full_dict.values())
all_extractors += list(sorting_extractor_full_dict.values())
all_extractors += list(event_extractor_full_dict.values())
all_extractors += list(snippets_extractor_full_dict.values())
# special cases because they don't have simple wrappers
Comment thread
chrishalcrow marked this conversation as resolved.
# instead a single wrapper maps to multiple classes so we return
# each class to check it
from .neoextractors import (
MEArecRecordingExtractor,
MEArecSortingExtractor,
OpenEphysBinaryEventExtractor,
OpenEphysBinaryRecordingExtractor,
OpenEphysLegacyRecordingExtractor,
SpikeGLXEventExtractor,
)

all_extractors += [
MEArecRecordingExtractor,
MEArecSortingExtractor,
OpenEphysBinaryEventExtractor,
OpenEphysBinaryRecordingExtractor,
OpenEphysLegacyRecordingExtractor,
SpikeGLXEventExtractor,
]
for reading_function in all_extractors:
if extractor_name == reading_function.__name__:
dep_msg = (
"Importing classes at __init__ has been deprecated in favor of only importing function-size wrappers "
"and will be removed in 0.105.0. For developers that prefer working with the class versions of extractors "
"they can be imported from spikeinterface.extractors.extractor_classes"
)
warn(dep_msg)
return reading_function
# this is necessary for objects that we don't support
# normally this is an ImportError but since this is in the __getattr__ pytest needs an AttributeError
raise AttributeError(f"cannot import name '{extractor_name}' from '{__name__}'")
202 changes: 202 additions & 0 deletions src/spikeinterface/extractors/extractor_classes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
from __future__ import annotations


# most important extractor are in spikeinterface.core
from spikeinterface.core import (
BinaryFolderRecording,
BinaryRecordingExtractor,
NumpyRecording,
NpzSortingExtractor,
NumpySorting,
NpySnippetsExtractor,
ZarrRecordingExtractor,
ZarrSortingExtractor,
Comment thread
zm711 marked this conversation as resolved.
read_binary,
read_zarr,
read_npz_sorting,
read_npy_snippets,
)

# sorting/recording/event from neo
from .neoextractors import *

# non-NEO objects implemented in neo folder
# keep for reference Currently pulling from neoextractor __init__
# from .neoextractors import NeuroScopeSortingExtractor, MaxwellEventExtractor

# NWB sorting/recording/event
from .nwbextractors import (
NwbRecordingExtractor,
NwbSortingExtractor,
NwbTimeSeriesExtractor,
read_nwb,
read_nwb_recording,
read_nwb_sorting,
read_nwb_timeseries,
)

from .cbin_ibl import CompressedBinaryIblExtractor, read_cbin_ibl
from .iblextractors import IblRecordingExtractor, IblSortingExtractor, read_ibl_recording, read_ibl_sorting
from .mcsh5extractors import MCSH5RecordingExtractor, read_mcsh5
from .whitematterrecordingextractor import WhiteMatterRecordingExtractor, read_whitematter

# sorting extractors in relation with a sorter
from .cellexplorersortingextractor import CellExplorerSortingExtractor, read_cellexplorer
from .klustaextractors import KlustaSortingExtractor, read_klusta
from .hdsortextractors import HDSortSortingExtractor, read_hdsort
from .mclustextractors import MClustSortingExtractor, read_mclust
from .waveclustextractors import WaveClusSortingExtractor, read_waveclus
from .yassextractors import YassSortingExtractor, read_yass
from .combinatoextractors import CombinatoSortingExtractor, read_combinato
from .tridesclousextractors import TridesclousSortingExtractor, read_tridesclous
from .spykingcircusextractors import SpykingCircusSortingExtractor, read_spykingcircus
from .herdingspikesextractors import HerdingspikesSortingExtractor, read_herdingspikes
from .mdaextractors import MdaRecordingExtractor, MdaSortingExtractor, read_mda_recording, read_mda_sorting
from .phykilosortextractors import PhySortingExtractor, KiloSortSortingExtractor, read_phy, read_kilosort
from .sinapsrecordingextractors import (
SinapsResearchPlatformRecordingExtractor,
SinapsResearchPlatformH5RecordingExtractor,
read_sinaps_research_platform,
read_sinaps_research_platform_h5,
)

# sorting in relation with simulator
from .shybridextractors import (
SHYBRIDRecordingExtractor,
SHYBRIDSortingExtractor,
read_shybrid_recording,
read_shybrid_sorting,
)

# snippers
from .waveclussnippetstextractors import WaveClusSnippetsExtractor, read_waveclus_snippets


# misc
from .alfsortingextractor import ALFSortingExtractor, read_alf_sorting


###############################################################################################
# the following code is necessary for controlling what the end user imports from spikeinterface.
# The strategy has three goals:
#
# * A mapping from the original class to its wrapper (because that's what we want to expose)
# * A mapping from the original class to its wrapper string (because of __all__)
# * A mapping from format to the class wrapper for convenience (exposed to users for ease of use)
#
# To achieve these there goals we do the following:
#
# 1) we line up each class with its wrapper that returns a snakecase version of the class (in some docs called
# the "function" version, although this is just a wrapper of the underlying class)
# 2) we do (1) by creating nested dicts where the key is the original class and the values are a nested dict with
# 3) a "wrapper_class" key which returns the wrapper to be exposed to the end user and
# 4) a "wrapper_string" which is added to the __all__ attribute of the __init__. This is necessary because __all__
# can only accept a list of strings
# 5) Finally we create dictionaries exposed to the user where we return a formatted file format as a key along
# with the value being the wrapper (see the comment below for examples for this dict)
#
# Note that some formats (e.g. binary and numpy) still use the class format as they aren't read-only (i.e. they
# have no wrapper)

Comment thread
h-mayorquin marked this conversation as resolved.
Comment thread
h-mayorquin marked this conversation as resolved.
_recording_extractor_full_dict = {
# core extractors that are returned as classes
BinaryFolderRecording: dict(wrapper_string="BinaryFolderRecording", wrapper_class=BinaryFolderRecording),
BinaryRecordingExtractor: dict(wrapper_string="BinaryRecordingExtractor", wrapper_class=BinaryRecordingExtractor),
ZarrRecordingExtractor: dict(wrapper_string="ZarrRecordingExtractor", wrapper_class=ZarrRecordingExtractor),
# natively implemented in spikeinterface.extractors
NumpyRecording: dict(wrapper_string="NumpyRecording", wrapper_class=NumpyRecording),
SHYBRIDRecordingExtractor: dict(wrapper_string="read_shybrid_recording", wrapper_class=read_shybrid_recording),
MdaRecordingExtractor: dict(wrapper_string="read_mda_recording", wrapper_class=read_mda_recording),
NwbRecordingExtractor: dict(wrapper_string="read_nwb_recording", wrapper_class=read_nwb_recording),
NwbTimeSeriesExtractor: dict(wrapper_string="read_nwb_timeseries", wrapper_class=read_nwb_timeseries),
# others
CompressedBinaryIblExtractor: dict(wrapper_string="read_cbin_ibl", wrapper_class=read_cbin_ibl),
IblRecordingExtractor: dict(wrapper_string="read_ibl_recording", wrapper_class=read_ibl_recording),
MCSH5RecordingExtractor: dict(wrapper_string="read_mcsh5", wrapper_class=read_mcsh5),
SinapsResearchPlatformRecordingExtractor: dict(
wrapper_string="read_sinaps_research_platform", wrapper_class=read_sinaps_research_platform
),
SinapsResearchPlatformH5RecordingExtractor: dict(
wrapper_string="read_sinaps_research_platform_h5", wrapper_class=read_sinaps_research_platform_h5
),
WhiteMatterRecordingExtractor: dict(wrapper_string="read_whitematter", wrapper_class=read_whitematter),
}
_recording_extractor_full_dict.update(neo_recording_extractors_dict)

_sorting_extractor_full_dict = {
NpzSortingExtractor: dict(wrapper_string="read_npz_sorting", wrapper_class=read_npz_sorting),
ZarrSortingExtractor: dict(wrapper_string="ZarrSortingExtractor", wrapper_class=ZarrSortingExtractor),
NumpySorting: dict(wrapper_string="NumpySorting", wrapper_class=NumpySorting),
# natively implemented in spikeinterface.extractors
MdaSortingExtractor: dict(wrapper_string="read_mda_sorting", wrapper_class=read_mda_sorting),
SHYBRIDSortingExtractor: dict(wrapper_string="read_shybrid_sorting", wrapper_class=read_shybrid_sorting),
ALFSortingExtractor: dict(wrapper_string="read_alf_sorting", wrapper_class=read_alf_sorting),
KlustaSortingExtractor: dict(wrapper_string="read_klusta", wrapper_class=read_klusta),
HDSortSortingExtractor: dict(wrapper_string="read_hdsort", wrapper_class=read_hdsort),
MClustSortingExtractor: dict(wrapper_string="read_mclust", wrapper_class=read_mclust),
WaveClusSortingExtractor: dict(wrapper_string="read_waveclus", wrapper_class=read_waveclus),
YassSortingExtractor: dict(wrapper_string="read_yass", wrapper_class=read_yass),
CombinatoSortingExtractor: dict(wrapper_string="read_combinato", wrapper_class=read_combinato),
TridesclousSortingExtractor: dict(wrapper_string="read_tridesclous", wrapper_class=read_tridesclous),
SpykingCircusSortingExtractor: dict(wrapper_string="read_spykingcircus", wrapper_class=read_spykingcircus),
HerdingspikesSortingExtractor: dict(wrapper_string="read_herdingspikes", wrapper_class=read_herdingspikes),
KiloSortSortingExtractor: dict(wrapper_string="read_kilosort", wrapper_class=read_kilosort),
PhySortingExtractor: dict(wrapper_string="read_phy", wrapper_class=read_phy),
NwbSortingExtractor: dict(wrapper_string="read_nwb_sorting", wrapper_class=read_nwb_sorting),
IblSortingExtractor: dict(wrapper_string="read_ibl_sorting", wrapper_class=read_ibl_sorting),
CellExplorerSortingExtractor: dict(wrapper_string="read_cellexplorer", wrapper_class=read_cellexplorer),
}
_sorting_extractor_full_dict.update(neo_sorting_extractors_dict)

# events only from neo
_event_extractor_full_dict = neo_event_extractors_dict

_snippets_extractor_full_dict = {
NpySnippetsExtractor: dict(wrapper_string="read_npy_snippets", wrapper_class=read_npy_snippets),
WaveClusSnippetsExtractor: dict(wrapper_string="read_waveclus_snippets", wrapper_class=read_waveclus_snippets),
}

############################################################################################################
# Organize the possible extractors into a user facing format with keys being extractor names
# (e.g. 'intan' , 'kilosort') and values being the appropriate Extractor class returned as its wrapper
# (e.g. IntanRecordingExtractor, KiloSortSortingExtractor)
# An important note is the the formats are returned after performing `.lower()` so a format like
# SpikeGLX will be a key of 'spikeglx'
# for example if we wanted to create a recording from an intan file we could do the following:
# >>> recording = se.recording_extractor_full_dict['intan'](file_path='path/to/data.rhd')


Comment thread
h-mayorquin marked this conversation as resolved.
recording_extractor_full_dict = {
rec_class.__name__.replace("Recording", "").replace("Extractor", "").lower(): rec_func["wrapper_class"]
for rec_class, rec_func in _recording_extractor_full_dict.items()
}
sorting_extractor_full_dict = {
sort_class.__name__.replace("Sorting", "").replace("Extractor", "").lower(): sort_func["wrapper_class"]
for sort_class, sort_func in _sorting_extractor_full_dict.items()
}
event_extractor_full_dict = {
event_class.__name__.replace("Event", "").replace("Extractor", "").lower(): event_func["wrapper_class"]
for event_class, event_func in _event_extractor_full_dict.items()
}
snippets_extractor_full_dict = {
snippets_class.__name__.replace("Snippets", "").replace("Extractor", "").lower(): snippets_func["wrapper_class"]
for snippets_class, snippets_func in _snippets_extractor_full_dict.items()
}


# we only do the functions in the init rather than pull in the classes
Comment thread
h-mayorquin marked this conversation as resolved.
__all__ = [func["wrapper_string"] for func in _recording_extractor_full_dict.values()]
__all__ += [func["wrapper_string"] for func in _sorting_extractor_full_dict.values()]
__all__ += [func["wrapper_string"] for func in _event_extractor_full_dict.values()]
__all__ += [func["wrapper_string"] for func in _snippets_extractor_full_dict.values()]
__all__.extend(
[
"read_nwb", # convenience function for multiple nwb formats
"recording_extractor_full_dict",
"sorting_extractor_full_dict",
"event_extractor_full_dict",
"snippets_extractor_full_dict",
"read_binary", # convenience function for binary formats
"read_zarr",
]
)
Loading