phasorpy 0.12__tar.gz → 0.13__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 (137) hide show
  1. {phasorpy-0.12/src/phasorpy.egg-info → phasorpy-0.13}/PKG-INFO +6 -6
  2. {phasorpy-0.12 → phasorpy-0.13}/docs/_static/switcher.json +8 -8
  3. {phasorpy-0.12 → phasorpy-0.13}/docs/conf.py +32 -2
  4. {phasorpy-0.12 → phasorpy-0.13}/docs/phasor_approach.rst +2 -2
  5. {phasorpy-0.12 → phasorpy-0.13}/pyproject.toml +43 -42
  6. {phasorpy-0.12 → phasorpy-0.13}/setup.py +40 -13
  7. phasorpy-0.13/src/phasorpy/__init__.py +13 -0
  8. {phasorpy-0.12 → phasorpy-0.13}/src/phasorpy/__main__.py +4 -0
  9. {phasorpy-0.12 → phasorpy-0.13}/src/phasorpy/_phasorpy.pyx +42 -17
  10. {phasorpy-0.12 → phasorpy-0.13}/src/phasorpy/_typing.py +4 -0
  11. {phasorpy-0.12 → phasorpy-0.13}/src/phasorpy/_utils.py +31 -13
  12. {phasorpy-0.12 → phasorpy-0.13}/src/phasorpy/cli.py +53 -25
  13. phasorpy-0.13/src/phasorpy/cluster.py +430 -0
  14. {phasorpy-0.12 → phasorpy-0.13}/src/phasorpy/color.py +33 -8
  15. {phasorpy-0.12 → phasorpy-0.13}/src/phasorpy/component.py +34 -16
  16. {phasorpy-0.12 → phasorpy-0.13}/src/phasorpy/conftest.py +4 -0
  17. {phasorpy-0.12 → phasorpy-0.13}/src/phasorpy/cursor.py +7 -1
  18. {phasorpy-0.12 → phasorpy-0.13}/src/phasorpy/datasets.py +47 -41
  19. {phasorpy-0.12 → phasorpy-0.13}/src/phasorpy/experimental.py +8 -2
  20. {phasorpy-0.12 → phasorpy-0.13}/src/phasorpy/filter.py +44 -23
  21. {phasorpy-0.12 → phasorpy-0.13}/src/phasorpy/io/__init__.py +4 -0
  22. {phasorpy-0.12 → phasorpy-0.13}/src/phasorpy/io/_flimlabs.py +22 -10
  23. {phasorpy-0.12 → phasorpy-0.13}/src/phasorpy/io/_leica.py +17 -6
  24. {phasorpy-0.12 → phasorpy-0.13}/src/phasorpy/io/_ometiff.py +12 -17
  25. {phasorpy-0.12 → phasorpy-0.13}/src/phasorpy/io/_other.py +44 -18
  26. {phasorpy-0.12 → phasorpy-0.13}/src/phasorpy/io/_simfcs.py +36 -10
  27. {phasorpy-0.12 → phasorpy-0.13}/src/phasorpy/lifetime.py +41 -26
  28. {phasorpy-0.12 → phasorpy-0.13}/src/phasorpy/phasor.py +32 -14
  29. {phasorpy-0.12 → phasorpy-0.13}/src/phasorpy/plot/__init__.py +4 -0
  30. {phasorpy-0.12 → phasorpy-0.13}/src/phasorpy/plot/_functions.py +30 -18
  31. {phasorpy-0.12 → phasorpy-0.13}/src/phasorpy/plot/_lifetime_plots.py +91 -2
  32. {phasorpy-0.12 → phasorpy-0.13}/src/phasorpy/plot/_phasorplot.py +86 -48
  33. {phasorpy-0.12 → phasorpy-0.13}/src/phasorpy/plot/_phasorplot_fret.py +8 -4
  34. {phasorpy-0.12 → phasorpy-0.13}/src/phasorpy/plot/_spectral_plots.py +18 -7
  35. {phasorpy-0.12 → phasorpy-0.13}/src/phasorpy/utils.py +16 -10
  36. {phasorpy-0.12 → phasorpy-0.13/src/phasorpy.egg-info}/PKG-INFO +6 -6
  37. {phasorpy-0.12 → phasorpy-0.13}/src/phasorpy.egg-info/SOURCES.txt +1 -0
  38. {phasorpy-0.12 → phasorpy-0.13}/src/phasorpy.egg-info/requires.txt +5 -6
  39. {phasorpy-0.12 → phasorpy-0.13}/tests/conftest.py +4 -0
  40. {phasorpy-0.12 → phasorpy-0.13}/tests/io/_conftest.py +4 -0
  41. {phasorpy-0.12 → phasorpy-0.13}/tests/io/test_flimlabs.py +4 -0
  42. {phasorpy-0.12 → phasorpy-0.13}/tests/io/test_leica.py +4 -0
  43. {phasorpy-0.12 → phasorpy-0.13}/tests/io/test_ometiff.py +5 -1
  44. {phasorpy-0.12 → phasorpy-0.13}/tests/io/test_other.py +18 -2
  45. {phasorpy-0.12 → phasorpy-0.13}/tests/io/test_simfcs.py +14 -0
  46. {phasorpy-0.12 → phasorpy-0.13}/tests/plot/test_functions.py +43 -0
  47. phasorpy-0.13/tests/plot/test_lifetime_plots.py +196 -0
  48. {phasorpy-0.12 → phasorpy-0.13}/tests/plot/test_phasorplot.py +102 -0
  49. {phasorpy-0.12 → phasorpy-0.13}/tests/plot/test_phasorplot_fret.py +23 -0
  50. {phasorpy-0.12 → phasorpy-0.13}/tests/plot/test_spectral_plots.py +33 -0
  51. {phasorpy-0.12 → phasorpy-0.13}/tests/test__phasorpy.py +52 -1
  52. {phasorpy-0.12 → phasorpy-0.13}/tests/test__typing.py +4 -0
  53. {phasorpy-0.12 → phasorpy-0.13}/tests/test__utils.py +17 -0
  54. {phasorpy-0.12 → phasorpy-0.13}/tests/test_cli.py +25 -0
  55. phasorpy-0.13/tests/test_cluster.py +372 -0
  56. {phasorpy-0.12 → phasorpy-0.13}/tests/test_color.py +19 -0
  57. {phasorpy-0.12 → phasorpy-0.13}/tests/test_component.py +56 -0
  58. {phasorpy-0.12 → phasorpy-0.13}/tests/test_cursor.py +4 -0
  59. {phasorpy-0.12 → phasorpy-0.13}/tests/test_datasets.py +24 -1
  60. {phasorpy-0.12 → phasorpy-0.13}/tests/test_experimental.py +4 -0
  61. {phasorpy-0.12 → phasorpy-0.13}/tests/test_filter.py +91 -0
  62. {phasorpy-0.12 → phasorpy-0.13}/tests/test_lifetime.py +79 -2
  63. {phasorpy-0.12 → phasorpy-0.13}/tests/test_nan.py +16 -1
  64. {phasorpy-0.12 → phasorpy-0.13}/tests/test_phasor.py +76 -5
  65. {phasorpy-0.12 → phasorpy-0.13}/tests/test_phasorpy.py +4 -0
  66. {phasorpy-0.12 → phasorpy-0.13}/tests/test_utils.py +4 -0
  67. {phasorpy-0.12 → phasorpy-0.13}/tools/sha256.py +4 -0
  68. phasorpy-0.13/tutorials/api/phasorpy_cluster.py +212 -0
  69. {phasorpy-0.12 → phasorpy-0.13}/tutorials/api/phasorpy_component.py +4 -0
  70. {phasorpy-0.12 → phasorpy-0.13}/tutorials/api/phasorpy_cursor.py +4 -0
  71. {phasorpy-0.12 → phasorpy-0.13}/tutorials/api/phasorpy_filter.py +4 -0
  72. {phasorpy-0.12 → phasorpy-0.13}/tutorials/api/phasorpy_fret.py +4 -0
  73. {phasorpy-0.12 → phasorpy-0.13}/tutorials/api/phasorpy_io.py +8 -4
  74. {phasorpy-0.12 → phasorpy-0.13}/tutorials/api/phasorpy_lifetime_to_signal.py +6 -2
  75. {phasorpy-0.12 → phasorpy-0.13}/tutorials/api/phasorpy_multi_harmonic.py +5 -0
  76. {phasorpy-0.12 → phasorpy-0.13}/tutorials/api/phasorpy_pca.py +4 -0
  77. {phasorpy-0.12 → phasorpy-0.13}/tutorials/api/phasorpy_phasor_from_lifetime.py +4 -0
  78. {phasorpy-0.12 → phasorpy-0.13}/tutorials/api/phasorpy_phasorplot.py +7 -3
  79. {phasorpy-0.12 → phasorpy-0.13}/tutorials/applications/phasorpy_component_fit.py +4 -0
  80. {phasorpy-0.12 → phasorpy-0.13}/tutorials/applications/phasorpy_fret_efficiency.py +27 -9
  81. {phasorpy-0.12 → phasorpy-0.13}/tutorials/applications/phasorpy_multidimensional.py +4 -0
  82. {phasorpy-0.12 → phasorpy-0.13}/tutorials/applications/phasorpy_nadh_concentration.py +4 -0
  83. {phasorpy-0.12 → phasorpy-0.13}/tutorials/applications/phasorpy_nadh_fraction.py +21 -12
  84. {phasorpy-0.12 → phasorpy-0.13}/tutorials/misc/phasorpy_apps.py +4 -0
  85. {phasorpy-0.12 → phasorpy-0.13}/tutorials/misc/phasorpy_logo.py +4 -0
  86. {phasorpy-0.12 → phasorpy-0.13}/tutorials/misc/phasorpy_phasor_from_signal.py +41 -34
  87. {phasorpy-0.12 → phasorpy-0.13}/tutorials/phasorpy_introduction.py +18 -6
  88. {phasorpy-0.12 → phasorpy-0.13}/tutorials/phasorpy_lfd_workshop.py +4 -0
  89. {phasorpy-0.12 → phasorpy-0.13}/tutorials/phasorpy_lifetime_geometry.py +19 -42
  90. phasorpy-0.12/src/phasorpy/__init__.py +0 -9
  91. phasorpy-0.12/src/phasorpy/cluster.py +0 -227
  92. phasorpy-0.12/tests/plot/test_lifetime_plots.py +0 -101
  93. phasorpy-0.12/tests/test_cluster.py +0 -117
  94. {phasorpy-0.12 → phasorpy-0.13}/LICENSE.txt +0 -0
  95. {phasorpy-0.12 → phasorpy-0.13}/MANIFEST.in +0 -0
  96. {phasorpy-0.12 → phasorpy-0.13}/README.md +0 -0
  97. {phasorpy-0.12 → phasorpy-0.13}/docs/_static/categorical.png +0 -0
  98. {phasorpy-0.12 → phasorpy-0.13}/docs/_static/custom-icons.js +0 -0
  99. {phasorpy-0.12 → phasorpy-0.13}/docs/_static/favicon.ico +0 -0
  100. {phasorpy-0.12 → phasorpy-0.13}/docs/_static/phasorpy_logo.png +0 -0
  101. {phasorpy-0.12 → phasorpy-0.13}/docs/_static/phasorpy_logo.svg +0 -0
  102. {phasorpy-0.12 → phasorpy-0.13}/docs/_static/srgb_spectrum.png +0 -0
  103. {phasorpy-0.12 → phasorpy-0.13}/docs/about.rst +0 -0
  104. {phasorpy-0.12 → phasorpy-0.13}/docs/acknowledgments.rst +0 -0
  105. {phasorpy-0.12 → phasorpy-0.13}/docs/api/_phasorpy.rst +0 -0
  106. {phasorpy-0.12 → phasorpy-0.13}/docs/api/_utils.rst +0 -0
  107. {phasorpy-0.12 → phasorpy-0.13}/docs/api/cli.rst +0 -0
  108. {phasorpy-0.12 → phasorpy-0.13}/docs/api/cluster.rst +0 -0
  109. {phasorpy-0.12 → phasorpy-0.13}/docs/api/color.rst +0 -0
  110. {phasorpy-0.12 → phasorpy-0.13}/docs/api/component.rst +0 -0
  111. {phasorpy-0.12 → phasorpy-0.13}/docs/api/cursor.rst +0 -0
  112. {phasorpy-0.12 → phasorpy-0.13}/docs/api/datasets.rst +0 -0
  113. {phasorpy-0.12 → phasorpy-0.13}/docs/api/experimental.rst +0 -0
  114. {phasorpy-0.12 → phasorpy-0.13}/docs/api/filter.rst +0 -0
  115. {phasorpy-0.12 → phasorpy-0.13}/docs/api/index.rst +0 -0
  116. {phasorpy-0.12 → phasorpy-0.13}/docs/api/io.rst +0 -0
  117. {phasorpy-0.12 → phasorpy-0.13}/docs/api/lifetime.rst +0 -0
  118. {phasorpy-0.12 → phasorpy-0.13}/docs/api/phasor.rst +0 -0
  119. {phasorpy-0.12 → phasorpy-0.13}/docs/api/phasorpy.rst +0 -0
  120. {phasorpy-0.12 → phasorpy-0.13}/docs/api/plot.rst +0 -0
  121. {phasorpy-0.12 → phasorpy-0.13}/docs/api/utils.rst +0 -0
  122. {phasorpy-0.12 → phasorpy-0.13}/docs/code_of_conduct.rst +0 -0
  123. {phasorpy-0.12 → phasorpy-0.13}/docs/contributing.rst +0 -0
  124. {phasorpy-0.12 → phasorpy-0.13}/docs/index.rst +0 -0
  125. {phasorpy-0.12 → phasorpy-0.13}/docs/license.rst +0 -0
  126. {phasorpy-0.12 → phasorpy-0.13}/setup.cfg +0 -0
  127. {phasorpy-0.12 → phasorpy-0.13}/src/phasorpy/py.typed +0 -0
  128. {phasorpy-0.12 → phasorpy-0.13}/src/phasorpy.egg-info/dependency_links.txt +0 -0
  129. {phasorpy-0.12 → phasorpy-0.13}/src/phasorpy.egg-info/entry_points.txt +0 -0
  130. {phasorpy-0.12 → phasorpy-0.13}/src/phasorpy.egg-info/not-zip-safe +0 -0
  131. {phasorpy-0.12 → phasorpy-0.13}/src/phasorpy.egg-info/top_level.txt +0 -0
  132. {phasorpy-0.12 → phasorpy-0.13}/tools/build_manylinux.cmd +0 -0
  133. {phasorpy-0.12 → phasorpy-0.13}/tools/build_manylinux.sh +0 -0
  134. {phasorpy-0.12 → phasorpy-0.13}/tutorials/README.rst +0 -0
  135. {phasorpy-0.12 → phasorpy-0.13}/tutorials/api/README.rst +0 -0
  136. {phasorpy-0.12 → phasorpy-0.13}/tutorials/applications/README.rst +0 -0
  137. {phasorpy-0.12 → phasorpy-0.13}/tutorials/misc/README.rst +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: phasorpy
