labdata 0.1.0__tar.gz → 0.1.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. {labdata-0.1.0 → labdata-0.1.1}/PKG-INFO +1 -1
  2. {labdata-0.1.0 → labdata-0.1.1}/labdata/__init__.py +1 -1
  3. {labdata-0.1.0 → labdata-0.1.1}/labdata/dashboard/segmentation.py +212 -116
  4. labdata-0.1.1/labdata/dashboard/sorting.py +207 -0
  5. {labdata-0.1.0 → labdata-0.1.1}/labdata.egg-info/PKG-INFO +1 -1
  6. labdata-0.1.0/labdata/dashboard/sorting.py +0 -102
  7. {labdata-0.1.0 → labdata-0.1.1}/LICENSE +0 -0
  8. {labdata-0.1.0 → labdata-0.1.1}/README.md +0 -0
  9. {labdata-0.1.0 → labdata-0.1.1}/labdata/cli.py +0 -0
  10. {labdata-0.1.0 → labdata-0.1.1}/labdata/compute/__init__.py +0 -0
  11. {labdata-0.1.0 → labdata-0.1.1}/labdata/compute/caiman.py +0 -0
  12. {labdata-0.1.0 → labdata-0.1.1}/labdata/compute/ec2.py +0 -0
  13. {labdata-0.1.0 → labdata-0.1.1}/labdata/compute/ephys.py +0 -0
  14. {labdata-0.1.0 → labdata-0.1.1}/labdata/compute/pose.py +0 -0
  15. {labdata-0.1.0 → labdata-0.1.1}/labdata/compute/schedulers.py +0 -0
  16. {labdata-0.1.0 → labdata-0.1.1}/labdata/compute/singularity.py +0 -0
  17. {labdata-0.1.0 → labdata-0.1.1}/labdata/compute/suite2p.py +0 -0
  18. {labdata-0.1.0 → labdata-0.1.1}/labdata/compute/utils.py +0 -0
  19. {labdata-0.1.0 → labdata-0.1.1}/labdata/copy.py +0 -0
  20. {labdata-0.1.0 → labdata-0.1.1}/labdata/dashboard/__init__.py +0 -0
  21. {labdata-0.1.0 → labdata-0.1.1}/labdata/dashboard/compute.py +0 -0
  22. {labdata-0.1.0 → labdata-0.1.1}/labdata/dashboard/console.py +0 -0
  23. {labdata-0.1.0 → labdata-0.1.1}/labdata/dashboard/decisiontask.py +0 -0
  24. {labdata-0.1.0 → labdata-0.1.1}/labdata/dashboard/explorer/__init__.py +0 -0
  25. {labdata-0.1.0 → labdata-0.1.1}/labdata/dashboard/explorer/cellsegmentation.py +0 -0
  26. {labdata-0.1.0 → labdata-0.1.1}/labdata/dashboard/explorer/spikesorting.py +0 -0
  27. {labdata-0.1.0 → labdata-0.1.1}/labdata/dashboard/index.py +0 -0
  28. {labdata-0.1.0 → labdata-0.1.1}/labdata/dashboard/notes.py +0 -0
  29. {labdata-0.1.0 → labdata-0.1.1}/labdata/dashboard/procedures.py +0 -0
  30. {labdata-0.1.0 → labdata-0.1.1}/labdata/dashboard/video.py +0 -0
  31. {labdata-0.1.0 → labdata-0.1.1}/labdata/export.py +0 -0
  32. {labdata-0.1.0 → labdata-0.1.1}/labdata/rules/__init__.py +0 -0
  33. {labdata-0.1.0 → labdata-0.1.1}/labdata/rules/ephys.py +0 -0
  34. {labdata-0.1.0 → labdata-0.1.1}/labdata/rules/imaging.py +0 -0
  35. {labdata-0.1.0 → labdata-0.1.1}/labdata/rules/utils.py +0 -0
  36. {labdata-0.1.0 → labdata-0.1.1}/labdata/s3.py +0 -0
  37. {labdata-0.1.0 → labdata-0.1.1}/labdata/schema/__init__.py +0 -0
  38. {labdata-0.1.0 → labdata-0.1.1}/labdata/schema/ephys.py +0 -0
  39. {labdata-0.1.0 → labdata-0.1.1}/labdata/schema/general.py +0 -0
  40. {labdata-0.1.0 → labdata-0.1.1}/labdata/schema/histology.py +0 -0
  41. {labdata-0.1.0 → labdata-0.1.1}/labdata/schema/onephoton.py +0 -0
  42. {labdata-0.1.0 → labdata-0.1.1}/labdata/schema/procedures.py +0 -0
  43. {labdata-0.1.0 → labdata-0.1.1}/labdata/schema/tasks.py +0 -0
  44. {labdata-0.1.0 → labdata-0.1.1}/labdata/schema/twophoton.py +0 -0
  45. {labdata-0.1.0 → labdata-0.1.1}/labdata/schema/utils.py +0 -0
  46. {labdata-0.1.0 → labdata-0.1.1}/labdata/schema/video.py +0 -0
  47. {labdata-0.1.0 → labdata-0.1.1}/labdata/stacks.py +0 -0
  48. {labdata-0.1.0 → labdata-0.1.1}/labdata/utils.py +0 -0
  49. {labdata-0.1.0 → labdata-0.1.1}/labdata/widgets.py +0 -0
  50. {labdata-0.1.0 → labdata-0.1.1}/labdata.egg-info/SOURCES.txt +0 -0
  51. {labdata-0.1.0 → labdata-0.1.1}/labdata.egg-info/dependency_links.txt +0 -0
  52. {labdata-0.1.0 → labdata-0.1.1}/labdata.egg-info/entry_points.txt +0 -0
  53. {labdata-0.1.0 → labdata-0.1.1}/labdata.egg-info/requires.txt +0 -0
  54. {labdata-0.1.0 → labdata-0.1.1}/labdata.egg-info/top_level.txt +0 -0
  55. {labdata-0.1.0 → labdata-0.1.1}/pyproject.toml +0 -0
  56. {labdata-0.1.0 → labdata-0.1.1}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: labdata
