Skip to content

Commit c31c53d

Browse files
committed
Cleanup, add comments
1 parent 9eb34cd commit c31c53d

File tree

1 file changed

+35
-30
lines changed
  • element_array_ephys/plotting

1 file changed

+35
-30
lines changed

element_array_ephys/plotting/qc.py

Lines changed: 35 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -42,32 +42,32 @@ def __init__(
4242
self._ephys = ephys
4343
self._key = key
4444
self._scale = scale
45-
self._plots = {}
45+
self._plots = {} # Empty default to defer set to dict property below
4646
self._fig_width = fig_width
4747
self._amplitude_cutoff_max = amplitude_cutoff_maximum
4848
self._presence_ratio_min = presence_ratio_minimum
4949
self._isi_violations_max = isi_violations_maximum
5050
self._dark_mode = dark_mode
51-
self._units = pd.DataFrame()
51+
self._units = pd.DataFrame() # Empty default
5252
self._x_fmt = dict(showgrid=False, zeroline=False, linewidth=2, ticks="outside")
5353
self._y_fmt = dict(showgrid=False, linewidth=0, zeroline=True, visible=False)
54-
self._no_data_text = "No data available"
55-
self._null_series = pd.Series(np.nan)
54+
self._no_data_text = "No data available" # What to show when no data in table
55+
self._null_series = pd.Series(np.nan) # What to substitute when no data
5656

5757
@property
5858
def key(self) -> dict:
5959
"""Key in ephys.QualityMetrics table"""
6060
return self._key
6161

62-
@key.setter
62+
@key.setter # Allows `cls.property = new_item` notation
6363
def key(self, key: dict):
6464
"""Use class_instance.key = your_key to reset key"""
6565
if key not in self._ephys.QualityMetrics.fetch("KEY"):
66-
# if not already key, check if unquely identifies entry
66+
# If not already full key, check if unquely identifies entry
6767
key = (self._ephys.QualityMetrics & key).fetch1("KEY")
6868
self._key = key
6969

70-
@key.deleter
70+
@key.deleter # Allows `del cls.property` to clear key
7171
def key(self):
7272
"""Use del class_instance.key to clear key"""
7373
logger.info("Cleared key")
@@ -103,15 +103,17 @@ def cutoffs(self, add_to_tables: bool = False, **cutoff_kwargs):
103103
"isi_violations_maximum", self._isi_violations_max
104104
)
105105
_ = self.units
106+
106107
if add_to_tables:
107108
ephys_report.QualityMetricCutoffs.insert_new_cutoffs(**cutoff_kwargs)
109+
logger.info("Added cutoffs to QualityMetricCutoffs table")
108110

109111
@property
110112
def units(self) -> pd.DataFrame:
111113
"""Pandas dataframe of QC metrics"""
112114
if not self._key:
113-
logger.info("No key set")
114115
return self._null_series
116+
115117
if self._units.empty:
116118
restrictions = ["TRUE"]
117119
if self._amplitude_cutoff_max:
@@ -120,14 +122,15 @@ def units(self) -> pd.DataFrame:
120122
restrictions.append(f"presence_ratio > {self._presence_ratio_min}")
121123
if self._isi_violations_max:
122124
restrictions.append(f"isi_violation < {self._isi_violations_max}")
123-
" AND ".join(restrictions)
125+
" AND ".join(restrictions) # Build restriction from cutoffs
124126
return (
125127
self._ephys.QualityMetrics
126128
* self._ephys.QualityMetrics.Cluster
127129
* self._ephys.QualityMetrics.Waveform
128130
& self._key
129131
& restrictions
130132
).fetch(format="frame")
133+
131134
return self._units
132135