3
- Version: 0.12
3
+ Version: 0.13
4
4
  Summary: Analysis of luminescence lifetime and hyperspectral images using the phasor approach
5
5
  Author: PhasorPy Contributors
6
6
  License-Expression: MIT
@@ -21,14 +21,15 @@ Classifier: Programming Language :: Python :: 3 :: Only
21
21
  Classifier: Programming Language :: Python :: 3.12
22
22
  Classifier: Programming Language :: Python :: 3.13
23
23
  Classifier: Programming Language :: Python :: 3.14
24
+ Classifier: Programming Language :: Python :: 3.15
24
25
  Requires-Python: >=3.12
25
26
  Description-Content-Type: text/markdown
26
27
  License-File: LICENSE.txt
27
28
  Requires-Dist: numpy>=2.1.0
28
- Requires-Dist: matplotlib>=3.9.0
29
- Requires-Dist: pandas>=2.3.0
29
+ Requires-Dist: matplotlib>=3.10.3
30
+ Requires-Dist: pandas>=2.3.2
30
31
  Requires-Dist: scikit-learn>=1.5.0
31
- Requires-Dist: scipy>=1.14.0
32
+ Requires-Dist: scipy>=1.14.1
32
33
  Requires-Dist: tifffile>=2026.5.2
33
34
  Requires-Dist: xarray>=2024.6.0
