|
| 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`. |
0 commit comments