np-workflows 1.6.87__py3-none-any.whl → 1.6.91__py3-none-any.whl

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 (76) hide show
  1. np_workflows/__init__.py +3 -5
  2. np_workflows/experiments/dynamic_routing/main.py +20 -41
  3. np_workflows/experiments/dynamic_routing/widgets.py +29 -24
  4. np_workflows/experiments/openscope_P3/P3_workflow_widget.py +75 -0
  5. np_workflows/experiments/openscope_P3/__init__.py +2 -0
  6. np_workflows/experiments/openscope_P3/main_P3_pilot.py +215 -0
  7. np_workflows/experiments/openscope_barcode/__init__.py +1 -1
  8. np_workflows/experiments/openscope_barcode/barcode_workflow_widget.py +14 -20
  9. np_workflows/experiments/openscope_barcode/camstim_scripts/barcode_mapping_script.py +8 -14
  10. np_workflows/experiments/openscope_barcode/camstim_scripts/barcode_opto_script.py +121 -68
  11. np_workflows/experiments/openscope_barcode/main_barcode_pilot.py +69 -69
  12. np_workflows/experiments/openscope_loop/__init__.py +1 -1
  13. np_workflows/experiments/openscope_loop/camstim_scripts/barcode_mapping_script.py +8 -14
  14. np_workflows/experiments/openscope_loop/camstim_scripts/barcode_opto_script.py +121 -68
  15. np_workflows/experiments/openscope_loop/loop_workflow_widget.py +11 -19
  16. np_workflows/experiments/openscope_loop/main_loop_pilot.py +66 -68
  17. np_workflows/experiments/openscope_psycode/__init__.py +1 -1
  18. np_workflows/experiments/openscope_psycode/main_psycode_pilot.py +69 -69
  19. np_workflows/experiments/openscope_psycode/psycode_workflow_widget.py +14 -20
  20. np_workflows/experiments/openscope_v2/__init__.py +2 -0
  21. np_workflows/experiments/openscope_v2/main_v2_pilot.py +215 -0
  22. np_workflows/experiments/openscope_v2/v2_workflow_widget.py +75 -0
  23. np_workflows/experiments/openscope_vippo/__init__.py +1 -1
  24. np_workflows/experiments/openscope_vippo/main_vippo_pilot.py +66 -68
  25. np_workflows/experiments/openscope_vippo/vippo_workflow_widget.py +14 -20
  26. np_workflows/experiments/task_trained_network/__init__.py +1 -1
  27. np_workflows/experiments/task_trained_network/camstim_scripts/make_tt_stims.py +24 -14
  28. np_workflows/experiments/task_trained_network/camstim_scripts/oct22_tt_stim_script.py +54 -41
  29. np_workflows/experiments/task_trained_network/camstim_scripts/ttn_main_script.py +19 -22
  30. np_workflows/experiments/task_trained_network/camstim_scripts/ttn_mapping_script.py +8 -14
  31. np_workflows/experiments/task_trained_network/camstim_scripts/ttn_opto_script.py +121 -68
  32. np_workflows/experiments/task_trained_network/main_ttn_pilot.py +73 -68
  33. np_workflows/experiments/task_trained_network/ttn_session_widget.py +11 -19
  34. np_workflows/experiments/task_trained_network/ttn_stim_config.py +23 -19
  35. np_workflows/experiments/templeton/main.py +18 -41
  36. np_workflows/experiments/templeton/widgets.py +26 -23
  37. np_workflows/shared/__init__.py +1 -1
  38. np_workflows/shared/base_experiments.py +430 -308
  39. np_workflows/shared/npxc.py +85 -53
  40. np_workflows/shared/widgets.py +374 -224
  41. {np_workflows-1.6.87.dist-info → np_workflows-1.6.91.dist-info}/METADATA +7 -21
  42. np_workflows-1.6.91.dist-info/RECORD +48 -0
  43. {np_workflows-1.6.87.dist-info → np_workflows-1.6.91.dist-info}/WHEEL +2 -1
  44. np_workflows-1.6.91.dist-info/entry_points.txt +2 -0
  45. np_workflows-1.6.91.dist-info/top_level.txt +1 -0
  46. np_workflows/assets/images/logo_np_hab.png +0 -0
  47. np_workflows/assets/images/logo_np_vis.png +0 -0
  48. np_workflows/experiments/task_trained_network/camstim_scripts/stims/densely_annotated_00.stim +0 -5
  49. np_workflows/experiments/task_trained_network/camstim_scripts/stims/densely_annotated_01.stim +0 -5
  50. np_workflows/experiments/task_trained_network/camstim_scripts/stims/densely_annotated_02.stim +0 -5
  51. np_workflows/experiments/task_trained_network/camstim_scripts/stims/densely_annotated_03.stim +0 -5
  52. np_workflows/experiments/task_trained_network/camstim_scripts/stims/densely_annotated_04.stim +0 -5
  53. np_workflows/experiments/task_trained_network/camstim_scripts/stims/densely_annotated_05.stim +0 -5
  54. np_workflows/experiments/task_trained_network/camstim_scripts/stims/densely_annotated_06.stim +0 -5
  55. np_workflows/experiments/task_trained_network/camstim_scripts/stims/densely_annotated_07.stim +0 -5
  56. np_workflows/experiments/task_trained_network/camstim_scripts/stims/densely_annotated_08.stim +0 -5
  57. np_workflows/experiments/task_trained_network/camstim_scripts/stims/densely_annotated_09.stim +0 -5
  58. np_workflows/experiments/task_trained_network/camstim_scripts/stims/densely_annotated_10.stim +0 -5
  59. np_workflows/experiments/task_trained_network/camstim_scripts/stims/densely_annotated_11.stim +0 -5
  60. np_workflows/experiments/task_trained_network/camstim_scripts/stims/densely_annotated_12.stim +0 -5
  61. np_workflows/experiments/task_trained_network/camstim_scripts/stims/densely_annotated_13.stim +0 -5
  62. np_workflows/experiments/task_trained_network/camstim_scripts/stims/densely_annotated_14.stim +0 -5
  63. np_workflows/experiments/task_trained_network/camstim_scripts/stims/densely_annotated_15.stim +0 -5
  64. np_workflows/experiments/task_trained_network/camstim_scripts/stims/densely_annotated_16.stim +0 -5
  65. np_workflows/experiments/task_trained_network/camstim_scripts/stims/densely_annotated_17.stim +0 -5
  66. np_workflows/experiments/task_trained_network/camstim_scripts/stims/densely_annotated_18.stim +0 -5
  67. np_workflows/experiments/task_trained_network/camstim_scripts/stims/flash_250ms.stim +0 -20
  68. np_workflows/experiments/task_trained_network/camstim_scripts/stims/gabor_20_deg_250ms.stim +0 -30
  69. np_workflows/experiments/task_trained_network/camstim_scripts/stims/old_stim.stim +0 -5
  70. np_workflows/experiments/task_trained_network/camstim_scripts/stims/shuffle_reversed.stim +0 -5
  71. np_workflows/experiments/task_trained_network/camstim_scripts/stims/shuffle_reversed_1st.stim +0 -5
  72. np_workflows/experiments/task_trained_network/camstim_scripts/stims/shuffle_reversed_2nd.stim +0 -5
  73. np_workflows/shared/camstim_scripts/flash_250ms.stim +0 -20
  74. np_workflows/shared/camstim_scripts/gabor_20_deg_250ms.stim +0 -30
  75. np_workflows-1.6.87.dist-info/RECORD +0 -70
  76. np_workflows-1.6.87.dist-info/entry_points.txt +0 -4
