labdata 0.0.25__tar.gz → 0.1.0__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.
- {labdata-0.0.25 → labdata-0.1.0}/PKG-INFO +1 -1
- labdata-0.1.0/labdata/__init__.py +11 -0
- {labdata-0.0.25 → labdata-0.1.0}/labdata/cli.py +4 -0
- labdata-0.1.0/labdata/dashboard/console.py +166 -0
- {labdata-0.0.25 → labdata-0.1.0}/labdata/dashboard/decisiontask.py +11 -12
- {labdata-0.0.25 → labdata-0.1.0}/labdata/dashboard/index.py +8 -0
- {labdata-0.0.25 → labdata-0.1.0}/labdata/dashboard/segmentation.py +159 -9
- {labdata-0.0.25 → labdata-0.1.0}/labdata/utils.py +42 -3
- {labdata-0.0.25 → labdata-0.1.0}/labdata.egg-info/PKG-INFO +1 -1
- {labdata-0.0.25 → labdata-0.1.0}/labdata.egg-info/SOURCES.txt +1 -0
- labdata-0.0.25/labdata/__init__.py +0 -16
- {labdata-0.0.25 → labdata-0.1.0}/LICENSE +0 -0
- {labdata-0.0.25 → labdata-0.1.0}/README.md +0 -0
- {labdata-0.0.25 → labdata-0.1.0}/labdata/compute/__init__.py +0 -0
- {labdata-0.0.25 → labdata-0.1.0}/labdata/compute/caiman.py +0 -0
- {labdata-0.0.25 → labdata-0.1.0}/labdata/compute/ec2.py +0 -0
- {labdata-0.0.25 → labdata-0.1.0}/labdata/compute/ephys.py +0 -0
- {labdata-0.0.25 → labdata-0.1.0}/labdata/compute/pose.py +0 -0
- {labdata-0.0.25 → labdata-0.1.0}/labdata/compute/schedulers.py +0 -0
- {labdata-0.0.25 → labdata-0.1.0}/labdata/compute/singularity.py +0 -0
- {labdata-0.0.25 → labdata-0.1.0}/labdata/compute/suite2p.py +0 -0
- {labdata-0.0.25 → labdata-0.1.0}/labdata/compute/utils.py +0 -0
- {labdata-0.0.25 → labdata-0.1.0}/labdata/copy.py +0 -0
- {labdata-0.0.25 → labdata-0.1.0}/labdata/dashboard/__init__.py +0 -0
- {labdata-0.0.25 → labdata-0.1.0}/labdata/dashboard/compute.py +0 -0
- {labdata-0.0.25 → labdata-0.1.0}/labdata/dashboard/explorer/__init__.py +0 -0
- {labdata-0.0.25 → labdata-0.1.0}/labdata/dashboard/explorer/cellsegmentation.py +0 -0
- {labdata-0.0.25 → labdata-0.1.0}/labdata/dashboard/explorer/spikesorting.py +0 -0
- {labdata-0.0.25 → labdata-0.1.0}/labdata/dashboard/notes.py +0 -0
- {labdata-0.0.25 → labdata-0.1.0}/labdata/dashboard/procedures.py +0 -0
- {labdata-0.0.25 → labdata-0.1.0}/labdata/dashboard/sorting.py +0 -0
- {labdata-0.0.25 → labdata-0.1.0}/labdata/dashboard/video.py +0 -0
- {labdata-0.0.25 → labdata-0.1.0}/labdata/export.py +0 -0
- {labdata-0.0.25 → labdata-0.1.0}/labdata/rules/__init__.py +0 -0
- {labdata-0.0.25 → labdata-0.1.0}/labdata/rules/ephys.py +0 -0
- {labdata-0.0.25 → labdata-0.1.0}/labdata/rules/imaging.py +0 -0
- {labdata-0.0.25 → labdata-0.1.0}/labdata/rules/utils.py +0 -0
- {labdata-0.0.25 → labdata-0.1.0}/labdata/s3.py +0 -0
- {labdata-0.0.25 → labdata-0.1.0}/labdata/schema/__init__.py +0 -0
- {labdata-0.0.25 → labdata-0.1.0}/labdata/schema/ephys.py +0 -0
- {labdata-0.0.25 → labdata-0.1.0}/labdata/schema/general.py +0 -0
- {labdata-0.0.25 → labdata-0.1.0}/labdata/schema/histology.py +0 -0
- {labdata-0.0.25 → labdata-0.1.0}/labdata/schema/onephoton.py +0 -0
- {labdata-0.0.25 → labdata-0.1.0}/labdata/schema/procedures.py +0 -0
- {labdata-0.0.25 → labdata-0.1.0}/labdata/schema/tasks.py +0 -0
- {labdata-0.0.25 → labdata-0.1.0}/labdata/schema/twophoton.py +0 -0
- {labdata-0.0.25 → labdata-0.1.0}/labdata/schema/utils.py +0 -0
- {labdata-0.0.25 → labdata-0.1.0}/labdata/schema/video.py +0 -0
- {labdata-0.0.25 → labdata-0.1.0}/labdata/stacks.py +0 -0
- {labdata-0.0.25 → labdata-0.1.0}/labdata/widgets.py +0 -0
- {labdata-0.0.25 → labdata-0.1.0}/labdata.egg-info/dependency_links.txt +0 -0
- {labdata-0.0.25 → labdata-0.1.0}/labdata.egg-info/entry_points.txt +0 -0
- {labdata-0.0.25 → labdata-0.1.0}/labdata.egg-info/requires.txt +0 -0
- {labdata-0.0.25 → labdata-0.1.0}/labdata.egg-info/top_level.txt +0 -0
- {labdata-0.0.25 → labdata-0.1.0}/pyproject.toml +0 -0
- {labdata-0.0.25 → labdata-0.1.0}/setup.cfg +0 -0
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
VERSION = "0.1.0"
|
|
2
|
+
|
|
3
|
+
from .utils import *
|
|
4
|
+
from .copy import copy_to_upload_server
|
|
5
|
+
from .s3 import copy_to_s3
|
|
6
|
+
|
|
7
|
+
_default_project = (prefs.get('database', {}) or {}).get('database.project', None)
|
|
8
|
+
plugins = load_plugins(project=_default_project)
|
|
9
|
+
for _name, _mod in plugins.items():
|
|
10
|
+
globals()[_name] = _mod
|
|
11
|
+
|
|
@@ -60,6 +60,8 @@ Server commands (don't run on experimental computers):
|
|
|
60
60
|
parser.add_argument('--spike-sorting' ,action = 'store_true', default = False)
|
|
61
61
|
parser.add_argument('--cell-segmentation' ,action = 'store_true', default = False)
|
|
62
62
|
parser.add_argument('--debug' ,action = 'store_true', default = False, help = 'To debug the dash (dashboard) errors.')
|
|
63
|
+
parser.add_argument('--console', action = 'store_true', default = False,
|
|
64
|
+
help = 'Include the IPython console tab in the dashboard.')
|
|
63
65
|
project = _get_project()
|
|
64
66
|
parser.add_argument('-p','--project',
|
|
65
67
|
default = project,
|
|
@@ -91,6 +93,8 @@ Server commands (don't run on experimental computers):
|
|
|
91
93
|
command = f'streamlit run {str(main_webpage_path)}'
|
|
92
94
|
if not args.project is None:
|
|
93
95
|
command += f' project={args.project}'
|
|
96
|
+
if args.console:
|
|
97
|
+
command += ' console=1'
|
|
94
98
|
sub.run(command.split(' '))
|
|
95
99
|
|
|
96
100
|
def subjects(self):
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import streamlit as st
|
|
2
|
+
import base64
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def _execute_silent(kc, code):
|
|
6
|
+
"""Fire-and-forget execution (used for kernel init)."""
|
|
7
|
+
kc.execute(code, silent=True)
|
|
8
|
+
while True:
|
|
9
|
+
try:
|
|
10
|
+
msg = kc.get_iopub_msg(timeout=60)
|
|
11
|
+
except Exception:
|
|
12
|
+
break
|
|
13
|
+
if (msg['msg_type'] == 'status' and
|
|
14
|
+
msg['content'].get('execution_state') == 'idle'):
|
|
15
|
+
break
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _execute(kc, code):
|
|
19
|
+
"""Execute code and collect text outputs, inline images, and errors."""
|
|
20
|
+
kc.execute(code)
|
|
21
|
+
outputs = [] # list of (kind, text) kind in ('stream','result')
|
|
22
|
+
images = [] # list of base-64 PNG strings
|
|
23
|
+
error = None
|
|
24
|
+
while True:
|
|
25
|
+
try:
|
|
26
|
+
msg = kc.get_iopub_msg(timeout=60)
|
|
27
|
+
except Exception:
|
|
28
|
+
break
|
|
29
|
+
mtype = msg['msg_type']
|
|
30
|
+
content = msg['content']
|
|
31
|
+
if mtype == 'stream':
|
|
32
|
+
outputs.append(('stream', content['text']))
|
|
33
|
+
elif mtype in ('execute_result', 'display_data'):
|
|
34
|
+
img = content['data'].get('image/png')
|
|
35
|
+
if img:
|
|
36
|
+
images.append(img)
|
|
37
|
+
else:
|
|
38
|
+
text = content['data'].get('text/plain', '')
|
|
39
|
+
if text:
|
|
40
|
+
outputs.append(('result', text))
|
|
41
|
+
elif mtype == 'error':
|
|
42
|
+
error = '\n'.join(content['traceback'])
|
|
43
|
+
elif mtype == 'status' and content.get('execution_state') == 'idle':
|
|
44
|
+
break
|
|
45
|
+
return outputs, images, error
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _start_kernel(project_name):
|
|
49
|
+
import jupyter_client
|
|
50
|
+
km = jupyter_client.KernelManager(kernel_name='python3')
|
|
51
|
+
km.start_kernel()
|
|
52
|
+
kc = km.client()
|
|
53
|
+
kc.start_channels()
|
|
54
|
+
kc.wait_for_ready(timeout=60)
|
|
55
|
+
|
|
56
|
+
# Configure matplotlib for inline PNG output, then preload labdata
|
|
57
|
+
init_lines = [
|
|
58
|
+
"import matplotlib",
|
|
59
|
+
"matplotlib.use('Agg')",
|
|
60
|
+
"import pylab as plt",
|
|
61
|
+
"%matplotlib inline",
|
|
62
|
+
]
|
|
63
|
+
if project_name:
|
|
64
|
+
init_lines += [
|
|
65
|
+
"import os",
|
|
66
|
+
f"os.environ['LABDATA_DATABASE_PROJECT'] = {project_name!r}",
|
|
67
|
+
]
|
|
68
|
+
init_lines += [
|
|
69
|
+
"from labdata import *",
|
|
70
|
+
"from labdata.schema import *",
|
|
71
|
+
]
|
|
72
|
+
_execute_silent(kc, '\n'.join(init_lines))
|
|
73
|
+
|
|
74
|
+
st.session_state['_console_km'] = km
|
|
75
|
+
st.session_state['_console_kc'] = kc
|
|
76
|
+
st.session_state['_console_hist'] = []
|
|
77
|
+
st.session_state['_console_n'] = 0
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _shutdown_kernel():
|
|
81
|
+
km = st.session_state.pop('_console_km', None)
|
|
82
|
+
st.session_state.pop('_console_kc', None)
|
|
83
|
+
st.session_state.pop('_console_hist', None)
|
|
84
|
+
st.session_state.pop('_console_n', None)
|
|
85
|
+
if km is not None:
|
|
86
|
+
try:
|
|
87
|
+
km.shutdown_kernel(now=True)
|
|
88
|
+
except Exception:
|
|
89
|
+
pass
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def console_tab(schema=None):
|
|
93
|
+
project_name = getattr(schema, 'schema_project', None) if schema else None
|
|
94
|
+
|
|
95
|
+
# kernel status bar
|
|
96
|
+
alive = '_console_km' in st.session_state
|
|
97
|
+
status_col, btn_col = st.columns([4, 1])
|
|
98
|
+
with status_col:
|
|
99
|
+
if alive:
|
|
100
|
+
st.success("Kernel running • `from labdata import *` and `from labdata.schema import *` pre-loaded")
|
|
101
|
+
else:
|
|
102
|
+
st.info("Kernel not started. Click **Start kernel** to launch an IPython kernel.")
|
|
103
|
+
with btn_col:
|
|
104
|
+
if not alive:
|
|
105
|
+
if st.button("Start kernel", type="primary", use_container_width=True):
|
|
106
|
+
with st.spinner("Starting IPython kernel…"):
|
|
107
|
+
_start_kernel(project_name)
|
|
108
|
+
st.rerun()
|
|
109
|
+
else:
|
|
110
|
+
if st.button("Restart kernel", type="secondary", use_container_width=True):
|
|
111
|
+
_shutdown_kernel()
|
|
112
|
+
with st.spinner("Restarting…"):
|
|
113
|
+
_start_kernel(project_name)
|
|
114
|
+
st.rerun()
|
|
115
|
+
|
|
116
|
+
if not alive:
|
|
117
|
+
return
|
|
118
|
+
|
|
119
|
+
kc = st.session_state['_console_kc']
|
|
120
|
+
history = st.session_state['_console_hist']
|
|
121
|
+
|
|
122
|
+
# cell history
|
|
123
|
+
for entry in history:
|
|
124
|
+
n = entry['n']
|
|
125
|
+
st.code(f"In [{n}]:\n{entry['input']}", language='python')
|
|
126
|
+
for kind, text in entry['outputs']:
|
|
127
|
+
if kind == 'result':
|
|
128
|
+
st.text(f"Out [{n}]: {text.rstrip()}")
|
|
129
|
+
else:
|
|
130
|
+
st.text(text.rstrip())
|
|
131
|
+
for b64 in entry['images']:
|
|
132
|
+
st.image(base64.b64decode(b64))
|
|
133
|
+
if entry['error']:
|
|
134
|
+
st.error(entry['error'])
|
|
135
|
+
|
|
136
|
+
if history:
|
|
137
|
+
st.divider()
|
|
138
|
+
|
|
139
|
+
# input area
|
|
140
|
+
code = st.text_area(
|
|
141
|
+
"code_input",
|
|
142
|
+
height=130,
|
|
143
|
+
placeholder="Python / IPython code — plots are displayed inline",
|
|
144
|
+
label_visibility='collapsed',
|
|
145
|
+
key='_console_code',
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
run_col, clear_col, _ = st.columns([1, 1, 6])
|
|
149
|
+
with run_col:
|
|
150
|
+
run_pressed = st.button("▶ Run", type="primary", use_container_width=True)
|
|
151
|
+
with clear_col:
|
|
152
|
+
if st.button("Clear", use_container_width=True):
|
|
153
|
+
st.session_state['_console_hist'] = []
|
|
154
|
+
st.session_state['_console_n'] = 0
|
|
155
|
+
st.rerun()
|
|
156
|
+
|
|
157
|
+
if run_pressed and code.strip():
|
|
158
|
+
with st.spinner("Running…"):
|
|
159
|
+
outputs, images, error = _execute(kc, code)
|
|
160
|
+
n = st.session_state['_console_n'] + 1
|
|
161
|
+
st.session_state['_console_n'] = n
|
|
162
|
+
history.append(dict(n=n, input=code,
|
|
163
|
+
outputs=outputs, images=images, error=error))
|
|
164
|
+
st.rerun()
|
|
165
|
+
|
|
166
|
+
|
|
@@ -125,11 +125,11 @@ def decisionmaking_tab(schema = None):
|
|
|
125
125
|
point_selector = alt.selection_point(on="click")
|
|
126
126
|
chart = alt.Chart(summarysource).mark_rect().encode(
|
|
127
127
|
x=alt.X('session_datetime:T',timeUnit = 'yearmonthdate',
|
|
128
|
-
scale=alt.Scale(nice={'interval': 'day', 'step': 1})).axis(labelAngle=45,format='%Y-%m-%d').title('Date'),
|
|
128
|
+
scale=alt.Scale(nice={'interval': 'day', 'step': 1})).axis(labelAngle=45,format='%Y-%m-%d').title('Date'),
|
|
129
129
|
y=alt.Y('subject_name:O').title('Subject name'),
|
|
130
130
|
color=alt.Color('performance_easy:Q',
|
|
131
131
|
scale=alt.Scale(scheme='spectral', reverse=True, domain=[0.5,1], clamp=True)).title('Perf easy'),
|
|
132
|
-
tooltip = ['subject_name','session_name','session_datetime','performance_easy','performance','n_trials']
|
|
132
|
+
tooltip = ['subject_name:N','session_name:N','session_datetime:T','performance_easy:Q','performance:Q','n_trials:Q']
|
|
133
133
|
|
|
134
134
|
).properties(width=1000, title ='Behavioral performance').add_params(point_selector)
|
|
135
135
|
event_data = st.altair_chart(chart.interactive(), on_select='rerun')
|
|
@@ -204,11 +204,11 @@ def decisionmaking_tab(schema = None):
|
|
|
204
204
|
x_enc = alt.X('session_datetime:T', axis=axis).title('Session date')
|
|
205
205
|
perf_chart = (
|
|
206
206
|
alt.Chart(trialset).mark_line(color=black).encode(x=x_enc, y=alt.Y('performance_easy:Q', axis=axis).title('Performance')).properties(width=1000) +
|
|
207
|
-
alt.Chart(trialset).mark_point(color=black).encode(x=x_enc, y=alt.Y('performance_easy:Q'), tooltip=['session_name','session_datetime','performance_easy','performance']) +
|
|
207
|
+
alt.Chart(trialset).mark_point(color=black).encode(x=x_enc, y=alt.Y('performance_easy:Q'), tooltip=['session_name:N','session_datetime:T','performance_easy:Q','performance:Q']) +
|
|
208
208
|
alt.Chart(perf_val).mark_text(align='left', dx=6, dy=100, color=black).encode(x=x_enc, y=alt.Y('performance_easy:Q'), text=alt.value('easy')) +
|
|
209
209
|
alt.Chart(trialset).mark_line(color=orange).encode(x=x_enc, y=alt.Y('performance:Q', axis=axis)) +
|
|
210
210
|
alt.Chart(trialset).mark_point(color=orange).encode(x=x_enc, y=alt.Y('performance:Q'),
|
|
211
|
-
tooltip=['session_name','session_datetime','performance_easy','performance']) +
|
|
211
|
+
tooltip=['session_name:N','session_datetime:T','performance_easy:Q','performance:Q']) +
|
|
212
212
|
alt.Chart(perf_val).mark_text(align='left', dx=6, dy=100, color=orange).encode(x=x_enc, y=alt.Y('performance:Q'), text=alt.value('all trials'))
|
|
213
213
|
)
|
|
214
214
|
ruler_df = pd.DataFrame({'t': [selected_dt]})
|
|
@@ -320,12 +320,11 @@ def decisionmaking_tab(schema = None):
|
|
|
320
320
|
st.caption(f"*{n['notetaker']} — {pd.Timestamp(n['note_datetime']).strftime('%Y-%m-%d %H:%M')}*")
|
|
321
321
|
st.write(n['notes'])
|
|
322
322
|
|
|
323
|
-
def line_point_plot(data,x,y,tooltip,color):
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
tooltip=tooltip)
|
|
323
|
+
def line_point_plot(data, x, y, tooltip, color):
|
|
324
|
+
if data is None or not len(data):
|
|
325
|
+
return alt.Chart(pd.DataFrame()).mark_point()
|
|
326
|
+
scatter = alt.Chart(data).mark_point(color=color).encode(
|
|
327
|
+
x=x, y=y, tooltip=tooltip)
|
|
328
328
|
line = alt.Chart(data).mark_line(color=color).encode(
|
|
329
|
-
x=x,
|
|
330
|
-
|
|
331
|
-
return scatter+line
|
|
329
|
+
x=x, y=y).properties(width=1000)
|
|
330
|
+
return scatter + line
|
|
@@ -196,9 +196,13 @@ def load_schema(project):
|
|
|
196
196
|
return load_project_schema(project)
|
|
197
197
|
|
|
198
198
|
project = None
|
|
199
|
+
show_console = False
|
|
199
200
|
for k in sys.argv:
|
|
200
201
|
if 'project=' in k:
|
|
201
202
|
project = k.replace('project=','')
|
|
203
|
+
if k == 'console=1':
|
|
204
|
+
from console import console_tab
|
|
205
|
+
show_console = True
|
|
202
206
|
with st.spinner(f'Connecting to {"project " + project if project else "database"}…'):
|
|
203
207
|
schema = load_schema(project)
|
|
204
208
|
|
|
@@ -213,11 +217,15 @@ page_names_to_funcs = {
|
|
|
213
217
|
"**Compute/Upload**": partial(compute_tab, schema = schema),
|
|
214
218
|
}
|
|
215
219
|
|
|
220
|
+
if show_console:
|
|
221
|
+
page_names_to_funcs["**Console**"] = partial(console_tab, schema=schema)
|
|
222
|
+
|
|
216
223
|
from labdata import plugins
|
|
217
224
|
for p in plugins.keys():
|
|
218
225
|
if hasattr(plugins[p],'dashboard_function'):
|
|
219
226
|
page_names_to_funcs[plugins[p].dashboard_name] = partial(plugins[p].dashboard_function, schema = schema)
|
|
220
227
|
|
|
228
|
+
|
|
221
229
|
tab_name = st.sidebar.radio(
|
|
222
230
|
"``labdata dashboard``",
|
|
223
231
|
page_names_to_funcs.keys())
|
|
@@ -29,18 +29,31 @@ def segmentation_tab(schema=None):
|
|
|
29
29
|
def get_subjects():
|
|
30
30
|
return list((schema.Subject & schema.CellSegmentation).fetch('subject_name'))
|
|
31
31
|
|
|
32
|
+
@st.cache_data
|
|
33
|
+
def get_session_counts():
|
|
34
|
+
rows = (schema.Subject * schema.CellSegmentationParams).aggr(
|
|
35
|
+
schema.CellSegmentation,
|
|
36
|
+
n_sessions='count(session_name)'
|
|
37
|
+
).fetch(as_dict=True)
|
|
38
|
+
if not rows:
|
|
39
|
+
return pd.DataFrame()
|
|
40
|
+
df = pd.DataFrame(rows)
|
|
41
|
+
df = df[df['n_sessions'] > 0].reset_index(drop=True)
|
|
42
|
+
df['parameter_set_num'] = df['parameter_set_num'].astype(str)
|
|
43
|
+
return df
|
|
44
|
+
|
|
32
45
|
@st.cache_data
|
|
33
46
|
def get_sessions(subject_name):
|
|
34
47
|
rows = (schema.CellSegmentation * schema.Session * schema.CellSegmentationParams
|
|
35
48
|
& dict(subject_name=subject_name)).fetch(
|
|
36
49
|
'session_name', 'dataset_name', 'parameter_set_num',
|
|
37
|
-
'algorithm_name', 'n_rois', 'segmentation_datetime',
|
|
50
|
+
'algorithm_name', 'algorithm_version', 'n_rois', 'segmentation_datetime',
|
|
38
51
|
'session_datetime', as_dict=True)
|
|
39
52
|
if not rows:
|
|
40
53
|
return pd.DataFrame()
|
|
41
54
|
df = pd.DataFrame(rows)
|
|
42
55
|
cols = ['session_name', 'dataset_name', 'parameter_set_num', 'algorithm_name',
|
|
43
|
-
'n_rois', 'session_datetime', 'segmentation_datetime']
|
|
56
|
+
'algorithm_version', 'n_rois', 'session_datetime', 'segmentation_datetime']
|
|
44
57
|
return df[[c for c in cols if c in df.columns]].sort_values(
|
|
45
58
|
'session_datetime', ascending=False).reset_index(drop=True)
|
|
46
59
|
|
|
@@ -121,7 +134,7 @@ def segmentation_tab(schema=None):
|
|
|
121
134
|
pass
|
|
122
135
|
return pd.DataFrame(traces)
|
|
123
136
|
|
|
124
|
-
# get the seg_picked_rois from session state
|
|
137
|
+
# get the seg_picked_rois from session state
|
|
125
138
|
if 'seg_picked_rois' not in st.session_state:
|
|
126
139
|
st.session_state['seg_picked_rois'] = []
|
|
127
140
|
|
|
@@ -131,6 +144,53 @@ def segmentation_tab(schema=None):
|
|
|
131
144
|
st.write('No cell segmentation data in the database.')
|
|
132
145
|
return
|
|
133
146
|
|
|
147
|
+
# sessions per subject for each parameter_set_num
|
|
148
|
+
counts_df = get_session_counts()
|
|
149
|
+
bar_param_filter = st.session_state.get('seg_bar_param_filter')
|
|
150
|
+
|
|
151
|
+
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
|
+
_colors = ['#000000', '#d62728', '#1f77b4', '#ff7f0e', '#2ca02c',
|
|
157
|
+
'#9467bd', '#8c564b', '#e377c2', '#7f7f7f', '#bcbd22']
|
|
158
|
+
param_sets = sorted(counts_df['parameter_set_num'].unique().tolist())
|
|
159
|
+
bar_color_scale = alt.Scale(
|
|
160
|
+
domain=param_sets,
|
|
161
|
+
range=[_colors[i % len(_colors)] for i in range(len(param_sets))])
|
|
162
|
+
|
|
163
|
+
bar_sel = alt.selection_point(
|
|
164
|
+
fields=['subject_name', 'parameter_set_num'], name='bar_sel')
|
|
165
|
+
chart = (
|
|
166
|
+
alt.Chart(counts_df).mark_bar().encode(
|
|
167
|
+
x=alt.X('subject_name:N', sort='ascending', title='Subject'),
|
|
168
|
+
xOffset=alt.XOffset('parameter_set_num:N', title='Param set'),
|
|
169
|
+
y=alt.Y('n_sessions:Q', title='# sessions'),
|
|
170
|
+
color=alt.Color('parameter_set_num:N', title='Param set',
|
|
171
|
+
scale=bar_color_scale),
|
|
172
|
+
opacity=alt.condition(bar_sel, alt.value(1.0), alt.value(0.4)),
|
|
173
|
+
tooltip=['subject_name:N', 'parameter_set_num:N', 'n_sessions:Q'],
|
|
174
|
+
).add_params(bar_sel).properties(height=200)
|
|
175
|
+
)
|
|
176
|
+
bar_event = st.altair_chart(chart, width='stretch', on_select='rerun',
|
|
177
|
+
key='seg_sessions_bar')
|
|
178
|
+
|
|
179
|
+
pts = (bar_event.selection or {}).get('bar_sel', [])
|
|
180
|
+
if pts and pts[0].get('subject_name'):
|
|
181
|
+
clicked_subject = pts[0]['subject_name']
|
|
182
|
+
clicked_param = str(pts[0].get('parameter_set_num', ''))
|
|
183
|
+
if clicked_subject in subjects:
|
|
184
|
+
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()
|
|
193
|
+
|
|
134
194
|
subject = st.selectbox('Subject', subjects, index=None, key='seg_subject')
|
|
135
195
|
if not subject:
|
|
136
196
|
return
|
|
@@ -140,15 +200,79 @@ def segmentation_tab(schema=None):
|
|
|
140
200
|
st.write('No segmentation sessions found for this subject.')
|
|
141
201
|
return
|
|
142
202
|
|
|
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
|
+
|
|
241
|
+
scatter_pts = (scatter_event.selection or {}).get('sess_click', [])
|
|
242
|
+
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)]
|
|
248
|
+
if not matches.empty:
|
|
249
|
+
scatter_sel_idx = int(matches.index[0])
|
|
250
|
+
st.session_state['seg_scatter_sel_key'] = f"{sname}|{dname}"
|
|
251
|
+
|
|
143
252
|
event_sess = st.dataframe(
|
|
144
253
|
sessions, hide_index=True, width='stretch',
|
|
145
254
|
on_select='rerun', selection_mode='single-row', key='seg_sessions_table',
|
|
146
255
|
)
|
|
147
|
-
|
|
148
|
-
|
|
256
|
+
table_rows = (event_sess.selection or {}).get('rows', [])
|
|
257
|
+
|
|
258
|
+
if table_rows:
|
|
259
|
+
sel_idx_final = table_rows[0]
|
|
260
|
+
elif scatter_sel_idx is not None:
|
|
261
|
+
sel_idx_final = scatter_sel_idx
|
|
262
|
+
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
|
|
271
|
+
|
|
272
|
+
if sel_idx_final is None:
|
|
149
273
|
return
|
|
150
274
|
|
|
151
|
-
row = sessions.iloc[
|
|
275
|
+
row = sessions.iloc[sel_idx_final]
|
|
152
276
|
param_set = int(row['parameter_set_num'])
|
|
153
277
|
seg_key = dict(subject_name=subject, session_name=row['session_name'],
|
|
154
278
|
dataset_name=row['dataset_name'], parameter_set_num=param_set)
|
|
@@ -175,6 +299,9 @@ def segmentation_tab(schema=None):
|
|
|
175
299
|
methods = get_selection_methods(
|
|
176
300
|
subject, row['session_name'], row['dataset_name'], param_set)
|
|
177
301
|
|
|
302
|
+
if 'seg_sel_method_pending' in st.session_state:
|
|
303
|
+
st.session_state['seg_sel_method'] = st.session_state.pop('seg_sel_method_pending')
|
|
304
|
+
|
|
178
305
|
cA, cB, cC = st.columns([1, 1, 2])
|
|
179
306
|
show_rois = cA.toggle('Show ROIs', value=True, key='seg_show_rois')
|
|
180
307
|
show_mode = cB.radio('ROIs to show', ['All', 'Selected'], horizontal=True,
|
|
@@ -184,10 +311,10 @@ def segmentation_tab(schema=None):
|
|
|
184
311
|
label_visibility='collapsed',
|
|
185
312
|
disabled=not show_rois)
|
|
186
313
|
|
|
187
|
-
# Duplicate
|
|
314
|
+
# Duplicate / delete selection methods
|
|
188
315
|
_, _, dC = st.columns([1, 1, 2])
|
|
189
316
|
with dC:
|
|
190
|
-
dname, dbtn = st.columns([3, 1])
|
|
317
|
+
dname, dbtn, del_btn = st.columns([3, 1, 1])
|
|
191
318
|
new_name = dname.text_input('New method name', key='seg_new_method_name',
|
|
192
319
|
placeholder=f'Duplicate "{sel_method}" as…',
|
|
193
320
|
label_visibility='collapsed')
|
|
@@ -208,11 +335,34 @@ def segmentation_tab(schema=None):
|
|
|
208
335
|
[{**r, 'selection_method': nm} for r in src_rows])
|
|
209
336
|
get_selection_methods.clear()
|
|
210
337
|
get_roi_data.clear()
|
|
211
|
-
st.session_state['
|
|
338
|
+
st.session_state['seg_sel_method_pending'] = nm
|
|
212
339
|
st.rerun()
|
|
213
340
|
except Exception as exc:
|
|
214
341
|
st.error(f'Insert failed: {exc}')
|
|
215
342
|
|
|
343
|
+
if del_btn.button('Delete', key='seg_del_btn', type='secondary'):
|
|
344
|
+
st.session_state['seg_del_confirm'] = sel_method
|
|
345
|
+
|
|
346
|
+
pending_del = st.session_state.get('seg_del_confirm')
|
|
347
|
+
if pending_del and pending_del == sel_method:
|
|
348
|
+
st.warning(f'Delete selection method **"{pending_del}"**? This cannot be undone.')
|
|
349
|
+
yes_col, no_col, _ = st.columns([1, 1, 6])
|
|
350
|
+
if yes_col.button('Yes, delete', key='seg_del_yes', type='primary'):
|
|
351
|
+
try:
|
|
352
|
+
schema.dj.config['safemode'] = False # disable temporarly
|
|
353
|
+
(schema.CellSegmentation.Selection
|
|
354
|
+
& dict(seg_key, selection_method=pending_del)).delete(force=True)
|
|
355
|
+
schema.dj.config['safemode'] = True # re-enable
|
|
356
|
+
get_selection_methods.clear()
|
|
357
|
+
get_roi_data.clear()
|
|
358
|
+
st.session_state.pop('seg_del_confirm', None)
|
|
359
|
+
st.rerun()
|
|
360
|
+
except Exception as exc:
|
|
361
|
+
st.error(f'Delete failed: {exc}')
|
|
362
|
+
if no_col.button('Cancel', key='seg_del_no'):
|
|
363
|
+
st.session_state.pop('seg_del_confirm', None)
|
|
364
|
+
st.rerun()
|
|
365
|
+
|
|
216
366
|
with st.spinner('Loading ROI positions…'):
|
|
217
367
|
centers = get_roi_data(
|
|
218
368
|
subject, row['session_name'], row['dataset_name'], param_set, sel_method)
|
|
@@ -313,6 +313,12 @@ def load_project_schema(project_name):
|
|
|
313
313
|
# delete so it can be reloaded
|
|
314
314
|
for k in [k for k in sys.modules if 'labdata.schema' in k]:
|
|
315
315
|
del sys.modules[k]
|
|
316
|
+
# update plugins in-place so existing references stay valid
|
|
317
|
+
import labdata
|
|
318
|
+
labdata.plugins.clear()
|
|
319
|
+
labdata.plugins.update(load_plugins(project=project_name))
|
|
320
|
+
for _name, _mod in labdata.plugins.items():
|
|
321
|
+
setattr(labdata, _name, _mod)
|
|
316
322
|
return newschema
|
|
317
323
|
|
|
318
324
|
def _get_table(schema,table_name):
|
|
@@ -412,12 +418,14 @@ def find_local_filepath(path,allowed_extensions = [],local_paths = None):
|
|
|
412
418
|
return p # found allowed extensions (use this to find ProcessedFiles)
|
|
413
419
|
return None # file not found
|
|
414
420
|
|
|
415
|
-
def plugin_lazy_import(name):
|
|
421
|
+
def plugin_lazy_import(name, modpath):
|
|
416
422
|
'''
|
|
417
|
-
Lazy import function to load
|
|
423
|
+
Lazy import function to load a plugin from modpath.
|
|
418
424
|
'''
|
|
425
|
+
if 'LABDATA_VERBOSE' in os.environ.keys():
|
|
426
|
+
print(f'[labdata] Loading plugin: {name}')
|
|
419
427
|
import importlib.util
|
|
420
|
-
spec = importlib.util.spec_from_file_location(name, str(Path(
|
|
428
|
+
spec = importlib.util.spec_from_file_location(name, str(Path(modpath)/"__init__.py"))
|
|
421
429
|
loader = importlib.util.LazyLoader(spec.loader)
|
|
422
430
|
spec.loader = loader
|
|
423
431
|
module = importlib.util.module_from_spec(spec)
|
|
@@ -425,6 +433,37 @@ def plugin_lazy_import(name):
|
|
|
425
433
|
loader.exec_module(module)
|
|
426
434
|
return module
|
|
427
435
|
|
|
436
|
+
|
|
437
|
+
def load_plugins(project=None):
|
|
438
|
+
'''Load plugins from preferences, filtered by project.
|
|
439
|
+
|
|
440
|
+
Plugin keys in prefs['plugins'] can be:
|
|
441
|
+
- "plugin_name" load for any project
|
|
442
|
+
- "plugin_name@proj1,proj2" load only for proj1 or proj2
|
|
443
|
+
|
|
444
|
+
When project is None only always-active plugins (no "@") are loaded.
|
|
445
|
+
'''
|
|
446
|
+
result = {}
|
|
447
|
+
if 'plugins' not in prefs:
|
|
448
|
+
return result
|
|
449
|
+
for modkey, modpath in prefs['plugins'].items():
|
|
450
|
+
if '@' in modkey:
|
|
451
|
+
name, projects_str = modkey.split('@', 1)
|
|
452
|
+
allowed = [p.strip() for p in projects_str.split(',')]
|
|
453
|
+
else:
|
|
454
|
+
name = modkey
|
|
455
|
+
allowed = None # no filter — load for any project
|
|
456
|
+
|
|
457
|
+
if allowed is not None and project not in allowed:
|
|
458
|
+
continue
|
|
459
|
+
|
|
460
|
+
try:
|
|
461
|
+
result[name] = plugin_lazy_import(name, modpath)
|
|
462
|
+
except Exception as err:
|
|
463
|
+
print(err)
|
|
464
|
+
print(f'[labdata] Failed to load plugin: {name}')
|
|
465
|
+
return result
|
|
466
|
+
|
|
428
467
|
def extrapolate_time_from_clock(master_clock,master_events, slave_events):
|
|
429
468
|
'''
|
|
430
469
|
Extrapolates the time for synchronizing events on different streams
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
VERSION = "0.0.25"
|
|
2
|
-
|
|
3
|
-
from .utils import *
|
|
4
|
-
from .copy import copy_to_upload_server
|
|
5
|
-
from .s3 import copy_to_s3
|
|
6
|
-
|
|
7
|
-
plugins = {} # to have all plugins in the same place
|
|
8
|
-
if 'plugins' in prefs.keys():
|
|
9
|
-
for modkey in prefs['plugins'].keys():
|
|
10
|
-
try:
|
|
11
|
-
modpath = str(Path(prefs['plugins'][modkey])/"__init__.py")
|
|
12
|
-
exec(f"{modkey} = plugin_lazy_import('{modkey}')")
|
|
13
|
-
exec(f'plugins["{modkey}"] = {modkey}')
|
|
14
|
-
except Exception as err:
|
|
15
|
-
print(err)
|
|
16
|
-
print(f'Failed to load plugin: {modkey}')
|
|
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
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|