phasorpy 0.11__tar.gz → 0.12__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 (136) hide show
  1. {phasorpy-0.11/src/phasorpy.egg-info → phasorpy-0.12}/PKG-INFO +3 -2
  2. {phasorpy-0.11 → phasorpy-0.12}/docs/_static/switcher.json +8 -3
  3. phasorpy-0.12/docs/about.rst +83 -0
  4. phasorpy-0.12/docs/acknowledgments.rst +3 -0
  5. {phasorpy-0.11 → phasorpy-0.12}/docs/conf.py +0 -4
  6. {phasorpy-0.11 → phasorpy-0.12}/docs/contributing.rst +28 -4
  7. {phasorpy-0.11 → phasorpy-0.12}/docs/index.rst +5 -5
  8. {phasorpy-0.11 → phasorpy-0.12}/docs/phasor_approach.rst +27 -0
  9. {phasorpy-0.11 → phasorpy-0.12}/pyproject.toml +13 -8
  10. {phasorpy-0.11 → phasorpy-0.12}/src/phasorpy/__init__.py +1 -1
  11. {phasorpy-0.11 → phasorpy-0.12}/src/phasorpy/_phasorpy.pyx +84 -6
  12. {phasorpy-0.11 → phasorpy-0.12}/src/phasorpy/cli.py +123 -0
  13. {phasorpy-0.11 → phasorpy-0.12}/src/phasorpy/component.py +1 -1
  14. phasorpy-0.12/src/phasorpy/experimental.py +264 -0
  15. {phasorpy-0.11 → phasorpy-0.12}/src/phasorpy/io/__init__.py +11 -6
  16. {phasorpy-0.11 → phasorpy-0.12}/src/phasorpy/io/_other.py +292 -0
  17. {phasorpy-0.11 → phasorpy-0.12}/src/phasorpy/lifetime.py +1 -1
  18. {phasorpy-0.11 → phasorpy-0.12}/src/phasorpy/phasor.py +1 -1
  19. {phasorpy-0.11 → phasorpy-0.12}/src/phasorpy/plot/__init__.py +3 -0
  20. {phasorpy-0.11 → phasorpy-0.12}/src/phasorpy/plot/_phasorplot.py +8 -1
  21. phasorpy-0.12/src/phasorpy/plot/_spectral_plots.py +734 -0
  22. {phasorpy-0.11 → phasorpy-0.12/src/phasorpy.egg-info}/PKG-INFO +3 -2
  23. {phasorpy-0.11 → phasorpy-0.12}/src/phasorpy.egg-info/SOURCES.txt +3 -0
  24. {phasorpy-0.11 → phasorpy-0.12}/src/phasorpy.egg-info/requires.txt +2 -1
  25. {phasorpy-0.11 → phasorpy-0.12}/tests/conftest.py +4 -4
  26. {phasorpy-0.11 → phasorpy-0.12}/tests/io/test_flimlabs.py +8 -10
  27. {phasorpy-0.11 → phasorpy-0.12}/tests/io/test_leica.py +4 -8
  28. {phasorpy-0.11 → phasorpy-0.12}/tests/io/test_ometiff.py +12 -13
  29. phasorpy-0.12/tests/io/test_other.py +729 -0
  30. {phasorpy-0.11 → phasorpy-0.12}/tests/io/test_simfcs.py +13 -17
  31. {phasorpy-0.11 → phasorpy-0.12}/tests/plot/test_functions.py +20 -18
  32. {phasorpy-0.11 → phasorpy-0.12}/tests/plot/test_lifetime_plots.py +6 -10
  33. {phasorpy-0.11 → phasorpy-0.12}/tests/plot/test_phasorplot.py +22 -24
  34. {phasorpy-0.11 → phasorpy-0.12}/tests/plot/test_phasorplot_fret.py +2 -6
  35. phasorpy-0.12/tests/plot/test_spectral_plots.py +242 -0
  36. {phasorpy-0.11 → phasorpy-0.12}/tests/test__phasorpy.py +42 -35
  37. {phasorpy-0.11 → phasorpy-0.12}/tests/test__typing.py +1 -5
  38. {phasorpy-0.11 → phasorpy-0.12}/tests/test__utils.py +20 -23
  39. {phasorpy-0.11 → phasorpy-0.12}/tests/test_cli.py +32 -7
  40. {phasorpy-0.11 → phasorpy-0.12}/tests/test_cluster.py +19 -12
  41. {phasorpy-0.11 → phasorpy-0.12}/tests/test_color.py +2 -6
  42. {phasorpy-0.11 → phasorpy-0.12}/tests/test_component.py +68 -63
  43. {phasorpy-0.11 → phasorpy-0.12}/tests/test_cursor.py +53 -26
  44. {phasorpy-0.11 → phasorpy-0.12}/tests/test_datasets.py +10 -14
  45. phasorpy-0.12/tests/test_experimental.py +114 -0
  46. {phasorpy-0.11 → phasorpy-0.12}/tests/test_filter.py +65 -50
  47. {phasorpy-0.11 → phasorpy-0.12}/tests/test_lifetime.py +100 -70
  48. {phasorpy-0.11 → phasorpy-0.12}/tests/test_nan.py +28 -32
  49. {phasorpy-0.11 → phasorpy-0.12}/tests/test_phasor.py +88 -66
  50. {phasorpy-0.11 → phasorpy-0.12}/tests/test_phasorpy.py +1 -5
  51. {phasorpy-0.11 → phasorpy-0.12}/tests/test_utils.py +3 -7
  52. {phasorpy-0.11 → phasorpy-0.12}/tools/build_manylinux.cmd +1 -1
  53. {phasorpy-0.11 → phasorpy-0.12}/tools/build_manylinux.sh +1 -1
  54. {phasorpy-0.11 → phasorpy-0.12}/tutorials/api/phasorpy_io.py +2 -0
  55. {phasorpy-0.11 → phasorpy-0.12}/tutorials/misc/phasorpy_apps.py +43 -6
  56. {phasorpy-0.11 → phasorpy-0.12}/tutorials/phasorpy_lifetime_geometry.py +17 -16
  57. phasorpy-0.11/docs/acknowledgments.rst +0 -42
  58. phasorpy-0.11/src/phasorpy/experimental.py +0 -146
  59. phasorpy-0.11/tests/io/test_other.py +0 -362
  60. phasorpy-0.11/tests/test_experimental.py +0 -37
  61. {phasorpy-0.11 → phasorpy-0.12}/LICENSE.txt +0 -0
  62. {phasorpy-0.11 → phasorpy-0.12}/MANIFEST.in +0 -0
  63. {phasorpy-0.11 → phasorpy-0.12}/README.md +0 -0
  64. {phasorpy-0.11 → phasorpy-0.12}/docs/_static/categorical.png +0 -0
  65. {phasorpy-0.11 → phasorpy-0.12}/docs/_static/custom-icons.js +0 -0
  66. {phasorpy-0.11 → phasorpy-0.12}/docs/_static/favicon.ico +0 -0
  67. {phasorpy-0.11 → phasorpy-0.12}/docs/_static/phasorpy_logo.png +0 -0
  68. {phasorpy-0.11 → phasorpy-0.12}/docs/_static/phasorpy_logo.svg +0 -0
  69. {phasorpy-0.11 → phasorpy-0.12}/docs/_static/srgb_spectrum.png +0 -0
  70. {phasorpy-0.11 → phasorpy-0.12}/docs/api/_phasorpy.rst +0 -0
  71. {phasorpy-0.11 → phasorpy-0.12}/docs/api/_utils.rst +0 -0
  72. {phasorpy-0.11 → phasorpy-0.12}/docs/api/cli.rst +0 -0
  73. {phasorpy-0.11 → phasorpy-0.12}/docs/api/cluster.rst +0 -0
  74. {phasorpy-0.11 → phasorpy-0.12}/docs/api/color.rst +0 -0
  75. {phasorpy-0.11 → phasorpy-0.12}/docs/api/component.rst +0 -0
  76. {phasorpy-0.11 → phasorpy-0.12}/docs/api/cursor.rst +0 -0
  77. {phasorpy-0.11 → phasorpy-0.12}/docs/api/datasets.rst +0 -0
  78. {phasorpy-0.11 → phasorpy-0.12}/docs/api/experimental.rst +0 -0
  79. {phasorpy-0.11 → phasorpy-0.12}/docs/api/filter.rst +0 -0
  80. {phasorpy-0.11 → phasorpy-0.12}/docs/api/index.rst +0 -0
  81. {phasorpy-0.11 → phasorpy-0.12}/docs/api/io.rst +0 -0
  82. {phasorpy-0.11 → phasorpy-0.12}/docs/api/lifetime.rst +0 -0
  83. {phasorpy-0.11 → phasorpy-0.12}/docs/api/phasor.rst +0 -0
  84. {phasorpy-0.11 → phasorpy-0.12}/docs/api/phasorpy.rst +0 -0
  85. {phasorpy-0.11 → phasorpy-0.12}/docs/api/plot.rst +0 -0
  86. {phasorpy-0.11 → phasorpy-0.12}/docs/api/utils.rst +0 -0
  87. {phasorpy-0.11 → phasorpy-0.12}/docs/code_of_conduct.rst +0 -0
  88. {phasorpy-0.11 → phasorpy-0.12}/docs/license.rst +0 -0
  89. {phasorpy-0.11 → phasorpy-0.12}/setup.cfg +0 -0
  90. {phasorpy-0.11 → phasorpy-0.12}/setup.py +0 -0
  91. {phasorpy-0.11 → phasorpy-0.12}/src/phasorpy/__main__.py +0 -0
  92. {phasorpy-0.11 → phasorpy-0.12}/src/phasorpy/_typing.py +0 -0
  93. {phasorpy-0.11 → phasorpy-0.12}/src/phasorpy/_utils.py +0 -0
  94. {phasorpy-0.11 → phasorpy-0.12}/src/phasorpy/cluster.py +0 -0
  95. {phasorpy-0.11 → phasorpy-0.12}/src/phasorpy/color.py +0 -0
  96. {phasorpy-0.11 → phasorpy-0.12}/src/phasorpy/conftest.py +0 -0
  97. {phasorpy-0.11 → phasorpy-0.12}/src/phasorpy/cursor.py +0 -0
  98. {phasorpy-0.11 → phasorpy-0.12}/src/phasorpy/datasets.py +0 -0
  99. {phasorpy-0.11 → phasorpy-0.12}/src/phasorpy/filter.py +0 -0
  100. {phasorpy-0.11 → phasorpy-0.12}/src/phasorpy/io/_flimlabs.py +0 -0
  101. {phasorpy-0.11 → phasorpy-0.12}/src/phasorpy/io/_leica.py +0 -0
  102. {phasorpy-0.11 → phasorpy-0.12}/src/phasorpy/io/_ometiff.py +0 -0
  103. {phasorpy-0.11 → phasorpy-0.12}/src/phasorpy/io/_simfcs.py +0 -0
  104. {phasorpy-0.11 → phasorpy-0.12}/src/phasorpy/plot/_functions.py +0 -0
  105. {phasorpy-0.11 → phasorpy-0.12}/src/phasorpy/plot/_lifetime_plots.py +0 -0
  106. {phasorpy-0.11 → phasorpy-0.12}/src/phasorpy/plot/_phasorplot_fret.py +0 -0
  107. {phasorpy-0.11 → phasorpy-0.12}/src/phasorpy/py.typed +0 -0
  108. {phasorpy-0.11 → phasorpy-0.12}/src/phasorpy/utils.py +0 -0
  109. {phasorpy-0.11 → phasorpy-0.12}/src/phasorpy.egg-info/dependency_links.txt +0 -0
  110. {phasorpy-0.11 → phasorpy-0.12}/src/phasorpy.egg-info/entry_points.txt +0 -0
  111. {phasorpy-0.11 → phasorpy-0.12}/src/phasorpy.egg-info/not-zip-safe +0 -0
  112. {phasorpy-0.11 → phasorpy-0.12}/src/phasorpy.egg-info/top_level.txt +0 -0
  113. {phasorpy-0.11 → phasorpy-0.12}/tests/io/_conftest.py +0 -0
  114. {phasorpy-0.11 → phasorpy-0.12}/tools/sha256.py +0 -0
  115. {phasorpy-0.11 → phasorpy-0.12}/tutorials/README.rst +0 -0
  116. {phasorpy-0.11 → phasorpy-0.12}/tutorials/api/README.rst +0 -0
  117. {phasorpy-0.11 → phasorpy-0.12}/tutorials/api/phasorpy_component.py +0 -0
  118. {phasorpy-0.11 → phasorpy-0.12}/tutorials/api/phasorpy_cursor.py +0 -0
  119. {phasorpy-0.11 → phasorpy-0.12}/tutorials/api/phasorpy_filter.py +0 -0
  120. {phasorpy-0.11 → phasorpy-0.12}/tutorials/api/phasorpy_fret.py +0 -0
  121. {phasorpy-0.11 → phasorpy-0.12}/tutorials/api/phasorpy_lifetime_to_signal.py +0 -0
  122. {phasorpy-0.11 → phasorpy-0.12}/tutorials/api/phasorpy_multi_harmonic.py +0 -0
  123. {phasorpy-0.11 → phasorpy-0.12}/tutorials/api/phasorpy_pca.py +0 -0
  124. {phasorpy-0.11 → phasorpy-0.12}/tutorials/api/phasorpy_phasor_from_lifetime.py +0 -0
  125. {phasorpy-0.11 → phasorpy-0.12}/tutorials/api/phasorpy_phasorplot.py +0 -0
  126. {phasorpy-0.11 → phasorpy-0.12}/tutorials/applications/README.rst +0 -0
  127. {phasorpy-0.11 → phasorpy-0.12}/tutorials/applications/phasorpy_component_fit.py +0 -0
  128. {phasorpy-0.11 → phasorpy-0.12}/tutorials/applications/phasorpy_fret_efficiency.py +0 -0
  129. {phasorpy-0.11 → phasorpy-0.12}/tutorials/applications/phasorpy_multidimensional.py +0 -0
  130. {phasorpy-0.11 → phasorpy-0.12}/tutorials/applications/phasorpy_nadh_concentration.py +0 -0
  131. {phasorpy-0.11 → phasorpy-0.12}/tutorials/applications/phasorpy_nadh_fraction.py +0 -0
  132. {phasorpy-0.11 → phasorpy-0.12}/tutorials/misc/README.rst +0 -0
  133. {phasorpy-0.11 → phasorpy-0.12}/tutorials/misc/phasorpy_logo.py +0 -0
  134. {phasorpy-0.11 → phasorpy-0.12}/tutorials/misc/phasorpy_phasor_from_signal.py +0 -0
  135. {phasorpy-0.11 → phasorpy-0.12}/tutorials/phasorpy_introduction.py +0 -0
  136. {phasorpy-0.11 → phasorpy-0.12}/tutorials/phasorpy_lfd_workshop.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: phasorpy
