Ripple Detection¶
Overview¶
Developer Note: if you may make a PR in the future, be sure to copy this
notebook, and use the gitignore prefix temp to avoid future conflicts.
This is one notebook in a multi-part series on Spyglass.
- To set up your Spyglass environment and database, see the Setup notebook
- For additional info on DataJoint syntax, including table definitions and inserts, see the Insert Data notebook
Ripple detection depends on a set of LFPs, the parameters used for detection and the speed of the animal. You will need RippleLFPSelection, RippleParameters, and PositionOutput to be populated accordingly.
Imports¶
import os
import datajoint as dj
import numpy as np
# change to the upper level folder to detect dj_local_conf.json
if os.path.basename(os.getcwd()) == "notebooks":
os.chdir("..")
dj.config.load("dj_local_conf.json") # load config for database connection info
import spyglass.common as sgc
import spyglass.position as sgp
import spyglass.lfp as lfp
import spyglass.lfp.analysis.v1 as lfp_analysis
from spyglass.lfp import LFPOutput
import spyglass.lfp.v1 as sglfp
from spyglass.position import PositionOutput
import spyglass.ripple.v1 as sgrip
import spyglass.ripple.v1 as sgr
# ignore datajoint+jupyter async warnings
import warnings
warnings.simplefilter("ignore", category=DeprecationWarning)
warnings.simplefilter("ignore", category=ResourceWarning)
[2023-07-28 14:45:50,776][INFO]: Connecting root@localhost:3306 [2023-07-28 14:45:50,804][INFO]: Connected root@localhost:3306
Selecting Electrodes¶
First, we'll pick the electrodes on which we'll run ripple detection on, using
RippleLFPSelection.set_lfp_electrodes
?sgr.RippleLFPSelection.set_lfp_electrodes
Signature: sgrip.RippleLFPSelection.set_lfp_electrodes( key, electrode_list=None, group_name='CA1', **kwargs, ) Docstring: Removes all electrodes for the specified nwb file and then adds back the electrodes in the list Parameters ---------- key : dict dictionary corresponding to the LFPBand entry to use for ripple detection electrode_list : list list of electrodes from LFPBandSelection.LFPBandElectrode to be used as the ripple LFP during detection group_name : str, optional description of the electrode group, by default "CA1" File: ~/Src2/spyglass/src/spyglass/ripple/v1/ripple.py Type: function
We'll need the nwb_file_name, an electrode_list, and to a group_name.
- By default,
group_nameis set to CA1 for ripple detection, but we could alternatively use PFC. - We use
nwb_file_nameto explore which electrodes are available for theelectrode_list.
nwb_file_name = "tonks20211103_.nwb"
interval_list_name = "test interval"
filter_name = "Ripple 150-250 Hz"
Now we can look at electrode_id in the Electrode table:
electrodes = (
(sgc.Electrode() & {"nwb_file_name": nwb_file_name})
* (
lfp_analysis.LFPBandSelection.LFPBandElectrode()
& {
"nwb_file_name": nwb_file_name,
"filter_name": filter_name,
"target_interval_list_name": interval_list_name,
}
)
* sgc.BrainRegion
).fetch(format="frame")
electrodes
| probe_id | probe_shank | probe_electrode | name | original_reference_electrode | x | y | z | filtering | impedance | bad_channel | x_warped | y_warped | z_warped | contacts | region_name | subregion_name | subsubregion_name | |||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| nwb_file_name | electrode_group_name | electrode_id | lfp_merge_id | filter_name | filter_sampling_rate | target_interval_list_name | lfp_band_sampling_rate | lfp_electrode_group_name | reference_elect_id | region_id | ||||||||||||||||||
| tonks20211103_.nwb | 7 | 28 | 2f3c93d5-5d5d-2d47-75b3-c346dddbd312 | Ripple 150-250 Hz | 1000 | test interval | 100 | CA1_test | -1 | 19 | tetrode_12.5 | 0 | 0 | 28 | 44 | 0.0 | 0.0 | 0.0 | None | 0.0 | False | 0.0 | 0.0 | 0.0 | ca1 | None | None | |
| 1000 | CA1_test | -1 | 19 | tetrode_12.5 | 0 | 0 | 28 | 44 | 0.0 | 0.0 | 0.0 | None | 0.0 | False | 0.0 | 0.0 | 0.0 | ca1 | None | None | ||||||||
| 8 | 32 | 2f3c93d5-5d5d-2d47-75b3-c346dddbd312 | Ripple 150-250 Hz | 1000 | test interval | 100 | CA1_test | -1 | 19 | tetrode_12.5 | 0 | 0 | 32 | 44 | 0.0 | 0.0 | 0.0 | None | 0.0 | False | 0.0 | 0.0 | 0.0 | ca1 | None | None | ||
| 1000 | CA1_test | -1 | 19 | tetrode_12.5 | 0 | 0 | 32 | 44 | 0.0 | 0.0 | 0.0 | None | 0.0 | False | 0.0 | 0.0 | 0.0 | ca1 | None | None |
For ripple detection, we want only tetrodes, and only the first good wire on each tetrode. We will assume that is the first wire on each tetrode. I will do this using pandas syntax but you could use datajoint to filter this table as well. Here is the filtered table.
hpc_names = ["ca1", "hippocampus", "CA1", "Hippocampus"]
electrodes.loc[
(electrodes.region_name.isin(hpc_names)) & (electrodes.probe_electrode == 0)
]
| probe_id | probe_shank | probe_electrode | name | original_reference_electrode | x | y | z | filtering | impedance | bad_channel | x_warped | y_warped | z_warped | contacts | region_name | subregion_name | subsubregion_name | |||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| nwb_file_name | electrode_group_name | electrode_id | lfp_merge_id | filter_name | filter_sampling_rate | target_interval_list_name | lfp_band_sampling_rate | lfp_electrode_group_name | reference_elect_id | region_id | ||||||||||||||||||
| tonks20211103_.nwb | 7 | 28 | 2f3c93d5-5d5d-2d47-75b3-c346dddbd312 | Ripple 150-250 Hz | 1000 | test interval | 100 | CA1_test | -1 | 19 | tetrode_12.5 | 0 | 0 | 28 | 44 | 0.0 | 0.0 | 0.0 | None | 0.0 | False | 0.0 | 0.0 | 0.0 | ca1 | None | None | |
| 1000 | CA1_test | -1 | 19 | tetrode_12.5 | 0 | 0 | 28 | 44 | 0.0 | 0.0 | 0.0 | None | 0.0 | False | 0.0 | 0.0 | 0.0 | ca1 | None | None | ||||||||
| 8 | 32 | 2f3c93d5-5d5d-2d47-75b3-c346dddbd312 | Ripple 150-250 Hz | 1000 | test interval | 100 | CA1_test | -1 | 19 | tetrode_12.5 | 0 | 0 | 32 | 44 | 0.0 | 0.0 | 0.0 | None | 0.0 | False | 0.0 | 0.0 | 0.0 | ca1 | None | None | ||
| 1000 | CA1_test | -1 | 19 | tetrode_12.5 | 0 | 0 | 32 | 44 | 0.0 | 0.0 | 0.0 | None | 0.0 | False | 0.0 | 0.0 | 0.0 | ca1 | None | None |
We only want the electrode_id to put in the electrode_list:
electrode_list = np.unique(
(
electrodes.loc[
(electrodes.region_name.isin(hpc_names))
& (electrodes.probe_electrode == 0)
]
.reset_index()
.electrode_id
).tolist()
)
electrode_list.sort()
By default, set_lfp_electrodes will use all the available electrodes from LFPBandV1.
We can insert into RippleLFPSelection and the RippleLFPElectrode part table,
passing the key for the entry from LFPBandV1, our electrode_list, and the
group_name into set_lfp_electrodes
group_name = "CA1_test"
lfp_band_key = (
lfp_analysis.LFPBandV1()
& {"filter_name": filter_name, "nwb_file_name": nwb_file_name}
).fetch1("KEY")
sgr.RippleLFPSelection.set_lfp_electrodes(
lfp_band_key,
electrode_list=electrode_list,
group_name=group_name,
)
sgr.RippleLFPSelection.RippleLFPElectrode()
| lfp_merge_id | filter_name descriptive name of this filter | filter_sampling_rate sampling rate for this filter | nwb_file_name name of the NWB file | target_interval_list_name descriptive name of this interval list | lfp_band_sampling_rate the sampling rate for this band | group_name | lfp_electrode_group_name the name of this group of electrodes | electrode_group_name electrode group name from NWBFile | electrode_id the unique number for this electrode | reference_elect_id the reference electrode to use; -1 for no reference |
|---|---|---|---|---|---|---|---|---|---|---|
| 2f3c93d5-5d5d-2d47-75b3-c346dddbd312 | Ripple 150-250 Hz | 1000 | tonks20211103_.nwb | test interval | 100 | CA1_test | CA1_test | 7 | 28 | -1 |
| 2f3c93d5-5d5d-2d47-75b3-c346dddbd312 | Ripple 150-250 Hz | 1000 | tonks20211103_.nwb | test interval | 100 | CA1_test | CA1_test | 8 | 32 | -1 |
Total: 2
Here's the ripple selection key we'll use downstream
rip_sel_key = (sgrip.RippleLFPSelection & lfp_band_key).fetch1("KEY")
Setting Ripple Parameters¶
sgr.RippleParameters()
| ripple_param_name a name for this set of parameters | ripple_param_dict dictionary of parameters |
|---|---|
| default | =BLOB= |
Total: 1
Here are the default ripple parameters:
(sgrip.RippleParameters() & {"ripple_param_name": "default"}).fetch1()
{'ripple_param_name': 'default',
'ripple_param_dict': {'speed_name': 'head_speed',
'ripple_detection_algorithm': 'Kay_ripple_detector',
'ripple_detection_params': {'speed_threshold': 4.0,
'minimum_duration': 0.015,
'zscore_threshold': 2.0,
'smoothing_sigma': 0.004,
'close_ripple_threshold': 0.0}}}
filter_name: which bandpass filter is usedspeed_name: the name of the speed parameters inIntervalPositionInfo
For the Kay_ripple_detector (options are currently Kay and Karlsson, see ripple_detection package for specifics) the parameters are:
speed_threshold(cm/s): maximum speed the animal can moveminimum_duration(s): minimum time above thresholdzscore_threshold(std): minimum value to be considered a ripple, in standard deviations from meansmoothing_sigma(s): how much to smooth the signal in timeclose_ripple_threshold(s): exclude ripples closer than this amount
Check interval speed¶
The speed for this interval should exist under the default position parameter set and for a given interval.
pos_key = sgp.PositionOutput.merge_get_part(
{
"nwb_file_name": nwb_file_name,
"position_info_param_name": "default",
"interval_list_name": "pos 1 valid times",
}
).fetch1("KEY")
(sgp.PositionOutput & pos_key).fetch1_dataframe()
| head_position_x | head_position_y | head_orientation | head_velocity_x | head_velocity_y | head_speed | |
|---|---|---|---|---|---|---|
| time | ||||||
| 1.635961e+09 | 98.670000 | 78.320000 | 1.878849 | -0.212384 | -1.050933e+00 | 1.072179 |
| 1.635961e+09 | 98.615000 | 78.210000 | 1.899349 | -0.143244 | -1.136351e+00 | 1.145344 |
| 1.635961e+09 | 98.633333 | 78.173333 | 1.919567 | -0.031501 | -1.123425e+00 | 1.123867 |
| 1.635961e+09 | 98.596667 | 78.100000 | 1.932884 | 0.094982 | -1.013202e+00 | 1.017644 |
| 1.635961e+09 | 98.633333 | 78.100000 | 1.946067 | 0.194273 | -8.272934e-01 | 0.849798 |
| ... | ... | ... | ... | ... | ... | ... |
| 1.635963e+09 | 96.323333 | 71.500000 | -2.265535 | -0.415082 | -1.486577e-05 | 0.415082 |
| 1.635963e+09 | 96.286667 | 71.500000 | -2.158799 | -0.413708 | -3.243187e-06 | 0.413708 |
| 1.635963e+09 | 96.250000 | 71.500000 | -2.034444 | -0.374655 | -6.383825e-07 | 0.374655 |
| 1.635963e+09 | 96.250000 | 71.500000 | -2.034444 | -0.307793 | -1.133319e-07 | 0.307793 |
| 1.635963e+09 | 96.250000 | 71.500000 | -2.034444 | -0.229237 | -1.813955e-08 | 0.229237 |
48950 rows × 6 columns
We'll use the head_speed above as part of RippleParameters.
Run Ripple Detection¶
Now we can put everything together.
key = {
"ripple_param_name": "default",
**rip_sel_key,
"pos_merge_id": pos_key["merge_id"],
}
sgrip.RippleTimesV1().populate(key)
Computing ripple times for: {'lfp_merge_id': UUID('2f3c93d5-5d5d-2d47-75b3-c346dddbd312'), 'filter_name': 'Ripple 150-250 Hz', 'filter_sampling_rate': 1000, 'nwb_file_name': 'tonks20211103_.nwb', 'target_interval_list_name': 'test interval', 'lfp_band_sampling_rate': 100, 'group_name': 'CA1_test', 'ripple_param_name': 'default', 'pos_merge_id': UUID('68959dc8-f8a3-c3c0-a534-096b3bc10f6c')}
<__array_function__ internals>:200: RuntimeWarning: invalid value encountered in cast
/home/dgramling/anaconda3/envs/spyglass-position3/lib/python3.9/site-packages/ripple_detection/detectors.py:31: RuntimeWarning: invalid value encountered in sqrt
return np.sqrt(ripple_consensus_trace)
/home/dgramling/anaconda3/envs/spyglass-position3/lib/python3.9/site-packages/hdmf/spec/namespace.py:531: UserWarning: Ignoring cached namespace 'hdmf-common' version 1.5.1 because version 1.6.0 is already loaded.
warn("Ignoring cached namespace '%s' version %s because version %s is already loaded."
/home/dgramling/anaconda3/envs/spyglass-position3/lib/python3.9/site-packages/hdmf/spec/namespace.py:531: UserWarning: Ignoring cached namespace 'core' version 2.4.0 because version 2.6.0-alpha is already loaded.
warn("Ignoring cached namespace '%s' version %s because version %s is already loaded."
/home/dgramling/anaconda3/envs/spyglass-position3/lib/python3.9/site-packages/hdmf/spec/namespace.py:531: UserWarning: Ignoring cached namespace 'hdmf-experimental' version 0.2.0 because version 0.3.0 is already loaded.
warn("Ignoring cached namespace '%s' version %s because version %s is already loaded."
/home/dgramling/anaconda3/envs/spyglass-position3/lib/python3.9/site-packages/pynwb/behavior.py:46: UserWarning: SpatialSeries 'series_0' has data shape (66792, 4) which is not compliant with NWB 2.5 and greater. The second dimension should have length <= 3 to represent at most x, y, z.
warnings.warn("SpatialSeries '%s' has data shape %s which is not compliant with NWB 2.5 and greater. "
/home/dgramling/anaconda3/envs/spyglass-position3/lib/python3.9/site-packages/pynwb/behavior.py:46: UserWarning: SpatialSeries 'series_1' has data shape (48950, 4) which is not compliant with NWB 2.5 and greater. The second dimension should have length <= 3 to represent at most x, y, z.
warnings.warn("SpatialSeries '%s' has data shape %s which is not compliant with NWB 2.5 and greater. "
/home/dgramling/anaconda3/envs/spyglass-position3/lib/python3.9/site-packages/pynwb/behavior.py:46: UserWarning: SpatialSeries 'series_2' has data shape (98507, 4) which is not compliant with NWB 2.5 and greater. The second dimension should have length <= 3 to represent at most x, y, z.
warnings.warn("SpatialSeries '%s' has data shape %s which is not compliant with NWB 2.5 and greater. "
/home/dgramling/anaconda3/envs/spyglass-position3/lib/python3.9/site-packages/pynwb/behavior.py:46: UserWarning: SpatialSeries 'series_3' has data shape (44892, 4) which is not compliant with NWB 2.5 and greater. The second dimension should have length <= 3 to represent at most x, y, z.
warnings.warn("SpatialSeries '%s' has data shape %s which is not compliant with NWB 2.5 and greater. "
/home/dgramling/anaconda3/envs/spyglass-position3/lib/python3.9/site-packages/pynwb/behavior.py:46: UserWarning: SpatialSeries 'series_4' has data shape (82313, 4) which is not compliant with NWB 2.5 and greater. The second dimension should have length <= 3 to represent at most x, y, z.
warnings.warn("SpatialSeries '%s' has data shape %s which is not compliant with NWB 2.5 and greater. "
/home/dgramling/anaconda3/envs/spyglass-position3/lib/python3.9/site-packages/pynwb/behavior.py:46: UserWarning: SpatialSeries 'series_5' has data shape (81566, 4) which is not compliant with NWB 2.5 and greater. The second dimension should have length <= 3 to represent at most x, y, z.
warnings.warn("SpatialSeries '%s' has data shape %s which is not compliant with NWB 2.5 and greater. "
/home/dgramling/anaconda3/envs/spyglass-position3/lib/python3.9/site-packages/pynwb/behavior.py:46: UserWarning: SpatialSeries 'series_6' has data shape (83811, 4) which is not compliant with NWB 2.5 and greater. The second dimension should have length <= 3 to represent at most x, y, z.
warnings.warn("SpatialSeries '%s' has data shape %s which is not compliant with NWB 2.5 and greater. "
Writing new NWB file tonks20211103_6VF2MQ65RR.nwb
/home/dgramling/Src2/datajoint-python/datajoint/external.py:276: DeprecationWarning: The truth value of an empty array is ambiguous. Returning False, but in future this will result in an error. Use `array.size > 0` to check that an array is not empty. if check_hash:
And then fetch1_dataframe for ripple times
ripple_times = (sgrip.RippleTimesV1() & key).fetch1_dataframe()
ripple_times
| start_time | end_time | |
|---|---|---|
| id | ||
| 0 | 1.635961e+09 | 1.635961e+09 |
| 1 | 1.635961e+09 | 1.635961e+09 |
| 2 | 1.635961e+09 | 1.635961e+09 |
| 3 | 1.635961e+09 | 1.635961e+09 |
| 4 | 1.635961e+09 | 1.635961e+09 |
| 5 | 1.635961e+09 | 1.635961e+09 |
| 6 | 1.635961e+09 | 1.635961e+09 |
| 7 | 1.635961e+09 | 1.635961e+09 |
| 8 | 1.635961e+09 | 1.635961e+09 |
| 9 | 1.635961e+09 | 1.635961e+09 |
| 10 | 1.635961e+09 | 1.635961e+09 |
| 11 | 1.635961e+09 | 1.635961e+09 |
| 12 | 1.635961e+09 | 1.635961e+09 |
| 13 | 1.635961e+09 | 1.635961e+09 |
| 14 | 1.635961e+09 | 1.635961e+09 |
| 15 | 1.635961e+09 | 1.635961e+09 |
| 16 | 1.635961e+09 | 1.635961e+09 |
Up Next¶
Next, we'll extract mark indicator.