@@ -21,17 +21,20 @@ import np_workflows.shared.npxc as npxc
21
21
 
22
22
  logger = np_logging.getLogger(__name__)
23
23
 
24
- np_logging.getLogger('Comm').propagate = False
25
- np_logging.getLogger('PIL').propagate = False
24
+ np_logging.getLogger("Comm").propagate = False
25
+ np_logging.getLogger("PIL").propagate = False
26
26
 
27
27
  global_state = {}
28
28
  """Global variable for persisting widget states."""
29
29
 
30
+
30
31
  def elapsed_time_widget() -> IPython.display.DisplayHandle | None:
31
32
  """Displays a clock showing the elapsed time since the cell was first run."""
32
33
 
33
34
  clock_widget = ipw.Label("")
34
- reminder_widget = ipw.Label("Remember to restart JupyterLab and run update.bat before every experiment!")
35
+ reminder_widget = ipw.Label(
36
+ "Remember to restart JupyterLab and run update.bat before every experiment!"
37
+ )
35
38
  global start_time
36
39
  if "start_time" not in globals():
37
40
  start_time = time.time()
@@ -44,11 +47,13 @@ def elapsed_time_widget() -> IPython.display.DisplayHandle | None:
44
47
  elapsed_sec = time.time() - start_time
45
48
  hours, remainder = divmod(elapsed_sec, 3600)
46
49
  minutes, seconds = divmod(remainder, 60)
