phasorpy 0.5__tar.gz → 0.6__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 (114) hide show
  1. {phasorpy-0.5/src/phasorpy.egg-info → phasorpy-0.6}/PKG-INFO +2 -2
  2. {phasorpy-0.5 → phasorpy-0.6}/README.md +1 -1
  3. {phasorpy-0.5 → phasorpy-0.6}/docs/_static/switcher.json +8 -8
  4. phasorpy-0.6/docs/acknowledgments.rst +42 -0
  5. phasorpy-0.6/docs/api/experimental.rst +5 -0
  6. {phasorpy-0.5 → phasorpy-0.6}/docs/api/index.rst +1 -0
  7. {phasorpy-0.5 → phasorpy-0.6}/docs/code_of_conduct.rst +4 -3
  8. {phasorpy-0.5 → phasorpy-0.6}/docs/conf.py +37 -3
  9. {phasorpy-0.5 → phasorpy-0.6}/docs/contributing.rst +6 -4
  10. {phasorpy-0.5 → phasorpy-0.6}/docs/index.rst +0 -1
  11. {phasorpy-0.5 → phasorpy-0.6}/docs/phasor_approach.rst +2 -2
  12. {phasorpy-0.5 → phasorpy-0.6}/docs/release.rst +64 -0
  13. {phasorpy-0.5 → phasorpy-0.6}/pyproject.toml +3 -3
  14. phasorpy-0.6/src/phasorpy/__init__.py +9 -0
  15. {phasorpy-0.5 → phasorpy-0.6}/src/phasorpy/_phasorpy.pyx +185 -2
  16. {phasorpy-0.5 → phasorpy-0.6}/src/phasorpy/_utils.py +121 -9
  17. {phasorpy-0.5 → phasorpy-0.6}/src/phasorpy/cli.py +56 -3
  18. {phasorpy-0.5 → phasorpy-0.6}/src/phasorpy/cluster.py +42 -6
  19. phasorpy-0.6/src/phasorpy/components.py +484 -0
  20. phasorpy-0.5/src/phasorpy/utils.py → phasorpy-0.6/src/phasorpy/experimental.py +132 -193
  21. phasorpy-0.6/src/phasorpy/io/__init__.py +137 -0
  22. phasorpy-0.6/src/phasorpy/io/_flimlabs.py +350 -0
  23. phasorpy-0.6/src/phasorpy/io/_leica.py +329 -0
  24. phasorpy-0.6/src/phasorpy/io/_ometiff.py +445 -0
  25. phasorpy-0.6/src/phasorpy/io/_other.py +782 -0
  26. phasorpy-0.6/src/phasorpy/io/_simfcs.py +627 -0
  27. {phasorpy-0.5 → phasorpy-0.6}/src/phasorpy/phasor.py +307 -1
  28. phasorpy-0.6/src/phasorpy/plot/__init__.py +27 -0
  29. phasorpy-0.6/src/phasorpy/plot/_functions.py +717 -0
  30. phasorpy-0.6/src/phasorpy/plot/_lifetime_plots.py +553 -0
  31. phasorpy-0.6/src/phasorpy/plot/_phasorplot.py +1119 -0
  32. phasorpy-0.6/src/phasorpy/plot/_phasorplot_fret.py +559 -0
  33. phasorpy-0.6/src/phasorpy/utils.py +161 -0
  34. {phasorpy-0.5 → phasorpy-0.6/src/phasorpy.egg-info}/PKG-INFO +2 -2
  35. {phasorpy-0.5 → phasorpy-0.6}/src/phasorpy.egg-info/SOURCES.txt +17 -6
  36. {phasorpy-0.5 → phasorpy-0.6}/tests/conftest.py +1 -2
  37. {phasorpy-0.5 → phasorpy-0.6}/tests/test__phasorpy.py +78 -18
  38. {phasorpy-0.5 → phasorpy-0.6}/tests/test__utils.py +3 -3
  39. {phasorpy-0.5 → phasorpy-0.6}/tests/test_cli.py +15 -1
  40. {phasorpy-0.5 → phasorpy-0.6}/tests/test_cluster.py +13 -9
  41. {phasorpy-0.5 → phasorpy-0.6}/tests/test_components.py +153 -32
  42. phasorpy-0.5/tests/test_utils.py → phasorpy-0.6/tests/test_experimental.py +11 -34
  43. {phasorpy-0.5 → phasorpy-0.6}/tests/test_nan.py +17 -8
  44. {phasorpy-0.5 → phasorpy-0.6}/tests/test_phasor.py +230 -7
  45. phasorpy-0.6/tests/test_phasorpy.py +12 -0
  46. phasorpy-0.6/tests/test_utils.py +52 -0
  47. {phasorpy-0.5 → phasorpy-0.6}/tutorials/api/phasorpy_components.py +69 -32
  48. {phasorpy-0.5 → phasorpy-0.6}/tutorials/api/phasorpy_io.py +2 -4
  49. phasorpy-0.6/tutorials/applications/phasorpy_component_fit.py +191 -0
  50. phasorpy-0.6/tutorials/applications/phasorpy_fret_efficiency.py +150 -0
  51. {phasorpy-0.5 → phasorpy-0.6}/tutorials/benchmarks/phasorpy_phasor_from_signal.py +31 -30
  52. {phasorpy-0.5 → phasorpy-0.6}/tutorials/phasorpy_introduction.py +3 -1
  53. phasorpy-0.6/tutorials/phasorpy_lifetime_geometry.py +306 -0
  54. phasorpy-0.5/docs/acknowledgments.rst +0 -36
  55. phasorpy-0.5/src/phasorpy/__init__.py +0 -10
  56. phasorpy-0.5/src/phasorpy/_io.py +0 -2655
  57. phasorpy-0.5/src/phasorpy/components.py +0 -313
  58. phasorpy-0.5/src/phasorpy/io.py +0 -9
  59. phasorpy-0.5/src/phasorpy/plot.py +0 -2318
  60. phasorpy-0.5/src/phasorpy/version.py +0 -80
  61. phasorpy-0.5/tests/test_io.py +0 -1324
  62. phasorpy-0.5/tests/test_phasorpy.py +0 -18
  63. phasorpy-0.5/tests/test_plot.py +0 -633
  64. {phasorpy-0.5 → phasorpy-0.6}/LICENSE.txt +0 -0
  65. {phasorpy-0.5 → phasorpy-0.6}/MANIFEST.in +0 -0
  66. {phasorpy-0.5 → phasorpy-0.6}/docs/_static/categorical.png +0 -0
  67. {phasorpy-0.5 → phasorpy-0.6}/docs/_static/custom-icons.js +0 -0
  68. {phasorpy-0.5 → phasorpy-0.6}/docs/_static/logo.png +0 -0
  69. {phasorpy-0.5 → phasorpy-0.6}/docs/_static/srgb_spectrum.png +0 -0
  70. {phasorpy-0.5 → phasorpy-0.6}/docs/api/_phasorpy.rst +0 -0
  71. {phasorpy-0.5 → phasorpy-0.6}/docs/api/_utils.rst +0 -0
  72. {phasorpy-0.5 → phasorpy-0.6}/docs/api/cli.rst +0 -0
  73. {phasorpy-0.5 → phasorpy-0.6}/docs/api/cluster.rst +0 -0
  74. {phasorpy-0.5 → phasorpy-0.6}/docs/api/color.rst +0 -0
  75. {phasorpy-0.5 → phasorpy-0.6}/docs/api/components.rst +0 -0
  76. {phasorpy-0.5 → phasorpy-0.6}/docs/api/cursors.rst +0 -0
  77. {phasorpy-0.5 → phasorpy-0.6}/docs/api/datasets.rst +0 -0
  78. {phasorpy-0.5 → phasorpy-0.6}/docs/api/io.rst +0 -0
  79. {phasorpy-0.5 → phasorpy-0.6}/docs/api/phasor.rst +0 -0
  80. {phasorpy-0.5 → phasorpy-0.6}/docs/api/phasorpy.rst +0 -0
  81. {phasorpy-0.5 → phasorpy-0.6}/docs/api/plot.rst +0 -0
  82. {phasorpy-0.5 → phasorpy-0.6}/docs/api/utils.rst +0 -0
  83. {phasorpy-0.5 → phasorpy-0.6}/docs/license.rst +0 -0
  84. {phasorpy-0.5 → phasorpy-0.6}/setup.cfg +0 -0
  85. {phasorpy-0.5 → phasorpy-0.6}/setup.py +0 -0
  86. {phasorpy-0.5 → phasorpy-0.6}/src/phasorpy/__main__.py +0 -0
  87. {phasorpy-0.5 → phasorpy-0.6}/src/phasorpy/_typing.py +0 -0
  88. {phasorpy-0.5 → phasorpy-0.6}/src/phasorpy/color.py +0 -0
  89. {phasorpy-0.5 → phasorpy-0.6}/src/phasorpy/conftest.py +0 -0
  90. {phasorpy-0.5 → phasorpy-0.6}/src/phasorpy/cursors.py +0 -0
  91. {phasorpy-0.5 → phasorpy-0.6}/src/phasorpy/datasets.py +0 -0
  92. {phasorpy-0.5 → phasorpy-0.6}/src/phasorpy/py.typed +0 -0
  93. {phasorpy-0.5 → phasorpy-0.6}/src/phasorpy.egg-info/dependency_links.txt +0 -0
  94. {phasorpy-0.5 → phasorpy-0.6}/src/phasorpy.egg-info/entry_points.txt +0 -0
  95. {phasorpy-0.5 → phasorpy-0.6}/src/phasorpy.egg-info/not-zip-safe +0 -0
  96. {phasorpy-0.5 → phasorpy-0.6}/src/phasorpy.egg-info/requires.txt +0 -0
  97. {phasorpy-0.5 → phasorpy-0.6}/src/phasorpy.egg-info/top_level.txt +0 -0
  98. {phasorpy-0.5 → phasorpy-0.6}/tests/test__typing.py +0 -0
  99. {phasorpy-0.5 → phasorpy-0.6}/tests/test_color.py +0 -0
  100. {phasorpy-0.5 → phasorpy-0.6}/tests/test_cursors.py +0 -0
  101. {phasorpy-0.5 → phasorpy-0.6}/tests/test_datasets.py +0 -0
  102. {phasorpy-0.5 → phasorpy-0.6}/tutorials/README.rst +0 -0
  103. {phasorpy-0.5 → phasorpy-0.6}/tutorials/api/README.rst +0 -0
  104. {phasorpy-0.5 → phasorpy-0.6}/tutorials/api/phasorpy_cursors.py +0 -0
  105. {phasorpy-0.5 → phasorpy-0.6}/tutorials/api/phasorpy_filtering.py +0 -0
  106. {phasorpy-0.5 → phasorpy-0.6}/tutorials/api/phasorpy_fret.py +0 -0
  107. {phasorpy-0.5 → phasorpy-0.6}/tutorials/api/phasorpy_lifetime_to_signal.py +0 -0
  108. {phasorpy-0.5 → phasorpy-0.6}/tutorials/api/phasorpy_multi-harmonic.py +0 -0
  109. {phasorpy-0.5 → phasorpy-0.6}/tutorials/api/phasorpy_pca.py +0 -0
  110. {phasorpy-0.5 → phasorpy-0.6}/tutorials/api/phasorpy_phasor_from_lifetime.py +0 -0
  111. {phasorpy-0.5 → phasorpy-0.6}/tutorials/api/phasorpy_phasorplot.py +0 -0
  112. {phasorpy-0.5 → phasorpy-0.6}/tutorials/applications/README.rst +0 -0
  113. {phasorpy-0.5 → phasorpy-0.6}/tutorials/benchmarks/README.rst +0 -0
  114. {phasorpy-0.5 → phasorpy-0.6}/tutorials/phasorpy_lfd_workshop.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: phasorpy