3
- Version: 0.1.0
3
+ Version: 0.1.1
4
4
  Summary: Package to manage data in experimental neuroscience labs
5
5
  Author-email: Joao Couto <jpcouto@gmail.com>
6
6
  Project-URL: Homepage, https://github.com/jcouto/labdata
@@ -1,4 +1,4 @@
1
- VERSION = "0.1.0"
1
+ VERSION = "0.1.1"
2
2
 
3
3
  from .utils import *
4
4
  from .copy import copy_to_upload_server
@@ -42,6 +42,18 @@ def segmentation_tab(schema=None):
42
42
  df['parameter_set_num'] = df['parameter_set_num'].astype(str)
43
43
  return df
44
44
 
45
+ @st.cache_data
46
+ def get_all_sessions():
47
+ rows = (schema.CellSegmentation * schema.Session * schema.CellSegmentationParams).fetch(
48
+ 'subject_name', 'session_name', 'dataset_name', 'parameter_set_num',
49
+ 'algorithm_name', 'algorithm_version', 'n_rois', 'session_datetime',
50
+ as_dict=True)
51
+ if not rows:
52
+ return pd.DataFrame()
53
+ df = pd.DataFrame(rows)
54
+ df['parameter_set_num'] = df['parameter_set_num'].astype(str)
55
+ return df.sort_values(['subject_name', 'session_datetime']).reset_index(drop=True)
56
+
45
57
  @st.cache_data
46
58
  def get_sessions(subject_name):
47
59
  rows = (schema.CellSegmentation * schema.Session * schema.CellSegmentationParams
@@ -70,6 +82,42 @@ def segmentation_tab(schema=None):
70
82
  del r['proj_im']
71
83
  return rows
72
84
 
85
+ @st.cache_data
86
+ def get_roi_color_proj(subject_name, session_name, dataset_name, parameter_set_num,
87
+ sel_method, selected_only):
88
+ from labdata.stacks import roi_color_projection
89
+ key = dict(subject_name=subject_name, session_name=session_name,
90
+ dataset_name=dataset_name, parameter_set_num=parameter_set_num)
91
+ plane_rows = (schema.CellSegmentation.Plane & key).fetch(
92
+ 'plane_num', 'dims', as_dict=True)
93
+ results = []
94
+ for plane in plane_rows:
95
+ pnum = plane['plane_num']
96
+ if plane['dims'] is None:
97
+ continue
98
+ plane_key = dict(key, plane_num=pnum)
99
+ roi_table = schema.CellSegmentation.ROI & plane_key
100
+ roi_nums = list(roi_table.fetch('roi_num'))
101
+ if not roi_nums:
102
+ continue
103
+ if selected_only:
104
+ sel_rois = set(
105
+ (schema.CellSegmentation.Selection
106
+ & dict(plane_key, selection_method=sel_method, selection=1)
107
+ ).fetch('roi_num'))
108
+ cell_indices = [i for i, rn in enumerate(roi_nums) if rn in sel_rois]
109
+ else:
110
+ cell_indices = list(range(len(roi_nums)))
111
+ if not cell_indices:
112
+ continue
113
+ all_masks = roi_table.get_roi_sparse_masks()
114
+ masks = [all_masks[i] for i in cell_indices]
115
+ rgb = roi_color_projection(masks)
116
+ h, w = rgb.shape[:2]
117
+ results.append(dict(plane_num=pnum, proj_name='roi color',
118
+ url=_to_base64(rgb), h=h, w=w))
119
+ return results
120
+
73
121
  @st.cache_data
74
122
  def get_selection_methods(subject_name, session_name, dataset_name, parameter_set_num):
75
123
  key = dict(subject_name=subject_name, session_name=session_name,
@@ -109,6 +157,18 @@ def segmentation_tab(schema=None):
109
157
  db_label=db_label))
110
158
  return centers
111
159
 
160
+ @st.cache_data
161
+ def get_frame_rate(subject_name, session_name, dataset_name):
162
+ key = dict(subject_name=subject_name, session_name=session_name,
163
+ dataset_name=dataset_name)
164
+ for table in (schema.TwoPhoton, schema.Miniscope):
165
+ try:
166
+ frame_rate, n_frames = (table & key).fetch1('frame_rate', 'n_frames')
167
+ return float(frame_rate), int(n_frames)
168
+ except Exception:
169
+ pass
170
+ return 1.0, 0
171
+
112
172
  @st.cache_data
113
173
  def get_traces(subject_name, session_name, dataset_name, parameter_set_num,
114
174
  roi_keys, trace_type):
@@ -127,7 +187,7 @@ def segmentation_tab(schema=None):
127
187
  data = (schema.CellSegmentation.RawTraces & roi_key).fetch1('f_trace')
128
188
  else:
129
189
  data = (schema.CellSegmentation.Deconvolved & roi_key).fetch1('deconv')
130
- data = np.asarray(data, dtype=float)[:10000]
190
+ data = np.asarray(data, dtype=float)
131
191
  for t, v in enumerate(data):
132
192
  traces.append({'t': t, 'value': float(v), 'roi': label})
133
193
  except Exception:
@@ -146,13 +206,8 @@ def segmentation_tab(schema=None):
146
206
 
147
207
  # sessions per subject for each parameter_set_num
148
208
  counts_df = get_session_counts()
149
- bar_param_filter = st.session_state.get('seg_bar_param_filter')
150
209
 
151
210
  if not counts_df.empty:
152
- if st.session_state.pop('seg_bar_clear_pending', False):
153
- st.session_state.pop('seg_bar_param_filter', None)
154
- bar_param_filter = None
155
-
156
211
  _colors = ['#000000', '#d62728', '#1f77b4', '#ff7f0e', '#2ca02c',
157
212
  '#9467bd', '#8c564b', '#e377c2', '#7f7f7f', '#bcbd22']
158
213
  param_sets = sorted(counts_df['parameter_set_num'].unique().tolist())
@@ -179,17 +234,60 @@ def segmentation_tab(schema=None):
179
234
  pts = (bar_event.selection or {}).get('bar_sel', [])
180
235
  if pts and pts[0].get('subject_name'):