3
- Version: 0.11
3
+ Version: 0.12
4
4
  Summary: Analysis of luminescence lifetime and hyperspectral images using the phasor approach
5
5
  Author: PhasorPy Contributors
6
6
  License-Expression: MIT
@@ -37,11 +37,12 @@ Requires-Dist: tqdm
37
37
  Provides-Extra: all
38
38
  Requires-Dist: czifile>=2026.4.11; extra == "all"
39
39
  Requires-Dist: fbdfile>=2025.9.18; extra == "all"
40
- Requires-Dist: lfdfiles>=2026.4.30; extra == "all"
40
+ Requires-Dist: lfdfiles>=2026.6.24; extra == "all"
41
41
  Requires-Dist: liffile>=2025.2.10; extra == "all"
42
42
  Requires-Dist: ptufile>=2024.9.14; extra == "all"
43
43
  Requires-Dist: sdtfile>=2024.5.24; extra == "all"
44
44
  Requires-Dist: pawflim>=1.0.4; extra == "all"
45
+ Requires-Dist: brighteyes-mcs-reader>=0.1.0; extra == "all"
45
46
  Requires-Dist: imagecodecs==2026.6.6; (sys_platform == "darwin" and platform_machine == "x86_64") and extra == "all"
46
47
  Dynamic: license-file