34
35
  Requires-Dist: click
@@ -42,8 +43,7 @@ Requires-Dist: liffile>=2025.2.10; extra == "all"
42
43
  Requires-Dist: ptufile>=2024.9.14; extra == "all"
43
44
  Requires-Dist: sdtfile>=2024.5.24; extra == "all"
44
45
  Requires-Dist: pawflim>=1.0.4; extra == "all"
45
- Requires-Dist: brighteyes-mcs-reader>=0.1.0; extra == "all"
46
- Requires-Dist: imagecodecs==2026.6.6; (sys_platform == "darwin" and platform_machine == "x86_64") and extra == "all"
46
+ Requires-Dist: brighteyes-mcs-reader>=0.1.0; python_version < "3.15" and extra == "all"
47
47
  Dynamic: license-file
48
48
 
49
49
  # <img src="https://www.phasorpy.org/_static/phasorpy_logo.svg" alt="&#x25CB;" width="48" height="48" align="left"/> PhasorPy
@@ -1,15 +1,20 @@
1
1
  [
2
2
  {
3
3
  "name": "dev",
4
- "version": "0.13",
4
+ "version": "0.14",
5
5
  "url": "https://www.phasorpy.org/docs/dev/"
6
6
  },
7
7
  {
8
- "name": "0.12 (stable)",
9
- "version": "0.12",
8
+ "name": "0.13 (stable)",
9
+ "version": "0.13",
10
10
  "url": "https://www.phasorpy.org/docs/stable/",
11
11
  "preferred": true
12
12
  },
13
+ {
14
+ "name": "0.12",
15
+ "version": "0.12",
16
+ "url": "https://www.phasorpy.org/docs/v0.12/"
17
+ },
13
18
  {
14
19
  "name": "0.11",
15
20
  "version": "0.11",
@@ -19,10 +24,5 @@
19
24
  "name": "0.10",
20
25
  "version": "0.10",
21
26
  "url": "https://www.phasorpy.org/docs/v0.10/"
22
- },
23
- {
24
- "name": "0.9",
25
- "version": "0.9",
26
- "url": "https://www.phasorpy.org/docs/v0.9/"
27
27
  }
28
28
  ]
