Skip to content

Commit fb9ddbb

Browse files
authored
Add additional unsigned_to_signed doc (#4076)
1 parent 7c91082 commit fb9ddbb

4 files changed

Lines changed: 108 additions & 1 deletion

File tree

doc/how_to/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,5 @@ Guides on how to solve specific, short problems in SpikeInterface. Learn how to.
1818
auto_curation_training
1919
auto_curation_prediction
2020
physical_units
21+
unsigned_to_signed
2122
customize_a_plot

doc/how_to/physical_units.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
.. _physical_units:
2+
13
Working with physical units in SpikeInterface recordings
24
========================================================
35

doc/how_to/unsigned_to_signed.rst

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
Unsigned to Signed Data types
2+
=============================
3+
4+
As of version 0.103.0 SpikeInterface has changed one of its defaults for interacting with
5+
:code:`Recording` objects. We no longer autocast unsigned dtypes to signed implicitly. This
6+
means that some users of SpikeInterface will need to add one additional line of code to their scripts
7+
to explicitly handle this conversion.
8+
9+
10+
Why this matters?
11+
-----------------
12+
13+
For those that want a deeper understanding of dtypes `NumPy provides a great explanation <https://numpy.org/doc/stable/reference/arrays.dtypes.html>`_.
14+
For our purposes it is important to know that many pieces of recording equipment opt to store their electrophysiological data as unsigned integers
15+
(e.g., Intan, Maxwell Biosystems, 3Brain Biocam).
16+
Similarly to signed integers, in order to convert to real units these file formats only need to store a :code:`gain`
17+
and an :code:`offset`. Our :code:`RecordingExtractor`'s maintain the dtype that the file format utilizes, which means that some of our
18+
:code:`RecordingExtractor`'s will have unsigned dtypes.
19+
20+
The problem with using unsigned dtypes is that many types of functions (including the ones we use from :code:`SciPy`) perform poorly with unsigned integers.
21+
This is made worse by the fact that these failures are silent (i.e. no error is triggered but the operation leads to nonsensical data). So the
22+
solution required is to convernt unsigned integers into signed integers. Previously we did this under the hood, automatically for users that had
23+
a :code:`Recording` object with an unsigned dtype.
24+
25+
We decided, however, that implicitly performing this action was not the best course of action, since:
26+
27+
1) *explicit* is always better than *implicit*
28+
2) some functions would *magically* change the dtype of the :code:`Recording` object, which can cause confusion
29+
30+
So from version 0.103.0, users will now explicitly have to perform this transformation of their data. This will help users better understand how they are
31+
processing their data during an analysis pipeline as well as better understand the provenance of their pipeline.
32+
33+
34+
Using :code:`unsigned_to_signed`
35+
--------------------------------
36+
37+
For users that receive an error because their :code:`Recording` is unsigned, their is one additional step that must be done:
38+
39+
.. code:: python
40+
41+
import spikeinterface.extractors as se
42+
import spikeinterface.preprocessing as spre
43+
44+
# Intan is an example of unsigned data
45+
recording = se.read_intan('path/to/my/file.rhd', stream_id='0')
46+
# to get a signed version of our Recording we use the following function
47+
recording_signed = spre.unsigned_to_signed(recording)
48+
# we can now apply any preprocessing functions like normal, e.g.
49+
recording_filtered = spre.bandpass_filter(recording_signed)
50+
51+
52+
Now with the signed dtype of the :code:`Recording` one can use a SpikeInterface pipeline as usual.
53+
54+
55+
If you are curious if your :code:`Recording` is unsigned you can simply check the repr or use :code:`get_dtype()`
56+
57+
.. code:: python
58+
59+
# the repr automatically displays the dtype
60+
print(recording)
61+
# use method on the Recording object
62+
print(recording.get_dtype())
63+
64+
In either case, if the dtype displayed has a :code:`u` at the beginning (e.g. :code:`uint16`) then your recording is
65+
unsigned. If it doesn't have the :code:`u` (e.g. :code:`int16`) then it is signed and would not need this preprocessing step.
66+
67+
68+
Bit depth
69+
---------
70+
71+
One final important piece of information for some users is the concept of bit depth, which is the number of bits used to
72+
sample the data. The :code:`bit_depth` argument that can be fed into the :code:`unsigned_to_signed` function.
73+
This should be used in cases where the ADC bit depth does not match the bit depth of the data type (e.g., if the data is
74+
stored as :code:`uint16` but the ADC is 12 bits).
75+
Let's make a concrete example: the Biocam acquisition system from 3Brain uses a 12-bit ADC and stores the data as
76+
:code:`uint16`. This means that the data is stored in a 16-bit unsigned integer format, but the actual data
77+
only covers a 12-bit range. Therefore, that the "zero" of the data is not at 0, nor at half of the :code:`uint16` range (i.e. 2^15),
78+
but rather at 2048 (i.e., 2^12).
79+
In this case, setting the :code:`bit_depth` argument to 12 will allow the :code:`unsigned_to_signed` function to
80+
correctly convert the unsigned data to signed data and offset the data to be centered around 0, by subtracting 2048
81+
while converting the data from unsigned to signed.
82+
83+
.. code:: python
84+
85+
recording_unsigned = se.read_biocam('path/to/my/file.brw')
86+
# we can now convert to signed with the correct bit depth
87+
recording_signed = spre.unsigned_to_signed(recording_unsigned, bit_depth=12)
88+
89+
90+
Additional Notes
91+
----------------
92+
93+
1) Some sorters make use of SpikeInterface preprocessing either
94+
within their wrappers or within their own code base. So remember to use the "signed" version of
95+
your recording for the rest of your pipeline.
96+
97+
2) Using :code:`unsigned_to_signed` in versions less than 0.103.0 does not hurt your scripts. This
98+
option was available previously along with the implicit option. Adding this into scripts with old
99+
versions of SpikeInterface will still work and will "future-proof" your scripts for when you
100+
update to a version greater than or equal to 0.103.0.
101+
102+
3) For additional information on units and scaling in SpikeInterface see :ref:`physical_units`.

src/spikeinterface/preprocessing/filter.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -423,9 +423,11 @@ def fix_dtype(recording, dtype):
423423
# if uint --> force int
424424
if dtype.kind == "u":
425425
raise ValueError(
426-
"Unsigned types are not supported, since they don't ineract well with "
426+
"Unsigned types are not supported, since they don't interact well with "
427427
"various preprocessing steps. You can use "
428428
"`spikeinterface.preprocessing.unsigned_to_signed` to convert the recording to a signed type."
429+
"For more information, please see "
430+
"https://spikeinterface.readthedocs.io/en/stable/how_to/unsigned_to_signed.html"
429431
)
430432

431433
return dtype

0 commit comments

Comments
 (0)