labdata 0.1.1__tar.gz → 0.1.2__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.1 → labdata-0.1.2}/PKG-INFO +1 -1
  2. {labdata-0.1.1 → labdata-0.1.2}/labdata/__init__.py +1 -1
  3. {labdata-0.1.1 → labdata-0.1.2}/labdata/compute/caiman.py +24 -27
  4. {labdata-0.1.1 → labdata-0.1.2}/labdata/dashboard/decisiontask.py +98 -81
  5. {labdata-0.1.1 → labdata-0.1.2}/labdata/dashboard/segmentation.py +248 -23
  6. labdata-0.1.2/labdata/schema/twophoton.py +368 -0
  7. {labdata-0.1.1 → labdata-0.1.2}/labdata.egg-info/PKG-INFO +1 -1
  8. labdata-0.1.1/labdata/schema/twophoton.py +0 -203
  9. {labdata-0.1.1 → labdata-0.1.2}/LICENSE +0 -0
  10. {labdata-0.1.1 → labdata-0.1.2}/README.md +0 -0
  11. {labdata-0.1.1 → labdata-0.1.2}/labdata/cli.py +0 -0
  12. {labdata-0.1.1 → labdata-0.1.2}/labdata/compute/__init__.py +0 -0
  13. {labdata-0.1.1 → labdata-0.1.2}/labdata/compute/ec2.py +0 -0
  14. {labdata-0.1.1 → labdata-0.1.2}/labdata/compute/ephys.py +0 -0
  15. {labdata-0.1.1 → labdata-0.1.2}/labdata/compute/pose.py +0 -0
  16. {labdata-0.1.1 → labdata-0.1.2}/labdata/compute/schedulers.py +0 -0
  17. {labdata-0.1.1 → labdata-0.1.2}/labdata/compute/singularity.py +0 -0
  18. {labdata-0.1.1 → labdata-0.1.2}/labdata/compute/suite2p.py +0 -0
  19. {labdata-0.1.1 → labdata-0.1.2}/labdata/compute/utils.py +0 -0
  20. {labdata-0.1.1 → labdata-0.1.2}/labdata/copy.py +0 -0
  21. {labdata-0.1.1 → labdata-0.1.2}/labdata/dashboard/__init__.py +0 -0
  22. {labdata-0.1.1 → labdata-0.1.2}/labdata/dashboard/compute.py +0 -0
  23. {labdata-0.1.1 → labdata-0.1.2}/labdata/dashboard/console.py +0 -0
  24. {labdata-0.1.1 → labdata-0.1.2}/labdata/dashboard/explorer/__init__.py +0 -0
  25. {labdata-0.1.1 → labdata-0.1.2}/labdata/dashboard/explorer/cellsegmentation.py +0 -0
  26. {labdata-0.1.1 → labdata-0.1.2}/labdata/dashboard/explorer/spikesorting.py +0 -0
  27. {labdata-0.1.1 → labdata-0.1.2}/labdata/dashboard/index.py +0 -0
  28. {labdata-0.1.1 → labdata-0.1.2}/labdata/dashboard/notes.py +0 -0
  29. {labdata-0.1.1 → labdata-0.1.2}/labdata/dashboard/procedures.py +0 -0
  30. {labdata-0.1.1 → labdata-0.1.2}/labdata/dashboard/sorting.py +0 -0
  31. {labdata-0.1.1 → labdata-0.1.2}/labdata/dashboard/video.py +0 -0
  32. {labdata-0.1.1 → labdata-0.1.2}/labdata/export.py +0 -0
  33. {labdata-0.1.1 → labdata-0.1.2}/labdata/rules/__init__.py +0 -0
  34. {labdata-0.1.1 → labdata-0.1.2}/labdata/rules/ephys.py +0 -0
  35. {labdata-0.1.1 → labdata-0.1.2}/labdata/rules/imaging.py +0 -0
  36. {labdata-0.1.1 → labdata-0.1.2}/labdata/rules/utils.py +0 -0
  37. {labdata-0.1.1 → labdata-0.1.2}/labdata/s3.py +0 -0
  38. {labdata-0.1.1 → labdata-0.1.2}/labdata/schema/__init__.py +0 -0
  39. {labdata-0.1.1 → labdata-0.1.2}/labdata/schema/ephys.py +0 -0
  40. {labdata-0.1.1 → labdata-0.1.2}/labdata/schema/general.py +0 -0
  41. {labdata-0.1.1 → labdata-0.1.2}/labdata/schema/histology.py +0 -0
  42. {labdata-0.1.1 → labdata-0.1.2}/labdata/schema/onephoton.py +0 -0
  43. {labdata-0.1.1 → labdata-0.1.2}/labdata/schema/procedures.py +0 -0
  44. {labdata-0.1.1 → labdata-0.1.2}/labdata/schema/tasks.py +0 -0
  45. {labdata-0.1.1 → labdata-0.1.2}/labdata/schema/utils.py +0 -0
  46. {labdata-0.1.1 → labdata-0.1.2}/labdata/schema/video.py +0 -0
  47. {labdata-0.1.1 → labdata-0.1.2}/labdata/stacks.py +0 -0
  48. {labdata-0.1.1 → labdata-0.1.2}/labdata/utils.py +0 -0
  49. {labdata-0.1.1 → labdata-0.1.2}/labdata/widgets.py +0 -0
  50. {labdata-0.1.1 → labdata-0.1.2}/labdata.egg-info/SOURCES.txt +0 -0
  51. {labdata-0.1.1 → labdata-0.1.2}/labdata.egg-info/dependency_links.txt +0 -0
  52. {labdata-0.1.1 → labdata-0.1.2}/labdata.egg-info/entry_points.txt +0 -0
  53. {labdata-0.1.1 → labdata-0.1.2}/labdata.egg-info/requires.txt +0 -0
  54. {labdata-0.1.1 → labdata-0.1.2}/labdata.egg-info/top_level.txt +0 -0
  55. {labdata-0.1.1 → labdata-0.1.2}/pyproject.toml +0 -0
  56. {labdata-0.1.1 → labdata-0.1.2}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: labdata
