mirror of
https://github.com/K-Dense-AI/claude-scientific-skills.git
synced 2026-03-27 07:09:27 +08:00
324 lines
8.5 KiB
Markdown
324 lines
8.5 KiB
Markdown
# 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"
|