labdata 0.1.2__tar.gz → 0.1.4__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 (55) hide show
  1. {labdata-0.1.2 → labdata-0.1.4}/PKG-INFO +1 -1
  2. {labdata-0.1.2 → labdata-0.1.4}/labdata/__init__.py +1 -1
  3. {labdata-0.1.2 → labdata-0.1.4}/labdata/compute/caiman.py +7 -5
  4. {labdata-0.1.2 → labdata-0.1.4}/labdata/compute/suite2p.py +2 -1
  5. {labdata-0.1.2 → labdata-0.1.4}/labdata/dashboard/segmentation.py +144 -82
  6. {labdata-0.1.2 → labdata-0.1.4}/labdata.egg-info/PKG-INFO +1 -1
  7. {labdata-0.1.2 → labdata-0.1.4}/LICENSE +0 -0
  8. {labdata-0.1.2 → labdata-0.1.4}/README.md +0 -0
  9. {labdata-0.1.2 → labdata-0.1.4}/labdata/cli.py +0 -0
  10. {labdata-0.1.2 → labdata-0.1.4}/labdata/compute/__init__.py +0 -0
  11. {labdata-0.1.2 → labdata-0.1.4}/labdata/compute/ec2.py +0 -0
  12. {labdata-0.1.2 → labdata-0.1.4}/labdata/compute/ephys.py +0 -0
  13. {labdata-0.1.2 → labdata-0.1.4}/labdata/compute/pose.py +0 -0
  14. {labdata-0.1.2 → labdata-0.1.4}/labdata/compute/schedulers.py +0 -0
  15. {labdata-0.1.2 → labdata-0.1.4}/labdata/compute/singularity.py +0 -0
  16. {labdata-0.1.2 → labdata-0.1.4}/labdata/compute/utils.py +0 -0
  17. {labdata-0.1.2 → labdata-0.1.4}/labdata/copy.py +0 -0
  18. {labdata-0.1.2 → labdata-0.1.4}/labdata/dashboard/__init__.py +0 -0
  19. {labdata-0.1.2 → labdata-0.1.4}/labdata/dashboard/compute.py +0 -0
  20. {labdata-0.1.2 → labdata-0.1.4}/labdata/dashboard/console.py +0 -0
  21. {labdata-0.1.2 → labdata-0.1.4}/labdata/dashboard/decisiontask.py +0 -0
  22. {labdata-0.1.2 → labdata-0.1.4}/labdata/dashboard/explorer/__init__.py +0 -0
  23. {labdata-0.1.2 → labdata-0.1.4}/labdata/dashboard/explorer/cellsegmentation.py +0 -0
  24. {labdata-0.1.2 → labdata-0.1.4}/labdata/dashboard/explorer/spikesorting.py +0 -0
  25. {labdata-0.1.2 → labdata-0.1.4}/labdata/dashboard/index.py +0 -0
  26. {labdata-0.1.2 → labdata-0.1.4}/labdata/dashboard/notes.py +0 -0
  27. {labdata-0.1.2 → labdata-0.1.4}/labdata/dashboard/procedures.py +0 -0
  28. {labdata-0.1.2 → labdata-0.1.4}/labdata/dashboard/sorting.py +0 -0
  29. {labdata-0.1.2 → labdata-0.1.4}/labdata/dashboard/video.py +0 -0
  30. {labdata-0.1.2 → labdata-0.1.4}/labdata/export.py +0 -0
  31. {labdata-0.1.2 → labdata-0.1.4}/labdata/rules/__init__.py +0 -0
  32. {labdata-0.1.2 → labdata-0.1.4}/labdata/rules/ephys.py +0 -0
  33. {labdata-0.1.2 → labdata-0.1.4}/labdata/rules/imaging.py +0 -0
  34. {labdata-0.1.2 → labdata-0.1.4}/labdata/rules/utils.py +0 -0
  35. {labdata-0.1.2 → labdata-0.1.4}/labdata/s3.py +0 -0
  36. {labdata-0.1.2 → labdata-0.1.4}/labdata/schema/__init__.py +0 -0
  37. {labdata-0.1.2 → labdata-0.1.4}/labdata/schema/ephys.py +0 -0
  38. {labdata-0.1.2 → labdata-0.1.4}/labdata/schema/general.py +0 -0
  39. {labdata-0.1.2 → labdata-0.1.4}/labdata/schema/histology.py +0 -0
  40. {labdata-0.1.2 → labdata-0.1.4}/labdata/schema/onephoton.py +0 -0
  41. {labdata-0.1.2 → labdata-0.1.4}/labdata/schema/procedures.py +0 -0
  42. {labdata-0.1.2 → labdata-0.1.4}/labdata/schema/tasks.py +0 -0
  43. {labdata-0.1.2 → labdata-0.1.4}/labdata/schema/twophoton.py +0 -0
  44. {labdata-0.1.2 → labdata-0.1.4}/labdata/schema/utils.py +0 -0
  45. {labdata-0.1.2 → labdata-0.1.4}/labdata/schema/video.py +0 -0
  46. {labdata-0.1.2 → labdata-0.1.4}/labdata/stacks.py +0 -0
  47. {labdata-0.1.2 → labdata-0.1.4}/labdata/utils.py +0 -0
  48. {labdata-0.1.2 → labdata-0.1.4}/labdata/widgets.py +0 -0
  49. {labdata-0.1.2 → labdata-0.1.4}/labdata.egg-info/SOURCES.txt +0 -0
  50. {labdata-0.1.2 → labdata-0.1.4}/labdata.egg-info/dependency_links.txt +0 -0
  51. {labdata-0.1.2 → labdata-0.1.4}/labdata.egg-info/entry_points.txt +0 -0
  52. {labdata-0.1.2 → labdata-0.1.4}/labdata.egg-info/requires.txt +0 -0
  53. {labdata-0.1.2 → labdata-0.1.4}/labdata.egg-info/top_level.txt +0 -0
  54. {labdata-0.1.2 → labdata-0.1.4}/pyproject.toml +0 -0
  55. {labdata-0.1.2 → labdata-0.1.4}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: labdata
