Skip to content

Commit 0e9fe2b

Browse files
committed
Add new version.
1 parent f533d19 commit 0e9fe2b

13 files changed

+751
-93
lines changed
25.4 KB
Loading

doc/how_to/handle_times.rst

Lines changed: 113 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ Extracellular electrophysiology commonly involves synchronisation of events
55
across many timestreams. For example, an experiment may involve
66
displaying a stimuli to an animal and recording the stimuli-evoked
77
neuronal responses. It is critical that timings is represented in
8-
a clear way across data streams so they may be properly syncronised during
8+
a clear way across data streams so they may be properly synchronised during
99
analysis.
1010

1111
Below, we will explore the ways that SpikeInterface represents time
@@ -26,127 +26,147 @@ please see the below dropdown for a refresher.
2626
finite-memory computers, we must 'sample' the real-world continuous signals
2727
at discrete time points.
2828

29-
When sampling our signal the fundamental question we need to ask is - how fast?
30-
A natural approach is to sample our signal every X seconds. For the sake of example,
31-
let's say we decide to sample our signal 4 times per second. This is known as the
32-
'sampling frequency' (sometimes denoted $f_s$) and is expressed in Hertz (Hz) i.e.
33-
'samples-per-second'.
29+
When sampling our signal the fundamental question we need to ask is - how fast should
30+
we sample the continuous data? A natural approach is to sample our signal every $N$ seconds.
31+
For example, let's say we decide to sample our signal 4 times per second.
32+
This is known as the 'sampling frequency' (sometimes denoted $f_s$) and is expressed
33+
in Hertz (Hz) i.e. 'samples-per-second'.
3434

3535
Another way of thinking about the same idea is to ask - how often are we
3636
sampling our signal? In our example, we are sampling the signal once every 0.25 seconds.
3737
This is known as the 'sampling step' (sometimes denoted $t_s$) and is the inverse of
3838
the sampling frequency.
3939

40-
[PICTURE]
40+
.. image:: handle-times-sampling-image.png
41+
:alt: Image of continuous signal (1 second) with dots indicating samples collected at 0, 0.25, 0.5 and 0.75 seconds.
42+
:width: 400px
43+
:align: center
4144

4245
In the real world, we will sample our signal much faster. For example, Neuropixels
4346
samples at 30 kHz (30,000 samples per second), with a
4447
sampling step of 1/30000 = 0.0003 seconds!
4548

46-
On our computer, we typically repesent time as a long array of numbers.
47-
For example, XXXX. Often, it is easier to access this array using 'indices'
48-
rather than the raw time units. MORE ON THIS. It is useful to remember
49-
these below quick conversion between time and indices that are often
50-
used in code:
51-
52-
``time * sampling_frequency = index`` and
53-
``index * sampling_step = index * (1/sampling_frequency = time``.
49+
Using computers, we typically represent time as a long array of numbers,
50+
here we refer to this as a 'time array' in seconds.
51+
For example, `[0, 0.25, 0.5, 0.75, ...]`.
5452

5553
------------------------------------------------------------------
5654
An Overview of the possible Time representations in SpikeInterface
5755
------------------------------------------------------------------
5856

59-
When loading a raw recording, SpikeInterface only has access to the
60-
sampling frequency and raw data samples of the data. Only in rare
61-
cases are the time-stamps of extracellular electrophysiological data
62-
are stored alongside the recordings and can be directly loaded
63-
(TODO: is any of that true?)
64-
65-
In some applications, having the exact time is very important.
66-
For example, NeuroPixels. In this case want to include the
67-
exact times. How do you do this? where is this information stored?
68-
69-
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
70-
Providing no times to spikeinterface
71-
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
72-
73-
In this case, all times used will be generated from the sample index
74-
and sampling frequency. For example, if a spike peak is detected at
75-
index 300 of the raw data, and the sampling frequency is 30 kHz,
76-
the peak time will be 300 * (1/30000) = 0.01 s.
77-
78-
Note that time is represented in spikeinterface in XXXX datatype, and so
79-
resolution will be this. For example, a spike at index 1000 of the raw
80-
data will be represented as 1000 * (1/30000) = 0.03333XXX seconds.
81-
82-
For a multi-segment recording, each segment will be fixed to start at
83-
0 seconds (TODO: is this true)? If there is a peak at index 300 in
84-
segment one and index in the peak at index 300 in segment two, both
85-
segments action potential peaks will be given a spike time of 0.1 s,
86-
and their segment tracked to distinguish them. TODO: CHECK
87-
88-
# TODO: yes follow a code example!
89-
```sorting.get_unit_spike_train(0, return_times=True, segment_index=1)```
90-
91-
^^^^^^^^^^^^^^^^^^^^^^^^^
92-
Providing the start time
93-
^^^^^^^^^^^^^^^^^^^^^^^^^
94-
95-
### TODO :::: This section make no sense because you can't set
96-
`t_start` directly as far as I can tell, it is only available on
97-
a few extractors.
98-
99-
Alternatively, the start-time of each segment can be provided to
100-
spikeinterface. The same method (index * (1 / sampling_frequency))
101-
will be used to determine event spike, but now an initial offset
102-
will be added using the `t_start` variable. TODO: reword, less confusing.
103-
104-
For example, imagine you have two recording sessions, recorded at
105-
30 kHz sampling frequency, both 30 minutes long. You record one session,
106-
wait 10 minutes, then record another session. In this example the will imagine
107-
these two sessions are loaded into spikeinterface as two segments on a
108-
single recording (however, the process would be the same if you had loaded
109-
the sessions separately into two recording objects).
110-
111-
If you wanted to represent the sessions as starting relative to the
112-
first session, and keeping the true timings of the recording, you can
113-
do:
57+
When you load a recording into SpikeInterface, it will be automatically
58+
associated with a time array. Depending on your data format, this might
59+
be loaded from metadata on your raw recording. [TODO: concrete example of this?]
60+
61+
If there is no time metadata on your raw recording, the times will be
62+
generated based on your sampling rate and number of samples.
63+
64+
You can use the `get_times()` method to inspect the time array associated
65+
with your recording.
11466

11567
```
116-
recording.select_segment(1).set_times(XXX)
68+
import spikeinterface.full as si
69+
70+
# Generate a recording for this example
71+
recording, _ = si.generate_ground_truth_recording(durations=[10])
72+
73+
print(f"number of samples: {recording.get_num_samples()}")
74+
print(f"sampling frequency: {recording.get_sampling_frequency()}"
75+
76+
print(
77+
recording.get_times()
78+
)
79+
11780
```
11881

119-
Alternatively, if you wanted to ignore the 10 minute gap, you could do:
82+
Here, we see that as no time metadata was associated with the loaded recording,
83+
the time array starts at 0 seconds and continues until 10 seconds
84+
(`10 * sampling_frequency`) in steps of sampling step (`1 / sampling_frequency`).
85+
86+
If timings were loaded from metadata, you may find that the first timepoint is
87+
not zero, or the times may not be separated by exactly `1/sampling_frequency` but
88+
may be irregular due to small drifts in sampling rate during acquisition.
89+
90+
^^^^^^^^^^^^^^^^^^^^^^^
91+
Shifting the start time
92+
^^^^^^^^^^^^^^^^^^^^^^^
93+
94+
Having loaded your recording object and inspected the associated
95+
time vector, you may want to change the start time of your recording.
96+
For example, your recording may not have metadata attached and you
97+
want to shift the default time vector (with zero start time) to the
98+
true (real world) start time of the recording, or relative to some
99+
other event (e.g. behavioural trial start time).
100+
101+
Alternatively, you may want to change the start time of the metadata-loaded
102+
recording for similar reasons.
103+
104+
To do this, you can use the `shift_start_time()` function to shift
105+
the first timepoint of the recording. Shifting by a positive value will
106+
increase the start time, while shifting by a negative value will decrease
107+
the start time.
120108

121109
```
122-
recording.select_segment(1).set_times(XXXX)
110+
recording.shift_start_time(100.15)
111+
112+
print(recording.get_times()) # time now start at 100.15 seconds
113+
114+
recording.shift_start_time(-50.15)
115+
116+
print(recording.get_times()) # time now start at 50 seconds
123117
```
124-
``` print spike times```
125-
sorting.get_unit_spike_train(0, return_times=True, segment_index=1)
126-
# TODO: it says use time vector but this doesn't return times.
127118

128-
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
129-
Providing the full time array
130-
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
131119

132-
Finally, may want to get full time array. Where do you get this from?
133-
Loaded - some more information on where from
120+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
121+
Manually setting a time vector
122+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
123+
124+
Less commonly, you may want to manually set the time vector on a recording.
125+
For example, maybe you have a known time vector with non-regularly spaced
126+
samples due to sampling drift, and you want to associate it with your recording.
127+
128+
You can associate any time vector with your recording (as long as it contains
129+
as many samples as the recording itself) using `recording.set_times()`.
130+
131+
[TODO - an example?]
134132

135-
`recording.has_time_vector()`.
133+
.. warning::
136134

137-
we can set times e.g. as above.
135+
In the case of regularly spaced time vectors, it is recommended
136+
to shift the default times rather than set your own time vector,
137+
as this will require more memory under the hood.
138138

139-
1) set the times
140-
2) print the times. Note how they are different to the above case!
141139

142-
Note that for some extractors, e.g. read_openephys you can load the
143-
syncrhonized timestamps directly (load_sync_timestamps param). It would be
144-
important to mention! Section on when files are loaded autoamticaly!
140+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
141+
Retrieving timepoints from sample index
142+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
145143

146-
--------------------------
147-
Accessing time information
148-
--------------------------
144+
SpikeInterface provides two convenience methods for obtaining the timepoint in seconds
145+
given an index of the time array:
149146

150-
Cover the The two time array functions.
147+
```
148+
sample_index = recording.time_to_sample_index(5.0)
149+
150+
print(sample_index)
151+
```
152+
153+
Similarly, you can retrieve the time array index given a timepoint:
154+
155+
156+
```
157+
timepoint = recording.sample_index_to_to_time(125000)
158+
159+
print(timepoint)
160+
```
151161

152-
`sample_index_to_time`
162+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
163+
Aligning events across timestreams
164+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
165+
166+
The alignment of electrophysiology recording time to other data streams (e.g. behaviour)
167+
is an important step in ephys analysis. To acheive this,it is common to collect
168+
a synconrisation ('sync') pulse on an additional channel. At present SpikeInterface does not include
169+
features for time-alignment, but some useful articles can be found on the following pages,
170+
[SpikeGLX](https://github.com/billkarsh/SpikeGLX/blob/master/Markdown/UserManual.md#procedure-to-calibrate-sample-rates),
171+
[OpenEphys](https://open-ephys.github.io/gui-docs/Tutorials/Data-Synchronization.html),
172+
[NWB](https://neuroconv.readthedocs.io/en/main/user_guide/temporal_alignment.html).

doc/how_to_new/how_to_new_jupyter.zip

5.73 KB
Binary file not shown.

doc/how_to_new/how_to_new_python.zip

3.88 KB
Binary file not shown.
26.2 KB
Loading

doc/how_to_new/index.rst

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
:orphan:
2+
3+
Unused
4+
======
5+
6+
This file is required by sphinx to build sphinx pages. But, we do not use the gallery
7+
page features for the How To and so do not need it. Instead, we have a custom .rst file
8+
in the How To folder that points to the sphinx-gallery outputs.
9+
10+
11+
12+
.. raw:: html
13+
14+
<div class="sphx-glr-thumbnails">
15+
16+
.. thumbnail-parent-div-open
17+
18+
.. raw:: html
19+
20+
<div class="sphx-glr-thumbcontainer" tooltip="In this tutorial, we will walk through combining multiple recording objects. Sometimes this occ...">
21+
22+
.. only:: html
23+
24+
.. image:: /how_to_new/images/thumb/sphx_glr_plot_combine_recordings_thumb.png
25+
:alt:
26+
27+
:ref:`sphx_glr_how_to_new_plot_combine_recordings.py`
28+
29+
.. raw:: html
30+
31+
<div class="sphx-glr-thumbnail-title">Combine recordings in SpikeInterface</div>
32+
</div>
33+
34+
35+
.. thumbnail-parent-div-close
36+
37+
.. raw:: html
38+
39+
</div>
40+
41+
42+
.. toctree::
43+
:hidden:
44+
45+
/how_to_new/plot_combine_recordings
46+
47+
48+
.. only:: html
49+
50+
.. container:: sphx-glr-footer sphx-glr-footer-gallery
51+
52+
.. container:: sphx-glr-download sphx-glr-download-python
53+
54+
:download:`Download all examples in Python source code: how_to_new_python.zip </how_to_new/how_to_new_python.zip>`
55+
56+
.. container:: sphx-glr-download sphx-glr-download-jupyter
57+
58+
:download:`Download all examples in Jupyter notebooks: how_to_new_jupyter.zip </how_to_new/how_to_new_jupyter.zip>`
59+
60+
61+
.. only:: html
62+
63+
.. rst-class:: sphx-glr-signature
64+
65+
`Gallery generated by Sphinx-Gallery <https://sphinx-gallery.github.io>`_

0 commit comments

Comments
 (0)