3
- Version: 0.1.1
3
+ Version: 0.1.2
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.1"
1
+ VERSION = "0.1.2"
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','gSiz','merge_thr','rf','stride','tsub',
25
+ self.parameter_keys = ['pw_rigid','p','gSig','gSig_filt','max_shift','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,12 +50,18 @@ 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',
54
+ action='store', default=[20,20], type = int, nargs = 2,
55
+ help = "maximum allowed shift for motion correction")
53
56
  parser.add_argument('-p',
54
57
  action='store', default=1, type = int,
55
58
  help = "Order of the autoregressive system")
56
59
  parser.add_argument('-g','--gsig',
57
60
  action='store', default=[6,6], type = int, nargs = 2,
58
61
  help = "Expected halfwidth of the neurons in pixels")
62
+ parser.add_argument('--gsig-filt',
63
+ action='store', default=None, type = int, nargs = 2,
64
+ help = "Filter for 1photon data")
59
65
  parser.add_argument('--nb',
60
66
  action='store', default=0, type = int,
61
67
  help = "number of background components (rank) if positive, set to 0 for CNMFE")
@@ -113,8 +119,10 @@ class CaimanCompute(Suite2pCompute):
113
119
  args = parser.parse_args(arguments[1:])
114
120
 
115
121
  params = dict(pw_rigid = args.pwrigid,
122
+ max_shifts = args.max_allowed_shifts,
116
123
  p = int(args.p),
117
124
  gSig = [int(a) for a in args.gsig],
125
+ gSig_filt = [int(a) for a in args.gsig_filt],
118
126
  gSiz = [int(a) for a in 2*np.array(args.gsig) + 1],
119
127
  merge_thr = float(args.merge_thr),
120
128
  rf = int(args.rf),
@@ -143,18 +151,18 @@ class CaimanCompute(Suite2pCompute):
143
151
  temporary_folder = Path(prefs['scratch_path'])/f'caiman_temporary_{rand}'
144
152
  dset = (self.schema.Miniscope() & self.dataset_key)
145
153
  cnmfparams = {
146
- 'motion_correct' : True,
147
- 'method_init': 'corr_pnr', # use this for 1 photon
148
- 'K': None, # for 1p
149
- 'nb': 0, # number of background components (rank) if positive, set to 0 for CNMFE
150
- 'nb_patch': 0,
151
- 'low_rank_background': None, # for 1p
152
- 'update_background_components': True, # sometimes setting to False improve the results
153
- 'del_duplicates': True, # whether to remove duplicates from initialization
154
- 'normalize_init': False, # just leave as is
155
- 'center_psf': True, # True for 1p
156
- 'only_init': True, # set it to True to run CNMF-E
157
- 'method_deconvolution': 'oasis'} # could use 'cvxpy' alternatively
154
+ 'motion_correct' : True,
155
+ 'method_init': 'corr_pnr', # use this for 1 photon
156
+ 'K': None, # for 1p
157
+ 'nb': 0, # number of background components (rank) if positive, set to 0 for CNMFE
158
+ 'nb_patch': 0,
159
+ 'low_rank_background': None, # for 1p
160
+ 'update_background_components': True, # sometimes setting to False improve the results
161
+ 'del_duplicates': True, # whether to remove duplicates from initialization
162
+ 'normalize_init': False, # just leave as is
163
+ 'center_psf': True, # True for 1p
164
+ 'only_init': True, # set it to True to run CNMF-E
165
+ 'method_deconvolution': 'oasis'} # could use 'cvxpy' alternatively
158
166
 