47
48
 
@@ -1,15 +1,20 @@
1
1
  [
2
2
  {
3
3
  "name": "dev",
4
- "version": "0.12",
4
+ "version": "0.13",
5
5
  "url": "https://www.phasorpy.org/docs/dev/"
6
6
  },
7
7
  {
8
- "name": "0.11 (stable)",
9
- "version": "0.11",
8
+ "name": "0.12 (stable)",
9
+ "version": "0.12",
10
10
  "url": "https://www.phasorpy.org/docs/stable/",
11
11
  "preferred": true
12
12
  },
13
+ {
14
+ "name": "0.11",
15
+ "version": "0.11",
16
+ "url": "https://www.phasorpy.org/docs/v0.11/"
17
+ },
13
18
  {
14
19
  "name": "0.10",
15
20
  "version": "0.10",
@@ -0,0 +1,83 @@
1
+ About PhasorPy
2
+ ==============
3
+
4
+ The PhasorPy project's mission is to advance and democratize phasor analysis
5
+ through the open-source PhasorPy library.
6
+
7
+ .. _funding:
8
+
9
+ Founding and funding
10
+ --------------------
11
+
12
+ The PhasorPy project was established in 2023 by the
13
+ `Advanced Bioimaging Unit <https://www.uba.uy>`_
14
+ at the University of the Republic and Institut Pasteur de Montevideo, and the
15
+ `Laboratory for Fluorescence Dynamics <https://www.lfd.uci.edu>`_
16
+ at the University of California, Irvine.
17
+
18
+ The project was supported by the
19
+ `Essential Open Source Software for Science (EOSS)
20
+ <https://chanzuckerberg.com/eoss/>`_ program at
21
+ `Chan Zuckerberg Initiative <https://chanzuckerberg.com/>`_
22
+ (grant number 2022-252604; 2022-2024).
23
+
24
+ Project goals
25
+ -------------
26
+
27
+ The original goals were to create an open-source Python library based on
28
+ sound software engineering principles, establish a community of users
29
+ and developers, and reimplement core phasor-based functionality of the
30
+ discontinued closed-source
31
+ `Globals for Images - SimFCS <https://www.lfd.uci.edu/globals/>`_
32
+ software by Enrico Gratton.
33
+
34
+ The current goals are to improve community sustainability and to advance the
35
+ PhasorPy library toward a stable, extensible platform that supports
36
+ large-scale automated workflows, AI-assisted analysis, and robust integration
37
+ with the scientific Python ecosystem, while continuing to serve as a reliable
38
+ base for graphical applications and training resources.
39
+
40
+ .. _governance:
41
+
42
+ .. _contributors:
43
+
44
+ Governance and contributors
45
+ ---------------------------
46
+
47
+ PhasorPy is developed collaboratively by
48
+ `contributors to the PhasorPy repository
49
+ <https://github.com/phasorpy/phasorpy/graphs/contributors?all=1>`_
50
+ and governed by
51
+ `members of the PhasorPy organization
52
+ <https://github.com/orgs/phasorpy/people>`_.
53
+
54
+ Technical direction is developed through open discussion and
55
+ consensus-building in issues, pull requests, and community meetings.
56
+ Members of the PhasorPy organization make final technical and release
57
+ decisions.
58
+
59
+ Infrastructure hosting, funded activities, and institutional partnerships are
60
+ coordinated through the `Advanced Bioimaging Unit <https://www.uba.uy>`__.
61
+
62
+ See the :doc:`Contributing guide <contributing>` to get involved.
63
+
64
+ Acknowledgments
65
+ ---------------
66
+
67
+ We thank the PhasorPy user community for feedback, bug reports, feature
68
+ suggestions, and data files that have helped improve the library.
69
+
70
+ We acknowledge the open-source scientific Python ecosystem that PhasorPy
71
+ builds on, including Python, NumPy, SciPy, Matplotlib, and scikit-learn,
72
+ the microscopy file-format stack (including tifffile, ptufile, and related
73
+ tools), and the documentation toolchain based on Sphinx and Sphinx-Gallery.
74
+
75
+ We also acknowledge GitHub for providing a platform for collaborative
76
+ development and hosting documentation, PyPI and conda-forge for package
77
+ distribution, and Zenodo for hosting sample data.
78
+
79
+ Citation
80
+ --------
81
+
82
+ If PhasorPy contributes to research that leads to a publication, please cite
83
+ `doi: 10.5281/zenodo.13862586 <https://doi.org/10.5281/zenodo.13862586>`_.
@@ -0,0 +1,3 @@
1
+ :orphan:
2
+
3
+ See :doc:`about`
@@ -47,7 +47,6 @@ extensions = [
47
47
  # don't enable intersphinx since tutorials are getting littered with links
48
48
  # 'sphinx.ext.intersphinx',
49
49
  # 'numpydoc',
50
- 'sphinx_inline_tabs',
51
50
  'sphinx_copybutton',
52
51
  'sphinx_click',
53
52
  'sphinx_issues',
@@ -148,9 +147,6 @@ sphinx_gallery_conf = {
148
147
  # 'backreferences_dir': None,
149
148
  }
150
149
 
151
- # TODO: remove MathJax 3 link once scroll bar issue is resolved
152
- mathjax_path = 'https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js'
153
-
154
150
 
155
151
  class TutorialOrder:
156
152
  """Order tutorials in gallery subsections."""
@@ -61,9 +61,9 @@ and include the following items in the bug report:
61
61
  output of::
62
62
 
63
63
  $ python -m phasorpy versions
64
- Python-3.14.1
65
- phasorpy-0.8
66
- numpy-2.3.5
64
+ Python-3.14.6
65
+ phasorpy-0.11
66
+ numpy-2.5.1
67
67
  ...
68
68
 
69
69
  Contribute code or documentation
@@ -88,6 +88,30 @@ The PhasorPy project loosely follows the
88
88
  `pyOpenSci <https://www.pyopensci.org/python-package-guide/>`_
89
89
  development guidelines.
90
90
 
91
+ The PhasorPy project follows and adapts the `SciPy AI policy
92
+ <https://scipy.github.io/devdocs/dev/conduct/ai_policy.html>`_.
93
+
94
+ Copyright
95
+ .........
96
+
97
+ All code in PhasorPy is released under the :doc:`license`.
98
+ Contributors license their code under the same license when submitting it
99
+ to PhasorPy's version control repository.
100
+ That means contributors must have the right to submit the code to PhasorPy
101
+ and must comply with the terms of any MIT-compatible open-source license
102
+ associated with submitted code.
103
+
104
+ Accountability
105
+ ..............
106
+
107
+ Contributors are responsible for all code submitted to the PhasorPy repository.
108
+ They must understand it and be able to explain it, regardless of whether it
109
+ was manually written or AI-generated.
110
+
111
+ Contributors must disclose whether AI has been used to assist in the
112
+ development of code or documentation. If so, specify the tool(s) used,
113
+ how they were used, and which code or text is AI-generated.
114
+
91
115
  Fork the repository
92
116
  ...................
93
117
 
@@ -195,7 +219,7 @@ must conform to certain styles, which can be checked and applied with
195
219
 
196
220
  $ python -m pre_commit run --all-files
197
221
 
198
- User-facing classes and functions must use
222
+ Tests and user-facing classes and functions must use
199
223
  `type hints <https://peps.python.org/pep-0484/>`_
200
224
  and pass verification with the
201
225
  `MyPy <https://mypy.readthedocs.io>`_
@@ -19,14 +19,14 @@ classes of the library.
19
19
  The :doc:`release/index` list recent bug fixes, feature enhancements,
20
20
  documentation, and maintenance changes.
21
21
 
22
- The :doc:`contributing` explains how to get support, report bugs,
23
- submit fixes, and improve the documentation.
22
+ The :doc:`Contributing guide <contributing>` explains how to get support,
23
+ report bugs, submit fixes, and improve the documentation.
24
24
 
25
25
  The :doc:`code_of_conduct` applies to everyone participating in the
26
26
  PhasorPy community.
27
27
 
28
- The :doc:`acknowledgments` list individuals and organizations that
29
- contributed to the development of PhasorPy.
28
+ The :doc:`About <about>` page describes the project's history, goals,
29
+ funding, and governance.
30
30
 
31
31
  The PhasorPy library and documentation are released under the permissive
32
32
  :doc:`license`.
@@ -62,8 +62,8 @@ Contents
62
62
  api/index
63
63
  release/index
64
64
  contributing
65
- acknowledgments
66
65
  license
66
+ about
67
67
 
68
68
  Indices and tables
69
69
  ------------------
@@ -148,6 +148,7 @@ luminescence time-resolved or spectral images:
148
148
 
149
149
  * - **Library**
150
150
  - :ref:`PhasorPy <phasorpy>`,
151
+ :ref:`PyFLI <pyfli>`,
151
152
  :ref:`tttrlib <tttrlib>`,
152
153
  :ref:`FLIMLib <flimlib>`:sup:`2`,
153
154
  :ref:`BrightEyes <brighteyes>`:sup:`2`,
@@ -156,10 +157,12 @@ luminescence time-resolved or spectral images:
156
157
  * - **Desktop**
157
158
  - :ref:`SimFCS <simfcs>`:sup:`3,4`,
158
159
  :ref:`FLIM Playground <flim_playground>`:sup:`1`,
160
+ :ref:`FLIMKit <flimkit>`:sup:`1`,
159
161
  :ref:`FLUTE <flute>`,
160
162
  :ref:`FLIMPA <flimpa>`,
161
163
  :ref:`GSLab <gslab>`,
162
164
  :ref:`AlliGator <alligator>`,
165
+ :ref:`Spectral Phasors Analysis <spectral_phasors_analysis>`:sup:`2`,
163
166
  :ref:`HySP <hysp>`:sup:`3,4`,
164
167
  :ref:`FlimFast <flimfast>`:sup:`3,4`
165
168
  * - **Napari**
@@ -316,6 +319,22 @@ Details:
316
319
  The software is written in Python, is web deployable, and is distributed
317
320
  under the MIT license.
318
321
 
322
+ -
323
+ .. _flimkit:
324
+
325
+ `FLIMKit <https://github.com/alex1075/FLIMKit>`__
326
+ is an open-source Python toolkit focused on PTU-based FLIM data. It includes
327
+ a phasor analysis workflow and is distributed under the MIT license.
328
+
329
+ -
330
+ .. _pyfli:
331
+
332
+ `PyFLI <https://github.com/vkp217/pyfli-pkg>`__
333
+ is an open-source Python platform for Fluorescence Lifetime Imaging
334
+ (FLI) data simulation, processing and visualization, including phasor
335
+ analysis.
336
+ The library is distributed under the CC BY-NC-ND 4.0 license.
337
+
319
338
  -
320
339
  .. _flim_tools:
321
340
 
@@ -333,6 +352,14 @@ Details:
333
352
  and correcting phasor coordinates. The library is distributed under the
334
353
  BSD license.
335
354
 
355
+ -
356
+ .. _spectral_phasors_analysis:
357
+
358
+ `Spectral Phasors Analysis <https://github.com/DBP008/spectral-phasors-analysis>`__
359
+ is a Python application to explore spectral phasors through simulations and
360
+ analyze experimental data. The software is distributed under the AGPL-3.0
361
+ license.
362
+
336
363
  -
337
364
  .. _phasor_analysis:
338
365
 
@@ -60,11 +60,12 @@ all = [
60
60
  # sync with [dependency-groups.optional]
61
61
  "czifile>=2026.4.11",
62
62
  "fbdfile>=2025.9.18",
63
- "lfdfiles>=2026.4.30",
63
+ "lfdfiles>=2026.6.24",
64
64
  "liffile>=2025.2.10",
65
65
  "ptufile>=2024.9.14",
66
66
  "sdtfile>=2024.5.24",
67
67
  "pawflim>=1.0.4",
68
+ "brighteyes-mcs-reader>=0.1.0",
68
69
  "imagecodecs==2026.6.6; sys_platform == 'darwin' and platform_machine == 'x86_64'",
69
70
  # "mkl-fft>=2.1.1",
70
71
  ]
@@ -87,11 +88,12 @@ optional = [
87
88
  # optional runtime dependencies; sync with [project.optional-dependencies]
88
89
  "czifile>=2026.4.11",
89
90
  "fbdfile>=2025.9.18",
90
- "lfdfiles>=2026.4.30",
91
+ "lfdfiles>=2026.6.24",
91
92
  "liffile>=2025.2.10",
92
93
  "ptufile>=2024.9.14",
93
94
  "sdtfile>=2024.5.24",
94
95
  "pawflim>=1.0.4",
96
+ "brighteyes-mcs-reader>=0.1.0",
95
97
  "imagecodecs==2026.6.6; sys_platform == 'darwin' and platform_machine == 'x86_64'",
96
98
  ]
97
99
  minimal = [
@@ -106,11 +108,12 @@ minimal = [
106
108
  # optional
107
109
  "czifile==2026.4.11; python_version == \"3.12\"",
108
110
  "fbdfile==2025.9.18; python_version == \"3.12\"",
109
- "lfdfiles==2026.4.30; python_version == \"3.12\"",
111
+ "lfdfiles==2026.6.24; python_version == \"3.12\"",
110
112
  "liffile==2025.2.10; python_version == \"3.12\"",
111
113
  "ptufile==2024.9.14; python_version == \"3.12\"",
112
114
  "sdtfile==2024.5.24; python_version == \"3.12\"",
113
115
  "pawflim==1.0.4; python_version == \"3.12\"",
116
+ "brighteyes-mcs-reader==0.1.0; python_version == \"3.12\""
114
117
  ]
115
118
  build = [
116
119
  # build requirements
@@ -140,7 +143,6 @@ docs = [
140
143
  "sphinx",
141
144
  "sphinx-copybutton",
142
145
  "sphinx-design",
143
- "sphinx-inline-tabs",
144
146
  "sphinx-issues",
145
147
  "sphinx_click",
146
148
  "sphinx_gallery",
@@ -260,7 +262,7 @@ typing-modules = ["_typing"]
260
262
  "__init__.py" = ["F401", "F403"]
261
263
  "conftest.py" = ["ARG001"]
262
264
  "setup.py" = ["T201"]
263
- "tests/*" = ["ANN", "D", "INP001", "PT011", "SLF001", "S101"]
265
+ "tests/*" = ["INP001", "PT011", "SLF001", "TC001"]
264
266
  "tools/*" = ["ANN", "INP001", "T201"]
265
267
  "tutorials/*" = ["ANN", "D", "E402", "INP001", "T201", "S101", "ERA001"]
266
268
  "docs/conf.py" = ["A001", "ANN204", "D102", "E402", "INP001", "RUF012"]
@@ -332,7 +334,7 @@ ND2 = "ND2"
332
334
  OIF = "OIF"
333
335
 
334
336
  [tool.mypy]
335
- packages = ["phasorpy"]
337
+ packages = ["phasorpy", "tests"]
336
338
  mypy_path = "$MYPY_CONFIG_FILE_DIR/src"
337
339
  plugins = []
338
340
  enable_error_code = ["ignore-without-code", "redundant-expr", "truthy-bool"]
@@ -341,11 +343,13 @@ warn_unreachable = true
341
343
 
342
344
  [[tool.mypy.overrides]]
343
345
  module = [
346
+ "_conftest",
344
347
  "phasorpy._phasorpy",
345
348
  "pooch.*",
346
349
  "scipy.*",
347
350
  "mkl_fft.*",
348
351
  "sklearn.*",
352
+ "h5py.*",
349
353
  ]
350
354
  ignore_missing_imports = true
351
355
 
@@ -403,21 +407,22 @@ norecursedirs = [
403
407
 
404
408
  [tool.cibuildwheel]
405
409
  skip = [
406
- "cp38*",
407
410
  "cp39*",
408
411
  "cp310*",
409
412
  "cp311*",
410
413
  "cp313*",
411
414
  "cp314-*",
415
+ "cp315*",
412
416
  "*musllinux*",
413
417
  "*i686*",
414
418
  "*ppc64le*",
415
419
  "*s390x*",
416
420
  ]
417
421
  test-command = "pytest {project}/tests"
418
- test-skip = ["cp314t*"]
422
+ test-skip = ["cp314t*", "cp315t*"]
419
423
  test-requires = [
420
424
  # sync with [dependency-groups.optional]
425
+ "brighteyes-mcs-reader",
421
426
  "czifile>=2026.4.11",
422
427
  "fbdfile",
423
428
  "lfdfiles",
@@ -5,5 +5,5 @@ from __future__ import annotations
5
5
  __all__ = ['__version__']
6
6
 
7
7
 
8
- __version__ = '0.11'
8
+ __version__ = '0.12'
9
9
  """PhasorPy version string."""
@@ -413,6 +413,7 @@ def _gaussian_signal(
413
413
  float_t[::1] signal,
414
414
  const double mean,
415
415
  const double stdev,
416
+ ssize_t folds = -1
416
417
  ):
417
418
  """Return normal distribution, wrapped around at borders.
418
419
 
@@ -420,21 +421,28 @@ def _gaussian_signal(
420
421
  ----------
421
422
  signal : memoryview of `float32` or `float64`
422
423
  Writable buffer where calculated signal samples are stored.
424
+ Must be initialized.
423
425
  mean : float
424
426
  Mean of normal distribution.
425
427
  stdev : float
426
428
  Standard deviation of normal distribution.
429
+ folds : int, optional, default=-1
430
+ Number of times to wrap around distribution at borders.
431
+ If negative (default), it is automatically calculated based on
432
+ `stdev` and the size of `signal` (number of samples).
427
433
 
428
434
  """
429
435
  cdef:
430
436
  ssize_t samples = signal.shape[0]
431
- ssize_t folds = 1 # TODO: calculate from stddev and samples
432
437
  ssize_t i
433
438
  double t, c
434
439
 
435
440
  if samples < 1:
436
441
  return
437
442
 
443
+ if folds < 0:
444
+ folds = <ssize_t> (-floor(-6.0 * stdev / <double> samples))
445
+
438
446
  with nogil:
439
447
  if isnan(mean) or isnan(stdev) or stdev <= 0.0:
440
448
  for i in range(samples):
@@ -2748,7 +2756,7 @@ def _median_filter_2d(
2748
2756
  ssize_t rows = image.shape[0]
2749
2757
  ssize_t cols = image.shape[1]
2750
2758
  ssize_t k = kernel_size // 2
2751
- ssize_t i, j, r, di, dj, ki, kj, valid_count
2759
+ ssize_t i, j, _r, di, dj, ki, kj, valid_count
2752
2760
  float_t element
2753
2761
  float_t* kernel
2754
2762
 
@@ -2766,7 +2774,7 @@ def _median_filter_2d(
2766
2774
  with gil:
2767
2775
  raise MemoryError('failed to allocate kernel')
2768
2776
 
2769
- for r in range(repeat):
2777
+ for _r in range(repeat):
2770
2778
  for i in prange(rows):
2771
2779
  for j in range(cols):
2772
2780
  if isnan(image[i, j]):
@@ -2846,7 +2854,7 @@ def _flimlabs_mean(
2846
2854
  cdef:
2847
2855
  float_t[::] mean_
2848
2856
  list channels, pixels
2849
- ssize_t c, i, h, count
2857
+ ssize_t c, i, _h, count
2850
2858
  double sum
2851
2859
 
2852
2860
  if channel < 0:
@@ -2856,7 +2864,7 @@ def _flimlabs_mean(
2856
2864
  i = 0
2857
2865
  for pixels in channels:
2858
2866
  sum = 0.0
2859
- for h, count in pixels:
2867
+ for _h, count in pixels:
2860
2868
  sum += <double> count
2861
2869
  mean_[i] = <float_t> (sum / 256.0)
2862
2870
  i += 1
@@ -2866,7 +2874,77 @@ def _flimlabs_mean(
2866
2874
  mean_ = mean[0]
2867
2875
  for pixels in data[channel]:
2868
2876
  sum = 0.0
2869
- for h, count in pixels:
2877
+ for _h, count in pixels:
2870
2878
  sum += <double> count
2871
2879
  mean_[i] = <float_t> (sum / 256.0)
2872
2880
  i += 1
2881
+
2882
+
2883
+ ###############################################################################
2884
+ # Synthesize spectra
2885
+
2886
+
2887
+ @cython.ufunc
2888
+ cdef float_t _signal_from_dho(
2889
+ float_t wavelength,
2890
+ float_t origin,
2891
+ float_t sigma,
2892
+ float_t hr_factor,
2893
+ float_t vib_frequency,
2894
+ float_t scale,
2895
+ float_t offset,
2896
+ int absorption,
2897
+ ) noexcept nogil:
2898
+ """Return DHO model intensity for emission or absorption.
2899
+
2900
+ Using the area-normalized Displaced Harmonic Oscillator (DHO) model.
2901
+
2902
+ """
2903
+ cdef:
2904
+ double wavenumber, gaussian, nu_0, nu_n, poisson_factor
2905
+ double intensity_nu = 0.0
2906
+ double weight = 1.0 # w[0] = hr_factor^0 / 0! = 1
2907
+ double sign = 1.0 if absorption else -1.0
2908
+ ssize_t max_n = 6 # N=5 in math notation; error <1% for S<=1.65
2909
+ ssize_t n
2910
+
2911
+ if (
2912
+ wavelength <= 0.0
2913
+ or origin <= 0.0
2914
+ or sigma <= 0.0
2915
+ or hr_factor < 0.0
2916
+ or vib_frequency < 0.0
2917
+ ):
2918
+ return <float_t> NAN
2919
+
2920
+ # convert to wavenumber domain (cm^-1)
2921
+ wavenumber = 1e7 / wavelength
2922
+ nu_0 = 1e7 / origin # 0->0 electronic origin
2923
+
2924
+ # Poisson PMF normalization factor exp(-S); sum of weights over all n is ~1
2925
+ poisson_factor = exp(-hr_factor)
2926
+
2927
+ for n in range(max_n):
2928
+ if n > 0:
2929
+ weight = weight * hr_factor / n # Iterative w[n] = hr_factor^n/n!
2930
+
2931
+ # In emission, vibronic sidebands go to lower energy
2932
+ # (red-shifted) -> subtract frequency
2933
+ # In absorption, sidebands go to HIGHER energy
2934
+ # (blue-shifted) -> add frequency
2935
+ nu_n = nu_0 + sign * n * vib_frequency
2936
+
2937
+ gaussian = exp(
2938
+ -((wavenumber - nu_n) * (wavenumber - nu_n))
2939
+ / (2.0 * sigma * sigma)
2940
+ )
2941
+
2942
+ intensity_nu += weight * poisson_factor * gaussian
2943
+
2944
+ # normalize the Gaussian peak heights in wavenumber space,
2945
+ # transform coordinates to wavelength space via the Jacobian: 1e7/lambda^2,
2946
+ # and apply affine transformation
2947
+ return <float_t> (
2948
+ (intensity_nu / (sigma * sqrt(2.0 * M_PI)))
2949
+ * (1e7 / (wavelength * wavelength))
2950
+ ) * scale + offset
@@ -156,6 +156,129 @@ def lifetime(
156
156
  plot.show()
157
157
 
158
158
 
159
+ @main.command(help='Start interactive spectral plots.')
160
+ @click.argument(
161
+ 'number_spectra',
162
+ default=1,
163
+ type=click.IntRange(1, 4),
164
+ required=False,
165
+ )
166
+ @click.option(
167
+ '-o',
168
+ '--origin',
169
+ type=float,
170
+ multiple=True,
171
+ required=False,
172
+ help='Origin wavelength.',
173
+ )
174
+ @click.option(
175
+ '-s',
176
+ '--sigma',
177
+ type=float,
178
+ multiple=True,
179
+ required=False,
180
+ help='Spectral broadening factor.',
181
+ )
182
+ @click.option(
183
+ '-a',
184
+ '--fraction',
185
+ type=float,
186
+ multiple=True,
187
+ required=False,
188
+ help='Fractional intensity of component.',
189
+ )
190
+ @click.option(
191
+ '--hr-factor',
192
+ type=float,
193
+ default=0.4,
194
+ show_default=True,
195
+ help='Huang-Rhys factor.',
196
+ )
197
+ @click.option(
198
+ '--vib-frequency',
199
+ type=float,
200
+ default=1200.0,
201
+ show_default=True,
202
+ help='Vibrational frequency.',
203
+ )
204
+ @click.option(
205
+ '--wavelength-min',
206
+ type=float,
207
+ default=450.0,
208
+ show_default=True,
209
+ help='Minimum wavelength to display.',
210
+ )
211
+ @click.option(
212
+ '--wavelength-max',
213
+ type=float,
214
+ default=650.0,
215
+ show_default=True,
216
+ help='Maximum wavelength to display.',
217
+ )
218
+ @click.option(
219
+ '--samples',
220
+ type=int,
221
+ default=128,
222
+ show_default=True,
223
+ help='Number of wavelength samples.',
224
+ )
225
+ @click.option(
226
+ '--dho',
227
+ default=False,
228
+ is_flag=True,
229
+ type=click.BOOL,
230
+ help='Use Displaced Harmonic Oscillator model.',
231
+ )
232
+ @click.option(
233
+ '--hide',
234
+ default=False,
235
+ is_flag=True,
236
+ type=click.BOOL,
237
+ help='Do not show interactive plot.',
238
+ )
239
+ def spectral(
240
+ *,
241
+ number_spectra: int,
242
+ origin: tuple[float, ...],
243
+ sigma: tuple[float, ...],
244
+ fraction: tuple[float, ...],
245
+ hr_factor: float,
246
+ vib_frequency: float,
247
+ wavelength_min: float,
248
+ wavelength_max: float,
249
+ samples: int,
250
+ dho: bool,
251
+ hide: bool,
252
+ ) -> None:
253
+ """Start interactive spectral plots."""
254
+ from .plot import SpectralPlots
255
+
256
+ _default_origins = {
257
+ 1: (518.0,),
258
+ 2: (490.0, 570.0),
259
+ 3: (480.0, 530.0, 590.0),
260
+ 4: (470.0, 510.0, 560.0, 620.0),
261
+ }
262
+
263
+ if not origin:
264
+ n = min(number_spectra, 2 if dho else 4)
265
+ origin = _default_origins[n]
266
+
267
+ plot = SpectralPlots(
268
+ origin,
269
+ sigma or None,
270
+ fraction if len(fraction) > 0 else None,
271
+ hr_factor=hr_factor,
272
+ vib_frequency=vib_frequency,
273
+ wavelength_range=(wavelength_min, wavelength_max),
274
+ samples=samples,
275
+ dho=dho,
276
+ interactive=True,
277
+ )
278
+ if not hide:
279
+ plot.show()
280
+
281
+
159
282
  if __name__ == '__main__':
160
283
  import sys
161
284