|
6 | 6 | import importlib
|
7 | 7 | import gc
|
8 | 8 | from decimal import Decimal
|
| 9 | +import pandas as pd |
9 | 10 |
|
10 | 11 | from element_interface.utils import find_root_directory, find_full_path, dict_to_uuid
|
11 | 12 |
|
@@ -683,7 +684,7 @@ class Unit(dj.Part):
|
683 | 684 | spike_count: int # how many spikes in this recording for this unit
|
684 | 685 | spike_times: longblob # (s) spike times of this unit, relative to the start of the EphysRecording
|
685 | 686 | spike_sites : longblob # array of electrode associated with each spike
|
686 |
| - spike_depths : longblob # (um) array of depths associated with each spike, relative to the (0, 0) of the probe |
| 687 | + spike_depths=null : longblob # (um) array of depths associated with each spike, relative to the (0, 0) of the probe |
687 | 688 | """
|
688 | 689 |
|
689 | 690 | def make(self, key):
|
@@ -845,6 +846,74 @@ def yield_unit_waveforms():
|
845 | 846 | self.Waveform.insert(unit_electrode_waveforms, ignore_extra_fields=True)
|
846 | 847 |
|
847 | 848 |
|
| 849 | +@schema |
| 850 | +class QualityMetrics(dj.Imported): |
| 851 | + definition = """ |
| 852 | + # Clusters and waveforms metrics |
| 853 | + -> CuratedClustering |
| 854 | + """ |
| 855 | + |
| 856 | + class Cluster(dj.Part): |
| 857 | + definition = """ |
| 858 | + # Cluster metrics for a particular unit |
| 859 | + -> master |
| 860 | + -> CuratedClustering.Unit |
| 861 | + --- |
| 862 | + firing_rate=null: float # (Hz) firing rate for a unit |
| 863 | + snr=null: float # signal-to-noise ratio for a unit |
| 864 | + presence_ratio=null: float # fraction of time in which spikes are present |
| 865 | + isi_violation=null: float # rate of ISI violation as a fraction of overall rate |
| 866 | + number_violation=null: int # total number of ISI violations |
| 867 | + amplitude_cutoff=null: float # estimate of miss rate based on amplitude histogram |
| 868 | + isolation_distance=null: float # distance to nearest cluster in Mahalanobis space |
| 869 | + l_ratio=null: float # |
| 870 | + d_prime=null: float # Classification accuracy based on LDA |
| 871 | + nn_hit_rate=null: float # Fraction of neighbors for target cluster that are also in target cluster |
| 872 | + nn_miss_rate=null: float # Fraction of neighbors outside target cluster that are in target cluster |
| 873 | + silhouette_score=null: float # Standard metric for cluster overlap |
| 874 | + max_drift=null: float # Maximum change in spike depth throughout recording |
| 875 | + cumulative_drift=null: float # Cumulative change in spike depth throughout recording |
| 876 | + contamination_rate=null: float # |
| 877 | + """ |
| 878 | + |
| 879 | + class Waveform(dj.Part): |
| 880 | + definition = """ |
| 881 | + # Waveform metrics for a particular unit |
| 882 | + -> master |
| 883 | + -> CuratedClustering.Unit |
| 884 | + --- |
| 885 | + amplitude: float # (uV) absolute difference between waveform peak and trough |
| 886 | + duration: float # (ms) time between waveform peak and trough |
| 887 | + halfwidth=null: float # (ms) spike width at half max amplitude |
| 888 | + pt_ratio=null: float # absolute amplitude of peak divided by absolute amplitude of trough relative to 0 |
| 889 | + repolarization_slope=null: float # the repolarization slope was defined by fitting a regression line to the first 30us from trough to peak |
| 890 | + recovery_slope=null: float # the recovery slope was defined by fitting a regression line to the first 30us from peak to tail |
| 891 | + spread=null: float # (um) the range with amplitude above 12% of the maximum amplitude along the probe |
| 892 | + velocity_above=null: float # (s/m) inverse velocity of waveform propagation from the soma toward the top of the probe |
| 893 | + velocity_below=null: float # (s/m) inverse velocity of waveform propagation from the soma toward the bottom of the probe |
| 894 | + """ |
| 895 | + |
| 896 | + def make(self, key): |
| 897 | + output_dir = (ClusteringTask & key).fetch1('clustering_output_dir') |
| 898 | + kilosort_dir = find_full_path(get_ephys_root_data_dir(), output_dir) |
| 899 | + |
| 900 | + metric_fp = kilosort_dir / 'metrics.csv' |
| 901 | + |
| 902 | + if not metric_fp.exists(): |
| 903 | + raise FileNotFoundError(f'QC metrics file not found: {metric_fp}') |
| 904 | + |
| 905 | + metrics_df = pd.read_csv(metric_fp) |
| 906 | + metrics_df.set_index('cluster_id', inplace=True) |
| 907 | + |
| 908 | + metrics_list = [ |
| 909 | + dict(metrics_df.loc[unit_key['unit']], **unit_key) |
| 910 | + for unit_key in (CuratedClustering.Unit & key).fetch('KEY')] |
| 911 | + |
| 912 | + self.insert1(key) |
| 913 | + self.Cluster.insert(metrics_list, ignore_extra_fields=True) |
| 914 | + self.Waveform.insert(metrics_list, ignore_extra_fields=True) |
| 915 | + |
| 916 | + |
848 | 917 | # ---------------- HELPER FUNCTIONS ----------------
|
849 | 918 |
|
850 | 919 | def get_spikeglx_meta_filepath(ephys_recording_key):
|
|
0 commit comments