Class: Motion¶
- class pykitPIV.motion.MotionSpecs(n_images=1, size=(512, 512), size_buffer=10, n_steps=10, particle_loss=(0, 0), particle_gain=(0, 0), dtype=<class 'numpy.float64'>, random_seed=None)¶
Configuration object for the
Motionclass.Example:
from pykitPIV import MotionSpecs # Instantiate an object of MotionSpecs class: motion_spec = MotionSpecs() # Change one field of motion_spec: motion_spec.n_steps = 20 # You can print the current values of all attributes: print(motion_spec)
- class pykitPIV.motion.Motion(particles, flowfield, particle_loss=(0, 0), particle_gain=(0, 0), verbose=False, dtype=<class 'numpy.float64'>, random_seed=None)¶
Applies velocity field defined by the
FlowFieldclass instance to particles defined by theParticleclass instance. TheMotionclass provides the position of particles at the next time instance, \(t_2 = t_1 + \Delta t\), where \(\Delta t\) is the time separation for the PIV image pair \(\mathbf{I} = (I_1, I_2)^{\top}\).Note
Particles that exit the image area as a result of their motion are removed from image \(I_2\).
To ensure that motion of particles does not cause unphysical disappearance of particles near image boundaries, set an appropriately large image buffer, \(b\), when instantiating objects of
ParticleandFlowFieldclass (see parametersize_buffer). This allows new particles to enter the image area.Example:
from pykitPIV import Particle, FlowField, Motion import numpy as np # We are going to generate 10 PIV image pairs: n_images = 10 # Specify size in pixels for each image: image_size = (128, 512) # Initialize a particle object: particles = Particle(n_images=n_images, size=image_size, size_buffer=10, diameters=(2, 4), distances=(1, 2), densities=(0.01, 0.05), diameter_std=(0.1,1), seeding_mode='random', dtype=np.float32, random_seed=100) # Initialize a flow field object: flowfield = FlowField(n_images=n_images, size=image_size, size_buffer=10, dtype=np.float32, random_seed=100) # Generate random velocity field: flowfield.generate_random_velocity_field(gaussian_filters=(2, 10), n_gaussian_filter_iter=10, displacement=(2, 5)) # Initialize a motion object: motion = Motion(particles, flowfield, particle_loss=(0, 2), particle_gain='matching', verbose=False, dtype=np.float32, random_seed=None)
- Parameters:
particles –
Particleclass instance specifying the properties and positions of particles.flowfield –
FlowFieldclass instance specifying the flow field.particle_loss – (optional)
tupleof two numerical elements specifying the minimum (first element) and maximum (second element) percentage of lost particles between two consecutive PIV images. This percentage of particles from image \(I_1\) will be randomly removed in image \(I_2\). This parameter mimics the complete loss of luminosity of particles that move perpendicular to the laser plane. It can also be set tointorfloatto generate a fixed particle loss value across all \(N\) image pairs.particle_gain – (optional)
tupleof two numerical elements specifying the minimum (first element) and maximum (second element) percentage of lost particles between two consecutive PIV images. This percentage of particles from image \(I_1\) will be randomly added in image \(I_2\). This parameter mimics the gain of luminosity for new particles that arrive into the laser plane. It can also be set to'matching', in which case the gain of particles will exactly match the number of particles lost per each PIV image pair. It can also be set tointorfloatto generate a fixed particle gain value across all \(N\) image pairs.verbose – (optional)
boolspecifying if the verbose print statements should be displayed.dtype – (optional)
numpy.dtypespecifying the data type for particle coordinates. To reduce memory, you can switch from the defaultnumpy.float64tonumpy.float32.random_seed – (optional)
intspecifying the random seed for random number generation innumpy. If specified, all operations are reproducible.
Attributes:
particle_loss - (can be re-set) as per user input.
particle_gain - (can be re-set) as per user input.
verbose - (read-only) as per user input.
dtype - (read-only) as per user input.
random_seed - (read-only) as per user input.
loss_percentage_per_image (read-only)
numpy.ndarrayspecifying the particle loss percentage for each PIV image pair.gain_percentage_per_image (read-only)
numpy.ndarrayspecifying the particle gain percentage for each PIV image pair.particle_coordinates_I1 - (read-only)
listoftuplespecifying the coordinates of particles in image \(I_1\). The first element in each tuple are the coordinates along the image height, and the second element are the coordinates along the image width.particle_coordinates_I2 - (read-only)
listoftuplespecifying the coordinates of particles in image \(I_2\). The first element in each tuple are the coordinates along the image height, and the second element are the coordinates along the image width.updated_particle_diameters - (read-only)
listofnumpy.ndarrayspecifying the updated particle diameters for each PIV image pair.
- pykitPIV.motion.Motion.forward_euler(self, n_steps)¶
Advects particles with the forward Euler numerical scheme according to the formula:
\[ \begin{align}\begin{aligned}x_{t + \Delta t} = x_{t} + u(x_t, y_t) \cdot \Delta t\\y_{t + \Delta t} = y_{t} + v(x_t, y_t) \cdot \Delta t\end{aligned}\end{align} \]where \(u\) and \(v\) are velocity components in the \(x\) and \(y\) direction respectively. Velocity components in-between the grid points are interpolated using scipy.interpolate.RegularGridInterpolator.
\(\Delta t\) is computed as:
\[\Delta t = T / n\]where \(T\) is the time separation between two images specified as
time_separationin theFlowFieldclass object and \(n\) is the number of steps for the solver to take specified by then_stepsinput parameter. The Euler scheme is applied \(n\) times from \(t=0\) to \(t=T\).Note
Note, that the central assumption for generating the kinematic relationship between two consecutive PIV images is that the velocity field defined by \((u, v)\) remains constant for the duration of time \(T\).
Example:
# Advect particles with the forward Euler scheme: motion.forward_euler(n_steps=10)
- Parameters:
n_steps –
intspecifying the number of time steps, \(n\), that the numerical solver should take.
- pykitPIV.motion.Motion.runge_kutta_4th(self, n_steps)¶
Advects particles with the 4th order Runge-Kutta (RK4) numerical scheme according to the formula:
\[ \begin{align}\begin{aligned}x_{t + \Delta t} = x_{t} + R_x\\y_{t + \Delta t} = y_{t} + R_y\end{aligned}\end{align} \]where the residuals are computed as:
\[ \begin{align}\begin{aligned}R_x = \frac{1}{6} \cdot \Big( R1_x + 2 \cdot R2_x + 2 \cdot R3_x + R4_x \Big)\\R_y = \frac{1}{6} \cdot \Big( R1_y + 2 \cdot R2_y + 2 \cdot R3_y + R4_y \Big)\end{aligned}\end{align} \]with the coefficients:
\[ \begin{align}\begin{aligned}R1_x = \Delta t \cdot u(x_{t}, y_{t})\\R2_x = \Delta t \cdot u\Big( x_{t} + \frac{1}{2} \cdot R1_x(t), y_{t} + \frac{1}{2} \cdot R1_y(t)\Big)\\R3_x = \Delta t \cdot u\Big( x_{t} + \frac{1}{2} \cdot R2_x(t), y_{t} + \frac{1}{2} \cdot R2_y(t)\Big)\\R4_x = \Delta t \cdot u\Big( x_{t} + R3_x(t), y_{t} + R3_y(t)\Big)\end{aligned}\end{align} \]and:
\[ \begin{align}\begin{aligned}R1_y = \Delta t \cdot v(x_{t}, y_{t})\\R2_y = \Delta t \cdot v\Big( x_{t} + \frac{1}{2} \cdot R1_x(t), y_{t} + \frac{1}{2} \cdot R1_y(t)\Big)\\R3_y = \Delta t \cdot v\Big( x_{t} + \frac{1}{2} \cdot R2_x(t), y_{t} + \frac{1}{2} \cdot R2_y(t)\Big)\\R4_y = \Delta t \cdot v\Big( x_{t} + R3_x(t), y_{t} + R3_y(t)\Big)\end{aligned}\end{align} \]where \(u\) and \(v\) are velocity components in the \(x\) and \(y\) direction respectively. Velocity components in-between the grid points are interpolated using scipy.interpolate.RegularGridInterpolator.
\(\Delta t\) is computed as:
\[\Delta t = T / n\]where \(T\) is the time separation between two images specified as
time_separationin theFlowFieldclass object and \(n\) is the number of steps for the solver to take specified by then_stepsinput parameter. The 4th order Runge-Kutta scheme is applied \(n\) times from \(t=0\) to \(t=T\).Note
Note, that the central assumption for generating the kinematic relationship between two consecutive PIV images is that the velocity field defined by \((u, v)\) remains constant for the duration of time \(T\).
Example:
# Advect particles with the RK4 scheme: motion.runge_kutta_4th(n_steps=10)
- Parameters:
n_steps –
intspecifying the number of time steps, \(n\), that the numerical solver should take.
- pykitPIV.motion.Motion.compute_order_parameter(self)¶
Computes an order parameter as per Vicsek et al. (1995):
\[v_a = \frac{1}{N} \left| \sum_{i=1}^N \frac{\vec{v}_i}{|\vec{v}_i|} \right|\]This is a number between 0 and 1, where a value approaching 0 indicates fully random motion of particles and a value approaching 1 indicates coherently moving particles (with ordered direction of velocities).
- pykitPIV.motion.Motion.plot_particle_motion(self, idx, s=10, xlabel=None, ylabel=None, xticks=True, yticks=True, title=None, color_I1='k', color_I2='#ee6c4d', figsize=(5, 5), dpi=300, filename=None)¶
Plots the positions of particles on images \(I_1\) and \(I_2\).
Example:
# Visualize the movement of particles: motion.plot_particle_motion(idx=0)
- Parameters:
idx –
intspecifying the index of the image to plot out ofn_imagesnumber of images.s – (optional)
intorfloatspecifying the scatter point size.xlabel – (optional)
strspecifying \(x\)-label.ylabel – (optional)
strspecifying \(y\)-label.xticks – (optional)
boolspecifying if ticks along the \(x\)-axis should be plotted.yticks – (optional)
boolspecifying if ticks along the \(y\)-axis should be plotted.title – (optional)
strspecifying figure title.color_I1 – (optional)
strspecifying the color for particles in image \(I_1\).color_I2 – (optional)
strspecifying the color for particles in image \(I_2\).figsize – (optional)
tupleof two numerical elements specifying the figure size as permatplotlib.pyplot.dpi – (optional)
intspecifying the dpi for the image.filename – (optional)
strspecifying the path and filename to save an image. If set toNone, the image will not be saved.
- Returns:
plt -
matplotlib.pyplotimage handle.