TwoPy 0.1.3__tar.gz → 0.1.4__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.
Files changed (138) hide show
  1. {twopy-0.1.3 → twopy-0.1.4}/PKG-INFO +8 -18
  2. {twopy-0.1.3 → twopy-0.1.4}/README.md +7 -17
  3. {twopy-0.1.3 → twopy-0.1.4}/pyproject.toml +1 -1
  4. {twopy-0.1.3 → twopy-0.1.4}/src/twopy/__init__.py +16 -5
  5. {twopy-0.1.3 → twopy-0.1.4}/src/twopy/analysis/__init__.py +12 -4
  6. {twopy-0.1.3 → twopy-0.1.4}/src/twopy/analysis/background_subtraction.py +223 -49
  7. {twopy-0.1.3 → twopy-0.1.4}/src/twopy/analysis/dff.py +175 -118
  8. twopy-0.1.4/src/twopy/analysis/dff_options.py +35 -0
  9. {twopy-0.1.3 → twopy-0.1.4}/src/twopy/analysis/epoch_mapping.py +2 -25
  10. {twopy-0.1.3 → twopy-0.1.4}/src/twopy/analysis/motion.py +2 -2
  11. {twopy-0.1.3 → twopy-0.1.4}/src/twopy/analysis/persistence.py +147 -87
  12. {twopy-0.1.3 → twopy-0.1.4}/src/twopy/analysis/response_processing/apply.py +16 -24
  13. {twopy-0.1.3 → twopy-0.1.4}/src/twopy/analysis/response_processing/qc.py +6 -16
  14. twopy-0.1.4/src/twopy/analysis/response_window_options.py +99 -0
  15. {twopy-0.1.3 → twopy-0.1.4}/src/twopy/analysis/responses.py +105 -16
  16. {twopy-0.1.3 → twopy-0.1.4}/src/twopy/analysis/trials.py +95 -17
  17. {twopy-0.1.3 → twopy-0.1.4}/src/twopy/analysis/workflow.py +190 -63
  18. {twopy-0.1.3 → twopy-0.1.4}/src/twopy/conversion/api.py +2 -1
  19. {twopy-0.1.3 → twopy-0.1.4}/src/twopy/conversion/types.py +9 -2
  20. {twopy-0.1.3 → twopy-0.1.4}/src/twopy/converted.py +38 -31
  21. {twopy-0.1.3/src/twopy/conversion → twopy-0.1.4/src/twopy}/frame_ranges.py +10 -5
  22. {twopy-0.1.3 → twopy-0.1.4}/src/twopy/napari/interactive.py +40 -0
  23. twopy-0.1.4/src/twopy/napari/plotting/data.py +561 -0
  24. twopy-0.1.4/src/twopy/napari/plotting/dff_options.py +396 -0
  25. twopy-0.1.4/src/twopy/napari/plotting/docks/__init__.py +23 -0
  26. twopy-0.1.4/src/twopy/napari/plotting/docks/api.py +151 -0
  27. twopy-0.1.4/src/twopy/napari/plotting/docks/dynamic_options.py +208 -0
  28. twopy-0.1.4/src/twopy/napari/plotting/docks/options_panel.py +170 -0
  29. twopy-0.1.4/src/twopy/napari/plotting/docks/paths.py +100 -0
  30. twopy-0.1.4/src/twopy/napari/plotting/docks/plot_area.py +248 -0
  31. twopy-0.1.4/src/twopy/napari/plotting/docks/response_plot_widget.py +723 -0
  32. twopy-0.1.4/src/twopy/napari/plotting/docks/save_actions.py +127 -0
  33. twopy-0.1.4/src/twopy/napari/plotting/docks/visibility_state.py +153 -0
  34. twopy-0.1.4/src/twopy/napari/plotting/form_controls.py +88 -0
  35. {twopy-0.1.3 → twopy-0.1.4}/src/twopy/napari/plotting/options.py +17 -7
  36. {twopy-0.1.3 → twopy-0.1.4}/src/twopy/napari/plotting/panels.py +4 -1
  37. {twopy-0.1.3 → twopy-0.1.4}/src/twopy/napari/plotting/processing_options.py +58 -37
  38. twopy-0.1.4/src/twopy/napari/plotting/response_window_options.py +171 -0
  39. {twopy-0.1.3 → twopy-0.1.4}/src/twopy/napari/plotting/widgets.py +28 -14
  40. {twopy-0.1.3 → twopy-0.1.4}/src/twopy/napari/responses.py +30 -0
  41. {twopy-0.1.3 → twopy-0.1.4}/src/twopy/napari/viewer.py +2 -1
  42. {twopy-0.1.3 → twopy-0.1.4}/src/twopy/parity/__init__.py +9 -1
  43. {twopy-0.1.3 → twopy-0.1.4}/src/twopy/parity/dff.py +11 -33
  44. twopy-0.1.4/src/twopy/parity/psycho5.py +243 -0
  45. {twopy-0.1.3 → twopy-0.1.4}/src/twopy/roi.py +3 -25
  46. {twopy-0.1.3 → twopy-0.1.4}/src/twopy.egg-info/PKG-INFO +8 -18
  47. {twopy-0.1.3 → twopy-0.1.4}/src/twopy.egg-info/SOURCES.txt +19 -2
  48. {twopy-0.1.3 → twopy-0.1.4}/tests/test_background_subtraction.py +90 -3
  49. {twopy-0.1.3 → twopy-0.1.4}/tests/test_conversion.py +20 -0
  50. {twopy-0.1.3 → twopy-0.1.4}/tests/test_converted.py +85 -1
  51. {twopy-0.1.3 → twopy-0.1.4}/tests/test_dff.py +84 -26
  52. twopy-0.1.4/tests/test_frame_ranges.py +71 -0
  53. {twopy-0.1.3 → twopy-0.1.4}/tests/test_motion.py +2 -2
  54. {twopy-0.1.3 → twopy-0.1.4}/tests/test_napari.py +784 -39
  55. {twopy-0.1.3 → twopy-0.1.4}/tests/test_parity.py +1 -1
  56. twopy-0.1.4/tests/test_parity_psycho5.py +211 -0
  57. {twopy-0.1.3 → twopy-0.1.4}/tests/test_persistence.py +36 -5
  58. {twopy-0.1.3 → twopy-0.1.4}/tests/test_real_fixture.py +4 -4
  59. {twopy-0.1.3 → twopy-0.1.4}/tests/test_response_processing.py +56 -2
  60. twopy-0.1.4/tests/test_response_window_options.py +73 -0
  61. {twopy-0.1.3 → twopy-0.1.4}/tests/test_responses.py +158 -2
  62. {twopy-0.1.3 → twopy-0.1.4}/tests/test_roi.py +18 -0
  63. {twopy-0.1.3 → twopy-0.1.4}/tests/test_stimulus.py +8 -2
  64. {twopy-0.1.3 → twopy-0.1.4}/tests/test_trials.py +62 -3
  65. {twopy-0.1.3 → twopy-0.1.4}/tests/test_workflow.py +63 -7
  66. twopy-0.1.3/src/twopy/napari/plotting/data.py +0 -290
  67. twopy-0.1.3/src/twopy/napari/plotting/docks.py +0 -1165
  68. {twopy-0.1.3 → twopy-0.1.4}/setup.cfg +0 -0
  69. {twopy-0.1.3 → twopy-0.1.4}/src/twopy/_segments.py +0 -0
  70. {twopy-0.1.3 → twopy-0.1.4}/src/twopy/_version.py +0 -0
  71. {twopy-0.1.3 → twopy-0.1.4}/src/twopy/analysis/response_processing/__init__.py +0 -0
  72. {twopy-0.1.3 → twopy-0.1.4}/src/twopy/analysis/response_processing/options.py +0 -0
  73. {twopy-0.1.3 → twopy-0.1.4}/src/twopy/analysis/response_processing/persistence.py +0 -0
  74. {twopy-0.1.3 → twopy-0.1.4}/src/twopy/analysis/response_processing/signals.py +0 -0
  75. {twopy-0.1.3 → twopy-0.1.4}/src/twopy/api.py +0 -0
  76. {twopy-0.1.3 → twopy-0.1.4}/src/twopy/config.py +0 -0
  77. {twopy-0.1.3 → twopy-0.1.4}/src/twopy/conversion/__init__.py +0 -0
  78. {twopy-0.1.3 → twopy-0.1.4}/src/twopy/conversion/alignment_crop.py +0 -0
  79. {twopy-0.1.3 → twopy-0.1.4}/src/twopy/conversion/hdf5_writing.py +0 -0
  80. {twopy-0.1.3 → twopy-0.1.4}/src/twopy/conversion/matlab_values.py +0 -0
  81. {twopy-0.1.3 → twopy-0.1.4}/src/twopy/conversion/source_loading.py +0 -0
  82. {twopy-0.1.3 → twopy-0.1.4}/src/twopy/conversion/stimulus_code.py +0 -0
  83. {twopy-0.1.3 → twopy-0.1.4}/src/twopy/database/__init__.py +0 -0
  84. {twopy-0.1.3 → twopy-0.1.4}/src/twopy/database/cache.py +0 -0
  85. {twopy-0.1.3 → twopy-0.1.4}/src/twopy/database/catalog.py +0 -0
  86. {twopy-0.1.3 → twopy-0.1.4}/src/twopy/database/connection.py +0 -0
  87. {twopy-0.1.3 → twopy-0.1.4}/src/twopy/database/generic.py +0 -0
  88. {twopy-0.1.3 → twopy-0.1.4}/src/twopy/database/modeled.py +0 -0
  89. {twopy-0.1.3 → twopy-0.1.4}/src/twopy/database/sql.py +0 -0
  90. {twopy-0.1.3 → twopy-0.1.4}/src/twopy/database/types.py +0 -0
  91. {twopy-0.1.3 → twopy-0.1.4}/src/twopy/inspection.py +0 -0
  92. {twopy-0.1.3 → twopy-0.1.4}/src/twopy/matlab.py +0 -0
  93. {twopy-0.1.3 → twopy-0.1.4}/src/twopy/napari/__init__.py +0 -0
  94. {twopy-0.1.3 → twopy-0.1.4}/src/twopy/napari/__main__.py +0 -0
  95. {twopy-0.1.3 → twopy-0.1.4}/src/twopy/napari/constants.py +0 -0
  96. {twopy-0.1.3 → twopy-0.1.4}/src/twopy/napari/controls.py +0 -0
  97. {twopy-0.1.3 → twopy-0.1.4}/src/twopy/napari/display.py +0 -0
  98. {twopy-0.1.3 → twopy-0.1.4}/src/twopy/napari/display_paths.py +0 -0
  99. {twopy-0.1.3 → twopy-0.1.4}/src/twopy/napari/launcher.py +0 -0
  100. {twopy-0.1.3 → twopy-0.1.4}/src/twopy/napari/layout.py +0 -0
  101. {twopy-0.1.3 → twopy-0.1.4}/src/twopy/napari/loading.py +0 -0
  102. {twopy-0.1.3 → twopy-0.1.4}/src/twopy/napari/movie.py +0 -0
  103. {twopy-0.1.3 → twopy-0.1.4}/src/twopy/napari/paths.py +0 -0
  104. {twopy-0.1.3 → twopy-0.1.4}/src/twopy/napari/plotting/__init__.py +0 -0
  105. {twopy-0.1.3 → twopy-0.1.4}/src/twopy/napari/plotting/export.py +0 -0
  106. {twopy-0.1.3 → twopy-0.1.4}/src/twopy/napari/plotting/export_controls.py +0 -0
  107. {twopy-0.1.3 → twopy-0.1.4}/src/twopy/napari/plotting/label_visibility.py +0 -0
  108. {twopy-0.1.3 → twopy-0.1.4}/src/twopy/napari/protocols.py +0 -0
  109. {twopy-0.1.3 → twopy-0.1.4}/src/twopy/napari/roi.py +0 -0
  110. {twopy-0.1.3 → twopy-0.1.4}/src/twopy/napari/session.py +0 -0
  111. {twopy-0.1.3 → twopy-0.1.4}/src/twopy/napari/state.py +0 -0
  112. {twopy-0.1.3 → twopy-0.1.4}/src/twopy/napari/text.py +0 -0
  113. {twopy-0.1.3 → twopy-0.1.4}/src/twopy/napari/types.py +0 -0
  114. {twopy-0.1.3 → twopy-0.1.4}/src/twopy/parity/roi.py +0 -0
  115. {twopy-0.1.3 → twopy-0.1.4}/src/twopy/parity/saved_analysis.py +0 -0
  116. {twopy-0.1.3 → twopy-0.1.4}/src/twopy/photodiode.py +0 -0
  117. {twopy-0.1.3 → twopy-0.1.4}/src/twopy/photodiode_classification.py +0 -0
  118. {twopy-0.1.3 → twopy-0.1.4}/src/twopy/session.py +0 -0
  119. {twopy-0.1.3 → twopy-0.1.4}/src/twopy/spatial.py +0 -0
  120. {twopy-0.1.3 → twopy-0.1.4}/src/twopy/stimulus.py +0 -0
  121. {twopy-0.1.3 → twopy-0.1.4}/src/twopy/synchronization.py +0 -0
  122. {twopy-0.1.3 → twopy-0.1.4}/src/twopy/typing_guards.py +0 -0
  123. {twopy-0.1.3 → twopy-0.1.4}/src/twopy.egg-info/dependency_links.txt +0 -0
  124. {twopy-0.1.3 → twopy-0.1.4}/src/twopy.egg-info/entry_points.txt +0 -0
  125. {twopy-0.1.3 → twopy-0.1.4}/src/twopy.egg-info/requires.txt +0 -0
  126. {twopy-0.1.3 → twopy-0.1.4}/src/twopy.egg-info/top_level.txt +0 -0
  127. {twopy-0.1.3 → twopy-0.1.4}/tests/test_api.py +0 -0
  128. {twopy-0.1.3 → twopy-0.1.4}/tests/test_config.py +0 -0
  129. {twopy-0.1.3 → twopy-0.1.4}/tests/test_database.py +0 -0
  130. {twopy-0.1.3 → twopy-0.1.4}/tests/test_epoch_mapping.py +0 -0
  131. {twopy-0.1.3 → twopy-0.1.4}/tests/test_import.py +0 -0
  132. {twopy-0.1.3 → twopy-0.1.4}/tests/test_inspection.py +0 -0
  133. {twopy-0.1.3 → twopy-0.1.4}/tests/test_matlab.py +0 -0
  134. {twopy-0.1.3 → twopy-0.1.4}/tests/test_packaging.py +0 -0
  135. {twopy-0.1.3 → twopy-0.1.4}/tests/test_photodiode_classification.py +0 -0
  136. {twopy-0.1.3 → twopy-0.1.4}/tests/test_saved_analysis.py +0 -0
  137. {twopy-0.1.3 → twopy-0.1.4}/tests/test_session.py +0 -0
  138. {twopy-0.1.3 → twopy-0.1.4}/tests/test_synchronization.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: twopy
