TwoPy 0.1.1__tar.gz → 0.1.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- twopy-0.1.3/PKG-INFO +97 -0
- twopy-0.1.3/README.md +77 -0
- {twopy-0.1.1 → twopy-0.1.3}/pyproject.toml +1 -1
- {twopy-0.1.1 → twopy-0.1.3}/src/twopy/__init__.py +1 -2
- twopy-0.1.3/src/twopy/_version.py +14 -0
- {twopy-0.1.1 → twopy-0.1.3}/src/twopy/database/cache.py +2 -5
- {twopy-0.1.1 → twopy-0.1.3}/src/twopy/napari/controls.py +43 -2
- {twopy-0.1.1 → twopy-0.1.3}/src/twopy/napari/launcher.py +13 -3
- {twopy-0.1.1 → twopy-0.1.3}/src/twopy/napari/loading.py +53 -1
- {twopy-0.1.1 → twopy-0.1.3}/src/twopy/napari/plotting/label_visibility.py +34 -8
- {twopy-0.1.1 → twopy-0.1.3}/src/twopy/napari/viewer.py +4 -2
- {twopy-0.1.1 → twopy-0.1.3}/src/twopy/session.py +5 -1
- {twopy-0.1.1 → twopy-0.1.3}/src/twopy/stimulus.py +8 -4
- twopy-0.1.3/src/twopy.egg-info/PKG-INFO +97 -0
- {twopy-0.1.1 → twopy-0.1.3}/src/twopy.egg-info/SOURCES.txt +1 -0
- {twopy-0.1.1 → twopy-0.1.3}/tests/test_import.py +2 -1
- {twopy-0.1.1 → twopy-0.1.3}/tests/test_napari.py +87 -1
- twopy-0.1.3/tests/test_packaging.py +63 -0
- {twopy-0.1.1 → twopy-0.1.3}/tests/test_session.py +24 -0
- {twopy-0.1.1 → twopy-0.1.3}/tests/test_stimulus.py +13 -0
- twopy-0.1.1/PKG-INFO +0 -304
- twopy-0.1.1/README.md +0 -284
- twopy-0.1.1/src/twopy.egg-info/PKG-INFO +0 -304
- twopy-0.1.1/tests/test_packaging.py +0 -32
- {twopy-0.1.1 → twopy-0.1.3}/setup.cfg +0 -0
- {twopy-0.1.1 → twopy-0.1.3}/src/twopy/_segments.py +0 -0
- {twopy-0.1.1 → twopy-0.1.3}/src/twopy/analysis/__init__.py +0 -0
- {twopy-0.1.1 → twopy-0.1.3}/src/twopy/analysis/background_subtraction.py +0 -0
- {twopy-0.1.1 → twopy-0.1.3}/src/twopy/analysis/dff.py +0 -0
- {twopy-0.1.1 → twopy-0.1.3}/src/twopy/analysis/epoch_mapping.py +0 -0
- {twopy-0.1.1 → twopy-0.1.3}/src/twopy/analysis/motion.py +0 -0
- {twopy-0.1.1 → twopy-0.1.3}/src/twopy/analysis/persistence.py +0 -0
- {twopy-0.1.1 → twopy-0.1.3}/src/twopy/analysis/response_processing/__init__.py +0 -0
- {twopy-0.1.1 → twopy-0.1.3}/src/twopy/analysis/response_processing/apply.py +0 -0
- {twopy-0.1.1 → twopy-0.1.3}/src/twopy/analysis/response_processing/options.py +0 -0
- {twopy-0.1.1 → twopy-0.1.3}/src/twopy/analysis/response_processing/persistence.py +0 -0
- {twopy-0.1.1 → twopy-0.1.3}/src/twopy/analysis/response_processing/qc.py +0 -0
- {twopy-0.1.1 → twopy-0.1.3}/src/twopy/analysis/response_processing/signals.py +0 -0
- {twopy-0.1.1 → twopy-0.1.3}/src/twopy/analysis/responses.py +0 -0
- {twopy-0.1.1 → twopy-0.1.3}/src/twopy/analysis/trials.py +0 -0
- {twopy-0.1.1 → twopy-0.1.3}/src/twopy/analysis/workflow.py +0 -0
- {twopy-0.1.1 → twopy-0.1.3}/src/twopy/api.py +0 -0
- {twopy-0.1.1 → twopy-0.1.3}/src/twopy/config.py +0 -0
- {twopy-0.1.1 → twopy-0.1.3}/src/twopy/conversion/__init__.py +0 -0
- {twopy-0.1.1 → twopy-0.1.3}/src/twopy/conversion/alignment_crop.py +0 -0
- {twopy-0.1.1 → twopy-0.1.3}/src/twopy/conversion/api.py +0 -0
- {twopy-0.1.1 → twopy-0.1.3}/src/twopy/conversion/frame_ranges.py +0 -0
- {twopy-0.1.1 → twopy-0.1.3}/src/twopy/conversion/hdf5_writing.py +0 -0
- {twopy-0.1.1 → twopy-0.1.3}/src/twopy/conversion/matlab_values.py +0 -0
- {twopy-0.1.1 → twopy-0.1.3}/src/twopy/conversion/source_loading.py +0 -0
- {twopy-0.1.1 → twopy-0.1.3}/src/twopy/conversion/stimulus_code.py +0 -0
- {twopy-0.1.1 → twopy-0.1.3}/src/twopy/conversion/types.py +0 -0
- {twopy-0.1.1 → twopy-0.1.3}/src/twopy/converted.py +0 -0
- {twopy-0.1.1 → twopy-0.1.3}/src/twopy/database/__init__.py +0 -0
- {twopy-0.1.1 → twopy-0.1.3}/src/twopy/database/catalog.py +0 -0
- {twopy-0.1.1 → twopy-0.1.3}/src/twopy/database/connection.py +0 -0
- {twopy-0.1.1 → twopy-0.1.3}/src/twopy/database/generic.py +0 -0
- {twopy-0.1.1 → twopy-0.1.3}/src/twopy/database/modeled.py +0 -0
- {twopy-0.1.1 → twopy-0.1.3}/src/twopy/database/sql.py +0 -0
- {twopy-0.1.1 → twopy-0.1.3}/src/twopy/database/types.py +0 -0
- {twopy-0.1.1 → twopy-0.1.3}/src/twopy/inspection.py +0 -0
- {twopy-0.1.1 → twopy-0.1.3}/src/twopy/matlab.py +0 -0
- {twopy-0.1.1 → twopy-0.1.3}/src/twopy/napari/__init__.py +0 -0
- {twopy-0.1.1 → twopy-0.1.3}/src/twopy/napari/__main__.py +0 -0
- {twopy-0.1.1 → twopy-0.1.3}/src/twopy/napari/constants.py +0 -0
- {twopy-0.1.1 → twopy-0.1.3}/src/twopy/napari/display.py +0 -0
- {twopy-0.1.1 → twopy-0.1.3}/src/twopy/napari/display_paths.py +0 -0
- {twopy-0.1.1 → twopy-0.1.3}/src/twopy/napari/interactive.py +0 -0
- {twopy-0.1.1 → twopy-0.1.3}/src/twopy/napari/layout.py +0 -0
- {twopy-0.1.1 → twopy-0.1.3}/src/twopy/napari/movie.py +0 -0
- {twopy-0.1.1 → twopy-0.1.3}/src/twopy/napari/paths.py +0 -0
- {twopy-0.1.1 → twopy-0.1.3}/src/twopy/napari/plotting/__init__.py +0 -0
- {twopy-0.1.1 → twopy-0.1.3}/src/twopy/napari/plotting/data.py +0 -0
- {twopy-0.1.1 → twopy-0.1.3}/src/twopy/napari/plotting/docks.py +0 -0
- {twopy-0.1.1 → twopy-0.1.3}/src/twopy/napari/plotting/export.py +0 -0
- {twopy-0.1.1 → twopy-0.1.3}/src/twopy/napari/plotting/export_controls.py +0 -0
- {twopy-0.1.1 → twopy-0.1.3}/src/twopy/napari/plotting/options.py +0 -0
- {twopy-0.1.1 → twopy-0.1.3}/src/twopy/napari/plotting/panels.py +0 -0
- {twopy-0.1.1 → twopy-0.1.3}/src/twopy/napari/plotting/processing_options.py +0 -0
- {twopy-0.1.1 → twopy-0.1.3}/src/twopy/napari/plotting/widgets.py +0 -0
- {twopy-0.1.1 → twopy-0.1.3}/src/twopy/napari/protocols.py +0 -0
- {twopy-0.1.1 → twopy-0.1.3}/src/twopy/napari/responses.py +0 -0
- {twopy-0.1.1 → twopy-0.1.3}/src/twopy/napari/roi.py +0 -0
- {twopy-0.1.1 → twopy-0.1.3}/src/twopy/napari/session.py +0 -0
- {twopy-0.1.1 → twopy-0.1.3}/src/twopy/napari/state.py +0 -0
- {twopy-0.1.1 → twopy-0.1.3}/src/twopy/napari/text.py +0 -0
- {twopy-0.1.1 → twopy-0.1.3}/src/twopy/napari/types.py +0 -0
- {twopy-0.1.1 → twopy-0.1.3}/src/twopy/parity/__init__.py +0 -0
- {twopy-0.1.1 → twopy-0.1.3}/src/twopy/parity/dff.py +0 -0
- {twopy-0.1.1 → twopy-0.1.3}/src/twopy/parity/roi.py +0 -0
- {twopy-0.1.1 → twopy-0.1.3}/src/twopy/parity/saved_analysis.py +0 -0
- {twopy-0.1.1 → twopy-0.1.3}/src/twopy/photodiode.py +0 -0
- {twopy-0.1.1 → twopy-0.1.3}/src/twopy/photodiode_classification.py +0 -0
- {twopy-0.1.1 → twopy-0.1.3}/src/twopy/roi.py +0 -0
- {twopy-0.1.1 → twopy-0.1.3}/src/twopy/spatial.py +0 -0
- {twopy-0.1.1 → twopy-0.1.3}/src/twopy/synchronization.py +0 -0
- {twopy-0.1.1 → twopy-0.1.3}/src/twopy/typing_guards.py +0 -0
- {twopy-0.1.1 → twopy-0.1.3}/src/twopy.egg-info/dependency_links.txt +0 -0
- {twopy-0.1.1 → twopy-0.1.3}/src/twopy.egg-info/entry_points.txt +0 -0
- {twopy-0.1.1 → twopy-0.1.3}/src/twopy.egg-info/requires.txt +0 -0
- {twopy-0.1.1 → twopy-0.1.3}/src/twopy.egg-info/top_level.txt +0 -0
- {twopy-0.1.1 → twopy-0.1.3}/tests/test_api.py +0 -0
- {twopy-0.1.1 → twopy-0.1.3}/tests/test_background_subtraction.py +0 -0
- {twopy-0.1.1 → twopy-0.1.3}/tests/test_config.py +0 -0
- {twopy-0.1.1 → twopy-0.1.3}/tests/test_conversion.py +0 -0
- {twopy-0.1.1 → twopy-0.1.3}/tests/test_converted.py +0 -0
- {twopy-0.1.1 → twopy-0.1.3}/tests/test_database.py +0 -0
- {twopy-0.1.1 → twopy-0.1.3}/tests/test_dff.py +0 -0
- {twopy-0.1.1 → twopy-0.1.3}/tests/test_epoch_mapping.py +0 -0
- {twopy-0.1.1 → twopy-0.1.3}/tests/test_inspection.py +0 -0
- {twopy-0.1.1 → twopy-0.1.3}/tests/test_matlab.py +0 -0
- {twopy-0.1.1 → twopy-0.1.3}/tests/test_motion.py +0 -0
- {twopy-0.1.1 → twopy-0.1.3}/tests/test_parity.py +0 -0
- {twopy-0.1.1 → twopy-0.1.3}/tests/test_persistence.py +0 -0
- {twopy-0.1.1 → twopy-0.1.3}/tests/test_photodiode_classification.py +0 -0
- {twopy-0.1.1 → twopy-0.1.3}/tests/test_real_fixture.py +0 -0
- {twopy-0.1.1 → twopy-0.1.3}/tests/test_response_processing.py +0 -0
- {twopy-0.1.1 → twopy-0.1.3}/tests/test_responses.py +0 -0
- {twopy-0.1.1 → twopy-0.1.3}/tests/test_roi.py +0 -0
- {twopy-0.1.1 → twopy-0.1.3}/tests/test_saved_analysis.py +0 -0
- {twopy-0.1.1 → twopy-0.1.3}/tests/test_synchronization.py +0 -0
- {twopy-0.1.1 → twopy-0.1.3}/tests/test_trials.py +0 -0
- {twopy-0.1.1 → twopy-0.1.3}/tests/test_workflow.py +0 -0
twopy-0.1.3/PKG-INFO
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: twopy
|
|
3
|
+
Version: 0.1.3
|
|
4
|
+
Summary: Two-photon imaging analysis tool with a napari interface.
|
|
5
|
+
Author-email: Gustavo Madeira Santana <fromweb1@gumadeiras.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Requires-Python: <3.14,>=3.13
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
Requires-Dist: h5py<4,>=3.16
|
|
10
|
+
Requires-Dist: matplotlib<4,>=3.10
|
|
11
|
+
Requires-Dist: napari[all]<0.8,>=0.7
|
|
12
|
+
Requires-Dist: numpy<3,>=2.3
|
|
13
|
+
Requires-Dist: pre-commit<5,>=4.6
|
|
14
|
+
Requires-Dist: qtpy<3,>=2.4
|
|
15
|
+
Requires-Dist: pyyaml<7,>=6.0.3
|
|
16
|
+
Requires-Dist: ruff>=0.15
|
|
17
|
+
Requires-Dist: scipy<2,>=1.17
|
|
18
|
+
Requires-Dist: tifffile<2027,>=2026.3
|
|
19
|
+
Requires-Dist: ty<0.1,>=0.0.34
|
|
20
|
+
|
|
21
|
+
# twopy
|
|
22
|
+
|
|
23
|
+
Two-photon imaging analysis tool for the Clark Lab output format.
|
|
24
|
+
|
|
25
|
+