181
236
  clicked_subject = pts[0]['subject_name']
182
- clicked_param = str(pts[0].get('parameter_set_num', ''))
183
237
  if clicked_subject in subjects:
184
238
  st.session_state['seg_subject'] = clicked_subject
185
- st.session_state['seg_bar_param_filter'] = clicked_param
186
- bar_param_filter = clicked_param
187
-
188
- if bar_param_filter:
189
- st.write(f'**Filtered by param set:** {bar_param_filter}')
190
- if st.button('Clear filter', key='seg_bar_clear'):
191
- st.session_state['seg_bar_clear_pending'] = True
192
- st.rerun()
239
+ subj_df = get_all_sessions()
240
+ subj_df = subj_df[subj_df['subject_name'] == clicked_subject]
241
+ if not subj_df.empty:
242
+ first = subj_df.sort_values('session_datetime', ascending=False).iloc[0]
243
+ st.session_state['seg_scatter_sel_key'] = (
244
+ f"{first['session_name']}|{first['dataset_name']}")
245
+
246
+ # All-sessions scatter: animals on Y, dates on X, color by parameter_set_num
247
+ all_sessions_df = get_all_sessions()
248
+ if not all_sessions_df.empty:
249
+ scatter_all_df = all_sessions_df.copy()
250
+
251
+ param_sets_all = sorted(scatter_all_df['parameter_set_num'].unique().tolist())
252
+ _colors = ['#000000', '#d62728', '#1f77b4', '#ff7f0e', '#2ca02c',
253
+ '#9467bd', '#8c564b', '#e377c2', '#7f7f7f', '#bcbd22']
254
+ all_color_scale = alt.Scale(
255
+ domain=param_sets_all,
256
+ range=[_colors[i % len(_colors)] for i in range(len(param_sets_all))])
257
+
258
+ all_scatter_click = alt.selection_point(
259
+ fields=['session_name', 'dataset_name', 'subject_name'], name='all_sess_click')
260
+ n_subjects = scatter_all_df['subject_name'].nunique()
261
+ all_scatter_chart = (
262
+ alt.Chart(scatter_all_df).mark_circle(size=80, opacity=0.7).encode(
263
+ x=alt.X('session_datetime:T', title='Date',
264
+ axis=alt.Axis(tickCount='month', format='%b %Y', labelAngle=-45)),
265
+ y=alt.Y('subject_name:N', title='Subject name', sort='ascending'),
266
+ yOffset=alt.YOffset('jitter:Q', scale=alt.Scale(range=[-12, 12])),
267
+ color=alt.Color('parameter_set_num:N', scale=all_color_scale,
268
+ title='Param set'),
269
+ opacity=alt.condition(all_scatter_click, alt.value(0.9), alt.value(0.35)),
270
+ tooltip=['subject_name:N', 'session_name:N', 'dataset_name:N',
271
+ 'parameter_set_num:N',
272
+ alt.Tooltip('session_datetime:T', format='%Y-%m-%d'),
273
+ 'n_rois:Q'],
274
+ ).transform_calculate(jitter='random() - 0.5')
275
+ .add_params(all_scatter_click)
276
+ .properties(height=max(150, 60 * n_subjects + 60))
277
+ )
278
+ all_scatter_event = st.altair_chart(
279
+ all_scatter_chart, width='stretch', on_select='rerun',
280
+ key='seg_all_scatter')
281
+
282
+ all_pts = (all_scatter_event.selection or {}).get('all_sess_click', [])
283
+ if all_pts and all_pts[0].get('subject_name'):
284
+ clicked_subject = all_pts[0]['subject_name']
285
+ clicked_sname = all_pts[0].get('session_name', '')
286
+ clicked_dname = all_pts[0].get('dataset_name', '')
287
+ if clicked_subject in subjects:
288
+ st.session_state['seg_subject'] = clicked_subject
289
+ if clicked_sname:
290
+ st.session_state['seg_scatter_sel_key'] = f"{clicked_sname}|{clicked_dname}"
193
291
 
194
292
  subject = st.selectbox('Subject', subjects, index=None, key='seg_subject')
195
293
  if not subject:
@@ -200,54 +298,21 @@ def segmentation_tab(schema=None):
200
298
  st.write('No segmentation sessions found for this subject.')
201
299
  return
202
300
 
203
- # Apply bar param filter when active
204
- if bar_param_filter is not None:
205
- filtered = sessions[
206
- sessions['parameter_set_num'].astype(str) == str(bar_param_filter)]
207
- if not filtered.empty:
208
- sessions = filtered.reset_index(drop=True)
209
-
210
- # Clear scatter sticky selection when subject changes
211
- if st.session_state.get('seg_scatter_subject') != subject:
212
- st.session_state['seg_scatter_subject'] = subject
213
- st.session_state.pop('seg_scatter_sel_key', None)
214
-
215
- _colors = ['#000000', '#d62728', '#1f77b4', '#ff7f0e', '#2ca02c',
216
- '#9467bd', '#8c564b', '#e377c2', '#7f7f7f', '#bcbd22']
217
- scatter_df = sessions.copy()
218
- scatter_df['parameter_set_num'] = scatter_df['parameter_set_num'].astype(str)
219
- param_sets_s = sorted(scatter_df['parameter_set_num'].unique().tolist())
220
- sess_color_scale = alt.Scale(
221
- domain=param_sets_s,
222
- range=[_colors[i % len(_colors)] for i in range(len(param_sets_s))])
223
-
224
- scatter_click = alt.selection_point(
225
- fields=['session_name', 'dataset_name'], name='sess_click')
226
- scatter_chart = (
227
- alt.Chart(scatter_df).mark_circle(size=120).encode(
228
- x=alt.X('session_datetime:T', title='Date'),
229
- y=alt.Y('parameter_set_num:N', title='Param set'),
230
- color=alt.Color('parameter_set_num:N', scale=sess_color_scale,
231
- title='Param set'),
232
- opacity=alt.condition(scatter_click, alt.value(1.0), alt.value(0.4)),
233
- tooltip=['session_name:N', 'dataset_name:N', 'parameter_set_num:N',
234
- alt.Tooltip('session_datetime:T', format='%Y-%m-%d'),
235
- 'n_rois:Q'],
236
- ).add_params(scatter_click).properties(height=150)
237
- )
238
- scatter_event = st.altair_chart(scatter_chart, width='stretch', on_select='rerun',
239
- key='seg_scatter_timeline')
240
301
 