159
167
  if not len(dset):
160
168
  dset = (self.schema.TwoPhoton() & self.dataset_key)
@@ -244,7 +252,7 @@ class CaimanCompute(Suite2pCompute):
244
252
  print(f"Number accepted: {len(cnmfe_model.estimates.idx_components)}")
245
253
  print(f"Number rejected: {len(cnmfe_model.estimates.idx_components_bad)}")
246
254
  cluster.terminate()
247
- from ..stacks import compute_projections
255
+ from ..stacks import compute_projections,get_roi_pixels,get_roi_contour
248
256
  mean_proj,std_proj,max_proj,corr_proj = compute_projections(images)
249
257
  print('Projections computed.')
250
258
  import caiman
@@ -330,19 +338,8 @@ class CaimanCompute(Suite2pCompute):
330
338
  os.unlink(f)
331
339
  else:
332
340
  print(f'[{self.name} job] Kept the temporary folder {temporary_folder}.')
333
-
334
- def get_roi_contour(roi_mask,percentile_threshold = 80):
335
- from skimage.measure import find_contours
336
- # find_contours?
337
- level = np.percentile(roi_mask[roi_mask>0], percentile_threshold)
338
- C = find_contours(roi_mask,level = level) # there should be only one contour, this takes the first one
339
- return C[0]
340
-
341
- def get_roi_pixels(roi_mask):
342
- ii = np.ravel_multi_index(np.where(roi_mask!=0), roi_mask.shape)
343
- return ii, np.take(roi_mask,ii)
344
-
345
-
341
+ CellSegmentationMetrics().populate(planekey, display_progress = True)
342
+
346
343
  def setup_cluster(n_cpus,cluster = None):
347
344
  from caiman import stop_server, cluster
348
345
  #%% start a cluster for parallel processing (if a cluster already exists it will be closed and a new session will be opened)
@@ -46,7 +46,7 @@ axis = alt.Axis(domain=True, domainColor='black', domainCap='butt',
46
46
 
47
47
  def compute_psychometric(trialset_rows):
48
48
  """Proportion of rightward responses per condition per trialset with Wilson CI.
49
- response_values convention: left=1, no response=0, right=-1.
49
+ response_values convention: side1=1, no response=0, side2=-1.
50
50
  """
51
51
  from scipy.stats import binomtest
52
52
  rows = []
@@ -73,44 +73,58 @@ def compute_psychometric(trialset_rows):
73
73
  trialset_description=desc))