3
- Version: 0.1.3
3
+ Version: 0.1.4
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
@@ -26,13 +26,9 @@ Two-photon imaging analysis tool for the Clark Lab output format.
26
26
 
27
27
  ## Getting Started
28
28
 
29
- twopy lets you open two-photon recordings, draw ROIs, plot responses in real time,
30
- process and analyze them, and save them.
29
+ twopy lets you open two-photon recordings, draw ROIs, plot responses in real time, process and analyze them, and save them.
31
30
 
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.
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.
36
32
 
37
33
  ## Install
38
34
 
@@ -50,8 +46,7 @@ Verify the install:
50
46
  micromamba run -n twopy python -c 'import twopy; print(twopy.__name__)'
51
47
  ```
52
48
 
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.
49
+ For a source checkout, copy `config.example.yml` to `config.yml` and edit the paths for your computer. `config.yml` stays local and is not tracked by git.
55
50
 
56
51
  ## Start
57
52
 
@@ -75,20 +70,15 @@ Basic GUI flow:
75
70
  4. Update response plots from the current ROIs.
76
71
  5. Save analysis outputs.
77
72
 
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.
73
+ 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.
81
74
 
82
75
  ## Docs
83
76
 
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.
77
+ - [GUI guide](docs/gui.md): napari loading, ROI editing, plotting, and saved outputs.
78
+ - [Python API guide](docs/python_api.md): find recordings, convert data, run analysis, and open napari from scripts.
88
79
  - [Development guide](docs/development.md): source setup and local checks.
89
80
  - [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.
81
+ - [Recording file reference](docs/recording_file_schema.md): detailed source and converted file schema.
92
82
 
93
83
  ## Check
94
84
 
@@ -6,13 +6,9 @@ Two-photon imaging analysis tool for the Clark Lab output format.
6
6
 
7
7
  ## Getting Started
8
8
 
9
- twopy lets you open two-photon recordings, draw ROIs, plot responses in real time,
10
- process and analyze them, and save them.
9
+ twopy lets you open two-photon recordings, draw ROIs, plot responses in real time, process and analyze them, and save them.
11
10
 
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.
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.
16
12
 
17
13
  ## Install
18
14
 
@@ -30,8 +26,7 @@ Verify the install:
30
26
  micromamba run -n twopy python -c 'import twopy; print(twopy.__name__)'
31
27
  ```