241
- scatter_pts = (scatter_event.selection or {}).get('sess_click', [])
302
+ # Resolve scatter selection before rendering the table so we can pre-select the row
303
+ stored = st.session_state.get('seg_scatter_sel_key', '')
242
304
  scatter_sel_idx = None
243
- if scatter_pts and scatter_pts[0].get('session_name'):
244
- sname = scatter_pts[0]['session_name']
245
- dname = scatter_pts[0].get('dataset_name', '')
246
- matches = sessions[(sessions['session_name'] == sname) &
247
- (sessions['dataset_name'] == dname)]
305
+ if stored:
306
+ parts = stored.split('|', 1)
307
+ matches = sessions[(sessions['session_name'] == parts[0]) &
308
+ (sessions['dataset_name'] == parts[1])]
248
309
  if not matches.empty:
249
310
  scatter_sel_idx = int(matches.index[0])
250
- st.session_state['seg_scatter_sel_key'] = f"{sname}|{dname}"
311
+
312
+ # Pre-select the table row only when the scatter key is freshly set
313
+ if scatter_sel_idx is not None and stored != st.session_state.get('seg_scatter_applied_key'):
314
+ st.session_state['seg_sessions_table'] = {'selection': {'rows': [scatter_sel_idx], 'columns': []}}
315
+ st.session_state['seg_scatter_applied_key'] = stored
251
316
 
