TwoPy 0.1.5__tar.gz → 0.1.7__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.5 → twopy-0.1.7}/PKG-INFO +2 -2
- {twopy-0.1.5 → twopy-0.1.7}/README.md +1 -1
- {twopy-0.1.5 → twopy-0.1.7}/pyproject.toml +1 -1
- {twopy-0.1.5 → twopy-0.1.7}/src/twopy/analysis/persistence.py +15 -2
- {twopy-0.1.5 → twopy-0.1.7}/src/twopy/analysis/response_processing/__init__.py +8 -0
- {twopy-0.1.5 → twopy-0.1.7}/src/twopy/analysis/response_processing/apply.py +131 -2
- {twopy-0.1.5 → twopy-0.1.7}/src/twopy/analysis/response_processing/options.py +38 -1
- {twopy-0.1.5 → twopy-0.1.7}/src/twopy/analysis/response_processing/persistence.py +100 -2
- {twopy-0.1.5 → twopy-0.1.7}/src/twopy/analysis/responses.py +95 -0
- {twopy-0.1.5 → twopy-0.1.7}/src/twopy/analysis/workflow.py +12 -0
- twopy-0.1.7/src/twopy/analysis_cache.py +263 -0
- {twopy-0.1.5 → twopy-0.1.7}/src/twopy/config.py +138 -2
- {twopy-0.1.5 → twopy-0.1.7}/src/twopy/conversion/api.py +5 -4
- {twopy-0.1.5 → twopy-0.1.7}/src/twopy/database/__init__.py +16 -0
- {twopy-0.1.5 → twopy-0.1.7}/src/twopy/database/modeled.py +7 -0
- twopy-0.1.7/src/twopy/database/search.py +433 -0
- {twopy-0.1.5 → twopy-0.1.7}/src/twopy/napari/controls.py +231 -45
- twopy-0.1.7/src/twopy/napari/database_search.py +491 -0
- twopy-0.1.7/src/twopy/napari/errors.py +24 -0
- {twopy-0.1.5 → twopy-0.1.7}/src/twopy/napari/interactive.py +2 -2
- {twopy-0.1.5 → twopy-0.1.7}/src/twopy/napari/loading.py +198 -3
- {twopy-0.1.5 → twopy-0.1.7}/src/twopy/napari/plotting/data.py +52 -47
- {twopy-0.1.5 → twopy-0.1.7}/src/twopy/napari/plotting/docks/dynamic_options.py +7 -1
- {twopy-0.1.5 → twopy-0.1.7}/src/twopy/napari/plotting/docks/options_panel.py +15 -1
- {twopy-0.1.5 → twopy-0.1.7}/src/twopy/napari/plotting/docks/response_plot_widget.py +181 -23
- {twopy-0.1.5 → twopy-0.1.7}/src/twopy/napari/plotting/docks/save_actions.py +16 -0
- {twopy-0.1.5 → twopy-0.1.7}/src/twopy/napari/plotting/docks/visibility_state.py +1 -1
- {twopy-0.1.5 → twopy-0.1.7}/src/twopy/napari/plotting/export_controls.py +44 -2
- {twopy-0.1.5 → twopy-0.1.7}/src/twopy/napari/plotting/label_visibility.py +1 -37
- twopy-0.1.7/src/twopy/napari/plotting/normalization_options.py +216 -0
- {twopy-0.1.5 → twopy-0.1.7}/src/twopy/napari/plotting/panels.py +2 -4
- {twopy-0.1.5 → twopy-0.1.7}/src/twopy/napari/plotting/processing_options.py +16 -0
- {twopy-0.1.5 → twopy-0.1.7}/src/twopy/napari/plotting/widgets.py +23 -7
- {twopy-0.1.5 → twopy-0.1.7}/src/twopy/napari/roi.py +41 -0
- {twopy-0.1.5 → twopy-0.1.7}/src/twopy/napari/session.py +34 -2
- {twopy-0.1.5 → twopy-0.1.7}/src/twopy/roi.py +74 -3
- {twopy-0.1.5 → twopy-0.1.7}/src/twopy.egg-info/PKG-INFO +2 -2
- {twopy-0.1.5 → twopy-0.1.7}/src/twopy.egg-info/SOURCES.txt +5 -0
- {twopy-0.1.5 → twopy-0.1.7}/tests/test_config.py +113 -1
- {twopy-0.1.5 → twopy-0.1.7}/tests/test_conversion.py +38 -0
- {twopy-0.1.5 → twopy-0.1.7}/tests/test_database.py +204 -0
- {twopy-0.1.5 → twopy-0.1.7}/tests/test_napari.py +1024 -13
- {twopy-0.1.5 → twopy-0.1.7}/tests/test_persistence.py +32 -0
- {twopy-0.1.5 → twopy-0.1.7}/tests/test_response_processing.py +98 -0
- {twopy-0.1.5 → twopy-0.1.7}/tests/test_roi.py +65 -3
- {twopy-0.1.5 → twopy-0.1.7}/setup.cfg +0 -0
- {twopy-0.1.5 → twopy-0.1.7}/src/twopy/__init__.py +0 -0
- {twopy-0.1.5 → twopy-0.1.7}/src/twopy/_segments.py +0 -0
- {twopy-0.1.5 → twopy-0.1.7}/src/twopy/_version.py +0 -0
- {twopy-0.1.5 → twopy-0.1.7}/src/twopy/analysis/__init__.py +0 -0
- {twopy-0.1.5 → twopy-0.1.7}/src/twopy/analysis/background_subtraction.py +0 -0
- {twopy-0.1.5 → twopy-0.1.7}/src/twopy/analysis/dff.py +0 -0
- {twopy-0.1.5 → twopy-0.1.7}/src/twopy/analysis/dff_options.py +0 -0
- {twopy-0.1.5 → twopy-0.1.7}/src/twopy/analysis/epoch_mapping.py +0 -0
- {twopy-0.1.5 → twopy-0.1.7}/src/twopy/analysis/motion.py +0 -0
- {twopy-0.1.5 → twopy-0.1.7}/src/twopy/analysis/response_processing/qc.py +0 -0
- {twopy-0.1.5 → twopy-0.1.7}/src/twopy/analysis/response_processing/signals.py +0 -0
- {twopy-0.1.5 → twopy-0.1.7}/src/twopy/analysis/response_window_options.py +0 -0
- {twopy-0.1.5 → twopy-0.1.7}/src/twopy/analysis/trials.py +0 -0
- {twopy-0.1.5 → twopy-0.1.7}/src/twopy/api.py +0 -0
- {twopy-0.1.5 → twopy-0.1.7}/src/twopy/conversion/__init__.py +0 -0
- {twopy-0.1.5 → twopy-0.1.7}/src/twopy/conversion/alignment_crop.py +0 -0
- {twopy-0.1.5 → twopy-0.1.7}/src/twopy/conversion/hdf5_writing.py +0 -0
- {twopy-0.1.5 → twopy-0.1.7}/src/twopy/conversion/matlab_values.py +0 -0
- {twopy-0.1.5 → twopy-0.1.7}/src/twopy/conversion/source_loading.py +0 -0
- {twopy-0.1.5 → twopy-0.1.7}/src/twopy/conversion/stimulus_code.py +0 -0
- {twopy-0.1.5 → twopy-0.1.7}/src/twopy/conversion/types.py +0 -0
- {twopy-0.1.5 → twopy-0.1.7}/src/twopy/converted.py +0 -0
- {twopy-0.1.5 → twopy-0.1.7}/src/twopy/database/cache.py +0 -0
- {twopy-0.1.5 → twopy-0.1.7}/src/twopy/database/catalog.py +0 -0
- {twopy-0.1.5 → twopy-0.1.7}/src/twopy/database/connection.py +0 -0
- {twopy-0.1.5 → twopy-0.1.7}/src/twopy/database/generic.py +0 -0
- {twopy-0.1.5 → twopy-0.1.7}/src/twopy/database/sql.py +0 -0
- {twopy-0.1.5 → twopy-0.1.7}/src/twopy/database/types.py +0 -0
- {twopy-0.1.5 → twopy-0.1.7}/src/twopy/frame_ranges.py +0 -0
- {twopy-0.1.5 → twopy-0.1.7}/src/twopy/inspection.py +0 -0
- {twopy-0.1.5 → twopy-0.1.7}/src/twopy/matlab.py +0 -0
- {twopy-0.1.5 → twopy-0.1.7}/src/twopy/napari/__init__.py +0 -0
- {twopy-0.1.5 → twopy-0.1.7}/src/twopy/napari/__main__.py +0 -0
- {twopy-0.1.5 → twopy-0.1.7}/src/twopy/napari/constants.py +0 -0
- {twopy-0.1.5 → twopy-0.1.7}/src/twopy/napari/display.py +0 -0
- {twopy-0.1.5 → twopy-0.1.7}/src/twopy/napari/display_paths.py +0 -0
- {twopy-0.1.5 → twopy-0.1.7}/src/twopy/napari/launcher.py +0 -0
- {twopy-0.1.5 → twopy-0.1.7}/src/twopy/napari/movie.py +0 -0
- {twopy-0.1.5 → twopy-0.1.7}/src/twopy/napari/paths.py +0 -0
- {twopy-0.1.5 → twopy-0.1.7}/src/twopy/napari/plotting/__init__.py +0 -0
- {twopy-0.1.5 → twopy-0.1.7}/src/twopy/napari/plotting/dff_options.py +0 -0
- {twopy-0.1.5 → twopy-0.1.7}/src/twopy/napari/plotting/docks/__init__.py +0 -0
- {twopy-0.1.5 → twopy-0.1.7}/src/twopy/napari/plotting/docks/api.py +0 -0
- {twopy-0.1.5 → twopy-0.1.7}/src/twopy/napari/plotting/docks/paths.py +0 -0
- {twopy-0.1.5 → twopy-0.1.7}/src/twopy/napari/plotting/docks/plot_area.py +0 -0
- {twopy-0.1.5 → twopy-0.1.7}/src/twopy/napari/plotting/export.py +0 -0
- {twopy-0.1.5 → twopy-0.1.7}/src/twopy/napari/plotting/form_controls.py +0 -0
- {twopy-0.1.5 → twopy-0.1.7}/src/twopy/napari/plotting/options.py +0 -0
- {twopy-0.1.5 → twopy-0.1.7}/src/twopy/napari/plotting/response_window_options.py +0 -0
- {twopy-0.1.5 → twopy-0.1.7}/src/twopy/napari/protocols.py +0 -0
- {twopy-0.1.5 → twopy-0.1.7}/src/twopy/napari/responses.py +0 -0
- {twopy-0.1.5 → twopy-0.1.7}/src/twopy/napari/sidebar.py +0 -0
- {twopy-0.1.5 → twopy-0.1.7}/src/twopy/napari/state.py +0 -0
- {twopy-0.1.5 → twopy-0.1.7}/src/twopy/napari/text.py +0 -0
- {twopy-0.1.5 → twopy-0.1.7}/src/twopy/napari/types.py +0 -0
- {twopy-0.1.5 → twopy-0.1.7}/src/twopy/napari/viewer.py +0 -0
- {twopy-0.1.5 → twopy-0.1.7}/src/twopy/parity/__init__.py +0 -0
- {twopy-0.1.5 → twopy-0.1.7}/src/twopy/parity/dff.py +0 -0
- {twopy-0.1.5 → twopy-0.1.7}/src/twopy/parity/psycho5.py +0 -0
- {twopy-0.1.5 → twopy-0.1.7}/src/twopy/parity/roi.py +0 -0
- {twopy-0.1.5 → twopy-0.1.7}/src/twopy/parity/saved_analysis.py +0 -0
- {twopy-0.1.5 → twopy-0.1.7}/src/twopy/photodiode.py +0 -0
- {twopy-0.1.5 → twopy-0.1.7}/src/twopy/photodiode_classification.py +0 -0
- {twopy-0.1.5 → twopy-0.1.7}/src/twopy/session.py +0 -0
- {twopy-0.1.5 → twopy-0.1.7}/src/twopy/spatial.py +0 -0
- {twopy-0.1.5 → twopy-0.1.7}/src/twopy/stimulus.py +0 -0
- {twopy-0.1.5 → twopy-0.1.7}/src/twopy/synchronization.py +0 -0
- {twopy-0.1.5 → twopy-0.1.7}/src/twopy/typing_guards.py +0 -0
- {twopy-0.1.5 → twopy-0.1.7}/src/twopy.egg-info/dependency_links.txt +0 -0
- {twopy-0.1.5 → twopy-0.1.7}/src/twopy.egg-info/entry_points.txt +0 -0
- {twopy-0.1.5 → twopy-0.1.7}/src/twopy.egg-info/requires.txt +0 -0
- {twopy-0.1.5 → twopy-0.1.7}/src/twopy.egg-info/top_level.txt +0 -0
- {twopy-0.1.5 → twopy-0.1.7}/tests/test_api.py +0 -0
- {twopy-0.1.5 → twopy-0.1.7}/tests/test_background_subtraction.py +0 -0
- {twopy-0.1.5 → twopy-0.1.7}/tests/test_converted.py +0 -0
- {twopy-0.1.5 → twopy-0.1.7}/tests/test_dff.py +0 -0
- {twopy-0.1.5 → twopy-0.1.7}/tests/test_epoch_mapping.py +0 -0
- {twopy-0.1.5 → twopy-0.1.7}/tests/test_frame_ranges.py +0 -0
- {twopy-0.1.5 → twopy-0.1.7}/tests/test_import.py +0 -0
- {twopy-0.1.5 → twopy-0.1.7}/tests/test_inspection.py +0 -0
- {twopy-0.1.5 → twopy-0.1.7}/tests/test_matlab.py +0 -0
- {twopy-0.1.5 → twopy-0.1.7}/tests/test_motion.py +0 -0
- {twopy-0.1.5 → twopy-0.1.7}/tests/test_packaging.py +0 -0
- {twopy-0.1.5 → twopy-0.1.7}/tests/test_parity.py +0 -0
- {twopy-0.1.5 → twopy-0.1.7}/tests/test_parity_psycho5.py +0 -0
- {twopy-0.1.5 → twopy-0.1.7}/tests/test_photodiode_classification.py +0 -0
- {twopy-0.1.5 → twopy-0.1.7}/tests/test_real_fixture.py +0 -0
- {twopy-0.1.5 → twopy-0.1.7}/tests/test_response_window_options.py +0 -0
- {twopy-0.1.5 → twopy-0.1.7}/tests/test_responses.py +0 -0
- {twopy-0.1.5 → twopy-0.1.7}/tests/test_saved_analysis.py +0 -0
- {twopy-0.1.5 → twopy-0.1.7}/tests/test_session.py +0 -0
- {twopy-0.1.5 → twopy-0.1.7}/tests/test_stimulus.py +0 -0
- {twopy-0.1.5 → twopy-0.1.7}/tests/test_synchronization.py +0 -0
- {twopy-0.1.5 → twopy-0.1.7}/tests/test_trials.py +0 -0
- {twopy-0.1.5 → twopy-0.1.7}/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.7
|
|
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
|
|
@@ -28,7 +28,7 @@ Two-photon imaging analysis tool for the Clark Lab output format.
|
|
|
28
28
|
|
|
29
29
|
twopy lets you open two-photon recordings, draw ROIs, plot responses in real time, process and analyze them, and save them.
|
|
30
30
|
|
|
31
|
-
When you first load a recording, twopy converts it to a standardized HDF5 format. The converted format includes the aligned movie, mean image, stimulus tables, photodiode signals, and recording metadata. Analysis and the GUI both work from the converted files, so the original source files remain separate from twopy's outputs.
|
|
31
|
+
When you first load a recording, twopy converts it to a standardized HDF5 format. The converted format includes the aligned movie, mean image, stimulus tables, photodiode signals, and recording metadata. Analysis and the GUI both work from the converted files, so the original source files remain separate from twopy's outputs. By default, converted recordings and interactive analysis outputs are cached locally under `~/.cache/twopy/recordings` for speed; saving ROIs and analysis publishes the changed output files back to the configured `analysis_output` location in the background.
|
|
32
32
|
|
|
33
33
|
## Install
|
|
34
34
|
|
|
@@ -8,7 +8,7 @@ Two-photon imaging analysis tool for the Clark Lab output format.
|
|
|
8
8
|
|
|
9
9
|
twopy lets you open two-photon recordings, draw ROIs, plot responses in real time, process and analyze them, and save them.
|
|
10
10
|
|
|
11
|
-
When you first load a recording, twopy converts it to a standardized HDF5 format. The converted format includes the aligned movie, mean image, stimulus tables, photodiode signals, and recording metadata. Analysis and the GUI both work from the converted files, so the original source files remain separate from twopy's outputs.
|
|
11
|
+
When you first load a recording, twopy converts it to a standardized HDF5 format. The converted format includes the aligned movie, mean image, stimulus tables, photodiode signals, and recording metadata. Analysis and the GUI both work from the converted files, so the original source files remain separate from twopy's outputs. By default, converted recordings and interactive analysis outputs are cached locally under `~/.cache/twopy/recordings` for speed; saving ROIs and analysis publishes the changed output files back to the configured `analysis_output` location in the background.
|
|
12
12
|
|
|
13
13
|
## Install
|
|
14
14
|
|
|
@@ -26,6 +26,7 @@ from twopy.analysis.dff import RoiDeltaFOverF
|
|
|
26
26
|
from twopy.analysis.response_processing import (
|
|
27
27
|
ResponseProcessingOptions,
|
|
28
28
|
RoiCorrelationScores,
|
|
29
|
+
RoiNormalizationFactors,
|
|
29
30
|
)
|
|
30
31
|
from twopy.analysis.response_processing.persistence import (
|
|
31
32
|
read_response_processing_group,
|
|
@@ -99,6 +100,7 @@ class LoadedAnalysisOutputs:
|
|
|
99
100
|
baseline_windows: tuple[FrameWindow, ...]
|
|
100
101
|
grouped_responses: GroupedRoiResponses | None
|
|
101
102
|
response_processing_options: ResponseProcessingOptions | None
|
|
103
|
+
normalization_factors: RoiNormalizationFactors | None
|
|
102
104
|
correlation_scores: RoiCorrelationScores | None
|
|
103
105
|
|
|
104
106
|
|
|
@@ -112,6 +114,7 @@ def save_analysis_outputs(
|
|
|
112
114
|
baseline_windows: Sequence[FrameWindow] = (),
|
|
113
115
|
grouped_responses: GroupedRoiResponses | None = None,
|
|
114
116
|
response_processing_options: ResponseProcessingOptions | None = None,
|
|
117
|
+
normalization_factors: RoiNormalizationFactors | None = None,
|
|
115
118
|
correlation_scores: RoiCorrelationScores | None = None,
|
|
116
119
|
response_summary_trials_csv: Path | None = None,
|
|
117
120
|
response_summary_grouped_csv: Path | None = None,
|
|
@@ -128,6 +131,8 @@ def save_analysis_outputs(
|
|
|
128
131
|
grouped_responses: Optional grouped trial responses.
|
|
129
132
|
response_processing_options: Optional smoothing, low-pass, and
|
|
130
133
|
correlation-QC settings used for these outputs.
|
|
134
|
+
normalization_factors: Optional per-ROI factors used for epoch-peak
|
|
135
|
+
normalization.
|
|
131
136
|
correlation_scores: Optional ROI-level correlation QC scores.
|
|
132
137
|
response_summary_trials_csv: Optional CSV path for one row per trial
|
|
133
138
|
and ROI, with one column per relative response timepoint.
|
|
@@ -149,8 +154,10 @@ def save_analysis_outputs(
|
|
|
149
154
|
if any(path is not None for path in summary_outputs) and grouped_responses is None:
|
|
150
155
|
msg = "response summary CSV outputs require grouped_responses"
|
|
151
156
|
raise ValueError(msg)
|
|
152
|
-
if
|
|
153
|
-
|
|
157
|
+
if (
|
|
158
|
+
correlation_scores is not None or normalization_factors is not None
|
|
159
|
+
) and response_processing_options is None:
|
|
160
|
+
msg = "response-processing audit outputs require response_processing_options"
|
|
154
161
|
raise ValueError(msg)
|
|
155
162
|
if grouped_responses is not None:
|
|
156
163
|
validate_grouped_roi_responses(grouped_responses)
|
|
@@ -181,6 +188,7 @@ def save_analysis_outputs(
|
|
|
181
188
|
write_response_processing_group(
|
|
182
189
|
h5_file.create_group("response_processing"),
|
|
183
190
|
options=response_processing_options,
|
|
191
|
+
normalization_factors=normalization_factors,
|
|
184
192
|
correlation_scores=correlation_scores,
|
|
185
193
|
)
|
|
186
194
|
|
|
@@ -249,6 +257,11 @@ def load_analysis_outputs(path: Path) -> LoadedAnalysisOutputs:
|
|
|
249
257
|
response_processing_options=(
|
|
250
258
|
response_processing.options if response_processing is not None else None
|
|
251
259
|
),
|
|
260
|
+
normalization_factors=(
|
|
261
|
+
response_processing.normalization_factors
|
|
262
|
+
if response_processing is not None
|
|
263
|
+
else None
|
|
264
|
+
),
|
|
252
265
|
correlation_scores=(
|
|
253
266
|
response_processing.correlation_scores
|
|
254
267
|
if response_processing is not None
|
|
@@ -12,8 +12,10 @@ how to save the resulting audit trail.
|
|
|
12
12
|
|
|
13
13
|
from twopy.analysis.response_processing.apply import (
|
|
14
14
|
ResponseProcessingResult,
|
|
15
|
+
RoiNormalizationFactors,
|
|
15
16
|
apply_correlation_filter_to_grouped_roi_responses,
|
|
16
17
|
mask_grouped_roi_responses_by_included_rois,
|
|
18
|
+
normalize_grouped_roi_responses_by_epoch_peak,
|
|
17
19
|
process_grouped_roi_responses,
|
|
18
20
|
process_roi_delta_f_over_f,
|
|
19
21
|
)
|
|
@@ -23,6 +25,8 @@ from twopy.analysis.response_processing.options import (
|
|
|
23
25
|
CorrelationWindowSeconds,
|
|
24
26
|
LowPassFilterMethod,
|
|
25
27
|
LowPassFilterOptions,
|
|
28
|
+
NormalizationMethod,
|
|
29
|
+
NormalizationOptions,
|
|
26
30
|
ResponseProcessingOptions,
|
|
27
31
|
SmoothingMethod,
|
|
28
32
|
SmoothingOptions,
|
|
@@ -45,15 +49,19 @@ __all__ = [
|
|
|
45
49
|
"CorrelationWindowSeconds",
|
|
46
50
|
"LowPassFilterMethod",
|
|
47
51
|
"LowPassFilterOptions",
|
|
52
|
+
"NormalizationMethod",
|
|
53
|
+
"NormalizationOptions",
|
|
48
54
|
"apply_correlation_filter_to_grouped_roi_responses",
|
|
49
55
|
"mask_grouped_roi_responses_by_included_rois",
|
|
50
56
|
"nan_aware_moving_average",
|
|
51
57
|
"nan_aware_savgol_filter",
|
|
58
|
+
"normalize_grouped_roi_responses_by_epoch_peak",
|
|
52
59
|
"process_grouped_roi_responses",
|
|
53
60
|
"process_roi_delta_f_over_f",
|
|
54
61
|
"ResponseProcessingOptions",
|
|
55
62
|
"ResponseProcessingResult",
|
|
56
63
|
"RoiCorrelationScores",
|
|
64
|
+
"RoiNormalizationFactors",
|
|
57
65
|
"score_roi_correlations",
|
|
58
66
|
"SmoothingMethod",
|
|
59
67
|
"SmoothingOptions",
|
|
@@ -15,6 +15,7 @@ import numpy.typing as npt
|
|
|
15
15
|
|
|
16
16
|
from twopy.analysis.dff import RoiDeltaFOverF
|
|
17
17
|
from twopy.analysis.response_processing.options import (
|
|
18
|
+
NormalizationOptions,
|
|
18
19
|
ResponseProcessingOptions,
|
|
19
20
|
validate_response_processing_options,
|
|
20
21
|
)
|
|
@@ -30,17 +31,36 @@ from twopy.analysis.response_processing.signals import (
|
|
|
30
31
|
from twopy.analysis.responses import (
|
|
31
32
|
GroupedRoiResponses,
|
|
32
33
|
RoiResponseTrial,
|
|
34
|
+
summarize_roi_response_trials,
|
|
33
35
|
validate_grouped_roi_responses,
|
|
34
36
|
)
|
|
35
37
|
|
|
36
38
|
__all__ = [
|
|
37
39
|
"apply_correlation_filter_to_grouped_roi_responses",
|
|
40
|
+
"normalize_grouped_roi_responses_by_epoch_peak",
|
|
38
41
|
"mask_grouped_roi_responses_by_included_rois",
|
|
42
|
+
"RoiNormalizationFactors",
|
|
39
43
|
"ResponseProcessingResult",
|
|
40
44
|
"process_grouped_roi_responses",
|
|
41
45
|
"process_roi_delta_f_over_f",
|
|
42
46
|
]
|
|
43
47
|
|
|
48
|
+
_NORMALIZATION_MIN_PEAK = 1e-12
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@dataclass(frozen=True)
|
|
52
|
+
class RoiNormalizationFactors:
|
|
53
|
+
"""Per-ROI factors used for epoch-peak response normalization.
|
|
54
|
+
|
|
55
|
+
Inputs: grouped response trials and one enabled normalization option.
|
|
56
|
+
Outputs: the positive peak factor for each ROI in the selected epoch.
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
roi_labels: tuple[str, ...]
|
|
60
|
+
factors: npt.NDArray[np.float64]
|
|
61
|
+
epoch_number: int
|
|
62
|
+
epoch_name: str | None
|
|
63
|
+
|
|
44
64
|
|
|
45
65
|
@dataclass(frozen=True)
|
|
46
66
|
class ResponseProcessingResult:
|
|
@@ -53,6 +73,7 @@ class ResponseProcessingResult:
|
|
|
53
73
|
|
|
54
74
|
grouped_responses: GroupedRoiResponses
|
|
55
75
|
options: ResponseProcessingOptions
|
|
76
|
+
normalization_factors: RoiNormalizationFactors | None
|
|
56
77
|
correlation_scores: RoiCorrelationScores | None
|
|
57
78
|
|
|
58
79
|
|
|
@@ -66,8 +87,8 @@ def process_roi_delta_f_over_f(
|
|
|
66
87
|
|
|
67
88
|
Args:
|
|
68
89
|
dff: Continuous ROI dF/F result.
|
|
69
|
-
options: Processing options.
|
|
70
|
-
because
|
|
90
|
+
options: Processing options. Normalization and correlation filtering
|
|
91
|
+
are ignored here because they need grouped repeated-trial responses.
|
|
71
92
|
data_rate_hz: Sampling rate in hertz.
|
|
72
93
|
|
|
73
94
|
Returns:
|
|
@@ -129,6 +150,10 @@ def process_grouped_roi_responses(
|
|
|
129
150
|
validate_response_processing_options(options, data_rate_hz=grouped.data_rate_hz)
|
|
130
151
|
validate_grouped_roi_responses(grouped)
|
|
131
152
|
processed = _copy_grouped_with_processed_values(grouped, options=options)
|
|
153
|
+
processed, normalization_factors = normalize_grouped_roi_responses_by_epoch_peak(
|
|
154
|
+
processed,
|
|
155
|
+
options=options.normalization,
|
|
156
|
+
)
|
|
132
157
|
processed, correlation_scores = apply_correlation_filter_to_grouped_roi_responses(
|
|
133
158
|
processed,
|
|
134
159
|
options=options,
|
|
@@ -136,10 +161,57 @@ def process_grouped_roi_responses(
|
|
|
136
161
|
return ResponseProcessingResult(
|
|
137
162
|
grouped_responses=processed,
|
|
138
163
|
options=options,
|
|
164
|
+
normalization_factors=normalization_factors,
|
|
139
165
|
correlation_scores=correlation_scores,
|
|
140
166
|
)
|
|
141
167
|
|
|
142
168
|
|
|
169
|
+
def normalize_grouped_roi_responses_by_epoch_peak(
|
|
170
|
+
grouped: GroupedRoiResponses,
|
|
171
|
+
*,
|
|
172
|
+
options: NormalizationOptions,
|
|
173
|
+
) -> tuple[GroupedRoiResponses, RoiNormalizationFactors | None]:
|
|
174
|
+
"""Normalize grouped responses by each ROI's selected-epoch peak.
|
|
175
|
+
|
|
176
|
+
Args:
|
|
177
|
+
grouped: Grouped ROI responses to normalize.
|
|
178
|
+
options: Normalization settings.
|
|
179
|
+
|
|
180
|
+
Returns:
|
|
181
|
+
``(grouped_responses, factors)``. ``factors`` is ``None`` when
|
|
182
|
+
normalization is disabled.
|
|
183
|
+
|
|
184
|
+
Raises:
|
|
185
|
+
ValueError: If the selected epoch is absent or any ROI lacks a positive
|
|
186
|
+
finite peak in that epoch.
|
|
187
|
+
"""
|
|
188
|
+
validate_grouped_roi_responses(grouped)
|
|
189
|
+
if options.method not in {"none", "epoch_peak"}:
|
|
190
|
+
msg = f"Unknown normalization method {options.method!r}"
|
|
191
|
+
raise ValueError(msg)
|
|
192
|
+
if options.method == "none":
|
|
193
|
+
return grouped, None
|
|
194
|
+
if options.epoch_number is None:
|
|
195
|
+
msg = "epoch-peak normalization requires an epoch_number"
|
|
196
|
+
raise ValueError(msg)
|
|
197
|
+
factors = _normalization_factors(grouped, options=options)
|
|
198
|
+
trials = tuple(
|
|
199
|
+
_copy_trial_with_values(trial, trial.values / factors.factors)
|
|
200
|
+
for trial in grouped.trials
|
|
201
|
+
)
|
|
202
|
+
return (
|
|
203
|
+
GroupedRoiResponses(
|
|
204
|
+
roi_labels=grouped.roi_labels,
|
|
205
|
+
data_rate_hz=grouped.data_rate_hz,
|
|
206
|
+
trials=trials,
|
|
207
|
+
pre_window_seconds=grouped.pre_window_seconds,
|
|
208
|
+
post_window_seconds=grouped.post_window_seconds,
|
|
209
|
+
response_window_auto=grouped.response_window_auto,
|
|
210
|
+
),
|
|
211
|
+
factors,
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
|
|
143
215
|
def apply_correlation_filter_to_grouped_roi_responses(
|
|
144
216
|
grouped: GroupedRoiResponses,
|
|
145
217
|
*,
|
|
@@ -259,6 +331,52 @@ def _copy_grouped_with_processed_values(
|
|
|
259
331
|
)
|
|
260
332
|
|
|
261
333
|
|
|
334
|
+
def _normalization_factors(
|
|
335
|
+
grouped: GroupedRoiResponses,
|
|
336
|
+
*,
|
|
337
|
+
options: NormalizationOptions,
|
|
338
|
+
) -> RoiNormalizationFactors:
|
|
339
|
+
"""Return per-ROI selected-epoch peak factors."""
|
|
340
|
+
selected_trials = tuple(
|
|
341
|
+
trial
|
|
342
|
+
for trial in grouped.trials
|
|
343
|
+
if trial.epoch_number == options.epoch_number
|
|
344
|
+
and (options.epoch_name is None or trial.epoch_name == options.epoch_name)
|
|
345
|
+
)
|
|
346
|
+
if len(selected_trials) == 0:
|
|
347
|
+
label = (
|
|
348
|
+
f"{options.epoch_number}: {options.epoch_name}"
|
|
349
|
+
if options.epoch_name is not None
|
|
350
|
+
else str(options.epoch_number)
|
|
351
|
+
)
|
|
352
|
+
msg = f"No trials matched normalization epoch {label}"
|
|
353
|
+
raise ValueError(msg)
|
|
354
|
+
summary = summarize_roi_response_trials(
|
|
355
|
+
selected_trials,
|
|
356
|
+
roi_labels=grouped.roi_labels,
|
|
357
|
+
data_rate_hz=grouped.data_rate_hz,
|
|
358
|
+
)
|
|
359
|
+
factors = np.nanmax(summary.mean_values, axis=1)
|
|
360
|
+
bad_indices = tuple(
|
|
361
|
+
index
|
|
362
|
+
for index, factor in enumerate(factors)
|
|
363
|
+
if not np.isfinite(factor) or factor <= _NORMALIZATION_MIN_PEAK
|
|
364
|
+
)
|
|
365
|
+
if len(bad_indices) > 0:
|
|
366
|
+
bad_labels = ", ".join(grouped.roi_labels[index] for index in bad_indices)
|
|
367
|
+
msg = (
|
|
368
|
+
"Normalization epoch peak must be positive for every ROI; "
|
|
369
|
+
f"invalid ROI labels: {bad_labels}"
|
|
370
|
+
)
|
|
371
|
+
raise ValueError(msg)
|
|
372
|
+
return RoiNormalizationFactors(
|
|
373
|
+
roi_labels=grouped.roi_labels,
|
|
374
|
+
factors=factors.astype(np.float64, copy=False),
|
|
375
|
+
epoch_number=int(options.epoch_number or 0),
|
|
376
|
+
epoch_name=options.epoch_name,
|
|
377
|
+
)
|
|
378
|
+
|
|
379
|
+
|
|
262
380
|
def _mask_excluded_rois(
|
|
263
381
|
grouped: GroupedRoiResponses,
|
|
264
382
|
*,
|
|
@@ -334,6 +452,17 @@ def _processing_metadata(
|
|
|
334
452
|
else float(options.low_pass.cutoff_hz)
|
|
335
453
|
),
|
|
336
454
|
"response_processing_low_pass_order": options.low_pass.order,
|
|
455
|
+
"response_processing_normalization_method": options.normalization.method,
|
|
456
|
+
"response_processing_normalization_epoch_number": (
|
|
457
|
+
"none"
|
|
458
|
+
if options.normalization.epoch_number is None
|
|
459
|
+
else int(options.normalization.epoch_number)
|
|
460
|
+
),
|
|
461
|
+
"response_processing_normalization_epoch_name": (
|
|
462
|
+
"none"
|
|
463
|
+
if options.normalization.epoch_name is None
|
|
464
|
+
else options.normalization.epoch_name
|
|
465
|
+
),
|
|
337
466
|
"response_processing_correlation_reference": (
|
|
338
467
|
options.correlation_filter.reference
|
|
339
468
|
),
|
|
@@ -19,6 +19,8 @@ __all__ = [
|
|
|
19
19
|
"CorrelationWindowSeconds",
|
|
20
20
|
"LowPassFilterMethod",
|
|
21
21
|
"LowPassFilterOptions",
|
|
22
|
+
"NormalizationMethod",
|
|
23
|
+
"NormalizationOptions",
|
|
22
24
|
"ResponseProcessingOptions",
|
|
23
25
|
"SmoothingMethod",
|
|
24
26
|
"SmoothingOptions",
|
|
@@ -27,6 +29,7 @@ __all__ = [
|
|
|
27
29
|
|
|
28
30
|
SmoothingMethod = Literal["none", "moving_average", "savgol"]
|
|
29
31
|
LowPassFilterMethod = Literal["none", "butterworth"]
|
|
32
|
+
NormalizationMethod = Literal["none", "epoch_peak"]
|
|
30
33
|
CorrelationFilterReference = Literal["none", "epoch_mean", "epoch_peak"]
|
|
31
34
|
CorrelationWindowSeconds = tuple[float | None, float | None]
|
|
32
35
|
|
|
@@ -66,6 +69,24 @@ class LowPassFilterOptions:
|
|
|
66
69
|
order: int = 2
|
|
67
70
|
|
|
68
71
|
|
|
72
|
+
@dataclass(frozen=True)
|
|
73
|
+
class NormalizationOptions:
|
|
74
|
+
"""Epoch-peak normalization settings for grouped ROI responses.
|
|
75
|
+
|
|
76
|
+
Inputs: a normalization method and the stimulus epoch used as the scale
|
|
77
|
+
reference.
|
|
78
|
+
Outputs: a typed contract consumed after trial grouping.
|
|
79
|
+
|
|
80
|
+
``method="none"`` leaves grouped responses unchanged. ``method="epoch_peak"``
|
|
81
|
+
divides every response for each ROI by that ROI's mean-response peak in the
|
|
82
|
+
selected epoch, so the selected epoch's plotted peak becomes one.
|
|
83
|
+
"""
|
|
84
|
+
|
|
85
|
+
method: NormalizationMethod = "none"
|
|
86
|
+
epoch_number: int | None = None
|
|
87
|
+
epoch_name: str | None = None
|
|
88
|
+
|
|
89
|
+
|
|
69
90
|
@dataclass(frozen=True)
|
|
70
91
|
class CorrelationFilterOptions:
|
|
71
92
|
"""Correlation-based response quality-control settings.
|
|
@@ -91,13 +112,15 @@ class CorrelationFilterOptions:
|
|
|
91
112
|
class ResponseProcessingOptions:
|
|
92
113
|
"""Complete response post-processing configuration.
|
|
93
114
|
|
|
94
|
-
Inputs: optional smoothing, low-pass filtering,
|
|
115
|
+
Inputs: optional smoothing, low-pass filtering, epoch-peak normalization,
|
|
116
|
+
and correlation QC settings.
|
|
95
117
|
Outputs: one immutable object passed through workflow, plotting, and future
|
|
96
118
|
persistence boundaries.
|
|
97
119
|
"""
|
|
98
120
|
|
|
99
121
|
smoothing: SmoothingOptions = field(default_factory=SmoothingOptions)
|
|
100
122
|
low_pass: LowPassFilterOptions = field(default_factory=LowPassFilterOptions)
|
|
123
|
+
normalization: NormalizationOptions = field(default_factory=NormalizationOptions)
|
|
101
124
|
correlation_filter: CorrelationFilterOptions = field(
|
|
102
125
|
default_factory=CorrelationFilterOptions
|
|
103
126
|
)
|
|
@@ -123,6 +146,7 @@ def validate_response_processing_options(
|
|
|
123
146
|
"""
|
|
124
147
|
_validate_smoothing_options(options.smoothing)
|
|
125
148
|
_validate_low_pass_options(options.low_pass, data_rate_hz=data_rate_hz)
|
|
149
|
+
_validate_normalization_options(options.normalization)
|
|
126
150
|
_validate_correlation_options(options.correlation_filter)
|
|
127
151
|
|
|
128
152
|
|
|
@@ -186,6 +210,19 @@ def _validate_low_pass_options(
|
|
|
186
210
|
raise ValueError(msg)
|
|
187
211
|
|
|
188
212
|
|
|
213
|
+
def _validate_normalization_options(options: NormalizationOptions) -> None:
|
|
214
|
+
"""Validate epoch-peak normalization options."""
|
|
215
|
+
if options.method not in {"none", "epoch_peak"}:
|
|
216
|
+
msg = f"Unknown normalization method {options.method!r}"
|
|
217
|
+
raise ValueError(msg)
|
|
218
|
+
if options.epoch_number is not None and options.epoch_number < 1:
|
|
219
|
+
msg = f"normalization epoch_number must be positive; got {options.epoch_number}"
|
|
220
|
+
raise ValueError(msg)
|
|
221
|
+
if options.method == "epoch_peak" and options.epoch_number is None:
|
|
222
|
+
msg = "epoch-peak normalization requires an epoch_number"
|
|
223
|
+
raise ValueError(msg)
|
|
224
|
+
|
|
225
|
+
|
|
189
226
|
def _validate_correlation_options(options: CorrelationFilterOptions) -> None:
|
|
190
227
|
"""Validate correlation-filter options."""
|
|
191
228
|
if options.reference not in {"none", "epoch_mean", "epoch_peak"}:
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"""Persist response-processing settings and QC audit data.
|
|
2
2
|
|
|
3
|
-
Inputs: typed response-processing options
|
|
3
|
+
Inputs: typed response-processing options plus optional normalization and ROI
|
|
4
|
+
correlation audit data.
|
|
4
5
|
Outputs: one inspectable HDF5 group that analysis persistence can embed.
|
|
5
6
|
|
|
6
7
|
This module keeps the response-processing audit contract near the processing
|
|
@@ -12,12 +13,15 @@ from dataclasses import dataclass
|
|
|
12
13
|
import h5py
|
|
13
14
|
import numpy as np
|
|
14
15
|
|
|
16
|
+
from twopy.analysis.response_processing.apply import RoiNormalizationFactors
|
|
15
17
|
from twopy.analysis.response_processing.options import (
|
|
16
18
|
CorrelationFilterOptions,
|
|
17
19
|
CorrelationFilterReference,
|
|
18
20
|
CorrelationWindowSeconds,
|
|
19
21
|
LowPassFilterMethod,
|
|
20
22
|
LowPassFilterOptions,
|
|
23
|
+
NormalizationMethod,
|
|
24
|
+
NormalizationOptions,
|
|
21
25
|
ResponseProcessingOptions,
|
|
22
26
|
SmoothingMethod,
|
|
23
27
|
SmoothingOptions,
|
|
@@ -41,6 +45,7 @@ _SMOOTHING_METHODS: tuple[SmoothingMethod, ...] = (
|
|
|
41
45
|
"savgol",
|
|
42
46
|
)
|
|
43
47
|
_LOW_PASS_METHODS: tuple[LowPassFilterMethod, ...] = ("none", "butterworth")
|
|
48
|
+
_NORMALIZATION_METHODS: tuple[NormalizationMethod, ...] = ("none", "epoch_peak")
|
|
44
49
|
_CORRELATION_REFERENCES: tuple[CorrelationFilterReference, ...] = (
|
|
45
50
|
"none",
|
|
46
51
|
"epoch_mean",
|
|
@@ -53,10 +58,12 @@ class PersistedResponseProcessing:
|
|
|
53
58
|
"""Response-processing audit data loaded from HDF5.
|
|
54
59
|
|
|
55
60
|
Inputs: one ``response_processing`` HDF5 group.
|
|
56
|
-
Outputs: typed options plus optional
|
|
61
|
+
Outputs: typed options plus optional normalization factors and ROI
|
|
62
|
+
correlation scores.
|
|
57
63
|
"""
|
|
58
64
|
|
|
59
65
|
options: ResponseProcessingOptions
|
|
66
|
+
normalization_factors: RoiNormalizationFactors | None
|
|
60
67
|
correlation_scores: RoiCorrelationScores | None
|
|
61
68
|
|
|
62
69
|
|
|
@@ -64,6 +71,7 @@ def write_response_processing_group(
|
|
|
64
71
|
group: h5py.Group,
|
|
65
72
|
*,
|
|
66
73
|
options: ResponseProcessingOptions,
|
|
74
|
+
normalization_factors: RoiNormalizationFactors | None = None,
|
|
67
75
|
correlation_scores: RoiCorrelationScores | None = None,
|
|
68
76
|
) -> None:
|
|
69
77
|
"""Write response-processing settings and optional QC scores.
|
|
@@ -71,6 +79,8 @@ def write_response_processing_group(
|
|
|
71
79
|
Args:
|
|
72
80
|
group: Destination ``response_processing`` HDF5 group.
|
|
73
81
|
options: Processing options used for the saved analysis output.
|
|
82
|
+
normalization_factors: Optional per-ROI normalization factors used for
|
|
83
|
+
epoch-peak normalization.
|
|
74
84
|
correlation_scores: Optional ROI-level correlation QC result.
|
|
75
85
|
|
|
76
86
|
Returns:
|
|
@@ -86,6 +96,24 @@ def write_response_processing_group(
|
|
|
86
96
|
low_pass.attrs["cutoff_hz"] = _optional_float_attr(options.low_pass.cutoff_hz)
|
|
87
97
|
low_pass.attrs["order"] = options.low_pass.order
|
|
88
98
|
|
|
99
|
+
normalization = group.create_group("normalization")
|
|
100
|
+
normalization.attrs["method"] = options.normalization.method
|
|
101
|
+
normalization.attrs["epoch_number"] = _optional_int_attr(
|
|
102
|
+
options.normalization.epoch_number,
|
|
103
|
+
)
|
|
104
|
+
normalization.attrs["epoch_name"] = _optional_text_attr(
|
|
105
|
+
options.normalization.epoch_name,
|
|
106
|
+
)
|
|
107
|
+
if normalization_factors is not None:
|
|
108
|
+
normalization.create_dataset(
|
|
109
|
+
"roi_labels",
|
|
110
|
+
data=np.asarray(
|
|
111
|
+
normalization_factors.roi_labels,
|
|
112
|
+
dtype=h5py.string_dtype("utf-8"),
|
|
113
|
+
),
|
|
114
|
+
)
|
|
115
|
+
normalization.create_dataset("factors", data=normalization_factors.factors)
|
|
116
|
+
|
|
89
117
|
correlation = group.create_group("correlation_filter")
|
|
90
118
|
correlation.attrs["reference"] = options.correlation_filter.reference
|
|
91
119
|
correlation.attrs["minimum_correlation"] = (
|
|
@@ -136,12 +164,25 @@ def read_response_processing_group(group: h5py.Group) -> PersistedResponseProces
|
|
|
136
164
|
options = ResponseProcessingOptions(
|
|
137
165
|
smoothing=_read_smoothing_options(group["smoothing"]),
|
|
138
166
|
low_pass=_read_low_pass_options(group["low_pass"]),
|
|
167
|
+
normalization=(
|
|
168
|
+
_read_normalization_options(group["normalization"])
|
|
169
|
+
if "normalization" in group
|
|
170
|
+
else NormalizationOptions()
|
|
171
|
+
),
|
|
139
172
|
correlation_filter=_read_correlation_filter_options(
|
|
140
173
|
group["correlation_filter"],
|
|
141
174
|
),
|
|
142
175
|
)
|
|
143
176
|
return PersistedResponseProcessing(
|
|
144
177
|
options=options,
|
|
178
|
+
normalization_factors=(
|
|
179
|
+
_read_normalization_factors(
|
|
180
|
+
group["normalization"],
|
|
181
|
+
options=options.normalization,
|
|
182
|
+
)
|
|
183
|
+
if "normalization" in group and "factors" in group["normalization"]
|
|
184
|
+
else None
|
|
185
|
+
),
|
|
145
186
|
correlation_scores=(
|
|
146
187
|
_read_correlation_scores(group["correlation_scores"])
|
|
147
188
|
if "correlation_scores" in group
|
|
@@ -176,6 +217,19 @@ def _read_low_pass_options(group: h5py.Group) -> LowPassFilterOptions:
|
|
|
176
217
|
)
|
|
177
218
|
|
|
178
219
|
|
|
220
|
+
def _read_normalization_options(group: h5py.Group) -> NormalizationOptions:
|
|
221
|
+
"""Read normalization settings from one HDF5 group."""
|
|
222
|
+
return NormalizationOptions(
|
|
223
|
+
method=require_string_choice(
|
|
224
|
+
_plain_attr_value(group.attrs["method"]),
|
|
225
|
+
name="response_processing normalization method",
|
|
226
|
+
allowed=_NORMALIZATION_METHODS,
|
|
227
|
+
),
|
|
228
|
+
epoch_number=_optional_int_from_attr(group.attrs["epoch_number"]),
|
|
229
|
+
epoch_name=_optional_text_from_attr(group.attrs["epoch_name"]),
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
|
|
179
233
|
def _read_correlation_filter_options(group: h5py.Group) -> CorrelationFilterOptions:
|
|
180
234
|
"""Read correlation-filter settings from one HDF5 group."""
|
|
181
235
|
return CorrelationFilterOptions(
|
|
@@ -189,6 +243,24 @@ def _read_correlation_filter_options(group: h5py.Group) -> CorrelationFilterOpti
|
|
|
189
243
|
)
|
|
190
244
|
|
|
191
245
|
|
|
246
|
+
def _read_normalization_factors(
|
|
247
|
+
group: h5py.Group,
|
|
248
|
+
*,
|
|
249
|
+
options: NormalizationOptions,
|
|
250
|
+
) -> RoiNormalizationFactors:
|
|
251
|
+
"""Read persisted epoch-peak normalization factors."""
|
|
252
|
+
return RoiNormalizationFactors(
|
|
253
|
+
roi_labels=_read_string_dataset(group, "roi_labels"),
|
|
254
|
+
factors=require_float64_array(
|
|
255
|
+
group["factors"][()],
|
|
256
|
+
name="response_processing/normalization/factors",
|
|
257
|
+
ndim=1,
|
|
258
|
+
),
|
|
259
|
+
epoch_number=int(options.epoch_number or 0),
|
|
260
|
+
epoch_name=options.epoch_name,
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
|
|
192
264
|
def _read_correlation_scores(group: h5py.Group) -> RoiCorrelationScores:
|
|
193
265
|
"""Read persisted ROI correlation QC scores."""
|
|
194
266
|
return RoiCorrelationScores(
|
|
@@ -226,6 +298,16 @@ def _optional_float_attr(value: float | None) -> str | float:
|
|
|
226
298
|
return "none" if value is None else float(value)
|
|
227
299
|
|
|
228
300
|
|
|
301
|
+
def _optional_int_attr(value: int | None) -> str | int:
|
|
302
|
+
"""Return an HDF5-friendly optional integer attribute value."""
|
|
303
|
+
return "none" if value is None else int(value)
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
def _optional_text_attr(value: str | None) -> str:
|
|
307
|
+
"""Return an HDF5-friendly optional text attribute value."""
|
|
308
|
+
return "none" if value is None else value
|
|
309
|
+
|
|
310
|
+
|
|
229
311
|
def _optional_float_from_attr(value: object) -> float | None:
|
|
230
312
|
"""Read an optional float value written by ``_optional_float_attr``."""
|
|
231
313
|
plain_value = _plain_attr_value(value)
|
|
@@ -234,6 +316,22 @@ def _optional_float_from_attr(value: object) -> float | None:
|
|
|
234
316
|
return float(plain_value)
|
|
235
317
|
|
|
236
318
|
|
|
319
|
+
def _optional_int_from_attr(value: object) -> int | None:
|
|
320
|
+
"""Read an optional integer value written by ``_optional_int_attr``."""
|
|
321
|
+
plain_value = _plain_attr_value(value)
|
|
322
|
+
if plain_value == "none":
|
|
323
|
+
return None
|
|
324
|
+
return int(plain_value)
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
def _optional_text_from_attr(value: object) -> str | None:
|
|
328
|
+
"""Read an optional text value written by ``_optional_text_attr``."""
|
|
329
|
+
plain_value = _plain_attr_value(value)
|
|
330
|
+
if plain_value == "none":
|
|
331
|
+
return None
|
|
332
|
+
return str(plain_value)
|
|
333
|
+
|
|
334
|
+
|
|
237
335
|
def _read_string_dataset(group: h5py.Group, name: str) -> tuple[str, ...]:
|
|
238
336
|
"""Read a UTF-8 string dataset from HDF5."""
|
|
239
337
|
return tuple(_decode_string(value) for value in group[name][()])
|