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,90 @@
1
+ """Tiny napari protocols used by tests and GUI adapter code.
2
+
3
+ Inputs: napari viewer, window, and layer objects.
4
+ Outputs: structural types for the small subset of napari behavior twopy uses.
5
+
6
+ These protocols keep the adapter testable without importing or starting napari
7
+ in unit tests.
8
+ """
9
+
10
+ from typing import Protocol
11
+
12
+
13
+ class NapariWindow(Protocol):
14
+ """Small protocol for adding dock widgets to a napari viewer.
15
+
16
+ Inputs: widget objects.
17
+ Outputs: dock widgets owned by napari.
18
+ """
19
+
20
+ def add_dock_widget(
21
+ self,
22
+ widget: object,
23
+ *,
24
+ name: str,
25
+ area: str,
26
+ ) -> object:
27
+ """Add a dock widget to the viewer window.
28
+
29
+ Args:
30
+ widget: Widget object to dock.
31
+ name: Dock title shown by napari.
32
+ area: Napari dock area name.
33
+
34
+ Returns:
35
+ The dock widget created by napari.
36
+ """
37
+ ...
38
+
39
+
40
+ class NapariViewer(Protocol):
41
+ """Small viewer protocol used to keep tests independent from napari.
42
+
43
+ Inputs: image or label layer data.
44
+ Outputs: napari layer objects.
45
+ """
46
+
47
+ @property
48
+ def window(self) -> NapariWindow:
49
+ """Return the viewer window used to add dock widgets.
50
+
51
+ Inputs: viewer object.
52
+ Outputs: napari window object.
53
+ """
54
+ ...
55
+
56
+ def add_image(self, data: object, *, name: str, **kwargs: object) -> object:
57
+ """Add an image layer to the viewer.
58
+
59
+ Args:
60
+ data: Array-like image data.
61
+ name: Layer name shown in napari.
62
+ kwargs: napari image-layer options.
63
+
64
+ Returns:
65
+ The layer object created by napari.
66
+ """
67
+ ...
68
+
69
+ def add_labels(self, data: object, *, name: str, **kwargs: object) -> object:
70
+ """Add a labels layer to the viewer.
71
+
72
+ Args:
73
+ data: Two-dimensional integer label image.
74
+ name: Layer name shown in napari.
75
+ kwargs: napari label-layer options.
76
+
77
+ Returns:
78
+ The layer object created by napari.
79
+ """
80
+ ...
81
+
82
+
83
+ class NapariLayerWithData(Protocol):
84
+ """Small protocol for napari layers that expose array-like data.
85
+
86
+ Inputs: a napari layer object.
87
+ Outputs: access to its ``data`` attribute.
88
+ """
89
+
90
+ data: object
@@ -0,0 +1,91 @@
1
+ """Response-computation helpers for the twopy napari adapter.
2
+
3
+ Inputs: a loaded converted recording and the active napari Labels ROI layer.
4
+ Outputs: plot-ready response data computed from the current labels.
5
+
6
+ This module is the small boundary between GUI ROI editing and core analysis.
7
+ It translates editable Labels pixels into a ``RoiSet`` and then calls the
8
+ GUI-independent in-memory response workflow.
9
+ """
10
+
11
+ from pathlib import Path
12
+
13
+ import numpy as np
14
+
15
+ from twopy.analysis.workflow import compute_recording_responses
16
+ from twopy.converted import RecordingData
17
+ from twopy.napari.plotting.data import (
18
+ ResponsePlotData,
19
+ response_plot_data_from_grouped,
20
+ )
21
+ from twopy.napari.roi import roi_label_image_from_layer_for_recording
22
+ from twopy.roi import RoiSet, make_roi_set_from_label_image
23
+
24
+ __all__ = [
25
+ "compute_response_plot_data_from_labels",
26
+ "compute_response_plot_data_from_roi_set",
27
+ ]
28
+
29
+
30
+ def compute_response_plot_data_from_labels(
31
+ recording: RecordingData,
32
+ roi_labels_layer: object,
33
+ *,
34
+ source_path: Path | None = None,
35
+ ) -> ResponsePlotData:
36
+ """Compute plot-ready ROI responses from the current napari Labels layer.
37
+
38
+ Args:
39
+ recording: Loaded converted recording shown in napari.
40
+ roi_labels_layer: Active napari Labels layer containing ROI pixels.
41
+ source_path: Optional display path attached to the plot data.
42
+
43
+ Returns:
44
+ Plot-ready response means and SEMs grouped by stimulus epoch.
45
+
46
+ Raises:
47
+ ValueError: If the layer shape is not the crop-native display shape, if
48
+ no ROI labels are present, or if analysis inputs are invalid.
49
+
50
+ The helper deliberately does not save analysis files. Live ROI editing can
51
+ call it repeatedly without creating HDF5 churn; explicit save/export actions
52
+ remain separate user decisions.
53
+ """
54
+ label_image = roi_label_image_from_layer_for_recording(roi_labels_layer, recording)
55
+ if not np.any(label_image > 0):
56
+ msg = "No ROI labels to analyze."
57
+ raise ValueError(msg)
58
+
59
+ roi_set = make_roi_set_from_label_image(label_image)
60
+ return compute_response_plot_data_from_roi_set(
61
+ recording,
62
+ roi_set,
63
+ source_path=source_path,
64
+ )
65
+
66
+
67
+ def compute_response_plot_data_from_roi_set(
68
+ recording: RecordingData,
69
+ roi_set: RoiSet,
70
+ *,
71
+ source_path: Path | None = None,
72
+ ) -> ResponsePlotData:
73
+ """Compute plot-ready ROI responses from a prepared ROI set.
74
+
75
+ Args:
76
+ recording: Loaded converted recording shown in napari.
77
+ roi_set: ROI masks created from the current Labels layer.
78
+ source_path: Optional display path attached to the plot data.
79
+
80
+ Returns:
81
+ Plot-ready response means and SEMs grouped by stimulus epoch.
82
+
83
+ This helper receives a prepared ROI set so live napari updates can copy
84
+ label pixels on the GUI thread, then run the heavier movie streaming work in
85
+ a worker thread.
86
+ """
87
+ computation = compute_recording_responses(recording, roi_set)
88
+ return response_plot_data_from_grouped(
89
+ computation.grouped_responses,
90
+ source_path=source_path,
91
+ )
twopy/napari/roi.py ADDED
@@ -0,0 +1,238 @@
1
+ """ROI helpers for the twopy napari adapter.
2
+
3
+ Inputs: napari Labels layers, label images, ROI HDF5 files, and converted
4
+ recording metadata.
5
+ Outputs: twopy ``RoiSet`` objects, label images for display, and validated ROI
6
+ save/load paths.
7
+
8
+ This module is the only napari-facing layer that translates between editable
9
+ Labels data and core ROI masks.
10
+ """
11
+
12
+ from pathlib import Path
13
+ from typing import cast
14
+
15
+ import numpy as np
16
+ import numpy.typing as npt
17
+
18
+ from twopy.converted import RecordingData
19
+ from twopy.napari.display import (
20
+ display_image_from_movie_image,
21
+ display_labels_from_movie_labels,
22
+ movie_labels_from_display_layer,
23
+ )
24
+ from twopy.napari.protocols import NapariLayerWithData
25
+ from twopy.roi import (
26
+ RoiSet,
27
+ load_roi_set,
28
+ make_roi_set_from_label_image,
29
+ roi_set_to_label_image,
30
+ save_roi_set,
31
+ )
32
+ from twopy.spatial import SpatialCrop, full_frame_crop
33
+
34
+ __all__ = [
35
+ "roi_label_image_from_layer",
36
+ "roi_label_image_from_layer_for_recording",
37
+ "save_napari_label_rois",
38
+ "validate_roi_layer_matches_recording_crop",
39
+ ]
40
+
41
+
42
+ def roi_label_image_from_layer(layer: object) -> npt.NDArray[np.int64]:
43
+ """Read integer label data from a napari Labels layer.
44
+
45
+ Args:
46
+ layer: Napari Labels layer, or a test object with a ``data`` attribute.
47
+
48
+ Returns:
49
+ Full-frame movie-coordinate integer label image.
50
+
51
+ This small helper keeps scripts from reaching into napari layer internals
52
+ in several different ways. The label image can be passed directly to
53
+ ``save_napari_label_rois``. When twopy opened a cropped/transposed display
54
+ layer, crop metadata on the layer is used to restore movie coordinates.
55
+ """
56
+ if hasattr(layer, "metadata") or hasattr(layer, "options"):
57
+ return movie_labels_from_display_layer(layer)
58
+ data = cast(NapariLayerWithData, layer).data
59
+ return np.asarray(data, dtype=np.int64)
60
+
61
+
62
+ def roi_label_image_from_layer_for_recording(
63
+ layer: object,
64
+ recording: RecordingData,
65
+ ) -> npt.NDArray[np.int64]:
66
+ """Return full-frame movie-coordinate ROI labels from a crop-native layer.
67
+
68
+ Args:
69
+ layer: Napari Labels layer, or a test layer with ``data``.
70
+ recording: Loaded converted recording that defines the displayed crop.
71
+
72
+ Returns:
73
+ Full-frame movie-coordinate label image with zeros outside the crop.
74
+
75
+ Raises:
76
+ ValueError: If the Labels layer is not shaped like the recording's
77
+ displayed alignment-valid crop.
78
+
79
+ Napari ROI editing is intentionally crop-native: users see and edit only
80
+ the pixels that analysis accepts by default. Persisted ROI files stay
81
+ full-frame so core scripts and analysis have one stable mask shape.
82
+ """
83
+ validate_roi_layer_matches_recording_crop(layer, recording)
84
+ display_labels = np.asarray(cast(NapariLayerWithData, layer).data, dtype=np.int64)
85
+ crop = recording.alignment_valid_crop
86
+ movie_crop_labels = display_image_from_movie_image(display_labels)
87
+ full_frame = np.zeros(crop.original_shape, dtype=np.int64)
88
+ full_frame[
89
+ crop.axis0_start : crop.axis0_stop,
90
+ crop.axis1_start : crop.axis1_stop,
91
+ ] = movie_crop_labels
92
+ return full_frame
93
+
94
+
95
+ def validate_roi_layer_matches_recording_crop(
96
+ layer: object,
97
+ recording: RecordingData,
98
+ ) -> None:
99
+ """Validate that a napari Labels layer matches the displayed recording crop.
100
+
101
+ Args:
102
+ layer: Napari Labels layer, or a test layer with ``data``.
103
+ recording: Loaded converted recording that defines the displayed crop.
104
+
105
+ Returns:
106
+ None.
107
+
108
+ Raises:
109
+ ValueError: If the Labels layer does not have the cropped display
110
+ shape.
111
+ """
112
+ display_labels = np.asarray(cast(NapariLayerWithData, layer).data)
113
+ crop = recording.alignment_valid_crop
114
+ expected_shape = (crop.shape[1], crop.shape[0])
115
+ if display_labels.shape != expected_shape:
116
+ msg = (
117
+ "ROI Labels layer must use the cropped recording view. "
118
+ f"Expected display shape {expected_shape} from "
119
+ f"{crop.source}; got {display_labels.shape}."
120
+ )
121
+ raise ValueError(msg)
122
+
123
+
124
+ def save_napari_label_rois(
125
+ label_image: npt.ArrayLike,
126
+ path: Path,
127
+ *,
128
+ labels: tuple[str, ...] | None = None,
129
+ label_prefix: str = "roi",
130
+ ) -> RoiSet:
131
+ """Save a napari labels-layer image as a twopy ROI set.
132
+
133
+ Args:
134
+ label_image: Two-dimensional labels-layer data where ``0`` is
135
+ background and positive integers are ROIs.
136
+ path: Destination ROI HDF5 file.
137
+ labels: Optional text labels matching positive ROI values.
138
+ label_prefix: Prefix for generated ROI labels when ``labels`` is
139
+ omitted.
140
+
141
+ Returns:
142
+ Saved ``RoiSet``.
143
+
144
+ This keeps napari editing output in the same HDF5 format that scripts and
145
+ analysis functions already load.
146
+ """
147
+ roi_set = make_roi_set_from_label_image(
148
+ np.asarray(label_image),
149
+ labels=labels,
150
+ label_prefix=label_prefix,
151
+ )
152
+ save_roi_set(roi_set, path)
153
+ return roi_set
154
+
155
+
156
+ def roi_label_image_for_display(
157
+ roi_set: RoiSet | Path | None,
158
+ recording: RecordingData,
159
+ *,
160
+ spatial_crop: SpatialCrop | None = None,
161
+ ) -> npt.NDArray[np.int64]:
162
+ """Create the label image shown in napari for ROI drawing/editing.
163
+
164
+ Args:
165
+ roi_set: Optional existing ROI set or saved ROI HDF5 path.
166
+ recording: Loaded converted recording.
167
+ spatial_crop: Optional crop shown in napari. When omitted, the full
168
+ movie frame is displayed.
169
+
170
+ Returns:
171
+ Integer label image in napari display coordinates.
172
+ """
173
+ crop = spatial_crop
174
+ if crop is None:
175
+ crop = full_frame_crop(recording.movie.shape[1:])
176
+ if roi_set is None:
177
+ return np.zeros((crop.shape[1], crop.shape[0]), dtype=np.int64)
178
+ loaded_roi_set = resolve_roi_set(roi_set)
179
+ validate_roi_shape(loaded_roi_set, recording)
180
+ return display_labels_from_movie_labels(
181
+ roi_set_to_label_image(loaded_roi_set),
182
+ crop,
183
+ )
184
+
185
+
186
+ def resolve_roi_save_file(
187
+ *,
188
+ recording_data_path: Path,
189
+ roi_set: RoiSet | Path | None,
190
+ explicit_roi_save_file: Path | None,
191
+ ) -> Path:
192
+ """Resolve the default path used by the magicgui Save ROIs button.
193
+
194
+ Args:
195
+ recording_data_path: Converted recording manifest path.
196
+ roi_set: Optional ROI object or path opened in napari.
197
+ explicit_roi_save_file: Optional caller-provided save path.
198
+
199
+ Returns:
200
+ Path for saving ROI HDF5 files.
201
+ """
202
+ if explicit_roi_save_file is not None:
203
+ return explicit_roi_save_file.expanduser()
204
+ if isinstance(roi_set, Path):
205
+ return roi_set.expanduser()
206
+ return recording_data_path.expanduser().parent / "rois.h5"
207
+
208
+
209
+ def resolve_roi_set(roi_set: RoiSet | Path) -> RoiSet:
210
+ """Return a ROI set object from either an object or HDF5 path.
211
+
212
+ Args:
213
+ roi_set: Existing ``RoiSet`` or saved ROI HDF5 path.
214
+
215
+ Returns:
216
+ Loaded ``RoiSet``.
217
+ """
218
+ if isinstance(roi_set, Path):
219
+ return load_roi_set(roi_set)
220
+ return roi_set
221
+
222
+
223
+ def validate_roi_shape(roi_set: RoiSet, recording: RecordingData) -> None:
224
+ """Validate that ROI labels can be displayed over the recording.
225
+
226
+ Args:
227
+ roi_set: ROI masks to display.
228
+ recording: Loaded converted recording.
229
+
230
+ Returns:
231
+ None.
232
+ """
233
+ if roi_set.masks.shape[1:] != recording.movie.shape[1:]:
234
+ msg = (
235
+ f"ROI mask shape {roi_set.masks.shape[1:]} does not match movie "
236
+ f"shape {recording.movie.shape[1:]}"
237
+ )
238
+ raise ValueError(msg)