74
74
  return pd.DataFrame(rows)
75
75
 
76
-
77
76
  def decisionmaking_tab(schema = None):
78
77
  @st.cache_data
79
- def get_subjects_last_ran():
78
+ def get_max_date():
79
+ try:
80
+ return pd.to_datetime(
81
+ (schema.DecisionTask.TrialSet() * schema.Session()).fetch('session_datetime')
82
+ ).max().date()
83
+ except:
84
+ return None
85
+
86
+ @st.cache_data
87
+ def get_subjects_last_ran(end_date):
88
+ date_str = end_date.strftime('%Y-%m-%d')
80
89
  subs = schema.Subject & schema.DecisionTask
81
90
  try:
82
- max_date = (subs).aggr(schema.Session,ss='max(session_datetime)').fetch('ss').max().strftime('%Y-%m-%d')
91
+ tt = (schema.DecisionTask() * schema.Session() &
92
+ f'session_datetime > (DATE("{date_str}") - INTERVAL 3 MONTH)' &
93
+ f'DATE(session_datetime) <= "{date_str}"')
94
+ subjects = (schema.Subject() & tt).fetch(format='frame').reset_index()
95
+ subjects = np.unique(subjects.subject_name.values)
83
96
  except:
84
- print('There are no sessions to max.')
85
- return [],[],None
86
- tt = (schema.DecisionTask()*schema.Session &
87
- f'session_datetime > (DATE("{max_date}") - INTERVAL 3 MONTH)')
88
- subjects = (schema.Subject() & tt).fetch(format = 'frame').reset_index()
89
- subjects = np.unique(subjects.subject_name.values)
90
- keys = [dict(subject_name = s) for s in subjects]
91
- return subs.fetch('subject_name'), keys, max_date
97
+ print('There are no sessions.')
98
+ return [], []
99
+ keys = [dict(subject_name=s) for s in subjects]
100
+ return subs.fetch('subject_name'), keys
92
101
 
93
102
  @st.cache_data
94
- def get_summary_data(selected_indices,max_date):
95
- keys = [dict(subject_name = i) for i in selected_indices]
96
- max_date = schema.Subject.aggr(schema.Session & keys,ss='max(session_datetime)').fetch('ss').max().strftime('%Y-%m-%d')
97
- ses = (schema.Session()*schema.DecisionTask.TrialSet() & keys & f'session_datetime > (DATE("{max_date}") - INTERVAL 3 MONTH)').proj().fetch(as_dict = True)
98
-
99
- from datetime import timedelta
103
+ def get_summary_data(selected_indices, end_date):
104
+ keys = [dict(subject_name=i) for i in selected_indices]
105
+ date_str = end_date.strftime('%Y-%m-%d')
106
+ date_filter = (f'session_datetime > (DATE("{date_str}") - INTERVAL 3 MONTH) '
107
+ f'AND DATE(session_datetime) <= "{date_str}"')
100
108
  summarysource = pd.DataFrame((schema.Session*schema.DecisionTask.TrialSet() & keys & 'trialset_description NOT LIKE "%opto%"'
101
- & f'session_datetime > (DATE("{max_date}") - INTERVAL 3 MONTH)').fetch())
109
+ & date_filter).fetch())
102
110
  if not len(summarysource):
103
111
  return []
104
- days = np.array([(summarysource.session_datetime.max() - timedelta(days=d)).date() for d in range(40,-2,-1)])
105
112
  summarysource['date'] = summarysource.session_datetime.map(
106
113
  lambda x: x.strftime("%y-%m-%d"))
107
114
  return summarysource
108
115
 