3
- Version: 0.1.2
3
+ Version: 0.1.4
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.2"
1
+ VERSION = "0.1.4"
2
2
 
3
3
  from .utils import *
4
4
  from .copy import copy_to_upload_server
@@ -22,7 +22,7 @@ class CaimanCompute(Suite2pCompute):
22
22
  # default parameters
23
23
  self.parameters = dict(algorithm_name = 'caiman')
24
24
  # will only store these in CellSegmentationParams
25
- self.parameter_keys = ['pw_rigid','p','gSig','gSig_filt','max_shift','gSiz','merge_thr','rf','stride','tsub',
25
+ self.parameter_keys = ['pw_rigid','p','gSig','gSig_filt','max_shifts','gSiz','merge_thr','rf','stride','tsub',
26
26
  'ssub','nb','min_corr','min_pnr','ssub_B','ring_size_factor',
27
27
  'min_SNR', 'rval_thr', 'use_cnn', 'detrendWin','quantileMin','denoise_dff']
28
28
 
@@ -50,7 +50,7 @@ class CaimanCompute(Suite2pCompute):
50
50
  parser.add_argument('-m','--pwrigid',
51
51
  action='store_true', default=False,
52
52
  help = "Piecewise-rigid registration")
53
- parser.add_argument('--max-allowed-shift',
53
+ parser.add_argument('--max-allowed-shifts',
54
54
  action='store', default=[20,20], type = int, nargs = 2,
55
55
  help = "maximum allowed shift for motion correction")
56
56
  parser.add_argument('-p',
@@ -122,7 +122,6 @@ class CaimanCompute(Suite2pCompute):
122
122
  max_shifts = args.max_allowed_shifts,
123
123
  p = int(args.p),
124
124
  gSig = [int(a) for a in args.gsig],
125
- gSig_filt = [int(a) for a in args.gsig_filt],
126
125
  gSiz = [int(a) for a in 2*np.array(args.gsig) + 1],
127
126
  merge_thr = float(args.merge_thr),
128
127
  rf = int(args.rf),
@@ -140,7 +139,10 @@ class CaimanCompute(Suite2pCompute):
140
139
  detrendWin = int(args.detrend_win),
141
140
  denoise_dff = bool(args.denoise_dff),
142
141
  quantileMin = float(args.quantile_min),
143
- roi = args.roi)
142
+ roi = args.roi)
143
+ if not args.gsig_filt is None:
144
+ params['gSig_filt'] = [int(a) for a in args.gsig_filt],
145
+
144
146
  self.parameters = params
145
147
 
146
148
  def _compute(self):
@@ -338,7 +340,7 @@ class CaimanCompute(Suite2pCompute):
338
340
  os.unlink(f)
339
341
  else:
340
342
  print(f'[{self.name} job] Kept the temporary folder {temporary_folder}.')
341
- CellSegmentationMetrics().populate(planekey, display_progress = True)
343
+ self.schema.CellSegmentationMetrics().populate(dkey, display_progress = True)
342
344
 
343
345
  def setup_cluster(n_cpus,cluster = None):
344
346
  from caiman import stop_server, cluster
@@ -384,7 +384,8 @@ class Suite2pCompute(BaseCompute):
384
384
  os.unlink(f)
385
385
  else:
386
386
  print(f'[{self.name} job] Kept the temporary folder {savedir}.')
387
-
387
+ for key in seskeys:
388
+ self.schema.CellSegmentationMetrics.populate(key,display_progress = True, processes = DEFAULT_N_JOBS)
388
389
 
389
390
  def extract_dff_fissa(stats,binary,dims,fs, batch_size,tau):
390
391
  '''
@@ -4,8 +4,7 @@ import pandas as pd
4
4
  import streamlit as st
5
5
  import numpy as np
6
6
 
7
- MAX_PICKED = 5
8
-
7
+ MAX_PLOT = 5
9
8
 
10
9
  def _normalize_image(img):
11
10
  img = np.asarray(img, dtype=float)
@@ -84,7 +83,7 @@ def segmentation_tab(schema=None):
84
83
 
85
84
  @st.cache_data
86
85
  def get_roi_color_proj(subject_name, session_name, dataset_name, parameter_set_num,
87
- sel_method, selected_only):
86
+ sel_method, sel_label):
88
87
  from labdata.stacks import roi_color_projection
89
88
  key = dict(subject_name=subject_name, session_name=session_name,
90
89
  dataset_name=dataset_name, parameter_set_num=parameter_set_num)
@@ -100,10 +99,10 @@ def segmentation_tab(schema=None):
100
99
  roi_nums = list(roi_table.fetch('roi_num'))
101
100
  if not roi_nums:
102
101
  continue
103
- if selected_only:
102
+ if sel_label is not None:
104
103
  sel_rois = set(
105
104
  (schema.CellSegmentation.Selection
106
- & dict(plane_key, selection_method=sel_method, selection=1)
105
+ & dict(plane_key, selection_method=sel_method, selection=sel_label)
107
106
  ).fetch('roi_num'))
108
107
  cell_indices = [i for i, rn in enumerate(roi_nums) if rn in sel_rois]
109
108
  else:
@@ -126,6 +125,16 @@ def segmentation_tab(schema=None):
126
125
  (schema.CellSegmentation.Selection & key).fetch('selection_method')))
127
126
  return methods or ['auto']
128
127
 
128
+ @st.cache_data
129
+ def get_selection_labels(subject_name, session_name, dataset_name, parameter_set_num,
130
+ sel_method):
131
+ key = dict(subject_name=subject_name, session_name=session_name,
132
+ dataset_name=dataset_name, parameter_set_num=parameter_set_num,
133
+ selection_method=sel_method)
134
+ vals = sorted(set(int(v) for v in
135
+ (schema.CellSegmentation.Selection & key).fetch('selection')))
136
+ return vals
137
+
129
138
  @st.cache_data
130
139
  def get_roi_data(subject_name, session_name, dataset_name, parameter_set_num, sel_method):
131
140
  key = dict(subject_name=subject_name, session_name=session_name,
@@ -154,7 +163,8 @@ def segmentation_tab(schema=None):
154
163
  centers.append(dict(roi_num=r['roi_num'], plane_num=pnum,
155
164
  cy=float(np.median(rows_idx)),
156
165
  cx=float(np.median(cols_idx)),
157
- db_label=db_label))
166
+ db_label=db_label,
167
+ sel_value=is_cell))
158
168
  return centers
159
169
 
160
170
  @st.cache_data
@@ -441,17 +451,22 @@ def segmentation_tab(schema=None):
441
451
  if 'seg_sel_method_pending' in st.session_state:
442
452
  st.session_state['seg_sel_method'] = st.session_state.pop('seg_sel_method_pending')
443
453
 
444
- cA, cB, cC, cD = st.columns([1, 1, 1, 2])
454
+ cA, cB, cC = st.columns([1, 1, 2])
445
455
  show_rois = cA.toggle('Show ROI centers', value=True, key='seg_show_rois')
446
- show_mode = cB.radio('ROIs to show', ['All', 'Selected'], horizontal=True,
447
- key='seg_show_mode', label_visibility='collapsed')
448
- show_traces = cC.toggle('Show traces', value=True, key='seg_show_traces')
449
- sel_method = cD.selectbox('Selection method', methods, key='seg_sel_method',
456
+ show_traces = cB.toggle('Show traces', value=True, key='seg_show_traces')
457
+ sel_method = cC.selectbox('Selection method', methods, key='seg_sel_method',
450
458
  label_visibility='collapsed',
451
459
  disabled=not show_rois)
452
460
 
453
- # Duplicate / delete selection methods
454
- _, _, dC = st.columns([1, 1, 2])
461
+ # Duplicate / delete selection methods + label filter
462
+ sel_labels = get_selection_labels(
463
+ subject, row['session_name'], row['dataset_name'], param_set, sel_method)
464
+ lsT, lsS, _, dC = st.columns([0.4, 0.6, 1, 2])
465
+ lsT.markdown('<div style="padding-top:0.4em"><b>Selection:</b></div>',
466
+ unsafe_allow_html=True)
467
+ sel_label_filter = lsS.selectbox(
468
+ 'Filter by label', ['All'] + sel_labels,
469
+ key='seg_label_filter', label_visibility='collapsed')
455
470
  with dC:
456
471
  dname, dbtn, del_btn = st.columns([3, 1, 1])
457
472
  new_name = dname.text_input('New method name', key='seg_new_method_name',
@@ -511,7 +526,7 @@ def segmentation_tab(schema=None):
511
526
  with st.spinner('Loading ROI color projection…'):
512
527
  roi_color_projs = get_roi_color_proj(
513
528
  subject, row['session_name'], row['dataset_name'], param_set, sel_method,
514
- selected_only=(show_mode == 'Selected'))
529
+ sel_label=None if sel_label_filter == 'All' else int(sel_label_filter))
515
530
 
516
531
  all_projections = roi_color_projs + list(projections)
517
532
 
@@ -522,18 +537,22 @@ def segmentation_tab(schema=None):
522
537
  size_domain = ['cell', 'not cell', 'unclassified', 'picked']
523
538
  size_range = [80, 80, 60, 160]
524
539
 
525
- # Cell navigation (prev/next among all ROIs), sorted by SNR if possible
540
+ # Cell navigation: only iterate through displayed ROIs, sorted by SNR if possible
526
541
  snr_map = get_snr_map(subject, row['session_name'], row['dataset_name'], param_set)
542
+ displayed_centers = centers
543
+ if sel_label_filter != 'All':
544
+ displayed_centers = [c for c in displayed_centers
545
+ if c['sel_value'] == int(sel_label_filter)]
527
546
  if snr_map:
528
547
  cell_list = sorted(
529
- [(c['plane_num'], c['roi_num']) for c in centers],
548
+ [(c['plane_num'], c['roi_num']) for c in displayed_centers],
530
549
  key=lambda x: -(snr_map.get(x, {}).get('snr') or 0.0)
531
- ) if centers else []
550
+ ) if displayed_centers else []
532
551
  else:
533
552
  cell_list = sorted(
534
- [(c['plane_num'], c['roi_num']) for c in centers],
553
+ [(c['plane_num'], c['roi_num']) for c in displayed_centers],
535
554
  key=lambda x: (x[0], x[1])
536
- ) if centers else []
555
+ ) if displayed_centers else []
537
556
 
538
557
  def _nav_cursor():
539
558
  """Return index of the last picked cell in cell_list, or -1."""
@@ -559,7 +578,7 @@ def segmentation_tab(schema=None):
559
578
  get_traces.clear()
560
579
  st.rerun()
561
580
 
562
- # Keyboard shortcuts: p/ArrowLeft = prev, n/ArrowRight = next
581
+ # Keyboard: w or p/s or n = prev/next cell, d/a = time forward/back
563
582
  _kb_html = """
564
583
  <script>
565
584
  (function() {
@@ -569,8 +588,10 @@ def segmentation_tab(schema=None):
569
588
  parent.addEventListener('keydown', function(e) {
570
589
  if (e.target && (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA')) return;
571
590
  var label = null;
572
- if (e.key === 'n' || e.key === 'ArrowRight') label = 'next cell';
573
- if (e.key === 'p' || e.key === 'ArrowLeft') label = 'prev cell';
591
+ if ((e.key === 'w') || (e.key === 'p')) label = 'prev cell';
592
+ if ((e.key === 's') || (e.key === 'n')) label = 'next cell';
593
+ if (e.key === 'd') label = '+';
594
+ if (e.key === 'a') label = '-';
574
595
  if (!label) return;
575
596
  var btns = parent.querySelectorAll('button');
576
597
  for (var i = 0; i < btns.length; i++) {
@@ -586,7 +607,7 @@ def segmentation_tab(schema=None):
586
607
  import streamlit.components.v1 as _stc
587
608
  _stc.html(_kb_html, height=0)
588
609
 
589
- # projection tabs — 'roi color' first (default), then others sorted
610
+ # projection tabs
590
611
  _other_names = sorted(set(r['proj_name'] for r in projections))
591
612
  proj_names = (['roi color'] if roi_color_projs else []) + _other_names
592
613
  if not proj_names:
@@ -619,12 +640,13 @@ def segmentation_tab(schema=None):
619
640
  scatter = None
620
641
  if show_rois and plane_centers:
621
642
  picked_set = {(p['plane_num'], p['roi_num']) for p in picked}
622
- visible = (
623
- [c for c in plane_centers
624
- if c['db_label'] == 'cell'
625
- or (c['plane_num'], c['roi_num']) in picked_set]
626
- if show_mode == 'Selected' else plane_centers
627
- )
643
+ if sel_label_filter == 'All':
644
+ visible = plane_centers
645
+ else:
646
+ lf = int(sel_label_filter)
647
+ visible = [c for c in plane_centers
648
+ if c['sel_value'] == lf
649
+ or (c['plane_num'], c['roi_num']) in picked_set]
628
650
  if visible:
629
651
  roi_df = pd.DataFrame(visible)
630
652
  roi_df['status'] = roi_df.apply(
@@ -694,7 +716,7 @@ def segmentation_tab(schema=None):
694
716
 
695
717
  st.divider()
696
718
 
697
- plot_picked = picked[-MAX_PICKED:]
719
+ plot_picked = picked[-MAX_PLOT:]
698
720
 
699
721
  hA, hB = st.columns([3, 1])
700
722
  hA.write(f'**ROIs** (plotting last {len(plot_picked)} of {len(picked)} selected): '
@@ -736,50 +758,77 @@ def segmentation_tab(schema=None):
736
758
  frame_rate, _ = get_frame_rate(subject, row['session_name'], row['dataset_name'])
737
759
  traces_df = traces_df.copy()
738
760
  traces_df['t_sec'] = traces_df['t'] / frame_rate
739
-
740
761
  y_min = min(0.0, float(traces_df['value'].min()))
741
762
  y_max = float(traces_df['value'].max())
742
-
743
763
  window_s = 300.0
744
764
  total_s = float(traces_df['t_sec'].max())
745
- if total_s > window_s:
746
- max_start = round(total_s - window_s, 2)
747
- _lc, _sc = st.columns([1, 11])
748
- _lc.markdown('**Time (s)**')
749
- t_start = _sc.slider('Time (s)', 0.0, max_start, 0.0, step=30.0,
750
- key='seg_trace_window', label_visibility='collapsed')
751
- else:
752
- t_start = 0.0
753
- t_end = t_start + window_s
754
-
755
- view_df = traces_df[
756
- (traces_df['t_sec'] >= t_start) & (traces_df['t_sec'] <= t_end)]
757
- trace_chart = (
758
- alt.Chart(view_df)
759
- .mark_line(strokeWidth=1.2)
760
- .encode(
761
- x=alt.X('t_sec:Q', title='Time (s)',
762
- scale=alt.Scale(domain=[t_start, t_end]),
763
- axis=alt.Axis(tickCount=5, tickSize=5, grid=False)),
764
- y=alt.Y('value:Q', title=trace_type,
765
- scale=alt.Scale(domain=[y_min-0.1, y_max+0.1]),
766
- axis=alt.Axis(tickCount=5, tickSize=5, grid=False)),
767
- color=alt.Color('roi:N', title='ROI',
768
- scale=alt.Scale(domain=roi_labels, range=roi_color_range)),
769
- tooltip=['roi:N', alt.Tooltip('t_sec:Q', title='Time (s)', format='.2f'),
770
- alt.Tooltip('value:Q', format='.4f')],
765
+
766
+ def _draw_traces():
767
+ if total_s > window_s:
768
+ max_start = round(total_s - window_s, 2)
769
+ _lc, _tb, _sc, _tf = st.columns([1, 0.2, 10, 0.2])
770
+ _lc.markdown('**Time (s)**')
771
+ if _tb.button('-', key='seg_time_back', use_container_width=True):
772
+ cur = st.session_state.get('seg_trace_window', 0.0)
773
+ st.session_state['seg_trace_window'] = max(0.0, cur - window_s/2)
774
+ _rerun()
775
+ if _tf.button('+', key='seg_time_fwd', use_container_width=True):
776
+ cur = st.session_state.get('seg_trace_window', 0.0)
777
+ st.session_state['seg_trace_window'] = min(max_start, cur + window_s/2)
778
+ _rerun()
779
+ t_start = _sc.slider('Time (s)', 0.0, max_start,
780
+ st.session_state.get('seg_trace_window', 0.0),
781
+ step=30.0, key='seg_trace_window',
782
+ label_visibility='collapsed')
783
+ else:
784
+ t_start = 0.0
785
+ t_end = t_start + window_s
786
+ view_df = traces_df[
787
+ (traces_df['t_sec'] >= t_start) & (traces_df['t_sec'] <= t_end)]
788
+ trace_chart = (
789
+ alt.Chart(view_df)
790
+ .mark_line(strokeWidth=1.2)
791
+ .encode(
792
+ x=alt.X('t_sec:Q', title='Time (s)',
793
+ scale=alt.Scale(domain=[t_start, t_end]),
794
+ axis=alt.Axis(tickCount=5, tickSize=5, grid=False)),
795
+ y=alt.Y('value:Q', title=trace_type,
796
+ scale=alt.Scale(domain=[y_min-0.1, y_max+0.1]),
797
+ axis=alt.Axis(tickCount=5, tickSize=5, grid=False)),
798
+ color=alt.Color('roi:N', title='ROI',
799
+ scale=alt.Scale(domain=roi_labels,
800
+ range=roi_color_range)),
801
+ tooltip=['roi:N',
802
+ alt.Tooltip('t_sec:Q', title='Time (s)', format='.2f'),
803
+ alt.Tooltip('value:Q', format='.4f')],
804
+ )
805
+ .properties(height=220).interactive()
771
806
  )
772
- .properties(height=220).interactive()
773
- )
774
- st.altair_chart(trace_chart, width='stretch')
807
+ st.altair_chart(trace_chart, width='stretch')
808
+
809
+ if hasattr(st, 'fragment'):
810
+ def _rerun(): st.rerun(scope='fragment')
811
+ st.fragment(_draw_traces)()
812
+ else:
813
+ def _rerun(): st.rerun()
814
+ _draw_traces()
775
815
 
776
- # classification editor — shows currently picked cells + any cells whose
816
+ # classification editor — shows currently picked cells and cells whose
777
817
  # classification was changed from the original value
778
818
  st.write('**ROI classification**')
819
+
820
+ # avoid second re-run
821
+ if st.session_state.pop('_seg_propagate_pending', False):
822
+ pv = st.session_state.pop('_seg_propagate_val', None)
823
+ if pv is not None:
824
+ st.session_state['seg_editor_rows'] = [
825
+ dict(r, selection=pv) for r in st.session_state['seg_editor_rows']]
826
+ st.session_state.pop('seg_selection_editor', None)
827
+
779
828
  editor_rows = list(st.session_state['seg_editor_rows'])
780
829
  picked_keys = {(p['plane_num'], p['roi_num']) for p in picked}
781
830
 
782
- # pending widget edits into editor_rows so filtering sees the current values
831
+ # pending editor_rows so filtering sees the current values
783
832
  widget_edits = (st.session_state.get('seg_selection_editor') or {}).get('edited_rows', {})
784
833
  for i in range(len(editor_rows)):
785
834
  edits = widget_edits.get(i, widget_edits.get(str(i), {}))
@@ -788,7 +837,7 @@ def segmentation_tab(schema=None):
788
837
 
789
838
  # currently picked cells OR cells whose classification changed from the original
790
839
  editor_rows = [r for r in editor_rows
791
- if (r['plane'], r['roi']) in picked_keys or r['is_cell'] != r['_orig']]
840
+ if (r['plane'], r['roi']) in picked_keys or r['selection'] != r['_orig']]
792
841
 
793
842
  # Add picked cells not yet in the list
794
843
  existing = {(r['plane'], r['roi']) for r in editor_rows}
@@ -798,9 +847,9 @@ def segmentation_tab(schema=None):
798
847
  entry = (schema.CellSegmentation.Selection
799
848
  & dict(roi_key, selection_method=sel_method)).fetch(
800
849
  'selection', as_dict=True)
801
- orig = bool(entry[0]['selection']) if entry else None
850
+ orig = int(entry[0]['selection']) if entry else None
802
851
  editor_rows.append({'plane': plane_num, 'roi': roi_num,
803
- 'method': sel_method, 'is_cell': orig, '_orig': orig})
852
+ 'method': sel_method, 'selection': orig, '_orig': orig})
804
853
  existing.add((plane_num, roi_num))
805
854
 
806
855
  if editor_rows != st.session_state['seg_editor_rows']:
@@ -830,48 +879,52 @@ def segmentation_tab(schema=None):
830
879
  editor_display = pd.DataFrame([
831
880
  {'●': _color_swatch(roi_color_map.get(f'plane {r["plane"]}, roi {r["roi"]}', '#888888')),
832
881
  **({'□': crop_map.get((r['plane'], r['roi']))} if crop_map else {}),
833
- 'plane': r['plane'], 'roi': r['roi'], 'method': r['method'], 'is_cell': r['is_cell'],
882
+ 'plane': r['plane'], 'roi': r['roi'], 'method': r['method'],
883
+ 'selection': r['selection'],
834
884
  **({
835
- 'diam': _fmt(snr_map.get((r['plane'], r['roi']), {}).get('diameter')),
836
- 'AR': _fmt(snr_map.get((r['plane'], r['roi']), {}).get('aspect_ratio')),
837
- 'skew': _fmt(snr_map.get((r['plane'], r['roi']), {}).get('trace_skewness')),
838
- 'noise':_fmt(snr_map.get((r['plane'], r['roi']), {}).get('noise_level')),
885
+ 'snr': _fmt(snr_map.get((r['plane'], r['roi']), {}).get('snr')),
886
+ 'diam': _fmt(snr_map.get((r['plane'], r['roi']), {}).get('diameter')),
887
+ 'aspect':_fmt(snr_map.get((r['plane'], r['roi']), {}).get('aspect_ratio')),
888
+ 'skew': _fmt(snr_map.get((r['plane'], r['roi']), {}).get('trace_skewness')),
889
+ 'noise': _fmt(snr_map.get((r['plane'], r['roi']), {}).get('noise_level')),
839
890
  } if snr_map else {})}
840
891
  for r in editor_rows
841
892
  ]) if editor_rows else pd.DataFrame(
842
- columns=['●'] + (['□'] if crop_map else []) + ['plane', 'roi', 'method', 'is_cell']
843
- + (['diam', 'AR', 'skew', 'noise'] if snr_map else []))
893
+ columns=['●'] + (['□'] if crop_map else []) + ['plane', 'roi', 'method', 'selection']
894
+ + (['snr', 'diam', 'aspect', 'skew', 'noise'] if snr_map else []))
844
895
 
845
896
  _crop_col_cfg = {'□': st.column_config.ImageColumn('ROI', width='small')} if crop_map else {}
846
897
  _metrics_col_cfg = {
898
+ 'snr': st.column_config.NumberColumn('SNR', format='%.1f', width='small'),
847
899
  'diam': st.column_config.NumberColumn('diam (px)', format='%.1f', width='small'),
848
- 'AR': st.column_config.NumberColumn('AR', format='%.2f', width='small'),
849
- 'skew': st.column_config.NumberColumn('skew', format='%.2f', width='small'),
850
- 'noise': st.column_config.NumberColumn('noise', format='%.3f', width='small'),
900
+ 'aspect':st.column_config.NumberColumn('aspect', format='%.2f', width='small'),
901
+ 'skew': st.column_config.NumberColumn('skew', format='%.2f', width='small'),
902
+ 'noise': st.column_config.NumberColumn('noise', format='%.3f', width='small'),
851
903
  } if snr_map else {}
852
904
  edited = st.data_editor(
853
905
  editor_display, hide_index=True, width='stretch',
854
906
  column_config={
855
907
  '●': st.column_config.ImageColumn(' ', width='small'),
856
908
  **_crop_col_cfg,
857
- 'is_cell': st.column_config.CheckboxColumn('Is cell?', width='small'),
909
+ 'selection': st.column_config.NumberColumn('selection', step=1, width='small'),
858
910
  **_metrics_col_cfg,
859
911
  },
860
- disabled=['●', '□', 'plane', 'roi', 'method', 'diam', 'AR', 'skew', 'noise'],
912
+ disabled=['●', '□', 'plane', 'roi', 'method', 'snr', 'diam', 'aspect', 'skew', 'noise'],
861
913
  key='seg_selection_editor',
862
914
  )
863
915
 
864
- if st.button('Save classification', key='seg_save_selection'):
916
+ def _do_save(df):
865
917
  changed = 0
866
- for _, erow in edited.iterrows():
867
- if erow['is_cell'] is None:
918
+ for _, erow in df.iterrows():
919
+ raw = erow.get('selection')
920
+ if raw is None:
868
921
  continue
869
922
  roi_key = dict(seg_key, plane_num=int(erow['plane']),
870
923
  roi_num=int(erow['roi']),
871
924
  selection_method=sel_method)
872
925
  try:
873
926
  schema.CellSegmentation.Selection.update1(
874
- {**roi_key, 'selection': 1 if erow['is_cell'] else 0})
927
+ {**roi_key, 'selection': int(raw)})
875
928
  changed += 1
876
929
  except Exception as exc:
877
930
  st.error(f'Error updating plane {erow["plane"]} roi {erow["roi"]}: {exc}')
@@ -883,3 +936,12 @@ def segmentation_tab(schema=None):
883
936
  st.session_state.pop('seg_selection_editor', None)
884
937
  st.rerun()
885
938
 
939
+ _bA, _bB = st.columns([1, 1])
940
+ if _bA.button('Save classification', key='seg_save_selection'):
941
+ _do_save(edited)
942
+ if _bB.button('Propagate first', key='seg_propagate_first',
943
+ disabled=edited.empty or bool(edited['selection'].isna().all())):
944
+ first_val = int(edited['selection'].dropna().iloc[0])
945
+ st.session_state['_seg_propagate_pending'] = True
946
+ st.session_state['_seg_propagate_val'] = first_val
947
+
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: labdata
3
- Version: 0.1.2
3
+ Version: 0.1.4
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
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