47
- clock_widget.value = "Elapsed time: {:02}h {:02}m {:02}s".format(
48
- int(hours), int(minutes), int(seconds)
50
+ clock_widget.value = (
51
+ f"Elapsed time: {int(hours):02}h {int(minutes):02}m {int(seconds):02}s"
49
52
  )
50
53
  if hours > 4: # ipywidgets >= 8.0
51
- clock_widget.style = dict(text_color="red",)
54
+ clock_widget.style = dict(
55
+ text_color="red",
56
+ )
52
57
  time.sleep(0.2)
53
58
 
54
59
  thread = threading.Thread(target=update_timer, args=())
@@ -61,12 +66,14 @@ def user_and_mouse_widget() -> tuple[np_session.User, np_session.Mouse]:
61
66
  user_description = "User:"
62
67
  mouse_description = "Mouse:"
63
68
  user_widget = ipw.Select(options=npxc.lims_user_ids, description=user_description)
64
- mouse_widget = ipw.Text(value=str(npxc.default_mouse_id), description=mouse_description)
65
- for widget, string in zip((user_widget, mouse_widget), ('user', 'mouse')):
66
- if (selected := global_state.get(f'selected_{string}')):
69
+ mouse_widget = ipw.Text(
70
+ value=str(npxc.default_mouse_id), description=mouse_description
71
+ )
72
+ for widget, string in zip((user_widget, mouse_widget), ("user", "mouse")):
73
+ if selected := global_state.get(f"selected_{string}"):
67
74
  widget.value = selected
68
75
  with console:
69
- print(f'Current {string}: {selected}')
76
+ print(f"Current {string}: {selected}")
70
77
  user = np_session.User(str(user_widget.value))
71
78
  mouse = np_session.Mouse(str(mouse_widget.value))
72
79
 
@@ -74,7 +81,7 @@ def user_and_mouse_widget() -> tuple[np_session.User, np_session.Mouse]:
74
81
  if str(user) == (new := str(new_user).strip()):
75
82
  return
76
83
  user.__init__(new)
77
- global_state['selected_user'] = new
84
+ global_state["selected_user"] = new
78
85
  with console:
79
86
  print(f"User updated: {user}")
80
87
 
@@ -83,26 +90,28 @@ def user_and_mouse_widget() -> tuple[np_session.User, np_session.Mouse]:
83
90
  return
84
91
  if len(new) < 6:
85
92
  return
86
- global_state['selected_mouse'] = new
93
+ global_state["selected_mouse"] = new
87
94
  mouse.__init__(new)
88
95
  with console:
89
96
  print(f"Mouse updated: {mouse}")
90
-
97
+
91
98
  def new_value(change) -> None:
92
- if change['name'] != 'value':
99
+ if change["name"] != "value":
93
100
  return
94
- if (options := getattr(change['owner'], 'options', None)) and change['new'] not in options:
101
+ if (options := getattr(change["owner"], "options", None)) and change[
102
+ "new"
103
+ ] not in options:
95
104
  return
96
- if change['new'] == change['old']:
105
+ if change["new"] == change["old"]:
97
106
  return
98
- if (desc := getattr(change['owner'], 'description')) == user_description:
99
- update_user(change['new'])
107
+ if (desc := change["owner"].description) == user_description:
108
+ update_user(change["new"])
100
109
  elif desc == mouse_description:
101
- update_mouse(change['new'])
102
-
110
+ update_mouse(change["new"])
111
+
103
112
  user_widget.observe(new_value)
104
113
  mouse_widget.observe(new_value)
105
-
114
+
106
115
  IPython.display.display(ipw.VBox([user_widget, mouse_widget, console]))
107
116
  return user, mouse
108
117
 
@@ -110,8 +119,7 @@ def user_and_mouse_widget() -> tuple[np_session.User, np_session.Mouse]:
110
119
  def mtrain_widget(
111
120
  labtracks_mouse_id: str | int | np_session.Mouse,
112
121
  ) -> IPython.display.DisplayHandle | None:
113
- """Displays a widget to view and edit MTrain regimen/stage for a mouse.
114
- """
122
+ """Displays a widget to view and edit MTrain regimen/stage for a mouse."""
115
123
  if not isinstance(labtracks_mouse_id, np_session.Mouse):
116
124
  mtrain = np_session.MTrain(labtracks_mouse_id)
117
125
  else:
@@ -162,9 +170,9 @@ def mtrain_widget(
162
170
  reset_update_button()
163
171
  if change["new"] is None:
164
172
  return
165
- if change["new"] != stage_label.value or str(
166
- regimen_dropdown.value
167
- ) != str(regimen_label.value):
173
+ if change["new"] != stage_label.value or str(regimen_dropdown.value) != str(
174
+ regimen_label.value
175
+ ):
168
176
  # enable button if stage name changed, or regimen name changed (some
169
177
  # regimens have the same stage names as other regimens)
170
178
  update_button.disabled = False
@@ -218,20 +226,24 @@ def mtrain_widget(
218
226
 
219
227
  return IPython.display.display(display)
220
228
 
229
+
221
230
  def check_widget(check: str, *checks: str) -> ipw.Widget:
222
231
  layout = ipw.Layout(min_width="600px")
223
- widget = ipw.VBox([
224
- ipw.Label(check, layout=layout),
225
- *(ipw.Checkbox(description=_, layout=layout) for _ in checks),
226
- # ipw.Button(description="Continue", disabled=True)
227
- ])
232
+ widget = ipw.VBox(
233
+ [
234
+ ipw.Label(check, layout=layout),
235
+ *(ipw.Checkbox(description=_, layout=layout) for _ in checks),
236
+ # ipw.Button(description="Continue", disabled=True)
237
+ ]
238
+ )
228
239
  return widget
229
240
 
241
+
230
242
  def await_all_checkboxes(widget: ipw.Box) -> None:
231
243
  while any(_.value is False for _ in widget.children if isinstance(_, ipw.Checkbox)):
232
244
  time.sleep(0.1)
233
-
234
-
245
+
246
+
235
247
  def check_openephys_widget() -> None:
236
248
  check = "OpenEphys checks:"
237
249
  checks = (
@@ -242,7 +254,8 @@ def check_openephys_widget() -> None:
242
254
  )
243
255
  IPython.display.display(widget := check_widget(check, *checks))
244
256
 
245
- def check_hardware_widget() -> None:
257
+
258
+ def check_hardware_widget() -> None:
246
259
  check = "Stage checks:"
247
260
  checks = (
248
261
  "Cartridge raised (fully retract probes before raising!)",
@@ -252,181 +265,273 @@ def check_hardware_widget() -> None:
252
265
  )
253
266
  IPython.display.display(widget := check_widget(check, *checks))
254
267
 
268
+
255
269
  def check_mouse_widget() -> None:
256
270
  check = "Mouse checks before lowering cartridge:"
257
271
  checks = (
258
272
  "Stabilization screw",
259
- ("Silicon oil applied" if npxc.RIG.idx == 0 else "Quickcast removed, agarose applied"),
273
+ (
274
+ "Silicon oil applied"
275
+ if npxc.RIG.idx == 0
276
+ else "Quickcast removed, agarose applied"
277
+ ),
260
278
  "Tail cone down",
261
279
  "Continuity/Resistance check",
262
280
  )
263
281
  IPython.display.display(widget := check_widget(check, *checks))
264
282
 
283
+
265
284
  def pre_stim_check_widget() -> None:
266
285
  check = "Before running stim:"
267
286
  checks = (
268
287
  "Behavior cameras are in focus",
269
- "Eye-tracking mirror in place",
288
+ "Eye-tracking mirror in place",
270
289
  "Windows minimized on Stim computer (Win+D)",
271
290
  "Monitor closed",
272
291
  "Photodoc light off",
273
- "Curtain down",
292
+ "Curtain down",
274
293
  )
275
294
  IPython.display.display(widget := check_widget(check, *checks))
276
-
295
+
296
+
277
297
  def finishing_checks_widget() -> None:
278
298
  check = "Finishing checks:"
279
299
  checks = (
280
300
  "Add quickcast etc.",
281
- "Remove and water mouse",
282
- "Dip probes",
301
+ "Remove and water mouse",
302
+ "Dip probes",
283
303
  )
284
304
  IPython.display.display(widget := check_widget(check, *checks))
285
-
286
-
287
- def wheel_height_widget(session: np_session.PipelineSession) -> IPython.display.DisplayHandle | None:
305
+
306
+
307
+ def wheel_height_widget(
308
+ session: np_session.PipelineSession,
309
+ ) -> IPython.display.DisplayHandle | None:
288
310
  "Saves wheel height to platform_json and stores in `mouse.state['wheel_height']`."
289
-
290
- layout = ipw.Layout(max_width='130px')
291
-
292
- prev_height = session.mouse.state.get('wheel_height', 0)
293
- height_counter = ipw.BoundedFloatText(value=prev_height, min=0, max=10, step=0.1, description="Wheel height", layout=layout)
294
- save_button = ipw.Button(description='Save', button_style='warning', layout=layout)
311
+
312
+ layout = ipw.Layout(max_width="130px")
313
+
314
+ prev_height = session.mouse.state.get("wheel_height", 0)
315
+ height_counter = ipw.BoundedFloatText(
316
+ value=prev_height,
317
+ min=0,
318
+ max=10,
319
+ step=0.1,
320
+ description="Wheel height",
321
+ layout=layout,
322
+ )
323
+ save_button = ipw.Button(description="Save", button_style="warning", layout=layout)
295
324
 
296
325
  def on_click(b):
297
326
  session.platform_json.wheel_height = height_counter.value
298
- session.mouse.state['wheel_height'] = height_counter.value
299
- save_button.button_style = 'success'
300
- save_button.description = 'Saved'
327
+ session.mouse.state["wheel_height"] = height_counter.value
328
+ save_button.button_style = "success"
329
+ save_button.description = "Saved"
330
+
301
331
  save_button.on_click(on_click)
302
- return IPython.display.display(ipw.VBox([height_counter,save_button]))
303
-
332
+ return IPython.display.display(ipw.VBox([height_counter, save_button]))
333
+
304
334
 
305
- def di_widget(session: np_session.PipelineSession) -> IPython.display.DisplayHandle | None:
335
+ def di_widget(
336
+ session: np_session.PipelineSession,
337
+ ) -> IPython.display.DisplayHandle | None:
306
338
  "Supply a path or a platform json instance. Saves a JSON file with the dye used in the session and a timestamp."
307
-
339
+
308
340
  di_info: dict[str, int | str] = dict(
309
- EndTime=0, StartTime=npxc.now(), dii_description="", times_dipped=0, previous_uses="",
341
+ EndTime=0,
342
+ StartTime=npxc.now(),
343
+ dii_description="",
344
+ times_dipped=0,
345
+ previous_uses="",
310
346
  )
311
347
  di_info.update(session.platform_json.DiINotes)
312
-
313
- layout = ipw.Layout(max_width='180px')
314
- dipped_counter = ipw.IntText(value=0, min=0, max=99, description="Dipped count", layout=layout)
315
- usage_counter = ipw.IntText(value=0, min=0, max=99, description="Previous uses", layout=layout)
316
- dye_dropdown = ipw.Dropdown(options=['CM-DiI 100%', 'DiO'], layout=layout)
317
- save_button = ipw.Button(description='Save', button_style='warning', layout=layout)
318
-
348
+
349
+ layout = ipw.Layout(max_width="180px")
350
+ dipped_counter = ipw.IntText(
351
+ value=0, min=0, max=99, description="Dipped count", layout=layout
352
+ )
353
+ usage_counter = ipw.IntText(
354
+ value=0, min=0, max=99, description="Previous uses", layout=layout
355
+ )
356
+ dye_dropdown = ipw.Dropdown(options=["CM-DiI 100%", "DiO"], layout=layout)
357
+ save_button = ipw.Button(description="Save", button_style="warning", layout=layout)
358
+
319
359
  def update_di_info():
320
- di_info['EndTime'] = npxc.now()
321
- di_info['times_dipped'] = str(dipped_counter.value)
322
- di_info['dii_description'] = str(dye_dropdown.value)
323
- di_info['previous_uses'] = str(usage_counter.value)
324
-
360
+ di_info["EndTime"] = npxc.now()
361
+ di_info["times_dipped"] = str(dipped_counter.value)
362
+ di_info["dii_description"] = str(dye_dropdown.value)
363
+ di_info["previous_uses"] = str(usage_counter.value)
364
+
325
365
  def on_click(b):
326
366
  update_di_info()
327
367
  session.platform_json.DiINotes = di_info
328
- save_button.description = 'Saved'
329
- save_button.button_style = 'success'
330
-
368
+ save_button.description = "Saved"
369
+ save_button.button_style = "success"
370
+
331
371
  save_button.on_click(on_click)
332
- return IPython.display.display(ipw.VBox([
333
- dipped_counter, dye_dropdown,
334
- usage_counter, save_button]))
372
+ return IPython.display.display(
373
+ ipw.VBox([dipped_counter, dye_dropdown, usage_counter, save_button])
374
+ )
335
375
 
336
-
337
- def dye_info_widget(session: np_session.PipelineSession) -> IPython.display.DisplayHandle | None:
376
+
377
+ def dye_info_widget(
378
+ session: np_session.PipelineSession,
379
+ ) -> IPython.display.DisplayHandle | None:
338
380
  """
339
381
  - scan barcode or enter ID number for the dye used
340
382
  - change dye description if incorrect (DiI, DiO)
341
383
  - increment number of times probes were dipped this session
342
384
  - hit `Save` to store info in platform.json
343
385
  """
344
-
386
+
345
387
  di_info: dict[str, int | str] = dict(
346
- EndTime=0, StartTime=npxc.now(), dii_description="", times_dipped=0, previous_uses="",
388
+ EndTime=0,
389
+ StartTime=npxc.now(),
390
+ dii_description="",
391
+ times_dipped=0,
392
+ previous_uses="",
347
393
  )
348
394
  di_info.update(session.platform_json.DiINotes)
349
-
395
+
350
396
  def width(w):
351
- return ipw.Layout(max_width=f'{w}px')
352
-
353
- dye_id_entry = ipw.Text(value=None, description='Dye ID', layout=width(250), placeholder='Enter ID or scan barcode')
354
- ipw.Button(description='Record single use', button_style='warning', layout=width(180))
355
- first_usage = ipw.Text(value='', description="First use", layout=width(250), disabled=True)
356
- dye_dropdown = ipw.Dropdown(description="Description:", options=np_session.Dye.descriptions, layout=width(180))
357
- dipped_counter = ipw.IntText(value=int(di_info['times_dipped'] or 0), min=0, max=99, description="Dipped count", layout=width(150))
358
- usage_counter = ipw.IntText(value=int(di_info['previous_uses'] or 0), min=0, max=99, description="Previous uses", layout=width(180), disabled=True)
359
- save_button = ipw.Button(description='Save', button_style='warning', layout=width(180))
360
- if (desc := di_info['dii_description']) in np_session.Dye.descriptions:
397
+ return ipw.Layout(max_width=f"{w}px")
398
+
399
+ dye_id_entry = ipw.Text(
400
+ value=None,
401
+ description="Dye ID",
402
+ layout=width(250),
403
+ placeholder="Enter ID or scan barcode",
404
+ )
405
+ ipw.Button(
406
+ description="Record single use", button_style="warning", layout=width(180)
407
+ )
408
+ first_usage = ipw.Text(
409
+ value="", description="First use", layout=width(250), disabled=True
410
+ )
411
+ dye_dropdown = ipw.Dropdown(
412
+ description="Description:",
413
+ options=np_session.Dye.descriptions,
414
+ layout=width(180),
415
+ )
416
+ dipped_counter = ipw.IntText(
417
+ value=int(di_info["times_dipped"] or 0),
418
+ min=0,
419
+ max=99,
420
+ description="Dipped count",
421
+ layout=width(150),
422
+ )
423
+ usage_counter = ipw.IntText(
424
+ value=int(di_info["previous_uses"] or 0),
425
+ min=0,
426
+ max=99,
427
+ description="Previous uses",
428
+ layout=width(180),
429
+ disabled=True,
430
+ )
431
+ save_button = ipw.Button(
432
+ description="Save", button_style="warning", layout=width(180)
433
+ )
434
+ if (desc := di_info["dii_description"]) in np_session.Dye.descriptions:
361
435
  dye_dropdown.value = desc
362
-
436
+
363
437
  def update_display(_):
364
438
  dye = np_session.Dye(int(str(dye_id_entry.value)))
365
439
  dye_dropdown.value = dye.description
366
440
  usage_counter.value = dye.previous_uses
367
- first_usage.value = f'{dye.first_use}'
368
- dye_id_entry.observe(update_display, 'value')
369
-
441
+ first_usage.value = f"{dye.first_use}"
442
+
443
+ dye_id_entry.observe(update_display, "value")
444
+
370
445
  def record_dye_usage():
371
446
  dye = np_session.Dye(int(str(dye_id_entry.value)))
372
447
  dye.description = dye_dropdown.value
373
448
  dye.increment_uses()
374
-
449
+
375
450
  def update_di_info():
376
- di_info['EndTime'] = npxc.now()
377
- di_info['times_dipped'] = str(dipped_counter.value)
378
- di_info['dii_description'] = str(dye_dropdown.value)
379
- di_info['previous_uses'] = str(usage_counter.value)
380
-
451
+ di_info["EndTime"] = npxc.now()
452
+ di_info["times_dipped"] = str(dipped_counter.value)
453
+ di_info["dii_description"] = str(dye_dropdown.value)
454
+ di_info["previous_uses"] = str(usage_counter.value)
455
+
381
456
  def on_click(b):
382
457
  update_di_info()
383
458
  record_dye_usage()
384
459
  session.platform_json.DiINotes = di_info
385
- save_button.description = 'Saved'
386
- save_button.button_style = 'success'
387
-
460
+ save_button.description = "Saved"
461
+ save_button.button_style = "success"
462
+
388
463
  save_button.on_click(on_click)
389
- return IPython.display.display(ipw.VBox([
390
- dye_id_entry,
391
- dipped_counter, dye_dropdown,
392
- usage_counter, first_usage, save_button]))
464
+ return IPython.display.display(
465
+ ipw.VBox(
466
+ [
467
+ dye_id_entry,
468
+ dipped_counter,
469
+ dye_dropdown,
470
+ usage_counter,
471
+ first_usage,
472
+ save_button,
473
+ ]
474
+ )
475
+ )
476
+
393
477
 
394
478
  def dye_widget(session_folder: pathlib.Path) -> IPython.display.DisplayHandle | None:
395
479
  "Supply a path - saves a JSON file with the dye used in the session and a timestamp."
396
480
 
397
481
  dict(
398
- EndTime=0, StartTime=0, dii_description="DiI", times_dipped=0,
482
+ EndTime=0,
483
+ StartTime=0,
484
+ dii_description="DiI",
485
+ times_dipped=0,
399
486
  )
400
-
487
+
401
488
  class DyeRecorder(np_services.JsonRecorder):
402
- log_name = f'{session_folder.name}_dye.json'
489
+ log_name = f"{session_folder.name}_dye.json"
403
490
  log_root = session_folder
404
491
 
405
- dye_dropdown = ipw.Dropdown(options=['DiI', 'DiO'])
406
- save_button = ipw.Button(description='Save', button_style='warning')
492
+ dye_dropdown = ipw.Dropdown(options=["DiI", "DiO"])
493
+ save_button = ipw.Button(description="Save", button_style="warning")
494
+
407
495
  def on_click(b):
408
- DyeRecorder.write(dict(dye=dye_dropdown.value, datetime=datetime.datetime.now(), time=time.time()))
409
- save_button.button_style = 'success'
410
- save_button.description = 'Saved'
496
+ DyeRecorder.write(
497
+ dict(
498
+ dye=dye_dropdown.value,
499
+ datetime=datetime.datetime.now(),
500
+ time=time.time(),
501
+ )
502
+ )
503
+ save_button.button_style = "success"
504
+ save_button.description = "Saved"
505
+
411
506
  save_button.on_click(on_click)
412
507
  return IPython.display.display(ipw.VBox([dye_dropdown, save_button]))
413
508
 
414
- ISICoords = list[dict[Literal['x', 'y', 'z'], float]]
415
- ISISpaces = dict[Literal['image_space', 'reticle_space'], ISICoords | None]
416
- ISITargets = dict[Literal['insertion_targets', 'intended_insertion', 'actual_insertion'], ISISpaces]
509
+
510
+ ISICoords = list[dict[Literal["x", "y", "z"], float]]
511
+ ISISpaces = dict[Literal["image_space", "reticle_space"], ISICoords | None]
512
+ ISITargets = dict[
513
+ Literal["insertion_targets", "intended_insertion", "actual_insertion"], ISISpaces
514
+ ]
515
+
417
516
 
418
517
  def isi_targets(
419
518
  labtracks_mouse_id: str | int | np_session.LIMS2MouseInfo,
420
- )-> None | ISITargets:
421
- mouse = np_session.LIMS2MouseInfo(labtracks_mouse_id) if not isinstance(labtracks_mouse_id, np_session.LIMS2MouseInfo) else labtracks_mouse_id
519
+ ) -> None | ISITargets:
520
+ mouse = (
521
+ np_session.LIMS2MouseInfo(labtracks_mouse_id)
522
+ if not isinstance(labtracks_mouse_id, np_session.LIMS2MouseInfo)
523
+ else labtracks_mouse_id
524
+ )
422
525
  if (exp_id := mouse.isi_id) is None:
423
526
  return None
424
- exps = mouse.isi_info['isi_experiments']
425
- isi = [e for e in exps if e['id'] == exp_id]
426
- return isi[0]['targets'] if isi else None
427
-
527
+ exps = mouse.isi_info["isi_experiments"]
528
+ isi = [e for e in exps if e["id"] == exp_id]
529
+ return isi[0]["targets"] if isi else None
530
+
531
+
428
532
  def isi_widget(
429
- labtracks_mouse_id: str | int | np_session.LIMS2MouseInfo, colormap: bool = False,
533
+ labtracks_mouse_id: str | int | np_session.LIMS2MouseInfo,
534
+ colormap: bool = False,
430
535
  ) -> IPython.display.DisplayHandle | None:
431
536
  """Displays ISI target map from lims (contours only), or colormap overlay if
432
537
  `show_colormap = True`."""
@@ -434,13 +539,13 @@ def isi_widget(
434
539
  mouse_info = np_session.LIMS2MouseInfo(labtracks_mouse_id)
435
540
  else:
436
541
  mouse_info = labtracks_mouse_id
437
- mouse_info.fetch() # refresh in case targets were updated recently
542
+ mouse_info.fetch() # refresh in case targets were updated recently
438
543
 
439
544
  if colormap:
440
545
  key = "isi_image_overlay_path"
441
546
  else:
442
547
  key = "target_map_image_path"
443
-
548
+
444
549
  try:
445
550
  lims_path = mouse_info.isi_info[key]
446
551
  except ValueError:
@@ -457,30 +562,39 @@ def isi_widget(
457
562
  print(f"ISI map found for {mouse_info.np_id}:\n{path}")
458
563
  img = PIL.Image.open(path)
459
564
  if all_targets := isi_targets(mouse_info):
460
- colors = {'insertion_targets': 'red', 'intended_insertion': 'yellow', 'actual_insertion': 'blue'}
565
+ colors = {
566
+ "insertion_targets": "red",
567
+ "intended_insertion": "yellow",
568
+ "actual_insertion": "blue",
569
+ }
461
570
  for targets, spaces in all_targets.items():
462
- coords = spaces['image_space']
571
+ coords = spaces["image_space"]
463
572
  if coords is None:
464
573
  continue
465
574
  draw = PIL.ImageDraw.Draw(img)
466
- draw.line([(_['x'], _['y']) for _ in coords],
467
- fill=colors[targets],
468
- width=3)
469
- else:
470
- logger.debug("No ISI targets found for %r in lims, ISI experiment id %s", mouse_info, mouse_info.isi_id)
575
+ draw.line(
576
+ [(_["x"], _["y"]) for _ in coords], fill=colors[targets], width=3
577
+ )
578
+ else:
579
+ logger.debug(
580
+ "No ISI targets found for %r in lims, ISI experiment id %s",
581
+ mouse_info,
582
+ mouse_info.isi_id,
583
+ )
471
584
  ## displaying img directly no longer works (due to jupyterlab 4.0?)
472
585
  # return IPython.display.display(img)
473
586
  membuf = io.BytesIO()
474
- img.save(membuf, format="png")
587
+ img.save(membuf, format="png")
475
588
  return IPython.display.display(ipw.VBox([ipw.Image(value=membuf.getvalue())]))
476
-
477
589
 
478
590
 
479
591
  def insertion_notes_widget(session: np_session.PipelineSession):
480
-
481
- probes = 'ABCDEF'
592
+
593
+ probes = "ABCDEF"
594
+
482
595
  def probe(_):
483
- return f'Probe{_}'
596
+ return f"Probe{_}"
597
+
484
598
  fields = (
485
599
  "FailedToInsert",
486
600
  # "ProbeLocationChanged",
@@ -488,86 +602,110 @@ def insertion_notes_widget(session: np_session.PipelineSession):
488
602
  # "ProbeBendingElsewhere",
489
603
  )
490
604
  # "NumAgarInsertions",
491
-
605
+
492
606
  def get_notes(_):
493
- return session.platform_json.InsertionNotes.get(probe(_), {}).get('Notes', '')
607
+ return session.platform_json.InsertionNotes.get(probe(_), {}).get("Notes", "")
608
+
494
609
  def get_field(_, field):
495
610
  return session.platform_json.InsertionNotes.get(probe(_), {}).get(field, None)
496
-
497
- def disp_str(s): # split PascalCase fieldname into 'Title case' words
498
- matches = re.finditer('.+?(?:(?<=[a-z])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])|$)', s)
499
- return ' '.join([m.group(0) for m in matches]).lower().capitalize()
611
+
612
+ def disp_str(s): # split PascalCase fieldname into 'Title case' words
613
+ matches = re.finditer(
614
+ ".+?(?:(?<=[a-z])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])|$)", s
615
+ )
616
+ return " ".join([m.group(0) for m in matches]).lower().capitalize()
617
+
500
618
  def save_str(s):
501
- return ''.join([_.capitalize() for _ in s.split(' ')])
502
-
619
+ return "".join([_.capitalize() for _ in s.split(" ")])
620
+
503
621
  def row(*args):
504
622
  return ipw.HBox([*args])
623
+
505
624
  def probe_row(p):
506
- return row(ipw.Text(value=get_notes(p), placeholder='Insertion notes', description=disp_str(probe(p).strip('Probe ')), layout=ipw.Layout(width='auto', min_width='400px')), *(ipw.Checkbox(value=get_field(p, field), description=disp_str(field)) for field in fields))
507
- button = ipw.Button(description="Save", button_style='warning')
625
+ return row(
626
+ ipw.Text(
627
+ value=get_notes(p),
628
+ placeholder="Insertion notes",
629
+ description=disp_str(probe(p).strip("Probe ")),
630
+ layout=ipw.Layout(width="auto", min_width="400px"),
631
+ ),
632
+ *(
633
+ ipw.Checkbox(value=get_field(p, field), description=disp_str(field))
634
+ for field in fields
635
+ ),
636
+ )
637
+
638
+ button = ipw.Button(description="Save", button_style="warning")
508
639
  console = ipw.Output()
509
-
640
+
510
641
  rows = [probe_row(p) for p in probes]
511
642
  widget = ipw.VBox([*rows, button, console])
512
-
643
+
513
644
  def save(b):
514
645
  d = {}
515
646
  for letter, row in zip(probes, rows):
516
647
  p = d.get(probe(letter), {})
517
648
  for widget in row.children:
518
649
  if isinstance(widget, ipw.Text):
519
- p['Notes'] = widget.value
650
+ p["Notes"] = widget.value
520
651
  elif isinstance(widget, ipw.Checkbox):
521
652
  p[save_str(widget.description)] = widget.value
522
653
  else:
523
654
  continue
524
655
  if p:
525
- d[probe(letter)] = p
526
-
527
- session.platform_json.InsertionNotes = d
656
+ d[probe(letter)] = p
657
+
658
+ session.platform_json.InsertionNotes = d
528
659
  with console:
529
- print('Updated notes')
530
- button.button_style = 'success'
531
-
660
+ print("Updated notes")
661
+ button.button_style = "success"
662
+
532
663
  button.on_click(save)
533
664
  return IPython.display.display(widget)
534
665
 
535
666
 
536
667
  def probe_depth_widget(session: np_session.PipelineSession):
537
-
538
- probes = 'ABCDEF'
539
-
668
+
669
+ probes = "ABCDEF"
670
+
540
671
  def coords():
541
672
  return session.platform_json.manipulator_coordinates
542
-
673
+
543
674
  if not coords():
544
675
  logger.warning("No photodocs have been captured yet.")
545
-
676
+
546
677
  def probe_coords(img):
547
678
  return coords().get(img, dict.fromkeys(probes, dict(x=None, y=None, z=None)))
679
+
548
680
  def field_str(s):
549
- return '_'.join(s.split(' ')).lower() + '_surface_image' if s else ''
550
-
681
+ return "_".join(s.split(" ")).lower() + "_surface_image" if s else ""
682
+
551
683
  selection = ipw.ToggleButtons(
552
- options=[' '.join(_.strip('_surface_image').split('_')).capitalize() for _ in coords().keys()],
553
- description='Depth',
554
- disabled=False,
555
- button_style='', # 'success', 'info', 'warning', 'danger' or ''
556
- tooltips=[field_str(_) for _ in coords().keys()],
684
+ options=[
685
+ " ".join(_.strip("_surface_image").split("_")).capitalize()
686
+ for _ in coords().keys()
687
+ ],
688
+ description="Depth",
689
+ disabled=False,
690
+ button_style="", # 'success', 'info', 'warning', 'danger' or ''
691
+ tooltips=[field_str(_) for _ in coords().keys()],
557
692
  )
558
-
693
+
559
694
  def update(_):
560
695
  for probe in probes:
561
696
  depth = probe_coords(field_str(selection.value))[probe]["z"]
562
- textbox[probe].value = f'{depth:6.1f}' if depth is not None else ''
563
-
697
+ textbox[probe].value = f"{depth:6.1f}" if depth is not None else ""
698
+
564
699
  textbox = {
565
700
  probe: ipw.Text(
566
- value='', description=probe, disabled=True,
567
- layout=ipw.Layout(max_width='150px'),)
701
+ value="",
702
+ description=probe,
703
+ disabled=True,
704
+ layout=ipw.Layout(max_width="150px"),
705
+ )
568
706
  for probe in probes
569
707
  }
570
- selection.observe(update, 'value')
708
+ selection.observe(update, "value")
571
709
  update(None)
572
710
  widget = ipw.VBox([selection, ipw.HBox([*textbox.values()])])
573
711
  return IPython.display.display(widget)
@@ -575,96 +713,106 @@ def probe_depth_widget(session: np_session.PipelineSession):
575
713
 
576
714
  def photodoc_widget(img_name: str) -> IPython.display.DisplayHandle | None:
577
715
  "Captures and displays snapshot from image camera, appending `img_name` to the filename."
578
- image = ipw.Image(value=b'', format='png', width='80%', layout=ipw.Layout(visibility='hidden'))
579
- widget = ipw.VBox([
580
- image,
581
- button := ipw.Button(description="Re-capture", button_style='warning'),
582
- console := ipw.Output(),
583
- ])
584
-
716
+ image = ipw.Image(
717
+ value=b"", format="png", width="80%", layout=ipw.Layout(visibility="hidden")
718
+ )
719
+ widget = ipw.VBox(
720
+ [
721
+ image,
722
+ button := ipw.Button(description="Re-capture", button_style="warning"),
723
+ console := ipw.Output(),
724
+ ]
725
+ )
726
+
585
727
  def capture() -> pathlib.Path:
586
- image.value = b''
587
- image.layout.visibility = 'hidden'
588
- button.button_style = ''
589
- button.description = 'Capturing new image...'
728
+ image.value = b""
729
+ image.layout.visibility = "hidden"
730
+ button.button_style = ""
731
+ button.description = "Capturing new image..."
590
732
  button.disabled = True
591
733
  return npxc.photodoc(img_name)
592
-
734
+
593
735
  def disp(img_path) -> None:
594
736
  image.value = img_path.read_bytes()
595
- image.layout.visibility = 'visible'
596
- button.button_style = 'warning'
597
- button.description = 'Re-capture'
737
+ image.layout.visibility = "visible"
738
+ button.button_style = "warning"
739
+ button.description = "Re-capture"
598
740
  button.disabled = False
599
741
  with console:
600
742
  print(img_path)
601
-
743
+
602
744
  def capture_and_display(*args):
603
745
  disp(capture())
604
-
746
+
605
747
  button.on_click(capture_and_display)
606
-
607
- if (matches := [_ for _ in (np_services.Cam3d.data_files or np_services.ImageMVR.data_files or []) if img_name in _.stem]):
748
+
749
+ if matches := [
750
+ _
751
+ for _ in (np_services.Cam3d.data_files or np_services.ImageMVR.data_files or [])
752
+ if img_name in _.stem
753
+ ]:
608
754
  disp(sorted(matches)[-1])
609
755
  else:
610
756
  capture_and_display()
611
-
757
+
612
758
  return IPython.display.display(widget)
613
759
 
760
+
614
761
  def probe_targeting_widget(session_folder) -> IPython.display.DisplayHandle | None:
615
762
  from np_probe_targets.implant_drawing import CurrentWeek, DRWeeklyTargets
763
+
616
764
  CurrentWeek.display()
617
765
  IPython.display.display(DRWeeklyTargets())
618
-
766
+
767
+
619
768
  def quiet_mode_widget() -> IPython.display.DisplayHandle | None:
620
769
  """Displays a toggle button that switches logging level INFO <-> DEBUG and
621
770
  hides/shows tracebacks.
622
771
  """
623
772
  debug_mode_toggle = ipw.ToggleButton(
624
- value=True,
625
- description='Quiet mode is on',
626
- disabled=False,
627
- button_style='info', # 'success', 'info', 'warning', 'danger' or ''
628
- icon='check',
629
- tooltip='Quiet mode: tracebacks hidden, logging level set to INFO.',
630
- )
631
-
773
+ value=True,
774
+ description="Quiet mode is on",
775
+ disabled=False,
776
+ button_style="info", # 'success', 'info', 'warning', 'danger' or ''
777
+ icon="check",
778
+ tooltip="Quiet mode: tracebacks hidden, logging level set to INFO.",
779
+ )
780
+
632
781
  def set_debug_mode(value: bool) -> None:
633
782
  if value:
634
783
  npxc.show_tracebacks()
635
784
  for handler in np_logging.getLogger().handlers:
636
785
  if isinstance(handler, logging.StreamHandler):
637
- handler.setLevel('DEBUG')
786
+ handler.setLevel("DEBUG")
638
787
  else:
639
788
  npxc.hide_tracebacks()
640
789
  for handler in np_logging.getLogger().handlers:
641
790
  if isinstance(handler, logging.StreamHandler):
642
- handler.setLevel('INFO')
643
-
791
+ handler.setLevel("INFO")
792
+
644
793
  def on_click(b) -> None:
645
794
  if not debug_mode_toggle.value:
646
795
  set_debug_mode(True)
647
- debug_mode_toggle.description = 'Quiet mode is off'
648
- debug_mode_toggle.button_style = ''
649
- debug_mode_toggle.icon = 'times'
796
+ debug_mode_toggle.description = "Quiet mode is off"
797
+ debug_mode_toggle.button_style = ""
798
+ debug_mode_toggle.icon = "times"
650
799
  else:
651
800
  set_debug_mode(False)
652
- debug_mode_toggle.description = 'Quiet mode is on'
653
- debug_mode_toggle.button_style = 'info'
654
- debug_mode_toggle.icon = 'check'
655
-
801
+ debug_mode_toggle.description = "Quiet mode is on"
802
+ debug_mode_toggle.button_style = "info"
803
+ debug_mode_toggle.icon = "check"
804
+
656
805
  debug_mode_toggle.observe(on_click)
657
-
806
+
658
807
  return IPython.display.display(debug_mode_toggle)
659
808
 
660
809
 
661
810
  def task_select_widget(
662
811
  experiment,
663
812
  ) -> None:
664
- """Select a task name for controlling behavior of TaskControl.
665
- """
813
+ """Select a task name for controlling behavior of TaskControl."""
666
814
  experiment.task_name = experiment.preset_task_names[0]
667
-
815
+
668
816
  task_dropdown = ipw.Select(
669
817
  options=tuple(experiment.preset_task_names),
670
818
  description="Presets",
@@ -676,7 +824,7 @@ def task_select_widget(
676
824
  )
677
825
  console = ipw.Output()
678
826
  with console:
679
- if last_task:= experiment.mouse.state.get('last_task'):
827
+ if last_task := experiment.mouse.state.get("last_task"):
680
828
  print(f"{experiment.mouse} last task: {last_task}")
681
829
 
682
830
  def update(change):
@@ -698,8 +846,10 @@ def task_select_widget(
698
846
  task_dropdown.value = None
699
847
  with console:
700
848
  print(f"Updated task: {experiment.task_name}")
701
- task_dropdown.observe(update, names='value')
702
- task_input_box.observe(update, names='value')
703
849
 
704
- IPython.display.display(ipw.VBox([ipw.HBox([task_dropdown, task_input_box]), console]))
705
-
850
+ task_dropdown.observe(update, names="value")
851
+ task_input_box.observe(update, names="value")
852
+
853
+ IPython.display.display(
854
+ ipw.VBox([ipw.HBox([task_dropdown, task_input_box]), console])
855
+ )