133136
def _format_fig(
@@ -145,12 +148,13 @@ class init, 1.
145148
Returns:
146149
go.Figure: Formatted figure
147150
"""
148-
149151
if not fig:
150152
fig = go.Figure()
151153
if not scale:
152154
scale = self._scale
155+
153156
width = self._fig_width * scale
157+
154158
return fig.update_layout(
155159
template="plotly_dark" if self._dark_mode else "simple_white",
156160
width=width,
@@ -160,14 +164,15 @@ class init, 1.
160164
)
161165

162166
def _empty_fig(
163-
self, annotation="Select a key to visualize QC metrics", scale=None
167+
self, text="Select a key to visualize QC metrics", scale=None
164168
) -> go.Figure:
165169
"""Return figure object for when no key is provided"""
166170
if not scale:
167171
scale = self._scale
172+
168173
return (
169174
self._format_fig(scale=scale)
170-
.add_annotation(text=annotation, showarrow=False)
175+
.add_annotation(text=text, showarrow=False)
171176
.update_layout(xaxis=self._y_fmt, yaxis=self._y_fmt)
172177
)
173178

@@ -196,10 +201,14 @@ class initialization.
196201
scale = self._scale
197202
if not fig:
198203
fig = self._format_fig(scale=scale)
199-
# if data.isnull().all():
200-
histogram, histogram_bins = np.histogram(data, bins=bins, density=True)
201204

202-
fig.add_trace(
205+
if not data.isnull().all():
206+
histogram, histogram_bins = np.histogram(data, bins=bins, density=True)
207+
else:
208+
# To quiet divide by zero error when no data
209+
histogram, histogram_bins = np.ndarray(0), np.ndarray(0)
210+
211+
return fig.add_trace(
203212
go.Scatter(
204213
x=histogram_bins[:-1],
205214
y=gaussian_filter1d(histogram, 1), # TODO: remove smoothing
@@ -209,7 +218,6 @@ class initialization.
209218
),
210219
**trace_kwargs,
211220
)
212-
return fig
213221

214222
def get_single_fig(self, fig_name: str, scale: float = None) -> go.Figure:
215223
"""Return a single figure of the plots listed in the plot_list property
@@ -224,7 +232,6 @@ def get_single_fig(self, fig_name: str, scale: float = None) -> go.Figure:
224232
"""
225233
if not self._key:
226234
return self._empty_fig()
227-
228235
if not scale:
229236
scale = self._scale
230237

@@ -234,7 +241,7 @@ def get_single_fig(self, fig_name: str, scale: float = None) -> go.Figure:
234241
vline = fig_dict.get("vline", None)
235242

236243
if data.isnull().all():
237-
return self._empty_fig(annotation=self._no_data_text)
244+
return self._empty_fig(text=self._no_data_text)
238245

239246
fig = (
240247
self._plot_metric(data=data, bins=bins, scale=scale)
@@ -265,11 +272,11 @@ def get_grid(self, n_columns: int = 4, scale: float = 1.0) -> go.Figure:
265272

266273
if not self._key:
267274
return self._empty_fig()
268-
269-
n_rows = int(np.ceil(len(self.plots) / n_columns))
270275
if not scale:
271276
scale = self._scale
272277

278+
n_rows = int(np.ceil(len(self.plots) / n_columns))
279+
273280
fig = self._format_fig(
274281
fig=make_subplots(
275282
rows=n_rows,
@@ -280,12 +287,12 @@ def get_grid(self, n_columns: int = 4, scale: float = 1.0) -> go.Figure:
280287
),
281288
scale=scale,
282289
ratio=(n_columns / n_rows),
283-
).update_layout(
290+
).update_layout( # Global title
284291
title=dict(text="Histograms of Quality Metrics", xanchor="center", x=0.5),
285292
font=dict(size=12 * scale),
286293
)
287294

288-
for idx, plot in enumerate(self._plots.values()):
295+
for idx, plot in enumerate(self._plots.values()): # Each subplot
289296
this_row = int(np.floor(idx / n_columns) + 1)
290297
this_col = idx % n_columns + 1
291298
data = plot.get("data", self._null_series)
@@ -302,7 +309,7 @@ def get_grid(self, n_columns: int = 4, scale: float = 1.0) -> go.Figure:
302309
),
303310
]
304311
)
305-
fig = self._plot_metric( # still need to plot so vlines y_value works right
312+
fig = self._plot_metric( # still need to plot empty to cal y_vals min/max
306313
data=data,
307314
bins=plot["bins"],
308315
fig=fig,
@@ -317,12 +324,11 @@ def get_grid(self, n_columns: int = 4, scale: float = 1.0) -> go.Figure:
317324
)
318325
if vline:
319326
y_vals = fig.to_dict()["data"][idx]["y"]
320-
# y_vals = plot["data"]
321-
fig.add_shape(
327+
fig.add_shape( # Add overlay WRT whole fig
322328
go.layout.Shape(
323329
type="line",
324330
yref="paper",
325-
xref="x",
331+
xref="x", # relative to subplot x
326332
x0=vline,
327333
y0=min(y_vals),
328334
x1=vline,
@@ -332,14 +338,13 @@ def get_grid(self, n_columns: int = 4, scale: float = 1.0) -> go.Figure:
332338
row=this_row,
333339
col=this_col,
334340
)
335-
fig.update_xaxes(**self._x_fmt)
336-
fig.update_yaxes(**self._y_fmt)
337-
return fig
341+
342+
return fig.update_xaxes(**self._x_fmt).update_yaxes(**self._y_fmt)
338343

339344
@property
340345
def plot_list(self):
341346
"""List of plots that can be rendered inidividually by name or as grid"""
342-
if not self.plots:
347+
if not self._plots:
343348
_ = self.plots
344349
return [plot for plot in self._plots]
345350

0 commit comments

Comments
 (0)