3
- Version: 0.5
3
+ Version: 0.6
4
4
  Summary: Analysis of fluorescence lifetime and hyperspectral images using the phasor approach
5
5
  Author: PhasorPy Contributors
6
6
  License-Expression: MIT
@@ -66,7 +66,7 @@ PhasorPy is a community-maintained project.
66
66
  in the form of bug reports, bug fixes, feature implementations, documentation,
67
67
  datasets, and enhancement proposals are welcome.
68
68
 
69
- This software project is supported in part by the
69
+ This software project was supported in part by the
70
70
  [Essential Open Source Software for Science (EOSS)](https://chanzuckerberg.com/eoss/)
71
71
  program at
72
72
  [Chan Zuckerberg Initiative](https://chanzuckerberg.com/).
@@ -15,7 +15,7 @@ PhasorPy is a community-maintained project.
15
15
  in the form of bug reports, bug fixes, feature implementations, documentation,
16
16
  datasets, and enhancement proposals are welcome.
17
17
 
18
- This software project is supported in part by the
18
+ This software project was supported in part by the
19
19
  [Essential Open Source Software for Science (EOSS)](https://chanzuckerberg.com/eoss/)
20
20
  program at
21
21
  [Chan Zuckerberg Initiative](https://chanzuckerberg.com/).
@@ -1,23 +1,23 @@
1
1
  [
2
2
  {
3
3
  "name": "dev",
4
- "version": "0.6",
4
+ "version": "0.7",
5
5
  "url": "https://www.phasorpy.org/docs/dev/"
6
6
  },
7
7
  {
8
- "name": "0.5 (stable)",
9
- "version": "0.5",
8
+ "name": "0.6 (stable)",
9
+ "version": "0.6",
10
10
  "url": "https://www.phasorpy.org/docs/stable/",
11
11
  "preferred": true
12
12
  },
13
+ {
14
+ "name": "0.5",
15
+ "version": "0.5",
16
+ "url": "https://www.phasorpy.org/docs/v0.5/"
17
+ },
13
18
  {
14
19
  "name": "0.4",
15
20
  "version": "0.4",
16
21
  "url": "https://www.phasorpy.org/docs/v0.4/"
17
- },
18
- {
19
- "name": "0.3",
20
- "version": "0.3",
21
- "url": "https://www.phasorpy.org/docs/v0.3/"
22
22
  }
23
23
  ]
@@ -0,0 +1,42 @@
1
+ Acknowledgments
2
+ ===============
3
+
4
+ The PhasorPy project was established by the
5
+ `Advanced Bioimaging Unit <https://pasteur.uy/en/units/advanced-bioimaging>`_
6
+ at the University of the Republic and Institut Pasteur de Montevideo and the
7
+ `Laboratory for Fluorescence Dynamics <https://www.lfd.uci.edu>`_
8
+ at the University of California, Irvine.
9
+
10
+ PhasorPy was inspired by the
11
+ `Globals for Images · SimFCS <https://www.lfd.uci.edu/globals/>`_ software by
12
+ Enrico Gratton.
13
+
14
+ This software project was supported in part by the
15
+ `Essential Open Source Software for Science (EOSS)
16
+ <https://chanzuckerberg.com/eoss/>`_ program at
17
+ `Chan Zuckerberg Initiative <https://chanzuckerberg.com/>`_
18
+ (grant number 2022-252604, 2022-2024).
19
+
20
+ .. _contributors:
21
+
22
+ Contributors
23
+ ------------
24
+
25
+ PhasorPy is developed by `contributors to the PhasorPy repository
26
+ <https://github.com/phasorpy/phasorpy/graphs/contributors>`_
27
+ and
28
+ `members of the PhasorPy organization
29
+ <https://github.com/orgs/phasorpy/people>`_.
30
+
31
+ User Community
32
+ --------------
33
+
34
+ Many thanks to the PhasorPy user community for their feedback, bug reports,
35
+ feature suggestions, and data files that have helped improve the library.
36
+
37
+ Citation
38
+ --------
39
+
40
+ If PhasorPy contributes to a project that leads to a publication,
41
+ please cite
42
+ `doi: 10.5281/zenodo.13862586 <https://doi.org/10.5281/zenodo.13862586>`_.
@@ -0,0 +1,5 @@
1
+ phasorpy.experimental
2
+ ---------------------
3
+
4
+ .. automodule:: phasorpy.experimental
5
+ :members:
@@ -19,6 +19,7 @@ PhasorPy library version |version|.
19
19
  plot
20
20
  io
21
21
  color
22
+ experimental
22
23
  datasets
23
24
  utils
24
25
  _utils
@@ -1,3 +1,5 @@
1
+ :orphan:
2
+
1
3
  Code of conduct
2
4
  ===============
3
5
 
@@ -68,9 +70,8 @@ Enforcement
68
70
  -----------
69
71
 
70
72
  Instances of abusive, harassing, or otherwise unacceptable behavior may be
71
- reported to the community leader responsible for enforcement:
72
-
73
- - `Leonel Malacrida <conduct@phasorpy.org>`_
73
+ reported to `<conduct@phasorpy.org>`_ and `PhasorPy organization members
74
+ <https://github.com/orgs/phasorpy/people>`_.
74
75
 
75
76
  All complaints will be reviewed and investigated promptly and fairly.
76
77
 
@@ -2,9 +2,10 @@
2
2
  # https://www.sphinx-doc.org/en/master/usage/configuration.html
3
3
  # pylint: skip-file
4
4
 
5
- # import os
6
- # import sys
7
- # sys.path.insert(0, os.path.abspath('../src/'))
5
+ import os
6
+ import sys
7
+
8
+ sys.path.insert(0, os.path.dirname(__file__))
8
9
 
9
10
  # remove the examples header from HTML tutorials
10
11
  import sphinx_gallery.gen_rst
@@ -123,8 +124,41 @@ sphinx_gallery_conf = {
123
124
  'gallery_dirs': 'tutorials',
124
125
  'reference_url': {'phasorpy': None},
125
126
  'matplotlib_animations': True,
127
+ 'within_subsection_order': 'conf.TutorialOrder',
126
128
  }
127
129
 
130
+
131
+ class TutorialOrder:
132
+ """Order tutorials in gallery subsections."""
133
+
134
+ tutorials = [
135
+ 'introduction',
136
+ 'lifetime_geometry',
137
+ 'lfd_workshop',
138
+ # api
139
+ 'io',
140
+ 'phasor_from_lifetime',
141
+ 'multi-harmonic',
142
+ 'filtering',
143
+ 'phasorplot',
144
+ 'cursors',
145
+ 'components',
146
+ 'fret',
147
+ 'lifetime_to_signal',
148
+ 'pca',
149
+ # applications
150
+ 'component_fit',
151
+ 'fret_efficiency',
152
+ # benchmarks
153
+ 'phasor_from_signal',
154
+ ]
155
+
156
+ def __init__(self, srcdir: str): ...
157
+
158
+ def __call__(self, filename: str) -> int:
159
+ return self.tutorials.index(filename[9:-3])
160
+
161
+
128
162
  copybutton_prompt_text = (
129
163
  r'>>> |\.\.\. |\$ |In \[\d*\]: | {2,5}\.\.\.: | {5,8}: '
130
164
  )
@@ -61,9 +61,9 @@ and include the following items in the bug report:
61
61
  of::
62
62
 
63
63
  $ python -m phasorpy versions
64
- Python 3.11.4 ...
65
- phasorpy 0.1.dev ...
66
- numpy 1.25.2
64
+ Python-3.13.3
65
+ phasorpy-0.5
66
+ numpy-2.2.6
67
67
  ...
68
68
 
69
69
  Contribute code or documentation
@@ -289,4 +289,6 @@ and must pass before code or documentation can be accepted.
289
289
  Other PhasorPy developers will review the pull request to check and help
290
290
  to improve its implementation, documentation, and style.
291
291
 
292
- Pull requests must be approved by a core team member before merging.
292
+ Pull requests must be approved by a
293
+ `PhasorPy organization member <https://github.com/orgs/phasorpy/people>`_
294
+ before merging.
@@ -62,7 +62,6 @@ Contents
62
62
  api/index
63
63
  release
64
64
  contributing
65
- code_of_conduct
66
65
  acknowledgments
67
66
  license
68
67
 
@@ -200,8 +200,8 @@ approach to analyze fluorescence time-resolved or spectral images:
200
200
  microscopy written in MATLAB by Alexander Vallmitjana, released under
201
201
  the CC BY 4.0 license.
202
202
 
203
- - `AlliGator <https://alligator-distribution.readthedocs.io>`_
204
- is a free, closed-source software for fluorescence lifetime image data
203
+ - `AlliGator <https://github.com/smXplorer/AlliGator>`_
204
+ is an open-source software for fluorescence lifetime image data
205
205
  analysis using the phasor approach and standard nonlinear fitting.
206
206
  The software is written in LabVIEW for Windows by Xavier Michalet.
207
207
 
@@ -9,6 +9,70 @@ documentation and maintenance changes.
9
9
  The PhasorPy library is still under construction. Backwards-incompatible
10
10
  changes may occur between revisions.
11
11
 
12
+ 0.6 (2025.6.22)
13
+ ---------------
14
+
15
+ This is the sixth alpha release of the PhasorPy library.
16
+ It contains several bug fixes, improvements, and breaking changes.
17
+
18
+ The new ``phasor_component_fit`` function fits fractions of multiple
19
+ components to phasor coordinates.
20
+ The ``phasor_nearest_neighbor`` function returns nearest neighbors in phasor
21
+ coordinates.
22
+ The ``phasor_to_normal_lifetime`` function returns single lifetimes closest
23
+ to phasor coordinates.
24
+ The ``phasor_semicircle_intersect`` function returns intersections of lines
25
+ with the universal semicircle.
26
+
27
+ The ``LifetimePlots`` class interactively plots lifetimes in the time domain,
28
+ frequency domain, and phasor plot. It can be invoked from the command line
29
+ with ``python -m phasorpy lifetime``.
30
+
31
+ The ``two_fractions_from_phasor`` and ``graphical_component_analysis``
32
+ functions are renamed to ``phasor_component_fraction`` and
33
+ ``phasor_component_graphical``, respectively.
34
+ The ``versions`` function has moved to the ``phasorpy.utils`` namespace.
35
+ The ``spectral_vector_denoise``, ``anscombe_transform``, and
36
+ ``anscombe_transform_inverse`` functions have moved to the
37
+ ``phasorpy.experimental`` namespace.
38
+
39
+ Functions in the ``io`` module now return only the first channel by default.
40
+
41
+ Three new tutorials are added: "Geometrical interpretation of lifetimes",
42
+ "Multi-component fit", and "FRET efficiency image".
43
+ This release supports Python 3.11 to 3.13.
44
+
45
+ What's Changed
46
+ ..............
47
+
48
+ * Bump version by @cgohlke in https://github.com/phasorpy/phasorpy/pull/221
49
+ * Bump pypa/cibuildwheel from 2.23.2 to 2.23.3 in the github-actions group by @dependabot in https://github.com/phasorpy/phasorpy/pull/223
50
+ * Update description of AlliGator software by @cgohlke in https://github.com/phasorpy/phasorpy/pull/225
51
+ * Cython 3.1.0 is released by @cgohlke in https://github.com/phasorpy/phasorpy/pull/226
52
+ * Add phasor_to_normal_lifetime function by @cgohlke in https://github.com/phasorpy/phasorpy/pull/228
53
+ * Read first channel from files by default by @cgohlke in https://github.com/phasorpy/phasorpy/pull/229
54
+ * Update acknowledgments by @cgohlke in https://github.com/phasorpy/phasorpy/pull/230
55
+ * Use importlib to get package versions by @cgohlke in https://github.com/phasorpy/phasorpy/pull/231
56
+ * Use Windows Server 2022 in GitHub Actions by @cgohlke in https://github.com/phasorpy/phasorpy/pull/232
57
+ * Add tutorial about geometrical interpretation of lifetimes by @cgohlke in https://github.com/phasorpy/phasorpy/pull/233
58
+ * Update reference phasor_from_signal benchmark results by @cgohlke in https://github.com/phasorpy/phasorpy/pull/234
59
+ * Sort clusters returned by phasor_cluster_gmm by @cgohlke in https://github.com/phasorpy/phasorpy/pull/236
60
+ * Reorganize io module by @cgohlke in https://github.com/phasorpy/phasorpy/pull/235
61
+ * Update pre-commit configuration by @cgohlke in https://github.com/phasorpy/phasorpy/pull/237
62
+ * Fix test failing with numpy 2.3.0 by @cgohlke in https://github.com/phasorpy/phasorpy/pull/239
63
+ * Add phasor_component_fit function by @cgohlke in https://github.com/phasorpy/phasorpy/pull/238
64
+ * Rename functions in components module by @cgohlke in https://github.com/phasorpy/phasorpy/pull/240
65
+ * Add phasor_semicircle_intersect function by @cgohlke in https://github.com/phasorpy/phasorpy/pull/241
66
+ * Add private helper functions to mask universal semicircle by @cgohlke in https://github.com/phasorpy/phasorpy/pull/242
67
+ * Add private _distance_from_semicircle function by @cgohlke in https://github.com/phasorpy/phasorpy/pull/244
68
+ * Reorganize plot module by @cgohlke in https://github.com/phasorpy/phasorpy/pull/245
69
+ * Add interactive LifetimePlots class by @cgohlke in https://github.com/phasorpy/phasorpy/pull/247
70
+ * Reorganize version, utils, and experimental modules by @cgohlke in https://github.com/phasorpy/phasorpy/pull/248
71
+ * Add phasor_nearest_neighbor function by @bruno-pannunzio in https://github.com/phasorpy/phasorpy/pull/243
72
+ * Release v0.6 by @cgohlke in https://github.com/phasorpy/phasorpy/pull/244
73
+
74
+ **Full Changelog**: https://github.com/phasorpy/phasorpy/compare/v0.5...v0.6
75
+
12
76
  0.5 (2025.4.11)
13
77
  ---------------
14
78
 
@@ -2,7 +2,7 @@
2
2
  requires = [
3
3
  "setuptools>=68",
4
4
  "numpy",
5
- "cython>=3.1.0b1",
5
+ "cython>=3.1.0",
6
6
  ]
7
7
  build-backend = "setuptools.build_meta"
8
8
 
@@ -77,7 +77,7 @@ all = [
77
77
  zip-safe = false
78
78
 
79
79
  [tool.setuptools.dynamic]
80
- version = { attr = "phasorpy.version.__version__" }
80
+ version = { attr = "phasorpy.__version__" }
81
81
 
82
82
  [tool.setuptools.package-data]
83
83
  phasorpy = ["py.typed"]
@@ -151,7 +151,7 @@ minversion = "7"
151
151
  log_cli_level = "INFO"
152
152
  # filterwarnings = ["error"] # breaks debugging tests in VSCode (Sept. 2024)
153
153
  xfail_strict = true
154
- addopts = "-rfEXs --strict-config --strict-markers --doctest-modules --doctest-glob=*.py --doctest-glob=*.rst --ignore=src/phasorpy/_io.py"
154
+ addopts = "-rfEXs --strict-config --strict-markers --doctest-modules --doctest-glob=*.py --doctest-glob=*.rst"
155
155
  doctest_optionflags = [
156
156
  "NORMALIZE_WHITESPACE",
157
157
  "ELLIPSIS",
@@ -0,0 +1,9 @@
1
+ """PhasorPy package."""
2
+
3
+ from __future__ import annotations
4
+
5
+ __all__ = ['__version__']
6
+
7
+
8
+ __version__ = '0.6'
9
+ """PhasorPy version string."""
@@ -50,6 +50,12 @@ ctypedef fused uint_t:
50
50
  uint32_t
51
51
  uint64_t
52
52
 
53
+ ctypedef fused int_t:
54
+ int8_t
55
+ int16_t
56
+ int32_t
57
+ int64_t
58
+
53
59
  ctypedef fused signal_t:
54
60
  uint8_t
55
61
  uint16_t
@@ -767,6 +773,33 @@ cdef (float_t, float_t) _phasor_from_apparent_lifetime(
767
773
  return <float_t> (mod * cos(phi)), <float_t> (mod * sin(phi))
768
774
 
769
775
 
776
+ @cython.ufunc
777
+ cdef float_t _phasor_to_normal_lifetime(
778
+ float_t real,
779
+ float_t imag,
780
+ float_t omega,
781
+ ) noexcept nogil:
782
+ """Return normal lifetimes from phasor coordinates."""
783
+ cdef:
784
+ double taunorm = INFINITY
785
+ double t
786
+
787
+ if isnan(real) or isnan(imag):
788
+ return <float_t> NAN
789
+
790
+ omega *= omega
791
+ if omega > 0.0:
792
+ t = 0.5 * (1.0 + cos(atan2(imag, real - 0.5)))
793
+ if t <= 0.0:
794
+ taunorm = INFINITY
795
+ elif t > 1.0:
796
+ taunorm = NAN
797
+ else:
798
+ taunorm = sqrt((1.0 - t) / (omega * t))
799
+
800
+ return <float_t> taunorm
801
+
802
+
770
803
  @cython.ufunc
771
804
  cdef (float_t, float_t) _phasor_from_single_lifetime(
772
805
  float_t lifetime,
@@ -1204,6 +1237,44 @@ cdef unsigned char _is_inside_stadium(
1204
1237
  _is_near_segment = _is_inside_stadium
1205
1238
 
1206
1239
 
1240
+ @cython.ufunc
1241
+ cdef unsigned char _is_inside_semicircle(
1242
+ float_t x, # point
1243
+ float_t y,
1244
+ float_t r, # distance
1245
+ ) noexcept nogil:
1246
+ """Return whether point is inside universal semicircle."""
1247
+ if r < 0.0 or isnan(x) or isnan(y):
1248
+ return False
1249
+ if y < -r:
1250
+ return False
1251
+ if y <= 0.0:
1252
+ if x >= 0.0 and x <= 1.0:
1253
+ return True
1254
+ # near endpoints?
1255
+ if x > 0.5:
1256
+ x -= <float_t> 1.0
1257
+ return x * x + y * y <= r * r
1258
+ return hypot(x - 0.5, y) <= r + 0.5
1259
+
1260
+
1261
+ @cython.ufunc
1262
+ cdef unsigned char _is_near_semicircle(
1263
+ float_t x, # point
1264
+ float_t y,
1265
+ float_t r, # distance
1266
+ ) noexcept nogil:
1267
+ """Return whether point is near universal semicircle."""
1268
+ if r < 0.0 or isnan(x) or isnan(y):
1269
+ return False
1270
+ if y < 0.0:
1271
+ # near endpoints?
1272
+ if x > 0.5:
1273
+ x -= <float_t> 1.0
1274
+ return x * x + y * y <= r * r
1275
+ return fabs(hypot(x - 0.5, y) - 0.5) <= r
1276
+
1277
+
1207
1278
  @cython.ufunc
1208
1279
  cdef unsigned char _is_near_line(
1209
1280
  float_t x, # point
@@ -1470,6 +1541,22 @@ cdef float_t _distance_from_line(
1470
1541
  return <float_t> hypot(x, y)
1471
1542
 
1472
1543
 
1544
+ @cython.ufunc
1545
+ cdef float_t _distance_from_semicircle(
1546
+ float_t x, # point
1547
+ float_t y,
1548
+ ) noexcept nogil:
1549
+ """Return distance from universal semicircle."""
1550
+ if isnan(x) or isnan(y):
1551
+ return NAN
1552
+ if y < 0.0:
1553
+ # distance to endpoints
1554
+ if x > 0.5:
1555
+ x -= <float_t> 1.0
1556
+ return <float_t> hypot(x, y)
1557
+ return <float_t> fabs(hypot(x - 0.5, y) - 0.5)
1558
+
1559
+
1473
1560
  @cython.ufunc
1474
1561
  cdef (float_t, float_t, float_t) _segment_direction_and_length(
1475
1562
  float_t x0, # segment start
@@ -1495,7 +1582,7 @@ cdef (float_t, float_t, float_t) _segment_direction_and_length(
1495
1582
 
1496
1583
 
1497
1584
  @cython.ufunc
1498
- cdef (float_t, float_t, float_t, float_t) _intersection_circle_circle(
1585
+ cdef (float_t, float_t, float_t, float_t) _intersect_circle_circle(
1499
1586
  float_t x0, # circle 0
1500
1587
  float_t y0,
1501
1588
  float_t r0,
@@ -1541,7 +1628,7 @@ cdef (float_t, float_t, float_t, float_t) _intersection_circle_circle(
1541
1628
 
1542
1629
 
1543
1630
  @cython.ufunc
1544
- cdef (float_t, float_t, float_t, float_t) _intersection_circle_line(
1631
+ cdef (float_t, float_t, float_t, float_t) _intersect_circle_line(
1545
1632
  float_t x, # circle
1546
1633
  float_t y,
1547
1634
  float_t r,
@@ -1583,10 +1670,106 @@ cdef (float_t, float_t, float_t, float_t) _intersection_circle_line(
1583
1670
  )
1584
1671
 
1585
1672
 
1673
+ @cython.ufunc
1674
+ cdef (float_t, float_t, float_t, float_t) _intersect_semicircle_line(
1675
+ float_t x0, # line start
1676
+ float_t y0,
1677
+ float_t x1, # line end
1678
+ float_t y1,
1679
+ ) noexcept nogil:
1680
+ """Return coordinates of intersections of line and universal semicircle."""
1681
+ cdef:
1682
+ double dx, dy, dr, dd, rdd
1683
+
1684
+ if isnan(x0) or isnan(x1) or isnan(y0) or isnan(y1):
1685
+ return NAN, NAN, NAN, NAN
1686
+
1687
+ dx = x1 - x0
1688
+ dy = y1 - y0
1689
+ dr = dx * dx + dy * dy
1690
+ dd = (x0 - 0.5) * y1 - (x1 - 0.5) * y0
1691
+ rdd = 0.25 * dr - dd * dd # discriminant
1692
+ if rdd < 0.0 or dr <= 0.0:
1693
+ # no intersection
1694
+ return NAN, NAN, NAN, NAN
1695
+ rdd = sqrt(rdd)
1696
+ x0 = <float_t> ((dd * dy - copysign(1.0, dy) * dx * rdd) / dr + 0.5)
1697
+ y0 = <float_t> ((-dd * dx - fabs(dy) * rdd) / dr)
1698
+ x1 = <float_t> ((dd * dy + copysign(1.0, dy) * dx * rdd) / dr + 0.5)
1699
+ y1 = <float_t> ((-dd * dx + fabs(dy) * rdd) / dr)
1700
+ if y0 < 0.0:
1701
+ x0 = NAN
1702
+ y0 = NAN
1703
+ if y1 < 0.0:
1704
+ x1 = NAN
1705
+ y1 = NAN
1706
+ return x0, y0, x1, y1
1707
+
1708
+
1709
+ def _nearest_neighbor_2d(
1710
+ int_t[::1] indices,
1711
+ const float_t[::1] x0,
1712
+ const float_t[::1] y0,
1713
+ const float_t[::1] x1,
1714
+ const float_t[::1] y1,
1715
+ const float_t distance_max,
1716
+ const int num_threads
1717
+ ):
1718
+ """Find nearest neighbors in 2D.
1719
+
1720
+ For each point in the first set of arrays (x0, y0) find the nearest point
1721
+ in the second set of arrays (x1, y1) and store the index of the nearest
1722
+ point in the second array in the indices array.
1723
+ If any coordinates are NaN, or the distance to the nearest point
1724
+ is larger than distance_max, the index is set to -1.
1725
+
1726
+ """
1727
+ cdef:
1728
+ ssize_t i, j, index
1729
+ float_t x, y, dmin
1730
+ float_t distance_max_squared = distance_max * distance_max
1731
+
1732
+ if (
1733
+ indices.shape[0] != x0.shape[0]
1734
+ or x0.shape[0] != y0.shape[0]
1735
+ or x1.shape[0] != y1.shape[0]
1736
+ ):
1737
+ raise ValueError('input array size mismatch')
1738
+
1739
+ with nogil, parallel(num_threads=num_threads):
1740
+ for i in prange(x0.shape[0]):
1741
+ x = x0[i]
1742
+ y = y0[i]
1743
+ if isnan(x) or isnan(y):
1744
+ indices[i] = -1
1745
+ continue
1746
+ index = -1
1747
+ dmin = INFINITY
1748
+ for j in range(x1.shape[0]):
1749
+ x = x0[i] - x1[j]
1750
+ y = y0[i] - y1[j]
1751
+ x = x * x + y * y
1752
+ if x < dmin:
1753
+ dmin = x
1754
+ index = j
1755
+ indices[i] = -1 if dmin > distance_max_squared else <int_t> index
1756
+
1757
+
1586
1758
  ###############################################################################
1587
1759
  # Blend ufuncs
1588
1760
 
1589
1761
 
1762
+ @cython.ufunc
1763
+ cdef float_t _blend_and(
1764
+ float_t a, # base layer
1765
+ float_t b, # blend layer
1766
+ ) noexcept nogil:
1767
+ """Return blended layers using `and` mode."""
1768
+ if isnan(a):
1769
+ return NAN
1770
+ return b
1771
+
1772
+
1590
1773
  @cython.ufunc
1591
1774
  cdef float_t _blend_normal(
1592
1775
  float_t a, # base layer