def FIR_header(fname_out, h):
    """
    Write FIR Filter Header Files

    Mark Wickert February 2015
    """
    M = len(h)
    N = 3  # Coefficients per line
    f = open(fname_out, 'wt')
    f.write('//define a FIR coefficient Array\n\n')
    f.write('#include <stdint.h>\n\n')
    f.write('#ifndef M_FIR\n')
    f.write('#define M_FIR %d\n' % M)
    f.write('#endif\n')
    f.write('/************************************************************************/\n');
    f.write('/*                         FIR Filter Coefficients                      */\n');
    f.write('float32_t h_FIR[M_FIR] = {')
    kk = 0;
    for k in range(M):
        # k_mod = k % M
        if (kk < N - 1) and (k < M - 1):
            f.write('%15.12f,' % h[k])
            kk += 1
        elif (kk == N - 1) & (k < M - 1):
            f.write('%15.12f,\n' % h[k])
            if k < M:
                f.write('                          ')
                kk = 0
        else:
            f.write('%15.12f' % h[k])
    f.write('};\n')
    f.write('/************************************************************************/\n')
    f.close()

'/*                         FIR Filter Coefficients                      */\n'


def FIR_fix_header(fname_out, h):
    """
    Write FIR Fixed-Point Filter Header Files

    Mark Wickert February 2015
    """
    M = len(h)
    hq = int16(rint(h * 2 ** 15))
    N = 8  # Coefficients per line
    f = open(fname_out, 'wt')
    f.write('//define a FIR coefficient Array\n\n')
    f.write('#include <stdint.h>\n\n')
    f.write('#ifndef M_FIR\n')
    f.write('#define M_FIR %d\n' % M)
    f.write('#endif\n')
    f.write('/************************************************************************/\n');
    f.write('/*                         FIR Filter Coefficients                      */\n');
    f.write('int16_t h_FIR[M_FIR] = {')
    kk = 0;
    for k in range(M):
        # k_mod = k % M
        if (kk < N - 1) and (k < M - 1):
            f.write('%5d,' % hq[k])
            kk += 1
        elif (kk == N - 1) & (k < M - 1):
            f.write('%5d,\n' % hq[k])
            if k < M:
                f.write('                        ')
                kk = 0
        else:
            f.write('%5d' % hq[k])
    f.write('};\n')
    f.write('/************************************************************************/\n')
    f.close()

Write FIR Fixed - Point Filter Header Files Mark Wickert February 2015


def IIR_sos_header(fname_out, SOS_mat):
    """
    Write IIR SOS Header Files
    File format is compatible with CMSIS-DSP IIR
    Directform II Filter Functions

    Mark Wickert March 2015-October 2016
    """
    Ns, Mcol = SOS_mat.shape
    f = open(fname_out, 'wt')
    f.write('//define a IIR SOS CMSIS-DSP coefficient array\n\n')
    f.write('#include <stdint.h>\n\n')
    f.write('#ifndef STAGES\n')
    f.write('#define STAGES %d\n' % Ns)
    f.write('#endif\n')
    f.write('/*********************************************************/\n');
    f.write('/*                     IIR SOS Filter Coefficients       */\n');
    f.write('float32_t ba_coeff[%d] = { //b0,b1,b2,a1,a2,... by stage\n' % (5 * Ns))
    for k in range(Ns):
        if (k < Ns - 1):
            f.write('    %+-13e, %+-13e, %+-13e,\n' % \
                    (SOS_mat[k, 0], SOS_mat[k, 1], SOS_mat[k, 2]))
            f.write('    %+-13e, %+-13e,\n' % \
                    (-SOS_mat[k, 4], -SOS_mat[k, 5]))
        else:
            f.write('    %+-13e, %+-13e, %+-13e,\n' % \
                    (SOS_mat[k, 0], SOS_mat[k, 1], SOS_mat[k, 2]))
            f.write('    %+-13e, %+-13e\n' % \
                    (-SOS_mat[k, 4], -SOS_mat[k, 5]))
    # for k in range(Ns):
    #     if (k < Ns-1):
    #         f.write('    %15.12f, %15.12f, %15.12f,\n' % \
    #                 (SOS_mat[k,0],SOS_mat[k,1],SOS_mat[k,2]))
    #         f.write('    %15.12f, %15.12f,\n' % \
    #                 (-SOS_mat[k,4],-SOS_mat[k,5]))
    #     else:
    #         f.write('    %15.12f, %15.12f, %15.12f,\n' % \
    #                 (SOS_mat[k,0],SOS_mat[k,1],SOS_mat[k,2]))
    #         f.write('    %15.12f, %15.12f\n' % \
    #                 (-SOS_mat[k,4],-SOS_mat[k,5]))
    f.write('};\n')
    f.write('/*********************************************************/\n')
    f.close()

'/*                     IIR SOS Filter Coefficients       */\n'

Write IIR SOS Header Files File format is compatible with CMSIS - DSP IIR Directform II Filter Functions Mark Wickert March 2015 - October 2016


def eye_plot(x,L,S=0):
    """
    Eye pattern plot of a baseband digital communications waveform.

    The signal must be real, but can be multivalued in terms of the underlying
    modulation scheme. Used for BPSK eye plots in the Case Study article.

    Parameters
    ----------
    x : ndarray of the real input data vector/array
    L : display length in samples (usually two symbols)
    S : start index

    Returns
    -------
    None : A plot window opens containing the eye plot

    Notes
    -----
    Increase S to eliminate filter transients.

    Examples
    --------
    1000 bits at 10 samples per bit with 'rc' shaping.

    >>> import matplotlib.pyplot as plt
    >>> from sk_dsp_comm import digitalcom as dc
    >>> x,b, data = dc.NRZ_bits(1000,10,'rc')
    >>> dc.eye_plot(x,20,60)
    >>> plt.show()
    """
    plt.figure(figsize=(6,4))
    idx = np.arange(0,L+1)
    plt.plot(idx,x[S:S+L+1],'b')
    k_max = int((len(x) - S)/L)-1
    for k in range(1,k_max):
         plt.plot(idx,x[S+k*L:S+L+1+k*L],'b')
    plt.grid()
    plt.xlabel('Time Index - n')
    plt.ylabel('Amplitude')
    plt.title('Eye Plot')
    return 0

def
eye_plot
(
x
,
L
,
S
=
0
)
:
plt
.
figure
(
figsize
=
(
6
,
4
)
)
idx
=
np
.
arange
(
0
,
L
+
1
)
plt
.
plot
(
idx
,
x
[
S
:
S
+
L
+
1
]
,
'b'
)
k_max
=
int
(
(
len
(
x
)
S
)
/
L
)
1
for
k
in
range
(
1
,
k_max
)
:
plt
.
plot
(
idx
,
x
[
S
+
k
*
L
:
S
+
L
+
1
+
k
*
L
]
,
'b'
)
plt
.
grid
(
)
plt
.
xlabel
(
'Time Index - n'
)
plt
.
ylabel
(
'Amplitude'
)
plt
.
title
(
'Eye Plot'
)
return
0

Eye pattern plot of a baseband digital communications waveform .


def scatter(x,Ns,start):
    """
    Sample a baseband digital communications waveform at the symbol spacing.

    Parameters
    ----------
    x : ndarray of the input digital comm signal
    Ns : number of samples per symbol (bit)
    start : the array index to start the sampling

    Returns
    -------
    xI : ndarray of the real part of x following sampling
    xQ : ndarray of the imaginary part of x following sampling

    Notes
    -----
    Normally the signal is complex, so the scatter plot contains
    clusters at point  in the complex plane. For a binary signal
    such as BPSK, the point centers are nominally +/-1 on the real
    axis. Start is used to eliminate transients from the FIR
    pulse shaping filters from appearing in the scatter plot.

    Examples
    --------
    >>> import matplotlib.pyplot as plt
    >>> from sk_dsp_comm import digitalcom as dc
    >>> x,b, data = dc.NRZ_bits(1000,10,'rc')

    Add some noise so points are now scattered about +/-1.

    >>> y = dc.cpx_AWGN(x,20,10)
    >>> yI,yQ = dc.scatter(y,10,60)
    >>> plt.plot(yI,yQ,'.')
    >>> plt.grid()
    >>> plt.xlabel('In-Phase')
    >>> plt.ylabel('Quadrature')
    >>> plt.axis('equal')
    >>> plt.show()
    """
    xI = np.real(x[start::Ns])
    xQ = np.imag(x[start::Ns])
    return xI, xQ

def scatter ( x , Ns , start ) : xI = np . real ( x [ start : : Ns ] ) xQ = np . imag ( x [ start : : Ns ] ) return xI , xQ

Sample a baseband digital communications waveform at the symbol spacing .


def MPSK_bb(N_symb,Ns,M,pulse='rect',alpha = 0.25,MM=6):
    """
    Generate a complex baseband MPSK signal with pulse shaping.

    Parameters
    ----------
    N_symb : number of MPSK symbols to produce
    Ns : the number of samples per bit,
    M : MPSK modulation order, e.g., 4, 8, 16, ...
    pulse_type : 'rect' , 'rc', 'src' (default 'rect')
    alpha : excess bandwidth factor(default 0.25)
    MM : single sided pulse duration (default = 6)

    Returns
    -------
    x : ndarray of the MPSK signal values
    b : ndarray of the pulse shape
    data : ndarray of the underlying data bits

    Notes
    -----
    Pulse shapes include 'rect' (rectangular), 'rc' (raised cosine),
    'src' (root raised cosine). The actual pulse length is 2*M+1 samples.
    This function is used by BPSK_tx in the Case Study article.

    Examples
    --------
    >>> from sk_dsp_comm import digitalcom as dc
    >>> import scipy.signal as signal
    >>> import matplotlib.pyplot as plt
    >>> x,b,data = dc.MPSK_bb(500,10,8,'src',0.35)
    >>> # Matched filter received signal x
    >>> y = signal.lfilter(b,1,x)
    >>> plt.plot(y.real[12*10:],y.imag[12*10:])
    >>> plt.xlabel('In-Phase')
    >>> plt.ylabel('Quadrature')
    >>> plt.axis('equal')
    >>> # Sample once per symbol
    >>> plt.plot(y.real[12*10::10],y.imag[12*10::10],'r.')
    >>> plt.show()
    """
    data = np.random.randint(0,M,N_symb)
    xs = np.exp(1j*2*np.pi/M*data)
    x = np.hstack((xs.reshape(N_symb,1),np.zeros((N_symb,int(Ns)-1))))
    x =x.flatten()
    if pulse.lower() == 'rect':
        b = np.ones(int(Ns))
    elif pulse.lower() == 'rc':
        b = rc_imp(Ns,alpha,MM)
    elif pulse.lower() == 'src':
        b = sqrt_rc_imp(Ns,alpha,MM)
    else:
        raise ValueError('pulse type must be rec, rc, or src')
    x = signal.lfilter(b,1,x)
    if M == 4:
        x = x*np.exp(1j*np.pi/4); # For QPSK points in quadrants
    return x,b/float(Ns),data

def
MPSK_bb
(
N_symb
,
Ns
,
M
,
pulse
=
'rect'
,
alpha
=
0.25
,
MM
=
6
)
:
data
=
np
.
random
.
randint
(
0
,
M
,
N_symb
)
xs
=
np
.
exp
(
1j
*
2
*
np
.
pi
/
M
*
data
)
x
=
np
.
hstack
(
(
xs
.
reshape
(
N_symb
,
1
)
,
np
.
zeros
(
(
N_symb
,
int
(
Ns
)
1
)
)
)
)
x
=
x
.
flatten
(
)
if
pulse
.
lower
(
)
==
'rect'
:
b
=
np
.
ones
(
int
(
Ns
)
)
elif
pulse
.
lower
(
)
==
'rc'
:
b
=
rc_imp
(
Ns
,
alpha
,
MM
)
elif
pulse
.
lower
(
)
==
'src'
:
b
=
sqrt_rc_imp
(
Ns
,
alpha
,
MM
)
else
:
raise
ValueError
(
'pulse type must be rec, rc, or src'
)
x
=
signal
.
lfilter
(
b
,
1
,
x
)
if
M
==
4
:
x
=
x
*
np
.
exp
(
1j
*
np
.
pi
/
4
)
# For QPSK points in quadrants
return
x
,
b
/
float
(
Ns
)
,
data

Generate a complex baseband MPSK signal with pulse shaping .


def QPSK_rx(fc,N_symb,Rs,EsN0=100,fs=125,lfsr_len=10,phase=0,pulse='src'):
    """
    This function generates
    """
    Ns = int(np.round(fs/Rs))
    print('Ns = ', Ns)
    print('Rs = ', fs/float(Ns))
    print('EsN0 = ', EsN0, 'dB')
    print('phase = ', phase, 'degrees')
    print('pulse = ', pulse)
    x, b, data = QPSK_bb(N_symb,Ns,lfsr_len,pulse)
    # Add AWGN to x
    x = cpx_AWGN(x,EsN0,Ns)
    n = np.arange(len(x))
    xc = x*np.exp(1j*2*np.pi*fc/float(fs)*n) * np.exp(1j*phase)
    return xc, b, data

