TwoPy 0.1.4__tar.gz → 0.1.5__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.4 → twopy-0.1.5}/PKG-INFO +3 -4
- {twopy-0.1.4 → twopy-0.1.5}/README.md +2 -3
- {twopy-0.1.4 → twopy-0.1.5}/pyproject.toml +1 -1
- {twopy-0.1.4 → twopy-0.1.5}/src/twopy/__init__.py +2 -2
- {twopy-0.1.4 → twopy-0.1.5}/src/twopy/napari/__init__.py +2 -2
- {twopy-0.1.4 → twopy-0.1.5}/src/twopy/napari/controls.py +131 -261
- {twopy-0.1.4 → twopy-0.1.5}/src/twopy/napari/launcher.py +9 -13
- {twopy-0.1.4 → twopy-0.1.5}/src/twopy/napari/plotting/__init__.py +2 -2
- {twopy-0.1.4 → twopy-0.1.5}/src/twopy/napari/plotting/dff_options.py +1 -1
- {twopy-0.1.4 → twopy-0.1.5}/src/twopy/napari/plotting/docks/__init__.py +2 -2
- {twopy-0.1.4 → twopy-0.1.5}/src/twopy/napari/plotting/docks/api.py +53 -20
- {twopy-0.1.4 → twopy-0.1.5}/src/twopy/napari/plotting/docks/options_panel.py +4 -2
- {twopy-0.1.4 → twopy-0.1.5}/src/twopy/napari/plotting/docks/response_plot_widget.py +2 -2
- {twopy-0.1.4 → twopy-0.1.5}/src/twopy/napari/plotting/options.py +1 -27
- {twopy-0.1.4 → twopy-0.1.5}/src/twopy/napari/plotting/processing_options.py +2 -1
- {twopy-0.1.4 → twopy-0.1.5}/src/twopy/napari/plotting/response_window_options.py +1 -1
- {twopy-0.1.4 → twopy-0.1.5}/src/twopy/napari/plotting/widgets.py +1 -1
- {twopy-0.1.4 → twopy-0.1.5}/src/twopy/napari/roi.py +1 -1
- {twopy-0.1.4 → twopy-0.1.5}/src/twopy/napari/session.py +4 -27
- twopy-0.1.5/src/twopy/napari/sidebar.py +123 -0
- {twopy-0.1.4 → twopy-0.1.5}/src/twopy/napari/types.py +3 -6
- {twopy-0.1.4 → twopy-0.1.5}/src/twopy/napari/viewer.py +19 -33
- {twopy-0.1.4 → twopy-0.1.5}/src/twopy.egg-info/PKG-INFO +3 -4
- {twopy-0.1.4 → twopy-0.1.5}/src/twopy.egg-info/SOURCES.txt +1 -1
- {twopy-0.1.4 → twopy-0.1.5}/tests/test_napari.py +155 -215
- twopy-0.1.4/src/twopy/napari/layout.py +0 -147
- {twopy-0.1.4 → twopy-0.1.5}/setup.cfg +0 -0
- {twopy-0.1.4 → twopy-0.1.5}/src/twopy/_segments.py +0 -0
- {twopy-0.1.4 → twopy-0.1.5}/src/twopy/_version.py +0 -0
- {twopy-0.1.4 → twopy-0.1.5}/src/twopy/analysis/__init__.py +0 -0
- {twopy-0.1.4 → twopy-0.1.5}/src/twopy/analysis/background_subtraction.py +0 -0
- {twopy-0.1.4 → twopy-0.1.5}/src/twopy/analysis/dff.py +0 -0
- {twopy-0.1.4 → twopy-0.1.5}/src/twopy/analysis/dff_options.py +0 -0
- {twopy-0.1.4 → twopy-0.1.5}/src/twopy/analysis/epoch_mapping.py +0 -0
- {twopy-0.1.4 → twopy-0.1.5}/src/twopy/analysis/motion.py +0 -0
- {twopy-0.1.4 → twopy-0.1.5}/src/twopy/analysis/persistence.py +0 -0
- {twopy-0.1.4 → twopy-0.1.5}/src/twopy/analysis/response_processing/__init__.py +0 -0
- {twopy-0.1.4 → twopy-0.1.5}/src/twopy/analysis/response_processing/apply.py +0 -0
- {twopy-0.1.4 → twopy-0.1.5}/src/twopy/analysis/response_processing/options.py +0 -0
- {twopy-0.1.4 → twopy-0.1.5}/src/twopy/analysis/response_processing/persistence.py +0 -0
- {twopy-0.1.4 → twopy-0.1.5}/src/twopy/analysis/response_processing/qc.py +0 -0
- {twopy-0.1.4 → twopy-0.1.5}/src/twopy/analysis/response_processing/signals.py +0 -0
- {twopy-0.1.4 → twopy-0.1.5}/src/twopy/analysis/response_window_options.py +0 -0
- {twopy-0.1.4 → twopy-0.1.5}/src/twopy/analysis/responses.py +0 -0
- {twopy-0.1.4 → twopy-0.1.5}/src/twopy/analysis/trials.py +0 -0
- {twopy-0.1.4 → twopy-0.1.5}/src/twopy/analysis/workflow.py +0 -0
- {twopy-0.1.4 → twopy-0.1.5}/src/twopy/api.py +0 -0
- {twopy-0.1.4 → twopy-0.1.5}/src/twopy/config.py +0 -0
- {twopy-0.1.4 → twopy-0.1.5}/src/twopy/conversion/__init__.py +0 -0
- {twopy-0.1.4 → twopy-0.1.5}/src/twopy/conversion/alignment_crop.py +0 -0
- {twopy-0.1.4 → twopy-0.1.5}/src/twopy/conversion/api.py +0 -0
- {twopy-0.1.4 → twopy-0.1.5}/src/twopy/conversion/hdf5_writing.py +0 -0
- {twopy-0.1.4 → twopy-0.1.5}/src/twopy/conversion/matlab_values.py +0 -0
- {twopy-0.1.4 → twopy-0.1.5}/src/twopy/conversion/source_loading.py +0 -0
- {twopy-0.1.4 → twopy-0.1.5}/src/twopy/conversion/stimulus_code.py +0 -0
- {twopy-0.1.4 → twopy-0.1.5}/src/twopy/conversion/types.py +0 -0
- {twopy-0.1.4 → twopy-0.1.5}/src/twopy/converted.py +0 -0
- {twopy-0.1.4 → twopy-0.1.5}/src/twopy/database/__init__.py +0 -0
- {twopy-0.1.4 → twopy-0.1.5}/src/twopy/database/cache.py +0 -0
- {twopy-0.1.4 → twopy-0.1.5}/src/twopy/database/catalog.py +0 -0
- {twopy-0.1.4 → twopy-0.1.5}/src/twopy/database/connection.py +0 -0
- {twopy-0.1.4 → twopy-0.1.5}/src/twopy/database/generic.py +0 -0
- {twopy-0.1.4 → twopy-0.1.5}/src/twopy/database/modeled.py +0 -0
- {twopy-0.1.4 → twopy-0.1.5}/src/twopy/database/sql.py +0 -0
- {twopy-0.1.4 → twopy-0.1.5}/src/twopy/database/types.py +0 -0
- {twopy-0.1.4 → twopy-0.1.5}/src/twopy/frame_ranges.py +0 -0
- {twopy-0.1.4 → twopy-0.1.5}/src/twopy/inspection.py +0 -0
- {twopy-0.1.4 → twopy-0.1.5}/src/twopy/matlab.py +0 -0
- {twopy-0.1.4 → twopy-0.1.5}/src/twopy/napari/__main__.py +0 -0
- {twopy-0.1.4 → twopy-0.1.5}/src/twopy/napari/constants.py +0 -0
- {twopy-0.1.4 → twopy-0.1.5}/src/twopy/napari/display.py +0 -0
- {twopy-0.1.4 → twopy-0.1.5}/src/twopy/napari/display_paths.py +0 -0
- {twopy-0.1.4 → twopy-0.1.5}/src/twopy/napari/interactive.py +0 -0
- {twopy-0.1.4 → twopy-0.1.5}/src/twopy/napari/loading.py +0 -0
- {twopy-0.1.4 → twopy-0.1.5}/src/twopy/napari/movie.py +0 -0
- {twopy-0.1.4 → twopy-0.1.5}/src/twopy/napari/paths.py +0 -0
- {twopy-0.1.4 → twopy-0.1.5}/src/twopy/napari/plotting/data.py +0 -0
- {twopy-0.1.4 → twopy-0.1.5}/src/twopy/napari/plotting/docks/dynamic_options.py +0 -0
- {twopy-0.1.4 → twopy-0.1.5}/src/twopy/napari/plotting/docks/paths.py +0 -0
- {twopy-0.1.4 → twopy-0.1.5}/src/twopy/napari/plotting/docks/plot_area.py +0 -0
- {twopy-0.1.4 → twopy-0.1.5}/src/twopy/napari/plotting/docks/save_actions.py +0 -0
- {twopy-0.1.4 → twopy-0.1.5}/src/twopy/napari/plotting/docks/visibility_state.py +0 -0
- {twopy-0.1.4 → twopy-0.1.5}/src/twopy/napari/plotting/export.py +0 -0
- {twopy-0.1.4 → twopy-0.1.5}/src/twopy/napari/plotting/export_controls.py +0 -0
- {twopy-0.1.4 → twopy-0.1.5}/src/twopy/napari/plotting/form_controls.py +0 -0
- {twopy-0.1.4 → twopy-0.1.5}/src/twopy/napari/plotting/label_visibility.py +0 -0
- {twopy-0.1.4 → twopy-0.1.5}/src/twopy/napari/plotting/panels.py +0 -0
- {twopy-0.1.4 → twopy-0.1.5}/src/twopy/napari/protocols.py +0 -0
- {twopy-0.1.4 → twopy-0.1.5}/src/twopy/napari/responses.py +0 -0
- {twopy-0.1.4 → twopy-0.1.5}/src/twopy/napari/state.py +0 -0
- {twopy-0.1.4 → twopy-0.1.5}/src/twopy/napari/text.py +0 -0
- {twopy-0.1.4 → twopy-0.1.5}/src/twopy/parity/__init__.py +0 -0
- {twopy-0.1.4 → twopy-0.1.5}/src/twopy/parity/dff.py +0 -0
- {twopy-0.1.4 → twopy-0.1.5}/src/twopy/parity/psycho5.py +0 -0
- {twopy-0.1.4 → twopy-0.1.5}/src/twopy/parity/roi.py +0 -0
- {twopy-0.1.4 → twopy-0.1.5}/src/twopy/parity/saved_analysis.py +0 -0
- {twopy-0.1.4 → twopy-0.1.5}/src/twopy/photodiode.py +0 -0
- {twopy-0.1.4 → twopy-0.1.5}/src/twopy/photodiode_classification.py +0 -0
- {twopy-0.1.4 → twopy-0.1.5}/src/twopy/roi.py +0 -0
- {twopy-0.1.4 → twopy-0.1.5}/src/twopy/session.py +0 -0
- {twopy-0.1.4 → twopy-0.1.5}/src/twopy/spatial.py +0 -0
- {twopy-0.1.4 → twopy-0.1.5}/src/twopy/stimulus.py +0 -0
- {twopy-0.1.4 → twopy-0.1.5}/src/twopy/synchronization.py +0 -0
- {twopy-0.1.4 → twopy-0.1.5}/src/twopy/typing_guards.py +0 -0
- {twopy-0.1.4 → twopy-0.1.5}/src/twopy.egg-info/dependency_links.txt +0 -0
- {twopy-0.1.4 → twopy-0.1.5}/src/twopy.egg-info/entry_points.txt +0 -0
- {twopy-0.1.4 → twopy-0.1.5}/src/twopy.egg-info/requires.txt +0 -0
- {twopy-0.1.4 → twopy-0.1.5}/src/twopy.egg-info/top_level.txt +0 -0
- {twopy-0.1.4 → twopy-0.1.5}/tests/test_api.py +0 -0
- {twopy-0.1.4 → twopy-0.1.5}/tests/test_background_subtraction.py +0 -0
- {twopy-0.1.4 → twopy-0.1.5}/tests/test_config.py +0 -0
- {twopy-0.1.4 → twopy-0.1.5}/tests/test_conversion.py +0 -0
- {twopy-0.1.4 → twopy-0.1.5}/tests/test_converted.py +0 -0
- {twopy-0.1.4 → twopy-0.1.5}/tests/test_database.py +0 -0
- {twopy-0.1.4 → twopy-0.1.5}/tests/test_dff.py +0 -0
- {twopy-0.1.4 → twopy-0.1.5}/tests/test_epoch_mapping.py +0 -0
- {twopy-0.1.4 → twopy-0.1.5}/tests/test_frame_ranges.py +0 -0
- {twopy-0.1.4 → twopy-0.1.5}/tests/test_import.py +0 -0
- {twopy-0.1.4 → twopy-0.1.5}/tests/test_inspection.py +0 -0
- {twopy-0.1.4 → twopy-0.1.5}/tests/test_matlab.py +0 -0
- {twopy-0.1.4 → twopy-0.1.5}/tests/test_motion.py +0 -0
- {twopy-0.1.4 → twopy-0.1.5}/tests/test_packaging.py +0 -0
- {twopy-0.1.4 → twopy-0.1.5}/tests/test_parity.py +0 -0
- {twopy-0.1.4 → twopy-0.1.5}/tests/test_parity_psycho5.py +0 -0
- {twopy-0.1.4 → twopy-0.1.5}/tests/test_persistence.py +0 -0
- {twopy-0.1.4 → twopy-0.1.5}/tests/test_photodiode_classification.py +0 -0
- {twopy-0.1.4 → twopy-0.1.5}/tests/test_real_fixture.py +0 -0
- {twopy-0.1.4 → twopy-0.1.5}/tests/test_response_processing.py +0 -0
- {twopy-0.1.4 → twopy-0.1.5}/tests/test_response_window_options.py +0 -0
- {twopy-0.1.4 → twopy-0.1.5}/tests/test_responses.py +0 -0
- {twopy-0.1.4 → twopy-0.1.5}/tests/test_roi.py +0 -0
- {twopy-0.1.4 → twopy-0.1.5}/tests/test_saved_analysis.py +0 -0
- {twopy-0.1.4 → twopy-0.1.5}/tests/test_session.py +0 -0
- {twopy-0.1.4 → twopy-0.1.5}/tests/test_stimulus.py +0 -0
- {twopy-0.1.4 → twopy-0.1.5}/tests/test_synchronization.py +0 -0
- {twopy-0.1.4 → twopy-0.1.5}/tests/test_trials.py +0 -0
- {twopy-0.1.4 → twopy-0.1.5}/tests/test_workflow.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: twopy
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.5
|
|
4
4
|
Summary: Two-photon imaging analysis tool with a napari interface.
|
|
5
5
|
Author-email: Gustavo Madeira Santana <fromweb1@gumadeiras.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -66,9 +66,8 @@ Basic GUI flow:
|
|
|
66
66
|
|
|
67
67
|
1. Choose a recording.
|
|
68
68
|
2. Draw or edit ROIs in the `rois` Labels layer.
|
|
69
|
-
3.
|
|
70
|
-
4.
|
|
71
|
-
5. Save analysis outputs.
|
|
69
|
+
3. Update response plots from the current ROIs.
|
|
70
|
+
4. Click Save ROIs + analysis when the plots look right.
|
|
72
71
|
|
|
73
72
|
When a source recording has not been converted yet, twopy converts it first. Converted data includes the aligned movie, mean image, stimulus tables, photodiode signals, and recording metadata.
|
|
74
73
|
|
|
@@ -46,9 +46,8 @@ Basic GUI flow:
|
|
|
46
46
|
|
|
47
47
|
1. Choose a recording.
|
|
48
48
|
2. Draw or edit ROIs in the `rois` Labels layer.
|
|
49
|
-
3.
|
|
50
|
-
4.
|
|
51
|
-
5. Save analysis outputs.
|
|
49
|
+
3. Update response plots from the current ROIs.
|
|
50
|
+
4. Click Save ROIs + analysis when the plots look right.
|
|
52
51
|
|
|
53
52
|
When a source recording has not been converted yet, twopy converts it first. Converted data includes the aligned movie, mean image, stimulus tables, photodiode signals, and recording metadata.
|
|
54
53
|
|
|
@@ -57,8 +57,8 @@ from twopy.api import find_recordings
|
|
|
57
57
|
from twopy.conversion import convert_recording_to_twopy, load_source_conversion_inputs
|
|
58
58
|
from twopy.converted import load_converted_recording, recording_frame_rate_hz
|
|
59
59
|
from twopy.napari import (
|
|
60
|
-
NapariControlDocks,
|
|
61
60
|
NapariRecordingView,
|
|
61
|
+
NapariSidebarWidgets,
|
|
62
62
|
add_twopy_magicgui_controls,
|
|
63
63
|
launch_napari,
|
|
64
64
|
open_recording_in_napari,
|
|
@@ -149,8 +149,8 @@ __all__ = [
|
|
|
149
149
|
"make_roi_set",
|
|
150
150
|
"map_stimulus_epochs_to_frame_windows",
|
|
151
151
|
"map_stimulus_specific_column",
|
|
152
|
-
"NapariControlDocks",
|
|
153
152
|
"NapariRecordingView",
|
|
153
|
+
"NapariSidebarWidgets",
|
|
154
154
|
"open_recording_in_napari",
|
|
155
155
|
"pair_photodiode_events_to_imaging_frames",
|
|
156
156
|
"PhotodiodeAlignment",
|
|
@@ -9,7 +9,7 @@ database, ROI, and analysis modules stay independent so a future napari plugin
|
|
|
9
9
|
can wrap these helpers instead of owning scientific logic.
|
|
10
10
|
"""
|
|
11
11
|
|
|
12
|
-
from twopy.napari.controls import
|
|
12
|
+
from twopy.napari.controls import NapariSidebarWidgets, add_twopy_magicgui_controls
|
|
13
13
|
from twopy.napari.launcher import launch_napari, main
|
|
14
14
|
from twopy.napari.roi import roi_label_image_from_layer, save_napari_label_rois
|
|
15
15
|
from twopy.napari.types import NapariRecordingView
|
|
@@ -17,7 +17,7 @@ from twopy.napari.viewer import open_recording_in_napari
|
|
|
17
17
|
|
|
18
18
|
__all__ = [
|
|
19
19
|
"NapariRecordingView",
|
|
20
|
-
"
|
|
20
|
+
"NapariSidebarWidgets",
|
|
21
21
|
"add_twopy_magicgui_controls",
|
|
22
22
|
"launch_napari",
|
|
23
23
|
"main",
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
"""Magicgui controls for the twopy napari adapter.
|
|
2
2
|
|
|
3
3
|
Inputs: napari viewer, current ROI Labels layer, and paths chosen by the user.
|
|
4
|
-
Outputs:
|
|
5
|
-
|
|
4
|
+
Outputs: one tabbed right-sidebar dock for loading converted recordings,
|
|
5
|
+
tracking loaded recordings, and controlling response plots.
|
|
6
6
|
|
|
7
|
-
The
|
|
8
|
-
|
|
7
|
+
The controls own GUI callbacks only. Loading recordings still goes through the
|
|
8
|
+
same typed helpers used by scripts.
|
|
9
9
|
"""
|
|
10
10
|
|
|
11
11
|
from collections.abc import Callable
|
|
@@ -13,27 +13,18 @@ from dataclasses import dataclass, field
|
|
|
13
13
|
from pathlib import Path
|
|
14
14
|
from typing import Protocol, cast
|
|
15
15
|
|
|
16
|
-
import numpy as np
|
|
17
16
|
from magicgui.widgets import FileEdit
|
|
17
|
+
from qtpy.QtCore import Qt
|
|
18
|
+
from qtpy.QtWidgets import QFormLayout, QLabel, QSizePolicy, QVBoxLayout, QWidget
|
|
18
19
|
|
|
19
20
|
from twopy.converted import RecordingData
|
|
20
21
|
from twopy.napari.constants import DEFAULT_PATH_TEXT
|
|
21
|
-
from twopy.napari.layout import (
|
|
22
|
-
place_loaded_recordings_dock_after_load,
|
|
23
|
-
place_save_rois_dock_before_layer_list,
|
|
24
|
-
)
|
|
25
22
|
from twopy.napari.loading import resolve_or_convert_recording
|
|
26
|
-
from twopy.napari.movie import resolve_movie_frame_range
|
|
27
23
|
from twopy.napari.paths import (
|
|
28
24
|
PathInput,
|
|
29
25
|
is_default_path,
|
|
30
|
-
resolve_widget_output_path,
|
|
31
26
|
)
|
|
32
27
|
from twopy.napari.protocols import NapariViewer
|
|
33
|
-
from twopy.napari.roi import (
|
|
34
|
-
roi_label_image_from_layer_for_recording,
|
|
35
|
-
save_napari_label_rois,
|
|
36
|
-
)
|
|
37
28
|
from twopy.napari.session import (
|
|
38
29
|
LoadedNapariRecording,
|
|
39
30
|
LoadedRecordingsPanel,
|
|
@@ -41,48 +32,33 @@ from twopy.napari.session import (
|
|
|
41
32
|
render_loaded_recordings_panel,
|
|
42
33
|
select_loaded_recording,
|
|
43
34
|
unload_loaded_recording,
|
|
44
|
-
|
|
35
|
+
)
|
|
36
|
+
from twopy.napari.sidebar import (
|
|
37
|
+
TWOPY_SIDEBAR_MINIMUM_WIDTH,
|
|
38
|
+
create_twopy_sidebar_widget,
|
|
45
39
|
)
|
|
46
40
|
from twopy.napari.state import (
|
|
47
41
|
read_last_recording_folder,
|
|
48
42
|
recording_folder_for_state,
|
|
49
43
|
write_last_recording_folder,
|
|
50
44
|
)
|
|
51
|
-
from twopy.napari.text import counted_noun
|
|
52
|
-
|
|
53
|
-
__all__ = ["NapariControlDocks", "add_twopy_magicgui_controls"]
|
|
54
45
|
|
|
55
|
-
|
|
56
|
-
class _RoiSavePathReceiver(Protocol):
|
|
57
|
-
"""Small protocol for widgets that mirror the selected ROI save path."""
|
|
58
|
-
|
|
59
|
-
def set_roi_save_file(self, roi_save_file: Path | None) -> None:
|
|
60
|
-
"""Store the ROI HDF5 path for the selected recording.
|
|
61
|
-
|
|
62
|
-
Args:
|
|
63
|
-
roi_save_file: ROI output path, or ``None`` when no recording is
|
|
64
|
-
selected.
|
|
65
|
-
|
|
66
|
-
Returns:
|
|
67
|
-
None.
|
|
68
|
-
"""
|
|
69
|
-
...
|
|
46
|
+
__all__ = ["NapariSidebarWidgets", "add_twopy_magicgui_controls"]
|
|
70
47
|
|
|
71
48
|
|
|
72
49
|
@dataclass
|
|
73
|
-
class
|
|
74
|
-
"""
|
|
50
|
+
class NapariSidebarWidgets:
|
|
51
|
+
"""Widgets created for the twopy napari right sidebar.
|
|
75
52
|
|
|
76
53
|
Inputs: Qt widgets and napari dock objects.
|
|
77
54
|
Outputs: grouped references for scripts and tests.
|
|
78
55
|
"""
|
|
79
56
|
|
|
80
57
|
load_widget: object
|
|
81
|
-
load_dock_widget: object
|
|
82
58
|
loaded_recordings_widget: object
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
59
|
+
response_options_widget: object | None
|
|
60
|
+
sidebar_widget: object
|
|
61
|
+
sidebar_dock_widget: object
|
|
86
62
|
|
|
87
63
|
|
|
88
64
|
@dataclass
|
|
@@ -111,6 +87,17 @@ class NapariControlState:
|
|
|
111
87
|
replace_selected_on_next_load: bool = False
|
|
112
88
|
|
|
113
89
|
|
|
90
|
+
class _LoadRecordingGui(Protocol):
|
|
91
|
+
"""Small shape of the magicgui recording-load callable.
|
|
92
|
+
|
|
93
|
+
Inputs: magicgui function widget created in ``_make_twopy_load_widget``.
|
|
94
|
+
Outputs: access to the child controls that need compact Qt placement.
|
|
95
|
+
"""
|
|
96
|
+
|
|
97
|
+
recording_folder: object
|
|
98
|
+
roi_file_to_load: object
|
|
99
|
+
|
|
100
|
+
|
|
114
101
|
def add_twopy_magicgui_controls(
|
|
115
102
|
viewer: NapariViewer,
|
|
116
103
|
*,
|
|
@@ -118,12 +105,13 @@ def add_twopy_magicgui_controls(
|
|
|
118
105
|
roi_save_file: Path,
|
|
119
106
|
recording: RecordingData | None = None,
|
|
120
107
|
response_plot_widget: object | None = None,
|
|
108
|
+
response_options_widget: object | None = None,
|
|
121
109
|
mean_image_layer: object | None = None,
|
|
122
110
|
movie_layer: object | None = None,
|
|
123
111
|
dock_name: str = "twopy",
|
|
124
112
|
dock_area: str = "right",
|
|
125
|
-
) ->
|
|
126
|
-
"""Add the
|
|
113
|
+
) -> NapariSidebarWidgets:
|
|
114
|
+
"""Add the twopy right sidebar to a napari viewer.
|
|
127
115
|
|
|
128
116
|
Args:
|
|
129
117
|
viewer: Napari viewer that should receive the dock widget.
|
|
@@ -134,6 +122,8 @@ def add_twopy_magicgui_controls(
|
|
|
134
122
|
recording: Optional loaded recording used to populate GUI defaults.
|
|
135
123
|
response_plot_widget: Optional response plotting widget to refresh
|
|
136
124
|
after a new recording is loaded.
|
|
125
|
+
response_options_widget: Optional response-options tabs to place in the
|
|
126
|
+
same right sidebar as the Load tab.
|
|
137
127
|
mean_image_layer: Optional mean-image layer for an already loaded
|
|
138
128
|
startup recording.
|
|
139
129
|
movie_layer: Optional movie layer for an already loaded startup
|
|
@@ -142,11 +132,12 @@ def add_twopy_magicgui_controls(
|
|
|
142
132
|
dock_area: Napari dock area.
|
|
143
133
|
|
|
144
134
|
Returns:
|
|
145
|
-
Created load and
|
|
135
|
+
Created load, loaded-recordings, and sidebar widgets plus the napari
|
|
136
|
+
dock widget that contains them.
|
|
146
137
|
|
|
147
|
-
The
|
|
148
|
-
|
|
149
|
-
|
|
138
|
+
The sidebar keeps twopy's right-side controls in one tabbed dock. The loader
|
|
139
|
+
itself stays small and uses the same helpers as scripts, so a future plugin
|
|
140
|
+
can wrap this without changing recording-load semantics.
|
|
150
141
|
"""
|
|
151
142
|
state = NapariControlState(
|
|
152
143
|
viewer=viewer,
|
|
@@ -168,69 +159,46 @@ def add_twopy_magicgui_controls(
|
|
|
168
159
|
)
|
|
169
160
|
state.selected_recording_index = 0
|
|
170
161
|
load_widget = _make_twopy_load_widget(state)
|
|
171
|
-
load_dock_widget = viewer.window.add_dock_widget(
|
|
172
|
-
load_widget,
|
|
173
|
-
name=dock_name,
|
|
174
|
-
area=dock_area,
|
|
175
|
-
)
|
|
176
162
|
loaded_recordings_widget = _make_loaded_recordings_widget(state)
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
)
|
|
182
|
-
place_loaded_recordings_dock_after_load(
|
|
183
|
-
viewer,
|
|
184
|
-
load_dock_widget,
|
|
185
|
-
loaded_recordings_dock_widget,
|
|
163
|
+
sidebar_widget = create_twopy_sidebar_widget(
|
|
164
|
+
load_widget=load_widget,
|
|
165
|
+
loaded_recordings_widget=loaded_recordings_widget,
|
|
166
|
+
response_options_widget=response_options_widget,
|
|
186
167
|
)
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
area="left",
|
|
168
|
+
sidebar_dock_widget = viewer.window.add_dock_widget(
|
|
169
|
+
sidebar_widget,
|
|
170
|
+
name=dock_name,
|
|
171
|
+
area=dock_area,
|
|
192
172
|
)
|
|
193
|
-
|
|
194
|
-
|
|
173
|
+
if isinstance(sidebar_dock_widget, QWidget):
|
|
174
|
+
sidebar_dock_widget.setMinimumWidth(TWOPY_SIDEBAR_MINIMUM_WIDTH)
|
|
175
|
+
return NapariSidebarWidgets(
|
|
195
176
|
load_widget=load_widget,
|
|
196
|
-
load_dock_widget=load_dock_widget,
|
|
197
177
|
loaded_recordings_widget=loaded_recordings_widget,
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
178
|
+
response_options_widget=response_options_widget,
|
|
179
|
+
sidebar_widget=sidebar_widget,
|
|
180
|
+
sidebar_dock_widget=sidebar_dock_widget,
|
|
201
181
|
)
|
|
202
182
|
|
|
203
183
|
|
|
204
184
|
def _make_twopy_load_widget(state: NapariControlState) -> object:
|
|
205
|
-
"""Create the right-side
|
|
185
|
+
"""Create the right-side widget for loading recordings.
|
|
206
186
|
|
|
207
187
|
Args:
|
|
208
188
|
state: Mutable napari control state.
|
|
209
189
|
|
|
210
190
|
Returns:
|
|
211
|
-
|
|
191
|
+
Qt widget with compact recording-load controls.
|
|
212
192
|
"""
|
|
213
193
|
from magicgui import magicgui
|
|
214
|
-
from magicgui.widgets import Container, Label
|
|
215
|
-
|
|
216
|
-
default_movie_end_frame = _default_movie_end_frame(state.recording)
|
|
217
194
|
|
|
218
195
|
@magicgui(
|
|
219
196
|
call_button=False,
|
|
220
197
|
layout="vertical",
|
|
221
|
-
movie_frame_range={
|
|
222
|
-
"widget_type": "RangeSlider",
|
|
223
|
-
"min": 0,
|
|
224
|
-
"max": max(default_movie_end_frame, 0),
|
|
225
|
-
"step": 1,
|
|
226
|
-
"tracking": False,
|
|
227
|
-
},
|
|
228
198
|
)
|
|
229
199
|
def load_recording(
|
|
230
200
|
recording_folder: Path = Path(DEFAULT_PATH_TEXT),
|
|
231
201
|
roi_file_to_load: Path = Path(DEFAULT_PATH_TEXT),
|
|
232
|
-
movie_frame_range: tuple[int, int] = (0, default_movie_end_frame),
|
|
233
|
-
load_movie: bool = True,
|
|
234
202
|
) -> str:
|
|
235
203
|
"""Load a recording into the current napari viewer.
|
|
236
204
|
|
|
@@ -240,11 +208,6 @@ def _make_twopy_load_widget(state: NapariControlState) -> object:
|
|
|
240
208
|
first when twopy output does not exist yet.
|
|
241
209
|
roi_file_to_load: Optional existing ROI HDF5 file. ``default`` uses
|
|
242
210
|
``rois.h5`` in the selected recording folder when it exists.
|
|
243
|
-
movie_frame_range: First and last movie frames to load. When the
|
|
244
|
-
empty-launch widget still shows ``(0, 0)``, twopy resolves it
|
|
245
|
-
to the recording's full frame range after the folder is
|
|
246
|
-
selected.
|
|
247
|
-
load_movie: Whether to load movie frames.
|
|
248
211
|
|
|
249
212
|
Returns:
|
|
250
213
|
Human-readable status for the dock widget.
|
|
@@ -266,23 +229,6 @@ def _make_twopy_load_widget(state: NapariControlState) -> object:
|
|
|
266
229
|
if is_default_path(roi_file_to_load)
|
|
267
230
|
else _resolve_optional_roi_path(roi_file_to_load)
|
|
268
231
|
)
|
|
269
|
-
loaded_recording = _load_recording_for_defaults(
|
|
270
|
-
recording_data_path=paths.recording_data_path,
|
|
271
|
-
movie_path=paths.movie_path,
|
|
272
|
-
)
|
|
273
|
-
movie_range = None
|
|
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
|
-
)
|
|
280
|
-
movie_range = resolve_movie_frame_range(
|
|
281
|
-
start_frame=start_frame,
|
|
282
|
-
end_frame=end_frame,
|
|
283
|
-
frame_count=loaded_recording.movie.shape[0],
|
|
284
|
-
zero_end_means_last=True,
|
|
285
|
-
)
|
|
286
232
|
|
|
287
233
|
# Import here to avoid a top-level cycle: viewer creates controls,
|
|
288
234
|
# while controls can load recordings into that same viewer.
|
|
@@ -294,7 +240,7 @@ def _make_twopy_load_widget(state: NapariControlState) -> object:
|
|
|
294
240
|
roi_set=roi_path,
|
|
295
241
|
roi_save_file=paths.roi_save_file,
|
|
296
242
|
movie_path=paths.movie_path,
|
|
297
|
-
movie_frame_range=
|
|
243
|
+
movie_frame_range=(0, None),
|
|
298
244
|
add_controls=False,
|
|
299
245
|
)
|
|
300
246
|
record_loaded_view(
|
|
@@ -309,9 +255,6 @@ def _make_twopy_load_widget(state: NapariControlState) -> object:
|
|
|
309
255
|
paths.recording_data_path,
|
|
310
256
|
),
|
|
311
257
|
)
|
|
312
|
-
frame_end = _default_movie_end_frame(view.recording)
|
|
313
|
-
load_recording.movie_frame_range.max = frame_end
|
|
314
|
-
load_recording.movie_frame_range.value = movie_range or (0, frame_end)
|
|
315
258
|
_set_recording_picker_display_path(
|
|
316
259
|
cast(FileEdit, load_recording.recording_folder),
|
|
317
260
|
state,
|
|
@@ -328,10 +271,10 @@ def _make_twopy_load_widget(state: NapariControlState) -> object:
|
|
|
328
271
|
load_recording.recording_folder.mode = "d"
|
|
329
272
|
load_recording.roi_file_to_load.mode = "r"
|
|
330
273
|
|
|
331
|
-
load_recording.recording_folder.label = "
|
|
332
|
-
load_recording.roi_file_to_load.label = "ROI file
|
|
333
|
-
load_recording.
|
|
334
|
-
load_recording.
|
|
274
|
+
load_recording.recording_folder.label = "Recording"
|
|
275
|
+
load_recording.roi_file_to_load.label = "ROI file"
|
|
276
|
+
cast(FileEdit, load_recording.recording_folder).choose_btn.text = "Browse"
|
|
277
|
+
cast(FileEdit, load_recording.roi_file_to_load).choose_btn.text = "Browse"
|
|
335
278
|
_configure_recording_folder_picker(
|
|
336
279
|
cast(FileEdit, load_recording.recording_folder),
|
|
337
280
|
state,
|
|
@@ -354,8 +297,8 @@ def _make_twopy_load_widget(state: NapariControlState) -> object:
|
|
|
354
297
|
return
|
|
355
298
|
load_recording()
|
|
356
299
|
|
|
357
|
-
def
|
|
358
|
-
"""Reload the current recording after
|
|
300
|
+
def reload_after_roi_change(_value: object) -> None:
|
|
301
|
+
"""Reload the current recording after the ROI file changes.
|
|
359
302
|
|
|
360
303
|
Args:
|
|
361
304
|
_value: Ignored widget signal value.
|
|
@@ -363,9 +306,8 @@ def _make_twopy_load_widget(state: NapariControlState) -> object:
|
|
|
363
306
|
Returns:
|
|
364
307
|
None.
|
|
365
308
|
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
shortened for display and is not always a real filesystem path.
|
|
309
|
+
The visible picker text may be shortened for display and is not always a
|
|
310
|
+
real filesystem path, so reloads reuse the stored recording path.
|
|
369
311
|
"""
|
|
370
312
|
if state.is_loading or not _has_recording_to_load(
|
|
371
313
|
load_recording.recording_folder.value,
|
|
@@ -381,18 +323,75 @@ def _make_twopy_load_widget(state: NapariControlState) -> object:
|
|
|
381
323
|
)
|
|
382
324
|
|
|
383
325
|
load_recording.recording_folder.changed.connect(load_after_selection)
|
|
384
|
-
load_recording.roi_file_to_load.changed.connect(
|
|
385
|
-
|
|
386
|
-
load_recording
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
326
|
+
load_recording.roi_file_to_load.changed.connect(reload_after_roi_change)
|
|
327
|
+
|
|
328
|
+
return LoadRecordingPanel(load_recording=load_recording)
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
class LoadRecordingPanel(QWidget):
|
|
332
|
+
"""Compact Qt panel that owns the recording-load controls.
|
|
333
|
+
|
|
334
|
+
Args:
|
|
335
|
+
load_recording: Magicgui callable widget whose child controls and
|
|
336
|
+
callbacks perform recording loading.
|
|
337
|
+
|
|
338
|
+
Outputs:
|
|
339
|
+
QWidget with a tight form layout for the Load tab.
|
|
340
|
+
|
|
341
|
+
The magicgui callable still owns validation and callback wiring. This panel
|
|
342
|
+
only arranges the existing controls with shorter labels so the path fields
|
|
343
|
+
and frame slider have enough horizontal room.
|
|
344
|
+
"""
|
|
345
|
+
|
|
346
|
+
load_recording: object
|
|
347
|
+
|
|
348
|
+
def __init__(self, *, load_recording: object) -> None:
|
|
349
|
+
"""Create a compact Load Recording panel.
|
|
350
|
+
|
|
351
|
+
Args:
|
|
352
|
+
load_recording: Magicgui callable widget built by
|
|
353
|
+
``_make_twopy_load_widget``.
|
|
354
|
+
|
|
355
|
+
Returns:
|
|
356
|
+
None.
|
|
357
|
+
"""
|
|
358
|
+
super().__init__()
|
|
359
|
+
self.load_recording = load_recording
|
|
360
|
+
|
|
361
|
+
layout = QVBoxLayout()
|
|
362
|
+
layout.setContentsMargins(0, 0, 0, 0)
|
|
363
|
+
layout.setSpacing(8)
|
|
364
|
+
title = QLabel("Load Recording")
|
|
365
|
+
title.setSizePolicy(QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Fixed)
|
|
366
|
+
layout.addWidget(title)
|
|
367
|
+
|
|
368
|
+
recording_gui = cast(_LoadRecordingGui, load_recording)
|
|
369
|
+
form = QFormLayout()
|
|
370
|
+
form.setContentsMargins(0, 0, 0, 0)
|
|
371
|
+
form.setHorizontalSpacing(8)
|
|
372
|
+
form.setVerticalSpacing(8)
|
|
373
|
+
form.setFieldGrowthPolicy(QFormLayout.FieldGrowthPolicy.AllNonFixedFieldsGrow)
|
|
374
|
+
form.setLabelAlignment(Qt.AlignmentFlag.AlignRight)
|
|
375
|
+
form.addRow("Recording", _control_native(recording_gui.recording_folder))
|
|
376
|
+
form.addRow("ROI file", _control_native(recording_gui.roi_file_to_load))
|
|
377
|
+
layout.addLayout(form)
|
|
378
|
+
self.setLayout(layout)
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
def _control_native(widget: object) -> QWidget:
|
|
382
|
+
"""Return the Qt widget wrapped by a magicgui control.
|
|
383
|
+
|
|
384
|
+
Args:
|
|
385
|
+
widget: Magicgui widget or Qt widget.
|
|
386
|
+
|
|
387
|
+
Returns:
|
|
388
|
+
QWidget that can be inserted into the compact Load form.
|
|
389
|
+
"""
|
|
390
|
+
native_widget = getattr(widget, "native", widget)
|
|
391
|
+
if not isinstance(native_widget, QWidget):
|
|
392
|
+
msg = f"Load control expected a QWidget, got {type(native_widget).__name__}."
|
|
393
|
+
raise TypeError(msg)
|
|
394
|
+
return native_widget
|
|
396
395
|
|
|
397
396
|
|
|
398
397
|
def _make_loaded_recordings_widget(state: NapariControlState) -> object:
|
|
@@ -413,63 +412,6 @@ def _make_loaded_recordings_widget(state: NapariControlState) -> object:
|
|
|
413
412
|
return panel
|
|
414
413
|
|
|
415
414
|
|
|
416
|
-
def _make_twopy_save_rois_widget(state: NapariControlState) -> object:
|
|
417
|
-
"""Create the left-side magicgui widget for saving ROI labels.
|
|
418
|
-
|
|
419
|
-
Args:
|
|
420
|
-
state: Mutable napari control state.
|
|
421
|
-
|
|
422
|
-
Returns:
|
|
423
|
-
magicgui widget with one Save ROIs button.
|
|
424
|
-
"""
|
|
425
|
-
from magicgui import magicgui
|
|
426
|
-
|
|
427
|
-
@magicgui(call_button="Save ROIs", layout="vertical")
|
|
428
|
-
def save_rois(roi_save_file: Path = Path(DEFAULT_PATH_TEXT)) -> str:
|
|
429
|
-
"""Save current napari ROI labels to twopy ROI HDF5.
|
|
430
|
-
|
|
431
|
-
Args:
|
|
432
|
-
roi_save_file: Destination ROI HDF5 path. ``default`` uses the
|
|
433
|
-
current recording's default ROI output path.
|
|
434
|
-
|
|
435
|
-
Returns:
|
|
436
|
-
Human-readable status for the dock widget.
|
|
437
|
-
"""
|
|
438
|
-
if state.roi_labels_layer is None:
|
|
439
|
-
return "No ROI Labels layer is available."
|
|
440
|
-
if state.recording is None:
|
|
441
|
-
return "No recording is selected."
|
|
442
|
-
try:
|
|
443
|
-
label_image = roi_label_image_from_layer_for_recording(
|
|
444
|
-
state.roi_labels_layer,
|
|
445
|
-
state.recording,
|
|
446
|
-
)
|
|
447
|
-
except ValueError as error:
|
|
448
|
-
return str(error)
|
|
449
|
-
if not np.any(label_image > 0):
|
|
450
|
-
return "No ROI labels to save."
|
|
451
|
-
|
|
452
|
-
resolved_output_path = resolve_widget_output_path(
|
|
453
|
-
roi_save_file,
|
|
454
|
-
default=state.roi_save_file,
|
|
455
|
-
)
|
|
456
|
-
roi_set = save_napari_label_rois(label_image, resolved_output_path)
|
|
457
|
-
state.roi_save_file = resolved_output_path
|
|
458
|
-
update_selected_roi_save_file(state, resolved_output_path)
|
|
459
|
-
if hasattr(state.response_plot_widget, "set_roi_save_file"):
|
|
460
|
-
cast(_RoiSavePathReceiver, state.response_plot_widget).set_roi_save_file(
|
|
461
|
-
resolved_output_path
|
|
462
|
-
)
|
|
463
|
-
return (
|
|
464
|
-
f"Saved {counted_noun(len(roi_set.labels), 'ROI', 'ROIs')} "
|
|
465
|
-
f"to {resolved_output_path}"
|
|
466
|
-
)
|
|
467
|
-
|
|
468
|
-
save_rois.roi_save_file.mode = "w"
|
|
469
|
-
save_rois.roi_save_file.label = "ROI save file"
|
|
470
|
-
return save_rois
|
|
471
|
-
|
|
472
|
-
|
|
473
415
|
def _configure_recording_folder_picker(
|
|
474
416
|
widget: FileEdit,
|
|
475
417
|
state: NapariControlState,
|
|
@@ -706,75 +648,3 @@ def _resolve_optional_roi_path(path: PathInput) -> Path | None:
|
|
|
706
648
|
msg = f"ROI file does not exist: {resolved}"
|
|
707
649
|
raise ValueError(msg)
|
|
708
650
|
return resolved.resolve()
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
def _default_movie_end_frame(recording: RecordingData | None) -> int:
|
|
712
|
-
"""Return the default inclusive movie end frame for a loaded recording.
|
|
713
|
-
|
|
714
|
-
Args:
|
|
715
|
-
recording: Optional loaded recording.
|
|
716
|
-
|
|
717
|
-
Returns:
|
|
718
|
-
Last available frame index, or ``0`` before a recording is loaded.
|
|
719
|
-
"""
|
|
720
|
-
if recording is None:
|
|
721
|
-
return 0
|
|
722
|
-
return max(recording.movie.shape[0] - 1, 0)
|
|
723
|
-
|
|
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
|
-
|
|
761
|
-
def _load_recording_for_defaults(
|
|
762
|
-
*,
|
|
763
|
-
recording_data_path: Path,
|
|
764
|
-
movie_path: Path | None,
|
|
765
|
-
) -> RecordingData:
|
|
766
|
-
"""Load recording metadata before resolving frame defaults.
|
|
767
|
-
|
|
768
|
-
Args:
|
|
769
|
-
recording_data_path: Converted ``recording_data.h5`` path.
|
|
770
|
-
movie_path: Optional explicit converted movie path.
|
|
771
|
-
|
|
772
|
-
Returns:
|
|
773
|
-
Loaded recording object.
|
|
774
|
-
|
|
775
|
-
Loading the small manifest lets the empty-launch widget convert its initial
|
|
776
|
-
``0`` end frame into the actual final frame before adding the movie layer.
|
|
777
|
-
"""
|
|
778
|
-
from twopy.converted import load_converted_recording
|
|
779
|
-
|
|
780
|
-
return load_converted_recording(recording_data_path, movie_path=movie_path)
|