Skip to content

Add documentation to handle (physical) units of recording channels #3844

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

Open
wants to merge 19 commits into
base: main
Choose a base branch
from

Conversation

h-mayorquin
Copy link
Collaborator

Ok guys, I will need your feedback. I am also adding another preprocessor wrapper that handles this for the user.

@h-mayorquin h-mayorquin added documentation Improvements or additions to documentation core Changes to core module preprocessing Related to preprocessing module labels Apr 7, 2025
@h-mayorquin h-mayorquin self-assigned this Apr 7, 2025
@chrishalcrow
Copy link
Member

Looks great - doc was super clear and easy to read!

h-mayorquin and others added 2 commits April 8, 2025 08:48
Co-authored-by: Chris Halcrow <57948917+chrishalcrow@users.noreply.github.com>
@h-mayorquin h-mayorquin marked this pull request as ready for review April 8, 2025 14:49
Copy link
Member

@zm711 zm711 left a comment

Choose a reason for hiding this comment

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

Before being really careful my one concern with this doc as written is that for the average user we don't need to scale_to_uV. It happens with argument flags in various functions. The way this documentation works it sounds like I always have to create a scaled recording rather than keep my unscaled and scale at the function level. I think the doc about other physical units is great, but I'm worried that this might actually confuse users on how to use the library since the scaling for uV is baked in to other functions the class is for people that just need their recording scaled for other purposes right?

@@ -0,0 +1,96 @@
Working with physical units in SpikeInterface recordings
===============================================
Copy link
Member

Choose a reason for hiding this comment

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

rendering is impossible for the suggestion but this needs to go to the end of the heading.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Generated by copilot before.
Fixed.



Converting to Physical Units
-------------------------
Copy link
Member

Choose a reason for hiding this comment

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

same here to end of the heading.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Thanks, fixed.

@h-mayorquin
Copy link
Collaborator Author

h-mayorquin commented Apr 16, 2025

but I'm worried that this might actually confuse users on how to use the library since the scaling for uV is baked in to other functions

what would be the confusion exactly?

@zm711
Copy link
Member

zm711 commented Apr 16, 2025

The confusion is do I need to do a scale_to_uV step in all of my pipelines? ie

recording = read_intan()
scaled_rec = scale_to_uV(recording)
sorting = run_sorter(xx)
# or spikeglx
recording = read_spikeglx()
scaled_rec= scale_to_uV(recording)
sorting=run_sorter()

The scaling is not required in a pipeline. But when I read these docs I feel like I have to explicitly run this step no matter what. I think it is helpful for people to know scaling is important but the main way the library has people interact with ephys scaling is with the return_scaled soon to be return_in_uV (or whatever we decided) as a function argument. Not as a preprocessing step. Does that make sense? I think the docs are good, but I'm worried people will not realize when they should scale outside of function calls vs inside. For another example. If I take a scale_to_uV recording what happens if I try to return_scaled? I shouldn't rescale again (I think you may have added an error, but then that mechanism seems weird. Because data should be scaled. Maybe this is worth an actual discussion because I'm worried I'm not being clear.

@h-mayorquin
Copy link
Collaborator Author

The scaling is not required in a pipeline. But when I read these docs I feel like I have to explicitly run this step no matter what.

Thanks.
We can add a comment that scaling is not needed in sorters.

I think the docs are good, but I'm worried people will not realize when they should scale outside of function calls vs inside.

This is not clear to me either. Is it to you?

but the main way the library has people interact with ephys scaling is with the return_scaled soon to be return_in_uV

I don't agree with this or want it.

Yes, I think we should discuss this on the meeting tomorrow. I am fine if you want add a list of pipes/algorithms where the units don't matter and an explanation on why they don't but I don't have that knowledge and I users are in a better position to understand if they want to run their algorithms on raw data or in units.

@zm711
Copy link
Member

zm711 commented Apr 16, 2025

@yger it seems like maybe some changes to SC2 broke testing?

@h-mayorquin
Copy link
Collaborator Author

Ok @zm711 I added some notes in the direction that you wanted. Check it out and let me know what you think. I still think that this is a good topic to discuss on the next meeting.

Copy link
Member

@zm711 zm711 left a comment

Choose a reason for hiding this comment

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

Few more comments.

SpikeInterface provides tools to handle both situations.

It's important to note that **most spike sorters work fine on raw digital (ADC) units** and not scaling is needed.
Many preprocessing tools are also linear transformations, and if the ADC is implemented as a linear transformation, the overall effect can be preserved.
Copy link
Member

Choose a reason for hiding this comment

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

we could say that is relatively common here to be a linear transformation (although you discuss gain/offset later on).

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I added a comment on this direction.

Therefore, **it is usually safe to work in raw ADC units unless a specific tool or analysis requires physical units**.
If you are interested in visualizations, comparability across devices, or outputs with interpretable physical scales (e.g., microvolts), converting to physical units is recommended.
Otherwise, remaining in raw units can simplify processing and preserve performance.

Copy link
Member

Choose a reason for hiding this comment

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

We might want Alessio or Sam to comment on some of the internal tooling. I think the scaling is automatic for some of our stuff so we should make it clear if/when we do that. I just don't remember off the top of my head. If Alessio doesn't have the time to look this over I can doublecheck in the code.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I think it is better if you check it.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I think this is only missing part to move this forward?

Copy link
Member

Choose a reason for hiding this comment

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

Yeah I agree @alejoe91 could you comment here when you have a moment :)