109
- if st.button('Refresh', icon=':material/refresh:'):
110
- get_subjects_last_ran.clear()
111
- get_summary_data.clear()
116
+ ctrl_col, date_col, _ = st.columns([1, 2, 6])
117
+ with ctrl_col:
118
+ if st.button('Refresh', icon=':material/refresh:'):
119
+ get_max_date.clear()
120
+ get_subjects_last_ran.clear()
121
+ get_summary_data.clear()
122
+ with date_col:
123
+ end_date = st.date_input('End date', value=get_max_date(), label_visibility='collapsed')
112
124
 
113
- all_subjects,subjects,max_date = get_subjects_last_ran()
125
+ if end_date is None:
126
+ return
127
+ all_subjects, subjects = get_subjects_last_ran(end_date)
114
128
  if not len(subjects):
115
129
  st.write('There are no sessions.')
116
130
  return
@@ -118,10 +132,12 @@ def decisionmaking_tab(schema = None):
118
132
  selected_indices = st.multiselect('Select subjects:',all_subjects,default = [s['subject_name'] for s in subjects])
119
133
  if len(selected_indices):
120
134
  # cache data to run faster
121
- summarysource = get_summary_data(selected_indices,max_date)
135
+ summarysource = get_summary_data(selected_indices, end_date)
122
136
  if not len(summarysource):
123
137
  st.write('Could not find sessions for the specified dates..')
124
138
  return
139
+
140
+ # Build heatmap chart
125
141
  point_selector = alt.selection_point(on="click")
126
142
  chart = alt.Chart(summarysource).mark_rect().encode(
127
143
  x=alt.X('session_datetime:T',timeUnit = 'yearmonthdate',
@@ -130,9 +146,13 @@ def decisionmaking_tab(schema = None):
130
146
  color=alt.Color('performance_easy:Q',
131
147
  scale=alt.Scale(scheme='spectral', reverse=True, domain=[0.5,1], clamp=True)).title('Perf easy'),
132
148
  tooltip = ['subject_name:N','session_name:N','session_datetime:T','performance_easy:Q','performance:Q','n_trials:Q']
149
+ ).properties(title ='Behavioral performance').add_params(point_selector)
150
+
151
+ # Heatmap (session selector) and psychometric curve side-by-side
152
+ col_map, col_psych = st.columns([3, 2])
133
153
 
134
- ).properties(width=1000, title ='Behavioral performance').add_params(point_selector)
135
- event_data = st.altair_chart(chart.interactive(), on_select='rerun')
154
+ with col_map:
155
+ event_data = st.altair_chart(chart.interactive(), on_select='rerun', width='stretch')
136
156
 
137
157
  if len(event_data["selection"]["param_1"]):
138
158
  subject_name = event_data["selection"]["param_1"][0]["subject_name"]
@@ -148,7 +168,6 @@ def decisionmaking_tab(schema = None):
148
168
  st.session_state['decision_last_clicked_session'] = new_session
149
169
  except Exception:
150
170
  pass
151
- st.write(f'### _{subject_name}_')
152
171
 
153
172
  @st.cache_resource
154
173
  def get_subject_data(subject_name):
@@ -164,10 +183,6 @@ def decisionmaking_tab(schema = None):
164
183
  & dict(subject_name=subject_name, session_name=session_name)
165
184
  ).fetch(*cols, as_dict=True)
166
185
 
167
- if st.button('Refresh sessions', icon=':material/refresh:'):
168
- get_subject_data.clear()
169
- get_session_trials.clear()
170
-
171
186
  trialset, weights, watering = get_subject_data(subject_name)
172
187
  sessions = sorted(trialset['session_datetime'].unique())
173
188
 
@@ -182,7 +197,7 @@ def decisionmaking_tab(schema = None):
182
197
  except Exception:
183
198
  pass
184
199
 
185
- # resolve selected session before columns so selected_dt is available everywhere
200
+ # resolve selected session before rendering so selected_dt is available everywhere
186
201
  session_names = trialset[['session_datetime','session_name']].drop_duplicates('session_name').sort_values('session_datetime')
