phasorpy 0.5__cp312-cp312-macosx_11_0_arm64.whl → 0.7__cp312-cp312-macosx_11_0_arm64.whl
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.
- phasorpy/__init__.py +2 -3
- phasorpy/_phasorpy.cpython-312-darwin.so +0 -0
- phasorpy/_phasorpy.pyx +466 -11
- phasorpy/_utils.py +222 -37
- phasorpy/cli.py +74 -3
- phasorpy/cluster.py +51 -21
- phasorpy/color.py +11 -7
- phasorpy/component.py +707 -0
- phasorpy/{cursors.py → cursor.py} +31 -33
- phasorpy/datasets.py +117 -7
- phasorpy/experimental.py +310 -0
- phasorpy/io/__init__.py +138 -0
- phasorpy/io/_flimlabs.py +360 -0
- phasorpy/io/_leica.py +331 -0
- phasorpy/io/_ometiff.py +444 -0
- phasorpy/io/_other.py +890 -0
- phasorpy/io/_simfcs.py +652 -0
- phasorpy/lifetime.py +2058 -0
- phasorpy/phasor.py +184 -1754
- phasorpy/plot/__init__.py +27 -0
- phasorpy/plot/_functions.py +723 -0
- phasorpy/plot/_lifetime_plots.py +563 -0
- phasorpy/plot/_phasorplot.py +1507 -0
- phasorpy/plot/_phasorplot_fret.py +561 -0
- phasorpy/utils.py +89 -290
- {phasorpy-0.5.dist-info → phasorpy-0.7.dist-info}/METADATA +3 -3
- phasorpy-0.7.dist-info/RECORD +35 -0
- {phasorpy-0.5.dist-info → phasorpy-0.7.dist-info}/WHEEL +1 -1
- phasorpy/_io.py +0 -2655
- phasorpy/components.py +0 -313
- phasorpy/io.py +0 -9
- phasorpy/plot.py +0 -2318
- phasorpy/version.py +0 -80
- phasorpy-0.5.dist-info/RECORD +0 -26
- {phasorpy-0.5.dist-info → phasorpy-0.7.dist-info}/entry_points.txt +0 -0
- {phasorpy-0.5.dist-info → phasorpy-0.7.dist-info}/licenses/LICENSE.txt +0 -0
- {phasorpy-0.5.dist-info → phasorpy-0.7.dist-info}/top_level.txt +0 -0
phasorpy/__init__.py
CHANGED
@@ -2,9 +2,8 @@
|
|
2
2
|
|
3
3
|
from __future__ import annotations
|
4
4
|
|
5
|
-
__all__ = ['__version__'
|
5
|
+
__all__ = ['__version__']
|
6
6
|
|
7
|
-
from .version import __version__, versions
|
8
7
|
|
9
|
-
__version__ =
|
8
|
+
__version__ = '0.7'
|
10
9
|
"""PhasorPy version string."""
|
Binary file
|
phasorpy/_phasorpy.pyx
CHANGED
@@ -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
|
@@ -482,10 +488,10 @@ cdef (double, double) _phasor_from_fret_donor(
|
|
482
488
|
return 1.0, 0.0
|
483
489
|
|
484
490
|
# phasor of pure donor at frequency
|
485
|
-
real, imag =
|
491
|
+
real, imag = phasor_from_single_lifetime(donor_lifetime, omega)
|
486
492
|
|
487
493
|
# phasor of quenched donor
|
488
|
-
quenched_real, quenched_imag =
|
494
|
+
quenched_real, quenched_imag = phasor_from_single_lifetime(
|
489
495
|
donor_lifetime * (1.0 - fret_efficiency), omega
|
490
496
|
)
|
491
497
|
|
@@ -549,14 +555,14 @@ cdef (double, double) _phasor_from_fret_acceptor(
|
|
549
555
|
acceptor_background = 0.0
|
550
556
|
|
551
557
|
# phasor of pure donor at frequency
|
552
|
-
donor_real, donor_imag =
|
558
|
+
donor_real, donor_imag = phasor_from_single_lifetime(donor_lifetime, omega)
|
553
559
|
|
554
560
|
if fret_efficiency == 0.0:
|
555
561
|
quenched_real = donor_real
|
556
562
|
quenched_imag = donor_imag
|
557
563
|
else:
|
558
564
|
# phasor of quenched donor
|
559
|
-
quenched_real, quenched_imag =
|
565
|
+
quenched_real, quenched_imag = phasor_from_single_lifetime(
|
560
566
|
donor_lifetime * (1.0 - fret_efficiency), omega
|
561
567
|
)
|
562
568
|
|
@@ -574,7 +580,7 @@ cdef (double, double) _phasor_from_fret_acceptor(
|
|
574
580
|
)
|
575
581
|
|
576
582
|
# phasor of acceptor at frequency
|
577
|
-
acceptor_real, acceptor_imag =
|
583
|
+
acceptor_real, acceptor_imag = phasor_from_single_lifetime(
|
578
584
|
acceptor_lifetime, omega
|
579
585
|
)
|
580
586
|
|
@@ -640,9 +646,9 @@ cdef inline (double, double) linear_combination(
|
|
640
646
|
)
|
641
647
|
|
642
648
|
|
643
|
-
cdef inline (
|
644
|
-
|
645
|
-
|
649
|
+
cdef inline (double, double) phasor_from_single_lifetime(
|
650
|
+
const double lifetime,
|
651
|
+
const double omega,
|
646
652
|
) noexcept nogil:
|
647
653
|
"""Return phasor coordinates from single lifetime component."""
|
648
654
|
cdef:
|
@@ -650,7 +656,7 @@ cdef inline (float_t, float_t) phasor_from_lifetime(
|
|
650
656
|
double mod = 1.0 / sqrt(1.0 + t * t)
|
651
657
|
double phi = atan(t)
|
652
658
|
|
653
|
-
return
|
659
|
+
return mod * cos(phi), mod * sin(phi)
|
654
660
|
|
655
661
|
|
656
662
|
###############################################################################
|
@@ -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)
|
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)
|
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,378 @@ 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
|
+
###############################################################################
|
1710
|
+
# Search functions
|
1711
|
+
|
1712
|
+
|
1713
|
+
def _lifetime_search_2(
|
1714
|
+
float_t[:, ::] lifetime, # (num_components, pixels)
|
1715
|
+
float_t[:, ::] fraction, # (num_components, pixels)
|
1716
|
+
const float_t[:, ::] real, # (num_components, pixels)
|
1717
|
+
const float_t[:, ::] imag, # (num_components, pixels)
|
1718
|
+
const double[::] candidate, # real coordinates to scan
|
1719
|
+
const double omega_sqr,
|
1720
|
+
const int num_threads
|
1721
|
+
):
|
1722
|
+
"""Find two lifetime components and fractions in harmonic coordinates.
|
1723
|
+
|
1724
|
+
https://doi.org/10.1021/acs.jpcb.0c06946
|
1725
|
+
|
1726
|
+
"""
|
1727
|
+
cdef:
|
1728
|
+
ssize_t i, u
|
1729
|
+
double re1, im1, re2, im2
|
1730
|
+
double g0, g1, g0h1, s0h1, g1h1, s1h1, g0h2, s0h2, g1h2, s1h2
|
1731
|
+
double x, y, dx, dy, dr, dd, rdd
|
1732
|
+
double dmin, d, f, t
|
1733
|
+
|
1734
|
+
if lifetime.shape[0] != 2 or lifetime.shape[1] != real.shape[1]:
|
1735
|
+
raise ValueError('lifetime shape invalid')
|
1736
|
+
if fraction.shape[0] != 2 or fraction.shape[1] != real.shape[1]:
|
1737
|
+
raise ValueError('fraction shape invalid')
|
1738
|
+
if real.shape[0] != imag.shape[0] != 2:
|
1739
|
+
raise ValueError('phasor harmonics invalid')
|
1740
|
+
if real.shape[1] != imag.shape[1]:
|
1741
|
+
raise ValueError('phasor size invalid')
|
1742
|
+
if candidate.shape[0] < 1:
|
1743
|
+
raise ValueError('candidate size < 1')
|
1744
|
+
|
1745
|
+
with nogil, parallel(num_threads=num_threads):
|
1746
|
+
|
1747
|
+
for u in prange(real.shape[1]):
|
1748
|
+
# loop over phasor coordinates
|
1749
|
+
re1 = real[0, u]
|
1750
|
+
re2 = real[1, u]
|
1751
|
+
im1 = imag[0, u]
|
1752
|
+
im2 = imag[1, u]
|
1753
|
+
|
1754
|
+
if (
|
1755
|
+
isnan(re1)
|
1756
|
+
or isnan(im1)
|
1757
|
+
or isnan(re2)
|
1758
|
+
or isnan(im2)
|
1759
|
+
# outside semicircle?
|
1760
|
+
or re1 < 0.0
|
1761
|
+
or re2 < 0.0
|
1762
|
+
or re1 > 1.0
|
1763
|
+
or re2 > 1.0
|
1764
|
+
or im1 < 0.0
|
1765
|
+
or im2 < 0.0
|
1766
|
+
or im1 * im1 > re1 - re1 * re1 + 1e-9
|
1767
|
+
or im2 * im2 > re2 - re2 * re2 + 1e-9
|
1768
|
+
):
|
1769
|
+
lifetime[0, u] = NAN
|
1770
|
+
lifetime[1, u] = NAN
|
1771
|
+
fraction[0, u] = NAN
|
1772
|
+
fraction[1, u] = NAN
|
1773
|
+
continue
|
1774
|
+
|
1775
|
+
dmin = INFINITY
|
1776
|
+
g0 = NAN
|
1777
|
+
g1 = NAN
|
1778
|
+
f = NAN
|
1779
|
+
|
1780
|
+
for i in range(candidate.shape[0]):
|
1781
|
+
# scan first component
|
1782
|
+
g0h1 = candidate[i]
|
1783
|
+
s0h1 = sqrt(g0h1 - g0h1 * g0h1)
|
1784
|
+
|
1785
|
+
# second component is intersection of semicircle with line
|
1786
|
+
# between first component and phasor coordinate
|
1787
|
+
dx = re1 - g0h1
|
1788
|
+
dy = im1 - s0h1
|
1789
|
+
dr = dx * dx + dy * dy
|
1790
|
+
dd = (g0h1 - 0.5) * im1 - (re1 - 0.5) * s0h1
|
1791
|
+
rdd = 0.25 * dr - dd * dd # discriminant
|
1792
|
+
if rdd < 0.0 or dr <= 0.0:
|
1793
|
+
# no intersection
|
1794
|
+
g0 = g0h1
|
1795
|
+
g1 = g0h1 # NAN?
|
1796
|
+
f = 1.0
|
1797
|
+
break
|
1798
|
+
rdd = sqrt(rdd)
|
1799
|
+
g0h1 = (dd * dy - copysign(1.0, dy) * dx * rdd) / dr + 0.5
|
1800
|
+
s0h1 = (-dd * dx - fabs(dy) * rdd) / dr
|
1801
|
+
g1h1 = (dd * dy + copysign(1.0, dy) * dx * rdd) / dr + 0.5
|
1802
|
+
s1h1 = (-dd * dx + fabs(dy) * rdd) / dr
|
1803
|
+
|
1804
|
+
# this check is numerically unstable if candidate=1.0
|
1805
|
+
if s0h1 < 0.0 or s1h1 < 0.0:
|
1806
|
+
# no other intersection with semicircle
|
1807
|
+
continue
|
1808
|
+
|
1809
|
+
if g0h1 < g1h1:
|
1810
|
+
t = g0h1
|
1811
|
+
g0h1 = g1h1
|
1812
|
+
g1h1 = t
|
1813
|
+
t = s0h1
|
1814
|
+
s0h1 = s1h1
|
1815
|
+
s1h1 = t
|
1816
|
+
|
1817
|
+
# second harmonic component coordinates on semicircle
|
1818
|
+
g0h2 = g0h1 / (4.0 - 3.0 * g0h1)
|
1819
|
+
s0h2 = sqrt(g0h2 - g0h2 * g0h2)
|
1820
|
+
g1h2 = g1h1 / (4.0 - 3.0 * g1h1)
|
1821
|
+
s1h2 = sqrt(g1h2 - g1h2 * g1h2)
|
1822
|
+
|
1823
|
+
# distance of phasor coordinates to line between
|
1824
|
+
# components at second harmonic
|
1825
|
+
# normalize line coordinates
|
1826
|
+
dx = g1h2 - g0h2
|
1827
|
+
dy = s1h2 - s0h2
|
1828
|
+
x = re2 - g0h2
|
1829
|
+
y = im2 - s0h2
|
1830
|
+
# square of line length
|
1831
|
+
t = dx * dx + dy * dy
|
1832
|
+
if t == 0.0:
|
1833
|
+
continue
|
1834
|
+
# projection of point on line using dot product
|
1835
|
+
t = (x * dx + y * dy) / t
|
1836
|
+
# square of distance of point to line
|
1837
|
+
dx = x - t * dx
|
1838
|
+
dy = y - t * dy
|
1839
|
+
d = dx * dx + dy * dy
|
1840
|
+
|
1841
|
+
if d < dmin:
|
1842
|
+
dmin = d
|
1843
|
+
g0 = g0h1
|
1844
|
+
g1 = g1h1
|
1845
|
+
f = t
|
1846
|
+
|
1847
|
+
lifetime[0, u] = <float_t> phasor_to_single_lifetime(g0, omega_sqr)
|
1848
|
+
lifetime[1, u] = <float_t> phasor_to_single_lifetime(g1, omega_sqr)
|
1849
|
+
fraction[0, u] = <float_t> (1.0 - f)
|
1850
|
+
fraction[1, u] = <float_t> f
|
1851
|
+
|
1852
|
+
|
1853
|
+
cdef inline double phasor_to_single_lifetime(
|
1854
|
+
const double real,
|
1855
|
+
const double omega_sqr,
|
1856
|
+
) noexcept nogil:
|
1857
|
+
"""Return single exponential lifetime from real coordinate."""
|
1858
|
+
cdef:
|
1859
|
+
double t
|
1860
|
+
|
1861
|
+
if isnan(real) or real < 0.0 or real > 1.0:
|
1862
|
+
return NAN
|
1863
|
+
t = real * omega_sqr
|
1864
|
+
return sqrt((1.0 - real) / t) if t > 0.0 else INFINITY
|
1865
|
+
|
1866
|
+
|
1867
|
+
def _nearest_neighbor_2d(
|
1868
|
+
int_t[::1] indices,
|
1869
|
+
const float_t[::1] x0,
|
1870
|
+
const float_t[::1] y0,
|
1871
|
+
const float_t[::1] x1,
|
1872
|
+
const float_t[::1] y1,
|
1873
|
+
const float_t distance_max,
|
1874
|
+
const int num_threads
|
1875
|
+
):
|
1876
|
+
"""Find nearest neighbors in 2D.
|
1877
|
+
|
1878
|
+
For each point in the first set of arrays (x0, y0) find the nearest point
|
1879
|
+
in the second set of arrays (x1, y1) and store the index of the nearest
|
1880
|
+
point in the second array in the indices array.
|
1881
|
+
If any coordinates are NaN, or the distance to the nearest point
|
1882
|
+
is larger than distance_max, the index is set to -1.
|
1883
|
+
|
1884
|
+
"""
|
1885
|
+
cdef:
|
1886
|
+
ssize_t i, j, index
|
1887
|
+
float_t x, y, dmin
|
1888
|
+
float_t distance_max_squared = distance_max * distance_max
|
1889
|
+
|
1890
|
+
if (
|
1891
|
+
indices.shape[0] != x0.shape[0]
|
1892
|
+
or x0.shape[0] != y0.shape[0]
|
1893
|
+
or x1.shape[0] != y1.shape[0]
|
1894
|
+
):
|
1895
|
+
raise ValueError('input array size mismatch')
|
1896
|
+
|
1897
|
+
with nogil, parallel(num_threads=num_threads):
|
1898
|
+
for i in prange(x0.shape[0]):
|
1899
|
+
x = x0[i]
|
1900
|
+
y = y0[i]
|
1901
|
+
if isnan(x) or isnan(y):
|
1902
|
+
indices[i] = -1
|
1903
|
+
continue
|
1904
|
+
index = -1
|
1905
|
+
dmin = INFINITY
|
1906
|
+
for j in range(x1.shape[0]):
|
1907
|
+
x = x0[i] - x1[j]
|
1908
|
+
y = y0[i] - y1[j]
|
1909
|
+
x = x * x + y * y
|
1910
|
+
if x < dmin:
|
1911
|
+
dmin = x
|
1912
|
+
index = j
|
1913
|
+
indices[i] = -1 if dmin > distance_max_squared else <int_t> index
|
1914
|
+
|
1915
|
+
|
1916
|
+
###############################################################################
|
1917
|
+
# Interpolation functions
|
1918
|
+
|
1919
|
+
|
1920
|
+
def _mean_value_coordinates(
|
1921
|
+
float_t[:, ::1] fraction, # vertices, points
|
1922
|
+
const ssize_t[::1] order,
|
1923
|
+
const float_t[::1] px, # points
|
1924
|
+
const float_t[::1] py,
|
1925
|
+
const float_t[::1] vx, # polygon vertices
|
1926
|
+
const float_t[::1] vy,
|
1927
|
+
const int num_threads
|
1928
|
+
):
|
1929
|
+
"""Calculate mean value coordinates of points in polygon.
|
1930
|
+
|
1931
|
+
https://doi.org/10.1016/j.cagd.2024.102310
|
1932
|
+
|
1933
|
+
"""
|
1934
|
+
cdef:
|
1935
|
+
ssize_t i, j, k, p, nv
|
1936
|
+
double x, y, alpha, weight, weight_sum
|
1937
|
+
double* weights = NULL
|
1938
|
+
double* sigma = NULL
|
1939
|
+
double* length = NULL
|
1940
|
+
|
1941
|
+
if px.shape[0] != py.shape[0]:
|
1942
|
+
raise ValueError('px and py shape mismatch')
|
1943
|
+
if vx.shape[0] != vy.shape[0]:
|
1944
|
+
raise ValueError('vx and vy shape mismatch')
|
1945
|
+
if fraction.shape[0] != vx.shape[0] or fraction.shape[1] != px.shape[0]:
|
1946
|
+
raise ValueError('fraction, vx or px shape mismatch')
|
1947
|
+
if fraction.shape[0] != order.shape[0]:
|
1948
|
+
raise ValueError('fraction and order shape mismatch')
|
1949
|
+
if fraction.shape[0] < 3:
|
1950
|
+
raise ValueError('not a polygon')
|
1951
|
+
|
1952
|
+
nv = fraction.shape[0]
|
1953
|
+
|
1954
|
+
with nogil, parallel(num_threads=num_threads):
|
1955
|
+
weights = <double *> malloc(3 * nv * sizeof(double))
|
1956
|
+
if weights == NULL:
|
1957
|
+
with gil:
|
1958
|
+
raise MemoryError('failed to allocate thread-local buffer')
|
1959
|
+
sigma = &weights[nv]
|
1960
|
+
length = &weights[nv * 2]
|
1961
|
+
|
1962
|
+
for p in prange(px.shape[0]):
|
1963
|
+
x = px[p]
|
1964
|
+
y = py[p]
|
1965
|
+
|
1966
|
+
if isnan(x) or isnan(y):
|
1967
|
+
for i in range(nv):
|
1968
|
+
fraction[i, p] = <float_t> NAN
|
1969
|
+
continue
|
1970
|
+
|
1971
|
+
for i in range(nv):
|
1972
|
+
j = (i + 1) % nv # next vertex, wrapped around
|
1973
|
+
sigma[i] = (
|
1974
|
+
angle(vx[j], vy[j], vx[i], vy[i], x, y) # beta
|
1975
|
+
- angle(vx[i], vy[i], vx[j], vy[j], x, y) # gamma
|
1976
|
+
)
|
1977
|
+
length[i] = hypot(vx[i] - x, vy[i] - y)
|
1978
|
+
|
1979
|
+
weight_sum = 0.0
|
1980
|
+
for i in range(nv):
|
1981
|
+
j = (i + 1) % nv # next vertex, wrapped around
|
1982
|
+
k = (i - 1 + nv) % nv # previous vertex, wrapped around
|
1983
|
+
|
1984
|
+
alpha = angle(vx[k], vy[k], x, y, vx[j], vy[j])
|
1985
|
+
if sign(alpha) != sign(
|
1986
|
+
M_PI * (sign(sigma[k]) + sign(sigma[i]))
|
1987
|
+
- sigma[k] - sigma[i]
|
1988
|
+
):
|
1989
|
+
alpha = -alpha
|
1990
|
+
weight = length[k] * sin(alpha * 0.5)
|
1991
|
+
for j in range(nv):
|
1992
|
+
if j != k and j != i:
|
1993
|
+
weight = weight * length[j] * sin(fabs(sigma[j]) * 0.5)
|
1994
|
+
weight_sum = weight_sum + weight
|
1995
|
+
weights[i] = weight
|
1996
|
+
|
1997
|
+
if fabs(weight_sum) > 1e-12:
|
1998
|
+
for i in range(nv):
|
1999
|
+
fraction[order[i], p] = <float_t> (weights[i] / weight_sum)
|
2000
|
+
else:
|
2001
|
+
for i in range(nv):
|
2002
|
+
fraction[i, p] = <float_t> NAN
|
2003
|
+
|
2004
|
+
free(weights)
|
2005
|
+
|
2006
|
+
|
2007
|
+
cdef inline int sign(const double x) noexcept nogil:
|
2008
|
+
"""Return sign of x."""
|
2009
|
+
return 0 if fabs(x) < 1e-12 else (1 if x > 0.0 else -1)
|
2010
|
+
|
2011
|
+
|
2012
|
+
cdef inline double angle(
|
2013
|
+
const double x0,
|
2014
|
+
const double y0,
|
2015
|
+
const double x1,
|
2016
|
+
const double y1,
|
2017
|
+
const double x2,
|
2018
|
+
const double y2,
|
2019
|
+
) noexcept nogil:
|
2020
|
+
"""Return angle at (x1, y1)."""
|
2021
|
+
cdef:
|
2022
|
+
double ax = x0 - x1
|
2023
|
+
double ay = y0 - y1
|
2024
|
+
double bx = x2 - x1
|
2025
|
+
double by = y2 - y1
|
2026
|
+
|
2027
|
+
return atan2(ax * by - ay * bx, ax * bx + ay * by)
|
2028
|
+
|
2029
|
+
|
1586
2030
|
###############################################################################
|
1587
2031
|
# Blend ufuncs
|
1588
2032
|
|
1589
2033
|
|
2034
|
+
@cython.ufunc
|
2035
|
+
cdef float_t _blend_and(
|
2036
|
+
float_t a, # base layer
|
2037
|
+
float_t b, # blend layer
|
2038
|
+
) noexcept nogil:
|
2039
|
+
"""Return blended layers using `and` mode."""
|
2040
|
+
if isnan(a):
|
2041
|
+
return NAN
|
2042
|
+
return b
|
2043
|
+
|
2044
|
+
|
1590
2045
|
@cython.ufunc
|
1591
2046
|
cdef float_t _blend_normal(
|
1592
2047
|
float_t a, # base layer
|