physical_value = raw_value * gain + offset


Copy link
Member

Choose a reason for hiding this comment

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

Maybe a note here saying that as we discussed above because this is a linear transformation we can do preprocessing etc without an issue.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Can you add a suggestion?

Copy link
Member

Choose a reason for hiding this comment

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

Yep will do. Have some experiments all day today, but my hope is tomorrow should be a little freer.

values = ["volts"] * num_channels
recording.set_property(key='physical_unit', value=values)

values = [0.001] * num_channels # Convert from ADC to volts
Copy link
Member

Choose a reason for hiding this comment

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

Maybe we say

gain_values = [0.001] * num_channels

to be even more clear and then below we would say
offset_values

just so we aren't using the same variable being overwritten for both? This is definitely optional.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yeah, makes sense.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Done.


def __init__(self, recording):
if "gain_to_physical_unit" not in recording.get_property_keys():
raise ValueError("Recording must have 'gain_to_physical_unit' property to convert to physical units")
Copy link
Member

Choose a reason for hiding this comment

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

Any interest in adding the way to do this in the error? for example. adding a line like

please use the set_property function in order to set "gain_to_physical_unit"

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Good idea.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Done.

self.set_channel_offsets(offsets=0.0)


scale_to_physical_units = ScaleToPhysicalUnits
Copy link
Member

Choose a reason for hiding this comment

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

Hello, could you change this to

from spikeinterface.core.core_tools import define_function_handling_dict_from_class
scale_to_physical_units = define_function_handling_dict_from_class(ScaleToPhysicalUnits, name="scale_to_physical_units")

then will works with dicts of recs

if "gain_to_physical_unit" not in recording.get_property_keys():
error_msg = (
"Recording must have 'gain_to_physical_unit' property to convert to physical units. \n"
"Set the gain using `recording.set_property(key='gain_to_physical_unit', value=values)`."
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
"Set the gain using `recording.set_property(key='gain_to_physical_unit', value=values)`."
"Set the gain using `recording.set_property(key='gain_to_physical_unit', values=values)`."

if "offset_to_physical_unit" not in recording.get_property_keys():
error_msg = (
"Recording must have 'offset_to_physical_unit' property to convert to physical units. \n"
"Set the offset using `recording.set_property(key='offset_to_physical_unit', value=values)`."
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
"Set the offset using `recording.set_property(key='offset_to_physical_unit', value=values)`."
"Set the offset using `recording.set_property(key='offset_to_physical_unit', values=values)`."

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
core Changes to core module documentation Improvements or additions to documentation preprocessing Related to preprocessing module
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants