-
Notifications
You must be signed in to change notification settings - Fork 257
Add stim demultiplex rhs intan #1660
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
Conversation
No idea why the docs are failing on this one. |
We've been having a lot of failures of gin lately. it's intermittent (3.9, 1.26 testing failed too because of that). I'm rerunning now :) |
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.
First round of comments.
Co-authored-by: Zach McKenzie <[email protected]>
Co-authored-by: Zach McKenzie <[email protected]>
Co-authored-by: Zach McKenzie <[email protected]>
@h-mayorquin, @zm711 |
Is your neo install editable? If you just installed neo during your spikeinterface install it would not be editable. So even if you switched branches it wouldn't change anything. You would need to editable install neo in the environment that you're using for spikeinterface to be able to test changes. Let us know if you need help with that! |
Yes, we installed neo editable! We are also sure that the neo version in environment is this newer one. @zm711
|
You would need to request the so, from neo.rawio import IntanRawIO
reader = IntanRawIO(path/to/data)
reader.parse_header()
sig_chunk = reader.get_analogsignal_chunk(stream_index=11) |
By trying these lines, it raised this error: ---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
Cell In[6], line 5
3 reader = IntanRawIO("Z:/SNN/K-SNN/R24-21/02_stim_240621_132910.rhs")
4 reader.parse_header()
----> 5 sig_chunk = reader.get_analogsignal_chunk(stream_index=11)
7 import matplotlib.pyplot as plt
8 plt.plot(sig_chunk.T)
File ~\Repositories\test_NEOIntan\mayorquin-neo\neo\rawio\baserawio.py:805, in BaseRawIO.get_analogsignal_chunk(self, block_index, seg_index, i_start, i_stop, stream_index, channel_indexes, channel_names, channel_ids, prefer_slice)
799 error_message = (
800 "get_analogsignal_chunk can't be called on a file with no signal streams or channels."
801 "Double check that your file contains signal streams and channels."
802 )
803 raise AttributeError(error_message)
--> 805 stream_index = self._get_stream_index_from_arg(stream_index)
806 channel_indexes = self._get_channel_indexes(stream_index, channel_indexes, channel_names, channel_ids)
808 # some check on channel_indexes
File ~\Repositories\test_NEOIntan\mayorquin-neo\neo\rawio\baserawio.py:653, in BaseRawIO._get_stream_index_from_arg(self, stream_index_arg)
651 else:
652 if stream_index_arg < 0 or stream_index_arg >= self.header["signal_streams"].size:
--> 653 raise ValueError(f"stream_index must be between 0 and {self.header['signal_streams'].size}")
654 stream_index = stream_index_arg
655 return stream_index
ValueError: stream_index must be between 0 and 3 As you can see the functions are called from my local repository, which contains the The previous code from from spikeinterface.extractors import IntanRecordingExtractor
recording = IntanRecordingExtractor("Z:/SNN/K-SNN/R24-21/02_stim_240621_132910.rhs", stream_name="Stim channel")
data = recording.get_traces() Actually, these lines didn't raised an error but simply returned the correct signals without converting from the binary format. |
Sorry I gave you the stream_id instead of the stream index. That was my bad! This is why Heberto and I would prefer if this function accepted the id instead of the index. Going back to your spikeinterface. You mean the data wasn't demultiplexed. It is still one array instead of multiple arrays? We need a scaling step to switch to current which spikeinterface doesn't do automatically. Let me rewrite my neo script for you :) |
from neo.rawio import IntanRawIO
reader = IntanRawIO(path/to/data)
reader.parse_header()
signal_streams = reader.header['signal_streams']
stream_ids = signal_streams['id'].tolist()
stream_index = stream_ids.index('11')
sig_chunk = reader.get_analogsignal_chunk(stream_index=stream_index)
final_stim = reader.rescale_signal_raw_to_float(sig_chunk, stream_index=stream_index) Sorry kept hitting enter accidentally. Try this. The key is you also need to rescale which doesn't occur with the raw data. |
Hi, @TommasoLambresa Using this branch, this is what I get for the file that you shared with me: from spikeinterface.extractors import IntanRecordingExtractor
from matplotlib import pyplot as plt
recording = IntanRecordingExtractor(file_path, stream_name="Stim channel")
traces = recording.get_traces(return_scaled=True)
times = recording.get_times()
num_channels = traces.shape[1]
for i in range(num_channels):
channel_traces = traces[:, i]
if channel_traces.sum() > 0:
plt.plot(times, channel_traces, label=f"Channel {i}")
plt.legend()
plt.xlabel("Time (s)")
plt.ylabel("Amperes") |
Hi @h-mayorquin The same goes from the |
Thanks a lot, this is very helpful and precisely the reason why I was waiting for someone to implement this. Two questions:
|
Yes, I have the same result: from spikeinterface.extractors import IntanRecordingExtractor
from matplotlib import pyplot as plt
recording = IntanRecordingExtractor("C:/Users/t.lambresa/Downloads/newintanFile 1.rhs", stream_name="Stim channel")
traces = recording.get_traces(return_scaled=True)
times = recording.get_times()
num_channels = traces.shape[1]
for i in range(num_channels):
channel_traces = traces[:, i]
if channel_traces.sum() > 0:
plt.plot(times, channel_traces, label=f"Channel {i}")
plt.legend()
plt.xlabel("Time (s)")
plt.ylabel("Amperes")
Sure here is the code @h-mayorquin: def decode_stim_data(orig_data, stim_step_size):
"""Converts the stimulation data from the Intan RHS format to micro-amps.
Parameters
----------
stim_data : np.ndarray (dtype=uint16)
One-dimensional array of STIM samples read from the .rhs file.
stim_step_size : float
'Stim Step Size' value read from the header (in amperes per step).
Returns
-------
stim_curr : np.ndarray (float)
Current in micro-amps (positive or negative).
"""
stim_data = orig_data.copy()
compliance_limit_data = stim_data >= 2**15
stim_data = stim_data - (compliance_limit_data * 2**15)
charge_recovery_data = stim_data >= 2**14
stim_data = stim_data - (charge_recovery_data * 2**14)
amp_settle_data = stim_data >= 2**13
stim_data = stim_data - (amp_settle_data * 2**13)
stim_polarity = stim_data >= 2**8
stim_data = stim_data - (stim_polarity * 2**8)
stim_polarity = 1 - 2 * stim_polarity # Convert (0 = pos, 1 = neg) to +/-1
stim_data = stim_data * stim_polarity
# Convert to amps
stim_curr = stim_data * stim_step_size
return stim_curr, compliance_limit_data, charge_recovery_data, amp_settle_data |
Yes, now it works :) |
@h-mayorquin wasn't our whole discussion for spikeinterface that we weren't going to allow people to do this through spikeinterface (without going through the scale mechanism)? |
Yeah, indeed. The scaling on the first code that I shown is wrong because it will put the scaling as one. There is another error though, I am fixing it. |
I think the issue could be that the signal is casted as |
Hey @TommasoLambresa I would buy you're right on the bit issues (I'm terrible on doing hex etc which is why I trust Heberto for that. But it is uint16 :) Unless you think Intan miswrote. |
No sorry, you're right! I'm terrible too... |
We will get this all sorted :) Like I said in my note, spikeinterface is moving toward limiting non-voltage scaling in the |
Perfect! Thank you guys @zm711 @h-mayorquin. |
Here is the gin PR: https://gin.g-node.org/NeuralEnsemble/ephy_testing_data/pulls/151 |
gin file is merged and can be added to testing. |
We may have discovered an issue with nixio by updating the io... let me look into that for a moment before we merge this. |
looks like setuptools just did a release five hours ago. I think that new release is breaking our nixio install. I'll see if I can limit setup tools for now, but I'll be away from my computer for a minute so I'll have to debug this more tomorrow. |
The last version of setuptools fixes it so just set a ceiling for setuptools https://setuptools.pypa.io/en/stable/history.html?utm_source=chatgpt.com#deprecations-and-removals (they probably went back after they realize a lot of people just won't upload and got people yelling at them x D) |
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.
I would push to comment these two spots out for the moment. We can add this later once we have the test data that way we can get this merged into intan.
@h-mayorquin could you scan through my changes really quick just to make sure I didn't do anything stupid! |
@h-mayorquin I added in the dc only test folder, because it has stim file tests and it tests the fact that the end user can choose not to save normal amp files this will allow to test one-file-per-channel for dc amp and for stim file. The other test is nice but not necessary to get this merged :) |
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.
@h-mayorquin I marked three comments of what I want you to glance at before we merge this. The fact that tests are passing means that my assumptions of the data org appear to be correct, but if you can think of a clearer/better way to do what I've done feel free to tweak. Once you glance over my additions this is ready for merge.
raw_file_paths_dict = create_one_file_per_channel_dict_rhs(dirname=filename.parent) | ||
channel_number_dict["Stim channel"] = len(raw_file_paths_dict["Stim channel"]) | ||
# but the user can shut off the normal amplifier and only save dc amplifier | ||
channel_number_dict["RHS2000 amplifier channel"] = len(raw_file_paths_dict["RHS2000 amplifier channel"]) |
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.
@h-mayorquin does this make sense or can you think of a better way of doing this?
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.
I don't understand the context. Why are we doing this? I re-read
# if using the `header-attached` or `one-file-per-signal` formats
# the amplifier data is stored in the same place in the header so no matter what the DC amp
# uses the same info as the RHS amp
There is something strange about it and makes me confused.
if file_format != "one-file-per-channel" or channel_number_dict["RHS2000 amplifier channel"] > 0: | ||
for chan_info in stream_name_to_channel_info_list["RHS2000 amplifier channel"]: |
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.
This is the only change here (the diff is wrong). Basically if the user shut off saving of files in the file-per-channel we need to skip this section. Does this make sense.
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.
@h-mayorquin you want to push your last change with our patch here? Then I merged. We can add the rhs channel test either now or in a new PR.
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.
Merge it like this, my comments are a mess.
if file_format != "one-file-per-channel" or any( | ||
[chan_info["native_channel_name"] in stim_file.stem for stim_file in raw_file_paths_dict["Stim channel"]] | ||
): |
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.
For this we check if these exist and if they do we analyze. This was my hacky way of doing this (since the user can choose individual channels to stim (ie stim files only exist for a subset of amp channels). Check this for me.
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.
Also fine if you want to do your reorg for this if statement before we merge :)
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.
Na, same thing, this is working, I am afraid of breaking something and I want to be sure we have some sort of support on the next release.
Should fix #1649
We will be needing test data but this work for reading the data for me:
If you can give it a try @TommasoLambresa and @FrancescoNegri and check that the outputs matches what you expect it would be great.
@zm711