def
QPSK_rx
(
fc
,
N_symb
,
Rs
,
EsN0
=
100
,
fs
=
125
,
lfsr_len
=
10
,
phase
=
0
,
pulse
=
'src'
)
:
Ns
=
int
(
np
.
round
(
fs
/
Rs
)
)
print
(
'Ns = '
,
Ns
)
print
(
'Rs = '
,
fs
/
float
(
Ns
)
)
print
(
'EsN0 = '
,
EsN0
,
'dB'
)
print
(
'phase = '
,
phase
,
'degrees'
)
print
(
'pulse = '
,
pulse
)
x
,
b
,
data
=
QPSK_bb
(
N_symb
,
Ns
,
lfsr_len
,
pulse
)
# Add AWGN to x
x
=
cpx_AWGN
(
x
,
EsN0
,
Ns
)
n
=
np
.
arange
(
len
(
x
)
)
xc
=
x
*
np
.
exp
(
1j
*
2
*
np
.
pi
*
fc
/
float
(
fs
)
*
n
)
*
np
.
exp
(
1j
*
phase
)
return
xc
,
b
,
data


def rc_imp(Ns,alpha,M=6):
    """
    A truncated raised cosine pulse used in digital communications.

    The pulse shaping factor :math:`0 < \\alpha < 1` is required as well as the
    truncation factor M which sets the pulse duration to be :math:`2*M*T_{symbol}`.

    Parameters
    ----------
    Ns : number of samples per symbol
    alpha : excess bandwidth factor on (0, 1), e.g., 0.35
    M : equals RC one-sided symbol truncation factor

    Returns
    -------
    b : ndarray containing the pulse shape

    See Also
    --------
    sqrt_rc_imp

    Notes
    -----
    The pulse shape b is typically used as the FIR filter coefficients
    when forming a pulse shaped digital communications waveform.

    Examples
    --------
    Ten samples per symbol and :math:`\\alpha = 0.35`.

    >>> import matplotlib.pyplot as plt
    >>> from sk_dsp_comm.digitalcom import rc_imp
    >>> from numpy import arange
    >>> b = rc_imp(10,0.35)
    >>> n = arange(-10*6,10*6+1)
    >>> plt.stem(n,b)
    >>> plt.show()
    """
    # Design the filter
    n = np.arange(-M*Ns,M*Ns+1)
    b = np.zeros(len(n))
    a = alpha
    Ns *= 1.0
    for i in range(len(n)):
        if (1 - 4*(a*n[i]/Ns)**2) == 0:
            b[i] = np.pi/4*np.sinc(1/(2.*a))
        else:
            b[i] = np.sinc(n[i]/Ns)*np.cos(np.pi*a*n[i]/Ns)/(1 - 4*(a*n[i]/Ns)**2)
    return b

def
rc_imp
(
Ns
,
alpha
,
M
=
6
)
:
# Design the filter
n
=
np
.
arange
(
M
*
Ns
,
M
*
Ns
+
1
)
b
=
np
.
zeros
(
len
(
n
)
)
a
=
alpha
Ns
*=
1.0
for
i
in
range
(
len
(
n
)
)
:
if
(
1
4
*
(
a
*
n
[
i
]
/
Ns
)
**
2
)
==
0
:
b
[
i
]
=
np
.
pi
/
4
*
np
.
sinc
(
1
/
(
2.
*
a
)
)
else
:
b
[
i
]
=
np
.
sinc
(
n
[
i
]
/
Ns
)
*
np
.
cos
(
np
.
pi
*
a
*
n
[
i
]
/
Ns
)
/
(
1
4
*
(
a
*
n
[
i
]
/
Ns
)
**
2
)
return
b

A truncated raised cosine pulse used in digital communications .


def sqrt_rc_imp(Ns,alpha,M=6):
    """
    A truncated square root raised cosine pulse used in digital communications.

    The pulse shaping factor :math:`0 < \\alpha < 1` is required as well as the
    truncation factor M which sets the pulse duration to be :math:`2*M*T_{symbol}`.

    Parameters
    ----------
    Ns : number of samples per symbol
    alpha : excess bandwidth factor on (0, 1), e.g., 0.35
    M : equals RC one-sided symbol truncation factor

    Returns
    -------
    b : ndarray containing the pulse shape

    Notes
    -----
    The pulse shape b is typically used as the FIR filter coefficients
    when forming a pulse shaped digital communications waveform. When
    square root raised cosine (SRC) pulse is used to generate Tx signals and
    at the receiver used as a matched filter (receiver FIR filter), the
    received signal is now raised cosine shaped, thus having zero
    intersymbol interference and the optimum removal of additive white
    noise if present at the receiver input.

    Examples
    --------
    Ten samples per symbol and :math:`\\alpha = 0.35`.

    >>> import matplotlib.pyplot as plt
    >>> from numpy import arange
    >>> from sk_dsp_comm.digitalcom import sqrt_rc_imp
    >>> b = sqrt_rc_imp(10,0.35)
    >>> n = arange(-10*6,10*6+1)
    >>> plt.stem(n,b)
    >>> plt.show()
    """
    # Design the filter
    n = np.arange(-M*Ns,M*Ns+1)
    b = np.zeros(len(n))
    Ns *= 1.0
    a = alpha
    for i in range(len(n)):
       if abs(1 - 16*a**2*(n[i]/Ns)**2) <= np.finfo(np.float).eps/2:
           b[i] = 1/2.*((1+a)*np.sin((1+a)*np.pi/(4.*a))-(1-a)*np.cos((1-a)*np.pi/(4.*a))+(4*a)/np.pi*np.sin((1-a)*np.pi/(4.*a)))
       else:
           b[i] = 4*a/(np.pi*(1 - 16*a**2*(n[i]/Ns)**2))
           b[i] = b[i]*(np.cos((1+a)*np.pi*n[i]/Ns) + np.sinc((1-a)*n[i]/Ns)*(1-a)*np.pi/(4.*a))
    return b

def
sqrt_rc_imp
(
Ns
,
alpha
,
M
=
6
)
:
# Design the filter
n
=
np
.
arange
(
M
*
Ns
,
M
*
Ns
+
1
)
b
=
np
.
zeros
(
len
(
n
)
)
Ns
*=
1.0
a
=
alpha
for
i
in
range
(
len
(
n
)
)
:
if
abs
(
1
16
*
a
**
2
*
(
n
[
i
]
/
Ns
)
**
2
)
<=
np
.
finfo
(
np
.
float
)
.
eps
/
2
:
b
[
i
]
=
1
/
2.
*
(
(
1
+
a
)
*
np
.
sin
(
(
1
+
a
)
*
np
.
pi
/
(
4.
*
a
)
)
(
1
a
)
*
np
.
cos
(
(
1
a
)
*
np
.
pi
/
(
4.
*
a
)
)
+
(
4
*
a
)
/
np
.
pi
*
np
.
sin
(
(
1
a
)
*
np
.
pi
/
(
4.
*
a
)
)
)
else
:
b
[
i
]
=
4
*
a
/
(
np
.
pi
*
(
1
16
*
a
**
2
*
(
n
[
i
]
/
Ns
)
**
2
)
)
b
[
i
]
=
b
[
i
]
*
(
np
.
cos
(
(
1
+
a
)
*
np
.
pi
*
n
[
i
]
/
Ns
)
+
np
.
sinc
(
(
1
a
)
*
n
[
i
]
/
Ns
)
*
(
1
a
)
*
np
.
pi
/
(
4.
*
a
)
)
return
b

A truncated square root raised cosine pulse used in digital communications .


def my_psd(x,NFFT=2**10,Fs=1):
    """
    A local version of NumPy's PSD function that returns the plot arrays.

    A mlab.psd wrapper function that returns two ndarrays;
    makes no attempt to auto plot anything.

    Parameters
    ----------
    x : ndarray input signal
    NFFT : a power of two, e.g., 2**10 = 1024
    Fs : the sampling rate in Hz

    Returns
    -------
    Px : ndarray of the power spectrum estimate
    f : ndarray of frequency values

    Notes
    -----
    This function makes it easier to overlay spectrum plots because
    you have better control over the axis scaling than when using psd()
    in the autoscale mode.

    Examples
    --------
    >>> import matplotlib.pyplot as plt
    >>> from sk_dsp_comm import digitalcom as dc
    >>> from numpy import log10
    >>> x,b, data = dc.NRZ_bits(10000,10)
    >>> Px,f = dc.my_psd(x,2**10,10)
    >>> plt.plot(f, 10*log10(Px))
    >>> plt.show()
    """
    Px,f = pylab.mlab.psd(x,NFFT,Fs)
    return Px.flatten(), f

def my_psd ( x , NFFT = 2 ** 10 , Fs = 1 ) : Px , f = pylab . mlab . psd ( x , NFFT , Fs ) return Px . flatten ( ) , f

A local version of NumPy s PSD function that returns the plot arrays .


def to_bin(data, width):
    """
    Convert an unsigned integer to a numpy binary array with the first
    element the MSB and the last element the LSB.
    """
    data_str = bin(data & (2**width-1))[2:].zfill(width)
    return [int(x) for x in tuple(data_str)]

def to_bin ( data , width ) : data_str = bin ( data & ( 2 ** width - 1 ) ) [ 2 : ] . zfill ( width ) return [ int ( x ) for x in tuple ( data_str ) ]

Convert an unsigned integer to a numpy binary array with the first element the MSB and the last element the LSB .


def from_bin(bin_array):
    """
    Convert binary array back a nonnegative integer. The array length is
    the bit width. The first input index holds the MSB and the last holds the LSB.
    """
    width = len(bin_array)
    bin_wgts = 2**np.arange(width-1,-1,-1)
    return int(np.dot(bin_array,bin_wgts))

def from_bin ( bin_array ) : width = len ( bin_array ) bin_wgts = 2 ** np . arange ( width - 1 , - 1 , - 1 ) return int ( np . dot ( bin_array , bin_wgts ) )

Convert binary array back a nonnegative integer . The array length is the bit width . The first input index holds the MSB and the last holds the LSB .


def filter(self,x):
        """
        Filter the signal
        """
        y = signal.lfilter(self.b,[1],x)
        return y

def filter ( self , x ) : y = signal . lfilter ( self . b , [ 1 ] , x ) return y


def filter(self,x):
        """
        Filter the signal using second-order sections
        """
        y = signal.sosfilt(self.sos,x)
        return y

def filter ( self , x ) : y = signal . sosfilt ( self . sos , x ) return y

Filter the signal using second - order sections


def freq_resp(self, mode= 'dB', fs = 8000, ylim = [-100,2]):
        """
        Frequency response plot
        """
        iir_d.freqz_resp_cas_list([self.sos],mode,fs=fs)
        pylab.grid()
        pylab.ylim(ylim)

def freq_resp ( self , mode = 'dB' , fs = 8000 , ylim = [ - 100 , 2 ] ) : iir_d . freqz_resp_cas_list ( [ self . sos ] , mode , fs = fs ) pylab . grid ( ) pylab . ylim ( ylim )


def _select_manager(backend_name):
    """Select the proper LockManager based on the current backend used by Celery.

    :raise NotImplementedError: If Celery is using an unsupported backend.

    :param str backend_name: Class name of the current Celery backend. Usually value of
        current_app.extensions['celery'].celery.backend.__class__.__name__.

    :return: Class definition object (not instance). One of the _LockManager* classes.
    """
    if backend_name == 'RedisBackend':
        lock_manager = _LockManagerRedis
    elif backend_name == 'DatabaseBackend':
        lock_manager = _LockManagerDB
    else:
        raise NotImplementedError
    return lock_manager

def _select_manager ( backend_name ) : if backend_name == 'RedisBackend' : lock_manager = _LockManagerRedis elif backend_name == 'DatabaseBackend' : lock_manager = _LockManagerDB else : raise NotImplementedError return lock_manager

Select the proper LockManager based on the current backend used by Celery .


def single_instance(func=None, lock_timeout=None, include_args=False):
    """Celery task decorator. Forces the task to have only one running instance at a time.

    Use with binded tasks (@celery.task(bind=True)).

    Modeled after:
    http://loose-bits.com/2010/10/distributed-task-locking-in-celery.html
    http://blogs.it.ox.ac.uk/inapickle/2012/01/05/python-decorators-with-optional-arguments/

    Written by @Robpol86.

    :raise OtherInstanceError: If another instance is already running.

    :param function func: The function to decorate, must be also decorated by @celery.task.
    :param int lock_timeout: Lock timeout in seconds plus five more seconds, in-case the task crashes and fails to
        release the lock. If not specified, the values of the task's soft/hard limits are used. If all else fails,
        timeout will be 5 minutes.
    :param bool include_args: Include the md5 checksum of the arguments passed to the task in the Redis key. This allows
        the same task to run with different arguments, only stopping a task from running if another instance of it is
        running with the same arguments.
    """
    if func is None:
        return partial(single_instance, lock_timeout=lock_timeout, include_args=include_args)

    @wraps(func)
    def wrapped(celery_self, *args, **kwargs):
        """Wrapped Celery task, for single_instance()."""
        # Select the manager and get timeout.
        timeout = (
            lock_timeout or celery_self.soft_time_limit or celery_self.time_limit
            or celery_self.app.conf.get('CELERYD_TASK_SOFT_TIME_LIMIT')
            or celery_self.app.conf.get('CELERYD_TASK_TIME_LIMIT')
            or (60 * 5)
        )
        manager_class = _select_manager(celery_self.backend.__class__.__name__)
        lock_manager = manager_class(celery_self, timeout, include_args, args, kwargs)

        # Lock and execute.
        with lock_manager:
            ret_value = func(*args, **kwargs)
        return ret_value
    return wrapped