@@ -1,3 +1,7 @@
1
+ # Copyright (c) PhasorPy Contributors
2
+ # SPDX-License-Identifier: MIT
3
+ # See LICENSE.txt file in the project root for details.
4
+
1
5
  """Sphinx build configuration file.
2
6
 
3
7
  https://www.sphinx-doc.org/en/master/usage/configuration.html
@@ -13,6 +17,8 @@ sys.path.insert(0, os.path.dirname(__file__))
13
17
 
14
18
  # remove the examples header from HTML tutorials
15
19
  import sphinx_gallery.gen_rst
20
+ from docutils import nodes
21
+ from sphinx.application import Sphinx
16
22
  from sphinx_gallery.sorting import ExplicitOrder
17
23
 
18
24
  sphinx_gallery.gen_rst.EXAMPLE_HEADER = (
@@ -170,6 +176,7 @@ class TutorialOrder:
170
176
  'multi_harmonic',
171
177
  'phasorplot',
172
178
  'cursor',
179
+ 'cluster',
173
180
  'component',
174
181
  'pca',
175
182
  # misc
@@ -206,6 +213,29 @@ intersphinx_mapping = {
206
213
  # do not show typehints
207
214
  autodoc_typehints = 'none'
208
215
 
216
+ # redirect links to defunct websites, for example, to the Web Archive
217
+ link_redirects = {
218
+ 'https://www.lfd.uci.edu': (
219
+ 'https://web.archive.org/web/20260926221331/https://www.lfd.uci.edu'
220
+ ),
221
+ }
222
+
223
+
224
+ def redirect_links(app: Sphinx, doctree: nodes.document) -> None:
225
+ """Replace URL prefixes in links according to link_redirects."""
226
+ del app
227
+ for node in doctree.findall(nodes.reference):
228
+ refuri = node.get('refuri', '')
229
+ for old, new in link_redirects.items():
230
+ if refuri.startswith(old):
231
+ node['refuri'] = new + refuri[len(old) :]
232
+ break
233
+
234
+
235
+ def setup(app: Sphinx) -> None:
236
+ """Set up Sphinx app with link redirection."""
237
+ app.connect('doctree-read', redirect_links)
238
+
209
239
 
210
240
  def linkcode_resolve(domain: str, info: dict[str, str]) -> str | None:
211
241
  """Return GitHub URL for Python object."""
@@ -231,8 +261,8 @@ def linkcode_resolve(domain: str, info: dict[str, str]) -> str | None:
231
261
  except AttributeError:
232
262
  return None
233
263
 
234
- if inspect.isfunction(obj):
235
- obj = inspect.unwrap(obj)
264
+ if inspect.isfunction(obj): # type: ignore[unreachable]
265
+ obj = inspect.unwrap(obj) # type: ignore[unreachable]
236
266
  try:
237
267
  fn = inspect.getsourcefile(obj)
238
268
  except TypeError:
@@ -148,9 +148,9 @@ luminescence time-resolved or spectral images:
148
148
 
149
149
  * - **Library**
150
150
  - :ref:`PhasorPy <phasorpy>`,
151
- :ref:`PyFLI <pyfli>`,
152
151
  :ref:`tttrlib <tttrlib>`,
153
152
  :ref:`FLIMLib <flimlib>`:sup:`2`,
153
+ :ref:`PyFLI <pyfli>`:sup:`2`,
154
154
  :ref:`BrightEyes <brighteyes>`:sup:`2`,
155
155
  :ref:`FLIM_tools <flim_tools>`:sup:`2,4`,
156
156
  :ref:`Cell-analysis-tools <cell_analysis_tools>`:sup:`2,4`
@@ -333,7 +333,7 @@ Details:
333
333
  is an open-source Python platform for Fluorescence Lifetime Imaging
334
334
  (FLI) data simulation, processing and visualization, including phasor
335
335
  analysis.
336
- The library is distributed under the CC BY-NC-ND 4.0 license.
336
+ The library is distributed under the AGPL-3.0 license.
337
337
 
338
338
  -
339
339
  .. _flim_tools:
@@ -1,8 +1,8 @@
1
1
  [build-system]
2
2
  requires = [
3
- "setuptools>=68",
3
+ "setuptools>=77",
4
4
  "numpy>=2.1.0",
5
- "cython>=3.2.4",
5
+ "cython>=3.2.9",
6
6
  ]
7
7
  build-backend = "setuptools.build_meta"
8
8
 
@@ -15,11 +15,11 @@ dependencies = [
15
15
  # sync with [dependency-groups.run]
16
16
  # https://scientific-python.org/specs/spec-0000/
17
17
  "numpy>=2.1.0",
18
- "matplotlib>=3.9.0",
19
- "pandas>=2.3.0",
18
+ "matplotlib>=3.10.3",
19
+ "pandas>=2.3.2",
20
20
  # "scikit-image>=0.24.0",
21
21
  "scikit-learn>=1.5.0",
22
- "scipy>=1.14.0",
22
+ "scipy>=1.14.1",
23
23
  "tifffile>=2026.5.2",
24
24
  "xarray>=2024.6.0",
25
25
  "click",
@@ -39,6 +39,7 @@ classifiers = [
39
39
  "Programming Language :: Python :: 3.12",
40
40
  "Programming Language :: Python :: 3.13",
41
41
  "Programming Language :: Python :: 3.14",
42
+ "Programming Language :: Python :: 3.15",
42
43
  ]
43
44
  authors = [{ name = "PhasorPy Contributors" }]
44
45
  license = "MIT"
@@ -65,8 +66,8 @@ all = [
65
66
  "ptufile>=2024.9.14",
66
67
  "sdtfile>=2024.5.24",
67
68
  "pawflim>=1.0.4",
68
- "brighteyes-mcs-reader>=0.1.0",
69
- "imagecodecs==2026.6.6; sys_platform == 'darwin' and platform_machine == 'x86_64'",
69
+ "brighteyes-mcs-reader>=0.1.0; python_version < '3.15'",
70
+ # "imagecodecs==2026.6.6; sys_platform == 'darwin' and platform_machine == 'x86_64'",
70
71
  # "mkl-fft>=2.1.1",
71
72
  ]
72
73
 
@@ -74,10 +75,10 @@ all = [
74
75
  run = [
75
76
  # runtime dependencies; sync with [project.dependencies]
76
77
  "numpy>=2.1.0",
77
- "matplotlib>=3.10.0",
78
- "pandas>=2.3.0",
78
+ "matplotlib>=3.10.3",
79
+ "pandas>=2.3.2",
79
80
  "scikit-learn>=1.5.0",
80
- "scipy>=1.14.0",
81
+ "scipy>=1.14.1",
81
82
  "tifffile>=2026.5.2",
82
83
  "xarray>=2024.6.0",
83
84
  "click",
@@ -93,8 +94,8 @@ optional = [
93
94
  "ptufile>=2024.9.14",
94
95
  "sdtfile>=2024.5.24",
95
96
  "pawflim>=1.0.4",
96
- "brighteyes-mcs-reader>=0.1.0",
97
- "imagecodecs==2026.6.6; sys_platform == 'darwin' and platform_machine == 'x86_64'",
97
+ "brighteyes-mcs-reader>=0.1.0; python_version < '3.15'",
98
+ # "imagecodecs==2026.6.6; sys_platform == 'darwin' and platform_machine == 'x86_64'",
98
99
  ]
99
100
  minimal = [
100
101
  # minimal runtime versions for testing against oldest supported versions
@@ -117,7 +118,7 @@ minimal = [
117
118
  ]
118
119
  build = [
119
120
  # build requirements
120
- "cython>=3.2.4",
121
+ "cython>=3.2.9",
121
122
  "numpy>=2.1.0",
122
123
  "abi3audit",
123
124
  "build",
@@ -150,7 +151,7 @@ docs = [
150
151
  lint = [
151
152
  # static code analysis requirements
152
153
  "autoflake",
153
- "black",
154
+ "black>=26.10.1",
154
155
  "blackdoc",
155
156
  # "codespell",
156
157
  "cython_lint",
@@ -262,9 +263,9 @@ typing-modules = ["_typing"]
262
263
  "__init__.py" = ["F401", "F403"]
263
264
  "conftest.py" = ["ARG001"]
264
265
  "setup.py" = ["T201"]
265
- "tests/*" = ["INP001", "PT011", "SLF001", "TC001"]
266
+ "tests/*" = ["INP001", "PT011", "SLF001", "TC001", "PLR0917"]
266
267
  "tools/*" = ["ANN", "INP001", "T201"]
267
- "tutorials/*" = ["ANN", "D", "E402", "INP001", "T201", "S101", "ERA001"]
268
+ "tutorials/*" = ["ANN", "D", "E402", "INP001", "T201", "PLR0917"]
268
269
  "docs/conf.py" = ["A001", "ANN204", "D102", "E402", "INP001", "RUF012"]
269
270
 
270
271
  [tool.ruff.format]
@@ -273,6 +274,9 @@ quote-style = "single"
273
274
  [tool.ruff.lint.flake8-quotes]
274
275
  inline-quotes = "single"
275
276
 
277
+ [tool.ruff.lint.flake8-copyright]
278
+ notice-rgx = "(#\\s*Copyright\\s*\\(c\\)\\s*PhasorPy\\s*Contributors)"
279
+
276
280
  [tool.ruff.lint.pydocstyle]
277
281
  convention = "numpy"
278
282
 
@@ -307,7 +311,7 @@ disable = [
307
311
 
308
312
  [tool.black]
309
313
  line-length = 79
310
- target-version = ["py312", "py313", "py314"]
314
+ target-version = ["py312", "py313", "py314", "py315"]
311
315
  skip-string-normalization = true
312
316
 
313
317
  [tool.isort]
@@ -345,6 +349,8 @@ warn_unreachable = true
345
349
  module = [
346
350
  "_conftest",
347
351
  "phasorpy._phasorpy",
352
+ "setuptools.*",
353
+ "sphinx_gallery.*",
348
354
  "pooch.*",
349
355
  "scipy.*",
350
356
  "mkl_fft.*",
@@ -353,6 +359,12 @@ module = [
353
359
  ]
354
360
  ignore_missing_imports = true
355
361
 
362
+ [[tool.mypy.overrides]]
363
+ # PyQt6 6.12 stubs contain syntax errors that block mypy
364
+ module = ["PyQt6.*"]
365
+ follow_imports = "skip"
366
+ follow_imports_for_stubs = true
367
+
356
368
  [tool.coverage.run]
357
369
  plugins = ["Cython.Coverage"]
358
370
  omit = ["conftest.py", "__main__.py"]
@@ -392,7 +404,6 @@ norecursedirs = [
392
404
  ".pytest_cache",
393
405
  ".ruff_cache",
394
406
  ".vscode",
395
- "htmlcov",
396
407
  "adhoc",
397
408
  "build",
398
409
  "docs/_build",
@@ -406,30 +417,20 @@ norecursedirs = [
406
417
  ]
407
418
 
408
419
  [tool.cibuildwheel]
409
- skip = [
410
- "cp39*",
411
- "cp310*",
412
- "cp311*",
413
- "cp313*",
414
- "cp314-*",
415
- "cp315*",
416
- "*musllinux*",
417
- "*i686*",
418
- "*ppc64le*",
419
- "*s390x*",
420
- ]
420
+ build = ["cp312-*", "cp314t-*", "cp315t-*"]
421
+ skip = ["*musllinux*", "*i686*", "*ppc64le*", "*s390x*"]
421
422
  test-command = "pytest {project}/tests"
422
- test-skip = ["cp314t*", "cp315t*"]
423
+ test-skip = ["cp3*t*"]
423
424
  test-requires = [
424
425
  # sync with [dependency-groups.optional]
425
- "brighteyes-mcs-reader",
426
+ "brighteyes-mcs-reader>=0.1.0; python_version < '3.15'",
426
427
  "czifile>=2026.4.11",
427
- "fbdfile",
428
- "lfdfiles",
429
- "liffile",
430
- "ptufile",
431
- "sdtfile",
432
- "pawflim",
428
+ "fbdfile>=2025.9.18",
429
+ "lfdfiles>=2026.6.24",
430
+ "liffile>=2025.2.10",
431
+ "ptufile>=2024.9.14",
432
+ "sdtfile>=2024.5.24",
433
+ "pawflim>=1.0.4",
433
434
  # sync with [dependency-groups.test]
434
435
  "coverage",
435
436
  "cython", # for Cython.Coverage plugin
@@ -439,10 +440,10 @@ test-requires = [
439
440
  "pytest-runner",
440
441
  ]
441
442
 
442
- [[tool.cibuildwheel.overrides]]
443
- select = "*-macosx_x86_64"
444
- inherit.test-requires = "append"
445
- test-requires = ["imagecodecs==2026.6.6"]
443
+ # [[tool.cibuildwheel.overrides]]
444
+ # select = "*-macosx_x86_64"
445
+ # inherit.test-requires = "append"
446
+ # test-requires = ["imagecodecs==2026.6.6"]
446
447
 
447
448
  [tool.cibuildwheel.test-environment]
448
449
  PHASORPY_DATA_ON_GITHUB = "1"
@@ -1,3 +1,7 @@
1
+ # Copyright (c) PhasorPy Contributors
2
+ # SPDX-License-Identifier: MIT
3
+ # See LICENSE.txt file in the project root for details.
4
+
1
5
  """PhasorPy package Setuptools script."""
2
6
 
3
7
  # project metadata are defined in pyproject.toml
@@ -8,17 +12,38 @@ import sysconfig
8
12
  import numpy
9
13
  from setuptools import Extension, setup
10
14
 
11
- DEBUG = bool(os.environ.get('PHASORPY_DEBUG', ''))
15
+ DEBUG = os.environ.get('PHASORPY_DEBUG', '').strip().lower() in {
16
+ '1',
17
+ 'true',
18
+ 'yes',
19
+ 'on',
20
+ }
12
21
  LIMITED_API = not sysconfig.get_config_var('Py_GIL_DISABLED')
13
22
 
14
23
  print()
15
24
  print(f'Building with numpy-{numpy.__version__}')
16
25
  print()
17
26
 
18
- if sys.platform == 'win32':
27
+ extra_compile_args: list[str] = []
28
+ extra_link_args: list[str] = []
29
+
30
+ if 'PYODIDE' in os.environ or 'PYODIDE_ROOT' in os.environ:
31
+ # OpenMP/threads not available in Pyodide
32
+ pass
33
+ elif sys.platform == 'darwin':
34
+ # OpenMP not available in Xcode
35
+ # https://mac.r-project.org/openmp/
36
+ # extra_compile_args = ['-Xclang', '-fopenmp']
37
+ # extra_link_args = ['-lomp']
38
+ pass
39
+ elif sys.platform == 'win32':
19
40
  extra_compile_args = ['/openmp']
20
- extra_link_args: list[str] = []
21
- if DEBUG:
41
+ else:
42
+ extra_compile_args = ['-fopenmp']
43
+ extra_link_args = ['-fopenmp']
44
+
45
+ if DEBUG:
46
+ if sys.platform == 'win32':
22
47
  extra_compile_args += [
23
48
  '/Zi',
24
49
  '/Od',
@@ -26,14 +51,9 @@ if sys.platform == 'win32':
26
51
  # '/DCYTHON_TRACE_NOGIL=1', # too slow
27
52
  ]
28
53
  extra_link_args += ['-debug:full']
29
- elif sys.platform == 'darwin':
30
- # OpenMP not available in Xcode
31
- # https://mac.r-project.org/openmp/
32
- extra_compile_args = [] # ['-Xclang', '-fopenmp']
33
- extra_link_args = [] # ['-lomp']
34
- else:
35
- extra_compile_args = ['-fopenmp']
36
- extra_link_args = ['-fopenmp']
54
+ else:
55
+ extra_compile_args += ['-g', '-O0', '-DCYTHON_TRACE=1']
56
+ extra_link_args += ['-g']
37
57
 
38
58
  ext_modules = [
39
59
  Extension(
@@ -44,7 +64,7 @@ ext_modules = [
44
64
  extra_link_args=extra_link_args,
45
65
  define_macros=[
46
66
  # ('CYTHON_TRACE_NOGIL', '1'),
47
- ('NPY_NO_DEPRECATED_API', 'NPY_2_0_API_VERSION'),
67
+ ('NPY_NO_DEPRECATED_API', 'NPY_2_1_API_VERSION'),
48
68
  ]
49
69
  + (
50
70
  [('Py_LIMITED_API', 0x030C0000), ('CYTHON_LIMITED_API', '1')]
@@ -55,6 +75,13 @@ ext_modules = [
55
75
  )
56
76
  ]
57
77
 
78
+ if DEBUG:
79
+ from Cython.Build import cythonize
80
+
81
+ ext_modules = cythonize(
82
+ ext_modules, compiler_directives={'linetrace': True}
83
+ ) # type: ignore[no-untyped-call]
84
+
58
85
 
59
86
  setup(
60
87
  ext_modules=ext_modules,
@@ -0,0 +1,13 @@
1
+ # Copyright (c) PhasorPy Contributors
2
+ # SPDX-License-Identifier: MIT
3
+ # See LICENSE.txt file in the project root for details.
4
+
5
+ """PhasorPy package."""
6
+
7
+ from __future__ import annotations
8
+
9
+ __all__ = ['__version__']
10
+
11
+
12
+ __version__ = '0.13'
13
+ """PhasorPy version string."""
@@ -1,3 +1,7 @@
1
+ # Copyright (c) PhasorPy Contributors
2
+ # SPDX-License-Identifier: MIT
3
+ # See LICENSE.txt file in the project root for details.
4
+
1
5
  """PhasorPy package command-line interface."""
2
6
 
3
7
  import sys
@@ -1,3 +1,7 @@
1
+ # Copyright (c) PhasorPy Contributors
2
+ # SPDX-License-Identifier: MIT
3
+ # See LICENSE.txt file in the project root for details.
4
+
1
5
  # distutils: language = c
2
6
  # cython: boundscheck = False
3
7
  # cython: wraparound = False
@@ -31,6 +35,7 @@ from libc.math cimport (
31
35
  fmax,
32
36
  fmin,
33
37
  hypot,
38
+ isinf,
34
39
  isnan,
35
40
  sin,
36
41
  sqrt,
@@ -132,6 +137,8 @@ def _phasor_from_signal(
132
137
  # TODO: use NumPy iterator API?
133
138
  # https://numpy.org/devdocs/reference/c-api/iterator.html
134
139
 
140
+ # allow samples=2: the calculation is mathematically defined, but
141
+ # the imaginary component is always zero and phase information is lost
135
142
  if (
136
143
  samples < 2
137
144
  or harmonics > samples // 2
@@ -295,6 +302,11 @@ def _phasor_from_lifetime(
295
302
 
296
303
  if nfreq == 1 and ntau == 1 and nfrac == 1 and ncomp == 1:
297
304
  # scalar
305
+ frac = fraction[0, 0]
306
+ if isnan(frac) or fabs(frac) < 1e-15:
307
+ phasor[0, 0, 0] = <float_t> NAN
308
+ phasor[1, 0, 0] = <float_t> NAN
309
+ return
298
310
  tau = lifetime[0, 0] * frequency[0] * twopi # omega_tau
299
311
  gs = 1.0 / (1.0 + tau * tau)
300
312
  phasor[0, 0, 0] = <float_t> gs
@@ -440,14 +452,14 @@ def _gaussian_signal(
440
452
  if samples < 1:
441
453
  return
442
454
 
443
- if folds < 0:
444
- folds = <ssize_t> (-floor(-6.0 * stdev / <double> samples))
445
-
446
455
  with nogil:
447
- if isnan(mean) or isnan(stdev) or stdev <= 0.0:
456
+ if isnan(mean) or isnan(stdev) or isinf(stdev) or stdev <= 0.0:
448
457
  for i in range(samples):
449
458
  signal[i] = <float_t> NAN
450
459
  else:
460
+ if folds < 0:
461
+ folds = <ssize_t> (-floor(-6.0 * stdev / <double> samples))
462
+
451
463
  c = 1.0 / (sqrt(2.0 * M_PI) * stdev)
452
464
 
453
465
  for i in range(-folds * samples, (folds + 1) * samples):
@@ -766,7 +778,7 @@ cdef (float_t, float_t) _phasor_to_apparent_lifetime(
766
778
  double taumod = INFINITY
767
779
  double t
768
780
 
769
- if isnan(real) or isnan(imag):
781
+ if isnan(real) or isnan(imag) or isnan(omega):
770
782
  return <float_t> NAN, <float_t> NAN
771
783
 
772
784
  t = real * real + imag * imag
@@ -816,7 +828,7 @@ cdef float_t _phasor_to_normal_lifetime(
816
828
  double taunorm = INFINITY
817
829
  double t
818
830
 
819
- if isnan(real) or isnan(imag):
831
+ if isnan(real) or isnan(imag) or isnan(omega):
820
832
  return <float_t> NAN
821
833
 
822
834
  omega *= omega
@@ -1162,6 +1174,7 @@ cdef unsigned char _is_inside_polar_rectangle(
1162
1174
  """Return whether point is inside polar rectangle.