32
28
 
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.
29
+ For a source checkout, copy `config.example.yml` to `config.yml` and edit the paths for your computer. `config.yml` stays local and is not tracked by git.
35
30
 
36
31
  ## Start
37
32
 
@@ -55,20 +50,15 @@ Basic GUI flow:
55
50
  4. Update response plots from the current ROIs.
56
51
  5. Save analysis outputs.
57
52
 
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.
53
+ 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.
61
54
 
62
55
  ## Docs
63
56
 
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.
57
+ - [GUI guide](docs/gui.md): napari loading, ROI editing, plotting, and saved outputs.
58
+ - [Python API guide](docs/python_api.md): find recordings, convert data, run analysis, and open napari from scripts.
68
59
  - [Development guide](docs/development.md): source setup and local checks.
69
60
  - [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.
61
+ - [Recording file reference](docs/recording_file_schema.md): detailed source and converted file schema.
72
62
 
73
63
  ## Check
74
64
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "twopy"
7
- version = "0.1.3"
7
+ version = "0.1.4"
8
8
  description = "Two-photon imaging analysis tool with a napari interface."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.13,<3.14"
@@ -15,6 +15,7 @@ from twopy.analysis import (
15
15
  BackgroundCorrectionMethod,
16
16
  CorrelationFilterOptions,
17
17
  DeltaFOverFFitMode,
18
+ DeltaFOverFOptions,
18
19
  EpochFrameWindow,
19
20
  FrameWindow,
20
21
  GroupedRoiResponses,
@@ -33,25 +34,28 @@ from twopy.analysis import (
33
34
  apply_motion_artifact_mask_to_delta_f_over_f,
34
35
  compute_recording_responses,
35
36
  compute_roi_delta_f_over_f,
37
+ default_baseline_epoch_number,
36
38
  extract_background_corrected_roi_traces,
37
39
  frame_windows_from_photodiode_alignment,
38
40
  group_delta_f_over_f_by_epoch,
39
41
  interpolate_stimulus_epochs_to_frame_windows,
40
- is_gray_epoch_name,
42
+ is_baseline_epoch_name,
41
43
  load_analysis_outputs,
42
44
  make_frame_windows,
43
45
  map_stimulus_epochs_to_frame_windows,
46
+ resolve_baseline_frame_windows,
44
47
  save_analysis_outputs,
45
- select_epoch_frame_windows,
48
+ select_baseline_frame_windows,
46
49
  split_traces_by_frame_windows,
47
50
  summarize_epoch_roi_responses,
48
51
  summarize_grouped_responses,
52
+ validate_grouped_roi_responses,
49
53
  write_response_summary_grouped_csv,
50
54
  write_response_summary_trials_csv,
51
55
  )
52
56
  from twopy.api import find_recordings
53
57
  from twopy.conversion import convert_recording_to_twopy, load_source_conversion_inputs
54
- from twopy.converted import load_converted_recording
58
+ from twopy.converted import load_converted_recording, recording_frame_rate_hz
55
59
  from twopy.napari import (
56
60
  NapariControlDocks,
57
61
  NapariRecordingView,
@@ -83,6 +87,7 @@ from twopy.spatial import SpatialCrop, SpatialDomain, full_frame_crop
83
87
  from twopy.stimulus import (
84
88
  StimulusSpecificColumnMapping,
85
89
  map_stimulus_specific_column,
90
+ stimulus_epoch_names_by_number,
86
91
  )
87
92
  from twopy.synchronization import (
88
93
  AlignedPhotodiodeEvent,
@@ -114,9 +119,11 @@ __all__ = [
114
119
  "convert_recording_to_twopy",
115
120
  "DEFAULT_RESPONSE_POST_WINDOW_SECONDS",
116
121
  "DEFAULT_RESPONSE_PRE_WINDOW_SECONDS",
122
+ "default_baseline_epoch_number",
117
123
  "detect_photodiode_events",
118
124
  "detect_recording_photodiode_events",
119
125
  "DeltaFOverFFitMode",
126
+ "DeltaFOverFOptions",
120
127
  "EpochFrameWindow",
121
128
  "extract_background_corrected_roi_traces",
122
129
  "extract_roi_traces",
@@ -129,7 +136,7 @@ __all__ = [
129
136
  "GroupedRoiResponses",
130
137
  "InterpolatedEpochMapping",
131
138
  "interpolate_stimulus_epochs_to_frame_windows",
132
- "is_gray_epoch_name",
139
+ "is_baseline_epoch_name",
133
140
  "launch_napari",
134
141
  "load_analysis_outputs",
135
142
  "load_converted_recording",
@@ -152,6 +159,8 @@ __all__ = [
152
159
  "PhotodiodeEventType",
153
160
  "PhotodiodeEventSet",
154
161
  "ResponseProcessingOptions",
162
+ "recording_frame_rate_hz",
163
+ "resolve_baseline_frame_windows",
155
164
  "RoiDeltaFOverF",
156
165
  "RoiCorrelationScores",
157
166
  "RoiResponseSummary",
@@ -163,14 +172,16 @@ __all__ = [
163
172
  "save_analysis_outputs",
164
173
  "save_napari_label_rois",
165
174
  "save_roi_set",
166
- "select_epoch_frame_windows",
175
+ "select_baseline_frame_windows",
167
176
  "split_traces_by_frame_windows",
168
177
  "SmoothingOptions",
169
178
  "SpatialCrop",
170
179
  "SpatialDomain",
171
180
  "StimulusSpecificColumnMapping",
181
+ "stimulus_epoch_names_by_number",
172
182
  "summarize_epoch_roi_responses",
173
183
  "summarize_grouped_responses",
184
+ "validate_grouped_roi_responses",
174
185
  "WindowedRoiResponse",
175
186
  "write_response_summary_grouped_csv",
176
187
  "write_response_summary_trials_csv",
@@ -18,6 +18,7 @@ from twopy.analysis.dff import (
18
18
  RoiDeltaFOverF,
19
19
  compute_roi_delta_f_over_f,
20
20
  )
21
+ from twopy.analysis.dff_options import DeltaFOverFOptions
21
22
  from twopy.analysis.epoch_mapping import (
22
23
  InterpolatedEpochMapping,
23
24
  interpolate_stimulus_epochs_to_frame_windows,
@@ -44,18 +45,21 @@ from twopy.analysis.responses import (
44
45
  RoiResponseSummary,
45
46
  RoiResponseTrial,
46
47
  group_delta_f_over_f_by_epoch,
47
- is_gray_epoch_name,
48
48
  summarize_epoch_roi_responses,
49
49
  summarize_grouped_responses,
50
+ validate_grouped_roi_responses,
50
51
  )
51
52
  from twopy.analysis.trials import (
52
53
  EpochFrameWindow,
53
54
  FrameWindow,
54
55
  WindowedRoiResponse,
56
+ default_baseline_epoch_number,
55
57
  frame_windows_from_photodiode_alignment,
58
+ is_baseline_epoch_name,
56
59
  make_frame_windows,
57
60
  map_stimulus_epochs_to_frame_windows,
58
- select_epoch_frame_windows,
61
+ resolve_baseline_frame_windows,
62
+ select_baseline_frame_windows,
59
63
  split_traces_by_frame_windows,
60
64
  )
61
65
  from twopy.analysis.workflow import (
@@ -80,6 +84,8 @@ __all__ = [
80
84
  "compute_roi_delta_f_over_f",
81
85
  "compute_recording_responses",
82
86
  "CorrelationFilterOptions",
87
+ "default_baseline_epoch_number",
88
+ "DeltaFOverFOptions",
83
89
  "DeltaFOverFFitMode",
84
90
  "EpochFrameWindow",
85
91
  "extract_background_corrected_roi_traces",
@@ -90,7 +96,7 @@ __all__ = [
90
96
  "GroupedRoiResponses",
91
97
  "InterpolatedEpochMapping",
92
98
  "interpolate_stimulus_epochs_to_frame_windows",
93
- "is_gray_epoch_name",
99
+ "is_baseline_epoch_name",
94
100
  "load_analysis_outputs",
95
101
  "LoadedAnalysisOutputs",
96
102
  "LowPassFilterOptions",
@@ -101,12 +107,14 @@ __all__ = [
101
107
  "RoiCorrelationScores",
102
108
  "RoiResponseSummary",
103
109
  "RoiResponseTrial",
110
+ "resolve_baseline_frame_windows",
104
111
  "save_analysis_outputs",
105
- "select_epoch_frame_windows",
112
+ "select_baseline_frame_windows",
106
113
  "split_traces_by_frame_windows",
107
114
  "SmoothingOptions",
108
115
  "summarize_epoch_roi_responses",
109
116
  "summarize_grouped_responses",
117
+ "validate_grouped_roi_responses",
110
118
  "WindowedRoiResponse",
111
119
  "write_response_summary_grouped_csv",
112
120
  "write_response_summary_trials_csv",
@@ -16,6 +16,7 @@ import numpy as np
16
16
  import numpy.typing as npt
17
17
 
18
18
  from twopy.converted import RecordingData
19
+ from twopy.frame_ranges import normalize_frame_range
19
20
  from twopy.roi import RoiSet, TraceStatistic, validate_trace_statistic
20
21
  from twopy.spatial import SpatialCrop, SpatialDomain, full_frame_crop
21
22
 
@@ -28,7 +29,8 @@ __all__ = [
28
29
  BackgroundCorrectionMethod = Literal[
29
30
  "none",
30
31
  "movie_global_percentile",
31
- "roi_local_percentile_y",
32
+ "movie_y_stripe_percentile",
33
+ "roi_y_stripe_percentile",
32
34
  ]
33
35
  BackgroundMetadataValue = str | int | float | bool
34
36
 
@@ -68,6 +70,8 @@ def extract_background_corrected_roi_traces(
68
70
  chunk_frames: int = 128,
69
71
  statistic: TraceStatistic = "mean",
70
72
  global_percentile: float = 10.0,
73
+ stripe_y_height: int | None = None,
74
+ stripe_percentile: float = 20.0,
71
75
  local_y_radius: int | None = None,
72
76
  local_percentile: float = 20.0,
73
77
  spatial_domain: SpatialDomain = "alignment_valid_crop",
@@ -80,9 +84,10 @@ def extract_background_corrected_roi_traces(
80
84
  method: Background correction method. ``"none"`` keeps raw traces inside
81
85
  the selected spatial domain, ``"movie_global_percentile"`` subtracts
82
86
  a framewise background estimated from dim global pixels in the mean
83
- image, and
84
- ``"roi_local_percentile_y"`` subtracts a local y-neighborhood
85
- background trace from each ROI.
87
+ image, ``"movie_y_stripe_percentile"`` subtracts a framewise
88
+ low-percentile y-stripe background field, and
89
+ ``"roi_y_stripe_percentile"`` subtracts a per-ROI y-stripe
90
+ background trace.
86
91
  start_frame: Optional first frame to extract.
87
92
  stop_frame: Optional exclusive stop frame.
88
93
  chunk_frames: Number of movie frames to read per chunk.
@@ -90,10 +95,15 @@ def extract_background_corrected_roi_traces(
90
95
  supported.
91
96
  global_percentile: Percentile of the recording mean image used to pick
92
97
  global background pixels.
93
- local_y_radius: Half-width in rows for local-y background pixels. When
98
+ stripe_y_height: Height in rows for framewise y-stripe background
99
+ correction. When omitted, twopy uses roughly five percent of the
100
+ image height.
101
+ stripe_percentile: Percentile inside each frame stripe used to estimate
102
+ the framewise y-stripe background field.
103
+ local_y_radius: Half-width in rows for the per-ROI y-stripe. When
94
104
  omitted, twopy uses roughly five percent of the image height.
95
- local_percentile: Percentile inside each local row window used to keep
96
- dim background pixels and reject bright non-ROI structures.
105
+ local_percentile: Percentile inside each ROI y-stripe used to keep dim
106
+ background pixels and reject bright non-ROI structures.
97
107
  spatial_domain: Spatial region used for analysis extraction. The default
98
108
  uses the alignment-valid crop saved during conversion, matching the
99
109
  crop-domain analysis contract while keeping ROI masks stored in
@@ -115,10 +125,11 @@ def extract_background_corrected_roi_traces(
115
125
  validate_trace_statistic(statistic)
116
126
  _validate_roi_movie_shape(roi_set, recording)
117
127
 
118
- frame_start, frame_stop = _normalize_trace_frame_range(
128
+ frame_start, frame_stop = normalize_frame_range(
119
129
  frame_count=recording.movie.shape[0],
120
130
  start_frame=start_frame,
121
131
  stop_frame=stop_frame,
132
+ context="trace frame range",
122
133
  )
123
134
 
124
135
  crop = _resolve_spatial_domain(recording, spatial_domain)
@@ -153,9 +164,23 @@ def extract_background_corrected_roi_traces(
153
164
  spatial_domain=spatial_domain,
154
165
  )
155
166
  )
156
- elif method == "roi_local_percentile_y":
167
+ elif method == "movie_y_stripe_percentile":
157
168
  raw_values, background_values, corrected_values, metadata = (
158
- _extract_roi_local_y_corrected_traces(
169
+ _extract_movie_y_stripe_percentile_corrected_traces(
170
+ recording=recording,
171
+ roi_pixel_indices=roi_pixel_indices,
172
+ frame_start=frame_start,
173
+ frame_stop=frame_stop,
174
+ chunk_frames=chunk_frames,
175
+ stripe_y_height=stripe_y_height,
176
+ percentile=stripe_percentile,
177
+ spatial_crop=crop,
178
+ spatial_domain=spatial_domain,
179
+ )
180
+ )
181
+ elif method == "roi_y_stripe_percentile":
182
+ raw_values, background_values, corrected_values, metadata = (
183
+ _extract_roi_y_stripe_percentile_corrected_traces(
159
184
  recording=recording,
160
185
  roi_masks=cropped_roi_masks,
161
186
  roi_pixel_indices=roi_pixel_indices,
@@ -200,7 +225,8 @@ def _validate_background_method(method: BackgroundCorrectionMethod) -> None:
200
225
  allowed_methods = {
201
226
  "none",
202
227
  "movie_global_percentile",
203
- "roi_local_percentile_y",
228
+ "movie_y_stripe_percentile",
229
+ "roi_y_stripe_percentile",
204
230
  }
205
231
  if method not in allowed_methods:
206
232
  msg = (
@@ -332,7 +358,104 @@ def _extract_movie_global_percentile_corrected_traces(
332
358
  return raw_values, background_values, corrected_values, metadata
333
359
 
334
360
 
335
- def _extract_roi_local_y_corrected_traces(
361
+ def _extract_movie_y_stripe_percentile_corrected_traces(
362
+ *,
363
+ recording: RecordingData,
364
+ roi_pixel_indices: tuple[npt.NDArray[np.int64], ...],
365
+ frame_start: int,
366
+ frame_stop: int,
367
+ chunk_frames: int,
368
+ stripe_y_height: int | None,
369
+ percentile: float,
370
+ spatial_crop: SpatialCrop,
371
+ spatial_domain: SpatialDomain,
372
+ ) -> tuple[
373
+ npt.NDArray[np.float64],
374
+ npt.NDArray[np.float64],
375
+ npt.NDArray[np.float64],
376
+ dict[str, BackgroundMetadataValue],
377
+ ]:
378
+ """Extract traces corrected by a framewise y-stripe background field.
379
+
380
+ Args:
381
+ recording: Converted recording with lazy movie batches.
382
+ roi_pixel_indices: Flattened pixel indices for each ROI.
383
+ frame_start: First frame to read.
384
+ frame_stop: Exclusive stop frame.
385
+ chunk_frames: Maximum number of frames per HDF5 read.
386
+ stripe_y_height: Optional stripe height in image rows. When omitted,
387
+ twopy uses roughly five percent of the image height.
388
+ percentile: Percentile used inside each frame stripe.
389
+ spatial_crop: Spatial domain used for background and ROI pixels.
390
+ spatial_domain: User-facing name of ``spatial_crop``.
391
+
392
+ Returns:
393
+ Raw ROI traces, stripe-field background traces, corrected traces, and
394
+ audit metadata.
395
+
396
+ Plain algorithm:
397
+ divide each frame into y-stripes
398
+ take the dim percentile inside each stripe for that frame
399
+ assign each ROI pixel the background from its stripe
400
+ average those background pixels over each ROI
401
+ subtract the stripe background from each ROI pixel
402
+
403
+ This models broad, row-dependent background without assuming that pixels
404
+ near one ROI are safe neuropil reference pixels.
405
+ """
406
+ _validate_percentile(percentile)
407
+ crop_shape = spatial_crop.crop_image(recording.mean_image).shape
408
+ stripe_height = _resolve_stripe_y_height(
409
+ row_count=crop_shape[0],
410
+ stripe_y_height=stripe_y_height,
411
+ )
412
+ row_slices = _stripe_row_slices(
413
+ row_count=crop_shape[0],
414
+ stripe_y_height=stripe_height,
415
+ )
416
+ frame_count = frame_stop - frame_start
417
+ roi_count = len(roi_pixel_indices)
418
+ raw_values = np.empty((frame_count, roi_count), dtype=np.float64)
419
+ background_values = np.empty((frame_count, roi_count), dtype=np.float64)
420
+ corrected_values = np.empty((frame_count, roi_count), dtype=np.float64)
421
+
422
+ for chunk_start, chunk_stop, frames in recording.movie.iter_frame_batches(
423
+ chunk_frames=chunk_frames,
424
+ start=frame_start,
425
+ stop=frame_stop,
426
+ spatial_crop=spatial_crop,
427
+ ):
428
+ chunk_offset = chunk_start - frame_start
429
+ chunk_slice = slice(chunk_offset, chunk_offset + (chunk_stop - chunk_start))
430
+ background_field = _framewise_y_stripe_background_field(
431
+ frames,
432
+ row_slices=row_slices,
433
+ percentile=percentile,
434
+ )
435
+ chunk_flat = frames.reshape(frames.shape[0], -1)
436
+ background_flat = background_field.reshape(background_field.shape[0], -1)
437
+ for roi_index, pixel_indices in enumerate(roi_pixel_indices):
438
+ roi_pixels = chunk_flat[:, pixel_indices]
439
+ roi_background_pixels = background_flat[:, pixel_indices]
440
+ raw_values[chunk_slice, roi_index] = roi_pixels.mean(axis=1)
441
+ background_values[chunk_slice, roi_index] = roi_background_pixels.mean(
442
+ axis=1,
443
+ )
444
+ corrected_pixels = np.maximum(roi_pixels - roi_background_pixels, 0.0)
445
+ corrected_values[chunk_slice, roi_index] = corrected_pixels.mean(axis=1)
446
+
447
+ metadata: dict[str, BackgroundMetadataValue] = {
448
+ "method": "movie_y_stripe_percentile",
449
+ "stripe_y_height": stripe_height,
450
+ "stripe_percentile": float(percentile),
451
+ "stripe_count": len(row_slices),
452
+ "negative_values_clamped": True,
453
+ **_spatial_crop_metadata(spatial_domain, spatial_crop),
454
+ }
455
+ return raw_values, background_values, corrected_values, metadata
456
+
457
+
458
+ def _extract_roi_y_stripe_percentile_corrected_traces(
336
459
  *,
337
460
  recording: RecordingData,
338
461
  roi_masks: npt.NDArray[np.bool_],
@@ -350,7 +473,7 @@ def _extract_roi_local_y_corrected_traces(
350
473
  npt.NDArray[np.float64],
351
474
  dict[str, BackgroundMetadataValue],
352
475
  ]:
353
- """Extract ROI traces corrected by local y-neighborhood background.
476
+ """Extract ROI traces corrected by per-ROI y-stripe background.
354
477
 
355
478
  Args:
356
479
  recording: Converted recording with lazy movie batches.
@@ -359,25 +482,33 @@ def _extract_roi_local_y_corrected_traces(
359
482
  frame_start: First frame to read.
360
483
  frame_stop: Exclusive stop frame.
361
484
  chunk_frames: Maximum number of frames per HDF5 read.
362
- local_y_radius: Optional row half-width for local background pixels.
363
- local_percentile: Percentile used to keep dim local background pixels.
485
+ local_y_radius: Optional row half-width for the ROI y-stripe.
486
+ local_percentile: Percentile used to keep dim pixels in each ROI
487
+ y-stripe.
364
488
  spatial_crop: Spatial domain used for background and ROI pixels.
365
489
  spatial_domain: User-facing name of ``spatial_crop``.
366
490
 
367
491
  Returns:
368
492
  Raw ROI traces, local background traces, corrected traces, and metadata.
369
493
 
494
+ Plain algorithm:
495
+ take rows near that ROI center
496
+ exclude all ROI pixels
497
+ keep dim pixels by percentile
498
+ average those pixels over time
499
+ subtract that trace from that ROI only
500
+
370
501
  The method estimates a background trace per ROI from dim pixels that are
371
- outside every ROI and close to that ROI along the scanline axis. It is
372
- designed for stimulus bleedthrough that varies across rows without
373
- requiring relaxation labeling or connected-component heuristics.
502
+ outside every ROI and inside that ROI's y-stripe. It is designed for
503
+ stimulus bleedthrough that varies across rows without requiring relaxation
504
+ labeling or connected-component heuristics.
374
505
  """
375
- radius = _resolve_local_y_radius(
506
+ radius = _resolve_roi_y_stripe_radius(
376
507
  row_count=roi_masks.shape[1],
377
508
  local_y_radius=local_y_radius,
378
509
  )
379
510
  _validate_percentile(local_percentile)
380
- background_indices_by_roi = _local_y_background_pixel_indices(
511
+ background_indices_by_roi = _roi_y_stripe_background_pixel_indices(
381
512
  roi_masks=roi_masks,
382
513
  mean_image=spatial_crop.crop_image(recording.mean_image),
383
514
  local_y_radius=radius,
@@ -404,7 +535,7 @@ def _extract_roi_local_y_corrected_traces(
404
535
  int(pixel_indices.size) for pixel_indices in background_indices_by_roi
405
536
  )
406
537
  metadata: dict[str, BackgroundMetadataValue] = {
407
- "method": "roi_local_percentile_y",
538
+ "method": "roi_y_stripe_percentile",
408
539
  "local_y_radius": radius,
409
540
  "local_percentile": float(local_percentile),
410
541
  "background_pixel_count_min": min(background_counts),
@@ -523,12 +654,12 @@ def _global_background_pixel_indices(
523
654
  return background_indices, threshold
524
655
 
525
656
 
526
- def _resolve_local_y_radius(
657
+ def _resolve_roi_y_stripe_radius(
527
658
  *,
528
659
  row_count: int,
529
660
  local_y_radius: int | None,
530
661
  ) -> int:
531
- """Resolve the row radius for local-y background correction.
662
+ """Resolve the row radius for ROI y-stripe background correction.
532
663
 
533
664
  Args:
534
665
  row_count: Number of image rows.
@@ -544,14 +675,81 @@ def _resolve_local_y_radius(
544
675
  return radius
545
676
 
546
677
 
547
- def _local_y_background_pixel_indices(
678
+ def _resolve_stripe_y_height(
679
+ *,
680
+ row_count: int,
681
+ stripe_y_height: int | None,
682
+ ) -> int:
683
+ """Resolve y-stripe height for framewise background correction.
684
+
685
+ Args:
686
+ row_count: Number of image rows.
687
+ stripe_y_height: Optional caller-provided stripe height.
688
+
689
+ Returns:
690
+ Positive stripe height in rows.
691
+ """
692
+ height = (
693
+ max(1, round(row_count / 20)) if stripe_y_height is None else stripe_y_height
694
+ )
695
+ if height <= 0:
696
+ msg = f"stripe_y_height must be positive; got {height}"
697
+ raise ValueError(msg)
698
+ return height
699
+
700
+
701
+ def _stripe_row_slices(
702
+ *,
703
+ row_count: int,
704
+ stripe_y_height: int,
705
+ ) -> tuple[slice, ...]:
706
+ """Split image rows into contiguous y-stripe slices.
707
+
708
+ Args:
709
+ row_count: Number of image rows.
710
+ stripe_y_height: Stripe height in rows.
711
+
712
+ Returns:
713
+ Tuple of row slices covering the image.
714
+ """
715
+ return tuple(
716
+ slice(start, min(start + stripe_y_height, row_count))
717
+ for start in range(0, row_count, stripe_y_height)
718
+ )
719
+
720
+
721
+ def _framewise_y_stripe_background_field(
722
+ frames: npt.NDArray[np.float64],
723
+ *,
724
+ row_slices: tuple[slice, ...],
725
+ percentile: float,
726
+ ) -> npt.NDArray[np.float64]:
727
+ """Build one low-percentile y-stripe background image per frame.
728
+
729
+ Args:
730
+ frames: Movie chunk shaped ``(frames, rows, columns)``.
731
+ row_slices: Row stripes used for the background field.
732
+ percentile: Percentile used inside each frame stripe.
733
+
734
+ Returns:
735
+ Background field shaped like ``frames``.
736
+ """
737
+ background = np.empty_like(frames, dtype=np.float64)
738
+ for row_slice in row_slices:
739
+ stripe_values = frames[:, row_slice, :].reshape(frames.shape[0], -1)
740
+ stripe_background = np.percentile(stripe_values, percentile, axis=1)
741
+ background[:, row_slice, :] = stripe_background[:, None, None]
742
+ return background
743
+
744
+
745
+ def _roi_y_stripe_background_pixel_indices(
548
746
  *,
549
747
  roi_masks: npt.NDArray[np.bool_],
550
748
  mean_image: npt.NDArray[np.float64],
551
749
  local_y_radius: int,
552
750
  local_percentile: float,
553
751
  ) -> tuple[npt.NDArray[np.int64], ...]:
554
- """Find local y-neighborhood background pixels for each ROI.
752
+ """Find ROI y-stripe background pixels for each ROI.
555
753
 
556
754
  Args:
557
755
  roi_masks: ROI masks used to exclude foreground pixels.
@@ -646,27 +844,3 @@ def _spatial_crop_metadata(
646
844
  "spatial_original_axis0": spatial_crop.original_shape[0],
647
845
  "spatial_original_axis1": spatial_crop.original_shape[1],
648
846
  }
649
-
650
-
651
- def _normalize_trace_frame_range(
652
- *,
653
- frame_count: int,
654
- start_frame: int | None,
655
- stop_frame: int | None,
656
- ) -> tuple[int, int]:
657
- """Normalize and validate the frame range for trace extraction.
658
-
659
- Args:
660
- frame_count: Number of frames in the movie.
661
- start_frame: Optional first frame index.
662
- stop_frame: Optional exclusive stop frame.
663
-
664
- Returns:
665
- ``(start, stop)`` frame bounds.
666
- """
667
- start = 0 if start_frame is None else start_frame
668
- stop = frame_count if stop_frame is None else stop_frame
669
- if start < 0 or stop > frame_count or start >= stop:
670
- msg = f"Invalid trace frame range [{start}, {stop}) for {frame_count} frames"
671
- raise ValueError(msg)
672
- return start, stop