phasorpy.phasor#
Calculate, convert, and reduce phasor coordinates.
The phasorpy.phasor
module provides functions to:
calculate phasor coordinates from time-resolved and spectral signals:
synthesize signals from phasor coordinates:
convert to and from polar coordinates (phase and modulation):
transform phasor coordinates:
reduce dimensionality of arrays of phasor coordinates:
find nearest neighbor phasor coordinates from other phasor coordinates:
- phasorpy.phasor.phasor_center(mean, real, imag, /, *, skip_axis=None, method='mean', nan_safe=True, **kwargs)[source]#
Return center of phasor coordinates.
- Parameters:
mean (array_like) – Intensity of phasor coordinates.
real (array_like) – Real component of phasor coordinates.
imag (array_like) – Imaginary component of phasor coordinates.
skip_axis (int or sequence of int, optional) – Axes in mean to excluded from center calculation. By default, all axes except harmonics are included.
method (str, optional) –
Method used for center calculation:
'mean'
: Arithmetic mean of phasor coordinates.'median'
: Spatial median of phasor coordinates.
nan_safe (bool, optional) – Ensure method is applied to same elements of input arrays. By default, distribute NaNs among input arrays before applying method. May be disabled if phasor coordinates were filtered by
phasor_threshold()
.**kwargs – Optional arguments passed to
numpy.nanmean()
ornumpy.nanmedian()
.
- Returns:
mean_center (ndarray) – Intensity center coordinates.
real_center (ndarray) – Real center coordinates.
imag_center (ndarray) – Imaginary center coordinates.
- Raises:
ValueError – If the specified method is not supported. If the shapes of mean, real, and imag do not match.
Examples
Compute center coordinates with the default ‘mean’ method:
>>> phasor_center( ... [2, 1, 2], [0.1, 0.2, 0.3], [0.4, 0.5, 0.6] ... ) (1.67, 0.2, 0.5)
Compute center coordinates with the ‘median’ method:
>>> phasor_center( ... [1, 2, 3], [0.1, 0.2, 0.3], [0.4, 0.5, 0.6], method='median' ... ) (2.0, 0.2, 0.5)
- phasorpy.phasor.phasor_divide(real, imag, divisor_real, divisor_imag, /, **kwargs)[source]#
Return complex division of two phasors.
Complex division can be used, for example, to deconvolve two signals such as exponential decay and instrument response functions.
- Parameters:
real (array_like) – Real component of phasor coordinates to divide.
imag (array_like) – Imaginary component of phasor coordinates to divide.
divisor_real (array_like) – Real component of phasor coordinates to divide by.
divisor_imag (array_like) – Imaginary component of phasor coordinates to divide by.
**kwargs – Optional arguments passed to numpy universal functions.
- Returns:
real (ndarray) – Real component of complex division.
imag (ndarray) – Imaginary component of complex division.
Notes
The phasor coordinates real (\(G\)) and imag (\(S\)) are divided by phasor coordinates divisor_real (\(g\)) and divisor_imag (\(s\)) according to:
\[ \begin{align}\begin{aligned}d &= g \cdot g + s \cdot s\\G' &= (G \cdot g + S \cdot s) / d\\S' &= (G \cdot s - S \cdot g) / d\end{aligned}\end{align} \]Examples
Divide two sets of phasor coordinates:
>>> phasor_divide([-0.16, -0.2], [0.22, 0.4], [0.5, 0.6], [0.7, 0.8]) (array([0.1, 0.2]), array([0.3, 0.4]))
- phasorpy.phasor.phasor_from_polar(phase, modulation, /, **kwargs)[source]#
Return phasor coordinates from polar coordinates.
- Parameters:
phase (array_like) – Angular component of polar coordinates in radians.
modulation (array_like) – Radial component of polar coordinates.
**kwargs –
- Returns:
real (ndarray) – Real component of phasor coordinates.
imag (ndarray) – Imaginary component of phasor coordinates.
See also
Notes
The polar coordinates phase (\(\phi\)) and modulation (\(M\)) are converted to phasor coordinates real (\(G\)) and imag (\(S\)) according to:
\[ \begin{align}\begin{aligned}G &= M \cdot \cos{\phi}\\S &= M \cdot \sin{\phi}\end{aligned}\end{align} \]Examples
Calculate phasor coordinates from three polar coordinates:
>>> phasor_from_polar( ... [0.0, math.pi / 4, math.pi / 2], [1.0, math.sqrt(0.5), 1.0] ... ) (array([1, 0.5, 0.0]), array([0, 0.5, 1]))
- phasorpy.phasor.phasor_from_signal(signal, /, *, axis=None, harmonic=None, sample_phase=None, use_fft=None, rfft=None, dtype=None, normalize=True, num_threads=None)[source]#
Return phasor coordinates from signal.
- Parameters:
signal (array_like) – Frequency-domain, time-domain, or hyperspectral data. A minimum of three samples are required along axis. The samples must be uniformly spaced.
axis (int or str, optional) – Axis over which to compute phasor coordinates. By default, the ‘H’ or ‘C’ axes if signal contains such dimension names, else the last axis (-1).
harmonic (int, sequence of int, or 'all', optional) – Harmonics to return. If ‘all’, return all harmonics for signal samples along axis. Else, harmonics must be at least one and no larger than half the number of signal samples along axis. The default is the first harmonic (fundamental frequency). A minimum of harmonic * 2 + 1 samples are required along axis to calculate correct phasor coordinates at harmonic.
sample_phase (array_like, optional) – Phase values (in radians) of signal samples along axis. If None (default), samples are assumed to be uniformly spaced along one period. The array size must equal the number of samples along axis. Cannot be used with harmonic!=1 or use_fft=True.
use_fft (bool, optional) – If true, use a real forward Fast Fourier Transform (FFT). If false, use a Cython implementation that is optimized (faster and resource saving) for calculating few harmonics. By default, FFT is only used when all or at least 8 harmonics are calculated, or rfft is specified.
rfft (callable, optional) – Drop-in replacement function for
numpy.fft.rfft
. For example,scipy.fft.rfft
ormkl_fft._numpy_fft.rfft
. Used to calculate the real forward FFT.dtype (dtype_like, optional) – Data type of output arrays. Either float32 or float64. The default is float64 unless the signal is float32.
normalize (bool, optional) – Return normalized phasor coordinates. If true (default), return average of signal along axis and Fourier coefficients divided by sum of signal along axis. Else, return sum of signal along axis and unscaled Fourier coefficients. Un-normalized phasor coordinates cannot be used with most of PhasorPy’s functions but may be required for intermediate processing.
num_threads (int, optional) – Number of OpenMP threads to use for parallelization when not using FFT. By default, multi-threading is disabled. If zero, up to half of logical CPUs are used. OpenMP may not be available on all platforms.
- Returns:
mean (ndarray) – Average of signal along axis (zero harmonic).
real (ndarray) – Real component of phasor coordinates at harmonic along axis.
imag (ndarray) – Imaginary component of phasor coordinates at harmonic along axis.
- Raises:
ValueError – The signal has less than three samples along axis. The sample_phase size does not equal the number of samples along axis.
IndexError – harmonic is smaller than 1 or greater than half the samples along axis.
TypeError – The signal, dtype, or harmonic types are not supported.
See also
phasorpy.phasor.phasor_to_signal
,phasorpy.phasor.phasor_normalize
, Benchmark phasor_from_signalNotes
The normalized phasor coordinates real (\(G\)), imag (\(S\)), and average intensity mean (\(F_{DC}\)) are calculated from \(K \ge 3\) samples of the signal \(F\) at harmonic \(h\) according to:
\[ \begin{align}\begin{aligned}F_{DC} &= \frac{1}{K} \sum_{k=0}^{K-1} F_{k}\\G &= \frac{1}{K} \sum_{k=0}^{K-1} F_{k} \cos{\left (2 \pi h \frac{k}{K} \right )} \cdot \frac{1}{F_{DC}}\\S &= \frac{1}{K} \sum_{k=0}^{K-1} F_{k} \sin{\left (2 \pi h \frac{k}{K} \right )} \cdot \frac{1}{F_{DC}}\end{aligned}\end{align} \]If \(F_{DC} = 0\), the phasor coordinates are undefined (resulting in NaN or infinity). Use NaN-aware software to further process the phasor coordinates.
The phasor coordinates may be zero, for example, in case of only constant background in time-resolved signals, or as the result of linear combination of non-zero spectral phasors coordinates.
Examples
Calculate phasor coordinates of a phase-shifted sinusoidal waveform:
>>> sample_phase = numpy.linspace(0, 2 * math.pi, 5, endpoint=False) >>> signal = 1.1 * (numpy.cos(sample_phase - 0.785398) * 2 * 0.707107 + 1) >>> phasor_from_signal(signal) (array(1.1), array(0.5), array(0.5))
The sinusoidal signal does not have a second harmonic component:
>>> phasor_from_signal(signal, harmonic=2) (array(1.1), array(0.0), array(0.0))
- phasorpy.phasor.phasor_multiply(real, imag, factor_real, factor_imag, /, **kwargs)[source]#
Return complex multiplication of two phasors.
Complex multiplication can be used, for example, to convolve two signals such as exponential decay and instrument response functions.
- Parameters:
real (array_like) – Real component of phasor coordinates to multiply.
imag (array_like) – Imaginary component of phasor coordinates to multiply.
factor_real (array_like) – Real component of phasor coordinates to multiply by.
factor_imag (array_like) – Imaginary component of phasor coordinates to multiply by.
**kwargs –
- Returns:
real (ndarray) – Real component of complex multiplication.
imag (ndarray) – Imaginary component of complex multiplication.
Notes
The phasor coordinates real (\(G\)) and imag (\(S\)) are multiplied by phasor coordinates factor_real (\(g\)) and factor_imag (\(s\)) according to:
\[ \begin{align}\begin{aligned}G' &= G \cdot g - S \cdot s\\S' &= G \cdot s + S \cdot g\end{aligned}\end{align} \]Examples
Multiply two sets of phasor coordinates:
>>> phasor_multiply([0.1, 0.2], [0.3, 0.4], [0.5, 0.6], [0.7, 0.8]) (array([-0.16, -0.2]), array([0.22, 0.4]))
- phasorpy.phasor.phasor_nearest_neighbor(real, imag, neighbor_real, neighbor_imag, /, *, values=None, dtype=None, distance_max=None, num_threads=None)[source]#
Return indices or values of nearest neighbors from other coordinates.
For each phasor coordinate, find the nearest neighbor in another set of phasor coordinates and return its flat index. If more than one neighbor has the same distance, return the smallest index.
For phasor coordinates that are NaN, or have a distance to the nearest neighbor that is larger than distance_max, return an index of -1.
If values are provided, return the values corresponding to the nearest neighbor coordinates instead of indices. Return NaN values for indices that are -1.
This function does not support multi-harmonic, multi-channel, or multi-frequency phasor coordinates.
- Parameters:
real (array_like) – Real component of phasor coordinates.
imag (array_like) – Imaginary component of phasor coordinates.
neighbor_real (array_like) – Real component of neighbor phasor coordinates.
neighbor_imag (array_like) – Imaginary component of neighbor phasor coordinates.
values (array_like, optional) – Array of values corresponding to neighbor coordinates. If provided, return the values corresponding to the nearest neighbor coordinates.
distance_max (float, optional) – Maximum Euclidean distance to consider a neighbor valid. By default, all neighbors are considered.
dtype (dtype_like, optional) – Floating point data type used for calculation and output values. Either float32 or float64. The default is float64.
num_threads (int, optional) – Number of OpenMP threads to use for parallelization. By default, multi-threading is disabled. If zero, up to half of logical CPUs are used. OpenMP may not be available on all platforms.
- Returns:
nearest – Flat indices (or the corresponding values if provided) of the nearest neighbor coordinates.
- Return type:
ndarray
- Raises:
ValueError – If the shapes of real, and imag do not match. If the shapes of neighbor_real and neighbor_imag do not match. If the shapes of values and neighbor_real do not match. If distance_max is less than or equal to zero.
See also
Notes
This function uses linear search, which is inefficient for large number of coordinates or neighbors.
scipy.spatial.KDTree.query()
would be more efficient in those cases. However, KDTree is known to return non-deterministic results in case of multiple neighbors with the same distance.Examples
>>> phasor_nearest_neighbor( ... [0.1, 0.5, numpy.nan], ... [0.1, 0.5, numpy.nan], ... [0, 0.4], ... [0, 0.4], ... values=[10, 20], ... ) array([10, 20, nan])
- phasorpy.phasor.phasor_normalize(mean_unnormalized, real_unnormalized, imag_unnormalized, /, samples=1, dtype=None)[source]#
Return normalized phasor coordinates.
Use to normalize the phasor coordinates returned by
phasor_from_signal(..., normalize=False)
.- Parameters:
mean_unnormalized (array_like) – Unnormalized intensity of phasor coordinates.
real_unnormalized (array_like) – Unnormalized real component of phasor coordinates.
imag_unnormalized (array_like) – Unnormalized imaginary component of phasor coordinates.
samples (int, default: 1) – Number of signal samples over which mean was integrated.
dtype (dtype_like, optional) – Data type of output arrays. Either float32 or float64. The default is float64 unless the real is float32.
- Returns:
mean (ndarray) – Normalized intensity.
real (ndarray) – Normalized real component.
imag (ndarray) – Normalized imaginary component.
Notes
The average intensity mean (\(F_{DC}\)) and normalized phasor coordinates real (\(G\)) and imag (\(S\)) are calculated from the signal intensity (\(F\)), the number of samples (\(K\)), real_unnormalized (\(G'\)), and imag_unnormalized (\(S'\)) according to:
\[ \begin{align}\begin{aligned}F_{DC} &= F / K\\G &= G' / F\\S &= S' / F\end{aligned}\end{align} \]If \(F = 0\), the normalized phasor coordinates (\(G\)) and (\(S\)) are undefined (NaN or infinity).
Examples
Normalize phasor coordinates with intensity integrated over 10 samples:
>>> phasor_normalize([0.0, 0.1], [0.0, 0.3], [0.4, 0.5], samples=10) (array([0, 0.01]), array([nan, 3]), array([inf, 5]))
Normalize multi-harmonic phasor coordinates:
>>> phasor_normalize(0.1, [0.0, 0.3], [0.4, 0.5], samples=10) (array(0.01), array([0, 3]), array([4, 5]))
- phasorpy.phasor.phasor_to_complex(real, imag, /, *, dtype=None)[source]#
Return phasor coordinates as complex numbers.
- Parameters:
real (array_like) – Real component of phasor coordinates.
imag (array_like) – Imaginary component of phasor coordinates.
dtype (dtype_like, optional) – Data type of output array. Either complex64 or complex128. By default, complex64 if real and imag are float32, else complex128.
- Returns:
complex – Phasor coordinates as complex numbers.
- Return type:
ndarray
Examples
Convert phasor coordinates to complex number arrays:
>>> phasor_to_complex([0.4, 0.5], [0.2, 0.3]) array([0.4+0.2j, 0.5+0.3j])
- phasorpy.phasor.phasor_to_polar(real, imag, /, **kwargs)[source]#
Return polar coordinates from phasor coordinates.
- Parameters:
real (array_like) – Real component of phasor coordinates.
imag (array_like) – Imaginary component of phasor coordinates.
**kwargs –
Notes
The phasor coordinates real (\(G\)) and imag (\(S\)) are converted to polar coordinates phase (\(\phi\)) and modulation (\(M\)) according to:
\[ \begin{align}\begin{aligned}\phi &= \arctan(S / G)\\M &= \sqrt{G^2 + S^2}\end{aligned}\end{align} \]- Returns:
phase (ndarray) – Angular component of polar coordinates in radians.
modulation (ndarray) – Radial component of polar coordinates.
Examples
Calculate polar coordinates from three phasor coordinates:
>>> phasor_to_polar([1.0, 0.5, 0.0], [0.0, 0.5, 1.0]) (array([0, 0.7854, 1.571]), array([1, 0.7071, 1]))
- phasorpy.phasor.phasor_to_principal_plane(real, imag, /, *, reorient=True)[source]#
Return multi-harmonic phasor coordinates projected onto principal plane.
Principal component analysis (PCA) is used to project multi-harmonic phasor coordinates onto a plane, along which coordinate axes the phasor coordinates have the largest variations.
The transformed coordinates are not phasor coordinates. However, the coordinates can be used in visualization and cursor analysis since the transformation is affine (preserving collinearity and ratios of distances).
- Parameters:
real (array_like) – Real component of multi-harmonic phasor coordinates. The first axis is the frequency dimension. If less than 2-dimensional, size-1 dimensions are prepended.
imag (array_like) – Imaginary component of multi-harmonic phasor coordinates. Must be of same shape as real.
reorient (bool, optional, default: True) – Reorient coordinates for easier visualization. The projected coordinates are rotated and scaled, such that the center lies in same quadrant and the projection of [1, 0] lies at [1, 0].
- Returns:
x (ndarray) – X-coordinates of projected phasor coordinates. If not reorient, this is the coordinate on the first principal axis. The shape is
real.shape[1:]
.y (ndarray) – Y-coordinates of projected phasor coordinates. If not reorient, this is the coordinate on the second principal axis.
transformation_matrix (ndarray) – Affine transformation matrix used to project phasor coordinates. The shape is
(2, 2 * real.shape[0])
.
See also
Notes
This implementation does not work with coordinates containing undefined NaN values.
The transformation matrix can be used to project multi-harmonic phasor coordinates, where the first axis is the frequency:
x, y = numpy.dot( numpy.vstack( real.reshape(real.shape[0], -1), imag.reshape(imag.shape[0], -1), ), transformation_matrix, ).reshape(2, *real.shape[1:])
An application of PCA to full-harmonic phasor coordinates from MRI signals can be found in [1].
References
Examples
The phasor coordinates of multi-exponential decays may be almost indistinguishable at certain frequencies but are separated in the projection on the principal plane:
>>> real = [[0.495, 0.502], [0.354, 0.304]] >>> imag = [[0.333, 0.334], [0.301, 0.349]] >>> x, y, transformation_matrix = phasor_to_principal_plane(real, imag) >>> x, y (array([0.294, 0.262]), array([0.192, 0.242])) >>> transformation_matrix array([[0.67, 0.33, -0.09, -0.41], [0.52, -0.52, -0.04, 0.44]])
- phasorpy.phasor.phasor_to_signal(mean, real, imag, /, *, samples=64, harmonic=None, axis=-1, irfft=None)[source]#
Return signal from phasor coordinates using inverse Fourier transform.
- Parameters:
mean (array_like) – Average signal intensity (DC). If not scalar, shape must match the last dimensions of real.
real (array_like) – Real component of phasor coordinates. Multiple harmonics, if any, must be in the first axis.
imag (array_like) – Imaginary component of phasor coordinates. Must be same shape as real.
samples (int, default: 64) – Number of signal samples to return. Must be at least three.
harmonic (int, sequence of int, or 'all', optional) – Harmonics included in first axis of real and imag. If None, lower harmonics are inferred from the shapes of phasor coordinates (most commonly, lower harmonics are present if the number of dimensions of mean is one less than real). If ‘all’, the harmonics in the first axis of phasor coordinates are the lower harmonics necessary to synthesize samples. Else, harmonics must be at least one and no larger than half of samples. The phasor coordinates of missing harmonics are zeroed if samples is greater than twice the number of harmonics.
axis (int, optional) – Axis at which to return signal samples. The default is the last axis (-1).
irfft (callable, optional) – Drop-in replacement function for
numpy.fft.irfft
. For example,scipy.fft.irfft
ormkl_fft._numpy_fft.irfft
. Used to calculate the real inverse FFT.
- Returns:
signal – Reconstructed signal with samples of one period along the last axis.
- Return type:
ndarray
See also
Notes
The reconstructed signal may be undefined if the input phasor coordinates, or signal mean contain NaN values.
Examples
Reconstruct exact signal from phasor coordinates at all harmonics:
>>> sample_phase = numpy.linspace(0, 2 * math.pi, 5, endpoint=False) >>> signal = 1.1 * (numpy.cos(sample_phase - 0.785398) * 2 * 0.707107 + 1) >>> signal array([2.2, 2.486, 0.8566, -0.4365, 0.3938]) >>> phasor_to_signal( ... *phasor_from_signal(signal, harmonic='all'), ... harmonic='all', ... samples=len(signal) ... ) array([2.2, 2.486, 0.8566, -0.4365, 0.3938])
Reconstruct a single-frequency waveform from phasor coordinates at first harmonic:
>>> phasor_to_signal(1.1, 0.5, 0.5, samples=5) array([2.2, 2.486, 0.8566, -0.4365, 0.3938])
- phasorpy.phasor.phasor_transform(real, imag, phase=0.0, modulation=1.0, /, **kwargs)[source]#
Return rotated and scaled phasor coordinates.
This function rotates and uniformly scales phasor coordinates around the origin. It can be used, for example, to calibrate phasor coordinates.
- Parameters:
real (array_like) – Real component of phasor coordinates to transform.
imag (array_like) – Imaginary component of phasor coordinates to transform.
phase (array_like, optional, default: 0.0) – Rotation angle in radians.
modulation (array_like, optional, default: 1.0) – Uniform scale factor.
**kwargs –
- Returns:
real (ndarray) – Real component of rotated and scaled phasor coordinates.
imag (ndarray) – Imaginary component of rotated and scaled phasor coordinates.
Notes
The phasor coordinates real (\(G\)) and imag (\(S\)) are rotated by phase (\(\phi\)) and scaled by modulation_zero (\(M\)) around the origin according to:
\[ \begin{align}\begin{aligned}g &= M \cdot \cos{\phi}\\s &= M \cdot \sin{\phi}\\G' &= G \cdot g - S \cdot s\\S' &= G \cdot s + S \cdot g\end{aligned}\end{align} \]Examples
Use scalar reference coordinates to rotate and scale phasor coordinates:
>>> phasor_transform( ... [0.1, 0.2, 0.3], [0.4, 0.5, 0.6], 0.1, 0.5 ... ) (array([0.0298, 0.0745, 0.119]), array([0.204, 0.259, 0.3135]))
Use separate reference coordinates for each phasor coordinate:
>>> phasor_transform( ... [0.1, 0.2, 0.3], [0.4, 0.5, 0.6], [0.2, 0.2, 0.3], [0.5, 0.2, 0.3] ... ) (array([0.00927, 0.0193, 0.0328]), array([0.206, 0.106, 0.1986]))