Celery task decorator . Forces the task to have only one running instance at a time .


def reset_lock(self):
        """Removed the lock regardless of timeout."""
        redis_key = self.CELERY_LOCK.format(task_id=self.task_identifier)
        self.celery_self.backend.client.delete(redis_key)

def reset_lock ( self ) : redis_key = self . CELERY_LOCK . format ( task_id = self . task_identifier ) self . celery_self . backend . client . delete ( redis_key )

Removed the lock regardless of timeout .


def init_app(self, app):
        """Actual method to read celery settings from app configuration and initialize the celery instance.

        :param app: Flask application instance.
        """
        _state._register_app = self.original_register_app  # Restore Celery app registration function.
        if not hasattr(app, 'extensions'):
            app.extensions = dict()
        if 'celery' in app.extensions:
            raise ValueError('Already registered extension CELERY.')
        app.extensions['celery'] = _CeleryState(self, app)

        # Instantiate celery and read config.
        super(Celery, self).__init__(app.import_name, broker=app.config['CELERY_BROKER_URL'])

        # Set result backend default.
        if 'CELERY_RESULT_BACKEND' in app.config:
            self._preconf['CELERY_RESULT_BACKEND'] = app.config['CELERY_RESULT_BACKEND']

        self.conf.update(app.config)
        task_base = self.Task

        # Add Flask app context to celery instance.
        class ContextTask(task_base):
            def __call__(self, *_args, **_kwargs):
                with app.app_context():
                    return task_base.__call__(self, *_args, **_kwargs)
        setattr(ContextTask, 'abstract', True)
        setattr(self, 'Task', ContextTask)

Actual method to read celery settings from app configuration and initialize the celery instance .


