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.
- twopy/__init__.py +168 -0
- twopy/_segments.py +53 -0
- twopy/analysis/__init__.py +101 -0
- twopy/analysis/background_subtraction.py +672 -0
- twopy/analysis/dff.py +524 -0
- twopy/analysis/epoch_mapping.py +223 -0
- twopy/analysis/motion.py +66 -0
- twopy/analysis/persistence.py +975 -0
- twopy/analysis/response_processing/__init__.py +55 -0
- twopy/analysis/response_processing/apply.py +298 -0
- twopy/analysis/response_processing/options.py +192 -0
- twopy/analysis/response_processing/qc.py +270 -0
- twopy/analysis/response_processing/signals.py +200 -0
- twopy/analysis/responses.py +345 -0
- twopy/analysis/trials.py +341 -0
- twopy/analysis/workflow.py +457 -0
- twopy/api.py +78 -0
- twopy/config.py +200 -0
- twopy/conversion/__init__.py +40 -0
- twopy/conversion/alignment_crop.py +329 -0
- twopy/conversion/api.py +113 -0
- twopy/conversion/frame_ranges.py +35 -0
- twopy/conversion/hdf5_writing.py +362 -0
- twopy/conversion/matlab_values.py +123 -0
- twopy/conversion/source_loading.py +403 -0
- twopy/conversion/stimulus_code.py +248 -0
- twopy/conversion/types.py +295 -0
- twopy/converted.py +681 -0
- twopy/database/__init__.py +33 -0
- twopy/database/cache.py +166 -0
- twopy/database/catalog.py +90 -0
- twopy/database/connection.py +27 -0
- twopy/database/generic.py +254 -0
- twopy/database/modeled.py +419 -0
- twopy/database/sql.py +46 -0
- twopy/database/types.py +116 -0
- twopy/inspection.py +492 -0
- twopy/matlab.py +398 -0
- twopy/napari/__init__.py +27 -0
- twopy/napari/__main__.py +10 -0
- twopy/napari/constants.py +13 -0
- twopy/napari/controls.py +739 -0
- twopy/napari/display.py +181 -0
- twopy/napari/display_paths.py +197 -0
- twopy/napari/interactive.py +310 -0
- twopy/napari/launcher.py +175 -0
- twopy/napari/layout.py +147 -0
- twopy/napari/loading.py +207 -0
- twopy/napari/movie.py +71 -0
- twopy/napari/paths.py +181 -0
- twopy/napari/plotting/__init__.py +21 -0
- twopy/napari/plotting/data.py +266 -0
- twopy/napari/plotting/docks.py +1052 -0
- twopy/napari/plotting/export.py +803 -0
- twopy/napari/plotting/export_controls.py +209 -0
- twopy/napari/plotting/label_visibility.py +260 -0
- twopy/napari/plotting/options.py +269 -0
- twopy/napari/plotting/panels.py +119 -0
- twopy/napari/plotting/widgets.py +767 -0
- twopy/napari/protocols.py +90 -0
- twopy/napari/responses.py +91 -0
- twopy/napari/roi.py +238 -0
- twopy/napari/session.py +381 -0
- twopy/napari/state.py +106 -0
- twopy/napari/text.py +26 -0
- twopy/napari/types.py +39 -0
- twopy/napari/viewer.py +405 -0
- twopy/parity/__init__.py +30 -0
- twopy/parity/dff.py +399 -0
- twopy/parity/roi.py +145 -0
- twopy/parity/saved_analysis.py +174 -0
- twopy/photodiode.py +375 -0
- twopy/photodiode_classification.py +484 -0
- twopy/roi.py +462 -0
- twopy/session.py +167 -0
- twopy/spatial.py +154 -0
- twopy/stimulus.py +233 -0
- twopy/synchronization.py +235 -0
- twopy/typing_guards.py +255 -0
- twopy-0.1.0.dist-info/METADATA +311 -0
- twopy-0.1.0.dist-info/RECORD +84 -0
- twopy-0.1.0.dist-info/WHEEL +5 -0
- twopy-0.1.0.dist-info/entry_points.txt +2 -0
- twopy-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
"""Typed data objects used by twopy conversion.
|
|
2
|
+
|
|
3
|
+
Inputs: MATLAB/HDF5 source metadata and loaded NumPy arrays.
|
|
4
|
+
Outputs: immutable Python objects passed between conversion steps.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import cast
|
|
10
|
+
|
|
11
|
+
import h5py
|
|
12
|
+
import numpy as np
|
|
13
|
+
import numpy.typing as npt
|
|
14
|
+
|
|
15
|
+
from twopy.conversion.frame_ranges import normalize_frame_range
|
|
16
|
+
from twopy.session import TwoPhotonSessionFiles
|
|
17
|
+
from twopy.spatial import SpatialCrop
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass(frozen=True)
|
|
21
|
+
class AlignedMovieSource:
|
|
22
|
+
"""Lazy source for the aligned imaging movie before conversion.
|
|
23
|
+
|
|
24
|
+
Inputs: HDF5-backed MATLAB file path and dataset metadata.
|
|
25
|
+
Outputs: movie shape/dtype metadata plus methods for reading frames.
|
|
26
|
+
|
|
27
|
+
This object is only for conversion. Downstream analysis should read the
|
|
28
|
+
copied movie from the twopy aligned-movie HDF5 file.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
path: Path
|
|
32
|
+
dataset_name: str
|
|
33
|
+
shape: tuple[int, ...]
|
|
34
|
+
dtype: str
|
|
35
|
+
chunks: tuple[int, ...] | None
|
|
36
|
+
compression: str | None
|
|
37
|
+
|
|
38
|
+
def read_frames(
|
|
39
|
+
self,
|
|
40
|
+
start: int | None = None,
|
|
41
|
+
stop: int | None = None,
|
|
42
|
+
) -> npt.NDArray[np.float64]:
|
|
43
|
+
"""Read a frame range from the aligned source movie.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
start: First frame index to read. ``None`` starts at frame zero.
|
|
47
|
+
stop: Exclusive stop frame index. ``None`` reads through the end.
|
|
48
|
+
|
|
49
|
+
Returns:
|
|
50
|
+
NumPy array with shape ``(frames, x, y)`` for the requested range.
|
|
51
|
+
|
|
52
|
+
This method exists for conversion checks and small previews, not for
|
|
53
|
+
long-lived analysis workflows.
|
|
54
|
+
"""
|
|
55
|
+
frame_slice = slice(start, stop)
|
|
56
|
+
with h5py.File(self.path, "r") as mat_file:
|
|
57
|
+
dataset = mat_file[self.dataset_name]
|
|
58
|
+
return cast(npt.NDArray[np.float64], dataset[frame_slice, :, :])
|
|
59
|
+
|
|
60
|
+
def mean_image(
|
|
61
|
+
self,
|
|
62
|
+
*,
|
|
63
|
+
start_frame: int | None = None,
|
|
64
|
+
stop_frame: int | None = None,
|
|
65
|
+
chunk_frames: int = 128,
|
|
66
|
+
) -> npt.NDArray[np.float64]:
|
|
67
|
+
"""Compute a mean image over a frame range without loading all frames.
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
start_frame: First frame included in the mean. ``None`` uses zero.
|
|
71
|
+
stop_frame: Exclusive stop frame. ``None`` uses the movie end.
|
|
72
|
+
chunk_frames: Number of frames read per chunk.
|
|
73
|
+
|
|
74
|
+
Returns:
|
|
75
|
+
Float64 mean image with shape ``(x, y)``.
|
|
76
|
+
|
|
77
|
+
The computation streams the movie in chunks, keeping memory use bounded
|
|
78
|
+
by ``chunk_frames`` rather than the full movie length.
|
|
79
|
+
"""
|
|
80
|
+
start, stop = normalize_frame_range(
|
|
81
|
+
frame_count=self.shape[0],
|
|
82
|
+
start_frame=start_frame,
|
|
83
|
+
stop_frame=stop_frame,
|
|
84
|
+
)
|
|
85
|
+
if chunk_frames < 1:
|
|
86
|
+
msg = f"chunk_frames must be at least 1; got {chunk_frames}"
|
|
87
|
+
raise ValueError(msg)
|
|
88
|
+
|
|
89
|
+
spatial_shape = self.shape[1:]
|
|
90
|
+
accumulator = np.zeros(spatial_shape, dtype=np.float64)
|
|
91
|
+
with h5py.File(self.path, "r") as mat_file:
|
|
92
|
+
dataset = mat_file[self.dataset_name]
|
|
93
|
+
for chunk_start in range(start, stop, chunk_frames):
|
|
94
|
+
chunk_stop = min(chunk_start + chunk_frames, stop)
|
|
95
|
+
block = np.asarray(dataset[chunk_start:chunk_stop, :, :])
|
|
96
|
+
accumulator += block.sum(axis=0, dtype=np.float64)
|
|
97
|
+
|
|
98
|
+
return accumulator / float(stop - start)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
@dataclass(frozen=True)
|
|
102
|
+
class AcquisitionMetadata:
|
|
103
|
+
"""Recording acquisition metadata from ``imageDescription.mat``.
|
|
104
|
+
|
|
105
|
+
Inputs: MATLAB ScanImage ``state`` struct.
|
|
106
|
+
Outputs: selected scalar fields in a dictionary.
|
|
107
|
+
|
|
108
|
+
The raw MATLAB struct is not exposed because it is source-format data. The
|
|
109
|
+
converted HDF5 file stores this dictionary as HDF5 attributes.
|
|
110
|
+
"""
|
|
111
|
+
|
|
112
|
+
path: Path
|
|
113
|
+
fields: dict[str, object]
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
@dataclass(frozen=True)
|
|
117
|
+
class StimulusParameters:
|
|
118
|
+
"""Stimulus epoch definitions from ``stimParams.mat``.
|
|
119
|
+
|
|
120
|
+
Inputs: MATLAB ``stimParams`` structs.
|
|
121
|
+
Outputs: one plain dictionary per epoch.
|
|
122
|
+
|
|
123
|
+
Field names and values vary with the stimulus design, so dictionaries keep
|
|
124
|
+
the conversion auditable without inventing a premature fixed schema.
|
|
125
|
+
"""
|
|
126
|
+
|
|
127
|
+
path: Path
|
|
128
|
+
epochs: tuple[dict[str, object], ...]
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
@dataclass(frozen=True)
|
|
132
|
+
class StimulusCodeMetadata:
|
|
133
|
+
"""Stimulus-code metadata decoded from ``stimulusData/filebackup.zip``.
|
|
134
|
+
|
|
135
|
+
Inputs: Backed-up MATLAB stimulus code from the exact recording.
|
|
136
|
+
Outputs: ``stimtype`` to function-name lookup and per-function assignments
|
|
137
|
+
into the stimulus-specific ``stimData`` slots.
|
|
138
|
+
|
|
139
|
+
This keeps the stable numeric ``stimulus/data`` table unchanged while
|
|
140
|
+
preserving experiment-specific meaning for columns such as
|
|
141
|
+
``stimulus_specific_04``.
|
|
142
|
+
"""
|
|
143
|
+
|
|
144
|
+
path: Path
|
|
145
|
+
function_lookup: dict[str, str]
|
|
146
|
+
stimulus_specific_columns: dict[str, dict[str, object]]
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
@dataclass(frozen=True)
|
|
150
|
+
class RunMetadata:
|
|
151
|
+
"""Stimulus-run metadata from ``stimulusData/runDetails.mat``.
|
|
152
|
+
|
|
153
|
+
Inputs: MATLAB run details produced by the stimulus computer.
|
|
154
|
+
Outputs: plain scalar fields such as rig name, genotype, and run number.
|
|
155
|
+
|
|
156
|
+
This is separate from acquisition metadata because it describes the
|
|
157
|
+
stimulus run and rig identity, not ScanImage acquisition settings.
|
|
158
|
+
"""
|
|
159
|
+
|
|
160
|
+
path: Path
|
|
161
|
+
fields: dict[str, object]
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
@dataclass(frozen=True)
|
|
165
|
+
class StimulusData:
|
|
166
|
+
"""Per-stimulus-frame data from ``stimdata.mat``.
|
|
167
|
+
|
|
168
|
+
Inputs: MATLAB ``stimData`` table.
|
|
169
|
+
Outputs: the full numeric table, column labels, and common columns as
|
|
170
|
+
direct arrays.
|
|
171
|
+
|
|
172
|
+
The stimulus data comes from the stimulus presentation computer. Its
|
|
173
|
+
clock is independent from the imaging computer clock, so response analysis
|
|
174
|
+
must use photodiode synchronization before assigning imaging frames to
|
|
175
|
+
stimulus trials.
|
|
176
|
+
|
|
177
|
+
The first observed columns are time, stimulus frame number, and epoch.
|
|
178
|
+
Later columns are protocol-specific closed-loop/stimulus/flash values, so
|
|
179
|
+
twopy stores auditable column labels with the full table.
|
|
180
|
+
"""
|
|
181
|
+
|
|
182
|
+
path: Path
|
|
183
|
+
data: npt.NDArray[np.float64]
|
|
184
|
+
column_names: tuple[str, ...]
|
|
185
|
+
time_seconds: npt.NDArray[np.float64]
|
|
186
|
+
frame_numbers: npt.NDArray[np.int64]
|
|
187
|
+
epoch_numbers: npt.NDArray[np.int64]
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
@dataclass(frozen=True)
|
|
191
|
+
class PhotodiodeSignals:
|
|
192
|
+
"""Photodiode signals used to align imaging frames and stimulus frames.
|
|
193
|
+
|
|
194
|
+
Inputs: frame-resolution and high-resolution MATLAB photodiode files.
|
|
195
|
+
Outputs: NumPy arrays ready to write into the twopy HDF5 file.
|
|
196
|
+
|
|
197
|
+
Imaging and stimulus presentation run on different computers with different
|
|
198
|
+
frame rates. The stimulus computer flashes the photodiode at key timepoints
|
|
199
|
+
such as start, trial transitions, and end. These signals are the bridge
|
|
200
|
+
between clocks and must be decoded before trial-level response analysis.
|
|
201
|
+
"""
|
|
202
|
+
|
|
203
|
+
imaging_res_path: Path
|
|
204
|
+
high_res_path: Path
|
|
205
|
+
imaging_res_pd: npt.NDArray[np.float64]
|
|
206
|
+
high_res_pd: npt.NDArray[np.float64]
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
@dataclass(frozen=True)
|
|
210
|
+
class FrameCountAudit:
|
|
211
|
+
"""Frame-count relationship checked during source conversion.
|
|
212
|
+
|
|
213
|
+
Inputs: aligned movie frames, imaging-resolution photodiode samples, and
|
|
214
|
+
ScanImage acquisition frame metadata.
|
|
215
|
+
Outputs: explicit counts and deltas for converted HDF5 audit metadata.
|
|
216
|
+
|
|
217
|
+
The aligned movie and imaging-resolution photodiode should describe the
|
|
218
|
+
same imaging frames. In sampled real recordings, ScanImage
|
|
219
|
+
``acq.numberOfFrames`` was usually one less than the aligned movie frame
|
|
220
|
+
count. twopy treats that as a known metadata convention, not as a dropped
|
|
221
|
+
frame, and records the offset so response analysis can audit it.
|
|
222
|
+
"""
|
|
223
|
+
|
|
224
|
+
aligned_movie_frames: int
|
|
225
|
+
imaging_res_pd_samples: int
|
|
226
|
+
acquisition_number_of_frames: int
|
|
227
|
+
imaging_res_pd_minus_movie: int
|
|
228
|
+
acquisition_minus_movie: int
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
@dataclass(frozen=True)
|
|
232
|
+
class AlignmentCropAudit:
|
|
233
|
+
"""Alignment-derived spatial crop saved during conversion.
|
|
234
|
+
|
|
235
|
+
Inputs: microscope alignment offsets, acquisition metadata, and photodiode
|
|
236
|
+
stimulus bounds.
|
|
237
|
+
Outputs: the spatial crop plus audit values that explain how it was chosen.
|
|
238
|
+
|
|
239
|
+
The crop describes the frame region that stayed valid after motion alignment.
|
|
240
|
+
Keeping it separate from the full aligned movie lets twopy preserve source
|
|
241
|
+
pixels while reproducing cropped analysis paths when needed.
|
|
242
|
+
"""
|
|
243
|
+
|
|
244
|
+
crop: SpatialCrop
|
|
245
|
+
alignment_frame_start: int
|
|
246
|
+
alignment_frame_stop: int
|
|
247
|
+
x_cutoff_pixels: int
|
|
248
|
+
y_cutoff_pixels: int
|
|
249
|
+
over_moved_frame_count: int
|
|
250
|
+
motion_threshold_pixels: float
|
|
251
|
+
alignment_shift_pixels: npt.NDArray[np.float64]
|
|
252
|
+
motion_artifact_mask: npt.NDArray[np.bool_]
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
@dataclass(frozen=True)
|
|
256
|
+
class SourceConversionInputs:
|
|
257
|
+
"""All source inputs needed to create a twopy converted recording.
|
|
258
|
+
|
|
259
|
+
Inputs: one recording session directory.
|
|
260
|
+
Outputs: validated file paths and Python objects used only for conversion.
|
|
261
|
+
|
|
262
|
+
This object is the boundary between microscope source data and twopy-owned
|
|
263
|
+
analysis files.
|
|
264
|
+
"""
|
|
265
|
+
|
|
266
|
+
session_files: TwoPhotonSessionFiles
|
|
267
|
+
aligned_movie: AlignedMovieSource
|
|
268
|
+
acquisition: AcquisitionMetadata
|
|
269
|
+
run: RunMetadata
|
|
270
|
+
stimulus_parameters: StimulusParameters
|
|
271
|
+
stimulus_code: StimulusCodeMetadata
|
|
272
|
+
stimulus_data: StimulusData
|
|
273
|
+
photodiode: PhotodiodeSignals
|
|
274
|
+
frame_counts: FrameCountAudit
|
|
275
|
+
alignment_crop: AlignmentCropAudit
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
@dataclass(frozen=True)
|
|
279
|
+
class ConvertedRecording:
|
|
280
|
+
"""Summary of converted twopy recording files.
|
|
281
|
+
|
|
282
|
+
Inputs: conversion output path and source recording folder.
|
|
283
|
+
Outputs: HDF5 paths plus mean-image frame range and movie shape.
|
|
284
|
+
|
|
285
|
+
Later analysis modules should accept these converted HDF5 paths, not source
|
|
286
|
+
MATLAB file paths. ``path`` points at the small recording data file;
|
|
287
|
+
``movie_path`` points at the large aligned movie file.
|
|
288
|
+
"""
|
|
289
|
+
|
|
290
|
+
path: Path
|
|
291
|
+
movie_path: Path
|
|
292
|
+
source_session_dir: Path
|
|
293
|
+
movie_shape: tuple[int, ...]
|
|
294
|
+
mean_image_start_frame: int
|
|
295
|
+
mean_image_stop_frame: int
|