1163
1175
 
1164
1176
  Angles should be in the range [-pi, pi]. Otherwise performance is degraded.
1177
+ If angle_min > angle_max, the angular range wraps around +/- pi.
1165
1178
 
1166
1179
  """
1167
1180
  cdef:
@@ -1180,10 +1193,11 @@ cdef unsigned char _is_inside_polar_rectangle(
1180
1193
  angle_min = <float_t> atan2(sin(angle_min), cos(angle_min))
1181
1194
  if angle_max < -M_PI or angle_max > M_PI:
1182
1195
  angle_max = <float_t> atan2(sin(angle_max), cos(angle_max))
1183
- if angle_min > angle_max:
1184
- angle_min, angle_max = angle_max, angle_min
1185
1196
  t = <float_t> atan2(y, x)
1186
- if t < angle_min or t > angle_max:
1197
+ if angle_min <= angle_max:
1198
+ if t < angle_min or t > angle_max:
1199
+ return False
1200
+ elif t < angle_min and t > angle_max:
1187
1201
  return False
1188
1202
 
1189
1203
  return True
@@ -1504,7 +1518,7 @@ cdef float_t _fraction_on_segment(
1504
1518
  # square of line length
1505
1519
  t = x0 * x0 + y0 * y0
1506
1520
  if t <= 0.0:
1507
- # not a line segment, return start
1521
+ # degenerate segment, return fraction at (x1, y1)
1508
1522
  return 0.0
1509
1523
  # projection of point on line
1510
1524
  t = (x * x0 + y * y0) / t
@@ -1549,8 +1563,8 @@ cdef float_t _fraction_on_line(
1549
1563
  # square of line length
1550
1564
  t = x0 * x0 + y0 * y0
1551
1565
  if t <= 0.0:
1552
- # not a line, return end
1553
- return 1.0
1566
+ # degenerate line, return fraction at (x1, y1)
1567
+ return 0.0
1554
1568
  # projection of point on line
1555
1569
  t = (x * x0 + y * y0) / t
1556
1570
  return t
@@ -1891,7 +1905,7 @@ def _lifetime_search_2(
1891
1905
  raise ValueError('invalid lifetime shape')
1892
1906
  if fraction.shape[0] != 2 or fraction.shape[1] != real.shape[1]:
1893
1907
  raise ValueError('invalid fraction shape')
1894
- if real.shape[0] != imag.shape[0] != 2:
1908
+ if real.shape[0] != 2 or imag.shape[0] != 2:
1895
1909
  raise ValueError('invalid phasor harmonics')
1896
1910
  if real.shape[1] != imag.shape[1]:
1897
1911
  raise ValueError('invalid phasor size')
@@ -2103,7 +2117,7 @@ def _mean_value_coordinates(
2103
2117
  """
