TwoPy 0.1.0__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 (84) hide show
  1. twopy/__init__.py +168 -0
  2. twopy/_segments.py +53 -0
  3. twopy/analysis/__init__.py +101 -0
  4. twopy/analysis/background_subtraction.py +672 -0
  5. twopy/analysis/dff.py +524 -0
  6. twopy/analysis/epoch_mapping.py +223 -0
  7. twopy/analysis/motion.py +66 -0
  8. twopy/analysis/persistence.py +975 -0
  9. twopy/analysis/response_processing/__init__.py +55 -0
  10. twopy/analysis/response_processing/apply.py +298 -0
  11. twopy/analysis/response_processing/options.py +192 -0
  12. twopy/analysis/response_processing/qc.py +270 -0
  13. twopy/analysis/response_processing/signals.py +200 -0
  14. twopy/analysis/responses.py +345 -0
  15. twopy/analysis/trials.py +341 -0
  16. twopy/analysis/workflow.py +457 -0
  17. twopy/api.py +78 -0
  18. twopy/config.py +200 -0
  19. twopy/conversion/__init__.py +40 -0
  20. twopy/conversion/alignment_crop.py +329 -0
  21. twopy/conversion/api.py +113 -0
  22. twopy/conversion/frame_ranges.py +35 -0
  23. twopy/conversion/hdf5_writing.py +362 -0
  24. twopy/conversion/matlab_values.py +123 -0
  25. twopy/conversion/source_loading.py +403 -0
  26. twopy/conversion/stimulus_code.py +248 -0
  27. twopy/conversion/types.py +295 -0
  28. twopy/converted.py +681 -0
  29. twopy/database/__init__.py +33 -0
  30. twopy/database/cache.py +166 -0
  31. twopy/database/catalog.py +90 -0
  32. twopy/database/connection.py +27 -0
  33. twopy/database/generic.py +254 -0
  34. twopy/database/modeled.py +419 -0
  35. twopy/database/sql.py +46 -0
  36. twopy/database/types.py +116 -0
  37. twopy/inspection.py +492 -0
  38. twopy/matlab.py +398 -0
  39. twopy/napari/__init__.py +27 -0
  40. twopy/napari/__main__.py +10 -0
  41. twopy/napari/constants.py +13 -0
  42. twopy/napari/controls.py +739 -0
  43. twopy/napari/display.py +181 -0
  44. twopy/napari/display_paths.py +197 -0
  45. twopy/napari/interactive.py +310 -0
  46. twopy/napari/launcher.py +175 -0
  47. twopy/napari/layout.py +147 -0
  48. twopy/napari/loading.py +207 -0
  49. twopy/napari/movie.py +71 -0
  50. twopy/napari/paths.py +181 -0
  51. twopy/napari/plotting/__init__.py +21 -0
  52. twopy/napari/plotting/data.py +266 -0
  53. twopy/napari/plotting/docks.py +1052 -0
  54. twopy/napari/plotting/export.py +803 -0
  55. twopy/napari/plotting/export_controls.py +209 -0
  56. twopy/napari/plotting/label_visibility.py +260 -0
  57. twopy/napari/plotting/options.py +269 -0
  58. twopy/napari/plotting/panels.py +119 -0
  59. twopy/napari/plotting/widgets.py +767 -0
  60. twopy/napari/protocols.py +90 -0
  61. twopy/napari/responses.py +91 -0
  62. twopy/napari/roi.py +238 -0
  63. twopy/napari/session.py +381 -0
  64. twopy/napari/state.py +106 -0
  65. twopy/napari/text.py +26 -0
  66. twopy/napari/types.py +39 -0
  67. twopy/napari/viewer.py +405 -0
  68. twopy/parity/__init__.py +30 -0
  69. twopy/parity/dff.py +399 -0
  70. twopy/parity/roi.py +145 -0
  71. twopy/parity/saved_analysis.py +174 -0
  72. twopy/photodiode.py +375 -0
  73. twopy/photodiode_classification.py +484 -0
  74. twopy/roi.py +462 -0
  75. twopy/session.py +167 -0
  76. twopy/spatial.py +154 -0
  77. twopy/stimulus.py +233 -0
  78. twopy/synchronization.py +235 -0
  79. twopy/typing_guards.py +255 -0
  80. twopy-0.1.0.dist-info/METADATA +311 -0
  81. twopy-0.1.0.dist-info/RECORD +84 -0
  82. twopy-0.1.0.dist-info/WHEEL +5 -0
  83. twopy-0.1.0.dist-info/entry_points.txt +2 -0
  84. twopy-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,457 @@