252
317
  event_sess = st.dataframe(
253
318
  sessions, hide_index=True, width='stretch',
@@ -260,14 +325,7 @@ def segmentation_tab(schema=None):
260
325
  elif scatter_sel_idx is not None:
261
326
  sel_idx_final = scatter_sel_idx
262
327
  else:
263
- stored = st.session_state.get('seg_scatter_sel_key', '')
264
- if stored:
265
- parts = stored.split('|', 1)
266
- matches = sessions[(sessions['session_name'] == parts[0]) &
267
- (sessions['dataset_name'] == parts[1])]
268
- sel_idx_final = int(matches.index[0]) if not matches.empty else None
269
- else:
270
- sel_idx_final = None
328
+ sel_idx_final = None
271
329
 
272
330
  if sel_idx_final is None:
273
331
  return
@@ -282,6 +340,7 @@ def segmentation_tab(schema=None):
282
340
  if st.session_state.get('seg_current_key') != seg_key_id:
283
341
  st.session_state['seg_current_key'] = seg_key_id
284
342
  st.session_state['seg_picked_rois'] = []
343
+ st.session_state.pop('seg_selection_editor', None)
285
344
  for k in list(st.session_state.keys()):
286
345
  if k.startswith('seg_last_click_'):
287
346
  del st.session_state[k]
@@ -291,9 +350,6 @@ def segmentation_tab(schema=None):
291
350
  with st.spinner('Loading projections…'):
292
351
  projections = get_projections(
293
352
  subject, row['session_name'], row['dataset_name'], param_set)
294
- if not projections:
295
- st.write('No projections found for this session.')
296
- return
297
353
 
298
354
  # ROI display controls
299
355
  methods = get_selection_methods(
@@ -302,14 +358,14 @@ def segmentation_tab(schema=None):
302
358
  if 'seg_sel_method_pending' in st.session_state:
303
359
  st.session_state['seg_sel_method'] = st.session_state.pop('seg_sel_method_pending')
304
360
 
305
- cA, cB, cC = st.columns([1, 1, 2])
306
- show_rois = cA.toggle('Show ROIs', value=True, key='seg_show_rois')
307
- show_mode = cB.radio('ROIs to show', ['All', 'Selected'], horizontal=True,
308
- key='seg_show_mode', label_visibility='collapsed',
309
- disabled=not show_rois)
310
- sel_method = cC.selectbox('Selection method', methods, key='seg_sel_method',
311
- label_visibility='collapsed',
312
- disabled=not show_rois)
361
+ cA, cB, cC, cD = st.columns([1, 1, 1, 2])
362
+ show_rois = cA.toggle('Show ROI centers', value=True, key='seg_show_rois')
363
+ show_mode = cB.radio('ROIs to show', ['All', 'Selected'], horizontal=True,
364
+ key='seg_show_mode', label_visibility='collapsed')
365
+ show_traces = cC.toggle('Show traces', value=True, key='seg_show_traces')
366
+ sel_method = cD.selectbox('Selection method', methods, key='seg_sel_method',
367
+ label_visibility='collapsed',
368
+ disabled=not show_rois)
313
369
 
314
370
  # Duplicate / delete selection methods
315
371
  _, _, dC = st.columns([1, 1, 2])
@@ -335,6 +391,7 @@ def segmentation_tab(schema=None):
335
391
  [{**r, 'selection_method': nm} for r in src_rows])
336
392
  get_selection_methods.clear()
337
393
  get_roi_data.clear()
394
+ get_roi_color_proj.clear()
338
395
  st.session_state['seg_sel_method_pending'] = nm
339
396
  st.rerun()
340
397
  except Exception as exc:
@@ -355,6 +412,7 @@ def segmentation_tab(schema=None):
355
412
  schema.dj.config['safemode'] = True # re-enable
356
413
  get_selection_methods.clear()
357
414
  get_roi_data.clear()
415
+ get_roi_color_proj.clear()
358
416
  st.session_state.pop('seg_del_confirm', None)
359
417
  st.rerun()
360
418
  except Exception as exc:
@@ -367,6 +425,13 @@ def segmentation_tab(schema=None):
367
425
  centers = get_roi_data(
368
426
  subject, row['session_name'], row['dataset_name'], param_set, sel_method)
369
427
 
428
+ with st.spinner('Loading ROI color projection…'):
429
+ roi_color_projs = get_roi_color_proj(
430
+ subject, row['session_name'], row['dataset_name'], param_set, sel_method,
431
+ selected_only=(show_mode == 'Selected'))
432
+
433
+ all_projections = roi_color_projs + list(projections)
434
+
370
435
  picked: list = st.session_state['seg_picked_rois']
371
436
 
372
437
  color_domain = ['cell', 'not cell', 'unclassified', 'picked']
@@ -374,13 +439,18 @@ def segmentation_tab(schema=None):
374
439
  size_domain = ['cell', 'not cell', 'unclassified', 'picked']
375
440
  size_range = [80, 80, 60, 160]
376
441
 
377
- # projection tabs
378
- proj_names = sorted(set(r['proj_name'] for r in projections))
442
+ # projection tabs — 'roi color' first (default), then others sorted
443
+ _other_names = sorted(set(r['proj_name'] for r in projections))
444
+ proj_names = (['roi color'] if roi_color_projs else []) + _other_names
445
+ if not proj_names:
446
+ st.write('No projections found for this session.')
447
+ return
448
+
379
449
  proj_tabs = st.tabs(proj_names)
380
450
 
381
451
  for tab, pname in zip(proj_tabs, proj_names):
382
452
  with tab:
383
- plane_imgs = sorted([r for r in projections if r['proj_name'] == pname],
453
+ plane_imgs = sorted([r for r in all_projections if r['proj_name'] == pname],
384
454
  key=lambda r: r['plane_num'])
385
455
  cols = st.columns(max(1, len(plane_imgs)))
386
456
  for col, r in zip(cols, plane_imgs):
@@ -444,9 +514,10 @@ def segmentation_tab(schema=None):
444
514
  else:
445
515
  display = img_layer
446
516
 
517
+ display = display.properties(width=350, height=350, title=f'Plane {pnum}').interactive()
518
+
447
519
  event = col.altair_chart(
448
- display.properties(width=350, height=350,
449
- title=f'Plane {pnum}').interactive(),
520
+ display,
450
521
  on_select='rerun',
451
522
  key=f'seg_scatter_{pname}_{pnum}',
452
523
  width='content',
@@ -464,23 +535,26 @@ def segmentation_tab(schema=None):
464
535
  entry = {'plane_num': int(pnum_click), 'roi_num': int(roi_num)}
465
536
  if entry in picked:
466
537
  picked.remove(entry)
467
- elif len(picked) < MAX_PICKED:
538
+ else:
468
539
  picked.append(entry)
469
540
  st.session_state['seg_picked_rois'] = picked
470
541
  get_traces.clear()
471
542
 
472
- # traces
543
+ # traces
473
544
  if not picked:
474
- st.caption(f'Click ROI dots on the projection to select traces (up to {MAX_PICKED}).')
545
+ st.caption('Click ROI dots on the projection to select traces.')
475
546
  return
476
547
 
477
548
  st.divider()
478
549
 
550
+ plot_picked = picked[-MAX_PICKED:]
551
+
479
552
  hA, hB = st.columns([3, 1])
480
- hA.write(f'**Picked ROIs** ({len(picked)}/{MAX_PICKED}): '
481
- + ' '.join(f'plane {p["plane_num"]}, roi {p["roi_num"]}' for p in picked))
553
+ hA.write(f'**ROIs** (plotting last {len(plot_picked)} of {len(picked)} selected): '
554
+ + ' '.join(f'plane {p["plane_num"]}, roi {p["roi_num"]}' for p in plot_picked))
482
555
  if hB.button('Clear all', key='seg_clear_picked'):
483
556
  st.session_state['seg_picked_rois'] = []
557
+ st.session_state.pop('seg_selection_editor', None)
484
558
  get_traces.clear()
485
559
  st.rerun()
486
560
 
@@ -493,39 +567,60 @@ def segmentation_tab(schema=None):
493
567
  st.rerun()
494
568
 
495
569
  trace_type = st.radio('Trace', ['dff', 'F', 'deconv'],
496
- horizontal=True, key='seg_trace_type')
570
+ horizontal=True, key='seg_trace_type',
571
+ disabled=not show_traces)
497
572
 
498
- roi_keys = tuple((p['plane_num'], p['roi_num']) for p in picked)
573
+ roi_keys = tuple((p['plane_num'], p['roi_num']) for p in plot_picked)
499
574
 
500
- with st.spinner('Loading traces…'):
501
- traces_df = get_traces(subject, row['session_name'], row['dataset_name'],
502
- param_set, roi_keys, trace_type)
575
+ if show_traces:
576
+ with st.spinner('Loading traces…'):
577
+ traces_df = get_traces(subject, row['session_name'], row['dataset_name'],
578
+ param_set, roi_keys, trace_type)
503
579
 
504
- if traces_df.empty:
505
- st.write(f'No `{trace_type}` traces found for the selected ROIs.')
506
- else:
507
- zoom_x = alt.selection_interval(bind='scales', encodings=['x'])
508
- trace_chart = (
509
- alt.Chart(traces_df)
510
- .mark_line(strokeWidth=1.2)
511
- .encode(
512
- x=alt.X('t:Q', title='Frame'),
513
- y=alt.Y('value:Q', title=trace_type),
514
- color=alt.Color('roi:N', title='ROI',
515
- scale=alt.Scale(
516
- range=['#000000', '#d62728', '#1f77b4', '#ff7f0e',
517
- '#2ca02c', '#9467bd', '#8c564b', '#e377c2',
518
- '#7f7f7f', '#bcbd22'])),
519
- tooltip=['roi:N', alt.Tooltip('t:Q', title='Frame'),
520
- alt.Tooltip('value:Q', format='.4f')],
580
+ if traces_df.empty:
581
+ st.write(f'No `{trace_type}` traces found for the selected ROIs.')
582
+ else:
583
+ frame_rate, _ = get_frame_rate(subject, row['session_name'], row['dataset_name'])
584
+ traces_df = traces_df.copy()
585
+ traces_df['t_sec'] = traces_df['t'] / frame_rate
586
+
587
+ window_s = 300.0
588
+ total_s = float(traces_df['t_sec'].max())
589
+ if total_s > window_s:
590
+ max_start = round(total_s - window_s, 2)
591
+ _lc, _sc = st.columns([1, 11])
592
+ _lc.markdown('**Time (s)**')
593
+ t_start = _sc.slider('Time (s)', 0.0, max_start, 0.0, step=30.0,
594
+ key='seg_trace_window', label_visibility='collapsed')
595
+ else:
596
+ t_start = 0.0
597
+ t_end = t_start + window_s
598
+
599
+ view_df = traces_df[
600
+ (traces_df['t_sec'] >= t_start) & (traces_df['t_sec'] <= t_end)]
601
+ trace_chart = (
602
+ alt.Chart(view_df)
603
+ .mark_line(strokeWidth=1.2)
604
+ .encode(
605
+ x=alt.X('t_sec:Q', title='Time (s)',
606
+ scale=alt.Scale(domain=[t_start, t_end]),
607
+ axis=alt.Axis(tickCount=5, tickSize=5, grid=False)),
608
+ y=alt.Y('value:Q', title=trace_type,
609
+ axis=alt.Axis(tickCount=5, tickSize=5, grid=False)),
610
+ color=alt.Color('roi:N', title='ROI',
611
+ scale=alt.Scale(
612
+ range=['#000000', '#d62728', '#1f77b4', '#ff7f0e',
613
+ '#2ca02c', '#9467bd', '#8c564b', '#e377c2',
614
+ '#7f7f7f', '#bcbd22'])),
615
+ tooltip=['roi:N', alt.Tooltip('t_sec:Q', title='Time (s)', format='.2f'),
616
+ alt.Tooltip('value:Q', format='.4f')],
617
+ )
618
+ .properties(height=220)
521
619
  )
522
- .properties(height=220)
523
- .add_params(zoom_x)
524
- )
525
- st.altair_chart(trace_chart, width='stretch')
620
+ st.altair_chart(trace_chart, width='stretch')
526
621
 
527
622
  # classification editor
528
- st.write('**Classification (is\_cell)**')
623
+ st.write('**ROI classification**')
529
624
  sel_rows = []
530
625
  for p in picked:
531
626
  roi_key = dict(seg_key, plane_num=p['plane_num'], roi_num=p['roi_num'])
@@ -558,7 +653,8 @@ def segmentation_tab(schema=None):
558
653
  except Exception as exc:
559
654
  st.error(f'Error updating plane {erow["plane"]} roi {erow["roi"]}: {exc}')
560
655
  if changed:
561
- get_roi_data.clear()
562
656
  st.success(f'Updated {changed} ROI classification(s).')
657
+ get_roi_data.clear()
658
+ get_roi_color_proj.clear()
563
659
  st.rerun()
564
660
 
@@ -0,0 +1,207 @@
1
+ import pandas as pd
2
+ import streamlit as st
3
+ import numpy as np
4
+ import altair as alt
5
+
6
+
7
+ colors = ['#1f77b4',
8
+ '#ff7f0e',
9
+ '#d62728',
10
+ '#2ca02c',
11
+ '#9467bd',
12
+ '#8c564b',
13
+ '#e377c2',
14
+ '#7f7f7f',
15
+ '#bcbd22']
16
+
17
+ def sorting_tab(schema=None):
18
+
19
+ @st.cache_data
20
+ def get_all_sorting_sessions():
21
+ rows = (schema.SpikeSorting * schema.Session).fetch(
22
+ 'subject_name', 'session_name', 'dataset_name',
23
+ 'parameter_set_num', 'session_datetime', as_dict=True)
24
+ if not rows:
25
+ return pd.DataFrame()
26
+ df = pd.DataFrame(rows).drop_duplicates(
27
+ subset=['subject_name', 'session_name', 'dataset_name', 'parameter_set_num'])
28
+ df['parameter_set_num'] = df['parameter_set_num'].astype(str)
29
+ return df.sort_values(['subject_name', 'session_datetime']).reset_index(drop=True)
30
+
31
+ @st.cache_data
32
+ def get_session_counts():
33
+ rows = (schema.Subject * schema.SpikeSortingParams).aggr(
34
+ schema.SpikeSorting,
35
+ n_sessions='count(session_name)'
36
+ ).fetch(as_dict=True)
37
+ if not rows:
38
+ return pd.DataFrame()
39
+ df = pd.DataFrame(rows)
40
+ df = df[df['n_sessions'] > 0].reset_index(drop=True)
41
+ df['parameter_set_num'] = df['parameter_set_num'].astype(str)
42
+ return df
43
+
44
+ pars = schema.SpikeSortingParams().fetch(format='frame')[['algorithm_name', 'parameters_dict']]
45
+ pars.insert(0, 'Select', False)
46
+ edited_df = st.data_editor(pars.sort_index(),
47
+ hide_index=False,
48
+ disabled=['parameter_set_num',
49
+ 'algorithm_name',
50
+ 'parameters_dict'])
51
+ sessions = schema.SpikeSorting().fetch(format='frame').reset_index()
52
+ par_sets = edited_df[edited_df.Select == 1].reset_index()
53
+ from functools import partial
54
+ st.button('Update unit counts',
55
+ on_click=partial(schema.UnitCount.populate, display_progress=True))
56
+
57
+ # histogram: session counts per subject
58
+ counts_df = get_session_counts()
59
+ if not counts_df.empty:
60
+ # filter to selected param sets if any are chosen
61
+ if len(par_sets):
62
+ selected_params = par_sets.parameter_set_num.astype(str).tolist()
63
+ counts_filt = counts_df[counts_df['parameter_set_num'].isin(selected_params)]
64
+ else:
65
+ counts_filt = counts_df
66
+
67
+ if not counts_filt.empty:
68
+ param_sets_h = sorted(counts_filt['parameter_set_num'].unique().tolist())
69
+ bar_color_scale = alt.Scale(
70
+ domain=param_sets_h,
71
+ range=[colors[i % len(colors)] for i in range(len(param_sets_h))])
72
+
73
+ bar_sel = alt.selection_point(
74
+ fields=['subject_name', 'parameter_set_num'], name='bar_sel')
75
+ bar_chart = (
76
+ alt.Chart(counts_filt).mark_bar().encode(
77
+ x=alt.X('subject_name:N', sort='ascending', title='Subject'),
78
+ xOffset=alt.XOffset('parameter_set_num:N', title='Param set'),
79
+ y=alt.Y('n_sessions:Q', title='# sessions'),
80
+ color=alt.Color('parameter_set_num:N', title='Param set',
81
+ scale=bar_color_scale),
82
+ opacity=alt.condition(bar_sel, alt.value(1.0), alt.value(0.4)),
83
+ tooltip=['subject_name:N', 'parameter_set_num:N', 'n_sessions:Q'],
84
+ ).add_params(bar_sel).properties(height=200)
85
+ )
86
+ bar_event = st.altair_chart(bar_chart, width='stretch', on_select='rerun',
87
+ key='srt_sessions_bar')
88
+
89
+ bar_pts = (bar_event.selection or {}).get('bar_sel', [])
90
+ if bar_pts and bar_pts[0].get('subject_name'):
91
+ clicked_subj = bar_pts[0]['subject_name']
92
+ current = list(st.session_state.get('srt_subjects', []))
93
+ if clicked_subj not in current:
94
+ current.append(clicked_subj)
95
+ st.session_state['srt_subjects'] = current
96
+
97
+ # all-sessions scatter
98
+ all_sessions_df = get_all_sorting_sessions()
99
+ if not all_sessions_df.empty:
100
+ scatter_df = all_sessions_df.copy()
101
+ if len(par_sets):
102
+ selected_params = par_sets.parameter_set_num.astype(str).tolist()
103
+ flt = scatter_df[scatter_df['parameter_set_num'].isin(selected_params)]
104
+ if not flt.empty:
105
+ scatter_df = flt.reset_index(drop=True)
106
+
107
+ param_sets_s = sorted(scatter_df['parameter_set_num'].unique().tolist())
108
+ scat_color_scale = alt.Scale(
109
+ domain=param_sets_s,
110
+ range=[colors[i % len(colors)] for i in range(len(param_sets_s))])
111
+
112
+ n_subjects = scatter_df['subject_name'].nunique()
113
+ scat_sel = alt.selection_point(
114
+ fields=['subject_name', 'session_name', 'dataset_name'], name='srt_scat_sel')
115
+ scat_chart = (
116
+ alt.Chart(scatter_df).mark_circle(size=80, opacity=0.7).encode(
117
+ x=alt.X('session_datetime:T', title='Date',
118
+ axis=alt.Axis(tickCount='month', format='%b %Y', labelAngle=-45)),
119
+ y=alt.Y('subject_name:N', title='Subject name', sort='ascending'),
120
+ yOffset=alt.YOffset('jitter:Q', scale=alt.Scale(range=[-12, 12])),
121
+ color=alt.Color('parameter_set_num:N', scale=scat_color_scale,
122
+ title='Param set'),
123
+ opacity=alt.condition(scat_sel, alt.value(0.9), alt.value(0.35)),
124
+ tooltip=['subject_name:N', 'session_name:N', 'parameter_set_num:N',
125
+ alt.Tooltip('session_datetime:T', format='%Y-%m-%d')],
126
+ ).transform_calculate(jitter='random() - 0.5')
127
+ .add_params(scat_sel)
128
+ .properties(height=max(150, 60 * n_subjects + 60))
129
+ )
130
+ scat_event = st.altair_chart(scat_chart, width='stretch', on_select='rerun',
131
+ key='srt_sessions_scatter')
132
+
133
+ scat_pts = (scat_event.selection or {}).get('srt_scat_sel', [])
134
+ if scat_pts and scat_pts[0].get('subject_name'):
135
+ clicked_subj = scat_pts[0]['subject_name']
136
+ current = list(st.session_state.get('srt_subjects', []))
137
+ if clicked_subj not in current:
138
+ current.append(clicked_subj)
139
+ st.session_state['srt_subjects'] = current
140
+
141
+ # subject/unit-count
142
+ selected_subjects = []
143
+ if len(par_sets):
144
+ sess = []
145
+ for p in par_sets.parameter_set_num.values:
146
+ sess.append(sessions[sessions.parameter_set_num == p])
147
+ sess = pd.concat(sess)
148
+ subjects = np.unique(sess.subject_name.values)
149
+
150
+ sesdict = []
151
+ for p in par_sets.parameter_set_num.values:
152
+ for subj in subjects:
153
+ sub = sessions[(sessions.subject_name == subj) &
154
+ (sessions.parameter_set_num == p)]
155
+ sesdict.append({'subject_name': subj,
156
+ 'number_of_sessions': len(sub),
157
+ 'parameter_set_num': p,
158
+ 'number_of_probes': len(np.unique(sub.probe_num.values))})
159
+ sesdict = pd.DataFrame(sesdict)
160
+ if not len(sesdict):
161
+ return
162
+
163
+ selected_subjects = st.multiselect('Select subject:', subjects,
164
+ key='srt_subjects')
165
+
166
+ criteria_ids = schema.UnitCountCriteria().fetch(as_dict=True)
167
+ if not len(criteria_ids):
168
+ st.write('Add a UnitCountCriteria to the table: example '
169
+ '"isi_contamination < 0.1 & amplitude_cutoff < 0.1 & '
170
+ 'spike_duration > 0.1 & spike_amplitude > 50 & presence_ratio > 0.6"')
171
+ return
172
+ selection = st.selectbox('Unit count criteria:',
173
+ [f'[{c["unit_criteria_id"]}] -> {c["sua_criteria"]}'
174
+ for c in criteria_ids])
175
+ if '->' in selection:
176
+ unit_criteria = int(int(selection.split('->')[0].strip(' []')))
177
+ else:
178
+ return
179
+
180
+ @st.cache_data
181
+ def get_unit_counts(s, p, unit_criteria):
182
+ return pd.DataFrame(
183
+ schema.UnitCount() * schema.Session() * schema.EphysRecording.ProbeSetting()
184
+ & dict(subject_name=s, parameter_set_num=p, unit_criteria_id=unit_criteria))
185
+
186
+ if len(selected_subjects):
187
+ units = []
188
+ for p in par_sets.parameter_set_num.values:
189
+ for s in selected_subjects:
190
+ units.append(get_unit_counts(s, p, unit_criteria))
191
+ if not len(units):
192
+ return
193
+ unit_counts = pd.concat(units).set_index('subject_name')
194
+ N = len(colors)
195
+ cc = np.array([colors[0] for c in np.arange(len(unit_counts.probe_id.values))])
196
+ for i, p in enumerate(np.unique(unit_counts.probe_id)):
197
+ idx = np.where(unit_counts.probe_id.values == p)[0]
198
+ cc[idx] = colors[np.mod(i, N)]
199
+ unit_counts['probe_color'] = cc
200
+ st.scatter_chart(unit_counts,
201
+ x='session_datetime', y='all',
202
+ x_label='Session date', y_label='Number of units',
203
+ color='probe_color', size=30)
204
+ st.scatter_chart(unit_counts,
205
+ x='session_datetime', y='sua',
206
+ x_label='Session date', y_label='Number of single units',
207
+ color='probe_color', size=30)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: labdata
3
- Version: 0.1.0
3
+ Version: 0.1.1
4
4
  Summary: Package to manage data in experimental neuroscience labs
5
5
  Author-email: Joao Couto <jpcouto@gmail.com>
6
6
  Project-URL: Homepage, https://github.com/jcouto/labdata
@@ -1,102 +0,0 @@
1
- import pandas as pd
2
- import streamlit as st
3
- import numpy as np
4
-
5
- colors = ['#1f77b4',
6
- '#ff7f0e',
7
- '#d62728',
8
- '#2ca02c',
9
- '#9467bd',
10
- '#8c564b',
11
- '#e377c2',
12
- '#7f7f7f',
13
- '#bcbd22']
14
-
15
- def sorting_tab(schema = None):
16
- pars = schema.SpikeSortingParams().fetch(format='frame')[['algorithm_name','parameters_dict']]
17
- pars.insert(0, "Select", False)
18
- edited_df = st.data_editor(pars.sort_index(),
19
- hide_index=False,
20
- disabled = ['parameter_set_num',
21
- 'algorithm_name',
22
- 'parameters_dict'])
23
- sessions = schema.SpikeSorting().fetch(format = 'frame').reset_index()
24
- par_sets = edited_df[edited_df.Select == 1].reset_index()
25
- from functools import partial
26
- st.button('Update unit counts',on_click = partial(schema.UnitCount.populate,display_progress = True))
27
-
28
- selected_subjects = []
29
- if len(par_sets):
30
- sess = []
31
- for p in par_sets.parameter_set_num.values:
32
- sess.append(sessions[sessions.parameter_set_num == p])
33
- sess = pd.concat(sess)
34
- subjects = np.unique(sess.subject_name.values)
35
- sesdict = []
36
- for p in par_sets.parameter_set_num.values:
37
- for subj in subjects:
38
- sub = sessions[(sessions.subject_name == subj) & (sessions.parameter_set_num == p)]
39
- sesdict.append({'subject_name' : subj,
40
- 'number_of_sessions': len(sub),
41
- 'parameter_set_num': p,
42
- 'number_of_probes':len(np.unique(sub.probe_num.values))})
43
- sesdict = pd.DataFrame(sesdict)
44
- if not len(sesdict):
45
- return
46
- # st.bar_chart(sesdict.set_index('subject_name'),y='number_of_sessions')
47
- import altair as alt
48
- ch = (alt.Chart(sesdict).mark_bar().encode(
49
- y = 'subject_name:N', #alt.Y(,type = 'nominal'),
50
- x = 'number_of_sessions:Q',#alt.X(,type = 'quantitative'),
51
- color='parameter_set_num:N').configure_axis(labelLimit=3000))#alt.Color('color',type = 'nominal')))
52
- st.altair_chart(ch,width="stretch")
53
- selected_subjects = st.multiselect('Select subject:',subjects)
54
-
55
-
56
-
57
- criteria_ids = schema.UnitCountCriteria().fetch(as_dict = True)
58
- if not len(criteria_ids):
59
- st.write('Add a UnitCountCriteria to the table: example "isi_contamination < 0.1 & amplitude_cutoff < 0.1 & spike_duration > 0.1 & spike_amplitude > 50 & presence_ratio > 0.6"')
60
- return
61
- selection = st.selectbox('Unit count criteria:',
62
- [f'[{c["unit_criteria_id"]}] -> {c["sua_criteria"]}' for c in criteria_ids])
63
- if '->' in selection:
64
- unit_criteria = int(int(selection.split('->')[0].strip(' []')))
65
- else:
66
- return
67
- @st.cache_data
68
- def get_unit_counts(s,p,unit_criteria):
69
- return pd.DataFrame(schema.UnitCount()*schema.Session()*schema.EphysRecording.ProbeSetting() & dict(
70
- subject_name = s,
71
- parameter_set_num = p,
72
- unit_criteria_id = unit_criteria))
73
-
74
- if len(selected_subjects):
75
- units = []
76
- for p in par_sets.parameter_set_num.values:
77
- for s in selected_subjects:
78
- units.append(get_unit_counts(s,p,unit_criteria))
79
- if not len(units):
80
- return
81
- unit_counts = pd.concat(units).set_index('subject_name')
82
- # make the probe color
83
- N = len(colors)
84
- cc = np.array([colors[0] for c in np.arange(len(unit_counts.probe_id.values))])
85
- for i,p in enumerate(np.unique(unit_counts.probe_id)):
86
- idx = np.where(unit_counts.probe_id.values == p)[0]
87
- cc[idx] = colors[np.mod(i,N)]
88
- unit_counts['probe_color'] = cc
89
- st.scatter_chart(unit_counts,
90
- x = 'session_datetime',
91
- y = 'all',
92
- x_label = 'Session date',
93
- y_label = 'Number of units',
94
- color = 'probe_color',
95
- size = 30)
96
- st.scatter_chart(unit_counts,
97
- x = 'session_datetime',
98
- y = 'sua',
99
- x_label = 'Session date',
100
- y_label = 'Number of single units',
101
- color = 'probe_color',
102
- size = 30)
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes