@@ -5,7 +5,7 @@ Extracellular electrophysiology commonly involves synchronisation of events
5
5
across many timestreams. For example, an experiment may involve
6
6
displaying a stimuli to an animal and recording the stimuli-evoked
7
7
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
9
9
analysis.
10
10
11
11
Below, we will explore the ways that SpikeInterface represents time
@@ -26,127 +26,147 @@ please see the below dropdown for a refresher.
26
26
finite-memory computers, we must 'sample' the real-world continuous signals
27
27
at discrete time points.
28
28
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'.
34
34
35
35
Another way of thinking about the same idea is to ask - how often are we
36
36
sampling our signal? In our example, we are sampling the signal once every 0.25 seconds.
37
37
This is known as the 'sampling step' (sometimes denoted $t_s$) and is the inverse of
38
38
the sampling frequency.
39
39
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
41
44
42
45
In the real world, we will sample our signal much faster. For example, Neuropixels
43
46
samples at 30 kHz (30,000 samples per second), with a
44
47
sampling step of 1/30000 = 0.0003 seconds!
45
48
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, ...] `.
54
52
55
53
------------------------------------------------------------------
56
54
An Overview of the possible Time representations in SpikeInterface
57
55
------------------------------------------------------------------
58
56
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.
114
66
115
67
```
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
+
117
80
```
118
81
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.
120
108
121
109
```
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
123
117
```
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.
127
118
128
- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
129
- Providing the full time array
130
- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
131
119
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?]
134
132
135
- ` recording.has_time_vector() `.
133
+ .. warning ::
136
134
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.
138
138
139
- 1) set the times
140
- 2) print the times. Note how they are different to the above case!
141
139
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
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
145
143
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:
149
146
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
+ ```
151
161
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).
0 commit comments