def iter_chunksize(num_samples, chunksize):
    """Iterator used to iterate in chunks over an array of size `num_samples`.
    At each iteration returns `chunksize` except for the last iteration.
    """
    last_chunksize = int(np.mod(num_samples, chunksize))
    chunksize = int(chunksize)
    for _ in range(int(num_samples) // chunksize):
        yield chunksize
    if last_chunksize > 0:
        yield last_chunksize

def iter_chunksize ( num_samples , chunksize ) : last_chunksize = int ( np . mod ( num_samples , chunksize ) ) chunksize = int ( chunksize ) for _ in range ( int ( num_samples ) // chunksize ) : yield chunksize if last_chunksize > 0 : yield last_chunksize

Iterator used to iterate in chunks over an array of size num_samples . At each iteration returns chunksize except for the last iteration .


def reduce_chunk(func, array):
    """Reduce with `func`, chunk by chunk, the passed pytable `array`.
    """
    res = []
    for slice in iter_chunk_slice(array.shape[-1], array.chunkshape[-1]):
        res.append(func(array[..., slice]))
    return func(res)

def reduce_chunk ( func , array ) : res = [ ] for slice in iter_chunk_slice ( array . shape [ - 1 ] , array . chunkshape [ - 1 ] ) : res . append ( func ( array [ ... , slice ] ) ) return func ( res )

Reduce with func chunk by chunk the passed pytable array .


def map_chunk(func, array, out_array):
    """Map with `func`, chunk by chunk, the input pytable `array`.
    The result is stored in the output pytable array `out_array`.
    """
    for slice in iter_chunk_slice(array.shape[-1], array.chunkshape[-1]):
        out_array.append(func(array[..., slice]))
    return out_array

def map_chunk ( func , array , out_array ) : for slice in iter_chunk_slice ( array . shape [ - 1 ] , array . chunkshape [ - 1 ] ) : out_array . append ( func ( array [ ... , slice ] ) ) return out_array

Map with func chunk by chunk the input pytable array . The result is stored in the output pytable array out_array .


def merge_ph_times(times_list, times_par_list, time_block):
    """Build an array of timestamps joining the arrays in `ph_times_list`.
    `time_block` is the duration of each array of timestamps.
    """
    offsets = np.arange(len(times_list)) * time_block
    cum_sizes = np.cumsum([ts.size for ts in times_list])
    times = np.zeros(cum_sizes[-1])
    times_par = np.zeros(cum_sizes[-1], dtype='uint8')
    i1 = 0
    for i2, ts, ts_par, offset in zip(cum_sizes, times_list, times_par_list,
                                      offsets):
        times[i1:i2] = ts + offset
        times_par[i1:i2] = ts_par
        i1 = i2
    return times, times_par

def merge_ph_times ( times_list , times_par_list , time_block ) : offsets = np . arange ( len ( times_list ) ) * time_block cum_sizes = np . cumsum ( [ ts . size for ts in times_list ] ) times = np . zeros ( cum_sizes [ - 1 ] ) times_par = np . zeros ( cum_sizes [ - 1 ] , dtype = 'uint8' ) i1 = 0 for i2 , ts , ts_par , offset in zip ( cum_sizes , times_list , times_par_list , offsets ) : times [ i1 : i2 ] = ts + offset times_par [ i1 : i2 ] = ts_par i1 = i2 return times , times_par

Build an array of timestamps joining the arrays in ph_times_list . time_block is the duration of each array of timestamps .


def merge_DA_ph_times(ph_times_d, ph_times_a):
    """Returns a merged timestamp array for Donor+Accept. and bool mask for A.
    """
    ph_times = np.hstack([ph_times_d, ph_times_a])
    a_em = np.hstack([np.zeros(ph_times_d.size, dtype=np.bool),
                      np.ones(ph_times_a.size, dtype=np.bool)])
    index_sort = ph_times.argsort()
    return ph_times[index_sort], a_em[index_sort]

def merge_DA_ph_times ( ph_times_d , ph_times_a ) : ph_times = np . hstack ( [ ph_times_d , ph_times_a ] ) a_em = np . hstack ( [ np . zeros ( ph_times_d . size , dtype = np . bool ) , np . ones ( ph_times_a . size , dtype = np . bool ) ] ) index_sort = ph_times . argsort ( ) return ph_times [ index_sort ] , a_em [ index_sort ]

Returns a merged timestamp array for Donor + Accept . and bool mask for A .


def load_PSFLab_file(fname):
    """Load the array `data` in the .mat file `fname`."""
    if os.path.exists(fname) or os.path.exists(fname + '.mat'):
        return loadmat(fname)['data']
    else:
        raise IOError("Can't find PSF file '%s'" % fname)

def
load_PSFLab_file
(
fname
)
:
if
os
.
path
.
exists
(
fname
)
or
os
.
path
.
exists
(
fname
+
'.mat'
)
:
return
loadmat
(
fname
)
[
'data'
]
else
:
raise
IOError
(
"Can't find PSF file '%s'"
%
fname
)

Load the array data in the . mat file fname .


def hash(self):
        """Return an hash string computed on the PSF data."""
        hash_list = []
        for key, value in sorted(self.__dict__.items()):
            if not callable(value):
                if isinstance(value, np.ndarray):
                    hash_list.append(value.tostring())
                else:
                    hash_list.append(str(value))
        return hashlib.md5(repr(hash_list).encode()).hexdigest()

def hash ( self ) : hash_list = [ ] for key , value in sorted ( self . __dict__ . items ( ) ) : if not callable ( value ) : if isinstance ( value , np . ndarray ) : hash_list . append ( value . tostring ( ) ) else : hash_list . append ( str ( value ) ) return hashlib . md5 ( repr ( hash_list ) . encode ( ) ) . hexdigest ( )

Return an hash string computed on the PSF data .


def git_path_valid(git_path=None):
    """
    Check whether the git executable is found.
    """
    if git_path is None and GIT_PATH is None:
        return False
    if git_path is None: git_path = GIT_PATH
    try:
        call([git_path, '--version'])
        return True
    except OSError:
        return False

def git_path_valid ( git_path = None ) : if git_path is None and GIT_PATH is None : return False if git_path is None : git_path = GIT_PATH try : call ( [ git_path , '--version' ] ) return True except OSError : return False

Check whether the git executable is found .


def get_git_version(git_path=None):
    """
    Get the Git version.
    """
    if git_path is None: git_path = GIT_PATH
    git_version = check_output([git_path, "--version"]).split()[2]
    return git_version

def get_git_version ( git_path = None ) : if git_path is None : git_path = GIT_PATH git_version = check_output ( [ git_path , "--version" ] ) . split ( ) [ 2 ] return git_version


def check_clean_status(git_path=None):
    """
    Returns whether there are uncommitted changes in the working dir.
    """
    output = get_status(git_path)
    is_unmodified = (len(output.strip()) == 0)
    return is_unmodified

def check_clean_status ( git_path = None ) : output = get_status ( git_path ) is_unmodified = ( len ( output . strip ( ) ) == 0 ) return is_unmodified

Returns whether there are uncommitted changes in the working dir .


def get_last_commit_line(git_path=None):
    """
    Get one-line description of HEAD commit for repository in current dir.
    """
    if git_path is None: git_path = GIT_PATH
    output = check_output([git_path, "log", "--pretty=format:'%ad %h %s'",
                           "--date=short", "-n1"])
    return output.strip()[1:-1]

def
get_last_commit_line
(
git_path
=
)
:
if
git_path
is
:
git_path
=
GIT_PATH
output
=
check_output
(
[
git_path
,
"log"
,
"--pretty=format:'%ad %h %s'"
,
"--date=short"
,
"-n1"
]
)
return
output
.
strip
(
)
[
1
:
1
]

Get one - line description of HEAD commit for repository in current dir .


def get_last_commit(git_path=None):
    """
    Get the HEAD commit SHA1 of repository in current dir.
    """
    if git_path is None: git_path = GIT_PATH
    line = get_last_commit_line(git_path)
    revision_id = line.split()[1]
    return revision_id

def get_last_commit ( git_path = None ) : if git_path is None : git_path = GIT_PATH line = get_last_commit_line ( git_path ) revision_id = line . split ( ) [ 1 ] return revision_id

Get the HEAD commit SHA1 of repository in current dir .


def print_summary(string='Repository', git_path=None):
    """
    Print the last commit line and eventual uncommitted changes.
    """
    if git_path is None: git_path = GIT_PATH

    # If git is available, check fretbursts version
    if not git_path_valid():
        print('\n%s revision unknown (git not found).' % string)
    else:
        last_commit = get_last_commit_line()
        print('\n{} revision:\n {}\n'.format(string, last_commit))
        if not check_clean_status():
            print('\nWARNING -> Uncommitted changes:')
            print(get_status())

Print the last commit line and eventual uncommitted changes .


def get_bromo_fnames_da(d_em_kHz, d_bg_kHz, a_em_kHz, a_bg_kHz,
        ID='1+2+3+4+5+6', t_tot='480', num_p='30', pM='64',
        t_step=0.5e-6, D=1.2e-11, dir_=''):
    """Get filenames for donor and acceptor timestamps for the given parameters
    """

    clk_p = t_step/32. # with t_step=0.5us -> 156.25 ns
    E_sim = 1.*a_em_kHz/(a_em_kHz + d_em_kHz)

    FRET_val = 100.*E_sim
    print("Simulated FRET value: %.1f%%" % FRET_val)

    d_em_kHz_str = "%04d" % d_em_kHz
    a_em_kHz_str = "%04d" % a_em_kHz
    d_bg_kHz_str = "%04.1f" % d_bg_kHz
    a_bg_kHz_str = "%04.1f" % a_bg_kHz

    print("D: EM %s BG %s " % (d_em_kHz_str, d_bg_kHz_str))
    print("A: EM %s BG %s " % (a_em_kHz_str, a_bg_kHz_str))

    fname_d = ('ph_times_{t_tot}s_D{D}_{np}P_{pM}pM_'
               'step{ts_us}us_ID{ID}_EM{em}kHz_BG{bg}kHz.npy').format(
                       em=d_em_kHz_str, bg=d_bg_kHz_str, t_tot=t_tot, pM=pM,
                       np=num_p, ID=ID, ts_us=t_step*1e6, D=D)

    fname_a = ('ph_times_{t_tot}s_D{D}_{np}P_{pM}pM_'
               'step{ts_us}us_ID{ID}_EM{em}kHz_BG{bg}kHz.npy').format(
                       em=a_em_kHz_str, bg=a_bg_kHz_str, t_tot=t_tot, pM=pM,
                       np=num_p, ID=ID, ts_us=t_step*1e6, D=D)
    print(fname_d)
    print(fname_a)

    name = ('BroSim_E{:.1f}_dBG{:.1f}k_aBG{:.1f}k_'
            'dEM{:.0f}k').format(FRET_val, d_bg_kHz, a_bg_kHz, d_em_kHz)

    return dir_+fname_d, dir_+fname_a, name, clk_p, E_sim


def set_sim_params(self, nparams, attr_params):
        """Store parameters in `params` in `h5file.root.parameters`.

        `nparams` (dict)
            A dict as returned by `get_params()` in `ParticlesSimulation()`
            The format is:
            keys:
                used as parameter name
            values: (2-elements tuple)
                first element is the parameter value
                second element is a string used as "title" (description)
        `attr_params` (dict)
            A dict whole items are stored as attributes in '/parameters'
        """
        for name, value in nparams.items():
            val = value[0] if value[0] is not None else 'none'
            self.h5file.create_array('/parameters', name, obj=val,
                                     title=value[1])
        for name, value in attr_params.items():
            self.h5file.set_node_attr('/parameters', name, value)

def set_sim_params ( self , nparams , attr_params ) : for name , value in nparams . items ( ) : val = value [ 0 ] if value [ 0 ] is not None else 'none' self . h5file . create_array ( '/parameters' , name , obj = val , title = value [ 1 ] ) for name , value in attr_params . items ( ) : self . h5file . set_node_attr ( '/parameters' , name , value )

Store parameters in params in h5file . root . parameters .


def volume(self):
        """Box volume in m^3."""
        return (self.x2 - self.x1) * (self.y2 - self.y1) * (self.z2 - self.z1)

def volume ( self ) : return ( self . x2 - self . x1 ) * ( self . y2 - self . y1 ) * ( self . z2 - self . z1 )


def _generate(num_particles, D, box, rs):
        """Generate a list of `Particle` objects."""
        X0 = rs.rand(num_particles) * (box.x2 - box.x1) + box.x1
        Y0 = rs.rand(num_particles) * (box.y2 - box.y1) + box.y1
        Z0 = rs.rand(num_particles) * (box.z2 - box.z1) + box.z1
        return [Particle(D=D, x0=x0, y0=y0, z0=z0)
                for x0, y0, z0 in zip(X0, Y0, Z0)]

def _generate ( num_particles , D , box , rs ) : X0 = rs . rand ( num_particles ) * ( box . x2 - box . x1 ) + box . x1 Y0 = rs . rand ( num_particles ) * ( box . y2 - box . y1 ) + box . y1 Z0 = rs . rand ( num_particles ) * ( box . z2 - box . z1 ) + box . z1 return [ Particle ( D = D , x0 = x0 , y0 = y0 , z0 = z0 ) for x0 , y0 , z0 in zip ( X0 , Y0 , Z0 ) ]


def add(self, num_particles, D):
        """Add particles with diffusion coefficient `D` at random positions.
        """
        self._plist += self._generate(num_particles, D, box=self.box,
                                      rs=self.rs)

def add ( self , num_particles , D ) : self . _plist += self . _generate ( num_particles , D , box = self . box , rs = self . rs )

Add particles with diffusion coefficient D at random positions .


def datafile_from_hash(hash_, prefix, path):
        """Return pathlib.Path for a data-file with given hash and prefix.
        """
        pattern = '%s_%s*.h*' % (prefix, hash_)
        datafiles = list(path.glob(pattern))
        if len(datafiles) == 0:
            raise NoMatchError('No matches for "%s"' % pattern)
        if len(datafiles) > 1:
            raise MultipleMatchesError('More than 1 match for "%s"' % pattern)
        return datafiles[0]

def
datafile_from_hash
(
hash_
,
prefix
,
path
)
:
pattern
=
'%s_%s*.h*'
%
(
prefix
,
hash_
)
datafiles
=
list
(
path
.
glob
(
pattern
)
)
if
len
(
datafiles
)
==
0
:
raise
NoMatchError
(
'No matches for "%s"'
%
pattern
)
if
len
(
datafiles
)
>
1
:
raise
MultipleMatchesError
(
'More than 1 match for "%s"'
%
pattern
)
return
datafiles
[
0
]

Return pathlib . Path for a data - file with given hash and prefix .


def _get_group_randomstate(rs, seed, group):
        """Return a RandomState, equal to the input unless rs is None.

        When rs is None, try to get the random state from the
        'last_random_state' attribute in `group`. When not available,
        use `seed` to generate a random state. When seed is None the returned
        random state will have a random seed.
        """
        if rs is None:
            rs = np.random.RandomState(seed=seed)
            # Try to set the random state from the last session to preserve
            # a single random stream when simulating timestamps multiple times
            if 'last_random_state' in group._v_attrs:
                rs.set_state(group._v_attrs['last_random_state'])
                print("INFO: Random state set to last saved state in '%s'." %
                      group._v_name)
            else:
                print("INFO: Random state initialized from seed (%d)." % seed)
        return rs

Return a RandomState equal to the input unless rs is None .


def compact_name(self, hashsize=6):
        """Compact representation of all simulation parameters
        """
        # this can be made more robust for ID > 9 (double digit)
        s = self.compact_name_core(hashsize, t_max=True)
        s += "_ID%d-%d" % (self.ID, self.EID)
        return s


def numeric_params(self):
        """A dict containing all the simulation numeric-parameters.

        The values are 2-element tuples: first element is the value and
        second element is a string describing the parameter (metadata).
        """
        nparams = dict(
            D = (self.diffusion_coeff.mean(), 'Diffusion coefficient (m^2/s)'),
            np = (self.num_particles, 'Number of simulated particles'),
            t_step = (self.t_step, 'Simulation time-step (s)'),
            t_max = (self.t_max, 'Simulation total time (s)'),
            ID = (self.ID, 'Simulation ID (int)'),
            EID = (self.EID, 'IPython Engine ID (int)'),
            pico_mol = (self.concentration() * 1e12,
                        'Particles concentration (pM)'))
        return nparams

def
numeric_params
(
self
)
:
nparams
=
dict
(
D
=
(
self
.
diffusion_coeff
.
mean
(
)
,
'Diffusion coefficient (m^2/s)'
)
,
np
=
(
self
.
num_particles
,
'Number of simulated particles'
)
,
t_step
=
(
self
.
t_step
,
'Simulation time-step (s)'
)
,
t_max
=
(
self
.
t_max
,
'Simulation total time (s)'
)
,
ID
=
(
self
.
ID
,
'Simulation ID (int)'
)
,
EID
=
(
self
.
EID
,
'IPython Engine ID (int)'
)
,
pico_mol
=
(
self
.
concentration
(
)
*
1e12
,
'Particles concentration (pM)'
)
)
return
nparams

A dict containing all the simulation numeric - parameters .


def print_sizes(self):
        """Print on-disk array sizes required for current set of parameters."""
        float_size = 4
        MB = 1024 * 1024
        size_ = self.n_samples * float_size
        em_size = size_ * self.num_particles / MB
        pos_size = 3 * size_ * self.num_particles / MB
        print("  Number of particles:", self.num_particles)
        print("  Number of time steps:", self.n_samples)
        print("  Emission array - 1 particle (float32): %.1f MB" % (size_ / MB))
        print("  Emission array (float32): %.1f MB" % em_size)
        print("  Position array (float32): %.1f MB " % pos_size)

Print on - disk array sizes required for current set of parameters .


def simulate_diffusion(self, save_pos=False, total_emission=True,
                           radial=False, rs=None, seed=1, path='./',
                           wrap_func=wrap_periodic,
                           chunksize=2**19, chunkslice='times', verbose=True):
        """Simulate Brownian motion trajectories and emission rates.

        This method performs the Brownian motion simulation using the current
        set of parameters. Before running this method you can check the
        disk-space requirements using :method:`print_sizes`.

        Results are stored to disk in HDF5 format and are accessible in
        in `self.emission`, `self.emission_tot` and `self.position` as
        pytables arrays.

        Arguments:
            save_pos (bool): if True, save the particles 3D trajectories
            total_emission (bool): if True, store only the total emission array
                containing the sum of emission of all the particles.
            rs (RandomState object): random state object used as random number
                generator. If None, use a random state initialized from seed.
            seed (uint): when `rs` is None, `seed` is used to initialize the
                random state, otherwise is ignored.
            wrap_func (function): the function used to apply the boundary
                condition (use :func:`wrap_periodic` or :func:`wrap_mirror`).
            path (string): a folder where simulation data is saved.
            verbose (bool): if False, prints no output.
        """
        if rs is None:
            rs = np.random.RandomState(seed=seed)
        self.open_store_traj(chunksize=chunksize, chunkslice=chunkslice,
                             radial=radial, path=path)
        # Save current random state for reproducibility
        self.traj_group._v_attrs['init_random_state'] = rs.get_state()

        em_store = self.emission_tot if total_emission else self.emission

        print('- Start trajectories simulation - %s' % ctime(), flush=True)
        if verbose:
            print('[PID %d] Diffusion time:' % os.getpid(), end='')
        i_chunk = 0
        t_chunk_size = self.emission.chunkshape[1]
        chunk_duration = t_chunk_size * self.t_step

        par_start_pos = self.particles.positions
        prev_time = 0
        for time_size in iter_chunksize(self.n_samples, t_chunk_size):
            if verbose:
                curr_time = int(chunk_duration * (i_chunk + 1))
                if curr_time > prev_time:
                    print(' %ds' % curr_time, end='', flush=True)
                    prev_time = curr_time

            POS, em = self._sim_trajectories(time_size, par_start_pos, rs,
                                             total_emission=total_emission,
                                             save_pos=save_pos, radial=radial,
                                             wrap_func=wrap_func)

            ## Append em to the permanent storage
            # if total_emission, data is just a linear array
            # otherwise is a 2-D array (self.num_particles, c_size)
            em_store.append(em)
            if save_pos:
                self.position.append(np.vstack(POS).astype('float32'))
            i_chunk += 1
            self.store.h5file.flush()

        # Save current random state
        self.traj_group._v_attrs['last_random_state'] = rs.get_state()
        self.store.h5file.flush()
        print('\n- End trajectories simulation - %s' % ctime(), flush=True)

Simulate Brownian motion trajectories and emission rates .


def _sim_timestamps(self, max_rate, bg_rate, emission, i_start, rs,
                        ip_start=0, scale=10, sort=True):
        """Simulate timestamps from emission trajectories.

        Uses attributes: `.t_step`.

        Returns:
            A tuple of two arrays: timestamps and particles.
        """
        counts_chunk = sim_timetrace_bg(emission, max_rate, bg_rate,
                                        self.t_step, rs=rs)
        nrows = emission.shape[0]
        if bg_rate is not None:
            nrows += 1
        assert counts_chunk.shape == (nrows, emission.shape[1])
        max_counts = counts_chunk.max()
        if max_counts == 0:
            return np.array([], dtype=np.int64), np.array([], dtype=np.int64)

        time_start = i_start * scale
        time_stop = time_start + counts_chunk.shape[1] * scale
        ts_range = np.arange(time_start, time_stop, scale, dtype='int64')

        # Loop for each particle to compute timestamps
        times_chunk_p = []
        par_index_chunk_p = []
        for ip, counts_chunk_ip in enumerate(counts_chunk):
            # Compute timestamps for particle ip for all bins with counts
            times_c_ip = []
            for v in range(1, max_counts + 1):
                times_c_ip.append(ts_range[counts_chunk_ip >= v])

            # Stack the timestamps from different "counts"
            t = np.hstack(times_c_ip)
            # Append current particle
            times_chunk_p.append(t)
            par_index_chunk_p.append(np.full(t.size, ip + ip_start, dtype='u1'))

        # Merge the arrays of different particles
        times_chunk = np.hstack(times_chunk_p)
        par_index_chunk = np.hstack(par_index_chunk_p)

        if sort:
            # Sort timestamps inside the merged chunk
            index_sort = times_chunk.argsort(kind='mergesort')
            times_chunk = times_chunk[index_sort]
            par_index_chunk = par_index_chunk[index_sort]

        return times_chunk, par_index_chunk

Simulate timestamps from emission trajectories .


def simulate_timestamps_mix(self, max_rates, populations, bg_rate,
                                rs=None, seed=1, chunksize=2**16,
                                comp_filter=None, overwrite=False,
                                skip_existing=False, scale=10,
                                path=None, t_chunksize=None, timeslice=None):
        """Compute one timestamps array for a mixture of N populations.

        Timestamp data are saved to disk and accessible as pytables arrays in
        `._timestamps` and `._tparticles`.
        The background generated timestamps are assigned a
        conventional particle number (last particle index + 1).

        Arguments:
            max_rates (list): list of the peak max emission rate for each
                population.
            populations (list of slices): slices to `self.particles`
                defining each population.
            bg_rate (float, cps): rate for a Poisson background process
            rs (RandomState object): random state object used as random number
                generator. If None, use a random state initialized from seed.
            seed (uint): when `rs` is None, `seed` is used to initialize the
                random state, otherwise is ignored.
            chunksize (int): chunk size used for the on-disk timestamp array
            comp_filter (tables.Filter or None): compression filter to use
                for the on-disk `timestamps` and `tparticles` arrays.
                If None use default compression.
            overwrite (bool): if True, overwrite any pre-existing timestamps
                array. If False, never overwrite. The outcome of simulating an
                existing array is controlled by `skip_existing` flag.
            skip_existing (bool): if True, skip simulation if the same
                timestamps array is already present.
            scale (int): `self.t_step` is multiplied by `scale` to obtain the
                timestamps units in seconds.
            path (string): folder where to save the data.
            timeslice (float or None): timestamps are simulated until
                `timeslice` seconds. If None, simulate until `self.t_max`.
        """
        self.open_store_timestamp(chunksize=chunksize, path=path)
        rs = self._get_group_randomstate(rs, seed, self.ts_group)
        if t_chunksize is None:
            t_chunksize = self.emission.chunkshape[1]
        timeslice_size = self.n_samples
        if timeslice is not None:
            timeslice_size = timeslice // self.t_step

        name = self._get_ts_name_mix(max_rates, populations, bg_rate, rs=rs)
        kw = dict(name=name, clk_p=self.t_step / scale,
                  max_rates=max_rates, bg_rate=bg_rate, populations=populations,
                  num_particles=self.num_particles,
                  bg_particle=self.num_particles,
                  overwrite=overwrite, chunksize=chunksize)
        if comp_filter is not None:
            kw.update(comp_filter=comp_filter)
        try:
            self._timestamps, self._tparticles = (self.ts_store
                                                  .add_timestamps(**kw))
        except ExistingArrayError as e:
            if skip_existing:
                print(' - Skipping already present timestamps array.')
                return
            else:
                raise e

        self.ts_group._v_attrs['init_random_state'] = rs.get_state()
        self._timestamps.attrs['init_random_state'] = rs.get_state()
        self._timestamps.attrs['PyBroMo'] = __version__

        ts_list, part_list = [], []
        # Load emission in chunks, and save only the final timestamps
        bg_rates = [None] * (len(max_rates) - 1) + [bg_rate]
        prev_time = 0
        for i_start, i_end in iter_chunk_index(timeslice_size, t_chunksize):

            curr_time = np.around(i_start * self.t_step, decimals=0)
            if curr_time > prev_time:
                print(' %.1fs' % curr_time, end='', flush=True)
                prev_time = curr_time

            em_chunk = self.emission[:, i_start:i_end]

            times_chunk_s, par_index_chunk_s = \
                self._sim_timestamps_populations(
                    em_chunk, max_rates, populations, bg_rates, i_start,
                    rs, scale)

            # Save sorted timestamps (suffix '_s') and corresponding particles
            ts_list.append(times_chunk_s)
            part_list.append(par_index_chunk_s)

        for ts, part in zip(ts_list, part_list):
            self._timestamps.append(ts)
            self._tparticles.append(part)

        # Save current random state so it can be resumed in the next session
        self.ts_group._v_attrs['last_random_state'] = rs.get_state()
        self._timestamps.attrs['last_random_state'] = rs.get_state()
        self.ts_store.h5file.flush()

Compute one timestamps array for a mixture of N populations .


def merge_da(ts_d, ts_par_d, ts_a, ts_par_a):
    """Merge donor and acceptor timestamps and particle arrays.

    Parameters:
        ts_d (array): donor timestamp array
        ts_par_d (array): donor particles array
        ts_a (array): acceptor timestamp array
        ts_par_a (array): acceptor particles array

    Returns:
        Arrays: timestamps, acceptor bool mask, timestamp particle
    """
    ts = np.hstack([ts_d, ts_a])
    ts_par = np.hstack([ts_par_d, ts_par_a])
    a_ch = np.hstack([np.zeros(ts_d.shape[0], dtype=bool),
                      np.ones(ts_a.shape[0], dtype=bool)])
    index_sort = ts.argsort()
    return ts[index_sort], a_ch[index_sort], ts_par[index_sort]

def merge_da ( ts_d , ts_par_d , ts_a , ts_par_a ) : ts = np . hstack ( [ ts_d , ts_a ] ) ts_par = np . hstack ( [ ts_par_d , ts_par_a ] ) a_ch = np . hstack ( [ np . zeros ( ts_d . shape [ 0 ] , dtype = bool ) , np . ones ( ts_a . shape [ 0 ] , dtype = bool ) ] ) index_sort = ts . argsort ( ) return ts [ index_sort ] , a_ch [ index_sort ] , ts_par [ index_sort ]

Merge donor and acceptor timestamps and particle arrays .


def em_rates_from_E_DA_mix(em_rates_tot, E_values):
    """D and A emission rates for two populations.
    """
    em_rates_d, em_rates_a = [], []
    for em_rate_tot, E_value in zip(em_rates_tot, E_values):
        em_rate_di, em_rate_ai = em_rates_from_E_DA(em_rate_tot, E_value)
        em_rates_d.append(em_rate_di)
        em_rates_a.append(em_rate_ai)
    return em_rates_d, em_rates_a

D and A emission rates for two populations .


def populations_diff_coeff(particles, populations):
    """Diffusion coefficients of the two specified populations.
    """
    D_counts = particles.diffusion_coeff_counts
    if len(D_counts) == 1:
        pop_sizes = [pop.stop - pop.start for pop in populations]
        assert D_counts[0][1] >= sum(pop_sizes)
        D_counts = [(D_counts[0][0], ps) for ps in pop_sizes]

    D_list = []
    D_pop_start = 0  # start index of diffusion-based populations
    for pop, (D, counts) in zip(populations, D_counts):
        D_list.append(D)
        assert pop.start >= D_pop_start
        assert pop.stop <= D_pop_start + counts
        D_pop_start += counts
    return D_list

Diffusion coefficients of the two specified populations .


def populations_slices(particles, num_pop_list):
    """2-tuple of slices for selection of two populations.
    """
    slices = []
    i_prev = 0
    for num_pop in num_pop_list:
        slices.append(slice(i_prev, i_prev + num_pop))
        i_prev += num_pop
    return slices

def populations_slices ( particles , num_pop_list ) : slices = [ ] i_prev = 0 for num_pop in num_pop_list : slices . append ( slice ( i_prev , i_prev + num_pop ) ) i_prev += num_pop return slices

2 - tuple of slices for selection of two populations .


def _calc_hash_da(self, rs):
        """Compute hash of D and A timestamps for single-step D+A case.
        """
        self.hash_d = hash_(rs.get_state())[:6]
        self.hash_a = self.hash_d

def _calc_hash_da ( self , rs ) : self . hash_d = hash_ ( rs . get_state ( ) ) [ : 6 ] self . hash_a = self . hash_d

Compute hash of D and A timestamps for single - step D + A case .


def merge_da(self):
        """Merge donor and acceptor timestamps, computes `ts`, `a_ch`, `part`.
        """
        print(' - Merging D and A timestamps', flush=True)
        ts_d, ts_par_d = self.S.get_timestamps_part(self.name_timestamps_d)
        ts_a, ts_par_a = self.S.get_timestamps_part(self.name_timestamps_a)
        ts, a_ch, part = merge_da(ts_d, ts_par_d, ts_a, ts_par_a)
        assert a_ch.sum() == ts_a.shape[0]
        assert (~a_ch).sum() == ts_d.shape[0]
        assert a_ch.size == ts_a.shape[0] + ts_d.shape[0]
        self.ts, self.a_ch, self.part = ts, a_ch, part
        self.clk_p = ts_d.attrs['clk_p']

def
merge_da
(
self
)
:
print
(
' - Merging D and A timestamps'
,
flush
=
True
)
ts_d
,
ts_par_d
=
self
.
S
.
get_timestamps_part
(
self
.
name_timestamps_d
)
ts_a
,
ts_par_a
=
self
.
S
.
get_timestamps_part
(
self
.
name_timestamps_a
)
ts
,
a_ch
,
part
=
merge_da
(
ts_d
,
ts_par_d
,
ts_a
,
ts_par_a
)
assert
a_ch
.
sum
(
)
==
ts_a
.
shape
[
0
]
assert
(
~
a_ch
)
.
sum
(
)
==
ts_d
.
shape
[
0
]
assert
a_ch
.
size
==
ts_a
.
shape
[
0
]
+
ts_d
.
shape
[
0
]
self
.
ts
,
self
.
a_ch
,
self
.
part
=
ts
,
a_ch
,
part
self
.
clk_p
=
ts_d
.
attrs
[
'clk_p'
]

Merge donor and acceptor timestamps computes ts a_ch part .


def save_photon_hdf5(self, identity=None, overwrite=True, path=None):
        """Create a smFRET Photon-HDF5 file with current timestamps."""
        filepath = self.filepath
        if path is not None:
            filepath = Path(path, filepath.name)
        self.merge_da()
        data = self._make_photon_hdf5(identity=identity)
        phc.hdf5.save_photon_hdf5(data, h5_fname=str(filepath),
                                  overwrite=overwrite)

def save_photon_hdf5 ( self , identity = None , overwrite = True , path = None ) : filepath = self . filepath if path is not None : filepath = Path ( path , filepath . name ) self . merge_da ( ) data = self . _make_photon_hdf5 ( identity = identity ) phc . hdf5 . save_photon_hdf5 ( data , h5_fname = str ( filepath ) , overwrite = overwrite )

Create a smFRET Photon - HDF5 file with current timestamps .


def print_attrs(data_file, node_name='/', which='user', compress=False):
    """Print the HDF5 attributes for `node_name`.

    Parameters:
        data_file (pytables HDF5 file object): the data file to print
        node_name (string): name of the path inside the file to be printed.
            Can be either a group or a leaf-node. Default: '/', the root node.
        which (string): Valid values are 'user' for user-defined attributes,
            'sys' for pytables-specific attributes and 'all' to print both
            groups of attributes. Default 'user'.
        compress (bool): if True displays at most a line for each attribute.
            Default False.
    """
    node = data_file.get_node(node_name)
    print ('List of attributes for:\n  %s\n' % node)
    for attr in node._v_attrs._f_list():
        print ('\t%s' % attr)
        attr_content = repr(node._v_attrs[attr])
        if compress:
            attr_content = attr_content.split('\n')[0]
        print ("\t    %s" % attr_content)

def
print_attrs
(
data_file
,
node_name
=
'/'
,
which
=
'user'
,
compress
=
False
)
:
node
=
data_file
.
get_node
(
node_name
)
print
(
'List of attributes for:\n  %s\n'
%
node
)
for
attr
in
node
.
_v_attrs
.
_f_list
(
)
:
print
(
'\t%s'
%
attr
)
attr_content
=
repr
(
node
.
_v_attrs
[
attr
]
)
if
compress
:
attr_content
=
attr_content
.
split
(
'\n'
)
[
0
]
print
(
"\t    %s"
%
attr_content
)

Print the HDF5 attributes for node_name .


def print_children(data_file, group='/'):
    """Print all the sub-groups in `group` and leaf-nodes children of `group`.

    Parameters:
        data_file (pytables HDF5 file object): the data file to print
        group (string): path name of the group to be printed.
            Default: '/', the root node.
    """
    base = data_file.get_node(group)
    print ('Groups in:\n  %s\n' % base)

    for node in base._f_walk_groups():
        if node is not base:
            print ('    %s' % node)

    print ('\nLeaf-nodes in %s:' % group)
    for node in base._v_leaves.itervalues():
        info = node.shape
        if len(info) == 0:
            info = node.read()
        print ('\t%s, %s' % (node.name, info))
        if len(node.title) > 0:
            print ('\t    %s' % node.title)

def
print_children
(
data_file
,
group
=
'/'
)
:
base
=
data_file
.
get_node
(
group
)
print
(
'Groups in:\n  %s\n'
%
base
)
for
node
in
base
.
_f_walk_groups
(
)
:
if
node
is
not
base
:
print
(
'    %s'
%
node
)
print
(
'\nLeaf-nodes in %s:'
%
group
)
for
node
in
base
.
_v_leaves
.
itervalues
(
)
:
info
=
node
.
shape
if
len
(
info
)
==
0
:
info
=
node
.
read
(
)
print
(
'\t%s, %s'
%
(
node
.
name
,
info
)
)
if
len
(
node
.
title
)
>
0
:
print
(
'\t    %s'
%
node
.
title
)

Print all the sub - groups in group and leaf - nodes children of group .


def fit(self, trX, trY, batch_size=64, n_epochs=1, len_filter=LenFilter(), snapshot_freq=1, path=None):
        """Train model on given training examples and return the list of costs after each minibatch is processed.

        Args:
          trX (list) -- Inputs
          trY (list) -- Outputs
          batch_size (int, optional) -- number of examples in a minibatch (default 64)
          n_epochs (int, optional)  -- number of epochs to train for (default 1)
          len_filter (object, optional) -- object to filter training example by length (default LenFilter())
          snapshot_freq (int, optional) -- number of epochs between saving model snapshots (default 1)
          path (str, optional) -- prefix of path where model snapshots are saved.
            If None, no snapshots are saved (default None)

        Returns:
          list -- costs of model after processing each minibatch
        """
        if len_filter is not None:
            trX, trY = len_filter.filter(trX, trY)
        trY = standardize_targets(trY, cost=self.cost)

        n = 0.
        t = time()
        costs = []
        for e in range(n_epochs):
            epoch_costs = []
            for xmb, ymb in self.iterator.iterXY(trX, trY):
                c = self._train(xmb, ymb)
                epoch_costs.append(c)
                n += len(ymb)
                if self.verbose >= 2:
                    n_per_sec = n / (time() - t)
                    n_left = len(trY) - n % len(trY)
                    time_left = n_left/n_per_sec
                    sys.stdout.write("\rEpoch %d Seen %d samples Avg cost %0.4f Time left %d seconds" % (e, n, np.mean(epoch_costs[-250:]), time_left))
                    sys.stdout.flush()
            costs.extend(epoch_costs)

            status = "Epoch %d Seen %d samples Avg cost %0.4f Time elapsed %d seconds" % (e, n, np.mean(epoch_costs[-250:]), time() - t)
            if self.verbose >= 2:
                sys.stdout.write("\r"+status)
                sys.stdout.flush()
                sys.stdout.write("\n")
            elif self.verbose == 1:
                print(status)
            if path and e % snapshot_freq == 0:
                save(self, "{0}.{1}".format(path, e))
        return costs

Train model on given training examples and return the list of costs after each minibatch is processed .


def plane_xz(size=(10, 10), resolution=(10, 10)) -> VAO:
    """
    Generates a plane on the xz axis of a specific size and resolution.
    Normals and texture coordinates are also included.

    Args:
        size: (x, y) tuple
        resolution: (x, y) tuple

    Returns:
        A :py:class:`demosys.opengl.vao.VAO` instance
    """
    sx, sz = size
    rx, rz = resolution
    dx, dz = sx / rx, sz / rz  # step
    ox, oz = -sx / 2, -sz / 2  # start offset

    def gen_pos():
        for z in range(rz):
            for x in range(rx):
                yield ox + x * dx
                yield 0
                yield oz + z * dz

    def gen_uv():
        for z in range(rz):
            for x in range(rx):
                yield x / (rx - 1)
                yield 1 - z / (rz - 1)

    def gen_normal():
        for _ in range(rx * rz):
            yield 0.0
            yield 1.0
            yield 0.0

    def gen_index():
        for z in range(rz - 1):
            for x in range(rx - 1):
                # quad poly left
                yield z * rz + x + 1
                yield z * rz + x
                yield z * rz + x + rx
                # quad poly right
                yield z * rz + x + 1
                yield z * rz + x + rx
                yield z * rz + x + rx + 1

    pos_data = numpy.fromiter(gen_pos(), dtype=numpy.float32)
    uv_data = numpy.fromiter(gen_uv(), dtype=numpy.float32)
    normal_data = numpy.fromiter(gen_normal(), dtype=numpy.float32)
    index_data = numpy.fromiter(gen_index(), dtype=numpy.uint32)

    vao = VAO("plane_xz", mode=moderngl.TRIANGLES)

    vao.buffer(pos_data, '3f', ['in_position'])
    vao.buffer(uv_data, '2f', ['in_uv'])
    vao.buffer(normal_data, '3f', ['in_normal'])

    vao.index_buffer(index_data, index_element_size=4)

    return vao

def
plane_xz
(
size
=
(
10
,
10
)
,
resolution
=
(
10
,
10
)
)
->
VAO
:
sx
,
sz
=
size
rx
,
rz
=
resolution
dx
,
dz
=
sx
/
rx
,
sz
/
rz
# step
ox
,
oz
=
sx
/
2
,
sz
/
2
# start offset
def
gen_pos
(
)
:
for
z
in
range
(
rz
)
:
for
x
in
range
(
rx
)
:
yield
ox
+
x
*
dx
yield
0
yield
oz
+
z
*
dz
def
gen_uv
(
)
:
for
z
in
range
(
rz
)
:
for
x
in
range
(
rx
)
:
yield
x
/
(
rx
1
)
yield
1
z
/
(
rz
1
)
def
gen_normal
(
)
:
for
_
in
range
(
rx
*
rz
)
:
yield
0.0
yield
1.0
yield
0.0
def
gen_index
(
)
:
for
z
in
range
(
rz
1
)
:
for
x
in
range
(
rx
1
)
:
# quad poly left
yield
z
*
rz
+
x
+
1
yield
z
*
rz
+
x
yield
z
*
rz
+
x
+
rx
# quad poly right
yield
z
*
rz
+
x
+
1
yield
z
*
rz
+
x
+
rx
yield
z
*
rz
+
x
+
rx
+
1
pos_data
=
numpy
.
fromiter
(
gen_pos
(
)
,
dtype
=
numpy
.
float32
)
uv_data
=
numpy
.
fromiter
(
gen_uv
(
)
,
dtype
=
numpy
.
float32
)
normal_data
=
numpy
.
fromiter
(
gen_normal
(
)
,
dtype
=
numpy
.
float32
)
index_data
=
numpy
.
fromiter
(
gen_index
(
)
,
dtype
=
numpy
.
uint32
)
vao
=
VAO
(
"plane_xz"
,
mode
=
moderngl
.
TRIANGLES
)
vao
.
buffer
(
pos_data
,
'3f'
,
[
'in_position'
]
)
vao
.
buffer
(
uv_data
,
'2f'
,
[
'in_uv'
]
)
vao
.
buffer
(
normal_data
,
'3f'
,
[
'in_normal'
]
)
vao
.
index_buffer
(
index_data
,
index_element_size
=
4
)
return
vao

Generates a plane on the xz axis of a specific size and resolution . Normals and texture coordinates are also included .


def load(self):
        """
        Deferred loading of the scene

        :param scene: The scene object
        :param file: Resolved path if changed by finder
        """
        self.path = self.find_scene(self.meta.path)
        if not self.path:
            raise ValueError("Scene '{}' not found".format(self.meta.path))

        self.scene = Scene(self.path)

        # Load gltf json file
        if self.path.suffix == '.gltf':
            self.load_gltf()

        # Load binary gltf file
        if self.path.suffix == '.glb':
            self.load_glb()

        self.meta.check_version()
        self.meta.check_extensions(self.supported_extensions)
        self.load_images()
        self.load_samplers()
        self.load_textures()
        self.load_materials()
        self.load_meshes()
        self.load_nodes()

        self.scene.calc_scene_bbox()
        self.scene.prepare()

        return self.scene

def
load
(
self
)
:
self
.
path
=
self
.
find_scene
(
self
.
meta
.
path
)
if
not
self
.
path
:
raise
ValueError
(
"Scene '{}' not found"
.
format
(
self
.
meta
.
path
)
)
self
.
scene
=
Scene
(
self
.
path
)
# Load gltf json file
if
self
.
path
.
suffix
==
'.gltf'
:
self
.
load_gltf
(
)
# Load binary gltf file
if
self
.
path
.
suffix
==
'.glb'
:
self
.
load_glb
(
)
self
.
meta
.
check_version
(
)
self
.
meta
.
check_extensions
(
self
.
supported_extensions
)
self
.
load_images
(
)
self
.
load_samplers
(
)
self
.
load_textures
(
)
self
.
load_materials
(
)
self
.
load_meshes
(
)
self
.
load_nodes
(
)
self
.
scene
.
calc_scene_bbox
(
)
self
.
scene
.
prepare
(
)
return
self
.
scene


def load_gltf(self):
        """Loads a gltf json file"""
        with open(self.path) as fd:
            self.meta = GLTFMeta(self.path, json.load(fd))

def load_gltf ( self ) : with open ( self . path ) as fd : self . meta = GLTFMeta ( self . path , json . load ( fd ) )


def load_glb(self):
        """Loads a binary gltf file"""
        with open(self.path, 'rb') as fd:
            # Check header
            magic = fd.read(4)
            if magic != GLTF_MAGIC_HEADER:
                raise ValueError("{} has incorrect header {} != {}".format(self.path, magic, GLTF_MAGIC_HEADER))

            version = struct.unpack('<I', fd.read(4))[0]
            if version != 2:
                raise ValueError("{} has unsupported version {}".format(self.path, version))

            # Total file size including headers
            _ = struct.unpack('<I', fd.read(4))[0]  # noqa

            # Chunk 0 - json
            chunk_0_length = struct.unpack('<I', fd.read(4))[0]
            chunk_0_type = fd.read(4)
            if chunk_0_type != b'JSON':
                raise ValueError("Expected JSON chunk, not {} in file {}".format(chunk_0_type, self.path))

            json_meta = fd.read(chunk_0_length).decode()

            # chunk 1 - binary buffer
            chunk_1_length = struct.unpack('<I', fd.read(4))[0]
            chunk_1_type = fd.read(4)
            if chunk_1_type != b'BIN\x00':
                raise ValueError("Expected BIN chunk, not {} in file {}".format(chunk_1_type, self.path))

            self.meta = GLTFMeta(self.path, json.loads(json_meta), binary_buffer=fd.read(chunk_1_length))


def buffers_exist(self):
        """Checks if the bin files referenced exist"""
        for buff in self.buffers:
            if not buff.is_separate_file:
                continue

            path = self.path.parent / buff.uri
            if not os.path.exists(path):
                raise FileNotFoundError("Buffer {} referenced in {} not found".format(path, self.path))

def
buffers_exist
(
self
)
:
for
buff
in
self
.
buffers
:
if
not
buff
.
is_separate_file
:
continue
path
=
self
.
path
.
parent
/
buff
.
uri
if
not
os
.
path
.
exists
(
path
)
:
raise
FileNotFoundError
(
"Buffer {} referenced in {} not found"
.
format
(
path
,
self
.
path
)
)


def prepare_attrib_mapping(self, primitive):
        """Pre-parse buffer mappings for each VBO to detect interleaved data for a primitive"""
        buffer_info = []
        for name, accessor in primitive.attributes.items():
            info = VBOInfo(*accessor.info())
            info.attributes.append((name, info.components))

            if buffer_info and buffer_info[-1].buffer_view == info.buffer_view:
                if buffer_info[-1].interleaves(info):
                    buffer_info[-1].merge(info)
                    continue

            buffer_info.append(info)

        return buffer_info

def prepare_attrib_mapping ( self , primitive ) : buffer_info = [ ] for name , accessor in primitive . attributes . items ( ) : info = VBOInfo ( * accessor . info ( ) ) info . attributes . append ( ( name , info . components ) ) if buffer_info and buffer_info [ - 1 ] . buffer_view == info . buffer_view : if buffer_info [ - 1 ] . interleaves ( info ) : buffer_info [ - 1 ] . merge ( info ) continue buffer_info . append ( info ) return buffer_info

Pre - parse buffer mappings for each VBO to detect interleaved data for a primitive


def get_bbox(self, primitive):
        """Get the bounding box for the mesh"""
        accessor = primitive.attributes.get('POSITION')
        return accessor.min, accessor.max

def get_bbox ( self , primitive ) : accessor = primitive . attributes . get ( 'POSITION' ) return accessor . min , accessor . max


def interleaves(self, info):
        """Does the buffer interleave with this one?"""
        return info.byte_offset == self.component_type.size * self.components

def interleaves ( self , info ) : return info . byte_offset == self . component_type . size * self . components


def create(self):
        """Create the VBO"""
        dtype = NP_COMPONENT_DTYPE[self.component_type.value]
        data = numpy.frombuffer(
            self.buffer.read(byte_length=self.byte_length, byte_offset=self.byte_offset),
            count=self.count * self.components,
            dtype=dtype,
        )
        return dtype, data

def create ( self ) : dtype = NP_COMPONENT_DTYPE [ self . component_type . value ] data = numpy . frombuffer ( self . buffer . read ( byte_length = self . byte_length , byte_offset = self . byte_offset ) , count = self . count * self . components , dtype = dtype , ) return dtype , data


def set_position(self, x, y, z):
        """
        Set the 3D position of the camera

        :param x: float
        :param y: float
        :param z: float
        """
        self.position = Vector3([x, y, z])

def set_position ( self , x , y , z ) : self . position = Vector3 ( [ x , y , z ] )


def _update_yaw_and_pitch(self):
        """
        Updates the camera vectors based on the current yaw and pitch
        """
        front = Vector3([0.0, 0.0, 0.0])
        front.x = cos(radians(self.yaw)) * cos(radians(self.pitch))
        front.y = sin(radians(self.pitch))
        front.z = sin(radians(self.yaw)) * cos(radians(self.pitch))

        self.dir = vector.normalise(front)
        self.right = vector.normalise(vector3.cross(self.dir, self._up))
        self.up = vector.normalise(vector3.cross(self.right, self.dir))

def _update_yaw_and_pitch ( self ) : front = Vector3 ( [ 0.0 , 0.0 , 0.0 ] ) front . x = cos ( radians ( self . yaw ) ) * cos ( radians ( self . pitch ) ) front . y = sin ( radians ( self . pitch ) ) front . z = sin ( radians ( self . yaw ) ) * cos ( radians ( self . pitch ) ) self . dir = vector . normalise ( front ) self . right = vector . normalise ( vector3 . cross ( self . dir , self . _up ) ) self . up = vector . normalise ( vector3 . cross ( self . right , self . dir ) )


def look_at(self, vec=None, pos=None):
        """
        Look at a specific point

        :param vec: Vector3 position
        :param pos: python list [x, y, x]
        :return: Camera matrix
        """
        if pos is None:
            vec = Vector3(pos)

        if vec is None:
            raise ValueError("vector or pos must be set")

        return self._gl_look_at(self.position, vec, self._up)

def
look_at
(
self
,
vec
=
,
pos
=
)
:
if
pos
is
:
vec
=
Vector3
(
pos
)
if
vec
is
:
raise
ValueError
(
"vector or pos must be set"
)
return
self
.
_gl_look_at
(
self
.
position
,
vec
,
self
.
_up
)


def _gl_look_at(self, pos, target, up):
        """
        The standard lookAt method

        :param pos: current position
        :param target: target position to look at
        :param up: direction up
        """
        z = vector.normalise(pos - target)
        x = vector.normalise(vector3.cross(vector.normalise(up), z))
        y = vector3.cross(z, x)

        translate = matrix44.create_identity()
        translate[3][0] = -pos.x
        translate[3][1] = -pos.y
        translate[3][2] = -pos.z

        rotate = matrix44.create_identity()
        rotate[0][0] = x[0]  # -- X
        rotate[1][0] = x[1]
        rotate[2][0] = x[2]
        rotate[0][1] = y[0]  # -- Y
        rotate[1][1] = y[1]
        rotate[2][1] = y[2]
        rotate[0][2] = z[0]  # -- Z
        rotate[1][2] = z[1]
        rotate[2][2] = z[2]

        return matrix44.multiply(translate, rotate)

def
_gl_look_at
(
self
,
pos
,
target
,
up
)
:
z
=
vector
.
normalise
(
pos
target
)
x
=
vector
.
normalise
(
vector3
.
cross
(
vector
.
normalise
(
up
)
,
z
)
)
y
=
vector3
.
cross
(
z
,
x
)
translate
=
matrix44
.
create_identity
(
)
translate
[
3
]
[
0
]
=
pos
.
x
translate
[
3
]
[
1
]
=
pos
.
y
translate
[
3
]
[
2
]
=
pos
.
z
rotate
=
matrix44
.
create_identity
(
)
rotate
[
0
]
[
0
]
=
x
[
0
]
# -- X
rotate
[
1
]
[
0
]
=
x
[
1
]
rotate
[
2
]
[
0
]
=
x
[
2
]
rotate
[
0
]
[
1
]
=
y
[
0
]
# -- Y
rotate
[
1
]
[
1
]
=
y
[
1
]
rotate
[
2
]
[
1
]
=
y
[
2
]
rotate
[
0
]
[
2
]
=
z
[
0
]
# -- Z
rotate
[
1
]
[
2
]
=
z
[
1
]
rotate
[
2
]
[
2
]
=
z
[
2
]
return
matrix44
.
multiply
(
translate
,
rotate
)


def move_state(self, direction, activate):
        """
        Set the camera position move state

        :param direction: What direction to update
        :param activate: Start or stop moving in the direction
        """
        if direction == RIGHT:
            self._xdir = POSITIVE if activate else STILL
        elif direction == LEFT:
            self._xdir = NEGATIVE if activate else STILL
        elif direction == FORWARD:
            self._zdir = NEGATIVE if activate else STILL
        elif direction == BACKWARD:
            self._zdir = POSITIVE if activate else STILL
        elif direction == UP:
            self._ydir = POSITIVE if activate else STILL
        elif direction == DOWN:
            self._ydir = NEGATIVE if activate else STILL

def move_state ( self , direction , activate ) : if direction == RIGHT : self . _xdir = POSITIVE if activate else STILL elif direction == LEFT : self . _xdir = NEGATIVE if activate else STILL elif direction == FORWARD : self . _zdir = NEGATIVE if activate else STILL elif direction == BACKWARD : self . _zdir = POSITIVE if activate else STILL elif direction == UP : self . _ydir = POSITIVE if activate else STILL elif direction == DOWN : self . _ydir = NEGATIVE if activate else STILL


def rot_state(self, x, y):
        """
        Set the rotation state of the camera

        :param x: viewport x pos
        :param y: viewport y pos
        """
        if self.last_x is None:
            self.last_x = x
        if self.last_y is None:
            self.last_y = y

        x_offset = self.last_x - x
        y_offset = self.last_y - y

        self.last_x = x
        self.last_y = y

        x_offset *= self.mouse_sensitivity
        y_offset *= self.mouse_sensitivity

        self.yaw -= x_offset
        self.pitch += y_offset

        if self.pitch > 85.0:
            self.pitch = 85.0
        if self.pitch < -85.0:
            self.pitch = -85.0

        self._update_yaw_and_pitch()

def rot_state ( self , x , y ) : if self . last_x is None : self . last_x = x if self . last_y is None : self . last_y = y x_offset = self . last_x - x y_offset = self . last_y - y self . last_x = x self . last_y = y x_offset *= self . mouse_sensitivity y_offset *= self . mouse_sensitivity self . yaw -= x_offset self . pitch += y_offset if self . pitch > 85.0 : self . pitch = 85.0 if self . pitch < - 85.0 : self . pitch = - 85.0 self . _update_yaw_and_pitch ( )


def _translate_string(self, data, length):
        """Translate string into character texture positions"""
        for index, char in enumerate(data):
            if index == length:
                break

            yield self._meta.characters - 1 - self._ct[char]

def _translate_string ( self , data , length ) : for index , char in enumerate ( data ) : if index == length : break yield self . _meta . characters - 1 - self . _ct [ char ]


def init(window=None, project=None, timeline=None):
    """
    Initialize, load and run

    :param manager: The effect manager to use
    """
    from demosys.effects.registry import Effect
    from demosys.scene import camera

    window.timeline = timeline

    # Inject attributes into the base Effect class
    setattr(Effect, '_window', window)
    setattr(Effect, '_ctx', window.ctx)
    setattr(Effect, '_project', project)

    # Set up the default system camera
    window.sys_camera = camera.SystemCamera(aspect=window.aspect_ratio, fov=60.0, near=1, far=1000)
    setattr(Effect, '_sys_camera', window.sys_camera)

    print("Loading started at", time.time())
    project.load()

    # Initialize timer
    timer_cls = import_string(settings.TIMER)
    window.timer = timer_cls()
    window.timer.start()


def draw(self, projection_matrix=None, camera_matrix=None, time=0):
        """
        Draw all the nodes in the scene

        :param projection_matrix: projection matrix (bytes)
        :param camera_matrix: camera_matrix (bytes)
        :param time: The current time
        """
        projection_matrix = projection_matrix.astype('f4').tobytes()
        camera_matrix = camera_matrix.astype('f4').tobytes()

        for node in self.root_nodes:
            node.draw(
                projection_matrix=projection_matrix,
                camera_matrix=camera_matrix,
                time=time,
            )

        self.ctx.clear_samplers(0, 4)

def draw ( self , projection_matrix = None , camera_matrix = None , time = 0 ) : projection_matrix = projection_matrix . astype ( 'f4' ) . tobytes ( ) camera_matrix = camera_matrix . astype ( 'f4' ) . tobytes ( ) for node in self . root_nodes : node . draw ( projection_matrix = projection_matrix , camera_matrix = camera_matrix , time = time , ) self . ctx . clear_samplers ( 0 , 4 )


def draw_bbox(self, projection_matrix=None, camera_matrix=None, all=True):
        """Draw scene and mesh bounding boxes"""
        projection_matrix = projection_matrix.astype('f4').tobytes()
        camera_matrix = camera_matrix.astype('f4').tobytes()

        # Scene bounding box
        self.bbox_program["m_proj"].write(projection_matrix)
        self.bbox_program["m_view"].write(self._view_matrix.astype('f4').tobytes())
        self.bbox_program["m_cam"].write(camera_matrix)
        self.bbox_program["bb_min"].write(self.bbox_min.astype('f4').tobytes())
        self.bbox_program["bb_max"].write(self.bbox_max.astype('f4').tobytes())
        self.bbox_program["color"].value = (1.0, 0.0, 0.0)
        self.bbox_vao.render(self.bbox_program)

        if not all:
            return

        # Draw bounding box for children
        for node in self.root_nodes:
            node.draw_bbox(projection_matrix, camera_matrix, self.bbox_program, self.bbox_vao)

def
draw_bbox
(
self
,
projection_matrix
=
,
camera_matrix
=
,
all
=
True
)
:
projection_matrix
=
projection_matrix
.
astype
(
'f4'
)
.
tobytes
(
)
camera_matrix
=
camera_matrix
.
astype
(
'f4'
)
.
tobytes
(
)
# Scene bounding box
self
.
bbox_program
[
"m_proj"
]
.
write
(
projection_matrix
)
self
.
bbox_program
[
"m_view"
]
.
write
(
self
.
_view_matrix
.
astype
(
'f4'
)
.
tobytes
(
)
)
self
.
bbox_program
[
"m_cam"
]
.
write
(
camera_matrix
)
self
.
bbox_program
[
"bb_min"
]
.
write
(
self
.
bbox_min
.
astype
(
'f4'
)
.
tobytes
(
)
)
self
.
bbox_program
[
"bb_max"
]
.
write
(
self
.
bbox_max
.
astype
(
'f4'
)
.
tobytes
(
)
)
self
.
bbox_program
[
"color"
]
.
value
=
(
1.0
,
0.0
,
0.0
)
self
.
bbox_vao
.
render
(
self
.
bbox_program
)
if
not
all
:
return
# Draw bounding box for children
for
node
in
self
.
root_nodes
:
node
.
draw_bbox
(
projection_matrix
,
camera_matrix
,
self
.
bbox_program
,
self
.
bbox_vao
)


def apply_mesh_programs(self, mesh_programs=None):
        """Applies mesh programs to meshes"""
        if not mesh_programs:
            mesh_programs = [ColorProgram(), TextureProgram(), FallbackProgram()]

        for mesh in self.meshes:
            for mp in mesh_programs:
                instance = mp.apply(mesh)
                if instance is not None:
                    if isinstance(instance, MeshProgram):
                        mesh.mesh_program = mp
                        break
                    else:
                        raise ValueError("apply() must return a MeshProgram instance, not {}".format(type(instance)))

            if not mesh.mesh_program:
                print("WARING: No mesh program applied to '{}'".format(mesh.name))


def calc_scene_bbox(self):
        """Calculate scene bbox"""
        bbox_min, bbox_max = None, None
        for node in self.root_nodes:
            bbox_min, bbox_max = node.calc_global_bbox(
                matrix44.create_identity(),
                bbox_min,
                bbox_max
            )

        self.bbox_min = bbox_min
        self.bbox_max = bbox_max

        self.diagonal_size = vector3.length(self.bbox_max - self.bbox_min)

def calc_scene_bbox ( self ) : bbox_min , bbox_max = None , None for node in self . root_nodes : bbox_min , bbox_max = node . calc_global_bbox ( matrix44 . create_identity ( ) , bbox_min , bbox_max ) self . bbox_min = bbox_min self . bbox_max = bbox_max self . diagonal_size = vector3 . length ( self . bbox_max - self . bbox_min )


def points_random_3d(count, range_x=(-10.0, 10.0), range_y=(-10.0, 10.0), range_z=(-10.0, 10.0), seed=None) -> VAO:
    """
    Generates random positions inside a confied box.

    Args:
        count (int): Number of points to generate

    Keyword Args:
        range_x (tuple): min-max range for x axis: Example (-10.0. 10.0)
        range_y (tuple): min-max range for y axis: Example (-10.0. 10.0)
        range_z (tuple): min-max range for z axis: Example (-10.0. 10.0)
        seed (int): The random seed

    Returns:
        A :py:class:`demosys.opengl.vao.VAO` instance
    """
    random.seed(seed)

    def gen():
        for _ in range(count):
            yield random.uniform(*range_x)
            yield random.uniform(*range_y)
            yield random.uniform(*range_z)

    data = numpy.fromiter(gen(), count=count * 3, dtype=numpy.float32)

    vao = VAO("geometry:points_random_3d", mode=moderngl.POINTS)
    vao.buffer(data, '3f', ['in_position'])

    return vao

def points_random_3d ( count , range_x = ( - 10.0 , 10.0 ) , range_y = ( - 10.0 , 10.0 ) , range_z = ( - 10.0 , 10.0 ) , seed = None ) -> VAO : random . seed ( seed ) def gen ( ) : for _ in range ( count ) : yield random . uniform ( * range_x ) yield random . uniform ( * range_y ) yield random . uniform ( * range_z ) data = numpy . fromiter ( gen ( ) , count = count * 3 , dtype = numpy . float32 ) vao = VAO ( "geometry:points_random_3d" , mode = moderngl . POINTS ) vao . buffer ( data , '3f' , [ 'in_position' ] ) return vao

Generates random positions inside a confied box .


def start(self):
        """Play the music"""
        if self.initialized:
            mixer.music.unpause()
        else:
            mixer.music.play()
            # FIXME: Calling play twice to ensure the music is actually playing
            mixer.music.play()
            self.initialized = True
        self.paused = False


def get_time(self) -> float:
        """
        Get the current position in the music in seconds
        """
        if self.paused:
            return self.pause_time

        return mixer.music.get_pos() / 1000.0

def get_time ( self ) -> float : if self . paused : return self . pause_time return mixer . music . get_pos ( ) / 1000.0


def set_time(self, value: float):
        """
        Set the current time in the music in seconds causing the player
        to seek to this location in the file.
        """
        if value < 0:
            value = 0

        # mixer.music.play(start=value)
        mixer.music.set_pos(value)

def
set_time
(
self
,
value
:
float
)
:
if
value
<
0
:
value
=
0
# mixer.music.play(start=value)
mixer
.
music
.
set_pos
(
value
)

Set the current time in the music in seconds causing the player to seek to this location in the file .


def draw_buffers(self, near, far):
        """
        Draw framebuffers for debug purposes.
        We need to supply near and far plane so the depth buffer can be linearized when visualizing.

        :param near: Projection near value
        :param far: Projection far value
        """
        self.ctx.disable(moderngl.DEPTH_TEST)

        helper.draw(self.gbuffer.color_attachments[0], pos=(0.0, 0.0), scale=(0.25, 0.25))
        helper.draw(self.gbuffer.color_attachments[1], pos=(0.5, 0.0), scale=(0.25, 0.25))
        helper.draw_depth(self.gbuffer.depth_attachment, near, far, pos=(1.0, 0.0), scale=(0.25, 0.25))
        helper.draw(self.lightbuffer.color_attachments[0], pos=(1.5, 0.0), scale=(0.25, 0.25))

def draw_buffers ( self , near , far ) : self . ctx . disable ( moderngl . DEPTH_TEST ) helper . draw ( self . gbuffer . color_attachments [ 0 ] , pos = ( 0.0 , 0.0 ) , scale = ( 0.25 , 0.25 ) ) helper . draw ( self . gbuffer . color_attachments [ 1 ] , pos = ( 0.5 , 0.0 ) , scale = ( 0.25 , 0.25 ) ) helper . draw_depth ( self . gbuffer . depth_attachment , near , far , pos = ( 1.0 , 0.0 ) , scale = ( 0.25 , 0.25 ) ) helper . draw ( self . lightbuffer . color_attachments [ 0 ] , pos = ( 1.5 , 0.0 ) , scale = ( 0.25 , 0.25 ) )

Draw framebuffers for debug purposes . We need to supply near and far plane so the depth buffer can be linearized when visualizing .


def add_point_light(self, position, radius):
        """Add point light"""
        self.point_lights.append(PointLight(position, radius))

def add_point_light ( self , position , radius ) : self . point_lights . append ( PointLight ( position , radius ) )


def render_lights(self, camera_matrix, projection):
        """Render light volumes"""
        # Draw light volumes from the inside
        self.ctx.front_face = 'cw'
        self.ctx.blend_func = moderngl.ONE, moderngl.ONE

        helper._depth_sampler.use(location=1)
        with self.lightbuffer_scope:
            for light in self.point_lights:
                # Calc light properties
                light_size = light.radius
                m_light = matrix44.multiply(light.matrix, camera_matrix)
                # Draw the light volume
                self.point_light_shader["m_proj"].write(projection.tobytes())
                self.point_light_shader["m_light"].write(m_light.astype('f4').tobytes())
                self.gbuffer.color_attachments[1].use(location=0)
                self.point_light_shader["g_normal"].value = 0
                self.gbuffer.depth_attachment.use(location=1)
                self.point_light_shader["g_depth"].value = 1
                self.point_light_shader["screensize"].value = (self.width, self.height)
                self.point_light_shader["proj_const"].value = projection.projection_constants
                self.point_light_shader["radius"].value = light_size
                self.unit_cube.render(self.point_light_shader)

        helper._depth_sampler.clear(location=1)

def
render_lights
(
self
,
camera_matrix
,
projection
)
:
# Draw light volumes from the inside
self
.
ctx
.
front_face
=
'cw'
self
.
ctx
.
blend_func
=
moderngl
.
ONE
,
moderngl
.
ONE
helper
.
_depth_sampler
.
use
(
location
=
1
)
with
self
.
lightbuffer_scope
:
for
light
in
self
.
point_lights
:
# Calc light properties
light_size
=
light
.
radius
m_light
=
matrix44
.
multiply
(
light
.
matrix
,
camera_matrix
)
# Draw the light volume
self
.
point_light_shader
[
"m_proj"
]
.
write
(
projection
.
tobytes
(
)
)
self
.
point_light_shader
[
"m_light"
]
.
write
(
m_light
.
astype
(
'f4'
)
.
tobytes
(
)
)
self
.
gbuffer
.
color_attachments
[
1
]
.
use
(
location
=
0
)
self
.
point_light_shader
[
"g_normal"
]
.
value
=
0
self
.
gbuffer
.
depth_attachment
.
use
(
location
=
1
)
self
.
point_light_shader
[
"g_depth"
]
.
value
=
1
self
.
point_light_shader
[
"screensize"
]
.
value
=
(
self
.
width
,
self
.
height
)
self
.
point_light_shader
[
"proj_const"
]
.
value
=
projection
.
projection_constants
self
.
point_light_shader
[
"radius"
]
.
value
=
light_size
self
.
unit_cube
.
render
(
self
.
point_light_shader
)
helper
.
_depth_sampler
.
clear
(
location
=
1
)


def render_lights_debug(self, camera_matrix, projection):
        """Render outlines of light volumes"""
        self.ctx.enable(moderngl.BLEND)
        self.ctx.blend_func = moderngl.SRC_ALPHA, moderngl.ONE_MINUS_SRC_ALPHA

        for light in self.point_lights:
            m_mv = matrix44.multiply(light.matrix, camera_matrix)
            light_size = light.radius
            self.debug_shader["m_proj"].write(projection.tobytes())
            self.debug_shader["m_mv"].write(m_mv.astype('f4').tobytes())
            self.debug_shader["size"].value = light_size
            self.unit_cube.render(self.debug_shader, mode=moderngl.LINE_STRIP)

        self.ctx.disable(moderngl.BLEND)

def render_lights_debug ( self , camera_matrix , projection ) : self . ctx . enable ( moderngl . BLEND ) self . ctx . blend_func = moderngl . SRC_ALPHA , moderngl . ONE_MINUS_SRC_ALPHA for light in self . point_lights : m_mv = matrix44 . multiply ( light . matrix , camera_matrix ) light_size = light . radius self . debug_shader [ "m_proj" ] . write ( projection . tobytes ( ) ) self . debug_shader [ "m_mv" ] . write ( m_mv . astype ( 'f4' ) . tobytes ( ) ) self . debug_shader [ "size" ] . value = light_size self . unit_cube . render ( self . debug_shader , mode = moderngl . LINE_STRIP ) self . ctx . disable ( moderngl . BLEND )


def combine(self):
        """Combine diffuse and light buffer"""
        self.gbuffer.color_attachments[0].use(location=0)
        self.combine_shader["diffuse_buffer"].value = 0
        self.lightbuffer.color_attachments[0].use(location=1)
        self.combine_shader["light_buffer"].value = 1
        self.quad.render(self.combine_shader)

def combine ( self ) : self . gbuffer . color_attachments [ 0 ] . use ( location = 0 ) self . combine_shader [ "diffuse_buffer" ] . value = 0 self . lightbuffer . color_attachments [ 0 ] . use ( location = 1 ) self . combine_shader [ "light_buffer" ] . value = 1 self . quad . render ( self . combine_shader )


def load_shader(self, shader_type: str, path: str):
        """Load a single shader"""
        if path:
            resolved_path = self.find_program(path)
            if not resolved_path:
                raise ValueError("Cannot find {} shader '{}'".format(shader_type, path))

            print("Loading:", path)

            with open(resolved_path, 'r') as fd:
                return fd.read()

def
load_shader
(
self
,
shader_type
:
str
,
path
:
str
)
:
if
path
:
resolved_path
=
self
.
find_program
(
path
)
if
not
resolved_path
:
raise
ValueError
(
"Cannot find {} shader '{}'"
.
format
(
shader_type
,
path
)
)
print
(
"Loading:"
,
path
)
with
open
(
resolved_path
,
'r'
)
as
fd
:
return
fd
.
read
(
)


def load(self):
        """Load a texture array"""
        self._open_image()

        width, height, depth = self.image.size[0], self.image.size[1] // self.layers, self.layers
        components, data = image_data(self.image)

        texture = self.ctx.texture_array(
            (width, height, depth),
            components,
            data,
        )
        texture.extra = {'meta': self.meta}

        if self.meta.mipmap:
            texture.build_mipmaps()

        self._close_image()

        return texture

def load ( self ) : self . _open_image ( ) width , height , depth = self . image . size [ 0 ] , self . image . size [ 1 ] // self . layers , self . layers components , data = image_data ( self . image ) texture = self . ctx . texture_array ( ( width , height , depth ) , components , data , ) texture . extra = { 'meta' : self . meta } if self . meta . mipmap : texture . build_mipmaps ( ) self . _close_image ( ) return texture


def draw(self, projection_matrix=None, view_matrix=None, camera_matrix=None, time=0):
        """
        Draw the mesh using the assigned mesh program

        :param projection_matrix: projection_matrix (bytes)
        :param view_matrix: view_matrix (bytes)
        :param camera_matrix: camera_matrix (bytes)
        """
        if self.mesh_program:
            self.mesh_program.draw(
                self,
                projection_matrix=projection_matrix,
                view_matrix=view_matrix,
                camera_matrix=camera_matrix,
                time=time
            )

def draw ( self , projection_matrix = None , view_matrix = None , camera_matrix = None , time = 0 ) : if self . mesh_program : self . mesh_program . draw ( self , projection_matrix = projection_matrix , view_matrix = view_matrix , camera_matrix = camera_matrix , time = time )


def set_time(self, value: float):
        """
        Set the current time jumping in the timeline.

        Args:
            value (float): The new time
        """
        if value < 0:
            value = 0

        self.controller.row = self.rps * value

def set_time ( self , value : float ) : if value < 0 : value = 0 self . controller . row = self . rps * value

Set the current time jumping in the timeline .


def draw(self, time: float, frametime: float, target: moderngl.Framebuffer):
        """
        Draw function called by the system every frame when the effect is active.
        This method raises ``NotImplementedError`` unless implemented.

        Args:
            time (float): The current time in seconds.
            frametime (float): The time the previous frame used to render in seconds.
            target (``moderngl.Framebuffer``): The target FBO for the effect.
        """
        raise NotImplementedError("draw() is not implemented")

def
draw
(
self
,
time
:
float
,
frametime
:
float
,
target
:
moderngl
.
Framebuffer
)
:
raise
NotImplementedError
(
"draw() is not implemented"
)

Draw function called by the system every frame when the effect is active . This method raises NotImplementedError unless implemented .


def get_program(self, label: str) -> moderngl.Program:
        """
        Get a program by its label

        Args:
            label (str): The label for the program

        Returns: py:class:`moderngl.Program` instance
        """
        return self._project.get_program(label)

def get_program ( self , label : str ) -> moderngl . Program : return self . _project . get_program ( label )


def get_texture(self, label: str) -> Union[moderngl.Texture, moderngl.TextureArray,
                                               moderngl.Texture3D, moderngl.TextureCube]:
        """
        Get a texture by its label

        Args:
            label (str): The Label for the texture

        Returns:
            The py:class:`moderngl.Texture` instance
        """
        return self._project.get_texture(label)

def get_texture ( self , label : str ) -> Union [ moderngl . Texture , moderngl . TextureArray , moderngl . Texture3D , moderngl . TextureCube ] : return self . _project . get_texture ( label )


def get_effect_class(self, effect_name: str, package_name: str = None) -> Type['Effect']:
        """
        Get an effect class by the class name

        Args:
            effect_name (str): Name of the effect class

        Keyword Args:
            package_name (str): The package the effect belongs to. This is optional and only
                                needed when effect class names are not unique.

        Returns:
            :py:class:`Effect` class
        """
        return self._project.get_effect_class(effect_name, package_name=package_name)

def get_effect_class ( self , effect_name : str , package_name : str = None ) -> Type [ 'Effect' ] : return self . _project . get_effect_class ( effect_name , package_name = package_name )


def create_projection(self, fov: float = 75.0, near: float = 1.0, far: float = 100.0, aspect_ratio: float = None):
        """
        Create a projection matrix with the following parameters.
        When ``aspect_ratio`` is not provided the configured aspect
        ratio for the window will be used.

        Args:
            fov (float): Field of view (float)
            near (float): Camera near value
            far (float): Camrea far value

        Keyword Args:
            aspect_ratio (float): Aspect ratio of the viewport

        Returns:
            The projection matrix as a float32 :py:class:`numpy.array`
        """
        return matrix44.create_perspective_projection_matrix(
            fov,
            aspect_ratio or self.window.aspect_ratio,
            near,
            far,
            dtype='f4',
        )

def create_projection ( self , fov : float = 75.0 , near : float = 1.0 , far : float = 100.0 , aspect_ratio : float = None ) : return matrix44 . create_perspective_projection_matrix ( fov , aspect_ratio or self . window . aspect_ratio , near , far , dtype = 'f4' , )

Create a projection matrix with the following parameters . When aspect_ratio is not provided the configured aspect ratio for the window will be used .