187
202
  snames = list(session_names['session_name'])
188
203
  clicked_session = st.session_state.pop('decision_selected_session', None)
@@ -196,46 +211,19 @@ def decisionmaking_tab(schema = None):
196
211
  selected_session = snames[session_idx]
197
212
  selected_dt = session_names.loc[session_names['session_name'] == selected_session, 'session_datetime'].iloc[0]
198
213
 
199
- col_summary, _, col_psych = st.columns([2, 0.15, 1])
200
-
201
- with col_summary:
202
- black, orange = '#000000','#ff7f0e'
203
- perf_val = trialset.dropna(subset=['performance_easy','performance']).sort_values('session_datetime').drop_duplicates('session_datetime').iloc[[-1]]
204
- x_enc = alt.X('session_datetime:T', axis=axis).title('Session date')
205
- perf_chart = (
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:N','session_datetime:T','performance_easy:Q','performance:Q']) +
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
- alt.Chart(trialset).mark_line(color=orange).encode(x=x_enc, y=alt.Y('performance:Q', axis=axis)) +
210
- alt.Chart(trialset).mark_point(color=orange).encode(x=x_enc, y=alt.Y('performance:Q'),
211
- tooltip=['session_name:N','session_datetime:T','performance_easy:Q','performance:Q']) +
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
- )
214
- ruler_df = pd.DataFrame({'t': [selected_dt]})
215
- def make_ruler(x_field):
216
- return alt.Chart(ruler_df).mark_rule(
217
- color='grey', strokeDash=[10, 10], strokeWidth=1.5
218
- ).encode(x=alt.X(f't:T'))
219
-
220
- chartw = line_point_plot(weights,
221
- x=alt.X('weighing_datetime:T',axis=axis).title(''),
222
- y=alt.Y('weight:Q',axis=axis).title('Weight (g)'),
223
- tooltip=['weighing_datetime', 'weight'],
224
- color = colors[0])
225
- charth2o = line_point_plot(watering,
226
- x=alt.X('watering_datetime:T',axis=axis).title('Session date'),
227
- y=alt.Y('water_volume:Q',axis=axis).title('Water volume (mL)'),
228
- tooltip=['watering_datetime', 'water_volume'],
229
- color = colors[2])
230
- linked = alt.vconcat(
231
- perf_chart + make_ruler('session_datetime'),
232
- chartw + make_ruler('weighing_datetime'),
233
- charth2o + make_ruler('watering_datetime'),
234
- ).resolve_scale(x='shared')
235
- st.altair_chart(linked.interactive(), width="stretch")
214
+ with col_map:
215
+ if st.button('Refresh sessions', icon=':material/refresh:'):
216
+ get_subject_data.clear()
217
+ get_session_trials.clear()
236
218
 
219
+ # Psychometric curve and reaction time in right column
237
220
  with col_psych:
238
-
221
+ name_col, slider_col = st.columns([1, 3])
222
+ name_col.write(f'### _{subject_name}_')
223
+ if len(snames) > 1:
224
+ with slider_col:
225
+ st.slider('Session', min_value=0, max_value=len(snames)-1,
226
+ key='decision_session_slider')
239
227
  trial_rows = get_session_trials(subject_name, selected_session)
240
228
  psych_df = compute_psychometric(trial_rows)
241
229
  if not psych_df.empty:
@@ -258,8 +246,8 @@ def decisionmaking_tab(schema = None):
258
246
  )
259
247
  lines = base.mark_line().encode(y=alt.Y('proportion:Q'))
260
248
  st.altair_chart((lines + errorbars + pts).properties(
261
- width=400, height=500, title=f'{pd.Timestamp(selected_dt).strftime("%Y-%m-%d %H:%M")}'
262
- ).interactive(), width="stretch")
249
+ width=400, height=400, title=f'{pd.Timestamp(selected_dt).strftime("%Y-%m-%d %H:%M")}'
250
+ ).interactive(), width='stretch')
263
251
 