|
|
26
|
+
|
|
27
|
+
## Getting Started
|
|
28
|
+
|
|
29
|
+
twopy lets you open two-photon recordings, draw ROIs, plot responses in real time,
|
|
30
|
+
process and analyze them, and save them.
|
|
31
|
+
|
|
32
|
+
When you first load a recording, twopy converts it to a standardized HDF5 format.
|
|
33
|
+
The converted format includes the aligned movie, mean image, stimulus tables,
|
|
34
|
+
photodiode signals, and recording metadata. Analysis and the GUI both work from
|
|
35
|
+
the converted files, so the original source files remain separate from twopy's outputs.
|
|
36
|
+
|
|
37
|
+
## Install
|
|
38
|
+
|
|
39
|
+
Examples use micromamba, but any conda-compatible environment manager should work.
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
```sh
|
|
43
|
+
micromamba create -n twopy -c conda-forge python=3.13 pip -y
|
|
44
|
+
micromamba run -n twopy python -m pip install twopy
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Verify the install:
|
|
48
|
+
|
|
49
|
+
```sh
|
|
50
|
+
micromamba run -n twopy python -c 'import twopy; print(twopy.__name__)'
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
For a source checkout, copy `config.example.yml` to `config.yml` and edit the
|
|
54
|
+
paths for your computer. `config.yml` stays local and is not tracked by git.
|
|
55
|
+
|
|
56
|
+
## Start
|
|
57
|
+
|
|
58
|
+
```sh
|
|
59
|
+
micromamba activate twopy
|
|
60
|
+
twopy
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Open a source recording or converted recording directly:
|
|
64
|
+
|
|
65
|
+
```sh
|
|
66
|
+
twopy /path/to/source/recording
|
|
67
|
+
twopy /path/to/recording_data.h5
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Basic GUI flow:
|
|
71
|
+
|
|
72
|
+
1. Choose a recording.
|
|
73
|
+
2. Draw or edit ROIs in the `rois` Labels layer.
|
|
74
|
+
3. Click Save ROIs.
|
|
75
|
+
4. Update response plots from the current ROIs.
|
|
76
|
+
5. Save analysis outputs.
|
|
77
|
+
|
|
78
|
+
When a source recording has not been converted yet, twopy converts it first.
|
|
79
|
+
Converted data includes the aligned movie, mean image, stimulus tables,
|
|
80
|
+
photodiode signals, and recording metadata.
|
|
81
|
+
|
|
82
|
+
## Docs
|
|
83
|
+
|
|
84
|
+
- [GUI guide](docs/gui.md): napari loading, ROI editing, plotting, and saved
|
|
85
|
+
outputs.
|
|
86
|
+
- [Python API guide](docs/python_api.md): find recordings, convert data, run
|
|
87
|
+
analysis, and open napari from scripts.
|
|
88
|
+
- [Development guide](docs/development.md): source setup and local checks.
|
|
89
|
+
- [Input data spec](docs/input_data_spec.md): short recording folder contract.
|
|
90
|
+
- [Recording file reference](docs/recording_file_schema.md): detailed source
|
|
91
|
+
and converted file schema.
|
|
92
|
+
|
|
93
|
+
## Check
|
|
94
|
+
|
|
95
|
+
```sh
|
|
96
|
+
micromamba run -n twopy pre-commit run --all-files
|
|
97
|
+
```
|
twopy-0.1.3/README.md
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# twopy
|
|
2
|
+
|
|
3
|
+
Two-photon imaging analysis tool for the Clark Lab output format.
|
|
4
|
+
|
|
5
|
+

