Skip to content

Commit 08d1291

Browse files
authored
Merge pull request #119 from CBroz1/qc
Add QC dashboard
2 parents 678cd95 + 4f6c301 commit 08d1291

File tree

7 files changed

+518
-13
lines changed

7 files changed

+518
-13
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,3 +123,6 @@ docker-compose.y*ml
123123
# vscode settings
124124
.vscode
125125
*.code-workspace
126+
127+
# exports/notes
128+
temp*

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ Observes [Semantic Versioning](https://semver.org/spec/v2.0.0.html) standard and
88
+ Update - clustering step, update duration for "median_subtraction" step
99
+ Bugfix - handles single probe recording in "Neuropix-PXI" format
1010
+ Update - safeguard in creating/inserting probe types upon probe activation
11+
+ Add - quality control metric dashboard
1112

1213
## [0.2.0] - 2022-10-28
1314

element_array_ephys/ephys_report.py

Lines changed: 96 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1-
import pathlib
21
import datetime
3-
import datajoint as dj
2+
import pathlib
43
import typing as T
5-
import json
4+
from uuid import UUID
5+
6+
import datajoint as dj
7+
from element_interface.utils import dict_to_uuid
68

79
schema = dj.schema()
810

@@ -92,18 +94,18 @@ class UnitLevelReport(dj.Computed):
9294
definition = """
9395
-> ephys.CuratedClustering.Unit
9496
---
95-
cluster_quality_label : varchar(100)
96-
waveform_plotly : longblob
97+
cluster_quality_label : varchar(100)
98+
waveform_plotly : longblob
9799
autocorrelogram_plotly : longblob
98100
depth_waveform_plotly : longblob
99101
"""
100102

101103
def make(self, key):
102104

103105
from .plotting.unit_level import (
104-
plot_waveform,
105106
plot_auto_correlogram,
106107
plot_depth_waveforms,
108+
plot_waveform,
107109
)
108110

109111
sampling_rate = (ephys.EphysRecording & key).fetch1(
@@ -136,6 +138,94 @@ def make(self, key):
136138
)
137139

138140

141+
@schema
142+
class QualityMetricCutoffs(dj.Lookup):
143+
definition = """
144+
cutoffs_id : smallint
145+
---
146+
amplitude_cutoff_maximum=null : float # Defualt null, no cutoff applied
147+
presence_ratio_minimum=null : float # Defualt null, no cutoff applied
148+
isi_violations_maximum=null : float # Defualt null, no cutoff applied
149+
cutoffs_hash: uuid
150+
unique index (cutoffs_hash)
151+
"""
152+
153+
contents = [
154+
(0, None, None, None, UUID("5d835de1-e1af-1871-d81f-d12a9702ff5f")),
155+
(1, 0.1, 0.9, 0.5, UUID("f74ccd77-0b3a-2bf8-0bfd-ec9713b5dca8")),
156+
]
157+
158+
@classmethod
159+
def insert_new_cutoffs(
160+
cls,
161+
cutoffs_id: int = None,
162+
amplitude_cutoff_maximum: float = None,
163+
presence_ratio_minimum: float = None,
164+
isi_violations_maximum: float = None,
165+
):
166+
if cutoffs_id is None:
167+
cutoffs_id = (dj.U().aggr(cls, n="max(cutoffs_id)").fetch1("n") or 0) + 1
168+
169+
param_dict = {
170+
"amplitude_cutoff_maximum": amplitude_cutoff_maximum,
171+
"presence_ratio_minimum": presence_ratio_minimum,
172+
"isi_violations_maximum": isi_violations_maximum,
173+
}
174+
param_hash = dict_to_uuid(param_dict)
175+
param_query = cls & {"cutoffs_hash": param_hash}
176+
177+
if param_query: # If the specified cutoff set already exists
178+
existing_paramset_idx = param_query.fetch1("cutoffs_id")
179+
if (
180+
existing_paramset_idx == cutoffs_id
181+
): # If the existing set has the same id: job done
182+
return
183+
# If not same name: human err, adding the same set with different name
184+
else:
185+
raise dj.DataJointError(
186+
f"The specified param-set already exists"
187+
f" - with paramset_idx: {existing_paramset_idx}"
188+
)
189+
else:
190+
if {"cutoffs_id": cutoffs_id} in cls.proj():
191+
raise dj.DataJointError(
192+
f"The specified cuttoffs_id {cutoffs_id} already exists,"
193+
f" please pick a different one."
194+
)
195+
cls.insert1(
196+
{"cutoffs_id": cutoffs_id, **param_dict, "cutoffs_hash": param_hash}
197+
)
198+
199+
200+
@schema
201+
class QualityMetricSet(dj.Manual):
202+
definition = """
203+
-> ephys.QualityMetrics
204+
-> QualityMetricCutoffs
205+
"""
206+
207+
208+
@schema
209+
class QualityMetricReport(dj.Computed):
210+
definition = """
211+
-> QualityMetricSet
212+
---
213+
plot_grid : longblob
214+
"""
215+
216+
def make(self, key):
217+
from .plotting.qc import QualityMetricFigs
218+
219+
cutoffs = (QualityMetricCutoffs & key).fetch1()
220+
qc_key = ephys.QualityMetrics & key
221+
222+
self.insert1(
223+
key.update(
224+
dict(plot_grid=QualityMetricFigs(qc_key, **cutoffs).get_grid().to_json)
225+
)
226+
)
227+
228+
139229
def _make_save_dir(root_dir: pathlib.Path = None) -> pathlib.Path:
140230
if root_dir is None:
141231
root_dir = pathlib.Path().absolute()

0 commit comments

Comments
 (0)