Source code for fluidimage.works.piv.multipass
"""Multipass PIV (:mod:`fluidimage.works.piv.multipass`)
========================================================
.. autoclass:: WorkPIV
:members:
:private-members:
"""
from copy import copy
from ...data_objects.piv import MultipassPIVResults
from .. import BaseWorkFromSerie
from .fix import WorkFIX
from .singlepass import FirstWorkPIV, InterpError, WorkPIVFromDisplacement
[docs]class WorkPIV(BaseWorkFromSerie):
"""Main work for PIV with multipass.
Parameters
----------
params : :class:`fluiddyn.util.paramcontainer.ParamContainer`
The default parameters are obtained from the class method
:func:`WorkPIV.create_default_params`.
Notes
-----
Steps for a computation:
- first estimation of the PIV field with a work
:class:`fluidimage.works.piv.singlepass.FirstWorkPIV`
- fix (remove "false vectors") with a work
:class:`fluidimage.works.piv.fix.WorkFIX`
- depending of the value of `params.multipass.number`, iteration to compute
PIV displacements with the works
:class:`fluidimage.works.piv.singlepass.WorkPIVFromDisplacement` and
:class:`fluidimage.works.piv.fix.WorkFIX`.
"""
[docs] @classmethod
def _complete_params_with_default(cls, params):
"""Complete the default parameters (class method)."""
super()._complete_params_with_default(params)
cls._complete_params_with_default_piv(params)
@classmethod
def _complete_params_with_default_piv(cls, params):
FirstWorkPIV._complete_params_with_default(params)
WorkFIX._complete_params_with_default(params)
params._set_child(
"multipass",
attribs={
"number": 1,
"coeff_zoom": 2,
"use_tps": "last",
"subdom_size": 200,
"smoothing_coef": 2.0,
"threshold_tps": 1.5,
},
)
params.multipass._set_doc(
"""Multipass PIV parameters:
- number : int (default 1)
Number of PIV passes.
- coeff_zoom : integer or iterable of size `number - 1`.
Reduction coefficient defining the size of the interrogation windows for
the passes 1 (second pass) to `number - 1` (last pass) (always
defined comparing the passes `i-1`).
- use_tps : bool or 'last'
If it is True, the interpolation is done using the Thin Plate Spline method
(computationally heavy but sometimes interesting). If it is 'last', the
TPS method is used only for the last pass.
- subdom_size : int
Number of vectors in the subdomains used for the TPS method.
- smoothing_coef : float
Coefficient used for the TPS method. The result is smoother for larger
smoothing_coef. 2 is often reasonable. Can typically be between 0 to 40.
- threshold_tps : float
Allowed difference of displacement (in pixels) between smoothed and input
field for TPS filter used in an iterative filtering method. Vectors too far
from the corresponding interpolated vector are removed.
"""
)
def __init__(self, params=None):
super().__init__(params)
work_piv = FirstWorkPIV(params)
self.works_piv = [work_piv]
self.works_fix = [WorkFIX(params.fix, work_piv)]
coeff_zoom = params.multipass.coeff_zoom
if isinstance(coeff_zoom, int):
coeffs_zoom = [coeff_zoom] * (params.multipass.number - 1)
elif len(coeff_zoom) == params.multipass.number - 1:
coeffs_zoom = coeff_zoom
else:
raise ValueError(
"params.multipass.coeff_zoom has to be an integer or "
"an iterable of length params.multipass.number - 1"
)
shape_crop_im0 = copy(params.piv0.shape_crop_im0)
shape_crop_im1 = copy(params.piv0.shape_crop_im1)
if shape_crop_im1 is None:
shape_crop_im1 = shape_crop_im0
if isinstance(shape_crop_im0, int):
shape_crop_im0 = (shape_crop_im0, shape_crop_im0)
elif (
isinstance(shape_crop_im0, (list, tuple)) and len(shape_crop_im0) == 2
):
shape_crop_im0 = tuple(shape_crop_im0)
else:
raise NotImplementedError(
"For now, shape_crop_im0 has to be one or two integer!"
)
if isinstance(shape_crop_im1, int):
shape_crop_im1 = (shape_crop_im1, shape_crop_im1)
elif (
isinstance(shape_crop_im1, (list, tuple)) and len(shape_crop_im1) == 2
):
shape_crop_im1 = tuple(shape_crop_im1)
else:
raise NotImplementedError(
"For now, shape_crop_im1 has to be one or two integer!"
)
for i in range(1, params.multipass.number):
shape_crop_im0 = (
copy(shape_crop_im0[0] / coeffs_zoom[i - 1]),
copy(shape_crop_im0[1] / coeffs_zoom[i - 1]),
)
shape_crop_im1 = (
copy(shape_crop_im1[0] / coeffs_zoom[i - 1]),
copy(shape_crop_im1[1] / coeffs_zoom[i - 1]),
)
work_piv = WorkPIVFromDisplacement(
params,
index_pass=i,
shape_crop_im0=shape_crop_im0,
shape_crop_im1=shape_crop_im1,
)
self.works_piv.append(work_piv)
self.works_fix.append(WorkFIX(params.fix, work_piv))
[docs] def calcul(self, couple):
"""Compute a PIV field (multipass) from a couple of image."""
results = MultipassPIVResults()
# the first work is a FirstWorkPIV and uses a couple
piv_result = couple
for work_piv, work_fix in zip(self.works_piv, self.works_fix):
piv_result = work_piv.calcul(piv_result)
piv_result = work_fix.calcul(piv_result)
results.append(piv_result)
try:
work_piv.apply_interp(piv_result, last=True)
except InterpError as e:
print("Warning: InterpError at the end of the last piv pass:", e)
return results
[docs] def _prepare_with_image(self, im=None, imshape=None):
"""Prepare the works PIV with an image."""
if imshape is None:
imshape = im.shape
for work_piv in self.works_piv:
work_piv._prepare_with_image(imshape=imshape)
_params = WorkPIV.create_default_params()
__doc__ += _params._get_formatted_docs()