1
+ """Script-facing workflow for ROI response analysis.
2
+
3
+ Inputs: a converted recording, ROI masks, and analysis parameters.
4
+ Outputs: computed traces, dF/F, grouped responses, and saved analysis files.
5
+
6
+ This module wires together existing twopy analysis helpers. It does not read
7
+ source microscope files; callers must convert recordings before using it.
8
+ """
9
+
10
+ from collections.abc import Sequence
11
+ from dataclasses import dataclass
12
+ from pathlib import Path
13
+
14
+ import numpy as np
15
+
16
+ from twopy.analysis.background_subtraction import (
17
+ BackgroundCorrectedRoiTraces,
18
+ BackgroundCorrectionMethod,
19
+ extract_background_corrected_roi_traces,
20
+ )
21
+ from twopy.analysis.dff import (
22
+ DeltaFOverFFitMode,
23
+ RoiDeltaFOverF,
24
+ compute_roi_delta_f_over_f,
25
+ )
26
+ from twopy.analysis.epoch_mapping import (
27
+ InterpolatedEpochMapping,
28
+ interpolate_stimulus_epochs_to_frame_windows,
29
+ )
30
+ from twopy.analysis.motion import apply_motion_artifact_mask_to_delta_f_over_f
31
+ from twopy.analysis.persistence import save_analysis_outputs
32
+ from twopy.analysis.responses import (
33
+ GroupedRoiResponses,
34
+ group_delta_f_over_f_by_epoch,
35
+ )
36
+ from twopy.analysis.trials import (
37
+ EpochFrameWindow,
38
+ FrameWindow,
39
+ select_epoch_frame_windows,
40
+ )
41
+ from twopy.converted import RecordingData, load_converted_recording
42
+ from twopy.roi import RoiSet, load_roi_set
43
+ from twopy.spatial import SpatialDomain
44
+
45
+ __all__ = [
46
+ "AnalysisResponseComputation",
47
+ "AnalysisResponseRun",
48
+ "DEFAULT_RESPONSE_POST_WINDOW_SECONDS",
49
+ "DEFAULT_RESPONSE_PRE_WINDOW_SECONDS",
50
+ "analyze_recording_responses",
51
+ "compute_recording_responses",
52
+ ]
53
+
54
+ DEFAULT_RESPONSE_PRE_WINDOW_SECONDS = 2.0
55
+ DEFAULT_RESPONSE_POST_WINDOW_SECONDS = 2.0
56
+
57
+
58
+ @dataclass(frozen=True)
59
+ class AnalysisResponseComputation:
60
+ """In-memory outputs from one response-analysis calculation.
61
+
62
+ Inputs: one converted recording and one ROI set.
63
+ Outputs: computed traces, dF/F, stimulus windows, and grouped responses.
64
+
65
+ This object lets interactive tools update plots without writing analysis
66
+ HDF5 files on every ROI edit. Persistence is a separate explicit step.
67
+ """
68
+
69
+ recording: RecordingData
70
+ roi_set: RoiSet
71
+ traces: BackgroundCorrectedRoiTraces
72
+ dff: RoiDeltaFOverF
73
+ epoch_mapping: InterpolatedEpochMapping | None
74
+ epoch_windows: tuple[EpochFrameWindow, ...]
75
+ interleave_windows: tuple[FrameWindow, ...]
76
+ grouped_responses: GroupedRoiResponses
77
+
78
+
79
+ @dataclass(frozen=True)
80
+ class AnalysisResponseRun(AnalysisResponseComputation):
81
+ """Outputs from one script-facing response-analysis run.
82
+
83
+ Inputs: one converted recording and one ROI set.
84
+ Outputs: computed analysis objects plus file paths written by the run.
85
+
86
+ Returning the objects lets scripts inspect results immediately while the
87
+ HDF5/CSV files keep the same run auditable and reloadable later.
88
+ """
89
+
90
+ output_path: Path
91
+ response_summary_trials_csv_path: Path | None
92
+ response_summary_grouped_csv_path: Path | None
93
+
94
+
95
+ def analyze_recording_responses(
96
+ recording_data_path: Path,
97
+ roi_set: RoiSet | Path,
98
+ *,
99
+ output_path: Path | None = None,
100
+ response_summary_trials_csv_path: Path | None = None,
101
+ response_summary_grouped_csv_path: Path | None = None,
102
+ write_summary_csv: bool = True,
103
+ epoch_windows: Sequence[EpochFrameWindow] | None = None,
104
+ interleave_epoch_number: int | None = 1,
105
+ interleave_epoch_name: str | None = None,
106
+ background_method: BackgroundCorrectionMethod = "movie_global_percentile",
107
+ seconds_interleave_use: float | None = 1.0,
108
+ fit_mode: DeltaFOverFFitMode = "robust",
109
+ apply_motion_mask: bool = True,
110
+ data_rate_hz: float | None = None,
111
+ chunk_frames: int = 128,
112
+ spatial_domain: SpatialDomain = "alignment_valid_crop",
113
+ response_pre_window_seconds: float = DEFAULT_RESPONSE_PRE_WINDOW_SECONDS,
114
+ response_post_window_seconds: float = 0.0,
115
+ ) -> AnalysisResponseRun:
116
+ """Run the standard ROI response analysis workflow.
117
+
118
+ Args:
119
+ recording_data_path: Path to converted ``recording_data.h5``.
120
+ roi_set: ``RoiSet`` object or path to a saved ROI HDF5 file.
121
+ output_path: Optional destination HDF5 path. When omitted, output is
122
+ written beside ``recording_data.h5`` as ``analysis_outputs.h5``.
123
+ response_summary_trials_csv_path: Optional trial-level CSV path. When
124
+ omitted and ``write_summary_csv`` is true, output is written under
125
+ ``exports/csvs/response_summary_trials.csv`` beside the HDF5 file.
126
+ response_summary_grouped_csv_path: Optional grouped CSV path. When
127
+ omitted and ``write_summary_csv`` is true, output is written under
128
+ ``exports/csvs/response_summary_grouped.csv`` beside the HDF5 file.
129
+ write_summary_csv: Whether to write response time-series CSV files.
130
+ epoch_windows: Optional explicit epoch windows. When omitted, twopy
131
+ interpolates epochs from converted stimulus data.
132
+ interleave_epoch_number: Optional baseline epoch number selector.
133
+ interleave_epoch_name: Optional baseline epoch name selector.
134
+ background_method: Background correction method before dF/F.
135
+ seconds_interleave_use: Seconds from the end of each interleave window.
136
+ fit_mode: dF/F exponential fit mode.
137
+ apply_motion_mask: Whether to mark converted high-motion frames as NaN
138
+ after dF/F.
139
+ data_rate_hz: Optional frame rate. When omitted, twopy reads
140
+ ``acq.frameRate`` from converted metadata.
141
+ chunk_frames: Number of movie frames to read per HDF5 chunk.
142
+ spatial_domain: Spatial domain used for trace extraction.
143
+ response_pre_window_seconds: Seconds before each stimulus window to
144
+ include in grouped responses. The default shows the transition from
145
+ gray interleave baseline into the stimulus.
146
+ response_post_window_seconds: Seconds after each stimulus window to
147
+ include in grouped responses. The default is zero so persisted
148
+ summaries keep their epoch-window meaning; plotting code can add
149
+ visual context separately from saved dF/F and epoch windows.
150
+
151
+ Returns:
152
+ ``AnalysisResponseRun`` with computed objects and output paths.
153
+
154
+ Raises:
155
+ ValueError: If no interleave windows match the selector.
156
+
157
+ This is the main script API for a converted recording: load, extract traces,
158
+ compute dF/F, group responses, and persist the results.
159
+ """
160
+ recording = load_converted_recording(recording_data_path)
161
+ loaded_roi_set = _resolve_roi_set(roi_set)
162
+ computation = compute_recording_responses(
163
+ recording,
164
+ loaded_roi_set,
165
+ epoch_windows=epoch_windows,
166
+ interleave_epoch_number=interleave_epoch_number,
167
+ interleave_epoch_name=interleave_epoch_name,
168
+ background_method=background_method,
169
+ seconds_interleave_use=seconds_interleave_use,
170
+ fit_mode=fit_mode,
171
+ apply_motion_mask=apply_motion_mask,
172
+ data_rate_hz=data_rate_hz,
173
+ chunk_frames=chunk_frames,
174
+ spatial_domain=spatial_domain,
175
+ response_pre_window_seconds=response_pre_window_seconds,
176
+ response_post_window_seconds=response_post_window_seconds,
177
+ )
178
+ resolved_output_path = _resolve_output_path(recording.path, output_path)
179
+ resolved_trials_summary_path = _resolve_summary_csv_path(
180
+ output_path=resolved_output_path,
181
+ requested_path=response_summary_trials_csv_path,
182
+ write_summary_csv=write_summary_csv,
183
+ filename="response_summary_trials.csv",
184
+ )
185
+ resolved_grouped_summary_path = _resolve_summary_csv_path(
186
+ output_path=resolved_output_path,
187
+ requested_path=response_summary_grouped_csv_path,
188
+ write_summary_csv=write_summary_csv,
189
+ filename="response_summary_grouped.csv",
190
+ )
191
+ save_analysis_outputs(
192
+ resolved_output_path,
193
+ roi_set=computation.roi_set,
194
+ traces=computation.traces,
195
+ dff=computation.dff,
196
+ epoch_windows=computation.epoch_windows,
197
+ grouped_responses=computation.grouped_responses,
198
+ response_summary_trials_csv=resolved_trials_summary_path,
199
+ response_summary_grouped_csv=resolved_grouped_summary_path,
200
+ )
201
+
202
+ return AnalysisResponseRun(
203
+ recording=computation.recording,
204
+ roi_set=computation.roi_set,
205
+ traces=computation.traces,
206
+ dff=computation.dff,
207
+ epoch_mapping=computation.epoch_mapping,
208
+ epoch_windows=computation.epoch_windows,
209
+ interleave_windows=computation.interleave_windows,
210
+ grouped_responses=computation.grouped_responses,
211
+ output_path=resolved_output_path,
212
+ response_summary_trials_csv_path=resolved_trials_summary_path,
213
+ response_summary_grouped_csv_path=resolved_grouped_summary_path,
214
+ )
215
+
216
+
217
+ def compute_recording_responses(
218
+ recording: RecordingData,
219
+ roi_set: RoiSet,
220
+ *,
221
+ epoch_windows: Sequence[EpochFrameWindow] | None = None,
222
+ interleave_epoch_number: int | None = 1,
223
+ interleave_epoch_name: str | None = None,
224
+ background_method: BackgroundCorrectionMethod = "movie_global_percentile",
225
+ seconds_interleave_use: float | None = 1.0,
226
+ fit_mode: DeltaFOverFFitMode = "robust",
227
+ apply_motion_mask: bool = True,
228
+ data_rate_hz: float | None = None,
229
+ chunk_frames: int = 128,
230
+ spatial_domain: SpatialDomain = "alignment_valid_crop",
231
+ response_pre_window_seconds: float = DEFAULT_RESPONSE_PRE_WINDOW_SECONDS,
232
+ response_post_window_seconds: float = 0.0,
233
+ ) -> AnalysisResponseComputation:
234
+ """Compute ROI responses from converted data without writing files.
235
+
236
+ Args:
237
+ recording: Loaded converted recording.
238
+ roi_set: ROI masks to extract from the recording.
239
+ epoch_windows: Optional explicit epoch windows. When omitted, twopy
240
+ interpolates epochs from converted stimulus data.
241
+ interleave_epoch_number: Optional baseline epoch number selector.
242
+ interleave_epoch_name: Optional baseline epoch name selector.
243
+ background_method: Background correction method before dF/F.
244
+ seconds_interleave_use: Seconds from the end of each interleave window.
245
+ fit_mode: dF/F exponential fit mode.
246
+ apply_motion_mask: Whether to mark converted high-motion frames as NaN
247
+ after dF/F.
248
+ data_rate_hz: Optional frame rate. When omitted, twopy reads
249
+ ``acq.frameRate`` from converted metadata.
250
+ chunk_frames: Number of movie frames to read per HDF5 chunk.
251
+ spatial_domain: Spatial domain used for trace extraction.
252
+ response_pre_window_seconds: Seconds before each stimulus window to
253
+ include in grouped responses.
254
+ response_post_window_seconds: Seconds after each stimulus window to
255
+ include in grouped responses.
256
+
257
+ Returns:
258
+ In-memory response-analysis objects.
259
+
260
+ Raises:
261
+ ValueError: If no interleave windows match the selector.
262
+
263
+ This is the shared calculation used by scripts, tests, and the napari live
264
+ plotter. It streams movie frames in chunks and leaves file writing to a
265
+ separate explicit call.
266
+ """
267
+ frame_rate_hz = (
268
+ _recording_frame_rate_hz(recording)
269
+ if data_rate_hz is None
270
+ else float(data_rate_hz)
271
+ )
272
+ mapping, resolved_epoch_windows = _resolve_epoch_windows(recording, epoch_windows)
273
+ trace_start, trace_stop = _frame_range_for_epoch_windows(
274
+ resolved_epoch_windows,
275
+ frame_count=recording.movie.shape[0],
276
+ data_rate_hz=frame_rate_hz,
277
+ pre_window_seconds=response_pre_window_seconds,
278
+ post_window_seconds=response_post_window_seconds,
279
+ )
280
+ interleave_windows = select_epoch_frame_windows(
281
+ resolved_epoch_windows,
282
+ epoch_name=interleave_epoch_name,
283
+ epoch_number=interleave_epoch_number,
284
+ )
285
+ if len(interleave_windows) == 0:
286
+ msg = "No interleave windows matched the requested selector"
287
+ raise ValueError(msg)
288
+
289
+ traces = extract_background_corrected_roi_traces(
290
+ recording,
291
+ roi_set,
292
+ method=background_method,
293
+ start_frame=trace_start,
294
+ stop_frame=trace_stop,
295
+ chunk_frames=chunk_frames,
296
+ spatial_domain=spatial_domain,
297
+ )
298
+ dff = compute_roi_delta_f_over_f(
299
+ traces,
300
+ interleave_windows,
301
+ data_rate_hz=frame_rate_hz,
302
+ seconds_interleave_use=seconds_interleave_use,
303
+ fit_mode=fit_mode,
304
+ )
305
+ if apply_motion_mask:
306
+ dff = apply_motion_artifact_mask_to_delta_f_over_f(dff, recording)
307
+
308
+ grouped_responses = group_delta_f_over_f_by_epoch(
309
+ dff,
310
+ resolved_epoch_windows,
311
+ data_rate_hz=frame_rate_hz,
312
+ pre_window_seconds=response_pre_window_seconds,
313
+ post_window_seconds=response_post_window_seconds,
314
+ )
315
+
316
+ return AnalysisResponseComputation(
317
+ recording=recording,
318
+ roi_set=roi_set,
319
+ traces=traces,
320
+ dff=dff,
321
+ epoch_mapping=mapping,
322
+ epoch_windows=resolved_epoch_windows,
323
+ interleave_windows=interleave_windows,
324
+ grouped_responses=grouped_responses,
325
+ )
326
+
327
+
328
+ def _resolve_roi_set(roi_set: RoiSet | Path) -> RoiSet:
329
+ """Return a ROI set from an object or HDF5 path.
330
+
331
+ Args:
332
+ roi_set: Existing ``RoiSet`` or path to one saved by ``save_roi_set``.
333
+
334
+ Returns:
335
+ ``RoiSet`` ready for trace extraction.
336
+ """
337
+ if isinstance(roi_set, Path):
338
+ return load_roi_set(roi_set)
339
+ return roi_set
340
+
341
+
342
+ def _resolve_epoch_windows(
343
+ recording: RecordingData,
344
+ epoch_windows: Sequence[EpochFrameWindow] | None,
345
+ ) -> tuple[InterpolatedEpochMapping | None, tuple[EpochFrameWindow, ...]]:
346
+ """Resolve epoch windows from caller input or converted stimulus data.
347
+
348
+ Args:
349
+ recording: Loaded converted recording.
350
+ epoch_windows: Optional explicit epoch windows.
351
+
352
+ Returns:
353
+ ``(mapping, windows)``. ``mapping`` is ``None`` when windows were passed
354
+ explicitly.
355
+ """
356
+ if epoch_windows is not None:
357
+ return None, tuple(epoch_windows)
358
+ mapping = interpolate_stimulus_epochs_to_frame_windows(recording)
359
+ return mapping, mapping.windows
360
+
361
+
362
+ def _frame_range_for_epoch_windows(
363
+ epoch_windows: Sequence[EpochFrameWindow],
364
+ *,
365
+ frame_count: int,
366
+ data_rate_hz: float,
367
+ pre_window_seconds: float,
368
+ post_window_seconds: float,
369
+ ) -> tuple[int, int]:
370
+ """Return the smallest frame range covering all epoch windows.
371
+
372
+ Args:
373
+ epoch_windows: Windows used for response analysis.
374
+ frame_count: Total aligned-movie frame count.
375
+ data_rate_hz: Imaging frame rate in hertz.
376
+ pre_window_seconds: Seconds before each epoch to include.
377
+ post_window_seconds: Seconds after each epoch to include.
378
+
379
+ Returns:
380
+ ``(start, stop)`` absolute movie-frame range.
381
+ """
382
+ if len(epoch_windows) == 0:
383
+ msg = "At least one epoch window is required for response analysis"
384
+ raise ValueError(msg)
385
+ pre_frames = int(round(pre_window_seconds * data_rate_hz))
386
+ post_frames = int(round(post_window_seconds * data_rate_hz))
387
+ starts = [
388
+ max(epoch_window.window.start_frame - pre_frames, 0)
389
+ for epoch_window in epoch_windows
390
+ ]
391
+ stops = [
392
+ min(epoch_window.window.stop_frame + post_frames, frame_count)
393
+ for epoch_window in epoch_windows
394
+ ]
395
+ return min(starts), max(stops)
396
+
397
+
398
+ def _recording_frame_rate_hz(recording: RecordingData) -> float:
399
+ """Read the imaging frame rate from converted metadata.
400
+
401
+ Args:
402
+ recording: Loaded converted recording.
403
+
404
+ Returns:
405
+ Frame rate in hertz.
406
+ """
407
+ try:
408
+ value = recording.acquisition_metadata["acq.frameRate"]
409
+ except KeyError as error:
410
+ msg = "Recording metadata is missing required field 'acq.frameRate'"
411
+ raise ValueError(msg) from error
412
+ if isinstance(value, str | bytes | int | float | np.generic):
413
+ return float(value)
414
+
415
+ value_type = type(value).__name__
416
+ msg = f"Recording metadata field 'acq.frameRate' must be numeric, got {value_type}"
417
+ raise ValueError(msg)
418
+
419
+
420
+ def _resolve_output_path(recording_data_path: Path, output_path: Path | None) -> Path:
421
+ """Resolve the analysis HDF5 output path.
422
+
423
+ Args:
424
+ recording_data_path: Converted recording manifest path.
425
+ output_path: Optional caller path.
426
+
427
+ Returns:
428
+ Expanded output path.
429
+ """
430
+ if output_path is not None:
431
+ return output_path.expanduser()
432
+ return recording_data_path.expanduser().parent / "analysis_outputs.h5"
433
+
434
+
435
+ def _resolve_summary_csv_path(
436
+ *,
437
+ output_path: Path,
438
+ requested_path: Path | None,
439
+ write_summary_csv: bool,
440
+ filename: str,
441
+ ) -> Path | None:
442
+ """Resolve the optional response time-series CSV path.
443
+
444
+ Args:
445
+ output_path: Analysis HDF5 output path.
446
+ requested_path: Optional caller path.
447
+ write_summary_csv: Whether a CSV should be written.
448
+ filename: Default CSV filename when the caller does not provide one.
449
+
450
+ Returns:
451
+ CSV path, or ``None`` when disabled.
452
+ """
453
+ if not write_summary_csv:
454
+ return None
455
+ if requested_path is not None:
456
+ return requested_path.expanduser()
457
+ return output_path.parent / "exports" / "csvs" / filename
twopy/api.py ADDED
@@ -0,0 +1,78 @@
1
+ """Public script-friendly API for twopy.
2
+
3
+ Inputs: simple Python arguments such as year, genotype, stimulus, and paths.
4
+ Outputs: typed twopy objects returned by lower-level modules.
5
+
6
+ This module is the stable place for scripts to import from. It keeps scripts away
7
+ from implementation details like SQLite table names.
8
+ """
9
+
10
+ from pathlib import Path
11
+
12
+ from twopy.config import DEFAULT_CONFIG_PATH, load_config
13
+ from twopy.database import DatabaseAccess, DatabaseExperiment
14
+ from twopy.database import find_recordings as _find_recordings
15
+
16
+ __all__ = ["find_recordings"]
17
+
18
+
19
+ def find_recordings(
20
+ *,
21
+ year: int | None = None,
22
+ month: int | None = None,
23
+ day: int | None = None,
24
+ genotype: str | None = None,
25
+ stimulus: str | None = None,
26
+ sensor: str | None = None,
27
+ cell_type: str | None = None,
28
+ hemisphere: str | None = None,
29
+ person: str | None = None,
30
+ database_access: DatabaseAccess | None = None,
31
+ database_cache_dir: Path | None = None,
32
+ limit: int = 100,
33
+ config_path: Path = DEFAULT_CONFIG_PATH,
34
+ ) -> tuple[DatabaseExperiment, ...]:
35
+ """Find recording experiments using the configured lab database path.
36
+
37
+ Args:
38
+ year: Optional experiment year.
39
+ month: Optional experiment month.
40
+ day: Optional experiment day-of-month.
41
+ genotype: Optional genotype substring.
42
+ stimulus: Optional stimulus-function substring.
43
+ sensor: Optional sensor substring from fly ``fluorescentProtein``.
44
+ cell_type: Optional cell-type substring from fly ``cellType``.
45
+ hemisphere: Optional exact fly-eye value, such as ``left`` or ``right``.
46
+ person: Optional experimenter substring from fly ``surgeon``.
47
+ database_access: Optional override for configured DB access. ``direct``
48
+ queries mounted DB files directly. ``copy`` copies DB files locally
49
+ because network DB queries can be slow while file transfer is fast.
50
+ database_cache_dir: Optional cache directory for local DB copies.
51
+ limit: Maximum number of recordings to return.
52
+ config_path: YAML config file containing ``database_path``.
53
+
54
+ Returns:
55
+ Typed recording experiment rows.
56
+
57
+ Example:
58
+ ``find_recordings(year=2023, month=10, genotype="gh146")``
59
+
60
+ The function loads ``config.yml`` by default so scripts can start with a
61
+ single import and call.
62
+ """
63
+ config = load_config(config_path)
64
+ return _find_recordings(
65
+ config.database_path,
66
+ year=year,
67
+ month=month,
68
+ day=day,
69
+ genotype=genotype,
70
+ stimulus=stimulus,
71
+ sensor=sensor,
72
+ cell_type=cell_type,
73
+ hemisphere=hemisphere,
74
+ person=person,
75
+ database_access=database_access or config.database_access,
76
+ cache_dir=database_cache_dir,
77
+ limit=limit,
78
+ )