phasorpy 0.1__cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.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 +10 -0
- phasorpy/__main__.py +7 -0
- phasorpy/_phasorpy.cpython-311-aarch64-linux-gnu.so +0 -0
- phasorpy/_phasorpy.pyx +1811 -0
- phasorpy/_typing.py +77 -0
- phasorpy/_utils.py +441 -0
- phasorpy/cli.py +87 -0
- phasorpy/color.py +581 -0
- phasorpy/components.py +313 -0
- phasorpy/conftest.py +36 -0
- phasorpy/cursors.py +502 -0
- phasorpy/datasets.py +433 -0
- phasorpy/io.py +1671 -0
- phasorpy/phasor.py +3135 -0
- phasorpy/plot.py +2074 -0
- phasorpy/py.typed +0 -0
- phasorpy/utils.py +68 -0
- phasorpy/version.py +71 -0
- phasorpy-0.1.dist-info/LICENSE.txt +21 -0
- phasorpy-0.1.dist-info/METADATA +78 -0
- phasorpy-0.1.dist-info/RECORD +25 -0
- phasorpy-0.1.dist-info/WHEEL +6 -0
- phasorpy-0.1.dist-info/entry_points.txt +2 -0
- phasorpy-0.1.dist-info/top_level.txt +1 -0
- phasorpy.libs/libgomp-d22c30c5.so.1.0.0 +0 -0
phasorpy/_phasorpy.pyx
ADDED
@@ -0,0 +1,1811 @@
|
|
1
|
+
# distutils: language = c
|
2
|
+
# cython: language_level = 3
|
3
|
+
# cython: boundscheck = False
|
4
|
+
# cython: wraparound = False
|
5
|
+
# cython: cdivision = True
|
6
|
+
# cython: nonecheck = False
|
7
|
+
|
8
|
+
"""Cython implementation of low-level functions for the PhasorPy library."""
|
9
|
+
|
10
|
+
# TODO: replace short with unsigned char when Cython supports it
|
11
|
+
# https://github.com/cython/cython/pull/6196#issuecomment-2209509572
|
12
|
+
|
13
|
+
# TODO: use fused return types for functions returning more than two items
|
14
|
+
# https://github.com/cython/cython/issues/6328
|
15
|
+
|
16
|
+
cimport cython
|
17
|
+
|
18
|
+
from cython.parallel import parallel, prange
|
19
|
+
|
20
|
+
from libc.math cimport (
|
21
|
+
INFINITY,
|
22
|
+
M_PI,
|
23
|
+
NAN,
|
24
|
+
atan,
|
25
|
+
atan2,
|
26
|
+
copysign,
|
27
|
+
cos,
|
28
|
+
exp,
|
29
|
+
fabs,
|
30
|
+
floor,
|
31
|
+
hypot,
|
32
|
+
isnan,
|
33
|
+
sin,
|
34
|
+
sqrt,
|
35
|
+
tan,
|
36
|
+
)
|
37
|
+
from libc.stdint cimport (
|
38
|
+
int8_t,
|
39
|
+
int16_t,
|
40
|
+
int32_t,
|
41
|
+
int64_t,
|
42
|
+
uint8_t,
|
43
|
+
uint16_t,
|
44
|
+
uint32_t,
|
45
|
+
uint64_t,
|
46
|
+
)
|
47
|
+
|
48
|
+
ctypedef fused float_t:
|
49
|
+
float
|
50
|
+
double
|
51
|
+
|
52
|
+
ctypedef fused signal_t:
|
53
|
+
uint8_t
|
54
|
+
uint16_t
|
55
|
+
uint32_t
|
56
|
+
uint64_t
|
57
|
+
int8_t
|
58
|
+
int16_t
|
59
|
+
int32_t
|
60
|
+
int64_t
|
61
|
+
float
|
62
|
+
double
|
63
|
+
|
64
|
+
|
65
|
+
def _phasor_from_signal(
|
66
|
+
float_t[:, :, ::1] phasor,
|
67
|
+
const signal_t[:, :, ::1] signal,
|
68
|
+
const double[:, :, ::1] sincos,
|
69
|
+
const int num_threads
|
70
|
+
):
|
71
|
+
"""Return phasor coordinates from signal along middle axis.
|
72
|
+
|
73
|
+
Parameters
|
74
|
+
----------
|
75
|
+
phasor : 3D memoryview of float32 or float64
|
76
|
+
Writable buffer of three dimensions where calculated phasor
|
77
|
+
coordinates are stored:
|
78
|
+
|
79
|
+
0. mean, real, and imaginary components
|
80
|
+
1. lower dimensions flat
|
81
|
+
2. upper dimensions flat
|
82
|
+
|
83
|
+
signal : 3D memoryview of float32 or float64
|
84
|
+
Buffer of three dimensions containing signal:
|
85
|
+
|
86
|
+
0. lower dimensions flat
|
87
|
+
1. dimension over which to compute FFT, number samples
|
88
|
+
2. upper dimensions flat
|
89
|
+
|
90
|
+
sincos : 3D memoryview of float64
|
91
|
+
Buffer of three dimensions containing sine and cosine terms to be
|
92
|
+
multiplied with signal:
|
93
|
+
|
94
|
+
0. number harmonics
|
95
|
+
1. number samples
|
96
|
+
2. cos and sin
|
97
|
+
|
98
|
+
num_threads : int
|
99
|
+
Number of OpenMP threads to use for parallelization.
|
100
|
+
|
101
|
+
Notes
|
102
|
+
-----
|
103
|
+
This implementation requires contiguous input arrays.
|
104
|
+
|
105
|
+
"""
|
106
|
+
cdef:
|
107
|
+
float_t[:, ::1] mean
|
108
|
+
float_t[:, :, ::1] real, imag
|
109
|
+
ssize_t samples = signal.shape[1]
|
110
|
+
ssize_t harmonics = sincos.shape[0]
|
111
|
+
ssize_t i, j, k, h
|
112
|
+
double dc, re, im, sample
|
113
|
+
|
114
|
+
# TODO: use Numpy iterator API?
|
115
|
+
# https://numpy.org/devdocs/reference/c-api/iterator.html
|
116
|
+
|
117
|
+
if (
|
118
|
+
samples < 3
|
119
|
+
or harmonics > samples // 2
|
120
|
+
or phasor.shape[0] != harmonics * 2 + 1
|
121
|
+
or phasor.shape[1] != signal.shape[0]
|
122
|
+
or phasor.shape[2] != signal.shape[2]
|
123
|
+
):
|
124
|
+
raise ValueError('invalid shape of phasor or signal')
|
125
|
+
if sincos.shape[1] != samples or sincos.shape[2] != 2:
|
126
|
+
raise ValueError('invalid shape of sincos')
|
127
|
+
|
128
|
+
mean = phasor[0]
|
129
|
+
real = phasor[1 : 1 + harmonics]
|
130
|
+
imag = phasor[1 + harmonics : 1 + harmonics * 2]
|
131
|
+
|
132
|
+
if num_threads > 1 and signal.shape[0] >= num_threads:
|
133
|
+
# parallelize outer dimensions
|
134
|
+
with nogil, parallel(num_threads=num_threads):
|
135
|
+
for i in prange(signal.shape[0]):
|
136
|
+
for h in range(harmonics):
|
137
|
+
for j in range(signal.shape[2]):
|
138
|
+
dc = 0.0
|
139
|
+
re = 0.0
|
140
|
+
im = 0.0
|
141
|
+
for k in range(samples):
|
142
|
+
sample = <double> signal[i, k, j]
|
143
|
+
dc = dc + sample
|
144
|
+
re = re + sample * sincos[h, k, 0]
|
145
|
+
im = im + sample * sincos[h, k, 1]
|
146
|
+
if dc != 0.0:
|
147
|
+
re = re / dc
|
148
|
+
im = im / dc
|
149
|
+
dc = dc /samples
|
150
|
+
else:
|
151
|
+
dc = 0.0
|
152
|
+
re = NAN if re == 0.0 else re * INFINITY
|
153
|
+
im = NAN if im == 0.0 else im * INFINITY
|
154
|
+
if h == 0:
|
155
|
+
mean[i, j] = <float_t> dc
|
156
|
+
real[h, i, j] = <float_t> re
|
157
|
+
imag[h, i, j] = <float_t> im
|
158
|
+
|
159
|
+
elif num_threads > 1 and signal.shape[2] >= num_threads:
|
160
|
+
# parallelize inner dimensions
|
161
|
+
# TODO: do not use when not built with OpenMP
|
162
|
+
with nogil, parallel(num_threads=num_threads):
|
163
|
+
for j in prange(signal.shape[2]):
|
164
|
+
for h in range(harmonics):
|
165
|
+
for i in range(signal.shape[0]):
|
166
|
+
dc = 0.0
|
167
|
+
re = 0.0
|
168
|
+
im = 0.0
|
169
|
+
for k in range(samples):
|
170
|
+
sample = <double> signal[i, k, j]
|
171
|
+
dc = dc + sample
|
172
|
+
re = re + sample * sincos[h, k, 0]
|
173
|
+
im = im + sample * sincos[h, k, 1]
|
174
|
+
if dc != 0.0:
|
175
|
+
re = re / dc
|
176
|
+
im = im / dc
|
177
|
+
dc = dc /samples
|
178
|
+
else:
|
179
|
+
dc = 0.0
|
180
|
+
re = NAN if re == 0.0 else re * INFINITY
|
181
|
+
im = NAN if im == 0.0 else im * INFINITY
|
182
|
+
if h == 0:
|
183
|
+
mean[i, j] = <float_t> dc
|
184
|
+
real[h, i, j] = <float_t> re
|
185
|
+
imag[h, i, j] = <float_t> im
|
186
|
+
|
187
|
+
else:
|
188
|
+
# do not parallelize
|
189
|
+
with nogil:
|
190
|
+
for h in range(harmonics):
|
191
|
+
# TODO: move harmonics to an inner loop?
|
192
|
+
for i in range(signal.shape[0]):
|
193
|
+
for j in range(signal.shape[2]):
|
194
|
+
dc = 0.0
|
195
|
+
re = 0.0
|
196
|
+
im = 0.0
|
197
|
+
for k in range(samples):
|
198
|
+
sample = <double> signal[i, k, j]
|
199
|
+
dc += sample
|
200
|
+
re += sample * sincos[h, k, 0]
|
201
|
+
im += sample * sincos[h, k, 1]
|
202
|
+
if dc != 0.0:
|
203
|
+
re /= dc
|
204
|
+
im /= dc
|
205
|
+
dc /= samples
|
206
|
+
else:
|
207
|
+
dc = 0.0
|
208
|
+
re = NAN if re == 0.0 else re * INFINITY
|
209
|
+
im = NAN if im == 0.0 else im * INFINITY
|
210
|
+
if h == 0:
|
211
|
+
mean[i, j] = <float_t> dc
|
212
|
+
real[h, i, j] = <float_t> re
|
213
|
+
imag[h, i, j] = <float_t> im
|
214
|
+
|
215
|
+
|
216
|
+
def _phasor_from_lifetime(
|
217
|
+
float_t[:, :, ::1] phasor,
|
218
|
+
const double[::1] frequency,
|
219
|
+
const double[:, ::1] lifetime,
|
220
|
+
const double[:, ::1] fraction,
|
221
|
+
const double unit_conversion,
|
222
|
+
const bint preexponential,
|
223
|
+
):
|
224
|
+
"""Calculate phasor coordinates from lifetime components.
|
225
|
+
|
226
|
+
Parameters
|
227
|
+
----------
|
228
|
+
phasor : 3D memoryview of float32 or float64
|
229
|
+
Writable buffer of three dimensions where calculated phasor
|
230
|
+
coordinates are stored:
|
231
|
+
|
232
|
+
0. real and imaginary components
|
233
|
+
1. frequencies
|
234
|
+
2. lifetimes or fractions
|
235
|
+
|
236
|
+
frequency : 2D memoryview of float64
|
237
|
+
One-dimensional sequence of laser-pulse or modulation frequencies.
|
238
|
+
lifetime : 2D memoryview of float64
|
239
|
+
Buffer of two dimensions:
|
240
|
+
|
241
|
+
0. lifetimes
|
242
|
+
1. components of lifetimes
|
243
|
+
|
244
|
+
fraction : 2D memoryview of float64
|
245
|
+
Buffer of two dimensions:
|
246
|
+
|
247
|
+
0. fractions
|
248
|
+
1. fractions of lifetime components
|
249
|
+
|
250
|
+
unit_conversion : float
|
251
|
+
Product of `frequency` and `lifetime` units' prefix factors.
|
252
|
+
1e-3 for MHz and ns. 1.0 for Hz and s.
|
253
|
+
preexponential : bool
|
254
|
+
If true, fractions are pre-exponential amplitudes, else fractional
|
255
|
+
intensities.
|
256
|
+
|
257
|
+
"""
|
258
|
+
cdef:
|
259
|
+
ssize_t nfreq = frequency.shape[0] # number frequencies
|
260
|
+
ssize_t ncomp = lifetime.shape[1] # number lifetime components
|
261
|
+
ssize_t ntau = lifetime.shape[0] # number lifetimes
|
262
|
+
ssize_t nfrac = fraction.shape[0] # number fractions
|
263
|
+
double twopi = 2.0 * M_PI * unit_conversion
|
264
|
+
double freq, tau, frac, sum, re, im, gs
|
265
|
+
ssize_t f, t, s
|
266
|
+
|
267
|
+
if phasor.shape[0] != 2 or phasor.shape[1] != nfreq:
|
268
|
+
raise ValueError(
|
269
|
+
f'invalid {phasor.shape=!r} != (2, {nfreq}, -1))'
|
270
|
+
)
|
271
|
+
if fraction.shape[1] != ncomp:
|
272
|
+
raise ValueError(f'{lifetime.shape[1]=} != {fraction.shape[1]=}')
|
273
|
+
|
274
|
+
if nfreq == 1 and ntau == 1 and nfrac == 1 and ncomp == 1:
|
275
|
+
# scalar
|
276
|
+
tau = lifetime[0, 0] * frequency[0] * twopi # omega_tau
|
277
|
+
gs = 1.0 / (1.0 + tau * tau)
|
278
|
+
phasor[0, 0, 0] = <float_t> gs
|
279
|
+
phasor[1, 0, 0] = <float_t> (gs * tau)
|
280
|
+
return
|
281
|
+
|
282
|
+
if ntau == nfrac:
|
283
|
+
# fractions specified for all lifetime components
|
284
|
+
if phasor.shape[2] != ntau:
|
285
|
+
raise ValueError(f'{phasor.shape[2]=} != {ntau}')
|
286
|
+
with nogil:
|
287
|
+
for f in range(nfreq):
|
288
|
+
freq = frequency[f] * twopi # omega
|
289
|
+
for t in range(ntau):
|
290
|
+
re = 0.0
|
291
|
+
im = 0.0
|
292
|
+
sum = 0.0
|
293
|
+
if preexponential:
|
294
|
+
for s in range(ncomp):
|
295
|
+
sum += fraction[t, s] * lifetime[t, s] # Fdc
|
296
|
+
else:
|
297
|
+
for s in range(ncomp):
|
298
|
+
sum += fraction[t, s]
|
299
|
+
if fabs(sum) < 1e-15:
|
300
|
+
phasor[0, f, t] = <float_t> NAN
|
301
|
+
phasor[1, f, t] = <float_t> NAN
|
302
|
+
continue
|
303
|
+
for s in range(ncomp):
|
304
|
+
tau = lifetime[t, s]
|
305
|
+
frac = fraction[t, s] / sum
|
306
|
+
if preexponential:
|
307
|
+
frac *= tau
|
308
|
+
tau *= freq # omega_tau
|
309
|
+
gs = frac / (1.0 + tau * tau)
|
310
|
+
re += gs
|
311
|
+
im += gs * tau
|
312
|
+
phasor[0, f, t] = <float_t> re
|
313
|
+
phasor[1, f, t] = <float_t> im
|
314
|
+
return
|
315
|
+
|
316
|
+
if ntau > 1 and nfrac == 1:
|
317
|
+
# varying lifetime components, same fractions
|
318
|
+
if phasor.shape[2] != ntau:
|
319
|
+
raise ValueError(f'{phasor.shape[2]=} != {ntau}')
|
320
|
+
with nogil:
|
321
|
+
for f in range(nfreq):
|
322
|
+
freq = frequency[f] * twopi # omega
|
323
|
+
sum = 0.0
|
324
|
+
if not preexponential:
|
325
|
+
for s in range(ncomp):
|
326
|
+
sum += fraction[0, s]
|
327
|
+
for t in range(ntau):
|
328
|
+
if preexponential:
|
329
|
+
sum = 0.0
|
330
|
+
for s in range(ncomp):
|
331
|
+
sum += fraction[0, s] * lifetime[t, s] # Fdc
|
332
|
+
if fabs(sum) < 1e-15:
|
333
|
+
phasor[0, f, t] = <float_t> NAN
|
334
|
+
phasor[1, f, t] = <float_t> NAN
|
335
|
+
continue
|
336
|
+
re = 0.0
|
337
|
+
im = 0.0
|
338
|
+
for s in range(ncomp):
|
339
|
+
tau = lifetime[t, s]
|
340
|
+
frac = fraction[0, s] / sum
|
341
|
+
if preexponential:
|
342
|
+
frac *= tau
|
343
|
+
tau *= freq # omega_tau
|
344
|
+
gs = frac / (1.0 + tau * tau)
|
345
|
+
re += gs
|
346
|
+
im += gs * tau
|
347
|
+
phasor[0, f, t] = <float_t> re
|
348
|
+
phasor[1, f, t] = <float_t> im
|
349
|
+
return
|
350
|
+
|
351
|
+
if ntau == 1 and nfrac > 1:
|
352
|
+
# same lifetime components, varying fractions
|
353
|
+
if phasor.shape[2] != nfrac:
|
354
|
+
raise ValueError(f'{phasor.shape[2]=} != {nfrac}')
|
355
|
+
with nogil:
|
356
|
+
for f in range(nfreq):
|
357
|
+
freq = frequency[f] * twopi # omega
|
358
|
+
for t in range(nfrac):
|
359
|
+
re = 0.0
|
360
|
+
im = 0.0
|
361
|
+
sum = 0.0
|
362
|
+
if preexponential:
|
363
|
+
for s in range(ncomp):
|
364
|
+
sum += fraction[t, s] * lifetime[0, s] # Fdc
|
365
|
+
else:
|
366
|
+
for s in range(ncomp):
|
367
|
+
sum += fraction[t, s]
|
368
|
+
if fabs(sum) < 1e-15:
|
369
|
+
phasor[0, f, t] = <float_t> NAN
|
370
|
+
phasor[1, f, t] = <float_t> NAN
|
371
|
+
continue
|
372
|
+
for s in range(ncomp):
|
373
|
+
tau = lifetime[0, s]
|
374
|
+
frac = fraction[t, s] / sum
|
375
|
+
if preexponential:
|
376
|
+
frac *= tau
|
377
|
+
tau *= freq # omega_tau
|
378
|
+
gs = frac / (1.0 + tau * tau)
|
379
|
+
re += gs
|
380
|
+
im += gs * tau
|
381
|
+
phasor[0, f, t] = <float_t> re
|
382
|
+
phasor[1, f, t] = <float_t> im
|
383
|
+
return
|
384
|
+
|
385
|
+
raise ValueError(
|
386
|
+
f'{lifetime.shape[0]=} and {fraction.shape[0]=} do not match'
|
387
|
+
)
|
388
|
+
|
389
|
+
|
390
|
+
def _gaussian_signal(
|
391
|
+
float_t[::1] signal,
|
392
|
+
const double mean,
|
393
|
+
const double stdev,
|
394
|
+
):
|
395
|
+
"""Return normal distribution, wrapped around at borders.
|
396
|
+
|
397
|
+
Parameters
|
398
|
+
----------
|
399
|
+
signal : memoryview of float32 or float64
|
400
|
+
Writable buffer where calculated signal samples are stored.
|
401
|
+
mean : float
|
402
|
+
Mean of normal distribution.
|
403
|
+
stdev : float
|
404
|
+
Standard deviation of normal distribution.
|
405
|
+
|
406
|
+
"""
|
407
|
+
cdef:
|
408
|
+
ssize_t samples = signal.shape[0]
|
409
|
+
ssize_t folds = 1 # TODO: calculate from stddev and samples
|
410
|
+
ssize_t i
|
411
|
+
double t, c
|
412
|
+
|
413
|
+
if stdev <= 0.0 or samples < 1:
|
414
|
+
return
|
415
|
+
|
416
|
+
with nogil:
|
417
|
+
c = 1.0 / sqrt(2.0 * M_PI) * stdev
|
418
|
+
|
419
|
+
for i in range(-folds * samples, (folds + 1) * samples):
|
420
|
+
t = (<double> i - mean) / stdev
|
421
|
+
t *= t
|
422
|
+
t = c * exp(-t / 2.0)
|
423
|
+
# i %= samples
|
424
|
+
i -= samples * <ssize_t> floor(<double> i / samples)
|
425
|
+
signal[i] += <float_t> t
|
426
|
+
|
427
|
+
|
428
|
+
###############################################################################
|
429
|
+
# FRET model
|
430
|
+
|
431
|
+
@cython.ufunc
|
432
|
+
cdef (double, double) _phasor_from_fret_donor(
|
433
|
+
double omega,
|
434
|
+
double donor_lifetime,
|
435
|
+
double fret_efficiency,
|
436
|
+
double donor_freting,
|
437
|
+
double donor_background,
|
438
|
+
double background_real,
|
439
|
+
double background_imag,
|
440
|
+
) noexcept nogil:
|
441
|
+
"""Return phasor coordinates of FRET donor channel.
|
442
|
+
|
443
|
+
See :py:func:`phasor_from_fret_donor` for parameter definitions.
|
444
|
+
|
445
|
+
"""
|
446
|
+
cdef:
|
447
|
+
double real, imag
|
448
|
+
double quenched_real, quenched_imag # quenched donor
|
449
|
+
double f_pure, f_quenched, sum
|
450
|
+
|
451
|
+
if fret_efficiency < 0.0:
|
452
|
+
fret_efficiency = 0.0
|
453
|
+
elif fret_efficiency > 1.0:
|
454
|
+
fret_efficiency = 1.0
|
455
|
+
|
456
|
+
if donor_freting < 0.0:
|
457
|
+
donor_freting = 0.0
|
458
|
+
elif donor_freting > 1.0:
|
459
|
+
donor_freting = 1.0
|
460
|
+
|
461
|
+
if donor_background < 0.0:
|
462
|
+
donor_background = 0.0
|
463
|
+
|
464
|
+
f_pure = 1.0 - donor_freting
|
465
|
+
f_quenched = (1.0 - fret_efficiency) * donor_freting
|
466
|
+
sum = f_pure + f_quenched + donor_background
|
467
|
+
if sum < 1e-9:
|
468
|
+
# no signal in donor channel
|
469
|
+
return 1.0, 0.0
|
470
|
+
|
471
|
+
# phasor of pure donor at frequency
|
472
|
+
real, imag = phasor_from_lifetime(donor_lifetime, omega)
|
473
|
+
|
474
|
+
# phasor of quenched donor
|
475
|
+
quenched_real, quenched_imag = phasor_from_lifetime(
|
476
|
+
donor_lifetime * (1.0 - fret_efficiency), omega
|
477
|
+
)
|
478
|
+
|
479
|
+
# weighted average
|
480
|
+
real = (
|
481
|
+
real * f_pure
|
482
|
+
+ quenched_real * f_quenched
|
483
|
+
+ donor_background * background_real
|
484
|
+
) / sum
|
485
|
+
|
486
|
+
imag = (
|
487
|
+
imag * f_pure
|
488
|
+
+ quenched_imag * f_quenched
|
489
|
+
+ background_imag * donor_background
|
490
|
+
) / sum
|
491
|
+
|
492
|
+
return real, imag
|
493
|
+
|
494
|
+
|
495
|
+
@cython.ufunc
|
496
|
+
cdef (double, double) _phasor_from_fret_acceptor(
|
497
|
+
double omega,
|
498
|
+
double donor_lifetime,
|
499
|
+
double acceptor_lifetime,
|
500
|
+
double fret_efficiency,
|
501
|
+
double donor_freting,
|
502
|
+
double donor_bleedthrough,
|
503
|
+
double acceptor_bleedthrough,
|
504
|
+
double acceptor_background,
|
505
|
+
double background_real,
|
506
|
+
double background_imag,
|
507
|
+
) noexcept nogil:
|
508
|
+
"""Return phasor coordinates of FRET acceptor channel.
|
509
|
+
|
510
|
+
See :py:func:`phasor_from_fret_acceptor` for parameter definitions.
|
511
|
+
|
512
|
+
"""
|
513
|
+
cdef:
|
514
|
+
double phi, mod
|
515
|
+
double donor_real, donor_imag
|
516
|
+
double acceptor_real, acceptor_imag
|
517
|
+
double quenched_real, quenched_imag # quenched donor
|
518
|
+
double sensitized_real, sensitized_imag # sensitized acceptor
|
519
|
+
double sum, f_donor, f_acceptor
|
520
|
+
|
521
|
+
if fret_efficiency < 0.0:
|
522
|
+
fret_efficiency = 0.0
|
523
|
+
elif fret_efficiency > 1.0:
|
524
|
+
fret_efficiency = 1.0
|
525
|
+
|
526
|
+
if donor_freting < 0.0:
|
527
|
+
donor_freting = 0.0
|
528
|
+
elif donor_freting > 1.0:
|
529
|
+
donor_freting = 1.0
|
530
|
+
|
531
|
+
if donor_bleedthrough < 0.0:
|
532
|
+
donor_bleedthrough = 0.0
|
533
|
+
if acceptor_bleedthrough < 0.0:
|
534
|
+
acceptor_bleedthrough = 0.0
|
535
|
+
if acceptor_background < 0.0:
|
536
|
+
acceptor_background = 0.0
|
537
|
+
|
538
|
+
# phasor of pure donor at frequency
|
539
|
+
donor_real, donor_imag = phasor_from_lifetime(donor_lifetime, omega)
|
540
|
+
|
541
|
+
if fret_efficiency == 0.0:
|
542
|
+
quenched_real = donor_real
|
543
|
+
quenched_imag = donor_imag
|
544
|
+
else:
|
545
|
+
# phasor of quenched donor
|
546
|
+
quenched_real, quenched_imag = phasor_from_lifetime(
|
547
|
+
donor_lifetime * (1.0 - fret_efficiency), omega
|
548
|
+
)
|
549
|
+
|
550
|
+
# phasor of pure and quenched donor
|
551
|
+
donor_real, donor_imag = linear_combination(
|
552
|
+
1.0,
|
553
|
+
0.0,
|
554
|
+
donor_real,
|
555
|
+
donor_imag,
|
556
|
+
quenched_real,
|
557
|
+
quenched_imag,
|
558
|
+
1.0,
|
559
|
+
1.0 - fret_efficiency,
|
560
|
+
1.0 - donor_freting
|
561
|
+
)
|
562
|
+
|
563
|
+
# phasor of acceptor at frequency
|
564
|
+
acceptor_real, acceptor_imag = phasor_from_lifetime(
|
565
|
+
acceptor_lifetime, omega
|
566
|
+
)
|
567
|
+
|
568
|
+
# phasor of acceptor sensitized by quenched donor
|
569
|
+
# TODO: use rotation formula
|
570
|
+
phi = (
|
571
|
+
atan2(quenched_imag, quenched_real)
|
572
|
+
+ atan2(acceptor_imag, acceptor_real)
|
573
|
+
)
|
574
|
+
mod = (
|
575
|
+
hypot(quenched_real, quenched_imag)
|
576
|
+
* hypot(acceptor_real, acceptor_imag)
|
577
|
+
)
|
578
|
+
sensitized_real = mod * cos(phi)
|
579
|
+
sensitized_imag = mod * sin(phi)
|
580
|
+
|
581
|
+
# weighted average
|
582
|
+
f_donor = donor_bleedthrough * (1.0 - donor_freting * fret_efficiency)
|
583
|
+
f_acceptor = donor_freting * fret_efficiency
|
584
|
+
sum = f_donor + f_acceptor + acceptor_bleedthrough + acceptor_background
|
585
|
+
if sum < 1e-9:
|
586
|
+
# no signal in acceptor channel
|
587
|
+
# do not return 0, 0 to avoid discontinuities
|
588
|
+
return sensitized_real, sensitized_imag
|
589
|
+
|
590
|
+
acceptor_real = (
|
591
|
+
donor_real * f_donor
|
592
|
+
+ sensitized_real * f_acceptor
|
593
|
+
+ acceptor_real * acceptor_bleedthrough
|
594
|
+
+ background_real * acceptor_background
|
595
|
+
) / sum
|
596
|
+
|
597
|
+
acceptor_imag = (
|
598
|
+
donor_imag * f_donor
|
599
|
+
+ sensitized_imag * f_acceptor
|
600
|
+
+ acceptor_imag * acceptor_bleedthrough
|
601
|
+
+ background_imag * acceptor_background
|
602
|
+
) / sum
|
603
|
+
|
604
|
+
return acceptor_real, acceptor_imag
|
605
|
+
|
606
|
+
|
607
|
+
cdef inline (double, double) linear_combination(
|
608
|
+
const double real,
|
609
|
+
const double imag,
|
610
|
+
const double real1,
|
611
|
+
const double imag1,
|
612
|
+
const double real2,
|
613
|
+
const double imag2,
|
614
|
+
double int1,
|
615
|
+
double int2,
|
616
|
+
double frac,
|
617
|
+
) noexcept nogil:
|
618
|
+
"""Return linear combinations of phasor coordinates."""
|
619
|
+
int1 *= frac
|
620
|
+
int2 *= 1.0 - frac
|
621
|
+
frac = int1 + int2
|
622
|
+
if fabs(frac) < 1e-15:
|
623
|
+
return real, imag
|
624
|
+
return (
|
625
|
+
(int1 * real1 + int2 * real2) / frac,
|
626
|
+
(int1 * imag1 + int2 * imag2) / frac
|
627
|
+
)
|
628
|
+
|
629
|
+
|
630
|
+
cdef inline (float_t, float_t) phasor_from_lifetime(
|
631
|
+
float_t lifetime,
|
632
|
+
float_t omega,
|
633
|
+
) noexcept nogil:
|
634
|
+
"""Return phasor coordinates from single lifetime component."""
|
635
|
+
cdef:
|
636
|
+
double t = omega * lifetime
|
637
|
+
double mod = 1.0 / sqrt(1.0 + t * t)
|
638
|
+
double phi = atan(t)
|
639
|
+
|
640
|
+
return <float_t> (mod * cos(phi)), <float_t> (mod * sin(phi))
|
641
|
+
|
642
|
+
|
643
|
+
###############################################################################
|
644
|
+
# Phasor conversions
|
645
|
+
|
646
|
+
|
647
|
+
@cython.ufunc
|
648
|
+
cdef (float_t, float_t) _phasor_transform(
|
649
|
+
float_t real,
|
650
|
+
float_t imag,
|
651
|
+
float_t angle,
|
652
|
+
float_t scale,
|
653
|
+
) noexcept nogil:
|
654
|
+
"""Return rotated and scaled phasor coordinates."""
|
655
|
+
cdef:
|
656
|
+
double g, s
|
657
|
+
|
658
|
+
if isnan(real) or isnan(imag) or isnan(angle) or isnan(scale):
|
659
|
+
return <float_t> NAN, <float_t> NAN
|
660
|
+
|
661
|
+
g = scale * cos(angle)
|
662
|
+
s = scale * sin(angle)
|
663
|
+
|
664
|
+
return <float_t> (real * g - imag * s), <float_t> (real * s + imag * g)
|
665
|
+
|
666
|
+
|
667
|
+
@cython.ufunc
|
668
|
+
cdef (float_t, float_t) _phasor_transform_const(
|
669
|
+
float_t real,
|
670
|
+
float_t imag,
|
671
|
+
float_t real2,
|
672
|
+
float_t imag2,
|
673
|
+
) noexcept nogil:
|
674
|
+
"""Return rotated and scaled phasor coordinates."""
|
675
|
+
if isnan(real) or isnan(imag) or isnan(real2) or isnan(imag2):
|
676
|
+
return <float_t> NAN, <float_t> NAN
|
677
|
+
|
678
|
+
return real * real2 - imag * imag2, real * imag2 + imag * real2
|
679
|
+
|
680
|
+
|
681
|
+
@cython.ufunc
|
682
|
+
cdef (float_t, float_t) _phasor_to_polar(
|
683
|
+
float_t real,
|
684
|
+
float_t imag,
|
685
|
+
) noexcept nogil:
|
686
|
+
"""Return polar from phasor coordinates."""
|
687
|
+
if isnan(real) or isnan(imag):
|
688
|
+
return <float_t> NAN, <float_t> NAN
|
689
|
+
|
690
|
+
return (
|
691
|
+
<float_t> atan2(imag, real),
|
692
|
+
<float_t> sqrt(real * real + imag * imag)
|
693
|
+
)
|
694
|
+
|
695
|
+
|
696
|
+
@cython.ufunc
|
697
|
+
cdef (float_t, float_t) _phasor_from_polar(
|
698
|
+
float_t phase,
|
699
|
+
float_t modulation,
|
700
|
+
) noexcept nogil:
|
701
|
+
"""Return phasor from polar coordinates."""
|
702
|
+
if isnan(phase) or isnan(modulation):
|
703
|
+
return <float_t> NAN, <float_t> NAN
|
704
|
+
|
705
|
+
return (
|
706
|
+
modulation * <float_t> cos(phase),
|
707
|
+
modulation * <float_t> sin(phase)
|
708
|
+
)
|
709
|
+
|
710
|
+
|
711
|
+
@cython.ufunc
|
712
|
+
cdef (float_t, float_t) _phasor_to_apparent_lifetime(
|
713
|
+
float_t real,
|
714
|
+
float_t imag,
|
715
|
+
float_t omega,
|
716
|
+
) noexcept nogil:
|
717
|
+
"""Return apparent single lifetimes from phasor coordinates."""
|
718
|
+
cdef:
|
719
|
+
double tauphi = INFINITY
|
720
|
+
double taumod = INFINITY
|
721
|
+
double t
|
722
|
+
|
723
|
+
if isnan(real) or isnan(imag):
|
724
|
+
return <float_t> NAN, <float_t> NAN
|
725
|
+
|
726
|
+
t = real * real + imag * imag
|
727
|
+
if omega > 0.0 and t > 0.0:
|
728
|
+
if fabs(real * omega) > 0.0:
|
729
|
+
tauphi = imag / (real * omega)
|
730
|
+
if t <= 1.0:
|
731
|
+
taumod = sqrt(1.0 / t - 1.0) / omega
|
732
|
+
else:
|
733
|
+
taumod = 0.0
|
734
|
+
|
735
|
+
return <float_t> tauphi, <float_t> taumod
|
736
|
+
|
737
|
+
|
738
|
+
@cython.ufunc
|
739
|
+
cdef (float_t, float_t) _phasor_from_apparent_lifetime(
|
740
|
+
float_t tauphi,
|
741
|
+
float_t taumod,
|
742
|
+
float_t omega,
|
743
|
+
) noexcept nogil:
|
744
|
+
"""Return phasor coordinates from apparent single lifetimes."""
|
745
|
+
cdef:
|
746
|
+
double phi, mod, t
|
747
|
+
|
748
|
+
if isnan(tauphi) or isnan(taumod):
|
749
|
+
return <float_t> NAN, <float_t> NAN
|
750
|
+
|
751
|
+
t = omega * taumod
|
752
|
+
mod = 1.0 / sqrt(1.0 + t * t)
|
753
|
+
phi = atan(omega * tauphi)
|
754
|
+
return <float_t> (mod * cos(phi)), <float_t> (mod * sin(phi))
|
755
|
+
|
756
|
+
|
757
|
+
@cython.ufunc
|
758
|
+
cdef (float_t, float_t) _phasor_from_single_lifetime(
|
759
|
+
float_t lifetime,
|
760
|
+
float_t omega,
|
761
|
+
) noexcept nogil:
|
762
|
+
"""Return phasor coordinates from single lifetime component."""
|
763
|
+
cdef:
|
764
|
+
double phi, mod, t
|
765
|
+
|
766
|
+
if isnan(lifetime):
|
767
|
+
return <float_t> NAN, <float_t> NAN
|
768
|
+
|
769
|
+
t = omega * lifetime
|
770
|
+
phi = atan(t)
|
771
|
+
mod = 1.0 / sqrt(1.0 + t * t)
|
772
|
+
return <float_t> (mod * cos(phi)), <float_t> (mod * sin(phi))
|
773
|
+
|
774
|
+
|
775
|
+
@cython.ufunc
|
776
|
+
cdef (float_t, float_t) _polar_from_single_lifetime(
|
777
|
+
float_t lifetime,
|
778
|
+
float_t omega,
|
779
|
+
) noexcept nogil:
|
780
|
+
"""Return polar coordinates from single lifetime component."""
|
781
|
+
cdef:
|
782
|
+
double t
|
783
|
+
|
784
|
+
if isnan(lifetime):
|
785
|
+
return <float_t> NAN, <float_t> NAN
|
786
|
+
|
787
|
+
t = omega * lifetime
|
788
|
+
return <float_t> atan(t), <float_t> (1.0 / sqrt(1.0 + t * t))
|
789
|
+
|
790
|
+
|
791
|
+
@cython.ufunc
|
792
|
+
cdef (float_t, float_t) _polar_to_apparent_lifetime(
|
793
|
+
float_t phase,
|
794
|
+
float_t modulation,
|
795
|
+
float_t omega,
|
796
|
+
) noexcept nogil:
|
797
|
+
"""Return apparent single lifetimes from polar coordinates."""
|
798
|
+
cdef:
|
799
|
+
double tauphi = INFINITY
|
800
|
+
double taumod = INFINITY
|
801
|
+
double t
|
802
|
+
|
803
|
+
if isnan(phase) or isnan(modulation):
|
804
|
+
return <float_t> NAN, <float_t> NAN
|
805
|
+
|
806
|
+
t = modulation * modulation
|
807
|
+
if omega > 0.0 and t > 0.0:
|
808
|
+
tauphi = tan(phase) / omega
|
809
|
+
if t <= 1.0:
|
810
|
+
taumod = sqrt(1.0 / t - 1.0) / omega
|
811
|
+
else:
|
812
|
+
taumod = 0.0
|
813
|
+
return <float_t> tauphi, <float_t> taumod
|
814
|
+
|
815
|
+
|
816
|
+
@cython.ufunc
|
817
|
+
cdef (float_t, float_t) _polar_from_apparent_lifetime(
|
818
|
+
float_t tauphi,
|
819
|
+
float_t taumod,
|
820
|
+
float_t omega,
|
821
|
+
) noexcept nogil:
|
822
|
+
"""Return polar coordinates from apparent single lifetimes."""
|
823
|
+
cdef:
|
824
|
+
double t
|
825
|
+
|
826
|
+
if isnan(tauphi) or isnan(taumod):
|
827
|
+
return <float_t> NAN, <float_t> NAN
|
828
|
+
|
829
|
+
t = omega * taumod
|
830
|
+
return (
|
831
|
+
<float_t> (atan(omega * tauphi)),
|
832
|
+
<float_t> (1.0 / sqrt(1.0 + t * t))
|
833
|
+
)
|
834
|
+
|
835
|
+
|
836
|
+
@cython.ufunc
|
837
|
+
cdef (float_t, float_t) _polar_from_reference(
|
838
|
+
float_t measured_phase,
|
839
|
+
float_t measured_modulation,
|
840
|
+
float_t known_phase,
|
841
|
+
float_t known_modulation,
|
842
|
+
) noexcept nogil:
|
843
|
+
"""Return polar coordinates for calibration from reference coordinates."""
|
844
|
+
if (
|
845
|
+
isnan(measured_phase)
|
846
|
+
or isnan(measured_modulation)
|
847
|
+
or isnan(known_phase)
|
848
|
+
or isnan(known_modulation)
|
849
|
+
):
|
850
|
+
return <float_t> NAN, <float_t> NAN
|
851
|
+
|
852
|
+
if fabs(measured_modulation) == 0.0:
|
853
|
+
# return known_phase - measured_phase, <float_t> INFINITY
|
854
|
+
return (
|
855
|
+
known_phase - measured_phase,
|
856
|
+
<float_t> (NAN if known_modulation == 0.0 else INFINITY)
|
857
|
+
)
|
858
|
+
return known_phase - measured_phase, known_modulation / measured_modulation
|
859
|
+
|
860
|
+
|
861
|
+
@cython.ufunc
|
862
|
+
cdef (float_t, float_t) _polar_from_reference_phasor(
|
863
|
+
float_t measured_real,
|
864
|
+
float_t measured_imag,
|
865
|
+
float_t known_real,
|
866
|
+
float_t known_imag,
|
867
|
+
) noexcept nogil:
|
868
|
+
"""Return polar coordinates for calibration from reference phasor."""
|
869
|
+
cdef:
|
870
|
+
double measured_phase, measured_modulation
|
871
|
+
double known_phase, known_modulation
|
872
|
+
|
873
|
+
if (
|
874
|
+
isnan(measured_real)
|
875
|
+
or isnan(measured_imag)
|
876
|
+
or isnan(known_real)
|
877
|
+
or isnan(known_imag)
|
878
|
+
):
|
879
|
+
return <float_t> NAN, <float_t> NAN
|
880
|
+
|
881
|
+
measured_phase = atan2(measured_imag, measured_real)
|
882
|
+
known_phase = atan2(known_imag, known_real)
|
883
|
+
measured_modulation = hypot(measured_real, measured_imag)
|
884
|
+
known_modulation = hypot(known_real, known_imag)
|
885
|
+
|
886
|
+
if fabs(measured_modulation) == 0.0:
|
887
|
+
# return <float_t> (known_phase - measured_phase), <float_t> INFINITY
|
888
|
+
return (
|
889
|
+
<float_t> (known_phase - measured_phase),
|
890
|
+
<float_t> (NAN if known_modulation == 0.0 else INFINITY)
|
891
|
+
)
|
892
|
+
return (
|
893
|
+
<float_t> (known_phase - measured_phase),
|
894
|
+
<float_t> (known_modulation / measured_modulation)
|
895
|
+
)
|
896
|
+
|
897
|
+
|
898
|
+
@cython.ufunc
|
899
|
+
cdef (float_t, float_t) _phasor_at_harmonic(
|
900
|
+
float_t real,
|
901
|
+
int harmonic,
|
902
|
+
int other_harmonic,
|
903
|
+
) noexcept nogil:
|
904
|
+
"""Return phasor coordinates on semicircle at other harmonic."""
|
905
|
+
if isnan(real):
|
906
|
+
return <float_t> NAN, <float_t> NAN
|
907
|
+
|
908
|
+
if real <= 0.0:
|
909
|
+
return 0.0, 0.0
|
910
|
+
if real >= 1.0:
|
911
|
+
return 1.0, 0.0
|
912
|
+
|
913
|
+
harmonic *= harmonic
|
914
|
+
other_harmonic *= other_harmonic
|
915
|
+
real = (
|
916
|
+
harmonic * real / (other_harmonic + (harmonic - other_harmonic) * real)
|
917
|
+
)
|
918
|
+
|
919
|
+
return real, <float_t> sqrt(real - real * real)
|
920
|
+
|
921
|
+
|
922
|
+
@cython.ufunc
|
923
|
+
cdef (float_t, float_t) _phasor_multiply(
|
924
|
+
float_t real1,
|
925
|
+
float_t imag1,
|
926
|
+
float_t real2,
|
927
|
+
float_t imag2,
|
928
|
+
) noexcept nogil:
|
929
|
+
"""Return multiplication of two phasors."""
|
930
|
+
return real1 * real2 - imag1 * imag2, real1 * imag2 + imag1 * real2
|
931
|
+
|
932
|
+
|
933
|
+
@cython.ufunc
|
934
|
+
cdef (float_t, float_t) _phasor_divide(
|
935
|
+
float_t real1,
|
936
|
+
float_t imag1,
|
937
|
+
float_t real2,
|
938
|
+
float_t imag2,
|
939
|
+
) noexcept nogil:
|
940
|
+
"""Return division of two phasors."""
|
941
|
+
cdef:
|
942
|
+
float_t denom = real2 * real2 + imag2 * imag2
|
943
|
+
|
944
|
+
if isnan(denom) or denom == 0.0:
|
945
|
+
return <float_t> NAN, <float_t> NAN
|
946
|
+
|
947
|
+
return (
|
948
|
+
(real1 * real2 + imag1 * imag2) / denom,
|
949
|
+
(imag1 * real2 - real1 * imag2) / denom
|
950
|
+
)
|
951
|
+
|
952
|
+
|
953
|
+
###############################################################################
|
954
|
+
# Geometry ufuncs
|
955
|
+
|
956
|
+
@cython.ufunc
|
957
|
+
cdef short _is_inside_range(
|
958
|
+
float_t x, # point
|
959
|
+
float_t y,
|
960
|
+
float_t xmin, # x range
|
961
|
+
float_t xmax,
|
962
|
+
float_t ymin, # y range
|
963
|
+
float_t ymax
|
964
|
+
) noexcept nogil:
|
965
|
+
"""Return whether point is inside range.
|
966
|
+
|
967
|
+
Range includes lower but not upper limit.
|
968
|
+
|
969
|
+
"""
|
970
|
+
if isnan(x) or isnan(y):
|
971
|
+
return False
|
972
|
+
|
973
|
+
return x >= xmin and x < xmax and y >= ymin and y < ymax
|
974
|
+
|
975
|
+
|
976
|
+
@cython.ufunc
|
977
|
+
cdef short _is_inside_rectangle(
|
978
|
+
float_t x, # point
|
979
|
+
float_t y,
|
980
|
+
float_t x0, # segment start
|
981
|
+
float_t y0,
|
982
|
+
float_t x1, # segment end
|
983
|
+
float_t y1,
|
984
|
+
float_t r, # half width
|
985
|
+
) noexcept nogil:
|
986
|
+
"""Return whether point is in rectangle.
|
987
|
+
|
988
|
+
The rectangle is defined by central line segment and half width.
|
989
|
+
|
990
|
+
"""
|
991
|
+
cdef:
|
992
|
+
float_t t
|
993
|
+
|
994
|
+
if r <= 0.0 or isnan(x) or isnan(y):
|
995
|
+
return False
|
996
|
+
|
997
|
+
# normalize coordinates
|
998
|
+
# x1 = 0
|
999
|
+
# y1 = 0
|
1000
|
+
x0 -= x1
|
1001
|
+
y0 -= y1
|
1002
|
+
x -= x1
|
1003
|
+
y -= y1
|
1004
|
+
# square of line length
|
1005
|
+
t = x0 * x0 + y0 * y0
|
1006
|
+
if t <= 0.0:
|
1007
|
+
return x * x + y * y <= r * r
|
1008
|
+
# projection of point on line using clamped dot product
|
1009
|
+
t = (x * x0 + y * y0) / t
|
1010
|
+
if t < 0.0 or t > 1.0:
|
1011
|
+
return False
|
1012
|
+
# compare square of lengths of projection and radius
|
1013
|
+
x -= t * x0
|
1014
|
+
y -= t * y0
|
1015
|
+
return x * x + y * y <= r * r
|
1016
|
+
|
1017
|
+
|
1018
|
+
@cython.ufunc
|
1019
|
+
cdef short _is_inside_polar_rectangle(
|
1020
|
+
float_t x, # point
|
1021
|
+
float_t y,
|
1022
|
+
float_t angle_min, # phase, -pi to pi
|
1023
|
+
float_t angle_max,
|
1024
|
+
float_t distance_min, # modulation
|
1025
|
+
float_t distance_max,
|
1026
|
+
) noexcept nogil:
|
1027
|
+
"""Return whether point is inside polar rectangle.
|
1028
|
+
|
1029
|
+
Angles should be in range -pi to pi, else performance is degraded.
|
1030
|
+
|
1031
|
+
"""
|
1032
|
+
cdef:
|
1033
|
+
double t
|
1034
|
+
|
1035
|
+
if isnan(x) or isnan(y):
|
1036
|
+
return False
|
1037
|
+
|
1038
|
+
if distance_min > distance_max:
|
1039
|
+
distance_min, distance_max = distance_max, distance_min
|
1040
|
+
t = hypot(x, y)
|
1041
|
+
if t < distance_min or t > distance_max or t == 0.0:
|
1042
|
+
return False
|
1043
|
+
|
1044
|
+
if angle_min < -M_PI or angle_min > M_PI:
|
1045
|
+
angle_min = <float_t> atan2(sin(angle_min), cos(angle_min))
|
1046
|
+
if angle_max < -M_PI or angle_max > M_PI:
|
1047
|
+
angle_max = <float_t> atan2(sin(angle_max), cos(angle_max))
|
1048
|
+
if angle_min > angle_max:
|
1049
|
+
angle_min, angle_max = angle_max, angle_min
|
1050
|
+
t = <float_t> atan2(y, x)
|
1051
|
+
if t < angle_min or t > angle_max:
|
1052
|
+
return False
|
1053
|
+
|
1054
|
+
return True
|
1055
|
+
|
1056
|
+
|
1057
|
+
@cython.ufunc
|
1058
|
+
cdef short _is_inside_circle(
|
1059
|
+
float_t x, # point
|
1060
|
+
float_t y,
|
1061
|
+
float_t x0, # circle center
|
1062
|
+
float_t y0,
|
1063
|
+
float_t r, # circle radius
|
1064
|
+
) noexcept nogil:
|
1065
|
+
"""Return whether point is inside circle."""
|
1066
|
+
if r <= 0.0 or isnan(x) or isnan(y):
|
1067
|
+
return False
|
1068
|
+
|
1069
|
+
x -= x0
|
1070
|
+
y -= y0
|
1071
|
+
return x * x + y * y <= r * r
|
1072
|
+
|
1073
|
+
|
1074
|
+
@cython.ufunc
|
1075
|
+
cdef short _is_inside_ellipse(
|
1076
|
+
float_t x, # point
|
1077
|
+
float_t y,
|
1078
|
+
float_t x0, # ellipse center
|
1079
|
+
float_t y0,
|
1080
|
+
float_t a, # ellipse radii
|
1081
|
+
float_t b,
|
1082
|
+
float_t phi, # ellipse angle
|
1083
|
+
) noexcept nogil:
|
1084
|
+
"""Return whether point is inside ellipse.
|
1085
|
+
|
1086
|
+
Same as _is_inside_circle if a == b.
|
1087
|
+
Consider using _is_inside_ellipse_ instead, which should be faster
|
1088
|
+
for arrays.
|
1089
|
+
|
1090
|
+
"""
|
1091
|
+
cdef:
|
1092
|
+
float_t sina, cosa
|
1093
|
+
|
1094
|
+
if a <= 0.0 or b <= 0.0 or isnan(x) or isnan(y):
|
1095
|
+
return False
|
1096
|
+
|
1097
|
+
x -= x0
|
1098
|
+
y -= y0
|
1099
|
+
if a == b:
|
1100
|
+
# circle
|
1101
|
+
return x * x + y * y <= a * a
|
1102
|
+
sina = <float_t> sin(phi)
|
1103
|
+
cosa = <float_t> cos(phi)
|
1104
|
+
x0 = (cosa * x + sina * y) / a
|
1105
|
+
y0 = (sina * x - cosa * y) / b
|
1106
|
+
return x0 * x0 + y0 * y0 <= 1.0
|
1107
|
+
|
1108
|
+
|
1109
|
+
@cython.ufunc
|
1110
|
+
cdef short _is_inside_ellipse_(
|
1111
|
+
float_t x, # point
|
1112
|
+
float_t y,
|
1113
|
+
float_t x0, # ellipse center
|
1114
|
+
float_t y0,
|
1115
|
+
float_t a, # ellipse radii
|
1116
|
+
float_t b,
|
1117
|
+
float_t sina, # sin/cos of ellipse angle
|
1118
|
+
float_t cosa,
|
1119
|
+
) noexcept nogil:
|
1120
|
+
"""Return whether point is inside ellipse.
|
1121
|
+
|
1122
|
+
Use pre-calculated sin(angle) and cos(angle).
|
1123
|
+
|
1124
|
+
"""
|
1125
|
+
if a <= 0.0 or b <= 0.0 or isnan(x) or isnan(y):
|
1126
|
+
return False
|
1127
|
+
|
1128
|
+
x -= x0
|
1129
|
+
y -= y0
|
1130
|
+
if a == b:
|
1131
|
+
# circle
|
1132
|
+
return x * x + y * y <= a * a
|
1133
|
+
x0 = (cosa * x + sina * y) / a
|
1134
|
+
y0 = (sina * x - cosa * y) / b
|
1135
|
+
return x0 * x0 + y0 * y0 <= 1.0
|
1136
|
+
|
1137
|
+
|
1138
|
+
@cython.ufunc
|
1139
|
+
cdef short _is_inside_stadium(
|
1140
|
+
float_t x, # point
|
1141
|
+
float_t y,
|
1142
|
+
float_t x0, # line start
|
1143
|
+
float_t y0,
|
1144
|
+
float_t x1, # line end
|
1145
|
+
float_t y1,
|
1146
|
+
float_t r, # radius
|
1147
|
+
) noexcept nogil:
|
1148
|
+
"""Return whether point is inside stadium.
|
1149
|
+
|
1150
|
+
A stadium shape is a thick line with rounded ends.
|
1151
|
+
Same as _is_near_segment.
|
1152
|
+
|
1153
|
+
"""
|
1154
|
+
cdef:
|
1155
|
+
float_t t
|
1156
|
+
|
1157
|
+
if r <= 0.0 or isnan(x) or isnan(y):
|
1158
|
+
return False
|
1159
|
+
|
1160
|
+
# normalize coordinates
|
1161
|
+
# x1 = 0
|
1162
|
+
# y1 = 0
|
1163
|
+
x0 -= x1
|
1164
|
+
y0 -= y1
|
1165
|
+
x -= x1
|
1166
|
+
y -= y1
|
1167
|
+
# square of line length
|
1168
|
+
t = x0 * x0 + y0 * y0
|
1169
|
+
if t <= 0.0:
|
1170
|
+
return x * x + y * y <= r * r
|
1171
|
+
# projection of point on line using clamped dot product
|
1172
|
+
t = (x * x0 + y * y0) / t
|
1173
|
+
t = <float_t> max(0.0, min(1.0, t))
|
1174
|
+
# compare square of lengths of projection and radius
|
1175
|
+
x -= t * x0
|
1176
|
+
y -= t * y0
|
1177
|
+
return x * x + y * y <= r * r
|
1178
|
+
|
1179
|
+
|
1180
|
+
# function alias
|
1181
|
+
_is_near_segment = _is_inside_stadium
|
1182
|
+
|
1183
|
+
|
1184
|
+
@cython.ufunc
|
1185
|
+
cdef short _is_near_line(
|
1186
|
+
float_t x, # point
|
1187
|
+
float_t y,
|
1188
|
+
float_t x0, # line start
|
1189
|
+
float_t y0,
|
1190
|
+
float_t x1, # line end
|
1191
|
+
float_t y1,
|
1192
|
+
float_t r, # distance
|
1193
|
+
) noexcept nogil:
|
1194
|
+
"""Return whether point is close to line."""
|
1195
|
+
cdef:
|
1196
|
+
float_t t
|
1197
|
+
|
1198
|
+
if r <= 0.0 or isnan(x) or isnan(y):
|
1199
|
+
return False
|
1200
|
+
|
1201
|
+
# normalize coordinates
|
1202
|
+
# x1 = 0
|
1203
|
+
# y1 = 0
|
1204
|
+
x0 -= x1
|
1205
|
+
y0 -= y1
|
1206
|
+
x -= x1
|
1207
|
+
y -= y1
|
1208
|
+
# square of line length
|
1209
|
+
t = x0 * x0 + y0 * y0
|
1210
|
+
if t <= 0.0:
|
1211
|
+
return x * x + y * y <= r * r
|
1212
|
+
# projection of point on line using clamped dot product
|
1213
|
+
t = (x * x0 + y * y0) / t
|
1214
|
+
# compare square of lengths of projection and radius
|
1215
|
+
x -= t * x0
|
1216
|
+
y -= t * y0
|
1217
|
+
return x * x + y * y <= r * r
|
1218
|
+
|
1219
|
+
|
1220
|
+
@cython.ufunc
|
1221
|
+
cdef (float_t, float_t) _point_on_segment(
|
1222
|
+
float_t x, # point
|
1223
|
+
float_t y,
|
1224
|
+
float_t x0, # segment start
|
1225
|
+
float_t y0,
|
1226
|
+
float_t x1, # segment end
|
1227
|
+
float_t y1,
|
1228
|
+
) noexcept nogil:
|
1229
|
+
"""Return point projected onto line segment."""
|
1230
|
+
cdef:
|
1231
|
+
float_t t
|
1232
|
+
|
1233
|
+
if isnan(x) or isnan(y):
|
1234
|
+
return <float_t> NAN, <float_t> NAN
|
1235
|
+
|
1236
|
+
# normalize coordinates
|
1237
|
+
# x1 = 0
|
1238
|
+
# y1 = 0
|
1239
|
+
x0 -= x1
|
1240
|
+
y0 -= y1
|
1241
|
+
x -= x1
|
1242
|
+
y -= y1
|
1243
|
+
# square of line length
|
1244
|
+
t = x0 * x0 + y0 * y0
|
1245
|
+
if t <= 0.0:
|
1246
|
+
return x0, y0
|
1247
|
+
# projection of point on line
|
1248
|
+
t = (x * x0 + y * y0) / t
|
1249
|
+
# clamp to line segment
|
1250
|
+
if t < 0.0:
|
1251
|
+
t = 0.0
|
1252
|
+
elif t > 1.0:
|
1253
|
+
t = 1.0
|
1254
|
+
x1 += t * x0
|
1255
|
+
y1 += t * y0
|
1256
|
+
return x1, y1
|
1257
|
+
|
1258
|
+
|
1259
|
+
@cython.ufunc
|
1260
|
+
cdef (float_t, float_t) _point_on_line(
|
1261
|
+
float_t x, # point
|
1262
|
+
float_t y,
|
1263
|
+
float_t x0, # line start
|
1264
|
+
float_t y0,
|
1265
|
+
float_t x1, # line end
|
1266
|
+
float_t y1,
|
1267
|
+
) noexcept nogil:
|
1268
|
+
"""Return point projected onto line."""
|
1269
|
+
cdef:
|
1270
|
+
float_t t
|
1271
|
+
|
1272
|
+
if isnan(x) or isnan(y):
|
1273
|
+
return <float_t> NAN, <float_t> NAN
|
1274
|
+
|
1275
|
+
# normalize coordinates
|
1276
|
+
# x1 = 0
|
1277
|
+
# y1 = 0
|
1278
|
+
x0 -= x1
|
1279
|
+
y0 -= y1
|
1280
|
+
x -= x1
|
1281
|
+
y -= y1
|
1282
|
+
# square of line length
|
1283
|
+
t = x0 * x0 + y0 * y0
|
1284
|
+
if t <= 0.0:
|
1285
|
+
return x0, y0
|
1286
|
+
# projection of point on line
|
1287
|
+
t = (x * x0 + y * y0) / t
|
1288
|
+
x1 += t * x0
|
1289
|
+
y1 += t * y0
|
1290
|
+
return x1, y1
|
1291
|
+
|
1292
|
+
|
1293
|
+
@cython.ufunc
|
1294
|
+
cdef float_t _fraction_on_segment(
|
1295
|
+
float_t x, # point
|
1296
|
+
float_t y,
|
1297
|
+
float_t x0, # segment start
|
1298
|
+
float_t y0,
|
1299
|
+
float_t x1, # segment end
|
1300
|
+
float_t y1,
|
1301
|
+
) noexcept nogil:
|
1302
|
+
"""Return normalized fraction of point projected onto line segment."""
|
1303
|
+
cdef:
|
1304
|
+
float_t t
|
1305
|
+
|
1306
|
+
if isnan(x) or isnan(y):
|
1307
|
+
return <float_t> NAN
|
1308
|
+
|
1309
|
+
# normalize coordinates
|
1310
|
+
x -= x1
|
1311
|
+
y -= y1
|
1312
|
+
x0 -= x1
|
1313
|
+
y0 -= y1
|
1314
|
+
# x1 = 0
|
1315
|
+
# y1 = 0
|
1316
|
+
# square of line length
|
1317
|
+
t = x0 * x0 + y0 * y0
|
1318
|
+
if t <= 0.0:
|
1319
|
+
# not a line segment
|
1320
|
+
return 0.0
|
1321
|
+
# projection of point on line
|
1322
|
+
t = (x * x0 + y * y0) / t
|
1323
|
+
# clamp to line segment
|
1324
|
+
if t < 0.0:
|
1325
|
+
t = 0.0
|
1326
|
+
elif t > 1.0:
|
1327
|
+
t = 1.0
|
1328
|
+
return t
|
1329
|
+
|
1330
|
+
|
1331
|
+
@cython.ufunc
|
1332
|
+
cdef float_t _fraction_on_line(
|
1333
|
+
float_t x, # point
|
1334
|
+
float_t y,
|
1335
|
+
float_t x0, # line start
|
1336
|
+
float_t y0,
|
1337
|
+
float_t x1, # line end
|
1338
|
+
float_t y1,
|
1339
|
+
) noexcept nogil:
|
1340
|
+
"""Return normalized fraction of point projected onto line."""
|
1341
|
+
cdef:
|
1342
|
+
float_t t
|
1343
|
+
|
1344
|
+
if isnan(x) or isnan(y):
|
1345
|
+
return <float_t> NAN
|
1346
|
+
|
1347
|
+
# normalize coordinates
|
1348
|
+
x -= x1
|
1349
|
+
y -= y1
|
1350
|
+
x0 -= x1
|
1351
|
+
y0 -= y1
|
1352
|
+
# x1 = 0
|
1353
|
+
# y1 = 0
|
1354
|
+
# square of line length
|
1355
|
+
t = x0 * x0 + y0 * y0
|
1356
|
+
if t <= 0.0:
|
1357
|
+
# not a line segment
|
1358
|
+
return 1.0
|
1359
|
+
# projection of point on line
|
1360
|
+
t = (x * x0 + y * y0) / t
|
1361
|
+
return t
|
1362
|
+
|
1363
|
+
|
1364
|
+
@cython.ufunc
|
1365
|
+
cdef float_t _distance_from_point(
|
1366
|
+
float_t x, # point
|
1367
|
+
float_t y,
|
1368
|
+
float_t x0, # other point
|
1369
|
+
float_t y0,
|
1370
|
+
) noexcept nogil:
|
1371
|
+
"""Return distance from point."""
|
1372
|
+
if isnan(x) or isnan(y): # or isnan(x0) or isnan(y0)
|
1373
|
+
return <float_t> NAN
|
1374
|
+
|
1375
|
+
return <float_t> hypot(x - x0, y - y0)
|
1376
|
+
|
1377
|
+
|
1378
|
+
@cython.ufunc
|
1379
|
+
cdef float_t _distance_from_segment(
|
1380
|
+
float_t x, # point
|
1381
|
+
float_t y,
|
1382
|
+
float_t x0, # segment start
|
1383
|
+
float_t y0,
|
1384
|
+
float_t x1, # segment end
|
1385
|
+
float_t y1,
|
1386
|
+
) noexcept nogil:
|
1387
|
+
"""Return distance from segment."""
|
1388
|
+
cdef:
|
1389
|
+
float_t t
|
1390
|
+
|
1391
|
+
if isnan(x) or isnan(y):
|
1392
|
+
return <float_t> NAN
|
1393
|
+
|
1394
|
+
# normalize coordinates
|
1395
|
+
# x1 = 0
|
1396
|
+
# y1 = 0
|
1397
|
+
x0 -= x1
|
1398
|
+
y0 -= y1
|
1399
|
+
x -= x1
|
1400
|
+
y -= y1
|
1401
|
+
# square of line length
|
1402
|
+
t = x0 * x0 + y0 * y0
|
1403
|
+
if t <= 0.0:
|
1404
|
+
return <float_t> hypot(x, y)
|
1405
|
+
# projection of point on line using dot product
|
1406
|
+
t = (x * x0 + y * y0) / t
|
1407
|
+
if t > 1.0:
|
1408
|
+
x -= x0
|
1409
|
+
y -= y0
|
1410
|
+
elif t > 0.0:
|
1411
|
+
x -= t * x0
|
1412
|
+
y -= t * y0
|
1413
|
+
return <float_t> hypot(x, y)
|
1414
|
+
|
1415
|
+
|
1416
|
+
@cython.ufunc
|
1417
|
+
cdef float_t _distance_from_line(
|
1418
|
+
float_t x, # point
|
1419
|
+
float_t y,
|
1420
|
+
float_t x0, # line start
|
1421
|
+
float_t y0,
|
1422
|
+
float_t x1, # line end
|
1423
|
+
float_t y1,
|
1424
|
+
) noexcept nogil:
|
1425
|
+
"""Return distance from line."""
|
1426
|
+
cdef:
|
1427
|
+
float_t t
|
1428
|
+
|
1429
|
+
if isnan(x) or isnan(y):
|
1430
|
+
return <float_t> NAN
|
1431
|
+
|
1432
|
+
# normalize coordinates
|
1433
|
+
# x1 = 0
|
1434
|
+
# y1 = 0
|
1435
|
+
x0 -= x1
|
1436
|
+
y0 -= y1
|
1437
|
+
x -= x1
|
1438
|
+
y -= y1
|
1439
|
+
# square of line length
|
1440
|
+
t = x0 * x0 + y0 * y0
|
1441
|
+
if t <= 0.0:
|
1442
|
+
return <float_t> hypot(x, y)
|
1443
|
+
# projection of point on line using dot product
|
1444
|
+
t = (x * x0 + y * y0) / t
|
1445
|
+
x -= t * x0
|
1446
|
+
y -= t * y0
|
1447
|
+
return <float_t> hypot(x, y)
|
1448
|
+
|
1449
|
+
|
1450
|
+
@cython.ufunc
|
1451
|
+
cdef (double, double, double) _segment_direction_and_length(
|
1452
|
+
float_t x0, # segment start
|
1453
|
+
float_t y0,
|
1454
|
+
float_t x1, # segment end
|
1455
|
+
float_t y1,
|
1456
|
+
) noexcept nogil:
|
1457
|
+
"""Return direction and length of line segment."""
|
1458
|
+
cdef:
|
1459
|
+
float_t length
|
1460
|
+
|
1461
|
+
if isnan(x0) or isnan(y0) or isnan(x1) or isnan(y1):
|
1462
|
+
return NAN, NAN, 0.0
|
1463
|
+
|
1464
|
+
x1 -= x0
|
1465
|
+
y1 -= y0
|
1466
|
+
length = <float_t> hypot(x1, y1)
|
1467
|
+
if length <= 0.0:
|
1468
|
+
return NAN, NAN, 0.0
|
1469
|
+
x1 /= length
|
1470
|
+
y1 /= length
|
1471
|
+
return x1, y1, length
|
1472
|
+
|
1473
|
+
|
1474
|
+
@cython.ufunc
|
1475
|
+
cdef (double, double, double, double) _intersection_circle_circle(
|
1476
|
+
float_t x0, # circle 0
|
1477
|
+
float_t y0,
|
1478
|
+
float_t r0,
|
1479
|
+
float_t x1, # circle 1
|
1480
|
+
float_t y1,
|
1481
|
+
float_t r1,
|
1482
|
+
) noexcept nogil:
|
1483
|
+
"""Return coordinates of intersections of two circles."""
|
1484
|
+
cdef:
|
1485
|
+
double dx, dy, dr, ll, dd, hd, ld
|
1486
|
+
|
1487
|
+
if (
|
1488
|
+
isnan(x0)
|
1489
|
+
or isnan(y0)
|
1490
|
+
or isnan(r0)
|
1491
|
+
or isnan(x1)
|
1492
|
+
or isnan(y1)
|
1493
|
+
or isnan(r1)
|
1494
|
+
or r0 == 0.0
|
1495
|
+
or r1 == 0.0
|
1496
|
+
):
|
1497
|
+
return NAN, NAN, NAN, NAN
|
1498
|
+
|
1499
|
+
dx = x1 - x0
|
1500
|
+
dy = y1 - y0
|
1501
|
+
dr = hypot(dx, dy)
|
1502
|
+
if dr <= 0.0:
|
1503
|
+
# circle positions identical
|
1504
|
+
return NAN, NAN, NAN, NAN
|
1505
|
+
ll = (r0 * r0 - r1 * r1 + dr * dr) / (dr + dr)
|
1506
|
+
dd = r0 * r0 - ll * ll
|
1507
|
+
if dd < 0.0 or dr <= 0.0:
|
1508
|
+
# circles not intersecting
|
1509
|
+
return NAN, NAN, NAN, NAN
|
1510
|
+
hd = sqrt(dd) / dr
|
1511
|
+
ld = ll / dr
|
1512
|
+
return (
|
1513
|
+
ld * dx + hd * dy + x0,
|
1514
|
+
ld * dy - hd * dx + y0,
|
1515
|
+
ld * dx - hd * dy + x0,
|
1516
|
+
ld * dy + hd * dx + y0,
|
1517
|
+
)
|
1518
|
+
|
1519
|
+
|
1520
|
+
@cython.ufunc
|
1521
|
+
cdef (double, double, double, double) _intersection_circle_line(
|
1522
|
+
float_t x, # circle
|
1523
|
+
float_t y,
|
1524
|
+
float_t r,
|
1525
|
+
float_t x0, # line start
|
1526
|
+
float_t y0,
|
1527
|
+
float_t x1, # line end
|
1528
|
+
float_t y1,
|
1529
|
+
) noexcept nogil:
|
1530
|
+
"""Return coordinates of intersections of circle and line."""
|
1531
|
+
cdef:
|
1532
|
+
double dx, dy, dr, dd, rdd
|
1533
|
+
|
1534
|
+
if (
|
1535
|
+
isnan(r)
|
1536
|
+
or isnan(x)
|
1537
|
+
or isnan(y)
|
1538
|
+
or isnan(x0)
|
1539
|
+
or isnan(y0)
|
1540
|
+
or isnan(x1)
|
1541
|
+
or isnan(y1)
|
1542
|
+
or r == 0.0
|
1543
|
+
):
|
1544
|
+
return NAN, NAN, NAN, NAN
|
1545
|
+
|
1546
|
+
dx = x1 - x0
|
1547
|
+
dy = y1 - y0
|
1548
|
+
dr = dx * dx + dy * dy
|
1549
|
+
dd = (x0 - x) * (y1 - y) - (x1 - x) * (y0 - y)
|
1550
|
+
rdd = r * r * dr - dd * dd # discriminant
|
1551
|
+
if rdd < 0.0 or dr <= 0.0:
|
1552
|
+
# no intersection
|
1553
|
+
return NAN, NAN, NAN, NAN
|
1554
|
+
rdd = sqrt(rdd)
|
1555
|
+
return (
|
1556
|
+
x + (dd * dy + copysign(1.0, dy) * dx * rdd) / dr,
|
1557
|
+
y + (-dd * dx + fabs(dy) * rdd) / dr,
|
1558
|
+
x + (dd * dy - copysign(1.0, dy) * dx * rdd) / dr,
|
1559
|
+
y + (-dd * dx - fabs(dy) * rdd) / dr,
|
1560
|
+
)
|
1561
|
+
|
1562
|
+
###############################################################################
|
1563
|
+
# Blend ufuncs
|
1564
|
+
|
1565
|
+
|
1566
|
+
@cython.ufunc
|
1567
|
+
cdef float_t _blend_normal(
|
1568
|
+
float_t a, # base layer
|
1569
|
+
float_t b, # blend layer
|
1570
|
+
) noexcept nogil:
|
1571
|
+
"""Return blended layers using `normal` mode."""
|
1572
|
+
if isnan(b):
|
1573
|
+
return a
|
1574
|
+
return b
|
1575
|
+
|
1576
|
+
|
1577
|
+
@cython.ufunc
|
1578
|
+
cdef float_t _blend_multiply(
|
1579
|
+
float_t a, # base layer
|
1580
|
+
float_t b, # blend layer
|
1581
|
+
) noexcept nogil:
|
1582
|
+
"""Return blended layers using `multiply` mode."""
|
1583
|
+
if isnan(b):
|
1584
|
+
return a
|
1585
|
+
return a * b
|
1586
|
+
|
1587
|
+
|
1588
|
+
@cython.ufunc
|
1589
|
+
cdef float_t _blend_screen(
|
1590
|
+
float_t a, # base layer
|
1591
|
+
float_t b, # blend layer
|
1592
|
+
) noexcept nogil:
|
1593
|
+
"""Return blended layers using `screen` mode."""
|
1594
|
+
if isnan(b):
|
1595
|
+
return a
|
1596
|
+
return <float_t> (1.0 - (1.0 - a) * (1.0 - b))
|
1597
|
+
|
1598
|
+
|
1599
|
+
@cython.ufunc
|
1600
|
+
cdef float_t _blend_overlay(
|
1601
|
+
float_t a, # base layer
|
1602
|
+
float_t b, # blend layer
|
1603
|
+
) noexcept nogil:
|
1604
|
+
"""Return blended layers using `overlay` mode."""
|
1605
|
+
if isnan(b) or isnan(a):
|
1606
|
+
return a
|
1607
|
+
if a < 0.5:
|
1608
|
+
return <float_t> (2.0 * a * b)
|
1609
|
+
return <float_t> (1.0 - 2.0 * (1.0 - a) * (1.0 - b))
|
1610
|
+
|
1611
|
+
|
1612
|
+
@cython.ufunc
|
1613
|
+
cdef float_t _blend_darken(
|
1614
|
+
float_t a, # base layer
|
1615
|
+
float_t b, # blend layer
|
1616
|
+
) noexcept nogil:
|
1617
|
+
"""Return blended layers using `darken` mode."""
|
1618
|
+
if isnan(b) or isnan(a):
|
1619
|
+
return a
|
1620
|
+
return <float_t> min(a, b)
|
1621
|
+
|
1622
|
+
|
1623
|
+
@cython.ufunc
|
1624
|
+
cdef float_t _blend_lighten(
|
1625
|
+
float_t a, # base layer
|
1626
|
+
float_t b, # blend layer
|
1627
|
+
) noexcept nogil:
|
1628
|
+
"""Return blended layers using `lighten` mode."""
|
1629
|
+
if isnan(b) or isnan(a):
|
1630
|
+
return a
|
1631
|
+
return <float_t> max(a, b)
|
1632
|
+
|
1633
|
+
###############################################################################
|
1634
|
+
# Threshold ufuncs
|
1635
|
+
|
1636
|
+
|
1637
|
+
@cython.ufunc
|
1638
|
+
cdef (double, double, double) _phasor_threshold_open(
|
1639
|
+
float_t mean,
|
1640
|
+
float_t real,
|
1641
|
+
float_t imag,
|
1642
|
+
float_t mean_min,
|
1643
|
+
float_t mean_max,
|
1644
|
+
float_t real_min,
|
1645
|
+
float_t real_max,
|
1646
|
+
float_t imag_min,
|
1647
|
+
float_t imag_max,
|
1648
|
+
float_t phase_min,
|
1649
|
+
float_t phase_max,
|
1650
|
+
float_t modulation_min,
|
1651
|
+
float_t modulation_max,
|
1652
|
+
) noexcept nogil:
|
1653
|
+
"""Return thresholded values by open intervals."""
|
1654
|
+
cdef:
|
1655
|
+
double phi = NAN
|
1656
|
+
double mod = NAN
|
1657
|
+
|
1658
|
+
if isnan(mean) or isnan(real) or isnan(imag):
|
1659
|
+
return <float_t> NAN, <float_t> NAN, <float_t> NAN
|
1660
|
+
|
1661
|
+
if not isnan(mean_min) and mean <= mean_min:
|
1662
|
+
return <float_t> NAN, <float_t> NAN, <float_t> NAN
|
1663
|
+
if not isnan(mean_max) and mean >= mean_max:
|
1664
|
+
return <float_t> NAN, <float_t> NAN, <float_t> NAN
|
1665
|
+
|
1666
|
+
if not isnan(real_min) and real <= real_min:
|
1667
|
+
return <float_t> NAN, <float_t> NAN, <float_t> NAN
|
1668
|
+
if not isnan(real_max) and real >= real_max:
|
1669
|
+
return <float_t> NAN, <float_t> NAN, <float_t> NAN
|
1670
|
+
|
1671
|
+
if not isnan(imag_min) and imag <= imag_min:
|
1672
|
+
return <float_t> NAN, <float_t> NAN, <float_t> NAN
|
1673
|
+
if not isnan(imag_max) and imag >= imag_max:
|
1674
|
+
return <float_t> NAN, <float_t> NAN, <float_t> NAN
|
1675
|
+
|
1676
|
+
if not isnan(modulation_min):
|
1677
|
+
mod = real * real + imag * imag
|
1678
|
+
if mod <= modulation_min * modulation_min:
|
1679
|
+
return <float_t> NAN, <float_t> NAN, <float_t> NAN
|
1680
|
+
if not isnan(modulation_max):
|
1681
|
+
if isnan(mod):
|
1682
|
+
mod = real * real + imag * imag
|
1683
|
+
if mod >= modulation_max * modulation_max:
|
1684
|
+
return <float_t> NAN, <float_t> NAN, <float_t> NAN
|
1685
|
+
|
1686
|
+
if not isnan(phase_min):
|
1687
|
+
phi = atan2(imag, real)
|
1688
|
+
if phi <= phase_min:
|
1689
|
+
return <float_t> NAN, <float_t> NAN, <float_t> NAN
|
1690
|
+
if not isnan(phase_max):
|
1691
|
+
if isnan(phi):
|
1692
|
+
phi = atan2(imag, real)
|
1693
|
+
if phi >= phase_max:
|
1694
|
+
return <float_t> NAN, <float_t> NAN, <float_t> NAN
|
1695
|
+
|
1696
|
+
return mean, real, imag
|
1697
|
+
|
1698
|
+
|
1699
|
+
@cython.ufunc
|
1700
|
+
cdef (double, double, double) _phasor_threshold_closed(
|
1701
|
+
float_t mean,
|
1702
|
+
float_t real,
|
1703
|
+
float_t imag,
|
1704
|
+
float_t mean_min,
|
1705
|
+
float_t mean_max,
|
1706
|
+
float_t real_min,
|
1707
|
+
float_t real_max,
|
1708
|
+
float_t imag_min,
|
1709
|
+
float_t imag_max,
|
1710
|
+
float_t phase_min,
|
1711
|
+
float_t phase_max,
|
1712
|
+
float_t modulation_min,
|
1713
|
+
float_t modulation_max,
|
1714
|
+
) noexcept nogil:
|
1715
|
+
"""Return thresholded values by closed intervals."""
|
1716
|
+
cdef:
|
1717
|
+
double phi = NAN
|
1718
|
+
double mod = NAN
|
1719
|
+
|
1720
|
+
if isnan(mean) or isnan(real) or isnan(imag):
|
1721
|
+
return <float_t> NAN, <float_t> NAN, <float_t> NAN
|
1722
|
+
|
1723
|
+
if not isnan(mean_min) and mean < mean_min:
|
1724
|
+
return <float_t> NAN, <float_t> NAN, <float_t> NAN
|
1725
|
+
if not isnan(mean_max) and mean > mean_max:
|
1726
|
+
return <float_t> NAN, <float_t> NAN, <float_t> NAN
|
1727
|
+
|
1728
|
+
if not isnan(real_min) and real < real_min:
|
1729
|
+
return <float_t> NAN, <float_t> NAN, <float_t> NAN
|
1730
|
+
if not isnan(real_max) and real > real_max:
|
1731
|
+
return <float_t> NAN, <float_t> NAN, <float_t> NAN
|
1732
|
+
|
1733
|
+
if not isnan(imag_min) and imag < imag_min:
|
1734
|
+
return <float_t> NAN, <float_t> NAN, <float_t> NAN
|
1735
|
+
if not isnan(imag_max) and imag > imag_max:
|
1736
|
+
return <float_t> NAN, <float_t> NAN, <float_t> NAN
|
1737
|
+
|
1738
|
+
if not isnan(modulation_min):
|
1739
|
+
mod = real * real + imag * imag
|
1740
|
+
if mod < modulation_min * modulation_min:
|
1741
|
+
return <float_t> NAN, <float_t> NAN, <float_t> NAN
|
1742
|
+
if not isnan(modulation_max):
|
1743
|
+
if isnan(mod):
|
1744
|
+
mod = real * real + imag * imag
|
1745
|
+
if mod > modulation_max * modulation_max:
|
1746
|
+
return <float_t> NAN, <float_t> NAN, <float_t> NAN
|
1747
|
+
|
1748
|
+
if not isnan(phase_min):
|
1749
|
+
phi = atan2(imag, real)
|
1750
|
+
if phi < phase_min:
|
1751
|
+
return <float_t> NAN, <float_t> NAN, <float_t> NAN
|
1752
|
+
if not isnan(phase_max):
|
1753
|
+
if isnan(phi):
|
1754
|
+
phi = atan2(imag, real)
|
1755
|
+
if phi > phase_max:
|
1756
|
+
return <float_t> NAN, <float_t> NAN, <float_t> NAN
|
1757
|
+
|
1758
|
+
return mean, real, imag
|
1759
|
+
|
1760
|
+
|
1761
|
+
@cython.ufunc
|
1762
|
+
cdef (double, double, double) _phasor_threshold_mean_open(
|
1763
|
+
float_t mean,
|
1764
|
+
float_t real,
|
1765
|
+
float_t imag,
|
1766
|
+
float_t mean_min,
|
1767
|
+
float_t mean_max,
|
1768
|
+
) noexcept nogil:
|
1769
|
+
"""Return thresholded values only by open interval of `mean`."""
|
1770
|
+
if isnan(mean) or isnan(real) or isnan(imag):
|
1771
|
+
return <float_t> NAN, <float_t> NAN, <float_t> NAN
|
1772
|
+
|
1773
|
+
if not isnan(mean_min) and mean <= mean_min:
|
1774
|
+
return <float_t> NAN, <float_t> NAN, <float_t> NAN
|
1775
|
+
if not isnan(mean_max) and mean >= mean_max:
|
1776
|
+
return <float_t> NAN, <float_t> NAN, <float_t> NAN
|
1777
|
+
|
1778
|
+
return mean, real, imag
|
1779
|
+
|
1780
|
+
|
1781
|
+
@cython.ufunc
|
1782
|
+
cdef (double, double, double) _phasor_threshold_mean_closed(
|
1783
|
+
float_t mean,
|
1784
|
+
float_t real,
|
1785
|
+
float_t imag,
|
1786
|
+
float_t mean_min,
|
1787
|
+
float_t mean_max,
|
1788
|
+
) noexcept nogil:
|
1789
|
+
"""Return thresholded values only by closed interval of `mean`."""
|
1790
|
+
if isnan(mean) or isnan(real) or isnan(imag):
|
1791
|
+
return <float_t> NAN, <float_t> NAN, <float_t> NAN
|
1792
|
+
|
1793
|
+
if not isnan(mean_min) and mean < mean_min:
|
1794
|
+
return <float_t> NAN, <float_t> NAN, <float_t> NAN
|
1795
|
+
if not isnan(mean_max) and mean > mean_max:
|
1796
|
+
return <float_t> NAN, <float_t> NAN, <float_t> NAN
|
1797
|
+
|
1798
|
+
return mean, real, imag
|
1799
|
+
|
1800
|
+
|
1801
|
+
@cython.ufunc
|
1802
|
+
cdef (double, double, double) _phasor_threshold_nan(
|
1803
|
+
float_t mean,
|
1804
|
+
float_t real,
|
1805
|
+
float_t imag,
|
1806
|
+
) noexcept nogil:
|
1807
|
+
"""Return the input values if any of them is not NaN."""
|
1808
|
+
if isnan(mean) or isnan(real) or isnan(imag):
|
1809
|
+
return <float_t> NAN, <float_t> NAN, <float_t> NAN
|
1810
|
+
|
1811
|
+
return mean, real, imag
|