-
Notifications
You must be signed in to change notification settings - Fork 195
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add extractors for SiNAPS Research Platform #2952
Merged
Merged
Changes from 14 commits
Commits
Show all changes
20 commits
Select commit
Hold shift + click to select a range
ee25d96
Add sinaps research platform recording
alejoe91 ddbce70
Add an H5 extractor for sinaps research platform
NinelK 6d0cd85
Fix OFFSET, variable naming and importing h5py
NinelK c3dbd28
Add 0 offset to support rescaling
NinelK 6c12f5c
Merge pull request #23 from NinelK/sinaps_h5
alejoe91 cb5e271
Attach a SiNAPS probe to recording
NinelK 1f396f4
Fix unsigned to signed
NinelK 5b82b3f
Fix AUX channels which should not be attached to a probe
NinelK 2526a1b
Fix _kwargs in extractors
NinelK d30a916
Merge branch 'main' into sinaps
NinelK 0bd4248
Merge branch 'main' into sinaps
zm711 0fde0d2
Run black locally
NinelK 3409418
Merge branch 'sinaps' of https://github.com/NinelK/spikeinterface int…
NinelK ca1c713
Merge branch 'main' into sinaps
NinelK f533225
Add unsigned offset to sinaps extractor, typing, docs, and cleaning
alejoe91 f089913
Merge branch 'main' into sinaps
alejoe91 c044633
fix extractorlist
alejoe91 b5d3135
Merge branch 'sinaps' of github.com:NinelK/spikeinterface into sinaps
alejoe91 465be42
Fix a missing argument (num_bits)
NinelK bfb42c5
Run black
NinelK File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
114 changes: 114 additions & 0 deletions
114
src/spikeinterface/extractors/sinapsrecordingextractor.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
from pathlib import Path | ||
import numpy as np | ||
|
||
from probeinterface import get_probe | ||
|
||
from ..core import BinaryRecordingExtractor, ChannelSliceRecording | ||
from ..core.core_tools import define_function_from_class | ||
|
||
|
||
class SinapsResearchPlatformRecordingExtractor(ChannelSliceRecording): | ||
extractor_name = "SinapsResearchPlatform" | ||
mode = "file" | ||
name = "sinaps_research_platform" | ||
|
||
def __init__(self, file_path, stream_name="filt"): | ||
from ..preprocessing import UnsignedToSignedRecording | ||
|
||
file_path = Path(file_path) | ||
meta_file = file_path.parent / f"metadata_{file_path.stem}.txt" | ||
meta = parse_sinaps_meta(meta_file) | ||
|
||
num_aux_channels = meta["nbHWAux"] + meta["numberUserAUX"] | ||
num_total_channels = 2 * meta["nbElectrodes"] + num_aux_channels | ||
num_electrodes = meta["nbElectrodes"] | ||
sampling_frequency = meta["samplingFreq"] | ||
|
||
probe_type = meta["probeType"] | ||
# channel_locations = meta["electrodePhysicalPosition"] # will be depricated soon by Sam, switching to probeinterface | ||
num_shanks = meta["nbShanks"] | ||
num_electrodes_per_shank = meta["nbElectrodesShank"] | ||
num_bits = int(np.log2(meta["nbADCLevels"])) | ||
|
||
# channel_groups = [] | ||
# for i in range(num_shanks): | ||
# channel_groups.extend([i] * num_electrodes_per_shank) | ||
|
||
gain_ephys = meta["voltageConverter"] | ||
gain_aux = meta["voltageAUXConverter"] | ||
|
||
recording = BinaryRecordingExtractor( | ||
file_path, sampling_frequency, dtype="uint16", num_channels=num_total_channels | ||
) | ||
recording = UnsignedToSignedRecording(recording, bit_depth=num_bits) | ||
|
||
if stream_name == "raw": | ||
channel_slice = recording.channel_ids[:num_electrodes] | ||
renamed_channels = np.arange(num_electrodes) | ||
# locations = channel_locations | ||
# groups = channel_groups | ||
gain = gain_ephys | ||
elif stream_name == "filt": | ||
channel_slice = recording.channel_ids[num_electrodes : 2 * num_electrodes] | ||
renamed_channels = np.arange(num_electrodes) | ||
# locations = channel_locations | ||
# groups = channel_groups | ||
gain = gain_ephys | ||
elif stream_name == "aux": | ||
channel_slice = recording.channel_ids[2 * num_electrodes :] | ||
hw_chans = meta["hwAUXChannelName"][1:-1].split(",") | ||
user_chans = meta["userAuxName"][1:-1].split(",") | ||
renamed_channels = hw_chans + user_chans | ||
# locations = None | ||
# groups = None | ||
gain = gain_aux | ||
else: | ||
raise ValueError("stream_name must be 'raw', 'filt', or 'aux'") | ||
|
||
ChannelSliceRecording.__init__(self, recording, channel_ids=channel_slice, renamed_channel_ids=renamed_channels) | ||
# if locations is not None: | ||
# self.set_channel_locations(locations) | ||
# if groups is not None: | ||
# self.set_channel_groups(groups) | ||
|
||
self.set_channel_gains(gain) | ||
self.set_channel_offsets(0) | ||
|
||
if (stream_name == "filt") | (stream_name == "raw"): | ||
if probe_type == "p1024s1NHP": | ||
probe = get_probe(manufacturer="sinaps", probe_name="SiNAPS-p1024s1NHP") | ||
# now wire the probe | ||
channel_indices = np.arange(1024) | ||
probe.set_device_channel_indices(channel_indices) | ||
self.set_probe(probe, in_place=True) | ||
else: | ||
raise ValueError(f"Unknown probe type: {probe_type}") | ||
|
||
self._kwargs = {"file_path": str(file_path.absolute())} | ||
|
||
|
||
read_sinaps_research_platform = define_function_from_class( | ||
source_class=SinapsResearchPlatformRecordingExtractor, name="read_sinaps_research_platform" | ||
) | ||
|
||
|
||
def parse_sinaps_meta(meta_file): | ||
meta_dict = {} | ||
with open(meta_file) as f: | ||
lines = f.readlines() | ||
for l in lines: | ||
if "**" in l or "=" not in l: | ||
continue | ||
else: | ||
key, val = l.split("=") | ||
val = val.replace("\n", "") | ||
try: | ||
val = int(val) | ||
except: | ||
pass | ||
try: | ||
val = eval(val) | ||
except: | ||
pass | ||
meta_dict[key] = val | ||
return meta_dict |
149 changes: 149 additions & 0 deletions
149
src/spikeinterface/extractors/sinapsrecordingh5extractor.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,149 @@ | ||
from pathlib import Path | ||
import numpy as np | ||
|
||
from probeinterface import get_probe | ||
|
||
from ..core.core_tools import define_function_from_class | ||
from ..core import BaseRecording, BaseRecordingSegment | ||
from ..preprocessing import UnsignedToSignedRecording | ||
|
||
|
||
class SinapsResearchPlatformH5RecordingExtractor_Unsigned(BaseRecording): | ||
extractor_name = "SinapsResearchPlatformH5" | ||
mode = "file" | ||
name = "sinaps_research_platform_h5" | ||
|
||
def __init__(self, file_path): | ||
|
||
try: | ||
import h5py | ||
|
||
self.installed = True | ||
except ImportError: | ||
self.installed = False | ||
|
||
assert self.installed, self.installation_mesg | ||
self._file_path = file_path | ||
|
||
sinaps_info = openSiNAPSFile(self._file_path) | ||
self._rf = sinaps_info["filehandle"] | ||
|
||
BaseRecording.__init__( | ||
self, | ||
sampling_frequency=sinaps_info["sampling_frequency"], | ||
channel_ids=sinaps_info["channel_ids"], | ||
dtype=sinaps_info["dtype"], | ||
) | ||
|
||
self.extra_requirements.append("h5py") | ||
|
||
recording_segment = SiNAPSRecordingSegment( | ||
self._rf, sinaps_info["num_frames"], sampling_frequency=sinaps_info["sampling_frequency"] | ||
) | ||
self.add_recording_segment(recording_segment) | ||
|
||
# set gain | ||
self.set_channel_gains(sinaps_info["gain"]) | ||
self.set_channel_offsets(sinaps_info["offset"]) | ||
self.num_bits = sinaps_info["num_bits"] | ||
|
||
# set probe | ||
if sinaps_info["probe_type"] == "p1024s1NHP": | ||
probe = get_probe(manufacturer="sinaps", probe_name="SiNAPS-p1024s1NHP") | ||
probe.set_device_channel_indices(np.arange(1024)) | ||
self.set_probe(probe, in_place=True) | ||
else: | ||
raise ValueError(f"Unknown probe type: {sinaps_info['probe_type']}") | ||
|
||
# set other properties | ||
|
||
self._kwargs = {"file_path": str(Path(file_path).absolute())} | ||
|
||
def __del__(self): | ||
self._rf.close() | ||
|
||
|
||
class SiNAPSRecordingSegment(BaseRecordingSegment): | ||
def __init__(self, rf, num_frames, sampling_frequency): | ||
BaseRecordingSegment.__init__(self, sampling_frequency=sampling_frequency) | ||
self._rf = rf | ||
self._num_samples = int(num_frames) | ||
self._stream = self._rf.require_group("RealTimeProcessedData") | ||
|
||
def get_num_samples(self): | ||
return self._num_samples | ||
|
||
def get_traces(self, start_frame=None, end_frame=None, channel_indices=None): | ||
if isinstance(channel_indices, slice): | ||
traces = self._stream.get("FilteredData")[channel_indices, start_frame:end_frame].T | ||
else: | ||
# channel_indices is np.ndarray | ||
if np.array(channel_indices).size > 1 and np.any(np.diff(channel_indices) < 0): | ||
# get around h5py constraint that it does not allow datasets | ||
# to be indexed out of order | ||
sorted_channel_indices = np.sort(channel_indices) | ||
resorted_indices = np.array([list(sorted_channel_indices).index(ch) for ch in channel_indices]) | ||
recordings = self._stream.get("FilteredData")[sorted_channel_indices, start_frame:end_frame].T | ||
traces = recordings[:, resorted_indices] | ||
else: | ||
traces = self._stream.get("FilteredData")[channel_indices, start_frame:end_frame].T | ||
return traces | ||
|
||
|
||
class SinapsResearchPlatformH5RecordingExtractor(UnsignedToSignedRecording): | ||
extractor_name = "SinapsResearchPlatformH5" | ||
mode = "file" | ||
name = "sinaps_research_platform_h5" | ||
|
||
def __init__(self, file_path): | ||
recording = SinapsResearchPlatformH5RecordingExtractor_Unsigned(file_path) | ||
UnsignedToSignedRecording.__init__(self, recording, bit_depth=recording.num_bits) | ||
|
||
self._kwargs = {"file_path": str(Path(file_path).absolute())} | ||
|
||
|
||
read_sinaps_research_platform_h5 = define_function_from_class( | ||
source_class=SinapsResearchPlatformH5RecordingExtractor, name="read_sinaps_research_platform_h5" | ||
) | ||
|
||
|
||
def openSiNAPSFile(filename): | ||
"""Open an SiNAPS hdf5 file, read and return the recording info.""" | ||
|
||
import h5py | ||
|
||
rf = h5py.File(filename, "r") | ||
|
||
stream = rf.require_group("RealTimeProcessedData") | ||
data = stream.get("FilteredData") | ||
dtype = data.dtype | ||
|
||
parameters = rf.require_group("Parameters") | ||
gain = parameters.get("VoltageConverter")[0] | ||
offset = 0 | ||
|
||
nRecCh, nFrames = data.shape | ||
|
||
samplingRate = parameters.get("SamplingFrequency")[0] | ||
|
||
probe_type = str( | ||
rf.require_group("Advanced Recording Parameters").require_group("Probe").get("probeType").asstr()[...] | ||
) | ||
num_bits = int( | ||
np.log2(rf.require_group("Advanced Recording Parameters").require_group("DAQ").get("nbADCLevels")[0]) | ||
) | ||
|
||
sinaps_info = { | ||
"filehandle": rf, | ||
"num_frames": nFrames, | ||
"sampling_frequency": samplingRate, | ||
"num_channels": nRecCh, | ||
"channel_ids": np.arange(nRecCh), | ||
"gain": gain, | ||
"offset": offset, | ||
"dtype": dtype, | ||
"probe_type": probe_type, | ||
"num_bits": num_bits, | ||
} | ||
|
||
return sinaps_info |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe we can just apply the unsigned_to_signed in the get_traces, which is simpler to read and will get rid of the complex class interplay. I can implement that
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done