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 Motion class.

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 FlowField class instance to particles defined by the Particle class instance. The Motion class 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 Particle and FlowField class (see parameter size_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:
  • particlesParticle class instance specifying the properties and positions of particles.

  • flowfieldFlowField class instance specifying the flow field.

  • particle_loss – (optional) tuple of 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 to int or float to generate a fixed particle loss value across all \(N\) image pairs.

  • particle_gain – (optional) tuple of 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 to int or float to generate a fixed particle gain value across all \(N\) image pairs.

  • verbose – (optional) bool specifying if the verbose print statements should be displayed.

  • dtype – (optional) numpy.dtype specifying the data type for particle coordinates. To reduce memory, you can switch from the default numpy.float64 to numpy.float32.

  • random_seed – (optional) int specifying the random seed for random number generation in numpy. 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.ndarray specifying the particle loss percentage for each PIV image pair.

  • gain_percentage_per_image (read-only) numpy.ndarray specifying the particle gain percentage for each PIV image pair.

  • particle_coordinates_I1 - (read-only) list of tuple specifying 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) list of tuple specifying 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) list of numpy.ndarray specifying 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_separation in the FlowField class object and \(n\) is the number of steps for the solver to take specified by the n_steps input 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_stepsint specifying 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_separation in the FlowField class object and \(n\) is the number of steps for the solver to take specified by the n_steps input 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_stepsint specifying 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:
  • idxint specifying the index of the image to plot out of n_images number of images.

  • s – (optional) int or float specifying the scatter point size.

  • xlabel – (optional) str specifying \(x\)-label.

  • ylabel – (optional) str specifying \(y\)-label.

  • xticks – (optional) bool specifying if ticks along the \(x\)-axis should be plotted.

  • yticks – (optional) bool specifying if ticks along the \(y\)-axis should be plotted.

  • title – (optional) str specifying figure title.

  • color_I1 – (optional) str specifying the color for particles in image \(I_1\).

  • color_I2 – (optional) str specifying the color for particles in image \(I_2\).

  • figsize – (optional) tuple of two numerical elements specifying the figure size as per matplotlib.pyplot.

  • dpi – (optional) int specifying the dpi for the image.

  • filename – (optional) str specifying the path and filename to save an image. If set to None, the image will not be saved.

Returns:

  • plt - matplotlib.pyplot image handle.