|
|
6
|
+
|
|
7
|
+
## Getting Started
|
|
8
|
+
|
|
9
|
+
twopy lets you open two-photon recordings, draw ROIs, plot responses in real time,
|
|
10
|
+
process and analyze them, and save them.
|
|
11
|
+
|
|
12
|
+
When you first load a recording, twopy converts it to a standardized HDF5 format.
|
|
13
|
+
The converted format includes the aligned movie, mean image, stimulus tables,
|
|
14
|
+
photodiode signals, and recording metadata. Analysis and the GUI both work from
|
|
15
|
+
the converted files, so the original source files remain separate from twopy's outputs.
|
|
16
|
+
|
|
17
|
+
## Install
|
|
18
|
+
|
|
19
|
+
Examples use micromamba, but any conda-compatible environment manager should work.
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
```sh
|
|
23
|
+
micromamba create -n twopy -c conda-forge python=3.13 pip -y
|
|
24
|
+
micromamba run -n twopy python -m pip install twopy
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Verify the install:
|
|
28
|
+
|
|
29
|
+
```sh
|
|
30
|
+
micromamba run -n twopy python -c 'import twopy; print(twopy.__name__)'
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
For a source checkout, copy `config.example.yml` to `config.yml` and edit the
|
|
34
|
+
paths for your computer. `config.yml` stays local and is not tracked by git.
|
|
35
|
+
|
|
36
|
+
## Start
|
|
37
|
+
|
|
38
|
+
```sh
|
|
39
|
+
micromamba activate twopy
|
|
40
|
+
twopy
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Open a source recording or converted recording directly:
|
|
44
|
+
|
|
45
|
+
```sh
|
|
46
|
+
twopy /path/to/source/recording
|
|
47
|
+
twopy /path/to/recording_data.h5
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Basic GUI flow:
|
|
51
|
+
|
|
52
|
+
1. Choose a recording.
|
|
53
|
+
2. Draw or edit ROIs in the `rois` Labels layer.
|
|
54
|
+
3. Click Save ROIs.
|
|
55
|
+
4. Update response plots from the current ROIs.
|
|
56
|
+
5. Save analysis outputs.
|
|
57
|
+
|
|
58
|
+
When a source recording has not been converted yet, twopy converts it first.
|
|
59
|
+
Converted data includes the aligned movie, mean image, stimulus tables,
|
|
60
|
+
photodiode signals, and recording metadata.
|
|
61
|
+
|
|
62
|
+
## Docs
|
|
63
|
+
|
|
64
|
+
- [GUI guide](docs/gui.md): napari loading, ROI editing, plotting, and saved
|
|
65
|
+
outputs.
|
|
66
|
+
- [Python API guide](docs/python_api.md): find recordings, convert data, run
|
|
67
|
+
analysis, and open napari from scripts.
|
|
68
|
+
- [Development guide](docs/development.md): source setup and local checks.
|
|
69
|
+
- [Input data spec](docs/input_data_spec.md): short recording folder contract.
|
|
70
|
+
- [Recording file reference](docs/recording_file_schema.md): detailed source
|
|
71
|
+
and converted file schema.
|
|
72
|
+
|
|
73
|
+
## Check
|
|
74
|
+
|
|
75
|
+
```sh
|
|
76
|
+
micromamba run -n twopy pre-commit run --all-files
|
|
77
|
+
```
|
|
@@ -5,6 +5,7 @@ Inputs: none.
|
|
|
5
5
|
Outputs: package constants and helper functions imported by users and scripts.
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
|
+
from twopy._version import __version__
|
|
8
9
|
from twopy.analysis import (
|
|
9
10
|
DEFAULT_RESPONSE_POST_WINDOW_SECONDS,
|
|
10
11
|
DEFAULT_RESPONSE_PRE_WINDOW_SECONDS,
|
|
@@ -174,5 +175,3 @@ __all__ = [
|
|
|
174
175
|
"write_response_summary_grouped_csv",
|
|
175
176
|
"write_response_summary_trials_csv",
|
|
176
177
|
]
|
|
177
|
-
|
|
178
|
-
__version__ = "0.1.0"
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""Package-version metadata for twopy.
|
|
2
|
+
|
|
3
|
+
Inputs: installed Python package metadata.
|
|
4
|
+
Outputs: the current twopy package version string.
|
|
5
|
+
|
|
6
|
+
Keeping this in one small module prevents the public Python API and command-line
|
|
7
|
+
interface from drifting when ``pyproject.toml`` changes.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from importlib.metadata import version
|
|
11
|
+
|
|
12
|
+
__all__ = ["__version__"]
|
|
13
|
+
|
|
14
|
+
__version__ = version("twopy")
|
|
@@ -11,9 +11,9 @@ import hashlib
|
|
|
11
11
|
import json
|
|
12
12
|
import shutil
|
|
13
13
|
from pathlib import Path
|
|
14
|
-
from typing import cast
|
|
15
14
|
|
|
16
15
|
from twopy.database.types import DatabaseAccess
|
|
16
|
+
from twopy.typing_guards import string_key_mapping_or_none
|
|
17
17
|
|
|
18
18
|
__all__ = [
|
|
19
19
|
"DEFAULT_DATABASE_CACHE_DIR",
|
|
@@ -107,10 +107,7 @@ def _read_copy_manifest(manifest_path: Path) -> dict[str, object] | None:
|
|
|
107
107
|
except (OSError, json.JSONDecodeError):
|
|
108
108
|
return None
|
|
109
109
|
|
|
110
|
-
|
|
111
|
-
return None
|
|
112
|
-
|
|
113
|
-
return cast(dict[str, object], loaded)
|
|
110
|
+
return string_key_mapping_or_none(loaded)
|
|
114
111
|
|
|
115
112
|
|
|
116
113
|
def _manifest_metadata_matches(
|
|
@@ -272,9 +272,14 @@ def _make_twopy_load_widget(state: NapariControlState) -> object:
|
|
|
272
272
|
)
|
|
273
273
|
movie_range = None
|
|
274
274
|
if load_movie:
|
|
275
|
+
start_frame, end_frame = _widget_movie_frame_range_for_load(
|
|
276
|
+
movie_frame_range,
|
|
277
|
+
frame_count=loaded_recording.movie.shape[0],
|
|
278
|
+
preserve_current_range=replace_selected,
|
|
279
|
+
)
|
|
275
280
|
movie_range = resolve_movie_frame_range(
|
|
276
|
-
start_frame=
|
|
277
|
-
end_frame=
|
|
281
|
+
start_frame=start_frame,
|
|
282
|
+
end_frame=end_frame,
|
|
278
283
|
frame_count=loaded_recording.movie.shape[0],
|
|
279
284
|
zero_end_means_last=True,
|
|
280
285
|
)
|
|
@@ -717,6 +722,42 @@ def _default_movie_end_frame(recording: RecordingData | None) -> int:
|
|
|
717
722
|
return max(recording.movie.shape[0] - 1, 0)
|
|
718
723
|
|
|
719
724
|
|
|
725
|
+
def _widget_movie_frame_range_for_load(
|
|
726
|
+
movie_frame_range: tuple[int, int],
|
|
727
|
+
*,
|
|
728
|
+
frame_count: int,
|
|
729
|
+
preserve_current_range: bool,
|
|
730
|
+
) -> tuple[int, int]:
|
|
731
|
+
"""Choose a GUI movie preview range for the recording being loaded.
|
|
732
|
+
|
|
733
|
+
Args:
|
|
734
|
+
movie_frame_range: Current range-slider value.
|
|
735
|
+
frame_count: Number of movie frames in the recording being loaded.
|
|
736
|
+
preserve_current_range: Whether this is a reload of the selected
|
|
737
|
+
recording after an option change.
|
|
738
|
+
|
|
739
|
+
Returns:
|
|
740
|
+
Inclusive preview range constrained to the recording length. New
|
|
741
|
+
recordings default to their full movie, while reloads preserve the
|
|
742
|
+
visible slider value where possible.
|
|
743
|
+
|
|
744
|
+
The frame-range widget keeps its previous value until after a recording
|
|
745
|
+
loads. That value is meaningful when reloading the same recording after an
|
|
746
|
+
option change, but stale when choosing a different recording.
|
|
747
|
+
"""
|
|
748
|
+
last_frame = max(frame_count - 1, 0)
|
|
749
|
+
if not preserve_current_range:
|
|
750
|
+
return (0, last_frame)
|
|
751
|
+
|
|
752
|
+
start_frame = int(movie_frame_range[0])
|
|
753
|
+
end_frame = int(movie_frame_range[1])
|
|
754
|
+
if start_frame > last_frame:
|
|
755
|
+
start_frame = last_frame
|
|
756
|
+
if end_frame > last_frame:
|
|
757
|
+
end_frame = last_frame
|
|
758
|
+
return (start_frame, end_frame)
|
|
759
|
+
|
|
760
|
+
|
|
720
761
|
def _load_recording_for_defaults(
|
|
721
762
|
*,
|
|
722
763
|
recording_data_path: Path,
|
|
@@ -9,8 +9,10 @@ analysis workflows belong in dedicated helpers that the controls call.
|
|
|
9
9
|
"""
|
|
10
10
|
|
|
11
11
|
from argparse import ArgumentParser, Namespace
|
|
12
|
+
from collections.abc import Sequence
|
|
12
13
|
from pathlib import Path
|
|
13
14
|
|
|
15
|
+
from twopy._version import __version__
|
|
14
16
|
from twopy.napari.controls import add_twopy_magicgui_controls
|
|
15
17
|
from twopy.napari.layout import place_response_options_after_recording_list
|
|
16
18
|
from twopy.napari.loading import resolve_or_convert_launch_recording
|
|
@@ -99,11 +101,12 @@ def launch_napari(
|
|
|
99
101
|
return view
|
|
100
102
|
|
|
101
103
|
|
|
102
|
-
def parse_launch_args() -> Namespace:
|
|
104
|
+
def parse_launch_args(args: Sequence[str] | None = None) -> Namespace:
|
|
103
105
|
"""Parse command-line arguments for ``python -m twopy.napari``.
|
|
104
106
|
|
|
105
107
|
Args:
|
|
106
|
-
None
|
|
108
|
+
args: Optional command-line arguments. ``None`` reads from
|
|
109
|
+
``sys.argv``.
|
|
107
110
|
|
|
108
111
|
Returns:
|
|
109
112
|
Parsed argument namespace.
|
|
@@ -111,6 +114,13 @@ def parse_launch_args() -> Namespace:
|
|
|
111
114
|
parser = ArgumentParser(
|
|
112
115
|
description="Launch napari for a twopy-converted recording.",
|
|
113
116
|
)
|
|
117
|
+
parser.add_argument(
|
|
118
|
+
"-v",
|
|
119
|
+
"--version",
|
|
120
|
+
action="version",
|
|
121
|
+
version=f"twopy {__version__}",
|
|
122
|
+
help="Print the current twopy package version and exit.",
|
|
123
|
+
)
|
|
114
124
|
parser.add_argument(
|
|
115
125
|
"recording_data_path",
|
|
116
126
|
nargs="?",
|
|
@@ -152,7 +162,7 @@ def parse_launch_args() -> Namespace:
|
|
|
152
162
|
action="store_true",
|
|
153
163
|
help="Skip loading movie preview frames and show only the mean image.",
|
|
154
164
|
)
|
|
155
|
-
return parser.parse_args()
|
|
165
|
+
return parser.parse_args(args)
|
|
156
166
|
|
|
157
167
|
|
|
158
168
|
def main() -> None:
|
|
@@ -193,6 +193,7 @@ def _first_source_recording_dir(candidates: tuple[Path | None, ...]) -> Path | N
|
|
|
193
193
|
Resolved source folder, or ``None``.
|
|
194
194
|
"""
|
|
195
195
|
seen: set[Path] = set()
|
|
196
|
+
validation_error: FileNotFoundError | ValueError | None = None
|
|
196
197
|
for candidate in candidates:
|
|
197
198
|
if candidate is None:
|
|
198
199
|
continue
|
|
@@ -202,6 +203,57 @@ def _first_source_recording_dir(candidates: tuple[Path | None, ...]) -> Path | N
|
|
|
202
203
|
seen.add(expanded)
|
|
203
204
|
try:
|
|
204
205
|
return discover_session_files(expanded).session_dir.resolve()
|
|
205
|
-
except (FileNotFoundError, ValueError):
|
|
206
|
+
except (FileNotFoundError, ValueError) as error:
|
|
207
|
+
if validation_error is None and _looks_like_source_recording_dir(expanded):
|
|
208
|
+
validation_error = error
|
|
206
209
|
continue
|
|
210
|
+
if validation_error is not None:
|
|
211
|
+
raise validation_error
|
|
207
212
|
return None
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def _looks_like_source_recording_dir(path: Path) -> bool:
|
|
216
|
+
"""Return whether a folder appears to be microscope source output.
|
|
217
|
+
|
|
218
|
+
Args:
|
|
219
|
+
path: Candidate folder.
|
|
220
|
+
|
|
221
|
+
Returns:
|
|
222
|
+
``True`` when the folder contains stable source-recording markers.
|
|
223
|
+
|
|
224
|
+
Discovery errors from source-like folders should reach the user. Errors
|
|
225
|
+
from arbitrary folders should stay quiet so twopy can continue checking
|
|
226
|
+
other candidates such as a parent source folder.
|
|
227
|
+
"""
|
|
228
|
+
if not path.is_dir():
|
|
229
|
+
return False
|
|
230
|
+
fixed_markers = (
|
|
231
|
+
"stimulusData",
|
|
232
|
+
"alignedMovie.mat",
|
|
233
|
+
"defaultAlignChannel.txt",
|
|
234
|
+
"highResPd.mat",
|
|
235
|
+
"imageDescription.mat",
|
|
236
|
+
"imagingResPd.mat",
|
|
237
|
+
)
|
|
238
|
+
if any((path / marker).exists() for marker in fixed_markers):
|
|
239
|
+
return True
|
|
240
|
+
return any(_source_candidate_files(path, "*.tif")) or any(
|
|
241
|
+
_source_candidate_files(path, "*_alignment.txt")
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
def _source_candidate_files(path: Path, pattern: str) -> tuple[Path, ...]:
|
|
246
|
+
"""Return source files matching a flexible microscope filename pattern.
|
|
247
|
+
|
|
248
|
+
Args:
|
|
249
|
+
path: Candidate source folder.
|
|
250
|
+
pattern: Glob pattern.
|
|
251
|
+
|
|
252
|
+
Returns:
|
|
253
|
+
Matching files excluding macOS AppleDouble sidecars.
|
|
254
|
+
"""
|
|
255
|
+
return tuple(
|
|
256
|
+
candidate
|
|
257
|
+
for candidate in path.glob(pattern)
|
|
258
|
+
if candidate.is_file() and not candidate.name.startswith("._")
|
|
259
|
+
)
|
|
@@ -9,10 +9,12 @@ image, so hiding an ROI in the plot options cannot delete ROI data.
|
|
|
9
9
|
"""
|
|
10
10
|
|
|
11
11
|
from collections.abc import Mapping
|
|
12
|
-
from typing import
|
|
12
|
+
from typing import Protocol, cast
|
|
13
13
|
|
|
14
14
|
from qtpy.QtGui import QColor
|
|
15
15
|
|
|
16
|
+
from twopy.typing_guards import int_or_none, string_key_mapping_or_none
|
|
17
|
+
|
|
16
18
|
__all__ = [
|
|
17
19
|
"apply_roi_visibility_to_labels_layer",
|
|
18
20
|
"roi_label_value_from_label",
|
|
@@ -241,13 +243,11 @@ def _base_label_colormap_params(
|
|
|
241
243
|
color cycle while users toggle ROI visibility.
|
|
242
244
|
"""
|
|
243
245
|
stored = layer.metadata.get(_BASE_LABEL_COLORMAP_METADATA_KEY)
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
"background_value": int(cast(Any, stored_params["background_value"])),
|
|
250
|
-
}
|
|
246
|
+
stored_params = string_key_mapping_or_none(stored)
|
|
247
|
+
if stored_params is not None:
|
|
248
|
+
params = _parsed_label_colormap_params(stored_params)
|
|
249
|
+
if params is not None:
|
|
250
|
+
return params
|
|
251
251
|
|
|
252
252
|
colormap = cast(_LabelColormapLike, layer.colormap)
|
|
253
253
|
colors = getattr(colormap, "colors", ())
|
|
@@ -258,3 +258,29 @@ def _base_label_colormap_params(
|
|
|
258
258
|
}
|
|
259
259
|
layer.metadata[_BASE_LABEL_COLORMAP_METADATA_KEY] = params
|
|
260
260
|
return params
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
def _parsed_label_colormap_params(
|
|
264
|
+
params: dict[str, object],
|
|
265
|
+
) -> dict[str, int | float] | None:
|
|
266
|
+
"""Return persisted label-colormap parameters when all fields parse."""
|
|
267
|
+
num_colors = int_or_none(params.get("num_colors"))
|
|
268
|
+
seed = _float_or_none(params.get("seed"))
|
|
269
|
+
background_value = int_or_none(params.get("background_value"))
|
|
270
|
+
if num_colors is None or seed is None or background_value is None:
|
|
271
|
+
return None
|
|
272
|
+
return {
|
|
273
|
+
"num_colors": num_colors,
|
|
274
|
+
"seed": seed,
|
|
275
|
+
"background_value": background_value,
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
def _float_or_none(value: object) -> float | None:
|
|
280
|
+
"""Return a float for simple numeric metadata values."""
|
|
281
|
+
if isinstance(value, bool) or not isinstance(value, int | float | str):
|
|
282
|
+
return None
|
|
283
|
+
try:
|
|
284
|
+
return float(value)
|
|
285
|
+
except ValueError:
|
|
286
|
+
return None
|
|
@@ -32,7 +32,9 @@ from twopy.napari.types import NapariRecordingView
|
|
|
32
32
|
from twopy.roi import RoiSet
|
|
33
33
|
from twopy.spatial import SpatialCrop
|
|
34
34
|
|
|
35
|
-
|
|
35
|
+
APPLICATION_TITLE = "twopy"
|
|
36
|
+
|
|
37
|
+
__all__ = ["APPLICATION_TITLE", "create_viewer", "open_recording_in_napari"]
|
|
36
38
|
|
|
37
39
|
|
|
38
40
|
class _LabelsLayerWithBrushSize(Protocol):
|
|
@@ -229,7 +231,7 @@ def create_viewer() -> NapariViewer:
|
|
|
229
231
|
"""
|
|
230
232
|
import napari
|
|
231
233
|
|
|
232
|
-
return cast(NapariViewer, napari.Viewer())
|
|
234
|
+
return cast(NapariViewer, napari.Viewer(title=APPLICATION_TITLE))
|
|
233
235
|
|
|
234
236
|
|
|
235
237
|
def set_labels_brush_size(layer: object, *, brush_size: int) -> None:
|
|
@@ -154,7 +154,11 @@ def _find_one_file(session_dir: Path, pattern: str, label: str) -> Path:
|
|
|
154
154
|
The raw movie and alignment text include stimulus/run details in their
|
|
155
155
|
names. A strict one-match check avoids guessing when the folder is messy.
|
|
156
156
|
"""
|
|
157
|
-
matches = sorted(
|
|
157
|
+
matches = sorted(
|
|
158
|
+
path
|
|
159
|
+
for path in session_dir.glob(pattern)
|
|
160
|
+
if path.is_file() and not path.name.startswith("._")
|
|
161
|
+
)
|
|
158
162
|
|
|
159
163
|
if len(matches) != 1:
|
|
160
164
|
joined = ", ".join(str(path) for path in matches) if matches else "none"
|
|
@@ -10,9 +10,9 @@ per-``stimtype`` mapping explicit for scripts and analysis code.
|
|
|
10
10
|
"""
|
|
11
11
|
|
|
12
12
|
from dataclasses import dataclass
|
|
13
|
-
from typing import cast
|
|
14
13
|
|
|
15
14
|
from twopy.converted import RecordingData
|
|
15
|
+
from twopy.typing_guards import require_string_key_mapping
|
|
16
16
|
|
|
17
17
|
__all__ = [
|
|
18
18
|
"StimulusSpecificColumnMapping",
|
|
@@ -176,10 +176,14 @@ def _mappings_from_metadata(
|
|
|
176
176
|
|
|
177
177
|
mappings: list[StimulusSpecificColumnMapping] = []
|
|
178
178
|
for column in columns:
|
|
179
|
-
|
|
179
|
+
try:
|
|
180
|
+
column_metadata = require_string_key_mapping(
|
|
181
|
+
column,
|
|
182
|
+
name=f"stimulus metadata for stimtype {stimtype!r} column",
|
|
183
|
+
)
|
|
184
|
+
except ValueError as error:
|
|
180
185
|
msg = f"stimulus metadata for stimtype {stimtype!r} is malformed"
|
|
181
|
-
raise ValueError(msg)
|
|
182
|
-
column_metadata = cast(dict[str, object], column)
|
|
186
|
+
raise ValueError(msg) from error
|
|
183
187
|
if column_metadata.get("column_name") != stable_column_name:
|
|
184
188
|
continue
|
|
185
189
|
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: twopy
|
|
3
|
+
Version: 0.1.3
|
|
4
|
+
Summary: Two-photon imaging analysis tool with a napari interface.
|
|
5
|
+
Author-email: Gustavo Madeira Santana <fromweb1@gumadeiras.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Requires-Python: <3.14,>=3.13
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
Requires-Dist: h5py<4,>=3.16
|
|
10
|
+
Requires-Dist: matplotlib<4,>=3.10
|
|
11
|
+
Requires-Dist: napari[all]<0.8,>=0.7
|
|
12
|
+
Requires-Dist: numpy<3,>=2.3
|
|
13
|
+
Requires-Dist: pre-commit<5,>=4.6
|
|
14
|
+
Requires-Dist: qtpy<3,>=2.4
|
|
15
|
+
Requires-Dist: pyyaml<7,>=6.0.3
|
|
16
|
+
Requires-Dist: ruff>=0.15
|
|
17
|
+
Requires-Dist: scipy<2,>=1.17
|
|
18
|
+
Requires-Dist: tifffile<2027,>=2026.3
|
|
19
|
+
Requires-Dist: ty<0.1,>=0.0.34
|
|
20
|
+
|
|
21
|
+
# twopy
|
|
22
|
+
|
|
23
|
+
Two-photon imaging analysis tool for the Clark Lab output format.
|
|
24
|
+
|
|
25
|
+

|
|
26
|
+
|
|
27
|
+
## Getting Started
|
|
28
|
+
|
|
29
|
+
twopy lets you open two-photon recordings, draw ROIs, plot responses in real time,
|
|
30
|
+
process and analyze them, and save them.
|
|
31
|
+
|
|
32
|
+
When you first load a recording, twopy converts it to a standardized HDF5 format.
|
|
33
|
+
The converted format includes the aligned movie, mean image, stimulus tables,
|
|
34
|
+
photodiode signals, and recording metadata. Analysis and the GUI both work from
|
|
35
|
+
the converted files, so the original source files remain separate from twopy's outputs.
|
|
36
|
+
|
|
37
|
+
## Install
|
|
38
|
+
|
|
39
|
+
Examples use micromamba, but any conda-compatible environment manager should work.
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
```sh
|
|
43
|
+
micromamba create -n twopy -c conda-forge python=3.13 pip -y
|
|
44
|
+
micromamba run -n twopy python -m pip install twopy
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Verify the install:
|
|
48
|
+
|
|
49
|
+
```sh
|
|
50
|
+
micromamba run -n twopy python -c 'import twopy; print(twopy.__name__)'
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
For a source checkout, copy `config.example.yml` to `config.yml` and edit the
|
|
54
|
+
paths for your computer. `config.yml` stays local and is not tracked by git.
|
|
55
|
+
|
|
56
|
+
## Start
|
|
57
|
+
|
|
58
|
+
```sh
|
|
59
|
+
micromamba activate twopy
|
|
60
|
+
twopy
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Open a source recording or converted recording directly:
|
|
64
|
+
|
|
65
|
+
```sh
|
|
66
|
+
twopy /path/to/source/recording
|
|
67
|
+
twopy /path/to/recording_data.h5
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Basic GUI flow:
|
|
71
|
+
|
|
72
|
+
1. Choose a recording.
|
|
73
|
+
2. Draw or edit ROIs in the `rois` Labels layer.
|
|
74
|
+
3. Click Save ROIs.
|
|
75
|
+
4. Update response plots from the current ROIs.
|
|
76
|
+
5. Save analysis outputs.
|
|
77
|
+
|
|
78
|
+
When a source recording has not been converted yet, twopy converts it first.
|
|
79
|
+
Converted data includes the aligned movie, mean image, stimulus tables,
|
|
80
|
+
photodiode signals, and recording metadata.
|
|
81
|
+
|
|
82
|
+
## Docs
|
|
83
|
+
|
|
84
|
+
- [GUI guide](docs/gui.md): napari loading, ROI editing, plotting, and saved
|
|
85
|
+
outputs.
|
|
86
|
+
- [Python API guide](docs/python_api.md): find recordings, convert data, run
|
|
87
|
+
analysis, and open napari from scripts.
|
|
88
|
+
- [Development guide](docs/development.md): source setup and local checks.
|
|
89
|
+
- [Input data spec](docs/input_data_spec.md): short recording folder contract.
|
|
90
|
+
- [Recording file reference](docs/recording_file_schema.md): detailed source
|
|
91
|
+
and converted file schema.
|
|
92
|
+
|
|
93
|
+
## Check
|
|
94
|
+
|
|
95
|
+
```sh
|
|
96
|
+
micromamba run -n twopy pre-commit run --all-files
|
|
97
|
+
```
|
|
@@ -5,6 +5,7 @@ Outputs: unittest assertions that confirm basic package metadata is available.
|
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
7
|
import unittest
|
|
8
|
+
from importlib.metadata import version
|
|
8
9
|
|
|
9
10
|
import twopy
|
|
10
11
|
|
|
@@ -18,7 +19,7 @@ class ImportTest(unittest.TestCase):
|
|
|
18
19
|
Inputs: imported twopy module.
|
|
19
20
|
Outputs: a passing assertion when the version matches package metadata.
|
|
20
21
|
"""
|
|
21
|
-
self.assertEqual(twopy.__version__, "
|
|
22
|
+
self.assertEqual(twopy.__version__, version("twopy"))
|
|
22
23
|
|
|
23
24
|
|
|
24
25
|
if __name__ == "__main__":
|