Initial commit for neuropixels-analysis
This commit is contained in:
323
references/MOTION_CORRECTION.md
Normal file
323
references/MOTION_CORRECTION.md
Normal file
@@ -0,0 +1,323 @@
|
||||
# Motion/Drift Correction Reference
|
||||
|
||||
Mechanical drift during acute probe insertion is a major challenge for Neuropixels recordings. This guide covers detection, estimation, and correction of motion artifacts.
|
||||
|
||||
## Why Motion Correction Matters
|
||||
|
||||
- Neuropixels probes can drift 10-100+ μm during recording
|
||||
- Uncorrected drift leads to:
|
||||
- Units appearing/disappearing mid-recording
|
||||
- Waveform amplitude changes
|
||||
- Incorrect spike-unit assignments
|
||||
- Reduced unit yield
|
||||
|
||||
## Detection: Check Before Sorting
|
||||
|
||||
**Always visualize drift before running spike sorting!**
|
||||
|
||||
```python
|
||||
import spikeinterface.full as si
|
||||
from spikeinterface.sortingcomponents.peak_detection import detect_peaks
|
||||
from spikeinterface.sortingcomponents.peak_localization import localize_peaks
|
||||
|
||||
# Preprocess first (don't whiten - affects peak localization)
|
||||
rec = si.highpass_filter(recording, freq_min=400.)
|
||||
rec = si.common_reference(rec, operator='median', reference='global')
|
||||
|
||||
# Detect peaks
|
||||
noise_levels = si.get_noise_levels(rec, return_in_uV=False)
|
||||
peaks = detect_peaks(
|
||||
rec,
|
||||
method='locally_exclusive',
|
||||
noise_levels=noise_levels,
|
||||
detect_threshold=5,
|
||||
radius_um=50.,
|
||||
n_jobs=8,
|
||||
chunk_duration='1s',
|
||||
progress_bar=True
|
||||
)
|
||||
|
||||
# Localize peaks
|
||||
peak_locations = localize_peaks(
|
||||
rec, peaks,
|
||||
method='center_of_mass',
|
||||
n_jobs=8,
|
||||
chunk_duration='1s'
|
||||
)
|
||||
|
||||
# Visualize drift
|
||||
si.plot_drift_raster_map(
|
||||
peaks=peaks,
|
||||
peak_locations=peak_locations,
|
||||
recording=rec,
|
||||
clim=(-200, 0) # Adjust color limits
|
||||
)
|
||||
```
|
||||
|
||||
### Interpreting Drift Plots
|
||||
|
||||
| Pattern | Interpretation | Action |
|
||||
|---------|---------------|--------|
|
||||
| Horizontal bands, stable | No significant drift | Skip correction |
|
||||
| Diagonal bands (slow) | Gradual settling drift | Use motion correction |
|
||||
| Rapid jumps | Brain pulsation or movement | Use non-rigid correction |
|
||||
| Chaotic patterns | Severe instability | Consider discarding segment |
|
||||
|
||||
## Motion Correction Methods
|
||||
|
||||
### Quick Correction (Recommended Start)
|
||||
|
||||
```python
|
||||
# Simple one-liner with preset
|
||||
rec_corrected = si.correct_motion(
|
||||
recording=rec,
|
||||
preset='nonrigid_fast_and_accurate'
|
||||
)
|
||||
```
|
||||
|
||||
### Available Presets
|
||||
|
||||
| Preset | Speed | Accuracy | Best For |
|
||||
|--------|-------|----------|----------|
|
||||
| `rigid_fast` | Fast | Low | Quick check, small drift |
|
||||
| `kilosort_like` | Medium | Good | Kilosort-compatible results |
|
||||
| `nonrigid_accurate` | Slow | High | Publication-quality |
|
||||
| `nonrigid_fast_and_accurate` | Medium | High | **Recommended default** |
|
||||
| `dredge` | Slow | Highest | Best results, complex drift |
|
||||
| `dredge_fast` | Medium | High | DREDge with less compute |
|
||||
|
||||
### Full Control Pipeline
|
||||
|
||||
```python
|
||||
from spikeinterface.sortingcomponents.motion import (
|
||||
estimate_motion,
|
||||
interpolate_motion
|
||||
)
|
||||
|
||||
# Step 1: Estimate motion
|
||||
motion, temporal_bins, spatial_bins = estimate_motion(
|
||||
rec,
|
||||
peaks,
|
||||
peak_locations,
|
||||
method='decentralized',
|
||||
direction='y',
|
||||
rigid=False, # Non-rigid for Neuropixels
|
||||
win_step_um=50, # Spatial window step
|
||||
win_sigma_um=150, # Spatial smoothing
|
||||
bin_s=2.0, # Temporal bin size
|
||||
progress_bar=True
|
||||
)
|
||||
|
||||
# Step 2: Visualize motion estimate
|
||||
si.plot_motion(
|
||||
motion,
|
||||
temporal_bins,
|
||||
spatial_bins,
|
||||
recording=rec
|
||||
)
|
||||
|
||||
# Step 3: Apply correction via interpolation
|
||||
rec_corrected = interpolate_motion(
|
||||
recording=rec,
|
||||
motion=motion,
|
||||
temporal_bins=temporal_bins,
|
||||
spatial_bins=spatial_bins,
|
||||
border_mode='force_extrapolate'
|
||||
)
|
||||
```
|
||||
|
||||
### Save Motion Estimate
|
||||
|
||||
```python
|
||||
# Save for later use
|
||||
import numpy as np
|
||||
np.savez('motion_estimate.npz',
|
||||
motion=motion,
|
||||
temporal_bins=temporal_bins,
|
||||
spatial_bins=spatial_bins)
|
||||
|
||||
# Load later
|
||||
data = np.load('motion_estimate.npz')
|
||||
motion = data['motion']
|
||||
temporal_bins = data['temporal_bins']
|
||||
spatial_bins = data['spatial_bins']
|
||||
```
|
||||
|
||||
## DREDge: State-of-the-Art Method
|
||||
|
||||
DREDge (Decentralized Registration of Electrophysiology Data) is currently the best-performing motion correction method.
|
||||
|
||||
### Using DREDge Preset
|
||||
|
||||
```python
|
||||
# AP-band motion estimation
|
||||
rec_corrected = si.correct_motion(rec, preset='dredge')
|
||||
|
||||
# Or compute explicitly
|
||||
motion, motion_info = si.compute_motion(
|
||||
rec,
|
||||
preset='dredge',
|
||||
output_motion_info=True,
|
||||
folder='motion_output/',
|
||||
**job_kwargs
|
||||
)
|
||||
```
|
||||
|
||||
### LFP-Based Motion Estimation
|
||||
|
||||
For very fast drift or when AP-band estimation fails:
|
||||
|
||||
```python
|
||||
# Load LFP stream
|
||||
lfp = si.read_spikeglx('/path/to/data', stream_name='imec0.lf')
|
||||
|
||||
# Estimate motion from LFP (faster, handles rapid drift)
|
||||
motion_lfp, motion_info = si.compute_motion(
|
||||
lfp,
|
||||
preset='dredge_lfp',
|
||||
output_motion_info=True
|
||||
)
|
||||
|
||||
# Apply to AP recording
|
||||
rec_corrected = interpolate_motion(
|
||||
recording=rec, # AP recording
|
||||
motion=motion_lfp,
|
||||
temporal_bins=motion_info['temporal_bins'],
|
||||
spatial_bins=motion_info['spatial_bins']
|
||||
)
|
||||
```
|
||||
|
||||
## Integration with Spike Sorting
|
||||
|
||||
### Option 1: Pre-correction (Recommended)
|
||||
|
||||
```python
|
||||
# Correct before sorting
|
||||
rec_corrected = si.correct_motion(rec, preset='nonrigid_fast_and_accurate')
|
||||
|
||||
# Save corrected recording
|
||||
rec_corrected = rec_corrected.save(folder='preprocessed_motion_corrected/',
|
||||
format='binary', n_jobs=8)
|
||||
|
||||
# Run spike sorting on corrected data
|
||||
sorting = si.run_sorter('kilosort4', rec_corrected, output_folder='ks4/')
|
||||
```
|
||||
|
||||
### Option 2: Let Kilosort Handle It
|
||||
|
||||
Kilosort 2.5+ has built-in drift correction:
|
||||
|
||||
```python
|
||||
sorting = si.run_sorter(
|
||||
'kilosort4',
|
||||
rec, # Not motion corrected
|
||||
output_folder='ks4/',
|
||||
nblocks=5, # Non-rigid blocks for drift correction
|
||||
do_correction=True # Enable Kilosort's drift correction
|
||||
)
|
||||
```
|
||||
|
||||
### Option 3: Post-hoc Correction
|
||||
|
||||
```python
|
||||
# Sort first
|
||||
sorting = si.run_sorter('kilosort4', rec, output_folder='ks4/')
|
||||
|
||||
# Then estimate motion from sorted spikes
|
||||
# (More accurate as it uses actual spike times)
|
||||
from spikeinterface.sortingcomponents.motion import estimate_motion_from_sorting
|
||||
|
||||
motion = estimate_motion_from_sorting(sorting, rec)
|
||||
```
|
||||
|
||||
## Parameters Deep Dive
|
||||
|
||||
### Peak Detection
|
||||
|
||||
```python
|
||||
peaks = detect_peaks(
|
||||
rec,
|
||||
method='locally_exclusive', # Best for dense probes
|
||||
noise_levels=noise_levels,
|
||||
detect_threshold=5, # Lower = more peaks (noisier estimate)
|
||||
radius_um=50., # Exclusion radius
|
||||
exclude_sweep_ms=0.1, # Temporal exclusion
|
||||
)
|
||||
```
|
||||
|
||||
### Motion Estimation
|
||||
|
||||
```python
|
||||
motion = estimate_motion(
|
||||
rec, peaks, peak_locations,
|
||||
method='decentralized', # 'decentralized' or 'iterative_template'
|
||||
direction='y', # Along probe axis
|
||||
rigid=False, # False for non-rigid
|
||||
bin_s=2.0, # Temporal resolution (seconds)
|
||||
win_step_um=50, # Spatial window step
|
||||
win_sigma_um=150, # Spatial smoothing sigma
|
||||
margin_um=0, # Margin at probe edges
|
||||
win_scale_um=150, # Window scale for weights
|
||||
)
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Over-correction (Wavy Patterns)
|
||||
|
||||
```python
|
||||
# Increase temporal smoothing
|
||||
motion = estimate_motion(..., bin_s=5.0) # Larger bins
|
||||
|
||||
# Or use rigid correction for small drift
|
||||
motion = estimate_motion(..., rigid=True)
|
||||
```
|
||||
|
||||
### Under-correction (Drift Remains)
|
||||
|
||||
```python
|
||||
# Decrease spatial window for finer non-rigid estimate
|
||||
motion = estimate_motion(..., win_step_um=25, win_sigma_um=75)
|
||||
|
||||
# Use more peaks
|
||||
peaks = detect_peaks(..., detect_threshold=4) # Lower threshold
|
||||
```
|
||||
|
||||
### Edge Artifacts
|
||||
|
||||
```python
|
||||
rec_corrected = interpolate_motion(
|
||||
rec, motion, temporal_bins, spatial_bins,
|
||||
border_mode='force_extrapolate', # or 'remove_channels'
|
||||
spatial_interpolation_method='kriging'
|
||||
)
|
||||
```
|
||||
|
||||
## Validation
|
||||
|
||||
After correction, re-visualize to confirm:
|
||||
|
||||
```python
|
||||
# Re-detect peaks on corrected recording
|
||||
peaks_corrected = detect_peaks(rec_corrected, ...)
|
||||
peak_locations_corrected = localize_peaks(rec_corrected, peaks_corrected, ...)
|
||||
|
||||
# Plot before/after comparison
|
||||
fig, axes = plt.subplots(1, 2, figsize=(14, 6))
|
||||
|
||||
# Before
|
||||
si.plot_drift_raster_map(peaks, peak_locations, rec, ax=axes[0])
|
||||
axes[0].set_title('Before Correction')
|
||||
|
||||
# After
|
||||
si.plot_drift_raster_map(peaks_corrected, peak_locations_corrected,
|
||||
rec_corrected, ax=axes[1])
|
||||
axes[1].set_title('After Correction')
|
||||
```
|
||||
|
||||
## References
|
||||
|
||||
- [SpikeInterface Motion Correction Docs](https://spikeinterface.readthedocs.io/en/stable/modules/motion_correction.html)
|
||||
- [Handle Drift Tutorial](https://spikeinterface.readthedocs.io/en/stable/how_to/handle_drift.html)
|
||||
- [DREDge GitHub](https://github.com/evarol/DREDge)
|
||||
- Windolf et al. (2023) "DREDge: robust motion correction for high-density extracellular recordings"
|
||||
Reference in New Issue
Block a user