# coding: utf-8
from typing import Optional
import numpy as np
import logging
import logging.handlers
from time import sleep
from .camera_process import CameraProcess
from ...tool.image_processing import DICVETool
from ...tool.camera_config import SpotsBoxes
[docs]
class DICVEProcess(CameraProcess):
"""This :class:`~crappy.blocks.camera_processes.CameraProcess` can perform
video-extensometry by tracking patches on images using various Digital Image
Correlation techniques.
It is used by the :class:`~crappy.blocks.DICVE` Block to parallelize the
image processing and the image acquisition. It delegates most of the
computation to the :class:`~crappy.tool.image_processing.DICVETool`. It is
from this class that the output values are sent to the downstream Blocks, and
that the :class:`~crappy.tool.camera_config.config_tools.SpotsBoxes` are sent
to the :class:`~crappy.blocks.camera_processes.Displayer` CameraProcess for
display.
.. versionadded:: 2.0.0
"""
[docs]
def __init__(self,
patches: SpotsBoxes,
method: str = 'Disflow',
alpha: float = 3,
delta: float = 1,
gamma: float = 0,
finest_scale: int = 1,
iterations: int = 1,
gradient_iterations: int = 10,
patch_size: int = 8,
patch_stride: int = 3,
border: float = 0.2,
safe: bool = True,
follow: bool = True,
raise_on_exit: bool = True) -> None:
"""Sets the arguments and initializes the parent class.
Args:
patches: An instance of the
:class:`~crappy.tool.camera_config.config_tools.SpotsBoxes` class,
containing the coordinates of the patches to track. This argument is
passed to the :obj:`~crappy.tool.image_processing.DICVETool` and not
used in this class.
method: The method to use to calculate the displacement. `Disflow` uses
opencv's DISOpticalFlow and `Lucas Kanade` uses opencv's
calcOpticalFlowPyrLK, while all other methods are based on a basic
cross-correlation in the Fourier domain. `Pixel precision` calculates
the displacement by getting the position of the maximum of the
cross-correlation, and has thus a 1-pixel resolution. It is mainly
meant for debugging. `Parabola` refines the result of
`Pixel precision` by interpolating the neighborhood of the maximum, and
has thus a sub-pixel resolution. This argument is passed to the
:obj:`~crappy.tool.image_processing.DICVETool` and not used in this
class.
alpha: Weight of the smoothness term in DISFlow, as a :obj:`float`. This
argument is passed to the
:obj:`~crappy.tool.image_processing.DICVETool` and not used in this
class.
delta: Weight of the color constancy term in DISFlow, as a :obj:`float`.
This argument is passed to the
:obj:`~crappy.tool.image_processing.DICVETool` and not used in this
class.
gamma: Weight of the gradient constancy term in DISFlow , as a
:obj:`float`. This argument is passed to the
:obj:`~crappy.tool.image_processing.DICVETool` and not used in this
class.
finest_scale: Finest level of the Gaussian pyramid on which the flow
is computed in DISFlow (`0` means full scale), as an :obj:`int`. This
argument is passed to the
:obj:`~crappy.tool.image_processing.DICVETool` and not used in this
class.
iterations: Maximum number of gradient descent iterations in the
patch inverse search stage in DISFlow, as an :obj:`int`. This argument
is passed to the :obj:`~crappy.tool.image_processing.DICVETool` and not
used in this class.
gradient_iterations: Maximum number of gradient descent iterations
in the patch inverse search stage in DISFlow, as an :obj:`int`. This
argument is passed to the
:obj:`~crappy.tool.image_processing.DICVETool` and not used in this
class.
patch_size: Size of an image patch for matching in DISFlow
(in pixels). This argument is passed to the
:obj:`~crappy.tool.image_processing.DICVETool` and not used in this
class.
patch_stride: Stride between neighbor patches in DISFlow. Must be
less than patch size. This argument is passed to the
:obj:`~crappy.tool.image_processing.DICVETool` and not used in this
class.
border: Crop the patch on each side according to this value before
calculating the displacements. 0 means no cropping, 1 means the entire
patch is cropped. This argument is passed to the
:obj:`~crappy.tool.image_processing.DICVETool` and not used in this
class.
safe: If :obj:`True`, checks whether the patches aren't exiting the
image, and raises an error if that's the case. This argument is passed
to the :obj:`~crappy.tool.image_processing.DICVETool` and not used in
this class.
follow: It :obj:`True`, the patches will move to follow the displacement
of the image. This argument is passed to the
:obj:`~crappy.tool.image_processing.DICVETool` and not used in this
class.
raise_on_exit: If :obj:`True`, raises an exception and stops the test
when losing the patches. Otherwise, simply stops processing but lets
the test go on.
"""
super().__init__()
# Arguments to pass to the DICVETool
self._patches = patches
self._method = method
self._alpha = alpha
self._delta = delta
self._gamma = gamma
self._finest_scale = finest_scale
self._iterations = iterations
self._gradient_iterations = gradient_iterations
self._patch_size = patch_size
self._patch_stride = patch_stride
self._border = border
self._safe = safe
self._follow = follow
# Other attributes
self._raise_on_exit = raise_on_exit
self._disve: Optional[DICVETool] = None
self._img0_set = False
self._lost_patch = False
[docs]
def init(self) -> None:
"""Instantiates the :obj:`~crappy.tool.image_processing.DICVETool` that
will perform the image correlation."""
self.log(logging.INFO, "Instantiating the Disve tool")
self._disve = DICVETool(patches=self._patches,
method=self._method,
alpha=self._alpha,
delta=self._delta,
gamma=self._gamma,
finest_scale=self._finest_scale,
iterations=self._iterations,
gradient_iterations=self._gradient_iterations,
patch_size=self._patch_size,
patch_stride=self._patch_stride,
border=self._border,
safe=self._safe,
follow=self._follow)
[docs]
def loop(self) -> None:
"""This method grabs the latest frame and gives it for processing to the
:obj:`~crappy.tool.image_processing.DICVETool`. Then sends the result of
the correlation to the downstream Blocks.
If there's no new frame grabbed, or if the patches were already lost,
doesn't do anything. On the first acquired frame, does not process it but
initializes the DICVETool with it instead. Also sends the current patches
for display to the :class:`~crappy.blocks.camera_processes.Displayer`
CameraProcess.
"""
# Do nothing if the patches were already lost
if not self._lost_patch:
try:
# On the first frame, initialize the correlation
if not self._img0_set:
self.log(logging.INFO, "Setting the reference image")
self._disve.set_img0(np.copy(self.img))
self._img0_set = True
return
# Calculating the displacement and sending it to downstream Blocks
self.log(logging.DEBUG, "Processing the received image")
data = self._disve.calculate_displacement(self.img)
self.send([self.metadata['t(s)'], self.metadata, *data])
# Sending the patches to the Displayer for display
self.send_to_draw(self._disve.patches)
# If the patches are lost, deciding whether to raise exception or not
except RuntimeError as exc:
if self._raise_on_exit:
self._logger.exception("Patch exiting the ROI !", exc_info=exc)
raise
self._lost_patch = True
self.log(logging.WARNING, "Patch exiting the ROI, not processing "
"data anymore !")
# If the patches are lost, sleep to avoid spamming the CPU in vain
else:
self.fps_count -= 1
sleep(0.1)