2104
2118
  cdef:
2105
2119
  ssize_t i, j, k, p, nv
2106
- double x, y, alpha, weight, weight_sum
2120
+ double x, y, alpha, weight, weight_sum, weight_abs_sum
2107
2121
  double* weights = NULL
2108
2122
  double* sigma = NULL
2109
2123
  double* length = NULL
@@ -2147,6 +2161,7 @@ def _mean_value_coordinates(
2147
2161
  length[i] = hypot(vx[i] - x, vy[i] - y)
2148
2162
 
2149
2163
  weight_sum = 0.0
2164
+ weight_abs_sum = 0.0
2150
2165
  for i in range(nv):
2151
2166
  j = (i + 1) % nv # next vertex, wrapped around
2152
2167
  k = (i - 1 + nv) % nv # previous vertex, wrapped around
@@ -2162,9 +2177,11 @@ def _mean_value_coordinates(
2162
2177
  if j != k and j != i:
2163
2178
  weight = weight * length[j] * sin(fabs(sigma[j]) * 0.5)
2164
2179
  weight_sum = weight_sum + weight
2180
+ weight_abs_sum = weight_abs_sum + fabs(weight)
2165
2181
  weights[i] = weight
2166
2182
 
2167
- if fabs(weight_sum) > 1e-12:
2183
+ # weights scale with polygon size. use relative threshold
2184
+ if fabs(weight_sum) > 1e-12 * weight_abs_sum:
2168
2185
  for i in range(nv):
2169
2186
  fraction[order[i], p] = <float_t> (weights[i] / weight_sum)
2170
2187
  else:
@@ -2323,11 +2340,13 @@ cdef (float_t, float_t, float_t) _phasor_threshold_open(
2323
2340
  if not isnan(imag_max) and imag >= imag_max:
2324
2341
  return <float_t> NAN, <float_t> NAN, <float_t> NAN
2325
2342
 
2326
- if not isnan(modulation_min):
2343
+ if not isnan(modulation_min) and modulation_min >= 0.0:
2327
2344
  mod = real * real + imag * imag
2328
2345
  if mod <= modulation_min * modulation_min:
2329
2346
  return <float_t> NAN, <float_t> NAN, <float_t> NAN
2330
2347
  if not isnan(modulation_max):
2348
+ if modulation_max <= 0.0:
2349
+ return <float_t> NAN, <float_t> NAN, <float_t> NAN
2331
2350
  if isnan(mod):
2332
2351
  mod = real * real + imag * imag
2333
2352
  if mod >= modulation_max * modulation_max:
@@ -2385,11 +2404,13 @@ cdef (float_t, float_t, float_t) _phasor_threshold_closed(
2385
2404
  if not isnan(imag_max) and imag > imag_max:
2386
2405
  return <float_t> NAN, <float_t> NAN, <float_t> NAN
2387
2406
 
2388
- if not isnan(modulation_min):
2407
+ if not isnan(modulation_min) and modulation_min > 0.0:
2389
2408
  mod = real * real + imag * imag
2390
2409
  if mod < modulation_min * modulation_min:
2391
2410
  return <float_t> NAN, <float_t> NAN, <float_t> NAN
2392
2411
  if not isnan(modulation_max):
2412
+ if modulation_max < 0.0:
2413
+ return <float_t> NAN, <float_t> NAN, <float_t> NAN
2393
2414
  if isnan(mod):
2394
2415
  mod = real * real + imag * imag
2395
2416
  if mod > modulation_max * modulation_max:
@@ -2832,6 +2853,8 @@ def _flimlabs_signal(
2832
2853
  for pixels in channels:
2833
2854
  signal_ = signal[c, i]
2834
2855
  for h, count in pixels:
2856
+ if count < 0 or <ssize_t> <uint_t> count != count:
2857
+ raise ValueError(f'{count=} overflows signal dtype')
2835
2858
  signal_[h] = <uint_t> count
2836
2859
  i += 1
2837
2860
  c += 1
@@ -2840,6 +2863,8 @@ def _flimlabs_signal(
2840
2863
  for pixels in data[channel]:
2841
2864
  signal_ = signal[0, i]
2842
2865
  for h, count in pixels:
2866
+ if count < 0 or <ssize_t> <uint_t> count != count:
2867
+ raise ValueError(f'{count=} overflows signal dtype')
2843
2868
  signal_[h] = <uint_t> count
2844
2869
  i += 1
2845
2870
 
@@ -1,3 +1,7 @@
1
+ # Copyright (c) PhasorPy Contributors
2
+ # SPDX-License-Identifier: MIT
3
+ # See LICENSE.txt file in the project root for details.
4
+
1
5
  """Type annotations.
2
6
 
3
7
  This module should only be imported when type-checking, for example::