264
252
  # reaction time per condition
265
253
  rt_rows = []
@@ -298,15 +286,11 @@ def decisionmaking_tab(schema = None):
298
286
  )
299
287
  rt_lines = rt_base.mark_line().encode(y=alt.Y('median_rt:Q'))
300
288
  st.altair_chart((rt_lines + rt_err + rt_pts).properties(
301
- width=400, height=300, title='Reaction time per condition'
302
- ).interactive(), width="stretch")
289
+ width=400, height=400, title='Reaction time per condition'
290
+ ).interactive(), width='stretch')
303
291
  else:
304
292
  st.error('No data available for this session.')
305
293
 
306
- if len(snames) > 1:
307
- st.slider('Session', min_value=0, max_value=len(snames)-1,
308
- key='decision_session_slider')
309
-
310
294
  @st.cache_data
311
295
  def get_session_notes(subject_name, session_name):
312
296
  rows = (schema.Dataset * schema.Note
@@ -320,11 +304,44 @@ def decisionmaking_tab(schema = None):
320
304
  st.caption(f"*{n['notetaker']} — {pd.Timestamp(n['note_datetime']).strftime('%Y-%m-%d %H:%M')}*")
321
305
  st.write(n['notes'])
322
306
 
323
- def line_point_plot(data, x, y, tooltip, color):
307
+ # Performance and watering charts under the colormap in the left column (80% width)
308
+ with col_map:
309
+ perf_col, _ = st.columns([4, 1])
310
+ black, orange = '#000000','#ff7f0e'
311
+ perf_val = trialset.dropna(subset=['performance_easy','performance']).sort_values('session_datetime').drop_duplicates('session_datetime').iloc[[-1]]
312
+ x_enc = alt.X('session_datetime:T', axis=axis).title('Session date')
313
+ perf_chart = (
314
+ alt.Chart(trialset).mark_line(color=black).encode(x=x_enc, y=alt.Y('performance_easy:Q', axis=axis).title('Performance')).properties(width=400, height=150) +
315
+ 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']) +
316
+ 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')) +
317
+ alt.Chart(trialset).mark_line(color=orange).encode(x=x_enc, y=alt.Y('performance:Q', axis=axis)) +
318
+ alt.Chart(trialset).mark_point(color=orange).encode(x=x_enc, y=alt.Y('performance:Q'),
319
+ tooltip=['session_name:N','session_datetime:T','performance_easy:Q','performance:Q']) +
320
+ 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'))
321
+ )
322
+ chartw = line_point_plot(weights,
323
+ x=alt.X('weighing_datetime:T',axis=axis).title(''),
324
+ y=alt.Y('weight:Q',axis=axis).title('Weight (g)'),
325
+ tooltip=['weighing_datetime', 'weight'],
326
+ color=colors[0], height=150)
327
+ charth2o = line_point_plot(watering,
328
+ x=alt.X('watering_datetime:T',axis=axis).title('Session date'),
329
+ y=alt.Y('water_volume:Q',axis=axis).title('Water volume (mL)'),
330
+ tooltip=['watering_datetime', 'water_volume'],
331
+ color=colors[2], height=150)
332
+ linked = alt.vconcat(
333
+ perf_chart,
334
+ chartw,
335
+ charth2o,
336
+ ).resolve_scale(x='shared')
337
+ with perf_col:
338
+ st.altair_chart(linked.interactive(), width='stretch')
339
+
340
+ def line_point_plot(data, x, y, tooltip, color, height=150):
324
341
  if data is None or not len(data):
325
342
  return alt.Chart(pd.DataFrame()).mark_point()
326
343
  scatter = alt.Chart(data).mark_point(color=color).encode(
327
344
  x=x, y=y, tooltip=tooltip)
328
345
  line = alt.Chart(data).mark_line(color=color).encode(
329
- x=x, y=y).properties(width=1000)
346
+ x=x, y=y).properties(width=400, height=height)
330
347
  return scatter + line